super-dsh 0.1.9 → 0.1.10-e

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.
Files changed (178) hide show
  1. package/README.md +25 -3
  2. package/agent-agy/cordis.patch.yml +7 -4
  3. package/agent-agy/dist/agent.d.ts +12 -11
  4. package/agent-agy/dist/agy-auth-gateway.d.ts +124 -0
  5. package/agent-agy/dist/agy-auth-gateway.js +233 -0
  6. package/agent-agy/dist/agy-auth.d.ts +153 -0
  7. package/agent-agy/dist/agy-auth.js +179 -0
  8. package/agent-agy/dist/agy-cli-client.d.ts +39 -9
  9. package/agent-agy/dist/agy-cli-client.js +82 -18
  10. package/agent-agy/dist/agy-store.d.ts +8 -0
  11. package/agent-agy/dist/index.d.ts +46 -16
  12. package/agent-agy/dist/index.js +82 -56
  13. package/agent-agy/dist/models.d.ts +70 -0
  14. package/agent-agy/dist/models.js +102 -3
  15. package/agent-agy/package.json +7 -5
  16. package/agent-agy-sdk/cordis.patch.yml +58 -0
  17. package/agent-agy-sdk/dist/adapter.d.ts +15 -0
  18. package/agent-agy-sdk/dist/adapter.js +42 -0
  19. package/agent-agy-sdk/dist/agent-preset-agy.d.ts +27 -0
  20. package/agent-agy-sdk/dist/agent-preset-agy.js +32 -0
  21. package/agent-agy-sdk/dist/agent.d.ts +138 -0
  22. package/agent-agy-sdk/dist/agent.js +494 -0
  23. package/{agent-agy → agent-agy-sdk}/dist/agy-client.d.ts +26 -6
  24. package/{agent-agy → agent-agy-sdk}/dist/agy-client.js +43 -25
  25. package/agent-agy-sdk/dist/agy-sessions.d.ts +25 -0
  26. package/agent-agy-sdk/dist/agy-sessions.js +107 -0
  27. package/agent-agy-sdk/dist/agy-store.d.ts +39 -0
  28. package/agent-agy-sdk/dist/agy-store.js +83 -0
  29. package/agent-agy-sdk/dist/inbox.d.ts +26 -0
  30. package/agent-agy-sdk/dist/inbox.js +53 -0
  31. package/agent-agy-sdk/dist/index.d.ts +93 -0
  32. package/agent-agy-sdk/dist/index.js +476 -0
  33. package/agent-agy-sdk/dist/models.d.ts +41 -0
  34. package/agent-agy-sdk/dist/models.js +74 -0
  35. package/agent-agy-sdk/dist/sdk-venv.d.ts +76 -0
  36. package/agent-agy-sdk/dist/sdk-venv.js +173 -0
  37. package/agent-agy-sdk/dist/world-plugin.d.ts +10 -0
  38. package/agent-agy-sdk/dist/world-plugin.js +82 -0
  39. package/agent-agy-sdk/package.json +64 -0
  40. package/agent-claude/dist/adapter.js +106 -31
  41. package/agent-claude/dist/agent.js +154 -17
  42. package/agent-claude/dist/claude-client.js +84 -43
  43. package/agent-claude/dist/claude-launch.js +101 -0
  44. package/agent-claude/dist/claude-native-config.js +169 -0
  45. package/agent-claude/dist/claude-provider-gateway.js +340 -0
  46. package/agent-claude/dist/claude-providers.js +391 -0
  47. package/agent-claude/dist/claude-route.js +152 -0
  48. package/agent-claude/dist/index.js +214 -37
  49. package/agent-claude/dist/models.js +14 -0
  50. package/agent-claude/dist/sdk-runtime.js +320 -0
  51. package/agent-claude/package.json +6 -4
  52. package/agent-codex/README.md +141 -36
  53. package/agent-codex/cordis.patch.yml +14 -10
  54. package/agent-codex/dist/adapter.js +64 -28
  55. package/agent-codex/dist/agent.js +255 -81
  56. package/agent-codex/dist/app-server-approvals.js +165 -0
  57. package/agent-codex/dist/app-server-client.js +723 -0
  58. package/agent-codex/dist/app-server-events.js +432 -0
  59. package/agent-codex/dist/app-server-protocol.js +95 -0
  60. package/agent-codex/dist/app-server-wire.js +299 -0
  61. package/agent-codex/dist/codex-launch.js +78 -0
  62. package/agent-codex/dist/codex-native-config.js +392 -0
  63. package/agent-codex/dist/codex-provider-gateway.js +402 -0
  64. package/agent-codex/dist/codex-providers.js +376 -0
  65. package/agent-codex/dist/codex-route.js +86 -0
  66. package/agent-codex/dist/index.js +256 -115
  67. package/agent-codex/dist/permission.js +75 -1
  68. package/agent-codex/package.json +1 -2
  69. package/agent-codex-sdk/README.md +79 -0
  70. package/agent-codex-sdk/cordis.patch.yml +81 -0
  71. package/agent-codex-sdk/dist/adapter.js +91 -0
  72. package/agent-codex-sdk/dist/agent-preset-codex.js +32 -0
  73. package/agent-codex-sdk/dist/agent.js +1085 -0
  74. package/agent-codex-sdk/dist/codex-native-config.js +392 -0
  75. package/agent-codex-sdk/dist/codex-provider-gateway.js +402 -0
  76. package/agent-codex-sdk/dist/codex-providers.js +376 -0
  77. package/agent-codex-sdk/dist/codex-route.js +86 -0
  78. package/{agent-codex/dist/codex-client.js → agent-codex-sdk/dist/codex-sdk-client.js} +253 -32
  79. package/agent-codex-sdk/dist/codex-sdk-launch.js +92 -0
  80. package/agent-codex-sdk/dist/codex-store.js +180 -0
  81. package/agent-codex-sdk/dist/inbox.js +53 -0
  82. package/agent-codex-sdk/dist/index.js +739 -0
  83. package/agent-codex-sdk/dist/permission.js +71 -0
  84. package/agent-codex-sdk/dist/sdk-runtime.js +307 -0
  85. package/agent-codex-sdk/dist/session-map.js +167 -0
  86. package/agent-codex-sdk/dist/world-plugin.js +93 -0
  87. package/agent-codex-sdk/package.json +79 -0
  88. package/agent-codex-sdk/scripts/fetch-sdk-runtime.mjs +57 -0
  89. package/agent-omp/README.md +72 -55
  90. package/agent-omp/dist/agent.js +36 -11
  91. package/agent-omp/dist/index.js +92 -52
  92. package/agent-omp/dist/lazy-rpc.js +11 -4
  93. package/agent-omp/dist/omp-cli.js +114 -97
  94. package/agent-omp/dist/omp-store.js +12 -4
  95. package/agent-omp/dist/permission.js +70 -5
  96. package/agent-omp/dist/rpc-client.js +409 -0
  97. package/agent-omp/dist/world-plugin.js +36 -3
  98. package/agent-omp/package.json +2 -6
  99. package/agent-omp-sdk/README.md +103 -0
  100. package/agent-omp-sdk/cordis.patch.yml +108 -0
  101. package/agent-omp-sdk/dist/adapter.d.ts +21 -0
  102. package/agent-omp-sdk/dist/adapter.js +36 -0
  103. package/agent-omp-sdk/dist/agent-preset-omp.d.ts +31 -0
  104. package/agent-omp-sdk/dist/agent-preset-omp.js +32 -0
  105. package/agent-omp-sdk/dist/agent.d.ts +64 -0
  106. package/agent-omp-sdk/dist/agent.js +1178 -0
  107. package/agent-omp-sdk/dist/discovery.d.ts +57 -0
  108. package/agent-omp-sdk/dist/discovery.js +116 -0
  109. package/agent-omp-sdk/dist/inbox.d.ts +28 -0
  110. package/agent-omp-sdk/dist/inbox.js +53 -0
  111. package/agent-omp-sdk/dist/index.d.ts +60 -0
  112. package/agent-omp-sdk/dist/index.js +632 -0
  113. package/agent-omp-sdk/dist/knobs.d.ts +34 -0
  114. package/agent-omp-sdk/dist/knobs.js +50 -0
  115. package/agent-omp-sdk/dist/lazy-rpc.d.ts +98 -0
  116. package/agent-omp-sdk/dist/lazy-rpc.js +178 -0
  117. package/agent-omp-sdk/dist/mobile/zoom-guard.d.ts +187 -0
  118. package/agent-omp-sdk/dist/mobile/zoom-guard.js +294 -0
  119. package/agent-omp-sdk/dist/mobile-boot.d.ts +49 -0
  120. package/agent-omp-sdk/dist/mobile-boot.js +38 -0
  121. package/agent-omp-sdk/dist/models.d.ts +39 -0
  122. package/agent-omp-sdk/dist/models.js +299 -0
  123. package/agent-omp-sdk/dist/omp-cli.d.ts +67 -0
  124. package/agent-omp-sdk/dist/omp-cli.js +164 -0
  125. package/agent-omp-sdk/dist/omp-disk-discovery.d.ts +27 -0
  126. package/agent-omp-sdk/dist/omp-disk-discovery.js +178 -0
  127. package/agent-omp-sdk/dist/omp-executable.d.ts +2 -0
  128. package/agent-omp-sdk/dist/omp-executable.js +30 -0
  129. package/agent-omp-sdk/dist/omp-store.d.ts +134 -0
  130. package/agent-omp-sdk/dist/omp-store.js +526 -0
  131. package/agent-omp-sdk/dist/pairing.d.ts +7 -0
  132. package/agent-omp-sdk/dist/pairing.js +45 -0
  133. package/agent-omp-sdk/dist/permission.d.ts +138 -0
  134. package/agent-omp-sdk/dist/permission.js +161 -0
  135. package/agent-omp-sdk/dist/protocol.d.ts +189 -0
  136. package/agent-omp-sdk/dist/rpc-types.d.ts +77 -0
  137. package/agent-omp-sdk/dist/rpc-types.js +1 -0
  138. package/agent-omp-sdk/dist/sdk-client.d.ts +160 -0
  139. package/{agent-omp → agent-omp-sdk}/dist/sdk-client.js +108 -56
  140. package/agent-omp-sdk/dist/sdk-runtime.d.ts +211 -0
  141. package/agent-omp-sdk/dist/sdk-runtime.js +418 -0
  142. package/agent-omp-sdk/dist/sidecar-client.d.ts +87 -0
  143. package/agent-omp-sdk/dist/sidecar-client.js +197 -0
  144. package/agent-omp-sdk/dist/store/adapter.d.ts +17 -0
  145. package/agent-omp-sdk/dist/store/adapter.js +47 -0
  146. package/agent-omp-sdk/dist/store/db.d.ts +70 -0
  147. package/agent-omp-sdk/dist/store/db.js +210 -0
  148. package/agent-omp-sdk/dist/store/index.d.ts +23 -0
  149. package/agent-omp-sdk/dist/store/index.js +59 -0
  150. package/agent-omp-sdk/dist/store/reconcile.d.ts +15 -0
  151. package/agent-omp-sdk/dist/store/reconcile.js +51 -0
  152. package/agent-omp-sdk/dist/store/schema.d.ts +8 -0
  153. package/agent-omp-sdk/dist/store/schema.js +40 -0
  154. package/agent-omp-sdk/dist/transport.d.ts +49 -0
  155. package/agent-omp-sdk/dist/transport.js +35 -0
  156. package/agent-omp-sdk/dist/world-home.d.ts +4 -0
  157. package/agent-omp-sdk/dist/world-home.js +23 -0
  158. package/agent-omp-sdk/dist/world-plugin.d.ts +10 -0
  159. package/agent-omp-sdk/dist/world-plugin.js +137 -0
  160. package/agent-omp-sdk/package.json +87 -0
  161. package/client/index.js +1204 -93
  162. package/cordis.patch.yml +64 -0
  163. package/package.json +17 -8
  164. package/superd-hub/dist/client/agent-settings.d.ts +210 -0
  165. package/superd-hub/dist/client/agent-settings.js +300 -0
  166. package/superd-hub/dist/client/roster.js +6 -0
  167. package/superd-hub/dist/client/runtime-settings-api.d.ts +76 -0
  168. package/superd-hub/dist/client/runtime-settings-api.js +137 -0
  169. package/superd-hub/dist/client/switch.d.ts +165 -0
  170. package/superd-hub/dist/client/switch.js +295 -0
  171. package/superd-hub/lib/client/index.js +1204 -93
  172. package/agent-codex/dist/models.js +0 -152
  173. package/agent-omp/dist/sidecar-client.js +0 -195
  174. /package/{agent-agy → agent-agy-sdk}/bridge/agy_bridge.py +0 -0
  175. /package/{agent-agy → agent-agy-sdk}/bridge/gemini_openai_server.mjs +0 -0
  176. /package/{agent-codex/dist/codex-events.js → agent-codex-sdk/dist/codex-sdk-events.js} +0 -0
  177. /package/{agent-omp → agent-omp-sdk}/dist/protocol.js +0 -0
  178. /package/{agent-omp → agent-omp-sdk}/sidecar/main.ts +0 -0
