Commit Graph
644 Commits
Author SHA1 Message Date
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
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
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
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 0239edf38e Release version 0.30.7 2026-09-15 09:07:47 +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
weishu 9f54e12c4f docs: streamline agent guidance and scope verification 2026-09-12 22:19:32 +08:00
weishu 1de9613df6 docs: align documentation with current implementation 2026-09-12 22:19:32 +08:00
weishu dd9cfdcee5 Release version 0.30.2 2026-09-12 19:44:56 +08:00
weishu d1f4972079 fix(codex): restore shared sessions after heartbeat and archive 2026-09-12 18:29:57 +08:00
weishu 2c738b6730 Release version 0.30.1 2026-09-12 18:01:49 +08:00
weishu f48b14a312 test(cli): render agent picker output in CI 2026-09-12 16:40:20 +08:00
weishu 18c454fb87 fix(cli): remove Codex startup warning and banner 2026-09-12 14:56:13 +08:00
weishu d18c01b4b6 revert(codex): remove Luna Reserve fallback (#1780)
Revert 8357da0a9d and its later shared-runtime integration.

Remove automatic model fallback, account usage polling, and the related UI, protocol fields, tests, and documentation without adding replacement quota handling.

Keep generic model pagination and method probing required by the current shared-session architecture. Cover idle sessions staying online without usage polling or agent-state churn.
2026-09-12 14:36:45 +08:00
weishu c75cf8154f Release version 0.30.0 2026-09-12 13:38:58 +08:00
weishu fc2fdfc25b feat(cli): add agent picker and remove Claude default
Separate top-level help and version flags from agent arguments. Require explicit agents in scripts and preserve command argument boundaries.

Validation: bun typecheck, bun run test, targeted runner integration tests, and PTY/source/compiled argv smoke checks.
2026-09-12 12:03:59 +08:00
weishu 0c4abcb3d1 feat(codex): share sessions across terminal and web
Use one native app-server for terminal, Web and phone clients while retaining the existing CLI and Runner lifecycle.

Synchronize native queues, permissions, question history and steering state; preserve explicit permission precedence and per-turn usage models. Resume inactive clear commands through Runner and reject independent child cold resumes.

Add shared-runtime regression tests, generated protocol fixtures and lifecycle documentation.
2026-09-12 10:59:17 +08:00
25af3f8e13 fix(cli): give Windows Codex MCP shim-spawn test a 20s budget (#1824)
Vitest's 5s default flakes under GHA Defender/cold-start on the only
unit test that real-spawns on Windows (#1823). Keep the global unit
default unchanged.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-11 15:56:56 +01:00
weishu a729456682 fix(claude): answer local permission prompts from web
Bridge main-session PermissionRequest hooks without suppressing the native
terminal dialog. Reconcile replies against native results and clean up on
timeout, cancellation, mode switches, and session changes.

Keep reply IDs distinct from native tool IDs across web and native clients;
add protocol fixtures and regression tests.

Refs #1796
2026-09-11 18:54:47 +08:00
Junmo KimandGitHub fd2822bab5 fix(agy): allow switching existing sessions to newly available models (#1814)
* fix(agy): say what the model picker is actually waiting on

The spinner in the New Session AGY picker read "Checking Antigravity
authentication…", but nothing at that point checks authentication — the
machine is running `agy models`, and the sign-in prompt is a separate
branch below it, shown only when agy reports the failure.

Name the wait after the work: "Fetching available models…", the same
words agy prints while it fetches.

* refactor(agy): describe a probe by its outcome, not by its response

The probe function returned a finished `AgyModelsResponse`, so "agy could
not be reached" and "agy listed no models" both arrived as a successful
response carrying the hardcoded mirror, and the caller could no longer
tell which had happened. Every policy decision about that answer has to
live inside the probe as a result.

Hand back what the probe observed — a live catalog, an auth failure, or
nothing usable — and let the caller turn it into a response. Same
behaviour: the mirror still stands in for both failure modes, and the
60s cache still holds whatever came out.

* fix(agy): serve the model catalog stale-while-revalidate

The `agy models` probe is a whole agy invocation — around 3s on a good
day, 15s when it times out — and the 60s window meant the New Session
picker paid that again a minute after the last look.

Keep the last listing agy actually returned and answer from it: fresh for
ten minutes, then still answered while a probe refreshes behind it, until
the entry is a day old and stops standing in for the machine at all. A
probe that times out or loses auth leaves that entry alone, so a blip no
longer empties a working picker, and the hardcoded mirror is no longer
recorded as if the machine had reported it.

Three things fall out of that and are handled here. A machine whose
sign-in has actually gone bad would otherwise look healthy for a day, so
an auth failure rides along with the catalog it can still serve — and,
because nothing else would re-probe a catalog that is still fresh, a
warning riding on the answer is itself a reason to look again. A failed
probe is not repeated on the very next request either, or a machine where
agy hangs would spawn it once per poll.

An explicit refresh always costs a probe, and never rides one that was
already running when it was asked for.

* fix(agy): let Retry force a fresh model catalog probe

With the catalog held for ten minutes, Retry would otherwise hand back the
answer it was pressed to replace, so the intent travels to the machine:
`?refresh=true` on the machine route, an optional RPC param, and a
one-shot flag on the query so ordinary mount and focus refetches stay
cheap. Every hop is optional, so a hub and a runner on different versions
still talk — the older side ignores it and answers from its cache.

Retry also has to be reachable, and honest, in the state that needs it.
The machine now answers with both a usable catalog and the sign-in failure
behind it, so the picker keeps the list and says why it may be out of
date, with the button right there rather than only once there is nothing
left to show. Pressing it runs agy, which can take tens of seconds, so the
button says so while it does.

The client contract covers both: `agy-models` is the one catalog route
that can carry an `error` on a successful response, and the one that
takes a refresh parameter.

* fix(agy): use the machine catalog in the in-session model picker

New Session already asks the machine what `agy models` lists, but a
session that is already open offered the built-in list in
`shared/src/models.ts`. That list is a hand-maintained mirror, so a model
agy started offering after the last release could be picked for a new
session and not for the one already running.

Point the composer at the same machine catalog. The mirror stays as the
fallback for the moment before the machine answers, and a model the
session is already on is kept selectable — and readable, when it is one
of the known presets — even after the catalog moves on without it.

* fix(agy): announce a model catalog re-check that changed the answer

Serving the last known catalog answers the picker instantly, but a picker
that was already open kept showing that answer until the user closed and
reopened it — the machine had no way to say it had found something newer.

Say it on the stream that already carries machine changes. The machine
daemon — the only process that answers `<machineId>:listAgyModels` —
emits it, and the hub forwards it as `machine-agy-models-updated` with
nothing but the machineId. Namespace resolution, per-machine delivery and
reconnect replay all come from the existing path.

What counts as a change is what the route would answer, not what sits in
the cache. That distinction carries the cases: a sign-in that lapsed or
came back changes no models yet changes what the user is told; a machine
whose agy was signed out has been answering from the hardcoded mirror, and
its first real listing is the largest change there is, for every client
except the one awaiting it.

* fix(agy): re-read the announced machine's model catalog

On `machine-agy-models-updated`, cancel and refetch that one machine's
catalog query — the app's global connection is always subscribed, so an
open picker redraws wherever it is.

Cancelling first is what makes it correct rather than merely likely.
query-core cancels an in-flight fetch only when the query already holds
data, so a picker opening for the first time would otherwise join the
request already on its way and settle on the listing the announcement
replaced. The refetch is answered from the machine's cache, so it starts
no probe and cannot bounce another announcement back.

A reconnect the hub could not replay takes the resync path, which clears
the agy catalogs the same way — that path has no announcement to fall back
on, so it is the one that can least afford to join a stale request.

* fix(agy): keep a model the user picked when the catalog moves under them

The catalog can now change while the New Session form is open, and the
form dropped any selection the machine no longer advertised — including
one the user had just made.

Keep that one, and list it as no longer listed so the form does not imply
agy is still offering it. A model restored from a draft or a saved
preference is still dropped: it may never have been runnable here.

* fix(agy): announce uncached authentication changes
2026-09-11 14:19:37 +08:00
e2518b6fba fix(codex): emit ready notifications after local turn completion (#1802)
Co-authored-by: Alireza Ghassemi <ravenblackdusk@gmail.com>
2026-09-09 21:14:08 +08:00
weishu de93d7b977 Release version 0.29.1 2026-09-09 09:44:45 +08:00
AnanovoandGitHub 2b402d24d9 fix(web): show Codex round usage metadata (#1685)
* fix(web): show Codex round usage metadata

* fix(web): skip imported Codex round duration
2026-09-09 09:32:43 +08:00
Junmo KimandGitHub 7031d60eb4 fix(opencode): expose model-specific reasoning effort options (#1716)
* fix(cli): discover opencode thought_level via set_config_option on model switch

* fix(cli): apply and refresh opencode model switches so thought_level stays discoverable

* fix(web): track opencode effort options across model switches

* feat(cli,hub,web): dynamic opencode effort options in new-session form

* test(cli): avoid platform-specific process event narrowing

* fix(opencode): address variant discovery review findings

* fix(opencode): synchronize effort options with model targets

* fix(opencode): roll back rejected model targets

* fix(cli): guard opencode variant probe workspace paths

* fix(web): clear stale opencode effort on model switch

* fix(cli): clear stale opencode effort metadata

* fix(web): reset stale effort options on model switch

* fix(web): reset opencode effort poll budget

* test(web): enforce opencode effort poll budget
2026-09-09 09:31:30 +08:00