open-research-protocol 0.4.19 → 0.4.20
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_INTEGRATION.md +8 -0
- package/README.md +197 -16
- package/cli/orp.py +14427 -9787
- package/docs/AGENT_LOOP.md +38 -0
- package/docs/ORP_AUTONOMY_PROJECT_COMPILATION_MODEL.md +252 -0
- package/docs/START_HERE.md +230 -9
- package/package.json +1 -1
- package/packages/orp-workspace-launcher/README.md +19 -1
- package/packages/orp-workspace-launcher/src/core-plan.js +72 -2
- package/packages/orp-workspace-launcher/src/hosted-state.js +9 -0
- package/packages/orp-workspace-launcher/src/index.js +2 -0
- package/packages/orp-workspace-launcher/src/ledger.js +74 -2
- package/packages/orp-workspace-launcher/src/list.js +18 -0
- package/packages/orp-workspace-launcher/src/orp-command.js +8 -4
- package/packages/orp-workspace-launcher/src/orp.js +13 -0
- package/packages/orp-workspace-launcher/src/registry.js +10 -0
- package/packages/orp-workspace-launcher/src/sync.js +12 -0
- package/packages/orp-workspace-launcher/src/tabs.js +39 -2
- package/packages/orp-workspace-launcher/test/core-plan.test.js +13 -0
- package/packages/orp-workspace-launcher/test/ledger.test.js +50 -0
- package/packages/orp-workspace-launcher/test/list.test.js +19 -0
- package/packages/orp-workspace-launcher/test/tabs.test.js +51 -6
- package/spec/v1/workspace.schema.json +36 -2
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
import process from "node:process";
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
buildCloneCommand,
|
|
5
|
+
buildDirectCommand,
|
|
6
|
+
buildLaunchPlan,
|
|
7
|
+
buildSetupCommand,
|
|
8
|
+
deriveWorkspaceId,
|
|
9
|
+
getResumeCommand,
|
|
10
|
+
parseWorkspaceSource,
|
|
11
|
+
} from "./core-plan.js";
|
|
4
12
|
import { loadWorkspaceSource } from "./orp.js";
|
|
5
13
|
|
|
6
14
|
export function parseWorkspaceTabsArgs(argv = []) {
|
|
@@ -61,6 +69,7 @@ export function buildWorkspaceTabsReport(source, parsed, options = {}) {
|
|
|
61
69
|
sourceLabel: source.sourceLabel,
|
|
62
70
|
title: parsed.manifest?.title || source.title,
|
|
63
71
|
workspaceId: deriveWorkspaceId(source, parsed),
|
|
72
|
+
machine: parsed.manifest?.machine || null,
|
|
64
73
|
parseMode: parsed.parseMode,
|
|
65
74
|
tabCount: launchTabs.length,
|
|
66
75
|
skippedCount: parsed.skipped.length,
|
|
@@ -68,10 +77,16 @@ export function buildWorkspaceTabsReport(source, parsed, options = {}) {
|
|
|
68
77
|
index: index + 1,
|
|
69
78
|
title: tab.title,
|
|
70
79
|
path: tab.path,
|
|
80
|
+
remoteUrl: tab.remoteUrl || null,
|
|
81
|
+
remoteBranch: tab.remoteBranch || null,
|
|
82
|
+
bootstrapCommand: tab.bootstrapCommand || null,
|
|
71
83
|
resumeCommand: getResumeCommand(tab),
|
|
72
84
|
restartCommand: buildDirectCommand(
|
|
73
85
|
{
|
|
74
86
|
path: tab.path,
|
|
87
|
+
remoteUrl: tab.remoteUrl || null,
|
|
88
|
+
remoteBranch: tab.remoteBranch || null,
|
|
89
|
+
bootstrapCommand: tab.bootstrapCommand || null,
|
|
75
90
|
resumeCommand: tab.resumeCommand || null,
|
|
76
91
|
resumeTool: tab.resumeTool || null,
|
|
77
92
|
resumeSessionId: tab.sessionId || null,
|
|
@@ -79,6 +94,8 @@ export function buildWorkspaceTabsReport(source, parsed, options = {}) {
|
|
|
79
94
|
},
|
|
80
95
|
{ resume: true },
|
|
81
96
|
),
|
|
97
|
+
cloneCommand: buildCloneCommand(tab),
|
|
98
|
+
setupCommand: buildSetupCommand(tab),
|
|
82
99
|
resumeTool: tab.resumeTool || null,
|
|
83
100
|
resumeSessionId: tab.sessionId || null,
|
|
84
101
|
codexSessionId: tab.resumeTool === "codex" ? tab.sessionId || null : null,
|
|
@@ -92,6 +109,13 @@ export function summarizeWorkspaceTabs(report) {
|
|
|
92
109
|
const lines = [
|
|
93
110
|
`Source: ${report.sourceLabel}`,
|
|
94
111
|
`Workspace ID: ${report.workspaceId}`,
|
|
112
|
+
...(report.machine?.machineLabel
|
|
113
|
+
? [
|
|
114
|
+
`Machine: ${report.machine.machineLabel}${
|
|
115
|
+
report.machine.platform ? ` (${report.machine.platform})` : ""
|
|
116
|
+
}${report.machine.machineId ? ` [${report.machine.machineId}]` : ""}`,
|
|
117
|
+
]
|
|
118
|
+
: []),
|
|
95
119
|
`Saved tabs: ${report.tabCount}`,
|
|
96
120
|
`Parse mode: ${report.parseMode}`,
|
|
97
121
|
"",
|
|
@@ -100,6 +124,18 @@ export function summarizeWorkspaceTabs(report) {
|
|
|
100
124
|
for (const tab of report.tabs) {
|
|
101
125
|
lines.push(`${String(tab.index).padStart(2, "0")}. ${tab.title}`);
|
|
102
126
|
lines.push(` path: ${tab.path}`);
|
|
127
|
+
if (tab.remoteUrl) {
|
|
128
|
+
lines.push(` remote: ${tab.remoteUrl}${tab.remoteBranch ? ` [branch ${tab.remoteBranch}]` : ""}`);
|
|
129
|
+
}
|
|
130
|
+
if (tab.cloneCommand) {
|
|
131
|
+
lines.push(` clone: ${tab.cloneCommand}`);
|
|
132
|
+
}
|
|
133
|
+
if (tab.bootstrapCommand) {
|
|
134
|
+
lines.push(` bootstrap: ${tab.bootstrapCommand}`);
|
|
135
|
+
}
|
|
136
|
+
if (tab.setupCommand) {
|
|
137
|
+
lines.push(` setup: ${tab.setupCommand}`);
|
|
138
|
+
}
|
|
103
139
|
if (tab.resumeCommand) {
|
|
104
140
|
lines.push(` resume: ${tab.restartCommand}`);
|
|
105
141
|
}
|
|
@@ -135,8 +171,9 @@ Options:
|
|
|
135
171
|
-h, --help Show this help text
|
|
136
172
|
|
|
137
173
|
Notes:
|
|
138
|
-
- This shows the saved tab order plus any stored \`codex resume ...\`
|
|
174
|
+
- This shows the saved tab order plus any stored local path, remote repo, bootstrap command, and \`codex resume ...\` / \`claude --resume ...\` metadata.
|
|
139
175
|
- The human-readable \`resume:\` line is already copyable and includes the saved \`cd ... && resume ...\` recovery command.
|
|
176
|
+
- When a tab also has \`remote:\` or \`setup:\` lines, those are the portable cross-machine clues for cloning and preparing the repo on another rig.
|
|
140
177
|
- The selector can be \`main\`, \`offhand\`, a hosted idea id, a hosted workspace id, a local workspace id, or a saved workspace title/slug.
|
|
141
178
|
`);
|
|
142
179
|
}
|
|
@@ -118,14 +118,23 @@ test("normalizeWorkspaceManifest keeps ledger fields and strips nothing importan
|
|
|
118
118
|
version: "1",
|
|
119
119
|
workspaceId: "terminal-paths",
|
|
120
120
|
title: "Terminal Paths",
|
|
121
|
+
machine: {
|
|
122
|
+
machineId: "mac-studio:darwin",
|
|
123
|
+
machineLabel: "Mac Studio",
|
|
124
|
+
platform: "darwin",
|
|
125
|
+
},
|
|
121
126
|
tabs: [
|
|
122
127
|
{
|
|
123
128
|
title: "orp",
|
|
124
129
|
path: "/Volumes/Code_2TB/code/orp",
|
|
130
|
+
remoteUrl: "git@github.com:SproutSeeds/orp.git",
|
|
131
|
+
bootstrapCommand: "npm install",
|
|
125
132
|
},
|
|
126
133
|
{
|
|
127
134
|
title: "lab",
|
|
128
135
|
path: "/Volumes/Code_2TB/code/anthropic-lab",
|
|
136
|
+
remoteUrl: "git@github.com:anthropic/lab.git",
|
|
137
|
+
remoteBranch: "main",
|
|
129
138
|
resumeTool: "claude",
|
|
130
139
|
resumeSessionId: "claude-456",
|
|
131
140
|
},
|
|
@@ -134,8 +143,12 @@ test("normalizeWorkspaceManifest keeps ledger fields and strips nothing importan
|
|
|
134
143
|
|
|
135
144
|
assert.equal(manifest.workspaceId, "terminal-paths");
|
|
136
145
|
assert.equal(manifest.title, "Terminal Paths");
|
|
146
|
+
assert.equal(manifest.machine?.machineLabel, "Mac Studio");
|
|
137
147
|
assert.equal(manifest.tabs[0]?.title, "orp");
|
|
148
|
+
assert.equal(manifest.tabs[0]?.remoteUrl, "git@github.com:SproutSeeds/orp.git");
|
|
149
|
+
assert.equal(manifest.tabs[0]?.bootstrapCommand, "npm install");
|
|
138
150
|
assert.equal(manifest.tabs[1]?.resumeTool, "claude");
|
|
151
|
+
assert.equal(manifest.tabs[1]?.remoteBranch, "main");
|
|
139
152
|
});
|
|
140
153
|
|
|
141
154
|
test("extractWorkspaceNarrativeNotes removes structured workspace blocks and legacy path lines", () => {
|
|
@@ -83,6 +83,10 @@ test("parseWorkspaceAddTabArgs accepts explicit resume metadata", () => {
|
|
|
83
83
|
"main",
|
|
84
84
|
"--path",
|
|
85
85
|
"/Volumes/Code_2TB/code/new-project",
|
|
86
|
+
"--remote-url",
|
|
87
|
+
"git@github.com:org/new-project.git",
|
|
88
|
+
"--bootstrap-command",
|
|
89
|
+
"npm install",
|
|
86
90
|
"--resume-tool",
|
|
87
91
|
"claude",
|
|
88
92
|
"--resume-session-id",
|
|
@@ -92,6 +96,8 @@ test("parseWorkspaceAddTabArgs accepts explicit resume metadata", () => {
|
|
|
92
96
|
|
|
93
97
|
assert.equal(parsed.ideaId, "main");
|
|
94
98
|
assert.equal(parsed.path, "/Volumes/Code_2TB/code/new-project");
|
|
99
|
+
assert.equal(parsed.remoteUrl, "git@github.com:org/new-project.git");
|
|
100
|
+
assert.equal(parsed.bootstrapCommand, "npm install");
|
|
95
101
|
assert.equal(parsed.resumeTool, "claude");
|
|
96
102
|
assert.equal(parsed.resumeSessionId, "claude-456");
|
|
97
103
|
assert.equal(parsed.json, true);
|
|
@@ -128,8 +134,12 @@ test("parseWorkspaceCreateArgs validates slug titles and optional seed metadata"
|
|
|
128
134
|
"main-cody-1",
|
|
129
135
|
"--slot",
|
|
130
136
|
"main",
|
|
137
|
+
"--machine-label",
|
|
138
|
+
"Mac Studio",
|
|
131
139
|
"--path",
|
|
132
140
|
"/Volumes/Code_2TB/code/orp",
|
|
141
|
+
"--remote-url",
|
|
142
|
+
"git@github.com:SproutSeeds/orp.git",
|
|
133
143
|
"--resume-tool",
|
|
134
144
|
"claude",
|
|
135
145
|
"--resume-session-id",
|
|
@@ -139,7 +149,9 @@ test("parseWorkspaceCreateArgs validates slug titles and optional seed metadata"
|
|
|
139
149
|
|
|
140
150
|
assert.equal(parsed.title, "main-cody-1");
|
|
141
151
|
assert.equal(parsed.slotName, "main");
|
|
152
|
+
assert.equal(parsed.machineLabel, "Mac Studio");
|
|
142
153
|
assert.equal(parsed.path, "/Volumes/Code_2TB/code/orp");
|
|
154
|
+
assert.equal(parsed.remoteUrl, "git@github.com:SproutSeeds/orp.git");
|
|
143
155
|
assert.equal(parsed.resumeTool, "claude");
|
|
144
156
|
assert.equal(parsed.resumeSessionId, "claude-456");
|
|
145
157
|
assert.equal(parsed.json, true);
|
|
@@ -157,6 +169,8 @@ test("addTabToManifest canonicalizes Claude resume commands from tool plus sessi
|
|
|
157
169
|
const result = addTabToManifest(sampleManifest(), {
|
|
158
170
|
path: "/Volumes/Code_2TB/code/anthropic-lab",
|
|
159
171
|
title: "anthropic-lab",
|
|
172
|
+
remoteUrl: "git@github.com:anthropic/anthropic-lab.git",
|
|
173
|
+
bootstrapCommand: "uv sync",
|
|
160
174
|
resumeTool: "claude",
|
|
161
175
|
resumeSessionId: "claude-456",
|
|
162
176
|
});
|
|
@@ -165,6 +179,8 @@ test("addTabToManifest canonicalizes Claude resume commands from tool plus sessi
|
|
|
165
179
|
assert.equal(result.manifest.tabs.length, 3);
|
|
166
180
|
assert.equal(result.manifest.tabs[2]?.path, "/Volumes/Code_2TB/code/anthropic-lab");
|
|
167
181
|
assert.equal(result.manifest.tabs[2]?.title, "anthropic-lab");
|
|
182
|
+
assert.equal(result.manifest.tabs[2]?.remoteUrl, "git@github.com:anthropic/anthropic-lab.git");
|
|
183
|
+
assert.equal(result.manifest.tabs[2]?.bootstrapCommand, "uv sync");
|
|
168
184
|
assert.equal(result.manifest.tabs[2]?.resumeCommand, "claude --resume claude-456");
|
|
169
185
|
assert.equal(result.manifest.tabs[2]?.resumeTool, "claude");
|
|
170
186
|
assert.equal(result.manifest.tabs[2]?.sessionId, "claude-456");
|
|
@@ -250,6 +266,10 @@ test("runWorkspaceAddTab updates a local workspace manifest file", async () => {
|
|
|
250
266
|
manifestPath,
|
|
251
267
|
"--path",
|
|
252
268
|
"/Volumes/Code_2TB/code/anthropic-lab",
|
|
269
|
+
"--remote-url",
|
|
270
|
+
"git@github.com:anthropic/anthropic-lab.git",
|
|
271
|
+
"--bootstrap-command",
|
|
272
|
+
"uv sync",
|
|
253
273
|
"--resume-tool",
|
|
254
274
|
"claude",
|
|
255
275
|
"--resume-session-id",
|
|
@@ -263,8 +283,11 @@ test("runWorkspaceAddTab updates a local workspace manifest file", async () => {
|
|
|
263
283
|
assert.equal(code, 0);
|
|
264
284
|
assert.equal(payload.action, "add-tab");
|
|
265
285
|
assert.equal(payload.tabCount, 3);
|
|
286
|
+
assert.equal(payload.tab.remoteUrl, "git@github.com:anthropic/anthropic-lab.git");
|
|
287
|
+
assert.equal(payload.tab.bootstrapCommand, "uv sync");
|
|
266
288
|
assert.equal(payload.tab.resumeCommand, "claude --resume claude-456");
|
|
267
289
|
assert.equal(saved.tabs.length, 3);
|
|
290
|
+
assert.equal(saved.tabs[2]?.remoteUrl, "git@github.com:anthropic/anthropic-lab.git");
|
|
268
291
|
assert.equal(saved.tabs[2]?.resumeCommand, "claude --resume claude-456");
|
|
269
292
|
});
|
|
270
293
|
});
|
|
@@ -339,8 +362,14 @@ test("runWorkspaceCreate creates a local managed workspace and auto-assigns main
|
|
|
339
362
|
const { code, stdout } = await captureStdout(() =>
|
|
340
363
|
runWorkspaceCreate([
|
|
341
364
|
"main-cody-1",
|
|
365
|
+
"--machine-label",
|
|
366
|
+
"Mac Studio",
|
|
342
367
|
"--path",
|
|
343
368
|
"/Volumes/Code_2TB/code/orp",
|
|
369
|
+
"--remote-url",
|
|
370
|
+
"git@github.com:SproutSeeds/orp.git",
|
|
371
|
+
"--bootstrap-command",
|
|
372
|
+
"npm install",
|
|
344
373
|
"--resume-tool",
|
|
345
374
|
"claude",
|
|
346
375
|
"--resume-session-id",
|
|
@@ -357,7 +386,28 @@ test("runWorkspaceCreate creates a local managed workspace and auto-assigns main
|
|
|
357
386
|
assert.equal(payload.workspaceTitle, "main-cody-1");
|
|
358
387
|
assert.equal(payload.tabCount, 1);
|
|
359
388
|
assert.equal(saved.title, "main-cody-1");
|
|
389
|
+
assert.equal(saved.machine.machineLabel, "Mac Studio");
|
|
390
|
+
assert.equal(saved.tabs[0]?.remoteUrl, "git@github.com:SproutSeeds/orp.git");
|
|
391
|
+
assert.equal(saved.tabs[0]?.bootstrapCommand, "npm install");
|
|
360
392
|
assert.equal(saved.tabs[0]?.resumeCommand, "claude --resume claude-456");
|
|
361
393
|
assert.equal(slots.slots.main.title, "main-cody-1");
|
|
362
394
|
});
|
|
363
395
|
});
|
|
396
|
+
|
|
397
|
+
test("runWorkspaceCreate allows an empty local ledger before any tabs are added", async () => {
|
|
398
|
+
await withTempConfigHome(async () => {
|
|
399
|
+
const { code, stdout } = await captureStdout(() =>
|
|
400
|
+
runWorkspaceCreate([
|
|
401
|
+
"empty-ledger",
|
|
402
|
+
"--json",
|
|
403
|
+
]),
|
|
404
|
+
);
|
|
405
|
+
const payload = JSON.parse(stdout);
|
|
406
|
+
const saved = JSON.parse(await fs.readFile(payload.manifestPath, "utf8"));
|
|
407
|
+
|
|
408
|
+
assert.equal(code, 0);
|
|
409
|
+
assert.equal(payload.workspaceTitle, "empty-ledger");
|
|
410
|
+
assert.equal(payload.tabCount, 0);
|
|
411
|
+
assert.deepEqual(saved.tabs, []);
|
|
412
|
+
});
|
|
413
|
+
});
|
|
@@ -39,6 +39,11 @@ test("registerWorkspaceManifest and listTrackedWorkspaces expose tracked saved r
|
|
|
39
39
|
version: "1",
|
|
40
40
|
workspaceId: "orp-main",
|
|
41
41
|
title: "ORP Main",
|
|
42
|
+
machine: {
|
|
43
|
+
machineId: "mac-studio:darwin",
|
|
44
|
+
machineLabel: "Mac Studio",
|
|
45
|
+
platform: "darwin",
|
|
46
|
+
},
|
|
42
47
|
capture: {
|
|
43
48
|
sourceApp: "iTerm",
|
|
44
49
|
mode: "watch",
|
|
@@ -74,6 +79,9 @@ test("registerWorkspaceManifest and listTrackedWorkspaces expose tracked saved r
|
|
|
74
79
|
manifestPath: path.resolve(manifestPath),
|
|
75
80
|
workspaceId: "orp-main",
|
|
76
81
|
title: "ORP Main",
|
|
82
|
+
machineId: "mac-studio:darwin",
|
|
83
|
+
machineLabel: "Mac Studio",
|
|
84
|
+
platform: "darwin",
|
|
77
85
|
host: "local-macbook",
|
|
78
86
|
captureMode: "watch",
|
|
79
87
|
capturedAt: "2026-03-28T12:00:00.000Z",
|
|
@@ -100,6 +108,7 @@ test("registerWorkspaceManifest and listTrackedWorkspaces expose tracked saved r
|
|
|
100
108
|
const summary = summarizeTrackedWorkspaces(result);
|
|
101
109
|
assert.match(summary, /Local tracked workspaces: 1/);
|
|
102
110
|
assert.match(summary, /ORP Main \[orp-main\]/);
|
|
111
|
+
assert.match(summary, /Machine: Mac Studio \(darwin\)/);
|
|
103
112
|
assert.match(summary, /Saved resume sessions: 1/);
|
|
104
113
|
assert.match(summary, /web: claude resume claude-999/);
|
|
105
114
|
});
|
|
@@ -172,6 +181,9 @@ test("buildWorkspaceInventory merges hosted and local workspace state", () => {
|
|
|
172
181
|
manifestPath: "/Users/example/.config/orp/workspaces/idea-123-deadbeef.json",
|
|
173
182
|
workspaceId: "idea-123",
|
|
174
183
|
title: "Main Cody 1",
|
|
184
|
+
machineId: "mac-studio:darwin",
|
|
185
|
+
machineLabel: "Mac Studio",
|
|
186
|
+
platform: "darwin",
|
|
175
187
|
tabCount: 2,
|
|
176
188
|
codexSessionCount: 1,
|
|
177
189
|
updatedAt: "2026-03-30T12:00:00.000Z",
|
|
@@ -198,6 +210,11 @@ test("buildWorkspaceInventory merges hosted and local workspace state", () => {
|
|
|
198
210
|
linkedIdea: { ideaId: "ef86" },
|
|
199
211
|
metrics: { tabCount: 2 },
|
|
200
212
|
state: {
|
|
213
|
+
capture_context: {
|
|
214
|
+
machine_id: "mac-studio:darwin",
|
|
215
|
+
machine_label: "Mac Studio",
|
|
216
|
+
platform: "darwin",
|
|
217
|
+
},
|
|
201
218
|
tabs: [
|
|
202
219
|
{ project_root: "/Volumes/Code_2TB/code/orp", codex_session_id: "abc-123" },
|
|
203
220
|
{ project_root: "/Volumes/Code_2TB/code/orp-web-app" },
|
|
@@ -211,6 +228,7 @@ test("buildWorkspaceInventory merges hosted and local workspace state", () => {
|
|
|
211
228
|
|
|
212
229
|
assert.equal(result.workspaces.length, 2);
|
|
213
230
|
assert.equal(result.workspaces[0]?.workspaceId, "idea-123");
|
|
231
|
+
assert.equal(result.workspaces[0]?.machineLabel, "Mac Studio");
|
|
214
232
|
assert.equal(result.workspaces[0]?.availability, "hosted+local");
|
|
215
233
|
assert.equal(result.workspaces[0]?.syncStatus, "synced");
|
|
216
234
|
assert.equal(result.workspaces[1]?.workspaceId, "local-only");
|
|
@@ -220,6 +238,7 @@ test("buildWorkspaceInventory merges hosted and local workspace state", () => {
|
|
|
220
238
|
assert.match(summary, /Workspace inventory: 2/);
|
|
221
239
|
assert.match(summary, /Hosted available: 1/);
|
|
222
240
|
assert.match(summary, /Local available: 2/);
|
|
241
|
+
assert.match(summary, /Machine: Mac Studio \(darwin\)/);
|
|
223
242
|
assert.match(summary, /Sync: synced/);
|
|
224
243
|
});
|
|
225
244
|
|
|
@@ -50,11 +50,35 @@ test("buildWorkspaceTabsReport keeps duplicate titles unique and exposes generic
|
|
|
50
50
|
sourceType: "hosted-idea",
|
|
51
51
|
sourceLabel: "Workspace idea",
|
|
52
52
|
title: "Workspace idea",
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
53
|
+
workspaceManifest: {
|
|
54
|
+
version: "1",
|
|
55
|
+
workspaceId: "workspace-idea",
|
|
56
|
+
machine: {
|
|
57
|
+
machineId: "mac-studio:darwin",
|
|
58
|
+
machineLabel: "Mac Studio",
|
|
59
|
+
platform: "darwin",
|
|
60
|
+
},
|
|
61
|
+
tabs: [
|
|
62
|
+
{
|
|
63
|
+
path: "/Volumes/Code_2TB/code/collaboration",
|
|
64
|
+
title: "collaboration",
|
|
65
|
+
remoteUrl: "git@github.com:org/collaboration.git",
|
|
66
|
+
bootstrapCommand: "npm install",
|
|
67
|
+
resumeCommand: "codex resume abc-123",
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
path: "/Volumes/Code_2TB/code/anthropic-lab",
|
|
71
|
+
title: "anthropic-lab",
|
|
72
|
+
remoteUrl: "git@github.com:anthropic/anthropic-lab.git",
|
|
73
|
+
remoteBranch: "main",
|
|
74
|
+
resumeCommand: "claude resume claude-456",
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
path: "/Volumes/Code_2TB/code/collaboration",
|
|
78
|
+
},
|
|
79
|
+
],
|
|
80
|
+
},
|
|
81
|
+
notes: "",
|
|
58
82
|
});
|
|
59
83
|
|
|
60
84
|
const report = buildWorkspaceTabsReport(
|
|
@@ -66,9 +90,17 @@ test("buildWorkspaceTabsReport keeps duplicate titles unique and exposes generic
|
|
|
66
90
|
parsed,
|
|
67
91
|
);
|
|
68
92
|
|
|
69
|
-
assert.
|
|
93
|
+
assert.equal(report.workspaceId, "workspace-idea");
|
|
94
|
+
assert.equal(report.machine?.machineLabel, "Mac Studio");
|
|
70
95
|
assert.equal(report.tabCount, 3);
|
|
71
96
|
assert.equal(report.tabs[0]?.title, "collaboration");
|
|
97
|
+
assert.equal(report.tabs[0]?.remoteUrl, "git@github.com:org/collaboration.git");
|
|
98
|
+
assert.equal(report.tabs[0]?.bootstrapCommand, "npm install");
|
|
99
|
+
assert.equal(report.tabs[0]?.cloneCommand, "git clone 'git@github.com:org/collaboration.git' 'collaboration'");
|
|
100
|
+
assert.equal(
|
|
101
|
+
report.tabs[0]?.setupCommand,
|
|
102
|
+
"git clone 'git@github.com:org/collaboration.git' 'collaboration' && cd 'collaboration' && npm install",
|
|
103
|
+
);
|
|
72
104
|
assert.equal(report.tabs[0]?.resumeCommand, "codex resume abc-123");
|
|
73
105
|
assert.equal(
|
|
74
106
|
report.tabs[0]?.restartCommand,
|
|
@@ -77,6 +109,7 @@ test("buildWorkspaceTabsReport keeps duplicate titles unique and exposes generic
|
|
|
77
109
|
assert.equal(report.tabs[0]?.codexSessionId, "abc-123");
|
|
78
110
|
assert.equal(report.tabs[1]?.title, "anthropic-lab");
|
|
79
111
|
assert.equal(report.tabs[1]?.resumeCommand, "claude resume claude-456");
|
|
112
|
+
assert.equal(report.tabs[1]?.remoteBranch, "main");
|
|
80
113
|
assert.equal(
|
|
81
114
|
report.tabs[1]?.restartCommand,
|
|
82
115
|
"cd '/Volumes/Code_2TB/code/anthropic-lab' && claude resume claude-456",
|
|
@@ -96,10 +129,17 @@ test("runWorkspaceTabs prints JSON without launch commands", async () => {
|
|
|
96
129
|
version: "1",
|
|
97
130
|
workspaceId: "orp-main",
|
|
98
131
|
title: "ORP Main",
|
|
132
|
+
machine: {
|
|
133
|
+
machineId: "mac-studio:darwin",
|
|
134
|
+
machineLabel: "Mac Studio",
|
|
135
|
+
platform: "darwin",
|
|
136
|
+
},
|
|
99
137
|
tabs: [
|
|
100
138
|
{
|
|
101
139
|
title: "orp",
|
|
102
140
|
path: "/Volumes/Code_2TB/code/orp",
|
|
141
|
+
remoteUrl: "git@github.com:SproutSeeds/orp.git",
|
|
142
|
+
bootstrapCommand: "npm install",
|
|
103
143
|
resumeCommand: "claude resume claude-999",
|
|
104
144
|
resumeTool: "claude",
|
|
105
145
|
resumeSessionId: "claude-999",
|
|
@@ -121,8 +161,11 @@ test("runWorkspaceTabs prints JSON without launch commands", async () => {
|
|
|
121
161
|
|
|
122
162
|
assert.equal(code, 0);
|
|
123
163
|
assert.equal(parsed.workspaceId, "orp-main");
|
|
164
|
+
assert.equal(parsed.machine.machineLabel, "Mac Studio");
|
|
124
165
|
assert.equal(parsed.tabCount, 2);
|
|
125
166
|
assert.equal(parsed.tabs[0]?.title, "orp");
|
|
167
|
+
assert.equal(parsed.tabs[0]?.remoteUrl, "git@github.com:SproutSeeds/orp.git");
|
|
168
|
+
assert.equal(parsed.tabs[0]?.bootstrapCommand, "npm install");
|
|
126
169
|
assert.equal(parsed.tabs[0]?.resumeCommand, "claude resume claude-999");
|
|
127
170
|
assert.equal(parsed.tabs[0]?.restartCommand, "cd '/Volumes/Code_2TB/code/orp' && claude resume claude-999");
|
|
128
171
|
assert.equal(parsed.tabs[0]?.claudeSessionId, "claude-999");
|
|
@@ -142,6 +185,7 @@ test("buildWorkspaceTabsReport canonicalizes Claude resume commands from tool an
|
|
|
142
185
|
{
|
|
143
186
|
title: "anthropic-lab",
|
|
144
187
|
path: "/Volumes/Code_2TB/code/anthropic-lab",
|
|
188
|
+
remoteUrl: "git@github.com:anthropic/anthropic-lab.git",
|
|
145
189
|
resumeTool: "claude",
|
|
146
190
|
resumeSessionId: "claude-456",
|
|
147
191
|
},
|
|
@@ -160,6 +204,7 @@ test("buildWorkspaceTabsReport canonicalizes Claude resume commands from tool an
|
|
|
160
204
|
);
|
|
161
205
|
|
|
162
206
|
assert.equal(report.tabs[0]?.resumeCommand, "claude --resume claude-456");
|
|
207
|
+
assert.equal(report.tabs[0]?.cloneCommand, "git clone 'git@github.com:anthropic/anthropic-lab.git' 'anthropic-lab'");
|
|
163
208
|
assert.equal(
|
|
164
209
|
report.tabs[0]?.restartCommand,
|
|
165
210
|
"cd '/Volumes/Code_2TB/code/anthropic-lab' && claude --resume claude-456",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
3
|
"$id": "https://orp.dev/spec/v1/workspace.schema.json",
|
|
4
4
|
"title": "ORP Workspace Manifest",
|
|
5
|
-
"description": "Structured workspace
|
|
5
|
+
"description": "Structured workspace ledger manifest for ORP local and hosted workspace recovery across machines.",
|
|
6
6
|
"type": "object",
|
|
7
7
|
"required": ["version", "tabs"],
|
|
8
8
|
"properties": {
|
|
@@ -18,6 +18,28 @@
|
|
|
18
18
|
"type": "string",
|
|
19
19
|
"minLength": 1
|
|
20
20
|
},
|
|
21
|
+
"machine": {
|
|
22
|
+
"type": "object",
|
|
23
|
+
"properties": {
|
|
24
|
+
"machineId": {
|
|
25
|
+
"type": "string",
|
|
26
|
+
"minLength": 1
|
|
27
|
+
},
|
|
28
|
+
"machineLabel": {
|
|
29
|
+
"type": "string",
|
|
30
|
+
"minLength": 1
|
|
31
|
+
},
|
|
32
|
+
"platform": {
|
|
33
|
+
"type": "string",
|
|
34
|
+
"minLength": 1
|
|
35
|
+
},
|
|
36
|
+
"host": {
|
|
37
|
+
"type": "string",
|
|
38
|
+
"minLength": 1
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
"additionalProperties": false
|
|
42
|
+
},
|
|
21
43
|
"tmuxPrefix": {
|
|
22
44
|
"type": "string",
|
|
23
45
|
"minLength": 1
|
|
@@ -66,7 +88,7 @@
|
|
|
66
88
|
},
|
|
67
89
|
"tabs": {
|
|
68
90
|
"type": "array",
|
|
69
|
-
"minItems":
|
|
91
|
+
"minItems": 0,
|
|
70
92
|
"items": {
|
|
71
93
|
"type": "object",
|
|
72
94
|
"required": ["path"],
|
|
@@ -79,6 +101,18 @@
|
|
|
79
101
|
"type": "string",
|
|
80
102
|
"pattern": "^/"
|
|
81
103
|
},
|
|
104
|
+
"remoteUrl": {
|
|
105
|
+
"type": "string",
|
|
106
|
+
"minLength": 1
|
|
107
|
+
},
|
|
108
|
+
"remoteBranch": {
|
|
109
|
+
"type": "string",
|
|
110
|
+
"minLength": 1
|
|
111
|
+
},
|
|
112
|
+
"bootstrapCommand": {
|
|
113
|
+
"type": "string",
|
|
114
|
+
"minLength": 1
|
|
115
|
+
},
|
|
82
116
|
"resumeCommand": {
|
|
83
117
|
"type": "string",
|
|
84
118
|
"minLength": 1
|