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.
- package/README.md +25 -3
- package/agent-agy/cordis.patch.yml +7 -4
- package/agent-agy/dist/agent.d.ts +12 -11
- package/agent-agy/dist/agy-auth-gateway.d.ts +124 -0
- package/agent-agy/dist/agy-auth-gateway.js +233 -0
- package/agent-agy/dist/agy-auth.d.ts +153 -0
- package/agent-agy/dist/agy-auth.js +179 -0
- package/agent-agy/dist/agy-cli-client.d.ts +39 -9
- package/agent-agy/dist/agy-cli-client.js +82 -18
- package/agent-agy/dist/agy-store.d.ts +8 -0
- package/agent-agy/dist/index.d.ts +46 -16
- package/agent-agy/dist/index.js +82 -56
- package/agent-agy/dist/models.d.ts +70 -0
- package/agent-agy/dist/models.js +102 -3
- package/agent-agy/package.json +7 -5
- package/agent-agy-sdk/cordis.patch.yml +58 -0
- package/agent-agy-sdk/dist/adapter.d.ts +15 -0
- package/agent-agy-sdk/dist/adapter.js +42 -0
- package/agent-agy-sdk/dist/agent-preset-agy.d.ts +27 -0
- package/agent-agy-sdk/dist/agent-preset-agy.js +32 -0
- package/agent-agy-sdk/dist/agent.d.ts +138 -0
- package/agent-agy-sdk/dist/agent.js +494 -0
- package/{agent-agy → agent-agy-sdk}/dist/agy-client.d.ts +26 -6
- package/{agent-agy → agent-agy-sdk}/dist/agy-client.js +43 -25
- package/agent-agy-sdk/dist/agy-sessions.d.ts +25 -0
- package/agent-agy-sdk/dist/agy-sessions.js +107 -0
- package/agent-agy-sdk/dist/agy-store.d.ts +39 -0
- package/agent-agy-sdk/dist/agy-store.js +83 -0
- package/agent-agy-sdk/dist/inbox.d.ts +26 -0
- package/agent-agy-sdk/dist/inbox.js +53 -0
- package/agent-agy-sdk/dist/index.d.ts +93 -0
- package/agent-agy-sdk/dist/index.js +476 -0
- package/agent-agy-sdk/dist/models.d.ts +41 -0
- package/agent-agy-sdk/dist/models.js +74 -0
- package/agent-agy-sdk/dist/sdk-venv.d.ts +76 -0
- package/agent-agy-sdk/dist/sdk-venv.js +173 -0
- package/agent-agy-sdk/dist/world-plugin.d.ts +10 -0
- package/agent-agy-sdk/dist/world-plugin.js +82 -0
- package/agent-agy-sdk/package.json +64 -0
- package/agent-claude/dist/adapter.js +106 -31
- package/agent-claude/dist/agent.js +154 -17
- package/agent-claude/dist/claude-client.js +84 -43
- package/agent-claude/dist/claude-launch.js +101 -0
- package/agent-claude/dist/claude-native-config.js +169 -0
- package/agent-claude/dist/claude-provider-gateway.js +340 -0
- package/agent-claude/dist/claude-providers.js +391 -0
- package/agent-claude/dist/claude-route.js +152 -0
- package/agent-claude/dist/index.js +214 -37
- package/agent-claude/dist/models.js +14 -0
- package/agent-claude/dist/sdk-runtime.js +320 -0
- package/agent-claude/package.json +6 -4
- package/agent-codex/README.md +141 -36
- package/agent-codex/cordis.patch.yml +14 -10
- package/agent-codex/dist/adapter.js +64 -28
- package/agent-codex/dist/agent.js +255 -81
- package/agent-codex/dist/app-server-approvals.js +165 -0
- package/agent-codex/dist/app-server-client.js +723 -0
- package/agent-codex/dist/app-server-events.js +432 -0
- package/agent-codex/dist/app-server-protocol.js +95 -0
- package/agent-codex/dist/app-server-wire.js +299 -0
- package/agent-codex/dist/codex-launch.js +78 -0
- package/agent-codex/dist/codex-native-config.js +392 -0
- package/agent-codex/dist/codex-provider-gateway.js +402 -0
- package/agent-codex/dist/codex-providers.js +376 -0
- package/agent-codex/dist/codex-route.js +86 -0
- package/agent-codex/dist/index.js +256 -115
- package/agent-codex/dist/permission.js +75 -1
- package/agent-codex/package.json +1 -2
- package/agent-codex-sdk/README.md +79 -0
- package/agent-codex-sdk/cordis.patch.yml +81 -0
- package/agent-codex-sdk/dist/adapter.js +91 -0
- package/agent-codex-sdk/dist/agent-preset-codex.js +32 -0
- package/agent-codex-sdk/dist/agent.js +1085 -0
- package/agent-codex-sdk/dist/codex-native-config.js +392 -0
- package/agent-codex-sdk/dist/codex-provider-gateway.js +402 -0
- package/agent-codex-sdk/dist/codex-providers.js +376 -0
- package/agent-codex-sdk/dist/codex-route.js +86 -0
- package/{agent-codex/dist/codex-client.js → agent-codex-sdk/dist/codex-sdk-client.js} +253 -32
- package/agent-codex-sdk/dist/codex-sdk-launch.js +92 -0
- package/agent-codex-sdk/dist/codex-store.js +180 -0
- package/agent-codex-sdk/dist/inbox.js +53 -0
- package/agent-codex-sdk/dist/index.js +739 -0
- package/agent-codex-sdk/dist/permission.js +71 -0
- package/agent-codex-sdk/dist/sdk-runtime.js +307 -0
- package/agent-codex-sdk/dist/session-map.js +167 -0
- package/agent-codex-sdk/dist/world-plugin.js +93 -0
- package/agent-codex-sdk/package.json +79 -0
- package/agent-codex-sdk/scripts/fetch-sdk-runtime.mjs +57 -0
- package/agent-omp/README.md +72 -55
- package/agent-omp/dist/agent.js +36 -11
- package/agent-omp/dist/index.js +92 -52
- package/agent-omp/dist/lazy-rpc.js +11 -4
- package/agent-omp/dist/omp-cli.js +114 -97
- package/agent-omp/dist/omp-store.js +12 -4
- package/agent-omp/dist/permission.js +70 -5
- package/agent-omp/dist/rpc-client.js +409 -0
- package/agent-omp/dist/world-plugin.js +36 -3
- package/agent-omp/package.json +2 -6
- package/agent-omp-sdk/README.md +103 -0
- package/agent-omp-sdk/cordis.patch.yml +108 -0
- package/agent-omp-sdk/dist/adapter.d.ts +21 -0
- package/agent-omp-sdk/dist/adapter.js +36 -0
- package/agent-omp-sdk/dist/agent-preset-omp.d.ts +31 -0
- package/agent-omp-sdk/dist/agent-preset-omp.js +32 -0
- package/agent-omp-sdk/dist/agent.d.ts +64 -0
- package/agent-omp-sdk/dist/agent.js +1178 -0
- package/agent-omp-sdk/dist/discovery.d.ts +57 -0
- package/agent-omp-sdk/dist/discovery.js +116 -0
- package/agent-omp-sdk/dist/inbox.d.ts +28 -0
- package/agent-omp-sdk/dist/inbox.js +53 -0
- package/agent-omp-sdk/dist/index.d.ts +60 -0
- package/agent-omp-sdk/dist/index.js +632 -0
- package/agent-omp-sdk/dist/knobs.d.ts +34 -0
- package/agent-omp-sdk/dist/knobs.js +50 -0
- package/agent-omp-sdk/dist/lazy-rpc.d.ts +98 -0
- package/agent-omp-sdk/dist/lazy-rpc.js +178 -0
- package/agent-omp-sdk/dist/mobile/zoom-guard.d.ts +187 -0
- package/agent-omp-sdk/dist/mobile/zoom-guard.js +294 -0
- package/agent-omp-sdk/dist/mobile-boot.d.ts +49 -0
- package/agent-omp-sdk/dist/mobile-boot.js +38 -0
- package/agent-omp-sdk/dist/models.d.ts +39 -0
- package/agent-omp-sdk/dist/models.js +299 -0
- package/agent-omp-sdk/dist/omp-cli.d.ts +67 -0
- package/agent-omp-sdk/dist/omp-cli.js +164 -0
- package/agent-omp-sdk/dist/omp-disk-discovery.d.ts +27 -0
- package/agent-omp-sdk/dist/omp-disk-discovery.js +178 -0
- package/agent-omp-sdk/dist/omp-executable.d.ts +2 -0
- package/agent-omp-sdk/dist/omp-executable.js +30 -0
- package/agent-omp-sdk/dist/omp-store.d.ts +134 -0
- package/agent-omp-sdk/dist/omp-store.js +526 -0
- package/agent-omp-sdk/dist/pairing.d.ts +7 -0
- package/agent-omp-sdk/dist/pairing.js +45 -0
- package/agent-omp-sdk/dist/permission.d.ts +138 -0
- package/agent-omp-sdk/dist/permission.js +161 -0
- package/agent-omp-sdk/dist/protocol.d.ts +189 -0
- package/agent-omp-sdk/dist/rpc-types.d.ts +77 -0
- package/agent-omp-sdk/dist/rpc-types.js +1 -0
- package/agent-omp-sdk/dist/sdk-client.d.ts +160 -0
- package/{agent-omp → agent-omp-sdk}/dist/sdk-client.js +108 -56
- package/agent-omp-sdk/dist/sdk-runtime.d.ts +211 -0
- package/agent-omp-sdk/dist/sdk-runtime.js +418 -0
- package/agent-omp-sdk/dist/sidecar-client.d.ts +87 -0
- package/agent-omp-sdk/dist/sidecar-client.js +197 -0
- package/agent-omp-sdk/dist/store/adapter.d.ts +17 -0
- package/agent-omp-sdk/dist/store/adapter.js +47 -0
- package/agent-omp-sdk/dist/store/db.d.ts +70 -0
- package/agent-omp-sdk/dist/store/db.js +210 -0
- package/agent-omp-sdk/dist/store/index.d.ts +23 -0
- package/agent-omp-sdk/dist/store/index.js +59 -0
- package/agent-omp-sdk/dist/store/reconcile.d.ts +15 -0
- package/agent-omp-sdk/dist/store/reconcile.js +51 -0
- package/agent-omp-sdk/dist/store/schema.d.ts +8 -0
- package/agent-omp-sdk/dist/store/schema.js +40 -0
- package/agent-omp-sdk/dist/transport.d.ts +49 -0
- package/agent-omp-sdk/dist/transport.js +35 -0
- package/agent-omp-sdk/dist/world-home.d.ts +4 -0
- package/agent-omp-sdk/dist/world-home.js +23 -0
- package/agent-omp-sdk/dist/world-plugin.d.ts +10 -0
- package/agent-omp-sdk/dist/world-plugin.js +137 -0
- package/agent-omp-sdk/package.json +87 -0
- package/client/index.js +1204 -93
- package/cordis.patch.yml +64 -0
- package/package.json +17 -8
- package/superd-hub/dist/client/agent-settings.d.ts +210 -0
- package/superd-hub/dist/client/agent-settings.js +300 -0
- package/superd-hub/dist/client/roster.js +6 -0
- package/superd-hub/dist/client/runtime-settings-api.d.ts +76 -0
- package/superd-hub/dist/client/runtime-settings-api.js +137 -0
- package/superd-hub/dist/client/switch.d.ts +165 -0
- package/superd-hub/dist/client/switch.js +295 -0
- package/superd-hub/lib/client/index.js +1204 -93
- package/agent-codex/dist/models.js +0 -152
- package/agent-omp/dist/sidecar-client.js +0 -195
- /package/{agent-agy → agent-agy-sdk}/bridge/agy_bridge.py +0 -0
- /package/{agent-agy → agent-agy-sdk}/bridge/gemini_openai_server.mjs +0 -0
- /package/{agent-codex/dist/codex-events.js → agent-codex-sdk/dist/codex-sdk-events.js} +0 -0
- /package/{agent-omp → agent-omp-sdk}/dist/protocol.js +0 -0
- /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
|
+
}
|