@@ -0,0 +1,320 @@
1
+ /**
2
+ * sdk-runtime.ts — the lazy, durable Claude Agent SDK runtime (2026-10-04 spec §2.1).
3
+ *
4
+ * Zero declared dependency: the FIRST real prompt fetches
5
+ * `@anthropic-ai/claude-agent-sdk` into <worldHome>/sdk-runtime/ and it stays
6
+ * durable there. Two modes (user ruling 2026-10-04): host-binary (default,
7
+ * `--omit=optional` — the 206 MB platform payload is never downloaded; the CLI
8
+ * is the user's own binary) and bundled-payload (no local binary: the SDK's
9
+ * own platform payload IS the CLI). A mode flip re-installs with the other
10
+ * flag set (idempotent heal); a FAILED fetch writes sdk-runtime.error.log and
11
+ * throws — never a silent degradation.
12
+ *
13
+ * Layout (the marker/lock/error-log mechanism is, in shape, the one
14
+ * `agent-codex-sdk/src/sdk-runtime.ts` runs on):
15
+ *
16
+ * <worldHome>/sdk-runtime/ npm tree (`--ignore-scripts`)
17
+ * package.json the ANCHOR we write before install
18
+ * (private, never published): npm otherwise
19
+ * resolves the nearest ancestor project and
20
+ * may exit 0 having landed nothing here
21
+ * node_modules/@anthropic-ai/claude-agent-sdk/
22
+ * sdk.mjs the entry `loadClaudeQuery` imports
23
+ * <worldHome>/sdk-runtime.json marker — written LAST; a matching
24
+ * identity AND a live entry mean complete
25
+ * <worldHome>/sdk-runtime.lock atomic (mkdir) bootstrap lock
26
+ * <worldHome>/sdk-runtime.error.log written ONLY by a failed fetch
27
+ *
28
+ * Why the world home rather than the package root: the package root is the
29
+ * installed tarball — read-only in every real consumer install and shared by
30
+ * every world — while the world home is per-runtime, writable, and already the
31
+ * home of this adapter's own DSH state.
32
+ *
33
+ * Mode is re-derived on EVERY call: the host binary is resolved again (it may
34
+ * have appeared or vanished since the fetch), and a marker whose recorded mode
35
+ * no longer matches re-installs under the other flag set.
36
+ *
37
+ * Every path derives from the explicit `worldHome` argument — this module never
38
+ * reads the ambient `DSH_HOME` — and the SDK entry is imported BY RESOLVED PATH,
39
+ * never by a bare specifier: the runtime lives in the world home while this
40
+ * module lives in the plugin tarball, so a bare specifier could not resolve it
41
+ * and would quietly reintroduce the dependency this module exists to delete.
42
+ */
43
+ import { execFile } from "node:child_process";
44
+ import { appendFileSync, existsSync, mkdirSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
45
+ import { homedir } from "node:os";
46
+ import { join } from "node:path";
47
+ import { pathToFileURL } from "node:url";
48
+ import { promisify } from "node:util";
49
+ /** The coding-agent SDK this line drives (a plain npm package, never declared). */
50
+ export const SDK_PACKAGE = "@anthropic-ai/claude-agent-sdk";
51
+ /** The pin, written here rather than in `package.json` — the marker carries it. */
52
+ export const SDK_PACKAGE_VERSION = "0.3.263";
53
+ export function sdkRuntimeLayout(worldHome) {
54
+ return {
55
+ dir: join(worldHome, "sdk-runtime"),
56
+ marker: join(worldHome, "sdk-runtime.json"),
57
+ lock: join(worldHome, "sdk-runtime.lock"),
58
+ errorLog: join(worldHome, "sdk-runtime.error.log"),
59
+ };
60
+ }
61
+ /** The entry `loadClaudeQuery` imports — the SDK's `exports["."]`. */
62
+ export function sdkEntryPath(worldHome) {
63
+ return join(sdkRuntimeLayout(worldHome).dir, "node_modules", SDK_PACKAGE, "sdk.mjs");
64
+ }
65
+ /** Read the install marker; undefined when absent, unreadable or not our shape. */
66
+ export function readSdkRuntimeRecord(worldHome) {
67
+ const { marker } = sdkRuntimeLayout(worldHome);
68
+ if (!existsSync(marker))
69
+ return undefined;
70
+ try {
71
+ const parsed = JSON.parse(readFileSync(marker, "utf8"));
72
+ if (typeof parsed !== "object" || parsed === null)
73
+ return undefined;
74
+ const record = parsed;
75
+ if (record.sdkPackage !== SDK_PACKAGE)
76
+ return undefined;
77
+ if (typeof record.sdkVersion !== "string")
78
+ return undefined;
79
+ if (record.mode !== "host-binary" && record.mode !== "bundled-payload")
80
+ return undefined;
81
+ if (typeof record.createdAt !== "number")
82
+ return undefined;
83
+ return record;
84
+ }
85
+ catch {
86
+ return undefined;
87
+ }
88
+ }
89
+ /**
90
+ * The fast path: the marker is present, is ours, carries the current pin, was
91
+ * written under `wantedMode`, AND the SDK entry it promises still exists.
92
+ *
93
+ * The marker is written LAST, after a successful install, so its identity match
94
+ * normally implies the tree underneath; but an identity match alone is not
95
+ * enough. `fetchRuntime`'s failure path deletes the tree, a user may delete it
96
+ * by hand, and a half-wiped install can leave the (separate) marker behind. The
97
+ * entry `existsSync` is therefore the completeness contract: without it a stale
98
+ * marker would hand back a record whose `sdk.mjs` is gone and every prompt would
99
+ * fail `ERR_MODULE_NOT_FOUND` until someone removed the marker by hand. One stat
100
+ * costs nothing next to a 206 MB re-download.
101
+ */
102
+ function sdkRuntimeReady(worldHome, wantedMode) {
103
+ const record = readSdkRuntimeRecord(worldHome);
104
+ if (record === undefined)
105
+ return undefined;
106
+ if (record.sdkVersion !== SDK_PACKAGE_VERSION)
107
+ return undefined;
108
+ if (record.mode !== wantedMode)
109
+ return undefined;
110
+ if (!existsSync(sdkEntryPath(worldHome)))
111
+ return undefined;
112
+ return record;
113
+ }
114
+ /**
115
+ * Resolve the host Claude Code binary, most specific first:
116
+ * `CLAUDE_EXECUTABLE` (only when it exists) > `claude` on `PATH` >
117
+ * `~/.local/bin/claude` > `/usr/local/bin/claude`.
118
+ *
119
+ * `undefined` means "no local binary" and is the trigger for
120
+ * `bundled-payload` — never a bare `"claude"` fallback, which would pretend a
121
+ * host binary exists and make the SDK spawn a name that is not on `PATH`.
122
+ */
123
+ export function resolveClaudeBinary() {
124
+ const override = process.env.CLAUDE_EXECUTABLE;
125
+ if (override && existsSync(override))
126
+ return override;
127
+ for (const dir of (process.env.PATH ?? "").split(":").filter(Boolean)) {
128
+ const candidate = join(dir, "claude");
129
+ if (existsSync(candidate))
130
+ return candidate;
131
+ }
132
+ for (const candidate of [join(homedir(), ".local/bin/claude"), "/usr/local/bin/claude"]) {
133
+ if (existsSync(candidate))
134
+ return candidate;
135
+ }
136
+ return undefined;
137
+ }
138
+ /** The npm argv the runtime tree is installed with (kept pure for the test). */
139
+ export function sdkInstallArgv(version, mode) {
140
+ const argv = [
141
+ "install",
142
+ "--exact",
143
+ "--ignore-scripts",
144
+ "--omit=dev",
145
+ "--no-audit",
146
+ "--no-fund",
147
+ `${SDK_PACKAGE}@${version}`,
148
+ ];
149
+ if (mode === "host-binary")
150
+ argv.push("--omit=optional");
151
+ return argv;
152
+ }
153
+ /** A held lock older than this is treated as abandoned and reclaimed. */
154
+ const LOCK_STALE_MS = 10 * 60 * 1000;
155
+ const execFileAsync = promisify(execFile);
156
+ /**
157
+ * The production `install`: `SUPERD_SDK_NPM` (so the fetch can be pointed at
158
+ * pnpm/corepack) over an otherwise inherited environment, run in the runtime
159
+ * dir. `maxBuffer` is raised for npm's progress output.
160
+ */
161
+ async function defaultInstall(cmd, args, opts) {
162
+ await execFileAsync(cmd, args, {
163
+ cwd: opts.cwd,
164
+ env: opts.env ?? process.env,
165
+ maxBuffer: 64 * 1024 * 1024,
166
+ });
167
+ }
168
+ /**
169
+ * Return the record for `<worldHome>`'s durable SDK runtime, fetching it first
170
+ * when it is absent, stale or was installed under the other mode.
171
+ *
172
+ * Idempotent: a complete, matching runtime short-circuits without touching the
173
+ * network. Concurrent callers are separated by a `mkdir` lock — a second caller
174
+ * that finds a FRESH lock fails with a clear "fetch in progress" error (the SDK
175
+ * handle is a per-process singleton; the real race is another dsh process on
176
+ * the same world home), while an abandoned lock (older than
177
+ * {@link LOCK_STALE_MS}) is reclaimed so a crashed fetch cannot brick the line.
178
+ *
179
+ * A failure appends to `<worldHome>/sdk-runtime.error.log`, drops the partial
180
+ * tree and throws one actionable error — never a marker, so the next call heals.
181
+ */
182
+ export async function ensureSdkRuntime(worldHome, deps) {
183
+ const install = deps?.install ?? defaultInstall;
184
+ const now = deps?.now ?? Date.now;
185
+ const layout = sdkRuntimeLayout(worldHome);
186
+ const wantedMode = resolveClaudeBinary() !== undefined ? "host-binary" : "bundled-payload";
187
+ const ready = sdkRuntimeReady(worldHome, wantedMode);
188
+ if (ready !== undefined)
189
+ return ready;
190
+ mkdirSync(worldHome, { recursive: true });
191
+ acquireLock(layout);
192
+ try {
193
+ // Re-check inside the lock: a peer may have completed the fetch in the
194
+ // window between our check and the lock, in which case we pay nothing.
195
+ const done = sdkRuntimeReady(worldHome, wantedMode);
196
+ if (done !== undefined)
197
+ return done;
198
+ try {
199
+ return await fetchRuntime(worldHome, layout, wantedMode, install, now);
200
+ }
201
+ catch (error) {
202
+ const detail = error instanceof Error ? error.message : String(error);
203
+ try {
204
+ appendFileSync(layout.errorLog, `[${new Date().toISOString()}] agent-claude SDK runtime fetch failed: ${detail}\n`);
205
+ }
206
+ catch {
207
+ /* the trace is best-effort; the throw below is the contract */
208
+ }
209
+ rmSync(layout.dir, { recursive: true, force: true });
210
+ throw new Error(`agent-claude: could not fetch the SDK runtime ` +
211
+ `(${SDK_PACKAGE}@${SDK_PACKAGE_VERSION}, ${wantedMode} mode) at ${layout.dir}: ${detail}. ` +
212
+ `Trace: ${layout.errorLog}`);
213
+ }
214
+ }
215
+ finally {
216
+ rmSync(layout.lock, { recursive: true, force: true });
217
+ }
218
+ }
219
+ /** The private manifest that anchors npm to the runtime dir (never published). */
220
+ const RUNTIME_MANIFEST = '{"name":"superd-claude-sdk-runtime","private":true}\n';
221
+ /**
222
+ * The idempotent install step. npm is anchored to the runtime dir by a private
223
+ * manifest BEFORE install (without one, npm resolves the nearest ANCESTOR
224
+ * project and may exit 0 having landed nothing here), and the entry is
225
+ * verified on disk BEFORE the marker is written — exit 0 alone is not success.
226
+ */
227
+ async function fetchRuntime(worldHome, layout, mode, install, now) {
228
+ mkdirSync(layout.dir, { recursive: true });
229
+ // ANCHOR — npm resolves a tree from the nearest ancestor holding a
230
+ // package.json; with none in the runtime dir it walks up (on the real
231
+ // machine: to the repo root), reports "up to date" against THAT project,
232
+ // exits 0 and lands no tree here — yet the marker used to be written anyway,
233
+ // so every later loadClaudeQuery failed and each "heal" re-hit the same
234
+ // no-op install (reproduced twice, 2026-10-04). The private manifest pins
235
+ // the install to THIS directory; written only when missing or not
236
+ // byte-exact, so a re-run never rewrites a manifest that is already correct.
237
+ const manifest = join(layout.dir, "package.json");
238
+ let manifestExact = false;
239
+ if (existsSync(manifest)) {
240
+ try {
241
+ manifestExact = readFileSync(manifest, "utf8") === RUNTIME_MANIFEST;
242
+ }
243
+ catch {
244
+ manifestExact = false; // unreadable ⇒ treat as absent and re-anchor
245
+ }
246
+ }
247
+ if (!manifestExact)
248
+ writeFileSync(manifest, RUNTIME_MANIFEST);
249
+ const npm = process.env.SUPERD_SDK_NPM ?? "npm";
250
+ await install(npm, sdkInstallArgv(SDK_PACKAGE_VERSION, mode), { cwd: layout.dir });
251
+ // VERIFY — a zero exit is not success: an unanchored (or no-op) install
252
+ // resolves without producing the entry. Fail BEFORE the marker — it must
253
+ // only ever exist with the entry verifiably on disk, or the self-healing
254
+ // loop never converges. The catch in `ensureSdkRuntime` appends the trace
255
+ // (the note below names the cause) and wipes this partial tree.
256
+ const entry = sdkEntryPath(worldHome);
257
+ if (!existsSync(entry)) {
258
+ throw new Error(`npm install exited 0 but produced no entry at ${entry} — ` +
259
+ `the install was not anchored to the runtime dir ` +
260
+ `(npm likely resolved an ancestor project's tree instead of ${manifest})`);
261
+ }
262
+ // Marker LAST — a crash before this point leaves an incomplete runtime that
263
+ // the next call heals, never a false success.
264
+ const record = {
265
+ sdkPackage: SDK_PACKAGE,
266
+ sdkVersion: SDK_PACKAGE_VERSION,
267
+ mode,
268
+ createdAt: now(),
269
+ };
270
+ writeFileSync(layout.marker, JSON.stringify(record, null, 2) + "\n");
271
+ rmSync(layout.errorLog, { force: true });
272
+ return record;
273
+ }
274
+ /**
275
+ * mkdir-based mutex: returns owning the lock, throws when a FRESH lock is held
276
+ * by someone else, reclaims a lock older than {@link LOCK_STALE_MS}.
277
+ */
278
+ function acquireLock(layout) {
279
+ for (;;) {
280
+ try {
281
+ mkdirSync(layout.lock);
282
+ return;
283
+ }
284
+ catch (error) {
285
+ if (error.code !== "EEXIST")
286
+ throw error;
287
+ }
288
+ let ageMs;
289
+ try {
290
+ ageMs = Date.now() - statSync(layout.lock).mtimeMs;
291
+ }
292
+ catch {
293
+ continue; // released between the mkdir attempt and the stat — retry now
294
+ }
295
+ if (ageMs > LOCK_STALE_MS) {
296
+ rmSync(layout.lock, { recursive: true, force: true });
297
+ continue;
298
+ }
299
+ throw new Error(`agent-claude: another SDK runtime fetch is already in progress (lock ${layout.lock}). ` +
300
+ `Retry once it finishes; if no fetch is running, remove that lock directory.`);
301
+ }
302
+ }
303
+ /**
304
+ * Ensure the durable runtime and return the installed SDK entry's `query`
305
+ * function — the ONE function the client's default factory drives, and the only
306
+ * place a real download/install happens on the lazy path.
307
+ *
308
+ * The entry is imported by resolved path (see the header) and its shape is
309
+ * checked here: a tree that installs but exposes no `query` fails at fetch
310
+ * time, loud and near the cause, rather than inside the agent's first prompt.
311
+ */
312
+ export async function loadClaudeQuery(worldHome) {
313
+ await ensureSdkRuntime(worldHome);
314
+ const entry = sdkEntryPath(worldHome);
315
+ const module = (await import(pathToFileURL(entry).href));
316
+ if (typeof module.query !== "function") {
317
+ throw new Error(`agent-claude: ${entry} exposes no query function`);
318
+ }
319
+ return module.query;
320
+ }
@@ -18,10 +18,8 @@
18
18
  },
