Commit Graph
898 Commits
Author SHA1 Message Date
362b1fe47e feat(teams,dsh): overview dashboard, lead task auth, and DSH launch tokens
- teams: cross-team overview read model + route/query/page; enforce member
  target visibility and restrict member task creation to the Lead
- dsh: accept 0.2.x launch tokens from the printed URL, HAPI_DSH_TOKEN, or
  HAPI_DSH_TOKEN_FILE; bundled wrapper captures the printed token securely;
  per-session agent availability and remote event/auth handling
- usage: support the 1d range and per-session totals in summaries
- cli/web: team prompt/client plumbing, availability gating, locale and
  route wiring for the overview page

Co-Authored-By: Claude Code <noreply@anthropic.com>
Co-Authored-By: HAPI <noreply@hapi.run>
2026-11-05 02:22:50 +08:00
bc6b1577fa merge: hapi upstream v0.34.1
Merge release tag v0.34.1 into the local deployment fork. Preserve local
features (Agent Team, Studio, session shares, skins, DSH Web runtime,
attachment cache, deploy tooling) while adopting upstream session-list,
composer, steering, model-catalog, native-client, and migration changes.

Regenerate the local chat_attachments migration as v31→v32 behind the
upstream v28-v31 ladder; keep DSH on the Web runtime instead of the ACP
transport added upstream.

