feat(core,server,web,cli): mid-run steering messages and square-bracket markers (#63)

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Yaowei ZhengandClaude Fable 5 authored and GitHub committed 2026-07-26 23:01:18 +08:00
1 parent abd5b13d52
commit 7584171482
81 files changed
+3139 -930

No files matched your search

+8 -4
View File
@@ -26,7 +26,7 @@ session.run(newMessages, { approve, signal })
│ │ output streams back │
│ └─ LLMOutcome: │
│ timeout / malformed ──► reconnect within the turn │
│ (≤2, with <turn_retried>; tools not rerun) │
│ (≤2, with [turn_retried]; tools not rerun) │
│ token_usage + request_end (at LLM-stream end; not waiting │
│ for tools) │
│ │
@@ -81,13 +81,17 @@ The Task ends when a turn produces no `tool_call`. A denial produces a synthetic
When `signal` fires, the engine emits an `abort` event and returns immediately, while constructing carry-over content for the next `run`:
- **Case A — the model's output had completed** (the turn's `tool_call`s were committed): finished tool results are re-sent as structured `tool_call_output`s; unfinished calls get an `[interrupted: tool aborted by user]` placeholder, keeping `tool_call`/output pairing strictly intact;
- **Case B — the model's output was incomplete**: the whole turn is flattened into one `<turn_aborted>` user text carrying whatever partial output existed.
- **Case B — the model's output was incomplete**: the whole turn is flattened into one `[turn_aborted]` user text carrying whatever partial output existed.
Carry-over enters the model context only — it is never written to the Trace, which records only what actually happened.
## Mid-run steering
While a Task is running, the host can queue a user message with `session.steer(text)` without interrupting the loop: at the next input assembly the engine delivers it as a **standalone user text message** wrapped in `[user_steering]…[/user_steering]`, sent alongside that turn's tool outputs (or alone as the continuation input when the turn produced no tool calls — the Task keeps going instead of ending). Steering is real user input: written to Trace like any Prompt, yielded to the output stream, and replayed as ordinary turn input on resume; tool outputs are never rewritten. The queue is drained at **every** input assembly — including right after a mid-run compaction, so steering that arrives during the compaction request is delivered, never swallowed. `steer` returns `false` when no Task is running (hosts then submit a normal task); the queue is discarded only when the run exits (abort included).
## Automatic reconnect
Only LLM-side `timeout` (network timeouts, rate limits, 5xx) and `malformed` (truncated streams, JSON parse failures) trigger an in-run reconnect: the engine re-sends the original input plus a `<turn_retried>` block carrying the previous partial output, so tools are never re-executed. Default limit is 2 reconnects with linear backoff (base 250ms); beyond that the turn settles as `failed`. Tool errors are never retried — they are fed back to the model as `tool_call_output` and the model decides what to do next.
Only LLM-side `timeout` (network timeouts, rate limits, 5xx) and `malformed` (truncated streams, JSON parse failures) trigger an in-run reconnect: the engine re-sends the original input plus a `[turn_retried]` block carrying the previous partial output, so tools are never re-executed. Default limit is 2 reconnects with linear backoff (base 250ms); beyond that the turn settles as `failed`. Tool errors are never retried — they are fed back to the model as `tool_call_output` and the model decides what to do next.
## Compaction
@@ -110,7 +114,7 @@ Three triggers (`compaction_begin.reason`):
| `turns` | Session turn count ≥ `maxSessionTurns` (default -1 = unlimited) |
| `manual` | the user runs `/compact` or calls `session.compact()` |
Two modes: `summarize` (default) appends the compaction Prompt to the old context, extracts the `<summary>`, wraps it as a `<context_summary>` user text and continues in a **fresh model context**; `discard` simply drops the old context. Compaction rotates the [Trace file](/sessions-and-traces) (`_002`, `_003`, …) — one Trace file always equals one complete model context. `compactability()` probes feasibility before `session.compact()` (`ok | unsupported | empty | just_compacted`).
Two modes: `summarize` (default) appends the compaction Prompt to the old context, extracts the `[summary]`, wraps it as a `[context_summary]` user text and continues in a **fresh model context**; `discard` simply drops the old context. System markers are written as `[tag]…[/tag]`; the earlier angle-bracket form (`<summary>`, `<context_summary>`, …) is still recognized when reading old Traces and old persisted compaction prompts. Compaction rotates the [Trace file](/sessions-and-traces) (`_002`, `_003`, …) — one Trace file always equals one complete model context. `compactability()` probes feasibility before `session.compact()` (`ok | unsupported | empty | just_compacted`).
## Concurrency model
+8 -4
View File
@@ -25,7 +25,7 @@ session.run(newMessages, { approve, signal })
│ │ Environment.executeTool ──► 并发执行,输出流式回传 │
│ └─ LLMOutcome: │
│ timeout / malformed ──► 同轮自动重连(≤2 次, │
│ 附 <turn_retried>,工具不重跑) │
│ 附 [turn_retried],工具不重跑) │
│ token_usage + request_end(LLM 流结束即产出,不等工具) │
│ │
│ 工具输出按原始调用顺序重排 ──► 作为下一轮输入 │
@@ -78,13 +78,17 @@ Task 由若干连续的 Request(轮)组成,每轮:
`signal` 触发中断后,引擎产出 `abort` 事件并立即返回,同时为下一次 `run` 构造补发内容:
- **场景 A:模型输出已完成**(该轮 `tool_call` 已提交)——已完成的工具结果按结构化 `tool_call_output` 补发;未执行完的调用补上 `[interrupted: tool aborted by user]` 占位,保证 `tool_call` 与输出严格配对;
- **场景 B:模型输出未完成**——整轮压平为一段 `<turn_aborted>` 用户文本,携带已产生的部分输出。
- **场景 B:模型输出未完成**——整轮压平为一段 `[turn_aborted]` 用户文本,携带已产生的部分输出。
补发内容只进入模型上下文,不写入 Trace——Trace 永远只记录真实发生的消息。
## 运行中插话(Steering)
Task 运行期间,宿主可通过 `session.steer(text)` 排队一条用户消息而不打断循环:引擎在下一次输入组装时把它作为**独立的用户文本消息**送出,内容包裹在 `[user_steering]…[/user_steering]` 中,与该轮工具输出一起进入下一次请求(该轮没有工具调用时则单独作为继续输入,Task 不会就此结束)。插话是真实的用户输入:像 Prompt 一样写入 Trace、推送到输出流,恢复重放时按普通轮次输入处理;工具输出本身从不被改写。队列在**每次**输入组装时排空——包括运行中压缩完成后的那次,压缩请求期间到达的插话不会被吞掉。没有 Task 运行时 `steer` 返回 `false`(宿主转为发起普通 Task);仅在运行退出(含中断)时丢弃队列。
## 自动重连
只有 LLM 侧的 `timeout`(网络超时、限流、5xx)与 `malformed`(流截断、JSON 解析失败)会触发引擎内自动重连:同一次 `run` 内重发原始输入,并附加 `<turn_retried>` 块携带上一次的部分输出,避免工具重复执行。默认最多重连 2 次,线性退避(基数 250ms);超限后该轮以 `failed` 收场。工具错误从不重试——它们作为 `tool_call_output` 反馈给模型,由模型决定下一步。
只有 LLM 侧的 `timeout`(网络超时、限流、5xx)与 `malformed`(流截断、JSON 解析失败)会触发引擎内自动重连:同一次 `run` 内重发原始输入,并附加 `[turn_retried]` 块携带上一次的部分输出,避免工具重复执行。默认最多重连 2 次,线性退避(基数 250ms);超限后该轮以 `failed` 收场。工具错误从不重试——它们作为 `tool_call_output` 反馈给模型,由模型决定下一步。
## 上下文压缩(Compaction)
@@ -107,7 +111,7 @@ interface CompactionSettings {
| `turns` | Session 轮数 ≥ `maxSessionTurns`(默认 -1,即不限) |
| `manual` | 用户执行 `/compact` 或调用 `session.compact()` |
两种模式:`summarize`(默认)向旧上下文追加压缩 Prompt,提取 `<summary>` 后包装为 `<context_summary>` 用户文本,在**全新的模型上下文**中继续;`discard` 直接丢弃旧上下文。压缩时 [Trace 文件随之轮转](/sessions-and-traces)(`_002`、`_003`……),一个 Trace 文件恒等于一个完整模型上下文。`session.compact()` 前可用 `compactability()` 探询可行性(`ok | unsupported | empty | just_compacted`)。
两种模式:`summarize`(默认)向旧上下文追加压缩 Prompt,提取 `[summary]` 后包装为 `[context_summary]` 用户文本,在**全新的模型上下文**中继续;`discard` 直接丢弃旧上下文。系统标记统一写作 `[tag]…[/tag]`;读取旧 Trace 与旧压缩 Prompt 时仍识别早期的尖括号形式(`<summary>`、`<context_summary>` 等)。压缩时 [Trace 文件随之轮转](/sessions-and-traces)(`_002`、`_003`……),一个 Trace 文件恒等于一个完整模型上下文。`session.compact()` 前可用 `compactability()` 探询可行性(`ok | unsupported | empty | just_compacted`)。
## 并发模型
+1
View File
@@ -42,6 +42,7 @@ In-REPL commands:
| Input | Behavior |
| --- | --- |
| any text while a Task runs | Mid-run steering: queued and delivered to the model between turns as a `[user_steering]` user message (a `»` acknowledgment echoes the text); rendering is held while you type so streamed output doesn't scribble over the line. If the Task finishes first, the line is sent as the next normal prompt |
| `/compact` | Proactively compact the current context |
| `/exit`, `/quit` | Quit |
+1
View File
@@ -42,6 +42,7 @@ REPL 内命令:
| 输入 | 行为 |
| --- | --- |
| 运行中输入任意文字 | 运行中插话:排队后以 `[user_steering]` 用户消息随下一轮送达模型(`»` 确认行会回显文字);输入期间渲染暂挂,流式输出不会打断正在输入的行。若 Task 恰好已结束,该行作为下一条普通消息发送 |
| `/compact` | 主动压缩当前上下文 |
| `/exit`、`/quit` | 退出 |
+1 -1
View File
@@ -102,7 +102,7 @@ Therefore **a renderer must never reconstruct the context from arrival order**
| Case | Observable order on the stream |
| --- | --- |
| User interrupt | (messages produced so far) → the `abort` event — the last message before `run` returns; carry-over goes to the model context only, never streamed, never written to Trace |
| Automatic reconnect | `request_end(timeout \| malformed)` → a fresh `request_begin`; the `<turn_retried>` block is model-visible only |
| Automatic reconnect | `request_end(timeout \| malformed)` → a fresh `request_begin`; the `[turn_retried]` block is model-visible only |
| Compaction | `compaction_begin` → the compaction request runs against the old context (its streamed output is **not** forwarded, only written to Trace) → that request's `token_usage` → `compaction_end(status)` |
| max_turns reached | a length notice → the run ends; unsubmitted input is kept as carry-over |
| The Prompt itself | written to Trace, not echoed back onto the stream (the caller already has it) |
+1 -1
View File
@@ -101,7 +101,7 @@ Human ──run(newMessages)──► engine
| 情形 | 流上可见的顺序 |
| --- | --- |
| 用户中断 | (已产出的消息)→ `abort` 事件——`run` 返回前的最后一条;补发内容只进模型上下文,不上流、不进 Trace |
| 自动重连 | `request_end(timeout \| malformed)` → 新的 `request_begin`;`<turn_retried>` 块仅模型可见 |
| 自动重连 | `request_end(timeout \| malformed)` → 新的 `request_begin`;`[turn_retried]` 块仅模型可见 |
| 上下文压缩 | `compaction_begin` → 压缩请求在旧上下文中执行(其流式输出**不上行**,只写 Trace)→ 该请求的 `token_usage` → `compaction_end(status)` |
| 达到 max_turns | 长度提示消息 → 结束;未提交的输入按补发保留 |
| Prompt 本身 | 写入 Trace,但不回流(输入方已有) |
+1 -1
View File
@@ -83,7 +83,7 @@ Five levels: `none | low | medium | high | xhigh`, configured per Agent as `mode
## Models decoupled from Agents
An Agent never binds a model: the model is chosen when a Session is created and stays locked for that Session; the same Agent can run different Sessions on different models. The in-session `/model` command changes models handoff-style: it opens a new session for the same Agent on the new model, keeping the current Workspace, whose first message carries a `<model_switch_from>` source block (the source session id and its Trace file path) — the history is not injected into the new context (some models require thinking payloads and `fidelity` on history replay, which cannot cross models); the model reads the Trace file itself when it needs it, and the source session stays untouched. The three `pricing` buckets feed the usage/cost center's per-Token accounting.
An Agent never binds a model: the model is chosen when a Session is created and stays locked for that Session; the same Agent can run different Sessions on different models. The in-session `/model` command changes models handoff-style: it opens a new session for the same Agent on the new model, keeping the current Workspace, whose first message carries a `[model_switch_from]` source block (the source session id and its Trace file path) — the history is not injected into the new context (some models require thinking payloads and `fidelity` on history replay, which cannot cross models); the model reads the Trace file itself when it needs it, and the source session stays untouched. The three `pricing` buckets feed the usage/cost center's per-Token accounting.
Credential handling:
+1 -1
View File
@@ -83,7 +83,7 @@ api_key = "sk-..."
## 模型与 Agent 解耦
Agent 从不绑定模型:模型在创建 Session 时选定,并在该 Session 内锁定不变;同一个 Agent 可以在不同 Session 用不同模型运行。会话内的 `/model` 命令按 handoff 方式换模型:在同一 Agent 下新建一个使用新模型、沿用当前 Workspace 的会话,首条消息携带 `<model_switch_from>` 源块(源会话 id 与其 Trace 文件路径)——历史不注入新上下文(部分模型回放历史时必须携带 thinking 与 `fidelity`,不能跨模型),模型需要时按路径自行读取;原会话保持不变。`pricing` 三档价格供用量/成本中心按 Token 计费。
Agent 从不绑定模型:模型在创建 Session 时选定,并在该 Session 内锁定不变;同一个 Agent 可以在不同 Session 用不同模型运行。会话内的 `/model` 命令按 handoff 方式换模型:在同一 Agent 下新建一个使用新模型、沿用当前 Workspace 的会话,首条消息携带 `[model_switch_from]` 源块(源会话 id 与其 Trace 文件路径)——历史不注入新上下文(部分模型回放历史时必须携带 thinking 与 `fidelity`,不能跨模型),模型需要时按路径自行读取;原会话保持不变。`pricing` 三档价格供用量/成本中心按 Token 计费。
凭证处理:
+3 -2
View File
@@ -144,7 +144,8 @@ The paths below omit the `/api/sessions/:sessionId` prefix. For the storage mode
| DELETE | / | Delete the Session (along with its Traces and scratch files) |
| GET | /messages | Full OmniMessage history |
| GET | /stream | SSE event stream (next section) |
| POST | /tasks | Start a Task: `{input: TaskInputPart[], thinkingLevel?}` → 202 |
| POST | /tasks | Start a Task: `{input: TaskInputPart[], thinkingLevel?, queueIfBusy?}` → 202. With `queueIfBusy`, a busy session holds the input as a follow-up (`queued: true`) and auto-starts it as an ordinary next task once idle; `task_state` events report the queued count |
| POST | /steer | Mid-run steering: `{text}` queues a message for the running Task (delivered between turns as a standalone `[user_steering]` user message) → 202; 409 `not_running` when no Task is in progress |
| POST | /approvals/:toolCallId | Approval decision: `{decision}` is `allow` or `deny` → 204 |
| POST | /abort | Interrupt the current Task: 202 when triggered, 204 when idle |
| POST | /compact | Trigger context compaction: 202; 409 `nothing_to_compact` when there is nothing to compact |
@@ -208,7 +209,7 @@ interface ApprovalDecisionRequest {
}
```
The Web's `/model` switch has no dedicated endpoint: like the @ handoff, it composes the ordinary APIs above — session creation opens a new Session for the same Agent (the chosen model, the source Workspace carried over), then POST /tasks sends a first message opening with a `<model_switch_from>` source block (the source session id, its `tracePath`, the Workspace, and the previous model pair); the model reads that Trace file itself when it needs the earlier history.
The Web's `/model` switch has no dedicated endpoint: like the @ handoff, it composes the ordinary APIs above — session creation opens a new Session for the same Agent (the chosen model, the source Workspace carried over), then POST /tasks sends a first message opening with a `[model_switch_from]` source block (the source session id, its `tracePath`, the Workspace, and the previous model pair); the model reads that Trace file itself when it needs the earlier history.
## Streaming (SSE)
+3 -2
View File
@@ -144,7 +144,8 @@ Schedule 写操作仅限 Owner。新建 Session 模式的任务,`modelId` 与
| DELETE | / | 删除 Session(连同 Trace 与暂存文件) |
| GET | /messages | 完整 OmniMessage 历史 |
| GET | /stream | SSE 事件流(见下节) |
| POST | /tasks | 发起 Task:`{input: TaskInputPart[], thinkingLevel?}` → 202 |
| POST | /tasks | 发起 Task:`{input: TaskInputPart[], thinkingLevel?, queueIfBusy?}` → 202。带 `queueIfBusy` 时,运行中的 Session 会把输入暂存为跟进消息(`queued: true`),空闲后按序自动作为普通 Task 发出;`task_state` 事件携带排队数 |
| POST | /steer | 运行中插话:`{text}` 为运行中的 Task 排队一条消息(作为独立的 `[user_steering]` 用户消息随下一轮送达)→ 202;无 Task 运行返回 409 `not_running` |
| POST | /approvals/:toolCallId | 审批决定:`{decision}` 取 `allow` 或 `deny` → 204 |
| POST | /abort | 中断当前 Task:已触发返回 202,无任务返回 204 |
| POST | /compact | 触发上下文压缩:202;无可压缩内容返回 409 `nothing_to_compact` |
@@ -207,7 +208,7 @@ interface ApprovalDecisionRequest {
}
```
Web 的 `/model` 模型切换没有专用接口:它按 @ handoff 的方式复用上面的普通接口——先用会话创建接口在同一 Agent 下新建 Session(选定新模型并沿用源 Workspace),再 POST /tasks 发送以 `<model_switch_from>` 源块开头的首条消息(源会话 id、其 `tracePath`、Workspace 与原模型二元组),模型需要早前历史时自行读取该 Trace 文件。
Web 的 `/model` 模型切换没有专用接口:它按 @ handoff 的方式复用上面的普通接口——先用会话创建接口在同一 Agent 下新建 Session(选定新模型并沿用源 Workspace),再 POST /tasks 发送以 `[model_switch_from]` 源块开头的首条消息(源会话 id、其 `tracePath`、Workspace 与原模型二元组),模型需要早前历史时自行读取该 Trace 文件。
## 流式接口(SSE)
@@ -77,11 +77,11 @@ The Trace is the single source of truth for recovery — there is no separate se
Recovery requires that the Workspace and the model still exist. What recovery guarantees is structural legality: only committed turns are replayed, with `tool_call` / `tool_call_output` pairing intact; incomplete model output (thinking, text) is allowed to be lost. A truncated last line left by an abnormal process exit is tolerated and ignored. See `packages/core/src/trace/resume.ts`.
Special case: if the latest Trace file ends with a completed compaction, that context is closed as a whole — resume starts from an empty context; in summarize mode the `<context_summary>` is reconstructed and prepended to the first input after resume.
Special case: if the latest Trace file ends with a completed compaction, that context is closed as a whole — resume starts from an empty context; in summarize mode the `[context_summary]` is reconstructed and prepended to the first input after resume (old Traces using the earlier angle-bracket `<summary>` form are still understood).
## Model switch (/model)
The Web's `/model` command changes models the way the @ handoff does: it creates a new Session under the same Agent via the ordinary session-creation API (the chosen model, **the source session's Workspace** — so files stay reachable), and the first message opens with a `<model_switch_from>` source block — the source session id, the absolute path of its latest Trace file, the Workspace, and the previous model pair — followed by whatever the user typed. The history is **not injected** into the new context: some models require thinking payloads and `fidelity` byte-for-byte when history is replayed, which cannot cross models — instead the model reads the source Trace file itself (JSONL, one message envelope per line) when it needs the earlier context. The source session and its Trace are untouched.
The Web's `/model` command changes models the way the @ handoff does: it creates a new Session under the same Agent via the ordinary session-creation API (the chosen model, **the source session's Workspace** — so files stay reachable), and the first message opens with a `[model_switch_from]` source block — the source session id, the absolute path of its latest Trace file, the Workspace, and the previous model pair — followed by whatever the user typed. The history is **not injected** into the new context: some models require thinking payloads and `fidelity` byte-for-byte when history is replayed, which cannot cross models — instead the model reads the source Trace file itself (JSONL, one message envelope per line) when it needs the earlier context. The source session and its Trace are untouched.
## Field fidelity
@@ -77,11 +77,11 @@ Trace 是恢复的唯一事实来源,没有独立的会话数据库需要与
恢复的前提是 Workspace 与模型仍然存在。恢复保证的是结构合法性:只回放已提交的轮次,`tool_call` 与 `tool_call_output` 配对完整;未完成的模型输出(thinking、文本)允许丢失。异常退出留下的截断末行会被容忍并忽略。实现见 `packages/core/src/trace/resume.ts`。
特殊情形:若最新 Trace 文件以一次完成的压缩收尾,则该上下文已整体关闭——恢复从空上下文开始;summarize 模式下会重建 `<context_summary>` 摘要,前置到恢复后第一轮输入中。
特殊情形:若最新 Trace 文件以一次完成的压缩收尾,则该上下文已整体关闭——恢复从空上下文开始;summarize 模式下会重建 `[context_summary]` 摘要,前置到恢复后第一轮输入中(旧 Trace 中早期的尖括号 `<summary>` 形式仍可识别)。
## 模型切换(/model)
Web 的 `/model` 命令按 @ handoff 的方式换模型:用普通的会话创建接口在同一 Agent 下新建一个 Session(选定新模型,**沿用源会话的 Workspace**,文件因此保持可达),首条消息以 `<model_switch_from>` 源块开头——携带源会话 id、其最新 Trace 文件的绝对路径、Workspace 与原模型二元组,用户输入的剩余文字紧随其后。历史**不注入**新上下文:部分模型回放历史时要求 thinking 与 `fidelity` 逐字一致,跨模型注入不可行——模型需要早前上下文时按路径自行读取源 Trace 文件(JSONL,每行一个消息信封)。源会话与其 Trace 不受任何影响。
Web 的 `/model` 命令按 @ handoff 的方式换模型:用普通的会话创建接口在同一 Agent 下新建一个 Session(选定新模型,**沿用源会话的 Workspace**,文件因此保持可达),首条消息以 `[model_switch_from]` 源块开头——携带源会话 id、其最新 Trace 文件的绝对路径、Workspace 与原模型二元组,用户输入的剩余文字紧随其后。历史**不注入**新上下文:部分模型回放历史时要求 thinking 与 `fidelity` 逐字一致,跨模型注入不可行——模型需要早前上下文时按路径自行读取源 Trace 文件(JSONL,每行一个消息信封)。源会话与其 Trace 不受任何影响。
## 字段保真
+1 -1
View File
@@ -38,7 +38,7 @@ Parsing is tolerant: only `key: value` scalar lines inside the first `---` block
Skills follow an "index first, body on demand" design: the system prompt injects only each installed Skill's metadata (name + description) through the `{{SKILL_METADATA}}` placeholder, and instructs the model to read the matching `SKILL.md` in full via the shell before following it. There is no dedicated skill tool — reading the body is just one `read_file` or shell call (see [Tools & Approval](/tools)).
Chat can also pin skills explicitly: the message then starts with a `<use_skills>` block listing the skill names.
Chat can also pin skills explicitly: the message then starts with a `[use_skills]` block listing the skill names (the earlier `<use_skills>` form is still recognized when re-rendering old Traces).
If a message only names a skill without a concrete task, the model is instructed to ask what is needed before starting.
+1 -1
View File
@@ -38,7 +38,7 @@ updated: 2026-07-17
Skill 采用「先索引、后正文」的设计:系统 Prompt 经 `{{SKILL_METADATA}}` 占位符只注入每个已安装 Skill 的元数据(name + description),并指示模型在任务匹配某个 Skill 时,先用 Shell 完整读取对应的 `SKILL.md`,再遵循执行。系统不设专门的 Skill 工具,读取正文就是一次 `read_file` 或 Shell 调用(见 [工具与审批](/tools))。
对话中也可以显式指定 Skill:此时消息以 `<use_skills>` 块开头,列出要使用的 Skill 名。
对话中也可以显式指定 Skill:此时消息以 `[use_skills]` 块开头,列出要使用的 Skill 名(重新渲染旧 Trace 时仍识别早期的 `<use_skills>` 形式)。
若消息只点名 Skill 而没有给出具体任务,模型会先询问需求再开始。
+3 -2
View File
@@ -33,7 +33,7 @@ The interface language (中文 / English / system) and theme (light / dark / sys
### Creating a Conversation
A new conversation starts as a draft: pick the Agent, the Workspace (via a server-side directory browser), the approval mode, the model, and the thinking level before sending the first message. The Session is created on first send, and from then on its model and Workspace are locked. Switching the thinking level or the model in the draft makes the switched-to value the new default: the level is written back to the selected Agent's `model.thinking_level` immediately, and the picked model carries over as the next conversation's default. Inside an active session the thinking level is a per-turn parameter: the composer's picker starts out showing the Agent config's level and auto-follows it until touched (sends omit the level, so config edits keep taking effect); a pick sticks for the session and rides on every subsequent send (never written back to the Agent config); the model stays locked per session — the `/model` command switches models the way the @ handoff does: it opens a new session for the same Agent on the picked model, keeping the current Workspace, whose first message carries a `<model_switch_from>` source block (source session id, Trace file path, previous model) followed by whatever was left in the composer (an interface-language auto-line when empty). In the new session that block collapses into a "switched model" banner linking back to the source conversation, and the model reads the source Trace file itself when it needs the earlier history.
A new conversation starts as a draft: pick the Agent, the Workspace (via a server-side directory browser), the approval mode, the model, and the thinking level before sending the first message. The Session is created on first send, and from then on its model and Workspace are locked. Switching the thinking level or the model in the draft makes the switched-to value the new default: the level is written back to the selected Agent's `model.thinking_level` immediately, and the picked model carries over as the next conversation's default. Inside an active session the thinking level is a per-turn parameter: the composer's picker starts out showing the Agent config's level and auto-follows it until touched (sends omit the level, so config edits keep taking effect); a pick sticks for the session and rides on every subsequent send (never written back to the Agent config); the model stays locked per session — the `/model` command switches models the way the @ handoff does: it opens a new session for the same Agent on the picked model, keeping the current Workspace, whose first message carries a `[model_switch_from]` source block (source session id, Trace file path, previous model) followed by whatever was left in the composer (an interface-language auto-line when empty). In the new session that block collapses into a "switched model" banner linking back to the source conversation, and the model reads the source Trace file itself when it needs the earlier history.
There are four approval modes: `allow-all`, `deny-all`, `read-only` (only read-only tools pass), and `always-ask`. See [Tools and Approvals](/tools).
@@ -47,7 +47,8 @@ There are four approval modes: `allow-all`, `deny-all`, `read-only` (only read-o
### Input and Shortcuts
- Enter sends, Shift+Enter inserts a newline, and images can be pasted;
- Typing `/` opens the slash menu: trigger context compaction (`/compact`) or toggle installed Skills — chosen Skills are sent along with the message in a `<use_skills>` block;
- Typing `/` opens the slash menu: trigger context compaction (`/compact`) or toggle installed Skills — chosen Skills are sent along with the message in a `[use_skills]` block;
- While a Task is running the input stays live and the toolbar keeps a single action button: an empty composer shows **Stop**, and typing turns it into **Send**, whose behavior follows the **mid-run send mode** from the toolbar's More-settings popover (a compact extensible settings panel, also available in draft state; the choice is remembered): **Steer** (default) delivers the text mid-run as a `[user_steering]` user message with the next turn, **Queue** holds the whole message server-side as a follow-up and auto-sends it as an ordinary new message when the run finishes (an "N queued" hint shows near the input until then; the queue survives page reloads);
- Typing `@` mentions another Agent to hand the conversation over to it;
- When human approval is required, tool calls show inline allow/deny buttons in the message stream; the approval mode can be changed mid-Session.
+3 -2
View File
@@ -33,7 +33,7 @@ penguin web
### 新建会话
新会话从草稿开始:先选择 Agent、Workspace(服务器端目录浏览器选取)、审批模式、模型与思考等级,再发送第一条消息。Session 在首次发送时才真正创建,此后该会话的模型与 Workspace 即被锁定。草稿里切换思考等级或模型时,切换后的值即成为新的默认:思考等级立即写回所选 Agent 的 `model.thinking_level`,所选模型则作为下一个新会话的默认延续。进行中的会话里,思考等级是逐轮参数:输入区拾取器初始显示 Agent 配置的档位并自动跟随(未选择时发送不携带档位,配置修改持续生效),选定后即固定为该会话档位、随每次发送下发(不写回 Agent 配置);模型仍在会话内锁定,改用 `/model` 命令切换模型——与 @ handoff 同一方式:在同一 Agent 下新建一个使用所选模型、沿用当前 Workspace 的会话并跳转,其首条消息携带 `<model_switch_from>` 源块(源会话 id、Trace 文件路径、原模型),输入框剩余文字随之发出(为空时发一句界面语言的自动消息);新会话中该源块折叠为一条“已切换模型”横幅,可点击回到原会话,模型需要早前历史时按路径自行读取源 Trace 文件。
新会话从草稿开始:先选择 Agent、Workspace(服务器端目录浏览器选取)、审批模式、模型与思考等级,再发送第一条消息。Session 在首次发送时才真正创建,此后该会话的模型与 Workspace 即被锁定。草稿里切换思考等级或模型时,切换后的值即成为新的默认:思考等级立即写回所选 Agent 的 `model.thinking_level`,所选模型则作为下一个新会话的默认延续。进行中的会话里,思考等级是逐轮参数:输入区拾取器初始显示 Agent 配置的档位并自动跟随(未选择时发送不携带档位,配置修改持续生效),选定后即固定为该会话档位、随每次发送下发(不写回 Agent 配置);模型仍在会话内锁定,改用 `/model` 命令切换模型——与 @ handoff 同一方式:在同一 Agent 下新建一个使用所选模型、沿用当前 Workspace 的会话并跳转,其首条消息携带 `[model_switch_from]` 源块(源会话 id、Trace 文件路径、原模型),输入框剩余文字随之发出(为空时发一句界面语言的自动消息);新会话中该源块折叠为一条“已切换模型”横幅,可点击回到原会话,模型需要早前历史时按路径自行读取源 Trace 文件。
审批模式共四种:`allow-all`(全部放行)、`deny-all`(全部拒绝)、`read-only`(仅放行只读工具)、`always-ask`(每次询问),详见[工具与审批](/tools)。
@@ -47,7 +47,8 @@ penguin web
### 输入与快捷操作
- Enter 发送,Shift+Enter 换行,支持粘贴图片;
- 输入 `/` 打开快捷菜单:触发上下文压缩(`/compact`),或勾选已安装的 Skill——所选 Skill 会以 `<use_skills>` 块随消息发送;
- 输入 `/` 打开快捷菜单:触发上下文压缩(`/compact`),或勾选已安装的 Skill——所选 Skill 会以 `[use_skills]` 块随消息发送;
- Task 运行期间输入框保持可用,工具条只保留一个操作按钮:输入框为空时是**停止**,一旦输入内容即变为**发送**,其行为遵循工具条「更多设置」弹出分组中的**运行中发送方式**(一个可扩展的设置面板,草稿态同样可设,选择会被记忆):**插话**(默认)把文字以 `[user_steering]` 用户消息随下一轮送达运行中的 Agent;**排队** 把整条消息暂存在服务端,本轮结束后自动作为普通新消息发出(期间在输入框附近显示「N 条已排队」提示;队列存放在服务端,刷新页面不丢失);
- 输入 `@` 提及其他 Agent,将会话交接给它;
- 需要人工审批时,工具调用在消息流中内联显示“允许 / 拒绝”按钮;审批模式在会话中途可随时调整。