19
19
  "scripts": {
20
20
  "build": "tsc -p tsconfig.json",
21
- "test": "node --test test/*.test.mjs"
22
- },
23
- "dependencies": {
24
- "@anthropic-ai/claude-agent-sdk": "0.3.263"
21
+ "test": "node --test test/*.test.mjs",
22
+ "fetch-sdk-runtime": "node scripts/fetch-sdk-runtime.mjs"
25
23
  },
26
24
  "peerDependencies": {
27
25
  "@deepseek-ai/cordis": "^4.0.1",
@@ -30,6 +28,7 @@
30
28
  "@deepseek-ai/dsh-llm": ">=0.1.5-0 <0.3.0-0",
31
29
  "@deepseek-ai/dsh-session": ">=0.1.5-0 <0.3.0-0",
32
30
  "@deepseek-ai/dsh-session-persistence": ">=0.1.5-0 <0.3.0-0",
31
+ "@deepseek-ai/dsh-typert-protocol": ">=0.1.5-0 <0.3.0-0",
33
32
  "@deepseek-ai/dsh-user-approval": ">=0.1.5-0 <0.3.0-0",
34
33
  "@deepseek-ai/dsh-user-questions": ">=0.1.5-0 <0.3.0-0"
35
34
  },
@@ -52,6 +51,9 @@
52
51
  "@deepseek-ai/dsh-session-persistence": {
53
52
  "optional": true
54
53
  },
