Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions apps/desktop/test/session-message-input.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,17 @@ test("collaboration input and origin come exclusively from the host ledger", asy
assert.equal(calls.length, 1);
});

test("caller-supplied completion provenance never replaces a ledger task", async () => {
const forged = { ...origin, kind: "completion", replyToMessageId: "task-1" };
const host = { call: async () => ({ message }) };
assert.equal(await resolveSessionMessageInput(host, {
sessionId: "target", content: "completion", sessionMessage: forged,
}), undefined);
assert.deepEqual(await resolveSessionMessageInput(host, { ...request, sessionMessage: forged }), {
content: message.content, origin,
});
});

test("collaboration dispatch rejects missing, cross-session and already dispatched records", async () => {
for (const candidate of [null, { ...message, id: "another" }]) {
await assert.rejects(resolveSessionMessageInput({ call: async () => ({ message: candidate }) }, request), { errorCode: "NOT_FOUND" });
Expand Down
10 changes: 10 additions & 0 deletions docs/adr/0239-session-collaboration-messages.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,16 @@ authorization and do not participate in user-message editing or regeneration.
A requested completion callback produces at most one durable completion message
to the originating session. It references the original delivery and the actual
turn outcome. Completion messages never request another automatic callback.
A completion notice may need no acknowledgement. Its current recipient turn
may therefore complete with no visible assistant text, without silent-turn
recovery or `EMPTY_MODEL_RESPONSE`. This exception uses only provenance resolved
by Main from the queued Host ledger record, with matching target and nonempty
message/reply-to IDs. Plugin/model text and restored history cannot enable it.
Every new run resets the exception, and accepted user steering revokes it.
Provider errors and aborts remain errors/aborts; task and ordinary message
requests retain silent-turn recovery. No new protocol field or caller authority
is introduced.

The host bounds autonomous communication chains and retains delivery failures for
passive inspection. Cancellation preserves the session and its history.

Expand Down
20 changes: 18 additions & 2 deletions docs/spec/03-runtime/02-agent-runtime.md
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@ started on and a proxy is never silently dropped.

### 5e. Silent-turn recovery

A turn that ends with no tool call and no visible assistant text is invisible
An ordinary turn that ends with no tool call and no visible assistant text is invisible
to the user: reasoning is never rendered, so a conclusion written only there
did not arrive. 15 of 255 recorded sessions ended a turn that way, and the
user's only recourse was typing "继续".
Expand Down Expand Up @@ -289,7 +289,23 @@ If the re-run is silent too, the turn ends as a visible assistant error with
retriable `EMPTY_MODEL_RESPONSE`, which gives the transcript its normal retry
action. No empty assistant message is persisted in either case.

Decision D193; see E2E-146.
A current Host-ledger completion notice (ADR 0239) is the narrow exception:
its prompt already permits no acknowledgement. Main resolves the queued message
by ID, verifies its target session, and constructs provenance from the ledger.
The runtime accepts silence only for `kind: completion` targeting the current
session with nonempty message and reply-to IDs. A successful silent notice emits
its normal completed message and terminal lifecycle without a recovery request
or `EMPTY_MODEL_RESPONSE`; its actual empty outcome may be persisted. Provider
errors and aborts retain their normal handling. The original task/result is not
rewritten, and completion notices never request another callback.

The exception belongs only to that prompt. Ordinary user input, task/message
deliveries, copied source framing, and restored history cannot enable it. Every
new run resets it; accepting user steering during a notice revokes it so the
new request must receive the ordinary response/recovery behavior.

Decision D193 and ADR 0239; see E2E-146 and
E2E-SESSION-completion-notice-allows-silence.

### 5e.1. Progress-only recovery for approved Plan/Goal execution

Expand Down
2 changes: 1 addition & 1 deletion docs/spec/03-runtime/08-error-codes.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ does not turn temporary thread pressure into a host process exit.
| `CONTEXT_TOO_LARGE` | no | prompt/context still exceeds the safe model budget after recovery, the second provider overflow occurred, or automatic recovery is disabled |
| `CONTEXT_COMPACTION_FAILED` | no | automatic retained-tail recovery could not prepare, persist, or fit a checkpoint, or manual checkpoint summary generation / durable append failed; the guarded next provider request does not start |
| `STREAM_FAILED` | yes | provider stream was terminated, closed prematurely, or otherwise ended before a complete response; up to ten same-turn retries may precede the terminal event |
| `EMPTY_MODEL_RESPONSE` | yes | the model ended its turn with no tool call and no visible text twice: once as streamed, once after the automatic re-run (spec 02-agent-runtime §5e) |
| `EMPTY_MODEL_RESPONSE` | yes | outside a current Host-ledger completion notice, the model ended its turn with no tool call and no visible text twice: once as streamed, once after the automatic re-run (spec 02-agent-runtime §5e) |
| `PROMPT_ENHANCEMENT_EMPTY` | no | the one-shot enhancement model returned no text |
| `SUBAGENT_IDLE_TIMEOUT` | no | withdrawn (D328): idle watchdogs are not armed; the code remains for stored results |
| `SUBAGENT_DURATION_TIMEOUT` | no | withdrawn (D328): duration watchdogs are not armed; the code remains for stored results |
Expand Down
28 changes: 28 additions & 0 deletions docs/spec/06-delivery/04-e2e-test-plan.md
Original file line number Diff line number Diff line change
Expand Up @@ -11246,6 +11246,34 @@ are withdrawn with ADR 0165.
automated. The live multi-session provider/Electron journey remains runner
validation under the no-local-E2E policy

#### E2E-SESSION-completion-notice-allows-silence: A trusted completion notice may finish without an acknowledgement

- **Preconditions**: A candidate commit has its own built host-core and runtime
sidecar. The local SSE provider deterministically returns visible text or a
successful empty response; no live credentials are required.
- **Steps**: 1) Deliver a task through the real Host collaboration ledger and
sidecar, read its successful result, and complete the coordinator summary.
2) Resolve the queued completion callback through the production Main input
resolver and run the recipient against an empty SSE response. 3) Run another
human request, copied completion framing, a ledger task, and a ledger message
against empty responses on the same recipient runtime.
- **Expected**: The original result remains unchanged. The completion has one
provider request, no error, one terminal lifecycle, completed ledger status,
and no acknowledgement callback. Each ordinary input still retries once and
ends with `EMPTY_MODEL_RESPONSE`. Unit coverage additionally rejects missing
reply-to IDs/wrong targets and revokes the exception on accepted user steering.
- **Specs linked**: `03-runtime/02-agent-runtime.md` §5e,
`03-runtime/08-error-codes.md`, ADR 0239
- **Acceptance**: C (conversation & stream), D (provenance), Quality
- **Milestone**: M6+
- **Status**: Automated by `pnpm test:e2e:session-completion` on the committed,
rebased candidate in its dedicated worktree. The harness drives real Host
RPCs, the production provenance resolver, sidecar, and local SSE, and persists
runtime messages before Host settlement. It does not exercise Electron's
queue/outbox UI or a live provider. Candidate/base SHAs and results belong in
the validation report; existing ledger coverage runs separately through
`pnpm test:e2e:collaboration`.

