open-research-protocol 0.4.14 → 0.4.15
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,166 @@
|
|
|
1
|
+
import process from "node:process";
|
|
2
|
+
|
|
3
|
+
import { buildDirectCommand, buildLaunchPlan, deriveWorkspaceId, getResumeCommand, parseWorkspaceSource } from "./core-plan.js";
|
|
4
|
+
import { loadWorkspaceSource } from "./orp.js";
|
|
5
|
+
|
|
6
|
+
export function parseWorkspaceTabsArgs(argv = []) {
|
|
7
|
+
const options = {
|
|
8
|
+
json: false,
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
12
|
+
const arg = argv[index];
|
|
13
|
+
|
|
14
|
+
if (arg === "-h" || arg === "--help") {
|
|
15
|
+
options.help = true;
|
|
16
|
+
continue;
|
|
17
|
+
}
|
|
18
|
+
if (arg === "--json") {
|
|
19
|
+
options.json = true;
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
if (arg.startsWith("--")) {
|
|
23
|
+
const next = argv[index + 1];
|
|
24
|
+
if (next == null || next.startsWith("--")) {
|
|
25
|
+
throw new Error(`missing value for ${arg}`);
|
|
26
|
+
}
|
|
27
|
+
if (arg === "--notes-file") {
|
|
28
|
+
options.notesFile = next;
|
|
29
|
+
} else if (arg === "--hosted-workspace-id") {
|
|
30
|
+
options.hostedWorkspaceId = next;
|
|
31
|
+
} else if (arg === "--workspace-file") {
|
|
32
|
+
options.workspaceFile = next;
|
|
33
|
+
} else if (arg === "--base-url") {
|
|
34
|
+
options.baseUrl = next;
|
|
35
|
+
} else if (arg === "--orp-command") {
|
|
36
|
+
options.orpCommand = next;
|
|
37
|
+
} else {
|
|
38
|
+
throw new Error(`unknown option: ${arg}`);
|
|
39
|
+
}
|
|
40
|
+
index += 1;
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (options.ideaId) {
|
|
45
|
+
throw new Error(`unexpected argument: ${arg}`);
|
|
46
|
+
}
|
|
47
|
+
options.ideaId = arg;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return options;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function buildWorkspaceTabsReport(source, parsed, options = {}) {
|
|
54
|
+
const launchTabs = buildLaunchPlan(parsed.entries, {
|
|
55
|
+
tmux: false,
|
|
56
|
+
resume: true,
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
sourceType: source.sourceType,
|
|
61
|
+
sourceLabel: source.sourceLabel,
|
|
62
|
+
title: parsed.manifest?.title || source.title,
|
|
63
|
+
workspaceId: deriveWorkspaceId(source, parsed),
|
|
64
|
+
parseMode: parsed.parseMode,
|
|
65
|
+
tabCount: launchTabs.length,
|
|
66
|
+
skippedCount: parsed.skipped.length,
|
|
67
|
+
tabs: launchTabs.map((tab, index) => ({
|
|
68
|
+
index: index + 1,
|
|
69
|
+
title: tab.title,
|
|
70
|
+
path: tab.path,
|
|
71
|
+
resumeCommand: getResumeCommand(tab),
|
|
72
|
+
restartCommand: buildDirectCommand(
|
|
73
|
+
{
|
|
74
|
+
path: tab.path,
|
|
75
|
+
resumeCommand: tab.resumeCommand || null,
|
|
76
|
+
resumeTool: tab.resumeTool || null,
|
|
77
|
+
resumeSessionId: tab.sessionId || null,
|
|
78
|
+
sessionId: tab.sessionId || null,
|
|
79
|
+
},
|
|
80
|
+
{ resume: true },
|
|
81
|
+
),
|
|
82
|
+
resumeTool: tab.resumeTool || null,
|
|
83
|
+
resumeSessionId: tab.sessionId || null,
|
|
84
|
+
codexSessionId: tab.resumeTool === "codex" ? tab.sessionId || null : null,
|
|
85
|
+
claudeSessionId: tab.resumeTool === "claude" ? tab.sessionId || null : null,
|
|
86
|
+
})),
|
|
87
|
+
skipped: parsed.skipped,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function summarizeWorkspaceTabs(report) {
|
|
92
|
+
const lines = [
|
|
93
|
+
`Source: ${report.sourceLabel}`,
|
|
94
|
+
`Workspace ID: ${report.workspaceId}`,
|
|
95
|
+
`Saved tabs: ${report.tabCount}`,
|
|
96
|
+
`Parse mode: ${report.parseMode}`,
|
|
97
|
+
"",
|
|
98
|
+
];
|
|
99
|
+
|
|
100
|
+
for (const tab of report.tabs) {
|
|
101
|
+
lines.push(`${String(tab.index).padStart(2, "0")}. ${tab.title}`);
|
|
102
|
+
lines.push(` path: ${tab.path}`);
|
|
103
|
+
if (tab.resumeCommand) {
|
|
104
|
+
lines.push(` resume: ${tab.restartCommand}`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (report.skipped.length > 0) {
|
|
109
|
+
lines.push("");
|
|
110
|
+
lines.push("Skipped lines:");
|
|
111
|
+
for (const skipped of report.skipped) {
|
|
112
|
+
lines.push(` line ${skipped.lineNumber}: ${skipped.rawLine}`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return lines.join("\n");
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function printWorkspaceTabsHelp() {
|
|
120
|
+
console.log(`ORP workspace tabs
|
|
121
|
+
|
|
122
|
+
Usage:
|
|
123
|
+
orp workspace tabs <name-or-id> [--json]
|
|
124
|
+
orp workspace tabs --hosted-workspace-id <workspace-id> [--json]
|
|
125
|
+
orp workspace tabs --notes-file <path> [--json]
|
|
126
|
+
orp workspace tabs --workspace-file <path> [--json]
|
|
127
|
+
|
|
128
|
+
Options:
|
|
129
|
+
--json Print saved tab metadata as JSON
|
|
130
|
+
--hosted-workspace-id <id> Read a first-class hosted workspace instead of an idea
|
|
131
|
+
--notes-file <path> Read a local notes file instead of ORP
|
|
132
|
+
--workspace-file <path> Read a structured workspace manifest JSON file
|
|
133
|
+
--base-url <url> Override the ORP hosted base URL
|
|
134
|
+
--orp-command <cmd> Override the ORP CLI executable used for hosted fetches
|
|
135
|
+
-h, --help Show this help text
|
|
136
|
+
|
|
137
|
+
Notes:
|
|
138
|
+
- This shows the saved tab order plus any stored \`codex resume ...\` or \`claude --resume ...\` command metadata.
|
|
139
|
+
- The human-readable \`resume:\` line is already copyable and includes the saved \`cd ... && resume ...\` recovery command.
|
|
140
|
+
- 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
|
+
`);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export async function runWorkspaceTabs(argv = process.argv.slice(2)) {
|
|
145
|
+
const options = parseWorkspaceTabsArgs(argv);
|
|
146
|
+
if (options.help) {
|
|
147
|
+
printWorkspaceTabsHelp();
|
|
148
|
+
return 0;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const source = await loadWorkspaceSource(options);
|
|
152
|
+
const parsed = parseWorkspaceSource(source);
|
|
153
|
+
const report = buildWorkspaceTabsReport(source, parsed, options);
|
|
154
|
+
|
|
155
|
+
if (report.tabCount === 0) {
|
|
156
|
+
throw new Error("No saved tabs were found in the provided workspace source.");
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (options.json) {
|
|
160
|
+
process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
|
|
161
|
+
return 0;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
process.stdout.write(`${summarizeWorkspaceTabs(report)}\n`);
|
|
165
|
+
return 0;
|
|
166
|
+
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import fs from "node:fs/promises";
|
|
4
|
+
import os from "node:os";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
buildWorkspaceCommandsReport,
|
|
9
|
+
parseWorkspaceCommandsArgs,
|
|
10
|
+
parseWorkspaceSource,
|
|
11
|
+
runWorkspaceCommands,
|
|
12
|
+
} from "../src/index.js";
|
|
13
|
+
|
|
14
|
+
async function makeTempDir() {
|
|
15
|
+
return fs.mkdtemp(path.join(os.tmpdir(), "orp-workspace-commands-"));
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async function captureStdout(fn) {
|
|
19
|
+
const chunks = [];
|
|
20
|
+
const originalWrite = process.stdout.write;
|
|
21
|
+
process.stdout.write = (chunk, encoding, callback) => {
|
|
22
|
+
chunks.push(typeof chunk === "string" ? chunk : chunk.toString(encoding || "utf8"));
|
|
23
|
+
if (typeof callback === "function") {
|
|
24
|
+
callback();
|
|
25
|
+
}
|
|
26
|
+
return true;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
const code = await fn();
|
|
31
|
+
return {
|
|
32
|
+
code,
|
|
33
|
+
stdout: chunks.join(""),
|
|
34
|
+
};
|
|
35
|
+
} finally {
|
|
36
|
+
process.stdout.write = originalWrite;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
test("parseWorkspaceCommandsArgs accepts JSON and workspace selectors", () => {
|
|
41
|
+
const parsed = parseWorkspaceCommandsArgs([
|
|
42
|
+
"--workspace-file",
|
|
43
|
+
"/tmp/workspace.json",
|
|
44
|
+
"--json",
|
|
45
|
+
]);
|
|
46
|
+
|
|
47
|
+
assert.equal(parsed.workspaceFile, "/tmp/workspace.json");
|
|
48
|
+
assert.equal(parsed.json, true);
|
|
49
|
+
assert.throws(() => parseWorkspaceCommandsArgs(["idea-123", "idea-456"]), /unexpected argument/);
|
|
50
|
+
assert.throws(() => parseWorkspaceCommandsArgs(["--wat"]), /missing value|unknown option/);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test("buildWorkspaceCommandsReport exposes direct restart commands and exact saved resume commands", () => {
|
|
54
|
+
const parsed = parseWorkspaceSource({
|
|
55
|
+
sourceType: "hosted-idea",
|
|
56
|
+
sourceLabel: "Workspace idea",
|
|
57
|
+
title: "Workspace idea",
|
|
58
|
+
notes: `
|
|
59
|
+
/Volumes/Code_2TB/code/collaboration: codex resume abc-123
|
|
60
|
+
/Volumes/Code_2TB/code/anthropic-lab: claude resume claude-456
|
|
61
|
+
/Volumes/Code_2TB/code/collaboration
|
|
62
|
+
`,
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
const report = buildWorkspaceCommandsReport(
|
|
66
|
+
{
|
|
67
|
+
sourceType: "hosted-idea",
|
|
68
|
+
sourceLabel: "Workspace idea",
|
|
69
|
+
title: "Workspace idea",
|
|
70
|
+
},
|
|
71
|
+
parsed,
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
assert.equal(report.commandCount, 3);
|
|
75
|
+
assert.equal(report.tabs[0]?.resumeCommand, "codex resume abc-123");
|
|
76
|
+
assert.equal(report.tabs[0]?.restartCommand, "cd '/Volumes/Code_2TB/code/collaboration' && codex resume abc-123");
|
|
77
|
+
assert.equal(report.tabs[1]?.resumeCommand, "claude resume claude-456");
|
|
78
|
+
assert.equal(
|
|
79
|
+
report.tabs[1]?.restartCommand,
|
|
80
|
+
"cd '/Volumes/Code_2TB/code/anthropic-lab' && claude resume claude-456",
|
|
81
|
+
);
|
|
82
|
+
assert.equal(report.tabs[2]?.restartCommand, "cd '/Volumes/Code_2TB/code/collaboration'");
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test("runWorkspaceCommands prints JSON with copyable commands", async () => {
|
|
86
|
+
const tempDir = await makeTempDir();
|
|
87
|
+
const manifestPath = path.join(tempDir, "workspace.json");
|
|
88
|
+
await fs.writeFile(
|
|
89
|
+
manifestPath,
|
|
90
|
+
`${JSON.stringify(
|
|
91
|
+
{
|
|
92
|
+
version: "1",
|
|
93
|
+
workspaceId: "orp-main",
|
|
94
|
+
title: "ORP Main",
|
|
95
|
+
tabs: [
|
|
96
|
+
{
|
|
97
|
+
title: "orp",
|
|
98
|
+
path: "/Volumes/Code_2TB/code/orp",
|
|
99
|
+
resumeCommand: "claude resume claude-999",
|
|
100
|
+
resumeTool: "claude",
|
|
101
|
+
resumeSessionId: "claude-999",
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
title: "web",
|
|
105
|
+
path: "/Volumes/Code_2TB/code/orp-web-app",
|
|
106
|
+
},
|
|
107
|
+
],
|
|
108
|
+
},
|
|
109
|
+
null,
|
|
110
|
+
2,
|
|
111
|
+
)}\n`,
|
|
112
|
+
"utf8",
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
const { code, stdout } = await captureStdout(() =>
|
|
116
|
+
runWorkspaceCommands(["--workspace-file", manifestPath, "--json"]),
|
|
117
|
+
);
|
|
118
|
+
const parsed = JSON.parse(stdout);
|
|
119
|
+
|
|
120
|
+
assert.equal(code, 0);
|
|
121
|
+
assert.equal(parsed.workspaceId, "orp-main");
|
|
122
|
+
assert.equal(parsed.commandCount, 2);
|
|
123
|
+
assert.equal(parsed.tabs[0]?.resumeCommand, "claude resume claude-999");
|
|
124
|
+
assert.equal(parsed.tabs[0]?.claudeSessionId, "claude-999");
|
|
125
|
+
assert.equal(parsed.tabs[0]?.restartCommand, "cd '/Volumes/Code_2TB/code/orp' && claude resume claude-999");
|
|
126
|
+
assert.equal(parsed.tabs[1]?.restartCommand, "cd '/Volumes/Code_2TB/code/orp-web-app'");
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
test("buildWorkspaceCommandsReport canonicalizes Claude restart commands from tool and session metadata", () => {
|
|
130
|
+
const parsed = parseWorkspaceSource({
|
|
131
|
+
sourceType: "workspace-file",
|
|
132
|
+
sourceLabel: "/tmp/workspace.json",
|
|
133
|
+
title: "workspace",
|
|
134
|
+
workspaceManifest: {
|
|
135
|
+
version: "1",
|
|
136
|
+
workspaceId: "orp-main",
|
|
137
|
+
tabs: [
|
|
138
|
+
{
|
|
139
|
+
title: "anthropic-lab",
|
|
140
|
+
path: "/Volumes/Code_2TB/code/anthropic-lab",
|
|
141
|
+
resumeTool: "claude",
|
|
142
|
+
resumeSessionId: "claude-456",
|
|
143
|
+
},
|
|
144
|
+
],
|
|
145
|
+
notes: "",
|
|
146
|
+
},
|
|
147
|
+
notes: "",
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
const report = buildWorkspaceCommandsReport(
|
|
151
|
+
{
|
|
152
|
+
sourceType: "workspace-file",
|
|
153
|
+
sourceLabel: "/tmp/workspace.json",
|
|
154
|
+
title: "workspace",
|
|
155
|
+
},
|
|
156
|
+
parsed,
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
assert.equal(report.tabs[0]?.resumeCommand, "claude --resume claude-456");
|
|
160
|
+
assert.equal(
|
|
161
|
+
report.tabs[0]?.restartCommand,
|
|
162
|
+
"cd '/Volumes/Code_2TB/code/anthropic-lab' && claude --resume claude-456",
|
|
163
|
+
);
|
|
164
|
+
});
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
buildLaunchPlan,
|
|
6
|
+
buildWorkspaceSyncPreview,
|
|
7
|
+
deriveBaseTitle,
|
|
8
|
+
extractStructuredWorkspaceFromNotes,
|
|
9
|
+
normalizeWorkspaceManifest,
|
|
10
|
+
parseCorePlanNotes,
|
|
11
|
+
parseWorkspaceSource,
|
|
12
|
+
resolveWorkspaceSyncTargetIdeaId,
|
|
13
|
+
} from "../src/index.js";
|
|
14
|
+
import { extractWorkspaceNarrativeNotes } from "../src/sync.js";
|
|
15
|
+
|
|
16
|
+
test("parseCorePlanNotes extracts paths and generic resume commands", () => {
|
|
17
|
+
const notes = `
|
|
18
|
+
/Volumes/Code_2TB/code/orp-web-app: codex resume 019ce2ee-6083-7d02-9d3a-d27a761132a8
|
|
19
|
+
/Volumes/Code_2TB/code/anthropic-lab: claude resume claude-456
|
|
20
|
+
|
|
21
|
+
/Volumes/Code_2TB/code/care-evidence-support
|
|
22
|
+
not a path
|
|
23
|
+
`;
|
|
24
|
+
|
|
25
|
+
const parsed = parseCorePlanNotes(notes);
|
|
26
|
+
assert.equal(parsed.entries.length, 3);
|
|
27
|
+
assert.deepEqual(
|
|
28
|
+
parsed.entries.map((entry) => ({
|
|
29
|
+
path: entry.path,
|
|
30
|
+
resumeCommand: entry.resumeCommand,
|
|
31
|
+
resumeTool: entry.resumeTool,
|
|
32
|
+
sessionId: entry.sessionId,
|
|
33
|
+
})),
|
|
34
|
+
[
|
|
35
|
+
{
|
|
36
|
+
path: "/Volumes/Code_2TB/code/orp-web-app",
|
|
37
|
+
resumeCommand: "codex resume 019ce2ee-6083-7d02-9d3a-d27a761132a8",
|
|
38
|
+
resumeTool: "codex",
|
|
39
|
+
sessionId: "019ce2ee-6083-7d02-9d3a-d27a761132a8",
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
path: "/Volumes/Code_2TB/code/anthropic-lab",
|
|
43
|
+
resumeCommand: "claude resume claude-456",
|
|
44
|
+
resumeTool: "claude",
|
|
45
|
+
sessionId: "claude-456",
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
path: "/Volumes/Code_2TB/code/care-evidence-support",
|
|
49
|
+
resumeCommand: null,
|
|
50
|
+
resumeTool: null,
|
|
51
|
+
sessionId: null,
|
|
52
|
+
},
|
|
53
|
+
],
|
|
54
|
+
);
|
|
55
|
+
assert.equal(parsed.skipped.length, 1);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test("parseCorePlanNotes accepts real Claude resume flag syntax", () => {
|
|
59
|
+
const parsed = parseCorePlanNotes(`
|
|
60
|
+
/Volumes/Code_2TB/code/anthropic-lab: claude --resume claude-456
|
|
61
|
+
`);
|
|
62
|
+
|
|
63
|
+
assert.equal(parsed.entries.length, 1);
|
|
64
|
+
assert.equal(parsed.entries[0]?.resumeCommand, "claude --resume claude-456");
|
|
65
|
+
assert.equal(parsed.entries[0]?.resumeTool, "claude");
|
|
66
|
+
assert.equal(parsed.entries[0]?.sessionId, "claude-456");
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test("buildLaunchPlan keeps duplicate titles unique and preserves exact resume commands", () => {
|
|
70
|
+
const parsed = parseCorePlanNotes(`
|
|
71
|
+
/Volumes/Code_2TB/code/collaboration: codex resume abc-123
|
|
72
|
+
/Volumes/Code_2TB/code/anthropic-lab: claude resume claude-456
|
|
73
|
+
/Volumes/Code_2TB/code/collaboration
|
|
74
|
+
`);
|
|
75
|
+
|
|
76
|
+
const plan = buildLaunchPlan(parsed.entries, {
|
|
77
|
+
tmux: false,
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
assert.equal(plan[0].title, "collaboration");
|
|
81
|
+
assert.equal(plan[1].title, "anthropic-lab");
|
|
82
|
+
assert.equal(plan[2].title, "collaboration (2)");
|
|
83
|
+
assert.equal(plan[0].resumeCommand, "codex resume abc-123");
|
|
84
|
+
assert.equal(plan[1].resumeCommand, "claude resume claude-456");
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test("structured workspace blocks are extracted from notes", () => {
|
|
88
|
+
const notes = `
|
|
89
|
+
Some overview text.
|
|
90
|
+
|
|
91
|
+
\`\`\`orp-workspace
|
|
92
|
+
{
|
|
93
|
+
"version": "1",
|
|
94
|
+
"workspaceId": "workspace-idea",
|
|
95
|
+
"tabs": [
|
|
96
|
+
{ "title": "orp", "path": "/Volumes/Code_2TB/code/orp" },
|
|
97
|
+
{ "title": "web", "path": "/Volumes/Code_2TB/code/orp-web-app", "resumeCommand": "claude resume claude-456", "resumeTool": "claude", "resumeSessionId": "claude-456" }
|
|
98
|
+
]
|
|
99
|
+
}
|
|
100
|
+
\`\`\`
|
|
101
|
+
`;
|
|
102
|
+
const manifest = extractStructuredWorkspaceFromNotes(notes);
|
|
103
|
+
const parsed = parseWorkspaceSource({
|
|
104
|
+
sourceType: "hosted-idea",
|
|
105
|
+
sourceLabel: "Workspace idea",
|
|
106
|
+
notes,
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
assert.equal(manifest.workspaceId, "workspace-idea");
|
|
110
|
+
assert.equal(parsed.parseMode, "manifest");
|
|
111
|
+
assert.equal(parsed.entries.length, 2);
|
|
112
|
+
assert.equal(parsed.entries[1]?.sessionId, "claude-456");
|
|
113
|
+
assert.equal(parsed.entries[1]?.resumeCommand, "claude resume claude-456");
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test("normalizeWorkspaceManifest keeps ledger fields and strips nothing important", () => {
|
|
117
|
+
const manifest = normalizeWorkspaceManifest({
|
|
118
|
+
version: "1",
|
|
119
|
+
workspaceId: "terminal-paths",
|
|
120
|
+
title: "Terminal Paths",
|
|
121
|
+
tabs: [
|
|
122
|
+
{
|
|
123
|
+
title: "orp",
|
|
124
|
+
path: "/Volumes/Code_2TB/code/orp",
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
title: "lab",
|
|
128
|
+
path: "/Volumes/Code_2TB/code/anthropic-lab",
|
|
129
|
+
resumeTool: "claude",
|
|
130
|
+
resumeSessionId: "claude-456",
|
|
131
|
+
},
|
|
132
|
+
],
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
assert.equal(manifest.workspaceId, "terminal-paths");
|
|
136
|
+
assert.equal(manifest.title, "Terminal Paths");
|
|
137
|
+
assert.equal(manifest.tabs[0]?.title, "orp");
|
|
138
|
+
assert.equal(manifest.tabs[1]?.resumeTool, "claude");
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
test("extractWorkspaceNarrativeNotes removes structured workspace blocks and legacy path lines", () => {
|
|
142
|
+
const notes = `
|
|
143
|
+
Workspace summary.
|
|
144
|
+
|
|
145
|
+
/Volumes/Code_2TB/code/orp
|
|
146
|
+
|
|
147
|
+
\`\`\`orp-workspace
|
|
148
|
+
{
|
|
149
|
+
"version": "1",
|
|
150
|
+
"workspaceId": "workspace-demo",
|
|
151
|
+
"tabs": [
|
|
152
|
+
{ "path": "/Volumes/Code_2TB/code/orp" }
|
|
153
|
+
]
|
|
154
|
+
}
|
|
155
|
+
\`\`\`
|
|
156
|
+
`;
|
|
157
|
+
|
|
158
|
+
assert.equal(
|
|
159
|
+
extractWorkspaceNarrativeNotes(notes, { stripLegacyWorkspaceLines: true }),
|
|
160
|
+
"Workspace summary.",
|
|
161
|
+
);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
test("buildWorkspaceSyncPreview converts path notes into a structured workspace block", () => {
|
|
165
|
+
const source = {
|
|
166
|
+
sourceType: "hosted-idea",
|
|
167
|
+
sourceLabel: "Workspace idea",
|
|
168
|
+
title: "Workspace idea",
|
|
169
|
+
notes: `
|
|
170
|
+
Workspace summary.
|
|
171
|
+
|
|
172
|
+
/Volumes/Code_2TB/code/orp: codex resume abc-123
|
|
173
|
+
/Volumes/Code_2TB/code/orp-web-app
|
|
174
|
+
`,
|
|
175
|
+
idea: { id: "idea-123" },
|
|
176
|
+
};
|
|
177
|
+
const parsed = parseWorkspaceSource(source);
|
|
178
|
+
const preview = buildWorkspaceSyncPreview({
|
|
179
|
+
source,
|
|
180
|
+
parsed,
|
|
181
|
+
targetIdea: {
|
|
182
|
+
id: "idea-123",
|
|
183
|
+
title: "Workspace idea",
|
|
184
|
+
notes: source.notes,
|
|
185
|
+
},
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
assert.equal(preview.workspaceId, "idea-idea-123");
|
|
189
|
+
assert.equal(preview.tabs.length, 2);
|
|
190
|
+
assert.equal(preview.tabs[0]?.resumeCommand, "codex resume abc-123");
|
|
191
|
+
assert.equal(preview.tabs[1]?.title, deriveBaseTitle(preview.tabs[1]));
|
|
192
|
+
assert.match(preview.nextNotes, /```orp-workspace/);
|
|
193
|
+
assert.match(preview.nextNotes, /"workspaceId": "idea-idea-123"/);
|
|
194
|
+
assert.match(preview.nextNotes, /Workspace summary\./);
|
|
195
|
+
assert.match(preview.nextNotes, /\/Volumes\/Code_2TB\/code\/orp: codex resume abc-123/);
|
|
196
|
+
assert.match(preview.nextNotes, /\/Volumes\/Code_2TB\/code\/orp-web-app/);
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
test("buildWorkspaceSyncPreview strips stale legacy path lines when syncing from a workspace file", () => {
|
|
200
|
+
const source = {
|
|
201
|
+
sourceType: "workspace-file",
|
|
202
|
+
sourceLabel: "/tmp/workspace.json",
|
|
203
|
+
sourcePath: "/tmp/workspace.json",
|
|
204
|
+
title: "Workspace idea",
|
|
205
|
+
notes: "",
|
|
206
|
+
workspaceManifest: {
|
|
207
|
+
version: "1",
|
|
208
|
+
workspaceId: "workspace-file-demo",
|
|
209
|
+
tabs: [{ title: "orp", path: "/Volumes/Code_2TB/code/orp" }],
|
|
210
|
+
},
|
|
211
|
+
};
|
|
212
|
+
const parsed = parseWorkspaceSource(source);
|
|
213
|
+
const preview = buildWorkspaceSyncPreview({
|
|
214
|
+
source,
|
|
215
|
+
parsed,
|
|
216
|
+
targetIdea: {
|
|
217
|
+
id: "idea-123",
|
|
218
|
+
title: "Workspace idea",
|
|
219
|
+
notes: `
|
|
220
|
+
Workspace summary.
|
|
221
|
+
|
|
222
|
+
/Volumes/Code_2TB/code/orp: codex resume stale-session
|
|
223
|
+
`,
|
|
224
|
+
},
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
assert.match(preview.nextNotes, /Workspace summary\./);
|
|
228
|
+
assert.match(preview.nextNotes, /```orp-workspace/);
|
|
229
|
+
assert.doesNotMatch(preview.nextNotes, /stale-session/);
|
|
230
|
+
assert.doesNotMatch(preview.nextNotes, /\/Volumes\/Code_2TB\/code\/orp: codex resume/);
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
test("resolveWorkspaceSyncTargetIdeaId supports hosted idea and hosted workspace sources", () => {
|
|
234
|
+
assert.equal(
|
|
235
|
+
resolveWorkspaceSyncTargetIdeaId({
|
|
236
|
+
sourceType: "hosted-idea",
|
|
237
|
+
idea: { id: "idea-123" },
|
|
238
|
+
}),
|
|
239
|
+
"idea-123",
|
|
240
|
+
);
|
|
241
|
+
|
|
242
|
+
assert.equal(
|
|
243
|
+
resolveWorkspaceSyncTargetIdeaId({
|
|
244
|
+
sourceType: "hosted-workspace",
|
|
245
|
+
hostedWorkspace: {
|
|
246
|
+
linkedIdea: {
|
|
247
|
+
ideaId: "idea-456",
|
|
248
|
+
},
|
|
249
|
+
},
|
|
250
|
+
}),
|
|
251
|
+
"idea-456",
|
|
252
|
+
);
|
|
253
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "1",
|
|
3
|
+
"workspaceId": "terminal-paths-and-codex-sessions",
|
|
4
|
+
"title": "Terminal paths and codex sessions",
|
|
5
|
+
"tmuxPrefix": "backtothefort",
|
|
6
|
+
"tabs": [
|
|
7
|
+
{
|
|
8
|
+
"title": "orp",
|
|
9
|
+
"path": "/Volumes/Code_2TB/code/orp"
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"title": "orp-web-app",
|
|
13
|
+
"path": "/Volumes/Code_2TB/code/orp-web-app",
|
|
14
|
+
"codexSessionId": "019ce2ee-6083-7d02-9d3a-d27a761132a8"
|
|
15
|
+
}
|
|
16
|
+
]
|
|
17
|
+
}
|