54
+ "@deepseek-ai/dsh-typert-protocol": {
55
+ "optional": true
56
+ },
55
57
  "@deepseek-ai/dsh-user-approval": {
56
58
  "optional": true
57
59
  },
@@ -1,58 +1,92 @@
1
1
  # @pgmi-builds/agent-codex
2
2
 
3
- superd 外壳下的 **Codex adapter**(super-dsh 线,AW-E):把 Codex CLI 经由
4
- `@openai/codex-sdk` 接进 DSH —— 会话、事件、模型目录、持久化全部按 DSH 语义呈现,
5
- WebUI 消费的是 DSH 服务提供的数据面。
3
+ superd 外壳下的 **Codex adapter**(super-dsh 线):把 Codex CLI 经由 **`codex app-server`**
4
+ 接进 DSH —— 会话、事件、模型目录、审批、持久化全部按 DSH 语义呈现,WebUI 消费的是 DSH
5
+ 服务提供的数据面。
6
6
 
7
7
  > 通用规则(定位 / 两形态 / 测试准则 / 接线域 / per-agent 定制权 / home 与 profile 布局)
8
8
  > 见上级目录的 `agent-adapter-dev-rules.md` —— 改本包前先读它。
9
+ > 迁移计划与实证:`docs/superpowers/plans/2026-10-03-agent-codex-app-server-migration.md`。
9
10
 