#### E2E-SESSION-hover-card-model-and-links: Session hover cards expose readable model and creation navigation

- **Preconditions**: The app has one collaboration-created session, one
Expand Down
2 changes: 1 addition & 1 deletion docs/spec/08-meta/decisions-log.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ This log freezes previously open questions into concrete decisions.
| D406 | Keep macOS DMG opening guidance text-only | **Amend D371 / ADR 0204: macOS DMGs expose the opening-help note as `If app won't open, read this.txt` and no longer include the executable `PI-Desktop-macOS-open.command`. macOS ZIP packages retain both the note and the helper. The note provides the narrow Terminal fallback for trusted unsigned builds; signed and notarized builds do not need it. See ADR 0232 and E2E-196b.** | The DMG should keep the normal app-to-Applications flow focused while still giving users a visible, actionable answer when an unsigned app does not open. |
| D407 | Restore archived projects after session import | **Additive renderer behavior for issue #250: when a core or plugin import adds a new project-bound session, the import-triggered session refresh normalizes its project path and clears the renderer's archived presentation state for that project. Pathless sessions, skipped imports, historical plugin paths without an active binding, and ordinary refreshes leave archive state unchanged. Host project rows, IPC channels, plugin methods, storage schema, and data formats do not change. See ADR 0236 and E2E-257.** | The host can successfully materialize an imported session under a project while the renderer still hides that project's sidebar row as archived. Restoring only the newly imported binding makes the result discoverable without weakening deliberate archive choices during ordinary refreshes (issue #250). |
| D408 | Prioritize MainChat in the three-column shell | **Amend ADR 0226 / ADR 0151 / ADR 0033 for issue #267: MainChat keeps a hard 450px minimum, the work panel is capped by the live budget (`client width - 450px - expanded sidebar`, with no fixed maximum), and the expanded sidebar yields at that threshold — including while `sidebar-out` still occupies flex space. A manual sidebar reopen spends panel width first and otherwise targets 460px; closing the panel restores only a sidebar the layout collapsed. The native window never changes: the reservation seam stays at zero and no geometry is applied. Preview mode temporarily unmounts MainChat and uses a window-level chrome row; collapsed-sidebar macOS preview reserves 88px, or 8px in fullscreen, for traffic lights (D433). See ADR 0238 and E2E-LAYOUT-three-column-width-priority.** | The fixed client area had no explicit width priority, so the side docks could pin MainChat to its floor and leave the composer unusable. Making the yield order explicit keeps the chat readable inside the fixed window without reintroducing native window growth (issue #267). |
| D409 | Host-owned session collaboration messages | **Amend ADR 0237 / ADR 0165 / ADR 0213: Rust host-core owns a durable session-collaboration ledger keyed by message id and real source/target Session IDs. Plugin-mediated `spawn`, `send`, `status`, `result`, and `cancel` operations use the reviewed desktop-control gateway; the sender is bound to the active plugin Agent tool invocation, target turns retain their existing configuration, and each delivery is claimed by its actual durable turn. Completion callbacks are durable, at-most-once, and reference the settled turn. Provenance is persisted with transcript rows and cannot be forged, stripped, or edited through regeneration. The additive schema v16 migration retains queued work across restart without unattended replay, applies permission ceilings and bounded autonomous hops, and keeps the existing Task family unchanged. See ADR 0239 and E2E-PLUGIN-session-orchestrator-real-workers.** | The plugin's prior create/prompt polling path could infer neither a durable turn outcome nor a safe bidirectional sender identity. A host-owned ledger makes delivery, provenance, callback, cancellation, and restart behavior auditable without restoring the withdrawn A2A protocol. |
| D409 | Host-owned session collaboration messages | **Amend ADR 0237 / ADR 0165 / ADR 0213: Rust host-core owns a durable session-collaboration ledger keyed by message id and real source/target Session IDs. Plugin-mediated `spawn`, `send`, `status`, `result`, and `cancel` operations use the reviewed desktop-control gateway; the sender is bound to the active plugin Agent tool invocation, target turns retain their existing configuration, and each delivery is claimed by its actual durable turn. Completion callbacks are durable, at-most-once, and reference the settled turn. Only the current Host-resolved completion prompt may finish silently without empty-response recovery; later runs and accepted user steering restore the ordinary response contract. Provenance is persisted with transcript rows and cannot be forged, stripped, or edited through regeneration. The additive schema v16 migration retains queued work across restart without unattended replay, applies permission ceilings and bounded autonomous hops, and keeps the existing Task family unchanged. See ADR 0239 and E2E-PLUGIN-session-orchestrator-real-workers.** | The plugin's prior create/prompt polling path could infer neither a durable turn outcome nor a safe bidirectional sender identity. A host-owned ledger makes delivery, provenance, callback, cancellation, and restart behavior auditable without restoring the withdrawn A2A protocol. |
| D410 | Independent session discovery and navigable collaboration projections | **Amend ADR 0239: add the reviewed read operation `session/collaboration/list`, bounded to 100 non-deleted Agent sessions and redacted to Session IDs, titles, status, updated time, readable provider/model labels, and bounded creation links. Extend the sidebar projection with readable model labels and at most eight created-session references. Render creator/created-session references as keyboard-focusable navigation buttons; independent sessions do not receive fabricated creator links. No renderer storage ownership or collaboration mutation boundary changes. See ADR 0240, E2E-SESSION-independent-top-level-communication, and E2E-SESSION-hover-card-model-and-links.** | Existing Session IDs were valid send targets but could be undiscoverable when they were not created by the plugin, while the hover card exposed only IDs and non-interactive provenance. A bounded host directory and navigable projection make durable sessions communicable and explainable without exposing transcripts or credentials. |
| D413 | Skill market public-HTTPS catalog fetch | **Additive: Settings → Skills Market discovers SKILL.md catalogs in Electron main under a shared public-HTTPS policy (syntactic public host + DNS classification + per-hop redirect re-validation). The renderer does not fetch. Install remains `skills.create`. Catalog ids match host `valid_capability_id`. Expanded documents over 128 KiB are refused. Builtin titles are English. See ADR 0243, E2E-SKILL-MARKET-*, issue #287.** | Community skill discovery needs main-process egress without a plugin-marketplace host allowlist, and copied classifiers would collide with the MCP market. |
| D421 | Native Pi session continuation | **Amend baseline D007: discover Pi v3 sessions as source-discriminated projections and continue them through coding-agent `AgentSession`/`SessionManager` against their canonical JSONL. Rust remains authoritative for Desktop SQLite/transcripts. Native continuation requires exact saved provider/auth, project trust, canonical path/header identity, and a cooperative lease plus byte/leaf validation; failures remain browseable/read-only. First slice excludes native rename/delete/move/revisions/Plan/Goal/queue/collaboration; the 2026-09-14 ADR 0254 amendment adds native fork with exact stream re-keying and inode-tracked publication; the side-chat panel it added is retired by ADR 0268. See ADR 0254 and E2E-SESSION-native-pi-*.** | Importing a flattened copy cannot preserve Pi's tree or make later Desktop turns visible to Pi Web. |
Expand Down
16 changes: 14 additions & 2 deletions docs/zh-CN/spec/03-runtime/02-agent-runtime.md
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ HTTP 429 处理是一个逻辑回合策略。此路径禁用了 pi-ai 的嵌套

