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.
- package/agent-agy/bridge/agy_bridge.py +250 -0
- package/agent-agy/bridge/gemini_openai_server.mjs +98 -0
- package/agent-agy/cordis.patch.yml +49 -0
- package/agent-agy/dist/adapter.d.ts +15 -0
- package/agent-agy/dist/adapter.js +42 -0
- package/agent-agy/dist/agent-preset-agy.d.ts +47 -0
- package/agent-agy/dist/agent-preset-agy.js +169 -0
- package/agent-agy/dist/agent-preset-projection.d.ts +31 -0
- package/agent-agy/dist/agent-preset-projection.js +33 -0
- package/agent-agy/dist/agent.d.ts +137 -0
- package/agent-agy/dist/agent.js +493 -0
- package/agent-agy/dist/agy-cli-client.d.ts +137 -0
- package/agent-agy/dist/agy-cli-client.js +301 -0
- package/agent-agy/dist/agy-client.d.ts +160 -0
- package/agent-agy/dist/agy-client.js +252 -0
- package/agent-agy/dist/agy-sessions.d.ts +24 -0
- package/agent-agy/dist/agy-sessions.js +116 -0
- package/agent-agy/dist/agy-store.d.ts +39 -0
- package/agent-agy/dist/agy-store.js +83 -0
- package/agent-agy/dist/inbox.d.ts +26 -0
- package/agent-agy/dist/inbox.js +53 -0
- package/agent-agy/dist/index.d.ts +80 -0
- package/agent-agy/dist/index.js +505 -0
- package/agent-agy/dist/models.d.ts +41 -0
- package/agent-agy/dist/models.js +74 -0
- package/agent-agy/dist/world-plugin.d.ts +9 -0
- package/agent-agy/dist/world-plugin.js +97 -0
- package/agent-agy/package.json +64 -0
- package/agent-claude/cordis.patch.yml +42 -0
- package/agent-claude/dist/adapter.js +62 -0
- package/agent-claude/dist/agent-preset-claude.js +169 -0
- package/agent-claude/dist/agent-preset-projection.js +30 -0
- package/agent-claude/dist/agent.js +1271 -0
- package/agent-claude/dist/claude-client.js +357 -0
- package/agent-claude/dist/claude-events.js +223 -0
- package/agent-claude/dist/claude-home.js +29 -0
- package/agent-claude/dist/commands.js +182 -0
- package/agent-claude/dist/content.js +134 -0
- package/agent-claude/dist/inbox.js +53 -0
- package/agent-claude/dist/index.js +555 -0
- package/agent-claude/dist/input-queue.js +64 -0
- package/agent-claude/dist/interaction.js +271 -0
- package/agent-claude/dist/models.js +158 -0
- package/agent-claude/dist/permission.js +120 -0
- package/agent-claude/dist/session-id.js +20 -0
- package/agent-claude/dist/session-map.js +165 -0
- package/agent-claude/dist/world-plugin.js +14 -0
- package/agent-claude/package.json +74 -0
- package/agent-codex/README.md +74 -0
- package/agent-codex/cordis.patch.yml +77 -0
- package/agent-codex/dist/adapter.js +55 -0
- package/agent-codex/dist/agent-preset-codex.js +171 -0
- package/agent-codex/dist/agent-preset-projection.js +37 -0
- package/agent-codex/dist/agent.js +1056 -0
- package/agent-codex/dist/app-home.js +65 -0
- package/agent-codex/dist/codex-client.js +469 -0
- package/agent-codex/dist/codex-events.js +297 -0
- package/agent-codex/dist/codex-store.js +190 -0
- package/agent-codex/dist/inbox.js +53 -0
- package/agent-codex/dist/index.js +605 -0
- package/agent-codex/dist/models.js +152 -0
- package/agent-codex/dist/permission.js +71 -0
- package/agent-codex/dist/session-map.js +170 -0
- package/agent-codex/dist/spike.js +46 -0
- package/agent-codex/dist/world-plugin.js +96 -0
- package/agent-codex/package.json +78 -0
- package/agent-hermes/README.md +144 -0
- package/agent-hermes/cordis.patch.yml +83 -0
- package/agent-hermes/dist/adapter.js +83 -0
- package/agent-hermes/dist/agent-preset-hermes.js +171 -0
- package/agent-hermes/dist/agent-preset-projection.js +34 -0
- package/agent-hermes/dist/agent.js +1068 -0
- package/agent-hermes/dist/hermes-client.js +953 -0
- package/agent-hermes/dist/hermes-events.js +289 -0
- package/agent-hermes/dist/hermes-store.js +158 -0
- package/agent-hermes/dist/inbox.js +53 -0
- package/agent-hermes/dist/index.js +541 -0
- package/agent-hermes/dist/models.js +324 -0
- package/agent-hermes/dist/permission.js +60 -0
- package/agent-hermes/dist/world-plugin.js +101 -0
- package/agent-hermes/package.json +76 -0
- package/agent-hub/cordis.patch.yml +12 -0
- package/agent-hub/dist/agent-roster.d.ts +34 -0
- package/agent-hub/dist/agent-roster.js +27 -0
- package/agent-hub/dist/carrier.d.ts +43 -0
- package/agent-hub/dist/carrier.js +241 -0
- package/agent-hub/dist/client-shim.d.ts +28 -0
- package/agent-hub/dist/client-shim.js +251 -0
- package/agent-hub/dist/envelope.d.ts +54 -0
- package/agent-hub/dist/envelope.js +92 -0
- package/agent-hub/dist/gateway.d.ts +28 -0
- package/agent-hub/dist/gateway.js +18 -0
- package/agent-hub/dist/index-pass.d.ts +22 -0
- package/agent-hub/dist/index-pass.js +47 -0
- package/agent-hub/dist/index.d.ts +29 -0
- package/agent-hub/dist/index.js +29 -0
- package/agent-hub/dist/labels.d.ts +45 -0
- package/agent-hub/dist/labels.js +83 -0
- package/agent-hub/dist/ownership.d.ts +37 -0
- package/agent-hub/dist/ownership.js +54 -0
- package/agent-hub/dist/roster.d.ts +28 -0
- package/agent-hub/dist/roster.js +38 -0
- package/agent-hub/dist/rpc.d.ts +38 -0
- package/agent-hub/dist/rpc.js +52 -0
- package/agent-hub/dist/spawn-world.d.ts +46 -0
- package/agent-hub/dist/spawn-world.js +72 -0
- package/agent-hub/dist/targets.d.ts +33 -0
- package/agent-hub/dist/targets.js +30 -0
- package/agent-hub/dist/world-entry.d.ts +31 -0
- package/agent-hub/dist/world-entry.js +51 -0
- package/agent-hub/dist/world-host.d.ts +69 -0
- package/agent-hub/dist/world-host.js +44 -0
- package/agent-hub/dist/world-join.d.ts +16 -0
- package/agent-hub/dist/world-join.js +122 -0
- package/agent-hub/dist/world-mount.d.ts +52 -0
- package/agent-hub/dist/world-mount.js +52 -0
- package/agent-hub/dist/world-mux.d.ts +72 -0
- package/agent-hub/dist/world-mux.js +224 -0
- package/agent-hub/dist/world-provision.d.ts +20 -0
- package/agent-hub/dist/world-provision.js +122 -0
- package/agent-hub/dist/world-web-server.d.ts +162 -0
- package/agent-hub/dist/world-web-server.js +248 -0
- package/agent-hub/lib/client/index.js +242 -0
- package/agent-hub/package.json +65 -0
- package/agent-omp/README.md +77 -0
- package/agent-omp/cordis.patch.yml +92 -0
- package/agent-omp/dist/adapter.js +36 -0
- package/agent-omp/dist/agent-preset-omp.js +171 -0
- package/agent-omp/dist/agent-preset-projection.js +36 -0
- package/agent-omp/dist/agent.js +1145 -0
- package/agent-omp/dist/app-home.js +86 -0
- package/agent-omp/dist/discovery.js +116 -0
- package/agent-omp/dist/inbox.js +53 -0
- package/agent-omp/dist/index.js +574 -0
- package/agent-omp/dist/knobs.js +50 -0
- package/agent-omp/dist/lazy-rpc.js +161 -0
- package/agent-omp/dist/mobile/gesture.js +148 -0
- package/agent-omp/dist/mobile/zoom-guard.js +294 -0
- package/agent-omp/dist/mobile-boot.js +38 -0
- package/agent-omp/dist/models.js +299 -0
- package/agent-omp/dist/omp-cli.js +147 -0
- package/agent-omp/dist/omp-disk-discovery.js +178 -0
- package/agent-omp/dist/omp-store.js +518 -0
- package/agent-omp/dist/pairing.js +45 -0
- package/agent-omp/dist/permission.js +96 -0
- package/agent-omp/dist/protocol.js +13 -0
- package/agent-omp/dist/rpc-types.js +1 -0
- package/agent-omp/dist/sdk-client.js +378 -0
- package/agent-omp/dist/sidecar-client.js +195 -0
- package/agent-omp/dist/store/adapter.js +47 -0
- package/agent-omp/dist/store/db.js +210 -0
- package/agent-omp/dist/store/index.js +59 -0
- package/agent-omp/dist/store/reconcile.js +51 -0
- package/agent-omp/dist/store/schema.js +40 -0
- package/agent-omp/dist/world-plugin.js +96 -0
- package/agent-omp/package.json +97 -0
- package/agent-omp/sidecar/main.ts +481 -0
- package/agent-pi/README.md +67 -0
- package/agent-pi/cordis.patch.yml +80 -0
- package/agent-pi/dist/adapter.js +53 -0
- package/agent-pi/dist/agent-preset-pi.js +169 -0
- package/agent-pi/dist/agent-preset-projection.js +29 -0
- package/agent-pi/dist/agent.js +949 -0
- package/agent-pi/dist/inbox.js +53 -0
- package/agent-pi/dist/index.js +595 -0
- package/agent-pi/dist/knobs.js +24 -0
- package/agent-pi/dist/models.js +200 -0
- package/agent-pi/dist/permission.js +66 -0
- package/agent-pi/dist/pi-client.js +224 -0
- package/agent-pi/dist/pi-events.js +155 -0
- package/agent-pi/dist/pi-home.js +55 -0
- package/agent-pi/dist/safe-json.js +25 -0
- package/agent-pi/dist/session-map.js +119 -0
- package/agent-pi/dist/world-plugin.js +97 -0
- package/agent-pi/package.json +77 -0
- package/cordis.patch.yml +22 -19
- package/package.json +88 -25
- package/client.js +0 -4
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
/** Watermark of streamed agent_message text (item id → last emitted full text). */
|
|
2
|
+
const textWatermark = new Map();
|
|
3
|
+
/** Items whose tool_execution_start already fired (id → true). */
|
|
4
|
+
const startedTools = new Set();
|
|
5
|
+
/** Reset the projection watermarks (test / new-session hygiene). */
|
|
6
|
+
export function resetProjectionState() {
|
|
7
|
+
textWatermark.clear();
|
|
8
|
+
startedTools.clear();
|
|
9
|
+
}
|
|
10
|
+
function asRecord(value) {
|
|
11
|
+
if (value === null || typeof value !== "object" || Array.isArray(value))
|
|
12
|
+
return null;
|
|
13
|
+
return value;
|
|
14
|
+
}
|
|
15
|
+
function asText(value) {
|
|
16
|
+
return typeof value === "string" ? value : "";
|
|
17
|
+
}
|
|
18
|
+
function textDelta(delta) {
|
|
19
|
+
const assistantMessageEvent = { type: "text_delta", contentIndex: 0, delta };
|
|
20
|
+
return { type: "message_update", assistantMessageEvent };
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Identity of a codex tool item on the omp tool_execution_* wire: the display
|
|
24
|
+
* `args` object (omp parity) plus `argsJson` — the RAW JSON string the model
|
|
25
|
+
* produced, which is what the DSH `tool/call` payload requires. The SDK hands
|
|
26
|
+
* `mcp_tool_call.arguments` over as an unparsed string, so that one rides
|
|
27
|
+
* through verbatim; the other item shapes expose discrete fields (command,
|
|
28
|
+
* query, changes, items), so their string is the faithful JSON rendering of
|
|
29
|
+
* exactly those fields.
|
|
30
|
+
*/
|
|
31
|
+
function toolIdentity(item) {
|
|
32
|
+
const json = (value) => JSON.stringify(value);
|
|
33
|
+
switch (item.type) {
|
|
34
|
+
case "command_execution": {
|
|
35
|
+
const args = { command: asText(item.command) };
|
|
36
|
+
return { toolName: "command_execution", args, argsJson: json(args) };
|
|
37
|
+
}
|
|
38
|
+
case "mcp_tool_call": {
|
|
39
|
+
const args = asRecord(item.arguments) ?? {};
|
|
40
|
+
return {
|
|
41
|
+
toolName: asText(item.tool) || "mcp_tool_call",
|
|
42
|
+
args,
|
|
43
|
+
argsJson: typeof item.arguments === "string" ? item.arguments : json(args),
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
case "file_change": {
|
|
47
|
+
const args = { changes: Array.isArray(item.changes) ? item.changes : [] };
|
|
48
|
+
return { toolName: "file_change", args, argsJson: json(args) };
|
|
49
|
+
}
|
|
50
|
+
case "web_search": {
|
|
51
|
+
const args = { query: asText(item.query) };
|
|
52
|
+
return { toolName: "web_search", args, argsJson: json(args) };
|
|
53
|
+
}
|
|
54
|
+
case "todo_list": {
|
|
55
|
+
const args = { items: Array.isArray(item.items) ? item.items : [] };
|
|
56
|
+
return { toolName: "todo_list", args, argsJson: json(args) };
|
|
57
|
+
}
|
|
58
|
+
default:
|
|
59
|
+
return { toolName: asText(item.type) || "tool", args: {}, argsJson: "{}" };
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/** Render a codex tool item's outcome as omp tool_execution_end result blocks. */
|
|
63
|
+
function toolResult(item) {
|
|
64
|
+
switch (item.type) {
|
|
65
|
+
case "command_execution": {
|
|
66
|
+
const output = asText(item.aggregated_output);
|
|
67
|
+
return { content: [{ type: "text", text: output }], isError: item.status === "failed" };
|
|
68
|
+
}
|
|
69
|
+
case "mcp_tool_call": {
|
|
70
|
+
const result = asRecord(item.result);
|
|
71
|
+
const content = [];
|
|
72
|
+
const rawContent = result !== null && Array.isArray(result.content) ? result.content : [];
|
|
73
|
+
for (const block of rawContent) {
|
|
74
|
+
const record = asRecord(block);
|
|
75
|
+
// Only MCP text blocks bridge (omp parity: images/unknown are dropped).
|
|
76
|
+
if (record !== null && record.type === "text" && typeof record.text === "string") {
|
|
77
|
+
content.push({ type: "text", text: record.text });
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
if (content.length === 0) {
|
|
81
|
+
const error = asRecord(item.error);
|
|
82
|
+
if (error !== null && typeof error.message === "string")
|
|
83
|
+
content.push({ type: "text", text: error.message });
|
|
84
|
+
}
|
|
85
|
+
return { content, isError: item.status === "failed" };
|
|
86
|
+
}
|
|
87
|
+
case "file_change": {
|
|
88
|
+
const changes = Array.isArray(item.changes) ? item.changes : [];
|
|
89
|
+
const lines = [];
|
|
90
|
+
for (const change of changes) {
|
|
91
|
+
const record = asRecord(change);
|
|
92
|
+
if (record !== null)
|
|
93
|
+
lines.push(`${asText(record.kind) || "update"} ${asText(record.path)}`);
|
|
94
|
+
}
|
|
95
|
+
return { content: [{ type: "text", text: lines.join("\n") }], isError: item.status === "failed" };
|
|
96
|
+
}
|
|
97
|
+
case "web_search":
|
|
98
|
+
return { content: [{ type: "text", text: asText(item.query) }], isError: false };
|
|
99
|
+
case "todo_list": {
|
|
100
|
+
const items = Array.isArray(item.items) ? item.items : [];
|
|
101
|
+
const lines = [];
|
|
102
|
+
for (const entry of items) {
|
|
103
|
+
const record = asRecord(entry);
|
|
104
|
+
if (record !== null)
|
|
105
|
+
lines.push(`${record.completed === true ? "[x]" : "[ ]"} ${asText(record.text)}`);
|
|
106
|
+
}
|
|
107
|
+
return { content: [{ type: "text", text: lines.join("\n") }], isError: false };
|
|
108
|
+
}
|
|
109
|
+
default:
|
|
110
|
+
return { content: [], isError: false };
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Codex `todo_list` items → the DSH `todo/write` whole-list snapshot payload
|
|
115
|
+
* (`TodoItem { content, status }`; upstream packages/todo/tool-todo). The
|
|
116
|
+
* whole list is replaced on every write (latest-wins); a codex item only
|
|
117
|
+
* carries the binary completed flag, so non-completed entries are `pending`.
|
|
118
|
+
*/
|
|
119
|
+
function todoSnapshot(items) {
|
|
120
|
+
const todos = [];
|
|
121
|
+
for (const entry of items) {
|
|
122
|
+
const record = asRecord(entry);
|
|
123
|
+
if (record === null)
|
|
124
|
+
continue;
|
|
125
|
+
const text = asText(record.text);
|
|
126
|
+
if (text === "")
|
|
127
|
+
continue;
|
|
128
|
+
todos.push({ content: text, status: record.completed === true ? "completed" : "pending" });
|
|
129
|
+
}
|
|
130
|
+
return todos;
|
|
131
|
+
}
|
|
132
|
+
function toolStart(item) {
|
|
133
|
+
const { toolName, args, argsJson } = toolIdentity(item);
|
|
134
|
+
const start = {
|
|
135
|
+
type: "tool_execution_start",
|
|
136
|
+
toolCallId: asText(item.id),
|
|
137
|
+
toolName,
|
|
138
|
+
args,
|
|
139
|
+
argumentsJson: argsJson,
|
|
140
|
+
};
|
|
141
|
+
if (item.type === "mcp_tool_call")
|
|
142
|
+
start.server = asText(item.server);
|
|
143
|
+
return start;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Terminal tool event for a completed/updated tool item. Emits the start first
|
|
147
|
+
* when this item's start was never projected (minimal chains), keeping the
|
|
148
|
+
* wire's start/end pairing intact without duplicate starts.
|
|
149
|
+
*/
|
|
150
|
+
function toolEnd(item) {
|
|
151
|
+
const id = asText(item.id);
|
|
152
|
+
const { toolName } = toolIdentity(item);
|
|
153
|
+
const result = toolResult(item);
|
|
154
|
+
const end = { type: "tool_execution_end", toolCallId: id, toolName, result, isError: result.isError };
|
|
155
|
+
if (item.type === "command_execution") {
|
|
156
|
+
end.command = asText(item.command);
|
|
157
|
+
if (typeof item.exit_code === "number")
|
|
158
|
+
end.exit_code = item.exit_code;
|
|
159
|
+
}
|
|
160
|
+
if (!startedTools.has(id)) {
|
|
161
|
+
// start never fired for this id (fixture-style minimal chain): emit the pair.
|
|
162
|
+
startedTools.add(id);
|
|
163
|
+
return [toolStart(item), end];
|
|
164
|
+
}
|
|
165
|
+
return end;
|
|
166
|
+
}
|
|
167
|
+
function projectItem(item, phase) {
|
|
168
|
+
const record = asRecord(item);
|
|
169
|
+
if (record === null)
|
|
170
|
+
return null;
|
|
171
|
+
const id = asText(record.id);
|
|
172
|
+
switch (record.type) {
|
|
173
|
+
case "agent_message": {
|
|
174
|
+
const text = asText(record.text);
|
|
175
|
+
if (phase === "started") {
|
|
176
|
+
textWatermark.set(id, text);
|
|
177
|
+
const start = { type: "message_start", message: { role: "assistant", content: [] } };
|
|
178
|
+
return text === "" ? start : [start, textDelta(text)];
|
|
179
|
+
}
|
|
180
|
+
if (phase === "updated") {
|
|
181
|
+
const previous = textWatermark.get(id) ?? "";
|
|
182
|
+
if (text === previous)
|
|
183
|
+
return null;
|
|
184
|
+
const delta = text.startsWith(previous) ? text.slice(previous.length) : text;
|
|
185
|
+
textWatermark.set(id, text);
|
|
186
|
+
return textDelta(delta);
|
|
187
|
+
}
|
|
188
|
+
textWatermark.delete(id);
|
|
189
|
+
return {
|
|
190
|
+
type: "message_end",
|
|
191
|
+
message: { role: "assistant", content: [{ type: "text", text }] },
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
case "reasoning": {
|
|
195
|
+
const text = asText(record.text);
|
|
196
|
+
if (phase === "started") {
|
|
197
|
+
return {
|
|
198
|
+
type: "message_start",
|
|
199
|
+
message: { role: "assistant", content: [{ type: "thinking", thinking: text }] },
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
if (phase === "updated") {
|
|
203
|
+
// Deltas fold into the final thinking block (omp folds thinking_* too).
|
|
204
|
+
return null;
|
|
205
|
+
}
|
|
206
|
+
return {
|
|
207
|
+
type: "message_end",
|
|
208
|
+
message: { role: "assistant", content: [{ type: "thinking", thinking: text }] },
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
case "command_execution":
|
|
212
|
+
case "mcp_tool_call":
|
|
213
|
+
case "file_change":
|
|
214
|
+
case "web_search":
|
|
215
|
+
case "todo_list": {
|
|
216
|
+
if (phase === "started") {
|
|
217
|
+
startedTools.add(id);
|
|
218
|
+
return toolStart(record);
|
|
219
|
+
}
|
|
220
|
+
const terminal = record.status === "completed" || record.status === "failed";
|
|
221
|
+
if (phase === "updated") {
|
|
222
|
+
return terminal ? toolEnd(record) : null; // still in_progress → nothing new
|
|
223
|
+
}
|
|
224
|
+
const events = toolEnd(record); // completed is terminal by definition (toolEnd dedups the start)
|
|
225
|
+
if (record.type !== "todo_list")
|
|
226
|
+
return events;
|
|
227
|
+
// `item.todo_list` ALSO projects the DSH `todo/write` whole-list snapshot
|
|
228
|
+
// (log-only, latest-wins) after the tool pair — the tool pair stays.
|
|
229
|
+
const todos = todoSnapshot(Array.isArray(record.items) ? record.items : []);
|
|
230
|
+
return [...(Array.isArray(events) ? events : [events]), { type: "todo_write", todos }];
|
|
231
|
+
}
|
|
232
|
+
case "error": {
|
|
233
|
+
if (phase === "completed") {
|
|
234
|
+
// Non-fatal item-level error: ride omp's stopReason "error" assistant
|
|
235
|
+
// message shape so agent.ts's ompFailure() classifies it downstream.
|
|
236
|
+
return {
|
|
237
|
+
type: "message_end",
|
|
238
|
+
message: {
|
|
239
|
+
role: "assistant",
|
|
240
|
+
content: [],
|
|
241
|
+
stopReason: "error",
|
|
242
|
+
errorMessage: asText(record.message),
|
|
243
|
+
},
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
return null;
|
|
247
|
+
}
|
|
248
|
+
default:
|
|
249
|
+
return null;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Project one codex SDK ThreadEvent (duck-typed, never imported into dist)
|
|
254
|
+
* onto the omp wire vocabulary. Returns one event, an ordered array, or null
|
|
255
|
+
* for ignorable/malformed input. Never throws.
|
|
256
|
+
*/
|
|
257
|
+
export function projectThreadEvent(evt) {
|
|
258
|
+
try {
|
|
259
|
+
const record = asRecord(evt);
|
|
260
|
+
if (record === null || typeof record.type !== "string")
|
|
261
|
+
return null;
|
|
262
|
+
switch (record.type) {
|
|
263
|
+
case "thread.started": {
|
|
264
|
+
const start = { type: "agent_start" };
|
|
265
|
+
if (typeof record.thread_id === "string" && record.thread_id !== "") {
|
|
266
|
+
start.data = { threadId: record.thread_id };
|
|
267
|
+
}
|
|
268
|
+
return start;
|
|
269
|
+
}
|
|
270
|
+
case "turn.started":
|
|
271
|
+
return { type: "turn_start" };
|
|
272
|
+
case "item.started":
|
|
273
|
+
return projectItem(record.item, "started");
|
|
274
|
+
case "item.updated":
|
|
275
|
+
return projectItem(record.item, "updated");
|
|
276
|
+
case "item.completed":
|
|
277
|
+
return projectItem(record.item, "completed");
|
|
278
|
+
case "turn.completed": {
|
|
279
|
+
const usage = asRecord(record.usage);
|
|
280
|
+
return usage === null ? { type: "turn_end" } : { type: "turn_end", data: { usage } };
|
|
281
|
+
}
|
|
282
|
+
case "turn.failed": {
|
|
283
|
+
const error = asRecord(record.error);
|
|
284
|
+
return error === null ? { type: "turn_end" } : { type: "turn_end", data: { error } };
|
|
285
|
+
}
|
|
286
|
+
case "error":
|
|
287
|
+
// Fatal stream error: the client reports it via onFailure; the wire
|
|
288
|
+
// stream stays silent (S5 ignorable-event invariant).
|
|
289
|
+
return null;
|
|
290
|
+
default:
|
|
291
|
+
return null;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
catch {
|
|
295
|
+
return null;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Codex home resolution + the rollout-HEAD metadata reader.
|
|
3
|
+
*
|
|
4
|
+
* The listing/replay scanner that used to live here is GONE (2026-09-16): the
|
|
5
|
+
* DSH session log is now the session authority (upstream `sessionPersistence`
|
|
6
|
+
* owns list/read/replay), so the adapter no longer derives session ids or
|
|
7
|
+
* transcripts from Codex's rollout store. What remains is:
|
|
8
|
+
*
|
|
9
|
+
* - home resolution (`<DSH home>/agents/codex`, spec S7) with the prod-home
|
|
10
|
+
* - home resolution (native `~/.codex`, never redirected) with the prod-home
|
|
11
|
+
* - {@link readRolloutHead}: a bounded, memoized reader of ONE rollout's
|
|
12
|
+
* FIRST line (`session_meta`), used only to recover the Codex system
|
|
13
|
+
* prompt (`payload.base_instructions`) and the recorded cwd. It never
|
|
14
|
+
* parses the transcript and never throws.
|
|
15
|
+
*
|
|
16
|
+
* Single-direction valve (spec S7): this module NEVER writes under the codex
|
|
17
|
+
* home; the rollout stays Codex's transcript of record.
|
|
18
|
+
*/
|
|
19
|
+
import { closeSync, openSync, readdirSync, readSync, statSync } from "node:fs";
|
|
20
|
+
import { homedir } from "node:os";
|
|
21
|
+
import { join, resolve } from "node:path";
|
|
22
|
+
/** Prod homes that must never be resolved into (repo red line). */
|
|
23
|
+
const PROD_HOMES = [join(homedir(), ".dsh"), join(homedir(), ".superd")];
|
|
24
|
+
export function assertNotProdHome(path, label) {
|
|
25
|
+
if (PROD_HOMES.includes(path) || PROD_HOMES.some((p) => path.startsWith(`${p}/`))) {
|
|
26
|
+
throw new Error(`codex-store: refusing prod home as ${label}: ${path}`);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Resolve the Codex CLI's app-data home — the NATIVE `~/.codex` (user ruling
|
|
31
|
+
* 2026-09-17, pi precedent): the installed app keeps its own home, so the
|
|
32
|
+
* adapter never redirects it when spawning the SDK/CLI and never seeds config
|
|
33
|
+
* into it. Precedence: `CODEX_HOME` (tests / operator override), then the
|
|
34
|
+
* native default.
|
|
35
|
+
*
|
|
36
|
+
* The world's DSH home (`<dshHome>/agents/codex`) is NOT an input here anymore
|
|
37
|
+
* — it anchors adapter-owned DSH state only (session-map.ts). A home without
|
|
38
|
+
* `config.toml` would make the CLI fall back to OpenAI's own endpoint
|
|
39
|
+
* (region-blocked here), but that is the operator's native home: provisioning
|
|
40
|
+
* it is the operator's business (test helper: scripts/setup-codex-home.mjs).
|
|
41
|
+
*/
|
|
42
|
+
export function resolveCodexHome() {
|
|
43
|
+
if (process.env.CODEX_HOME !== undefined) {
|
|
44
|
+
const env = resolve(process.env.CODEX_HOME);
|
|
45
|
+
assertNotProdHome(env, "codex home");
|
|
46
|
+
return env;
|
|
47
|
+
}
|
|
48
|
+
const native = join(homedir(), ".codex");
|
|
49
|
+
assertNotProdHome(native, "codex home");
|
|
50
|
+
return native;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Take an explicit codex home as-is (prod-guarded) — for callers that already
|
|
54
|
+
* hold the final path (models.ts, the spike); distinct from
|
|
55
|
+
* {@link resolveCodexHome}, which resolves the native default.
|
|
56
|
+
*/
|
|
57
|
+
export function useCodexHome(path) {
|
|
58
|
+
const resolved = resolve(path);
|
|
59
|
+
assertNotProdHome(resolved, "codex home");
|
|
60
|
+
return resolved;
|
|
61
|
+
}
|
|
62
|
+
/** Bounded read budget: `session_meta` is the FIRST line, so this is generous. */
|
|
63
|
+
const HEAD_BYTES = 64 * 1024;
|
|
64
|
+
/** Memoized heads by thread id — the head line is immutable once written. */
|
|
65
|
+
const headCache = new Map();
|
|
66
|
+
/**
|
|
67
|
+
* Locate one thread's rollout file under `<codexHome>/sessions/<Y>/<M>/<D>/`.
|
|
68
|
+
* The rollout file name embeds the thread id, so presence is the match test;
|
|
69
|
+
* a miss is retried (a thread's rollout may not exist yet at first call).
|
|
70
|
+
*/
|
|
71
|
+
function findRolloutFile(codexHome, threadId) {
|
|
72
|
+
const root = join(codexHome, "sessions");
|
|
73
|
+
try {
|
|
74
|
+
for (const year of readdirSync(root, { withFileTypes: true })) {
|
|
75
|
+
if (!year.isDirectory())
|
|
76
|
+
continue;
|
|
77
|
+
for (const month of readdirSync(join(root, year.name), { withFileTypes: true })) {
|
|
78
|
+
if (!month.isDirectory())
|
|
79
|
+
continue;
|
|
80
|
+
for (const day of readdirSync(join(root, year.name, month.name), { withFileTypes: true })) {
|
|
81
|
+
if (!day.isDirectory())
|
|
82
|
+
continue;
|
|
83
|
+
const dir = join(root, year.name, month.name, day.name);
|
|
84
|
+
for (const file of readdirSync(dir)) {
|
|
85
|
+
if (file.endsWith(".jsonl") && file.includes(threadId))
|
|
86
|
+
return join(dir, file);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
return undefined;
|
|
94
|
+
}
|
|
95
|
+
return undefined;
|
|
96
|
+
}
|
|
97
|
+
/** Read (at most) the first line of a file; fail-soft. */
|
|
98
|
+
function readFirstLine(path) {
|
|
99
|
+
let fd;
|
|
100
|
+
try {
|
|
101
|
+
fd = openSync(path, "r");
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
return undefined;
|
|
105
|
+
}
|
|
106
|
+
try {
|
|
107
|
+
const chunk = Buffer.allocUnsafe(HEAD_BYTES);
|
|
108
|
+
const read = readSync(fd, chunk, 0, chunk.length, 0);
|
|
109
|
+
if (read <= 0)
|
|
110
|
+
return undefined;
|
|
111
|
+
const text = chunk.subarray(0, read).toString("utf8");
|
|
112
|
+
const end = text.indexOf("\n");
|
|
113
|
+
return end === -1 ? text : text.slice(0, end);
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
return undefined;
|
|
117
|
+
}
|
|
118
|
+
finally {
|
|
119
|
+
closeSync(fd);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Read the `session_meta` head of the rollout belonging to `threadId`:
|
|
124
|
+
* the Codex system prompt and the recorded cwd. Memoized per thread id (the
|
|
125
|
+
* head line never changes); fail-soft — an unknown thread, an unwritten
|
|
126
|
+
* rollout, or a malformed head all yield `undefined`.
|
|
127
|
+
*/
|
|
128
|
+
/**
|
|
129
|
+
* Codex writes `payload.base_instructions` either as a plain string or as an
|
|
130
|
+
* object `{ text }` (observed 2026-09-16 on the SDK line: the object form is
|
|
131
|
+
* what the vendored binary emits today). Accept both; anything else is absent.
|
|
132
|
+
*/
|
|
133
|
+
function baseInstructionsText(value) {
|
|
134
|
+
if (typeof value === "string")
|
|
135
|
+
return value.trim() === "" ? undefined : value;
|
|
136
|
+
if (value !== null && typeof value === "object") {
|
|
137
|
+
const text = value["text"];
|
|
138
|
+
if (typeof text === "string" && text.trim() !== "")
|
|
139
|
+
return text;
|
|
140
|
+
}
|
|
141
|
+
return undefined;
|
|
142
|
+
}
|
|
143
|
+
export function readRolloutHead(codexHome, threadId) {
|
|
144
|
+
const cached = headCache.get(threadId);
|
|
145
|
+
if (cached !== undefined)
|
|
146
|
+
return cached;
|
|
147
|
+
const file = findRolloutFile(codexHome, threadId);
|
|
148
|
+
if (file === undefined)
|
|
149
|
+
return undefined;
|
|
150
|
+
// Cheap existence/size gate before paying for a read.
|
|
151
|
+
try {
|
|
152
|
+
if (!statSync(file).isFile())
|
|
153
|
+
return undefined;
|
|
154
|
+
}
|
|
155
|
+
catch {
|
|
156
|
+
return undefined;
|
|
157
|
+
}
|
|
158
|
+
const line = readFirstLine(file);
|
|
159
|
+
if (line === undefined)
|
|
160
|
+
return undefined;
|
|
161
|
+
let record;
|
|
162
|
+
try {
|
|
163
|
+
record = JSON.parse(line);
|
|
164
|
+
}
|
|
165
|
+
catch {
|
|
166
|
+
return undefined;
|
|
167
|
+
}
|
|
168
|
+
if (record === null || typeof record !== "object")
|
|
169
|
+
return undefined;
|
|
170
|
+
const rec = record;
|
|
171
|
+
if (rec["type"] !== "session_meta")
|
|
172
|
+
return undefined;
|
|
173
|
+
const payload = rec["payload"];
|
|
174
|
+
if (payload === null || typeof payload !== "object")
|
|
175
|
+
return undefined;
|
|
176
|
+
const meta = payload;
|
|
177
|
+
const head = {
|
|
178
|
+
...(typeof meta["id"] === "string" && meta["id"] !== "" ? { threadId: meta["id"] } : {}),
|
|
179
|
+
...(typeof meta["cwd"] === "string" && meta["cwd"] !== "" ? { cwd: meta["cwd"] } : {}),
|
|
180
|
+
...(baseInstructionsText(meta["base_instructions"]) === undefined
|
|
181
|
+
? {}
|
|
182
|
+
: { baseInstructions: baseInstructionsText(meta["base_instructions"]) }),
|
|
183
|
+
};
|
|
184
|
+
headCache.set(threadId, head);
|
|
185
|
+
return head;
|
|
186
|
+
}
|
|
187
|
+
/** Test hygiene: drop memoized heads. */
|
|
188
|
+
export function clearRolloutHeadCache() {
|
|
189
|
+
headCache.clear();
|
|
190
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
export class Inbox {
|
|
2
|
+
#nextTurn = [];
|
|
3
|
+
#nextStep = [];
|
|
4
|
+
get nextTurn() {
|
|
5
|
+
return this.#nextTurn;
|
|
6
|
+
}
|
|
7
|
+
get nextStep() {
|
|
8
|
+
return this.#nextStep;
|
|
9
|
+
}
|
|
10
|
+
clear() {
|
|
11
|
+
this.#nextTurn = [];
|
|
12
|
+
this.#nextStep = [];
|
|
13
|
+
}
|
|
14
|
+
append(target, message) {
|
|
15
|
+
this.#list(target).push(message);
|
|
16
|
+
}
|
|
17
|
+
prepend(target, message) {
|
|
18
|
+
this.#list(target).unshift(message);
|
|
19
|
+
}
|
|
20
|
+
replace(messageId, newMessage) {
|
|
21
|
+
const list = this.#find(messageId);
|
|
22
|
+
if (list === undefined)
|
|
23
|
+
return false;
|
|
24
|
+
const index = list.findIndex((message) => message.id === messageId);
|
|
25
|
+
if (index < 0)
|
|
26
|
+
return false;
|
|
27
|
+
list.splice(index, 1, newMessage);
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
remove(messageId) {
|
|
31
|
+
const list = this.#find(messageId);
|
|
32
|
+
if (list === undefined)
|
|
33
|
+
return false;
|
|
34
|
+
const index = list.findIndex((message) => message.id === messageId);
|
|
35
|
+
if (index < 0)
|
|
36
|
+
return false;
|
|
37
|
+
list.splice(index, 1);
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
splice(target, start, deleteCount, inserted) {
|
|
41
|
+
return this.#list(target).splice(start, deleteCount, ...inserted);
|
|
42
|
+
}
|
|
43
|
+
#list(target) {
|
|
44
|
+
return target === "next-turn" ? this.#nextTurn : this.#nextStep;
|
|
45
|
+
}
|
|
46
|
+
#find(messageId) {
|
|
47
|
+
if (this.#nextTurn.some((message) => message.id === messageId))
|
|
48
|
+
return this.#nextTurn;
|
|
49
|
+
if (this.#nextStep.some((message) => message.id === messageId))
|
|
50
|
+
return this.#nextStep;
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
53
|
+
}
|