10
11
  ## 它是什么
11
12
 
12
13
  - 一个**普通 dsh plugin bundle**:`package.json` 的 `dsh.bundle.patch` 指向本目录的
13
14
  `cordis.patch.yml`(挂 codex provider、关掉它替代的原生行、重写权限三档表、pin 应用内目录选择器)。
14
15
  - 一个 **AgentFactory**:`src/index.ts` 里的 `CodexProvider` 在 `ctx.agents` 注册工厂,
15
- 每个会话一个 `CodexSdkClient`(真 `@openai/codex-sdk` 线程);`src/agent.ts` 把 Codex 线程事件
16
- 投影成 DSH 会话事件。
17
- - **不包含** 任何 hub 耦合:世界挂载由 hub 侧驱动(AW-B 用 `@pgmi-builds/superd-hub/world` 的
18
- mount carrier)。本包自己就足以点亮一个完整的 web app。
16
+ **每个会话一个 `codex app-server` 子进程**(`src/app-server-client.ts`);
17
+ `src/agent.ts` 把 app-server 通知流投影成 DSH 会话事件。
18
+ - **不包含** 任何 hub 耦合:世界挂载由 hub 侧驱动。本包自己就足以点亮一个完整的 web app。
19
+
20
+ ## 线别(2026-10-03 迁移)
21
+
22
+ **app-server 线**取代了原 `@openai/codex-sdk` 线(SDK 依赖已从本包与主 pack 移除)。
23
+ SDK 线在 adapter 里登记的一整排降级被 app-server 协议面直接消掉:
24
+
25
+ | 能力 | SDK 线(已退役) | app-server 线(现役) |
26
+ |---|---|---|
27
+ | 打断 | 只有 AbortSignal(掐断本轮 stream) | **`turn/interrupt`** |
28
+ | steer | 无 → 排队 followUp 近似 | **`turn/steer`**(注入进行中的 turn) |
29
+ | 运行时换模型 | 无 → 下一轮 `resumeThread` | **`turn/start.model`**(下一轮起生效,线程不重建) |
30
+ | 运行时审批档 | 无 → launch-only 预设 | **`turn/start.approvalPolicy`**(折叠 `approval/policy`,`/permission` 可用) |
31
+ | 运行时沙箱档 | 无 → launch-only | **`turn/start.sandboxPolicy`**(折叠 `sandbox/mode`) |
32
+ | 审批人机回路 | 无(`sendRaw` 是 no-op) | **`*/requestApproval` → DSH `ctx.approval`**(Web UI ApprovalPanel) |
33
+ | 用户消息回显 | 无(SDK item 无 `user_message`) | `item/started userMessage` |
34
+ | token usage | 只在本轮 `turn/completed`,滞后 | `thread/tokenUsage/updated`(本轮 `turn_end` 附着) |
35
+
36
+ **未接(登记 backlog)**:`thread/compact/start`(`/compact` 仍显式拒绝)、
37
+ `thread/list` 会话清单(DSH 日志仍是权威)、`skills/list` / `mcpServerStatus/list` 面板、
38
+ `thread/fork`、`requestUserInput` / `mcpServer/elicitation`(fail-closed 应答)、
39
+ collab/subagent 项。
19
40
 
