Commit Graph
293 Commits
Author SHA1 Message Date
Yichen Jiang 9f996be8e3 fix(web-config): close the wire boundary, the redacted-replace data loss, and three P2s
Five findings from the #939 review, each reproduced before being fixed.

**Configuration reads are as privileged as writes.** `settings.describe`
returns every exposed namespace's configuration and `credentials.describe`
reports whether an arbitrary environment-variable name is configured and from
where — reconnaissance no anonymous caller should have. Both join
PRIVILEGED_METHODS, so the whole configuration plane is loopback-only until
real authentication exists; `trustedHosts` was never authentication. The model
catalog stays reachable: it carries no endpoints or key state, and a LAN
client's model picker legitimately needs it. Asserted over a real HTTP server,
because the Host header a browser actually sends is what decides this.

**The proxy serves only namespaces a registered model provider addresses.**
The settings seam is general — any plugin may register one — but the Web
configuration plane is the model-provider surface. Without the gate, every
future `settings.register()` would silently become remotely readable and
writable configuration. An unregistered namespace and an unexposed one answer
identically, so no caller can enumerate the registry one probe at a time.

**Path-addressed writes replace the redacted-document rebuild.** The editor
reads the REDACTED descriptor, so rebuilding a section from it and replacing
wholesale deleted every literal secret the wire never returned — reproduced as
`{baseURL, reasoning}` in, stored `apiKey` gone out. `settings.mutate` applies
set/unset ops to the section as it stands at the front of the seam's write
queue, and the client names only fields it can see, so an unseen secret is
untouched by construction rather than by care.

P2s in the same pass: `llm/adapters-updated` now contains async listener
rejections (an uncontained one escaped as unhandledRejection, contradicting
the documented "observer failures are contained"); llm-deepseek's retry-policy
swap uses the atomic `registration.replace` instead of dispose-then-register,
which published `[]` then `["deepseek-official"]` so an observer saw the
provider disappear and come back; and a transport rejection no longer strands
the page in `loading` or a card in `busy`, with removal failures surfaced on
the page banner instead of swallowed.
2026-07-30 18:30:15 +08:00
Chinesezjc 7a60a236bc feat(web): render read tool output as a line-numbered code card
Consume the card:'read' result view (path, numbered lines, totalLines, lang)
the read backend PR added. ReadBlock (ui-primitives) draws a per-line gutter
with each line's own file number, shiki highlighting via a new highlightLines
returning per-line token arrays, a 显示 X / Y 行 window note, a height cap
matching TerminalBlock, and a copy control. read-card-model is the single
resultView derivation; a keyed ReadRow registers under read with the card
resident under its path-link summary. The generic fallback and the details
panel are read-aware. Fixture gains a windowed read turn for the built-boot
snapshot.
2026-07-30 18:15:38 +08:00
creatixchu f4111a3701 fix(ui): drop the caption from the /permission error text too
Review round: the no-caption rule the Agent Note states applied only to the
success texts, leaving `permission · unknown permission preset "bogus" (…)`.
The error text now reads `unknown preset "bogus" (…)` and its exact wording
is pinned; the fixture mirror drops `JSON.stringify` for the host's own
quoting so the two cannot drift on a quoted argument.

The Note now states the line it draws: the rule bans a caption for the
command's own value, not the vocabulary, so `/plan`'s `Plan mode off.` and
`/goal`'s `Goal cleared.` conform as written — recorded with the broader
name-ban as a rejected alternative. The web row assertions are scoped to the
row so unrelated page text reading `permission` cannot satisfy them.
2026-07-30 18:07:35 +08:00
Chinesezjc f6802ee019 feat(web): render web_search/web_fetch output as a web card
Consume the card:'web' result view (structured sources + answer for search, the
URL and HTTP status for fetch) the web backend PR added. WebBlock
(ui-primitives) draws both kinds via the kind discriminant: a citation list of
safe external links (the MarkdownText protocol allowlist, title-or-hostname
label), a truncation indicator, a height cap matching TerminalBlock; a fetch
summary for the other kind. web-card-model is the single resultView derivation;
a keyed WebRow registers under web_search and web_fetch with the card resident
under its summary. The generic fallback and the details panel are web-aware.
Fixture gains web_search and web_fetch turns for the built-boot snapshot.
2026-07-30 17:43:13 +08:00
Chinesezjc 71adea8ba4 feat(web): render grep/glob search output as a search card
Consume the card:'search' result view (matches grouped by file for grep, a
path list for glob) the search backend PR added. SearchBlock (ui-primitives)
draws both kinds via the kind discriminant with a per-file collapse, a
truncation pill, a height cap matching TerminalBlock, and a copy control;
search-card-model is the single resultView derivation; a keyed SearchRow
registers under grep and glob with the card resident under its summary. The
generic fallback and the details panel are search-aware. Fixture gains grep and
glob turns for the built-boot snapshot.
2026-07-30 17:42:59 +08:00
creatixchu d9cb19f276 feat(ui): drop the slash and the argument echo from the command row
The web command row renders `title · summary` from one logged command
lifecycle pair, and the two halves were written without knowing about each
other: the title was the dispatched line rebuilt from `command/run` and the
summary was `command/done`'s verbatim text, so every Access-chip pick read
`/permission workspace-write · Permission preset: workspace-write.` — the
command name twice and its argument twice.

