open-research-protocol 0.4.34 → 0.4.36
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 +34 -0
- package/cli/__pycache__/orp.cpython-311.pyc +0 -0
- package/docs/ORP_HOSTED_WORKSPACE_CONTRACT.md +39 -0
- package/package.json +1 -1
- package/packages/orp-workspace-launcher/src/core-plan.js +32 -0
- package/packages/orp-workspace-launcher/src/hosted-state.js +41 -9
- package/packages/orp-workspace-launcher/src/index.js +6 -0
- package/packages/orp-workspace-launcher/src/local-inventory.js +681 -0
- package/packages/orp-workspace-launcher/src/orp.js +23 -0
- package/packages/orp-workspace-launcher/src/registry.js +24 -11
- package/packages/orp-workspace-launcher/src/sync.js +234 -13
- package/packages/orp-workspace-launcher/test/hosted-state.test.js +53 -0
- package/packages/orp-workspace-launcher/test/local-inventory.test.js +126 -0
- package/packages/orp-workspace-launcher/test/orp.test.js +19 -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
|
@@ -218,6 +218,22 @@ export function findHostedWorkspaceLinkedToIdea(workspaces = [], ideaId) {
|
|
|
218
218
|
);
|
|
219
219
|
}
|
|
220
220
|
|
|
221
|
+
export function findHostedWorkspaceByWorkspaceId(workspaces = [], workspaceId) {
|
|
222
|
+
const targetWorkspaceId = normalizeOptionalString(workspaceId);
|
|
223
|
+
if (!targetWorkspaceId || !Array.isArray(workspaces)) {
|
|
224
|
+
return null;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return (
|
|
228
|
+
workspaces.find((workspace) => {
|
|
229
|
+
if (!workspace || typeof workspace !== "object" || Array.isArray(workspace)) {
|
|
230
|
+
return false;
|
|
231
|
+
}
|
|
232
|
+
return normalizeOptionalString(workspace.workspace_id ?? workspace.id) === targetWorkspaceId;
|
|
233
|
+
}) || null
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
|
|
221
237
|
export async function findHostedWorkspaceByLinkedIdea(ideaId, options = {}) {
|
|
222
238
|
const payload = await fetchHostedWorkspacesPayload(options);
|
|
223
239
|
return findHostedWorkspaceLinkedToIdea(payload.workspaces, ideaId);
|
|
@@ -742,6 +758,13 @@ export function buildWorkspaceManifestFromHostedWorkspacePayload(payload) {
|
|
|
742
758
|
resumeSessionId: getTextValue(tab, "resume_session_id", "resumeSessionId"),
|
|
743
759
|
codexSessionId: getTextValue(tab, "codex_session_id", "codexSessionId"),
|
|
744
760
|
tmuxSessionName: getTextValue(tab, "tmux_session_name", "tmuxSessionName"),
|
|
761
|
+
linkedIdeaId: getTextValue(tab, "linked_idea_id", "linkedIdeaId"),
|
|
762
|
+
linkedFeatureId: getTextValue(tab, "linked_feature_id", "linkedFeatureId"),
|
|
763
|
+
plan: getObjectValue(tab, "plan") || undefined,
|
|
764
|
+
tasks: getArrayValue(tab, "tasks").length > 0 ? getArrayValue(tab, "tasks") : undefined,
|
|
765
|
+
lastActivityAt: getTextValue(tab, "last_activity_at_utc", "lastActivityAtUtc"),
|
|
766
|
+
lastSyncedAt: getTextValue(tab, "last_synced_at_utc", "lastSyncedAtUtc"),
|
|
767
|
+
syncSource: getTextValue(tab, "sync_source", "syncSource"),
|
|
745
768
|
}).filter(([, value]) => value !== undefined && value !== null),
|
|
746
769
|
),
|
|
747
770
|
),
|
|
@@ -53,12 +53,14 @@ function buildResumeSessionRows(tabs) {
|
|
|
53
53
|
Object.entries({
|
|
54
54
|
title: normalizeOptionalString(tab.title) ?? undefined,
|
|
55
55
|
path: normalizeOptionalString(tab.path) ?? undefined,
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
56
|
+
resumeCommand: resumeCommand ?? undefined,
|
|
57
|
+
resumeTool: resumeTool ?? undefined,
|
|
58
|
+
resumeSessionId: resumeSessionId ?? undefined,
|
|
59
|
+
codexSessionId: resumeTool === "codex" ? resumeSessionId ?? undefined : undefined,
|
|
60
|
+
lastActivityAt: normalizeOptionalString(tab.lastActivityAt ?? tab.last_activity_at_utc) ?? undefined,
|
|
61
|
+
lastSyncedAt: normalizeOptionalString(tab.lastSyncedAt ?? tab.last_synced_at_utc) ?? undefined,
|
|
62
|
+
}).filter(([, value]) => value !== undefined),
|
|
63
|
+
);
|
|
62
64
|
})
|
|
63
65
|
.filter(Boolean);
|
|
64
66
|
}
|
|
@@ -112,11 +114,13 @@ function normalizeRegistryEntry(rawEntry) {
|
|
|
112
114
|
title: normalizeOptionalString(session.title) ?? undefined,
|
|
113
115
|
path: normalizeOptionalString(session.path) ?? undefined,
|
|
114
116
|
resumeCommand: resumeCommand ?? undefined,
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
117
|
+
resumeTool: normalizeOptionalString(session.resumeTool) ?? undefined,
|
|
118
|
+
resumeSessionId: resumeSessionId ?? undefined,
|
|
119
|
+
codexSessionId: normalizeOptionalString(session.codexSessionId) ?? undefined,
|
|
120
|
+
lastActivityAt: normalizeOptionalString(session.lastActivityAt ?? session.last_activity_at_utc) ?? undefined,
|
|
121
|
+
lastSyncedAt: normalizeOptionalString(session.lastSyncedAt ?? session.last_synced_at_utc) ?? undefined,
|
|
122
|
+
}).filter(([, value]) => value !== undefined),
|
|
123
|
+
);
|
|
120
124
|
})
|
|
121
125
|
.filter(Boolean)
|
|
122
126
|
: Array.isArray(rawEntry.codexSessions)
|
|
@@ -137,6 +141,8 @@ function normalizeRegistryEntry(rawEntry) {
|
|
|
137
141
|
resumeTool: codexSessionId ? "codex" : undefined,
|
|
138
142
|
resumeSessionId: codexSessionId,
|
|
139
143
|
codexSessionId,
|
|
144
|
+
lastActivityAt: normalizeOptionalString(session.lastActivityAt ?? session.last_activity_at_utc) ?? undefined,
|
|
145
|
+
lastSyncedAt: normalizeOptionalString(session.lastSyncedAt ?? session.last_synced_at_utc) ?? undefined,
|
|
140
146
|
}).filter(([, value]) => value !== undefined),
|
|
141
147
|
);
|
|
142
148
|
})
|
|
@@ -351,6 +357,13 @@ function serializeManagedWorkspaceManifest(manifest) {
|
|
|
351
357
|
resumeSessionId: resumeSessionId ?? undefined,
|
|
352
358
|
codexSessionId: resumeTool === "codex" ? resumeSessionId ?? undefined : undefined,
|
|
353
359
|
claudeSessionId: resumeTool === "claude" ? resumeSessionId ?? undefined : undefined,
|
|
360
|
+
linkedIdeaId: normalizeOptionalString(tab.linkedIdeaId ?? tab.linked_idea_id) ?? undefined,
|
|
361
|
+
linkedFeatureId: normalizeOptionalString(tab.linkedFeatureId ?? tab.linked_feature_id) ?? undefined,
|
|
362
|
+
plan: tab.plan && typeof tab.plan === "object" && !Array.isArray(tab.plan) ? tab.plan : undefined,
|
|
363
|
+
tasks: Array.isArray(tab.tasks) && tab.tasks.length > 0 ? tab.tasks : undefined,
|
|
364
|
+
lastActivityAt: normalizeOptionalString(tab.lastActivityAt ?? tab.last_activity_at_utc) ?? undefined,
|
|
365
|
+
lastSyncedAt: normalizeOptionalString(tab.lastSyncedAt ?? tab.last_synced_at_utc) ?? undefined,
|
|
366
|
+
syncSource: normalizeOptionalString(tab.syncSource ?? tab.sync_source) ?? undefined,
|
|
354
367
|
}).filter(([, value]) => value !== undefined),
|
|
355
368
|
);
|
|
356
369
|
})
|
|
@@ -10,12 +10,22 @@ import {
|
|
|
10
10
|
resolveResumeMetadata,
|
|
11
11
|
WORKSPACE_SCHEMA_VERSION,
|
|
12
12
|
} from "./core-plan.js";
|
|
13
|
-
import { enrichWorkspaceManifestWithProjectContext } from "./hosted-state.js";
|
|
14
|
-
import {
|
|
13
|
+
import { buildHostedWorkspaceState, enrichWorkspaceManifestWithProjectContext } from "./hosted-state.js";
|
|
14
|
+
import { mergeLocalProjectInventoryIntoManifest } from "./local-inventory.js";
|
|
15
|
+
import {
|
|
16
|
+
buildWorkspaceManifestFromHostedWorkspacePayload,
|
|
17
|
+
fetchHostedWorkspacesPayload,
|
|
18
|
+
fetchIdeaPayload,
|
|
19
|
+
findHostedWorkspaceByWorkspaceId,
|
|
20
|
+
loadWorkspaceSource,
|
|
21
|
+
pushHostedWorkspaceState,
|
|
22
|
+
updateIdeaPayload,
|
|
23
|
+
} from "./orp.js";
|
|
15
24
|
import { cacheManagedWorkspaceManifest } from "./registry.js";
|
|
16
25
|
|
|
17
26
|
const STRUCTURED_WORKSPACE_BLOCK_PATTERN = /```orp-workspace\s*[\s\S]*?```/i;
|
|
18
27
|
const WORKSPACE_TITLE_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
28
|
+
const MAX_HOSTED_IDEA_NOTES_LENGTH = 9500;
|
|
19
29
|
|
|
20
30
|
function printSyncHelp() {
|
|
21
31
|
console.log(`ORP workspace sync
|
|
@@ -129,11 +139,38 @@ async function resolveWorkspaceSyncTargetSource(source, options) {
|
|
|
129
139
|
if (!options.workspaceFile && !options.notesFile && (source.sourceType === "hosted-idea" || source.sourceType === "hosted-workspace")) {
|
|
130
140
|
return source;
|
|
131
141
|
}
|
|
132
|
-
|
|
142
|
+
const targetSource = await loadWorkspaceSource({
|
|
133
143
|
ideaId: options.ideaId,
|
|
134
144
|
baseUrl: options.baseUrl,
|
|
135
145
|
orpCommand: options.orpCommand,
|
|
136
146
|
});
|
|
147
|
+
if (targetSource.sourceType !== "workspace-file") {
|
|
148
|
+
return targetSource;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const workspaceId =
|
|
152
|
+
normalizeOptionalString(source.workspaceManifest?.workspaceId) ||
|
|
153
|
+
normalizeOptionalString(targetSource.workspaceManifest?.workspaceId);
|
|
154
|
+
if (!workspaceId) {
|
|
155
|
+
return targetSource;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const payload = await fetchHostedWorkspacesPayload(options).catch(() => null);
|
|
159
|
+
const hostedWorkspace = findHostedWorkspaceByWorkspaceId(payload?.workspaces || [], workspaceId);
|
|
160
|
+
if (!hostedWorkspace) {
|
|
161
|
+
return targetSource;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return {
|
|
165
|
+
sourceType: "hosted-workspace",
|
|
166
|
+
sourceLabel: hostedWorkspace.title || workspaceId,
|
|
167
|
+
title: hostedWorkspace.title || workspaceId,
|
|
168
|
+
workspaceManifest: buildWorkspaceManifestFromHostedWorkspacePayload(hostedWorkspace),
|
|
169
|
+
notes: "",
|
|
170
|
+
hostedWorkspace,
|
|
171
|
+
payload: { ok: true, workspace: hostedWorkspace },
|
|
172
|
+
bridgedFromLocalWorkspaceFile: targetSource.sourcePath || targetSource.sourceLabel || true,
|
|
173
|
+
};
|
|
137
174
|
}
|
|
138
175
|
|
|
139
176
|
export function validateWorkspaceTitle(value, label = "--title") {
|
|
@@ -203,6 +240,11 @@ function serializeWorkspaceManifest(manifest) {
|
|
|
203
240
|
linkedFeatureId: normalizeOptionalString(entry.linkedFeatureId ?? entry.linked_feature_id) ?? undefined,
|
|
204
241
|
plan: entry.plan && typeof entry.plan === "object" && !Array.isArray(entry.plan) ? entry.plan : undefined,
|
|
205
242
|
tasks: Array.isArray(entry.tasks) && entry.tasks.length > 0 ? entry.tasks : undefined,
|
|
243
|
+
lastActivityAt:
|
|
244
|
+
normalizeOptionalString(entry.lastActivityAt ?? entry.last_activity_at_utc ?? entry.lastActivityAtUtc) ?? undefined,
|
|
245
|
+
lastSyncedAt:
|
|
246
|
+
normalizeOptionalString(entry.lastSyncedAt ?? entry.last_synced_at_utc ?? entry.lastSyncedAtUtc) ?? undefined,
|
|
247
|
+
syncSource: normalizeOptionalString(entry.syncSource ?? entry.sync_source) ?? undefined,
|
|
206
248
|
codexSessionId:
|
|
207
249
|
normalizeOptionalString(entry.resumeTool) === "codex"
|
|
208
250
|
? normalizeOptionalString(entry.codexSessionId ?? entry.resumeSessionId ?? entry.sessionId) ?? undefined
|
|
@@ -237,6 +279,110 @@ function composeWorkspaceNotes({ narrativeNotes, manifest }) {
|
|
|
237
279
|
]);
|
|
238
280
|
}
|
|
239
281
|
|
|
282
|
+
function serializeCompactWorkspaceManifest(manifest, options = {}) {
|
|
283
|
+
const tabs = manifest.tabs.map((entry) =>
|
|
284
|
+
Object.fromEntries(
|
|
285
|
+
Object.entries({
|
|
286
|
+
title: normalizeOptionalString(entry.title) ?? undefined,
|
|
287
|
+
path: String(entry.path).trim(),
|
|
288
|
+
remoteUrl: normalizeOptionalString(entry.remoteUrl) ?? undefined,
|
|
289
|
+
remoteBranch: normalizeOptionalString(entry.remoteBranch) ?? undefined,
|
|
290
|
+
bootstrapCommand: normalizeOptionalString(entry.bootstrapCommand) ?? undefined,
|
|
291
|
+
resumeCommand: normalizeOptionalString(entry.resumeCommand) ?? undefined,
|
|
292
|
+
resumeTool: normalizeOptionalString(entry.resumeTool) ?? undefined,
|
|
293
|
+
resumeSessionId: normalizeOptionalString(entry.resumeSessionId ?? entry.sessionId) ?? undefined,
|
|
294
|
+
linkedIdeaId: normalizeOptionalString(entry.linkedIdeaId ?? entry.linked_idea_id) ?? undefined,
|
|
295
|
+
linkedFeatureId: normalizeOptionalString(entry.linkedFeatureId ?? entry.linked_feature_id) ?? undefined,
|
|
296
|
+
lastActivityAt:
|
|
297
|
+
normalizeOptionalString(entry.lastActivityAt ?? entry.last_activity_at_utc ?? entry.lastActivityAtUtc) ?? undefined,
|
|
298
|
+
lastSyncedAt:
|
|
299
|
+
normalizeOptionalString(entry.lastSyncedAt ?? entry.last_synced_at_utc ?? entry.lastSyncedAtUtc) ?? undefined,
|
|
300
|
+
}).filter(([, value]) => value !== undefined),
|
|
301
|
+
),
|
|
302
|
+
);
|
|
303
|
+
|
|
304
|
+
return JSON.stringify(
|
|
305
|
+
Object.fromEntries(
|
|
306
|
+
Object.entries({
|
|
307
|
+
version: WORKSPACE_SCHEMA_VERSION,
|
|
308
|
+
workspaceId: normalizeOptionalString(manifest.workspaceId) ?? undefined,
|
|
309
|
+
title: normalizeOptionalString(manifest.title) ?? undefined,
|
|
310
|
+
source: "hosted-workspace",
|
|
311
|
+
hostedWorkspaceId: normalizeOptionalString(options.hostedWorkspaceId) ?? undefined,
|
|
312
|
+
tabCount: tabs.length,
|
|
313
|
+
projectCount: new Set(tabs.map((tab) => tab.path).filter(Boolean)).size,
|
|
314
|
+
tabs,
|
|
315
|
+
}).filter(([, value]) => value !== undefined),
|
|
316
|
+
),
|
|
317
|
+
null,
|
|
318
|
+
2,
|
|
319
|
+
);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
function buildStoredIdeaNotes({ narrativeNotes, manifest, hostedWorkspaceId }) {
|
|
323
|
+
const fullNotes = composeWorkspaceNotes({ narrativeNotes, manifest });
|
|
324
|
+
if (fullNotes.length <= MAX_HOSTED_IDEA_NOTES_LENGTH) {
|
|
325
|
+
return {
|
|
326
|
+
notes: fullNotes,
|
|
327
|
+
compacted: false,
|
|
328
|
+
omittedPlanTaskDetails: false,
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
const compactManifest = serializeCompactWorkspaceManifest(manifest, { hostedWorkspaceId });
|
|
333
|
+
const compactNotes = combineNoteSections([
|
|
334
|
+
narrativeNotes,
|
|
335
|
+
hostedWorkspaceId
|
|
336
|
+
? `Full workspace state, including plans and tasks, is stored on hosted ORP workspace ${hostedWorkspaceId}.`
|
|
337
|
+
: "Full workspace plan and task state is omitted from this idea-note compatibility mirror.",
|
|
338
|
+
`\`\`\`orp-workspace\n${compactManifest}\n\`\`\``,
|
|
339
|
+
]);
|
|
340
|
+
if (compactNotes.length <= MAX_HOSTED_IDEA_NOTES_LENGTH) {
|
|
341
|
+
return {
|
|
342
|
+
notes: compactNotes,
|
|
343
|
+
compacted: true,
|
|
344
|
+
omittedPlanTaskDetails: true,
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
const buildPointerManifest = (includeTabs) => ({
|
|
349
|
+
version: WORKSPACE_SCHEMA_VERSION,
|
|
350
|
+
workspaceId: normalizeOptionalString(manifest.workspaceId),
|
|
351
|
+
title: normalizeOptionalString(manifest.title),
|
|
352
|
+
source: "hosted-workspace",
|
|
353
|
+
hostedWorkspaceId: normalizeOptionalString(hostedWorkspaceId),
|
|
354
|
+
tabCount: Array.isArray(manifest.tabs) ? manifest.tabs.length : 0,
|
|
355
|
+
projectCount: new Set((manifest.tabs || []).map((tab) => tab.path).filter(Boolean)).size,
|
|
356
|
+
tabs: includeTabs
|
|
357
|
+
? (manifest.tabs || []).map((tab) =>
|
|
358
|
+
Object.fromEntries(
|
|
359
|
+
Object.entries({
|
|
360
|
+
title: normalizeOptionalString(tab.title) ?? undefined,
|
|
361
|
+
path: normalizeOptionalString(tab.path) ?? undefined,
|
|
362
|
+
}).filter(([, value]) => value !== undefined),
|
|
363
|
+
),
|
|
364
|
+
)
|
|
365
|
+
: undefined,
|
|
366
|
+
});
|
|
367
|
+
const pointerText = (includeTabs) => JSON.stringify(buildPointerManifest(includeTabs), null, 2);
|
|
368
|
+
const pointerNotes = (includeTabs) =>
|
|
369
|
+
combineNoteSections([
|
|
370
|
+
narrativeNotes,
|
|
371
|
+
hostedWorkspaceId
|
|
372
|
+
? `Full workspace state is stored on hosted ORP workspace ${hostedWorkspaceId}.`
|
|
373
|
+
: "Full workspace state is stored in the ORP workspace sync payload.",
|
|
374
|
+
`\`\`\`orp-workspace\n${pointerText(includeTabs)}\n\`\`\``,
|
|
375
|
+
]);
|
|
376
|
+
return {
|
|
377
|
+
notes:
|
|
378
|
+
pointerNotes(true).length <= MAX_HOSTED_IDEA_NOTES_LENGTH
|
|
379
|
+
? pointerNotes(true)
|
|
380
|
+
: pointerNotes(false),
|
|
381
|
+
compacted: true,
|
|
382
|
+
omittedPlanTaskDetails: true,
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
|
|
240
386
|
export function buildWorkspaceSyncPreview({ source, parsed, targetIdea, workspaceTitle = null }) {
|
|
241
387
|
const manifest = parsed.manifest
|
|
242
388
|
? {
|
|
@@ -259,6 +405,9 @@ export function buildWorkspaceSyncPreview({ source, parsed, targetIdea, workspac
|
|
|
259
405
|
linkedFeatureId: entry.linkedFeatureId || null,
|
|
260
406
|
plan: entry.plan || null,
|
|
261
407
|
tasks: Array.isArray(entry.tasks) ? entry.tasks : [],
|
|
408
|
+
lastActivityAt: entry.lastActivityAt || null,
|
|
409
|
+
lastSyncedAt: entry.lastSyncedAt || null,
|
|
410
|
+
syncSource: entry.syncSource || null,
|
|
262
411
|
})),
|
|
263
412
|
}
|
|
264
413
|
: {
|
|
@@ -279,9 +428,20 @@ export function buildWorkspaceSyncPreview({ source, parsed, targetIdea, workspac
|
|
|
279
428
|
resolveResumeMetadata(entry).resumeTool === "codex" ? resolveResumeMetadata(entry).resumeSessionId : null,
|
|
280
429
|
claudeSessionId:
|
|
281
430
|
resolveResumeMetadata(entry).resumeTool === "claude" ? resolveResumeMetadata(entry).resumeSessionId : null,
|
|
431
|
+
lastActivityAt: entry.lastActivityAt || null,
|
|
432
|
+
lastSyncedAt: entry.lastSyncedAt || null,
|
|
433
|
+
syncSource: entry.syncSource || null,
|
|
282
434
|
})),
|
|
283
435
|
};
|
|
284
|
-
const
|
|
436
|
+
const syncTimestamp = new Date().toISOString();
|
|
437
|
+
const timestampedManifest = {
|
|
438
|
+
...manifest,
|
|
439
|
+
tabs: manifest.tabs.map((tab) => ({
|
|
440
|
+
...tab,
|
|
441
|
+
lastSyncedAt: tab.lastSyncedAt || syncTimestamp,
|
|
442
|
+
})),
|
|
443
|
+
};
|
|
444
|
+
const enrichedManifest = enrichWorkspaceManifestWithProjectContext(timestampedManifest);
|
|
285
445
|
|
|
286
446
|
const narrativeSourceNotes =
|
|
287
447
|
source.sourceType === "workspace-file" ? targetIdea.notes || "" : source.notes || targetIdea.notes || "";
|
|
@@ -316,12 +476,25 @@ function summarizeSyncPreview(preview) {
|
|
|
316
476
|
` parse mode: ${preview.parseMode}`,
|
|
317
477
|
` workspace id: ${preview.workspaceId}`,
|
|
318
478
|
` tabs: ${preview.tabs.length}`,
|
|
319
|
-
` stored notes: ${preview.nextNotesLength} chars`,
|
|
320
479
|
];
|
|
480
|
+
if (preview.hostedWorkspaceId) {
|
|
481
|
+
lines.push(` compatibility notes: ${preview.nextNotesLength} chars (not written when hosted push succeeds)`);
|
|
482
|
+
} else {
|
|
483
|
+
lines.push(` stored notes: ${preview.nextNotesLength} chars`);
|
|
484
|
+
}
|
|
485
|
+
if (preview.compactedIdeaNotes && !preview.hostedWorkspaceId) {
|
|
486
|
+
lines.push(` idea notes: compact compatibility mirror; hosted workspace state carries full details`);
|
|
487
|
+
}
|
|
488
|
+
if (preview.hostedWorkspaceId) {
|
|
489
|
+
lines.push(` hosted push target: ${preview.hostedWorkspaceId}`);
|
|
490
|
+
}
|
|
321
491
|
|
|
322
492
|
if (preview.skipped.length > 0) {
|
|
323
493
|
lines.push(` skipped non-path lines: ${preview.skipped.length}`);
|
|
324
494
|
}
|
|
495
|
+
if (preview.inventory) {
|
|
496
|
+
lines.push(` local inventory: ${preview.inventory.rowCount} rows / ${preview.inventory.projectCount} projects`);
|
|
497
|
+
}
|
|
325
498
|
return lines.join("\n");
|
|
326
499
|
}
|
|
327
500
|
|
|
@@ -340,10 +513,12 @@ export async function runWorkspaceSync(argv = process.argv.slice(2)) {
|
|
|
340
513
|
if (parsed.entries.length === 0) {
|
|
341
514
|
throw new Error("No launchable workspace lines were found in the provided source.");
|
|
342
515
|
}
|
|
516
|
+
const targetSource = await resolveWorkspaceSyncTargetSource(source, options);
|
|
343
517
|
const resolvedWorkspaceTitle = options.title
|
|
344
518
|
? validateWorkspaceTitle(options.title)
|
|
345
|
-
: normalizeOptionalString(
|
|
346
|
-
|
|
519
|
+
: normalizeOptionalString(targetSource.hostedWorkspace?.title) ||
|
|
520
|
+
normalizeOptionalString(parsed.manifest?.title) ||
|
|
521
|
+
(await promptForWorkspaceTitle());
|
|
347
522
|
const targetIdeaId = resolveWorkspaceSyncTargetIdeaId(targetSource);
|
|
348
523
|
if (!targetIdeaId) {
|
|
349
524
|
throw new Error(
|
|
@@ -366,23 +541,69 @@ export async function runWorkspaceSync(argv = process.argv.slice(2)) {
|
|
|
366
541
|
targetIdea: targetPayload.idea,
|
|
367
542
|
workspaceTitle: resolvedWorkspaceTitle,
|
|
368
543
|
});
|
|
544
|
+
const reconciled = await mergeLocalProjectInventoryIntoManifest(preview.manifest, {
|
|
545
|
+
...options,
|
|
546
|
+
workspaceSelector: options.ideaId,
|
|
547
|
+
});
|
|
548
|
+
const hostedWorkspaceId = normalizeOptionalString(targetSource.hostedWorkspace?.workspace_id ?? targetSource.hostedWorkspace?.id);
|
|
549
|
+
const narrativeNotes = extractWorkspaceNarrativeNotes(preview.nextNotes, {
|
|
550
|
+
stripLegacyWorkspaceLines: true,
|
|
551
|
+
});
|
|
552
|
+
const storedIdeaNotes = buildStoredIdeaNotes({
|
|
553
|
+
narrativeNotes,
|
|
554
|
+
manifest: reconciled.manifest,
|
|
555
|
+
hostedWorkspaceId,
|
|
556
|
+
});
|
|
557
|
+
const finalPreview = {
|
|
558
|
+
...preview,
|
|
559
|
+
manifest: reconciled.manifest,
|
|
560
|
+
tabs: reconciled.manifest.tabs,
|
|
561
|
+
nextNotes: storedIdeaNotes.notes,
|
|
562
|
+
inventory: reconciled.inventory,
|
|
563
|
+
hostedWorkspaceId,
|
|
564
|
+
compactedIdeaNotes: storedIdeaNotes.compacted,
|
|
565
|
+
omittedPlanTaskDetailsFromIdeaNotes: storedIdeaNotes.omittedPlanTaskDetails,
|
|
566
|
+
};
|
|
567
|
+
finalPreview.nextNotesLength = finalPreview.nextNotes.length;
|
|
369
568
|
|
|
370
569
|
if (options.json) {
|
|
371
|
-
process.stdout.write(`${JSON.stringify(
|
|
570
|
+
process.stdout.write(`${JSON.stringify(finalPreview, null, 2)}\n`);
|
|
372
571
|
return 0;
|
|
373
572
|
}
|
|
374
573
|
|
|
375
574
|
if (options.dryRun) {
|
|
376
|
-
process.stdout.write(`${summarizeSyncPreview(
|
|
575
|
+
process.stdout.write(`${summarizeSyncPreview(finalPreview)}\n`);
|
|
377
576
|
return 0;
|
|
378
577
|
}
|
|
379
578
|
|
|
380
|
-
|
|
381
|
-
|
|
579
|
+
let pushedWorkspace = null;
|
|
580
|
+
if (hostedWorkspaceId) {
|
|
581
|
+
const state = buildHostedWorkspaceState(finalPreview.manifest, {
|
|
582
|
+
previousWorkspace: targetSource.hostedWorkspace,
|
|
583
|
+
updatedAt: new Date().toISOString(),
|
|
584
|
+
localInventory: finalPreview.inventory,
|
|
585
|
+
});
|
|
586
|
+
const pushed = await pushHostedWorkspaceState(hostedWorkspaceId, state, options);
|
|
587
|
+
pushedWorkspace = pushed?.workspace || null;
|
|
588
|
+
}
|
|
589
|
+
const updated = hostedWorkspaceId
|
|
590
|
+
? { title: finalPreview.targetIdeaTitle }
|
|
591
|
+
: await updateIdeaPayload(targetIdeaId, { notes: finalPreview.nextNotes }, options);
|
|
592
|
+
const managedCache = await cacheManagedWorkspaceManifest(finalPreview.manifest);
|
|
382
593
|
process.stdout.write(
|
|
383
|
-
`Synced workspace '${
|
|
594
|
+
`Synced workspace '${finalPreview.workspaceId}' to idea '${updated.title || finalPreview.targetIdeaTitle}'.\n`,
|
|
384
595
|
);
|
|
385
|
-
|
|
596
|
+
if (pushedWorkspace) {
|
|
597
|
+
process.stdout.write(`Pushed hosted workspace state to '${hostedWorkspaceId}'.\n`);
|
|
598
|
+
process.stdout.write("Skipped idea-note mirror update because hosted workspace state is authoritative.\n");
|
|
599
|
+
}
|
|
600
|
+
if (hostedWorkspaceId) {
|
|
601
|
+
process.stdout.write(
|
|
602
|
+
`Tabs: ${finalPreview.tabs.length}. Compatibility notes would be ${finalPreview.nextNotesLength} chars.\n`,
|
|
603
|
+
);
|
|
604
|
+
} else {
|
|
605
|
+
process.stdout.write(`Tabs: ${finalPreview.tabs.length}. Stored notes: ${finalPreview.nextNotesLength} chars.\n`);
|
|
606
|
+
}
|
|
386
607
|
process.stdout.write(`Updated local workspace cache at ${managedCache.manifestPath}.\n`);
|
|
387
608
|
return 0;
|
|
388
609
|
}
|
|
@@ -132,3 +132,56 @@ test("buildHostedWorkspaceState compiles local ORP frontier plan and tasks", asy
|
|
|
132
132
|
assert.equal(state.projects[0].linked_idea_id, "idea-123");
|
|
133
133
|
assert.equal(state.projects[0].linked_feature_id, "feature-regime-metadata-quality");
|
|
134
134
|
});
|
|
135
|
+
|
|
136
|
+
test("buildHostedWorkspaceState preserves manifest plan tasks and activity timestamps", () => {
|
|
137
|
+
const state = buildHostedWorkspaceState(
|
|
138
|
+
{
|
|
139
|
+
version: "1",
|
|
140
|
+
workspaceId: "main-cody-1",
|
|
141
|
+
title: "main-cody-1",
|
|
142
|
+
tabs: [
|
|
143
|
+
{
|
|
144
|
+
title: "tailnet-app",
|
|
145
|
+
path: "/Volumes/Code_2TB/code/tailnet-app",
|
|
146
|
+
resumeTool: "codex",
|
|
147
|
+
resumeSessionId: "019dcd50-111d-7451-bd01-dbc21336c679",
|
|
148
|
+
linkedIdeaId: "idea-tailnet",
|
|
149
|
+
linkedFeatureId: "feature-tailnet",
|
|
150
|
+
plan: {
|
|
151
|
+
summary: "Ship Tailnet App workspace sync",
|
|
152
|
+
body: "Keep the hosted workspace aligned with local project inventory.",
|
|
153
|
+
},
|
|
154
|
+
tasks: [
|
|
155
|
+
{
|
|
156
|
+
id: "sync-contract",
|
|
157
|
+
title: "Define sync contract",
|
|
158
|
+
status: "in_progress",
|
|
159
|
+
},
|
|
160
|
+
],
|
|
161
|
+
lastActivityAt: "2026-04-30T02:59:15.000Z",
|
|
162
|
+
lastSyncedAt: "2026-04-30T12:00:00.000Z",
|
|
163
|
+
syncSource: "orp-project-startup",
|
|
164
|
+
},
|
|
165
|
+
],
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
updatedAt: "2026-04-30T12:30:00.000Z",
|
|
169
|
+
localInventory: {
|
|
170
|
+
contract: {
|
|
171
|
+
source_of_truth: "orp-workspace-ledger",
|
|
172
|
+
},
|
|
173
|
+
},
|
|
174
|
+
},
|
|
175
|
+
);
|
|
176
|
+
|
|
177
|
+
assert.equal(state.tabs[0].plan.summary, "Ship Tailnet App workspace sync");
|
|
178
|
+
assert.equal(state.tabs[0].tasks[0].id, "sync-contract");
|
|
179
|
+
assert.equal(state.tabs[0].linked_idea_id, "idea-tailnet");
|
|
180
|
+
assert.equal(state.tabs[0].linked_feature_id, "feature-tailnet");
|
|
181
|
+
assert.equal(state.tabs[0].last_activity_at_utc, "2026-04-30T02:59:15.000Z");
|
|
182
|
+
assert.equal(state.tabs[0].last_synced_at_utc, "2026-04-30T12:00:00.000Z");
|
|
183
|
+
assert.equal(state.tabs[0].sync_source, "orp-project-startup");
|
|
184
|
+
assert.equal(state.projects[0].last_activity_at_utc, "2026-04-30T02:59:15.000Z");
|
|
185
|
+
assert.equal(state.projects[0].sessions[0].last_synced_at_utc, "2026-04-30T12:00:00.000Z");
|
|
186
|
+
assert.equal(state.source_contract.source_of_truth, "orp-workspace-ledger");
|
|
187
|
+
});
|
|
@@ -0,0 +1,126 @@
|
|
|
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 { mergeLocalProjectInventoryIntoManifest } from "../src/index.js";
|
|
8
|
+
|
|
9
|
+
async function makeTempDir() {
|
|
10
|
+
return fs.mkdtemp(path.join(os.tmpdir(), "orp-local-inventory-"));
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async function writeJson(filePath, payload) {
|
|
14
|
+
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
15
|
+
await fs.writeFile(filePath, `${JSON.stringify(payload, null, 2)}\n`, "utf8");
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
test("mergeLocalProjectInventoryIntoManifest reconciles ORP startup, Clawdad, and known Codex sessions", async () => {
|
|
19
|
+
const root = await makeTempDir();
|
|
20
|
+
const codexHome = path.join(root, "codex-home");
|
|
21
|
+
const clawdadStatePath = path.join(root, "clawdad", "state.json");
|
|
22
|
+
const existingPath = path.join(root, "existing");
|
|
23
|
+
const tailnetPath = path.join(root, "tailnet-app");
|
|
24
|
+
const financialPath = path.join(root, "financial-stack");
|
|
25
|
+
|
|
26
|
+
await fs.mkdir(existingPath, { recursive: true });
|
|
27
|
+
await writeJson(path.join(tailnetPath, "orp", "state.json"), {
|
|
28
|
+
startup: {
|
|
29
|
+
updated_at_utc: "2026-04-30T02:59:15Z",
|
|
30
|
+
workspace: {
|
|
31
|
+
requested: true,
|
|
32
|
+
workspace: "main",
|
|
33
|
+
path: tailnetPath,
|
|
34
|
+
result: {
|
|
35
|
+
manifest: {
|
|
36
|
+
version: "1",
|
|
37
|
+
workspaceId: "main",
|
|
38
|
+
title: "main",
|
|
39
|
+
tabs: [
|
|
40
|
+
{
|
|
41
|
+
title: "financial-stack",
|
|
42
|
+
path: financialPath,
|
|
43
|
+
resumeTool: "codex",
|
|
44
|
+
resumeSessionId: "019dc348-ce52-7f52-8ac8-0200a9bf946a",
|
|
45
|
+
},
|
|
46
|
+
],
|
|
47
|
+
},
|
|
48
|
+
tab: {
|
|
49
|
+
title: "Tailnet App",
|
|
50
|
+
path: tailnetPath,
|
|
51
|
+
bootstrapCommand: "npm test",
|
|
52
|
+
resumeTool: "codex",
|
|
53
|
+
resumeSessionId: "019dcd50-111d-7451-bd01-dbc21336c679",
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
await writeJson(clawdadStatePath, {
|
|
60
|
+
projects: {
|
|
61
|
+
[financialPath]: {
|
|
62
|
+
status: "completed",
|
|
63
|
+
last_dispatch: "2026-04-25T20:47:50Z",
|
|
64
|
+
last_response: "2026-04-25T20:49:37Z",
|
|
65
|
+
registered_at: "2026-04-25T20:11:21.625Z",
|
|
66
|
+
sessions: {
|
|
67
|
+
"019dc348-ce52-7f52-8ac8-0200a9bf946a": {
|
|
68
|
+
slug: "financial-stack",
|
|
69
|
+
provider: "codex",
|
|
70
|
+
quarantined: "true",
|
|
71
|
+
},
|
|
72
|
+
"019dc644-d31d-78e1-a3ed-8575aead1c96": {
|
|
73
|
+
slug: "financial-stack",
|
|
74
|
+
provider: "codex",
|
|
75
|
+
tracked_at: "2026-04-25T20:11:21.625Z",
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
quarantined_sessions: {
|
|
79
|
+
"019dc348-ce52-7f52-8ac8-0200a9bf946a": true,
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
const codexSessionPath = path.join(codexHome, "sessions", "2026", "04", "30", "rollout-existing.jsonl");
|
|
85
|
+
await fs.mkdir(path.dirname(codexSessionPath), { recursive: true });
|
|
86
|
+
await fs.writeFile(codexSessionPath, `${JSON.stringify({
|
|
87
|
+
timestamp: "2026-04-30T12:00:00.000Z",
|
|
88
|
+
type: "session_meta",
|
|
89
|
+
payload: {
|
|
90
|
+
id: "019df000-1111-7222-8333-444455556666",
|
|
91
|
+
cwd: existingPath,
|
|
92
|
+
timestamp: "2026-04-30T12:00:00.000Z",
|
|
93
|
+
},
|
|
94
|
+
})}\n`, "utf8");
|
|
95
|
+
|
|
96
|
+
const merged = await mergeLocalProjectInventoryIntoManifest(
|
|
97
|
+
{
|
|
98
|
+
version: "1",
|
|
99
|
+
workspaceId: "main",
|
|
100
|
+
title: "main",
|
|
101
|
+
tabs: [
|
|
102
|
+
{
|
|
103
|
+
title: "existing",
|
|
104
|
+
path: existingPath,
|
|
105
|
+
resumeTool: "codex",
|
|
106
|
+
resumeSessionId: "019d0000-1111-7222-8333-444455556666",
|
|
107
|
+
},
|
|
108
|
+
],
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
localProjectRoots: [root],
|
|
112
|
+
clawdadStatePath,
|
|
113
|
+
codexHome,
|
|
114
|
+
workspaceSelector: "main",
|
|
115
|
+
codexScanDays: 30,
|
|
116
|
+
},
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
const byPath = new Map(merged.manifest.tabs.map((tab) => [tab.path, tab]));
|
|
120
|
+
assert.equal(byPath.get(existingPath)?.resumeSessionId, "019df000-1111-7222-8333-444455556666");
|
|
121
|
+
assert.equal(byPath.get(tailnetPath)?.bootstrapCommand, "npm test");
|
|
122
|
+
assert.equal(byPath.get(tailnetPath)?.resumeSessionId, "019dcd50-111d-7451-bd01-dbc21336c679");
|
|
123
|
+
assert.equal(byPath.get(financialPath)?.resumeSessionId, "019dc644-d31d-78e1-a3ed-8575aead1c96");
|
|
124
|
+
assert.equal([...byPath.values()].some((tab) => tab.resumeSessionId === "019dc348-ce52-7f52-8ac8-0200a9bf946a"), false);
|
|
125
|
+
assert.equal(merged.inventory.projectCount, 3);
|
|
126
|
+
});
|
|
@@ -6,6 +6,7 @@ import path from "node:path";
|
|
|
6
6
|
|
|
7
7
|
import {
|
|
8
8
|
chooseImplicitMainCandidate,
|
|
9
|
+
findHostedWorkspaceByWorkspaceId,
|
|
9
10
|
loadWorkspaceSource,
|
|
10
11
|
resolveWorkspaceSelectorFromCollections,
|
|
11
12
|
resolveWorkspaceWatchTargets,
|
|
@@ -44,6 +45,24 @@ test("resolveWorkspaceSelectorFromCollections matches hosted ideas by saved work
|
|
|
44
45
|
assert.equal(byTitleSlug?.title, "Main Cody 1");
|
|
45
46
|
});
|
|
46
47
|
|
|
48
|
+
test("findHostedWorkspaceByWorkspaceId matches hosted workspace records by durable id", () => {
|
|
49
|
+
const workspace = findHostedWorkspaceByWorkspaceId(
|
|
50
|
+
[
|
|
51
|
+
{
|
|
52
|
+
workspace_id: "focused-items",
|
|
53
|
+
title: "focused-items",
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
workspace_id: "captured-iterm-window-20260401t032225z",
|
|
57
|
+
title: "main-workspace",
|
|
58
|
+
},
|
|
59
|
+
],
|
|
60
|
+
"captured-iterm-window-20260401t032225z",
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
assert.equal(workspace?.title, "main-workspace");
|
|
64
|
+
});
|
|
65
|
+
|
|
47
66
|
test("resolveWorkspaceSelectorFromCollections can match local tracked workspaces by title", () => {
|
|
48
67
|
const resolved = resolveWorkspaceSelectorFromCollections("ORP Main", {
|
|
49
68
|
localWorkspaces: [
|
|
Binary file
|
|
Binary file
|
|
Binary file
|