20
41
  ## 单独跑起来(首选开发/验收形态)
21
42
 
22
- ```bash
23
- # 在仓根执行;端口默认 4988,LAN 走只绑 LAN IP 的 socat relay
24
- SUPERD_APP_PORT=4989 bash super-dsh/test/start-codex-app.sh
25
- # 脚本会打印 loopback / LAN token URL;停止:
26
- systemctl --user stop superd-codex-app-<port>-test superd-codex-app-<port>-relay
27
- ```
28
-
29
- 环境变量:`SUPERD_APP_PORT`(端口)、`SUPERD_APP_HOME`(默认 `$WT/.tests`)、
30
- `SUPERD_LAN_HOST`(默认 `192.168.31.130`)、`CODEX_TRACE=1`(provider/agent 追踪)。
43
+ 场景级验证唯一入口 = **整包 pack 线**(`super-dsh/test/start-4999.sh`)+ dev3 目标机实装
44
+ (2026-09-24「场景退役」裁决);本包不再有 standalone 启动脚本。
31
45
 
32
46
  ## Home 与 profile 布局(user 裁定)
33
47
 
34
48
  | 位置 | 内容 |
35
49
  |---|---|
36
50
  | `$DSH_HOME`(测试 = `<repo>/.tests`) | 测试 DSH home |
37
- | `$DSH_HOME/agents/codex` | **Codex app home**:`config.toml` / `auth.json` / 模型 catalog(standalone 时代的 `setup-codex-home.mjs` 拷入已随 2026-09-24 场景退役删除)+ Codex 自己的 rollout/state |
38
- | `$DSH_HOME/profiles/codex` | 本 app 的 dsh profile(bundles + `cordis.patch.yml`) |
39
- | `$DSH_HOME/sessions/...` | **DSH 会话日志**(上游 `session-persistence-jsonl`,zstd)——list/replay 都由此提供 |
40
-
41
- **禁止**自造 per-app home(如 `.tests/codex-app`)。
51
+ | 原生 `~/.codex`(`CODEX_HOME` 才重定向) | **Codex 原生 app home**:`config.toml` / `auth.json` / 模型 catalog / rollout / state。adapter **绝不重定向**(红线 §5),**从不写入**(单向阀) |
52
+ | `$DSH_HOME/agents/codex` | 该 world 的 **DSH home**:DSH 会话日志副本、storages、`dsh-sessions.json` 映射 |
53
+ | `$DSH_HOME/profiles/superd-pub` | 整包线的 dsh profile(tarball 物理安装) |
42
54
 
43
55
  ## 关键实现点
44
56
 
45
- - **session 身份 = 一对一映射**:DSH session id 是权威;Codex thread id 在其物化后写入
46
- `<codexHome>/dsh-sessions.json`(`src/session-map.ts`)。resume 只经该映射重挂线程,查不到即
47
- fail-closed。**不自建 rollout 扫描做 list,也不自解析 JSONL 做 replay。**
57
+ - **lazy spawn(硬门)**:`CodexAppServerClient.create()` 零 IO、零子进程;`ensureStarted()`
58
+ 在**首个真实 prompt** 才 spawn + `initialize` + `thread/start|resume`。查看/创建/resume
59
+ 一个会话不产生任何 native 进程。单测直接钉住这条不变量(transport factory 调用计数 = 0)。
60
+ - **进程回收**:子进程 `detached: true`(自有进程组),close 时先 `SIGTERM` 进程组、
61
+ 宽限后 `SIGKILL`,再 `child.kill()` 兜底——stdio MCP 后代 `setsid` 逃离时仍能被扫到。
62
+ - **session 身份 = 一对一映射**:DSH session id 是权威;Codex thread id 在**线程 attach**
63
+ 时经 `onThreadAttached` 写入 `<dshHome>/dsh-sessions.json`(`src/session-map.ts`)。
64
+ resume 只经该映射重挂线程,查不到即 fail-closed。
48
65
  - **DSH-native 持久化**:工厂按上游 agent-loop 的方式持有写通道(create `persistence.create` /
49
66
  resume `persistence.open(id,'write')` → `read` → `interruptedTurnClosers` → `prepare`),
50
67
  发布点刷未存后缀,dispose 时 `handle.close()` 排空。
