Repository navigation
fix(engine): turn-end delivery seal + deaf-sweep escalation - #1
Merged
Merged
Conversation
On Linux the window had no minimize/maximize/close at all wherever the compositor ends up in client-side-decoration mode (GNOME/Mutter has no SSD support; gpui's WindowControlArea hit-testing is Windows-only, so the Windows caption cluster never applied). Meanwhile leaving window_decorations unset requested SERVER decorations, which stacked a compositor titlebar on top of zeron's own chrome under sway/labwc. - Request client decorations on Linux (WindowOptions.window_decorations) to match the unified-titlebar design; the compositor can still override via xdg-decoration and the shell re-resolves every frame. - Draw zeron-styled caption buttons (24px cluster rhythm, hand-drawn linear glyphs) with explicit handlers - minimize_window / zoom_window / remove_window - since the native control-area hit-test is inert on Linux. Mounted at the root like the Windows cluster so they survive gates and the splash. - Follow the desktop's button layout (GNOME button-layout via cx.button_layout, observe_button_layout_changed): right side by default; "close:..." layouts put buttons on the left and the app cluster shifts past them at the shared 2px rhythm. Controls the compositor can't do (per wm_capabilities) drop out; close always stays. Server decorations draw nothing - no double controls. - Reserve titlebar right padding for the caption strip (tabs strip, changes-pane header, settings bar) via titlebar_right_pad. - Give gate/splash pages a real drag strip on Linux/Windows (titlebar_drag_region - the WindowControlArea::Drag surface alone is dead on Linux). Rig-verified under labwc (CSD: all three buttons work - minimize unmaps, zoom maximizes and flips to the restore glyph, close exits; close hover shows the red wash) and sway (SSD: no double controls); left-side layout verified end-to-end through xdg-desktop-portal-gtk with button-layout "close:". Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…s-platform-fix linux: window controls under client-side decorations
* feat: adopt external pi sessions into workspace (ID01-420) Add pi_adopt module that discovers pi harness sessions created outside the Zeron engine (CLI runs, pi-worker) and surfaces them as regular workspace chats so every client (iOS included) can list and open them. Discovery reads two sources: - pi-acp session map (~/.pi/pi-acp/session-map.json) - pi agent session store (~/.pi/agent/sessions/*/*.jsonl) as fallback Each session becomes a Chat row with deterministic UUIDv5 id, resolved space (created if needed), and idle Session row. Rows are idempotent — re-scans never duplicate or rewrite existing rows. Feature flag: ZERON_ADOPT_PI_SESSIONS=1 (off by default). With the flag absent, the binary behaves identically to upstream. Includes integration tests (2 passing) and proper CancellationToken-based shutdown. * docs: notas de implementación y verificación de pi_adopt * fix(pi-adopt): post-review fixes — skip adoption without space, downgrade map warn to debug - If resolve_space returns None (create_space transient error), skip adoption for this scan instead of writing an invisible chat row - Downgrade recurring session-map warning to debug (45s spam) - Update NOTAS with adversarial review findings * fix(pi-adopt): don't guess a dangling space_id after create_space Review (kimi-coding/k3): if create_space no-ops on an existing (device,path) and the re-read fails, the derived UUIDv5 would dangle and SpacesSync's orphan sweep would delete the chat. Also: run the scan in spawn_blocking, log skip reasons at debug, and document the spec-5 warning→debug deviation. --------- Co-authored-by: David <davidhsutton@gmail.com>
…only Los workflows heredados apuntan a la infra de zeron.sh (R2/releases) y no aplican al fork; quedan en workflow_dispatch. El gate del fork es ci.yml: cargo check --workspace + tests del engine. (ID01-424)
Five to the top row, four to the reverse row, avatars mirrored into assets/tweets. Marquee durations scaled (72s->117s, 80s->126s) so the longer tracks keep the same scroll speed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Resurrect the pre-ACP claude driver (ca05336^) and modernize it against CLI 2.1.228, replacing the claude-agent-acp adapter for HarnessId::ClaudeCode: - Permissions ride the stdio control channel (--permission-prompt-tool stdio, validated live: can_use_tool requests arrive and allow/deny responses are honored). Tools auto-allow (unattended parity); AskUserQuestion round-trips through the engine input bridge. - Done is the CLI's own result frame, eagerly — background subagents never hold the turn. The CLI's native wake turn (second init on the same session id + second result, live-verified) flows through the same stream; the engine's existing parked-session resume path turns it into the done→Working→done wake with no engine changes. - Subagent frames (top-level parent_tool_use_id) are wrapped in the new AgentEvent::Subagent variant instead of dropped — never folded into the parent feed (they'd split contiguous text around phantom tool calls, and their message boundaries must not rotate the parent's assistant message id). The parent doc fold ignores them; routing to per-subagent docs lands with the subagent-visualization commit. - --thinking-display summarized (newer models emit no readable thinking otherwise) alongside --verbose/--include-partial-messages. Tests: fake-CLI scenarios (init dedupe, tagging, eager-done+wake, ask- user round-trip, steering, interrupt escalation, error mapping), a verbatim live-captured 2.1.228 background-subagent frame replay, and an ignored live-CLI smoke. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ends at state Three cold-open wire cuts for thin links (450kbps target): - iOS caps background room dials at 8 (attention order). All 46 sessions still hydrate from disk instantly; sidebar status rides the registry room; an undialed chat connects the moment it's opened. Foreground and path-recovery kick sweeps skip dial-held stores — no more 46-socket stampede on every flap. - Both clients send the rows request BEFORE the checkpoint HTTP fetch and stream the backfill in parallel with the download, buffering rows until the import lands (order pinned by test). The download used to push the whole join back by its own duration. - Both clients flush pending pushes right after the state answer instead of after full convergence — a message typed offline now lands ~2 RTTs after the socket does, not after checkpoint + backfill (server batchId dedupe makes replays no-ops; deadlock-shaped regression test). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…stry-first dial gating On-device NLC (Edge profile) round 2 fixes: - ChatRoomClient: one checkpoint download per chat that OUTLIVES socket generations (a saturated link trips the silence lease mid-download — pongs queue behind the blob — and the redial used to discard the bytes and start over, looping a fresh-chat open forever). Partial downloads Range-resume across attempts; download progress defers the silence lease (120s backfill deadline stays as the backstop); a completed blob applies even if its socket died (CRDT import + max-cursor are socket- independent), so the next session joins RowsOnly. - RegistryClient.kick: don't redial while a handshake is in flight — foregrounded() fires on scene activation at launch and killed the dial mid-TLS every cold open, doubling the spinner (6.9s → measured single dial 4.5s on Edge). Mirrors ChatRoomClient's existing guard. - AppModel.preloadSessions: warm chat dials wait for the registry to connect (10s cap) — the sidebar's own handshake+state gets the thin pipe to itself; open views still dial instantly via releaseDial. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ancement
The airplane-wifi fix. The WS needs 4+ serial round trips (DNS, TCP, TLS,
upgrade, hello) before byte one, and hostile middleboxes strip the upgrade
entirely — the user-reported 'never connects on the plane'. A plain HTTPS
GET is 1 RTT on a warm connection and indistinguishable from a webpage to
any proxy. So: push/pull/poke.
Edge (worker + DOs):
- GET /registry/{org}/rows?since= — the WS hello's exact delta answer
(full/gcFloor rules shared with handleHello); ?beat=1 doubles as a
presence beat. POST /registry/{org}/push — the WS push twin via a shared
applyPushBatch core (validate, atomic apply, rows broadcast to live
sockets); LWW clocks make replays no-ops.
- GET /chat2/{id}/rows?after= — one request collapsing connect→hello→
state→rowsReq→backfill: u32-LE length-prefixed frames (state with
frontier payload, rows, rowsDone), byte-identical encoding to the WS.
POST /chat2/{id}/rows?batchId= — push twin, batchId-deduped.
Desktop (Rust): RegistryTransport/ChatTransport seams; with a transport,
construction resolves immediately (local-first), an HTTP pull bootstraps
in ~1 RTT while the socket dials, and every backoff cycle pushes pending +
pulls over HTTPS — a WS-hostile network degrades to ≤30s polling instead
of silence. Engine wires both via the existing bearer/reqwest plumbing
(registry: URL derived from the WS provider; chat: EdgeChatTransport).
iOS (Swift): WorkspaceStore pull-first bootstrap + 20s poll-while-down +
HTTP flush of pending writes; the header spinner now means 'server state
reached this session' (flips on the ~1-RTT pull, not the 4-RTT socket).
ChatRoomClient.pullSync: HTTP push of pending sends (permanent rejections
retire, mirroring the error-frame path), framed rows pull applied through
the exact socket path, checkpoint fetch shared via fetchInFlight.
Validation: new edge/scripts/pullpush-check.mjs green against wrangler dev
(push/ack/no-op replay, delta-at-cursor, presence beat, framed pull,
excludeOwn, batchId dedupe); existing smoke + chat2-check (65) + reset
suites green; zeron-sync 34, engine+rpc 180; iOS sim build clean.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…shes only on edge verdicts Pre-merge audit findings: - With immediate-ready construction, the host's bootstrap heal could read all-zero placeholder stats 3s after construction (server state not yet known on a slow link) and upload a spurious full-doc checkpoint. New ChatStatsSnapshot.server_known gates it; the heal waits (bounded 2min) for a real state answer. - iOS pullSync retired pending batches on ANY http 400/413 — a captive portal answering a POST with junk would silently drop a real message on exactly the networks this path serves. Retirement now requires a parsed edge error code (bad_push/too_large/empty), mirroring the WS error-frame discipline. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Resurrect the pre-ACP codex driver and modernize it against codex-cli 0.146.1 (the app-server API is experimental — the pin is noted in the module header), replacing the codex-acp adapter for HarnessId::Codex: - Done stays turn/completed (turn.status == "failed" now maps to an errored Done even without a turn.error; the error notification's nested params.error.message shape is accepted alongside the old flat one — both live-verified). - Approvals ride the app-server request RPCs as before (wire policy "never" for unattended parity, stray requests round-trip as yes/no questions); the experimental item/tool/requestUserInput content questions now reach the input bridge and answer keyed by wire id. - Subagents are full child threads. A registered child's notifications route through an EXPLICIT table (route_child_notification), pure and tested: item lifecycles AND message/reasoning deltas become tagged AgentEvent::Subagent events (live capture shows child threads DO stream deltas on this wire — token-level live transcripts without touching the rollout files), child turn/thread bookkeeping is consumed so it can never settle the parent turn, and unknown methods fall through to the parent path — fail open, never silent loss (the reference implementation shipped two bugs from a catch-all swallow: a child error left the agent running forever, and a swallowed serverRequest/resolved stuck approvals). Registration comes from subAgentActivity items on the parent thread; the root's own activity marker never registers (registering the root as its own child intercepted its turn/completed and hung the thread Working). Live-validated: basic turn end-to-end (marker file written, clean turn/completed) and a two-child fan-out capture archived under tests/fixtures/codex/. Ignored live smoke included. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…eronsh#30) * feat(engine): drain session-safe en shutdown (ID01-459) C1: flag `draining` (AtomicBool en SessionsEngine) seteado por EngineCore::drain_for_restart ANTES de cualquier interrupt y de server.abort(); rechazo de despacho de runs nuevos con el texto exacto 'engine is draining for a restart' en DOS costuras: RPC QueueCommand (frame de error estándar) y dispatch_inner (frames en vuelo). Steer a un run vivo steerable y RPCs de observación siguen permitidos. C2: wait AGGREGATE (poll del total de live runs, no por chat) con deadline 120s default, env ZERON_DRAIN_TIMEOUT_SECS (>=0; 0=interrupt inmediato). Vencido el deadline cae al teardown interrupt existente. Logs greppeables: waiting N live runs / all completed / timeout fallback. SPEC: ~/factory/SPEC-ID01-459-session-safe-deploys.md * test(engine): suite drain — natural completion, rechazo QueueCommand, fallback interrupt, StopEngine (ID01-459) Los 4 tests del criterio 1 del spec: - sigterm_waits_for_natural_completion: el turno completa natural durante el drain (journal Done{Completed}, sin stamp aborted); - queue_rejected_while_draining: error exacto por RPC y por dispatch seam + WatchSessions sigue streamando durante el drain; - drain_timeout_falls_back_to_interrupt: turno colgado + deadline corto via ZERON_DRAIN_TIMEOUT_SECS -> stamp aborted + Done{Interrupted}, teardown bounded; - stopengine_shares_drain_path: daemon headless real, StopEngine -> exit 0 <=5s (mismo camino post-select que SIGTERM). * fix(engine): drain gate rejects only commands that dispatch a new run (ID01-459 review) QUEUE_COMMAND during drain: Run always rejected; Steer only without a live steerable run (its fallback dispatches); Interrupt and RespondInput pass (accelerate/destraban the in-flight turn). The dispatch seam in sessions.rs still re-checks. Test: interrupt_and_respond_input_pass_while_draining. * test(engine): drain fallback uses explicit deadline, drops env mutation (ID01-459 review) The SAFETY comment on set_var was false — the StopEngine daemon test in the same binary also reads ZERON_DRAIN_TIMEOUT_SECS. drain_for_restart already takes the timeout explicitly, so the suite no longer touches the global env at all. * test(engine): rename drain natural-completion test, document SIGTERM gap (ID01-459 review) The old name (sigterm_waits_for_natural_completion) over-claimed: the test drives drain_for_restart directly, no real SIGTERM with a live run. The known gap is now stated in the doc comment. * fix(engine): sign-out skips the restart drain (ID01-459 review) The spec freezes the drain-wait for SIGTERM + StopEngine only. A signed- out runtime has nothing restarting: waiting up to the drain deadline (or answering commands with 'for a restart') buys nothing — go straight to the interrupting teardown. * fix(engine): prefer SIGTERM drain over same-tick sign-out (ID01-459 review) biased select so a deploy signal wins if sign-out is also ready. --------- Co-authored-by: David <davidhsutton@gmail.com>
…nsh#32) * feat(proto,engine): RunRequest.utility additive marker; title runs set it (ID01-491 C1+C2) - RunRequest.utility: bool con #[serde(default)] + skip-if-false (misma convención que attachments/worktree): wire viejo parsea a false, JSON de runs normales no cambia (campo omitido). Test de round-trip. - 35 literales RunRequest { del workspace rellenados con utility: false (cero cambio semántico; diff = inserciones únicamente). - titles: title_run_request() extraído y testeable — utility: true, sandbox ReadOnly, reasoning Minimal, resume None. El prompt/prefijo NO cambian (congelados: pi_adopt matchea TITLE_PROMPT_PREFIX). * feat(harness): ACP child de un run utility exporta ZERON_UTILITY=1 (ID01-491 C3) - compose_child_command() extraído y unit-testeado: program+args+PATH+cwd + marker env. utility:true ⇒ ZERON_UTILITY=1 en el child (herencia estándar harness→pi-acp→pi; cubre steer/followUp del run, child por-run). utility:false/ausente ⇒ la var NO se inserta ni se blanquea (herencia intacta). Sin RPC nuevo, sin _meta, sin patch de pi-acp. * feat(extensions): utilityRun = env ZERON_UTILITY=1 OR prefijo (ID01-491 C4+C5) - utility-guard + gate de subagents: el marker estructural se lee de process.env al instalar/session_start (guardo) y en el primer input (subagents); el sniff de prefijo QUEDA como fallback pre-swap. - Guard: session_start re-evalúa el env (un proceso de utilería lo es en toda sesión); tool_call pre-input también cubierto. - Tests: env-only sin prefijo bloquea/deniega spawn, '0' no activa, session_start respeta env; anti-drift cross-repo contra titles.rs (prefijo corto + frase fija del template). 12/12 + 85/85 verdes. * fix(harness): env_remove ZERON_UTILITY on non-utility ACP children (ID01-491 review) A leaked parent export would otherwise mark every session as utility and silently block all tools. --------- Co-authored-by: David <davidhsutton@gmail.com>
…nsh#33) * feat(tools): forward custom messages como agent_message_chunk (ID01-502) Patch pi-acp 0.0.33 por-parte (steering + ZERON-CUSTOM-FORWARD), idempotente, con gate+flatten en módulo puro testeado (18 tests node --test). Solo tools/. * fix(tools): --check sin extensión .js + test array sucio (ID01-502 review) --------- Co-authored-by: David <davidhsutton@gmail.com>
fix(acp): support Grok Build model switching
Fix terminal rendering after sidebar reopen
…ap-rendering Fix decorated ranges at soft-wrap boundaries
…erminal-scrollbar Fix selection edge scrolling and terminal scrollbar
fix: harden OpenCode model discovery
v0.2.16 report: the percent loader showed for ~50-100ms then fell to the
dot loader. The percent was real but measured the wrong leg — it covered
only the UI staging bytes to the LOCAL engine (disk-speed), and the leg
that actually takes time, the engine pushing chunks over the relay to
the host, reported nothing.
Engine: push_attachments publishes per-upload raw-byte progress to a
watch channel — an entry appears when a file's chunks start moving,
updates per landed 45KB chunk, and a Drop guard retires it on every
exit (commit, timeout, refusal), so a dead attempt falls back to the
spinner and the retry re-publishes from zero.
RPC: new WatchTransfers subscription (mirrors WatchConnectivity —
current set first, then a snapshot per landed chunk).
UI: the sending thumbnail resolves its pending://{uploadId}/… ref
against the stream — percent sources in order: the attachment's own
relay transfer, else the send-wide staging percent, else the
indeterminate spinner (staged-but-waiting, retry backoff, or
committed-awaiting-rewrite). The ring never shows a number that isn't
a real transfer position.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Attachment thumbnail percent ring tracks the real relay transfer
…p (ID01-435) (zeronsh#34) * feat(tools): check proactivo de capability steering del adapter pi-acp (ID01-435) node tools/check-pi-acp-steering.mjs → exit 0 si el dist tiene _session/steering + _meta.steering.supported; exit 1 con remedio (patch_piacp.mjs) si un update del adapter borró la capability. Acepta path como argv[2] para testear otros dists. * fix(tools): pin check-pi-acp-steering to engine 0.0.33 (ID01-435 review) --------- Co-authored-by: David <davidhsutton@gmail.com>
…h#172 port) The Swift ChatRoomClient carried the identical root cause of the random new-session forever-hangs: cursor = max(cursor, seq) let a live broadcast row (mid-join race) jump the cursor over a hole; the skipped rows' dependents park invisibly in loro's pending buffer, so the doc reads empty under a cursor that claims convergence — and every later pull asks after=cursor and gets nothing, surviving restarts. - Contiguity rule at every advance (live row, socket ack, HTTP pull row, HTTP push ack): the cursor may walk, never jump. A gap applies the bytes, holds the honest cursor, and flags a repair. - Gap repair: rowsReq from the held cursor, 3/session, then redial for a full catch-up. The HTTP pull path needs no send — its next cycle re-requests from the held cursor. - Cursor amnesty extended to checkpoint-less rooms (first join clamps a suspicious cursor to 0, bounded by the checkpoint threshold policy) — heals every already-wedged chat on disk, no migration. - The catch-up plan's `after` now assigns the cursor in both directions (new Delegate.setCursor) — without the raise, a contained checkpoint's backfill reads as a false gap. - Retry re-issues dead attempts (phone half of zeronsh#172's recovery): a Run/Steer whose message never landed and whose command is rejected / expired / pending-past-TTL gets a fresh command id with the same payload and messageId (the host's user-entry pre-write dedupes); one re-issue per message, skipped while a live attempt is queued. - Status strip narrates the escort's REAL relay transfer as "Uploading… N%" (the zeronsh#185 progress-truth analog). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Agent calls were folded into the same "Called N tools" group as ordinary Reads and Runs, so a running subagent disappeared behind the chevron. Split them into their own always-open row. Co-authored-by: Cursor <cursoragent@cursor.com>
…lity ios: port the network-reliability train (PRs zeronsh#159/zeronsh#164/zeronsh#165/zeronsh#168/zeronsh#170) to mobile
The 30px header sat in a 30px border-box, so gpui clipped 2px off the bottom and icons/labels read high. Size the row to the inner height and pin labels to the same 18px box as the tiles. Co-authored-by: Cursor <cursoragent@cursor.com>
…separately Keep agent spawn chips visible outside the tool-group collapse
…sh#35) * feat(harness): pin pi-acp to the davidhs26 fork by commit SHA (ID01-435) NpmPin now understands 'github:owner/repo#<sha>' pins: name is the repo's last segment (pi-acp), version is the full SHA (immutable install dir ~/.zeron/adapters/pi-acp/<sha>), and spec() keeps the whole github string so npm installs from the fork, never the stale registry release. Existing name@version pins (grok, cursor SDK) are unchanged. pi_spec() pins github:davidhs26/pi-acp#d7f9cb2428c992c62aa759919c799c5619a9b10b (includes _session/steering). check-pi-acp-steering.mjs no longer assumes semver-only installs: prefers the SHA dir, then 0.0.33, then whatever exists; argv override intact. No npm publish, no engine swap; PI_ACP_EXECUTABLE override still wins. * fix(harness): re-pin pi-acp SHA after adding prepare (ID01-435 review) Git deps only run prepare; prepack left dist/ empty. Fork now has prepare; pin 7a8548c empirically installs dist/index.js + steering. * fix(harness): drop stale short SHA from pi-acp install_hint --------- Co-authored-by: David <davidhsutton@gmail.com>
Squash de nuestro delta sobre upstream/main (983e23d). 18 archivos en conflicto resueltos con politica declarada: - upstream gana: Cargo/gpui rev, workspace_host (TRIPWIRE RETIRADO definitivamente + wiring en lib.rs — superseded por gap-repair/delivery durable), doc_host (transfers), crates/ui, DeviceRelayClient (PeerLiveness, zombie-link teardown), WorkspaceStore, HomeView (badges Queued/Failed). - union: exports DRAINING_ERROR, spawn_agent con stderr_tail (upstream) + utility marker (nuestro), tests de ambos lados, SessionView = sendState upstream + chip de to-dos ID01-503. - fixes: utility:false en RunRequest del test helper de sessions.rs; feature v5 de uuid restaurada (la usa pi-adopt). - CI hosted -> workflow_dispatch only (facturacion Actions /mnt/c/Users/David/commit_sync17.sh; el gate es local). - Gotcha ambiental documentado: la captura shell-env de upstream (bash -l -i) queda STOPPED por job control en WSL sin tty y cuelga tests/runtime pese a su timeout — mitigado con ZERON_NO_LOGIN_SHELL=1 en ~/.zeron/env (hatch de upstream); defecto a reportar upstream. Validacion: build release OK - engine lib 140/140 - harness lib 99/99 - extensiones 85+12+34 - todo sin skips.
…se (review k3, blocker de compilacion Swift)
Merge del arbol del branch sync/upstream-v0217 (squash upstream+delta). Detalle, validacion y review k3 en el PR zeronsh#36.
…abort (ID01-519) (zeronsh#37) * fix(engine): zombie turn — quiesce window on user-turn resume + zombie prompt abort (ID01-519) Bug 3: track WHY the last park happened (resume_arms_short_window). A quiesce-parked USER turn (missing harness Done) keeps the LONG window on resume — it is the same turn continuing — instead of arming the 20s self-continued window that re-parked it ~24s later (2026-08-18 incident). Done-parks (and self-continued quiesces) still arm the short window. The quiesce WARN now logs the window that actually fired, not the knob. Bug 2: dispatch/steer refuse to route a prompt as a steer into a Working run silent past ZERON_ZOMBIE_PROMPT_MS (default 8s, 0 disables) with nothing in flight (the watchdog's own fold gate) — they interrupt the zombie and dispatch fresh (TTFT 435s in the incident). Open tools, pending inputs and parked runs still steer as before. Tests: user_turn_quiesce_resume.rs, zombie_prompt.rs (new binaries); turn_quiesce + self_continued_quiesce stay green. * test(engine): pin ZERON_ZOMBIE_PROMPT_MS=0 always-steer (ID01-519 review) Sibling binary because env knobs are process-global; silence past the default 8s window must keep the same run id. --------- Co-authored-by: davidhs26 <davidhs26@users.noreply.github.com> Co-authored-by: David <davidhsutton@gmail.com>
…D01-529) (zeronsh#38) * fix(engine): a finished subagent no longer pins the in-flight gate (ID01-529) A spawn chip closes on LIFECYCLE, not on `resolved`. The adapter mints two tool ids per subagent: the spawn call (`call-parent`, settled eagerly by its own ToolResult) and the chip that indexes the nested transcript (`call-parent:agent-1`), which no ToolResult ever carries — it reaches its terminal state through `Subagent { Done }`, i.e. through `subagent_status`. `fold_inflight` read `resolved` alone, so every finished subagent left one phantom in-flight part in the fold. That gate guards BOTH silence paths — the quiesce watchdog park and dispatch's zombie abort — so a single spawn disarmed both for the rest of the chat. Incident 2026-08-20, chat 9d321b30 (Pi via pi-acp, 4 subagents): the provider stream died mid-turn at 20:43:47 and the turn read Working until pi's own socket timeout fired 35 minutes later. No park (ZERON_TURN_QUIESCE_MS=300000 never armed), and a user prompt would have queued as a steer instead of aborting the zombie. A RUNNING subagent still pins the gate — a spawn legitimately runs silent for many minutes (an 18.5-minute implement agent in that same chat), so this is a lifecycle read, not an exemption. Tests: subagent_chip_inflight.rs covers both halves (a settled chip releases the watchdog, a live one holds it). Both fail against the pre-fix gate. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01M5BJv4bnBHcCUUXdvhTnTN * fix(engine): Running chip releases in-flight gate when resolved (review ID01-529) --------- Co-authored-by: David <davidhsutton@gmail.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Incidente 2026-08-21 (3ra falla de la clase "entrega a la app"): un turno termina Done en el journal pero el mensaje final nunca llega al telefono; reiniciar el engine lo curaba porque el rejoin postea un checkpoint. El sync v0.2.17 retiro el tripwire escalado de PR zeronsh#7 apostando a que gap-repair + delivery durable lo superseded; la auditoria encontro tres huecos: - el probe quiet de los rooms chat2 es de 15 min (PROBE_QUIET_DEFAULT=900s): un socket sordo al final del turno deja las filas finales unacked hasta 15 min; - el checkpoint (HTTP, independiente del WS y unica cura para un lector con el cursor retenido sobre un row gap) solo se postea por threshold, bootstrap o server-reset — nunca al finalizar un turno; - el tripwire de sordera del registry redialea solo el registry y nunca barre los chat rooms (la mitad retirada de PR zeronsh#7). El fix, sobre primitivas existentes: - DocHost::seal_turn(chat): en cada finalize de turno (Done de cualquier status + quiesce-park con segmento visible) probea el socket del room (sordo → PROBE_DEADLINE 10s → redial + flush de pending) y postea un checkpoint "turn-end" salvo room probadamente cubierto (server_known, checkpoint >= cursor, sin pending); - DeafEscalationHook en workspace_host: el redial del tripwire spawnea (nunca inline bajo los locks room/presence — leccion AB-BA de la review de PR zeronsh#7) un sweep probe_open_chats() de todos los rooms abiertos; captura Weak del grafo (un strong DocHost en el slot del workspace ciclaba el grafo y el shutdown leak test local_first lo cazo); - el PUT del tail sidecar loguea rechazos y errores (antes let _ = silencioso: el fallback de lectores thin fallaba sin dejar señal); - docs/chat2-sync.md C3 documenta el trigger nuevo. Tests: zeron-engine + zeron-sync completos en verde (196 passed), incluido online_runtime_shutdown_stops_edge_workers_and_retires_the_graph que cazo el ciclo de Arcs de la primera version del wiring. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GyJydy2tozB5X1X8ZvbdKQ
…wn nothing-streamed + warn en sweep sin runtime MAJOR costo por turno documentado como tradeoff deliberado (no se salta el checkpoint con probe vivo: no cura al lector wedgeado). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Qué
Sello de entrega al final de cada turno + barrido de rooms al escalar el tripwire de sordera del registry. Cierra de raíz la clase "el turno terminó en el journal pero el teléfono nunca ve el mensaje final" (3 incidentes: 2026-08-18 ×2, 2026-08-21).
Por qué
El sync a upstream v0.2.17 retiró el tripwire escalado de PR zeronsh#7 ("superseded por gap-repair/delivery durable"). La auditoría del incidente del 21/08 mostró que la apuesta era parcialmente falsa — quedaron tres huecos:
PROBE_QUIET_DEFAULT = 900s: un socket chat2 sordo justo al final de un turno deja las filas finales sin ackear hasta 15 min.Cómo
DocHost::seal_turn— en cada finalize (todoDone+ quiesce-park con segmento visible):probe()inmediato (sordo → deadline 10s → redial + flush) y checkpoint"turn-end"salvo room probadamente cubierto (server_known && checkpoint_size>0 && cursor<=checkpoint_seq && pending_pushes==0).DeafEscalationHook— el redial del tripwire spawnea (nunca inline bajo los locks room/presence; lección AB-BA de la review de PR 申请 alpha 内测资格 —— eda91371@gmail.com 提示账户不存在 zeronsh/zeron#7)probe_open_chats(). Captura Weak del grafo: la versión strong ciclaba el grafo ylocal_first::online_runtime_shutdown_stops_edge_workers_and_retires_the_graphlo cazó.let _ =silencioso).docs/chat2-sync.mdC3 actualizado.Tests
cargo test -p zeron-engine -p zeron-sync: 196 passed, 0 failed (el leak test falló contra la primera versión del wiring y validó la corrección a Weak).Review adversarial independiente con Kimi K3 en curso; findings se aplican antes del merge.
🤖 Generated with Claude Code
https://claude.ai/code/session_01GyJydy2tozB5X1X8ZvbdKQ