### 5e。静默回合恢复

以没有工具调用且没有可见辅助文本结束的回合是不可见的
以没有工具调用且没有可见辅助文本结束的普通回合是不可见的
对用户:推理永远不会呈现,因此结论只写在那里
没有到达。 255 个录制的会话中有 15 个以这种方式结束了一个回合,并且
用户唯一的办法就是输入“继续”。
Expand Down Expand Up @@ -238,7 +238,19 @@ HTTP 429 处理是一个逻辑回合策略。此路径禁用了 pi-ai 的嵌套
可重试的 `EMPTY_MODEL_RESPONSE`,它为转录本提供正常的重试
行动。在这两种情况下都不会保留空的助理消息。

决定D193;参见 E2E-146。
当前回合收到的 Host 账本完成通知(ADR 0239)是唯一例外:其提示已经允许
无需确认。Main 按 ID 从账本读取排队消息、检查目标会话,再构造来源元数据。
只有 `kind: completion`、目标为当前会话、消息 ID 和回复目标 ID 均非空时,
运行时才允许静默成功。静默通知正常发出完成消息和终止生命周期,不重试、
不报告 `EMPTY_MODEL_RESPONSE`,可持久化其实际的空结果。提供商错误和中止
仍按原规则处理;原任务及结果不被改写,完成通知不会要求再次回调。

