iosm-cli 0.2.15 → 0.2.17
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 +39 -0
- package/README.md +23 -1
- package/dist/cli/args.d.ts +2 -1
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +16 -3
- package/dist/cli/args.js.map +1 -1
- package/dist/core/agent-profiles.d.ts.map +1 -1
- package/dist/core/agent-profiles.js +3 -0
- package/dist/core/agent-profiles.js.map +1 -1
- package/dist/core/agent-session.d.ts +2 -0
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +26 -10
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/agent-teams.d.ts.map +1 -1
- package/dist/core/agent-teams.js +12 -9
- package/dist/core/agent-teams.js.map +1 -1
- package/dist/core/checkpoint/fs-checkpoint.d.ts +14 -0
- package/dist/core/checkpoint/fs-checkpoint.d.ts.map +1 -0
- package/dist/core/checkpoint/fs-checkpoint.js +211 -0
- package/dist/core/checkpoint/fs-checkpoint.js.map +1 -0
- package/dist/core/command-dispatcher.d.ts +2 -0
- package/dist/core/command-dispatcher.d.ts.map +1 -1
- package/dist/core/command-dispatcher.js +78 -13
- package/dist/core/command-dispatcher.js.map +1 -1
- package/dist/core/mcp/cli.d.ts.map +1 -1
- package/dist/core/mcp/cli.js +26 -0
- package/dist/core/mcp/cli.js.map +1 -1
- package/dist/core/mcp/config.d.ts.map +1 -1
- package/dist/core/mcp/config.js +55 -0
- package/dist/core/mcp/config.js.map +1 -1
- package/dist/core/mcp/index.d.ts +1 -1
- package/dist/core/mcp/index.d.ts.map +1 -1
- package/dist/core/mcp/index.js.map +1 -1
- package/dist/core/mcp/runtime.d.ts +3 -1
- package/dist/core/mcp/runtime.d.ts.map +1 -1
- package/dist/core/mcp/runtime.js +21 -2
- package/dist/core/mcp/runtime.js.map +1 -1
- package/dist/core/mcp/types.d.ts +30 -2
- package/dist/core/mcp/types.d.ts.map +1 -1
- package/dist/core/mcp/types.js.map +1 -1
- package/dist/core/package-manager.d.ts +10 -0
- package/dist/core/package-manager.d.ts.map +1 -1
- package/dist/core/package-manager.js +100 -2
- package/dist/core/package-manager.js.map +1 -1
- package/dist/core/policy/engine.d.ts +77 -0
- package/dist/core/policy/engine.d.ts.map +1 -0
- package/dist/core/policy/engine.js +614 -0
- package/dist/core/policy/engine.js.map +1 -0
- package/dist/core/policy/index.d.ts +2 -0
- package/dist/core/policy/index.d.ts.map +1 -0
- package/dist/core/policy/index.js +2 -0
- package/dist/core/policy/index.js.map +1 -0
- package/dist/core/sandbox/executor.d.ts +13 -0
- package/dist/core/sandbox/executor.d.ts.map +1 -0
- package/dist/core/sandbox/executor.js +64 -0
- package/dist/core/sandbox/executor.js.map +1 -0
- package/dist/core/sdk.d.ts +2 -2
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +3 -3
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/security/index.d.ts +3 -0
- package/dist/core/security/index.d.ts.map +1 -0
- package/dist/core/security/index.js +3 -0
- package/dist/core/security/index.js.map +1 -0
- package/dist/core/security/source-security.d.ts +43 -0
- package/dist/core/security/source-security.d.ts.map +1 -0
- package/dist/core/security/source-security.js +94 -0
- package/dist/core/security/source-security.js.map +1 -0
- package/dist/core/security/trust-ledger.d.ts +24 -0
- package/dist/core/security/trust-ledger.d.ts.map +1 -0
- package/dist/core/security/trust-ledger.js +66 -0
- package/dist/core/security/trust-ledger.js.map +1 -0
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +128 -15
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/settings-manager.d.ts +6 -0
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +36 -1
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/settings.schema.json +71 -0
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +9 -2
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/core/task-plan.d.ts +1 -0
- package/dist/core/task-plan.d.ts.map +1 -1
- package/dist/core/task-plan.js +103 -0
- package/dist/core/task-plan.js.map +1 -1
- package/dist/core/tools/apply-patch.d.ts +29 -0
- package/dist/core/tools/apply-patch.d.ts.map +1 -0
- package/dist/core/tools/apply-patch.js +167 -0
- package/dist/core/tools/apply-patch.js.map +1 -0
- package/dist/core/tools/bash.d.ts.map +1 -1
- package/dist/core/tools/bash.js +15 -1
- package/dist/core/tools/bash.js.map +1 -1
- package/dist/core/tools/git-common.d.ts.map +1 -1
- package/dist/core/tools/git-common.js +15 -1
- package/dist/core/tools/git-common.js.map +1 -1
- package/dist/core/tools/index.d.ts +20 -2
- package/dist/core/tools/index.d.ts.map +1 -1
- package/dist/core/tools/index.js +85 -25
- package/dist/core/tools/index.js.map +1 -1
- package/dist/core/tools/permissions.d.ts +16 -0
- package/dist/core/tools/permissions.d.ts.map +1 -1
- package/dist/core/tools/permissions.js +34 -1
- package/dist/core/tools/permissions.js.map +1 -1
- package/dist/core/tools/task.d.ts.map +1 -1
- package/dist/core/tools/task.js +68 -24
- package/dist/core/tools/task.js.map +1 -1
- package/dist/core/tools/tool-search.d.ts +24 -0
- package/dist/core/tools/tool-search.d.ts.map +1 -0
- package/dist/core/tools/tool-search.js +85 -0
- package/dist/core/tools/tool-search.js.map +1 -0
- package/dist/core/tools/tool-suggest.d.ts +18 -0
- package/dist/core/tools/tool-suggest.d.ts.map +1 -0
- package/dist/core/tools/tool-suggest.js +94 -0
- package/dist/core/tools/tool-suggest.js.map +1 -0
- package/dist/core/tools/verification-runner.d.ts.map +1 -1
- package/dist/core/tools/verification-runner.js +15 -1
- package/dist/core/tools/verification-runner.js.map +1 -1
- package/dist/core/unified-exec.d.ts +39 -0
- package/dist/core/unified-exec.d.ts.map +1 -0
- package/dist/core/unified-exec.js +286 -0
- package/dist/core/unified-exec.js.map +1 -0
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +93 -11
- package/dist/main.js.map +1 -1
- package/dist/modes/acp/acp-mode.d.ts +17 -0
- package/dist/modes/acp/acp-mode.d.ts.map +1 -0
- package/dist/modes/acp/acp-mode.js +352 -0
- package/dist/modes/acp/acp-mode.js.map +1 -0
- package/dist/modes/index.d.ts +2 -1
- package/dist/modes/index.d.ts.map +1 -1
- package/dist/modes/index.js +1 -0
- package/dist/modes/index.js.map +1 -1
- package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/tool-execution.js +217 -0
- package/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +8 -0
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +159 -72
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-client.d.ts +25 -1
- package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-client.js +33 -0
- package/dist/modes/rpc/rpc-client.js.map +1 -1
- package/dist/modes/rpc/rpc-mode.d.ts +13 -1
- package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-mode.js +189 -28
- package/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-types.d.ts +54 -1
- package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-types.js.map +1 -1
- package/dist/utils/tools-manager.d.ts.map +1 -1
- package/dist/utils/tools-manager.js +96 -9
- package/dist/utils/tools-manager.js.map +1 -1
- package/docs/README.md +2 -0
- package/docs/acp-rpc-mapping.md +38 -0
- package/docs/configuration.generated.md +81 -0
- package/docs/configuration.md +7 -0
- package/package.json +6 -3
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent-teams.d.ts","sourceRoot":"","sources":["../../src/core/agent-teams.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,cAAc,GAAG,SAAS,GAAG,SAAS,GAAG,MAAM,GAAG,OAAO,GAAG,WAAW,CAAC;AAEpF,MAAM,WAAW,cAAc;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,MAAM,EAAE,cAAc,CAAC;CACvB;AAED,MAAM,WAAW,aAAa;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,UAAU,GAAG,YAAY,CAAC;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,cAAc,EAAE,CAAC;CACxB;
|
|
1
|
+
{"version":3,"file":"agent-teams.d.ts","sourceRoot":"","sources":["../../src/core/agent-teams.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,cAAc,GAAG,SAAS,GAAG,SAAS,GAAG,MAAM,GAAG,OAAO,GAAG,WAAW,CAAC;AAEpF,MAAM,WAAW,cAAc;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,MAAM,EAAE,cAAc,CAAC;CACvB;AAED,MAAM,WAAW,aAAa;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,UAAU,GAAG,YAAY,CAAC;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,cAAc,EAAE,CAAC;CACxB;AAiGD,wBAAgB,aAAa,CAAC,KAAK,EAAE;IACpC,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,UAAU,GAAG,YAAY,CAAC;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,KAAK,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC,CAAC;CAC5F,GAAG,aAAa,CA2BhB;AAED,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS,CAQhF;AAED,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,SAAK,GAAG,aAAa,EAAE,CAkBrE;AAyCD,wBAAgB,oBAAoB,CAAC,KAAK,EAAE;IAC3C,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,cAAc,CAAC;CACvB,GAAG,aAAa,GAAG,SAAS,CAY5B"}
|
package/dist/core/agent-teams.js
CHANGED
|
@@ -7,8 +7,8 @@ function getTeamsDir(cwd) {
|
|
|
7
7
|
function getTeamRunPath(cwd, runId) {
|
|
8
8
|
return join(getTeamsDir(cwd), `${runId}.json`);
|
|
9
9
|
}
|
|
10
|
-
const
|
|
11
|
-
const
|
|
10
|
+
const statusUpdateRetryBaseDelayMs = 25;
|
|
11
|
+
const statusUpdateRetryMaxDelayMs = 1_000;
|
|
12
12
|
const pendingStatusUpdates = new Map();
|
|
13
13
|
let pendingStatusFlushTimer;
|
|
14
14
|
function isTerminalStatus(status) {
|
|
@@ -28,7 +28,11 @@ function shouldReplacePendingStatus(current, next) {
|
|
|
28
28
|
function pendingStatusKey(input) {
|
|
29
29
|
return `${resolve(input.cwd).toLowerCase()}::${input.runId}::${input.taskId}`;
|
|
30
30
|
}
|
|
31
|
-
function
|
|
31
|
+
function statusRetryDelayMs(attempts) {
|
|
32
|
+
const exponent = Math.min(6, Math.max(0, attempts));
|
|
33
|
+
return Math.min(statusUpdateRetryBaseDelayMs * 2 ** exponent, statusUpdateRetryMaxDelayMs);
|
|
34
|
+
}
|
|
35
|
+
function schedulePendingStatusFlush(delayMs = statusUpdateRetryBaseDelayMs) {
|
|
32
36
|
if (pendingStatusFlushTimer)
|
|
33
37
|
return;
|
|
34
38
|
pendingStatusFlushTimer = setTimeout(() => {
|
|
@@ -55,23 +59,22 @@ function queuePendingStatusUpdate(input) {
|
|
|
55
59
|
function flushPendingStatusUpdates() {
|
|
56
60
|
if (pendingStatusUpdates.size === 0)
|
|
57
61
|
return;
|
|
62
|
+
let nextDelayMs = statusUpdateRetryMaxDelayMs;
|
|
58
63
|
for (const [key, pending] of Array.from(pendingStatusUpdates.entries())) {
|
|
59
|
-
if (pending.attempts >= maxQueuedStatusAttempts) {
|
|
60
|
-
pendingStatusUpdates.delete(key);
|
|
61
|
-
continue;
|
|
62
|
-
}
|
|
63
64
|
const result = tryUpdateTeamTaskStatus(pending.input);
|
|
64
65
|
if (result === "locked") {
|
|
66
|
+
const attempts = pending.attempts + 1;
|
|
65
67
|
pendingStatusUpdates.set(key, {
|
|
66
68
|
input: pending.input,
|
|
67
|
-
attempts
|
|
69
|
+
attempts,
|
|
68
70
|
});
|
|
71
|
+
nextDelayMs = Math.min(nextDelayMs, statusRetryDelayMs(attempts));
|
|
69
72
|
continue;
|
|
70
73
|
}
|
|
71
74
|
pendingStatusUpdates.delete(key);
|
|
72
75
|
}
|
|
73
76
|
if (pendingStatusUpdates.size > 0) {
|
|
74
|
-
schedulePendingStatusFlush();
|
|
77
|
+
schedulePendingStatusFlush(nextDelayMs);
|
|
75
78
|
}
|
|
76
79
|
}
|
|
77
80
|
export function createTeamRun(input) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent-teams.js","sourceRoot":"","sources":["../../src/core/agent-teams.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from \"node:fs\";\nimport { join, resolve } from \"node:path\";\nimport lockfile from \"proper-lockfile\";\n\nexport type TeamTaskStatus = \"pending\" | \"running\" | \"done\" | \"error\" | \"cancelled\";\n\nexport interface TeamTaskRecord {\n\tid: string;\n\tagentIndex: number;\n\tprofile: string;\n\tcwd: string;\n\tlockKey?: string;\n\tdependsOn: string[];\n\tstatus: TeamTaskStatus;\n}\n\nexport interface TeamRunRecord {\n\trunId: string;\n\tcreatedAt: string;\n\tmode: \"parallel\" | \"sequential\";\n\tagents: number;\n\tmaxParallel?: number;\n\ttask: string;\n\ttasks: TeamTaskRecord[];\n}\n\nfunction getTeamsDir(cwd: string): string {\n\treturn join(cwd, \".iosm\", \"subagents\", \"teams\");\n}\n\nfunction getTeamRunPath(cwd: string, runId: string): string {\n\treturn join(getTeamsDir(cwd), `${runId}.json`);\n}\n\ntype PendingStatusUpdate = {\n\tinput: {\n\t\tcwd: string;\n\t\trunId: string;\n\t\ttaskId: string;\n\t\tstatus: TeamTaskStatus;\n\t};\n\tattempts: number;\n};\n\nconst statusUpdateRetryDelayMs = 25;\nconst maxQueuedStatusAttempts = 50;\nconst pendingStatusUpdates = new Map<string, PendingStatusUpdate>();\nlet pendingStatusFlushTimer: ReturnType<typeof setTimeout> | undefined;\n\nfunction isTerminalStatus(status: TeamTaskStatus): boolean {\n\treturn status === \"done\" || status === \"error\" || status === \"cancelled\";\n}\n\nfunction shouldReplacePendingStatus(current: TeamTaskStatus, next: TeamTaskStatus): boolean {\n\tif (current === next) return false;\n\tif (isTerminalStatus(current)) return false;\n\tif (isTerminalStatus(next)) return true;\n\tif (current === \"running\" && next === \"pending\") return false;\n\treturn true;\n}\n\nfunction pendingStatusKey(input: { cwd: string; runId: string; taskId: string }): string {\n\treturn `${resolve(input.cwd).toLowerCase()}::${input.runId}::${input.taskId}`;\n}\n\nfunction schedulePendingStatusFlush(delayMs = statusUpdateRetryDelayMs): void {\n\tif (pendingStatusFlushTimer) return;\n\tpendingStatusFlushTimer = setTimeout(() => {\n\t\tpendingStatusFlushTimer = undefined;\n\t\tflushPendingStatusUpdates();\n\t}, delayMs);\n}\n\nfunction queuePendingStatusUpdate(input: {\n\tcwd: string;\n\trunId: string;\n\ttaskId: string;\n\tstatus: TeamTaskStatus;\n}): void {\n\tconst key = pendingStatusKey(input);\n\tconst existing = pendingStatusUpdates.get(key);\n\tif (!existing) {\n\t\tpendingStatusUpdates.set(key, { input, attempts: 0 });\n\t\tschedulePendingStatusFlush();\n\t\treturn;\n\t}\n\tif (shouldReplacePendingStatus(existing.input.status, input.status)) {\n\t\tpendingStatusUpdates.set(key, {\n\t\t\tinput,\n\t\t\tattempts: existing.attempts,\n\t\t});\n\t}\n\tschedulePendingStatusFlush();\n}\n\nfunction flushPendingStatusUpdates(): void {\n\tif (pendingStatusUpdates.size === 0) return;\n\tfor (const [key, pending] of Array.from(pendingStatusUpdates.entries())) {\n\t\tif (pending.attempts >= maxQueuedStatusAttempts) {\n\t\t\tpendingStatusUpdates.delete(key);\n\t\t\tcontinue;\n\t\t}\n\t\tconst result = tryUpdateTeamTaskStatus(pending.input);\n\t\tif (result === \"locked\") {\n\t\t\tpendingStatusUpdates.set(key, {\n\t\t\t\tinput: pending.input,\n\t\t\t\tattempts: pending.attempts + 1,\n\t\t\t});\n\t\t\tcontinue;\n\t\t}\n\t\tpendingStatusUpdates.delete(key);\n\t}\n\tif (pendingStatusUpdates.size > 0) {\n\t\tschedulePendingStatusFlush();\n\t}\n}\n\nexport function createTeamRun(input: {\n\tcwd: string;\n\tmode: \"parallel\" | \"sequential\";\n\tagents: number;\n\tmaxParallel?: number;\n\ttask: string;\n\tassignments: Array<{ profile: string; cwd: string; lockKey?: string; dependsOn: number[] }>;\n}): TeamRunRecord {\n\tconst runId = `team_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;\n\tconst tasks: TeamTaskRecord[] = input.assignments.map((assignment, index) => {\n\t\tconst id = `task_${index + 1}`;\n\t\tconst dependsOn = assignment.dependsOn.map((dep) => `task_${dep}`);\n\t\treturn {\n\t\t\tid,\n\t\t\tagentIndex: index + 1,\n\t\t\tprofile: assignment.profile,\n\t\t\tcwd: assignment.cwd,\n\t\t\tlockKey: assignment.lockKey,\n\t\t\tdependsOn,\n\t\t\tstatus: \"pending\",\n\t\t};\n\t});\n\tconst record: TeamRunRecord = {\n\t\trunId,\n\t\tcreatedAt: new Date().toISOString(),\n\t\tmode: input.mode,\n\t\tagents: input.agents,\n\t\tmaxParallel: input.maxParallel,\n\t\ttask: input.task,\n\t\ttasks,\n\t};\n\tmkdirSync(getTeamsDir(input.cwd), { recursive: true });\n\twriteFileSync(getTeamRunPath(input.cwd, runId), JSON.stringify(record, null, 2), \"utf8\");\n\treturn record;\n}\n\nexport function getTeamRun(cwd: string, runId: string): TeamRunRecord | undefined {\n\tconst path = getTeamRunPath(cwd, runId);\n\tif (!existsSync(path)) return undefined;\n\ttry {\n\t\treturn JSON.parse(readFileSync(path, \"utf8\")) as TeamRunRecord;\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nexport function listTeamRuns(cwd: string, limit = 20): TeamRunRecord[] {\n\tconst dir = getTeamsDir(cwd);\n\tif (!existsSync(dir)) return [];\n\tconst names = readdirSync(dir)\n\t\t.filter((name) => name.toLowerCase().endsWith(\".json\"))\n\t\t.sort()\n\t\t.reverse()\n\t\t.slice(0, Math.max(1, limit));\n\tconst runs: TeamRunRecord[] = [];\n\tfor (const name of names) {\n\t\ttry {\n\t\t\tconst parsed = JSON.parse(readFileSync(join(dir, name), \"utf8\")) as TeamRunRecord;\n\t\t\truns.push(parsed);\n\t\t} catch {\n\t\t\t// ignore malformed files\n\t\t}\n\t}\n\treturn runs;\n}\n\nfunction tryUpdateTeamTaskStatus(input: {\n\tcwd: string;\n\trunId: string;\n\ttaskId: string;\n\tstatus: TeamTaskStatus;\n}): TeamRunRecord | \"locked\" | undefined {\n\tconst runPath = getTeamRunPath(input.cwd, input.runId);\n\tif (!existsSync(runPath)) return undefined;\n\tlet release: (() => void) | undefined;\n\ttry {\n\t\tmkdirSync(getTeamsDir(input.cwd), { recursive: true });\n\t\ttry {\n\t\t\trelease = lockfile.lockSync(runPath, { realpath: false });\n\t\t} catch (error) {\n\t\t\tconst code = error && typeof error === \"object\" && \"code\" in error ? String(error.code) : \"\";\n\t\t\tif (code === \"ELOCKED\") return \"locked\";\n\t\t\treturn undefined;\n\t\t}\n\t\tif (!release) return undefined;\n\t\tconst raw = readFileSync(runPath, \"utf8\");\n\t\tconst existing = JSON.parse(raw) as TeamRunRecord;\n\t\tconst nextTasks = existing.tasks.map((task) => (task.id === input.taskId ? { ...task, status: input.status } : task));\n\t\tif (!nextTasks.some((task) => task.id === input.taskId)) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\tconst next: TeamRunRecord = {\n\t\t\t...existing,\n\t\t\ttasks: nextTasks,\n\t\t};\n\t\twriteFileSync(runPath, JSON.stringify(next, null, 2), \"utf8\");\n\t\treturn next;\n\t} catch {\n\t\treturn undefined;\n\t} finally {\n\t\trelease?.();\n\t}\n}\n\nexport function updateTeamTaskStatus(input: {\n\tcwd: string;\n\trunId: string;\n\ttaskId: string;\n\tstatus: TeamTaskStatus;\n}): TeamRunRecord | undefined {\n\tconst result = tryUpdateTeamTaskStatus(input);\n\tif (result === \"locked\") {\n\t\t// Non-blocking reliability path: queue status update for retry instead of dropping lifecycle transitions.\n\t\tqueuePendingStatusUpdate(input);\n\t\treturn undefined;\n\t}\n\tconst key = pendingStatusKey(input);\n\tif (pendingStatusUpdates.has(key) && result) {\n\t\tpendingStatusUpdates.delete(key);\n\t}\n\treturn result;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"agent-teams.js","sourceRoot":"","sources":["../../src/core/agent-teams.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from \"node:fs\";\nimport { join, resolve } from \"node:path\";\nimport lockfile from \"proper-lockfile\";\n\nexport type TeamTaskStatus = \"pending\" | \"running\" | \"done\" | \"error\" | \"cancelled\";\n\nexport interface TeamTaskRecord {\n\tid: string;\n\tagentIndex: number;\n\tprofile: string;\n\tcwd: string;\n\tlockKey?: string;\n\tdependsOn: string[];\n\tstatus: TeamTaskStatus;\n}\n\nexport interface TeamRunRecord {\n\trunId: string;\n\tcreatedAt: string;\n\tmode: \"parallel\" | \"sequential\";\n\tagents: number;\n\tmaxParallel?: number;\n\ttask: string;\n\ttasks: TeamTaskRecord[];\n}\n\nfunction getTeamsDir(cwd: string): string {\n\treturn join(cwd, \".iosm\", \"subagents\", \"teams\");\n}\n\nfunction getTeamRunPath(cwd: string, runId: string): string {\n\treturn join(getTeamsDir(cwd), `${runId}.json`);\n}\n\ntype PendingStatusUpdate = {\n\tinput: {\n\t\tcwd: string;\n\t\trunId: string;\n\t\ttaskId: string;\n\t\tstatus: TeamTaskStatus;\n\t};\n\tattempts: number;\n};\n\nconst statusUpdateRetryBaseDelayMs = 25;\nconst statusUpdateRetryMaxDelayMs = 1_000;\nconst pendingStatusUpdates = new Map<string, PendingStatusUpdate>();\nlet pendingStatusFlushTimer: ReturnType<typeof setTimeout> | undefined;\n\nfunction isTerminalStatus(status: TeamTaskStatus): boolean {\n\treturn status === \"done\" || status === \"error\" || status === \"cancelled\";\n}\n\nfunction shouldReplacePendingStatus(current: TeamTaskStatus, next: TeamTaskStatus): boolean {\n\tif (current === next) return false;\n\tif (isTerminalStatus(current)) return false;\n\tif (isTerminalStatus(next)) return true;\n\tif (current === \"running\" && next === \"pending\") return false;\n\treturn true;\n}\n\nfunction pendingStatusKey(input: { cwd: string; runId: string; taskId: string }): string {\n\treturn `${resolve(input.cwd).toLowerCase()}::${input.runId}::${input.taskId}`;\n}\n\nfunction statusRetryDelayMs(attempts: number): number {\n\tconst exponent = Math.min(6, Math.max(0, attempts));\n\treturn Math.min(statusUpdateRetryBaseDelayMs * 2 ** exponent, statusUpdateRetryMaxDelayMs);\n}\n\nfunction schedulePendingStatusFlush(delayMs = statusUpdateRetryBaseDelayMs): void {\n\tif (pendingStatusFlushTimer) return;\n\tpendingStatusFlushTimer = setTimeout(() => {\n\t\tpendingStatusFlushTimer = undefined;\n\t\tflushPendingStatusUpdates();\n\t}, delayMs);\n}\n\nfunction queuePendingStatusUpdate(input: {\n\tcwd: string;\n\trunId: string;\n\ttaskId: string;\n\tstatus: TeamTaskStatus;\n}): void {\n\tconst key = pendingStatusKey(input);\n\tconst existing = pendingStatusUpdates.get(key);\n\tif (!existing) {\n\t\tpendingStatusUpdates.set(key, { input, attempts: 0 });\n\t\tschedulePendingStatusFlush();\n\t\treturn;\n\t}\n\tif (shouldReplacePendingStatus(existing.input.status, input.status)) {\n\t\tpendingStatusUpdates.set(key, {\n\t\t\tinput,\n\t\t\tattempts: existing.attempts,\n\t\t});\n\t}\n\tschedulePendingStatusFlush();\n}\n\nfunction flushPendingStatusUpdates(): void {\n\tif (pendingStatusUpdates.size === 0) return;\n\tlet nextDelayMs = statusUpdateRetryMaxDelayMs;\n\tfor (const [key, pending] of Array.from(pendingStatusUpdates.entries())) {\n\t\tconst result = tryUpdateTeamTaskStatus(pending.input);\n\t\tif (result === \"locked\") {\n\t\t\tconst attempts = pending.attempts + 1;\n\t\t\tpendingStatusUpdates.set(key, {\n\t\t\t\tinput: pending.input,\n\t\t\t\tattempts,\n\t\t\t});\n\t\t\tnextDelayMs = Math.min(nextDelayMs, statusRetryDelayMs(attempts));\n\t\t\tcontinue;\n\t\t}\n\t\tpendingStatusUpdates.delete(key);\n\t}\n\tif (pendingStatusUpdates.size > 0) {\n\t\tschedulePendingStatusFlush(nextDelayMs);\n\t}\n}\n\nexport function createTeamRun(input: {\n\tcwd: string;\n\tmode: \"parallel\" | \"sequential\";\n\tagents: number;\n\tmaxParallel?: number;\n\ttask: string;\n\tassignments: Array<{ profile: string; cwd: string; lockKey?: string; dependsOn: number[] }>;\n}): TeamRunRecord {\n\tconst runId = `team_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;\n\tconst tasks: TeamTaskRecord[] = input.assignments.map((assignment, index) => {\n\t\tconst id = `task_${index + 1}`;\n\t\tconst dependsOn = assignment.dependsOn.map((dep) => `task_${dep}`);\n\t\treturn {\n\t\t\tid,\n\t\t\tagentIndex: index + 1,\n\t\t\tprofile: assignment.profile,\n\t\t\tcwd: assignment.cwd,\n\t\t\tlockKey: assignment.lockKey,\n\t\t\tdependsOn,\n\t\t\tstatus: \"pending\",\n\t\t};\n\t});\n\tconst record: TeamRunRecord = {\n\t\trunId,\n\t\tcreatedAt: new Date().toISOString(),\n\t\tmode: input.mode,\n\t\tagents: input.agents,\n\t\tmaxParallel: input.maxParallel,\n\t\ttask: input.task,\n\t\ttasks,\n\t};\n\tmkdirSync(getTeamsDir(input.cwd), { recursive: true });\n\twriteFileSync(getTeamRunPath(input.cwd, runId), JSON.stringify(record, null, 2), \"utf8\");\n\treturn record;\n}\n\nexport function getTeamRun(cwd: string, runId: string): TeamRunRecord | undefined {\n\tconst path = getTeamRunPath(cwd, runId);\n\tif (!existsSync(path)) return undefined;\n\ttry {\n\t\treturn JSON.parse(readFileSync(path, \"utf8\")) as TeamRunRecord;\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nexport function listTeamRuns(cwd: string, limit = 20): TeamRunRecord[] {\n\tconst dir = getTeamsDir(cwd);\n\tif (!existsSync(dir)) return [];\n\tconst names = readdirSync(dir)\n\t\t.filter((name) => name.toLowerCase().endsWith(\".json\"))\n\t\t.sort()\n\t\t.reverse()\n\t\t.slice(0, Math.max(1, limit));\n\tconst runs: TeamRunRecord[] = [];\n\tfor (const name of names) {\n\t\ttry {\n\t\t\tconst parsed = JSON.parse(readFileSync(join(dir, name), \"utf8\")) as TeamRunRecord;\n\t\t\truns.push(parsed);\n\t\t} catch {\n\t\t\t// ignore malformed files\n\t\t}\n\t}\n\treturn runs;\n}\n\nfunction tryUpdateTeamTaskStatus(input: {\n\tcwd: string;\n\trunId: string;\n\ttaskId: string;\n\tstatus: TeamTaskStatus;\n}): TeamRunRecord | \"locked\" | undefined {\n\tconst runPath = getTeamRunPath(input.cwd, input.runId);\n\tif (!existsSync(runPath)) return undefined;\n\tlet release: (() => void) | undefined;\n\ttry {\n\t\tmkdirSync(getTeamsDir(input.cwd), { recursive: true });\n\t\ttry {\n\t\t\trelease = lockfile.lockSync(runPath, { realpath: false });\n\t\t} catch (error) {\n\t\t\tconst code = error && typeof error === \"object\" && \"code\" in error ? String(error.code) : \"\";\n\t\t\tif (code === \"ELOCKED\") return \"locked\";\n\t\t\treturn undefined;\n\t\t}\n\t\tif (!release) return undefined;\n\t\tconst raw = readFileSync(runPath, \"utf8\");\n\t\tconst existing = JSON.parse(raw) as TeamRunRecord;\n\t\tconst nextTasks = existing.tasks.map((task) => (task.id === input.taskId ? { ...task, status: input.status } : task));\n\t\tif (!nextTasks.some((task) => task.id === input.taskId)) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\tconst next: TeamRunRecord = {\n\t\t\t...existing,\n\t\t\ttasks: nextTasks,\n\t\t};\n\t\twriteFileSync(runPath, JSON.stringify(next, null, 2), \"utf8\");\n\t\treturn next;\n\t} catch {\n\t\treturn undefined;\n\t} finally {\n\t\trelease?.();\n\t}\n}\n\nexport function updateTeamTaskStatus(input: {\n\tcwd: string;\n\trunId: string;\n\ttaskId: string;\n\tstatus: TeamTaskStatus;\n}): TeamRunRecord | undefined {\n\tconst result = tryUpdateTeamTaskStatus(input);\n\tif (result === \"locked\") {\n\t\t// Non-blocking reliability path: queue status update for retry instead of dropping lifecycle transitions.\n\t\tqueuePendingStatusUpdate(input);\n\t\treturn undefined;\n\t}\n\tconst key = pendingStatusKey(input);\n\tif (pendingStatusUpdates.has(key) && result) {\n\t\tpendingStatusUpdates.delete(key);\n\t}\n\treturn result;\n}\n"]}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export type CheckpointBackend = "shadow-git" | "archive";
|
|
2
|
+
export interface FsCheckpointMetadata {
|
|
3
|
+
checkpointId: string;
|
|
4
|
+
checkpointName: string;
|
|
5
|
+
createdAt: string;
|
|
6
|
+
backend: CheckpointBackend;
|
|
7
|
+
rootDir: string;
|
|
8
|
+
snapshotDir: string;
|
|
9
|
+
filesDir: string;
|
|
10
|
+
files: string[];
|
|
11
|
+
}
|
|
12
|
+
export declare function createFsCheckpointSnapshot(cwd: string, checkpointName: string, checkpointId: string): Promise<FsCheckpointMetadata>;
|
|
13
|
+
export declare function restoreFsCheckpointSnapshot(cwd: string, metadata: FsCheckpointMetadata): Promise<void>;
|
|
14
|
+
//# sourceMappingURL=fs-checkpoint.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fs-checkpoint.d.ts","sourceRoot":"","sources":["../../../src/core/checkpoint/fs-checkpoint.ts"],"names":[],"mappings":"AAKA,MAAM,MAAM,iBAAiB,GAAG,YAAY,GAAG,SAAS,CAAC;AAEzD,MAAM,WAAW,oBAAoB;IACpC,YAAY,EAAE,MAAM,CAAC;IACrB,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,iBAAiB,CAAC;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,EAAE,CAAC;CAChB;AAsHD,wBAAsB,0BAA0B,CAC/C,GAAG,EAAE,MAAM,EACX,cAAc,EAAE,MAAM,EACtB,YAAY,EAAE,MAAM,GAClB,OAAO,CAAC,oBAAoB,CAAC,CA8B/B;AAgCD,wBAAsB,2BAA2B,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,oBAAoB,GAAG,OAAO,CAAC,IAAI,CAAC,CA2B5G"}
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync, unlinkSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { copyFile, mkdir, readdir } from "node:fs/promises";
|
|
3
|
+
import { dirname, join, relative, resolve } from "node:path";
|
|
4
|
+
import { spawnSync } from "node:child_process";
|
|
5
|
+
const CHECKPOINT_ROOT_SEGMENTS = [".iosm", "checkpoints", "files"];
|
|
6
|
+
const DEFAULT_RETENTION = 20;
|
|
7
|
+
function getCheckpointRoot(cwd) {
|
|
8
|
+
return join(cwd, ...CHECKPOINT_ROOT_SEGMENTS);
|
|
9
|
+
}
|
|
10
|
+
function safeId(value) {
|
|
11
|
+
return value
|
|
12
|
+
.toLowerCase()
|
|
13
|
+
.replace(/[^a-z0-9._-]+/g, "-")
|
|
14
|
+
.replace(/^-+|-+$/g, "")
|
|
15
|
+
.slice(0, 64);
|
|
16
|
+
}
|
|
17
|
+
function ensureDir(path) {
|
|
18
|
+
if (!existsSync(path)) {
|
|
19
|
+
mkdirSync(path, { recursive: true });
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function parseGitTrackedAndUntrackedFiles(cwd) {
|
|
23
|
+
const result = spawnSync("git", ["-C", cwd, "ls-files", "-co", "--exclude-standard", "-z"], {
|
|
24
|
+
encoding: "utf8",
|
|
25
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
26
|
+
});
|
|
27
|
+
if (result.status !== 0) {
|
|
28
|
+
throw new Error(`git ls-files failed: ${(result.stderr ?? "").toString().trim() || "unknown error"}`);
|
|
29
|
+
}
|
|
30
|
+
const raw = result.stdout ?? "";
|
|
31
|
+
if (typeof raw !== "string" || raw.length === 0)
|
|
32
|
+
return [];
|
|
33
|
+
return raw
|
|
34
|
+
.split("\0")
|
|
35
|
+
.map((entry) => entry.trim())
|
|
36
|
+
.filter((entry) => entry.length > 0)
|
|
37
|
+
.filter((entry) => !entry.startsWith(".iosm/checkpoints/"));
|
|
38
|
+
}
|
|
39
|
+
async function walkAllFiles(cwd, dir = cwd, collector = []) {
|
|
40
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
41
|
+
for (const entry of entries) {
|
|
42
|
+
if (entry.name === ".git")
|
|
43
|
+
continue;
|
|
44
|
+
if (entry.name === ".iosm" && resolve(dir, entry.name) === resolve(cwd, ".iosm")) {
|
|
45
|
+
const checkpointsDir = resolve(cwd, ".iosm", "checkpoints");
|
|
46
|
+
if (checkpointsDir.startsWith(resolve(dir, entry.name))) {
|
|
47
|
+
// Continue traversing .iosm, but skip .iosm/checkpoints subtree.
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
const full = join(dir, entry.name);
|
|
51
|
+
const rel = relative(cwd, full).replace(/\\/g, "/");
|
|
52
|
+
if (rel.startsWith(".iosm/checkpoints/"))
|
|
53
|
+
continue;
|
|
54
|
+
if (entry.isDirectory()) {
|
|
55
|
+
await walkAllFiles(cwd, full, collector);
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
if (entry.isFile()) {
|
|
59
|
+
collector.push(rel);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return collector;
|
|
63
|
+
}
|
|
64
|
+
function detectBackend(cwd) {
|
|
65
|
+
const probe = spawnSync("git", ["-C", cwd, "rev-parse", "--is-inside-work-tree"], {
|
|
66
|
+
encoding: "utf8",
|
|
67
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
68
|
+
});
|
|
69
|
+
if (probe.status === 0 && `${probe.stdout}`.trim() === "true") {
|
|
70
|
+
return "shadow-git";
|
|
71
|
+
}
|
|
72
|
+
return "archive";
|
|
73
|
+
}
|
|
74
|
+
async function copySnapshotFiles(cwd, filesDir, files) {
|
|
75
|
+
for (const rel of files) {
|
|
76
|
+
const source = resolve(cwd, rel);
|
|
77
|
+
if (!existsSync(source))
|
|
78
|
+
continue;
|
|
79
|
+
const target = resolve(filesDir, rel);
|
|
80
|
+
await mkdir(dirname(target), { recursive: true });
|
|
81
|
+
await copyFile(source, target);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
function listCheckpointMetadata(cwd) {
|
|
85
|
+
const root = getCheckpointRoot(cwd);
|
|
86
|
+
if (!existsSync(root))
|
|
87
|
+
return [];
|
|
88
|
+
const metadata = [];
|
|
89
|
+
for (const entry of readdirSync(root, { withFileTypes: true })) {
|
|
90
|
+
if (!entry.isDirectory())
|
|
91
|
+
continue;
|
|
92
|
+
const snapshotDir = join(root, entry.name);
|
|
93
|
+
const metadataPath = join(snapshotDir, "metadata.json");
|
|
94
|
+
if (!existsSync(metadataPath))
|
|
95
|
+
continue;
|
|
96
|
+
try {
|
|
97
|
+
const parsed = JSON.parse(readFileSync(metadataPath, "utf8"));
|
|
98
|
+
if (!parsed || typeof parsed !== "object")
|
|
99
|
+
continue;
|
|
100
|
+
metadata.push(parsed);
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return metadata.sort((a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime());
|
|
107
|
+
}
|
|
108
|
+
function cleanupOldSnapshots(cwd, retain = DEFAULT_RETENTION) {
|
|
109
|
+
const all = listCheckpointMetadata(cwd);
|
|
110
|
+
if (all.length <= retain)
|
|
111
|
+
return;
|
|
112
|
+
const remove = all.slice(0, all.length - retain);
|
|
113
|
+
for (const entry of remove) {
|
|
114
|
+
try {
|
|
115
|
+
rmSync(entry.snapshotDir, { recursive: true, force: true });
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
// Best effort cleanup.
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
export async function createFsCheckpointSnapshot(cwd, checkpointName, checkpointId) {
|
|
123
|
+
const root = getCheckpointRoot(cwd);
|
|
124
|
+
ensureDir(root);
|
|
125
|
+
const backend = detectBackend(cwd);
|
|
126
|
+
const now = new Date().toISOString();
|
|
127
|
+
const snapshotId = `${safeId(checkpointName || "checkpoint")}-${Date.now()}-${safeId(checkpointId).slice(0, 16)}`;
|
|
128
|
+
const snapshotDir = join(root, snapshotId);
|
|
129
|
+
const filesDir = join(snapshotDir, "files");
|
|
130
|
+
ensureDir(filesDir);
|
|
131
|
+
const files = backend === "shadow-git"
|
|
132
|
+
? parseGitTrackedAndUntrackedFiles(cwd)
|
|
133
|
+
: await walkAllFiles(cwd);
|
|
134
|
+
await copySnapshotFiles(cwd, filesDir, files);
|
|
135
|
+
const metadata = {
|
|
136
|
+
checkpointId,
|
|
137
|
+
checkpointName,
|
|
138
|
+
createdAt: now,
|
|
139
|
+
backend,
|
|
140
|
+
rootDir: cwd,
|
|
141
|
+
snapshotDir,
|
|
142
|
+
filesDir,
|
|
143
|
+
files,
|
|
144
|
+
};
|
|
145
|
+
writeFileSync(join(snapshotDir, "metadata.json"), `${JSON.stringify(metadata, null, 2)}\n`, "utf8");
|
|
146
|
+
cleanupOldSnapshots(cwd, DEFAULT_RETENTION);
|
|
147
|
+
return metadata;
|
|
148
|
+
}
|
|
149
|
+
function getCurrentFileList(cwd, backend) {
|
|
150
|
+
if (backend === "shadow-git") {
|
|
151
|
+
try {
|
|
152
|
+
return parseGitTrackedAndUntrackedFiles(cwd);
|
|
153
|
+
}
|
|
154
|
+
catch {
|
|
155
|
+
// fall through
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
const all = [];
|
|
159
|
+
const stack = [cwd];
|
|
160
|
+
while (stack.length > 0) {
|
|
161
|
+
const dir = stack.pop();
|
|
162
|
+
if (!dir)
|
|
163
|
+
continue;
|
|
164
|
+
const entries = readdirSync(dir, { withFileTypes: true });
|
|
165
|
+
for (const entry of entries) {
|
|
166
|
+
const full = join(dir, entry.name);
|
|
167
|
+
const rel = relative(cwd, full).replace(/\\/g, "/");
|
|
168
|
+
if (rel.startsWith(".git/"))
|
|
169
|
+
continue;
|
|
170
|
+
if (rel.startsWith(".iosm/checkpoints/"))
|
|
171
|
+
continue;
|
|
172
|
+
if (entry.isDirectory()) {
|
|
173
|
+
stack.push(full);
|
|
174
|
+
}
|
|
175
|
+
else if (entry.isFile()) {
|
|
176
|
+
all.push(rel);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return all;
|
|
181
|
+
}
|
|
182
|
+
export async function restoreFsCheckpointSnapshot(cwd, metadata) {
|
|
183
|
+
const snapshotMetadataPath = join(metadata.snapshotDir, "metadata.json");
|
|
184
|
+
if (!existsSync(snapshotMetadataPath)) {
|
|
185
|
+
throw new Error(`Checkpoint snapshot metadata not found: ${snapshotMetadataPath}`);
|
|
186
|
+
}
|
|
187
|
+
const fileSet = new Set(metadata.files);
|
|
188
|
+
const currentFiles = getCurrentFileList(cwd, metadata.backend);
|
|
189
|
+
for (const rel of currentFiles) {
|
|
190
|
+
if (fileSet.has(rel))
|
|
191
|
+
continue;
|
|
192
|
+
const path = resolve(cwd, rel);
|
|
193
|
+
if (existsSync(path)) {
|
|
194
|
+
try {
|
|
195
|
+
unlinkSync(path);
|
|
196
|
+
}
|
|
197
|
+
catch {
|
|
198
|
+
// best effort cleanup before restore
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
for (const rel of metadata.files) {
|
|
203
|
+
const source = resolve(metadata.filesDir, rel);
|
|
204
|
+
if (!existsSync(source))
|
|
205
|
+
continue;
|
|
206
|
+
const target = resolve(cwd, rel);
|
|
207
|
+
await mkdir(dirname(target), { recursive: true });
|
|
208
|
+
await copyFile(source, target);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
//# sourceMappingURL=fs-checkpoint.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fs-checkpoint.js","sourceRoot":"","sources":["../../../src/core/checkpoint/fs-checkpoint.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync, statSync, unlinkSync, writeFileSync } from \"node:fs\";\nimport { copyFile, mkdir, readdir } from \"node:fs/promises\";\nimport { dirname, join, relative, resolve } from \"node:path\";\nimport { spawnSync } from \"node:child_process\";\n\nexport type CheckpointBackend = \"shadow-git\" | \"archive\";\n\nexport interface FsCheckpointMetadata {\n\tcheckpointId: string;\n\tcheckpointName: string;\n\tcreatedAt: string;\n\tbackend: CheckpointBackend;\n\trootDir: string;\n\tsnapshotDir: string;\n\tfilesDir: string;\n\tfiles: string[];\n}\n\nconst CHECKPOINT_ROOT_SEGMENTS = [\".iosm\", \"checkpoints\", \"files\"] as const;\nconst DEFAULT_RETENTION = 20;\n\nfunction getCheckpointRoot(cwd: string): string {\n\treturn join(cwd, ...CHECKPOINT_ROOT_SEGMENTS);\n}\n\nfunction safeId(value: string): string {\n\treturn value\n\t\t.toLowerCase()\n\t\t.replace(/[^a-z0-9._-]+/g, \"-\")\n\t\t.replace(/^-+|-+$/g, \"\")\n\t\t.slice(0, 64);\n}\n\nfunction ensureDir(path: string): void {\n\tif (!existsSync(path)) {\n\t\tmkdirSync(path, { recursive: true });\n\t}\n}\n\nfunction parseGitTrackedAndUntrackedFiles(cwd: string): string[] {\n\tconst result = spawnSync(\"git\", [\"-C\", cwd, \"ls-files\", \"-co\", \"--exclude-standard\", \"-z\"], {\n\t\tencoding: \"utf8\",\n\t\tstdio: [\"ignore\", \"pipe\", \"pipe\"],\n\t});\n\tif (result.status !== 0) {\n\t\tthrow new Error(`git ls-files failed: ${(result.stderr ?? \"\").toString().trim() || \"unknown error\"}`);\n\t}\n\tconst raw = result.stdout ?? \"\";\n\tif (typeof raw !== \"string\" || raw.length === 0) return [];\n\treturn raw\n\t\t.split(\"\\0\")\n\t\t.map((entry) => entry.trim())\n\t\t.filter((entry) => entry.length > 0)\n\t\t.filter((entry) => !entry.startsWith(\".iosm/checkpoints/\"));\n}\n\nasync function walkAllFiles(cwd: string, dir: string = cwd, collector: string[] = []): Promise<string[]> {\n\tconst entries = await readdir(dir, { withFileTypes: true });\n\tfor (const entry of entries) {\n\t\tif (entry.name === \".git\") continue;\n\t\tif (entry.name === \".iosm\" && resolve(dir, entry.name) === resolve(cwd, \".iosm\")) {\n\t\t\tconst checkpointsDir = resolve(cwd, \".iosm\", \"checkpoints\");\n\t\t\tif (checkpointsDir.startsWith(resolve(dir, entry.name))) {\n\t\t\t\t// Continue traversing .iosm, but skip .iosm/checkpoints subtree.\n\t\t\t}\n\t\t}\n\t\tconst full = join(dir, entry.name);\n\t\tconst rel = relative(cwd, full).replace(/\\\\/g, \"/\");\n\t\tif (rel.startsWith(\".iosm/checkpoints/\")) continue;\n\t\tif (entry.isDirectory()) {\n\t\t\tawait walkAllFiles(cwd, full, collector);\n\t\t\tcontinue;\n\t\t}\n\t\tif (entry.isFile()) {\n\t\t\tcollector.push(rel);\n\t\t}\n\t}\n\treturn collector;\n}\n\nfunction detectBackend(cwd: string): CheckpointBackend {\n\tconst probe = spawnSync(\"git\", [\"-C\", cwd, \"rev-parse\", \"--is-inside-work-tree\"], {\n\t\tencoding: \"utf8\",\n\t\tstdio: [\"ignore\", \"pipe\", \"pipe\"],\n\t});\n\tif (probe.status === 0 && `${probe.stdout}`.trim() === \"true\") {\n\t\treturn \"shadow-git\";\n\t}\n\treturn \"archive\";\n}\n\nasync function copySnapshotFiles(cwd: string, filesDir: string, files: string[]): Promise<void> {\n\tfor (const rel of files) {\n\t\tconst source = resolve(cwd, rel);\n\t\tif (!existsSync(source)) continue;\n\t\tconst target = resolve(filesDir, rel);\n\t\tawait mkdir(dirname(target), { recursive: true });\n\t\tawait copyFile(source, target);\n\t}\n}\n\nfunction listCheckpointMetadata(cwd: string): FsCheckpointMetadata[] {\n\tconst root = getCheckpointRoot(cwd);\n\tif (!existsSync(root)) return [];\n\tconst metadata: FsCheckpointMetadata[] = [];\n\tfor (const entry of readdirSync(root, { withFileTypes: true })) {\n\t\tif (!entry.isDirectory()) continue;\n\t\tconst snapshotDir = join(root, entry.name);\n\t\tconst metadataPath = join(snapshotDir, \"metadata.json\");\n\t\tif (!existsSync(metadataPath)) continue;\n\t\ttry {\n\t\t\tconst parsed = JSON.parse(readFileSync(metadataPath, \"utf8\")) as FsCheckpointMetadata;\n\t\t\tif (!parsed || typeof parsed !== \"object\") continue;\n\t\t\tmetadata.push(parsed);\n\t\t} catch {\n\t\t\tcontinue;\n\t\t}\n\t}\n\treturn metadata.sort((a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime());\n}\n\nfunction cleanupOldSnapshots(cwd: string, retain = DEFAULT_RETENTION): void {\n\tconst all = listCheckpointMetadata(cwd);\n\tif (all.length <= retain) return;\n\tconst remove = all.slice(0, all.length - retain);\n\tfor (const entry of remove) {\n\t\ttry {\n\t\t\trmSync(entry.snapshotDir, { recursive: true, force: true });\n\t\t} catch {\n\t\t\t// Best effort cleanup.\n\t\t}\n\t}\n}\n\nexport async function createFsCheckpointSnapshot(\n\tcwd: string,\n\tcheckpointName: string,\n\tcheckpointId: string,\n): Promise<FsCheckpointMetadata> {\n\tconst root = getCheckpointRoot(cwd);\n\tensureDir(root);\n\n\tconst backend = detectBackend(cwd);\n\tconst now = new Date().toISOString();\n\tconst snapshotId = `${safeId(checkpointName || \"checkpoint\")}-${Date.now()}-${safeId(checkpointId).slice(0, 16)}`;\n\tconst snapshotDir = join(root, snapshotId);\n\tconst filesDir = join(snapshotDir, \"files\");\n\tensureDir(filesDir);\n\n\tconst files =\n\t\tbackend === \"shadow-git\"\n\t\t\t? parseGitTrackedAndUntrackedFiles(cwd)\n\t\t\t: await walkAllFiles(cwd);\n\tawait copySnapshotFiles(cwd, filesDir, files);\n\n\tconst metadata: FsCheckpointMetadata = {\n\t\tcheckpointId,\n\t\tcheckpointName,\n\t\tcreatedAt: now,\n\t\tbackend,\n\t\trootDir: cwd,\n\t\tsnapshotDir,\n\t\tfilesDir,\n\t\tfiles,\n\t};\n\twriteFileSync(join(snapshotDir, \"metadata.json\"), `${JSON.stringify(metadata, null, 2)}\\n`, \"utf8\");\n\tcleanupOldSnapshots(cwd, DEFAULT_RETENTION);\n\treturn metadata;\n}\n\nfunction getCurrentFileList(cwd: string, backend: CheckpointBackend): string[] {\n\tif (backend === \"shadow-git\") {\n\t\ttry {\n\t\t\treturn parseGitTrackedAndUntrackedFiles(cwd);\n\t\t} catch {\n\t\t\t// fall through\n\t\t}\n\t}\n\n\tconst all: string[] = [];\n\tconst stack = [cwd];\n\twhile (stack.length > 0) {\n\t\tconst dir = stack.pop();\n\t\tif (!dir) continue;\n\t\tconst entries = readdirSync(dir, { withFileTypes: true });\n\t\tfor (const entry of entries) {\n\t\t\tconst full = join(dir, entry.name);\n\t\t\tconst rel = relative(cwd, full).replace(/\\\\/g, \"/\");\n\t\t\tif (rel.startsWith(\".git/\")) continue;\n\t\t\tif (rel.startsWith(\".iosm/checkpoints/\")) continue;\n\t\t\tif (entry.isDirectory()) {\n\t\t\t\tstack.push(full);\n\t\t\t} else if (entry.isFile()) {\n\t\t\t\tall.push(rel);\n\t\t\t}\n\t\t}\n\t}\n\treturn all;\n}\n\nexport async function restoreFsCheckpointSnapshot(cwd: string, metadata: FsCheckpointMetadata): Promise<void> {\n\tconst snapshotMetadataPath = join(metadata.snapshotDir, \"metadata.json\");\n\tif (!existsSync(snapshotMetadataPath)) {\n\t\tthrow new Error(`Checkpoint snapshot metadata not found: ${snapshotMetadataPath}`);\n\t}\n\tconst fileSet = new Set(metadata.files);\n\tconst currentFiles = getCurrentFileList(cwd, metadata.backend);\n\n\tfor (const rel of currentFiles) {\n\t\tif (fileSet.has(rel)) continue;\n\t\tconst path = resolve(cwd, rel);\n\t\tif (existsSync(path)) {\n\t\t\ttry {\n\t\t\t\tunlinkSync(path);\n\t\t\t} catch {\n\t\t\t\t// best effort cleanup before restore\n\t\t\t}\n\t\t}\n\t}\n\n\tfor (const rel of metadata.files) {\n\t\tconst source = resolve(metadata.filesDir, rel);\n\t\tif (!existsSync(source)) continue;\n\t\tconst target = resolve(cwd, rel);\n\t\tawait mkdir(dirname(target), { recursive: true });\n\t\tawait copyFile(source, target);\n\t}\n}\n"]}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import type { AgentSession } from "./agent-session.js";
|
|
2
|
+
import type { PolicyEngineV2 } from "./policy/index.js";
|
|
2
3
|
import type { SettingsManager } from "./settings-manager.js";
|
|
3
4
|
export interface BuiltinCommandContext {
|
|
4
5
|
session: AgentSession;
|
|
5
6
|
settingsManager: SettingsManager;
|
|
7
|
+
policyEngine?: PolicyEngineV2;
|
|
6
8
|
}
|
|
7
9
|
export interface BuiltinCommandResult {
|
|
8
10
|
handled: boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"command-dispatcher.d.ts","sourceRoot":"","sources":["../../src/core/command-dispatcher.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"command-dispatcher.d.ts","sourceRoot":"","sources":["../../src/core/command-dispatcher.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAIvD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAG7D,MAAM,WAAW,qBAAqB;IACrC,OAAO,EAAE,YAAY,CAAC;IACtB,eAAe,EAAE,eAAe,CAAC;IACjC,YAAY,CAAC,EAAE,cAAc,CAAC;CAC9B;AAED,MAAM,WAAW,oBAAoB;IACpC,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,QAAQ,GAAG,SAAS,GAAG,OAAO,CAAC;IACvC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;CAClB;AAID,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CA8CtD;AAwOD,wBAAsB,2BAA2B,CAChD,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,qBAAqB,GAC5B,OAAO,CAAC,oBAAoB,CAAC,CAggB/B"}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { spawn, spawnSync } from "node:child_process";
|
|
2
|
-
import { existsSync, rmSync } from "node:fs";
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { tmpdir } from "node:os";
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import { getShareViewerUrl } from "../config.js";
|
|
6
|
+
import { createFsCheckpointSnapshot, restoreFsCheckpointSnapshot } from "./checkpoint/fs-checkpoint.js";
|
|
6
7
|
import { getChangelogPath, parseChangelog } from "../utils/changelog.js";
|
|
7
8
|
const CHECKPOINT_LABEL_PREFIX = "checkpoint:";
|
|
8
9
|
export function parseSlashArgs(input) {
|
|
@@ -50,10 +51,10 @@ export function parseSlashArgs(input) {
|
|
|
50
51
|
}
|
|
51
52
|
return args;
|
|
52
53
|
}
|
|
53
|
-
function formatPermissionStatus(settingsManager) {
|
|
54
|
+
function formatPermissionStatus(settingsManager, policyEngine) {
|
|
54
55
|
const mode = settingsManager.getPermissionMode();
|
|
55
|
-
const allowRules = settingsManager.getPermissionAllowRules();
|
|
56
|
-
const denyRules = settingsManager.getPermissionDenyRules();
|
|
56
|
+
const allowRules = policyEngine?.getLegacyRules("allow") ?? settingsManager.getPermissionAllowRules();
|
|
57
|
+
const denyRules = policyEngine?.getLegacyRules("deny") ?? settingsManager.getPermissionDenyRules();
|
|
57
58
|
return `Permissions: ${mode}${allowRules.length > 0 ? ` · allow rules: ${allowRules.length}` : ""}${denyRules.length > 0 ? ` · deny rules: ${denyRules.length}` : ""}`;
|
|
58
59
|
}
|
|
59
60
|
function buildSessionStatsText(session) {
|
|
@@ -156,6 +157,32 @@ function buildTreeText(session) {
|
|
|
156
157
|
lines.push("Usage: /tree [list|<entry-id>|goto <entry-id>]");
|
|
157
158
|
return lines.join("\n");
|
|
158
159
|
}
|
|
160
|
+
function getCheckpointIndexPath(cwd) {
|
|
161
|
+
return join(cwd, ".iosm", "checkpoints", "index.json");
|
|
162
|
+
}
|
|
163
|
+
function readCheckpointSnapshotIndex(cwd) {
|
|
164
|
+
const path = getCheckpointIndexPath(cwd);
|
|
165
|
+
if (!existsSync(path)) {
|
|
166
|
+
return { snapshots: {} };
|
|
167
|
+
}
|
|
168
|
+
try {
|
|
169
|
+
const raw = readFileSync(path, "utf8");
|
|
170
|
+
const parsed = JSON.parse(raw);
|
|
171
|
+
const snapshots = parsed.snapshots && typeof parsed.snapshots === "object" ? parsed.snapshots : {};
|
|
172
|
+
return { snapshots: snapshots };
|
|
173
|
+
}
|
|
174
|
+
catch {
|
|
175
|
+
return { snapshots: {} };
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
function writeCheckpointSnapshotIndex(cwd, index) {
|
|
179
|
+
const path = getCheckpointIndexPath(cwd);
|
|
180
|
+
const dir = join(cwd, ".iosm", "checkpoints");
|
|
181
|
+
if (!existsSync(dir)) {
|
|
182
|
+
mkdirSync(dir, { recursive: true });
|
|
183
|
+
}
|
|
184
|
+
writeFileSync(path, `${JSON.stringify(index, null, 2)}\n`, "utf8");
|
|
185
|
+
}
|
|
159
186
|
function parseCheckpointNameFromLabel(label) {
|
|
160
187
|
if (!label)
|
|
161
188
|
return undefined;
|
|
@@ -244,7 +271,8 @@ export async function dispatchBuiltinSlashCommand(text, context) {
|
|
|
244
271
|
}
|
|
245
272
|
const command = commandToken.slice(1);
|
|
246
273
|
const rest = args.slice(1);
|
|
247
|
-
const { session, settingsManager } = context;
|
|
274
|
+
const { session, settingsManager, policyEngine } = context;
|
|
275
|
+
policyEngine?.refresh();
|
|
248
276
|
if (command === "help") {
|
|
249
277
|
return {
|
|
250
278
|
handled: true,
|
|
@@ -283,7 +311,7 @@ export async function dispatchBuiltinSlashCommand(text, context) {
|
|
|
283
311
|
if (command === "permissions") {
|
|
284
312
|
const value = rest[0]?.toLowerCase();
|
|
285
313
|
if (!value || value === "status") {
|
|
286
|
-
return { handled: true, level: "status", message: formatPermissionStatus(settingsManager) };
|
|
314
|
+
return { handled: true, level: "status", message: formatPermissionStatus(settingsManager, policyEngine) };
|
|
287
315
|
}
|
|
288
316
|
if (value === "ask" || value === "auto" || value === "yolo") {
|
|
289
317
|
settingsManager.setPermissionMode(value);
|
|
@@ -292,7 +320,9 @@ export async function dispatchBuiltinSlashCommand(text, context) {
|
|
|
292
320
|
if (value === "allow" || value === "deny") {
|
|
293
321
|
const action = rest[1]?.toLowerCase();
|
|
294
322
|
const isAllow = value === "allow";
|
|
295
|
-
let rules = isAllow
|
|
323
|
+
let rules = isAllow
|
|
324
|
+
? (policyEngine?.getLegacyRules("allow") ?? settingsManager.getPermissionAllowRules())
|
|
325
|
+
: (policyEngine?.getLegacyRules("deny") ?? settingsManager.getPermissionDenyRules());
|
|
296
326
|
if (action === "list") {
|
|
297
327
|
if (rules.length === 0) {
|
|
298
328
|
return { handled: true, level: "status", message: `Permissions ${value} rules: (empty)` };
|
|
@@ -311,7 +341,11 @@ export async function dispatchBuiltinSlashCommand(text, context) {
|
|
|
311
341
|
}
|
|
312
342
|
if (!rules.includes(normalizedRule)) {
|
|
313
343
|
rules = [...rules, normalizedRule];
|
|
314
|
-
if (
|
|
344
|
+
if (policyEngine) {
|
|
345
|
+
policyEngine.setLegacyRules(isAllow ? "allow" : "deny", rules);
|
|
346
|
+
policyEngine.refresh();
|
|
347
|
+
}
|
|
348
|
+
else if (isAllow) {
|
|
315
349
|
settingsManager.setPermissionAllowRules(rules);
|
|
316
350
|
}
|
|
317
351
|
else {
|
|
@@ -330,7 +364,11 @@ export async function dispatchBuiltinSlashCommand(text, context) {
|
|
|
330
364
|
};
|
|
331
365
|
}
|
|
332
366
|
rules = rules.filter((rule) => rule !== rawRule);
|
|
333
|
-
if (
|
|
367
|
+
if (policyEngine) {
|
|
368
|
+
policyEngine.setLegacyRules(isAllow ? "allow" : "deny", rules);
|
|
369
|
+
policyEngine.refresh();
|
|
370
|
+
}
|
|
371
|
+
else if (isAllow) {
|
|
334
372
|
settingsManager.setPermissionAllowRules(rules);
|
|
335
373
|
}
|
|
336
374
|
else {
|
|
@@ -379,7 +417,7 @@ export async function dispatchBuiltinSlashCommand(text, context) {
|
|
|
379
417
|
`Streaming: ${session.isStreaming ? "yes" : "no"}`,
|
|
380
418
|
`Compacting: ${session.isCompacting ? "yes" : "no"}`,
|
|
381
419
|
`Queued messages: ${session.pendingMessageCount}`,
|
|
382
|
-
formatPermissionStatus(settingsManager),
|
|
420
|
+
formatPermissionStatus(settingsManager, policyEngine),
|
|
383
421
|
];
|
|
384
422
|
return { handled: true, level: "status", text: lines.join("\n") };
|
|
385
423
|
}
|
|
@@ -527,8 +565,27 @@ export async function dispatchBuiltinSlashCommand(text, context) {
|
|
|
527
565
|
if (!name) {
|
|
528
566
|
return { handled: true, level: "warning", message: "Invalid checkpoint name. Use 1-80 visible characters." };
|
|
529
567
|
}
|
|
530
|
-
session.sessionManager.appendLabelChange(leafId, buildCheckpointLabel(name));
|
|
531
|
-
|
|
568
|
+
const labelEntryId = session.sessionManager.appendLabelChange(leafId, buildCheckpointLabel(name));
|
|
569
|
+
try {
|
|
570
|
+
const cwd = typeof session.sessionManager.getCwd === "function" ? session.sessionManager.getCwd() : process.cwd();
|
|
571
|
+
const snapshot = await createFsCheckpointSnapshot(cwd, name, labelEntryId);
|
|
572
|
+
const index = readCheckpointSnapshotIndex(cwd);
|
|
573
|
+
index.snapshots[labelEntryId] = snapshot;
|
|
574
|
+
writeCheckpointSnapshotIndex(cwd, index);
|
|
575
|
+
return {
|
|
576
|
+
handled: true,
|
|
577
|
+
level: "status",
|
|
578
|
+
message: `Checkpoint saved: ${name} (${leafId})`,
|
|
579
|
+
text: `Filesystem snapshot: ${snapshot.backend} @ ${snapshot.snapshotDir}`,
|
|
580
|
+
};
|
|
581
|
+
}
|
|
582
|
+
catch (error) {
|
|
583
|
+
return {
|
|
584
|
+
handled: true,
|
|
585
|
+
level: "warning",
|
|
586
|
+
message: `Checkpoint label saved, but filesystem snapshot failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
587
|
+
};
|
|
588
|
+
}
|
|
532
589
|
}
|
|
533
590
|
if (command === "rollback") {
|
|
534
591
|
const subcommand = rest[0]?.toLowerCase();
|
|
@@ -568,6 +625,12 @@ export async function dispatchBuiltinSlashCommand(text, context) {
|
|
|
568
625
|
return { handled: true, level: "warning", message: "No rollback target selected." };
|
|
569
626
|
}
|
|
570
627
|
try {
|
|
628
|
+
const cwd = typeof session.sessionManager.getCwd === "function" ? session.sessionManager.getCwd() : process.cwd();
|
|
629
|
+
const checkpointIndex = readCheckpointSnapshotIndex(cwd);
|
|
630
|
+
const snapshot = checkpointIndex.snapshots[target.labelEntryId];
|
|
631
|
+
if (snapshot) {
|
|
632
|
+
await restoreFsCheckpointSnapshot(cwd, snapshot);
|
|
633
|
+
}
|
|
571
634
|
const result = await session.navigateTree(target.targetId, { summarize: false });
|
|
572
635
|
if (result.cancelled || result.aborted) {
|
|
573
636
|
return { handled: true, level: "warning", message: "Rollback cancelled." };
|
|
@@ -576,7 +639,9 @@ export async function dispatchBuiltinSlashCommand(text, context) {
|
|
|
576
639
|
handled: true,
|
|
577
640
|
level: "status",
|
|
578
641
|
message: `Rolled back to checkpoint: ${target.name}`,
|
|
579
|
-
text:
|
|
642
|
+
text: snapshot
|
|
643
|
+
? `${result.editorText}\n\nFilesystem restored from ${snapshot.backend} snapshot.`
|
|
644
|
+
: result.editorText,
|
|
580
645
|
};
|
|
581
646
|
}
|
|
582
647
|
catch (error) {
|