@agentrouter-top/relay-dsh-plugin-codex 0.2.2-agentrouter.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTROUTER-FORK.md +7 -0
- package/LICENSE +21 -0
- package/README.md +419 -0
- package/README.zh.md +429 -0
- package/codex-activity-wire.mjs +16 -0
- package/cordis.patch.yml +5 -0
- package/docs/dsh-0.1.2-alpha.3.md +37 -0
- package/docs/images/dsh-new-session-backends.jpg +0 -0
- package/docs/reliability-acceptance.md +85 -0
- package/docs/reliability-spec.md +305 -0
- package/docs/spec/dsh-interaction-bridge.md +168 -0
- package/docs/spec/execution-presentation.md +103 -0
- package/dsh-client-compat.d.mts +4 -0
- package/dsh-client-compat.mjs +26 -0
- package/dsh-compat.mjs +92 -0
- package/lib/client.js +37202 -0
- package/lib/client.js.map +1 -0
- package/lib/host-plugin.js +5142 -0
- package/lib/host-plugin.js.map +1 -0
- package/lib/typert.host.js +16 -0
- package/lib/typert.host.js.map +1 -0
- package/package.json +157 -0
- package/presets/relay-codex/.relay-managed +1 -0
- package/presets/relay-codex/agent.cordis.yml +3 -0
- package/presets/relay-codex/preset.yml +3 -0
- package/scripts/repair-activity-history.mjs +123 -0
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
# Codex Reliability Specification
|
|
2
|
+
|
|
3
|
+
Status: Accepted for implementation
|
|
4
|
+
|
|
5
|
+
This specification defines the user-visible and safety-critical behavior of
|
|
6
|
+
`relay-dsh-plugin-codex`. The official DSH checkout remains unmodified.
|
|
7
|
+
|
|
8
|
+
## App Server ownership and connection state
|
|
9
|
+
|
|
10
|
+
The plugin Host process owns one Codex App Server child process. It starts the
|
|
11
|
+
child while the DSH Host activates the plugin, before Codex models are used,
|
|
12
|
+
and stops it when the plugin is disposed or DSH exits. The default launcher is
|
|
13
|
+
the pinned `@openai/codex` package and its platform optional dependency. A
|
|
14
|
+
global `codex` command is not required. `codexCommand` overrides
|
|
15
|
+
`RELAY_CODEX_COMMAND`, which overrides the bundled launcher.
|
|
16
|
+
|
|
17
|
+
The default launcher disables Codex `features.shell_snapshot`. Shell commands still
|
|
18
|
+
receive the effective Codex child environment; Relay does not turn secret delivery
|
|
19
|
+
into transcript text or strip variables merely because their names contain `KEY`,
|
|
20
|
+
`SECRET`, or `TOKEN`. Disabling snapshots prevents Codex from serializing that complete
|
|
21
|
+
effective environment into durable files under `CODEX_HOME/shell_snapshots`. Explicit
|
|
22
|
+
operator-supplied App Server arguments remain an exact override and carry responsibility
|
|
23
|
+
for any snapshot policy they enable.
|
|
24
|
+
|
|
25
|
+
## Plugin Hook trust propagation
|
|
26
|
+
|
|
27
|
+
Codex evaluates installed Plugin Hooks when each Thread is started, forked, or
|
|
28
|
+
resumed. App Server launch arguments and Thread request configuration are separate
|
|
29
|
+
configuration layers. Relay therefore mirrors an explicit standalone
|
|
30
|
+
`--dangerously-bypass-hook-trust` launch argument into
|
|
31
|
+
`config.bypass_hook_trust: true` on `thread/start`, `thread/fork`, and
|
|
32
|
+
`thread/resume`, while preserving the operator's launch argument array exactly.
|
|
33
|
+
|
|
34
|
+
This is an opt-in security exception. Relay never enables Hook trust bypass by
|
|
35
|
+
default, never recognizes the flag as a substring of another argument, and never
|
|
36
|
+
propagates unrelated launch configuration into Thread requests. Restarting without
|
|
37
|
+
the exact flag removes the request-level override. Existing realtime, dynamic-tool,
|
|
38
|
+
permission, resume, and fork settings remain unchanged.
|
|
39
|
+
|
|
40
|
+
The observable state machine is:
|
|
41
|
+
|
|
42
|
+
| State | Meaning | Required user behavior |
|
|
43
|
+
| --- | --- | --- |
|
|
44
|
+
| `not-started` | Plugin is loaded but start has not begun. | Wait for DSH startup. |
|
|
45
|
+
| `starting` | Child spawn and App Server initialization are in progress. | Wait; do not create a Thread. |
|
|
46
|
+
| `connected` | Initialize and model discovery succeeded. | Codex conversations may run. |
|
|
47
|
+
| `connection-failed` | A child existed or was attempted, but protocol initialization, connection, or process lifetime failed. | Preserve bindings; show restart/authentication diagnostics. |
|
|
48
|
+
| `unavailable` | The executable, bundled platform runtime, or supported platform is unavailable. | Show reinstall or absolute-path configuration guidance. |
|
|
49
|
+
| `rebind-required` | A DSH fork could not establish its App Server child binding safely. | Preserve provenance and retry Fork from the original Session after fixing the condition. |
|
|
50
|
+
|
|
51
|
+
User-facing status must never expose raw `spawn codex ENOENT`. Stable error
|
|
52
|
+
codes include `CODEX_EXECUTABLE_NOT_FOUND`, `CODEX_RUNTIME_MISSING`,
|
|
53
|
+
`CODEX_PLATFORM_UNSUPPORTED`, `CODEX_APP_SERVER_NOT_RUNNING`,
|
|
54
|
+
`CODEX_APP_SERVER_CONNECTION_FAILED`, and `CODEX_REBIND_REQUIRED`.
|
|
55
|
+
|
|
56
|
+
## Shell environment persistence
|
|
57
|
+
|
|
58
|
+
A Host-only environment secret may be inherited by an intended shell consumer without
|
|
59
|
+
appearing in DSH messages, Codex rollout events, plugin diagnostics, or regular Codex
|
|
60
|
+
state files. The Relay default must create no shell snapshot at all, because name-based
|
|
61
|
+
redaction cannot identify every secret and filtering the command environment would break
|
|
62
|
+
legitimate consumers. Restart and resume preserve the Session/Thread binding without
|
|
63
|
+
backfilling a snapshot for an earlier or later command.
|
|
64
|
+
|
|
65
|
+
## Backend model selection
|
|
66
|
+
|
|
67
|
+
For a blank DSH Session, the selected Agent preset determines the model
|
|
68
|
+
provider group:
|
|
69
|
+
|
|
70
|
+
- `relay-codex` selects `relay-codex` and its default model/default reasoning
|
|
71
|
+
effort;
|
|
72
|
+
- `relay-claude` is never rewritten by the Codex coordinator;
|
|
73
|
+
- leaving Codex for a native preset selects a provider group that is neither
|
|
74
|
+
Codex nor Claude.
|
|
75
|
+
|
|
76
|
+
Only the newest preset generation may select a model. Model discovery may be
|
|
77
|
+
retried with bounded delays while the App Server becomes ready. A non-blank
|
|
78
|
+
Session is never rewritten by this synchronization.
|
|
79
|
+
|
|
80
|
+
## Workspace Thread discovery and selective import
|
|
81
|
+
|
|
82
|
+
The provider-neutral `relay-dsh-plugin-session-import` hub owns the sidebar
|
|
83
|
+
footer's single **Import sessions...** entry. Codex contributes one explicit
|
|
84
|
+
**Import from Codex** menu row through `relay.session-import.provider`; it owns
|
|
85
|
+
no standalone footer trigger. Selecting that row presents a visible Workspace
|
|
86
|
+
selector. The current Session owner,
|
|
87
|
+
then the recent Workspace, is only an initial choice. The user may change it,
|
|
88
|
+
and the plugin must not scan until the user invokes **Scan sessions**.
|
|
89
|
+
|
|
90
|
+
The import scan lists only Codex Threads whose canonical `cwd` belongs to the
|
|
91
|
+
explicitly selected DSH Workspace and whose binding state is `ready` or `recoverable`.
|
|
92
|
+
Each candidate exposes its complete Codex Thread id, deterministic title, canonical
|
|
93
|
+
path, App Server `updatedAt`, and binding status. Candidates are unique and ordered
|
|
94
|
+
by source activity time, with Thread id as the deterministic tie-breaker. Already
|
|
95
|
+
bound Threads remain part of aggregate counts but are never selectable; Threads
|
|
96
|
+
from another Workspace are neither counted nor disclosed.
|
|
97
|
+
|
|
98
|
+
The UI defaults to all eligible candidates and supports selecting one, several,
|
|
99
|
+
all, or none. An empty selection cannot be submitted. The import request carries
|
|
100
|
+
the exact selected Thread ids. Before creating or changing any DSH Session, the
|
|
101
|
+
Host rescans the Workspace and validates the entire selection for non-empty unique
|
|
102
|
+
ids, current Workspace membership, and unbound or recoverable state. Any unknown,
|
|
103
|
+
duplicate, cross-Workspace, or newly-bound id rejects the whole request without a
|
|
104
|
+
partial mutation. A request that omits `threadIds` retains the previous import-all
|
|
105
|
+
Host API behavior for compatible clients; an explicit empty array is invalid.
|
|
106
|
+
|
|
107
|
+
Recoverable imports remain idempotent. After a successful selective import, DSH
|
|
108
|
+
refreshes Sessions before Workspace membership so the imported Session appears
|
|
109
|
+
with the original Codex title and source activity ordering.
|
|
110
|
+
|
|
111
|
+
## Thread binding and forks
|
|
112
|
+
|
|
113
|
+
One DSH Session binds at most one Codex Thread, and one Codex Thread binds at
|
|
114
|
+
most one DSH Session. A persisted binding is never deleted merely
|
|
115
|
+
because `thread/resume` fails. Active-writer and transient failures retain the
|
|
116
|
+
binding for retry. A missing Thread enters `rebind-required`.
|
|
117
|
+
|
|
118
|
+
DSH forks inherit assistant messages and their Codex `replayState`. When an
|
|
119
|
+
unbound child contains an original `threadId` and completed `turnId`, and that
|
|
120
|
+
Thread is still owned by another DSH Session, the plugin calls App Server
|
|
121
|
+
`thread/fork` with `threadId` and `lastTurnId`. It persists the returned new
|
|
122
|
+
Thread as the child's one-to-one binding before starting the child Turn. It
|
|
123
|
+
never writes the child continuation to the parent Thread.
|
|
124
|
+
|
|
125
|
+
Missing Turn provenance, an unowned or rebind-required source, an in-progress
|
|
126
|
+
Turn, an App Server rejection, or an invalid fork response enters
|
|
127
|
+
`CODEX_REBIND_REQUIRED`. Diagnostics retain the original Thread and, when
|
|
128
|
+
available, Turn and Item ids. These paths perform neither `thread/start` nor
|
|
129
|
+
`turn/start`, and the plugin must not silently create a replacement Thread.
|
|
130
|
+
Retrying the same provenance may retry `thread/fork`; it still cannot fall back
|
|
131
|
+
to fresh Thread creation.
|
|
132
|
+
|
|
133
|
+
## Approval provenance and reconnect
|
|
134
|
+
|
|
135
|
+
Every App Server approval is owned by this tuple:
|
|
136
|
+
|
|
137
|
+
`(DSH Session id, Codex Thread id, Turn id, Item id, App Server request id, binding epoch)`.
|
|
138
|
+
|
|
139
|
+
The tuple is captured before asking DSH for approval and validated again after
|
|
140
|
+
the user decision but before responding to Codex. Detach, rebind state, binding
|
|
141
|
+
replacement, request identity change, or provenance mismatch makes the
|
|
142
|
+
approval stale. A stale approval is rejected with `CODEX_STALE_APPROVAL`; it is
|
|
143
|
+
never accepted or routed to another Thread. The diagnostic names the original
|
|
144
|
+
Thread, Turn, and Item.
|
|
145
|
+
|
|
146
|
+
DSH may replay the same still-pending approval rpc id after a browser
|
|
147
|
+
disconnect. That replay is safe only while the ownership tuple remains valid.
|
|
148
|
+
|
|
149
|
+
## Subagent interaction ownership
|
|
150
|
+
|
|
151
|
+
A Codex subagent Thread does not receive an independent DSH Session binding. While
|
|
152
|
+
the root Turn is active, App Server emits `subAgentActivity` items whose enclosing
|
|
153
|
+
`threadId` is the parent and whose `agentThreadId` is the spawned child. The adapter
|
|
154
|
+
records those observed edges with the root binding epoch. Thread inventory metadata
|
|
155
|
+
is not treated as authorization because it may be absent before the interaction or
|
|
156
|
+
outlive the Turn that created the child. Dynamic tools, approvals,
|
|
157
|
+
and structured questions from a descendant may use the root DSH Agent only after the
|
|
158
|
+
adapter proves an acyclic observed parent chain from the requesting
|
|
159
|
+
Thread to the currently bound root Thread.
|
|
160
|
+
|
|
161
|
+
Resolution never uses cwd, title, recency, or model as ownership evidence. An unknown
|
|
162
|
+
Thread, missing parent, cycle, inconsistent shared Session, unbound root, disposed DSH
|
|
163
|
+
Agent, changed binding epoch, or rebind-required root fails closed. A descendant uses
|
|
164
|
+
only the DSH tool names captured for the owning root Turn; it cannot gain a capability
|
|
165
|
+
that was absent from that Turn. Observed edges are released when the root Turn ends or
|
|
166
|
+
the DSH Agent detaches. Conflicting observations permanently reject that child identity.
|
|
167
|
+
|
|
168
|
+
## Reasoning summary presentation
|
|
169
|
+
|
|
170
|
+
Business Turns request App Server reasoning summaries with `summary: auto`.
|
|
171
|
+
When Codex supplies a summary, the adapter projects its public summary deltas as
|
|
172
|
+
one DSH reasoning block that remains distinct from the final answer. It never
|
|
173
|
+
projects encrypted or raw hidden reasoning content.
|
|
174
|
+
|
|
175
|
+
An App Server reasoning item with no public summary produces no DSH reasoning
|
|
176
|
+
block rather than an empty `Think` disclosure. Ephemeral title and compaction
|
|
177
|
+
Turns explicitly use `summary: none`; their internal work is not added to the
|
|
178
|
+
business conversation and does not incur a presentation-only summary.
|
|
179
|
+
|
|
180
|
+
## Image projection and failure isolation
|
|
181
|
+
|
|
182
|
+
Codex `imageView` and `imageGeneration` items are admitted according to their
|
|
183
|
+
encoded byte signature, not a local filename extension or unverified data-URI
|
|
184
|
+
declaration. PNG, JPEG, GIF, and WebP signatures map to the corresponding DSH
|
|
185
|
+
media type. The DSH attachment store remains the authority for full decode,
|
|
186
|
+
normalization, size, and pixel-limit validation.
|
|
187
|
+
|
|
188
|
+
A filename such as `completed-clean.png` may therefore produce an
|
|
189
|
+
`image/jpeg` attachment when its bytes are JPEG. Workspace and generated-image
|
|
190
|
+
root checks still run before any local file is read; byte detection does not
|
|
191
|
+
expand the allowed filesystem boundary.
|
|
192
|
+
|
|
193
|
+
Image preview admission and storage are projection concerns. Failure of one
|
|
194
|
+
image emits one terminal text placeholder and a Host warning containing only a
|
|
195
|
+
stable reason code and the owning Thread, Turn, and Item identifiers. Raw
|
|
196
|
+
storage errors and absolute paths are not projected or logged. Projection then
|
|
197
|
+
continues through later Codex items and the source Turn's terminal status. It
|
|
198
|
+
must not throw out of the adapter stream, mark an otherwise successful DSH Turn
|
|
199
|
+
as failed, or interrupt the backing Codex Thread.
|
|
200
|
+
|
|
201
|
+
A completed App Server `mcpToolCall` may carry standard MCP image entries in
|
|
202
|
+
`result.content`. Each `type: image` entry is decoded independently in content order,
|
|
203
|
+
limited to 25 MiB, and admitted only when its declared supported MIME exactly matches
|
|
204
|
+
the encoded PNG, JPEG, GIF, or WebP signature. Text, resources, and
|
|
205
|
+
`structuredContent` are not reinterpreted as images. Valid bytes are saved directly
|
|
206
|
+
through the owning DSH attachment service with deterministic sanitized names; no
|
|
207
|
+
temporary Workspace file is created.
|
|
208
|
+
|
|
209
|
+
Malformed base64, unsupported or mismatched media, oversized data, and attachment
|
|
210
|
+
storage rejection follow the same failure-isolation contract: one sanitized placeholder
|
|
211
|
+
and stable warning reason per failed image, followed by the remaining MCP images and
|
|
212
|
+
the source Turn's final answer. Raw base64, storage errors, and private paths are never
|
|
213
|
+
logged or projected.
|
|
214
|
+
|
|
215
|
+
## DSH image input transport
|
|
216
|
+
|
|
217
|
+
DSH user image blocks normally contain a content-addressed attachment reference,
|
|
218
|
+
not a local path. Before creating or resuming a Codex Thread, the adapter reads
|
|
219
|
+
each image through DSH's attachment service, preserves message order, and verifies
|
|
220
|
+
the encoded PNG, JPEG, GIF, or WebP signature. Encoded bytes are authoritative
|
|
221
|
+
when stored metadata or the display name disagrees.
|
|
222
|
+
|
|
223
|
+
Verified bytes are materialized outside the Workspace under
|
|
224
|
+
`$CODEX_HOME/dsh-input-images` (or the default `~/.codex` equivalent). Files use
|
|
225
|
+
their SHA-256 digest plus a signature-derived extension, directories are private,
|
|
226
|
+
and writes are atomic without replacing an existing digest. Repeated immutable
|
|
227
|
+
attachments reuse the same verified path. The Workspace is not modified.
|
|
228
|
+
|
|
229
|
+
The resulting path is sent through App Server `turn/start` as native
|
|
230
|
+
`localImage` input and attachment metadata. Multiple images retain DSH order and
|
|
231
|
+
pure-image messages are valid. Existing trusted path-backed image blocks remain
|
|
232
|
+
supported.
|
|
233
|
+
|
|
234
|
+
Missing/corrupt attachments, unavailable attachment service, invalid bytes,
|
|
235
|
+
oversized data, and cancellation fail before a Codex Thread or Turn starts. They
|
|
236
|
+
use stable `CODEX_IMAGE_*` codes and never silently degrade an image-bearing user
|
|
237
|
+
message to text-only input.
|
|
238
|
+
|
|
239
|
+
## Turn interruption and process cleanup
|
|
240
|
+
|
|
241
|
+
Stopping a DSH Codex Turn must stop both model generation and every active App
|
|
242
|
+
Server background terminal owned by that Turn. Before sending `turn/interrupt`,
|
|
243
|
+
the runtime identifies the Turn's in-progress `commandExecution` item ids and
|
|
244
|
+
terminates only matching `thread/backgroundTerminals` process ids. It repeats
|
|
245
|
+
discovery after interruption to close races and confirms that no matching
|
|
246
|
+
terminal remains.
|
|
247
|
+
|
|
248
|
+
Background terminals owned by another Turn are not terminated. The plugin must
|
|
249
|
+
not use the thread-wide background-terminal cleanup operation for an ordinary
|
|
250
|
+
Turn stop.
|
|
251
|
+
|
|
252
|
+
The Turn is reported as aborted only after targeted cleanup and
|
|
253
|
+
`turn/interrupt` succeed. If cleanup cannot be confirmed, the DSH Turn ends with
|
|
254
|
+
`CODEX_TURN_INTERRUPT_CLEANUP_FAILED`, tells the user to check for late Workspace
|
|
255
|
+
side effects, and logs only the stable code plus Thread and Turn identifiers.
|
|
256
|
+
|
|
257
|
+
## Command output streaming
|
|
258
|
+
|
|
259
|
+
App Server shell output belongs to the user-visible Codex response even though the
|
|
260
|
+
command is executed inside Codex rather than by the DSH tool dispatcher. Code mode
|
|
261
|
+
returns the first yielded bytes in a raw `custom_tool_call_output`, while later PTY
|
|
262
|
+
bytes also arrive as native `item/commandExecution/outputDelta` notifications. New
|
|
263
|
+
durable plugin-owned Threads opt into raw response items; ephemeral auxiliary Threads
|
|
264
|
+
do not. The runtime never forwards a raw item: it correlates only `exec` call/output pairs, parses structured text results, and
|
|
265
|
+
projects only a non-empty result containing `session_id`, `wall_time_seconds`, and
|
|
266
|
+
`output`. Raw messages, prompts, reasoning, encrypted content, unrelated tools,
|
|
267
|
+
malformed results, and completed results without a live `session_id` remain private.
|
|
268
|
+
|
|
269
|
+
The adapter correlates the sanitized first yield and native command notifications by
|
|
270
|
+
`session_id`/`processId`, then retains one reconciled activity output. Mirrored raw
|
|
271
|
+
and native output is deduplicated while repeated native output remains repeated.
|
|
272
|
+
It must not emit an executable DSH `tool-call` stream chunk, because doing so would
|
|
273
|
+
ask the DSH Agent to execute the already-running command a second time. Instead,
|
|
274
|
+
presentation-only calls/results use native persistence envelopes with the
|
|
275
|
+
`relay_codex_activity` name and validated activity metadata.
|
|
276
|
+
|
|
277
|
+
All deltas for one process share one buffer and retain App Server order. For
|
|
278
|
+
native-only commands, the completed item's `aggregatedOutput` supplies the settled
|
|
279
|
+
snapshot. Code-mode output also reconciles sanitized first yields with the native
|
|
280
|
+
PTY side. A completed item with no preceding delta retains its non-empty aggregate.
|
|
281
|
+
Empty output stays empty. Late deltas after completion are ignored. Cancellation
|
|
282
|
+
also drains already-owned command notifications received during the interrupt RPC
|
|
283
|
+
before settling any remaining activity as failed.
|
|
284
|
+
|
|
285
|
+
Bounded command output is persisted in the native tool result's activity metadata,
|
|
286
|
+
separate from assistant commentary and the final answer. The grouped presentation
|
|
287
|
+
shows it as literal text in an expandable panel, never assistant Markdown. Native
|
|
288
|
+
persistence vocabulary survives Session reload without introducing plugin-private
|
|
289
|
+
mandatory event types. See [Execution presentation](spec/execution-presentation.md)
|
|
290
|
+
for ordering, ownership, legacy fallback, and file-delivery acceptance.
|
|
291
|
+
|
|
292
|
+
`experimentalRawEvents` is immutable App Server Thread creation state in the pinned
|
|
293
|
+
runtime: resume, settings update, and fork cannot enable it for a Thread created by an
|
|
294
|
+
older plugin version. Such Sessions continue to receive native command deltas, but
|
|
295
|
+
complete first-yield streaming requires a new DSH Session created after this feature
|
|
296
|
+
is installed. The plugin must not silently replace or summarize an existing Codex
|
|
297
|
+
Thread because that would weaken its model-context continuity.
|
|
298
|
+
|
|
299
|
+
## Platform contract
|
|
300
|
+
|
|
301
|
+
The bundled launcher supports darwin, linux, and win32 on arm64 and x64 using
|
|
302
|
+
the matching `@openai/codex-<platform>-<arch>` package. Commands are spawned
|
|
303
|
+
directly with an argument array and never through a shell, so spaces and
|
|
304
|
+
Windows backslashes remain literal. CI runs launcher, App Server client, and
|
|
305
|
+
status/error tests on macOS, Windows, and Linux.
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
# DSH Interaction Bridge Specification
|
|
2
|
+
|
|
3
|
+
## Scope
|
|
4
|
+
|
|
5
|
+
Codex App Server can pause a Turn to request approval for a command, file
|
|
6
|
+
change, or permission profile, or to ask the user a structured question. The
|
|
7
|
+
plugin routes those requests through the owning DSH Session so DSH remains the
|
|
8
|
+
authority for human interaction and conversation continuity.
|
|
9
|
+
|
|
10
|
+
## Composition contract
|
|
11
|
+
|
|
12
|
+
`approval` and `userQuestions` are required Host injections. They are provided
|
|
13
|
+
by sibling plugins in the official DSH composition, so listing them in the Host
|
|
14
|
+
plugin's exported `inject` array binds them into the Codex consumer fiber and
|
|
15
|
+
makes activation wait for both services.
|
|
16
|
+
|
|
17
|
+
The bridge must not read either service as an undeclared context property. It
|
|
18
|
+
must not make either dependency optional, and it must not bypass DSH approval or
|
|
19
|
+
question handling when a service is missing, cancelled, or fails. Failure is
|
|
20
|
+
closed: no App Server request is accepted and no protected operation executes.
|
|
21
|
+
|
|
22
|
+
## Request ownership
|
|
23
|
+
|
|
24
|
+
Every modern request carries a Codex `threadId`; legacy command and patch
|
|
25
|
+
requests carry the same identity as `conversationId`. The adapter normalizes
|
|
26
|
+
both forms and must resolve that id to one live DSH Session and Agent before
|
|
27
|
+
invoking a DSH interaction service. Modern `itemId` and legacy `callId` are
|
|
28
|
+
normalized into the same ownership slot.
|
|
29
|
+
|
|
30
|
+
Subagent requests carry the descendant Thread id rather than the root Thread id.
|
|
31
|
+
During the root Turn, the adapter observes `subAgentActivity.agentThreadId` as the
|
|
32
|
+
child of the notification's enclosing `threadId` and records that edge with the
|
|
33
|
+
current root binding epoch. App Server inventory can also identify the root through
|
|
34
|
+
shared `sessionId` and each edge through `parentThreadId`, but that durable inventory
|
|
35
|
+
is not authorization because it may be absent before the request or outlive the Turn.
|
|
36
|
+
The adapter must prove the complete acyclic observed parent chain to a currently
|
|
37
|
+
bound root before routing a descendant dynamic tool, approval, or question through
|
|
38
|
+
that root's DSH Agent.
|
|
39
|
+
Matching cwd, title, model, or recency is never sufficient ownership evidence.
|
|
40
|
+
|
|
41
|
+
An observed edge is valid only for its root Turn and binding epoch. Turn completion,
|
|
42
|
+
Agent detach, conflicting parent observations, a missing edge, or a root rebind makes
|
|
43
|
+
the descendant fail closed without DSH tool execution.
|
|
44
|
+
|
|
45
|
+
Approval ownership includes the DSH Session, Codex Thread, Turn, Item, App
|
|
46
|
+
Server request id, and binding epoch. Unknown, stale, re-bound, or unowned
|
|
47
|
+
requests are rejected without asking the user or executing the operation.
|
|
48
|
+
|
|
49
|
+
## Approval mapping
|
|
50
|
+
|
|
51
|
+
For command, file-change, and permission approval requests:
|
|
52
|
+
|
|
53
|
+
| DSH outcome | App Server response |
|
|
54
|
+
| --- | --- |
|
|
55
|
+
| `allowed-once` | `accept` for the current request |
|
|
56
|
+
| `rejected` | `decline` |
|
|
57
|
+
| `cancelled` | `decline` |
|
|
58
|
+
| `unavailable` | `decline` |
|
|
59
|
+
|
|
60
|
+
The requested command or App Server reason is shown through DSH's approval
|
|
61
|
+
service. The protected operation must not begin before `allowed-once` returns.
|
|
62
|
+
Thrown service errors reject the pending App Server request and never become an
|
|
63
|
+
implicit allow.
|
|
64
|
+
|
|
65
|
+
## Question mapping
|
|
66
|
+
|
|
67
|
+
For `item/tool/requestUserInput`, the bridge maps at most three Codex questions
|
|
68
|
+
to DSH questions, waits for `userQuestions.ask()`, and maps selected and custom
|
|
69
|
+
answers back to App Server. Cancellation or provider failure rejects the
|
|
70
|
+
pending request. Unsupported interaction methods are rejected.
|
|
71
|
+
|
|
72
|
+
## Activity presentation contract
|
|
73
|
+
|
|
74
|
+
Official DSH conversation rendering remains unmodified. New Codex runtime
|
|
75
|
+
activities use the official `assistant/message` (tool-call block), `tool/call`,
|
|
76
|
+
and `tool/result` envelopes with the plugin-owned tool name
|
|
77
|
+
`relay_codex_activity`. Versioned activity data travels in call arguments and
|
|
78
|
+
result `meta.codexActivity`. The plugin renders that name through the official
|
|
79
|
+
`tool.call.toolview` slot. This records work executed by Codex; it does not
|
|
80
|
+
register or dispatch an additional DSH tool. Calls have matching result messages,
|
|
81
|
+
carry the current DSH Turn/Step, and use Thread/Turn/Item-scoped call IDs.
|
|
82
|
+
|
|
83
|
+
Compatibility was checked against official DSH commit
|
|
84
|
+
`b150a551b8d465e31e418e1b2eaf5e79bbb7d28e`. Its persistence reader refuses unknown
|
|
85
|
+
event types unless `ignorable: true` is on the envelope, but its public
|
|
86
|
+
`Session.append()` does not accept that marker. Type declaration merging and
|
|
87
|
+
client conversation registration do not extend the persistence vocabulary.
|
|
88
|
+
New writes MUST NOT use `relay-codex/activity` or patch the official registry.
|
|
89
|
+
|
|
90
|
+
The legacy `relay-codex-activity` chat node remains read-only compatibility for
|
|
91
|
+
old logs. `scripts/repair-activity-history.mjs` defaults to dry-run; with DSH
|
|
92
|
+
stopped, `--write` backs up the original bytes and atomically adds only the
|
|
93
|
+
`ignorable: true` envelope marker to validated legacy activity events. It preserves
|
|
94
|
+
all sequence numbers, timestamps, payloads, and other records, handles every zstd
|
|
95
|
+
frame (with a separate header frame), is idempotent, and refuses malformed or
|
|
96
|
+
torn input. No other unknown event is made ignorable. Backups stay beside the
|
|
97
|
+
private session log, outside the repository.
|
|
98
|
+
|
|
99
|
+
Recovery must audit the complete configured session root without a modification
|
|
100
|
+
date or workspace filter. `--root <sessions-directory>` discovers canonical
|
|
101
|
+
plaintext/zstd logs across workspaces, excluding backups and symlinks. Batch
|
|
102
|
+
repair refuses to write if any log could not be inspected and rescans the root
|
|
103
|
+
after applying fixes; acceptance requires no unmarked legacy activity events
|
|
104
|
+
and no scan errors. Fixing only the first reported Session is not sufficient.
|
|
105
|
+
|
|
106
|
+
Assistant-facing text and reasoning keep using DSH's native text and reasoning
|
|
107
|
+
blocks. Command output, file changes, image views/generations, MCP tool calls,
|
|
108
|
+
web searches, plans, and future non-message App Server items must not be
|
|
109
|
+
serialized into assistant markdown. The adapter must append one started activity
|
|
110
|
+
when an item starts and one completed activity when it settles. Unsettled calls
|
|
111
|
+
are closed as failures when the stream terminates. Completed command
|
|
112
|
+
activities carry bounded output in their event payload so the UI can show it in
|
|
113
|
+
an expandable shell/detail panel without changing the assistant message body.
|
|
114
|
+
Command activity labels distinguish running from completed work, prefer Codex's
|
|
115
|
+
structured command actions (read/search/list), and retain the full command only
|
|
116
|
+
in expandable details. Consecutive activities are grouped in presentation; naming
|
|
117
|
+
each individual call `Ran commands` is not grouping. See
|
|
118
|
+
[Execution presentation acceptance](execution-presentation.md).
|
|
119
|
+
|
|
120
|
+
When App Server exposes duplicate command output through both legacy
|
|
121
|
+
`codeModeShell/outputDelta` and modern `commandExecution/outputDelta`
|
|
122
|
+
notifications, the adapter keeps a single reconciled activity output. Late
|
|
123
|
+
output deltas after item completion are ignored.
|
|
124
|
+
|
|
125
|
+
Unknown App Server item types that are not user, assistant, or reasoning items
|
|
126
|
+
must also become activity rows. This fail-open-for-presentation rule prevents new
|
|
127
|
+
Codex tool surfaces from falling back into MarkdownText while preserving DSH as
|
|
128
|
+
the owner of conversation layout.
|
|
129
|
+
|
|
130
|
+
## Verification contract
|
|
131
|
+
|
|
132
|
+
1. A unit contract test fails when either required Host injection is absent.
|
|
133
|
+
2. A Cordis composition test mounts interaction services as sibling providers,
|
|
134
|
+
mounts the Codex consumer with its exported `inject`, and completes one
|
|
135
|
+
approval plus one question request.
|
|
136
|
+
3. Handler and runtime tests cover allow, deny, permission, answer, stale
|
|
137
|
+
ownership, unknown Session, and unsupported request mappings.
|
|
138
|
+
4. An official DSH Web acceptance test uses two independent Codex Sessions.
|
|
139
|
+
Both request the same class of outside-Workspace write. Before either answer,
|
|
140
|
+
both target files are absent and the sentinel is unchanged. One-time allow
|
|
141
|
+
creates only the allow target with exact bytes. Reject leaves the deny target
|
|
142
|
+
absent. Both Sessions finish normally and remain usable.
|
|
143
|
+
5. The historical pre-fix plugin commit must reproduce the missing approval UI
|
|
144
|
+
and automatic fail-closed response for the same request shape.
|
|
145
|
+
6. Subagent tests emit App Server-shaped `subAgentActivity` notifications while the
|
|
146
|
+
root Turn remains live, cover direct and nested descendants plus duplicate,
|
|
147
|
+
conflicting, unbound, orphaned, cyclic, stale, expired, and cross-Session trees,
|
|
148
|
+
and assert rejected trees execute zero DSH tools. An official DSH Web regression
|
|
149
|
+
proves one child reads the exact oracle through the DSH tool bridge and returns it
|
|
150
|
+
to the owning parent without a parent-side read.
|
|
151
|
+
7. Codex adapter tests prove command output, mixed file/image/MCP/search items,
|
|
152
|
+
duplicate legacy/native command streams, empty outputs, and late deltas are
|
|
153
|
+
emitted as bounded native tool events and never as assistant text deltas.
|
|
154
|
+
8. Codex client tests prove the plugin registers the activity conversation
|
|
155
|
+
definition for legacy reads, injects the native keyed tool view, and renders collapsed
|
|
156
|
+
activity rows plus expandable shell/details without markdown interpretation.
|
|
157
|
+
9. Real official JSONL persistence tests cover both plaintext and zstd: stream a
|
|
158
|
+
tool through the adapter, persist it, dispose the writer, load using a fresh
|
|
159
|
+
reader, and reopen twice. Assert exact event preservation, known event types,
|
|
160
|
+
call/result pairing, coordinates, output, and final assistant text.
|
|
161
|
+
10. Legacy repair tests reproduce `SessionFormatUnsupportedError` before repair,
|
|
162
|
+
verify exact backup bytes, then cold-load through official persistence.
|
|
163
|
+
Verify dry-run, idempotence, malformed/torn refusal, and unchanged unrelated
|
|
164
|
+
unknown events. Component tests cover native running-to-settled updates,
|
|
165
|
+
result-only history pages, interruption, and malformed payload fallback.
|
|
166
|
+
11. Root scan tests include an old-dated log, multiple workspaces, both physical
|
|
167
|
+
encodings, an already repaired log, backups, symlinks, and corrupt input.
|
|
168
|
+
No date or workspace selection may omit an affected canonical log.
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# Codex Execution Presentation
|
|
2
|
+
|
|
3
|
+
## Scope and baseline
|
|
4
|
+
|
|
5
|
+
Plugin-only integration against immutable official DSH
|
|
6
|
+
`b150a551b8d465e31e418e1b2eaf5e79bbb7d28e`. DSH continues to own navigation,
|
|
7
|
+
composer, history, attachments, and execution controls. No official source edits,
|
|
8
|
+
new mandatory persistence event types, PR, or private session logs in this repo.
|
|
9
|
+
|
|
10
|
+
The supplied 2026-08-30 recording is the behavioral reference: readable progress
|
|
11
|
+
paragraphs interleave with grouped tool activity; only the current activity is
|
|
12
|
+
foregrounded while tools run; the process can collapse when the answer is ready.
|
|
13
|
+
The current one-row-per-command implementation is not accepted as grouping.
|
|
14
|
+
|
|
15
|
+
## Delivery slices and review gates
|
|
16
|
+
|
|
17
|
+
1. Projection: reconstruct an ordered, turn-scoped presentation from native
|
|
18
|
+
chunks and native tool envelopes. Tool messages must not finalize or erase
|
|
19
|
+
streamed commentary. Keep text, reasoning, images, and tool data distinct.
|
|
20
|
+
Review ordering, incremental versus full replay, duplicates, and ownership.
|
|
21
|
+
2. UI: group adjacent tool activities between text/image boundaries; expose one
|
|
22
|
+
summary/current action by default and all individual details on expansion.
|
|
23
|
+
Use stable keys, semantic labels, truthful status, bounded shell panels,
|
|
24
|
+
accessible disclosure controls, and existing DSH design primitives.
|
|
25
|
+
Review long labels, interrupted/error work, images, and foreign-provider views.
|
|
26
|
+
3. Integration: build/typecheck/full tests plus official persistence round trips,
|
|
27
|
+
restart and history reload. Review loader compatibility and source cleanliness.
|
|
28
|
+
4. Live acceptance: submit exactly `当前Relay项目还有哪些逻辑没有实现为DSH插件?`
|
|
29
|
+
in the Relay workspace using `gpt-5.6-sol` with `high` reasoning. Capture live,
|
|
30
|
+
expanded-detail, final-answer, and reopened-history screenshots. Record model
|
|
31
|
+
and actual outcome; do not substitute a synthetic preview for live evidence.
|
|
32
|
+
|
|
33
|
+
Each slice needs its own test results and review before delivery. Synthetic tests
|
|
34
|
+
cover nondeterministic branches; the real question is the final acceptance, not
|
|
35
|
+
a speed comparison with an independent Codex App run.
|
|
36
|
+
|
|
37
|
+
## Acceptance scenarios
|
|
38
|
+
|
|
39
|
+
| ID | Scenario | Required observation |
|
|
40
|
+
| --- | --- | --- |
|
|
41
|
+
| EXEC-01 | Commentary, ten tools, commentary, more tools, answer | Commentary appears while running, in source order; two compact tool groups, not ten top-level rows |
|
|
42
|
+
| EXEC-02 | Streaming commentary followed by native tool request | Tool-only assistant message cannot erase or freeze commentary |
|
|
43
|
+
| EXEC-03 | Read/search/list, shell, edit, image and unknown tools | Semantic current label and category icon; unknown tool remains inspectable |
|
|
44
|
+
| EXEC-04 | Open a group and child while new items arrive | Stable disclosure state; command/output retains newlines and is never Markdown |
|
|
45
|
+
| EXEC-05 | Concurrent tools, nonzero exit, cancellation | Active count is truthful; error is not green success; unresolved calls settle on termination |
|
|
46
|
+
| EXEC-06 | Final text-only answer and answer after tools | Answer remains visible; process collapses without losing history; reopenable process |
|
|
47
|
+
| EXEC-07 | Images and consecutive file edits | Images use DSH attachment rendering; edits remain expandable and ordered |
|
|
48
|
+
| EXEC-08 | Refresh/reopen/cold-load plaintext and multi-frame zstd | Same logical order and grouping, no duplicate text or unsupported event error |
|
|
49
|
+
| EXEC-09 | Non-Codex session and mixed-provider history | Native rendering and controls unaffected; ownership is turn-scoped |
|
|
50
|
+
| EXEC-10 | Desktop/narrow viewport, long path, keyboard disclosure | No horizontal overflow/overlap; Enter/Space disclosure works; status is not color-only |
|
|
51
|
+
| EXEC-11 | Exact user question, Sol High | Real execution visibly meets live grouping and commentary criteria; retained screenshots |
|
|
52
|
+
| EXEC-12 | Cancel after a command emitted partial output | Failed terminal result retains all buffered output within the existing output limit |
|
|
53
|
+
| EXEC-13 | Imported native tools or foreign-provider steps between Codex paragraphs | Preserve native chronological rendering where a complete grouped projection is unavailable |
|
|
54
|
+
| EXEC-14 | Generated image before final prose, or image-only completion | Deliverable remains visible after process collapse; viewing an image remains tool activity |
|
|
55
|
+
| EXEC-15 | Closing answer references a known produced file | Preserve the native closing-turn resolver; completed structured Codex edits also resolve through the native DSH opener |
|
|
56
|
+
| EXEC-16 | Replacement mount fails or plugin unloads | Native assistant and tool content remains available or is restored; no invisible conversation |
|
|
57
|
+
| EXEC-17 | Pack and clean-install in isolated official DSH profile | Install the actual tarball, not a workspace link; execute its browser loader without missing modules; verify installed bundle identity |
|
|
58
|
+
| EXEC-18 | Upgrade published 0.1.4 to candidate, restart | Existing native history stays readable, new grouped history reloads; installed client is the candidate, not stale same-version package content |
|
|
59
|
+
| EXEC-19 | Native DSH, Codex-only, Claude-only and combined profiles | All selected loaders execute; native and Claude histories do not receive Codex process ownership |
|
|
60
|
+
| EXEC-20 | Keyboard and real 390 x 844 viewport | Enter and Space toggle process/group/child disclosures; focus remains visible; no horizontal document overflow |
|
|
61
|
+
| EXEC-21 | Real edit, image-view and produced-file workflow | Actual file content changes; edit and image categories render; final file reference opens via native DSH; viewed image bytes decode |
|
|
62
|
+
| EXEC-22 | Browser failure/cancel, cold replay and image delivery | Explicit terminal states and retained partial output; no duplicate activities after reload; generated image output remains visible after collapse |
|
|
63
|
+
|
|
64
|
+
Delivery evidence distinguishes real model execution, deterministic protocol
|
|
65
|
+
fixtures, component tests, and manual-only checks. Fixture image delivery is not
|
|
66
|
+
reported as a successful live image-generation service call. Tarballs, raw logs,
|
|
67
|
+
isolated profiles and screenshots stay outside the repository. No publishing or
|
|
68
|
+
PR creation is part of these acceptance commands.
|
|
69
|
+
|
|
70
|
+
## Output and file contracts
|
|
71
|
+
|
|
72
|
+
Cancellation preserves stdout already received by the adapter, including
|
|
73
|
+
notifications queued while the interrupt RPC is pending. Only commands already
|
|
74
|
+
owned by that turn may be settled; foreign turns, unknown commands and new prose
|
|
75
|
+
are not admitted during cleanup. An immediate stop before the execution backend
|
|
76
|
+
delivers stdout may legitimately have no output. The acceptance oracle examines
|
|
77
|
+
persisted activity output, not text echoed in the command input.
|
|
78
|
+
|
|
79
|
+
The pinned Codex runtime cannot enable first-yield raw notifications for a
|
|
80
|
+
Thread created without them by an older plugin. Upgrade acceptance must preserve
|
|
81
|
+
that Thread and its history, not silently replace it. First-yield cancellation
|
|
82
|
+
acceptance uses a new post-upgrade Session; an old Thread can retain only output
|
|
83
|
+
the backend actually delivers. See the compatibility contract in
|
|
84
|
+
[Reliability](../reliability-spec.md#command-output-streaming).
|
|
85
|
+
|
|
86
|
+
Synthetic Codex file activities do not have native DSH mutation presenters.
|
|
87
|
+
Supplement the native file-mention resolver only with completed structured
|
|
88
|
+
file-change records. Exact paths and unique basenames can open through DSH;
|
|
89
|
+
ambiguous, failed, running, deleted, malformed and prose-only paths cannot.
|
|
90
|
+
Renames replace the old path with the destination. Native resolution retains
|
|
91
|
+
priority and is returned unchanged when there are no produced Codex files.
|
|
92
|
+
|
|
93
|
+
Reasoning is secondary and collapsed, not mixed into assistant prose. Missing
|
|
94
|
+
model commentary must not be invented. Partial history must fall back without
|
|
95
|
+
discarding available content. Debug context remains available without dominating
|
|
96
|
+
normal Codex process presentation. Model phase information may be used only when
|
|
97
|
+
actually supplied; do not guess a final answer from its wording.
|
|
98
|
+
|
|
99
|
+
Older text-only projections without presentation metadata remain native. A
|
|
100
|
+
visible legacy activity between projected segments also disables takeover; its
|
|
101
|
+
native row must not be crossed by moving later prose to the process anchor.
|
|
102
|
+
Missing tool-call history, unrepresented tool types, and mixed providers likewise
|
|
103
|
+
retain native presentation until the projection has sufficient evidence.
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { Context } from '@deepseek-ai/cordis'
|
|
2
|
+
import type {} from '@deepseek-ai/dsh-client-ui-model-selection/client'
|
|
3
|
+
export function sessionPreset(session: { agentPreset?: string | null; projectionValues?: { agentPreset?: string | null } } | undefined): string | null | undefined
|
|
4
|
+
export function modelDirectory(ctx: unknown, sessionId: string): Pick<ReturnType<Context['modelDirectories']['directoryFor']>, 'load' | 'select'>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// Public interface adapters shared by both supported DSH generations.
|
|
2
|
+
export function sessionPreset(session) {
|
|
3
|
+
if (session?.projectionValues && Object.hasOwn(session.projectionValues, 'agentPreset')) {
|
|
4
|
+
return session.projectionValues.agentPreset;
|
|
5
|
+
}
|
|
6
|
+
return session?.agentPreset;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const service = (ctx, name) => typeof ctx.get === 'function' ? ctx.get(name) : ctx[name];
|
|
10
|
+
export function modelDirectory(ctx, sessionId) {
|
|
11
|
+
const directories = service(ctx, 'modelDirectories');
|
|
12
|
+
if (directories) return directories.directoryFor(sessionId);
|
|
13
|
+
const sessions = service(ctx, 'connection')?.api?.sessions;
|
|
14
|
+
if (!sessions) throw new Error('DSH model selection interface is unavailable');
|
|
15
|
+
return {
|
|
16
|
+
async load() {
|
|
17
|
+
const response = await sessions.models({ sessionId });
|
|
18
|
+
if (!response.result.ok) throw new Error('DSH model discovery failed');
|
|
19
|
+
return response.result.value;
|
|
20
|
+
},
|
|
21
|
+
async select(selection) {
|
|
22
|
+
const response = await sessions.selectModel({ sessionId, ...selection });
|
|
23
|
+
if (!response.result.ok) throw new Error('DSH model selection failed');
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
}
|