@jjlabsio/claude-crew 0.1.40 → 0.1.41
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.
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"name": "claude-crew",
|
|
12
12
|
"source": "./",
|
|
13
13
|
"description": "오케스트레이터 + PM, 플래너, 개발, QA, 마케팅 에이전트 팀으로 단일 제품의 개발과 마케팅을 통합 관리",
|
|
14
|
-
"version": "0.1.
|
|
14
|
+
"version": "0.1.41",
|
|
15
15
|
"author": {
|
|
16
16
|
"name": "Jaejin Song",
|
|
17
17
|
"email": "wowlxx28@gmail.com"
|
|
@@ -28,5 +28,5 @@
|
|
|
28
28
|
"category": "workflow"
|
|
29
29
|
}
|
|
30
30
|
],
|
|
31
|
-
"version": "0.1.
|
|
31
|
+
"version": "0.1.41"
|
|
32
32
|
}
|
package/package.json
CHANGED
|
@@ -9,6 +9,10 @@ import {
|
|
|
9
9
|
loadUserConfig
|
|
10
10
|
} from "./lib/config.mjs";
|
|
11
11
|
import { parseArgv } from "./lib/cli.mjs";
|
|
12
|
+
import {
|
|
13
|
+
persistCrewArtifact,
|
|
14
|
+
ArtifactPersistError
|
|
15
|
+
} from "./lib/artifacts.mjs";
|
|
12
16
|
import {
|
|
13
17
|
dispatch,
|
|
14
18
|
DispatchError,
|
|
@@ -42,6 +46,10 @@ async function main(argv) {
|
|
|
42
46
|
return dispatchCommand(flags);
|
|
43
47
|
}
|
|
44
48
|
|
|
49
|
+
if (command === "persist-artifact") {
|
|
50
|
+
return persistArtifactCommand(flags);
|
|
51
|
+
}
|
|
52
|
+
|
|
45
53
|
if (command === "render-followup") {
|
|
46
54
|
return renderFollowupCommand(flags);
|
|
47
55
|
}
|
|
@@ -224,6 +232,61 @@ function renderFollowupCommand(flags) {
|
|
|
224
232
|
}
|
|
225
233
|
}
|
|
226
234
|
|
|
235
|
+
async function persistArtifactCommand(flags) {
|
|
236
|
+
if (typeof flags.role !== "string" || flags.role.length === 0) {
|
|
237
|
+
console.error("Missing required --role <name>");
|
|
238
|
+
return 1;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (
|
|
242
|
+
typeof flags["result-file"] !== "string" ||
|
|
243
|
+
flags["result-file"].length === 0
|
|
244
|
+
) {
|
|
245
|
+
console.error("Missing required --result-file <path>");
|
|
246
|
+
return 1;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (
|
|
250
|
+
typeof flags["request-file"] !== "string" ||
|
|
251
|
+
flags["request-file"].length === 0
|
|
252
|
+
) {
|
|
253
|
+
console.error("Missing required --request-file <path>");
|
|
254
|
+
return 1;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
try {
|
|
258
|
+
const contracts = loadContracts();
|
|
259
|
+
const contract = findContract(flags.role, contracts);
|
|
260
|
+
if (!contract) {
|
|
261
|
+
throw new Error(`Unknown role: ${flags.role}`);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const agentResult = JSON.parse(readFileSync(flags["result-file"], "utf8"));
|
|
265
|
+
const request = JSON.parse(readFileSync(flags["request-file"], "utf8"));
|
|
266
|
+
|
|
267
|
+
const savedPath = await persistCrewArtifact({
|
|
268
|
+
workspaceRoot: process.cwd(),
|
|
269
|
+
contract,
|
|
270
|
+
request,
|
|
271
|
+
agentResult
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
if (savedPath) {
|
|
275
|
+
process.stdout.write(`${JSON.stringify({ artifact_path: savedPath })}\n`);
|
|
276
|
+
} else {
|
|
277
|
+
process.stdout.write(`${JSON.stringify({ artifact_path: null })}\n`);
|
|
278
|
+
}
|
|
279
|
+
return 0;
|
|
280
|
+
} catch (error) {
|
|
281
|
+
if (error instanceof ArtifactPersistError) {
|
|
282
|
+
console.error(error.message);
|
|
283
|
+
return 1;
|
|
284
|
+
}
|
|
285
|
+
console.error(error.message);
|
|
286
|
+
return 1;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
227
290
|
async function dispatchCommand(flags) {
|
|
228
291
|
if (typeof flags.role !== "string" || flags.role.length === 0) {
|
|
229
292
|
console.error("Missing required --role <name>");
|
|
@@ -376,6 +439,9 @@ function usage() {
|
|
|
376
439
|
console.error(
|
|
377
440
|
" crew-agent-runner dispatch --role <name> --request-file <path> [--json] [--resume-handle <thread-id>]"
|
|
378
441
|
);
|
|
442
|
+
console.error(
|
|
443
|
+
" crew-agent-runner persist-artifact --role <name> --result-file <path> --request-file <path>"
|
|
444
|
+
);
|
|
379
445
|
}
|
|
380
446
|
|
|
381
447
|
const exitCode = await main(process.argv.slice(2));
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
2
|
+
import { dirname, normalize, resolve, relative } from "node:path";
|
|
3
|
+
|
|
4
|
+
export class ArtifactPersistError extends Error {
|
|
5
|
+
constructor(message) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.name = "ArtifactPersistError";
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export async function persistCrewArtifact({ workspaceRoot, contract, request, agentResult }) {
|
|
12
|
+
if (!shouldPersist(contract, agentResult)) {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const target = findArtifactTarget(contract);
|
|
17
|
+
if (!target) {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const resolvedTarget = replaceTaskId(target, request?.taskId);
|
|
22
|
+
const absolutePath = validateTargetPath(workspaceRoot, resolvedTarget);
|
|
23
|
+
|
|
24
|
+
await mkdir(dirname(absolutePath), { recursive: true });
|
|
25
|
+
await writeFile(absolutePath, agentResult.artifact, "utf8");
|
|
26
|
+
|
|
27
|
+
return absolutePath;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function shouldPersist(contract, agentResult) {
|
|
31
|
+
if (contract?.capabilities?.workspaceAccess !== "read-only") {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (agentResult?.status !== "complete") {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (typeof agentResult.artifact !== "string" || agentResult.artifact.length === 0) {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function findArtifactTarget(contract) {
|
|
47
|
+
const outputs = Array.isArray(contract?.outputs) ? contract.outputs : [];
|
|
48
|
+
for (const output of outputs) {
|
|
49
|
+
if (
|
|
50
|
+
output?.type === "artifact" &&
|
|
51
|
+
typeof output.target === "string" &&
|
|
52
|
+
output.target.startsWith(".crew/")
|
|
53
|
+
) {
|
|
54
|
+
return output.target;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function replaceTaskId(target, taskId) {
|
|
61
|
+
if (typeof taskId === "string" && taskId.length > 0) {
|
|
62
|
+
return target.replace(/\{task-id\}/g, taskId);
|
|
63
|
+
}
|
|
64
|
+
return target;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function validateTargetPath(workspaceRoot, target) {
|
|
68
|
+
if (!workspaceRoot || typeof workspaceRoot !== "string") {
|
|
69
|
+
throw new ArtifactPersistError("workspaceRoot is required.");
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const normalized = normalize(target);
|
|
73
|
+
|
|
74
|
+
if (normalized.startsWith("/") || normalized.startsWith("\\")) {
|
|
75
|
+
throw new ArtifactPersistError(`Absolute path rejected: ${target}`);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const crewBase = resolve(workspaceRoot, ".crew");
|
|
79
|
+
const absolutePath = resolve(workspaceRoot, normalized);
|
|
80
|
+
const rel = relative(crewBase, absolutePath);
|
|
81
|
+
|
|
82
|
+
if (rel.startsWith("..") || rel === "") {
|
|
83
|
+
throw new ArtifactPersistError(
|
|
84
|
+
`Target path escapes .crew/ directory: ${target}`
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return absolutePath;
|
|
89
|
+
}
|
package/scripts/lib/dispatch.mjs
CHANGED
|
@@ -4,6 +4,7 @@ import { tmpdir } from "node:os";
|
|
|
4
4
|
import { basename, join } from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
|
|
7
|
+
import { persistCrewArtifact, ArtifactPersistError } from "./artifacts.mjs";
|
|
7
8
|
import { renderPrompt } from "./render.mjs";
|
|
8
9
|
|
|
9
10
|
const DEFAULT_COMPANION = fileURLToPath(
|
|
@@ -84,6 +85,11 @@ export async function dispatch(input) {
|
|
|
84
85
|
});
|
|
85
86
|
}
|
|
86
87
|
|
|
88
|
+
const artifactPath = await persistArtifactSafe(input, agentResult);
|
|
89
|
+
if (artifactPath) {
|
|
90
|
+
return { ...agentResult, artifact_path: artifactPath };
|
|
91
|
+
}
|
|
92
|
+
|
|
87
93
|
return agentResult;
|
|
88
94
|
} finally {
|
|
89
95
|
await rm(tmpDir, { recursive: true, force: true });
|
|
@@ -235,6 +241,24 @@ function resolveCompanion(input = {}) {
|
|
|
235
241
|
};
|
|
236
242
|
}
|
|
237
243
|
|
|
244
|
+
async function persistArtifactSafe(input, agentResult) {
|
|
245
|
+
try {
|
|
246
|
+
return await persistCrewArtifact({
|
|
247
|
+
workspaceRoot: process.cwd(),
|
|
248
|
+
contract: input.contract,
|
|
249
|
+
request: input.request,
|
|
250
|
+
agentResult
|
|
251
|
+
});
|
|
252
|
+
} catch (error) {
|
|
253
|
+
if (error instanceof ArtifactPersistError) {
|
|
254
|
+
throw new DispatchError(`Artifact persist failed: ${error.message}`, {
|
|
255
|
+
agentResult
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
throw error;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
238
262
|
function isNodeScript(value) {
|
|
239
263
|
const name = basename(String(value));
|
|
240
264
|
return name.endsWith(".mjs") || name.endsWith(".js");
|
package/scripts/lib/render.mjs
CHANGED
|
@@ -8,7 +8,7 @@ export function renderPrompt(input) {
|
|
|
8
8
|
section("Instructions", request.instruction, { required: true }),
|
|
9
9
|
section("Success Gate", request.successGate),
|
|
10
10
|
section("Failure Handling", request.failureHandling),
|
|
11
|
-
section("AgentResult Contract", renderAgentResultContract())
|
|
11
|
+
section("AgentResult Contract", renderAgentResultContract(contract))
|
|
12
12
|
].filter(Boolean);
|
|
13
13
|
|
|
14
14
|
return `${parts.join("\n\n")}\n`;
|
|
@@ -41,7 +41,7 @@ function renderCapability(contract) {
|
|
|
41
41
|
`canAskUser: ${String(tools.includes("AskUserQuestion"))}`,
|
|
42
42
|
`canRequestAgent: ${String(tools.includes("Agent"))}`,
|
|
43
43
|
`canUseShell: ${String(tools.includes("Bash"))}`,
|
|
44
|
-
`
|
|
44
|
+
`canReturnCrewArtifact: ${String(canReturnCrewArtifact(outputs, contract.capabilities?.workspaceAccess))}`
|
|
45
45
|
].join("\n");
|
|
46
46
|
}
|
|
47
47
|
|
|
@@ -73,15 +73,19 @@ function renderJson(value) {
|
|
|
73
73
|
return JSON.stringify(value, null, 2);
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
-
function renderAgentResultContract() {
|
|
77
|
-
|
|
76
|
+
function renderAgentResultContract(contract = {}) {
|
|
77
|
+
const outputs = Array.isArray(contract.outputs) ? contract.outputs : [];
|
|
78
|
+
const workspaceAccess = contract.capabilities?.workspaceAccess;
|
|
79
|
+
const returnArtifact = canReturnCrewArtifact(outputs, workspaceAccess);
|
|
80
|
+
|
|
81
|
+
const lines = [
|
|
78
82
|
"Return exactly one final AgentResult JSON object wrapped in these tags:",
|
|
79
83
|
"",
|
|
80
84
|
"```text",
|
|
81
85
|
"<crew-agent-result>",
|
|
82
86
|
"{",
|
|
83
87
|
' "status": "complete | blocked_on_user | needs_agent | needs_tool | failed",',
|
|
84
|
-
|
|
88
|
+
` "artifact": ${returnArtifact ? '"full Markdown content of the artifact"' : "null"},`,
|
|
85
89
|
' "questions": [],',
|
|
86
90
|
' "requests": [],',
|
|
87
91
|
' "summary": "short summary",',
|
|
@@ -97,7 +101,15 @@ function renderAgentResultContract() {
|
|
|
97
101
|
"- Use blocked_on_user only with a non-empty questions array.",
|
|
98
102
|
"- Use needs_agent or needs_tool only with a non-empty requests array.",
|
|
99
103
|
"- Use failed with an error string when the task cannot continue."
|
|
100
|
-
]
|
|
104
|
+
];
|
|
105
|
+
|
|
106
|
+
if (returnArtifact) {
|
|
107
|
+
lines.push(
|
|
108
|
+
"- Do NOT write the artifact file yourself. Instead, put the full Markdown content into the artifact field as a string. The runner will validate and save it to the target path."
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return lines.join("\n");
|
|
101
113
|
}
|
|
102
114
|
|
|
103
115
|
function normalizeBody(body) {
|
|
@@ -127,7 +139,11 @@ function titleCase(value) {
|
|
|
127
139
|
.join("-");
|
|
128
140
|
}
|
|
129
141
|
|
|
130
|
-
function
|
|
142
|
+
export function canReturnCrewArtifact(outputs, workspaceAccess) {
|
|
143
|
+
if (workspaceAccess !== "read-only") {
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
|
|
131
147
|
return outputs.some((output) => {
|
|
132
148
|
return (
|
|
133
149
|
output?.type === "artifact" &&
|