super-dsh 0.1.0 → 0.1.2

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/agent-agy/bridge/agy_bridge.py +250 -0
  2. package/agent-agy/bridge/gemini_openai_server.mjs +98 -0
  3. package/agent-agy/cordis.patch.yml +49 -0
  4. package/agent-agy/dist/adapter.d.ts +15 -0
  5. package/agent-agy/dist/adapter.js +42 -0
  6. package/agent-agy/dist/agent-preset-agy.d.ts +47 -0
  7. package/agent-agy/dist/agent-preset-agy.js +169 -0
  8. package/agent-agy/dist/agent-preset-projection.d.ts +31 -0
  9. package/agent-agy/dist/agent-preset-projection.js +33 -0
  10. package/agent-agy/dist/agent.d.ts +137 -0
  11. package/agent-agy/dist/agent.js +493 -0
  12. package/agent-agy/dist/agy-cli-client.d.ts +137 -0
  13. package/agent-agy/dist/agy-cli-client.js +301 -0
  14. package/agent-agy/dist/agy-client.d.ts +160 -0
  15. package/agent-agy/dist/agy-client.js +252 -0
  16. package/agent-agy/dist/agy-sessions.d.ts +24 -0
  17. package/agent-agy/dist/agy-sessions.js +116 -0
  18. package/agent-agy/dist/agy-store.d.ts +39 -0
  19. package/agent-agy/dist/agy-store.js +83 -0
  20. package/agent-agy/dist/inbox.d.ts +26 -0
  21. package/agent-agy/dist/inbox.js +53 -0
  22. package/agent-agy/dist/index.d.ts +80 -0
  23. package/agent-agy/dist/index.js +505 -0
  24. package/agent-agy/dist/models.d.ts +41 -0
  25. package/agent-agy/dist/models.js +74 -0
  26. package/agent-agy/dist/world-plugin.d.ts +9 -0
  27. package/agent-agy/dist/world-plugin.js +97 -0
  28. package/agent-agy/package.json +64 -0
  29. package/agent-claude/cordis.patch.yml +42 -0
  30. package/agent-claude/dist/adapter.js +62 -0
  31. package/agent-claude/dist/agent-preset-claude.js +169 -0
  32. package/agent-claude/dist/agent-preset-projection.js +30 -0
  33. package/agent-claude/dist/agent.js +1271 -0
  34. package/agent-claude/dist/claude-client.js +357 -0
  35. package/agent-claude/dist/claude-events.js +223 -0
  36. package/agent-claude/dist/claude-home.js +29 -0
  37. package/agent-claude/dist/commands.js +182 -0
  38. package/agent-claude/dist/content.js +134 -0
  39. package/agent-claude/dist/inbox.js +53 -0
  40. package/agent-claude/dist/index.js +555 -0
  41. package/agent-claude/dist/input-queue.js +64 -0
  42. package/agent-claude/dist/interaction.js +271 -0
  43. package/agent-claude/dist/models.js +158 -0
  44. package/agent-claude/dist/permission.js +120 -0
  45. package/agent-claude/dist/session-id.js +20 -0
  46. package/agent-claude/dist/session-map.js +165 -0
  47. package/agent-claude/dist/world-plugin.js +14 -0
  48. package/agent-claude/package.json +74 -0
  49. package/agent-codex/README.md +74 -0
  50. package/agent-codex/cordis.patch.yml +77 -0
  51. package/agent-codex/dist/adapter.js +55 -0
  52. package/agent-codex/dist/agent-preset-codex.js +171 -0
  53. package/agent-codex/dist/agent-preset-projection.js +37 -0
  54. package/agent-codex/dist/agent.js +1056 -0
  55. package/agent-codex/dist/app-home.js +65 -0
  56. package/agent-codex/dist/codex-client.js +469 -0
  57. package/agent-codex/dist/codex-events.js +297 -0
  58. package/agent-codex/dist/codex-store.js +190 -0
  59. package/agent-codex/dist/inbox.js +53 -0
  60. package/agent-codex/dist/index.js +605 -0
  61. package/agent-codex/dist/models.js +152 -0
  62. package/agent-codex/dist/permission.js +71 -0
  63. package/agent-codex/dist/session-map.js +170 -0
  64. package/agent-codex/dist/spike.js +46 -0
  65. package/agent-codex/dist/world-plugin.js +96 -0
  66. package/agent-codex/package.json +78 -0
  67. package/agent-hermes/README.md +144 -0
  68. package/agent-hermes/cordis.patch.yml +83 -0
  69. package/agent-hermes/dist/adapter.js +83 -0
  70. package/agent-hermes/dist/agent-preset-hermes.js +171 -0
  71. package/agent-hermes/dist/agent-preset-projection.js +34 -0
  72. package/agent-hermes/dist/agent.js +1068 -0
  73. package/agent-hermes/dist/hermes-client.js +953 -0
  74. package/agent-hermes/dist/hermes-events.js +289 -0
  75. package/agent-hermes/dist/hermes-store.js +158 -0
  76. package/agent-hermes/dist/inbox.js +53 -0
  77. package/agent-hermes/dist/index.js +541 -0
  78. package/agent-hermes/dist/models.js +324 -0
  79. package/agent-hermes/dist/permission.js +60 -0
  80. package/agent-hermes/dist/world-plugin.js +101 -0
  81. package/agent-hermes/package.json +76 -0
  82. package/agent-hub/cordis.patch.yml +12 -0
  83. package/agent-hub/dist/agent-roster.d.ts +34 -0
  84. package/agent-hub/dist/agent-roster.js +27 -0
  85. package/agent-hub/dist/carrier.d.ts +43 -0
  86. package/agent-hub/dist/carrier.js +241 -0
  87. package/agent-hub/dist/client-shim.d.ts +28 -0
  88. package/agent-hub/dist/client-shim.js +251 -0
  89. package/agent-hub/dist/envelope.d.ts +54 -0
  90. package/agent-hub/dist/envelope.js +92 -0
  91. package/agent-hub/dist/gateway.d.ts +28 -0
  92. package/agent-hub/dist/gateway.js +18 -0
  93. package/agent-hub/dist/index-pass.d.ts +22 -0
  94. package/agent-hub/dist/index-pass.js +47 -0
  95. package/agent-hub/dist/index.d.ts +29 -0
  96. package/agent-hub/dist/index.js +29 -0
  97. package/agent-hub/dist/labels.d.ts +45 -0
  98. package/agent-hub/dist/labels.js +83 -0
  99. package/agent-hub/dist/ownership.d.ts +37 -0
  100. package/agent-hub/dist/ownership.js +54 -0
  101. package/agent-hub/dist/roster.d.ts +28 -0
  102. package/agent-hub/dist/roster.js +38 -0
  103. package/agent-hub/dist/rpc.d.ts +38 -0
  104. package/agent-hub/dist/rpc.js +52 -0
  105. package/agent-hub/dist/spawn-world.d.ts +46 -0
  106. package/agent-hub/dist/spawn-world.js +72 -0
  107. package/agent-hub/dist/targets.d.ts +33 -0
  108. package/agent-hub/dist/targets.js +30 -0
  109. package/agent-hub/dist/world-entry.d.ts +31 -0
  110. package/agent-hub/dist/world-entry.js +51 -0
  111. package/agent-hub/dist/world-host.d.ts +69 -0
  112. package/agent-hub/dist/world-host.js +44 -0
  113. package/agent-hub/dist/world-join.d.ts +16 -0
  114. package/agent-hub/dist/world-join.js +122 -0
  115. package/agent-hub/dist/world-mount.d.ts +52 -0
  116. package/agent-hub/dist/world-mount.js +52 -0
  117. package/agent-hub/dist/world-mux.d.ts +72 -0
  118. package/agent-hub/dist/world-mux.js +224 -0
  119. package/agent-hub/dist/world-provision.d.ts +20 -0
  120. package/agent-hub/dist/world-provision.js +122 -0
  121. package/agent-hub/dist/world-web-server.d.ts +162 -0
  122. package/agent-hub/dist/world-web-server.js +248 -0
  123. package/agent-hub/lib/client/index.js +242 -0
  124. package/agent-hub/package.json +65 -0
  125. package/agent-omp/README.md +77 -0
  126. package/agent-omp/cordis.patch.yml +92 -0
  127. package/agent-omp/dist/adapter.js +36 -0
  128. package/agent-omp/dist/agent-preset-omp.js +171 -0
  129. package/agent-omp/dist/agent-preset-projection.js +36 -0
  130. package/agent-omp/dist/agent.js +1145 -0
  131. package/agent-omp/dist/app-home.js +86 -0
  132. package/agent-omp/dist/discovery.js +116 -0
  133. package/agent-omp/dist/inbox.js +53 -0
  134. package/agent-omp/dist/index.js +574 -0
  135. package/agent-omp/dist/knobs.js +50 -0
  136. package/agent-omp/dist/lazy-rpc.js +161 -0
  137. package/agent-omp/dist/mobile/gesture.js +148 -0
  138. package/agent-omp/dist/mobile/zoom-guard.js +294 -0
  139. package/agent-omp/dist/mobile-boot.js +38 -0
  140. package/agent-omp/dist/models.js +299 -0
  141. package/agent-omp/dist/omp-cli.js +147 -0
  142. package/agent-omp/dist/omp-disk-discovery.js +178 -0
  143. package/agent-omp/dist/omp-store.js +518 -0
  144. package/agent-omp/dist/pairing.js +45 -0
  145. package/agent-omp/dist/permission.js +96 -0
  146. package/agent-omp/dist/protocol.js +13 -0
  147. package/agent-omp/dist/rpc-types.js +1 -0
  148. package/agent-omp/dist/sdk-client.js +378 -0
  149. package/agent-omp/dist/sidecar-client.js +195 -0
  150. package/agent-omp/dist/store/adapter.js +47 -0
  151. package/agent-omp/dist/store/db.js +210 -0
  152. package/agent-omp/dist/store/index.js +59 -0
  153. package/agent-omp/dist/store/reconcile.js +51 -0
  154. package/agent-omp/dist/store/schema.js +40 -0
  155. package/agent-omp/dist/world-plugin.js +96 -0
  156. package/agent-omp/package.json +97 -0
  157. package/agent-omp/sidecar/main.ts +481 -0
  158. package/agent-pi/README.md +67 -0
  159. package/agent-pi/cordis.patch.yml +80 -0
  160. package/agent-pi/dist/adapter.js +53 -0
  161. package/agent-pi/dist/agent-preset-pi.js +169 -0
  162. package/agent-pi/dist/agent-preset-projection.js +29 -0
  163. package/agent-pi/dist/agent.js +949 -0
  164. package/agent-pi/dist/inbox.js +53 -0
  165. package/agent-pi/dist/index.js +595 -0
  166. package/agent-pi/dist/knobs.js +24 -0
  167. package/agent-pi/dist/models.js +200 -0
  168. package/agent-pi/dist/permission.js +66 -0
  169. package/agent-pi/dist/pi-client.js +224 -0
  170. package/agent-pi/dist/pi-events.js +155 -0
  171. package/agent-pi/dist/pi-home.js +55 -0
  172. package/agent-pi/dist/safe-json.js +25 -0
  173. package/agent-pi/dist/session-map.js +119 -0
  174. package/agent-pi/dist/world-plugin.js +97 -0
  175. package/agent-pi/package.json +77 -0
  176. package/cordis.patch.yml +22 -19
  177. package/package.json +88 -25
  178. package/client.js +0 -4