Co-Authored-By: Claude Code <noreply@anthropic.com>
Co-Authored-By: HAPI <noreply@hapi.run>
2026-11-05 00:51:11 +08:00
KiyoshiandGitHub 49c22d73a1 feat(web): group and collapse inactive session mentions (#2271)
* feat(web): filter session mentions to active sessions

* feat(web): group and collapse inactive session mentions
2026-11-04 15:58:01 +08:00
7b20218c40 feat(web): keep unread sessions visible and drop the date and unread filters
With sort and filter in place, two toolbar filters no longer earned
their space. The date range filtered on updatedAt while the list orders
by the user's last message, and sort plus search already cover finding
older sessions. The unread toggle was a transient lens that compensated
for unread rows hiding in auto-collapsed projects.

- Remove the date range picker from the session list search and the
  share-target picker.
- Remove the Unread only toggle. Instead, a collapsed project still
  lists its unread sessions under its header, and the preview limit
  never hides an unread row.
- Reorder the toolbar by purpose: list controls next to search, then
  New, then browse and settings. Mark all as read only appears with
  unread sessions, so it moves last and no longer shifts the other
  controls when it comes and goes.

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

Co-Authored-By: HAPI <noreply@hapi.run>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 15:53:29 +08:00
db7ace3dc3 feat(web): sort and filter the session list (#2263)
* feat(web): sort and filter the session list

The session list had one fixed order, the user's last message, and
active and archived sessions were interleaved inside each project. With
in-progress pinning off by default, a connected session could sit below
a stack of archived ones, and archived sessions could not be listed on
their own.

A sort and filter button in the list toolbar now sets:
- Sort by: last message (the existing default), date created, or recent
  activity including agent output.
- Active first (on by default): connected sessions lead each project,
  and projects with one lead the list. Manual pins still come first.
- Show: all sessions, active only, or archived only. The open session
  stays visible, and archived-only keeps projects expanded instead of
  auto-collapsing them.

Choices persist and stay in sync across consumers and tabs. The Display
setting "Show active sessions only" is now the Active status, and an
existing true value carries over.

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

Co-Authored-By: HAPI <noreply@hapi.run>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(web): keep the default project order while searching

Relevance ranking keeps the incoming project order for equal scores, so
passing the list view into grouping let Active first and the chosen sort
reorder equally relevant search results. Group with the default
last-message order while a text query is active, as before.

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

Co-Authored-By: HAPI <noreply@hapi.run>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(web): drop the Recent activity session sort

The Hub advances updatedAt only for human messages; agent output moves
an in-memory progress clock that clients never see. Recent activity
therefore ordered like Last message. Ordering by user input is the
agreed contract across clients (see #1512), so remove the option rather
than add a new activity clock. A stored "activity" value falls back to
the default.

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

Co-Authored-By: HAPI <noreply@hapi.run>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: HAPI <noreply@hapi.run>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 15:07:13 +08:00
631e38faaf feat(web): archive a session from its row on hover (#2264)
* feat(web): archive a session from its row on hover

Archiving a session took a right-click or long-press, a menu item and a
confirmation. On pointer devices, hovering a connected session's row
now swaps its time/progress cluster for an archive button, the way chat
apps tidy their history. Keyboard focus reveals it too; touch devices
keep the long-press menu.

Archiving stops the session's agent, so the button only skips the
confirmation for an idle session: it archives at once and shows a toast
with Reopen. A session that is replying, waiting on approval or running
background tasks still opens the existing confirmation. Failures are
reported in a toast.

Toasts gain an optional inline action for this, and the archive icon
moves into the shared icon set.

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

Co-Authored-By: HAPI <noreply@hapi.run>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(web): keep the hover archive spinner until archiving settles

The hub can report a session inactive while the archive request is still
confirming cleanup. The row's archive button rendered only for active
sessions, so its spinner vanished early and a slow or failing cleanup
looked finished. Keep the button, and the hidden time cluster, while
the request is pending.

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

Co-Authored-By: HAPI <noreply@hapi.run>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(web): keep archive progress across session list regrouping

The hover archive spinner lived in row state. With in-progress pinning
on, a session that turns inactive mid-archive moves out of the Active
section and remounts, which reset that state and hid the spinner while
cleanup was still being confirmed. Read the pending state from the
shared archive mutation key instead, as the session header and delete
dialog already do.

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

Co-Authored-By: HAPI <noreply@hapi.run>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: HAPI <noreply@hapi.run>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 15:06:56 +08:00
KiyoshiandGitHub 306d2821f7 feat(web): remember and toggle Codex fast mode in the composer (#2276)
* feat(web): remember Codex fast mode and add it to model picker

* feat(web): add Codex fast toggle to composer toolbar
2026-11-04 14:48:52 +08:00
Junmo KimandGitHub c4847b241e feat(opencode): support mid-turn steering (#1981)
* feat(opencode): support mid-turn steering

Register the per-message Queue/Steer RPC on the OpenCode remote launcher so
a waiting-queue row can be delivered into the active turn as a concurrent
ACP session/prompt (soft inject, no cancel, no mode/model/effort mutation),
reusing the existing reservation/delivery-state protocol shared with Codex
and Cursor ACP.

The steer gate is closed synchronously by every cancelling or terminal path
(normal-prompt finally, abort, switch/exit, teardown, stderr stall cancel),
each steer operation is tracked from the moment its reservation is taken
until completion plus all restore/consumed bookkeeping settles, and the
foreground prompt drains those operations — abort-aware through the original
turn signal — before ready, thinking=false or the next batch. steerability is
published through the existing agentState.steeringActive flag so the web's
queued Steer button is reachable.

Gate the feature on the shared steering flavors (now including opencode) and
update the hub's supported-flavor wording.

* fix(opencode): acknowledge native steering acceptance

* fix(opencode): preserve steering delivery outcomes

* docs(opencode): remove obsolete abort-race comment

* fix(opencode): preserve steering resolution ownership

* fix(opencode): isolate ambiguous steering attempts

* fix(opencode): compare resolved steering settings

* fix(opencode): reconcile steering receipts after dispatch

* fix(opencode): distinguish pending steering delivery outcomes

* fix(opencode): restore receipts after successful native responses

Track native input requests separately from detached steer operations. Resume receipt correlation after inputs quarantined by cancellation return their own successful responses, while keeping uncertain writes on completion-based confirmation.

* fix(opencode): bound receipt proof to unambiguous native inputs

* feat(opencode): steer v2 inputs through native inbox

* fix(opencode): preserve v2 restoration and cancellation ownership

* docs(opencode): describe v2 inbox steering confirmation

* fix(steer): expire Claude confirmation with its owning worker
2026-11-03 18:37:53 +08:00
AnanovoandGitHub 82c5c00a38 feat(web,hub): add opt-in search for session message content (#1598)
Adds an opt-in content search over session messages, alongside the
existing metadata search. An FTS5 index covers visible user and assistant
prose; the sidebar gains a scope toggle, and selecting a result opens the
matching message rather than the session tail.

Search scope is a two-state toggle rather than a dropdown: a 48x48 target
matching the clear endcap, distinct glyphs for session details vs message
text, tooltips naming the real scopes, and the active mode kept on the
collapsed chip (tiann/hapi#2081).

Dogfooded on a live 1178-session fleet. The scope toggle and reading
forward from a search result were both click-tested on that install —
the latter walking six bounded pages through a 1,507-message session and
stopping correctly at the session head.

The per-commit history does not survive this squash; the review trail —
including which commit closed which finding — is on the PR threads.

Fixes #1554
Fixes #2081
2026-11-02 15:20:45 +00:00
AnanovoandGitHub 3fc15e92cf fix(dsh): resume DSH ACP sessions after HAPI restart (#1926)
* fix(dsh): resume DSH ACP sessions after HAPI restart

* fix(dsh): register active session handoff

* fix(dsh): preserve native id in summaries

* fix(dsh): keep native resume on recorded machine

* test: refresh CI checks
2026-11-02 18:24:08 +08:00
AnanovoandGitHub 3208199f99 fix(files): optimize exact path and directory search (#1778)
* fix(files): optimize exact path and directory search

* fix(web): preserve incremental file mention search

* fix(cli): contain recursive file search within workspace

* perf(cli): reuse canonical workspace path

* fix(cli): classify archived workspace file stats
2026-11-02 18:08:32 +08:00
AnanovoandGitHub 55f0f7b11d fix(web): dismiss fullscreen image previews on browser Back (#2191) 2026-11-02 18:07:10 +08:00
AnanovoandGitHub d4039081cd fix(web): collapse expanded composer immediately on submit (#1728) 2026-11-02 18:05:01 +08:00
AnanovoandGitHub 50b5e76e70 fix(web): allow retrying failed attachment uploads (#1722)
* fix(web): allow retrying failed attachment uploads

* fix(web): address attachment retry review feedback

* fix(web): respect attachment adapter retryability

* fix(web): preserve attachment order on retry

* fix(web): finish attachment retry review fixes

* chore: sync upstream main at a259ad3

* fix(web): preserve order across overlapping attachment retries

* fix(web): cancel and guard overlapping attachment retries
2026-11-02 18:04:45 +08:00
AnanovoandGitHub fe25c24d4e feat(web): add mobile attachment source picker (#1713)
* feat(web): add mobile attachment source picker

* fix(web): gate mobile attachment picker by adapter

* chore(web): trigger PR checks

* fix(web): start mobile attachment uploads concurrently
2026-11-02 18:04:26 +08:00
480a476e9b feat(web): give every harness the same effort control (#2261)
Effort looked and behaved differently per harness. Codex and OpenCode
had no toolbar button and called it "Reasoning effort", Claude called the
unset choice "Auto" while others said "Default", Chinese used both 思考强度
and 推理强度, and the status bar and header showed effort only for
Codex, OpenCode and Pi.

- Codex/OpenCode get the same toolbar effort button as Claude; it opens
  the same anchored Effort section.
- Every effort picker is titled Effort (推理强度), and the unset choice is
  Default everywhere. An unset toolbar button reads "Default effort".
- The status bar, header, shared-turn metadata and Codex agent tool cards
  use one "effort <level>" label. Claude and Grok now show their effort,
  with "default" when unset; Pi and AGY show it only when set, since
  their models may not take one.

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

Co-authored-by: HAPI <noreply@hapi.run>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 18:01:39 +08:00
7f5d6b17f3 feat(peer): spawn-peer remit + rename-proof Parent UUID stamps (#1922)
* feat(cli): spawn-peer with required remit and MCP spawn_peer

Machine spawn creates an empty composer; stuffing message into that
JSON is stripped. Add hapi spawn-peer and MCP spawn_peer that spawn,
rename, deliver via the ping-peer path, and fail closed unless the
remit is on the new session. Reject message/prompt/text on machine
spawn with 400. Do not auto-approve spawn_peer in default/read-only.

Fixes #1509

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

* fix(cli): reject overlong spawn-peer names before spawn

Hub rename max is 255; continuing past PATCH 400 left a live session under a different title. MCP/steer copy now says the hub 400s a spawn-body remit instead of stripping it.

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

* fix(cli): wait for fresh spawn-peer child instead of resuming it

Machine spawn can return while the hub row is still inactive. ping-peer then POSTed /resume, which can launch a second agent because the original spawn has no existingSessionId for runner dedupe.

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

* fix(cli): reject flavor-illegal permissionMode before spawn-peer

Codex + bypassPermissions (and Pi + any mode) used to reach the runner and fail after a child existed, or silently drop the mode. Check isPermissionModeAllowedForFlavor when both agent and mode are explicit.

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

* fix(cli): treat omitted spawn-peer agent as Claude for permissionMode

Hub and runner default agent to claude when spawn omits it. Validate permissionMode against that effective flavor so yolo/read-only fail closed before a child starts.

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

* fix(cli): resolve spawn-peer dir locally and archive failed children

Relative --dir values were forwarded unchanged, so the runner resolved them against daemon cwd. Remit delivery failures also left the new session running, so a retry spawned a duplicate. Resolve in the caller and best-effort archive the child before rethrowing.

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

* fix(cli): verify remit before archiving a failed spawn-peer child

A timed-out ping-peer POST can still land the remit. Check the transcript before archive, and only say the child was archived when the hub returns ok.

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

* fix(cli): swallow spawn-peer transcript GET errors and skip unapplied names

A thrown messages GET after spawn skipped archive and leaked a live child. Treat transport errors as a miss so the verify loop can retry, then clean up. Also do not return a requested name the hub never applied.

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

* feat(spawn-peer): hub peerSpawnDefaults + model/effort spawn surface

Add hub settings peerSpawnDefaults (agent, permissionMode, per-flavor models)
with stock claude/yolo/sonnet fallbacks. spawnPeer resolves explicit args →
hub settings → stock, maps yolo to flavor equivalents, and forwards model/effort
into machine spawn. Settings UI under General → Agents; MCP/CLI optional overrides.

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

* test(hub): use cursor-valid permission mode in peer spawn defaults test

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

* fix(cli): teach spawn_peer that omit permissionMode means yolo

MCP copy said "pass permissionMode only when the operator asked", so agents
omitted it and got hub/stock whatever was stored — often approval-gated
default. Clarify omit = yolo via peerSpawnDefaults/stock; pass only to tighten.

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

* fix(hub,web): apply peerSpawnDefaults to machine spawn and New Session

POST /api/machines/:id/spawn now resolves omitted agent/permissionMode/model
from hub Settings, and New Session seeds from the same peerSpawnDefaults so
UI create and peer spawn share one authoritative default (stock yolo).

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

* fix(web): NewSession hub seed respects sticky prefs + QueryClient in tests

Sticky localStorage last-used agent wins over hub seed; machine spawn still
applies peerSpawnDefaults server-side. Tests provide QueryClientProvider.

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

* test(web): mock CREATABLE_AGENT_FLAVORS for settings general peerSpawn UI

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

* test(web): stabilize NewSession tests after hub peerSpawnDefaults seed

Wrap remaining mounts in QueryClientProvider, pre-seed hub settings so a
late useQuery resolve cannot wipe in-test model picks, and use neutral
stock permission defaults in the fixture.

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

* fix(spawn-peer): address tip Codex Majors for peerSpawnDefaults

Preserve explicit yolo booleans on machine spawn, fail closed when hub
defaults cannot load, validate permission modes against the resolved
agent, remap auto-approval across flavors, and stop late hub settings
from wiping New Session edits. Reject retired agents in spawn defaults
and restore the CLI executable build docs.

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

* fix(spawn-peer): address follow-up tip Codex Majors

Stop absent Yolo localStorage from beating hub defaults, track New Session
edits before late hub seed, reject incompatible machine-spawn permissions,
resolve MCP spawn_peer paths against the session working directory, and
hide unsupported Codex safe-yolo from Settings spawn defaults.

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

* fix(spawn-peer): clear tip-2 CI and follow-up Codex Majors

Omit permissionMode for Pi/DSH, preserve explicit Yolo-off across delayed
hub seed, fix settings test mocks for launch permission helpers, and
restore unrelated CLI README lifecycle/ACP docs.

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

* fix(spawn-peer): tip-3 Majors for seed edits and launchable modes

Skip hub agent seed after permission edits, resolve inherited modes via
the launch catalog (AGY request-review fallback), preserve hub Cursor
models through preference restore, and restore Codex auth README nuance.

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

* fix(web): tip Minors for Cursor/Grok hub permission seeding

Give Cursor the native permission select so Plan/Ask survive machine
spawn, and re-apply Grok hub permissions after agent-init resets.

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

* fix(spawn-peer): tip Minors for Cursor select and hub seed remap

Map hub permission through resolvePermissionModeForFlavor, keep native
Default when agent is explicit without a model, and assert Cursor Plan/Ask Mode labels.

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

* fix(web): tip Minors for delayed hub seed and Default model

Apply hub permission/model to unset fields even with a sticky agent,
preserve explicitly saved auto models, and seed OpenCode/AGY selectors
from hub defaults.

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

* fix(spawn-peer): tip Minors for model edit sticky and clear override

Mark OpenCode/AGY model picks as edited so delayed hub seed cannot
overwrite them, and treat empty hub model values as clear sentinels.

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

* fix(spawn-peer): tip Majors for models patch typing and remit page scan

Type the models patch as NonNullable and paginate transcript verification
oldest-ward so a busy child is not archived when the remit leaves page 1.

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

* fix(web): tip Minor — keep agent after explicit model pick

Do not apply a late hub default agent when the operator already chose a
model; otherwise preference restore drops that selection.

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

* fix(spawn-peer): tip Cursor YOLO migrate and Codex effort map

Bridge Cursor's saved YOLO toggle (incl. false) into native Default over
hub yolo, and send Codex/OpenCode --effort as modelReasoningEffort.

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

* fix(web): tip Majors — scoped YOLO bridge and catalog fallback

Keep unsupported saved permission modes at Default instead of hub yolo,
and apply delayed YOLO migration only when the sticky bridge agent matches.

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

* fix(cli): drop rebase conflict markers in startHappyServer

Both sessionDisplayRename and spawnPeer imports are required after
rebasing onto upstream rename work; markers had slipped into tip.

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

* fix(web): wrap Kimi NewSession tests in QueryClientProvider

Hub settings useQuery requires a provider; upstream Kimi catalog tests
rendered bare and broke after the spawn-peer rebase onto main.

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

* feat(peer): rename-proof Parent UUID stamp on spawn remits

Orchestrated spawn_peer / spawn-peer remits now auto-stamp a durable
[title](/sessions/<uuid>) Parent chip (fail-closed in-session). MCP copy
forbids inventing titles; UUID stays identity after parent rename.

Refs heavygee/hapi#175

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

* style(peer): plain hyphen in Parent-stamp warning and test comment

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

* fix(peer): reject blank remit before Parent stamp

Codex review on #1922: stamping first turned empty remits non-empty and
bypassed the idle-session guard. Also only treat ## Parent blocks as
already-present so bare /sessions citations still get the chip.

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

* fix(peer): gate New Session Create on hub defaults; require Parent chip

Codex tip review on #1922: block Create while getHubSettings is pending so
stock mount defaults cannot bypass restrictive hub peerSpawnDefaults; skip
Parent stamp only when ## Parent already has a markdown session chip; clarify
codex resume takes a native thread id.

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

* fix(peer): resolve MCP spawn cwd; Agents settings section

Codex Major on #1922: spawn_peer relative directories now prefer the
launcher cwd (Codex --cd), then skillLookup, then session metadata.path.
Move peerSpawnDefaults UI into the unused Agents settings section.

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

* fix(peer): pass session cwd to MCP spawn_peer; Agents settings section

Codex tip review on #1922: resolve relative spawn_peer directories from the
launcher working directory (Codex --cd), skillLookup, then metadata.path;
move peerSpawnDefaults UI into the Agents settings section.

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

* fix(peer): skip archive on unread transcript; validate startingMode after hub agent

Codex tip review: do not archive a child when remit verification cannot read
the transcript; run AGY/DSH remote-only startingMode checks against the
resolved hub agent; update MCP bridge test expectations for workingDirectory.

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

* fix(peer): treat exhausted remit pagination as unverifiable

Hitting the messages page cap with hasMore still true must not count as an
observed-empty transcript — that would archive a child whose remit may still
exist on older pages.

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

* docs(cli): list HAPI_API_URL as optional with localhost default

Codex tip review: the README had moved HAPI_API_URL under Required even
though configuration defaults it to http://localhost:3006 (and settings can
supply a URL). Also drop trailing whitespace flagged by git diff --check.

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

* fix(peer): re-verify empty transcript before archiving failed spawn

An earlier observed-absent page plus later unread GETs must not archive a
child whose remit may have landed after the first empty read.

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

* fix(peer): never auto-archive on spawnPeer failure path

Timed-out remit POSTs and empty-read/archive TOCTOU races can still land
work on the child. Leave the session running with its id for inspection
instead of terminating an in-flight peer.

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

* fix(peer): classify empty_session from final remit verify

An earlier empty transcript read must not stick as empty_session once
later message GETs become unavailable — report verify_failed instead.

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

* fix(peer): recognize escaped brackets in Parent chip labels

Titles with ] were escaped in the chip label but the idempotency regex
stopped at the first raw bracket and re-stamped a second Parent block.

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

* fix(peer): avoid Parent chip regex backtracking on backslashes

Exclude \\ from the single-char label alternative so escaped pairs have
one parse path — long nonmatching backslash runs no longer explode.

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

* feat(cli): MCP/CLI spawn_peer --machine for cross-host remits

Wire optional machine (UUID or hostname via GET /api/machines) through
MCP spawn_peer and hapi spawn-peer into existing spawnPeer({ machineId }).
Skip local directory existence checks for cross-host targets to match the
estate wrapper; keep fail-closed remit verify.

Fixes tiann/hapi#1931

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

* fix(peer): reject ambiguous spawn --machine label matches

Tip bot Major on #1922: hostname/displayName selectors that match more
than one online runner now fail closed with ambiguous + UUID guidance.

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

* fix(peer): preserve cross-OS absolute dirs for spawn --machine

path.resolve on the caller OS was rewriting foreign absolute roots
(C:\… on Linux, /srv/… on Windows) before the hub spawn POST. Detect
POSIX or Windows absolute paths and only resolve relative directories.

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

* fix(peer): resolve spawn dirs after local vs remote is known

Defer directory resolution until the target machine is selected. Remote
spawns still preserve foreign absolute roots; local spawns use native
path.resolve so Windows drive-less paths like \repo bind to the caller
drive.

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

* fix(web): apply delayed DSH model defaults without replacing edits

* fix(web): preserve hub permission defaults across form remounts

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: weishu <twsxtd@gmail.com>
2026-11-02 13:17:22 +08:00
AnanovoandGitHub b8a13b044a feat(web): show unavailable Agents on demand (#1798)
* feat(web): add unavailable Agent visibility toggle

* feat(web): show unavailable Agents on demand

* chore: retrigger PR checks

* fix(web): keep unavailable Agent disclosure order
2026-11-01 20:08:37 +08:00
weishu 832c5922f2 feat(web): align session rows and connection banners with native apps
- Agent icon spans both lines with a presence badge (pulsing while a turn
  or background work runs); no dimmed rows or row spinners
- Unread is a semibold title plus accent time instead of a dot; pending
  requests are amber hand/bubble symbols, keeping one dot per row
- Reconnecting/syncing banners appear after 2s; reconnecting is neutral,
  amber stays for offline
2026-11-01 16:00:56 +08:00
weishu 6ed83eda3c fix: show unread for running sessions
Running sessions previously suppressed the ordinary unread dot, hiding
answers the user had not seen. Unread is now
manualUnread || contentRevision > readRevision on web, iOS and Android.

Web rows draw the running/background spinner beside the unread dot, and
unread outranks background status. Because session patches arrive before
the message they count, a chat following its visible tail hides its own
ordinary dot until the rendered acknowledgement lands (display-only; web
and iPad split view).
2026-11-01 15:39:46 +08:00
AnanovoandGitHub ecf40d8851 feat(web): fork active and archived sessions from the session list (#2258)
* feat(web): fork active and archived sessions from the session list

* fix(codex): preserve fork titles during history replay

* fix(web): limit list forks to supported Codex sessions
2026-11-01 14:52:42 +08:00
AnanovoandGitHub 46bae9b0f1 fix(codex): fork archived sessions without reopening the source (#2231)
* fix(codex): fork archived sessions without reopening the source

* fix(codex): reconcile interrupted archived fork recovery

* fix(codex): release rejected forks and serialize source startup

* fix(codex): fence unconfirmed resume startup across retries

* test(codex): validate durable resume recovery and serialize archive

* fix(codex): block local resume of pending archived fork sources

* fix(hub): coalesce concurrent resume requests to preserve idempotent startup

* fix(hub): bypass startup coalescing for PTY sessions
2026-11-01 09:55:01 +08:00
Junmo KimandGitHub be41e854e0 feat(codex,opencode): show compaction summaries and token counts (#2249)
* fix(chat): render available compaction facts

* feat(opencode): unify native compaction summary cards

* fix(opencode): report actual compaction application lifecycle

* feat(codex): render native compaction checkpoints and metrics

* fix(chat): render compaction summaries and token facts in native clients

* fix(opencode): preserve transcript priming across read failures

* fix(opencode): recover snapshot-only replies after attachment
2026-11-01 09:54:10 +08:00
9c6639c3c3 feat(hub): title untitled sessions with their first prompt (#2252)
New sessions showed the working directory name until the first turn
finished and a generated or native title arrived. The Hub now records
the first non-command user prompt as metadata.promptTitle, with
whitespace collapsed and capped at 80 characters, and the web shows it
after name and summary but before the path fallback.

The prompt title is Hub-owned and kept outside summary, so the existing
"only while untitled" title sources (native ai-title, change_title, the
remote prompt fallback and automatic suggestions) still apply and
replace it. It is published as a versioned metadata patch, so it does
not race the thinking patch sent while the prompt is accepted.

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

Co-authored-by: HAPI <noreply@hapi.run>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 09:53:35 +08:00
weishu 5ef4304a4a fix: synchronize read state across web and native clients
Track readable content revisions and durable read cursors in the hub. Acknowledge only rendered foreground tails, persist offline read operations, and preserve newer manual unread choices across devices.

Migrate existing history to a read baseline and cover pagination receipts with shared native conformance fixtures.
2026-11-01 09:52:51 +08:00
SSU-WEI HUANGandGitHub 5fcd4a067a fix(agy): separate model and effort selection (#2244)
* fix(agy): separate model families and reasoning effort

* fix(agy): report startup before model discovery
2026-10-30 07:36:13 +08:00
AnanovoandGitHub 75aa3d929a fix(web): preserve completed status summaries during follow-up sends (#2241) 2026-10-30 07:17:44 +08:00
SSU-WEI HUANGandGitHub a7022370d0 fix(web): keep subagent parent when trimming the message window (#2243)
* fix(web): keep subagent parent when trimming the message window (#2242)

Tail mode trims to the newest 400 raw messages. When a long subagent run
fills that window, its Agent tool call falls outside it and the tracer
hides every sidechain row, leaving a single visible message. Keep the
dropped Agent/Task tool call whose sidechain rows survive, and derive
the older-page cursor from the contiguous window so the gap still loads.

* test(web): cover subagent parent pinning across repeated trims
2026-10-30 07:16:10 +08:00
3f8686e623 feat(claude): offer the four current Claude models, drop the 1M presets (#2153)
* fix(claude): drop the obsolete 1M model presets

The opus, sonnet and fable aliases now resolve to models with a native 1M
window (Opus 5.5, Sonnet 5.5, Fable 5.1), so Claude Code documents the
"[1m]" suffix as having no effect on them and its own /model picker no
longer lists 1M rows. Remove Sonnet 1M / Opus 1M / Fable 1M from the
shared, Android and iOS catalogs.

Sessions stored with a "[1m]" value keep working: the composer keeps
the current model selectable, and the context-budget fallback treats any
"[1m]" model and the remaining aliases as 1M, so local-mode sessions on
plain opus/sonnet no longer show a 200k budget.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(cli): seed a 1M turn-1 context window for bare Claude aliases

With the 1M presets gone, a session on plain fable/opus/sonnet must not
seed 200k before its first result: those aliases now resolve to models
with a native 1M window. Seed 1M when the selected model is a preset
alias; an explicit full id still seeds conservatively until its result
arrives.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat(claude): offer the four current Claude models as presets

The Claude presets now mirror the aliases Claude Code's /model lists, in
its order: Opus, Fable, Sonnet, Haiku. Each alias resolves to the newest
model of its family, and all four have a native 1M window, so neither
1M variants nor pinned older versions are offered. Haiku was missing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat(claude): read the effective context window from get_context_usage

The 1M seed for the aliases is wrong for users who disable 1M
(CLAUDE_CODE_DISABLE_1M_CONTEXT) or cap it with autoCompactWindow. In
remote mode, ask Claude via the get_context_usage control request after
each system/init and use its maxTokens, which is what /context shows.
A result's raw contextWindow no longer overwrites that value. Older CLIs
that reject the request keep the existing seed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: weishu <twsxtd@gmail.com>
2026-10-29 22:28:59 +08:00
JeeandGitHub 13fec3e157 fix(web): show session mentions without waiting for remote file search (#2170)
* fix(web): show session mentions without waiting for remote file search

* fix(web): preserve selected files during mention refresh

* test(runner): avoid fixture PID collision with test process
2026-10-29 22:13:38 +08:00
AnanovoandGitHub 0f76485ad9 feat(web): support local Markdown images in conversation messages (#2215)
* fix(web): resolve relative Markdown assets in file previews

* fix(web): harden standalone Markdown asset resolution

* fix(web): preserve Markdown URI and image policies

* fix(web): route Markdown file links through app history

* fix(web): preserve session reference navigation in previews

* feat(web): support local Markdown images in conversation messages

* fix(cli): bound session file reads

* fix(cli): avoid blocking bounded reads on FIFOs

* test(cli): run FIFO regression with Bun

* fix(hub): preserve legacy session file RPC calls

* test(web): update markdown preview fixture context

* fix(cli): reject symlinked session files outside workspace
2026-10-29 22:12:57 +08:00
AnanovoandGitHub f29be07bb9 fix(codex,web): preserve late command result identity and timing (#2239) 2026-10-29 22:12:27 +08:00
AnanovoandGitHub f48a6696cf fix(web): reveal directory tails and support touch scrolling (#2237) 2026-10-29 21:22:31 +08:00
AnanovoandGitHub bb51684339 fix(web): hide reorder handles for single composer attachments (#2235) 2026-10-29 12:49:12 +08:00
weishu 01dabad204 fix(sessions): stabilize ordering by latest user input
Persist and backfill the last user-message timestamp, and use it for deterministic session ordering across web and native clients while preserving manual pins.

Remove the fixed-order setting and guard ordering against transcript echoes, replayed messages, and stale REST/SSE updates. Add regression coverage and regenerate the SSE fixture.
2026-10-29 12:47:52 +08:00
Junmo KimandGitHub ba7494c5b5 fix(web): show Codex cached input percentage in response metadata (#2230) 2026-10-29 08:07:11 +08:00
e0b4aac78c fix(web): do not claim Agents are missing when no machine is connected (#1978)
New Session treated a disabled availability query (empty agents, not
loading) as "this machine has no CLIs." That painted the same string as
a real empty probe, next to "No machines available."

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-28 12:18:57 +08:00
ShinnandGitHub d730996e7b fix(web): dismiss composer settings before Escape aborts a turn (#1957)
* fix(web): dismiss composer settings before Escape aborts a turn

* fix(web): preserve IME Escape in composer settings
2026-10-28 12:18:40 +08:00
AnanovoandGitHub 8f1318ed8e fix(web): allow directory collapse during session search (#1797)
* fix(web): allow directory collapse during session search

* fix(web): preserve directory collapse after filtering
2026-10-28 12:17:59 +08:00
Junmo KimandGitHub 2a94782412 fix(web): make composer session mention pills ellipsize (#1678) 2026-10-28 12:17:41 +08:00
a23d78f416 fix(web): avoid session misclicks during live reordering (#1656)
Co-authored-by: NPUlrk <21106497+NPUlrk@users.noreply.github.com>
2026-10-28 12:17:28 +08:00
Junmo KimandGitHub a4a5779829 fix: render opencode edit/write/read tool calls through the shared views (#1644)
* feat(cli): add diff-shape tool input canonicalization helper

* fix(cli): canonicalize ACP diff-shaped tool inputs for the Edit/Write views

* fix(opencode): normalize local-mode edit/write tool parts to the diff view shapes

* fix(opencode): preserve replaceAll and fix id-less hook pairing

- canonicalizeDiffToolInput now carries OpenCode's optional replaceAll through
  to the canonical Edit input as replace_all, so persisted history keeps
  whether an edit replaced one occurrence or all.
- opencodeLocalLauncher computes the execute-hook pairing signature from the
  raw name/input BEFORE canonicalizing, so an id-less 'before' with empty or
  partial args shares a queue key with the full 'after'.

* fix(opencode): preserve snake_case replace_all in edit canonicalization

canonicalizeDiffToolInput accepts already-canonical snake_case edit inputs
(file_path/old_string/new_string), but only read camelCase replaceAll. Read
replace_all too so the execution argument survives every call site.

* fix(opencode): upgrade partial local edit/write calls when full args arrive

A partial native before ({filePath}) was emitted as-is and recorded as sent,
so the later canonical full input was deduped and the web Edit/Write view never
received the complete arguments. Track the emitted input per callId and, when
a partial native edit/write call is superseded by a canonical full input, emit
a replacement tool-call (same callId) before the result. Applies to both the
message.part.updated and tool.execute.* local event paths.

* fix(opencode): release completed tool inputs from upgrade tracking

emittedToolInputs retained the full emitted Write content and Edit old/new
strings for the whole launcher lifetime. Once a call is completed
(sentToolResults), later upgrades are already blocked, so drop the stored
payload in both result paths. Add a lifecycle test that a stale part for a
completed callId neither re-emits nor emits a duplicate result.

* style(opencode): restore 4-space indentation on local launcher tracking decls

The per-session tracking declarations (sentTextParts, sentToolCalls,
sentToolResults, emittedToolInputs) were flush-left inside opencodeLocalLauncher,
violating the repo's 4-space indentation rule.

* fix(opencode): gate diff canonicalization on edit/write semantics

canonicalizeDiffToolInput now takes the tool's semantic name/kind and only
normalizes known edit/write aliases (edit, write, write_file, replace, modify,
file_edit). The Edit shape (path + both old/new) is unambiguous on its own, but
Write is just {path, content} — a shape many non-edit MCP tools also accept —
so without the gate those tools were renamed to Write and their extra args
dropped. OpenCode maps both edit and write to kind 'edit', which stays in the
allow-list, so the native-shape cases keep working. ACP callers pass the update
kind (falling back to the derived name); local callers pass the tool name.

* fix(opencode): re-emit canonical edit/write when the input changes

shouldEmit only handled the null->canonical transition, so a shape-complete
Write/Edit that already canonicalizes (e.g. {content: ""}) was suppressed
when the final content arrived, leaving the web diff with stale input. Emit on
first usable input and re-emit whenever the canonical input value changes,
across both message.part.updated and tool.execute.* paths.

* test(opencode): cover changed-input re-emit on the message.part.updated path

The execute-hook path already had a regression for canonical inputs changing
from empty to final content; add the matching case for message.part.updated so
both local emit paths are covered.

* fix(web): render opencode native read output as a file content code block

OpenCode's lowercase read tool fell through to the generic markdown
result view, collapsing the whole file into a soft-wrapped paragraph
and dropping the <path>/<type>/<content> markup. Register the read
alias, unwrap the opencode payload (including the trailing
"(End of file ...)" trailer), parse its 1-indexed line numbering into
the code block gutter, and resolve the camelCase filePath key in the
card title.

* fix(web): only unwrap complete opencode read envelopes

Anchor the envelope match to the full <path>/<type>file/<content>
structure so plain Read results whose file text contains a literal
<content> element are left untouched, capture the body greedily so an
embedded </content> survives, and stop trimming real trailing
whitespace.
2026-10-28 12:17:14 +08:00
AnanovoandGitHub 8d94a745b5 fix(web): prevent session navigation on right-click (#1636) 2026-10-28 12:16:47 +08:00
AnanovoandGitHub 21b0ff30f3 fix(web): add exact PWA notification icon sizes (#1625) 2026-10-28 12:16:25 +08:00
AnanovoandGitHub aa0172e6aa fix(web): align fork and rewind confirmations (#1603) 2026-10-28 12:16:12 +08:00
AnanovoandGitHub 7765fd3378 fix(web): make long file errors expandable (#1422)
* fix(web): make long file errors expandable

* fix(web): count graphemes in expandable errors

* fix(web): reset expanded errors when messages change

* fix(web): reset expansion when error text changes

* fix(web): avoid duplicate file error alerts
2026-10-28 12:15:58 +08:00
AnanovoandGitHub f6bc583471 feat(web): open a session in a separate window (#2192)
* feat(web): open a session in a separate window

* test(web): cover session sidebar restore control

* fix(web): preserve hidden sidebar across session navigation

* fix(web): retain hidden sidebar on session routes
2026-10-28 12:15:22 +08:00
AnanovoandGitHub 009e6d7799 feat(codex): allow historical forks while a turn is running (#2185)
* feat(codex): allow historical forks while a turn is running

* fix(web): fork user-only completed turns during generation
2026-10-28 12:15:11 +08:00
AnanovoandGitHub 577ab7546b feat(sessions): add explicit per-session title locks (#2072)
* feat(sessions): add explicit per-session title locks

* fix(sessions): preserve title locks across rename entry points
2026-10-28 12:14:48 +08:00
Junmo KimandGitHub 328ddb0ee3 feat(cli): show Claude compaction as a summary card with token delta (#1691)
* feat(cli): report claude compact completion with token delta

Replace the plain 'Compaction completed' line with a pi-style
'📦 Compacted (<before> → <after> tokens)' fallback built from the
system/compact_boundary metadata, and prefix the start notice with 📦.
The SDK stream carries no summary body (measured), so the delta line is
the terminal state of the completion event.

* fix(cli): hide local-command tag user messages from chat

Claude Code echoes slash-command bookkeeping back over the SDK as plain
user-role messages wrapped in <local-command-*> tags. Flag them isMeta so
the existing downstream filters drop them instead of leaking raw stdout.

* feat(web): collapse compact summary card by default

Keep the header (title, token delta, timestamp) always visible and hide
the summary body behind a chevron toggle. Toggle state is component-local
and non-persistent.

* feat(cli): promote claude compact summary from the local transcript

* fix(cli): don't double-render the manual /compact boundary as a status line

* fix(cli): detect later-turn /compact and ignore stale transcript summaries

* fix(cli): establish the compact transcript baseline before the result can race it

* fix(cli): narrow the local-command meta filter to the compact stdout echo

* fix(cli): range-read only the transcript tail when polling for the compact summary

* fix(cli): complete the transcript tail read before parsing the compact summary

* fix(cli): refresh the context bar with post-compaction tokens after /compact

* fix(cli): scope the Compacted stdout suppression to the active compact turn

* fix(cli): make compact lifecycle ownership race-safe

* fix(cli): publish compact outcome before accepting the next turn

* fix(cli): cancel compact completion with its failed stream attempt

* fix(cli): propagate deferred compact completion failures

* refactor(cli): coordinate compact completion in the response loop

* fix(cli): correlate compact completion with its stream signal

* fix(cli): commit compact ownership when its result arrives

* refactor(cli): name the manual compact stream suppression predicates

* fix(cli): suppress the compact echo even when hook output is appended

* fix(cli): carry the resolved context window into the compact usage refresh
2026-10-28 12:14:34 +08:00