@saga-ai/cli 0.4.0 → 0.5.0
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/cli.cjs +164 -31
- package/package.json +1 -1
package/dist/cli.cjs
CHANGED
|
@@ -5022,7 +5022,114 @@ function spawnWorker(prompt, model, settings, workingDir) {
|
|
|
5022
5022
|
}
|
|
5023
5023
|
return result.stdout || "";
|
|
5024
5024
|
}
|
|
5025
|
-
function
|
|
5025
|
+
function formatStreamLine(line) {
|
|
5026
|
+
try {
|
|
5027
|
+
const data = JSON.parse(line);
|
|
5028
|
+
if (data.type === "assistant" && data.message?.content) {
|
|
5029
|
+
for (const block of data.message.content) {
|
|
5030
|
+
if (block.type === "text" && block.text) {
|
|
5031
|
+
return block.text;
|
|
5032
|
+
}
|
|
5033
|
+
if (block.type === "tool_use") {
|
|
5034
|
+
return `[Tool: ${block.name}]`;
|
|
5035
|
+
}
|
|
5036
|
+
}
|
|
5037
|
+
}
|
|
5038
|
+
if (data.type === "system" && data.subtype === "init") {
|
|
5039
|
+
return `[Session started: ${data.session_id}]`;
|
|
5040
|
+
}
|
|
5041
|
+
if (data.type === "result") {
|
|
5042
|
+
const status = data.subtype === "success" ? "completed" : "failed";
|
|
5043
|
+
return `
|
|
5044
|
+
[Worker ${status} in ${Math.round(data.duration_ms / 1e3)}s]`;
|
|
5045
|
+
}
|
|
5046
|
+
return null;
|
|
5047
|
+
} catch {
|
|
5048
|
+
return null;
|
|
5049
|
+
}
|
|
5050
|
+
}
|
|
5051
|
+
function parseStreamingResult(buffer) {
|
|
5052
|
+
const lines = buffer.split("\n").filter((line) => line.trim());
|
|
5053
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
5054
|
+
try {
|
|
5055
|
+
const data = JSON.parse(lines[i]);
|
|
5056
|
+
if (data.type === "result") {
|
|
5057
|
+
if (data.is_error) {
|
|
5058
|
+
throw new Error(`Worker failed: ${data.result || "Unknown error"}`);
|
|
5059
|
+
}
|
|
5060
|
+
if (!data.structured_output) {
|
|
5061
|
+
throw new Error("Worker result missing structured_output");
|
|
5062
|
+
}
|
|
5063
|
+
const output = data.structured_output;
|
|
5064
|
+
if (!VALID_STATUSES.has(output.status)) {
|
|
5065
|
+
throw new Error(`Invalid status: ${output.status}`);
|
|
5066
|
+
}
|
|
5067
|
+
return {
|
|
5068
|
+
status: output.status,
|
|
5069
|
+
summary: output.summary || "",
|
|
5070
|
+
blocker: output.blocker ?? null
|
|
5071
|
+
};
|
|
5072
|
+
}
|
|
5073
|
+
} catch (e) {
|
|
5074
|
+
if (e instanceof Error && e.message.startsWith("Worker")) {
|
|
5075
|
+
throw e;
|
|
5076
|
+
}
|
|
5077
|
+
}
|
|
5078
|
+
}
|
|
5079
|
+
throw new Error("No result found in worker output");
|
|
5080
|
+
}
|
|
5081
|
+
function spawnWorkerAsync(prompt, model, settings, workingDir) {
|
|
5082
|
+
return new Promise((resolve2, reject) => {
|
|
5083
|
+
let buffer = "";
|
|
5084
|
+
const args = [
|
|
5085
|
+
"-p",
|
|
5086
|
+
prompt,
|
|
5087
|
+
"--model",
|
|
5088
|
+
model,
|
|
5089
|
+
"--output-format",
|
|
5090
|
+
"stream-json",
|
|
5091
|
+
"--verbose",
|
|
5092
|
+
"--json-schema",
|
|
5093
|
+
JSON.stringify(WORKER_OUTPUT_SCHEMA),
|
|
5094
|
+
"--settings",
|
|
5095
|
+
JSON.stringify(settings),
|
|
5096
|
+
"--dangerously-skip-permissions"
|
|
5097
|
+
];
|
|
5098
|
+
const child = (0, import_node_child_process.spawn)("claude", args, {
|
|
5099
|
+
cwd: workingDir,
|
|
5100
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
5101
|
+
});
|
|
5102
|
+
child.stdout.on("data", (chunk) => {
|
|
5103
|
+
const text = chunk.toString();
|
|
5104
|
+
buffer += text;
|
|
5105
|
+
const lines = text.split("\n");
|
|
5106
|
+
for (const line of lines) {
|
|
5107
|
+
if (line.trim()) {
|
|
5108
|
+
const formatted = formatStreamLine(line);
|
|
5109
|
+
if (formatted) {
|
|
5110
|
+
process.stdout.write(formatted);
|
|
5111
|
+
}
|
|
5112
|
+
}
|
|
5113
|
+
}
|
|
5114
|
+
});
|
|
5115
|
+
child.stderr.on("data", (chunk) => {
|
|
5116
|
+
process.stderr.write(chunk);
|
|
5117
|
+
});
|
|
5118
|
+
child.on("error", (err) => {
|
|
5119
|
+
reject(new Error(`Failed to spawn worker: ${err.message}`));
|
|
5120
|
+
});
|
|
5121
|
+
child.on("close", (code) => {
|
|
5122
|
+
process.stdout.write("\n");
|
|
5123
|
+
try {
|
|
5124
|
+
const result = parseStreamingResult(buffer);
|
|
5125
|
+
resolve2(result);
|
|
5126
|
+
} catch (e) {
|
|
5127
|
+
reject(e);
|
|
5128
|
+
}
|
|
5129
|
+
});
|
|
5130
|
+
});
|
|
5131
|
+
}
|
|
5132
|
+
async function runLoop(epicSlug, storySlug, maxCycles, maxTime, model, projectDir, pluginRoot, stream = false) {
|
|
5026
5133
|
const worktree = (0, import_node_path4.join)(projectDir, ".saga", "worktrees", epicSlug, storySlug);
|
|
5027
5134
|
const validation = validateStoryFiles(worktree, epicSlug, storySlug);
|
|
5028
5135
|
if (!validation.valid) {
|
|
@@ -5064,33 +5171,55 @@ function runLoop(epicSlug, storySlug, maxCycles, maxTime, model, projectDir, plu
|
|
|
5064
5171
|
break;
|
|
5065
5172
|
}
|
|
5066
5173
|
cycles += 1;
|
|
5067
|
-
let output;
|
|
5068
|
-
try {
|
|
5069
|
-
output = spawnWorker(workerPrompt, model, settings, worktree);
|
|
5070
|
-
} catch (e) {
|
|
5071
|
-
return {
|
|
5072
|
-
status: "ERROR",
|
|
5073
|
-
summary: e.message,
|
|
5074
|
-
cycles,
|
|
5075
|
-
elapsedMinutes: (Date.now() - startTime) / 6e4,
|
|
5076
|
-
blocker: null,
|
|
5077
|
-
epicSlug,
|
|
5078
|
-
storySlug
|
|
5079
|
-
};
|
|
5080
|
-
}
|
|
5081
5174
|
let parsed;
|
|
5082
|
-
|
|
5083
|
-
|
|
5084
|
-
|
|
5085
|
-
|
|
5086
|
-
|
|
5087
|
-
|
|
5088
|
-
|
|
5089
|
-
|
|
5090
|
-
|
|
5091
|
-
|
|
5092
|
-
|
|
5093
|
-
|
|
5175
|
+
if (stream) {
|
|
5176
|
+
console.log(`
|
|
5177
|
+
--- Worker ${cycles} started ---
|
|
5178
|
+
`);
|
|
5179
|
+
try {
|
|
5180
|
+
parsed = await spawnWorkerAsync(workerPrompt, model, settings, worktree);
|
|
5181
|
+
} catch (e) {
|
|
5182
|
+
return {
|
|
5183
|
+
status: "ERROR",
|
|
5184
|
+
summary: e.message,
|
|
5185
|
+
cycles,
|
|
5186
|
+
elapsedMinutes: (Date.now() - startTime) / 6e4,
|
|
5187
|
+
blocker: null,
|
|
5188
|
+
epicSlug,
|
|
5189
|
+
storySlug
|
|
5190
|
+
};
|
|
5191
|
+
}
|
|
5192
|
+
console.log(`
|
|
5193
|
+
--- Worker ${cycles} result: ${parsed.status} ---
|
|
5194
|
+
`);
|
|
5195
|
+
} else {
|
|
5196
|
+
let output;
|
|
5197
|
+
try {
|
|
5198
|
+
output = spawnWorker(workerPrompt, model, settings, worktree);
|
|
5199
|
+
} catch (e) {
|
|
5200
|
+
return {
|
|
5201
|
+
status: "ERROR",
|
|
5202
|
+
summary: e.message,
|
|
5203
|
+
cycles,
|
|
5204
|
+
elapsedMinutes: (Date.now() - startTime) / 6e4,
|
|
5205
|
+
blocker: null,
|
|
5206
|
+
epicSlug,
|
|
5207
|
+
storySlug
|
|
5208
|
+
};
|
|
5209
|
+
}
|
|
5210
|
+
try {
|
|
5211
|
+
parsed = parseWorkerOutput(output);
|
|
5212
|
+
} catch (e) {
|
|
5213
|
+
return {
|
|
5214
|
+
status: "ERROR",
|
|
5215
|
+
summary: e.message,
|
|
5216
|
+
cycles,
|
|
5217
|
+
elapsedMinutes: (Date.now() - startTime) / 6e4,
|
|
5218
|
+
blocker: null,
|
|
5219
|
+
epicSlug,
|
|
5220
|
+
storySlug
|
|
5221
|
+
};
|
|
5222
|
+
}
|
|
5094
5223
|
}
|
|
5095
5224
|
summaries.push(parsed.summary);
|
|
5096
5225
|
if (parsed.status === "FINISH") {
|
|
@@ -5157,19 +5286,22 @@ Searched in: ${(0, import_node_path4.join)(projectPath, ".saga", "worktrees")}`)
|
|
|
5157
5286
|
const maxCycles = options.maxCycles ?? DEFAULT_MAX_CYCLES;
|
|
5158
5287
|
const maxTime = options.maxTime ?? DEFAULT_MAX_TIME;
|
|
5159
5288
|
const model = options.model ?? DEFAULT_MODEL;
|
|
5289
|
+
const stream = options.stream ?? false;
|
|
5160
5290
|
console.log("Starting story implementation...");
|
|
5161
5291
|
console.log(` Epic: ${storyInfo.epicSlug}`);
|
|
5162
5292
|
console.log(` Story: ${storyInfo.storySlug}`);
|
|
5163
5293
|
console.log(` Worktree: ${storyInfo.worktreePath}`);
|
|
5294
|
+
console.log(` Streaming: ${stream ? "enabled" : "disabled"}`);
|
|
5164
5295
|
console.log("");
|
|
5165
|
-
const result = runLoop(
|
|
5296
|
+
const result = await runLoop(
|
|
5166
5297
|
storyInfo.epicSlug,
|
|
5167
5298
|
storyInfo.storySlug,
|
|
5168
5299
|
maxCycles,
|
|
5169
5300
|
maxTime,
|
|
5170
5301
|
model,
|
|
5171
5302
|
projectPath,
|
|
5172
|
-
pluginRoot
|
|
5303
|
+
pluginRoot,
|
|
5304
|
+
stream
|
|
5173
5305
|
);
|
|
5174
5306
|
console.log(JSON.stringify(result, null, 2));
|
|
5175
5307
|
if (result.status === "ERROR") {
|
|
@@ -5441,14 +5573,15 @@ program2.command("init").description("Initialize .saga/ directory structure").op
|
|
|
5441
5573
|
const globalOpts = program2.opts();
|
|
5442
5574
|
await initCommand({ path: globalOpts.path, dryRun: options.dryRun });
|
|
5443
5575
|
});
|
|
5444
|
-
program2.command("implement <story-slug>").description("Run story implementation").option("--max-cycles <n>", "Maximum number of implementation cycles", parseInt).option("--max-time <n>", "Maximum time in minutes", parseInt).option("--model <name>", "Model to use for implementation").option("--dry-run", "Validate dependencies without running implementation").action(async (storySlug, options) => {
|
|
5576
|
+
program2.command("implement <story-slug>").description("Run story implementation").option("--max-cycles <n>", "Maximum number of implementation cycles", parseInt).option("--max-time <n>", "Maximum time in minutes", parseInt).option("--model <name>", "Model to use for implementation").option("--dry-run", "Validate dependencies without running implementation").option("--stream", "Stream worker output in real-time").action(async (storySlug, options) => {
|
|
5445
5577
|
const globalOpts = program2.opts();
|
|
5446
5578
|
await implementCommand(storySlug, {
|
|
5447
5579
|
path: globalOpts.path,
|
|
5448
5580
|
maxCycles: options.maxCycles,
|
|
5449
5581
|
maxTime: options.maxTime,
|
|
5450
5582
|
model: options.model,
|
|
5451
|
-
dryRun: options.dryRun
|
|
5583
|
+
dryRun: options.dryRun,
|
|
5584
|
+
stream: options.stream
|
|
5452
5585
|
});
|
|
5453
5586
|
});
|
|
5454
5587
|
program2.command("find <query>").description("Find an epic or story by slug/title").option("--type <type>", "Type to search for: epic or story (default: story)").action(async (query, options) => {
|