@@ -0,0 +1,152 @@
1
+ /**
2
+ * Codex model catalog for the Dash model selector (AW-E Task 5).
3
+ *
4
+ * Reads the adapter's nested codex home the native way (spec S7: the native
5
+ * app self-populates its config; we read, never write):
6
+ * - `config.toml` top-level keys `model`, `model_reasoning_effort`,
7
+ * `model_provider`, `model_catalog_json` (minimal line parser — the full
8
+ * TOML grammar is deliberately not implemented; unknown lines are skipped);
9
+ * - `model_catalog_json` (relative to the codex home) → per-model identity
10
+ * fields (slug / display_name / default_reasoning_level / context_window).
11
+ *
12
+ * The frozen multi-agent-ctx models.ts used the app-server `model/list` —
13
+ * that surface does not exist on the SDK line, so the TOML+catalog pair is
14
+ * the only catalog source. Fail-soft everywhere: any missing/unparsable piece
15
+ * degrades toward a single `codex-default` placeholder, never a throw.
16
+ */
17
+ import { existsSync, readFileSync } from "node:fs";
18
+ import { join } from "node:path";
19
+ import { resolveCodexHome, useCodexHome } from "./codex-store.js";
20
+ /** Minimal top-level TOML scan: `key = value` pairs + `[section]` headers. */
21
+ function parseTopLevelToml(text) {
22
+ const values = {};
23
+ const sections = new Map();
24
+ let current = null;
25
+ let currentName = "";
26
+ for (const rawLine of text.split("\n")) {
27
+ const lineText = rawLine.trim();
28
+ if (lineText === "" || lineText.startsWith("#"))
29
+ continue;
30
+ const header = /^\[([^\]]+)\]$/.exec(lineText);
31
+ if (header !== null) {
32
+ currentName = header[1].trim();
33
+ current = {};
34
+ sections.set(currentName, current);
35
+ continue;
36
+ }
37
+ const eq = lineText.indexOf("=");
38
+ if (eq <= 0)
39
+ continue; // broken line — tolerated
40
+ const key = lineText.slice(0, eq).trim();
41
+ let value = lineText.slice(eq + 1).trim();
42
+ if (value.startsWith('"') && value.endsWith('"') && value.length >= 2) {
43
+ value = value.slice(1, -1);
44
+ }
45
+ else if (value.startsWith("'") && value.endsWith("'") && value.length >= 2) {
46
+ value = value.slice(1, -1);
47
+ }
48
+ else {
49
+ // strip trailing comment on bare values; keep numbers/bools as strings
50
+ const hash = value.indexOf(" #");
51
+ if (hash >= 0)
52
+ value = value.slice(0, hash).trim();
53
+ }
54
+ if (current !== null && currentName !== "")
55
+ current[key] = value;
56
+ else
57
+ values[key] = value;
58
+ }
59
+ return { values, sections };
60
+ }
61
+ /** The fail-soft placeholder model id (never sent to the real codex binary). */
62
+ export const CODEX_DEFAULT_MODEL_PLACEHOLDER = "codex-default";
63
+ /** The fail-soft placeholder: one entry, no claims about the runtime. */
64
+ function placeholder(defaultModel = CODEX_DEFAULT_MODEL_PLACEHOLDER) {
65
+ return { defaultModel, models: [{ id: defaultModel, label: "Codex default" }] };
66
+ }
67
+ /** Read the model catalog out of the codex home (fail-soft, never throws). */
68
+ export function readCodexModelCatalog(codexHome) {
69
+ let home;
70
+ try {
71
+ home = codexHome !== undefined ? useCodexHome(codexHome) : resolveCodexHome();
72
+ }
73
+ catch {
74
+ return placeholder();
75
+ }
76
+ let configValues = {};
77
+ let providerName;
78
+ const configPath = join(home, "config.toml");
79
+ if (existsSync(configPath)) {
80
+ try {
81
+ const parsed = parseTopLevelToml(readFileSync(configPath, "utf8"));
82
+ configValues = parsed.values;
83
+ const providerId = configValues["model_provider"];
84
+ if (providerId !== undefined) {
85
+ const section = parsed.sections.get(`model_providers.${providerId}`);
86
+ if (section !== undefined && typeof section["name"] === "string" && section["name"] !== "") {
87
+ providerName = section["name"];
88
+ }
89
+ }
90
+ }
91
+ catch {
92
+ return placeholder();
93
+ }
94
+ }
95
+ const configModel = configValues["model"];
96
+ const catalogRel = configValues["model_catalog_json"];
97
+ const entries = [];
98
+ if (catalogRel !== undefined && catalogRel !== "") {
99
+ const catalogPath = join(home, catalogRel);
100
+ if (existsSync(catalogPath)) {
101
+ try {
102
+ const parsed = JSON.parse(readFileSync(catalogPath, "utf8"));
103
+ const models = parsed?.models;
104
+ if (Array.isArray(models)) {
105
+ for (const raw of models) {
106
+ if (raw === null || typeof raw !== "object")
107
+ continue;
108
+ const m = raw;
109
+ const slug = m["slug"];
110
+ if (typeof slug !== "string" || slug === "")
111
+ continue;
112
+ const label = typeof m["display_name"] === "string" && m["display_name"] !== "" ? m["display_name"] : slug;
113
+ const entry = {
114
+ id: slug,
115
+ label,
116
+ ...(typeof m["default_reasoning_level"] === "string" && m["default_reasoning_level"] !== ""
117
+ ? { reasoningEffort: m["default_reasoning_level"] }
118
+ : {}),
119
+ ...(typeof m["context_window"] === "number" && Number.isSafeInteger(m["context_window"])
120
+ ? { contextWindow: m["context_window"] }
121
+ : {}),
122
+ };
123
+ entries.push(entry);
124
+ }
125
+ }
126
+ }
127
+ catch {
128
+ // unparsable catalog — fall through to the config-model entry
129
+ }
130
+ }
131
+ }
132
+ if (entries.length === 0) {
133
+ if (configModel === undefined || configModel === "")
134
+ return placeholder();
135
+ return {
136
+ defaultModel: configModel,
137
+ ...(providerName === undefined ? {} : { provider: providerName }),
138
+ models: [{ id: configModel, label: configModel }],
139
+ };
140
+ }
141
+ // The configured model is what sessions actually run, so it must be
142
+ // selectable even when the provider's own catalog file does not list it
143
+ // (cc-switch ships a fixed list; `config.toml` may name a newer entry).
144
+ const models = configModel !== undefined && configModel !== "" && !entries.some((entry) => entry.id === configModel)
145
+ ? [{ id: configModel, label: configModel }, ...entries]
146
+ : entries;
147
+ return {
148
+ defaultModel: configModel !== undefined && configModel !== "" ? configModel : entries[0].id,
149
+ ...(providerName === undefined ? {} : { provider: providerName }),
150
+ models,
151
+ };
152
+ }
@@ -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,170 @@
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/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
+ import { assertNotProdHome } from "./codex-store.js";
23
+ /** An `Error` carrying a stable {@link CodexResumeErrorCode}. */
24
+ export class CodexResumeError extends Error {
25
+ code;
26
+ constructor(code, message) {
27
+ super(message);
28
+ this.name = "CodexResumeError";
29
+ this.code = code;
30
+ }
31
+ }
32
+ /** The map file's path inside the adapter's app home. */
33
+ /**
34
+ * The adapter's DSH-side state dir — the world's DSH home root
35
+ * (`<dshHome>/agents/codex`), never the codex app home. Precedence: explicit
36
+ * world home (boot-provided `dshHomePath`), then `$DSH_HOME` (a hub home, so
37
+ * the world layout nests `agents/codex`), then the line's historical test-home
38
+ * default. Prod-guarded.
39
+ */
40
+ export function resolveCodexStateDir(home) {
41
+ if (home !== undefined) {
42
+ const dir = resolve(home);
43
+ assertNotProdHome(dir, "codex state dir");
44
+ return dir;
45
+ }
46
+ const fallbackHome = resolve(process.env.DSH_HOME ?? join(process.cwd(), ".tests", "aw"));
47
+ const dir = join(fallbackHome, "agents", "codex");
48
+ assertNotProdHome(dir, "codex state dir");
49
+ return dir;
50
+ }
51
+ /** The map file's path at the world DSH-home root. */
52
+ export function sessionMapPath(home) {
53
+ return join(resolveCodexStateDir(home), "dsh-sessions.json");
54
+ }
55
+ /** Read the map. Fail-soft: a missing or malformed file reads as `{}`. */
56
+ export function readSessionMap(home) {
57
+ let raw;
58
+ try {
59
+ raw = readFileSync(sessionMapPath(home), "utf8");
60
+ }
61
+ catch {
62
+ return {};
63
+ }
64
+ let parsed;
65
+ try {
66
+ parsed = JSON.parse(raw);
67
+ }
68
+ catch {
69
+ return {};
70
+ }
71
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed))
72
+ return {};
73
+ const out = {};
74
+ for (const [id, value] of Object.entries(parsed)) {
75
+ const record = coerceRecord(value);
76
+ if (record !== undefined)
77
+ out[id] = record;
78
+ }
79
+ return out;
80
+ }
81
+ /** Validate one entry; malformed entries are dropped (fail-soft). */
82
+ function coerceRecord(value) {
83
+ if (value === null || typeof value !== "object" || Array.isArray(value))
84
+ return undefined;
85
+ const raw = value;
86
+ const cwd = raw["cwd"];
87
+ const createdAt = raw["createdAt"];
88
+ if (typeof cwd !== "string" || cwd === "")
89
+ return undefined;
90
+ const threadId = raw["threadId"];
91
+ const preset = raw["preset"];
92
+ return {
93
+ threadId: typeof threadId === "string" && threadId !== "" ? threadId : null,
94
+ cwd,
95
+ createdAt: typeof createdAt === "number" && Number.isFinite(createdAt) ? createdAt : 0,
96
+ preset: typeof preset === "string" && preset !== "" ? preset : null,
97
+ ...(typeof raw["resumable"] === "boolean" ? { resumable: raw["resumable"] } : {}),
98
+ };
99
+ }
100
+ /** One session's record, or `undefined` when the id is unknown. */
101
+ export function sessionRecord(home, dshSessionId) {
102
+ return readSessionMap(home)[dshSessionId];
103
+ }
104
+ /**
105
+ * Insert or merge one session's record and persist the map atomically
106
+ * (temp file + rename, same directory). Merge semantics: an omitted `patch`
107
+ * field keeps the stored value; `threadId: null` explicitly records "not yet
108
+ * materialized". Throws only when the write itself fails — the caller owns
109
+ * that decision (create treats an unwritable map as fatal; the observation
110
+ * path logs and continues).
111
+ */
112
+ export function upsertSession(home, dshSessionId, patch) {
113
+ const path = sessionMapPath(home);
114
+ const map = readSessionMap(home);
115
+ const previous = map[dshSessionId];
116
+ const next = {
117
+ threadId: patch.threadId !== undefined ? patch.threadId : previous?.threadId ?? null,
118
+ cwd: patch.cwd !== undefined ? patch.cwd : previous?.cwd ?? "",
119
+ createdAt: patch.createdAt !== undefined ? patch.createdAt : previous?.createdAt ?? Date.now(),
120
+ preset: patch.preset !== undefined ? patch.preset : previous?.preset ?? null,
121
+ // Sticky once false (an omitted patch keeps the stored refusal).
122
+ ...(patch.resumable !== undefined
123
+ ? { resumable: patch.resumable }
124
+ : previous?.resumable === false
125
+ ? { resumable: false }
126
+ : {}),
127
+ };
128
+ map[dshSessionId] = next;
129
+ const dir = dirname(path);
130
+ mkdirSync(dir, { recursive: true });
131
+ const temp = `${path}.${process.pid}.${Date.now()}.tmp`;
132
+ writeFileSync(temp, `${JSON.stringify(map, null, 2)}\n`, { encoding: "utf8", mode: 0o600 });
133
+ try {
134
+ renameSync(temp, path);
135
+ }
136
+ catch (error) {
137
+ try {
138
+ rmSync(temp, { force: true });
139
+ }
140
+ catch {
141
+ // The rename failure is primary.
142
+ }
143
+ throw error;
144
+ }
145
+ return next;
146
+ }
147
+ /** Drop one session's record (idempotent; failure to persist rethrows). */
148
+ export function forgetSession(home, dshSessionId) {
149
+ const path = sessionMapPath(home);
150
+ if (!existsSync(path))
151
+ return;
152
+ const map = readSessionMap(home);
153
+ if (map[dshSessionId] === undefined)
154
+ return;
155
+ delete map[dshSessionId];
156
+ const temp = `${path}.${process.pid}.${Date.now()}.tmp`;
157
+ writeFileSync(temp, `${JSON.stringify(map, null, 2)}\n`, { encoding: "utf8", mode: 0o600 });
158
+ try {
159
+ renameSync(temp, path);
160
+ }
161
+ catch (error) {
162
+ try {
163
+ rmSync(temp, { force: true });
164
+ }
165
+ catch {
166
+ // The rename failure is primary.
167
+ }
168
+ throw error;
169
+ }
170
+ }
@@ -0,0 +1,46 @@
1
+ /**
2
+ * AW-E Task 1 spike (P0 gate): prove `@openai/codex-sdk@0.154.0` drives a real
3
+ * turn against the local cc-switch provider (glm-5.2) under an ISOLATED
4
+ * CODEX_HOME, and capture the raw ThreadEvent chain for the Task 2 projection
5
+ * fixtures. The SDK spawns its own exact-pinned codex binary — the 0.153.4
6
+ * CLI on PATH is never consulted (version-mix safety, 2026-09-10 fact).
7
+ */
8
+ import { Codex } from "@openai/codex-sdk";
9
+ function lastOfType(events, type) {
10
+ for (let i = events.length - 1; i >= 0; i -= 1) {
11
+ const evt = events[i];
12
+ if (evt.type === type)
13
+ return evt;
14
+ }
15
+ return undefined;
16
+ }
17
+ /** Run one real streamed turn and collect the full event chain. */
18
+ export async function runSpike(opts) {
19
+ const codex = new Codex({
20
+ // env REPLACES process.env (SDK contract) — carry it forward + redirect home.
21
+ env: { ...process.env, CODEX_HOME: opts.codexHome },
22
+ });
23
+ const thread = codex.startThread({
24
+ workingDirectory: opts.cwd,
25
+ skipGitRepoCheck: true,
26
+ approvalPolicy: "never",
27
+ sandboxMode: "read-only",
28
+ });
29
+ const events = [];
30
+ const streamed = await thread.runStreamed(opts.prompt ?? "Reply with exactly: ok");
31
+ for await (const evt of streamed.events) {
32
+ events.push(evt);
33
+ if (evt.type === "turn.failed") {
34
+ const error = evt.error;
35
+ throw new Error(`spike turn failed: ${error?.message ?? "unknown"}`);
36
+ }
37
+ }
38
+ const completed = lastOfType(events, "turn.completed");
39
+ const message = lastOfType(events, "item.completed");
40
+ return {
41
+ threadId: thread.id ?? "",
42
+ finalResponse: message?.item?.text ?? "",
43
+ usage: completed?.usage ?? null,
44
+ events,
45
+ };
46
+ }
@@ -0,0 +1,96 @@
1
+ /**
2
+ * aw.agent-adapter-codex — the Codex adapter's WORLD plugin (spec S2):
3
+ * activation IS roster membership.
4
+ *
5
+ * AW-B URL scheme (DL1–DL5), mirroring `@pgmi-builds/agent-adapter-omp/world`
6
+ * so the hub sees both foreign runtimes through one shape: the world is spawned
7
+ * as a sibling ROOT context with NO listener (`worldMountPatches`) and mounts
8
+ * itself under `/codex` through the hub —
9
+ *
10
+ * 1. ctx0 publishes the host mount (real webServer instance) before spawn;
11
+ * 2. the world's entry row provides the virtual `webServer` in the world
12
+ * root, translating every route its plugins declare into `/codex+path`;
13
+ * 3. the hub's carrier answers `/codex/api` + `/codex/api/remote.mux` in
14
+ * ctx0's auth domain and dispatches into this world's gateway instance.
15
+ */
16
+ import { mkdirSync } from 'node:fs';
17
+ import { resolveDshHome } from '@deepseek-ai/dsh-home-paths';
18
+ import { join } from 'node:path';
19
+ import { spawnWorld, registerAgent, setReady, registerForeignTarget, registerHostMount, mountWorld, worldMountPatches, provisionWorldProfile, } from '../../agent-hub/dist/index.js';
20
+ export const name = 'aw.agent-adapter-codex';
21
+ /** Runtime key — also the mount label. */
22
+ const KEY = 'codex';
23
+ const ANCHOR = process.env.SUPERD_DSH_ANCHOR
24
+ ?? '/home/u1/.local/lib/node_modules/@deepseek-ai/dsh/package.json';
25
+ export function apply(ctx) {
26
+ registerAgent({ key: KEY, label: 'Codex', ready: false });
27
+ // Harness resolver (env → ~/.dsh): a consumer install has no DSH_HOME and
28
+ // no cwd contract — deriving the world root from cwd spawns worlds into a
29
+ // garbage tree or throws inside apply() and kills the whole host boot.
30
+ const root = resolveDshHome();
31
+ // S7 nested home: every runtime owns one root under `agents/<label>`; the
32
+ // world's DSH data lives INSIDE it (sessions / settings / workspaces) and the
33
+ // foreign app's own store (`.codex`) sits in the same root.
34
+ const worldHome = join(root, 'agents', KEY);
35
+ mkdirSync(join(worldHome, 'profiles'), { recursive: true });
36
+ // Publish story: a foreign install has no line bootstrap, so the nested
37
+ // profile + @pgmi-builds links are provisioned HERE (idempotent; dev lines
38
+ // keep parity — smoke.mjs wrote the same layout, and AW_BARE_BASE still
39
+ // overrides the resolved base).
40
+ const bareBase = provisionWorldProfile({
41
+ key: KEY,
42
+ adapterPkg: '@pgmi-builds/agent-adapter-codex',
43
+ worldHome,
44
+ });
45
+ const world = (async () => {
46
+ // Wait for ctx0's listening stack: the mount needs the real webServer and
47
+ // the browser-trust fence from the live connection service.
48
+ const host = await new Promise((resolve) => {
49
+ ctx.inject(['webServer', 'connection'], (hostCtx) => { resolve(hostCtx); });
50
+ });
51
+ const real = host['webServer'];
52
+ const connection = host['connection'];
53
+ const dropHostMount = registerHostMount({
54
+ key: KEY,
55
+ labelPath: `/${KEY}`,
56
+ real,
57
+ // ctx0's index gate, applied by the mount to this world's pages and assets:
58
+ // the world is reached in-ctx and owns no listener, so ctx0 decides auth.
59
+ authorize: (req, res) => connection.authorizeIndex(req, res),
60
+ });
61
+ const ctxW = await spawnWorld({
62
+ appName: `aw-${KEY}`,
63
+ profileName: 'web',
64
+ installAnchor: ANCHOR,
65
+ home: worldHome,
66
+ dataHome: worldHome,
67
+ // Adapter-local bundle names resolve from the profile's own
68
+ // node_modules (heal owns @deepseek-ai there; @pgmi-builds links are
69
+ // provisioned by provisionWorldProfile above, or by the line bootstrap
70
+ // when the adapter package was not resolvable — env wins over both).
71
+ bareModuleBaseUrl: process.env.AW_BARE_BASE ?? bareBase ?? undefined,
72
+ extraPatches: worldMountPatches(KEY),
73
+ });
74
+ const gateway = ctxW.get('typertGateway');
75
+ registerForeignTarget({ key: KEY, gateway });
76
+ const mounted = mountWorld({
77
+ real,
78
+ label: KEY,
79
+ gateway,
80
+ requestRejection: (request) => connection
81
+ .requestRejection(request),
82
+ });
83
+ setReady(KEY, true);
84
+ ctx.effect?.(() => () => {
85
+ mounted.dispose();
86
+ dropHostMount();
87
+ }, `aw.agent-adapter-${KEY}: mount`);
88
+ return ctxW;
89
+ })().catch((cause) => {
90
+ // A failed world degrades to a not-ready roster row; a rejected provided
91
+ // promise would fail the entire host boot (loader entry apply).
92
+ console.error(`[super-dsh] ${KEY} world failed:`, cause);
93
+ return null;
94
+ });
95
+ ctx.provide(`aw.world.${KEY}`, world);
96
+ }
@@ -0,0 +1,78 @@
1
+ {
2
+ "name": "@pgmi-builds/agent-adapter-codex",
3
+ "version": "0.1.0",
4
+ "description": "Codex provider for DeepSeek Harness (dsh) — an AgentFactory that embeds Codex CLI via the @openai/codex-sdk and bridges Codex's session surface into Dash Agent/Session contracts (Agent Worlds AW-E, SDK line).",
5
+ "type": "module",
6
+ "exports": {
7
+ ".": "./dist/index.js",
8
+ "./world": "./dist/world-plugin.js"
9
+ },
10
+ "files": [
11
+ "dist",
12
+ "cordis.patch.yml",
13
+ "README.md"
14
+ ],
15
+ "dsh": {
16
+ "bundle": {
17
+ "patch": "./cordis.patch.yml"
18
+ }
19
+ },
20
+ "scripts": {
21
+ "build": "tsc -p tsconfig.json",
22
+ "test": "node --test test/*.test.mjs"
23
+ },
24
+ "dependencies": {
25
+ "@openai/codex": "0.154.0",
26
+ "@openai/codex-sdk": "0.154.0",
27
+ "@pgmi-builds/agent-hub": "file:../agent-hub",
28
+ "zod": "3.25.76"
29
+ },
30
+ "peerDependencies": {
31
+ "@deepseek-ai/cordis": "^4.0.1",
32
+ "@deepseek-ai/dsh-agent": "0.1.5-rc.2",
33
+ "@deepseek-ai/dsh-agent-presets": "0.1.5-rc.2",
34
+ "@deepseek-ai/dsh-llm": "0.1.5-rc.2",
35
+ "@deepseek-ai/dsh-scope": "0.1.5-rc.2",
36
+ "@deepseek-ai/dsh-session": "0.1.5-rc.2",
37
+ "@deepseek-ai/dsh-session-persistence": "0.1.5-rc.2",
38
+ "@deepseek-ai/dsh-typert-protocol": "0.1.5-rc.2"
39
+ },
40
+ "peerDependenciesMeta": {
41
+ "@deepseek-ai/cordis": {
42
+ "optional": true
43
+ },
44
+ "@deepseek-ai/dsh-agent": {
45
+ "optional": true
46
+ },
47
+ "@deepseek-ai/dsh-agent-presets": {
48
+ "optional": true
49
+ },
50
+ "@deepseek-ai/dsh-llm": {
51
+ "optional": true
52
+ },
53
+ "@deepseek-ai/dsh-scope": {
54
+ "optional": true
55
+ },
56
+ "@deepseek-ai/dsh-session": {
57
+ "optional": true
58
+ },
59
+ "@deepseek-ai/dsh-session-persistence": {
60
+ "optional": true
61
+ },
62
+ "@deepseek-ai/dsh-typert-protocol": {
63
+ "optional": true
64
+ }
65
+ },
66
+ "devDependencies": {
67
+ "@types/node": "^26.2.0",
68
+ "typescript": "^5.6.0"
69
+ },
70
+ "publishConfig": {
71
+ "access": "public"
72
+ },
73
+ "license": "MIT",
74
+ "main": "dist/index.js",
75
+ "engines": {
76
+ "node": ">=22.18"
77
+ }
78
+ }