open-research-protocol 0.4.14 → 0.4.16
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 +50 -0
- package/README.md +273 -144
- package/bin/orp.js +14 -1
- package/cli/orp.py +14846 -9925
- package/docs/AGENT_LOOP.md +13 -0
- package/docs/AGENT_MODES.md +79 -0
- package/docs/CANONICAL_CLI_BOUNDARY.md +15 -0
- package/docs/EXCHANGE.md +94 -0
- package/docs/LAUNCH_KIT.md +107 -0
- package/docs/ORP_HOSTED_WORKSPACE_CONTRACT.md +295 -0
- package/docs/ORP_PUBLIC_LAUNCH_CHECKLIST.md +5 -0
- package/docs/START_HERE.md +567 -0
- package/package.json +4 -2
- package/packages/lifeops-orp/README.md +67 -0
- package/packages/lifeops-orp/package.json +48 -0
- package/packages/lifeops-orp/src/index.d.ts +106 -0
- package/packages/lifeops-orp/src/index.js +7 -0
- package/packages/lifeops-orp/src/mapping.js +309 -0
- package/packages/lifeops-orp/src/workspace.js +108 -0
- package/packages/lifeops-orp/test/orp.test.js +187 -0
- package/packages/orp-workspace-launcher/README.md +82 -0
- package/packages/orp-workspace-launcher/package.json +39 -0
- package/packages/orp-workspace-launcher/src/commands.js +77 -0
- package/packages/orp-workspace-launcher/src/core-plan.js +506 -0
- package/packages/orp-workspace-launcher/src/hosted-state.js +208 -0
- package/packages/orp-workspace-launcher/src/index.js +82 -0
- package/packages/orp-workspace-launcher/src/ledger.js +745 -0
- package/packages/orp-workspace-launcher/src/list.js +488 -0
- package/packages/orp-workspace-launcher/src/orp-command.js +126 -0
- package/packages/orp-workspace-launcher/src/orp.js +912 -0
- package/packages/orp-workspace-launcher/src/registry.js +558 -0
- package/packages/orp-workspace-launcher/src/slot.js +188 -0
- package/packages/orp-workspace-launcher/src/sync.js +363 -0
- package/packages/orp-workspace-launcher/src/tabs.js +166 -0
- package/packages/orp-workspace-launcher/test/commands.test.js +164 -0
- package/packages/orp-workspace-launcher/test/core-plan.test.js +253 -0
- package/packages/orp-workspace-launcher/test/fixtures/smoke-notes.txt +2 -0
- package/packages/orp-workspace-launcher/test/fixtures/workspace-manifest.json +17 -0
- package/packages/orp-workspace-launcher/test/ledger.test.js +244 -0
- package/packages/orp-workspace-launcher/test/list.test.js +299 -0
- package/packages/orp-workspace-launcher/test/orp-command.test.js +44 -0
- package/packages/orp-workspace-launcher/test/orp.test.js +224 -0
- package/packages/orp-workspace-launcher/test/tabs.test.js +168 -0
- package/scripts/orp-kernel-agent-pilot.py +10 -1
- package/scripts/orp-kernel-agent-replication.py +10 -1
- package/scripts/orp-kernel-canonical-continuation.py +10 -1
- package/scripts/orp-kernel-continuation-pilot.py +10 -1
- package/scripts/render-terminal-demo.py +416 -0
- package/spec/v1/exchange-report.schema.json +105 -0
- package/spec/v1/hosted-workspace-event.schema.json +102 -0
- package/spec/v1/hosted-workspace.schema.json +332 -0
- package/spec/v1/workspace.schema.json +108 -0
|
@@ -0,0 +1,488 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import process from "node:process";
|
|
3
|
+
|
|
4
|
+
import { fetchHostedWorkspacesPayload } from "./orp.js";
|
|
5
|
+
import {
|
|
6
|
+
isManagedWorkspaceManifestPath,
|
|
7
|
+
listTrackedWorkspaces,
|
|
8
|
+
loadWorkspaceSlots,
|
|
9
|
+
normalizeWorkspaceSlotName,
|
|
10
|
+
} from "./registry.js";
|
|
11
|
+
|
|
12
|
+
function normalizeOptionalString(value) {
|
|
13
|
+
if (value == null) {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
const trimmed = String(value).trim();
|
|
17
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function slugify(value) {
|
|
21
|
+
const normalized = String(value || "")
|
|
22
|
+
.trim()
|
|
23
|
+
.toLowerCase()
|
|
24
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
25
|
+
.replace(/^-+|-+$/g, "");
|
|
26
|
+
return normalized || null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function countHostedResumeSessions(workspace) {
|
|
30
|
+
const tabs = Array.isArray(workspace?.state?.tabs)
|
|
31
|
+
? workspace.state.tabs
|
|
32
|
+
: Array.isArray(workspace?.tabs)
|
|
33
|
+
? workspace.tabs
|
|
34
|
+
: Array.isArray(workspace?.manifest?.tabs)
|
|
35
|
+
? workspace.manifest.tabs
|
|
36
|
+
: [];
|
|
37
|
+
return tabs.reduce((count, tab) => {
|
|
38
|
+
const sessionId = normalizeOptionalString(
|
|
39
|
+
tab?.resume_session_id ??
|
|
40
|
+
tab?.resumeSessionId ??
|
|
41
|
+
tab?.codex_session_id ??
|
|
42
|
+
tab?.codexSessionId ??
|
|
43
|
+
tab?.sessionId,
|
|
44
|
+
);
|
|
45
|
+
return sessionId ? count + 1 : count;
|
|
46
|
+
}, 0);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function buildHostedWorkspaceSummary(workspace) {
|
|
50
|
+
const linkedIdea = workspace?.linked_idea || workspace?.linkedIdea || {};
|
|
51
|
+
const metrics = workspace?.metrics || {};
|
|
52
|
+
return {
|
|
53
|
+
workspaceId: normalizeOptionalString(workspace?.workspace_id ?? workspace?.id),
|
|
54
|
+
title: normalizeOptionalString(workspace?.title),
|
|
55
|
+
visibility: normalizeOptionalString(workspace?.visibility),
|
|
56
|
+
ideaId: normalizeOptionalString(linkedIdea.idea_id ?? linkedIdea.ideaId),
|
|
57
|
+
tabCount:
|
|
58
|
+
Number.isInteger(metrics.tab_count) ? metrics.tab_count : Number.isInteger(metrics.tabCount) ? metrics.tabCount : 0,
|
|
59
|
+
codexSessionCount: countHostedResumeSessions(workspace),
|
|
60
|
+
updatedAt: normalizeOptionalString(workspace?.updated_at_utc ?? workspace?.updatedAt),
|
|
61
|
+
source: normalizeOptionalString(workspace?.source_kind) || "hosted",
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function buildLocalWorkspaceSummary(workspace) {
|
|
66
|
+
return {
|
|
67
|
+
workspaceId: normalizeOptionalString(workspace?.workspaceId),
|
|
68
|
+
title: normalizeOptionalString(workspace?.title),
|
|
69
|
+
status: normalizeOptionalString(workspace?.status) || "ok",
|
|
70
|
+
manifestPath: normalizeOptionalString(workspace?.manifestPath),
|
|
71
|
+
tabCount: Number.isInteger(workspace?.tabCount) ? workspace.tabCount : 0,
|
|
72
|
+
codexSessionCount: Number.isInteger(workspace?.codexSessionCount) ? workspace.codexSessionCount : 0,
|
|
73
|
+
resumeSessions: Array.isArray(workspace?.resumeSessions) ? workspace.resumeSessions : [],
|
|
74
|
+
updatedAt: normalizeOptionalString(workspace?.updatedAt),
|
|
75
|
+
managedCache: isManagedWorkspaceManifestPath(workspace?.manifestPath),
|
|
76
|
+
host: normalizeOptionalString(workspace?.host),
|
|
77
|
+
captureMode: normalizeOptionalString(workspace?.captureMode),
|
|
78
|
+
error: normalizeOptionalString(workspace?.error),
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function mergeKeyForWorkspace(summary) {
|
|
83
|
+
const workspaceId = normalizeOptionalString(summary?.workspaceId);
|
|
84
|
+
if (workspaceId) {
|
|
85
|
+
return `id:${workspaceId}`;
|
|
86
|
+
}
|
|
87
|
+
const titleSlug = slugify(summary?.title);
|
|
88
|
+
if (titleSlug) {
|
|
89
|
+
return `title:${titleSlug}`;
|
|
90
|
+
}
|
|
91
|
+
const manifestPath = normalizeOptionalString(summary?.manifestPath);
|
|
92
|
+
if (manifestPath) {
|
|
93
|
+
return `path:${path.resolve(manifestPath)}`;
|
|
94
|
+
}
|
|
95
|
+
return `unknown:${Math.random().toString(36).slice(2, 10)}`;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function timestampsSortValue(record) {
|
|
99
|
+
return Date.parse(record?.updatedAt || "") || 0;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function preferNewerSummary(current, candidate) {
|
|
103
|
+
if (!current) {
|
|
104
|
+
return candidate;
|
|
105
|
+
}
|
|
106
|
+
return timestampsSortValue(candidate) > timestampsSortValue(current) ? candidate : current;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function inferSyncStatus(entry) {
|
|
110
|
+
if (entry.hosted && entry.local) {
|
|
111
|
+
if (entry.local.status !== "ok") {
|
|
112
|
+
return entry.local.status;
|
|
113
|
+
}
|
|
114
|
+
const matches =
|
|
115
|
+
normalizeOptionalString(entry.hosted.workspaceId) === normalizeOptionalString(entry.local.workspaceId) &&
|
|
116
|
+
normalizeOptionalString(entry.hosted.title) === normalizeOptionalString(entry.local.title) &&
|
|
117
|
+
Number(entry.hosted.tabCount || 0) === Number(entry.local.tabCount || 0) &&
|
|
118
|
+
Number(entry.hosted.codexSessionCount || 0) === Number(entry.local.codexSessionCount || 0);
|
|
119
|
+
if (matches) {
|
|
120
|
+
return "synced";
|
|
121
|
+
}
|
|
122
|
+
return entry.local.managedCache ? "needs-sync" : "linked";
|
|
123
|
+
}
|
|
124
|
+
if (entry.hosted) {
|
|
125
|
+
return "hosted-only";
|
|
126
|
+
}
|
|
127
|
+
if (entry.local?.managedCache) {
|
|
128
|
+
return "local-cache-only";
|
|
129
|
+
}
|
|
130
|
+
return "local-only";
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function workspaceMatchesSlot(workspace, slot) {
|
|
134
|
+
if (!workspace || !slot) {
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const slotKind = normalizeOptionalString(slot.kind);
|
|
139
|
+
if (slotKind === "workspace-file") {
|
|
140
|
+
return (
|
|
141
|
+
normalizeOptionalString(workspace.local?.manifestPath) === normalizeOptionalString(slot.manifestPath) ||
|
|
142
|
+
normalizeOptionalString(workspace.workspaceId) === normalizeOptionalString(slot.workspaceId)
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (slotKind === "hosted-idea") {
|
|
147
|
+
return (
|
|
148
|
+
normalizeOptionalString(workspace.hosted?.ideaId) === normalizeOptionalString(slot.ideaId) ||
|
|
149
|
+
normalizeOptionalString(workspace.workspaceId) === normalizeOptionalString(slot.workspaceId)
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (slotKind === "hosted-workspace") {
|
|
154
|
+
const slotWorkspaceId = normalizeOptionalString(slot.hostedWorkspaceId) || normalizeOptionalString(slot.workspaceId);
|
|
155
|
+
return normalizeOptionalString(workspace.workspaceId) === slotWorkspaceId;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export function applyWorkspaceSlotsToInventory(result, slots = {}) {
|
|
162
|
+
const normalizedSlots = {};
|
|
163
|
+
for (const [slotName, slot] of Object.entries(slots || {})) {
|
|
164
|
+
const normalizedSlotName = normalizeWorkspaceSlotName(slotName);
|
|
165
|
+
if (normalizedSlotName && slot && typeof slot === "object" && !Array.isArray(slot)) {
|
|
166
|
+
normalizedSlots[normalizedSlotName] = slot;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const workspaces = Array.isArray(result?.workspaces) ? result.workspaces : [];
|
|
171
|
+
const annotatedWorkspaces = workspaces.map((workspace) => {
|
|
172
|
+
const slotNames = Object.entries(normalizedSlots)
|
|
173
|
+
.filter(([, slot]) => workspaceMatchesSlot(workspace, slot))
|
|
174
|
+
.map(([slotName]) => slotName)
|
|
175
|
+
.sort();
|
|
176
|
+
return {
|
|
177
|
+
...workspace,
|
|
178
|
+
slots: slotNames,
|
|
179
|
+
};
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
const hasExplicitMain = annotatedWorkspaces.some((workspace) => workspace.slots.includes("main"));
|
|
183
|
+
if (!hasExplicitMain && annotatedWorkspaces.length === 1) {
|
|
184
|
+
annotatedWorkspaces[0] = {
|
|
185
|
+
...annotatedWorkspaces[0],
|
|
186
|
+
slots: [...annotatedWorkspaces[0].slots, "main"],
|
|
187
|
+
implicitMain: true,
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return {
|
|
192
|
+
...result,
|
|
193
|
+
slots: normalizedSlots,
|
|
194
|
+
workspaces: annotatedWorkspaces,
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export function buildWorkspaceInventory({ localResult, hostedResult, hostedError = null }) {
|
|
199
|
+
const merged = new Map();
|
|
200
|
+
const localWorkspaces = Array.isArray(localResult?.workspaces) ? localResult.workspaces : [];
|
|
201
|
+
const hostedWorkspaces = Array.isArray(hostedResult?.workspaces) ? hostedResult.workspaces : [];
|
|
202
|
+
|
|
203
|
+
for (const workspace of hostedWorkspaces) {
|
|
204
|
+
const hosted = buildHostedWorkspaceSummary(workspace);
|
|
205
|
+
const key = mergeKeyForWorkspace(hosted);
|
|
206
|
+
const current = merged.get(key) || {};
|
|
207
|
+
merged.set(key, {
|
|
208
|
+
...current,
|
|
209
|
+
workspaceId: hosted.workspaceId || current.workspaceId || null,
|
|
210
|
+
title: hosted.title || current.title || hosted.workspaceId || null,
|
|
211
|
+
hosted: preferNewerSummary(current.hosted, hosted),
|
|
212
|
+
sources: {
|
|
213
|
+
hosted: true,
|
|
214
|
+
local: Boolean(current.local),
|
|
215
|
+
},
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
for (const workspace of localWorkspaces) {
|
|
220
|
+
const local = buildLocalWorkspaceSummary(workspace);
|
|
221
|
+
const key = mergeKeyForWorkspace(local);
|
|
222
|
+
const current = merged.get(key) || {};
|
|
223
|
+
const preferredLocal = preferNewerSummary(current.local, local);
|
|
224
|
+
merged.set(key, {
|
|
225
|
+
...current,
|
|
226
|
+
workspaceId: preferredLocal.workspaceId || current.workspaceId || null,
|
|
227
|
+
title:
|
|
228
|
+
preferredLocal.title ||
|
|
229
|
+
current.title ||
|
|
230
|
+
preferredLocal.workspaceId ||
|
|
231
|
+
path.basename(preferredLocal.manifestPath || ""),
|
|
232
|
+
local: preferredLocal,
|
|
233
|
+
sources: {
|
|
234
|
+
hosted: Boolean(current.hosted),
|
|
235
|
+
local: true,
|
|
236
|
+
},
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const workspaces = [...merged.values()]
|
|
241
|
+
.map((entry) => ({
|
|
242
|
+
workspaceId: entry.workspaceId,
|
|
243
|
+
title: entry.title,
|
|
244
|
+
availability:
|
|
245
|
+
entry.hosted && entry.local ? "hosted+local" : entry.hosted ? "hosted" : "local",
|
|
246
|
+
syncStatus: inferSyncStatus(entry),
|
|
247
|
+
sources: entry.sources,
|
|
248
|
+
hosted: entry.hosted || null,
|
|
249
|
+
local: entry.local || null,
|
|
250
|
+
}))
|
|
251
|
+
.sort((left, right) => {
|
|
252
|
+
const dateDelta = timestampsSortValue(right.hosted || right.local) - timestampsSortValue(left.hosted || left.local);
|
|
253
|
+
if (dateDelta !== 0) {
|
|
254
|
+
return dateDelta;
|
|
255
|
+
}
|
|
256
|
+
return String(left.title || left.workspaceId || "").localeCompare(String(right.title || right.workspaceId || ""));
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
return {
|
|
260
|
+
registryPath: localResult?.registryPath || null,
|
|
261
|
+
hostedSource: normalizeOptionalString(hostedResult?.source) || null,
|
|
262
|
+
hostedError: normalizeOptionalString(hostedError),
|
|
263
|
+
workspaces,
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
export function summarizeWorkspaceInventory(result) {
|
|
268
|
+
const workspaces = Array.isArray(result?.workspaces) ? result.workspaces : [];
|
|
269
|
+
const hostedCount = workspaces.filter((workspace) => workspace.sources?.hosted).length;
|
|
270
|
+
const localCount = workspaces.filter((workspace) => workspace.sources?.local).length;
|
|
271
|
+
const syncedCount = workspaces.filter((workspace) => workspace.syncStatus === "synced").length;
|
|
272
|
+
const slotNames = Object.keys(result?.slots || {}).filter(Boolean).sort();
|
|
273
|
+
|
|
274
|
+
const lines = [
|
|
275
|
+
`Workspace inventory: ${workspaces.length}`,
|
|
276
|
+
`Hosted available: ${hostedCount}`,
|
|
277
|
+
`Local available: ${localCount}`,
|
|
278
|
+
];
|
|
279
|
+
if (result?.registryPath) {
|
|
280
|
+
lines.push(`Local registry: ${result.registryPath}`);
|
|
281
|
+
}
|
|
282
|
+
if (result?.hostedSource) {
|
|
283
|
+
lines.push(`Hosted source: ${result.hostedSource}`);
|
|
284
|
+
}
|
|
285
|
+
lines.push(`Synced pairs: ${syncedCount}`);
|
|
286
|
+
if (slotNames.length > 0) {
|
|
287
|
+
lines.push(`Named slots: ${slotNames.join(", ")}`);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
if (result?.hostedError) {
|
|
291
|
+
lines.push("");
|
|
292
|
+
lines.push(`Hosted unavailable: ${result.hostedError}`);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
if (workspaces.length === 0) {
|
|
296
|
+
lines.push("");
|
|
297
|
+
lines.push("No workspaces yet.");
|
|
298
|
+
lines.push("Start one locally without an account or sync a hosted workspace later.");
|
|
299
|
+
lines.push(" orp workspace create main-cody-1");
|
|
300
|
+
return lines.join("\n");
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
workspaces.forEach((workspace, index) => {
|
|
304
|
+
lines.push("");
|
|
305
|
+
lines.push(`[${index + 1}] ${formatWorkspaceHeading(workspace)}`);
|
|
306
|
+
if (workspace.slots?.length) {
|
|
307
|
+
lines.push(
|
|
308
|
+
`Slots: ${workspace.slots.join(", ")}${workspace.implicitMain ? " (main inferred because this is the only workspace)" : ""}`,
|
|
309
|
+
);
|
|
310
|
+
}
|
|
311
|
+
lines.push(`Availability: ${workspace.availability}`);
|
|
312
|
+
lines.push(`Sync: ${workspace.syncStatus}`);
|
|
313
|
+
if (workspace.hosted) {
|
|
314
|
+
lines.push(
|
|
315
|
+
`Hosted: ORP${workspace.hosted.ideaId ? ` (idea ${workspace.hosted.ideaId})` : ""}, tabs ${workspace.hosted.tabCount || 0}, updated ${workspace.hosted.updatedAt || "unknown"}`,
|
|
316
|
+
);
|
|
317
|
+
}
|
|
318
|
+
if (workspace.local) {
|
|
319
|
+
lines.push(
|
|
320
|
+
`Local: ${workspace.local.manifestPath}${workspace.local.managedCache ? " [managed cache]" : ""}`,
|
|
321
|
+
);
|
|
322
|
+
lines.push(`Local status: ${workspace.local.status}, tabs ${workspace.local.tabCount || 0}`);
|
|
323
|
+
}
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
return lines.join("\n");
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
export function parseWorkspaceListArgs(argv = []) {
|
|
330
|
+
const options = {
|
|
331
|
+
json: false,
|
|
332
|
+
};
|
|
333
|
+
|
|
334
|
+
for (const arg of argv) {
|
|
335
|
+
if (arg === "-h" || arg === "--help") {
|
|
336
|
+
options.help = true;
|
|
337
|
+
continue;
|
|
338
|
+
}
|
|
339
|
+
if (arg === "--json") {
|
|
340
|
+
options.json = true;
|
|
341
|
+
continue;
|
|
342
|
+
}
|
|
343
|
+
throw new Error(`unexpected argument: ${arg}`);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
return options;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
function formatWorkspaceHeading(workspace) {
|
|
350
|
+
const title = normalizeOptionalString(workspace.title);
|
|
351
|
+
const workspaceId = normalizeOptionalString(workspace.workspaceId);
|
|
352
|
+
if (title && workspaceId && title !== workspaceId) {
|
|
353
|
+
return `${title} [${workspaceId}]`;
|
|
354
|
+
}
|
|
355
|
+
if (title) {
|
|
356
|
+
return title;
|
|
357
|
+
}
|
|
358
|
+
if (workspaceId) {
|
|
359
|
+
return workspaceId;
|
|
360
|
+
}
|
|
361
|
+
return path.basename(workspace.manifestPath);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
function summarizeResumeSessions(workspace) {
|
|
365
|
+
const sessions = Array.isArray(workspace.resumeSessions)
|
|
366
|
+
? workspace.resumeSessions
|
|
367
|
+
: Array.isArray(workspace.codexSessions)
|
|
368
|
+
? workspace.codexSessions
|
|
369
|
+
: [];
|
|
370
|
+
const lines = [`Saved resume sessions: ${workspace.codexSessionCount || 0}`];
|
|
371
|
+
for (const session of sessions.slice(0, 5)) {
|
|
372
|
+
const label = normalizeOptionalString(session.title) || path.basename(session.path || "") || "(untitled tab)";
|
|
373
|
+
lines.push(` ${label}: ${normalizeOptionalString(session.resumeCommand) || normalizeOptionalString(session.codexSessionId) || "(unknown)"}`);
|
|
374
|
+
}
|
|
375
|
+
if (sessions.length > 5) {
|
|
376
|
+
lines.push(` ... ${sessions.length - 5} more`);
|
|
377
|
+
}
|
|
378
|
+
return lines;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
export function summarizeTrackedWorkspaces(result) {
|
|
382
|
+
if (!result.workspaces || result.workspaces.length === 0) {
|
|
383
|
+
return [
|
|
384
|
+
"No local tracked workspaces yet.",
|
|
385
|
+
`Registry: ${result.registryPath}`,
|
|
386
|
+
"",
|
|
387
|
+
"Create one with:",
|
|
388
|
+
" orp workspace create main-cody-1",
|
|
389
|
+
"",
|
|
390
|
+
"For the combined hosted + local inventory, use:",
|
|
391
|
+
" orp workspace list",
|
|
392
|
+
].join("\n");
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
const lines = [`Local tracked workspaces: ${result.workspaces.length}`, `Registry: ${result.registryPath}`];
|
|
396
|
+
|
|
397
|
+
result.workspaces.forEach((workspace, index) => {
|
|
398
|
+
lines.push("");
|
|
399
|
+
lines.push(`[${index + 1}] ${formatWorkspaceHeading(workspace)}`);
|
|
400
|
+
lines.push(`Status: ${workspace.status}`);
|
|
401
|
+
lines.push(`File: ${workspace.manifestPath}`);
|
|
402
|
+
lines.push(`Saved tabs: ${workspace.tabCount || 0}`);
|
|
403
|
+
|
|
404
|
+
if (workspace.captureMode) {
|
|
405
|
+
lines.push(`Capture mode: ${workspace.captureMode}`);
|
|
406
|
+
}
|
|
407
|
+
if (workspace.host) {
|
|
408
|
+
lines.push(`Host: ${workspace.host}`);
|
|
409
|
+
}
|
|
410
|
+
if (workspace.capturedAt) {
|
|
411
|
+
lines.push(`Captured at: ${workspace.capturedAt}`);
|
|
412
|
+
}
|
|
413
|
+
if (workspace.trackingStartedAt) {
|
|
414
|
+
lines.push(`Tracking since: ${workspace.trackingStartedAt}`);
|
|
415
|
+
}
|
|
416
|
+
if (workspace.windowIndex != null || workspace.windowId != null) {
|
|
417
|
+
lines.push(
|
|
418
|
+
`Window: #${workspace.windowIndex != null ? workspace.windowIndex : "?"}${workspace.windowId != null ? ` (id ${workspace.windowId})` : ""}`,
|
|
419
|
+
);
|
|
420
|
+
}
|
|
421
|
+
if (workspace.updatedAt) {
|
|
422
|
+
lines.push(`Registry updated: ${workspace.updatedAt}`);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
lines.push(...summarizeResumeSessions(workspace));
|
|
426
|
+
|
|
427
|
+
if (workspace.error) {
|
|
428
|
+
lines.push(`Error: ${workspace.error}`);
|
|
429
|
+
}
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
return lines.join("\n");
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
function printWorkspaceListHelp() {
|
|
436
|
+
console.log(`ORP workspace list
|
|
437
|
+
|
|
438
|
+
Usage:
|
|
439
|
+
orp workspace list [--json]
|
|
440
|
+
|
|
441
|
+
Options:
|
|
442
|
+
--json Print tracked workspace metadata as JSON
|
|
443
|
+
-h, --help Show this help text
|
|
444
|
+
|
|
445
|
+
Notes:
|
|
446
|
+
- This shows one merged inventory of hosted ORP workspaces and local manifests on this Mac.
|
|
447
|
+
- Hosted workspaces are labeled separately from local manifests.
|
|
448
|
+
- Syncing or editing a hosted workspace stores a managed local cache so it can show up on both sides.
|
|
449
|
+
- Slot labels show which workspace is currently assigned as \`main\` or \`offhand\`.
|
|
450
|
+
`);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
export async function runWorkspaceList(argv = process.argv.slice(2)) {
|
|
454
|
+
const options = parseWorkspaceListArgs(argv);
|
|
455
|
+
if (options.help) {
|
|
456
|
+
printWorkspaceListHelp();
|
|
457
|
+
return 0;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
const localResult = await listTrackedWorkspaces();
|
|
461
|
+
let hostedResult = { source: null, workspaces: [] };
|
|
462
|
+
let hostedError = null;
|
|
463
|
+
try {
|
|
464
|
+
hostedResult = await fetchHostedWorkspacesPayload();
|
|
465
|
+
} catch (error) {
|
|
466
|
+
hostedError = error instanceof Error ? error.message : String(error);
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
let result = buildWorkspaceInventory({
|
|
470
|
+
localResult,
|
|
471
|
+
hostedResult,
|
|
472
|
+
hostedError,
|
|
473
|
+
});
|
|
474
|
+
try {
|
|
475
|
+
const slotsResult = await loadWorkspaceSlots();
|
|
476
|
+
result = applyWorkspaceSlotsToInventory(result, slotsResult.slots);
|
|
477
|
+
} catch {
|
|
478
|
+
result = applyWorkspaceSlotsToInventory(result, {});
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
if (options.json) {
|
|
482
|
+
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
483
|
+
return 0;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
process.stdout.write(`${summarizeWorkspaceInventory(result)}\n`);
|
|
487
|
+
return 0;
|
|
488
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { runWorkspaceCommands } from "./commands.js";
|
|
2
|
+
import { runWorkspaceAddTab, runWorkspaceCreate, runWorkspaceRemoveTab } from "./ledger.js";
|
|
3
|
+
import { runWorkspaceList } from "./list.js";
|
|
4
|
+
import { runWorkspaceSlot } from "./slot.js";
|
|
5
|
+
import { runWorkspaceSync } from "./sync.js";
|
|
6
|
+
import { runWorkspaceTabs } from "./tabs.js";
|
|
7
|
+
|
|
8
|
+
function printWorkspaceHelp() {
|
|
9
|
+
console.log(`ORP workspace
|
|
10
|
+
|
|
11
|
+
Usage:
|
|
12
|
+
orp workspace create <title-slug> [--workspace-file <path>] [--slot <main|offhand>] [--path <absolute-path>] [--resume-command <text> | --resume-tool <codex|claude> --resume-session-id <id>] [--json]
|
|
13
|
+
orp workspace ledger <name-or-id> [--json]
|
|
14
|
+
orp workspace ledger add <name-or-id> --path <absolute-path> [--title <title>] [--resume-command <text> | --resume-tool <codex|claude> --resume-session-id <id>] [--json]
|
|
15
|
+
orp workspace ledger remove <name-or-id> (--index <n> | --path <absolute-path> | --title <title> | --resume-session-id <id> | --resume-command <text>) [--all] [--json]
|
|
16
|
+
orp workspace tabs <name-or-id> [--json]
|
|
17
|
+
orp workspace tabs --hosted-workspace-id <workspace-id> [--json]
|
|
18
|
+
orp workspace tabs --notes-file <path> [--json]
|
|
19
|
+
orp workspace tabs --workspace-file <path> [--json]
|
|
20
|
+
orp workspace add-tab <name-or-id> --path <absolute-path> [--title <title>] [--resume-command <text> | --resume-tool <codex|claude> --resume-session-id <id>] [--json]
|
|
21
|
+
orp workspace remove-tab <name-or-id> (--index <n> | --path <absolute-path> | --title <title> | --resume-session-id <id> | --resume-command <text>) [--all] [--json]
|
|
22
|
+
orp workspace list [--json]
|
|
23
|
+
orp workspace slot <list|set|clear> ...
|
|
24
|
+
orp workspace sync <name-or-id> [--workspace-file <path> | --notes-file <path>] [--dry-run] [--json]
|
|
25
|
+
orp workspace -h
|
|
26
|
+
|
|
27
|
+
Commands:
|
|
28
|
+
create Create a local workspace ledger so ORP works without a hosted account
|
|
29
|
+
ledger Terminal-native saved workspace ledger flow: inspect, add, and remove saved paths plus resume commands
|
|
30
|
+
tabs List the saved tabs inside a workspace with copyable resume/recovery lines
|
|
31
|
+
add-tab Add a saved tab/path/session to the workspace ledger directly
|
|
32
|
+
remove-tab Remove one or more saved tabs from the workspace ledger directly
|
|
33
|
+
list List one merged inventory of hosted ORP workspaces and local manifests
|
|
34
|
+
slot Assign and inspect named workspace slots like main and offhand
|
|
35
|
+
sync Post a CLI-authored workspace manifest back to the hosted ORP idea
|
|
36
|
+
|
|
37
|
+
Notes:
|
|
38
|
+
- Local-only usage works: create a workspace with \`orp workspace create <title-slug>\`, then use \`orp workspace add-tab ...\`, \`orp workspace tabs ...\`, and \`orp workspace remove-tab ...\` without authenticating.
|
|
39
|
+
- The ledger-first flow is: \`orp workspace ledger <workspace>\`, \`orp workspace ledger add ...\`, \`orp workspace ledger remove ...\`, and \`orp workspace tabs <workspace>\`.
|
|
40
|
+
- Use \`orp workspace list\` for the combined hosted + local workspace inventory.
|
|
41
|
+
- Use \`orp workspace tabs <workspace>\` when you want saved paths plus copyable \`cd ... && codex resume ...\` / \`claude --resume ...\` recovery lines.
|
|
42
|
+
- Use \`orp workspace add-tab ...\` and \`orp workspace remove-tab ...\` when you want to edit the saved workspace ledger explicitly from Terminal.app or any other shell.
|
|
43
|
+
- \`main\` and \`offhand\` are reserved slot selectors; use \`orp workspace slot set ...\` to assign them.
|
|
44
|
+
- Syncing or editing a hosted workspace writes a managed local cache on this Mac.
|
|
45
|
+
- \`<name-or-id>\` can be a saved workspace title, workspace id, idea id, or local tracked workspace title/id.
|
|
46
|
+
|
|
47
|
+
Examples:
|
|
48
|
+
orp workspace create main-cody-1
|
|
49
|
+
orp workspace create main-cody-1 --slot main
|
|
50
|
+
orp workspace ledger main
|
|
51
|
+
orp workspace ledger add main --path /Volumes/Code_2TB/code/new-project --resume-command "codex resume 019d..."
|
|
52
|
+
orp workspace ledger remove main --title frg-site
|
|
53
|
+
orp workspace tabs main-cody-1
|
|
54
|
+
orp workspace add-tab main --path /Volumes/Code_2TB/code/new-project --resume-command "codex resume 019d..."
|
|
55
|
+
orp workspace remove-tab main --path /Volumes/Code_2TB/code/frg-site --resume-session-id 019d348d-5031-78e1-9840-a66deaac33ae
|
|
56
|
+
orp workspace slot set main main-cody-1
|
|
57
|
+
orp workspace slot set offhand research-lab
|
|
58
|
+
orp workspace slot list
|
|
59
|
+
orp workspace tabs --hosted-workspace-id ws_orp_main
|
|
60
|
+
orp workspace list
|
|
61
|
+
orp workspace sync main --workspace-file ./workspace.json
|
|
62
|
+
`);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export async function runOrpWorkspaceCommand(argv = []) {
|
|
66
|
+
const [subcommand, ...rest] = argv;
|
|
67
|
+
|
|
68
|
+
if (!subcommand || subcommand === "-h" || subcommand === "--help" || subcommand === "help") {
|
|
69
|
+
printWorkspaceHelp();
|
|
70
|
+
return 0;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (subcommand === "ledger") {
|
|
74
|
+
const [ledgerSubcommand, ...ledgerRest] = rest;
|
|
75
|
+
if (!ledgerSubcommand || ledgerSubcommand === "show") {
|
|
76
|
+
return runWorkspaceTabs(ledgerRest);
|
|
77
|
+
}
|
|
78
|
+
if (ledgerSubcommand === "-h" || ledgerSubcommand === "--help") {
|
|
79
|
+
return runWorkspaceTabs(["-h"]);
|
|
80
|
+
}
|
|
81
|
+
if (ledgerSubcommand === "add") {
|
|
82
|
+
return runWorkspaceAddTab(ledgerRest);
|
|
83
|
+
}
|
|
84
|
+
if (ledgerSubcommand === "remove") {
|
|
85
|
+
return runWorkspaceRemoveTab(ledgerRest);
|
|
86
|
+
}
|
|
87
|
+
if (ledgerSubcommand === "commands") {
|
|
88
|
+
return runWorkspaceTabs(ledgerRest);
|
|
89
|
+
}
|
|
90
|
+
return runWorkspaceTabs(rest);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (subcommand === "create") {
|
|
94
|
+
return runWorkspaceCreate(rest);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (subcommand === "tabs") {
|
|
98
|
+
return runWorkspaceTabs(rest);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (subcommand === "commands") {
|
|
102
|
+
return runWorkspaceTabs(rest);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (subcommand === "add-tab") {
|
|
106
|
+
return runWorkspaceAddTab(rest);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (subcommand === "remove-tab") {
|
|
110
|
+
return runWorkspaceRemoveTab(rest);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (subcommand === "list") {
|
|
114
|
+
return runWorkspaceList(rest);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (subcommand === "slot") {
|
|
118
|
+
return runWorkspaceSlot(rest);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (subcommand === "sync") {
|
|
122
|
+
return runWorkspaceSync(rest);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
throw new Error(`unknown workspace subcommand: ${subcommand}`);
|
|
126
|
+
}
|