Commit Graph
662 Commits
Author SHA1 Message Date
661f2178c6 fix(cli): keep fresh machine-spawn prealloc on adopt-stub
rpcGateway sends sessionId = reservedSessionId ?? existingSessionId, so a
fresh Codex spawn arrived with sessionId set. The local reopen fallback then
stamped --existing-session-id for the preallocated stub, and Codex exited 1
with "Existing HAPI session has no Codex thread binding". Only an explicit
existingSessionId may reopen; reservedSessionId stays on --hapi-session-id.

via [HAPI](https://hapi.run)

Co-Authored-By: HAPI <noreply@hapi.run>
Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-23 18:22:05 +08:00
ee8802784d merge: hapi upstream main 2026-10-19 (34 commits past v0.30.7)
Bring in the post-0.30.7 upstream work while keeping local team, macaron,
requirement, DSH and deployment features:

- runner/hub: cross-platform orphan reap on archive and tracking loss,
  machine-spawn preallocated stubs, keepalive-only sessions reconcile to idle
- hub: prepared-statement cache, stale/canceled permission request handling
- cli: Codex compaction announcements, dynamic Kimi models, MCP bridge reap
- web: session toolbar icon shedding, mark-all-read, unread/active sections,
  Russian locale, external-link preference, 48px search chrome
- ios/android: locale and font-size updates

Conflict resolution keeps local-only behavior (agent team spawns, macaron
skins, collapsible sidebar, workspace markdown images) layered on the new
upstream toolbar and session lifecycle plumbing.

Test infrastructure: skip the Docker-only pwsh integration test quickly when
Docker is unavailable, and exclude the Bun-only usageScan test from vitest.

via [HAPI](https://hapi.run)

Co-Authored-By: HAPI <noreply@hapi.run>
Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-23 12:44:05 +08:00
7fe397c595 feat(web,cli): load workspace markdown images from the session machine
Local Markdown image paths now resolve through the session file API and
render from a data URL, with loading/failure/retry states and a portal-based
viewer. Out-of-workspace paths show a resend hint; the display_image prompt
tells agents not to emit local-path Markdown images.

via [HAPI](https://hapi.run)

Co-Authored-By: HAPI <noreply@hapi.run>
Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-23 11:43:24 +08:00
AnanovoandGitHub 6f155313ff fix(codex): keep status summaries out of intermediate messages (#2041) 2026-10-20 07:21:21 +08:00
wushenghua ee4422dbc5 feat(codex,web): show live context compaction status
The composer status bar surfaces an in-flight Codex context compaction
with the native start time and an elapsed timer; completion,
interruption, reconnect, and shutdown all clear it. The CLI follows the
contextCompaction item on the app-server stream and publishes
contextCompactionStartedAt through the shared agent state.
2026-10-16 20:32:57 +08:00
wushenghua 2d57763177 fix(hub,cli): run OpenCode usage scans off the process event loop
The 5-minute reconciliation read the multi-GB OpenCode store on the hub's
event loop with bun:sqlite; a cold scan froze every request for ~88s and
stalled each machine's runner the same way.

- scan in a spawned `hapi usage-scan` child process (stdin/stdout JSON)
- refresh on demand from the usage page (debounced) + a daily fallback
- a runner whose hub is localhost skips the store the hub already owns
- machine scans run every 30min and read the message table sequentially
  in one pass instead of per-session indexed lookups
2026-10-16 19:38:07 +08:00
Junmo KimandGitHub 5847b034e5 fix(codex): announce automatic context compaction when it starts (#1999) 2026-10-13 08:20:02 +08:00
b3a9c0cddf fix(cli): strip agent-context env from web Terminal PTYs (#2014)
Runner injects HAPI_AGENT_CONTEXT into session CLIs; TerminalManager was
forwarding it into human-facing web Terminal shells, so operator tools
(lane B auth, stack-switch guards) refused a real person. Strip agent
markers, keep HAPI_SESSION_ID, set HAPI_USER_TERMINAL=1.

Fixes #2013

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-12 22:16:03 +01:00
c3e33835c3 fix(cli): stamp and reap orphan HAPI MCP bridge helpers (#2004)
* fix(cli): stamp and reap orphan HAPI MCP bridge helpers

MCP stdio bridges survive agent-root death (OOM / partial kills) and
were invisible to #1911 argv orphan reap. Stamp --hapi-session-id on
bridge spawn and match mcp --url helpers in orphanReap.

Fixes #2003

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(cli): use ApiSessionClient.sessionId for MCP bridge stamp

bootstrapSession returns ApiSessionClient directly; session.client was a
typecheck failure that broke CI typecheck:cli on #2004.

Co-authored-by: Cursor <cursoragent@cursor.com>

* chore: retrigger Codex PR review after 503 infra failure

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-12 21:58:57 +01:00
wushenghua a759a62b77 fix(codex): surface retrying stream errors in shared sessions
The app-server emits error notifications with willRetry: true while it
retries a stream failure, but the converter drops them, so the chat went
silent until the terminal error appeared. Surface them as the existing
api-error status event (per-turn attempt counter, root thread only) so
every client renders "API error: Retrying..." during the retry window.
2026-10-12 15:42:13 +08:00
wushenghua baeeb11356 fix: preserve deployed session fixes
Persist paged Codex history checkpoints and bounded, acknowledged delivery.
Keep usage sources and accounting across deleted or merged sessions.
Include native Goal controls, faster peer/session queries, and release guards.
Track the complete deployed source so isolated builds cannot omit local fixes.

Validation: all typechecks pass. The full suite reports 7,997 passing tests
and 46 existing failures, each reproduced against the prior commit.
2026-10-08 19:51:45 +08:00
wushenghua cc1fa87bd4 fix(hub,cli): stream large generated media in bounded chunks
A 19MB video download traveled as one ~26MB base64 socket ack. On a remote
runner behind a slow tunnel (measured ~0.35MB/s k2lab->mini) that exceeds
the hub's RPC budget and Bun's HTTP idle cutoff, so the request died with
an empty reply at ~50s (and at ~240s after the first timeout bump).

The route now probes with a 1MB read that reports total size + mime, then
streams the rest chunk by chunk with Content-Length, honours Range/206 for
players and download tools (416 on unsatisfiable ranges), and falls back
to the legacy full payload when the session process predates this change
(it ignores offset/length and reports no size).

Also gives media/files/upload RPCs a 300s budget and raises the hub HTTP
idle timeout to 240s so a slow transfer is not killed while waiting on the
CLI round-trip.
2026-10-01 00:13:38 +08:00
wushenghua e998f05eb2 merge: hapi v0.30.7
Six text conflicts, all resolved:
- cli/apiMachine + runner/run: adopt upstream's MachinePathPolicy; keep the
  local policy that browsing stays disabled until --workspace-root is set
  (banner text + upstream's browse test adapted to assert the local gate).
- cli/codex root.test.ts: keep both the syncHistory split test and upstream's
  change_title/name test; fixture return merges metadata + syncHistory.
- web/SessionList: combine the local personal/global pinned split with
  upstream's search-relevance ranking (deps union).
- web/AskUserQuestionFooter: take upstream's draft-restore effect and thread
  the local skippedByQuestion through the draft store; the local DSH skip test
  moves to AskUserQuestionFooter.skip.test.tsx so upstream's vi.mock setup in
  the original file cannot shadow it.

Also: bun install for the assistant-ui 0.15.21 bump, regenerated
shared/fixtures from the merged pipeline.

Test status vs the pre-merge baseline (d6cf54d7), identical pre-existing
failures only: cli 7, hub 2 (opencodeClear), web 35, shared 2 (DSH); relay
clean; typecheck clean across all packages.
2026-09-29 21:14:39 +08:00
wushenghua d6cf54d724 fix(codex): report session started before native history replay
The runner kills a runner-spawned child whose "session started" webhook
misses its bounded wait (15s by default), so a cold resume whose thread
has a large rollout always failed with "Session webhook timeout for PID
...": the codex shared runtime only reported after reconciling the full
native history (projection.history + refresh + refreshChildren), and
replaying tens of thousands of items took longer than that wait.

Measured on this machine before the fix: two ~800MB rollouts (36k/43k
lines) needed 17-20s to bind; one attempt was killed 2s before its late
webhook landed and was reaped as an orphan.

Split SharedCodexRoot.bind() into the local claim (queue, projection,
metadata, settings) and a separate syncHistory(); runSharedRuntime.bind()
now activates the session and reports it to the runner before the replay.
The session is controllable when the signal fires (controls registered),
and the replay continues in the background; re-sent history is idempotent
because projection uses stable message ids that the hub dedupes by
local_id.

Covered by root.test.ts: bind performs no native history reads, syncHistory
does the reconciliation.
2026-09-29 20:18:51 +08:00
86c88df93b fix(runner,hub): cross-platform orphan reap on archive / tracking loss (#1911)
* fix(runner,hub): reap detached driver CLI on archive and tracking loss

Archive always verifies via stopRunnerSession; runner stopSession scans
argv orphans and awaits webhook-timeout tree-kill so PPID=1 children
cannot accumulate after KillSession miss or map drop.

Fixes #1910

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(cli): keep --hapi-session-id reap-only; test orphan kill path

Claude/cursor swallow the stamp without bootstrap reuse, and
reapRunnerSpawnedOrphans is unit-tested as the stopSession map-miss path.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(cli,hub): address #1911 CI and bot Majors

Use Vitest in orphanReap tests, add Pi session.on mock, refuse archive
when machine StopSession is unreachable, trust full tree-kill results,
and argv-sweep orphans after every mapped stop.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(runner): gate late-webhook kill on timeout orphans; fail closed on scan error

Track webhook-timeout PIDs separately so recovered shared roots are not
killed, and treat ps-list failures as still_alive rather than empty.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(runner): keep shared Codex wrapper alive when archiving one root

StopSession must detach a single shared root without tree-killing the
wrapper PID while sibling roots remain, including after KillSession has
already marked the runtime inactive.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(runner): keep shared Codex siblings after restart tracking loss

Before persisted-PID or argv orphan kills, consult the durable runtime
registry for active sibling roots on the same wrapper PID so archiving
one root after a runner restart cannot tree-kill live shared sessions.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(hub,cli): deliver hub-authored archive metadata to reconnecting CLIs

When KillSession misses and archive is written via markSessionArchivedFromHub,
emit Socket.IO update-session to the session room. On reconnect, ApiSessionClient
reconciles GET /cli/sessions/:id metadata so hub-archived still fires.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(runner): argv-scan orphans even when shared siblings remain

Remove session-wide early returns that skipped orphan reaping whenever any
shared wrapper had active siblings. Keep only the PID-specific filter so
older untracked CLIs with the same HAPI id are still reaped.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(runner): protect tracked shared wrappers when runtime registry is empty

Argv orphan sweeps must exclude in-memory shared wrapper PIDs that still
host sibling roots, not only durable registry siblings — otherwise a missing
runtime record lets the sweep tree-kill the wrapper while archiving one root.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(runner): adopt untracked nonshared CLIs after runner restart

When a runner-spawned Claude webhook arrives with no TrackedSession (typical
after restart mid-bootstrap), durably adopt the PID instead of ignoring it so
StopSession can reap. Keep killing only nonshared timeout orphans; never kill
shared Codex wrappers on late webhooks.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(runner): keep recovered shared wrappers for registry siblings

After restart adoption tracks only a newly reported Codex root, archiving
it must consult the durable registry before tree-killing the wrapper PID
so older active sibling roots survive.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(hub): do not archive on KillSession ack when StopSession is unknown

KillSession returns before cleanupAndExit finishes. When the runner cannot
find the PID (unknown), wait for the session KillSession handler to drop
before archiving; refuse if the CLI socket remains reachable.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(hub,cli): confirm KillSession pid via StopSession before archive

KillSession now returns the CLI pid. When session-id StopSession is
unknown, archive confirms that OS pid (without treating socket loss as
exit) and still refuses when confirmation stays unknown or still_alive.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(runner,hub): tree-kill adopted CLIs and require PID start markers

Adopted post-restart runner sessions now use killProcessTreeByPid when no
ChildProcess handle exists. KillSession carries a process-start marker so
raw PID confirmation cannot tree-kill a reused OS pid.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(runner,hub): verify adopted PID markers and hub-archive after stop

Adopted sessions compare the persisted start marker before tree-kill and
drop stale tracking on mismatch. After confirmed StopSession, hub-authors
archive metadata when the CLI did not finish its flush.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(runner): refuse unconfirmed shared/adopted stops

Keep shared wrappers when siblings remain, but return unknown unless
runtimeControl already ended the root. Adopted PIDs without a start
marker no longer tree-kill by tracked PID alone.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(runner): require inactive binding before sibling-kept archive

Siblings alone must not flip StopSession to stopped on retry, while an
acknowledged inactive Codex registry binding remains stop proof. Handle
PID- markers before any tracked kill so reuse cannot nuke strangers.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(runner): distinguish orphan reap from unknown keep-wrapper base

Pass unknown into finishWithOrphanSweep so a successful argv reap stays
stopped instead of being overwritten by an absent registry binding.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(runner): honor orphan reap when adopted PID lacks a start marker

finishWithOrphanSweep with an unknown base lets a successful argv kill
report stopped instead of forcing unknown after the marker probe fails.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(hub): confirm archive via metadata.hostPid tombstone

When StopSession(sessionId) is unknown and KillSession supplies no
confirmable pid+marker, probe PID-<metadata.hostPid>. already_gone
allows archive; still_alive/unknown still refuse (#1911 Peer #1820).

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(cli): escalate Windows taskkill to /F like SIGTERM→SIGKILL

Soft taskkill /T is refused on win32 console trees; orphanReap left
orphans still_alive. waitForProcessToDie now force-escalates on both
platforms so cross-platform reaping matches.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(cli): widen process.kill mock signal to string | number

Match @types/node process.kill(pid, signal?: string | number) so the
Windows taskkill escalation mocks typecheck on tip 54f141368.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(cli): tasklist liveness + immediate /F after soft taskkill refuse

Windows orphan reap still failed in 5ms on Teemo: soft taskkill refused
but escalate waited on an unreliable process.kill(0) probe. Prefer
tasklist for win32 liveness and escalate to /F as soon as the PID is
still live after soft kill.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(cli): coerce win32 ps-list PIDs and escalate soft taskkill to /F

String PIDs from ps-list made Number.isFinite fail so orphan kill
returned still_alive without calling taskkill. Coerce PIDs, prefer
tasklist liveness, and escalate soft taskkill to /F like SIGTERM→SIGKILL.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(cli): Windows orphan scan via CIM CommandLine + taskkill /F escalate

ps-list/fastlist is missing CommandLine and often missing from single-exe
bundles, so Teemo orphan reap returned scan_failed (logged as live PIDs).
List via Win32_Process CIM, coerce PIDs, escalate soft taskkill to /F.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(cli): grace-wait after successful soft taskkill before /F

Escalate immediately only when soft taskkill is refused. When soft
succeeds but the PID is still draining, honor the existing grace period
before force (Codex Major on #1911).

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(runner): re-verify start marker before orphan argv tree-kill

Capture getProcessStartMarker after the argv match list, re-check
immediately before killTree, and skip on mismatch/null-while-alive so
PID reuse cannot tree-kill an unrelated process on the orphan fallback.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(hub): preallocate HAPI id before fresh machine spawn

Fresh POST /machines/:id/spawn omitted existingSessionId, so argv never
got a reap stamp. Preallocate the hub row (fork/clear pattern) and pass
that id so a runner restart before the first webhook remains reapeable.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(cli,hub): close three post-prealloc unreapable paths

Pass --hapi-session-id into Claude/cursor/kimi/copilot bootstrap so the
preallocated hub row is used. Treat null marker re-check while alive as
still_alive (not stopped). Keep the preallocated stub when spawn-fail
StopSession cannot confirm the child is gone.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(cli): adopt-stub via reservedSessionId, not existingSessionId

`--hapi-session-id` binds a fresh create bootstrap to a hub-preallocated
UUID (getOrCreateSession id) without taking the reopen path. Non-UUID
stamps stay reap-only to match hub create schema. Restores integration
spawn invariants broken by mapping hapi→existingSessionId.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(cli,hub): close Opus cold-read B1–B3 for #1911

B1: capture start markers from the same process snapshot as argv
match (POSIX ps lstart / Win32 CreationDate); re-check before kill.
Continue other orphans when one target is unconfirmed.

B2: Windows killProcessTreeByPid verifies every collected descendant,
not root-only — taskkill /T exit 0 is signalled, not gone.

B3: runner sets childStarted:false on pre-exec rejection; hub deletes
the preallocated stub without StopSession. Post-exec ambiguity still
keeps the row until stop confirms. Terminal archives no longer refuse
when no runner is connected.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(cli,hub): explicit adopt for hub-preallocated session stubs

Fresh machine spawn preallocates with tag machine-spawn:<uuid>; CLI
create with a different tag and the same id 409'd on identity conflict.
Add adoptPreallocatedSession (hub-internal stub check) and POST
/cli/sessions { adopt: true, id } so create binds the reserved row and
overwrites tag/metadata without guessing the hub tag. Tests fail-first
on the real prealloc→CLI create path.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(cli): preserve snapshot markers + unify Windows CIM start markers

Runner stopSession was still on findOrphans (PID-only), discarding same-snapshot
markers and reopening the PID-reuse kill window. Switch both call sites to
findTargets. Force UTC ISO 'o' CreationDate for list and single-PID probe so
ConvertTo-Json cannot disagree with getProcessStartMarker on WinPS 5.1.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(cli): exercise production orphan sweep wiring in tests

Extract findStopSessionOrphanTargets as the stopSession discovery path both
run.ts sweeps call. Add tests that drive that helper end-to-end (not an
injected findOrphans seam), assert run.ts source wiring, and cover CIM-list
ISO vs probe ISO agreement plus the broken WinPS shape mismatch.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(hub,cli): never delete live spawn on RPC timeout; release stub tags

Ambiguous machine-spawn failures (timeout / unset childStarted) no longer
call stopRunnerSession or deleteSession — that path killed healthy late
boots and CASCADE-wiped transcripts. Pre-exec delete still requires a real
machine-spawn stub (store tag check). Metadata updates release the stub
tag so reopen flavors are not permanently adoptable; adopt refuses
archived stubs. POSIX pgrep ENOENT fails closed instead of root-only verify.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(cli): parseable Windows tree scan + fail closed on CIM failure

Space-joined PowerShell for collectWindowsProcessTree was a WinPS parse
error ($seen=@{} $bfs=@()), so every scan fell open to [root] and B2
false-stopped again. Join with newlines; return scan_failed on error/empty;
killProcessTreeByPid refuses root-only verify. Assert on generated command
text (Teemo-confirmed) so mocks cannot hide the next malformed script.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(cli,hub): reopen stamps existing-session-id; scan-fail signals root

Opus Critical on #1911: runner always stamps --existing-session-id for
known hub ids so Claude/Kimi/Copilot reopen does not hit adopt-stub 409.
On pgrep/CIM tree-scan failure still signal the verified root but never
claim stopped. Archive when both KillSession and machine RPC are missing.
Empty Win process-list stdout and strict Codex registry reads fail closed.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(cli,hub): OK: sentinel for Windows tree scan; adopt CAS

CIM failure under SilentlyContinue printed root-only with exit 0 —
byte-identical to a healthy childless tree, so scan_failed never fired
and killProcessTreeByPid false-stopped (#1911 Overseer B1). Require
ErrorActionPreference=Stop, trap exit 1, and an OK: success sentinel;
parse anything else as scan_failed. Same Stop/trap on the machine-wide
list command. Execute the generated scripts under Docker pwsh in CI
when the image is present. Treat pgrep status=null as scan_failed;
coarsen Windows tasklist poll; wrap adoptPreallocatedSession in a
transaction with stub-tag CAS.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(cli,hub): reservedSessionId for fresh prealloc; signal Win survivors

Fresh machine-spawn stubs must not stamp --existing-session-id — that
sends Codex down reopen without a thread binding (#1911 bot Major) and
re-broke local HTTP non-UUID hints as reopen (round-4). Hub passes
prealloc as reservedSessionId; buildCliArgs stamps --hapi-session-id
for reserved/sessionId and --existing-session-id only for live reopen.
Codex parses --hapi-session-id and adopts via bootstrapSession.

Windows tree kill now taskkills surviving pre-scan PIDs after /T misses
broken intermediate links.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(cli,hub): fail-loud Docker pwsh CI; scope Codex registry; stub leak

CI skipped all four windowsProcessTree.pwsh tests on 48bb (image inspect
without pull). Pull the image in test.yml and throw under CI when missing.
Scope strict runtime-registry failure to Codex stop contexts so one bad
JSON cannot block archive machine-wide. Stamp childStarted:false on
machine RPC outside_workspace_roots. Add Codex reservedSessionId prealloc
and runSharedCodex regression coverage for the #1911 Critical.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(cli,hub): single-use reservedSessionId; skip UUID HTTP hints

Consume the machine-spawn reservation on first prepare() so a second
Codex root/fork cannot re-adopt a consumed stub (409). Stop stamping
UUID local-HTTP sessionId as --hapi-session-id (adopt hard-fail). Update
stale hub spawn mocks for the reservedSessionId arity.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(hub): archive never-started machine-spawn stubs on unknown

Ambiguous keep-stubs (startedBy=runner, no hostPid, still machine-spawn
tagged) made StopSession return unknown forever while the runner was
online, so archive threw permanently. Treat unknown + prealloc stub +
no hostPid as already_gone. Also reject spawn success when the runner
reports a different id than the preallocated stub.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(hub,cli): refuse dual-RPC-miss archive; generation-check Win survivors

Both KillSession and StopSession missing is not proof a detached CLI exited —
keep the row unconfirmed until the runner reconnects. Windows tree kill now
re-checks process-start markers before individually signalling survivors so
PID reuse is not killed.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(cli): refuse archived reopen in bootstrapExistingSession

Hub-archived rows could be resurrected when a late-booting child hit
--existing-session-id: buildSessionMetadata stamped lifecycleState=running
and updateMetadata overwrote the archive. Adopt already rejected archived
stubs; reopen now matches. Intentional revive still goes through hub
reopenSession first. Correct stub-hatch comment (no post-archive argv sweep).

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(hub,cli): reject un-archive in updateSessionMetadata CAS

Hub-side transactional guard (Discovery #1911 M1): refuse metadata writes
that clear archived lifecycle unless allowUnarchive (reopen clear). Client
stops infinite updateMetadata backoff when mismatch applies archived.
Clear archive immediately before spawn so Pi/PTY attempt snapshots stay valid.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(hub,cli): success+preserve hub archive; M3 reservedSessionId split

Replace version-mismatch refuse (livelock in CAS backoff) with success +
merge-preserve of hub-archived fields unless allowUnarchive. Ack path
routes through noteHubArchived so the CLI exits (criterion 6). Clear-
before-spawn remains the authorized revive. Thread --hapi-session-id to
reservedSessionId for shared-parser flavors (adopt≠reopen).

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(cli): fail-closed orphan ps signal; PID- registry; spawn timeouts

Overseer B1: signalled ps (status null) is scan_failed, not empty table.
B2: unreadable registry on PID-* stops returns null (no soft []).
B3: 10s timeout on process/orphanReap spawn.sync. Narrow bootstrap
archived belt to archivedBy=hub to match hub merge-preserve.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(cli,hub): hubArchived latch; PTY archiveSnapshot durability

Criterion 6: noteHubArchived latches hubArchived so late
registerKillSessionHandler registrations still EXIT (EventEmitter
does not replay). Mirror Pi: persist archiveSnapshot on
ptyResumeAttempt across clear-before-spawn and quarantine, restore
on failed-resume cleanup (#1911 Discovery Majors).

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(cli): Codex hub-archived exit; AC6 latch test production order

C2: SharedCodexRoot registers hub-archived (and latch check) beside
its private KillSession — Codex never called registerKillSessionHandler.
C1: keep latch+subscribe; test write-then-register order Discovery proved.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(cli): type Codex hub-archived end mock for tsc

vi.fn(async () => {}) inferred zero-arg calls; RootHost['end']
signature unblocks end.mock.calls[0][0] (TS2493).

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(cli): catch hub-archived Codex host.end rejection

void this.host.end() discarded archive-RPC failures as unhandled
rejections; Codex has no unhandledRejection handler, so Node can
exit the shared wrapper and kill sibling roots mid-turn.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-25 09:15:08 +01:00
a259ad3144 fix(cli): change_title updates metadata.name for visible renames (#1896)
* fix(cli): change_title updates metadata.name for visible renames

Agent change_title was writing metadata.summary only, so spawn --name
sessions kept showing the old title. Route explicit renames through
metadata.name (web rename semantics) across MCP, Codex, and Pi.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(web): voice context prefers metadata.name for session title

Voice bootstrap/full formatters only read summary.text, so after
change_title sets name the spoken context kept a stale title. Use the
same name→summary precedence as getSessionTitle.

Co-authored-by: Cursor <cursoragent@cursor.com>

* chore: polish title rename tests after cold review

Rename the Codex root title test to match last-writer-wins behavior,
and skip the voice full-context summary header when no title signal
exists (avoid literal undefined).

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-21 13:49:53 +01:00
MiroandGitHub b6a27bdc68 fix(kimi): discover configured models dynamically (#1873)
* fix(kimi): discover configured models dynamically

* fix(kimi): address dynamic model review findings

Probe settlement (Major): parse `kimi provider list --json` on the child's
'close' event instead of 'exit', so stdio is fully drained first, and run the
parse inside a guarded settle() helper. A parser throw previously escaped the
promise from an async event callback and reached the runner's uncaughtException
shutdown handler instead of listKimiModelsForCwd's catch. The helper also
settles the promise exactly once across timeout, spawn error and close, names
the terminating signal when there is no exit code, and tolerates a kill() that
throws.

Running-session discovery (Minor): add a session-scoped Kimi catalog. The
shared RPC method `listKimiModels`, a CLI handler registered on the session
connection with the probe cwd taken from the session metadata (never from RPC
arguments), hub rpcGateway/syncEngine pass-throughs, and
GET /api/sessions/:id/kimi-models gated on an active session of flavor `kimi`.
SessionChat now reads the catalog from that endpoint, so an active session
advertises its models even when no background runner holds the machine socket.
New Session keeps the machine-scoped discovery unchanged, and model switching
still goes through the existing ACP set_model path.

* fix(kimi): register session model discovery before the local/remote loop

The session catalog handler was registered inside the remote launcher, after
ACP initialization. A session started with `hapi kimi` in local mode therefore
answered the web picker's request with an unregistered-handler error, and the
failed query was not retried or invalidated when remote control was taken over.

The catalog is backend independent, so it is now registered in runKimi session
setup alongside the other session-level handlers, using the session's working
directory, and the late registration in the remote launcher is removed. This
covers local, remote and resumed sessions on the same session connection.

Adds cli/src/kimi/runKimi.test.ts asserting the handler is registered after
bootstrap and before the loop, and that the probe cwd is the session's working
directory. The launcher wiring assertion moves out of
kimiRemoteLauncher.test.ts with the registration.
2026-09-21 13:58:59 +08:00
wushenghua 28f409f2ca feat(hub,cli): reconcile OpenCode usage from its own token ledger
The live pipeline records only the final model step of an OpenCode turn,
so the usage dashboard showed roughly 42% of OpenCode's own totals. Add
an offline reconciliation ledger instead of touching the streaming path
(a previous attempt that fed cumulative counters into the live pipeline
made the chat/status UI numbers jump):

- shared: read and aggregate `~/.local/share/opencode/opencode.db` per
  (day, model), exposed as `@hapi/protocol/opencodeUsage`
- hub: additive `usage_reconciliation` table (no schema bump, safe to
  roll back), local scan at startup and every 30 minutes, and the usage
  summary prefers snapshots over live events for reconciled sessions
- cli: each runner scans its machine's store every 30 minutes and reports
  snapshots over the new `opencode-usage-report` socket event, covering
  remote machines and sessions whose process already exited
- the hub-local scan skips sessions whose store lives on another machine,
  so it can never wipe remotely reported rows
2026-09-18 01:45:43 +08:00
wushenghua 03b4d2673c fix(cursor): prefer cursor-agent over the legacy agent CLI
Cursor renamed their CLI to cursor-agent; the old agent name is easily
shadowed by unrelated tools (on one machine the Grok TUI owns agent), which
made the cursor model probe run the wrong binary - four seconds of clap usage
errors and a broken ACP backend. Resolve cursor commands via PATH candidates
(cursor-agent first, then agent); HAPI_CURSOR_PATH overrides both.
2026-09-17 09:54:46 +08:00
wushenghua 51de344c98 feat(hub,cli,web): requirement layer, smart scroll and default-folded AI chatter
- requirements: a fresh human ask opens one automatically (replies inherit);
  tasks and messages carry requirementId (spawn_peer / team_task / team_send
  taskId); the lead closes one with team_requirement (status + conclusion)
- web: the team timeline groups messages into collapsible requirement cards
  (title, status, conclusion, message count; a card with a pending decision
  opens automatically); unassigned messages fall into an 'Other' card
- web: entering a team scrolls to the newest message, follows new messages only
  while pinned to the bottom, and offers a 'jump to latest' button otherwise
- web: AI-to-AI chatter (status/chat/question/task-update, not addressed to the
  human) folds into a one-line summary by default (runs of >= 2), with the
  participants and latest line preview
- tests: requirement store/service coverage and grouping helper tests
2026-09-17 00:27:07 +08:00
wushenghua 5ae0a9f2cf fix(hub,web,cli): only decisions enter the human inbox
to=human alone is an out-of-band notification, not a request for an answer;
the inbox (待你确认) now only contains kind=decision messages (legacy rows
with awaitingHuman still count). Prompts and tool descriptions now tell agents
to use kind=decision when the human must decide and plain broadcasts for
progress they merely need to see.
2026-09-16 23:25:40 +08:00
ca1fd80afd fix(cursor): pin Auto to CLI --model auto instead of account default (#1819)
* fix(cursor): pin Auto to CLI --model auto instead of account default

Selecting Auto was treated as unset/ACP default[] after the Default→Auto rename. Persist and spawn `auto` so the picker matches `agent --list-models`.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(cursor): keep live ACP model until Auto actually applies

Pin session.model to auto for the next `--model auto` spawn, but do not claim the live backend switched when ACP only advertises default[].

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(cursor): reject live Auto unless ACP auto or spawn confirmed it

Do not persist Auto when the running catalog has no auto option. Clear the spawn-auto flag after a concrete model apply so Auto → concrete → Auto cannot fake a live switch.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(cursor): keep ACP wire ids spawn-safe and apply effort

Cursor's parameterized picker (`clientCapabilities._meta.parameterizedModelPicker`)
advertises bare model bases plus separate `fast` / `thought_level` config options.
`buildCursorModelsSnapshotFromAcp` synthesized every base as
`${base}[fast=${fast}]`, and Cursor rejects any bracket id whose parameter set is
not that model's complete set, so 74 of the 76 published picker ids failed at ACP
`session/new` with `Cannot use this model` (#1818).

- Stop synthesizing wire ids: keep the advertised values verbatim (bare bases are
  accepted by `--model`, defaults applied). Parameter sets differ per model, so a
  synthesized id cannot be validated from the options alone.
- Flag such catalogs as `parameterized` so variant CLI skus still attach to their
  base rows — the ACP apply path expresses them through config options.
- Apply effort in `applyParameterizedCursorModel` (ids `effort`, `reasoning`,
  `reasoning_effort`), applied against the config options Cursor re-advertises for
  the newly selected model, and skipped on models without that axis.
- Match CLI sku bases across Cursor's legacy `cursor-` family prefix
  (`cursor-grok-4.6-high` → ACP base `grok-4.6`) so those variant rows are not
  dropped.
- Version the shared on-disk catalog cache (v2) so runners that already cached the
  rejected synthesized ids re-probe instead of serving them.

Verified with live `cursor-agent 2026.09.10-fd3934a` ACP runs: the catalog no
longer contains bracket ids, every advertised base and sampled variant sku spawns,
and in-session `claude-opus-4-8-low` / `cursor-grok-4.6-low` set the matching
`effort`/`reasoning` option.

* test(cursor): cover aliased effort ids and thinking coexistence

Covers the branches the HAPI bot review called out for #1818: the
`xhigh` → `extra-high` alias on `gpt-5.5` (`reasoning`), the
`reasoning_effort` id used by `gemini-3.8-flash`, and switching effort on a
model that also advertises Claude's separate `thought_level` `thinking`
toggle.

* test(cursor): cover legacy sku base and post-switch effort option

Adds the two apply-path cases the HAPI bot review called out for #1818:
resolving `cursor-grok-4.6-high` onto an advertised `grok-4.6` base, and
reading the effort option Cursor advertises for the newly selected model
(`reasoning` for gpt-5.5 → `effort` for claude-opus-4-8) instead of the
previous model's options.

* test(cursor): create the cache dir in the migration test

The version-envelope test wrote $HAPI_HOME/cache/cursor-models.json without
creating the directory, so running it alone (or filtering with -t) failed with
ENOENT before the assertion; it only passed because sibling tests in the same
file had already created the path. Reported in the HAPI bot review of #1822.

Verified: `vitest run ... -t 'ignores pre-versioned'` → 1 passed, whole file → 4 passed,
and every other changed test file passes when run standalone.

* Clarify Cursor Auto live-switch limitation in session pickers

* Reconcile partial Cursor model changes before acknowledging Auto

* Serialize Cursor model changes across clients with the existing async lock

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-16 08:12:16 +08:00
wushenghua 3eca03be18 feat(hub,cli,web): team config inheritance, reply visibility, task loop, scoped tokens
- spawned members inherit the caller's tool/model/thinking level/permission
  (spawn request schema + hub runtime + spawn_peer tool); explicit overrides win
- replies from turns triggered by teammates (not by the human) no longer vanish:
  system prompt and ACP briefs now require an explicit team_send(to=human) for
  human-facing conclusions produced on those turns
- task loop: team_task tool (list/update), dependsOn gating, deliverable evidence
  required when a member marks done, auto broadcast on change, web shows deps and
  deliverable on the task board
- team-scoped agent tokens (hapi_team_*): issued per team, injected as
  HAPI_TEAM_TOKEN at spawn, restricted by the auth middleware to this team's
  messages/tasks/status and audited via [TeamToken] hub log lines
2026-09-16 07:59:58 +08:00
wushenghuaandCodex 3f81120312 fix(cli): detect DSH through the Web runtime
Co-Authored-By: Codex <noreply@anthropic.com>
2026-09-16 01:58:51 +08:00
wushenghua 6c2301ae0f fix(cli): keep team prompt tests out of the workspace
teamPrompt tests ran with the real cwd and wrote .hapi/team memory into the
checkout (one charter.md was accidentally committed in ef33332d). Tests now
pass an explicit temp cwd; getTeamPromptBlock/withTeamInstruction accept a cwd.
Drops the stray artifact and ignores .hapi/ in this repo.
2026-09-16 01:28:47 +08:00
wushenghua 073e08ae47 feat(cli): per-team memory dir with legacy migration and gitignore
- team memory moves to <repo>/.hapi/teams/<slug>-<short-id>/ so several teams
  on one repository no longer share (and clobber) a single charter
- the pre-teams/ shared .hapi/team dir is moved into the first team dir that
  starts after the upgrade
- .hapi/ is appended to the repo .gitignore once (idempotent, best effort)
2026-09-16 01:27:43 +08:00
wushenghuaandCodex 388082ccd4 fix(deploy): gate unavailable agents and bound session scans
Co-Authored-By: Codex <noreply@anthropic.com>
2026-09-16 00:54:18 +08:00
wushenghua e4cc2f8eb1 fix(deploy): skip --hapi-starting-mode for codex runner spawns (lost in v0.30.3 rebase) 2026-09-15 14:13:58 +08:00
wushenghua e66dc1b0cc fix(deploy): keep sharedCodexRuntime across runner heartbeat (spread fileState) 2026-09-15 14:08:17 +08:00
wushenghua 41f0935bd3 fix(deploy): restore sharedCodexRuntime flag lost in v0.30.3 rebase; add compact to opencode slash builtins 2026-09-15 13:58:53 +08:00
08dfce67c3 fix(grok): preserve ACP text deltas (#1860)
Grok ACP emits true agent_message_chunk deltas. Default overlap
dedupe treats a later "0" after "300" as an already-buffered suffix
and drops it, so the hub stores 300 instead of 3000.

Set textChunkMode: 'delta' on createGrokBackend, matching OpenCode.

Co-authored-by: Aqi77su <304702613+Aqi77su@users.noreply.github.com>
2026-09-15 13:07:46 +08:00
wushenghua ef33332dc5 refactor(hub,cli,web): move team memory into the repo (runner side)
Team memory now lives with the code, like CLAUDE.md: <main repo>/.hapi/team/
on the machine where the agent runs. Worktrees resolve to the main repo so
every member shares one directory. The CLI creates charter.md + handoffs/ at
session start (team env only), points the agent at it in the system prompt,
and reports the path in session metadata; the web reads it through the
session file API, so remote runners work too.

Hub keeps only coordination data (teams.db): members/tasks/messages/status.
Its memory-dir management and the /memory API are removed.
2026-09-15 11:40:51 +08:00
wushenghua 94c4a2d223 fix(hub,cli,web): give web-created team sessions their team context at spawn
Root cause of 'lead never answers': sessions created through the web form
never received HAPI_TEAM_* env, so their system prompts had no team rules
(only spawn_peer-created members did). Now:
- /api/machines/:id/spawn accepts a team descriptor; the runner exports the
  env as before
- team mode creates the team first, spawns the lead with the team context,
  then attaches it as lead; member mode spawns with the team context before
  joining
- OpenCode system prompts (title + native tool instruction) carry the team
  block too
- human ping copy is action-first: reply directly (auto-synced to the
  group); do not run shell commands or explore the team
2026-09-15 11:09:32 +08:00
wushenghua b76ae314be feat(hub): bridge human<->member replies into the team group chat
Members replied in their own session and the group chat stayed empty,
which read as 'no response'. The hub now records a pending ping when a
human message is pushed to a member, and on the member's next
thinking->idle transition mirrors its last assistant text into the team
log (deduped: skipped if the member answered via team_send).
Prompts updated: normal replies sync to the group automatically; team_send
is for member collaboration and explicit reporting.
2026-09-15 10:34:04 +08:00
weishu facb66ee03 Release version 0.30.7 2026-09-15 09:08:33 +08:00
weishu 0239edf38e Release version 0.30.7 2026-09-15 09:07:47 +08:00
6c69e2f315 fix(cli,hub): finalize canceled permission requests and surface stale answers (#1854)
* fix(cli,hub): finalize canceled permission requests and surface stale answers

When Claude Code sends control_cancel_request for a pending AskUserQuestion,
the abort handler only removed the local pendingRequests entry and rejected
the promise — it never finalized the request in agentState the way the
session-level cancelPendingRequests path already does. A late answer to the
now-stale widget would still pass the hub's requests[id] check, forward via
RPC, land on a CLI that had already forgotten the request, and get silently
absorbed with no error anywhere.

Two changes, needed together:
- permissionHandler.ts's abort handler now calls finalizeRequest with
  status: 'canceled', so cancellation is reflected in agentState the same
  way a session-level cancel already is. Once synced to the hub, the
  existing requests[requestId] check in permissions.ts naturally rejects a
  late answer with 404 before it ever reaches the CLI.
- For the narrower race where an answer's RPC reaches the CLI before that
  sync lands, handleMissingPendingResponse now throws instead of only
  logging. RpcHandlerManager.handleRequest already turns a thrown error
  into a `{ error }` RPC response; RpcGateway.approvePermission/
  denyPermission now check for that and throw a typed
  PermissionRequestNotFoundError, which the hub route turns into a 409
  instead of silently returning { ok: true }.

Fixes #1735

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* fix(cli,hub): match the specific not-found message instead of any RPC error

approvePermission/denyPermission treated any error on the Permission RPC
method as "request not found", reasoning only in a code comment that
nothing else on that path currently throws. Share the exact message via a
new PERMISSION_REQUEST_NOT_FOUND_MESSAGE constant in shared/rpcMethods.ts,
imported by both sides, so a future unrelated throw in handlePermissionResponse
surfaces as itself instead of being relabeled as a stale request.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

---------

Co-authored-by: Ian Stevenson <is@lockhouse.systems>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-15 09:01:05 +08:00
wushenghua b59c0608d4 feat(hub,web,cli): add Agent Team (sessions-as-teammates)
Hub
- independent teams.db (own user_version ladder); feature gate default OFF,
  so a disabled hub never creates the file and team routes simply do not exist
- /api/teams: list/detail/by-session/memory/messages/spawn/tasks,
  human message channel, team rename/archive/delete
- team runtime: spawn members via the existing runner RPC (worktree/yolo
  options), peer message routing (mention/dm push, broadcast pull-only),
  budgets (max members, message rate, reply chain depth), member-offline
  announcements that wake the lead or escalate to the human
- team-attention notifications: in-app toast when visible, web push otherwise
- team memory dir {HAPI_HOME}/teams/<id>/ with charter.md template; read-only
  browse API with path-traversal guards

CLI
- MCP tools: team_status / team_read / team_send / spawn_peer (claude, codex
  bridge and ACP flavors); spawn_peer stays behind manual approval
- HAPI_TEAM_* env for spawned members; system-prompt block for Claude/Codex;
  assignment brief carries team rules for flavors without prompt injection
- RUNNER_CAPABILITIES.agentTeam advertised to the hub

Web
- team group chat route with folded peer threads, key/task filters,
  human composer, member/decision/system message cards
- side panel: live member status, interactive task board (create/status/
  assignee), team memory browser, budget
- team create / add-member / settings dialogs; sidebar team section;
  hub settings toggle for the feature (restart to apply)
- SSE team-updated invalidation

Zero impact when disabled: no teams.db, no routes, no tool or prompt changes.
2026-09-15 07:16:57 +08:00
weishu 6d44996a59 Release version 0.30.6 2026-09-14 15:05:49 +08:00
weishu b7e892b99e fix(release): sync iOS and Android versions 2026-09-14 09:48:19 +08:00
weishu 40b6ac462c fix: align directory browsing and iOS session creation 2026-09-14 08:44:02 +08:00
weishu 93e9c5ac65 fix: restore Codex titles and iOS collaboration controls 2026-09-14 08:44:02 +08:00
weishu 8cefb0f04c Release version 0.30.4 2026-09-13 21:22:11 +08:00
weishu 0a9f15a9a7 fix: align request_user_input other answers across clients 2026-09-13 19:48:14 +08:00
wushenghua d1ccd5f6db fix: record usage from OpenCode native responses 2026-09-13 16:43:15 +08:00
wushenghua db63079cd2 fix: preserve peer queue local ids 2026-09-13 16:36:14 +08:00
wushenghua 22e6fecdc0 fix: finish v0.30.3 local feature integration 2026-09-13 16:34:34 +08:00
wushenghua cb0dbd7d22 chore: rebase local deployment features onto hapi v0.30.3 2026-09-13 15:55:04 +08:00
weishu 95b06bd502 Release version 0.30.3 2026-09-13 13:04:33 +08:00
weishu 917baf1201 fix(codex): sync shared plan proposals and web actions 2026-09-13 12:09:39 +08:00