The title is now the bare command name (no `/`, no arguments — the summary
already says what the command did), and a command handler's settlement text
never repeats the command's own name, so `/permission` returns `preset
workspace-write`. The row reads `permission · preset workspace-write`, and
the TUI notice still names the preset that now applies. The log is
unchanged: `command/run` keeps its structured name/args split for a richer
registered row.
2026-07-30 17:34:03 +08:00
Hypatia May 6d58953f30 fix(token-meter): close projected usage review gaps 2026-07-30 17:22:15 +08:00
Chinesezjc d2582b8dc1 feat(web): render write/edit tool output as a diff card
The write/edit tools already declare card:'diff' with applied hunks on
callView/resultView, but the Web client discarded it: a mutation landed on
GenericToolCard and the details panel flattened the result to a <pre>. Add
DiffBlock (ui-primitives), diff-card-model (the single callView/resultView
derivation), and FileMutationRow (keyed under write and edit), and make the
generic fallback row and the details panel diff-aware. The +/- block form,
per-file path header, same-file gap, and footer mirror the TUI diff card;
the chat row caps at CHAT_DIFF_MAX_LINES against the panel's full height.
2026-07-30 16:11:59 +08:00
_Kerman 4370004360 refactor(agent): expose mutable inbox state 2026-07-30 15:35:18 +08:00
Yichen Jiang 483199d47a Merge branch 'worktree-llm-dynamic-config' into worktree-llm-web-config
# Conflicts:
#	apps/cli/cordis.yml
#	apps/web/tests/snapshots/code-mode-round/session.jsonl
#	apps/web/tests/snapshots/cordis-tool-round/session.jsonl
#	apps/web/tests/snapshots/fresh-round-trip/session.jsonl
#	apps/web/tests/snapshots/lifecycle-chrome/session.jsonl
#	apps/web/tests/snapshots/live-interactions/session.jsonl
#	apps/web/tests/snapshots/navigation-panes/seed.jsonl
#	apps/web/tests/snapshots/question-composer/session.jsonl
#	apps/web/tests/snapshots/seeded-history/seed.jsonl
#	apps/web/tests/snapshots/steering/session.jsonl
#	docs/cordis-catalog/events.md
#	docs/cordis-catalog/services.md
#	docs/core-data-structures/core.i18n.yaml
#	docs/core-data-structures/settings.i18n.yaml
#	docs/event-producer-consumer.md
#	docs/module-graph.md
#	examples/acp-agent/tests/snapshots/workspace-context/session.jsonl
#	packages/client/connection/README.i18n.yaml
#	packages/client/connection/src/index.ts
#	packages/client/connection/tests/node-half.spec.ts
#	packages/client/runtime/README.i18n.yaml
#	packages/client/runtime/README.md
#	packages/client/runtime/README.zh.md
#	packages/client/runtime/src/client/index.ts
#	packages/client/runtime/tests/fake-api.ts
#	packages/client/ui-models/README.i18n.yaml
#	packages/examples/tui-demo/README.i18n.yaml
#	packages/host/apiproxy/README.i18n.yaml
#	packages/host/apiproxy/package.json
#	packages/host/apiproxy/src/api-proxy.ts
#	packages/host/apiproxy/src/api/rpc.schema.ts
#	packages/host/apiproxy/src/api/rpc.ts
#	packages/llm/llm-deepseek/README.i18n.yaml
#	packages/llm/llm-deepseek/README.zh.md
#	packages/llm/llm-pi-ai/README.i18n.yaml
#	packages/llm/llm/README.i18n.yaml
#	packages/llm/llm/README.zh.md
#	packages/sdk/sdk-client/README.i18n.yaml
#	packages/settings/settings/README.i18n.yaml
#	packages/settings/settings/README.md
#	packages/settings/settings/README.zh.md
#	packages/subagent/subagent-dsh-sdk/README.i18n.yaml
#	packages/subagent/subagent-dsh-sdk/README.zh.md
#	packages/support/llm-replay/README.i18n.yaml
#	packages/ui/jsonrpc/README.i18n.yaml
#	packages/ui/jsonrpc/README.zh.md
#	packages/ui/tui/tests/snapshots/model-selector.expected.txt
#	packages/ui/tui/tests/snapshots/model-switching.expected.txt
#	packages/ui/tui/tests/snapshots/resume-sessions.expected.txt
#	packages/ui/tui/tests/snapshots/status-diagnostics-narrow.expected.txt
#	packages/ui/tui/tests/snapshots/status-diagnostics.expected.txt
#	packages/ui/tui/tests/tui.snapshot.ts
#	pnpm-lock.yaml
#	python/sdk/README.i18n.yaml
#	scripts/snapshots/translation-prompt-v4/request-response.expected.json
2026-07-30 15:18:26 +08:00
Hypatia May 8a8c1965d7 feat(web): show durable token usage and context occupancy in the stats line
The chat stats line took its token totals from the loaded conversation nodes,
so paging changed them and compaction erased the billing behind replaced
content. It also had no way to show context occupancy: the numerator and
capacity never reached the browser.

Both now come from token-meter session projections read through the standard
useProjection seat. Window nodes keep supplying turn and step counts plus LLM
and tool wall times, which are correctly window-scoped facts about what is on
screen; accounting no longer comes from there.

`tokenUsage` supplies billing and cache hit. `contextPressure` supplies
occupancy, pairing the newest provider-reported prompt size with the newest
capacity recorded by `request/context`. Deployments without token-meter drop
the token groups; a route whose adapter advertises no capacity drops the
occupancy group rather than rendering a placeholder.

Occupancy is deliberately approximate: the numerator and capacity are
independent last-wins fields, not one atomic request observation, so switching
models pairs a fresh capacity with the prior route's pressure until the next
request reports usage. It is a user-facing reference figure that nothing in the
harness makes decisions from, and it matches how the TUI status line has always
computed occupancy. The Agent Note and token-meter README state this as a
decision, including why the atomic alternative was implemented and rejected, so
it is not re-litigated as a defect.

Snapshot delta is one added `Context N% of 128K` segment across eight web
goldens; the preceding commit absorbed master's pre-existing golden drift.
2026-07-30 14:48:19 +08:00
_Kerman c0ef93efc8 Merge remote-tracking branch 'origin/master' into xtr/react-loop-simplification
# Conflicts:
#	.agents/notes/implemented/architecture/2026-07-22-unified-send-and-coalesced-user-messages.i18n.yaml
#	.agents/notes/implemented/architecture/2026-07-22-unified-send-and-coalesced-user-messages.md
#	.agents/notes/implemented/architecture/2026-07-22-unified-send-and-coalesced-user-messages.zh.md
#	.agents/notes/implemented/feature/2026-07-21-tui-skill-slash-command.i18n.yaml
#	.agents/notes/implemented/feature/2026-07-21-tui-skill-slash-command.md
#	.agents/notes/implemented/feature/2026-07-21-tui-skill-slash-command.zh.md
#	.agents/notes/implemented/simplification/2026-07-17-one-send-one-turn.i18n.yaml
#	.agents/notes/implemented/simplification/2026-07-17-one-send-one-turn.md
#	.agents/notes/implemented/simplification/2026-07-17-one-send-one-turn.zh.md
#	docs/architecture.i18n.yaml
#	docs/cordis-catalog/events.md
#	docs/cordis-catalog/services.md
#	docs/core-data-structures/core.i18n.yaml
#	docs/core-data-structures/core.md
#	docs/core-data-structures/core.zh.md
#	docs/defensive-patterns.i18n.yaml
#	packages/client/runtime/src/client/sessions/session.ts
#	packages/client/runtime/tests/queue-store.spec.ts
#	packages/context/time-context/tests/time-context.spec.ts
#	packages/context/workspace-context/tests/workspace-context.spec.ts
#	packages/cordis/tool-cordis/src/api-catalog.ts
#	packages/core/agent-loop/README.i18n.yaml
#	packages/core/agent-loop/README.md
#	packages/core/agent-loop/README.zh.md
#	packages/core/agent-loop/src/agent.ts
#	packages/core/agent/README.i18n.yaml
#	packages/core/agent/README.md
#	packages/core/agent/README.zh.md
#	packages/core/agent/src/types.ts
#	packages/core/agent/tests/agent.spec.ts
#	packages/core/scope/src/scoped-events.generated.ts
#	packages/goal/command-goal/tests/command-goal.spec.ts
#	packages/goal/goal-session/src/index.ts
#	packages/goal/goal-session/tests/goal-session.spec.ts
#	packages/goal/goal/tests/goal.spec.ts
#	packages/goal/goal/tests/projection.spec.ts
#	packages/goal/tool-goal/tests/tool-goal.spec.ts
#	packages/host/apiproxy/src/api-proxy.ts
#	packages/host/apiproxy/src/api/events.schema.ts
#	packages/host/apiproxy/src/api/events.ts
#	packages/host/apiproxy/tests/api-proxy-workspace.spec.ts
#	packages/llm/llm/README.i18n.yaml
#	packages/llm/llm/README.zh.md
#	packages/llm/llm/src/index.ts
#	packages/pty/pty-local/tests/index.spec.ts
#	packages/pty/pty-local/tests/local.spec.ts
#	packages/pty/pty/tests/service.spec.ts
#	packages/pty/tool-pty/tests/loader-composition.spec.ts
#	packages/pty/tool-pty/tests/tools.spec.ts
#	packages/skill/tool-skill/tests/tool-skill.spec.ts
#	packages/tasks/tasks-local/tests/tasks.spec.ts
#	packages/ui/tui/src/index.ts
#	packages/ui/tui/tests/harness.ts
#	packages/ui/tui/tests/tui.spec.ts
#	scripts/gen-cordis-catalog.ts
#	scripts/type-equiv.manifest.json
2026-07-30 14:04:53 +08:00
Hypatia May 7f30a7e30c Merge remote-tracking branch 'origin/master' into codex/status-bar-token-metrics
Conflict resolution notes:

- StatsLine: master redesigned the row into pipe-separated groups with LLM and
  tool wall times. Kept that design and swapped only the token accounting
  source, so counts and durations stay window-scoped while billing and context
  occupancy read the durable projections.
- Generated artifacts (cordis catalog, module graph, event producer/consumer,
  i18n pairing hashes) and web snapshots took master's side; they are
  regenerated and re-recorded after this merge.
- Web e2e goldens and details-panel/timeline assertions took master's side:
  that evolution is unrelated to this branch.
- ui-conversation package.json: kept master's devDependency ordering, re-adding
  only the token-meter entry this branch needs.
2026-07-30 13:56:39 +08:00
Hypatia May 4819210142 refactor(token-meter): make context occupancy durable projection state
Replace the transient `session/model-request` mux frame with ordinary durable
session state. Occupancy now rides two last-wins projection fields instead of a
non-replayable frame that needed removal tombstones and cross-stream fencing.

The frame was the only non-replayable class on the mux stream. Because host and
mux are independent SSE streams with no cross-stream order, a request emitted
before a removal could arrive after `host/session-removed`, and a legitimate
request for a new lifecycle reusing the same id could be fenced by a late
removal. Fixing that needed a lifecycle generation on every frame; the frame
itself was the problem.

Removed: the `session/model-request` frame and schema, the `agent/model-request`
core event, the ApiProxy measurement point, the client-side telemetry map and
removal tombstone, and the synthetic `cancelled` open error used to signal
reconnect through the error channel.

Added: `request/context`, a log-only session event recording the
registration-bound capacity of the route a request resolved to, appended beside
`request/header` from the lookup that already prepared the call and skipped when
the route is unchanged. Capacity stays out of `EpochHeader` because it is
adapter metadata about a route, not an input the request was built from, so it
must not join request reconstruction or header equality.

The `contextPressure` projection pairs the newest provider-reported prompt size
with the newest recorded capacity. The two are deliberately not one atomic
request observation: switching models can pair a fresh capacity with the prior
route's pressure until the next request reports usage. The figure is a
user-facing reference, and this matches how the TUI status line has always
computed occupancy.
2026-07-30 13:53:08 +08:00
_Kerman f2e20c1ef0 refactor(agent-loop): simplify message machine 2026-07-30 13:49:57 +08:00
Yichen Jiang 9182db00ef feat(web): configure DeepSeek during onboarding 2026-07-30 12:41:09 +08:00
Yichen Jiang 51415debe5 docs: bilingual config-plane documentation, regenerated catalogs, and the web-config-plane Agent Note 2026-07-30 10:53:39 +08:00
Yichen Jiang a35e372594 Merge remote-tracking branch 'origin/master' into worktree/web-model-request-retry
# Conflicts:
#	apps/web/tests/session-title.snapshot.ts
2026-07-30 10:27:00 +08:00
Hypatia May 61163b7da5 Merge remote-tracking branch 'origin/master' into codex/basic-session-search
Conflict resolutions:

- `session.list`: master's projection columns fold into the PR's cancellable,
  batched `listVisibleSessionSummaries`, which `session.search` shares as its
  visibility baseline; master's goal helpers stay beside it.
- Client sessions face: master narrowed `ctx.sessions` to `ISessions`, so the
  search verb and its protocol-constant bound are declared there and the
  test-runtime double implements them (recorded, empty page unless a scenario
  stubs hits).
- `WorkspaceBrowser`: master's per-row Rename wiring rides the PR's search
  results view; the tree keeps the PR's query-free derivations.
- `dsh web` bin: the PR's shutdown-handlers-before-readiness order with
  master's boot-time LAN address snapshot.
- `session-query-sqlite`: master's `SCHEMA_VERSION` 7 stands; the PR's bump
  carried no schema change.
- Specs: master wraps assistant/steering message payloads and requires an
  `application/json` carrier request, so the search fixtures and tests follow.
- Web aria goldens keep master's recording plus the PR's search placeholder;
  the navigation-panes inventory keeps master's terminal-card golden next to
  the PR's search-results golden.
2026-07-30 09:40:38 +08:00
kingwl ebbcfdd03b Merge remote-tracking branch 'origin/master' into codex/web-queue-actions
# Conflicts:
#	packages/core/agent-loop/src/agent.ts
2026-07-30 03:33:41 +08:00
kingwl c97b1d025e Merge remote-tracking branch 'origin/master' into codex/web-queue-actions
# Conflicts:
#	apps/web/tsconfig.json
#	docs/architecture.i18n.yaml
#	docs/event-producer-consumer.md
#	packages/client/runtime/README.i18n.yaml
#	packages/client/tsdown.client.ts
#	packages/client/ui-conversation/README.i18n.yaml
#	packages/core/agent-loop/README.i18n.yaml
#	packages/core/agent/README.i18n.yaml
#	packages/core/agent/README.zh.md
#	packages/host/apiproxy/README.i18n.yaml
#	tsconfig.host.json
2026-07-30 00:30:38 +08:00
Yichen Jiang 3eec30c278 Merge remote-tracking branch 'origin/master' into worktree/web-model-request-retry
# Conflicts:
#	apps/cli/README.i18n.yaml
#	apps/web/tests/snapshots/live-interactions/retry.expected.md
#	packages/client/runtime/README.i18n.yaml
#	packages/client/runtime/src/client/index.ts
#	packages/client/ui-conversation/README.i18n.yaml
#	packages/client/ui-conversation/README.md
#	packages/client/ui-conversation/README.zh.md
#	packages/client/ui-conversation/src/client/chat/MessageItem.tsx
#	packages/client/ui-conversation/tests/chat-branch-tails.spec.tsx
#	packages/client/ui-trajectory/README.i18n.yaml
#	packages/client/ui-trajectory/README.md
#	packages/client/ui-trajectory/README.zh.md
#	packages/client/ui-trajectory/src/client/spans.ts
#	packages/client/ui-trajectory/tests/views.spec.tsx
#	packages/llm/llm-retry/README.i18n.yaml
#	packages/llm/llm-retry/README.zh.md
2026-07-30 00:16:32 +08:00
Yichen Jiang 191067559e feat(apiproxy): settings/credentials/llm wire domains, frames, and write guard
Eight compiler-locked methods: settings.describe/update/replace serve
redacted layered namespace views (secrets structurally absent from every
layer, write-only in the update direction) and fold seam refusals into
settings-rejected; credentials.describe/set/unset expose value-free views
with credential-rejected on shadowed writes; llm.providers merges the
configurable directory with live routes and llm.models claims the
host-scoped catalog reservation through the buildModelCatalog extraction
session.models now shares. Three HostFrame invalidations bridge the seam
events (host/settings-changed, host/credentials-changed,
host/models-changed), and the connection route generalizes the native-
dialog check into a privileged-method set covering all four writes. The
fixture and both fake clients grow the same face.
2026-07-30 00:13:12 +08:00
kingwl f893e2281d feat(agent): add addressable queue operations 2026-07-30 00:05:00 +08:00
Tianyi Cui 2d3338a03b Merge latest master into codex/migrate-to-oxlint 2026-07-29 22:39:06 +08:00
Tianyi Cui 32aaa8983e Merge commit 'ecbf75a5e70f662b6420375140cf12eb6bac7860' into worktree/retarget-pr885-20260729
# Conflicts:
#	docs/development.i18n.yaml
#	packages/client/ui-conversation/src/client/chat/MessageItem.tsx
#	packages/client/ui-primitives/src/markdown/CodeBlock.tsx
#	scripts/snapshots/translation-prompt-v4/request-response.expected.json
2026-07-29 21:37:43 +08:00
imccyu 6e8ad4b382 Merge remote-tracking branch 'origin/master' into worktree-renameweb
# Conflicts:
#	packages/client/runtime/README.i18n.yaml
#	packages/client/runtime/README.zh.md
#	packages/session-title/session-title/README.i18n.yaml
#	packages/session-title/session-title/README.zh.md
2026-07-29 20:58:22 +08:00
imccyu acd9716634 fix(client): fixture rename via requireSession, unchanged-title pin gesture
The fixture route shares the requireSession guard, reads the accepted seq off
the appended event, and gains the error-arm spec the workspace.rename case
set as precedent; WorkspaceBrowser drops the unchanged-title block — confirming
the current automatic title IS the pin gesture — and both touched client
READMEs document the new dialog and the unary-settle rule bilingually.
2026-07-29 20:11:43 +08:00
imccyu ff049f1e82 feat(client): ISession.rename settles the title projection cell from the response
rename is a per-session verb on the outward session face (prompt/cancel
precedent), not a list-service verb: the Session calls session.rename and
applies the response {title, seq} to its projection store under
higher-seq-wins, so the list row updates before the push frame. The fixture
api and the test-runtime double follow the same face.
2026-07-29 18:59:34 +08:00
Hypatia May 55b47657e5 fix(web): fence request telemetry lifecycles 2026-07-29 17:19:01 +08:00
Hypatia May 840e5eaa40 fix(web): address request telemetry review 2026-07-29 16:42:36 +08:00
Chinesezjc fdcb45b619 fix(web): bound the SGR state and follow real terminal widths
Nine findings, one critical. Terminal cases verified in a real terminal first.

CRITICAL: cells held the accumulated SGR history, so every state boundary
re-emitted the whole chain — output switching color without a full reset
emitted O(n^2) characters. Measured: 3200 such cells produced 25 MB, and the
reviewer's ~90 KB alternating-color case is well under bash's own output cap.
State is now a normalized record (foreground, background, attribute set) with
one canonical sequence per boundary, so the emitted text is linear in cells;
the 90 KB case parses in 36 ms. That also makes the attribute closers every
chalk-based tool writes actually close: `\x1b[1mbold\x1b[22mplain` leaves the
following write PLAIN, which a real terminal confirms.

Width follows emoji presentation, not the U+2600-U+27BF block: `A✓B` redrawn
with `XY` shows `XYB`, so the check every progress line writes is ONE column.
Taking the block as wide misaligned exactly the output this card exists for.

Writing over either half of a wide pair blanks the other, since a terminal
cannot leave one cell of a two-cell glyph standing.

`line\n\x1b[0m` does not end in a newline as a string yet its last parsed line
holds nothing visible, so the terminator check now reads the parsed lines — it
had added a blank row and inflated the collapse count.

A line with no cursor movement no longer builds a column buffer at all; only
its SGR is folded, so an `ls -R` or a 5k-line log allocates nothing per
character.

The `.terminalDescription` rule had been inserted into an existing grouped
selector, silently giving `.codeBody` description typography and changing its
bottom margin from 4px to 0 — a pre-existing surface this PR does not own.
Split out, `.codeBody`'s margin restored.

Three comments contradicted their code: the fixture's exit-marker line (still
claiming recovery from a marker deliberately removed), `bash-sample`'s header
(still routing a click to the details panel, and calling the consumer's cap the
block's own), and a DetailsPanel comment stacked above the wrong rule. The
ui-primitives README documented only the CR/BS half of the replay, so a reader
would expect `OK0%` where `100%\r\x1b[KOK` renders `OK`.
2026-07-29 16:42:10 +08:00
Yichen Jiang fee12f1af0 refactor(llm-deepseek)!: rename the provider route to deepseek-official
The native adapter's route was named deepseek, colliding with pi-ai's
catalog provider of the same name, so the two DeepSeek paths could never
be mounted side by side. The web settings page needs both configurable at
once. Compositions, fixtures, goldens, scaffolding defaults, and docs all
move together (pre-release, no shim); TUI/session-query-spill/
missing-credential goldens re-recorded through their keyless refresh
modes because provider-name length shifts box padding and spill
truncation points.
2026-07-29 16:36:07 +08:00
Chinesezjc b588060b90 fix(web): close a run at the line end and count columns correctly
Eight findings, each terminal case verified in a real terminal first:

`\x1b[32mdone\rok\x1b[0m` then `plain` shows `okne` green and `plain` in the
DEFAULT color. The replay returned the last written cell's state, so a reset
landing after the final write vanished from both the text and the returned
state — and every build tool writes exactly that shape, so the color leaked
onto all later output. The replay now converges to the state the scan ended
in, which is also what it hands to the next line.

`abcd\b\x1b[1K|` shows `   |`: CSI 1K erases THROUGH the cursor column, and
the loop stopped before it. The erase mode also reads only the first
parameter now, since a terminal treats `1;2K` exactly as `1K`.

`éx\rYZ` shows `YZ`: a combining mark takes no column, so it attaches
to the cell already written instead of advancing the cursor and leaving the
`x` standing.

`中x\rA` shows `A x`: overwriting a wide character's lead cell leaves its
spacer as a blank rather than closing the gap, which would shift everything
after it one column left.

The banner lost its span when the gutter became padding — a plain block child
only reaches the content box, so the reserved column was painted in the body
color and the card's top-left radius drawn in it. Invisible in the light
theme, where banner and body share a token; visible in the dark one. The
header now pulls back across the gutter and re-insets by the same amount.

The replay trigger matches the same CSI shape the parser accepts, so a form
like `\x1b[1;2K` can no longer skip its own erase, and `replayLine`'s JSDoc
documents its new parameter and returned pair.

Docs: four places still described the dot as sitting left of the card surface,
which stopped being true when the gutter became the card's own padding, and
two fixture comments still referenced the exit marker that was deliberately
removed.
2026-07-29 16:10:37 +08:00
Chinesezjc 43de478d10 fix(web): model erase-in-line, tab stops, and cross-line SGR
Six findings. Each terminal-semantics case was checked in a real terminal
(tmux, reading back the painted screen) before changing anything:

`100%\r\x1b[KOK` shows `OK`. Modelling the `\r` without its erase left the
previous frame's tail standing — a regression against the old truncate, since
`\r\x1b[K` is the single idiom every spinner and progress bar writes. Erase is
now part of the same replay, in all three parameter forms.

`a\tb\rXY` shows `XY      b`. Counting a tab as one column produced `XYb` and
destroyed the alignment this card exists to hold, so the cursor now advances
by terminal columns: tabs reach the next 8-column stop and a wide character
takes two cells.

`\x1b[31mabc\rX\nnext` paints BOTH lines red. A newline does not reset the
graphic state, so state threads from one replayed line to the next instead of
closing at each line end.

Only `m` accumulates into a cell's style now. Folding cursor and erase
sequences in grew the state string per redraw and emitted boundaries anser
had to discard.

The empty check reads the parsed lines the card renders rather than the raw
text: output that is only escapes or control bytes survives `trim()` yet
parses to nothing, and drew blank rows plus a copy control for invisible
bytes instead of the placeholder.

The gutter is the card's own left padding rather than a margin. Every render
site rewrites `margin` wholesale for its own indent, which silently cancelled
the reservation and let a container clip the dot.

The fixture sample no longer carries an `[exit code: 1]` line: the real bash
presenter consumes that marker precisely because the card shows the exit as
its own pill, so the built-bundle snapshot had pinned a frame showing it
twice — one the product path cannot produce.
2026-07-29 15:36:16 +08:00
Hypatia May bf618dabf9 refactor(web): project usage and snapshot request context 2026-07-29 15:27:59 +08:00
Chinesezjc 612b3c7c4e Merge remote-tracking branch 'origin/master' into feat/web-terminal-card
# Conflicts:
#	packages/client/ui-conversation/README.i18n.yaml
2026-07-29 14:45:23 +08:00
Yichen Jiang f8238ab75d Merge remote-tracking branch 'origin/master' into worktree/web-model-request-retry
# Conflicts:
#	apps/cli/README.i18n.yaml
#	packages/client/ui-conversation/README.i18n.yaml
#	packages/client/ui-conversation/README.zh.md
#	packages/client/ui-conversation/tests/chat-branch-tails.spec.tsx
2026-07-29 14:38:55 +08:00
Turtle 711efc0a8e Merge remote-tracking branch 'origin/master' into codex/migrate-to-oxlint 2026-07-29 14:32:21 +08:00
Turtle 95a995968b refactor: migrate linting to Oxlint 2026-07-29 14:32:11 +08:00
Hypatia May 741b760557 Merge remote-tracking branch 'origin/master' into codex/status-bar-token-metrics
# Conflicts:
#	apps/web/tests/snapshots/fresh-round-trip/ui.expected.md
#	docs/architecture.i18n.yaml
#	docs/event-producer-consumer.md
#	packages/client/connection/src/client/api.ts
#	packages/client/runtime/src/client/index.ts
#	packages/client/runtime/src/client/sessions/manager.ts
#	packages/client/runtime/src/client/sessions/service.ts
#	packages/client/ui-conversation/README.i18n.yaml
#	packages/client/ui-conversation/tests/chat-toolview-slot.spec.tsx
#	packages/host/apiproxy/README.i18n.yaml
#	packages/host/apiproxy/src/api-proxy.ts
2026-07-29 14:26:43 +08:00
Yichen Jiang 49131c47c5 fix(web): address model retry review feedback 2026-07-29 14:23:03 +08:00
Chinesezjc eba81fe1f2 Merge remote-tracking branch 'origin/master' into feat/web-terminal-card
# Conflicts:
#	packages/client/ui-conversation/README.i18n.yaml
2026-07-29 11:49:10 +08:00
Chinesezjc 439c206658 fix(web): honor the terminal view's description and resolved workdir
Three review findings, each verified against the presentation contract:

The call view's `description` was dropped, so a presenter that authors one
(`terminal_send` declares `Terminal <id>`) lost the contract's above-card text
and the row fell back to an unrelated args-derived summary. It now rides the
same derivation and outranks that summary.

A relative workdir was concatenated but never normalized, while the bash
executor resolves it before running: with session cwd `/w/app` and workdir
`..` the command runs in `/w`, yet the card displayed the label `..`. The
resolved path now collapses `.`/`..` segments, drops a `..` that would climb
past a root the way a filesystem does, and keeps a Windows path's separators
since the value is only ever displayed.

`run_code` sub-dispatches carry no presenter views on the shipped wire —
`session.ts` folds `tool/code-dispatch(-start)` with null views and the host's
`viewFor` presents only top-level call/result events — so a nested bash call
cannot reach a terminal card. The existing test only passed by injecting views
that path cannot produce; it now says so, and a second arm pins the no-view
shape the wire actually delivers.

Restoring master's fixture also fixed the todo snapshot lane, which my earlier
merge had broken by dropping the projection support the todo dock reads. The
terminal sample turn moved ahead of the todo turn, because the standing plan
retires at the next `turn/start` and a turn appended after it emptied the dock.

The card props are now nested under `card` so a render site spreads exactly the
primitive's own surface, and the fixture reads each sample's authored exit
status instead of re-implementing the bash tool's `parseExitStatus`.
2026-07-29 11:43:23 +08:00
imccyu 5c432955ed Merge remote-tracking branch 'origin/master' into web-permission-sandbox-merge-master 2026-07-29 11:16:49 +08:00
Yichen Jiang 602f594c4b Merge remote-tracking branch 'origin/master' into worktree/web-model-request-retry
# Conflicts:
#	apps/cli/README.i18n.yaml
#	packages/client/connection/tests/fixture.spec.ts
#	packages/client/runtime/README.i18n.yaml
#	packages/client/runtime/src/client/index.ts
#	packages/client/runtime/src/client/sessions/session.ts
#	packages/client/ui-conversation/README.i18n.yaml
#	packages/client/ui-conversation/README.md
#	packages/client/ui-conversation/README.zh.md
#	packages/client/ui-conversation/src/client/chat/ChatView.tsx
#	packages/client/ui-conversation/tests/chat-view.spec.tsx
#	tsconfig.base.json
2026-07-29 10:14:02 +08:00
creatixchu c5ae7dd65e Merge remote-tracking branch 'origin/doc/host-client-group-readmes' into feat/directory-picker
# Conflicts:
#	packages/host/apiproxy/package.json
#	pnpm-lock.yaml
2026-07-29 06:43:09 +08:00
creatixchu 1779488c99 Merge remote-tracking branch 'origin/master' into doc/host-client-group-readmes 2026-07-29 06:40:26 +08:00
creatixchu baaa535235 fix(host): race directory reads against the caller's signal; report aborts as cancelled
Every filesystem await in the browse scan (opendir and each read) now
races the signal through raceAbort, so a stalled network open/read stops
with a departed caller and an already-aborted request rejects even for
an empty level; the abandoned settlement is swallowed and an abandoned
open that still mints a handle is closed, never leaked. apiproxy maps an
aborted listing to the cancelled wire code, matching pickDirectory and
command.execute, instead of reporting a false internal failure. The
fixture spec call sites gain the wire signal argument the previous
commit's static lane flagged.
2026-07-29 05:05:36 +08:00
creatixchu 7503390590 fix(host): cancellable listing scans and O(log window) insertion
capability.list gains an optional AbortSignal threaded from the RPC
carrier's request signal (the pickDirectory pattern): a disconnected or
timed-out caller stops the opendir loop instead of the scan outliving
its caller, and the abort surfaces as its own reason rather than a
directory-unreadable dressing. boundedInsert rejects a full window's
at-or-beyond-tail candidate on one comparison and binary-inserts
retained candidates, so an oversized level no longer pays a window scan
per dirent.
2026-07-29 04:42:13 +08:00