@mclawnet/swarm 0.1.4 → 0.1.6
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/README.md +118 -0
- package/dist/__tests__/action-parser.test.js +29 -82
- package/dist/__tests__/action-parser.test.js.map +1 -1
- package/dist/__tests__/coordinator-create-tx.test.d.ts +2 -0
- package/dist/__tests__/coordinator-create-tx.test.d.ts.map +1 -0
- package/dist/__tests__/coordinator-create-tx.test.js +114 -0
- package/dist/__tests__/coordinator-create-tx.test.js.map +1 -0
- package/dist/__tests__/coordinator-inbox-migration.test.d.ts +2 -0
- package/dist/__tests__/coordinator-inbox-migration.test.d.ts.map +1 -0
- package/dist/__tests__/coordinator-inbox-migration.test.js +56 -0
- package/dist/__tests__/coordinator-inbox-migration.test.js.map +1 -0
- package/dist/__tests__/inbox-integration.test.d.ts +2 -0
- package/dist/__tests__/inbox-integration.test.d.ts.map +1 -0
- package/dist/__tests__/inbox-integration.test.js +120 -0
- package/dist/__tests__/inbox-integration.test.js.map +1 -0
- package/dist/__tests__/inbox-persistence-recovery.test.d.ts +2 -0
- package/dist/__tests__/inbox-persistence-recovery.test.d.ts.map +1 -0
- package/dist/__tests__/inbox-persistence-recovery.test.js +139 -0
- package/dist/__tests__/inbox-persistence-recovery.test.js.map +1 -0
- package/dist/__tests__/inbox-relay-interceptor.test.d.ts +2 -0
- package/dist/__tests__/inbox-relay-interceptor.test.d.ts.map +1 -0
- package/dist/__tests__/inbox-relay-interceptor.test.js +156 -0
- package/dist/__tests__/inbox-relay-interceptor.test.js.map +1 -0
- package/dist/__tests__/inbox-relay.test.d.ts +2 -0
- package/dist/__tests__/inbox-relay.test.d.ts.map +1 -0
- package/dist/__tests__/inbox-relay.test.js +318 -0
- package/dist/__tests__/inbox-relay.test.js.map +1 -0
- package/dist/__tests__/inbox-store.test.d.ts +2 -0
- package/dist/__tests__/inbox-store.test.d.ts.map +1 -0
- package/dist/__tests__/inbox-store.test.js +129 -0
- package/dist/__tests__/inbox-store.test.js.map +1 -0
- package/dist/__tests__/inbox-watcher.test.d.ts +2 -0
- package/dist/__tests__/inbox-watcher.test.d.ts.map +1 -0
- package/dist/__tests__/inbox-watcher.test.js +104 -0
- package/dist/__tests__/inbox-watcher.test.js.map +1 -0
- package/dist/__tests__/persistence-path.test.d.ts +2 -0
- package/dist/__tests__/persistence-path.test.d.ts.map +1 -0
- package/dist/__tests__/persistence-path.test.js +79 -0
- package/dist/__tests__/persistence-path.test.js.map +1 -0
- package/dist/__tests__/persistence-robust.test.d.ts +2 -0
- package/dist/__tests__/persistence-robust.test.d.ts.map +1 -0
- package/dist/__tests__/persistence-robust.test.js +125 -0
- package/dist/__tests__/persistence-robust.test.js.map +1 -0
- package/dist/__tests__/persistence.test.d.ts +2 -0
- package/dist/__tests__/persistence.test.d.ts.map +1 -0
- package/dist/__tests__/persistence.test.js +105 -0
- package/dist/__tests__/persistence.test.js.map +1 -0
- package/dist/__tests__/phase4-5-e2e.test.d.ts +2 -0
- package/dist/__tests__/phase4-5-e2e.test.d.ts.map +1 -0
- package/dist/__tests__/phase4-5-e2e.test.js +203 -0
- package/dist/__tests__/phase4-5-e2e.test.js.map +1 -0
- package/dist/__tests__/phase6-7-e2e.test.d.ts +2 -0
- package/dist/__tests__/phase6-7-e2e.test.d.ts.map +1 -0
- package/dist/__tests__/phase6-7-e2e.test.js +93 -0
- package/dist/__tests__/phase6-7-e2e.test.js.map +1 -0
- package/dist/__tests__/project-files.test.d.ts +2 -0
- package/dist/__tests__/project-files.test.d.ts.map +1 -0
- package/dist/__tests__/project-files.test.js +143 -0
- package/dist/__tests__/project-files.test.js.map +1 -0
- package/dist/__tests__/projects-fs.test.d.ts +2 -0
- package/dist/__tests__/projects-fs.test.d.ts.map +1 -0
- package/dist/__tests__/projects-fs.test.js +107 -0
- package/dist/__tests__/projects-fs.test.js.map +1 -0
- package/dist/__tests__/recovery-cross-project.test.d.ts +2 -0
- package/dist/__tests__/recovery-cross-project.test.d.ts.map +1 -0
- package/dist/__tests__/recovery-cross-project.test.js +87 -0
- package/dist/__tests__/recovery-cross-project.test.js.map +1 -0
- package/dist/__tests__/recovery-forwards-to-coordinator.test.d.ts +2 -0
- package/dist/__tests__/recovery-forwards-to-coordinator.test.d.ts.map +1 -0
- package/dist/__tests__/recovery-forwards-to-coordinator.test.js +59 -0
- package/dist/__tests__/recovery-forwards-to-coordinator.test.js.map +1 -0
- package/dist/__tests__/recovery-resume.test.d.ts +2 -0
- package/dist/__tests__/recovery-resume.test.d.ts.map +1 -0
- package/dist/__tests__/recovery-resume.test.js +132 -0
- package/dist/__tests__/recovery-resume.test.js.map +1 -0
- package/dist/__tests__/retrospective.test.js +1 -0
- package/dist/__tests__/retrospective.test.js.map +1 -1
- package/dist/__tests__/role-loader-preamble-all.test.d.ts +2 -0
- package/dist/__tests__/role-loader-preamble-all.test.d.ts.map +1 -0
- package/dist/__tests__/role-loader-preamble-all.test.js +38 -0
- package/dist/__tests__/role-loader-preamble-all.test.js.map +1 -0
- package/dist/__tests__/role-loader-tools.test.d.ts +2 -0
- package/dist/__tests__/role-loader-tools.test.d.ts.map +1 -0
- package/dist/__tests__/role-loader-tools.test.js +39 -0
- package/dist/__tests__/role-loader-tools.test.js.map +1 -0
- package/dist/__tests__/role-loader.test.js +116 -1
- package/dist/__tests__/role-loader.test.js.map +1 -1
- package/dist/__tests__/role-prompt-no-legacy-protocol.test.d.ts +2 -0
- package/dist/__tests__/role-prompt-no-legacy-protocol.test.d.ts.map +1 -0
- package/dist/__tests__/role-prompt-no-legacy-protocol.test.js +37 -0
- package/dist/__tests__/role-prompt-no-legacy-protocol.test.js.map +1 -0
- package/dist/__tests__/role-tools.test.d.ts +2 -0
- package/dist/__tests__/role-tools.test.d.ts.map +1 -0
- package/dist/__tests__/role-tools.test.js +80 -0
- package/dist/__tests__/role-tools.test.js.map +1 -0
- package/dist/__tests__/spawn-role-injects-briefings.test.d.ts +2 -0
- package/dist/__tests__/spawn-role-injects-briefings.test.d.ts.map +1 -0
- package/dist/__tests__/spawn-role-injects-briefings.test.js +182 -0
- package/dist/__tests__/spawn-role-injects-briefings.test.js.map +1 -0
- package/dist/__tests__/spawn-role-tool-policy.test.d.ts +2 -0
- package/dist/__tests__/spawn-role-tool-policy.test.d.ts.map +1 -0
- package/dist/__tests__/spawn-role-tool-policy.test.js +96 -0
- package/dist/__tests__/spawn-role-tool-policy.test.js.map +1 -0
- package/dist/__tests__/swarm-coordinator-inbox-watcher.test.d.ts +2 -0
- package/dist/__tests__/swarm-coordinator-inbox-watcher.test.d.ts.map +1 -0
- package/dist/__tests__/swarm-coordinator-inbox-watcher.test.js +61 -0
- package/dist/__tests__/swarm-coordinator-inbox-watcher.test.js.map +1 -0
- package/dist/__tests__/swarm-coordinator-inbox.test.d.ts +2 -0
- package/dist/__tests__/swarm-coordinator-inbox.test.d.ts.map +1 -0
- package/dist/__tests__/swarm-coordinator-inbox.test.js +182 -0
- package/dist/__tests__/swarm-coordinator-inbox.test.js.map +1 -0
- package/dist/__tests__/swarm-coordinator-init.test.js +36 -8
- package/dist/__tests__/swarm-coordinator-init.test.js.map +1 -1
- package/dist/__tests__/swarm-coordinator-legacy-plan-review-warn.test.d.ts +2 -0
- package/dist/__tests__/swarm-coordinator-legacy-plan-review-warn.test.d.ts.map +1 -0
- package/dist/__tests__/swarm-coordinator-legacy-plan-review-warn.test.js +113 -0
- package/dist/__tests__/swarm-coordinator-legacy-plan-review-warn.test.js.map +1 -0
- package/dist/__tests__/swarm-coordinator-plan-review-intercept.test.d.ts +2 -0
- package/dist/__tests__/swarm-coordinator-plan-review-intercept.test.d.ts.map +1 -0
- package/dist/__tests__/swarm-coordinator-plan-review-intercept.test.js +465 -0
- package/dist/__tests__/swarm-coordinator-plan-review-intercept.test.js.map +1 -0
- package/dist/__tests__/swarm-coordinator-plan-review-recovery.test.d.ts +2 -0
- package/dist/__tests__/swarm-coordinator-plan-review-recovery.test.d.ts.map +1 -0
- package/dist/__tests__/swarm-coordinator-plan-review-recovery.test.js +284 -0
- package/dist/__tests__/swarm-coordinator-plan-review-recovery.test.js.map +1 -0
- package/dist/__tests__/swarm-coordinator-plan-review.test.d.ts +2 -0
- package/dist/__tests__/swarm-coordinator-plan-review.test.d.ts.map +1 -0
- package/dist/__tests__/swarm-coordinator-plan-review.test.js +294 -0
- package/dist/__tests__/swarm-coordinator-plan-review.test.js.map +1 -0
- package/dist/__tests__/swarm-coordinator-resume.test.d.ts +2 -0
- package/dist/__tests__/swarm-coordinator-resume.test.d.ts.map +1 -0
- package/dist/__tests__/swarm-coordinator-resume.test.js +93 -0
- package/dist/__tests__/swarm-coordinator-resume.test.js.map +1 -0
- package/dist/__tests__/swarm-coordinator-roleId.test.js +2 -2
- package/dist/__tests__/swarm-coordinator-roleId.test.js.map +1 -1
- package/dist/__tests__/swarm-destroy-detach.test.d.ts +2 -0
- package/dist/__tests__/swarm-destroy-detach.test.d.ts.map +1 -0
- package/dist/__tests__/swarm-destroy-detach.test.js +135 -0
- package/dist/__tests__/swarm-destroy-detach.test.js.map +1 -0
- package/dist/action-parser.d.ts +0 -9
- package/dist/action-parser.d.ts.map +1 -1
- package/dist/action-parser.js +0 -114
- package/dist/action-parser.js.map +1 -1
- package/dist/inbox-relay.d.ts +50 -0
- package/dist/inbox-relay.d.ts.map +1 -0
- package/dist/inbox-relay.js +168 -0
- package/dist/inbox-relay.js.map +1 -0
- package/dist/inbox-store.d.ts +25 -0
- package/dist/inbox-store.d.ts.map +1 -0
- package/dist/inbox-store.js +95 -0
- package/dist/inbox-store.js.map +1 -0
- package/dist/inbox-watcher.d.ts +13 -0
- package/dist/inbox-watcher.d.ts.map +1 -0
- package/dist/inbox-watcher.js +89 -0
- package/dist/inbox-watcher.js.map +1 -0
- package/dist/index.d.ts +8 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -2
- package/dist/index.js.map +1 -1
- package/dist/persistence.d.ts +19 -5
- package/dist/persistence.d.ts.map +1 -1
- package/dist/persistence.js +97 -22
- package/dist/persistence.js.map +1 -1
- package/dist/project-files.d.ts +60 -0
- package/dist/project-files.d.ts.map +1 -0
- package/dist/project-files.js +214 -0
- package/dist/project-files.js.map +1 -0
- package/dist/projects-fs.d.ts +28 -0
- package/dist/projects-fs.d.ts.map +1 -0
- package/dist/projects-fs.js +111 -0
- package/dist/projects-fs.js.map +1 -0
- package/dist/recovery.d.ts +12 -0
- package/dist/recovery.d.ts.map +1 -1
- package/dist/recovery.js +14 -19
- package/dist/recovery.js.map +1 -1
- package/dist/roles/role-loader.d.ts +28 -1
- package/dist/roles/role-loader.d.ts.map +1 -1
- package/dist/roles/role-loader.js +73 -1
- package/dist/roles/role-loader.js.map +1 -1
- package/dist/roles/role-tools.d.ts +16 -0
- package/dist/roles/role-tools.d.ts.map +1 -0
- package/dist/roles/role-tools.js +25 -0
- package/dist/roles/role-tools.js.map +1 -0
- package/dist/roles/types.d.ts +4 -0
- package/dist/roles/types.d.ts.map +1 -1
- package/dist/swarm-coordinator.d.ts +176 -12
- package/dist/swarm-coordinator.d.ts.map +1 -1
- package/dist/swarm-coordinator.js +863 -370
- package/dist/swarm-coordinator.js.map +1 -1
- package/dist/types.d.ts +26 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +9 -6
- package/roles/analyst-livermore.md +6 -30
- package/roles/designer-rams.md +2 -30
- package/roles/dev-torvalds.md +8 -44
- package/roles/developer.md +5 -21
- package/roles/director-jia.md +20 -49
- package/roles/editor-boyong.md +8 -40
- package/roles/macro-dalio.md +6 -30
- package/roles/planner-maoni.md +24 -53
- package/roles/pm-jobs.md +20 -71
- package/roles/preset-analyst-simons.md +2 -18
- package/roles/preset-architect-knuth.md +2 -18
- package/roles/preset-designer-norman.md +2 -18
- package/roles/preset-designer.md +2 -18
- package/roles/preset-dev-carmack.md +2 -18
- package/roles/preset-dev-gosling.md +2 -18
- package/roles/preset-developer.md +7 -23
- package/roles/preset-manager-grove.md +2 -18
- package/roles/preset-manager-musk.md +2 -18
- package/roles/preset-pm.md +7 -34
- package/roles/preset-researcher-feynman.md +2 -18
- package/roles/preset-reviewer.md +5 -21
- package/roles/preset-strategist-buffett.md +2 -18
- package/roles/preset-strategist-munger.md +2 -18
- package/roles/preset-strategist-sunzi.md +2 -18
- package/roles/preset-tester-beck.md +2 -18
- package/roles/preset-tester.md +5 -21
- package/roles/preset-writer-orwell.md +2 -18
- package/roles/preset-writer.md +2 -18
- package/roles/quant-simons.md +5 -32
- package/roles/queen.md +25 -41
- package/roles/reviewer-martin.md +11 -37
- package/roles/reviewer.md +20 -21
- package/roles/rhythm-tangsan.md +5 -29
- package/roles/risk-taleb.md +4 -32
- package/roles/script-shitiesheng.md +8 -31
- package/roles/storyboard-xuke.md +9 -29
- package/roles/strategist-soros.md +16 -73
- package/roles/tester-beck.md +4 -40
- package/roles/tester.md +5 -21
- package/roles/trader-jones.md +4 -32
- package/roles/vfx-guchangwei.md +8 -27
- package/roles/writer-zhouzi.md +7 -39
- package/templates/dev-team-pro.md +4 -1
- package/templates/dev-team.md +3 -1
- package/templates/minimal.md +2 -1
- package/templates/trading-team.md +6 -1
- package/templates/video-team.md +4 -1
- package/templates/writing-team.md +4 -1
package/dist/persistence.js
CHANGED
|
@@ -1,52 +1,89 @@
|
|
|
1
|
-
import { writeFileSync, readFileSync, existsSync, mkdirSync, appendFileSync, rmSync, readdirSync, } from "node:fs";
|
|
1
|
+
import { writeFileSync, readFileSync, existsSync, mkdirSync, appendFileSync, rmSync, readdirSync, renameSync, } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { homedir } from "node:os";
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
import { randomBytes } from "node:crypto";
|
|
5
|
+
import { projectRoot } from "@mclawnet/task";
|
|
6
|
+
import { createLogger } from "@mclawnet/logger";
|
|
7
|
+
const log = createLogger({ module: "swarm-persistence" });
|
|
8
|
+
function getHome() {
|
|
9
|
+
return process.env.CLAWNET_HOME ?? homedir();
|
|
6
10
|
}
|
|
7
|
-
function
|
|
8
|
-
return join(
|
|
11
|
+
function getSwarmsDir(workDir) {
|
|
12
|
+
return join(projectRoot(workDir, getHome()), "swarms");
|
|
13
|
+
}
|
|
14
|
+
function getSwarmDir(workDir, swarmId) {
|
|
15
|
+
return join(getSwarmsDir(workDir), swarmId);
|
|
9
16
|
}
|
|
10
17
|
export function saveSwarmSnapshot(swarm) {
|
|
11
|
-
|
|
18
|
+
if (!swarm.workDir) {
|
|
19
|
+
throw new Error("saveSwarmSnapshot requires swarm.workDir");
|
|
20
|
+
}
|
|
21
|
+
const dir = getSwarmDir(swarm.workDir, swarm.id);
|
|
12
22
|
mkdirSync(dir, { recursive: true });
|
|
13
23
|
const snapshot = {
|
|
14
24
|
id: swarm.id,
|
|
15
25
|
hubSessionId: swarm.hubSessionId,
|
|
16
26
|
workDir: swarm.workDir,
|
|
27
|
+
teamName: swarm.teamName ?? "default",
|
|
17
28
|
roles: Array.from(swarm.roles.values()).map((r) => ({
|
|
18
29
|
instanceId: r.instanceId,
|
|
19
30
|
roleName: r.roleName,
|
|
20
31
|
status: r.status,
|
|
21
32
|
currentTask: r.currentTask,
|
|
33
|
+
claudeSessionId: r.claudeSessionId,
|
|
34
|
+
type: r.definition?.type,
|
|
22
35
|
})),
|
|
23
36
|
plan: swarm.plan,
|
|
24
37
|
nextInstanceSeq: Object.fromEntries(swarm.nextInstanceSeq),
|
|
25
38
|
savedAt: Date.now(),
|
|
26
39
|
status: swarm.status,
|
|
27
40
|
planStatus: swarm.planStatus,
|
|
41
|
+
partialRecover: swarm.partialRecover,
|
|
28
42
|
};
|
|
29
|
-
|
|
43
|
+
// Atomic write: write to a unique tmp file in the same dir, then rename
|
|
44
|
+
// over the target. rename(2) within a filesystem is atomic — readers either
|
|
45
|
+
// see the previous valid snapshot or the new one, never a half-written file.
|
|
46
|
+
const finalPath = join(dir, "recovery.json");
|
|
47
|
+
const tmpPath = join(dir, `recovery.${randomBytes(6).toString("hex")}.tmp`);
|
|
48
|
+
writeFileSync(tmpPath, JSON.stringify(snapshot, null, 2));
|
|
49
|
+
try {
|
|
50
|
+
renameSync(tmpPath, finalPath);
|
|
51
|
+
}
|
|
52
|
+
catch (err) {
|
|
53
|
+
// best-effort cleanup of the tmp file so we don't leak garbage on disk
|
|
54
|
+
try {
|
|
55
|
+
rmSync(tmpPath, { force: true });
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
// ignore
|
|
59
|
+
}
|
|
60
|
+
throw err;
|
|
61
|
+
}
|
|
30
62
|
}
|
|
31
|
-
export function loadSwarmSnapshot(swarmId) {
|
|
32
|
-
const filePath = join(getSwarmDir(swarmId), "recovery.json");
|
|
63
|
+
export function loadSwarmSnapshot(workDir, swarmId) {
|
|
64
|
+
const filePath = join(getSwarmDir(workDir, swarmId), "recovery.json");
|
|
33
65
|
if (!existsSync(filePath))
|
|
34
66
|
return null;
|
|
35
67
|
try {
|
|
36
|
-
|
|
68
|
+
const raw = JSON.parse(readFileSync(filePath, "utf-8"));
|
|
69
|
+
return {
|
|
70
|
+
...raw,
|
|
71
|
+
teamName: raw.teamName ?? "default",
|
|
72
|
+
};
|
|
37
73
|
}
|
|
38
|
-
catch {
|
|
74
|
+
catch (err) {
|
|
75
|
+
log.warn({ err: err instanceof Error ? err.message : String(err), filePath, swarmId, workDir }, "loadSwarmSnapshot: failed to parse recovery.json — treating as missing");
|
|
39
76
|
return null;
|
|
40
77
|
}
|
|
41
78
|
}
|
|
42
|
-
export function deleteSwarmSnapshot(swarmId) {
|
|
43
|
-
const recoveryPath = join(getSwarmDir(swarmId), "recovery.json");
|
|
79
|
+
export function deleteSwarmSnapshot(workDir, swarmId) {
|
|
80
|
+
const recoveryPath = join(getSwarmDir(workDir, swarmId), "recovery.json");
|
|
44
81
|
if (existsSync(recoveryPath)) {
|
|
45
82
|
rmSync(recoveryPath);
|
|
46
83
|
}
|
|
47
84
|
}
|
|
48
|
-
export function readMessageLog(swarmId) {
|
|
49
|
-
const filePath = join(getSwarmDir(swarmId), "messages.jsonl");
|
|
85
|
+
export function readMessageLog(workDir, swarmId) {
|
|
86
|
+
const filePath = join(getSwarmDir(workDir, swarmId), "messages.jsonl");
|
|
50
87
|
if (!existsSync(filePath))
|
|
51
88
|
return [];
|
|
52
89
|
try {
|
|
@@ -68,17 +105,55 @@ export function readMessageLog(swarmId) {
|
|
|
68
105
|
return [];
|
|
69
106
|
}
|
|
70
107
|
}
|
|
71
|
-
export function appendMessageLog(swarmId, entry) {
|
|
72
|
-
const dir = getSwarmDir(swarmId);
|
|
108
|
+
export function appendMessageLog(workDir, swarmId, entry) {
|
|
109
|
+
const dir = getSwarmDir(workDir, swarmId);
|
|
73
110
|
mkdirSync(dir, { recursive: true });
|
|
74
111
|
appendFileSync(join(dir, "messages.jsonl"), JSON.stringify(entry) + "\n");
|
|
75
112
|
}
|
|
113
|
+
/**
|
|
114
|
+
* List recoverable swarms across all per-project roots. The authoritative
|
|
115
|
+
* `workDir` is read from each snapshot's `workDir` field (snapshots written
|
|
116
|
+
* since 0.1.6 always include it). Falling back on the encoded directory
|
|
117
|
+
* name is lossy for paths whose segments contain `-`, so we deliberately
|
|
118
|
+
* skip entries whose snapshot can't be parsed or doesn't carry `workDir`.
|
|
119
|
+
*/
|
|
76
120
|
export function listRecoverableSwarmIds() {
|
|
77
|
-
const
|
|
78
|
-
if (!existsSync(
|
|
121
|
+
const projectsDir = join(getHome(), ".clawnet", "projects");
|
|
122
|
+
if (!existsSync(projectsDir))
|
|
79
123
|
return [];
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
.
|
|
124
|
+
const result = [];
|
|
125
|
+
for (const projEntry of readdirSync(projectsDir, { withFileTypes: true })) {
|
|
126
|
+
if (!projEntry.isDirectory())
|
|
127
|
+
continue;
|
|
128
|
+
const swarmsDir = join(projectsDir, projEntry.name, "swarms");
|
|
129
|
+
if (!existsSync(swarmsDir))
|
|
130
|
+
continue;
|
|
131
|
+
for (const swarmEntry of readdirSync(swarmsDir, { withFileTypes: true })) {
|
|
132
|
+
if (!swarmEntry.isDirectory())
|
|
133
|
+
continue;
|
|
134
|
+
const swarmDir = join(swarmsDir, swarmEntry.name);
|
|
135
|
+
const recoveryPath = join(swarmDir, "recovery.json");
|
|
136
|
+
if (!existsSync(recoveryPath)) {
|
|
137
|
+
// Orphan: a swarm directory exists but was never persisted (likely
|
|
138
|
+
// an aborted SwarmCoordinator.create()). Surface it for the operator
|
|
139
|
+
// — never auto-delete (could destroy in-progress user state).
|
|
140
|
+
log.warn({ swarmDir, swarmId: swarmEntry.name, projectDir: projEntry.name }, "listRecoverableSwarmIds: swarm directory has no recovery.json — skipping (manual cleanup may be needed)");
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
try {
|
|
144
|
+
const raw = JSON.parse(readFileSync(recoveryPath, "utf-8"));
|
|
145
|
+
if (typeof raw.workDir === "string" && raw.workDir.length > 0) {
|
|
146
|
+
result.push({ workDir: raw.workDir, swarmId: swarmEntry.name });
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
log.warn({ recoveryPath, swarmId: swarmEntry.name }, "listRecoverableSwarmIds: snapshot missing workDir field — skipping");
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
catch (err) {
|
|
153
|
+
log.warn({ err: err instanceof Error ? err.message : String(err), recoveryPath, swarmId: swarmEntry.name }, "listRecoverableSwarmIds: failed to parse recovery.json — skipping");
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return result;
|
|
83
158
|
}
|
|
84
159
|
//# sourceMappingURL=persistence.js.map
|
package/dist/persistence.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"persistence.js","sourceRoot":"","sources":["../src/persistence.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,aAAa,EACb,YAAY,EACZ,UAAU,EACV,SAAS,EACT,cAAc,EACd,MAAM,EACN,WAAW,
|
|
1
|
+
{"version":3,"file":"persistence.js","sourceRoot":"","sources":["../src/persistence.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Project files endpoint helpers.
|
|
3
|
+
*
|
|
4
|
+
* Surfaces a fixed whitelist of files inside a project's `workDir` (CLAUDE.md
|
|
5
|
+
* and .claude/settings*.json) so the UI can read/write them without giving
|
|
6
|
+
* blanket FS access.
|
|
7
|
+
*
|
|
8
|
+
* Why a hard-coded whitelist (no globs in P1):
|
|
9
|
+
* The Claude CLI auto-loads exactly these paths from cwd. Limiting to the
|
|
10
|
+
* exact set lets us audit every read/write and rules out mistakes like a
|
|
11
|
+
* user typing `relPath=../../etc/passwd` or `.git/config`. Phase 2 may add
|
|
12
|
+
* `.claude/rules/*.md`, which will require a careful prefix-glob design.
|
|
13
|
+
*
|
|
14
|
+
* Defense-in-depth path checks:
|
|
15
|
+
* 1. relPath must be in WHITELIST (exact string match)
|
|
16
|
+
* 2. Resolved absolute path must live under realpath(workDir)
|
|
17
|
+
* 3. realpath() resolves symlinks, so a symlink inside .claude/ that
|
|
18
|
+
* points outside workDir is still rejected by check (2).
|
|
19
|
+
*/
|
|
20
|
+
export declare const PROJECT_FILE_WHITELIST: readonly ["CLAUDE.md", ".claude/CLAUDE.md", ".claude/settings.json", ".claude/settings.local.json"];
|
|
21
|
+
export type ProjectFileRelPath = (typeof PROJECT_FILE_WHITELIST)[number];
|
|
22
|
+
export interface ProjectFileEntry {
|
|
23
|
+
relPath: ProjectFileRelPath;
|
|
24
|
+
exists: boolean;
|
|
25
|
+
size?: number;
|
|
26
|
+
mtime?: number;
|
|
27
|
+
loadedByCli: true;
|
|
28
|
+
}
|
|
29
|
+
export interface ProjectFileContent {
|
|
30
|
+
relPath: ProjectFileRelPath;
|
|
31
|
+
content: string;
|
|
32
|
+
mtime: number;
|
|
33
|
+
etag: string;
|
|
34
|
+
}
|
|
35
|
+
export declare class ProjectFilesError extends Error {
|
|
36
|
+
status: number;
|
|
37
|
+
details?: Record<string, unknown> | undefined;
|
|
38
|
+
constructor(status: number, message: string, details?: Record<string, unknown> | undefined);
|
|
39
|
+
}
|
|
40
|
+
/** Type guard — narrows an arbitrary string to the whitelist union. */
|
|
41
|
+
export declare function isWhitelistedRelPath(rel: string): rel is ProjectFileRelPath;
|
|
42
|
+
/** List the whitelist with stat info for present files. Never throws. */
|
|
43
|
+
export declare function listProjectFiles(workDir: string): ProjectFileEntry[];
|
|
44
|
+
/**
|
|
45
|
+
* Read a whitelisted file. Returns content="" + etag of empty string when
|
|
46
|
+
* the file does not exist, so the editor UI can populate a blank textarea
|
|
47
|
+
* with a valid etag for first-write conflict detection.
|
|
48
|
+
*/
|
|
49
|
+
export declare function readProjectFile(workDir: string, relPath: string): ProjectFileContent;
|
|
50
|
+
export interface WriteOptions {
|
|
51
|
+
ifMatchEtag?: string;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Atomic write: validates JSON syntax for `.json` files, ensures parent
|
|
55
|
+
* `.claude/` exists, writes to a tmp sibling, then renames. Returns the
|
|
56
|
+
* post-write content snapshot. On etag mismatch throws 409 with the current
|
|
57
|
+
* server-side content so the UI can render a 3-way diff.
|
|
58
|
+
*/
|
|
59
|
+
export declare function writeProjectFile(workDir: string, relPath: string, newContent: string, opts?: WriteOptions): ProjectFileContent;
|
|
60
|
+
//# sourceMappingURL=project-files.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"project-files.d.ts","sourceRoot":"","sources":["../src/project-files.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAcH,eAAO,MAAM,sBAAsB,qGAKzB,CAAC;AAEX,MAAM,MAAM,kBAAkB,GAAG,CAAC,OAAO,sBAAsB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEzE,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,kBAAkB,CAAC;IAC5B,MAAM,EAAE,OAAO,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,IAAI,CAAC;CACnB;AAED,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,kBAAkB,CAAC;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;CACd;AAED,qBAAa,iBAAkB,SAAQ,KAAK;IAEjC,MAAM,EAAE,MAAM;IAEd,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;gBAFjC,MAAM,EAAE,MAAM,EACrB,OAAO,EAAE,MAAM,EACR,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,YAAA;CAK3C;AAED,uEAAuE;AACvE,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,IAAI,kBAAkB,CAE3E;AA8CD,yEAAyE;AACzE,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,gBAAgB,EAAE,CAmCpE;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,kBAAkB,CAmBpF;AAED,MAAM,WAAW,YAAY;IAC3B,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,EAClB,IAAI,GAAE,YAAiB,GACtB,kBAAkB,CA6DpB"}
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Project files endpoint helpers.
|
|
3
|
+
*
|
|
4
|
+
* Surfaces a fixed whitelist of files inside a project's `workDir` (CLAUDE.md
|
|
5
|
+
* and .claude/settings*.json) so the UI can read/write them without giving
|
|
6
|
+
* blanket FS access.
|
|
7
|
+
*
|
|
8
|
+
* Why a hard-coded whitelist (no globs in P1):
|
|
9
|
+
* The Claude CLI auto-loads exactly these paths from cwd. Limiting to the
|
|
10
|
+
* exact set lets us audit every read/write and rules out mistakes like a
|
|
11
|
+
* user typing `relPath=../../etc/passwd` or `.git/config`. Phase 2 may add
|
|
12
|
+
* `.claude/rules/*.md`, which will require a careful prefix-glob design.
|
|
13
|
+
*
|
|
14
|
+
* Defense-in-depth path checks:
|
|
15
|
+
* 1. relPath must be in WHITELIST (exact string match)
|
|
16
|
+
* 2. Resolved absolute path must live under realpath(workDir)
|
|
17
|
+
* 3. realpath() resolves symlinks, so a symlink inside .claude/ that
|
|
18
|
+
* points outside workDir is still rejected by check (2).
|
|
19
|
+
*/
|
|
20
|
+
import { existsSync, mkdirSync, readFileSync, realpathSync, renameSync, statSync, writeFileSync, } from "node:fs";
|
|
21
|
+
import { createHash, randomBytes } from "node:crypto";
|
|
22
|
+
import { dirname, join, sep } from "node:path";
|
|
23
|
+
export const PROJECT_FILE_WHITELIST = [
|
|
24
|
+
"CLAUDE.md",
|
|
25
|
+
".claude/CLAUDE.md",
|
|
26
|
+
".claude/settings.json",
|
|
27
|
+
".claude/settings.local.json",
|
|
28
|
+
];
|
|
29
|
+
export class ProjectFilesError extends Error {
|
|
30
|
+
status;
|
|
31
|
+
details;
|
|
32
|
+
constructor(status, message, details) {
|
|
33
|
+
super(message);
|
|
34
|
+
this.status = status;
|
|
35
|
+
this.details = details;
|
|
36
|
+
this.name = "ProjectFilesError";
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/** Type guard — narrows an arbitrary string to the whitelist union. */
|
|
40
|
+
export function isWhitelistedRelPath(rel) {
|
|
41
|
+
return PROJECT_FILE_WHITELIST.includes(rel);
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Resolve `relPath` against `workDir` and verify the result is still inside
|
|
45
|
+
* workDir even after symlink resolution. Throws ProjectFilesError on any
|
|
46
|
+
* violation. Returns the absolute path (which may not exist yet — caller
|
|
47
|
+
* decides whether absence is OK).
|
|
48
|
+
*/
|
|
49
|
+
function resolveSafe(workDir, relPath) {
|
|
50
|
+
// workDir itself must exist for any of this to make sense.
|
|
51
|
+
let workDirReal;
|
|
52
|
+
try {
|
|
53
|
+
workDirReal = realpathSync(workDir);
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
throw new ProjectFilesError(404, "workDir does not exist on disk");
|
|
57
|
+
}
|
|
58
|
+
const candidate = join(workDirReal, relPath);
|
|
59
|
+
// For an existing file, realpath the file. For a not-yet-existing file,
|
|
60
|
+
// realpath the *parent* (which we may need to mkdir later) — this still
|
|
61
|
+
// catches symlink escapes from intermediate components.
|
|
62
|
+
let probe;
|
|
63
|
+
if (existsSync(candidate)) {
|
|
64
|
+
probe = realpathSync(candidate);
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
// Walk up to the first existing ancestor and realpath it; that's enough
|
|
68
|
+
// to ensure we won't end up writing outside workDir even after rename().
|
|
69
|
+
let parent = dirname(candidate);
|
|
70
|
+
while (!existsSync(parent))
|
|
71
|
+
parent = dirname(parent);
|
|
72
|
+
probe = realpathSync(parent);
|
|
73
|
+
}
|
|
74
|
+
if (probe !== workDirReal && !probe.startsWith(workDirReal + sep)) {
|
|
75
|
+
throw new ProjectFilesError(403, "Path resolves outside workDir", {
|
|
76
|
+
relPath,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
return candidate;
|
|
80
|
+
}
|
|
81
|
+
function computeEtag(content) {
|
|
82
|
+
return createHash("sha256").update(content).digest("hex").slice(0, 12);
|
|
83
|
+
}
|
|
84
|
+
/** List the whitelist with stat info for present files. Never throws. */
|
|
85
|
+
export function listProjectFiles(workDir) {
|
|
86
|
+
const out = [];
|
|
87
|
+
for (const rel of PROJECT_FILE_WHITELIST) {
|
|
88
|
+
let abs;
|
|
89
|
+
try {
|
|
90
|
+
abs = resolveSafe(workDir, rel);
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
// workDir missing or path escape — surface as "not exists" rather than
|
|
94
|
+
// erroring the whole list call (e.g. a project whose workDir was deleted
|
|
95
|
+
// on disk should still be navigable).
|
|
96
|
+
out.push({ relPath: rel, exists: false, loadedByCli: true });
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
if (!existsSync(abs)) {
|
|
100
|
+
out.push({ relPath: rel, exists: false, loadedByCli: true });
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
try {
|
|
104
|
+
const st = statSync(abs);
|
|
105
|
+
if (!st.isFile()) {
|
|
106
|
+
out.push({ relPath: rel, exists: false, loadedByCli: true });
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
out.push({
|
|
110
|
+
relPath: rel,
|
|
111
|
+
exists: true,
|
|
112
|
+
size: st.size,
|
|
113
|
+
mtime: st.mtime.getTime(),
|
|
114
|
+
loadedByCli: true,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
out.push({ relPath: rel, exists: false, loadedByCli: true });
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return out;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Read a whitelisted file. Returns content="" + etag of empty string when
|
|
125
|
+
* the file does not exist, so the editor UI can populate a blank textarea
|
|
126
|
+
* with a valid etag for first-write conflict detection.
|
|
127
|
+
*/
|
|
128
|
+
export function readProjectFile(workDir, relPath) {
|
|
129
|
+
if (!isWhitelistedRelPath(relPath)) {
|
|
130
|
+
throw new ProjectFilesError(400, "relPath not in whitelist", { relPath });
|
|
131
|
+
}
|
|
132
|
+
const abs = resolveSafe(workDir, relPath);
|
|
133
|
+
if (!existsSync(abs)) {
|
|
134
|
+
return { relPath, content: "", mtime: 0, etag: computeEtag("") };
|
|
135
|
+
}
|
|
136
|
+
const st = statSync(abs);
|
|
137
|
+
if (!st.isFile()) {
|
|
138
|
+
throw new ProjectFilesError(400, "Path is not a regular file", { relPath });
|
|
139
|
+
}
|
|
140
|
+
const content = readFileSync(abs, "utf-8");
|
|
141
|
+
return {
|
|
142
|
+
relPath,
|
|
143
|
+
content,
|
|
144
|
+
mtime: st.mtime.getTime(),
|
|
145
|
+
etag: computeEtag(content),
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Atomic write: validates JSON syntax for `.json` files, ensures parent
|
|
150
|
+
* `.claude/` exists, writes to a tmp sibling, then renames. Returns the
|
|
151
|
+
* post-write content snapshot. On etag mismatch throws 409 with the current
|
|
152
|
+
* server-side content so the UI can render a 3-way diff.
|
|
153
|
+
*/
|
|
154
|
+
export function writeProjectFile(workDir, relPath, newContent, opts = {}) {
|
|
155
|
+
if (!isWhitelistedRelPath(relPath)) {
|
|
156
|
+
throw new ProjectFilesError(400, "relPath not in whitelist", { relPath });
|
|
157
|
+
}
|
|
158
|
+
// Optimistic-concurrency check happens BEFORE any FS mutation.
|
|
159
|
+
if (opts.ifMatchEtag !== undefined) {
|
|
160
|
+
const current = readProjectFile(workDir, relPath);
|
|
161
|
+
if (current.etag !== opts.ifMatchEtag) {
|
|
162
|
+
throw new ProjectFilesError(409, "etag mismatch", {
|
|
163
|
+
relPath,
|
|
164
|
+
currentEtag: current.etag,
|
|
165
|
+
currentContent: current.content,
|
|
166
|
+
currentMtime: current.mtime,
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
// Validate JSON before touching disk so we never leave broken settings.json.
|
|
171
|
+
if (relPath.endsWith(".json")) {
|
|
172
|
+
if (newContent.trim().length === 0) {
|
|
173
|
+
// Empty JSON → store as `{}` so the file remains valid for CLI consumers.
|
|
174
|
+
newContent = "{}\n";
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
try {
|
|
178
|
+
JSON.parse(newContent);
|
|
179
|
+
}
|
|
180
|
+
catch (err) {
|
|
181
|
+
throw new ProjectFilesError(400, "Invalid JSON", {
|
|
182
|
+
relPath,
|
|
183
|
+
parseError: err instanceof Error ? err.message : String(err),
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
else if (relPath.endsWith(".md")) {
|
|
189
|
+
// Ensure trailing newline — POSIX text-file convention; avoids editors
|
|
190
|
+
// appending a newline on next manual edit and producing spurious diffs.
|
|
191
|
+
if (newContent.length > 0 && !newContent.endsWith("\n")) {
|
|
192
|
+
newContent = newContent + "\n";
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
const abs = resolveSafe(workDir, relPath);
|
|
196
|
+
const parent = dirname(abs);
|
|
197
|
+
if (!existsSync(parent)) {
|
|
198
|
+
mkdirSync(parent, { recursive: true });
|
|
199
|
+
}
|
|
200
|
+
// tmp file lives in the same directory so rename() is atomic on the same FS.
|
|
201
|
+
// randomBytes avoids collisions if two writes race; the loser gets EEXIST on
|
|
202
|
+
// its own tmp (very unlikely) and retries via the API layer.
|
|
203
|
+
const tmp = join(parent, `.${randomBytes(6).toString("hex")}.tmp`);
|
|
204
|
+
writeFileSync(tmp, newContent, "utf-8");
|
|
205
|
+
renameSync(tmp, abs);
|
|
206
|
+
const st = statSync(abs);
|
|
207
|
+
return {
|
|
208
|
+
relPath,
|
|
209
|
+
content: newContent,
|
|
210
|
+
mtime: st.mtime.getTime(),
|
|
211
|
+
etag: computeEtag(newContent),
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
//# sourceMappingURL=project-files.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"project-files.js","sourceRoot":"","sources":["../src/project-files.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export type TaskStatus = "pending" | "in_progress" | "completed" | "cancelled";
|
|
2
|
+
export interface TaskCounts {
|
|
3
|
+
pending: number;
|
|
4
|
+
in_progress: number;
|
|
5
|
+
completed: number;
|
|
6
|
+
cancelled: number;
|
|
7
|
+
}
|
|
8
|
+
export interface ProjectSummary {
|
|
9
|
+
encodedCwd: string;
|
|
10
|
+
workDir: string;
|
|
11
|
+
createdAt: string | null;
|
|
12
|
+
swarmCount: number;
|
|
13
|
+
taskCount: number;
|
|
14
|
+
taskCounts: TaskCounts;
|
|
15
|
+
}
|
|
16
|
+
export interface SwarmSummary {
|
|
17
|
+
swarmId: string;
|
|
18
|
+
teamName: string;
|
|
19
|
+
status: string;
|
|
20
|
+
roleCount: number;
|
|
21
|
+
savedAt: number | null;
|
|
22
|
+
}
|
|
23
|
+
export declare function listProjectDirs(home?: string): string[];
|
|
24
|
+
export declare function resolveWorkDir(home: string, encodedCwd: string): string | null;
|
|
25
|
+
export declare function loadProjectSummary(home: string, encodedCwd: string): ProjectSummary | null;
|
|
26
|
+
export declare function loadSwarmSummaries(home: string, workDir: string): SwarmSummary[];
|
|
27
|
+
export declare function listAllProjectSummaries(home?: string): ProjectSummary[];
|
|
28
|
+
//# sourceMappingURL=projects-fs.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"projects-fs.d.ts","sourceRoot":"","sources":["../src/projects-fs.ts"],"names":[],"mappings":"AAMA,MAAM,MAAM,UAAU,GAAG,SAAS,GAAG,aAAa,GAAG,WAAW,GAAG,WAAW,CAAC;AAC/E,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AACD,MAAM,WAAW,cAAc;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,UAAU,CAAC;CACxB;AACD,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACxB;AAkBD,wBAAgB,eAAe,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAMvD;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAY9E;AAiBD,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,cAAc,GAAG,IAAI,CAkB1F;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,YAAY,EAAE,CAiBhF;AAED,wBAAgB,uBAAuB,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,cAAc,EAAE,CAKvE"}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { TaskStore, projectRoot } from "@mclawnet/task";
|
|
5
|
+
import { loadSwarmSnapshot } from "./persistence.js";
|
|
6
|
+
function getHome(homeOverride) {
|
|
7
|
+
return homeOverride ?? process.env.CLAWNET_HOME ?? homedir();
|
|
8
|
+
}
|
|
9
|
+
function projectsDir(home) {
|
|
10
|
+
return join(home, ".clawnet", "projects");
|
|
11
|
+
}
|
|
12
|
+
function isSafeEncoded(encoded) {
|
|
13
|
+
if (!encoded)
|
|
14
|
+
return false;
|
|
15
|
+
if (encoded.includes("/") || encoded.includes("\\"))
|
|
16
|
+
return false;
|
|
17
|
+
if (encoded.includes("\0"))
|
|
18
|
+
return false;
|
|
19
|
+
if (encoded === "." || encoded === ".." || encoded.includes(".."))
|
|
20
|
+
return false;
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
export function listProjectDirs(home) {
|
|
24
|
+
const dir = projectsDir(getHome(home));
|
|
25
|
+
if (!existsSync(dir))
|
|
26
|
+
return [];
|
|
27
|
+
return readdirSync(dir, { withFileTypes: true })
|
|
28
|
+
.filter((e) => e.isDirectory())
|
|
29
|
+
.map((e) => e.name);
|
|
30
|
+
}
|
|
31
|
+
export function resolveWorkDir(home, encodedCwd) {
|
|
32
|
+
if (!isSafeEncoded(encodedCwd))
|
|
33
|
+
return null;
|
|
34
|
+
const dirs = listProjectDirs(home);
|
|
35
|
+
if (!dirs.includes(encodedCwd))
|
|
36
|
+
return null;
|
|
37
|
+
const metaPath = join(projectsDir(home), encodedCwd, "meta.json");
|
|
38
|
+
if (!existsSync(metaPath))
|
|
39
|
+
return null;
|
|
40
|
+
try {
|
|
41
|
+
const meta = JSON.parse(readFileSync(metaPath, "utf-8"));
|
|
42
|
+
return typeof meta.workDir === "string" ? meta.workDir : null;
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
function readMetaCreatedAt(home, encodedCwd) {
|
|
49
|
+
const metaPath = join(projectsDir(home), encodedCwd, "meta.json");
|
|
50
|
+
if (!existsSync(metaPath))
|
|
51
|
+
return null;
|
|
52
|
+
try {
|
|
53
|
+
const meta = JSON.parse(readFileSync(metaPath, "utf-8"));
|
|
54
|
+
return meta.createdAt ?? null;
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
function emptyCounts() {
|
|
61
|
+
return { pending: 0, in_progress: 0, completed: 0, cancelled: 0 };
|
|
62
|
+
}
|
|
63
|
+
export function loadProjectSummary(home, encodedCwd) {
|
|
64
|
+
const workDir = resolveWorkDir(home, encodedCwd);
|
|
65
|
+
if (!workDir)
|
|
66
|
+
return null;
|
|
67
|
+
const store = new TaskStore({ workDir, home });
|
|
68
|
+
const tasks = store.list();
|
|
69
|
+
const counts = emptyCounts();
|
|
70
|
+
for (const t of tasks) {
|
|
71
|
+
if (t.status in counts)
|
|
72
|
+
counts[t.status] += 1;
|
|
73
|
+
}
|
|
74
|
+
const swarms = loadSwarmSummaries(home, workDir);
|
|
75
|
+
return {
|
|
76
|
+
encodedCwd,
|
|
77
|
+
workDir,
|
|
78
|
+
createdAt: readMetaCreatedAt(home, encodedCwd),
|
|
79
|
+
swarmCount: swarms.length,
|
|
80
|
+
taskCount: tasks.length,
|
|
81
|
+
taskCounts: counts,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
export function loadSwarmSummaries(home, workDir) {
|
|
85
|
+
const swarmsRoot = join(projectRoot(workDir, home), "swarms");
|
|
86
|
+
if (!existsSync(swarmsRoot))
|
|
87
|
+
return [];
|
|
88
|
+
const out = [];
|
|
89
|
+
for (const entry of readdirSync(swarmsRoot, { withFileTypes: true })) {
|
|
90
|
+
if (!entry.isDirectory())
|
|
91
|
+
continue;
|
|
92
|
+
const snap = loadSwarmSnapshot(workDir, entry.name);
|
|
93
|
+
if (!snap)
|
|
94
|
+
continue;
|
|
95
|
+
out.push({
|
|
96
|
+
swarmId: snap.id,
|
|
97
|
+
teamName: snap.teamName,
|
|
98
|
+
status: snap.status ?? "unknown",
|
|
99
|
+
roleCount: snap.roles?.length ?? 0,
|
|
100
|
+
savedAt: snap.savedAt ?? null,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
return out;
|
|
104
|
+
}
|
|
105
|
+
export function listAllProjectSummaries(home) {
|
|
106
|
+
const h = getHome(home);
|
|
107
|
+
return listProjectDirs(h)
|
|
108
|
+
.map((e) => loadProjectSummary(h, e))
|
|
109
|
+
.filter((s) => !!s);
|
|
110
|
+
}
|
|
111
|
+
//# sourceMappingURL=projects-fs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"projects-fs.js","sourceRoot":"","sources":["../src/projects-fs.ts"],"names":[],"mappings":"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"}
|
package/dist/recovery.d.ts
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
import type { SwarmCoordinator } from "./swarm-coordinator.js";
|
|
2
2
|
import { type SwarmSnapshot } from "./persistence.js";
|
|
3
3
|
export declare function listRecoverableSwarms(): SwarmSnapshot[];
|
|
4
|
+
/**
|
|
5
|
+
* Backwards-compatible recovery helper.
|
|
6
|
+
*
|
|
7
|
+
* Delegates to {@link SwarmCoordinator.recover}, which:
|
|
8
|
+
* - respawns each role with its persisted instanceId,
|
|
9
|
+
* - passes `claudeSessionId` through as `--resume` so per-role conversations
|
|
10
|
+
* continue,
|
|
11
|
+
* - drains each role's offline inbox via `inboxRelay.deliver`,
|
|
12
|
+
* - leaves the recovered swarm in `paused` state.
|
|
13
|
+
*
|
|
14
|
+
* Older callers still pass a full snapshot — we only need its `id`.
|
|
15
|
+
*/
|
|
4
16
|
export declare function recoverSwarm(coordinator: SwarmCoordinator, snapshot: SwarmSnapshot): Promise<void>;
|
|
5
17
|
//# sourceMappingURL=recovery.d.ts.map
|