Skip to content
Merged
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
39 changes: 37 additions & 2 deletions docs/adr/0239-session-collaboration-messages.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# ADR 0239: Host-owned session collaboration messages

- Status: Accepted
- Status: Accepted; amended by D446
- Date: 2026-09-13
- Decision: D409
- Decision: D409 (amended by D446)
- Amends: ADR 0237, ADR 0165, ADR 0213

## Context
Expand Down Expand Up @@ -69,3 +69,38 @@ Validation covers concurrent senders, durable identity, reuse, queue admission,
source preservation across reload, exactly-once callback creation, failure and
cancellation results, permission ceilings, model resolution, hover lifecycle,
and the relevant host/Electron E2E journeys.

## Amendment (2026-09-18, D446): a completion notice's own reply may stay silent

Issue #504: the completion prompt states that a notice needs no
acknowledgement, yet the runtime's silent-turn recovery (spec 02-agent-runtime
§5e) retried the resulting silence and reported `EMPTY_MODEL_RESPONSE` after a
task that had succeeded. This amendment bounds one exception; every other
decision above is unchanged.

- The first settled assistant reply to a completion notice may end with no
visible text and no tool call without silent-turn recovery or
`EMPTY_MODEL_RESPONSE`. The reply is emitted as a completed message with the
normal terminal lifecycle. Provider errors and aborts keep their handling;
a provider retry of the same attempt keeps the exception.
- The exception covers exactly that reply. It is spent by the first settled
response whether silent, textual, or a tool batch, so a reply that follows
tool results or accepted user steering in the same run is ordinary. It is
also revoked as soon as an accepted steering message enters the model
context, and every new run recomputes it.
- Only provenance that Main resolved from the queued Host ledger record can
enable it: `kind: completion`, the current target session, and nonempty
message and reply-to IDs. Prompt text, plugin or model content, task and
ordinary message deliveries, copied notice framing, and restored history
cannot. No protocol field or caller authority is added.
- An accepted silent reply is still not worth resending. Main persists it as
an empty completed row (hidden by the renderer, `NULL` text in the host), but
the runtime keeps it out of its entries and pi's transcript state, and the
context projection drops any assistant without content blocks, matching what
a restored transcript already did. The next request therefore carries no
empty assistant message for a provider to reject or skip.

Validation: runtime unit coverage for the accepted shapes, the trust boundary,
tool batches, provider retry, steering before and after the reply, and context
exclusion; `pnpm test:e2e:session-completion` against the real host ledger,
sidecar, and a local SSE provider. See E2E-SESSION-completion-notice-allows-silence.
2 changes: 1 addition & 1 deletion docs/adr/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,7 @@ Each ADR includes:
| 0236 | Restore archived projects when session import adds a bound session | Accepted |
| 0237 | Keep Session Orchestration in an Official Plugin | Accepted |
| 0238 | Prioritize MainChat in the three-column shell | Accepted (amends ADR 0226) |
| 0239 | Host-owned session collaboration messages | Accepted (amends ADR 0237 / 0165 / 0213) |
| 0239 | Host-owned session collaboration messages | Accepted (amends ADR 0237 / 0165 / 0213; amended by D446) |
| 0240 | Independent session discovery and navigable collaboration projections | Accepted (amends ADR 0239) |
| 0241 | Ship the file view as a vendored, updatable plugin | Accepted (supersedes ADR 0105; issue #304) |
| 0242 | Delta-only coalesced streaming updates | Accepted (amends 0127 / 0130 / 0149 / 0153; issue #299) |
Expand Down
32 changes: 30 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,35 @@ 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 Host-ledger completion notice (ADR 0239, D446) 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 silent notice reply emits its
normal completed message and terminal lifecycle without a recovery request or
`EMPTY_MODEL_RESPONSE`. Provider errors and aborts retain their normal handling,
and a provider retry of the same attempt keeps the exception. The original
task/result is not rewritten, and completion notices never request another
callback.

The exception covers exactly the notice's own reply: the first settled
assistant response of the run spends it, whether that response is silent,
textual, or a tool batch. A reply that follows tool results is therefore
ordinary work under this section, and the exception is revoked as soon as an
accepted steering message enters the model context, so a reply to the user
keeps its full re-run and error path. Every new run recomputes it from the
prompt's provenance. Ordinary user input, task/message deliveries, copied
source framing, and restored history cannot enable it.

An accepted silent reply is still not worth resending. Main persists it as an
empty completed row (the transcript hides it and the host stores no text), but
the runtime keeps it out of its entries and out of pi's transcript state, and
the context projection drops any assistant with no content blocks, exactly as
a restored transcript already did. The next provider request therefore carries
no empty assistant message.

Decision D193 and D446 (ADR 0239 amendment); 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 | the model ended its turn with no tool call and no visible text twice: once as streamed, once after the automatic re-run; the first reply to a Host-ledger completion notice is exempt (spec 02-agent-runtime §5e, D446) |
| `PROMPT_ENHANCEMENT_EMPTY` | no | the one-shot enhancement model returned no text |
| `SPEECH_NOT_CONFIGURED` | no | host speech ASR or TTS is not bound in settings |
| `SPEECH_PROTOCOL_UNSUPPORTED` | no | the speech protocol is unknown or does not support this role |
Expand Down
33 changes: 33 additions & 0 deletions docs/spec/06-delivery/04-e2e-test-plan.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ The minimum selection is:
- Imported-extension dependency installation or registry-boundary changes: `pnpm test:e2e:plugin-import-deps`.
- Trusted extension or plugin-extension changes: `pnpm test:e2e:trusted-extensions`.
- Session collaboration / Session Orchestrator: `pnpm test:e2e:collaboration`.
- Completion-notice silence or the silent-turn contract (D193 / D446): `pnpm test:e2e:session-completion`.
- Changes spanning multiple surfaces use the union of the applicable suites.

`pnpm test:e2e` is the default cross-system smoke suite for host RPC, IPC,
Expand Down Expand Up @@ -11290,6 +11291,38 @@ 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`, and no request the recipient sends after
the silent notice carries an empty assistant message. Unit coverage
additionally rejects missing reply-to IDs/wrong targets, spends the exception
on a tool batch, keeps it across a provider retry, revokes it once accepted
user steering enters the context, and keeps the accepted silence out of the
runtime entries and pi transcript state.
- **Specs linked**: `03-runtime/02-agent-runtime.md` §5e,
`03-runtime/08-error-codes.md`, ADR 0239 (D446 amendment)
- **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
Loading