open-research-protocol 0.4.33 → 0.4.35
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 +50 -0
- package/cli/__pycache__/orp.cpython-311.pyc +0 -0
- package/cli/orp.py +430 -11
- package/docs/ORP_HOSTED_WORKSPACE_CONTRACT.md +39 -0
- package/package.json +1 -1
- package/packages/orp-workspace-launcher/src/core-plan.js +62 -0
- package/packages/orp-workspace-launcher/src/hosted-state.js +328 -4
- package/packages/orp-workspace-launcher/src/index.js +11 -1
- 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 +241 -14
- package/packages/orp-workspace-launcher/test/core-plan.test.js +48 -0
- package/packages/orp-workspace-launcher/test/hosted-state.test.js +187 -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,11 +10,22 @@ import {
|
|
|
10
10
|
resolveResumeMetadata,
|
|
11
11
|
WORKSPACE_SCHEMA_VERSION,
|
|
12
12
|
} from "./core-plan.js";
|
|
13
|
-
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";
|
|
14
24
|
import { cacheManagedWorkspaceManifest } from "./registry.js";
|
|
15
25
|
|
|
16
26
|
const STRUCTURED_WORKSPACE_BLOCK_PATTERN = /```orp-workspace\s*[\s\S]*?```/i;
|
|
17
27
|
const WORKSPACE_TITLE_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
28
|
+
const MAX_HOSTED_IDEA_NOTES_LENGTH = 9500;
|
|
18
29
|
|
|
19
30
|
function printSyncHelp() {
|
|
20
31
|
console.log(`ORP workspace sync
|
|
@@ -128,11 +139,38 @@ async function resolveWorkspaceSyncTargetSource(source, options) {
|
|
|
128
139
|
if (!options.workspaceFile && !options.notesFile && (source.sourceType === "hosted-idea" || source.sourceType === "hosted-workspace")) {
|
|
129
140
|
return source;
|
|
130
141
|
}
|
|
131
|
-
|
|
142
|
+
const targetSource = await loadWorkspaceSource({
|
|
132
143
|
ideaId: options.ideaId,
|
|
133
144
|
baseUrl: options.baseUrl,
|
|
134
145
|
orpCommand: options.orpCommand,
|
|
135
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
|
+
};
|
|
136
174
|
}
|
|
137
175
|
|
|
138
176
|
export function validateWorkspaceTitle(value, label = "--title") {
|
|
@@ -198,6 +236,15 @@ function serializeWorkspaceManifest(manifest) {
|
|
|
198
236
|
resumeCommand: normalizeOptionalString(entry.resumeCommand) ?? undefined,
|
|
199
237
|
resumeTool: normalizeOptionalString(entry.resumeTool) ?? undefined,
|
|
200
238
|
resumeSessionId: normalizeOptionalString(entry.resumeSessionId ?? entry.sessionId) ?? undefined,
|
|
239
|
+
linkedIdeaId: normalizeOptionalString(entry.linkedIdeaId ?? entry.linked_idea_id) ?? undefined,
|
|
240
|
+
linkedFeatureId: normalizeOptionalString(entry.linkedFeatureId ?? entry.linked_feature_id) ?? undefined,
|
|
241
|
+
plan: entry.plan && typeof entry.plan === "object" && !Array.isArray(entry.plan) ? entry.plan : undefined,
|
|
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,
|
|
201
248
|
codexSessionId:
|
|
202
249
|
normalizeOptionalString(entry.resumeTool) === "codex"
|
|
203
250
|
? normalizeOptionalString(entry.codexSessionId ?? entry.resumeSessionId ?? entry.sessionId) ?? undefined
|
|
@@ -232,6 +279,110 @@ function composeWorkspaceNotes({ narrativeNotes, manifest }) {
|
|
|
232
279
|
]);
|
|
233
280
|
}
|
|
234
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
|
+
|
|
235
386
|
export function buildWorkspaceSyncPreview({ source, parsed, targetIdea, workspaceTitle = null }) {
|
|
236
387
|
const manifest = parsed.manifest
|
|
237
388
|
? {
|
|
@@ -250,6 +401,13 @@ export function buildWorkspaceSyncPreview({ source, parsed, targetIdea, workspac
|
|
|
250
401
|
resumeSessionId: entry.sessionId || null,
|
|
251
402
|
codexSessionId: entry.resumeTool === "codex" ? entry.sessionId || null : null,
|
|
252
403
|
claudeSessionId: entry.resumeTool === "claude" ? entry.sessionId || null : null,
|
|
404
|
+
linkedIdeaId: entry.linkedIdeaId || null,
|
|
405
|
+
linkedFeatureId: entry.linkedFeatureId || null,
|
|
406
|
+
plan: entry.plan || null,
|
|
407
|
+
tasks: Array.isArray(entry.tasks) ? entry.tasks : [],
|
|
408
|
+
lastActivityAt: entry.lastActivityAt || null,
|
|
409
|
+
lastSyncedAt: entry.lastSyncedAt || null,
|
|
410
|
+
syncSource: entry.syncSource || null,
|
|
253
411
|
})),
|
|
254
412
|
}
|
|
255
413
|
: {
|
|
@@ -270,8 +428,20 @@ export function buildWorkspaceSyncPreview({ source, parsed, targetIdea, workspac
|
|
|
270
428
|
resolveResumeMetadata(entry).resumeTool === "codex" ? resolveResumeMetadata(entry).resumeSessionId : null,
|
|
271
429
|
claudeSessionId:
|
|
272
430
|
resolveResumeMetadata(entry).resumeTool === "claude" ? resolveResumeMetadata(entry).resumeSessionId : null,
|
|
431
|
+
lastActivityAt: entry.lastActivityAt || null,
|
|
432
|
+
lastSyncedAt: entry.lastSyncedAt || null,
|
|
433
|
+
syncSource: entry.syncSource || null,
|
|
273
434
|
})),
|
|
274
435
|
};
|
|
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);
|
|
275
445
|
|
|
276
446
|
const narrativeSourceNotes =
|
|
277
447
|
source.sourceType === "workspace-file" ? targetIdea.notes || "" : source.notes || targetIdea.notes || "";
|
|
@@ -280,7 +450,7 @@ export function buildWorkspaceSyncPreview({ source, parsed, targetIdea, workspac
|
|
|
280
450
|
});
|
|
281
451
|
const nextNotes = composeWorkspaceNotes({
|
|
282
452
|
narrativeNotes,
|
|
283
|
-
manifest,
|
|
453
|
+
manifest: enrichedManifest,
|
|
284
454
|
});
|
|
285
455
|
|
|
286
456
|
return {
|
|
@@ -289,11 +459,11 @@ export function buildWorkspaceSyncPreview({ source, parsed, targetIdea, workspac
|
|
|
289
459
|
sourceType: source.sourceType,
|
|
290
460
|
sourceLabel: source.sourceLabel,
|
|
291
461
|
parseMode: parsed.parseMode,
|
|
292
|
-
workspaceId:
|
|
293
|
-
manifest,
|
|
462
|
+
workspaceId: enrichedManifest.workspaceId,
|
|
463
|
+
manifest: enrichedManifest,
|
|
294
464
|
nextNotes,
|
|
295
465
|
nextNotesLength: nextNotes.length,
|
|
296
|
-
tabs:
|
|
466
|
+
tabs: enrichedManifest.tabs,
|
|
297
467
|
skipped: parsed.skipped,
|
|
298
468
|
};
|
|
299
469
|
}
|
|
@@ -308,10 +478,19 @@ function summarizeSyncPreview(preview) {
|
|
|
308
478
|
` tabs: ${preview.tabs.length}`,
|
|
309
479
|
` stored notes: ${preview.nextNotesLength} chars`,
|
|
310
480
|
];
|
|
481
|
+
if (preview.compactedIdeaNotes) {
|
|
482
|
+
lines.push(` idea notes: compact compatibility mirror; hosted workspace state carries full details`);
|
|
483
|
+
}
|
|
484
|
+
if (preview.hostedWorkspaceId) {
|
|
485
|
+
lines.push(` hosted push target: ${preview.hostedWorkspaceId}`);
|
|
486
|
+
}
|
|
311
487
|
|
|
312
488
|
if (preview.skipped.length > 0) {
|
|
313
489
|
lines.push(` skipped non-path lines: ${preview.skipped.length}`);
|
|
314
490
|
}
|
|
491
|
+
if (preview.inventory) {
|
|
492
|
+
lines.push(` local inventory: ${preview.inventory.rowCount} rows / ${preview.inventory.projectCount} projects`);
|
|
493
|
+
}
|
|
315
494
|
return lines.join("\n");
|
|
316
495
|
}
|
|
317
496
|
|
|
@@ -330,10 +509,12 @@ export async function runWorkspaceSync(argv = process.argv.slice(2)) {
|
|
|
330
509
|
if (parsed.entries.length === 0) {
|
|
331
510
|
throw new Error("No launchable workspace lines were found in the provided source.");
|
|
332
511
|
}
|
|
512
|
+
const targetSource = await resolveWorkspaceSyncTargetSource(source, options);
|
|
333
513
|
const resolvedWorkspaceTitle = options.title
|
|
334
514
|
? validateWorkspaceTitle(options.title)
|
|
335
|
-
: normalizeOptionalString(
|
|
336
|
-
|
|
515
|
+
: normalizeOptionalString(targetSource.hostedWorkspace?.title) ||
|
|
516
|
+
normalizeOptionalString(parsed.manifest?.title) ||
|
|
517
|
+
(await promptForWorkspaceTitle());
|
|
337
518
|
const targetIdeaId = resolveWorkspaceSyncTargetIdeaId(targetSource);
|
|
338
519
|
if (!targetIdeaId) {
|
|
339
520
|
throw new Error(
|
|
@@ -356,23 +537,69 @@ export async function runWorkspaceSync(argv = process.argv.slice(2)) {
|
|
|
356
537
|
targetIdea: targetPayload.idea,
|
|
357
538
|
workspaceTitle: resolvedWorkspaceTitle,
|
|
358
539
|
});
|
|
540
|
+
const reconciled = await mergeLocalProjectInventoryIntoManifest(preview.manifest, {
|
|
541
|
+
...options,
|
|
542
|
+
workspaceSelector: options.ideaId,
|
|
543
|
+
});
|
|
544
|
+
const hostedWorkspaceId = normalizeOptionalString(targetSource.hostedWorkspace?.workspace_id ?? targetSource.hostedWorkspace?.id);
|
|
545
|
+
const narrativeNotes = extractWorkspaceNarrativeNotes(preview.nextNotes, {
|
|
546
|
+
stripLegacyWorkspaceLines: true,
|
|
547
|
+
});
|
|
548
|
+
const storedIdeaNotes = buildStoredIdeaNotes({
|
|
549
|
+
narrativeNotes,
|
|
550
|
+
manifest: reconciled.manifest,
|
|
551
|
+
hostedWorkspaceId,
|
|
552
|
+
});
|
|
553
|
+
const finalPreview = {
|
|
554
|
+
...preview,
|
|
555
|
+
manifest: reconciled.manifest,
|
|
556
|
+
tabs: reconciled.manifest.tabs,
|
|
557
|
+
nextNotes: storedIdeaNotes.notes,
|
|
558
|
+
inventory: reconciled.inventory,
|
|
559
|
+
hostedWorkspaceId,
|
|
560
|
+
compactedIdeaNotes: storedIdeaNotes.compacted,
|
|
561
|
+
omittedPlanTaskDetailsFromIdeaNotes: storedIdeaNotes.omittedPlanTaskDetails,
|
|
562
|
+
};
|
|
563
|
+
finalPreview.nextNotesLength = finalPreview.nextNotes.length;
|
|
359
564
|
|
|
360
565
|
if (options.json) {
|
|
361
|
-
process.stdout.write(`${JSON.stringify(
|
|
566
|
+
process.stdout.write(`${JSON.stringify(finalPreview, null, 2)}\n`);
|
|
362
567
|
return 0;
|
|
363
568
|
}
|
|
364
569
|
|
|
365
570
|
if (options.dryRun) {
|
|
366
|
-
process.stdout.write(`${summarizeSyncPreview(
|
|
571
|
+
process.stdout.write(`${summarizeSyncPreview(finalPreview)}\n`);
|
|
367
572
|
return 0;
|
|
368
573
|
}
|
|
369
574
|
|
|
370
|
-
|
|
371
|
-
|
|
575
|
+
let pushedWorkspace = null;
|
|
576
|
+
if (hostedWorkspaceId) {
|
|
577
|
+
const state = buildHostedWorkspaceState(finalPreview.manifest, {
|
|
578
|
+
previousWorkspace: targetSource.hostedWorkspace,
|
|
579
|
+
updatedAt: new Date().toISOString(),
|
|
580
|
+
localInventory: finalPreview.inventory,
|
|
581
|
+
});
|
|
582
|
+
const pushed = await pushHostedWorkspaceState(hostedWorkspaceId, state, options);
|
|
583
|
+
pushedWorkspace = pushed?.workspace || null;
|
|
584
|
+
}
|
|
585
|
+
const updated = hostedWorkspaceId
|
|
586
|
+
? { title: finalPreview.targetIdeaTitle }
|
|
587
|
+
: await updateIdeaPayload(targetIdeaId, { notes: finalPreview.nextNotes }, options);
|
|
588
|
+
const managedCache = await cacheManagedWorkspaceManifest(finalPreview.manifest);
|
|
372
589
|
process.stdout.write(
|
|
373
|
-
`Synced workspace '${
|
|
590
|
+
`Synced workspace '${finalPreview.workspaceId}' to idea '${updated.title || finalPreview.targetIdeaTitle}'.\n`,
|
|
374
591
|
);
|
|
375
|
-
|
|
592
|
+
if (pushedWorkspace) {
|
|
593
|
+
process.stdout.write(`Pushed hosted workspace state to '${hostedWorkspaceId}'.\n`);
|
|
594
|
+
process.stdout.write("Skipped idea-note mirror update because hosted workspace state is authoritative.\n");
|
|
595
|
+
}
|
|
596
|
+
if (hostedWorkspaceId) {
|
|
597
|
+
process.stdout.write(
|
|
598
|
+
`Tabs: ${finalPreview.tabs.length}. Compatibility notes would be ${finalPreview.nextNotesLength} chars.\n`,
|
|
599
|
+
);
|
|
600
|
+
} else {
|
|
601
|
+
process.stdout.write(`Tabs: ${finalPreview.tabs.length}. Stored notes: ${finalPreview.nextNotesLength} chars.\n`);
|
|
602
|
+
}
|
|
376
603
|
process.stdout.write(`Updated local workspace cache at ${managedCache.manifestPath}.\n`);
|
|
377
604
|
return 0;
|
|
378
605
|
}
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import test from "node:test";
|
|
2
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";
|
|
3
6
|
|
|
4
7
|
import {
|
|
5
8
|
buildLaunchPlan,
|
|
@@ -243,6 +246,51 @@ Workspace summary.
|
|
|
243
246
|
assert.doesNotMatch(preview.nextNotes, /\/Volumes\/Code_2TB\/code\/orp: codex resume/);
|
|
244
247
|
});
|
|
245
248
|
|
|
249
|
+
test("buildWorkspaceSyncPreview enriches workspace-file tabs with linked ORP project context", async () => {
|
|
250
|
+
const projectRoot = await fs.mkdtemp(path.join(os.tmpdir(), "orp-workspace-sync-frontier-"));
|
|
251
|
+
await fs.mkdir(path.join(projectRoot, ".git", "orp", "link"), { recursive: true });
|
|
252
|
+
await fs.writeFile(
|
|
253
|
+
path.join(projectRoot, ".git", "orp", "link", "project.json"),
|
|
254
|
+
JSON.stringify(
|
|
255
|
+
{
|
|
256
|
+
idea_id: "idea-linked",
|
|
257
|
+
active_feature_id: "feature-active",
|
|
258
|
+
project_root: projectRoot,
|
|
259
|
+
},
|
|
260
|
+
null,
|
|
261
|
+
2,
|
|
262
|
+
),
|
|
263
|
+
"utf8",
|
|
264
|
+
);
|
|
265
|
+
const source = {
|
|
266
|
+
sourceType: "workspace-file",
|
|
267
|
+
sourceLabel: "/tmp/workspace.json",
|
|
268
|
+
sourcePath: "/tmp/workspace.json",
|
|
269
|
+
title: "Workspace idea",
|
|
270
|
+
notes: "",
|
|
271
|
+
workspaceManifest: {
|
|
272
|
+
version: "1",
|
|
273
|
+
workspaceId: "workspace-file-demo",
|
|
274
|
+
tabs: [{ title: "Linked project", path: projectRoot }],
|
|
275
|
+
},
|
|
276
|
+
};
|
|
277
|
+
const parsed = parseWorkspaceSource(source);
|
|
278
|
+
const preview = buildWorkspaceSyncPreview({
|
|
279
|
+
source,
|
|
280
|
+
parsed,
|
|
281
|
+
targetIdea: {
|
|
282
|
+
id: "idea-123",
|
|
283
|
+
title: "Workspace idea",
|
|
284
|
+
notes: "",
|
|
285
|
+
},
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
assert.equal(preview.tabs[0]?.linkedIdeaId, "idea-linked");
|
|
289
|
+
assert.equal(preview.tabs[0]?.linkedFeatureId, "feature-active");
|
|
290
|
+
assert.match(preview.nextNotes, /"linkedIdeaId": "idea-linked"/);
|
|
291
|
+
assert.match(preview.nextNotes, /"linkedFeatureId": "feature-active"/);
|
|
292
|
+
});
|
|
293
|
+
|
|
246
294
|
test("resolveWorkspaceSyncTargetIdeaId supports hosted idea and hosted workspace sources", () => {
|
|
247
295
|
assert.equal(
|
|
248
296
|
resolveWorkspaceSyncTargetIdeaId({
|