Commit Graph
1644 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
5d8deb2011 fix(web): allow pasting non-image files into the composer (#2026)
Paste only accepted image/* files, silently dropping PDFs, archives,
etc. copied from the file manager, while drag-and-drop and the attach
button accept any file. Remove the image-only filter from both paste
handlers so paste matches drag-and-drop. The scheduled-send guard is
unchanged.

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

Co-authored-by: HAPI <noreply@hapi.run>
2026-10-20 07:22:14 +08:00
AnanovoandGitHub 6f155313ff fix(codex): keep status summaries out of intermediate messages (#2041) 2026-10-20 07:21:21 +08:00
f13a1aaeab fix(web): shed session toolbar icons by keep rank instead of overlapping (#2040)
* fix(web): stop session toolbar search/clear overlapping neighbors

Collapsed search used min-w-0 and crushed under shrink-0 icons, so clear
painted over filter/BlockedJumpPill on narrow widths. Keep search chrome
shrink-0 and wrap the header actions group instead (#2039).

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

* fix(web): size session toolbar actions to content so they wrap

flex-1 min-w-0 kept the action group beside a long search chip in a 280px
sidebar, so Browse/Settings overflowed onto the calendar. Use w-max shrink-0
and keep the router header-action cluster from shrinking (#2040).

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

* fix(web): keep session toolbar icons in one slab when the row wraps

max-w-full plus inner flex-wrap let the action group split, so some icons
stayed beside search and the rest dropped underneath. The group is nowrap
and content-sized, so a tight row moves every icon together.

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

* fix(web): shed secondary session toolbar icons instead of wrapping

Quest was burning a full toolbar line when the icon slab did not fit
beside collapsed search. Keep one nowrap row; hide browse/settings (and
similar headerActions) below a container-query width, with a higher bar
when the query chip is present (#2039).

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

* fix(web): satisfy typecheck for session header-row test

CI failed because optional chaining left headerRow possibly null when
reading className. Use getByTestId instead.

* fix(web): put shed session toolbar actions in a More menu

Hiding headerActions on narrow headers removed Settings with no
alternative. Keep one toolbar row, but surface secondary actions in an
accessible ⋯ popover below the container-query threshold (#2039).

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

* fix(web): fold primary toolbar into More when query chip is active

At ~320px the collapsed chip plus unread/filter/New still overflowed past
More/Settings. When searchQuery is non-empty, put primary controls in the
same overflow menu so the row is [chip][⋯] (#2039).

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

* fix(web): pack session toolbar actions beside search

ml-auto shoved the action slab to the right edge and left a Quest-width
gap between the search icon and BlockedJumpPill (#2039).

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

* fix(web): widen machine menu clamp and width-gate toolbar More

MachineFilterMenu near the left edge (inside More) got a tiny right-only
maxWidth. Clamp to the wider horizontal pocket and left-align when that
side wins. Fold primaries into More only when a query chip is present and
the header is under 32rem (#2039 / #2040).

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

* fix(web): fold toolbar into More on narrow headers without a query

Empty query still forced toolbarCrowded=false, so at 320px / 130% font
with machines+unread the More control went offscreen. Crowd from header
width in both query states (#2039 / #2040).

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

* fix(web): stop Quest-width empty query collapsing to search+More

A flat 32rem crowd gate folded every primary into More on typical Quest
sidebars (~400px), leaving [search][⋯] and a desert of empty space. Empty
query now crowds only below ~22rem (still covers 320px/130% font); query
chip keeps the higher 32rem bar (#2039 / #2040).

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

* fix(web): pin toolbar More to the trailing edge on Quest widths

Left-packing every action left a dead zone after New while Settings hid
in More. Primaries stay beside search; secondary/More get ml-auto on a
flex-1 actions row so the chrome reads [search][primaries] … [⋯]
(#2039 / #2040).

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

* fix(web): shed session toolbar icons by keep rank

A single width gate either hid the whole row or left a hole before More.
Drop the lowest-priority controls first (browse, mark-all, settings) and
keep search, new, and the unread lens on the row while they fit (#2039).

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

* fix(web): size toolbar shed from actions box, not a search guess

Active query reserved 200px for a max-w-[9rem] chip and collapsed the
row to search+More. Measure the actions flex box and each control;
keep daily icons until width is known (#2039).

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

* fix(web): remeasure toolbar shed after search collapse

The actions box unmounts while search is expanded, so a one-shot observer
left the next layout on a stale width. Fit math now reattaches on the
new node, subtracts a visible machine menu, and uses the measured More
width and column gap.

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

* fix(web): let query chip yield so toolbar More stays on-screen

At 320px and 130% type a long chip plus calendar left too little room
for the machine menu and More. An active query now uses min-w-0 on
search and shrink-0 on actions so only the chip text truncates.

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

* fix(web): stop desktop toolbar using an infinite shed budget

Empty-query first paint treated the actions gutter as infinite, so every
shrink-0 icon stayed inline and painted past the split. Use a finite
fallback, cap the budget by the real row gutter, and give the sidebar
min-w-0 so --sidebar-w actually constrains the flex item.

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

* fix(web): use one-gap slack instead of leaving an icon-sized hole

The shed refused the next icon when the gutter was only a column-gap
short, so the row kept an empty slot the size of that icon.

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

* fix(web): fill the toolbar gutter before opening More

The shed was budgeting a guessed remainder, so More stayed open while
a wide empty stretch sat before the split. The budget is now the pixels
from the actions' left edge to the row's right edge. If every icon fits,
overflow is empty.

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

* fix(web): size the toolbar budget from the flex leftover, not the icons

shrink-0 made the actions box as wide as whatever was already showing, so
search lost its clear and calendar and a collapsed query could hide every
action. The actions box is flex-1 again, and a query chip cannot shrink
below its icon, clear, and calendar.

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

* fix(web): reserve machine menu and More before search width

A long 9rem query chip at 320px / 130% left the actions box under 50px
while machine menu + More need ~94px, so More slid off-screen. Cap the
collapsed search group to the row minus those trailing controls.

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

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-18 14:12:16 +01:00
wushenghua 19c4df05cd docs: record macaron list layout rollout verification 2026-10-17 00:47:44 +08:00
wushenghua e156e5b907 fix(web): tighten macaron session rows and align metadata 2026-10-17 00:44:37 +08:00
wushenghua bd7683883d docs: record macaron safe-area rollout verification 2026-10-17 00:31:46 +08:00
wushenghua f7fde1b963 fix(web): extend macaron backgrounds through safe areas 2026-10-17 00:29:24 +08:00
wushenghua 2a377f960e docs: record cohesive macaron rollout 2026-10-17 00:19:44 +08:00
wushenghua aa082c0ff0 fix: unify macaron surfaces and page backdrop 2026-10-17 00:16:43 +08:00
wushenghua 9f22f15190 docs: record macaron colorway rollout 2026-10-17 00:01:16 +08:00
wushenghua 8924dba80e feat: add five macaron color palettes 2026-10-16 23:57:18 +08:00
wushenghua e789067546 docs: record macaron skin rollout 2026-10-16 23:35:35 +08:00
wushenghua 1bb12c8685 feat: add macaron gradient skin 2026-10-16 23:31:25 +08:00
wushenghua ee0b3f8c3b docs: record full-page forest skin rollout 2026-10-16 23:13:18 +08:00
wushenghua 08bb829e5f fix: use full-page forest skin backgrounds 2026-10-16 23:08:53 +08:00
wushenghua 8e6d6b5f20 docs: record richer forest skin rollout 2026-10-16 22:56:07 +08:00
wushenghua 65887b171e feat: enrich forest skin artwork
Add layered forest clearings, a responsive chat scene strip, larger companions and transparent nine-slice wooden frames. Export artwork for phone pixel density and preserve compact navigation, drafts, default appearance and existing session actions.
2026-10-16 22:47:44 +08:00
wushenghua c4ac102dea docs: record forest skin deployment 2026-10-16 22:17:24 +08:00
wushenghua eb540f80c8 feat: add forest adventure mobile skin
Add an opt-in forest skin with original transparent artwork, session companions, matching mobile surfaces, and on-demand offline art caching. Preserve existing theme customization and session actions. Include reproducible art slicing, browser coverage, and baseline test evidence.
2026-10-16 22:10:09 +08:00
wushenghua a8feb4feaf fix(scripts): refresh the local runner from a clean login environment
The refresh now runs in a pristine env (env -i + runner.env.local
overrides) so session-scoped vars from the invoking shell
(HAPI_SESSION_ID, CODEX_*, OPENCODE_*, ...) cannot leak into the runner
and every session it spawns, and it cds to $HOME so a deploy run from a
throwaway worktree cannot leave the runner with a deleted cwd.
2026-10-16 20:33:36 +08:00
wushenghua a1db22afc3 feat(web): show the current git branch in the session header
Active non-worktree sessions fetch the branch of their working
directory with a single lightweight git-status RPC and render it in the
header metadata row (live, so mid-session branch switches follow).
Worktree sessions keep their existing worktree chip and skip the extra
RPC; a new display preference toggles the branch item.
2026-10-16 20:33:27 +08:00
wushenghua 8a9f134777 fix(web): prefer the configured Claude models in pickers
With custom Claude models configured (e.g. a custom ANTHROPIC_BASE_URL),
the composer and new-session pickers now offer exactly those plus the
Default entry instead of merging the built-in Anthropic alias presets,
and fall back to the first configured model when there is no auto
option.
2026-10-16 20:33:02 +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
317e39a0bb fix(web): 48px session search clear with calendar left of it (#2034)
* fix(web): 48px session search clear with calendar left of it (#2019)

Quest/touch: expanded search uses a min 48x48 clear end-cap as the
furthest-right control, with the calendar embedded immediately to its
left inside the field. Collapsed search stays shrink-0 with a larger
clear chip so it does not steal the toolbar row.

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

* fix(web): anchor date picker to search group; let collapsed chip shrink (#2019)

HAPI Bot: mount the calendar popup on the search control (not the 48px
button) so left/right align stays on-screen at narrow widths. Drop
shrink-0 on the collapsed group so the query chip can yield space while
calendar/clear stay fixed hit targets.

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

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-15 19:15:50 +01:00
7b00df458c fix(voice): configurable ElevenLabs max conversation duration (#2017)
* feat(voice): configurable ElevenLabs max conversation duration

Auto-created ConvAI agents never set conversation.max_duration_seconds, so
ElevenLabs applied its 600s default and cut voice conversations off.

Add ELEVENLABS_MAX_DURATION_SECONDS (default 1800, validated 60-7200). The cap
is applied when the hub creates an agent and reconciled onto existing
hub-managed agents (found by name) once per process. Operator-supplied agents
(ELEVENLABS_AGENT_ID / voice map / customAgentId) keep their own cap. Invalid
values fail the token request with a clear message instead of an API 422.

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

* feat(voice): Settings UI for ElevenLabs max conversation duration

Extend the hub-owner credentials panel (Settings -> Voice -> manage
providers) with a numeric field for ELEVENLABS_MAX_DURATION_SECONDS,
reusing the same env-wins-over-Settings overlay that already backs the
ElevenLabs API key field. A Settings save takes effect on the very next
token request in the same process; the env var still requires a restart.

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

* fix(voice): reconcile max duration on cached agents, not just once per process

Codex review on heavygee/hapi#230 caught this: getOrCreateAgentIdForVoice's
agentIdCache short-circuited before ever reaching ensureAgentOverrides, and
ensureAgentOverrides's overridesEnsuredAgents gate was a one-time-forever
marker that didn't account for the configured duration changing after the
first reconciliation. Together, any voice already used once in the hub
process would keep its old cap until a restart, contradicting the Settings
UI's 'takes effect immediately' claim.

Track the last-applied max duration per agent and compare it against the
live setting on every call, including agentIdCache hits, so a Settings
change reaches an already-cached agent on its very next token request.

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

* fix(voice): address tiann/hapi#2017 review findings

- Fix TS2769 typecheck failure: test assertions casting the credentials
  response narrowed the type to omit 'editable', which upstream CI's
  bun typecheck catches and this fork's (currently broken) CI did not.
- An invalid ELEVENLABS_MAX_DURATION_SECONDS env value threw out of
  buildMaxDurationStatus(), which is shared by every provider's
  GET/PUT credentials status — breaking visibility into OpenAI/Deepgram/
  Groq settings too, not just ElevenLabs. The token-issuance hot path
  (getConfiguredElevenLabsMaxDurationSeconds) still throws there by
  design; only the status/update endpoints now degrade gracefully.

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

* fix(voice): interpolate duration range in the Settings hint

Nit from tiann/hapi#2017 follow-up review: the hint under the max-duration
field rendered the literal '{min}-{max}' placeholders instead of '60-7200'.

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

---------

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-15 15:42:01 +01: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
eca9e99331 feat(web): Enlarge button for dictation and text submission (#2006) (#2007)
* feat(web): larger empty-composer voice/dictate hit target

When the primary UnifiedButton is idle voice/dictate (empty draft),
use h-12 w-12 so Quest and touch clients get a usable target. Compact
h-8 w-8 remains for Send, stop, and disabled states.

Closes #2006

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

* feat(web): full-height composer primary (voice ↔ send)

Park UnifiedButton beside the composer card so it stretches the
full input+toolbar height. Empty shows giant voice/dictate; draft
swaps the same giant control to Send (icon only).

Refs #2006

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

* test(web): fix fill-height primary goldens + UnifiedButton mocks (#2006)

Fill-height tip regressed no-arg onSend assertions and empty-voice helper
expect; HappyComposer omitPrimaryButton needs UnifiedButton in vi.mocks.

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

* test(web): assert onSend('default') + fill-height helpers (#2006)

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

* fix(web): flush fill-height composer primary to card edge (#2006)

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

* fix(web): keep draft voice companion beside fill-height Send (#2006)

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

* fix(web): keep draft voice companion beside fill-height Send (#2006)

Revert accidental soup dump of expandEnter + fat ComposerButtons. Empty
stays giant voice; draft shows companion waveform left of giant Send.

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

* fix(web): draft voice as toolbar circle, not beside giant Send (#2006)

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

* fix(web): hide draft voice when scratchlist owns Send (#2006)

Codex P2: toolbar DictationButton and Ctrl/Cmd+Shift+D must not start
voice while routesToScratchlist — same suppress as UnifiedButton.

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

* fix(web): compact primary when composer is expanded (#2006)

Fill-height Send/voice is for the collapsed Quest card. Expanded mode
stays a compact control so we do not paint a full-shell action strip.

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

* chore: re-trigger upstream pr-review after Sol distributor 503

Empty commit — test/integration already green on tip; pr-review failed
twice with "No available channel for model gpt-6.1-sol".

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

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-12 20:15:15 +08: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
wushenghuaandCodex 8ade6571eb fix(hub): deduplicate restored Codex usage across legacy thread IDs
Resolve HAPI-id thread fallbacks only with a unique native source binding.
Match recovered snapshots across aliases and align missing intermediate
requests between native snapshots before calculating cumulative usage.
Keep stored evidence and prevent replays from rewinding usage baselines.

Validation: all workspace typechecks pass; 51 usage tests pass. The full
suite retains previously reproduced failures outside these changes.

Co-Authored-By: Codex <noreply@anthropic.com>
2026-10-08 20:35:31 +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
99cd3d4172 feat(android): add Russian locale (#1963)
* feat(android): add Russian locale

Translate all 518 app strings into values-ru/strings.xml, register ru in
locales_config.xml, and add AppLanguage.RUSSIAN to the in-app language
picker.

* fix(android): use count-agnostic Russian forms for dynamic counts

The *_many strings used a genitive-plural noun after the number, which is
wrong for 2-4 (e.g. "2 инструментов"). The house style avoids <plurals>,
so phrase these labels with the noun first ("Инструменты: %1") instead:
tool groups, review findings, queued/new messages, agent steps, questions,
lines, chars, edits, usage requests, scratchlist count and attachment cap.

* fix(android): make the process-steps label count-safe

* fix(android): use a count-safe token label in the usage tooltip

---------

Co-authored-by: mrudy <mrudy@local>
2026-10-05 17:14:11 +08:00
efc9004fda feat(web): add Russian locale and detect initial language (#1958)
* feat(web): add Russian locale

- Add web/src/lib/locales/ru.ts
- Register 'ru' in the locale registry, i18n context, language switcher and settings
- Format outline timestamps with the day.month convention for 'ru'

* feat(web): detect initial locale from Telegram and browser

The Telegram Mini App runs in its own web view, so a language chosen in
the browser does not carry over and the app always opened in English.
Pick the first session locale from an explicit stored choice, then the
Telegram user's language_code, then navigator.language, then English.

* feat(web): translate the newly localized strings

Russian values for the keys added by the hardcoded-string fix (see the
separate web i18n PR). Keeping them on this branch so the locale PR is
complete when both land.

* feat(web): add Russian for the newly localized strings

* fix(web): recheck the Telegram language after a slow SDK load

* fix(web): keep a manual language choice over late Telegram detection

---------

Co-authored-by: mrudy <mrudy@local>
2026-10-05 17:13:53 +08:00
Junmo KimandGitHub 0ef5f976c0 fix(web): render Codex goal responses as cards (#1954) 2026-10-05 17:13:14 +08:00
d3d8bf2620 feat(ios): add Russian locale (#1964)
* feat(ios): add Russian locale

Add Russian translations to Localizable.xcstrings for all 591 catalog
entries (reusing matching Android translations and translating the rest);
zh-Hans and format placeholders are preserved.

* fix(ios): make Russian selectable and use correct plural forms

- add AppLanguage.russian ("ru"), its display name, the AppleLanguages
  override, and register ru in the Xcode project's knownRegions so the
  language picker can actually select Russian
- add Russian one/few/many/other plural variations for %lld sessions,
  %lld attachments and %@, %lld sessions
- use count-agnostic phrasing for the other dynamic counts (tools, edits,
  lines, chars, findings, notes, queued messages, questions, ...)
- correct Input/Output to Ввод/Вывод
- cover the new language case in ThemePrefsTests

* fix(ios): use Russian plural forms for the remaining count labels

* fix(ios): pass the step count to the plural localization

* fix(ios): pass tool counts to the plural localization

* fix(ios): keep the singular English tool-group labels

---------

Co-authored-by: mrudy <mrudy@local>
2026-10-05 17:12:57 +08:00
Dave RapinandGitHub c2d2cfe6f8 Add 130% app font size (#1975) 2026-10-05 17:09:07 +08:00
AnanovoandGitHub 5153ff23c1 test(ios): make SessionSplit width assertion responsive (#1907) 2026-10-05 08:35:53 +08:00
wushenghua 4d8a72c146 feat(web): show byte progress while loading generated media
Large media downloads (a 19MB video takes ~60s over the slow runner
tunnel) previously showed only a pulsing placeholder, so the load looked
dead and users gave up. Read the response stream against Content-Length
and render a percentage bar while the blob downloads; fall back to an
indeterminate "Loading …" label when the header is absent, and keep the
instant path for cache hits.
2026-10-01 01:16:00 +08:00
wushenghua 587705dedb fix(web): bound loaded generated-media memory with a small LRU
Loaded video/audio/file blobs stayed in memory for the whole session
(object URLs were only revoked on unmount), so opening several tens-of-MB
videos in one chat grew memory without bound. Explicitly loaded media now
goes through a shared cache that keeps at most three entries; the least
recently used object URL is revoked on overflow and its card falls back to
the Load button (a sticky eviction flag prevents a slow load from
re-publishing an already-revoked URL). Auto-loaded images keep their
existing per-card lifetime.
2026-10-01 00:13:38 +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 8e61772bb4 feat(hub,web): serve chat image originals as cached attachments
Pasted images were stored inline in the message JSON as full-size base64
data URLs. Every conversation load (open, paginate, mobile reconnect) had
to download and parse those megabytes before anything rendered, with no
lazy loading and no per-image caching. Measured on this hub: 115 messages
holding 138MB of base64, the largest single message 18.3MB.

New messages now carry only a small thumbnail (browser-generated, 768px
webp/jpeg data URL); the original is uploaded as before and the hub keeps
a durable copy under ~/.hapi/attachments (table chat_attachments, schema
v27). The chat renders the thumbnail instantly and lazily swaps in the
original from GET /api/sessions/:id/attachments/:id when the viewer opens;
that endpoint answers sha256 ETag + immutable Cache-Control so repeated
opens are free. Sessions scope the URL, deletes remove the copy, and
hub/scripts/attachments.ts covers stats/verify/gc/export.

Old messages keep their inline previews (migration deliberately deferred).

Verified end-to-end against the local hub: upload returns the attachment
id/url, original 200 with ETag, revalidation 304, cross-session 404, delete
removes file+row (404 after). Typecheck clean; suite status matches the
pre-change baseline; fixtures unchanged.
2026-09-30 00:58:09 +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
97cf69bb59 feat(web): dictate keyboard shortcut (Ctrl/Cmd+Shift+D) (#1902)
* feat(web): add Ctrl/Cmd+Shift+D dictate keyboard shortcut

Toggle composer dictation or voice assistant via the same path as the
mic buttons, with dialog/input guards and tooltip documentation.

Fixes tiann/hapi#1900

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

* fix(web): address dictate hotkey bot findings on PR 1902

Ignore key repeat on the session listener and align assistant-mode
invoke guards with UnifiedButton send mode (canSend).

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

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-24 07:25:17 +08:00