Commit Graph

357 Commits

Author SHA1 Message Date
Yichen Jiang 86fbc5fc15 Merge remote-tracking branch 'origin/master' into worktree/pi-ai-per-model-reasoning
# Conflicts:
#	docs/config-catalog.md
#	docs/user/guide/providers.i18n.yaml
#	packages/llm/llm-pi-ai/README.i18n.yaml
#	packages/llm/llm-pi-ai/README.md
#	packages/llm/llm-pi-ai/README.zh.md
#	packages/llm/llm-pi-ai/tests/config.spec.ts
2026-08-08 10:32:22 +08:00
Yichen Jiang 69bd00ae76 chore(web): register the skill-user-invoke scenario in both typecheck planes
The web app project excludes every e2e file (they are host-plane programs)
and tsconfig.host.json includes them one by one; the new scenario joins
both lists so it keeps typecheck coverage without dragging host sources
into the client project.
2026-08-08 10:28:30 +08:00
imccyu 9c3d5725a5 build: order Host and Client compilation faces 2026-08-08 04:20:39 +08:00
Yichen Jiang a64f5c34ea Merge remote-tracking branch 'origin/master' into worktree/web-multimodal-image-input 2026-08-08 01:56:58 +08:00
Yichen Jiang 25d959aa08 Merge remote-tracking branch 'origin/master' into worktree/web-multimodal-image-input
# Conflicts:
#	.agents/notes/implemented/architecture/2026-07-05-reconstructable-requests.i18n.yaml
#	.agents/notes/implemented/architecture/2026-07-05-reconstructable-requests.md
#	.agents/notes/implemented/architecture/2026-07-05-reconstructable-requests.zh.md
#	.agents/notes/implemented/architecture/2026-07-25-web-input-machine-and-slash-pipeline.i18n.yaml
#	.agents/notes/implemented/architecture/2026-07-25-web-input-machine-and-slash-pipeline.md
#	.agents/notes/implemented/architecture/2026-07-25-web-input-machine-and-slash-pipeline.zh.md
#	THIRD_PARTY_NOTICES.md
#	apps/cli/composition.md
#	apps/cli/config/base.cordis.yml
#	apps/cli/package.json
#	apps/cli/src/app-cli-entry.ts
#	apps/cli/src/bin.ts
#	apps/cli/tests/args.spec.ts
#	apps/web/tests/built-boot.snapshot.ts
#	apps/web/tests/navigation-panes.e2e.ts
#	docs/architecture.i18n.yaml
#	docs/architecture.md
#	docs/architecture.zh.md
#	docs/config-catalog.md
#	docs/cordis-catalog/services.md
#	docs/core-data-structures/core.i18n.yaml
#	docs/core-data-structures/llm-streaming.i18n.yaml
#	docs/event-producer-consumer.md
#	docs/module-graph.md
#	examples/acp-agent/tests/snapshots/cordis-inspect-jsdoc/session.jsonl
#	packages/README.i18n.yaml
#	packages/bundle/README.i18n.yaml
#	packages/client/connection/README.i18n.yaml
#	packages/client/connection/README.md
#	packages/client/connection/README.zh.md
#	packages/client/connection/src/client/fixture.ts
#	packages/client/connection/src/http-bridge.ts
#	packages/client/connection/src/index.ts
#	packages/client/connection/tests/fixture.spec.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/contract/session.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/apply.ts
#	packages/client/ui-conversation/src/client/chat/AssistantMarkdown.tsx
#	packages/client/ui-conversation/src/client/chat/ChatView.tsx
#	packages/client/ui-conversation/src/client/chat/MessageItem.module.css
#	packages/client/ui-conversation/src/client/chat/MessageItem.tsx
#	packages/client/ui-conversation/src/client/contract/slots.ts
#	packages/client/ui-conversation/src/client/index.ts
#	packages/client/ui-conversation/src/client/input/contract.ts
#	packages/client/ui-conversation/src/client/input/facade.ts
#	packages/client/ui-conversation/src/client/input/hub.ts
#	packages/client/ui-conversation/src/client/locales.ts
#	packages/client/ui-conversation/src/client/service.ts
#	packages/client/ui-conversation/src/client/skeleton/ConversationSession.tsx
#	packages/client/ui-conversation/src/client/skeleton/InputBar.tsx
#	packages/client/ui-conversation/tests/apply-inject.spec.tsx
#	packages/client/ui-conversation/tests/input-bar.spec.tsx
#	packages/client/ui-conversation/tests/input-matrix.spec.tsx
#	packages/client/ui-conversation/tests/input-scenarios.spec.tsx
#	packages/client/ui-conversation/tests/service-orchestration.spec.ts
#	packages/client/ui-conversation/tests/skeleton.spec.tsx
#	packages/client/ui-trajectory/tests/views.spec.tsx
#	packages/compact/compact-basic/README.i18n.yaml
#	packages/cordis/tool-cordis/src/api-catalog.ts
#	packages/host/apiproxy/README.i18n.yaml
#	packages/host/apiproxy/README.md
#	packages/host/apiproxy/README.zh.md
#	packages/host/apiproxy/src/api-proxy.ts
#	packages/host/apiproxy/src/api/rpc.ts
#	packages/host/apiproxy/src/api/sessions.ts
#	packages/host/apiproxy/src/index.ts
#	packages/host/apiproxy/tests/api-proxy-models.spec.ts
#	packages/host/apiproxy/tests/rpc-schemas.spec.ts
#	packages/llm/llm-pi-ai/README.i18n.yaml
#	packages/llm/llm-pi-ai/README.md
#	packages/llm/llm-pi-ai/README.zh.md
#	packages/llm/llm-pi-ai/src/adapter.ts
#	packages/llm/llm/README.i18n.yaml
#	packages/ui/tui/README.md
#	packages/ui/tui/README.zh.md
#	packages/ui/tui/src/components/content.ts
#	packages/ui/tui/src/components/transcript.ts
#	packages/ui/tui/tests/tui.spec.ts
#	pnpm-lock.yaml
2026-08-08 01:56:29 +08:00
Huanqi Cao f64ba40f43 feat(sandbox): Windows ACL write-restriction sandbox (restricted-token runner)
Confine Windows command execution through a WRITE_RESTRICTED token whose restricting SIDs carry an orphan-SID write allowlist, ported from https://github.com/huoyaoyuan/windows-acl-restrict-poc (@ 10e4dfb). Every Win32 call is checked and fails closed - the POC silently ran children with the FULL token when CreateRestrictedToken failed.

