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,71 @@
1
+ /** The approval policies Codex actually implements (unknown values are ignored). */
2
+ const APPROVAL_POLICIES = ["never", "on-request", "on-failure", "untrusted"];
3
+ /** The Dash preset table (mirrors the base bundle's permission plugin config). */
4
+ const PRESETS = {
5
+ "danger-full-access": { sandbox: "danger-full-access", approval: "never", codexPolicy: "never" },
6
+ "workspace-write": { sandbox: "workspace-write", approval: "ask", codexPolicy: "on-request" },
7
+ "read-only": { sandbox: "read-only", approval: "ask", codexPolicy: "never" },
8
+ };
9
+ /** Narrow an untrusted value to a known preset name. */
10
+ export function isPresetName(raw) {
11
+ return typeof raw === "string" && Object.hasOwn(PRESETS, raw);
12
+ }
13
+ /**
14
+ * `CODEX_APPROVAL_MODE` when it names an approval policy Codex implements,
15
+ * else undefined. An invalid value is IGNORED (the preset mapping applies).
16
+ */
17
+ export function envApprovalMode() {
18
+ const raw = process.env.CODEX_APPROVAL_MODE;
19
+ return raw !== undefined && APPROVAL_POLICIES.includes(raw) ? raw : undefined;
20
+ }
21
+ /**
22
+ * Map a preset onto Codex's `--approval-mode` value; no preset → never.
23
+ * A value that is ALREADY a codex approval policy (the env override rides
24
+ * through the same folded call) passes through untouched; unknown preset
25
+ * names degrade to never.
26
+ */
27
+ export function codexApprovalMode(preset) {
28
+ if (preset !== undefined && APPROVAL_POLICIES.includes(preset))
29
+ return preset;
30
+ return isPresetName(preset) ? PRESETS[preset].codexPolicy : "never";
31
+ }
32
+ /**
33
+ * The default preset for FUTURE sessions, as the permission service
34
+ * resolves it (settings-backed; the profile patch pins the base default).
35
+ * Undefined when the service is absent or its getter fails — callers fall
36
+ * back to the codex default policy.
37
+ */
38
+ export function defaultPermissionPreset(ctx) {
39
+ const service = ctx.get("permissionPresets");
40
+ if (service === undefined)
41
+ return undefined;
42
+ try {
43
+ const preset = service.defaultPreset;
44
+ return isPresetName(preset) ? preset : undefined;
45
+ }
46
+ catch {
47
+ return undefined;
48
+ }
49
+ }
50
+ /** The session's effective preset: the last `permission/preset` event. */
51
+ export function presetFromEvents(events) {
52
+ for (let index = events.length - 1; index >= 0; index -= 1) {
53
+ const event = events[index];
54
+ if (event.type === "permission/preset" && isPresetName(event.data?.preset))
55
+ return event.data.preset;
56
+ }
57
+ return undefined;
58
+ }
59
+ /**
60
+ * The three Dash events `pinInitialPermission` stamps for a fresh session,
61
+ * in its append order (preset, sandbox, approval). Cold replay prepends
62
+ * them so the UI shows the session's permission after wrapper restarts.
63
+ */
64
+ export function permissionEventsFor(preset, time) {
65
+ const spec = PRESETS[preset];
66
+ return [
67
+ { type: "permission/preset", time, data: { preset } },
68
+ { type: "sandbox/mode", time, data: { mode: spec.sandbox } },
69
+ { type: "approval/policy", time, data: { policy: spec.approval } },
70
+ ];
71
+ }
@@ -0,0 +1,307 @@
1
+ /**
2
+ * sdk-runtime.ts — the lazy, durable Codex SDK runtime the opt-in `codex-sdk`
3
+ * line runs on.
4
+ *
5
+ * 2026-10-04 user ruling (dev-rules §7 note + super-dsh/AGENTS.md): this
6
+ * package declares NO SDK dependency — not in `dependencies`, not in
7
+ * `optionalDependencies`, and not in `peerDependencies` — and ships NO
8
+ * postinstall/allow-scripts hook. Nothing is fetched at install time; the FIRST
9
+ * real prompt fetches the runtime into the world home, where it is durable
10
+ * across sessions, restarts and package upgrades. A second use reuses it
11
+ * (idempotent), and a FAILED fetch leaves a trace on disk instead of silently
12
+ * degrading into a confusing import crash at the first prompt.
13
+ *
14
+ * Why the world home rather than the package root: the package root is the
15
+ * installed tarball — read-only in every real consumer install, and shared by
16
+ * every world. The world home is per-runtime, writable, and already the home of
17
+ * this adapter's own DSH state (`dsh-sessions.json`).
18
+ *
19
+ * Layout (the codex line is far simpler than the OMP one: the runtime is a
20
+ * plain npm tree, so there is no bun, no sidecar, no per-platform native
21
+ * binding dance):
22
+ *
23
+ * <worldHome>/sdk-runtime/
24
+ * package.json written by us (private, never published)
25
+ * node_modules/ `npm install --exact --ignore-scripts --omit=dev`
26
+ * node_modules/@openai/codex-sdk/dist/index.js the entry `loadCodexSdk` imports
27
+ * <worldHome>/sdk-runtime.json marker — written LAST, its presence is the contract
28
+ * <worldHome>/sdk-runtime.lock atomic (mkdir) bootstrap lock
29
+ * <worldHome>/sdk-runtime.error.log written ONLY by a failed fetch
30
+ *
31
+ * Both packages are pinned together at 0.154.0 — the SDK resolves the native
32
+ * `@openai/codex` binary package as its one dependency, so pinning only the
33
+ * SDK would let a re-resolve drift the binary underneath it.
34
+ *
35
+ * The entry is imported BY RESOLVED PATH, never by bare specifier: the runtime
36
+ * lives in the world home while this module lives in the plugin tarball, so a
37
+ * bare-specifier form could not resolve it (and would quietly reintroduce the
38
+ * dependency this module exists to delete). The package guard in
39
+ * test/package-contract.test.mjs scans the EMITTED JS for that shape, so keep
40
+ * this paragraph free of a literal import statement.
41
+ *
42
+ * Measured probe (2026-10-04): `@openai/codex-sdk@0.154.0` exports `"." ->
43
+ * "./dist/index.js"` and its only dependency is `@openai/codex@0.154.0`.
44
+ */
45
+ import { execFile } from "node:child_process";
46
+ import { appendFileSync, existsSync, mkdirSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
47
+ import { dirname, join } from "node:path";
48
+ import { fileURLToPath, pathToFileURL } from "node:url";
49
+ import { promisify } from "node:util";
50
+ /** The coding-agent SDK this line drives (a plain npm package). */
51
+ export const SDK_PACKAGE = "@openai/codex-sdk";
52
+ /** The native binary package the SDK resolves at run time; pinned with the SDK. */
53
+ export const SDK_BINARY_PACKAGE = "@openai/codex";
54
+ /**
55
+ * Pin of BOTH packages — the same 0.154.0 the app-server (primary) line pins,
56
+ * so the two Codex faces agree on the wire behaviour they bridge.
57
+ */
58
+ export const SDK_PACKAGE_VERSION = "0.154.0";
59
+ /** The entry `loadCodexSdk` imports, relative to the runtime dir. */
60
+ const SDK_ENTRY_RELATIVE = ["node_modules", SDK_PACKAGE, "dist", "index.js"];
61
+ export function sdkRuntimeLayout(worldHome) {
62
+ const dir = join(worldHome, "sdk-runtime");
63
+ return {
64
+ dir,
65
+ manifest: join(dir, "package.json"),
66
+ nodeModules: join(dir, "node_modules"),
67
+ entry: join(dir, ...SDK_ENTRY_RELATIVE),
68
+ marker: join(worldHome, "sdk-runtime.json"),
69
+ lock: join(worldHome, "sdk-runtime.lock"),
70
+ errorLog: join(worldHome, "sdk-runtime.error.log"),
71
+ };
72
+ }
73
+ /** Read the install marker; undefined when absent or unreadable. */
74
+ export function readSdkRuntimeRecord(worldHome) {
75
+ const { marker } = sdkRuntimeLayout(worldHome);
76
+ if (!existsSync(marker))
77
+ return undefined;
78
+ try {
79
+ const parsed = JSON.parse(readFileSync(marker, "utf8"));
80
+ return typeof parsed === "object" && parsed !== null ? parsed : undefined;
81
+ }
82
+ catch {
83
+ return undefined;
84
+ }
85
+ }
86
+ /**
87
+ * Fast path: the runtime is complete AND matches `identity` → its record.
88
+ *
89
+ * Returns undefined when the marker is absent/corrupt, when any identity field
90
+ * differs (the adapter or a pin changed — a pin bump must refetch), or when the
91
+ * recorded entry file is gone. In every such case the caller must (re)fetch.
92
+ */
93
+ export function sdkRuntimeReady(worldHome, identity) {
94
+ const record = readSdkRuntimeRecord(worldHome);
95
+ if (record === undefined)
96
+ return undefined;
97
+ if (record.pluginVersion !== identity.pluginVersion)
98
+ return undefined;
99
+ if (record.packageVersion !== identity.packageVersion)
100
+ return undefined;
101
+ if (record.binaryVersion !== identity.binaryVersion)
102
+ return undefined;
103
+ if (!existsSync(sdkRuntimeLayout(worldHome).entry))
104
+ return undefined;
105
+ return record;
106
+ }
107
+ /** A held lock older than this is treated as abandoned and reclaimed. */
108
+ const LOCK_STALE_MS = 30 * 60 * 1000;
109
+ /** The npm command the runtime tree is installed with (kept pure for the test). */
110
+ export function sdkInstallArgv(packageVersion = SDK_PACKAGE_VERSION) {
111
+ return [
112
+ "install",
113
+ "--exact",
114
+ "--ignore-scripts",
115
+ "--omit=dev",
116
+ `${SDK_PACKAGE}@${packageVersion}`,
117
+ `${SDK_BINARY_PACKAGE}@${packageVersion}`,
118
+ ];
119
+ }
120
+ /**
121
+ * The PRODUCTION {@link SdkRuntimeDeps}: the npm invocation plus the marker
122
+ * identity read from this package's own manifest.
123
+ *
124
+ * Kept IN THE MODULE (not in `scripts/fetch-sdk-runtime.mjs`) so the manual
125
+ * pre-warm script and the first-prompt lazy fetch (`loadCodexSdk`) share ONE
126
+ * implementation: a second copy is exactly how the two paths would drift.
127
+ *
128
+ * `pluginDir` defaults to this package's root (the parent of the compiled
129
+ * `dist/`), from which the marker identity reads the package's own version.
130
+ *
131
+ * @param pluginDir - package root holding `package.json`.
132
+ * @returns deps for {@link ensureSdkRuntime}.
133
+ */
134
+ export function createSdkRuntimeDeps(pluginDir = dirname(dirname(fileURLToPath(import.meta.url)))) {
135
+ return {
136
+ run: runCommand,
137
+ pluginVersion: readPluginVersion(pluginDir),
138
+ pluginDir,
139
+ log: (line) => process.stderr.write(`[agent-codex-sdk] ${line}\n`),
140
+ };
141
+ }
142
+ /** The version of the adapter package at `pluginDir` (the marker identity half). */
143
+ export function readPluginVersion(pluginDir) {
144
+ const raw = readFileSync(join(pluginDir, "package.json"), "utf8");
145
+ const parsed = JSON.parse(raw);
146
+ if (typeof parsed.version !== "string" || parsed.version === "") {
147
+ throw new Error(`codex-sdk: ${join(pluginDir, "package.json")} carries no version`);
148
+ }
149
+ return parsed.version;
150
+ }
151
+ const execFileAsync = promisify(execFile);
152
+ /**
153
+ * Run a command to completion, inheriting the environment except for the
154
+ * explicit overrides ({@link SdkRuntimeRunOptions.env}); an `undefined`
155
+ * override REMOVES the key from the child. Rejects on a non-zero exit.
156
+ */
157
+ async function runCommand(cmd, args, opts) {
158
+ const env = { ...process.env };
159
+ if (opts?.env !== undefined) {
160
+ for (const [key, value] of Object.entries(opts.env)) {
161
+ if (value === undefined)
162
+ delete env[key];
163
+ else
164
+ env[key] = value;
165
+ }
166
+ }
167
+ await execFileAsync(cmd, args, { cwd: opts?.cwd, env, maxBuffer: 64 * 1024 * 1024 });
168
+ }
169
+ /**
170
+ * Return the record for `<worldHome>`'s durable SDK runtime, fetching it first
171
+ * when it is absent, incomplete or does not match the running adapter.
172
+ *
173
+ * Idempotent: a complete, matching runtime short-circuits without touching the
174
+ * network. Concurrent callers: the bootstrap is guarded by a `mkdir` lock — a
175
+ * second caller that finds a FRESH lock throws a clear "fetch in progress"
176
+ * error (no waiting; the SDK handle is a per-process singleton and the real
177
+ * race is another dsh process on the same world home), while an abandoned lock
178
+ * (older than {@link LOCK_STALE_MS}) is reclaimed so a crashed fetch cannot
179
+ * brick the line.
180
+ *
181
+ * Failures append to `<worldHome>/sdk-runtime.error.log` and throw a single
182
+ * actionable error — never a partial runtime behind a success marker.
183
+ */
184
+ export async function ensureSdkRuntime(worldHome, deps) {
185
+ const layout = sdkRuntimeLayout(worldHome);
186
+ const identity = {
187
+ pluginVersion: deps.pluginVersion,
188
+ packageVersion: SDK_PACKAGE_VERSION,
189
+ binaryVersion: SDK_PACKAGE_VERSION,
190
+ };
191
+ const ready = sdkRuntimeReady(worldHome, identity);
192
+ if (ready !== undefined) {
193
+ deps.log(`codex-sdk: runtime ready (${SDK_PACKAGE}@${ready.packageVersion})`);
194
+ return ready;
195
+ }
196
+ mkdirSync(worldHome, { recursive: true });
197
+ acquireLock(layout, deps);
198
+ try {
199
+ // Re-check inside the lock: a peer may have completed the fetch in the
200
+ // window between our check and the lock, in which case we pay nothing.
201
+ const done = sdkRuntimeReady(worldHome, identity);
202
+ if (done !== undefined)
203
+ return done;
204
+ try {
205
+ return await buildRuntime(layout, deps, identity);
206
+ }
207
+ catch (error) {
208
+ const detail = error instanceof Error ? error.message : String(error);
209
+ const stamp = new Date().toISOString();
210
+ try {
211
+ appendFileSync(layout.errorLog, `[${stamp}] codex-sdk runtime fetch failed: ${detail}\n`);
212
+ }
213
+ catch {
214
+ /* the trace is best-effort; the throw below is the contract */
215
+ }
216
+ throw new Error(`codex-sdk: could not fetch the SDK runtime ` +
217
+ `(${SDK_PACKAGE}@${SDK_PACKAGE_VERSION} + ${SDK_BINARY_PACKAGE}@${SDK_PACKAGE_VERSION}) at ${layout.dir}: ${detail}. ` +
218
+ `Pre-warm it by hand with \`npm run fetch-sdk-runtime\`. Trace: ${layout.errorLog}`);
219
+ }
220
+ }
221
+ finally {
222
+ rmSync(layout.lock, { recursive: true, force: true });
223
+ }
224
+ }
225
+ /** The idempotent build steps; the marker is written last, after the smoke check. */
226
+ async function buildRuntime(layout, deps, identity) {
227
+ mkdirSync(layout.dir, { recursive: true });
228
+ // npm needs a manifest to own the tree; `private` + a throwaway version keep
229
+ // it unambiguously not-a-publishable-package, and an existing one is left
230
+ // alone so a later install can still be reproduced by hand.
231
+ if (!existsSync(layout.manifest)) {
232
+ writeFileSync(layout.manifest, JSON.stringify({ name: "codex-sdk-runtime", private: true, version: "0.0.0" }, null, 2) + "\n");
233
+ }
234
+ deps.log(`codex-sdk: installing ${SDK_PACKAGE}@${SDK_PACKAGE_VERSION} + ${SDK_BINARY_PACKAGE}@${SDK_PACKAGE_VERSION}`);
235
+ await deps.run("npm", sdkInstallArgv(), { cwd: layout.dir });
236
+ // Smoke step: import the installed entry through its RESOLVED path and
237
+ // require a constructor. Without it a broken tree would only surface at the
238
+ // first prompt, inside the agent's cold-start error path, far from the fetch
239
+ // that produced it.
240
+ if (!existsSync(layout.entry)) {
241
+ throw new Error(`npm install produced no ${layout.entry} (the package layout changed?)`);
242
+ }
243
+ deps.log(`codex-sdk: smoke-importing ${SDK_PACKAGE}@${SDK_PACKAGE_VERSION}`);
244
+ const module = (await import(pathToFileURL(layout.entry).href));
245
+ if (typeof module.Codex !== "function") {
246
+ throw new Error(`smoke import of ${SDK_PACKAGE}@${SDK_PACKAGE_VERSION} exposed no Codex constructor`);
247
+ }
248
+ // Marker LAST — a crash before this point leaves an incomplete runtime that
249
+ // the next call heals, never a false success.
250
+ const record = {
251
+ ...identity,
252
+ entryPath: layout.entry,
253
+ installedAt: new Date().toISOString(),
254
+ };
255
+ writeFileSync(layout.marker, JSON.stringify(record, null, 2) + "\n");
256
+ rmSync(layout.errorLog, { force: true });
257
+ deps.log(`codex-sdk: runtime ready at ${layout.dir}`);
258
+ return record;
259
+ }
260
+ /**
261
+ * Ensure the durable runtime and return the installed SDK module — the ONE
262
+ * function the client's default factory calls, and the only place a real
263
+ * download/install happens on the lazy path.
264
+ *
265
+ * The returned module is a real ESM namespace; the caller treats it
266
+ * structurally (the client declares its own interfaces and never imports this
267
+ * package's types).
268
+ */
269
+ export async function loadCodexSdk(worldHome) {
270
+ await ensureSdkRuntime(worldHome, createSdkRuntimeDeps());
271
+ const { entry } = sdkRuntimeLayout(worldHome);
272
+ const module = (await import(pathToFileURL(entry).href));
273
+ if (typeof module.Codex !== "function") {
274
+ throw new Error(`codex-sdk: ${entry} exposes no Codex constructor`);
275
+ }
276
+ return module;
277
+ }
278
+ /**
279
+ * mkdir-based mutex. Returns owning the lock; throws when a fresh lock is held
280
+ * by someone else; reclaims a lock older than {@link LOCK_STALE_MS}.
281
+ */
282
+ function acquireLock(layout, deps) {
283
+ for (;;) {
284
+ try {
285
+ mkdirSync(layout.lock);
286
+ return;
287
+ }
288
+ catch (error) {
289
+ if (error.code !== "EEXIST")
290
+ throw error;
291
+ }
292
+ let ageMs;
293
+ try {
294
+ ageMs = Date.now() - statSync(layout.lock).mtimeMs;
295
+ }
296
+ catch {
297
+ continue; // released between the mkdir attempt and the stat — retry now
298
+ }
299
+ if (ageMs > LOCK_STALE_MS) {
300
+ deps.log(`codex-sdk: reclaiming stale runtime lock (age ${Math.round(ageMs / 1000)}s)`);
301
+ rmSync(layout.lock, { recursive: true, force: true });
302
+ continue;
303
+ }
304
+ throw new Error(`codex-sdk: another SDK runtime fetch is already in progress (lock ${layout.lock}). ` +
305
+ `Retry once it finishes; if no fetch is running, remove that lock directory.`);
306
+ }
307
+ }
@@ -0,0 +1,167 @@
1
+ /**
2
+ * DSH-session ↔ Codex-thread identity map (the "mapping only" model,
3
+ * 2026-09-16).
4
+ *
5
+ * The DSH session id is the authority: it is supplied by the harness at
6
+ * create/resume and is what the API layer routes on. Codex's thread id only
7
+ * materializes after the first turn, so the pairing is persisted here, in an
8
+ * adapter-owned JSON file in the world's DSH home root (NOT inside the codex
9
+ * app home — the app home is the CLI's native `~/.codex` since the 2026-09-17
10
+ * home ruling; DSH bookkeeping never lives in the runtime's native tree):
11
+ *
12
+ * `<worldHome>/dsh-sessions.json` (`<DSH home>/agents/codex-sdk/dsh-sessions.json`)
13
+ *
14
+ * Shape: `{ "<dshSessionId>": { threadId, cwd, createdAt, preset } }` —
15
+ * `threadId` is `null` until the Codex thread starts. Writes are atomic
16
+ * (temp file + rename); reads are fail-soft (malformed JSON reads as empty)
17
+ * and never throw, so a corrupt map degrades to "unknown session" rather
18
+ * than breaking every operation.
19
+ */
20
+ import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
21
+ import { dirname, join, resolve } from "node:path";
22
+ /** An `Error` carrying a stable {@link CodexResumeErrorCode}. */
23
+ export class CodexResumeError extends Error {
24
+ code;
25
+ constructor(code, message) {
26
+ super(message);
27
+ this.name = "CodexResumeError";
28
+ this.code = code;
29
+ }
30
+ }
31
+ /**
32
+ * The adapter's DSH-side state dir — the world's DSH home root
33
+ * (`<dshHome>/agents/codex-sdk`, the world key this package registers in
34
+ * world-plugin.ts), never the codex app home. Precedence: explicit world home
35
+ * (boot-provided `dshHomePath`), then `$DSH_HOME` (a hub home, so the world
36
+ * layout nests `agents/codex-sdk`), then the line's historical test-home
37
+ * default.
38
+ */
39
+ export function resolveCodexStateDir(home) {
40
+ if (home !== undefined) {
41
+ const dir = resolve(home);
42
+ return dir;
43
+ }
44
+ const fallbackHome = resolve(process.env.DSH_HOME ?? join(process.cwd(), ".tests", "superd"));
45
+ const dir = join(fallbackHome, "agents", "codex-sdk");
46
+ return dir;
47
+ }
48
+ /** The map file's path at the world DSH-home root. */
49
+ export function sessionMapPath(home) {
50
+ return join(resolveCodexStateDir(home), "dsh-sessions.json");
51
+ }
52
+ /** Read the map. Fail-soft: a missing or malformed file reads as `{}`. */
53
+ export function readSessionMap(home) {
54
+ let raw;
55
+ try {
56
+ raw = readFileSync(sessionMapPath(home), "utf8");
57
+ }
58
+ catch {
59
+ return {};
60
+ }
61
+ let parsed;
62
+ try {
63
+ parsed = JSON.parse(raw);
64
+ }
65
+ catch {
66
+ return {};
67
+ }
68
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed))
69
+ return {};
70
+ const out = {};
71
+ for (const [id, value] of Object.entries(parsed)) {
72
+ const record = coerceRecord(value);
73
+ if (record !== undefined)
74
+ out[id] = record;
75
+ }
76
+ return out;
77
+ }
78
+ /** Validate one entry; malformed entries are dropped (fail-soft). */
79
+ function coerceRecord(value) {
80
+ if (value === null || typeof value !== "object" || Array.isArray(value))
81
+ return undefined;
82
+ const raw = value;
83
+ const cwd = raw["cwd"];
84
+ const createdAt = raw["createdAt"];
85
+ if (typeof cwd !== "string" || cwd === "")
86
+ return undefined;
87
+ const threadId = raw["threadId"];
88
+ const preset = raw["preset"];
89
+ return {
90
+ threadId: typeof threadId === "string" && threadId !== "" ? threadId : null,
91
+ cwd,
92
+ createdAt: typeof createdAt === "number" && Number.isFinite(createdAt) ? createdAt : 0,
93
+ preset: typeof preset === "string" && preset !== "" ? preset : null,
94
+ ...(typeof raw["resumable"] === "boolean" ? { resumable: raw["resumable"] } : {}),
95
+ };
96
+ }
97
+ /** One session's record, or `undefined` when the id is unknown. */
98
+ export function sessionRecord(home, dshSessionId) {
99
+ return readSessionMap(home)[dshSessionId];
100
+ }
101
+ /**
102
+ * Insert or merge one session's record and persist the map atomically
103
+ * (temp file + rename, same directory). Merge semantics: an omitted `patch`
104
+ * field keeps the stored value; `threadId: null` explicitly records "not yet
105
+ * materialized". Throws only when the write itself fails — the caller owns
106
+ * that decision (create treats an unwritable map as fatal; the observation
107
+ * path logs and continues).
108
+ */
109
+ export function upsertSession(home, dshSessionId, patch) {
110
+ const path = sessionMapPath(home);
111
+ const map = readSessionMap(home);
112
+ const previous = map[dshSessionId];
113
+ const next = {
114
+ threadId: patch.threadId !== undefined ? patch.threadId : previous?.threadId ?? null,
115
+ cwd: patch.cwd !== undefined ? patch.cwd : previous?.cwd ?? "",
116
+ createdAt: patch.createdAt !== undefined ? patch.createdAt : previous?.createdAt ?? Date.now(),
117
+ preset: patch.preset !== undefined ? patch.preset : previous?.preset ?? null,
118
+ // Sticky once false (an omitted patch keeps the stored refusal).
119
+ ...(patch.resumable !== undefined
120
+ ? { resumable: patch.resumable }
121
+ : previous?.resumable === false
122
+ ? { resumable: false }
123
+ : {}),
124
+ };
125
+ map[dshSessionId] = next;
126
+ const dir = dirname(path);
127
+ mkdirSync(dir, { recursive: true });
128
+ const temp = `${path}.${process.pid}.${Date.now()}.tmp`;
129
+ writeFileSync(temp, `${JSON.stringify(map, null, 2)}\n`, { encoding: "utf8", mode: 0o600 });
130
+ try {
131
+ renameSync(temp, path);
132
+ }
133
+ catch (error) {
134
+ try {
135
+ rmSync(temp, { force: true });
136
+ }
137
+ catch {
138
+ // The rename failure is primary.
139
+ }
140
+ throw error;
141
+ }
142
+ return next;
143
+ }
144
+ /** Drop one session's record (idempotent; failure to persist rethrows). */
145
+ export function forgetSession(home, dshSessionId) {
146
+ const path = sessionMapPath(home);
147
+ if (!existsSync(path))
148
+ return;
149
+ const map = readSessionMap(home);
150
+ if (map[dshSessionId] === undefined)
151
+ return;
152
+ delete map[dshSessionId];
153
+ const temp = `${path}.${process.pid}.${Date.now()}.tmp`;
154
+ writeFileSync(temp, `${JSON.stringify(map, null, 2)}\n`, { encoding: "utf8", mode: 0o600 });
155
+ try {
156
+ renameSync(temp, path);
157
+ }
158
+ catch (error) {
159
+ try {
160
+ rmSync(temp, { force: true });
161
+ }
162
+ catch {
163
+ // The rename failure is primary.
164
+ }
165
+ throw error;
166
+ }
167
+ }
@@ -0,0 +1,93 @@
1
+ /**
2
+ * agent-codex-sdk — the Codex adapter's WORLD plugin (spec S2):
3
+ * activation IS roster membership.
4
+ *
5
+ * AW-B data-plane shape (DL1–DL5 → path-separation T-D), mirroring
6
+ * `@pgmi-builds/agent-omp/world` so the hub sees both foreign runtimes through
7
+ * one shape: the world is spawned as a sibling ROOT context with NO listener
8
+ * and NO webui composition (`worldMountPatches`) — a pure data plane behind
9
+ * the hub's `/hub` family —
10
+ *
11
+ * 1. ctx0 publishes the host mount record (the world's addressability
12
+ * entry — no webServer, no fence: those legs retired with the world
13
+ * pages) before spawn;
14
+ * 2. the world's entry row provides the virtual `webServer` (a silent
15
+ * sink) plus the neutral `webRuntime` facts its connection row injects;
16
+ * 3. the hub's `/hub` family answers `?agent=codex-sdk` traffic in ctx0's auth
17
+ * domain and dispatches into this world's gateway instance (the
18
+ * per-label `/<label>/api` + `/<label>/api/remote.mux` mounts retired
19
+ * in T-C, `mountWorld` deleted in T-D).
20
+ */
21
+ import { mkdirSync } from 'node:fs';
22
+ import { resolveDshHome } from '@deepseek-ai/dsh-home-paths';
23
+ import { join } from 'node:path';
24
+ import { spawnWorld, registerAgent, unregisterAgent, setReady, registerForeignTarget, registerHostMount, worldMountPatches, provisionWorldProfile, resolveInstallAnchor, } from '../../superd-hub/dist/index.js';
25
+ export const name = 'agent-codex-sdk';
26
+ /** Runtime key — also the mount label. */
27
+ const KEY = 'codex-sdk';
28
+ export function apply(ctx) {
29
+ // Roster membership is FIBER-SCOPED (2026-10-03 acceptance finding):
30
+ // disabling this row from the plugin manager disposes the fiber, and the
31
+ // entry must go with it. The `/api/agent-runtime` view hides a row whose
32
+ // host mount is gone, but the raw roster map is never pruned — so without
33
+ // this disposer a later re-enable dies on `roster: duplicate agent key
34
+ // codex-sdk`.
35
+ ctx.effect?.(() => {
36
+ registerAgent({ key: KEY, label: 'Codex (SDK)', ready: false });
37
+ return () => unregisterAgent(KEY);
38
+ }, `agent-${KEY}: roster`);
39
+ // Harness resolver (env → ~/.dsh): a consumer install has no DSH_HOME and
40
+ // no cwd contract — deriving the world root from cwd spawns worlds into a
41
+ // garbage tree or throws inside apply() and kills the whole host boot.
42
+ const root = resolveDshHome();
43
+ // S7 nested home: every runtime owns one root under `agents/<label>`, and the
44
+ // world's DSH data lives INSIDE it (sessions / settings / workspaces), so no
45
+ // two runtimes ever share a session list. This world's own durable SDK
46
+ // runtime lives in the same root (`<worldHome>/sdk-runtime/`).
47
+ const worldHome = join(root, 'agents', KEY);
48
+ mkdirSync(join(worldHome, 'profiles'), { recursive: true });
49
+ // Publish story: a foreign install has no line bootstrap, so the nested
50
+ // profile + @pgmi-builds links are provisioned HERE (idempotent).
51
+ provisionWorldProfile({
52
+ key: KEY,
53
+ adapterPkg: '@pgmi-builds/agent-codex-sdk',
54
+ worldHome,
55
+ });
56
+ const world = (async () => {
57
+ // T-D: no ctx0 listening-stack wait — the mount record is addressability
58
+ // only and the /hub family wires its own gate in ctx0 (gateway.ts).
59
+ const dropHostMount = registerHostMount({ key: KEY, labelPath: `/${KEY}` });
60
+ const ctxW = await spawnWorld({
61
+ appName: `superd-${KEY}`,
62
+ profileName: 'web',
63
+ installAnchor: resolveInstallAnchor(ctx), // lazy: never at module scope — import must stay side-effect-free
64
+ home: worldHome,
65
+ dataHome: worldHome,
66
+ // The host's live table carries this world: spawnWorld republishes a
67
+ // widened successor before boot imports anything (spec §2.1), and
68
+ // @pgmi-builds rows resolve from the profile-local links written above.
69
+ host: ctx,
70
+ extraPatches: worldMountPatches(KEY),
71
+ });
72
+ const gateway = ctxW.get('typertGateway');
73
+ registerForeignTarget({ key: KEY, gateway });
74
+ setReady(KEY, true);
75
+ ctx.effect?.(() => () => {
76
+ dropHostMount();
77
+ }, `agent-${KEY}: mount`);
78
+ // Toggling the row off must really UNLOAD the world (2026-10-03 omp-sdk
79
+ // acceptance finding, same shape here): disposing the world's own fiber
80
+ // disposes its provider, which releases every transport and the refcounted
81
+ // SDK handle — without this the world outlives its own row.
82
+ ctx.effect?.(() => () => {
83
+ void ctxW.fiber?.dispose();
84
+ }, `agent-${KEY}: world teardown`);
85
+ return ctxW;
86
+ })().catch((cause) => {
87
+ // A failed world degrades to a not-ready roster row; a rejected provided
88
+ // promise would fail the entire host boot (loader entry apply).
89
+ console.error(`[super-dsh] ${KEY} world failed:`, cause);
90
+ return null;
91
+ });
92
+ ctx.provide(`superd.world.${KEY}`, world);
93
+ }