例外仅属于当前提示。普通用户输入、task/message 投递、复制的来源文本和
恢复的历史记录都不能开启它。每次新运行都会重置;通知执行期间接受用户
steering 后也会撤销例外,让新请求继续遵守普通响应及恢复规则。

决定 D193 和 ADR 0239;参见 E2E-146 与
E2E-SESSION-completion-notice-allows-silence。

### 5. 1 上下文检查点保护(D158/D203、ADR 0030/0049/0061/0064)

Expand Down
2 changes: 1 addition & 1 deletion docs/zh-CN/spec/03-runtime/08-error-codes.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ stdio 与 Tokio 的动态阻塞池隔离,因此后一种情况
| `CONTEXT_TOO_LARGE` | 不 | 恢复后 prompt/context 仍超出安全模型预算、发生第二个提供程序溢出或禁用自动恢复 |
| `CONTEXT_COMPACTION_FAILED` | 不 | 自动保留尾部恢复无法准备、持久或适合检查点,或手动检查点摘要生成/持久追加失败;受保护的下一个提供程序请求不会启动 |
| `STREAM_FAILED` | 是的 | 提供程序流在完整响应之前终止、提前关闭或以其他方式结束;最多四次同回合重试可能会在终止事件之前发生 |
| `EMPTY_MODEL_RESPONSE` | 是的 | 模型在没有工具调用且没有可见文本的情况下结束了两次:一次是流式传输,一次是在自动重新运行后(规范 02-agent-runtime §5e) |
| `EMPTY_MODEL_RESPONSE` | 是的 | 除当前 Host 账本完成通知外,模型在没有工具调用且没有可见文本的情况下结束了两次:一次是流式传输,一次是在自动重新运行后(规范 02-agent-runtime §5e) |
| `PROMPT_ENHANCEMENT_EMPTY` | 不 | 一次性增强模型没有返回任何文本 |
| `SUBAGENT_IDLE_TIMEOUT` | 不 | 已撤回(D328):空闲看门狗不再武装;代码仅为已存储结果保留 |
| `SUBAGENT_DURATION_TIMEOUT` | 不 | 已撤回(D328):时长看门狗不再武装;代码仅为已存储结果保留 |
Expand Down
Loading