open-research-protocol 0.4.27 → 0.4.29
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/CHANGELOG.md +48 -0
- package/README.md +39 -14
- package/bin/orp.js +14 -1
- package/cli/__pycache__/orp.cpython-311.pyc +0 -0
- package/cli/orp.py +1124 -5
- package/docs/START_HERE.md +14 -0
- package/package.json +5 -1
- package/packages/orp-workspace-launcher/src/codex.js +822 -0
- package/packages/orp-workspace-launcher/src/index.js +10 -0
- package/packages/orp-workspace-launcher/src/ledger.js +11 -1
- package/packages/orp-workspace-launcher/test/codex.test.js +309 -0
- package/scripts/__pycache__/orp-kernel-agent-pilot.cpython-311.pyc +0 -0
- package/scripts/__pycache__/orp-kernel-agent-replication.cpython-311.pyc +0 -0
- package/scripts/__pycache__/orp-kernel-benchmark.cpython-311.pyc +0 -0
- package/scripts/__pycache__/orp-kernel-canonical-continuation.cpython-311.pyc +0 -0
- package/scripts/__pycache__/orp-kernel-continuation-pilot.cpython-311.pyc +0 -0
- package/scripts/render-terminal-demo.py +262 -134
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
export {
|
|
2
|
+
applyCodexReconcilePlan,
|
|
3
|
+
buildCodexReconcilePlan,
|
|
4
|
+
buildCodexStatusReport,
|
|
5
|
+
parseCodexSessionMetaLine,
|
|
6
|
+
runOrpCodexCommand,
|
|
7
|
+
scanCodexSessions,
|
|
8
|
+
summarizeCodexReconcile,
|
|
9
|
+
summarizeCodexStatus,
|
|
10
|
+
} from "./codex.js";
|
|
1
11
|
export {
|
|
2
12
|
buildCloneCommand,
|
|
3
13
|
buildDirectCommand,
|
|
@@ -945,7 +945,7 @@ function summarizeWorkspaceLedgerMutation(result) {
|
|
|
945
945
|
return lines.join("\n");
|
|
946
946
|
}
|
|
947
947
|
|
|
948
|
-
async function
|
|
948
|
+
async function applyWorkspaceLedgerMutation(options, mutate, action) {
|
|
949
949
|
const source = await loadWorkspaceSource(options);
|
|
950
950
|
const parsed = parseWorkspaceSource(source);
|
|
951
951
|
const manifest = normalizeEditableManifest(source, parsed);
|
|
@@ -974,6 +974,12 @@ async function runWorkspaceLedgerMutation(options, mutate, action) {
|
|
|
974
974
|
manifest: finalManifest,
|
|
975
975
|
};
|
|
976
976
|
|
|
977
|
+
return result;
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
async function runWorkspaceLedgerMutation(options, mutate, action) {
|
|
981
|
+
const result = await applyWorkspaceLedgerMutation(options, mutate, action);
|
|
982
|
+
|
|
977
983
|
if (options.json) {
|
|
978
984
|
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
979
985
|
return 0;
|
|
@@ -983,6 +989,10 @@ async function runWorkspaceLedgerMutation(options, mutate, action) {
|
|
|
983
989
|
return 0;
|
|
984
990
|
}
|
|
985
991
|
|
|
992
|
+
export async function applyWorkspaceAddTabOptions(options = {}) {
|
|
993
|
+
return applyWorkspaceLedgerMutation(options, addTabToManifest, "add-tab");
|
|
994
|
+
}
|
|
995
|
+
|
|
986
996
|
export async function runWorkspaceAddTab(argv = process.argv.slice(2)) {
|
|
987
997
|
const options = parseWorkspaceAddTabArgs(argv);
|
|
988
998
|
if (options.help) {
|
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import fs from "node:fs/promises";
|
|
4
|
+
import os from "node:os";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
applyCodexReconcilePlan,
|
|
9
|
+
buildCodexReconcilePlan,
|
|
10
|
+
buildCodexStatusReport,
|
|
11
|
+
parseCodexSessionMetaLine,
|
|
12
|
+
runOrpCodexCommand,
|
|
13
|
+
scanCodexSessions,
|
|
14
|
+
} from "../src/index.js";
|
|
15
|
+
|
|
16
|
+
async function makeTempDir() {
|
|
17
|
+
return fs.mkdtemp(path.join(os.tmpdir(), "orp-codex-"));
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async function writeSession(codexHome, sessionId, cwd, timestamp, extraPayload = {}) {
|
|
21
|
+
const day = timestamp.slice(0, 10).split("-");
|
|
22
|
+
const sessionsDir = path.join(codexHome, "sessions", day[0], day[1], day[2]);
|
|
23
|
+
await fs.mkdir(sessionsDir, { recursive: true });
|
|
24
|
+
const filePath = path.join(sessionsDir, `rollout-${timestamp.replaceAll(":", "-")}-${sessionId}.jsonl`);
|
|
25
|
+
const row = {
|
|
26
|
+
timestamp,
|
|
27
|
+
type: "session_meta",
|
|
28
|
+
payload: {
|
|
29
|
+
id: sessionId,
|
|
30
|
+
timestamp,
|
|
31
|
+
cwd,
|
|
32
|
+
originator: "codex-tui",
|
|
33
|
+
cli_version: "0.125.0",
|
|
34
|
+
...extraPayload,
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
await fs.writeFile(filePath, `${JSON.stringify(row)}\n`, "utf8");
|
|
38
|
+
const mtime = new Date(timestamp);
|
|
39
|
+
await fs.utimes(filePath, mtime, mtime);
|
|
40
|
+
return filePath;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async function writeSessionWithPrefix(codexHome, sessionId, cwd, timestamp) {
|
|
44
|
+
const filePath = await writeSession(codexHome, sessionId, cwd, timestamp);
|
|
45
|
+
const original = await fs.readFile(filePath, "utf8");
|
|
46
|
+
await fs.writeFile(filePath, `${JSON.stringify({ type: "response_item", payload: {} })}\n${original}`, "utf8");
|
|
47
|
+
const mtime = new Date(timestamp);
|
|
48
|
+
await fs.utimes(filePath, mtime, mtime);
|
|
49
|
+
return filePath;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async function writeWorkspaceManifest(filePath, tabs) {
|
|
53
|
+
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
54
|
+
await fs.writeFile(
|
|
55
|
+
filePath,
|
|
56
|
+
`${JSON.stringify(
|
|
57
|
+
{
|
|
58
|
+
version: "1",
|
|
59
|
+
workspaceId: "orp-main",
|
|
60
|
+
title: "ORP Main",
|
|
61
|
+
tabs,
|
|
62
|
+
},
|
|
63
|
+
null,
|
|
64
|
+
2,
|
|
65
|
+
)}\n`,
|
|
66
|
+
"utf8",
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
test("parseCodexSessionMetaLine reads stable session metadata", () => {
|
|
71
|
+
const row = JSON.stringify({
|
|
72
|
+
timestamp: "2026-04-25T12:00:00Z",
|
|
73
|
+
type: "session_meta",
|
|
74
|
+
payload: {
|
|
75
|
+
id: "019dc2cb-d435-7072-bbfd-4ae4280474dd",
|
|
76
|
+
timestamp: "2026-04-25T12:00:00Z",
|
|
77
|
+
cwd: "/tmp/example",
|
|
78
|
+
originator: "codex-tui",
|
|
79
|
+
cli_version: "0.125.0",
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
const parsed = parseCodexSessionMetaLine(row, "/tmp/session.jsonl", { mtimeMs: 123 });
|
|
84
|
+
assert.equal(parsed.sessionId, "019dc2cb-d435-7072-bbfd-4ae4280474dd");
|
|
85
|
+
assert.equal(parsed.cwd, "/tmp/example");
|
|
86
|
+
assert.equal(parsed.cliVersion, "0.125.0");
|
|
87
|
+
assert.equal(parsed.updatedMs, 123);
|
|
88
|
+
assert.equal(parseCodexSessionMetaLine("not json", "/tmp/session.jsonl"), null);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test("scanCodexSessions ignores delegated sessions by default", async () => {
|
|
92
|
+
const tempDir = await makeTempDir();
|
|
93
|
+
const codexHome = path.join(tempDir, "codex-home");
|
|
94
|
+
await writeSession(
|
|
95
|
+
codexHome,
|
|
96
|
+
"019dc2cb-d435-7072-bbfd-4ae4280474aa",
|
|
97
|
+
path.join(tempDir, "repo"),
|
|
98
|
+
"2026-04-25T12:00:00Z",
|
|
99
|
+
);
|
|
100
|
+
await writeSession(
|
|
101
|
+
codexHome,
|
|
102
|
+
"019dc2cb-d435-7072-bbfd-4ae4280474bb",
|
|
103
|
+
path.join(tempDir, "repo"),
|
|
104
|
+
"2026-04-25T12:01:00Z",
|
|
105
|
+
{ source: { subagent: { other: "guardian" } } },
|
|
106
|
+
);
|
|
107
|
+
await writeSession(
|
|
108
|
+
codexHome,
|
|
109
|
+
"019dc2cb-d435-7072-bbfd-4ae4280474cc",
|
|
110
|
+
path.join(tempDir, "repo"),
|
|
111
|
+
"2026-04-25T12:02:00Z",
|
|
112
|
+
{ originator: "clawdad" },
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
const defaultSessions = await scanCodexSessions({ codexHome, sinceMs: 0 });
|
|
116
|
+
assert.deepEqual(
|
|
117
|
+
defaultSessions.map((session) => session.sessionId),
|
|
118
|
+
["019dc2cb-d435-7072-bbfd-4ae4280474aa"],
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
const allSessions = await scanCodexSessions({ codexHome, sinceMs: 0, includeSubagents: true });
|
|
122
|
+
assert.deepEqual(
|
|
123
|
+
allSessions.map((session) => session.sessionId),
|
|
124
|
+
[
|
|
125
|
+
"019dc2cb-d435-7072-bbfd-4ae4280474cc",
|
|
126
|
+
"019dc2cb-d435-7072-bbfd-4ae4280474bb",
|
|
127
|
+
"019dc2cb-d435-7072-bbfd-4ae4280474aa",
|
|
128
|
+
],
|
|
129
|
+
);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
test("scanCodexSessions finds session metadata near the start of a rollout file", async () => {
|
|
133
|
+
const tempDir = await makeTempDir();
|
|
134
|
+
const codexHome = path.join(tempDir, "codex-home");
|
|
135
|
+
const repoRoot = path.join(tempDir, "repo");
|
|
136
|
+
await writeSessionWithPrefix(
|
|
137
|
+
codexHome,
|
|
138
|
+
"019dc2cb-d435-7072-bbfd-4ae4280474dd",
|
|
139
|
+
repoRoot,
|
|
140
|
+
"2026-04-25T12:00:00Z",
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
const sessions = await scanCodexSessions({ codexHome, sinceMs: 0 });
|
|
144
|
+
assert.equal(sessions.length, 1);
|
|
145
|
+
assert.equal(sessions[0].sessionId, "019dc2cb-d435-7072-bbfd-4ae4280474dd");
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test("buildCodexStatusReport marks a tracked repo stale when local Codex metadata is newer", async () => {
|
|
149
|
+
const tempDir = await makeTempDir();
|
|
150
|
+
const repoRoot = path.join(tempDir, "repo");
|
|
151
|
+
const codexHome = path.join(tempDir, "codex-home");
|
|
152
|
+
const workspaceFile = path.join(tempDir, "workspace.json");
|
|
153
|
+
await fs.mkdir(repoRoot, { recursive: true });
|
|
154
|
+
await writeWorkspaceManifest(workspaceFile, [
|
|
155
|
+
{
|
|
156
|
+
title: "repo",
|
|
157
|
+
path: repoRoot,
|
|
158
|
+
resumeTool: "codex",
|
|
159
|
+
resumeSessionId: "019dc2cb-d435-7072-bbfd-4ae428047401",
|
|
160
|
+
},
|
|
161
|
+
]);
|
|
162
|
+
await writeSession(codexHome, "019dc2cb-d435-7072-bbfd-4ae428047402", repoRoot, "2026-04-25T12:00:00Z");
|
|
163
|
+
|
|
164
|
+
const report = await buildCodexStatusReport({
|
|
165
|
+
workspaceFile,
|
|
166
|
+
codexHome,
|
|
167
|
+
path: repoRoot,
|
|
168
|
+
sinceDays: 0,
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
assert.equal(report.status, "stale");
|
|
172
|
+
assert.equal(report.trackedTab.codexSessionId, "019dc2cb-d435-7072-bbfd-4ae428047401");
|
|
173
|
+
assert.equal(report.latestCodexSession.sessionId, "019dc2cb-d435-7072-bbfd-4ae428047402");
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
test("Codex reconcile updates tracked workspace tabs without appending by default", async () => {
|
|
177
|
+
const tempDir = await makeTempDir();
|
|
178
|
+
const repoRoot = path.join(tempDir, "repo");
|
|
179
|
+
const codexHome = path.join(tempDir, "codex-home");
|
|
180
|
+
const workspaceFile = path.join(tempDir, "workspace.json");
|
|
181
|
+
await fs.mkdir(repoRoot, { recursive: true });
|
|
182
|
+
await writeWorkspaceManifest(workspaceFile, [
|
|
183
|
+
{
|
|
184
|
+
title: "repo",
|
|
185
|
+
path: repoRoot,
|
|
186
|
+
resumeTool: "codex",
|
|
187
|
+
resumeSessionId: "019dc2cb-d435-7072-bbfd-4ae428047411",
|
|
188
|
+
},
|
|
189
|
+
]);
|
|
190
|
+
await writeSession(codexHome, "019dc2cb-d435-7072-bbfd-4ae428047412", repoRoot, "2026-04-25T12:00:00Z");
|
|
191
|
+
|
|
192
|
+
const plan = await buildCodexReconcilePlan({
|
|
193
|
+
workspaceFile,
|
|
194
|
+
codexHome,
|
|
195
|
+
sinceDays: 0,
|
|
196
|
+
});
|
|
197
|
+
assert.equal(plan.actionCount, 1);
|
|
198
|
+
assert.equal(plan.actions[0].action, "update");
|
|
199
|
+
|
|
200
|
+
const applied = await applyCodexReconcilePlan(plan, {
|
|
201
|
+
workspaceFile,
|
|
202
|
+
codexHome,
|
|
203
|
+
});
|
|
204
|
+
assert.equal(applied.actions[0].applied, true);
|
|
205
|
+
|
|
206
|
+
const manifest = JSON.parse(await fs.readFile(workspaceFile, "utf8"));
|
|
207
|
+
assert.equal(manifest.tabs.length, 1);
|
|
208
|
+
assert.equal(manifest.tabs[0].resumeSessionId, "019dc2cb-d435-7072-bbfd-4ae428047412");
|
|
209
|
+
assert.equal(manifest.tabs[0].codexSessionId, "019dc2cb-d435-7072-bbfd-4ae428047412");
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
test("bare orp codex routes to start", async () => {
|
|
213
|
+
const tempDir = await makeTempDir();
|
|
214
|
+
const codexHome = path.join(tempDir, "codex-home");
|
|
215
|
+
const fakeCodex = path.join(tempDir, "fake-codex");
|
|
216
|
+
await fs.writeFile(fakeCodex, "#!/bin/sh\nexit 0\n", "utf8");
|
|
217
|
+
await fs.chmod(fakeCodex, 0o755);
|
|
218
|
+
|
|
219
|
+
const originalWrite = process.stderr.write;
|
|
220
|
+
let stderr = "";
|
|
221
|
+
process.stderr.write = (chunk) => {
|
|
222
|
+
stderr += String(chunk);
|
|
223
|
+
return true;
|
|
224
|
+
};
|
|
225
|
+
try {
|
|
226
|
+
const code = await runOrpCodexCommand([
|
|
227
|
+
"--path",
|
|
228
|
+
tempDir,
|
|
229
|
+
"--codex-home",
|
|
230
|
+
codexHome,
|
|
231
|
+
"--codex-bin",
|
|
232
|
+
fakeCodex,
|
|
233
|
+
"--watch-timeout-ms",
|
|
234
|
+
"1",
|
|
235
|
+
"--search",
|
|
236
|
+
]);
|
|
237
|
+
assert.equal(code, 0);
|
|
238
|
+
assert.match(stderr, /Fallback: orp workspace add-tab main --here --current-codex/);
|
|
239
|
+
} finally {
|
|
240
|
+
process.stderr.write = originalWrite;
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
test("bare orp codex saves the new session when Codex writes metadata", async () => {
|
|
245
|
+
const tempDir = await makeTempDir();
|
|
246
|
+
const repoRoot = path.join(tempDir, "repo");
|
|
247
|
+
const codexHome = path.join(tempDir, "codex-home");
|
|
248
|
+
const workspaceFile = path.join(tempDir, "workspace.json");
|
|
249
|
+
const fakeCodex = path.join(tempDir, "fake-codex.js");
|
|
250
|
+
const sessionId = "019dc2cb-d435-7072-bbfd-4ae4280474ee";
|
|
251
|
+
await fs.mkdir(repoRoot, { recursive: true });
|
|
252
|
+
await writeWorkspaceManifest(workspaceFile, []);
|
|
253
|
+
await fs.writeFile(
|
|
254
|
+
fakeCodex,
|
|
255
|
+
`#!/usr/bin/env node
|
|
256
|
+
const fs = require("node:fs");
|
|
257
|
+
const path = require("node:path");
|
|
258
|
+
if (!process.argv.includes("--search")) process.exit(7);
|
|
259
|
+
const codexHome = ${JSON.stringify(codexHome)};
|
|
260
|
+
const repoRoot = ${JSON.stringify(repoRoot)};
|
|
261
|
+
const sessionId = ${JSON.stringify(sessionId)};
|
|
262
|
+
const timestamp = new Date().toISOString();
|
|
263
|
+
const dir = path.join(codexHome, "sessions", "2026", "04", "25");
|
|
264
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
265
|
+
fs.writeFileSync(
|
|
266
|
+
path.join(dir, "rollout-2026-04-25T12-00-00Z-" + sessionId + ".jsonl"),
|
|
267
|
+
JSON.stringify({
|
|
268
|
+
timestamp,
|
|
269
|
+
type: "session_meta",
|
|
270
|
+
payload: { id: sessionId, timestamp, cwd: repoRoot, originator: "codex-tui" },
|
|
271
|
+
}) + "\\n",
|
|
272
|
+
);
|
|
273
|
+
`,
|
|
274
|
+
"utf8",
|
|
275
|
+
);
|
|
276
|
+
await fs.chmod(fakeCodex, 0o755);
|
|
277
|
+
|
|
278
|
+
const originalWrite = process.stderr.write;
|
|
279
|
+
let stderr = "";
|
|
280
|
+
process.stderr.write = (chunk) => {
|
|
281
|
+
stderr += String(chunk);
|
|
282
|
+
return true;
|
|
283
|
+
};
|
|
284
|
+
try {
|
|
285
|
+
const code = await runOrpCodexCommand([
|
|
286
|
+
"--path",
|
|
287
|
+
repoRoot,
|
|
288
|
+
"--workspace-file",
|
|
289
|
+
workspaceFile,
|
|
290
|
+
"--codex-home",
|
|
291
|
+
codexHome,
|
|
292
|
+
"--codex-bin",
|
|
293
|
+
fakeCodex,
|
|
294
|
+
"--watch-timeout-ms",
|
|
295
|
+
"2000",
|
|
296
|
+
"--search",
|
|
297
|
+
]);
|
|
298
|
+
assert.equal(code, 0);
|
|
299
|
+
assert.match(stderr, new RegExp(`ORP saved Codex session for .*: codex resume ${sessionId}`));
|
|
300
|
+
} finally {
|
|
301
|
+
process.stderr.write = originalWrite;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
const manifest = JSON.parse(await fs.readFile(workspaceFile, "utf8"));
|
|
305
|
+
assert.equal(manifest.tabs.length, 1);
|
|
306
|
+
assert.equal(manifest.tabs[0].path, repoRoot);
|
|
307
|
+
assert.equal(manifest.tabs[0].resumeTool, "codex");
|
|
308
|
+
assert.equal(manifest.tabs[0].resumeSessionId, sessionId);
|
|
309
|
+
});
|
|
Binary file
|
|
Binary file
|
|
Binary file
|