@mclawnet/swarm 0.1.5 → 0.1.7
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/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__/role-loader-preamble-all.test.js +6 -6
- package/dist/__tests__/role-loader-preamble-all.test.js.map +1 -1
- package/dist/__tests__/role-loader-tools.test.js +2 -2
- package/dist/__tests__/role-loader-tools.test.js.map +1 -1
- package/dist/__tests__/role-loader.test.js +3 -3
- package/dist/__tests__/role-loader.test.js.map +1 -1
- package/dist/__tests__/role-tools.test.js +4 -4
- package/dist/__tests__/role-tools.test.js.map +1 -1
- package/dist/__tests__/spawn-role-tool-policy.test.js +2 -2
- package/dist/__tests__/spawn-role-tool-policy.test.js.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.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/roles/role-loader.d.ts +1 -1
- package/dist/roles/role-loader.d.ts.map +1 -1
- package/dist/roles/role-loader.js +7 -7
- package/dist/roles/role-tools.d.ts +1 -1
- package/dist/roles/role-tools.d.ts.map +1 -1
- package/dist/roles/role-tools.js +2 -2
- package/dist/roles/role-tools.js.map +1 -1
- package/dist/swarm-coordinator.js +3 -3
- package/dist/swarm-coordinator.js.map +1 -1
- package/package.json +3 -3
- package/roles/queen.md +2 -2
|
@@ -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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"}
|
|
@@ -25,7 +25,7 @@ export declare function listRoles(): string[];
|
|
|
25
25
|
* Replaces the legacy in-band ```swarm``` action blocks previously
|
|
26
26
|
* duplicated across each role md.
|
|
27
27
|
*/
|
|
28
|
-
export declare const INBOX_PROTOCOL_PREAMBLE = "\n\n### Inbox \u901A\u4FE1\u534F\u8BAE\n\n\u4F60\u4E0E\u8702\u7FA4\u5176\u4ED6\u6210\u5458\u3001user\u3001\u7CFB\u7EDF\u7684\u5168\u90E8\u901A\u4FE1\u90FD\u901A\u8FC7 **inbox \u6D88\u606F**\u8FDB\u884C\u3002\u6BCF\u5F53\u6709\u65B0\u6D88\u606F\u5230\u8FBE\uFF0C\u7CFB\u7EDF\u4F1A\u4EE5\u4E0B\u9762\u7684\u4FE1\u5C01\u6CE8\u5165\u5230\u4F60\u7684\u4E0B\u4E00\u8F6E\u8F93\u5165\uFF1A\n\n```xml\n<info_for_agent>\n <message id=\"...\" type=\"...\" from=\"...\" taskId=\"...\">\n <data>JSON-encoded payload</data>\n </message>\n ...\n</info_for_agent>\n```\n\n#### \u4F60\u4F1A\u6536\u5230\u7684\u6D88\u606F\u7C7B\u578B\n\n- **`task_assigned`** \u2014 \u4F60\u88AB\u6307\u6D3E\u4E86\u4E00\u4E2A\u65B0\u4EFB\u52A1\u3002`data` \u542B `{ taskId, subject, description }`\u3002**\u6536\u5230\u540E\u7B2C\u4E00\u6B65\u5148\u7528 `task_set_status` \u628A\u72B6\u6001\u6539\u4E3A `in_progress`**\uFF08\u8BA9 queen \u77E5\u9053\u4F60\u5DF2\u63A5\u6D3B\uFF09\uFF0C\u518D\u5F00\u59CB\u6267\u884C\uFF1B**\u5B8C\u6210\u7684\u6700\u540E\u4E00\u6B65\u5FC5\u987B\u662F `task_set_status({ status: \"completed\" })` + `task_add_comment({ body: \u6458\u8981 })`**\u2014\u2014\u5149\u5199\u5B8C\u4EE3\u7801/\u6587\u6863\u4E0D\u7B97\u4EA4\u4ED8\uFF0Cqueen \u53EA\u80FD\u901A\u8FC7\u72B6\u6001\u53D8\u5316\u8BC6\u522B\u4F60\u5B8C\u5DE5\uFF0C\u5426\u5219\u4F1A\u53CD\u590D nudge \u4F60\u3002\n- **`task_status_changed`** \u2014 \u4F60\u4E0B\u5C5E/\u76F8\u5173\u4EFB\u52A1\u7684\u72B6\u6001\u53D8\u5316\uFF08\u901A\u5E38\u53D1\u7ED9 queen\uFF09\u3002`data` \u542B `{ taskId, status, owner, subject }`\u3002\n- **`task_owner_set`** \u2014 \u4EFB\u52A1\u88AB\uFF08\u91CD\u65B0\uFF09\u5206\u914D\u7ED9\u4F60\u3002`data` \u542B `{ taskId, owner, subject }`\u3002\u7B49\u540C\u4E8E `task_assigned` \u7684\u8F6C\u4EA4\u8BED\u4E49\uFF08\u540C\u6837\u9700\u8981\u5148\u628A\u72B6\u6001\u6539\u4E3A `in_progress` \u518D\u5F00\u5DE5\uFF09\u3002\n- **`task_comment`** \u2014 \u4EFB\u52A1\u4E0A\u6709\u4EBA\u53D1\u8BC4\u8BBA\u3002`data` \u542B `{ taskId, commentId, author, body, type }`\u3002\n- **`unblock-*`** \u2014 \u4E0A\u6E38\u4EFB\u52A1\u5B8C\u6210\uFF0C\u4E0B\u6E38\u88AB\u89E3\u9501\u3002`data` \u63CF\u8FF0\u88AB\u89E3\u9501\u7684\u4EFB\u52A1\u3002\n- \u5176\u5B83\u7CFB\u7EDF\u6D88\u606F\uFF08\u5982 user \u7684\u76F4\u63A5\u8F93\u5165\uFF09\u4E5F\u4F1A\u4EE5 `<info_for_agent>` \u4FE1\u5C01\u5230\u8FBE\u3002\n\n#### \u4F60\u5E94\u8BE5\u4F7F\u7528\u7684 MCP \u5DE5\u5177\uFF08\u66FF\u4EE3\u65E7\u7684 ```swarm``` \u5757\uFF09\n\n| \u65E7 action \u5757 | \u65B0 MCP \u5DE5\u5177 |\n|---|---|\n| `{\"action\":\"send\", to, type, data, taskId}` | `
|
|
28
|
+
export declare const INBOX_PROTOCOL_PREAMBLE = "\n\n### Inbox \u901A\u4FE1\u534F\u8BAE\n\n\u4F60\u4E0E\u8702\u7FA4\u5176\u4ED6\u6210\u5458\u3001user\u3001\u7CFB\u7EDF\u7684\u5168\u90E8\u901A\u4FE1\u90FD\u901A\u8FC7 **inbox \u6D88\u606F**\u8FDB\u884C\u3002\u6BCF\u5F53\u6709\u65B0\u6D88\u606F\u5230\u8FBE\uFF0C\u7CFB\u7EDF\u4F1A\u4EE5\u4E0B\u9762\u7684\u4FE1\u5C01\u6CE8\u5165\u5230\u4F60\u7684\u4E0B\u4E00\u8F6E\u8F93\u5165\uFF1A\n\n```xml\n<info_for_agent>\n <message id=\"...\" type=\"...\" from=\"...\" taskId=\"...\">\n <data>JSON-encoded payload</data>\n </message>\n ...\n</info_for_agent>\n```\n\n#### \u4F60\u4F1A\u6536\u5230\u7684\u6D88\u606F\u7C7B\u578B\n\n- **`task_assigned`** \u2014 \u4F60\u88AB\u6307\u6D3E\u4E86\u4E00\u4E2A\u65B0\u4EFB\u52A1\u3002`data` \u542B `{ taskId, subject, description }`\u3002**\u6536\u5230\u540E\u7B2C\u4E00\u6B65\u5148\u7528 `task_set_status` \u628A\u72B6\u6001\u6539\u4E3A `in_progress`**\uFF08\u8BA9 queen \u77E5\u9053\u4F60\u5DF2\u63A5\u6D3B\uFF09\uFF0C\u518D\u5F00\u59CB\u6267\u884C\uFF1B**\u5B8C\u6210\u7684\u6700\u540E\u4E00\u6B65\u5FC5\u987B\u662F `task_set_status({ status: \"completed\" })` + `task_add_comment({ body: \u6458\u8981 })`**\u2014\u2014\u5149\u5199\u5B8C\u4EE3\u7801/\u6587\u6863\u4E0D\u7B97\u4EA4\u4ED8\uFF0Cqueen \u53EA\u80FD\u901A\u8FC7\u72B6\u6001\u53D8\u5316\u8BC6\u522B\u4F60\u5B8C\u5DE5\uFF0C\u5426\u5219\u4F1A\u53CD\u590D nudge \u4F60\u3002\n- **`task_status_changed`** \u2014 \u4F60\u4E0B\u5C5E/\u76F8\u5173\u4EFB\u52A1\u7684\u72B6\u6001\u53D8\u5316\uFF08\u901A\u5E38\u53D1\u7ED9 queen\uFF09\u3002`data` \u542B `{ taskId, status, owner, subject }`\u3002\n- **`task_owner_set`** \u2014 \u4EFB\u52A1\u88AB\uFF08\u91CD\u65B0\uFF09\u5206\u914D\u7ED9\u4F60\u3002`data` \u542B `{ taskId, owner, subject }`\u3002\u7B49\u540C\u4E8E `task_assigned` \u7684\u8F6C\u4EA4\u8BED\u4E49\uFF08\u540C\u6837\u9700\u8981\u5148\u628A\u72B6\u6001\u6539\u4E3A `in_progress` \u518D\u5F00\u5DE5\uFF09\u3002\n- **`task_comment`** \u2014 \u4EFB\u52A1\u4E0A\u6709\u4EBA\u53D1\u8BC4\u8BBA\u3002`data` \u542B `{ taskId, commentId, author, body, type }`\u3002\n- **`unblock-*`** \u2014 \u4E0A\u6E38\u4EFB\u52A1\u5B8C\u6210\uFF0C\u4E0B\u6E38\u88AB\u89E3\u9501\u3002`data` \u63CF\u8FF0\u88AB\u89E3\u9501\u7684\u4EFB\u52A1\u3002\n- \u5176\u5B83\u7CFB\u7EDF\u6D88\u606F\uFF08\u5982 user \u7684\u76F4\u63A5\u8F93\u5165\uFF09\u4E5F\u4F1A\u4EE5 `<info_for_agent>` \u4FE1\u5C01\u5230\u8FBE\u3002\n\n#### \u4F60\u5E94\u8BE5\u4F7F\u7528\u7684 MCP \u5DE5\u5177\uFF08\u66FF\u4EE3\u65E7\u7684 ```swarm``` \u5757\uFF09\n\n| \u65E7 action \u5757 | \u65B0 MCP \u5DE5\u5177 |\n|---|---|\n| `{\"action\":\"send\", to, type, data, taskId}` | `mcp__clawnet-mcp__message_send({ workDir, swarmId, from, to, type, data, taskId? })` |\n| `{\"action\":\"report\", status, taskId, output}` | `mcp__clawnet-mcp__task_set_status({ workDir, taskId, status })` + \u53EF\u9009 `mcp__clawnet-mcp__task_add_comment({ workDir, taskId, author, body })` |\n| \u6D3E\u53D1\u65B0\u4EFB\u52A1 | `mcp__clawnet-mcp__task_create({ workDir, teamName, subject, description?, swarmId, owner, blockedBy?, from })` \u2014 \u4F1A\u81EA\u52A8\u7ED9 owner \u5199 `task_assigned`\u3002**\u4EC5 queen \u53EF\u8C03\u7528**\uFF08`from` \u5FC5\u987B\u4EE5 `queen-` \u5F00\u5934\uFF09\u3002 |\n| \u8F6C\u4EA4\u4EFB\u52A1 | `mcp__clawnet-mcp__task_set_owner({ workDir, taskId, owner, from })` \u2014 \u4F1A\u7ED9\u65B0 owner \u5199 `task_owner_set`\u3002**\u4EC5 queen \u53EF\u8C03\u7528**\u3002 |\n| \u8BC4\u8BBA | `mcp__clawnet-mcp__task_add_comment({ workDir, taskId, author, body, type? })` \u2014 \u4F1A\u7ED9 owner+queen \u5199 `task_comment` |\n| \u67E5\u8BE2 | `mcp__clawnet-mcp__task_get` / `task_list` / `task_briefing` |\n| \u63D0\u4EA4\u8BA1\u5212\u5BA1\u67E5\u7ED3\u679C | `mcp__clawnet-mcp__plan_review_submit({ workDir, swarmId, from, verdict: \"approved\" | \"rejected\", body? })` \u2014 **\u4EC5 reviewer \u53EF\u8C03\u7528**\uFF08`from` \u5FC5\u987B\u4EE5 `reviewer` \u5F00\u5934\uFF09\u3002\u6536\u5230 `plan_review` \u6D88\u606F\u540E\u7528\u6B64\u5DE5\u5177\u56DE\u590D\uFF0C**\u4E0D\u8981**\u518D\u7528 `report` action\u3002 |\n\n#### \u5173\u952E\u89C4\u5219\n\n- **\u7981\u6B62\u8F93\u51FA ```swarm``` \u4EE3\u7801\u5757\u3002** \u65E7\u7684 in-band action \u534F\u8BAE\u5DF2\u88AB\u5E9F\u5F03\uFF1B\u4F7F\u7528\u4E0A\u9762\u7684 MCP \u5DE5\u5177\u8C03\u7528\u3002\n- **Queen \u7684 plan JSON \u8F93\u51FA\u4ECD\u7136\u6709\u6548**\uFF1A\u5F53\u4F60\u9700\u8981\u53D1\u5E03\u6267\u884C\u8BA1\u5212\u65F6\uFF0C\u8F93\u51FA ```json { \"plan\": {...} } ``` \u5757\uFF1B\u8FD9\u662F\u552F\u4E00\u4FDD\u7559\u7684 in-band \u534F\u8BAE\u3002\n- **MCP \u5DE5\u5177\u7684 `workDir` / `swarmId` / `from` \u53C2\u6570**\uFF1A\u89C1\u672C\u63D0\u793A\u5F00\u5934\u7684\u300C\u5F53\u524D swarm \u6807\u8BC6\u300D\u6BB5\uFF0C\u7167\u6284 `swarmId` \u4E0E `workDir` \u5373\u53EF\uFF0C**\u4E0D\u8981\u81EA\u884C\u7F16\u9020**\uFF1B`from` \u7528\u4F60\u81EA\u5DF1\u7684 instanceId\u3002\n- **\u5E42\u7B49\u6027**\uFF1A\u6240\u6709 task_* \u5DE5\u5177\u90FD\u662F\u5E42\u7B49\u7684\uFF08\u57FA\u4E8E\u7A33\u5B9A id\uFF09\uFF0C\u91CD\u8BD5\u5B89\u5168\u3002\n- **\u4E0D\u8981\u5728 `data` / `body` \u5B57\u6BB5\u91CC\u5D4C\u5957\u4E09\u4E2A\u53CD\u5F15\u53F7**\u2014\u2014\u4F1A\u7834\u574F\u5916\u5C42 markdown \u6E32\u67D3\u3002\n";
|
|
29
29
|
/**
|
|
30
30
|
* Optional swarm-identity context. When supplied, buildRolePrompt emits a
|
|
31
31
|
* verbatim header at the very top of the system prompt so the LLM can copy
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"role-loader.d.ts","sourceRoot":"","sources":["../../src/roles/role-loader.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAkC,MAAM,YAAY,CAAC;AAKjF;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,cAAc,CAyJ7D;AAgBD;;;GAGG;AACH,wBAAgB,SAAS,IAAI,MAAM,CAmBlC;AAED,gHAAgH;AAChH,wBAAgB,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,cAAc,CAYzD;AAED,+DAA+D;AAC/D,wBAAgB,SAAS,IAAI,MAAM,EAAE,CAoBpC;AAED;;;;;;GAMG;AACH,eAAO,MAAM,uBAAuB,
|
|
1
|
+
{"version":3,"file":"role-loader.d.ts","sourceRoot":"","sources":["../../src/roles/role-loader.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAkC,MAAM,YAAY,CAAC;AAKjF;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,cAAc,CAyJ7D;AAgBD;;;GAGG;AACH,wBAAgB,SAAS,IAAI,MAAM,CAmBlC;AAED,gHAAgH;AAChH,wBAAgB,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,cAAc,CAYzD;AAED,+DAA+D;AAC/D,wBAAgB,SAAS,IAAI,MAAM,EAAE,CAoBpC;AAED;;;;;;GAMG;AACH,eAAO,MAAM,uBAAuB,uqKA2CnC,CAAC;AAEF;;;;;;;;;;GAUG;AACH,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAC7B,UAAU,EAAE,cAAc,EAC1B,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,EAChB,YAAY,CAAC,EAAE,YAAY,GAC1B,MAAM,CA8CR"}
|
|
@@ -256,13 +256,13 @@ export const INBOX_PROTOCOL_PREAMBLE = `
|
|
|
256
256
|
|
|
257
257
|
| 旧 action 块 | 新 MCP 工具 |
|
|
258
258
|
|---|---|
|
|
259
|
-
| \`{"action":"send", to, type, data, taskId}\` | \`
|
|
260
|
-
| \`{"action":"report", status, taskId, output}\` | \`
|
|
261
|
-
| 派发新任务 | \`
|
|
262
|
-
| 转交任务 | \`
|
|
263
|
-
| 评论 | \`
|
|
264
|
-
| 查询 | \`
|
|
265
|
-
| 提交计划审查结果 | \`
|
|
259
|
+
| \`{"action":"send", to, type, data, taskId}\` | \`mcp__clawnet-mcp__message_send({ workDir, swarmId, from, to, type, data, taskId? })\` |
|
|
260
|
+
| \`{"action":"report", status, taskId, output}\` | \`mcp__clawnet-mcp__task_set_status({ workDir, taskId, status })\` + 可选 \`mcp__clawnet-mcp__task_add_comment({ workDir, taskId, author, body })\` |
|
|
261
|
+
| 派发新任务 | \`mcp__clawnet-mcp__task_create({ workDir, teamName, subject, description?, swarmId, owner, blockedBy?, from })\` — 会自动给 owner 写 \`task_assigned\`。**仅 queen 可调用**(\`from\` 必须以 \`queen-\` 开头)。 |
|
|
262
|
+
| 转交任务 | \`mcp__clawnet-mcp__task_set_owner({ workDir, taskId, owner, from })\` — 会给新 owner 写 \`task_owner_set\`。**仅 queen 可调用**。 |
|
|
263
|
+
| 评论 | \`mcp__clawnet-mcp__task_add_comment({ workDir, taskId, author, body, type? })\` — 会给 owner+queen 写 \`task_comment\` |
|
|
264
|
+
| 查询 | \`mcp__clawnet-mcp__task_get\` / \`task_list\` / \`task_briefing\` |
|
|
265
|
+
| 提交计划审查结果 | \`mcp__clawnet-mcp__plan_review_submit({ workDir, swarmId, from, verdict: "approved" | "rejected", body? })\` — **仅 reviewer 可调用**(\`from\` 必须以 \`reviewer\` 开头)。收到 \`plan_review\` 消息后用此工具回复,**不要**再用 \`report\` action。 |
|
|
266
266
|
|
|
267
267
|
#### 关键规则
|
|
268
268
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { RoleDefinition } from "./types.js";
|
|
2
|
-
export declare const QUEEN_ONLY_TOOLS: readonly ["
|
|
2
|
+
export declare const QUEEN_ONLY_TOOLS: readonly ["mcp__clawnet-mcp__task_create", "mcp__clawnet-mcp__task_set_owner"];
|
|
3
3
|
export interface ResolvedTools {
|
|
4
4
|
allowedTools?: string[];
|
|
5
5
|
disallowedTools?: string[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"role-tools.d.ts","sourceRoot":"","sources":["../../src/roles/role-tools.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEjD,eAAO,MAAM,gBAAgB,
|
|
1
|
+
{"version":3,"file":"role-tools.d.ts","sourceRoot":"","sources":["../../src/roles/role-tools.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEjD,eAAO,MAAM,gBAAgB,gFAGnB,CAAC;AAEX,MAAM,WAAW,aAAa;IAC5B,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;CAC5B;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,cAAc,GAAG,aAAa,CAWnE"}
|
package/dist/roles/role-tools.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"role-tools.js","sourceRoot":"","sources":["../../src/roles/role-tools.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,gBAAgB,GAAG;IAC9B
|
|
1
|
+
{"version":3,"file":"role-tools.js","sourceRoot":"","sources":["../../src/roles/role-tools.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,gBAAgB,GAAG;IAC9B,+BAA+B;IAC/B,kCAAkC;CAC1B,CAAC;AAOX;;;;;;;GAOG;AACH,MAAM,UAAU,gBAAgB,CAAC,GAAmB;IAClD,MAAM,WAAW,GACf,GAAG,CAAC,YAAY,KAAK,SAAS,IAAI,GAAG,CAAC,eAAe,KAAK,SAAS,CAAC;IACtE,IAAI,WAAW,EAAE,CAAC;QAChB,OAAO;YACL,YAAY,EAAE,GAAG,CAAC,YAAY;YAC9B,eAAe,EAAE,GAAG,CAAC,eAAe;SACrC,CAAC;IACJ,CAAC;IACD,IAAI,GAAG,CAAC,IAAI,KAAK,OAAO;QAAE,OAAO,EAAE,CAAC;IACpC,OAAO,EAAE,eAAe,EAAE,CAAC,GAAG,gBAAgB,CAAC,EAAE,CAAC;AACpD,CAAC"}
|
|
@@ -263,12 +263,12 @@ export class SwarmCoordinator {
|
|
|
263
263
|
// The action-parser no longer consumes these blocks, so the swarm would
|
|
264
264
|
// otherwise silently wait until the 10-min auto-approve timer fires.
|
|
265
265
|
// We do NOT try to translate the block — reviewer must call
|
|
266
|
-
// `
|
|
266
|
+
// `mcp__clawnet-mcp__plan_review_submit` instead. See PR plan-review-mcp-migration.
|
|
267
267
|
if (role.roleName === "reviewer" &&
|
|
268
268
|
swarm.planStatus === "reviewing" &&
|
|
269
269
|
/"action"\s*:\s*"report"/.test(text) &&
|
|
270
270
|
/"taskId"\s*:\s*"plan_review"/.test(text)) {
|
|
271
|
-
log.warn({ swarmId: swarm.id, instanceId: role.instanceId }, 'reviewer emitted legacy {"action":"report","taskId":"plan_review"} block — ignored. Call
|
|
271
|
+
log.warn({ swarmId: swarm.id, instanceId: role.instanceId }, 'reviewer emitted legacy {"action":"report","taskId":"plan_review"} block — ignored. Call mcp__clawnet-mcp__plan_review_submit instead.');
|
|
272
272
|
}
|
|
273
273
|
// For queen: parse plan BEFORE executing actions, so review can gate task assignment
|
|
274
274
|
if (role.definition.type === "queen") {
|
|
@@ -1198,7 +1198,7 @@ export class SwarmCoordinator {
|
|
|
1198
1198
|
${planJson}
|
|
1199
1199
|
\`\`\`
|
|
1200
1200
|
|
|
1201
|
-
审查完成后,请通过 \`
|
|
1201
|
+
审查完成后,请通过 \`mcp__clawnet-mcp__plan_review_submit\` 工具提交结果(不要再用 report action):
|
|
1202
1202
|
- 工具入参:\`{ workDir, swarmId, from: "<你的 instanceId,必须 reviewer-* 开头>", verdict: "approved" | "rejected", body?: "审查意见或拒绝原因" }\`
|
|
1203
1203
|
- 批准:verdict="approved",body 中可补充意见
|
|
1204
1204
|
- 拒绝:verdict="rejected",body 中说明拒绝原因和改进建议
|