- @deepseek-ai/dsh-sandbox-windows-acl: koffi primitives verified against the MinGW Windows headers (verify/abi-probe.cpp) plus the confinement runner ([node, runner, --workspace, --temp, --mode, --, argv...]: kill-on-close job, stdio passthrough, exit-code mirroring, windows-acl-run: failure signature, grant revocation). read-only = strict zero grants (NUL device not writable; documented). Windows-only execution: exempted from the Linux coverage lane (windowsOnlyCoverageExclusions).
- @deepseek-ai/dsh-sandbox-local: PLATFORM_CHAINS.win32 filled with the windows-acl runner (full enforcement, ACL denial dialect, runner-failure rules).
- @deepseek-ai/dsh-pwsh-sandbox: sandbox-consuming pwsh executor (call-for-call mirror of dsh-bash-sandbox) over a new argv-level seam in dsh-pwsh-local; per-file coverage complete via the fake-provider spec.
- bundle/base: the Windows platform layer mounts the confined pwsh roster - sandbox/policy/fs-sandbox/permission/approval re-enabled, the POSIX bash stack stays disabled.

Co-authored-by: Huo Yaoyuan <huoyaoyuan@hotmail.com>
2026-08-08 01:24:33 +08:00
Yichen Jiang 0490f8bb06 feat(llm-pi-ai): per-model reasoningEfforts and reasoning-dispatch compat switches
A model entry's reasoningEfforts dict declares its selectable thinking
levels — key = offered level, value = the wire spelling dispatch sends;
only off may leave the value empty (supported, send nothing). false
strips reasoning from a catalog model; every level is materialized
explicitly into pi-ai's thinkingLevelMap so nobody has to know pi-ai's
asymmetric absent-key defaulting. compat.thinkingFormat and
compat.supportsReasoningEffort become configurable on the route and per
model (model > route > catalog entry > pi-ai's URL-derived guess),
openai-completions only, so a private gateway speaking the DeepSeek
reasoning dialect no longer depends on its URL being recognizable.
Record-typed drift gates pin both enums to pi-ai's, and an unserviceable
declaration is refused at the write that produced it, naming route,
model, and level.
2026-08-08 00:51:54 +08:00
ZiyaZhang 525651d883 Merge remote-tracking branch 'upstream/master' into feat/web-inline-file-mentions
# Conflicts:
#	packages/client/ui-conversation/README.i18n.yaml
#	packages/client/ui-primitives/README.i18n.yaml
2026-08-07 09:50:43 -07:00
Tianyi Cui f00f45e3f9 Merge origin/master at f1402327fa 2026-08-07 23:57:23 +08:00
imccyu bb61dc13f2 refactor(api): colocate gateway and remote assembly 2026-08-07 21:47:17 +08:00
imccyu 64a963da0b feat: add TypeRT remote gateway infrastructure 2026-08-07 21:47:14 +08:00
Yichen Jiang 209fb4d355 Merge remote-tracking branch 'origin/master' into worktree/default-model-persistence
# Conflicts:
#	packages/client/ui-conversation/README.i18n.yaml
#	packages/host/apiproxy/README.i18n.yaml
2026-08-07 18:18:40 +08:00
ZiyaZhang fe505e1e91 feat(web): a prose mention of a produced file opens it
The chatFileMentions service (provided by ui-deliverables beside its
turn-tail entry, reached via ctx.get) resolves inline-code tokens in the
closing message against the turn's produced locations: exact path or
unique basename links, ambiguity and unknowns stay inert. MarkdownText
gains the optional fileMentions seam — settled renders only, never
inside anchors.
2026-08-07 03:02:10 -07:00
imccyu a6ae15c0f6 Merge remote-tracking branch 'origin/master' into feat/web-workspace-file-links
# Conflicts:
#	packages/client/ui-conversation/README.i18n.yaml
#	packages/client/ui-conversation/src/client/chat/ToolRow.module.css
#	packages/host/apiproxy/src/native-path-opener.ts
2026-08-07 16:49:20 +08:00
Yichen Jiang 3c81ac1212 Merge branch 'stack/agent-profiles-5-web-ui' into stack/agent-profiles-8-authoring 2026-08-07 16:22:09 +08:00
Yichen Jiang 41938b9da6 Merge branch 'stack/agent-profiles-1-seam' into stack/agent-profiles-3-wire
# Conflicts:
#	apps/web/tests/shipped-composition.e2e.ts
2026-08-07 16:20:38 +08:00
Yichen Jiang 2a515cce94 Merge remote-tracking branch 'origin/master' into stack/agent-profiles-1-seam
# Conflicts:
#	packages/README.i18n.yaml
2026-08-07 16:19:45 +08:00
Yichen Jiang dfaf3c288e Merge remote-tracking branch 'origin/master' into claude/unified-environment-credentials-c8841a
# Conflicts:
#	apps/web/tests/models-settings.e2e.ts
#	docs/config-catalog.md
#	packages/llm/llm-deepseek/README.i18n.yaml
#	packages/llm/llm-deepseek/README.md
#	packages/llm/llm-deepseek/README.zh.md
#	packages/llm/llm-deepseek/src/index.ts
#	packages/llm/llm-pi-ai/src/config.ts
#	packages/llm/llm-pi-ai/src/index.ts
2026-08-07 15:54:13 +08:00
Yichen Jiang 53bd0a90a6 Merge pull request #1754 from deepseek-harness/worktree/web-skill-tool-row
feat(web): add dedicated skill tool row
2026-08-07 15:45:24 +08:00
Turtle d3af1d79b8 Merge origin/master into codex/dsh-badge-plugin 2026-08-07 15:33:56 +08:00
Turtle 5e5664e668 Merge remote-tracking branch 'origin/master' into task/command-feedback-master
# Conflicts:
#	.agents/notes/proposed/architecture/2026-07-27-session-projection-and-command-log.i18n.yaml
#	apps/cli/config/base.cordis.yml
#	apps/cli/package.json
#	packages/README.i18n.yaml
#	pnpm-lock.yaml
2026-08-07 14:40:42 +08:00
Yichen Jiang 9b56349ba3 Merge branch 'stack/agent-profiles-8-authoring' into stack/agent-profiles-9-rename
# Conflicts:
#	docs/config-catalog.md
#	docs/cordis-catalog/services.md
#	packages/core/tools/README.i18n.yaml
#	packages/core/tools/README.md
#	packages/core/tools/README.zh.md
#	packages/core/tools/src/index.ts
2026-08-07 13:59:20 +08:00
Yichen Jiang 9679597204 feat(ui-models): tag the provider rows this deployment declared
A row's stored profile could not tell a hand-declared gateway from a
shipped provider whose models someone narrowed — both look identical from
outside the adapter — so the Models page had no way to mark the routes a
deployment added itself.

The directory entry now carries `declared`, answered by the owning adapter
against its own installed catalog, and the page renders a Custom tag from
it. Absence stays "this adapter draws no such distinction" rather than
"shipped", so a route no adapter claims is labelled neither way.

Also records the default-route work's Agent Note and the e2e evidence for
all three changes: the composer switch writing the section, and the Models
page declaring a route with its own reasoning effort.
2026-08-07 13:49:47 +08:00
Yichen Jiang 90734c188e Merge branch 'stack/agent-profiles-7-docs' into stack/agent-profiles-8-authoring 2026-08-07 13:39:41 +08:00
Yichen Jiang f3f2129154 Merge branch 'stack/agent-profiles-2-configs' into stack/agent-profiles-3-wire
# Conflicts:
#	packages/host/apiproxy/src/api-proxy.ts
#	packages/host/apiproxy/tests/api-proxy-subagents.spec.ts
2026-08-07 13:39:29 +08:00
Yichen Jiang a7be2ecb0f Merge remote-tracking branch 'origin/master' into stack/agent-profiles-1-seam 2026-08-07 13:34:08 +08:00
Yichen Jiang 9264f383e6 Merge remote-tracking branch 'origin/master' into worktree/web-skill-tool-row
# Conflicts:
#	apps/web/tsconfig.json
#	tsconfig.host.json
2026-08-07 12:48:08 +08:00
creatixchu aa831f4f5e Merge remote-tracking branch 'origin/master' into worktree/turn-tail-actions
# Conflicts:
#	packages/client/ui-conversation/README.i18n.yaml
2026-08-07 11:55:53 +08:00
creatixchu 3c6188fb99 test(web): cover Goal actions across turns 2026-08-07 11:51:18 +08:00
Yichen Jiang df5790919c Merge branch 'master' into worktree/web-skill-tool-row 2026-08-07 11:31:49 +08:00
creatixchu fb8f5f5eb4 Merge master into fix/conversation-column-one-axis-scroll 2026-08-07 10:51:29 +08:00
creatixchu 600ee462d5 Merge remote-tracking branch 'origin/master' into worktree/turn-tail-actions 2026-08-07 10:50:49 +08:00
Yichen Jiang e72bf46f1b Merge branch 'stack/agent-profiles-8-authoring' into stack/agent-profiles-9-rename 2026-08-07 01:01:51 +08:00
Yichen Jiang f56c7d6638 Merge branch 'stack/agent-profiles-7-docs' into stack/agent-profiles-8-authoring 2026-08-07 01:01:51 +08:00
Yichen Jiang 2b7d50642c Merge branch 'stack/agent-profiles-2-configs' into stack/agent-profiles-3-wire 2026-08-07 01:01:49 +08:00
Yichen Jiang 7c7cd1aa35 Merge remote-tracking branch 'origin/master' into stack/agent-profiles-1-seam 2026-08-07 00:59:28 +08:00
Yichen Jiang 9eaa9d22a5 feat(tools): let one agent choose its tool presentation, and ship code
Code Mode was a deployment-wide field on the host `tools` row: a
deployment ran every session that way or none. The obvious product
shape — 代码模式 beside 标准/极简/创造 in the preset picker — had
nothing to hang on.

The registry itself cannot move into a preset; the agent loop's
scheduler, the api-proxy's presenters, and every tool plugin are its
consumers. So split the registry from its projection: `presentAs(mode)`
writes one cell on the calling agent's scope layer, exactly as
`restrict()` does, and the three reads that decided presentation take
that scope's mode instead of the service's. The config `mode` becomes
the default agents shadow rather than a process-wide fact.

Two consequences are load-bearing. `run_code` now enters a view only
for scopes whose own mode presents it — a native agent must not find it
dispatchable because another agent in the process does — and the
reserved name holds whatever the configured mode, since any agent may
select a code mode later.

`dsh-agent-tool-mode` is the row a preset carries to declare this. A
code mode waits for the host's `codeRuntime` rather than assuming it,
so a runtime-less deployment fails the preset at mount, naming the
row, instead of at the session's first request.

The shipped `code` preset is `standard` plus that row, ordered second.
2026-08-07 00:44:49 +08:00
Yichen Jiang 2b8a0a8cff feat(web): choose the agent preset on the new-session screen
The composer seat spent nearly all its life disabled: a session's
composition is fixed once a turn has run. Move the choice to the
new-session screen beside the workspace picker, where it still works,
and let the session header report what a running session runs.

The hero pick is staged rather than applied — that screen precedes the
session it belongs to. It lands when a session becomes current and is
still blank, which covers both the session a workspace connect creates
and the blank one it reuses; riding `sessions.create` would miss the
second. It is spent on first use, matching the workspace picker.

Fix the durability the header field claimed but never had: `agentPreset`
was declared on `SessionHeader` and dropped by the JSONL header line, the
SQLite sessions row, the derived query index, and the cold list
projection, so every resumed session came back composed from nothing.

Add the web e2e lane that would have caught it — the one lane that mounts
the shipped roster, which needed `cordis:group` in the scaffold's Loader
builtins, as `mountRootInclude` already registers.
2026-08-07 00:41:50 +08:00
Yichen Jiang 739042a804 feat(persona): make the agent persona a composable row
`dsh-system-prompt` owns the deployment persona as its own config and registers
that section unconditionally, so a process has exactly one. An agent preset
cannot mount the prompt registry itself, which means that without a row of its
own a preset could change an agent's tools but never its identity — and a
roster of presets that all sound the same is not worth having.

The row is scope-only by construction: mounted outside an agent scope it
collides with the registry's own `deployment:persona` registration and fails
loud. That is the correct shape rather than a gap, because the unscoped slot
already has an owner and this row exists to shadow it for one agent.

Two behaviours are pinned by test because both read the other way at a glance:
an empty persona still occupies the slot (shadowing the deployment persona away
entirely, then disappearing at render), and `assemble()` keeps section text
uninterpolated — `renderPrompt()` is the stage that resolves `{{…}}`.
2026-08-07 00:35:30 +08:00
Yichen Jiang fe31cd7609 Merge remote-tracking branch 'origin/master' into claude/unified-environment-credentials-c8841a
# Conflicts:
#	apps/cli/src/profile-boot.ts
#	apps/cli/src/web.ts
2026-08-06 22:34:46 +08:00
Tianyi Cui 4b1528bcec Merge retargeted parent into Claude Code subagent provider 2026-08-06 21:38:47 +08:00
Yichen Jiang 5e139ac64e Merge remote-tracking branch 'origin/master' into claude/unified-environment-credentials-c8841a
# Conflicts:
#	.agents/notes/implemented/feature/2026-07-20-dsh-cli-personal-config.i18n.yaml
#	.agents/notes/implemented/feature/2026-07-20-dsh-cli-personal-config.md
#	.agents/notes/implemented/feature/2026-07-20-dsh-cli-personal-config.zh.md
#	apps/cli/config/base.cordis.yml
#	apps/cli/package.json
#	apps/cli/reference/README.i18n.yaml
#	apps/cli/reference/README.md
#	apps/cli/reference/README.zh.md
#	apps/cli/src/app-cli-entry.ts
#	apps/cli/src/args.ts
#	apps/cli/src/bin.ts
#	apps/cli/src/config.ts
#	apps/cli/src/dump-config.ts
#	apps/cli/src/headless.ts
#	apps/cli/src/web.ts
#	apps/cli/tests/args.spec.ts
#	apps/cli/tests/built-bin.e2e.ts
#	apps/cli/tests/headless-shutdown.e2e.ts
#	apps/cli/tsconfig.json
#	docs/user/guide/config.i18n.yaml
#	docs/user/guide/config.md
#	docs/user/guide/config.zh.md
#	examples/mcp-memory/README.i18n.yaml
#	examples/mcp-memory/README.md
#	examples/mcp-memory/README.zh.md
#	packages/bundle/web-app/cordis.patch.yml
#	packages/cordis/repository-plugin/README.i18n.yaml
#	packages/cordis/repository-plugin/README.md
#	packages/cordis/repository-plugin/README.zh.md
#	packages/credentials/credentials-local/README.i18n.yaml
#	packages/credentials/credentials-local/README.md
#	packages/credentials/credentials-local/README.zh.md
#	packages/ui/app-boot/README.i18n.yaml
#	packages/ui/app-boot/README.md
#	packages/ui/app-boot/README.zh.md
#	packages/ui/app-boot/src/index.ts
#	packages/ui/app-boot/tests/config-reload.spec.ts
#	packages/ui/app-boot/tests/user-patches.spec.ts
#	pnpm-lock.yaml
2026-08-06 21:34:45 +08:00
Tianyi Cui 0748ebee39 Merge latest master into Codex subagent provider 2026-08-06 21:26:31 +08:00
ZiyaZhang 4767285694 Merge remote-tracking branch 'upstream/master' into feat/web-workspace-file-links
# Conflicts:
#	apps/cli/package.json
#	pnpm-lock.yaml
2026-08-06 06:24:53 -07:00
Yichen Jiang 18fe174897 feat(agent-presets): compose each session's agent from a preset cordis.yml
A preset is a directory holding one `agent.cordis.yml`. Mounting it under an
agent's scope context during `setup(agentCtx)` gives that one session its own
tools and prompt sections while every other live session keeps its own.

No registry gains a tier. `dsh-tools` and `dsh-system-prompt` already file
registrations into the calling context's scope layer, and entry contexts chain
to the context a subtree was plugged into, so a composition mounted under
`agent.ctx` is that agent's alone and unwinds with it.

The mount audits itself because a directly-plugged subtree is absent from
`ctx.loader.entries()` and no boot audit covers it. It rejects an unscoped
target, a row that never became usable, and a row that published a service into
the root service realm — that last one is process-global rather than
per-session, and its collision with the next session surfaces as an unhandled
rejection `setup` never observes, leaving a half-composed agent that looks
healthy. The package invariant re-checks that rule on every service
notification, since a row publishing from a timer would escape a one-shot audit.

Raises the `packages/README.md` word ceiling from 920 to 980: the group table
must enumerate every group, and the new `preset/` row is necessary content.

Design: .agents/notes/implemented/architecture/2026-08-03-per-session-agent-presets.md
2026-08-06 21:03:18 +08:00
Yichen Jiang c16cad7c03 Merge remote-tracking branch 'origin/master' into worktree/web-skill-tool-row
# Conflicts:
#	packages/host/apiproxy/README.i18n.yaml
2026-08-06 20:41:13 +08:00
Turtle a51b88d2aa Merge remote-tracking branch 'origin/master' into feat/profile-plugin-management
# Conflicts:
#	apps/cli/src/headless.ts
#	docs/event-producer-consumer.md
#	packages/host/apiproxy/README.i18n.yaml
2026-08-06 20:03:22 +08:00
ZiyaZhang 3742214226 Merge remote-tracking branch 'upstream/master' into feat/web-workspace-file-links
# Conflicts:
#	apps/web/tsconfig.json
#	packages/client/connection/README.i18n.yaml
#	packages/client/connection/README.zh.md
#	packages/client/ui-conversation/README.i18n.yaml
#	packages/client/ui-conversation/src/client/chat/AssistantMarkdown.tsx
#	packages/client/ui-conversation/src/client/chat/ChatView.tsx
#	packages/client/ui-conversation/src/client/chat/ToolRow.module.css
#	packages/client/ui-conversation/src/client/chat/chat-flow.ts
#	packages/client/ui-conversation/tests/chat-view.spec.tsx
#	packages/host/apiproxy/src/native-path-opener.ts
#	tsconfig.host.json
2026-08-06 04:44:13 -07:00
creatixchu 53e210348d fix(web): grant turn-tail IconActions only after the turn ends
`assistantActionsSeqs` picked the last content-text assistant of each turn
from the finalized transcript alone. That quantity is stable only once the
turn closes: while a turn is still producing steps, the narration written
before a tool call is the last content assistant so far, so copy, branch,
and the clock appeared under an intermediate sentence for as long as the
tool ran and then moved down to the next step's text.

Pass `ConversationSnapshot.turnEnds` into the derivation and grant the row
only inside a turn that has a durable `turn/end`. This is the same
completion fact the branch control and the `Ran for` label already read, so
the three parts of one row now agree; mid-turn narration owns nothing, and
the seat appears once under the settled answer.

`hasContentText` moves to chat-flow.ts so the ownership gate and
AssistantMarkdown's mount gate cannot drift apart.

apps/web/tests/turn-tail-actions.e2e.ts pins both states through the
assembled application: a hang sidecar on the second model call parks a turn
whose first step narrated before calling bash, and the two goldens hold the
parked flow and the flow after stopping.
2026-08-06 16:42:41 +08:00
creatixchu 12e5fe0330 Merge master into fix/conversation-column-one-axis-scroll 2026-08-06 16:39:59 +08:00