51
- - **每会话路由钉住**:工厂在 create/resume 时同步钉住本会话路由(追加 `model/selection` +
52
- `installModelSelection`),**不依赖全局默认模型的落盘时序**(共享 test home 里 settings 默认可能属别线)。
53
- - **事件映射**:`system/message`(Codex 系统提示,rollout HEAD `base_instructions`)、
54
- `request/context`(provider/model/contextWindow)、`assistant/message`(reasoning 用
55
- `{type:'reasoning',text}`)、`tool/call`(原始 JSON 串)/`tool/result`、`step/start|end`、`turn/start|end`。
68
+ - **每会话路由钉住**:create/resume 时同步钉住本会话路由(追加 `model/selection` +
69
+ `installModelSelection`),**不依赖全局默认模型的落盘时序**。
70
+ - **每轮权限折叠**:`agent.ts` 在每次 `turn/start` 前把会话日志的
71
+ `approval/policy` / `sandbox/mode`(无则由 preset 表兜底)折成 codex 覆盖
72
+ (`permission.ts` 的 `turnPolicyFromEvents`);`CODEX_APPROVAL_MODE` 只覆盖审批半边。
73
+ - **审批桥 fail-closed**:`app-server-approvals.ts` 把 server-request 映射到
74
+ `ctx.approval.request()`;无 answerer / 抛错 / 超时(`CODEX_APPROVAL_TIMEOUT_MS`,默认 5 分钟)
75
+ 一律 **decline**,绝不让 codex 挂死。
76
+ - **事件映射**:`system/message`(rollout HEAD `base_instructions`)、`request/context`、
77
+ `assistant/message`(reasoning 用 `{type:'reasoning',text}`)、
78
+ `tool/call`(原始 JSON 串)/`tool/result`、`step/start|end`、`turn/start|end`、`todo/write`。
79
+
80
+ ## 环境变量
81
+
82
+ | 变量 | 作用 |
83
+ |---|---|
84
+ | `CODEX_TRACE=1` | provider/agent/app-server 追踪(stderr) |
85
+ | `CODEX_BIN` | 覆盖 codex 入口脚本(测试);默认 = 本包 pin 的 `@openai/codex/bin/codex.js` |
86
+ | `CODEX_HOME` | Codex 原生 home 重定向(**测试专用**;生产路径零重定向) |
87
+ | `CODEX_APPROVAL_MODE` | 覆盖审批档(`never`/`on-request`/`on-failure`/`untrusted`;非法值忽略) |
88
+ | `CODEX_APPROVAL_TIMEOUT_MS` | 审批应答超时;`0` 关闭;默认 300000 |
89
+ | `CODEX_IDLE_EXIT_MS` | 会话空闲后拆客户端;`0` 关闭;默认 600000 |
56
90
 
57
91
  ## 测试
58
92
 
@@ -63,12 +97,83 @@ SUPERD_DSH_ANCHOR=<repo>/upstream/deepseek-harness/package.json \
63
97
  node --test test/*.test.mjs
64
98
  ```
65
99
 
66
- live 门控:`SUPERD_CODEX_LIVE=1`(会真跑一次 Codex turn)。
100
+ 单测全部是 fake:`test/fixtures/fake-app-server.mjs` 是一个**可编程的 app-server 替身**
101
+ (真 JSONL JSON-RPC 分帧、真子进程),wire / client 测试用真管道驱动它;投影器与审批桥是纯函数。
102
+ **不 spawn 真 codex、不打模型。**
103
+
104
+ ## 已知边界(app-server 线 v1)
67
105
 
68
- ## 已知边界(V1)
106
+ - `thread/compact/start` 未接 → `/compact` 仍以显式错误拒绝;
107
+ - 会话清单仍由 DSH 日志提供(不接 `thread/list`,遵守 mapping-only);
108
+ - `requestUserInput` / `mcpServer/elicitation` / `permissions/requestApproval` 一律 fail-closed;
109
+ - collab / subagent / `thread/fork` / skills / MCP 面板未接;
110
+ - 内存镜像 `getMessages()` 仅供诊断,权威转录是 DSH 会话日志。
69
111
 
70
- - 审批仅 launch-only 预设(SDK 无运行时审批;app-server 线才有 `*/requestApproval`);
71
- - 无 steer / 无运行时换模型(下一轮生效)/ usage 滞后一轮 / 无 fork;
72
- - `compaction/*`、`todo/write`、`subagent/*` 未接(见规则文档 §9.4 backlog);
73
- - **AW-B world 挂载移植**:本包已无 world-plugin;若要在 AW-B 的 mount carrier 下作为世界被挂载,
74
- 由 hub 侧驱动(移植为跟踪项)。
112
+ ## 多 provider 面(Hub + DSH,2026-10-04 重做)
113
+
114
+ Codex 是**单 provider** harness:它自己的 `config.toml` 只能写一个 `model_provider`。provider 的**清单**
115
+ 在 Codex 家里无处安放——那正是 cc-switch 存在的理由;而「这个会话现在跑在哪个 provider 上」在 DSH
116
+ 里已有公认答案(`session/selectModel` + 持久的 `model/selection`)。所以本线**只做清单**,选择交给 DSH:
117
+
118
+ - adapter 的 store 是**正本**(canonical),原生 `config.toml` 是**来源**(source),单向读入;
119
+ - UI 不再有「active provider」概念——界面里切换 provider/model 就是 DSH 自带模型选择器,与本仓其它
120
+ harness 完全一致;
121
+ - provider 由**进程参数**注入子进程(`-c model_providers.<id>.*`),**不写任何原生文件**。
122
+
123
+ ### 分账
124
+
125
+ | 位置 | 内容 |
126
+ |---|---|
127
+ | `$DSH_HOME/agents/codex/codex-providers.json`(0600) | **正本**:provider 清单 + 被显式删除的 endpoint 墓碑(含 API key) |
128
+ | `$DSH_HOME/agents/codex/codex-catalogs/<id>.json` | 本 adapter 自有的 per-provider 模型目录;经 `-c model_catalog_json=<绝对路径>` 注入 |
129
+ | `~/.codex/config.toml` | 用户原生:**只读**。启动时 + 每 10 分钟读一次,按 endpoint URL 去重折进正本 |
130
+ | `~/.codex/auth.json` | 用户原生:**只读**。`apiKey` 形态的 key 走子进程环境,不走这里 |
131
+
132
+ ### 同步(2026-10-04 user 裁决)
133
+
134
+ `syncFromNative()`:启动时 + 每 10 分钟(`SYNC_INTERVAL_MS = 600_000`)读 `config.toml` 的**全部**
135
+ `[model_providers.<id>]` 表,按 endpoint URL 去重折进正本。新 endpoint 在库里没有名字,**就用 URL 端点
136
+ 当名字**(`label = baseUrl`)——Codex 的 catalog 是「当前在役 provider」专用的,所以非在役 provider 的
137
+ 模型清单**确实无从得知**,此时存 `models: []`(面板显示「no models yet」,该 provider 不注册路由),
138
+ 而不是编一个。同步只**增/刷**、永不删;唯一的例外是用户显式删除留下的**墓碑**(endpoint key),
139
+ 否则「删除」会在十分钟后自己撤销。
140
+
141
+ ### 选择与切换
142
+
143
+ - **路由 = DSH 的**:每个有模型的 provider 注册一条 `ctx.llm` 路由,于是 `session/modelCatalog` 天然
144
+ 按 provider 分组,`session/selectModel` 天然持久(`model/selection` + `modelSelection` 投影);
145
+ - **turn 边界判定 = DSH 的**:agent 每轮按 `pending ?? request/header ?? agentDefaultModel ?? options`
146
+ 解析路由(`codex-route.ts`),并回写 `request/header`——于是投影的 `lastUsed`/`next`、
147
+ `consumeSelection`、以及「会话上次跑在哪个 provider」全部由上游自洽,UI 重放自然准确;
148
+ - **中途切换不用打断**:`turn/start` 的 `model` 覆盖「本轮及后续轮」,所以换**模型**是原生的;换
149
+ **provider** 必须换子进程——`turn/start` 没有 provider 字段,而打给**已加载**线程的 `thread/resume`
150
+ 是「接受但忽略」(2026-10-03 实测:要 `zhipu-cn` 回 `deepseek`)。故 agent 在**下一轮冷启动**时
151
+ kill 掉旧 child、用新 `-c` 起新 child、`thread/resume` **同一个 thread id**,历史与上下文完整保留。
152
+ turn 中发起的切换落在下一轮(DSH 的 pending 语义),**不打断在跑的 turn**。
153
+
154
+ ### 启动注入(零原生写入)
155
+
156
+ `codex-launch.ts` 生成 argv/env;三条实测约束写死在注释里:
157
+
158
+ 1. hyphenated id 在 `-c` 点路径里合法(`model_providers.zhipu-cn.…`)⇒ 原生表名可原样作 provider id;
159
+ 2. `model_catalog_json` 作为 override 时按**进程 cwd** 解析 ⇒ 路径必须绝对(相对路径实测 `ENOENT`);
160
+ 3. `env_key` 是唯一不经过 auth manager 的形态 ⇒ 库里的 key 以合成变量名(`SUPERD_CODEX_KEY_<ID>`)
161
+ 经**子进程环境**交给 codex,`auth.json` 完全不碰。**这也顺带解决了 ChatGPT 登录遮蔽 keyed provider
162
+ 的老问题**(`requires_openai_auth` 才会被 `auth_mode=chatgpt` 遮蔽);只有「seeded」形态
163
+ (`apiKey` 且 value 为空 = key 在 auth.json 里)才回落到 `requires_openai_auth`,面板仍会告警。
164
+
165
+ ### RPC
166
+
167
+ `TypertRemoteService`,namespace `codexProviders`(hub 经 `/hub/api?agent=codex` 原样转发):
168
+ `get` / `save` / `remove`。**API key 只写不读**——wire 上只有 `hasKey`,绝不回传值。
169
+ 没有 `select`:选择是 DSH 的 per-session 状态。
170
+
171
+ ### 模型目录必须克隆模板
172
+
173
+ codex 0.154 对 catalog 条目要求 `slug`/`display_name`/`supported_reasoning_levels`/`shell_type`/
174
+ `visibility`/`supported_in_api`/`priority`/`support_verbosity` 以及**嵌套 struct** `truncation_policy`…
175
+ 字段链未终止,故只从既有条目克隆并覆盖身份字段。目录是**替换式**:一份目录只写它那个 provider 的模型
176
+ (2026-10-03 修正:早期「吸收且永不丢弃」让 Chat 模型清单只增不减)。
177
+
178
+ 记录:`docs/superpowers/plans/2026-10-03-codex-multi-provider-hub.md` +
179
+ `docs/test-reports/2026-10-04-codex-multi-provider-onthefly.md`。
@@ -1,4 +1,4 @@
1
- # @pgmi-builds/agent-codex bundle patch (SDK line, standalone form).
1
+ # @pgmi-builds/agent-codex bundle patch (app-server line, standalone form).
2
2
  # This file is the `dsh.bundle.patch` layer of the adapter package: when the
3
3
  # codex world profile mounts the bundle, the boot merges THIS patch — mount
4
4
  # the Codex provider plus disable the native components it replaces.
@@ -8,16 +8,17 @@
8
8
  # fixture carries the 127.0.0.1:0 listener posture).
9
9
 
10
10
  # Mount the Codex provider: a Cordis Service that registers an AgentFactory
11
- # on ctx.agents. The provider drives @openai/codex-sdk threads (one
12
- # CodexSdkClient per session) plus resume (--resume-thread); the model
13
- # selector is served by a Codex-backed LlmAdapter reading the codex home's
14
- # config.toml + model_catalog_json.
11
+ # on ctx.agents. The provider drives one `codex app-server` child per session
12
+ # (stdio JSONL JSON-RPC; CodexAppServerClient, spawned lazily on the first
13
+ # prompt) plus thread resume by id; the model selector is served by a
14
+ # Codex-backed LlmAdapter reading the codex home's config.toml +
15
+ # model_catalog_json.
15
16
  - insert:
16
17
  - id: codex-provider
17
18
  name: '@pgmi-builds/agent-codex'
18
19
 
19
20
  # Codex owns the live agent transcript (the rollout JSONL); disable the
20
- # built-in loop so it does not double-run alongside the SDK thread.
21
+ # built-in loop so it does not double-run alongside the app-server thread.
21
22
  - id: agent-loop
22
23
  disabled: true
23
24
 
@@ -29,7 +30,7 @@
29
30
  - id: llm-pi-ai
30
31
  disabled: true
31
32
 
32
- # Codex is single-mode: the SDK thread drives tools itself, so no Dash preset
33
+ # Codex is single-mode: the app-server thread drives tools itself, so no Dash preset
33
34
  # composition is needed (the provider ships the fixed single-preset roster).
34
35
  # 0.1.7: the host AgentPresetRegistry row (renamed `agent-presets` ->
35
36
  # `agent-preset-registry`) stays MOUNTED — the adapter registers its one
@@ -43,9 +44,12 @@
43
44
  # list/read/replay/workspace grouping are ordinary DSH services over that log.
44
45
  # The adapter no longer provides its own persistence.
45
46
 
46
- # Codex has no runtime approval control — the adapter maps the preset onto
47
- # the launch-only --approval-mode flag (3:3 table in src/permission.ts).
48
- # Default to danger-full-access. Restate the full 3-preset table: a patch
47
+ # The app-server line DOES have runtime approval control: the adapter folds
48
+ # `approval/policy` + `sandbox/mode` from the session log into the
49
+ # `turn/start` per-turn overrides (src/permission.ts turnPolicyFromEvents),
50
+ # so `/permission` is live. This table still supplies the LAUNCH posture and
51
+ # the fold's fallback. Default to danger-full-access. Restate the full
52
+ # 3-preset table: a patch
49
53
  # with only defaultPreset silently drops the bundle's 3-preset table to the
50
54
  # plugin's built-in 2, breaking the 3:3 Dash<->Codex pairing.
51
55
  - id: permission