declare-cc 0.6.0 → 1.0.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/README.md +72 -32
- package/dist/declare-tools.cjs +2043 -543
- package/dist/public/app.js +2576 -385
- package/dist/public/index.html +984 -37
- package/package.json +6 -1
package/dist/declare-tools.cjs
CHANGED
|
@@ -1,9 +1,30 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
"use strict";
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
6
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
4
9
|
var __commonJS = (cb, mod) => function __require() {
|
|
5
10
|
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
6
11
|
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
7
28
|
|
|
8
29
|
// src/git/commit.js
|
|
9
30
|
var require_commit = __commonJS({
|
|
@@ -1531,7 +1552,7 @@ var require_help = __commonJS({
|
|
|
1531
1552
|
usage: "/declare:help"
|
|
1532
1553
|
}
|
|
1533
1554
|
],
|
|
1534
|
-
version: "0.
|
|
1555
|
+
version: "1.0.0"
|
|
1535
1556
|
};
|
|
1536
1557
|
}
|
|
1537
1558
|
module2.exports = { runHelp: runHelp2 };
|
|
@@ -1862,6 +1883,7 @@ var require_add_milestones_batch = __commonJS({
|
|
|
1862
1883
|
milestones.push({
|
|
1863
1884
|
id,
|
|
1864
1885
|
title: input.title,
|
|
1886
|
+
description: input.description || "",
|
|
1865
1887
|
status: "PENDING",
|
|
1866
1888
|
realizes,
|
|
1867
1889
|
hasPlan: false
|
|
@@ -1872,7 +1894,7 @@ var require_add_milestones_batch = __commonJS({
|
|
|
1872
1894
|
decl.milestones.push(id);
|
|
1873
1895
|
}
|
|
1874
1896
|
}
|
|
1875
|
-
results.push({ id, title: input.title, realizes, status: "PENDING" });
|
|
1897
|
+
results.push({ id, title: input.title, description: input.description || "", realizes, status: "PENDING" });
|
|
1876
1898
|
}
|
|
1877
1899
|
const futureOutput = writeFutureFile(declarations, projectName);
|
|
1878
1900
|
writeFileSync(futurePath, futureOutput, "utf-8");
|
|
@@ -4255,11 +4277,92 @@ var require_health_check = __commonJS({
|
|
|
4255
4277
|
}
|
|
4256
4278
|
});
|
|
4257
4279
|
|
|
4280
|
+
// src/server/ai-runner.js
|
|
4281
|
+
var require_ai_runner = __commonJS({
|
|
4282
|
+
"src/server/ai-runner.js"(exports2, module2) {
|
|
4283
|
+
"use strict";
|
|
4284
|
+
var _query;
|
|
4285
|
+
async function getQuery() {
|
|
4286
|
+
if (!_query) {
|
|
4287
|
+
const sdk = await import("@anthropic-ai/claude-agent-sdk");
|
|
4288
|
+
_query = sdk.query;
|
|
4289
|
+
}
|
|
4290
|
+
return _query;
|
|
4291
|
+
}
|
|
4292
|
+
async function runAI(prompt, opts = {}) {
|
|
4293
|
+
const queryFn = await getQuery();
|
|
4294
|
+
const abortController = opts.abortController || new AbortController();
|
|
4295
|
+
const timeoutMs = opts.withTools || opts.allowedTools ? 10 * 60 * 1e3 : 2 * 60 * 1e3;
|
|
4296
|
+
const timeoutId = setTimeout(() => {
|
|
4297
|
+
if (!abortController.signal.aborted) abortController.abort();
|
|
4298
|
+
}, timeoutMs);
|
|
4299
|
+
const savedClaudeCode = process.env.CLAUDECODE;
|
|
4300
|
+
delete process.env.CLAUDECODE;
|
|
4301
|
+
try {
|
|
4302
|
+
const env = { ...process.env };
|
|
4303
|
+
delete env.CLAUDECODE;
|
|
4304
|
+
const queryOpts = {
|
|
4305
|
+
model: opts.model || "haiku",
|
|
4306
|
+
maxTurns: opts.maxTurns || 1,
|
|
4307
|
+
cwd: opts.cwd || process.cwd(),
|
|
4308
|
+
abortController,
|
|
4309
|
+
env,
|
|
4310
|
+
systemPrompt: opts.systemPrompt || "You are a helpful assistant. Respond concisely and directly."
|
|
4311
|
+
};
|
|
4312
|
+
if (opts.withTools || opts.allowedTools) {
|
|
4313
|
+
queryOpts.allowedTools = opts.allowedTools || ["Read", "Write", "Edit", "Bash", "Glob", "Grep"];
|
|
4314
|
+
queryOpts.permissionMode = "bypassPermissions";
|
|
4315
|
+
if (!opts.maxTurns) queryOpts.maxTurns = 10;
|
|
4316
|
+
} else {
|
|
4317
|
+
queryOpts.tools = [];
|
|
4318
|
+
}
|
|
4319
|
+
const conversation = queryFn({
|
|
4320
|
+
prompt,
|
|
4321
|
+
options: queryOpts
|
|
4322
|
+
});
|
|
4323
|
+
let resultText = "";
|
|
4324
|
+
for await (const message of conversation) {
|
|
4325
|
+
if (message.type === "assistant") {
|
|
4326
|
+
const content = message.message?.content;
|
|
4327
|
+
if (Array.isArray(content)) {
|
|
4328
|
+
for (const block of content) {
|
|
4329
|
+
if (block.type === "text" && block.text) {
|
|
4330
|
+
resultText += block.text;
|
|
4331
|
+
if (opts.onText) opts.onText(block.text);
|
|
4332
|
+
}
|
|
4333
|
+
}
|
|
4334
|
+
}
|
|
4335
|
+
} else if (message.type === "result") {
|
|
4336
|
+
if (message.subtype === "success" && message.result) {
|
|
4337
|
+
resultText = message.result;
|
|
4338
|
+
} else if (message.is_error) {
|
|
4339
|
+
const errDetail = message.errors?.join(", ") || message.error || JSON.stringify(message);
|
|
4340
|
+
console.error("[ai-runner] SDK error result:", errDetail);
|
|
4341
|
+
return { text: "", error: errDetail || "AI query failed" };
|
|
4342
|
+
}
|
|
4343
|
+
}
|
|
4344
|
+
}
|
|
4345
|
+
return { text: resultText };
|
|
4346
|
+
} catch (err) {
|
|
4347
|
+
console.error("[ai-runner] Exception:", err.message || err);
|
|
4348
|
+
if (abortController.signal.aborted) {
|
|
4349
|
+
return { text: "", error: "Cancelled" };
|
|
4350
|
+
}
|
|
4351
|
+
return { text: "", error: String(err.message || err) };
|
|
4352
|
+
} finally {
|
|
4353
|
+
clearTimeout(timeoutId);
|
|
4354
|
+
if (savedClaudeCode) process.env.CLAUDECODE = savedClaudeCode;
|
|
4355
|
+
}
|
|
4356
|
+
}
|
|
4357
|
+
module2.exports = { runAI };
|
|
4358
|
+
}
|
|
4359
|
+
});
|
|
4360
|
+
|
|
4258
4361
|
// src/server/process-manager.js
|
|
4259
4362
|
var require_process_manager = __commonJS({
|
|
4260
4363
|
"src/server/process-manager.js"(exports2, module2) {
|
|
4261
4364
|
"use strict";
|
|
4262
|
-
var {
|
|
4365
|
+
var { runAI } = require_ai_runner();
|
|
4263
4366
|
var fs = require("node:fs");
|
|
4264
4367
|
var path = require("node:path");
|
|
4265
4368
|
var { findMilestoneFolder } = require_milestone_folders();
|
|
@@ -4270,7 +4373,49 @@ var require_process_manager = __commonJS({
|
|
|
4270
4373
|
} catch (_) {
|
|
4271
4374
|
}
|
|
4272
4375
|
}
|
|
4273
|
-
function
|
|
4376
|
+
function buildExecutionPrompt(actionId, milestoneId, ctx) {
|
|
4377
|
+
if (!ctx) {
|
|
4378
|
+
return `Run /declare:execute ${milestoneId} for action ${actionId} only. Do not ask questions, execute autonomously.`;
|
|
4379
|
+
}
|
|
4380
|
+
const lines = [
|
|
4381
|
+
`Execute action ${actionId} for milestone ${milestoneId}. Work autonomously \u2014 do not ask questions.`,
|
|
4382
|
+
""
|
|
4383
|
+
];
|
|
4384
|
+
if (ctx.declaration) {
|
|
4385
|
+
lines.push(`## Declaration: ${ctx.declaration.id}`);
|
|
4386
|
+
lines.push(ctx.declaration.statement);
|
|
4387
|
+
lines.push("");
|
|
4388
|
+
}
|
|
4389
|
+
if (ctx.milestone) {
|
|
4390
|
+
lines.push(`## Milestone: ${ctx.milestone.id} \u2014 ${ctx.milestone.title}`);
|
|
4391
|
+
if (ctx.milestone.description) {
|
|
4392
|
+
lines.push(ctx.milestone.description);
|
|
4393
|
+
}
|
|
4394
|
+
lines.push("");
|
|
4395
|
+
}
|
|
4396
|
+
lines.push(`## Action: ${ctx.action.id} \u2014 ${ctx.action.title}`);
|
|
4397
|
+
if (ctx.action.produces) {
|
|
4398
|
+
lines.push(`**Produces:** ${ctx.action.produces}`);
|
|
4399
|
+
}
|
|
4400
|
+
lines.push("");
|
|
4401
|
+
if (ctx.siblingActions.length > 0) {
|
|
4402
|
+
lines.push("## Other actions for this milestone (do NOT do these, just for context):");
|
|
4403
|
+
for (const s of ctx.siblingActions) {
|
|
4404
|
+
const statusMark = s.status === "DONE" ? " [DONE]" : "";
|
|
4405
|
+
lines.push(`- ${s.id}: ${s.title}${s.produces ? " (produces: " + s.produces + ")" : ""}${statusMark}`);
|
|
4406
|
+
}
|
|
4407
|
+
lines.push("");
|
|
4408
|
+
}
|
|
4409
|
+
lines.push("## Instructions");
|
|
4410
|
+
lines.push("1. Read the project context files: .planning/FUTURE.md, .planning/MILESTONES.md, .planning/STATE.md");
|
|
4411
|
+
lines.push('2. Understand what this action needs to deliver based on the "Produces" field above');
|
|
4412
|
+
lines.push("3. Explore the codebase to find the right files to modify");
|
|
4413
|
+
lines.push("4. Implement the changes \u2014 write real, working code");
|
|
4414
|
+
lines.push("5. Verify your changes work (run relevant tests, check for errors)");
|
|
4415
|
+
lines.push("6. Commit your changes with a descriptive message");
|
|
4416
|
+
return lines.join("\n");
|
|
4417
|
+
}
|
|
4418
|
+
function createProcessManager(sseClients, cwd, registry) {
|
|
4274
4419
|
const processes = /* @__PURE__ */ new Map();
|
|
4275
4420
|
function broadcast(event, data) {
|
|
4276
4421
|
const payload = `event: ${event}
|
|
@@ -4285,32 +4430,14 @@ data: ${JSON.stringify(data)}
|
|
|
4285
4430
|
}
|
|
4286
4431
|
}
|
|
4287
4432
|
}
|
|
4288
|
-
function
|
|
4289
|
-
let buffer = "";
|
|
4290
|
-
return (chunk) => {
|
|
4291
|
-
buffer += chunk.toString();
|
|
4292
|
-
const lines = buffer.split("\n");
|
|
4293
|
-
buffer = lines.pop() || "";
|
|
4294
|
-
for (const line of lines) {
|
|
4295
|
-
broadcast("action-output", { actionId, text: line, stream: streamName });
|
|
4296
|
-
appendLog(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] [${actionId}] [${streamName}] ${line}`);
|
|
4297
|
-
}
|
|
4298
|
-
};
|
|
4299
|
-
}
|
|
4300
|
-
function execute(actionId, milestoneId) {
|
|
4433
|
+
function execute(actionId, milestoneId, ctx) {
|
|
4301
4434
|
if (processes.size > 0) {
|
|
4302
4435
|
return { error: "busy", status: 409 };
|
|
4303
4436
|
}
|
|
4304
4437
|
if (processes.has(actionId)) {
|
|
4305
4438
|
return { error: "already_running", status: 409 };
|
|
4306
4439
|
}
|
|
4307
|
-
const prompt =
|
|
4308
|
-
const spawnEnv = { ...process.env, FORCE_COLOR: "0" };
|
|
4309
|
-
delete spawnEnv.CLAUDECODE;
|
|
4310
|
-
const proc = spawn("claude", ["-p", prompt], {
|
|
4311
|
-
cwd,
|
|
4312
|
-
env: spawnEnv
|
|
4313
|
-
});
|
|
4440
|
+
const prompt = buildExecutionPrompt(actionId, milestoneId, ctx);
|
|
4314
4441
|
const planningDir = path.join(cwd, ".planning");
|
|
4315
4442
|
const milestoneFolder = findMilestoneFolder(planningDir, milestoneId);
|
|
4316
4443
|
let logPath;
|
|
@@ -4320,28 +4447,56 @@ data: ${JSON.stringify(data)}
|
|
|
4320
4447
|
process.stderr.write(`[declare] Warning: milestone folder not found for ${milestoneId}, skipping execution log
|
|
4321
4448
|
`);
|
|
4322
4449
|
}
|
|
4323
|
-
|
|
4450
|
+
const abortController = new AbortController();
|
|
4451
|
+
let agentId;
|
|
4452
|
+
if (registry) {
|
|
4453
|
+
const agent = registry.spawn("execution", actionId, milestoneId);
|
|
4454
|
+
agentId = agent.id;
|
|
4455
|
+
}
|
|
4456
|
+
processes.set(actionId, { abortController, milestoneId, logPath, agentId });
|
|
4324
4457
|
appendLog(logPath, `
|
|
4325
4458
|
=== START ${actionId} @ ${(/* @__PURE__ */ new Date()).toISOString()} ===`);
|
|
4326
|
-
|
|
4327
|
-
|
|
4328
|
-
|
|
4329
|
-
|
|
4330
|
-
|
|
4331
|
-
|
|
4332
|
-
|
|
4459
|
+
runAI(prompt, {
|
|
4460
|
+
cwd,
|
|
4461
|
+
model: "sonnet",
|
|
4462
|
+
withTools: true,
|
|
4463
|
+
maxTurns: 10,
|
|
4464
|
+
abortController,
|
|
4465
|
+
onText: (chunk) => {
|
|
4466
|
+
broadcast("action-output", { actionId, text: chunk, stream: "stdout" });
|
|
4467
|
+
appendLog(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] [${actionId}] [stdout] ${chunk}`);
|
|
4468
|
+
}
|
|
4469
|
+
}).then(({ text, error }) => {
|
|
4333
4470
|
const entry = processes.get(actionId);
|
|
4334
|
-
|
|
4471
|
+
const entryAgentId = entry?.agentId;
|
|
4472
|
+
const exitCode = error ? 1 : 0;
|
|
4473
|
+
appendLog(entry?.logPath, `=== END ${actionId} @ ${(/* @__PURE__ */ new Date()).toISOString()} exit=${exitCode} ===
|
|
4335
4474
|
`);
|
|
4336
4475
|
processes.delete(actionId);
|
|
4337
|
-
broadcast("action-complete", { actionId, exitCode
|
|
4338
|
-
|
|
4339
|
-
|
|
4476
|
+
broadcast("action-complete", { actionId, exitCode });
|
|
4477
|
+
if (registry && entryAgentId) {
|
|
4478
|
+
if (exitCode === 0) {
|
|
4479
|
+
const mFolder = entry?.logPath ? path.dirname(entry.logPath) : null;
|
|
4480
|
+
registry.complete(entryAgentId, {
|
|
4481
|
+
actionId,
|
|
4482
|
+
milestoneId: entry?.milestoneId || milestoneId,
|
|
4483
|
+
summaryPath: mFolder ? path.join(mFolder, actionId + "-SUMMARY.md") : null,
|
|
4484
|
+
logPath: entry?.logPath || null
|
|
4485
|
+
});
|
|
4486
|
+
} else {
|
|
4487
|
+
registry.fail(entryAgentId, exitCode, error || "execution failed");
|
|
4488
|
+
}
|
|
4489
|
+
}
|
|
4490
|
+
}).catch((err) => {
|
|
4340
4491
|
const entry = processes.get(actionId);
|
|
4492
|
+
const entryAgentId = entry?.agentId;
|
|
4341
4493
|
appendLog(entry?.logPath, `=== ERROR ${actionId} @ ${(/* @__PURE__ */ new Date()).toISOString()} ===
|
|
4342
4494
|
`);
|
|
4343
4495
|
processes.delete(actionId);
|
|
4344
4496
|
broadcast("action-complete", { actionId, exitCode: -1 });
|
|
4497
|
+
if (registry && entryAgentId) {
|
|
4498
|
+
registry.fail(entryAgentId, -1, String(err.message || err));
|
|
4499
|
+
}
|
|
4345
4500
|
});
|
|
4346
4501
|
return { ok: true };
|
|
4347
4502
|
}
|
|
@@ -4350,7 +4505,7 @@ data: ${JSON.stringify(data)}
|
|
|
4350
4505
|
if (!entry) {
|
|
4351
4506
|
return { error: "not_running", status: 404 };
|
|
4352
4507
|
}
|
|
4353
|
-
entry.
|
|
4508
|
+
entry.abortController.abort();
|
|
4354
4509
|
return { ok: true };
|
|
4355
4510
|
}
|
|
4356
4511
|
function running() {
|
|
@@ -4366,7 +4521,7 @@ data: ${JSON.stringify(data)}
|
|
|
4366
4521
|
var require_derivation_runner = __commonJS({
|
|
4367
4522
|
"src/server/derivation-runner.js"(exports2, module2) {
|
|
4368
4523
|
"use strict";
|
|
4369
|
-
var {
|
|
4524
|
+
var { runAI } = require_ai_runner();
|
|
4370
4525
|
function buildPrompt(declarationId, declarations) {
|
|
4371
4526
|
let targets;
|
|
4372
4527
|
if (declarationId) {
|
|
@@ -4377,10 +4532,10 @@ var require_derivation_runner = __commonJS({
|
|
|
4377
4532
|
);
|
|
4378
4533
|
}
|
|
4379
4534
|
const formatted = targets.map((d) => `- ${d.id}: ${d.statement}`).join("\n");
|
|
4380
|
-
return 'You are deriving milestones for a Declare project. Given these declarations, propose 2-4 milestones per declaration by asking "For this to be true, what must be true?" Output ONLY a JSON array with no markdown fencing: [{"title": "milestone title", "realizes": "D-XX", "reason": "why this must be true"}]. Declarations:\n\n' + formatted;
|
|
4535
|
+
return 'You are deriving milestones for a Declare project. Given these declarations, propose 2-4 milestones per declaration by asking "For this to be true, what must be true?" Each milestone needs a concise title AND a detailed description explaining what it delivers, its scope, and success criteria. Output ONLY a JSON array with no markdown fencing: [{"title": "short milestone title", "description": "Detailed description of what this milestone delivers, its scope and boundaries, and how to verify it is complete.", "realizes": "D-XX", "reason": "why this must be true"}]. Declarations:\n\n' + formatted;
|
|
4381
4536
|
}
|
|
4382
|
-
function createDerivationRunner(sseClients, cwd) {
|
|
4383
|
-
|
|
4537
|
+
function createDerivationRunner(sseClients, cwd, registry) {
|
|
4538
|
+
const sessions = /* @__PURE__ */ new Map();
|
|
4384
4539
|
function broadcast(event, data) {
|
|
4385
4540
|
const payload = `event: ${event}
|
|
4386
4541
|
data: ${JSON.stringify(data)}
|
|
@@ -4394,88 +4549,117 @@ data: ${JSON.stringify(data)}
|
|
|
4394
4549
|
}
|
|
4395
4550
|
}
|
|
4396
4551
|
}
|
|
4397
|
-
function
|
|
4398
|
-
|
|
4399
|
-
|
|
4400
|
-
|
|
4401
|
-
|
|
4402
|
-
|
|
4403
|
-
|
|
4404
|
-
|
|
4405
|
-
|
|
4406
|
-
|
|
4407
|
-
|
|
4552
|
+
function derive(declarationId, declarations) {
|
|
4553
|
+
const sessionId = `deriv-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;
|
|
4554
|
+
const prompt = buildPrompt(declarationId, declarations);
|
|
4555
|
+
const abortController = new AbortController();
|
|
4556
|
+
let agentId;
|
|
4557
|
+
if (registry) {
|
|
4558
|
+
const agent = registry.spawn("derivation", declarationId || "all", "");
|
|
4559
|
+
agentId = agent.id;
|
|
4560
|
+
}
|
|
4561
|
+
sessions.set(sessionId, { abortController, agentId, declarationId, startTime: Date.now() });
|
|
4562
|
+
broadcast("derivation-output", {
|
|
4563
|
+
sessionId,
|
|
4564
|
+
declarationId,
|
|
4565
|
+
text: "Spawning AI agent\u2026",
|
|
4566
|
+
stream: "status"
|
|
4567
|
+
});
|
|
4568
|
+
runAI(prompt, {
|
|
4569
|
+
cwd,
|
|
4570
|
+
model: "sonnet",
|
|
4571
|
+
maxTurns: 1,
|
|
4572
|
+
abortController,
|
|
4573
|
+
onText: (chunk) => {
|
|
4408
4574
|
broadcast("derivation-output", {
|
|
4409
4575
|
sessionId,
|
|
4410
|
-
|
|
4411
|
-
|
|
4576
|
+
declarationId,
|
|
4577
|
+
text: chunk,
|
|
4578
|
+
stream: "stdout"
|
|
4412
4579
|
});
|
|
4413
4580
|
}
|
|
4414
|
-
}
|
|
4415
|
-
|
|
4416
|
-
|
|
4417
|
-
|
|
4418
|
-
|
|
4419
|
-
|
|
4420
|
-
|
|
4421
|
-
|
|
4422
|
-
|
|
4423
|
-
|
|
4424
|
-
|
|
4425
|
-
|
|
4426
|
-
|
|
4427
|
-
|
|
4428
|
-
|
|
4429
|
-
|
|
4430
|
-
|
|
4431
|
-
proc.stdout.on("data", createLineHandler(sessionId, "stdout", stdout));
|
|
4432
|
-
}
|
|
4433
|
-
if (proc.stderr) {
|
|
4434
|
-
proc.stderr.on("data", createLineHandler(sessionId, "stderr", stdout));
|
|
4435
|
-
}
|
|
4436
|
-
proc.on("close", (exitCode) => {
|
|
4581
|
+
}).then(({ text, error }) => {
|
|
4582
|
+
const session = sessions.get(sessionId);
|
|
4583
|
+
const closingAgentId = session ? session.agentId : void 0;
|
|
4584
|
+
sessions.delete(sessionId);
|
|
4585
|
+
if (error) {
|
|
4586
|
+
broadcast("derivation-complete", {
|
|
4587
|
+
sessionId,
|
|
4588
|
+
declarationId,
|
|
4589
|
+
exitCode: 1,
|
|
4590
|
+
milestones: null,
|
|
4591
|
+
error
|
|
4592
|
+
});
|
|
4593
|
+
if (registry && closingAgentId) {
|
|
4594
|
+
registry.fail(closingAgentId, 1, error);
|
|
4595
|
+
}
|
|
4596
|
+
return;
|
|
4597
|
+
}
|
|
4437
4598
|
let milestones = null;
|
|
4438
|
-
|
|
4439
|
-
|
|
4440
|
-
|
|
4441
|
-
|
|
4599
|
+
try {
|
|
4600
|
+
milestones = JSON.parse(text.trim());
|
|
4601
|
+
} catch (_) {
|
|
4602
|
+
const match = text.match(/\[[\s\S]*\]/);
|
|
4603
|
+
if (match) {
|
|
4604
|
+
try {
|
|
4605
|
+
milestones = JSON.parse(match[0]);
|
|
4606
|
+
} catch (__) {
|
|
4607
|
+
}
|
|
4442
4608
|
}
|
|
4443
4609
|
}
|
|
4444
|
-
current = null;
|
|
4445
4610
|
broadcast("derivation-complete", {
|
|
4446
4611
|
sessionId,
|
|
4447
|
-
|
|
4612
|
+
declarationId,
|
|
4613
|
+
exitCode: 0,
|
|
4448
4614
|
milestones
|
|
4449
4615
|
});
|
|
4450
|
-
|
|
4451
|
-
|
|
4452
|
-
|
|
4616
|
+
if (registry && closingAgentId) {
|
|
4617
|
+
const milestoneIds = Array.isArray(milestones) ? milestones.map((m) => m.id || m.title || "unknown").filter(Boolean) : [];
|
|
4618
|
+
registry.complete(closingAgentId, { milestones: milestoneIds });
|
|
4619
|
+
}
|
|
4620
|
+
}).catch((err) => {
|
|
4621
|
+
const session = sessions.get(sessionId);
|
|
4622
|
+
const errorAgentId = session ? session.agentId : void 0;
|
|
4623
|
+
sessions.delete(sessionId);
|
|
4453
4624
|
broadcast("derivation-complete", {
|
|
4454
4625
|
sessionId,
|
|
4626
|
+
declarationId,
|
|
4455
4627
|
exitCode: -1,
|
|
4456
|
-
milestones: null
|
|
4628
|
+
milestones: null,
|
|
4629
|
+
error: String(err.message || err)
|
|
4457
4630
|
});
|
|
4631
|
+
if (registry && errorAgentId) {
|
|
4632
|
+
registry.fail(errorAgentId, -1, "error");
|
|
4633
|
+
}
|
|
4458
4634
|
});
|
|
4459
4635
|
return { ok: true, sessionId };
|
|
4460
4636
|
}
|
|
4461
|
-
function stop() {
|
|
4462
|
-
if (
|
|
4463
|
-
|
|
4637
|
+
function stop(sessionId) {
|
|
4638
|
+
if (sessionId) {
|
|
4639
|
+
const session = sessions.get(sessionId);
|
|
4640
|
+
if (!session) {
|
|
4641
|
+
return { error: "not_running", status: 404 };
|
|
4642
|
+
}
|
|
4643
|
+
session.abortController.abort();
|
|
4644
|
+
return { ok: true };
|
|
4645
|
+
}
|
|
4646
|
+
return stopAll();
|
|
4647
|
+
}
|
|
4648
|
+
function stopAll() {
|
|
4649
|
+
for (const [, session] of sessions) {
|
|
4650
|
+
session.abortController.abort();
|
|
4464
4651
|
}
|
|
4465
|
-
current.proc.kill("SIGTERM");
|
|
4466
4652
|
return { ok: true };
|
|
4467
4653
|
}
|
|
4468
4654
|
function running() {
|
|
4469
|
-
|
|
4655
|
+
if (sessions.size === 0) return null;
|
|
4656
|
+
return Array.from(sessions.entries()).map(([id, s]) => ({
|
|
4657
|
+
sessionId: id,
|
|
4658
|
+
declarationId: s.declarationId,
|
|
4659
|
+
startTime: s.startTime
|
|
4660
|
+
}));
|
|
4470
4661
|
}
|
|
4471
|
-
return { derive, stop, running };
|
|
4472
|
-
}
|
|
4473
|
-
if (require.main === module2) {
|
|
4474
|
-
const runner = createDerivationRunner(/* @__PURE__ */ new Set(), ".");
|
|
4475
|
-
console.log("derive:", typeof runner.derive);
|
|
4476
|
-
console.log("stop:", typeof runner.stop);
|
|
4477
|
-
console.log("running:", typeof runner.running);
|
|
4478
|
-
console.log("OK");
|
|
4662
|
+
return { derive, stop, stopAll, running };
|
|
4479
4663
|
}
|
|
4480
4664
|
module2.exports = { createDerivationRunner };
|
|
4481
4665
|
}
|
|
@@ -4485,7 +4669,7 @@ data: ${JSON.stringify(data)}
|
|
|
4485
4669
|
var require_action_derivation_runner = __commonJS({
|
|
4486
4670
|
"src/server/action-derivation-runner.js"(exports2, module2) {
|
|
4487
4671
|
"use strict";
|
|
4488
|
-
var {
|
|
4672
|
+
var { runAI } = require_ai_runner();
|
|
4489
4673
|
function buildActionPrompt(milestone, existingActions) {
|
|
4490
4674
|
let prompt = `You are deriving actions for a Declare project milestone. An action is a concrete piece of work that causes (moves toward) a milestone. Given this milestone, propose 2-5 actions by asking "What work must be done to achieve this?" Output ONLY a JSON array with no markdown fencing: [{"title": "action title", "produces": "what this action delivers", "reason": "why this is needed"}].
|
|
4491
4675
|
|
|
@@ -4501,8 +4685,8 @@ Milestone:
|
|
|
4501
4685
|
}
|
|
4502
4686
|
return prompt;
|
|
4503
4687
|
}
|
|
4504
|
-
function createActionDerivationRunner(sseClients, cwd) {
|
|
4505
|
-
|
|
4688
|
+
function createActionDerivationRunner(sseClients, cwd, registry) {
|
|
4689
|
+
const sessions = /* @__PURE__ */ new Map();
|
|
4506
4690
|
function broadcast(event, data) {
|
|
4507
4691
|
const payload = `event: ${event}
|
|
4508
4692
|
data: ${JSON.stringify(data)}
|
|
@@ -4516,86 +4700,107 @@ data: ${JSON.stringify(data)}
|
|
|
4516
4700
|
}
|
|
4517
4701
|
}
|
|
4518
4702
|
}
|
|
4519
|
-
function createLineHandler(sessionId, streamName, accumulator) {
|
|
4520
|
-
let buffer = "";
|
|
4521
|
-
return (chunk) => {
|
|
4522
|
-
const text = chunk.toString();
|
|
4523
|
-
if (streamName === "stdout") {
|
|
4524
|
-
accumulator.text += text;
|
|
4525
|
-
}
|
|
4526
|
-
buffer += text;
|
|
4527
|
-
const lines = buffer.split("\n");
|
|
4528
|
-
buffer = lines.pop() || "";
|
|
4529
|
-
for (const line of lines) {
|
|
4530
|
-
broadcast("action-derivation-output", {
|
|
4531
|
-
sessionId,
|
|
4532
|
-
text: line,
|
|
4533
|
-
stream: streamName
|
|
4534
|
-
});
|
|
4535
|
-
}
|
|
4536
|
-
};
|
|
4537
|
-
}
|
|
4538
4703
|
function derive(milestone, existingActions) {
|
|
4539
|
-
if (current) {
|
|
4540
|
-
return { error: "busy", status: 409 };
|
|
4541
|
-
}
|
|
4542
4704
|
const sessionId = `action-deriv-${Date.now()}`;
|
|
4543
4705
|
const prompt = buildActionPrompt(milestone, existingActions);
|
|
4544
|
-
const
|
|
4545
|
-
|
|
4546
|
-
|
|
4706
|
+
const abortController = new AbortController();
|
|
4707
|
+
let agentId;
|
|
4708
|
+
if (registry) {
|
|
4709
|
+
const agent = registry.spawn("action-derivation", milestone.id, milestone.id);
|
|
4710
|
+
agentId = agent.id;
|
|
4711
|
+
}
|
|
4712
|
+
sessions.set(sessionId, { milestoneId: milestone.id, agentId, abortController, startTime: Date.now() });
|
|
4713
|
+
runAI(prompt, {
|
|
4547
4714
|
cwd,
|
|
4548
|
-
|
|
4549
|
-
|
|
4550
|
-
|
|
4551
|
-
|
|
4552
|
-
|
|
4553
|
-
|
|
4554
|
-
|
|
4555
|
-
|
|
4556
|
-
|
|
4557
|
-
|
|
4558
|
-
|
|
4715
|
+
model: "haiku",
|
|
4716
|
+
maxTurns: 1,
|
|
4717
|
+
abortController,
|
|
4718
|
+
onText: (text) => {
|
|
4719
|
+
broadcast("action-derivation-output", {
|
|
4720
|
+
sessionId,
|
|
4721
|
+
milestoneId: milestone.id,
|
|
4722
|
+
text,
|
|
4723
|
+
stream: "stdout"
|
|
4724
|
+
});
|
|
4725
|
+
}
|
|
4726
|
+
}).then(({ text, error }) => {
|
|
4727
|
+
const session = sessions.get(sessionId);
|
|
4728
|
+
const closingAgentId = session ? session.agentId : void 0;
|
|
4729
|
+
sessions.delete(sessionId);
|
|
4559
4730
|
let actions = null;
|
|
4560
|
-
|
|
4731
|
+
const exitCode = error ? 1 : 0;
|
|
4732
|
+
if (!error && text) {
|
|
4561
4733
|
try {
|
|
4562
|
-
actions = JSON.parse(
|
|
4734
|
+
actions = JSON.parse(text.trim());
|
|
4563
4735
|
} catch (_) {
|
|
4736
|
+
const jsonMatch = text.match(/\[[\s\S]*\]/);
|
|
4737
|
+
if (jsonMatch) {
|
|
4738
|
+
try {
|
|
4739
|
+
actions = JSON.parse(jsonMatch[0]);
|
|
4740
|
+
} catch (_2) {
|
|
4741
|
+
}
|
|
4742
|
+
}
|
|
4564
4743
|
}
|
|
4565
4744
|
}
|
|
4566
|
-
current = null;
|
|
4567
4745
|
broadcast("action-derivation-complete", {
|
|
4568
4746
|
sessionId,
|
|
4569
|
-
|
|
4747
|
+
milestoneId: milestone.id,
|
|
4748
|
+
exitCode,
|
|
4570
4749
|
actions
|
|
4571
4750
|
});
|
|
4572
|
-
|
|
4573
|
-
|
|
4574
|
-
|
|
4575
|
-
|
|
4576
|
-
|
|
4577
|
-
|
|
4578
|
-
|
|
4579
|
-
|
|
4751
|
+
if (registry && closingAgentId) {
|
|
4752
|
+
if (exitCode === 0) {
|
|
4753
|
+
registry.complete(closingAgentId, {
|
|
4754
|
+
milestoneId: milestone.id,
|
|
4755
|
+
actionCount: Array.isArray(actions) ? actions.length : null
|
|
4756
|
+
});
|
|
4757
|
+
} else {
|
|
4758
|
+
registry.fail(closingAgentId, 1, error || "action derivation failed");
|
|
4759
|
+
}
|
|
4760
|
+
}
|
|
4761
|
+
}).catch((err) => {
|
|
4762
|
+
const session = sessions.get(sessionId);
|
|
4763
|
+
const closingAgentId = session ? session.agentId : void 0;
|
|
4764
|
+
sessions.delete(sessionId);
|
|
4765
|
+
broadcast("action-derivation-complete", { sessionId, milestoneId: milestone.id, exitCode: 1, actions: null });
|
|
4766
|
+
if (registry && closingAgentId) {
|
|
4767
|
+
registry.fail(closingAgentId, 1, String(err));
|
|
4768
|
+
}
|
|
4580
4769
|
});
|
|
4581
4770
|
return { ok: true, sessionId };
|
|
4582
4771
|
}
|
|
4583
|
-
function stop() {
|
|
4584
|
-
if (
|
|
4585
|
-
|
|
4772
|
+
function stop(sessionId) {
|
|
4773
|
+
if (sessionId) {
|
|
4774
|
+
const session = sessions.get(sessionId);
|
|
4775
|
+
if (!session) {
|
|
4776
|
+
return { error: "not_running", status: 404 };
|
|
4777
|
+
}
|
|
4778
|
+
session.abortController.abort();
|
|
4779
|
+
return { ok: true };
|
|
4780
|
+
}
|
|
4781
|
+
return stopAll();
|
|
4782
|
+
}
|
|
4783
|
+
function stopAll() {
|
|
4784
|
+
for (const [, session] of sessions) {
|
|
4785
|
+
session.abortController.abort();
|
|
4586
4786
|
}
|
|
4587
|
-
current.proc.kill("SIGTERM");
|
|
4588
4787
|
return { ok: true };
|
|
4589
4788
|
}
|
|
4590
4789
|
function running() {
|
|
4591
|
-
|
|
4790
|
+
if (sessions.size === 0) return null;
|
|
4791
|
+
return Array.from(sessions.entries()).map(([id, s]) => ({
|
|
4792
|
+
sessionId: id,
|
|
4793
|
+
milestoneId: s.milestoneId,
|
|
4794
|
+
startTime: s.startTime
|
|
4795
|
+
}));
|
|
4592
4796
|
}
|
|
4593
|
-
return { derive, stop, running };
|
|
4797
|
+
return { derive, stop, stopAll, running };
|
|
4594
4798
|
}
|
|
4595
4799
|
if (require.main === module2) {
|
|
4596
4800
|
const runner = createActionDerivationRunner(/* @__PURE__ */ new Set(), ".");
|
|
4597
4801
|
console.log("derive:", typeof runner.derive);
|
|
4598
4802
|
console.log("stop:", typeof runner.stop);
|
|
4803
|
+
console.log("stopAll:", typeof runner.stopAll);
|
|
4599
4804
|
console.log("running:", typeof runner.running);
|
|
4600
4805
|
console.log("OK");
|
|
4601
4806
|
}
|
|
@@ -4607,14 +4812,15 @@ data: ${JSON.stringify(data)}
|
|
|
4607
4812
|
var require_revision_runner = __commonJS({
|
|
4608
4813
|
"src/server/revision-runner.js"(exports2, module2) {
|
|
4609
4814
|
"use strict";
|
|
4610
|
-
var {
|
|
4815
|
+
var { runAI } = require_ai_runner();
|
|
4611
4816
|
var fs = require("node:fs");
|
|
4612
4817
|
var path = require("node:path");
|
|
4613
4818
|
function buildRevisionPrompt(artifactContent, annotations) {
|
|
4614
4819
|
const annotationList = annotations.map((a) => `- Line ${a.line}: ${a.text}`).join("\n");
|
|
4615
|
-
return "
|
|
4820
|
+
return "## Current plan content\n\n" + artifactContent + "\n\n## Reviewer annotations to address\n\n" + annotationList + "\n\n## Instructions\n\nRevise the plan above to address ALL the reviewer's annotations. Output ONLY the revised plan content \u2014 no explanations, no markdown fencing, no preamble. The output will directly replace the current file.";
|
|
4616
4821
|
}
|
|
4617
|
-
|
|
4822
|
+
var SYSTEM_PROMPT = "You are revising a plan artifact based on reviewer annotations. Do NOT implement anything \u2014 only update the plan document. Output ONLY the revised content with no markdown fencing or preamble.";
|
|
4823
|
+
function createRevisionRunner(sseClients, cwd, onComplete, registry) {
|
|
4618
4824
|
let current = null;
|
|
4619
4825
|
function broadcast(event, data) {
|
|
4620
4826
|
const payload = `event: ${event}
|
|
@@ -4629,26 +4835,6 @@ data: ${JSON.stringify(data)}
|
|
|
4629
4835
|
}
|
|
4630
4836
|
}
|
|
4631
4837
|
}
|
|
4632
|
-
function createLineHandler(sessionId, nodeId, streamName, accumulator) {
|
|
4633
|
-
let buffer = "";
|
|
4634
|
-
return (chunk) => {
|
|
4635
|
-
const text = chunk.toString();
|
|
4636
|
-
if (streamName === "stdout") {
|
|
4637
|
-
accumulator.text += text;
|
|
4638
|
-
}
|
|
4639
|
-
buffer += text;
|
|
4640
|
-
const lines = buffer.split("\n");
|
|
4641
|
-
buffer = lines.pop() || "";
|
|
4642
|
-
for (const line of lines) {
|
|
4643
|
-
broadcast("revision-output", {
|
|
4644
|
-
sessionId,
|
|
4645
|
-
nodeId,
|
|
4646
|
-
text: line,
|
|
4647
|
-
stream: streamName
|
|
4648
|
-
});
|
|
4649
|
-
}
|
|
4650
|
-
};
|
|
4651
|
-
}
|
|
4652
4838
|
function stripMarkdownFencing(text) {
|
|
4653
4839
|
const trimmed = text.trim();
|
|
4654
4840
|
const fenceMatch = trimmed.match(/^```(?:markdown)?\s*\n([\s\S]*?)\n```\s*$/);
|
|
@@ -4677,70 +4863,90 @@ data: ${JSON.stringify(data)}
|
|
|
4677
4863
|
fs.copyFileSync(artifactPath, versionedPath);
|
|
4678
4864
|
} catch (_) {
|
|
4679
4865
|
}
|
|
4680
|
-
const
|
|
4681
|
-
|
|
4682
|
-
|
|
4683
|
-
|
|
4684
|
-
|
|
4685
|
-
});
|
|
4686
|
-
current = { sessionId, proc, nodeId };
|
|
4687
|
-
const stdout = { text: "" };
|
|
4688
|
-
if (proc.stdout) {
|
|
4689
|
-
proc.stdout.on("data", createLineHandler(sessionId, nodeId, "stdout", stdout));
|
|
4690
|
-
}
|
|
4691
|
-
if (proc.stderr) {
|
|
4692
|
-
proc.stderr.on("data", createLineHandler(sessionId, nodeId, "stderr", stdout));
|
|
4866
|
+
const abortController = new AbortController();
|
|
4867
|
+
let agentId;
|
|
4868
|
+
if (registry) {
|
|
4869
|
+
const agent = registry.spawn("revision", nodeId, "");
|
|
4870
|
+
agentId = agent.id;
|
|
4693
4871
|
}
|
|
4694
|
-
|
|
4872
|
+
current = { sessionId, abortController, nodeId, agentId };
|
|
4873
|
+
runAI(prompt, {
|
|
4874
|
+
cwd,
|
|
4875
|
+
model: "sonnet",
|
|
4876
|
+
systemPrompt: SYSTEM_PROMPT,
|
|
4877
|
+
abortController,
|
|
4878
|
+
onText: (chunk) => {
|
|
4879
|
+
broadcast("revision-output", {
|
|
4880
|
+
sessionId,
|
|
4881
|
+
nodeId,
|
|
4882
|
+
text: chunk,
|
|
4883
|
+
stream: "stdout"
|
|
4884
|
+
});
|
|
4885
|
+
}
|
|
4886
|
+
}).then(({ text, error }) => {
|
|
4695
4887
|
const completedNodeId = current ? current.nodeId : nodeId;
|
|
4888
|
+
const closingAgentId = current ? current.agentId : void 0;
|
|
4696
4889
|
current = null;
|
|
4697
|
-
if (
|
|
4698
|
-
|
|
4699
|
-
|
|
4700
|
-
|
|
4701
|
-
|
|
4702
|
-
|
|
4703
|
-
|
|
4704
|
-
|
|
4705
|
-
|
|
4706
|
-
|
|
4707
|
-
|
|
4890
|
+
if (error) {
|
|
4891
|
+
broadcast("revision-complete", {
|
|
4892
|
+
sessionId,
|
|
4893
|
+
nodeId: completedNodeId,
|
|
4894
|
+
exitCode: 1,
|
|
4895
|
+
error: true
|
|
4896
|
+
});
|
|
4897
|
+
if (registry && closingAgentId) {
|
|
4898
|
+
registry.fail(closingAgentId, 1, error);
|
|
4899
|
+
}
|
|
4900
|
+
return;
|
|
4901
|
+
}
|
|
4902
|
+
try {
|
|
4903
|
+
const revisedContent = stripMarkdownFencing(text);
|
|
4904
|
+
fs.writeFileSync(artifactPath, revisedContent, "utf-8");
|
|
4905
|
+
const annPath = path.join(cwd, ".planning", "annotations", completedNodeId.toUpperCase() + ".json");
|
|
4906
|
+
let annData = { nodeId: completedNodeId.toUpperCase(), annotations: [], revisionRound: 0 };
|
|
4907
|
+
if (fs.existsSync(annPath)) {
|
|
4908
|
+
try {
|
|
4909
|
+
annData = JSON.parse(fs.readFileSync(annPath, "utf-8"));
|
|
4910
|
+
} catch (_) {
|
|
4708
4911
|
}
|
|
4709
|
-
|
|
4710
|
-
|
|
4711
|
-
|
|
4712
|
-
|
|
4713
|
-
|
|
4714
|
-
|
|
4715
|
-
|
|
4716
|
-
|
|
4717
|
-
|
|
4718
|
-
|
|
4719
|
-
|
|
4720
|
-
|
|
4721
|
-
|
|
4722
|
-
|
|
4723
|
-
|
|
4724
|
-
|
|
4912
|
+
}
|
|
4913
|
+
const newRound = (annData.revisionRound || 0) + 1;
|
|
4914
|
+
annData.revisionRound = newRound;
|
|
4915
|
+
const annDir = path.dirname(annPath);
|
|
4916
|
+
fs.mkdirSync(annDir, { recursive: true });
|
|
4917
|
+
fs.writeFileSync(annPath, JSON.stringify(annData, null, 2), "utf-8");
|
|
4918
|
+
broadcast("revision-complete", {
|
|
4919
|
+
sessionId,
|
|
4920
|
+
nodeId: completedNodeId,
|
|
4921
|
+
exitCode: 0,
|
|
4922
|
+
revisionRound: newRound
|
|
4923
|
+
});
|
|
4924
|
+
if (onComplete) {
|
|
4925
|
+
try {
|
|
4926
|
+
onComplete(completedNodeId);
|
|
4927
|
+
} catch (_) {
|
|
4725
4928
|
}
|
|
4726
|
-
}
|
|
4727
|
-
|
|
4728
|
-
|
|
4929
|
+
}
|
|
4930
|
+
if (registry && closingAgentId) {
|
|
4931
|
+
registry.complete(closingAgentId, {
|
|
4729
4932
|
nodeId: completedNodeId,
|
|
4730
|
-
|
|
4731
|
-
|
|
4933
|
+
planPath: artifactPath,
|
|
4934
|
+
revisionRound: newRound
|
|
4732
4935
|
});
|
|
4733
4936
|
}
|
|
4734
|
-
}
|
|
4937
|
+
} catch (err) {
|
|
4735
4938
|
broadcast("revision-complete", {
|
|
4736
4939
|
sessionId,
|
|
4737
4940
|
nodeId: completedNodeId,
|
|
4738
|
-
exitCode:
|
|
4941
|
+
exitCode: -1,
|
|
4739
4942
|
error: true
|
|
4740
4943
|
});
|
|
4944
|
+
if (registry && closingAgentId) {
|
|
4945
|
+
registry.fail(closingAgentId, -1, "revision post-processing error");
|
|
4946
|
+
}
|
|
4741
4947
|
}
|
|
4742
|
-
})
|
|
4743
|
-
|
|
4948
|
+
}).catch((err) => {
|
|
4949
|
+
const errorAgentId = current ? current.agentId : void 0;
|
|
4744
4950
|
current = null;
|
|
4745
4951
|
broadcast("revision-complete", {
|
|
4746
4952
|
sessionId,
|
|
@@ -4748,6 +4954,9 @@ data: ${JSON.stringify(data)}
|
|
|
4748
4954
|
exitCode: -1,
|
|
4749
4955
|
error: true
|
|
4750
4956
|
});
|
|
4957
|
+
if (registry && errorAgentId) {
|
|
4958
|
+
registry.fail(errorAgentId, -1, "error");
|
|
4959
|
+
}
|
|
4751
4960
|
});
|
|
4752
4961
|
return { ok: true, sessionId };
|
|
4753
4962
|
}
|
|
@@ -4755,7 +4964,7 @@ data: ${JSON.stringify(data)}
|
|
|
4755
4964
|
if (!current) {
|
|
4756
4965
|
return { error: "not_running", status: 404 };
|
|
4757
4966
|
}
|
|
4758
|
-
current.
|
|
4967
|
+
current.abortController.abort();
|
|
4759
4968
|
return { ok: true };
|
|
4760
4969
|
}
|
|
4761
4970
|
function running() {
|
|
@@ -5204,57 +5413,252 @@ data: ${JSON.stringify(data)}
|
|
|
5204
5413
|
}
|
|
5205
5414
|
});
|
|
5206
5415
|
|
|
5207
|
-
// src/
|
|
5208
|
-
var
|
|
5209
|
-
"src/
|
|
5416
|
+
// src/commands/lifecycle-stages.js
|
|
5417
|
+
var require_lifecycle_stages = __commonJS({
|
|
5418
|
+
"src/commands/lifecycle-stages.js"(exports2, module2) {
|
|
5210
5419
|
"use strict";
|
|
5211
|
-
var
|
|
5212
|
-
var
|
|
5213
|
-
|
|
5214
|
-
|
|
5215
|
-
|
|
5216
|
-
|
|
5217
|
-
|
|
5218
|
-
|
|
5219
|
-
|
|
5220
|
-
|
|
5221
|
-
|
|
5420
|
+
var COMPLETED_STATUSES = /* @__PURE__ */ new Set(["DONE", "KEPT", "HONORED"]);
|
|
5421
|
+
var EXECUTING_STATUSES = /* @__PURE__ */ new Set(["EXECUTING", "IN_PROGRESS", "RUNNING"]);
|
|
5422
|
+
function computeLifecycleStages(graph, runningActionIds) {
|
|
5423
|
+
const { declarations = [], milestones = [], actions = [] } = graph;
|
|
5424
|
+
const running = runningActionIds || /* @__PURE__ */ new Set();
|
|
5425
|
+
const stages = {
|
|
5426
|
+
"needs-planning": [],
|
|
5427
|
+
"needs-approval": [],
|
|
5428
|
+
"ready-to-execute": [],
|
|
5429
|
+
"in-execution": [],
|
|
5430
|
+
"done": []
|
|
5431
|
+
};
|
|
5432
|
+
const milestoneById = new Map(milestones.map((m) => [m.id, m]));
|
|
5433
|
+
const actionsByMilestone = /* @__PURE__ */ new Map();
|
|
5434
|
+
for (const a of actions) {
|
|
5435
|
+
for (const mId of a.causes || []) {
|
|
5436
|
+
if (!actionsByMilestone.has(mId)) actionsByMilestone.set(mId, []);
|
|
5437
|
+
actionsByMilestone.get(mId).push(a);
|
|
5438
|
+
}
|
|
5222
5439
|
}
|
|
5223
|
-
|
|
5224
|
-
|
|
5225
|
-
|
|
5226
|
-
|
|
5227
|
-
|
|
5228
|
-
|
|
5229
|
-
|
|
5230
|
-
|
|
5231
|
-
|
|
5232
|
-
try {
|
|
5233
|
-
endSha = execSync("git rev-parse --short HEAD", { cwd }).toString().trim();
|
|
5234
|
-
} catch (_) {
|
|
5440
|
+
const milestonesByDecl = /* @__PURE__ */ new Map();
|
|
5441
|
+
for (const d of declarations) {
|
|
5442
|
+
milestonesByDecl.set(d.id, (d.milestones || []).filter((mId) => milestoneById.has(mId)));
|
|
5443
|
+
}
|
|
5444
|
+
for (const d of declarations) {
|
|
5445
|
+
const status = (d.status || "").toUpperCase();
|
|
5446
|
+
if (COMPLETED_STATUSES.has(status)) {
|
|
5447
|
+
stages["done"].push(makeItem(d, "declaration", "done"));
|
|
5448
|
+
continue;
|
|
5235
5449
|
}
|
|
5236
|
-
const
|
|
5237
|
-
|
|
5238
|
-
|
|
5239
|
-
|
|
5240
|
-
const passed = results.filter((r) => r.exitCode === 0).length;
|
|
5241
|
-
const failed = results.filter((r) => r.exitCode !== 0).length;
|
|
5242
|
-
const overallStatus = pipelineStopped ? "STOPPED" : failed > 0 ? "FAILED" : "SUCCESS";
|
|
5243
|
-
const startedAt = new Date(pipelineStartTime).toISOString();
|
|
5244
|
-
const completedAt = new Date(endTime).toISOString();
|
|
5245
|
-
let rows = "";
|
|
5246
|
-
for (let i = 0; i < results.length; i++) {
|
|
5247
|
-
const r = results[i];
|
|
5248
|
-
const status = r.exitCode === 0 ? "PASS" : "FAIL";
|
|
5249
|
-
const durMin = Math.floor(r.durationMs / 6e4);
|
|
5250
|
-
const durSec = Math.floor(r.durationMs % 6e4 / 1e3);
|
|
5251
|
-
const retried = r.retried ? `Yes (${r.attempts})` : "No";
|
|
5252
|
-
rows += `| ${i + 1} | ${r.actionId} | ${r.milestoneId} | ${status} | ${durMin}m ${durSec}s | ${retried} |
|
|
5253
|
-
`;
|
|
5450
|
+
const myMilestones = milestonesByDecl.get(d.id) || [];
|
|
5451
|
+
if (myMilestones.length === 0) {
|
|
5452
|
+
stages["needs-planning"].push(makeItem(d, "declaration", "needs-planning"));
|
|
5453
|
+
continue;
|
|
5254
5454
|
}
|
|
5255
|
-
|
|
5256
|
-
|
|
5257
|
-
|
|
5455
|
+
if (d.reviewState !== "approved") {
|
|
5456
|
+
stages["needs-approval"].push(makeItem(d, "declaration", "needs-approval"));
|
|
5457
|
+
continue;
|
|
5458
|
+
}
|
|
5459
|
+
const allMilestonesDone = myMilestones.every((mId) => {
|
|
5460
|
+
const m = milestoneById.get(mId);
|
|
5461
|
+
return m && COMPLETED_STATUSES.has((m.status || "").toUpperCase());
|
|
5462
|
+
});
|
|
5463
|
+
if (allMilestonesDone) {
|
|
5464
|
+
stages["done"].push(makeItem(d, "declaration", "done"));
|
|
5465
|
+
} else {
|
|
5466
|
+
}
|
|
5467
|
+
}
|
|
5468
|
+
for (const m of milestones) {
|
|
5469
|
+
const status = (m.status || "").toUpperCase();
|
|
5470
|
+
if (COMPLETED_STATUSES.has(status)) {
|
|
5471
|
+
stages["done"].push(makeItem(m, "milestone", "done"));
|
|
5472
|
+
continue;
|
|
5473
|
+
}
|
|
5474
|
+
const myActions = actionsByMilestone.get(m.id) || [];
|
|
5475
|
+
const hasPlan = m.hasPlan || myActions.length > 0;
|
|
5476
|
+
if (!hasPlan) {
|
|
5477
|
+
stages["needs-planning"].push(makeItem(m, "milestone", "needs-planning"));
|
|
5478
|
+
continue;
|
|
5479
|
+
}
|
|
5480
|
+
if (m.reviewState !== "approved") {
|
|
5481
|
+
stages["needs-approval"].push(makeItem(m, "milestone", "needs-approval"));
|
|
5482
|
+
continue;
|
|
5483
|
+
}
|
|
5484
|
+
const hasExecuting = myActions.some(
|
|
5485
|
+
(a) => EXECUTING_STATUSES.has((a.status || "").toUpperCase()) || running.has(a.id)
|
|
5486
|
+
);
|
|
5487
|
+
if (hasExecuting) {
|
|
5488
|
+
stages["in-execution"].push(makeItem(m, "milestone", "in-execution"));
|
|
5489
|
+
continue;
|
|
5490
|
+
}
|
|
5491
|
+
const allActionsDone = myActions.length > 0 && myActions.every(
|
|
5492
|
+
(a) => COMPLETED_STATUSES.has((a.status || "").toUpperCase())
|
|
5493
|
+
);
|
|
5494
|
+
if (allActionsDone) {
|
|
5495
|
+
stages["done"].push(makeItem(m, "milestone", "done"));
|
|
5496
|
+
continue;
|
|
5497
|
+
}
|
|
5498
|
+
const deps = m.dependsOn || [];
|
|
5499
|
+
const depsBlocked = deps.some((depId) => {
|
|
5500
|
+
const dep = milestoneById.get(depId);
|
|
5501
|
+
return !dep || !COMPLETED_STATUSES.has((dep.status || "").toUpperCase());
|
|
5502
|
+
});
|
|
5503
|
+
if (depsBlocked) {
|
|
5504
|
+
stages["needs-approval"].push(makeItem(m, "milestone", "needs-approval"));
|
|
5505
|
+
} else {
|
|
5506
|
+
stages["ready-to-execute"].push(makeItem(m, "milestone", "ready-to-execute"));
|
|
5507
|
+
}
|
|
5508
|
+
}
|
|
5509
|
+
for (const a of actions) {
|
|
5510
|
+
const status = (a.status || "").toUpperCase();
|
|
5511
|
+
if (COMPLETED_STATUSES.has(status)) {
|
|
5512
|
+
stages["done"].push(makeItem(a, "action", "done"));
|
|
5513
|
+
continue;
|
|
5514
|
+
}
|
|
5515
|
+
if (EXECUTING_STATUSES.has(status) || running.has(a.id)) {
|
|
5516
|
+
stages["in-execution"].push(makeItem(a, "action", "in-execution"));
|
|
5517
|
+
continue;
|
|
5518
|
+
}
|
|
5519
|
+
if (a.reviewState !== "approved") {
|
|
5520
|
+
stages["needs-approval"].push(makeItem(a, "action", "needs-approval"));
|
|
5521
|
+
continue;
|
|
5522
|
+
}
|
|
5523
|
+
const parentMilestones = (a.causes || []).map((mId) => milestoneById.get(mId)).filter(Boolean);
|
|
5524
|
+
const parentBlocked = parentMilestones.some((m) => {
|
|
5525
|
+
const deps = m.dependsOn || [];
|
|
5526
|
+
return deps.some((depId) => {
|
|
5527
|
+
const dep = milestoneById.get(depId);
|
|
5528
|
+
return !dep || !COMPLETED_STATUSES.has((dep.status || "").toUpperCase());
|
|
5529
|
+
});
|
|
5530
|
+
});
|
|
5531
|
+
if (parentBlocked) {
|
|
5532
|
+
stages["needs-approval"].push(makeItem(a, "action", "needs-approval"));
|
|
5533
|
+
} else {
|
|
5534
|
+
stages["ready-to-execute"].push(makeItem(a, "action", "ready-to-execute"));
|
|
5535
|
+
}
|
|
5536
|
+
}
|
|
5537
|
+
const nextAction = computeNextAction(stages, declarations, milestones, actions);
|
|
5538
|
+
const total = declarations.length + milestones.length + actions.length;
|
|
5539
|
+
const done = stages["done"].length;
|
|
5540
|
+
const percentage = total > 0 ? Math.round(done / total * 100) : 0;
|
|
5541
|
+
return {
|
|
5542
|
+
stages,
|
|
5543
|
+
nextAction,
|
|
5544
|
+
progress: { total, done, percentage }
|
|
5545
|
+
};
|
|
5546
|
+
}
|
|
5547
|
+
function computeNextAction(stages, declarations, milestones, actions) {
|
|
5548
|
+
const planningDecl = stages["needs-planning"].find((item) => item.type === "declaration");
|
|
5549
|
+
if (planningDecl) {
|
|
5550
|
+
return {
|
|
5551
|
+
action: "derive-milestones",
|
|
5552
|
+
label: `Plan milestones for ${planningDecl.id}`,
|
|
5553
|
+
targetId: planningDecl.id,
|
|
5554
|
+
targetType: "declaration"
|
|
5555
|
+
};
|
|
5556
|
+
}
|
|
5557
|
+
const planningMile = stages["needs-planning"].find((item) => item.type === "milestone");
|
|
5558
|
+
if (planningMile) {
|
|
5559
|
+
return {
|
|
5560
|
+
action: "derive-actions",
|
|
5561
|
+
label: `Plan actions for ${planningMile.id}`,
|
|
5562
|
+
targetId: planningMile.id,
|
|
5563
|
+
targetType: "milestone"
|
|
5564
|
+
};
|
|
5565
|
+
}
|
|
5566
|
+
if (stages["needs-approval"].length > 0) {
|
|
5567
|
+
const first = stages["needs-approval"][0];
|
|
5568
|
+
return {
|
|
5569
|
+
action: "approve",
|
|
5570
|
+
label: `Review ${first.id}`,
|
|
5571
|
+
targetId: first.id,
|
|
5572
|
+
targetType: first.type
|
|
5573
|
+
};
|
|
5574
|
+
}
|
|
5575
|
+
if (stages["ready-to-execute"].length > 0) {
|
|
5576
|
+
return {
|
|
5577
|
+
action: "execute",
|
|
5578
|
+
label: "Execute approved actions"
|
|
5579
|
+
};
|
|
5580
|
+
}
|
|
5581
|
+
if (stages["in-execution"].length > 0) {
|
|
5582
|
+
return {
|
|
5583
|
+
action: "view-execution",
|
|
5584
|
+
label: "Execution in progress"
|
|
5585
|
+
};
|
|
5586
|
+
}
|
|
5587
|
+
const total = declarations.length + milestones.length + actions.length;
|
|
5588
|
+
if (total > 0) {
|
|
5589
|
+
return {
|
|
5590
|
+
action: "complete",
|
|
5591
|
+
label: "All items complete"
|
|
5592
|
+
};
|
|
5593
|
+
}
|
|
5594
|
+
return null;
|
|
5595
|
+
}
|
|
5596
|
+
function makeItem(node, type, stage) {
|
|
5597
|
+
return {
|
|
5598
|
+
id: node.id,
|
|
5599
|
+
title: node.title || node.statement || node.id,
|
|
5600
|
+
type,
|
|
5601
|
+
status: node.status || "PENDING",
|
|
5602
|
+
reviewState: node.reviewState,
|
|
5603
|
+
stage
|
|
5604
|
+
};
|
|
5605
|
+
}
|
|
5606
|
+
module2.exports = { computeLifecycleStages, COMPLETED_STATUSES, EXECUTING_STATUSES };
|
|
5607
|
+
}
|
|
5608
|
+
});
|
|
5609
|
+
|
|
5610
|
+
// src/server/pipeline-runner.js
|
|
5611
|
+
var require_pipeline_runner = __commonJS({
|
|
5612
|
+
"src/server/pipeline-runner.js"(exports2, module2) {
|
|
5613
|
+
"use strict";
|
|
5614
|
+
var { runAI } = require_ai_runner();
|
|
5615
|
+
var { execSync } = require("node:child_process");
|
|
5616
|
+
var fs = require("node:fs");
|
|
5617
|
+
var path = require("node:path");
|
|
5618
|
+
var { findMilestoneFolder } = require_milestone_folders();
|
|
5619
|
+
var STATE_FILE = ".planning/pipeline-state.json";
|
|
5620
|
+
var OUTPUT_BUFFER_MAX = 5e4;
|
|
5621
|
+
function appendLog(logPath, line) {
|
|
5622
|
+
if (!logPath) return;
|
|
5623
|
+
try {
|
|
5624
|
+
fs.appendFileSync(logPath, line + "\n", "utf-8");
|
|
5625
|
+
} catch (_) {
|
|
5626
|
+
}
|
|
5627
|
+
}
|
|
5628
|
+
function isTransientFailure(exitCode, errorOutput) {
|
|
5629
|
+
if (exitCode === 124 || exitCode === 137 || exitCode === -1) return true;
|
|
5630
|
+
const patterns = /ETIMEDOUT|ECONNRESET|ECONNREFUSED|ENOMEM|SIGKILL|SIGTERM|socket hang up|network timeout|Cancelled/i;
|
|
5631
|
+
return patterns.test(errorOutput);
|
|
5632
|
+
}
|
|
5633
|
+
function generateExecutionReport(cwd, results, pipelineStartTime, pipelineStopped, startSha) {
|
|
5634
|
+
try {
|
|
5635
|
+
let endSha = "unknown";
|
|
5636
|
+
try {
|
|
5637
|
+
endSha = execSync("git rev-parse --short HEAD", { cwd }).toString().trim();
|
|
5638
|
+
} catch (_) {
|
|
5639
|
+
}
|
|
5640
|
+
const endTime = Date.now();
|
|
5641
|
+
const totalMs = endTime - pipelineStartTime;
|
|
5642
|
+
const totalMin = Math.floor(totalMs / 6e4);
|
|
5643
|
+
const totalSec = Math.floor(totalMs % 6e4 / 1e3);
|
|
5644
|
+
const passed = results.filter((r) => r.exitCode === 0).length;
|
|
5645
|
+
const failed = results.filter((r) => r.exitCode !== 0).length;
|
|
5646
|
+
const overallStatus = pipelineStopped ? "STOPPED" : failed > 0 ? "FAILED" : "SUCCESS";
|
|
5647
|
+
const startedAt = new Date(pipelineStartTime).toISOString();
|
|
5648
|
+
const completedAt = new Date(endTime).toISOString();
|
|
5649
|
+
let rows = "";
|
|
5650
|
+
for (let i = 0; i < results.length; i++) {
|
|
5651
|
+
const r = results[i];
|
|
5652
|
+
const status = r.exitCode === 0 ? "PASS" : "FAIL";
|
|
5653
|
+
const durMin = Math.floor(r.durationMs / 6e4);
|
|
5654
|
+
const durSec = Math.floor(r.durationMs % 6e4 / 1e3);
|
|
5655
|
+
const retried = r.retried ? `Yes (${r.attempts})` : "No";
|
|
5656
|
+
rows += `| ${i + 1} | ${r.actionId} | ${r.milestoneId} | ${status} | ${durMin}m ${durSec}s | ${retried} |
|
|
5657
|
+
`;
|
|
5658
|
+
}
|
|
5659
|
+
const report = `# Execution Report
|
|
5660
|
+
|
|
5661
|
+
**Status:** ${overallStatus}
|
|
5258
5662
|
**Started:** ${startedAt}
|
|
5259
5663
|
**Completed:** ${completedAt}
|
|
5260
5664
|
**Duration:** ${totalMin}m ${totalSec}s
|
|
@@ -5278,16 +5682,18 @@ ${rows}
|
|
|
5278
5682
|
return null;
|
|
5279
5683
|
}
|
|
5280
5684
|
}
|
|
5281
|
-
function createPipelineRunner(sseClients, cwd) {
|
|
5685
|
+
function createPipelineRunner(sseClients, cwd, registry) {
|
|
5282
5686
|
let isRunning = false;
|
|
5283
5687
|
let stopRequested = false;
|
|
5284
|
-
const
|
|
5688
|
+
const activeControllers = /* @__PURE__ */ new Map();
|
|
5285
5689
|
let results = [];
|
|
5286
5690
|
let pipelineState = null;
|
|
5287
5691
|
let pausedOnFailure = null;
|
|
5288
5692
|
let skipResolve = null;
|
|
5289
5693
|
const outputBuffers = {};
|
|
5290
5694
|
let totalActionCount = 0;
|
|
5695
|
+
let pipelineAgentId;
|
|
5696
|
+
const actionAgentIds = /* @__PURE__ */ new Map();
|
|
5291
5697
|
function persistState() {
|
|
5292
5698
|
if (!pipelineState) {
|
|
5293
5699
|
try {
|
|
@@ -5304,7 +5710,7 @@ ${rows}
|
|
|
5304
5710
|
completedActions: pipelineState.completedActions,
|
|
5305
5711
|
failedActions: pipelineState.failedActions,
|
|
5306
5712
|
stoppedActions: pipelineState.stoppedActions,
|
|
5307
|
-
activeActions: [...
|
|
5713
|
+
activeActions: [...activeControllers.keys()],
|
|
5308
5714
|
outputBuffers,
|
|
5309
5715
|
pausedOnFailure,
|
|
5310
5716
|
timestamp: Date.now()
|
|
@@ -5359,78 +5765,75 @@ data: ${JSON.stringify(data)}
|
|
|
5359
5765
|
}
|
|
5360
5766
|
}
|
|
5361
5767
|
function executeAction(actionId, milestoneId) {
|
|
5362
|
-
|
|
5363
|
-
|
|
5364
|
-
|
|
5365
|
-
const
|
|
5366
|
-
|
|
5367
|
-
|
|
5368
|
-
|
|
5369
|
-
|
|
5370
|
-
|
|
5371
|
-
|
|
5372
|
-
|
|
5373
|
-
|
|
5374
|
-
|
|
5375
|
-
const logPath = milestoneFolder ? path.join(milestoneFolder, "execution.log") : void 0;
|
|
5376
|
-
appendLog(logPath, `
|
|
5768
|
+
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
5769
|
+
const startTime = Date.now();
|
|
5770
|
+
if (registry) {
|
|
5771
|
+
const agent = registry.spawn("execution", actionId, milestoneId);
|
|
5772
|
+
actionAgentIds.set(actionId, agent.id);
|
|
5773
|
+
}
|
|
5774
|
+
const prompt = `Run /declare:execute ${milestoneId} for action ${actionId} only. Do not ask questions, execute autonomously.`;
|
|
5775
|
+
const abortController = new AbortController();
|
|
5776
|
+
activeControllers.set(actionId, abortController);
|
|
5777
|
+
const planningDir = path.join(cwd, ".planning");
|
|
5778
|
+
const milestoneFolder = findMilestoneFolder(planningDir, milestoneId);
|
|
5779
|
+
const logPath = milestoneFolder ? path.join(milestoneFolder, "execution.log") : void 0;
|
|
5780
|
+
appendLog(logPath, `
|
|
5377
5781
|
=== START ${actionId} (pipeline) @ ${startedAt} ===`);
|
|
5378
|
-
|
|
5379
|
-
|
|
5380
|
-
|
|
5381
|
-
|
|
5382
|
-
|
|
5383
|
-
|
|
5384
|
-
|
|
5385
|
-
|
|
5386
|
-
|
|
5387
|
-
|
|
5388
|
-
|
|
5389
|
-
|
|
5390
|
-
|
|
5391
|
-
|
|
5392
|
-
outputBuffers[actionId] = outputBuffers[actionId].slice(-OUTPUT_BUFFER_MAX);
|
|
5393
|
-
}
|
|
5394
|
-
}
|
|
5395
|
-
});
|
|
5782
|
+
return runAI(prompt, {
|
|
5783
|
+
cwd,
|
|
5784
|
+
model: "sonnet",
|
|
5785
|
+
withTools: true,
|
|
5786
|
+
maxTurns: 10,
|
|
5787
|
+
abortController,
|
|
5788
|
+
onText: (chunk) => {
|
|
5789
|
+
broadcast("action-output", { actionId, text: chunk, stream: "stdout" });
|
|
5790
|
+
appendLog(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] [${actionId}] [stdout] ${chunk}`);
|
|
5791
|
+
if (!outputBuffers[actionId]) outputBuffers[actionId] = "";
|
|
5792
|
+
outputBuffers[actionId] += chunk;
|
|
5793
|
+
if (outputBuffers[actionId].length > OUTPUT_BUFFER_MAX) {
|
|
5794
|
+
outputBuffers[actionId] = outputBuffers[actionId].slice(-OUTPUT_BUFFER_MAX);
|
|
5795
|
+
}
|
|
5396
5796
|
}
|
|
5397
|
-
|
|
5398
|
-
|
|
5399
|
-
|
|
5400
|
-
|
|
5401
|
-
|
|
5402
|
-
|
|
5403
|
-
|
|
5404
|
-
|
|
5405
|
-
|
|
5406
|
-
|
|
5407
|
-
|
|
5408
|
-
|
|
5409
|
-
|
|
5410
|
-
|
|
5411
|
-
|
|
5797
|
+
}).then(({ text, error }) => {
|
|
5798
|
+
const exitCode = error ? 1 : 0;
|
|
5799
|
+
const completedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
5800
|
+
const durationMs = Date.now() - startTime;
|
|
5801
|
+
appendLog(logPath, `=== END ${actionId} (pipeline) @ ${completedAt} exit=${exitCode} duration=${durationMs}ms ===
|
|
5802
|
+
`);
|
|
5803
|
+
activeControllers.delete(actionId);
|
|
5804
|
+
broadcast("action-complete", { actionId, exitCode, durationMs });
|
|
5805
|
+
if (registry) {
|
|
5806
|
+
const aId = actionAgentIds.get(actionId);
|
|
5807
|
+
if (aId) {
|
|
5808
|
+
if (exitCode === 0) {
|
|
5809
|
+
registry.complete(aId, {
|
|
5810
|
+
actionId,
|
|
5811
|
+
milestoneId,
|
|
5812
|
+
logPath: logPath || null,
|
|
5813
|
+
durationMs
|
|
5814
|
+
});
|
|
5815
|
+
} else {
|
|
5816
|
+
registry.fail(aId, exitCode, error || "execution failed");
|
|
5412
5817
|
}
|
|
5413
|
-
|
|
5818
|
+
actionAgentIds.delete(actionId);
|
|
5819
|
+
}
|
|
5414
5820
|
}
|
|
5415
|
-
|
|
5416
|
-
|
|
5417
|
-
|
|
5418
|
-
|
|
5419
|
-
|
|
5420
|
-
`);
|
|
5421
|
-
activeProcesses.delete(actionId);
|
|
5422
|
-
broadcast("action-complete", { actionId, exitCode: code, durationMs });
|
|
5423
|
-
resolve({ actionId, milestoneId, exitCode: code, stderrOutput: stderrFull, durationMs, startedAt, completedAt, retried: false, attempts: 1 });
|
|
5424
|
-
});
|
|
5425
|
-
proc.on("error", (_err) => {
|
|
5426
|
-
const completedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
5427
|
-
const durationMs = Date.now() - startTime;
|
|
5428
|
-
appendLog(logPath, `=== ERROR ${actionId} (pipeline) @ ${completedAt} ===
|
|
5821
|
+
return { actionId, milestoneId, exitCode, errorOutput: error || "", durationMs, startedAt, completedAt, retried: false, attempts: 1 };
|
|
5822
|
+
}).catch((err) => {
|
|
5823
|
+
const completedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
5824
|
+
const durationMs = Date.now() - startTime;
|
|
5825
|
+
appendLog(logPath, `=== ERROR ${actionId} (pipeline) @ ${completedAt} ===
|
|
5429
5826
|
`);
|
|
5430
|
-
|
|
5431
|
-
|
|
5432
|
-
|
|
5433
|
-
|
|
5827
|
+
activeControllers.delete(actionId);
|
|
5828
|
+
broadcast("action-complete", { actionId, exitCode: -1, durationMs });
|
|
5829
|
+
if (registry) {
|
|
5830
|
+
const aId = actionAgentIds.get(actionId);
|
|
5831
|
+
if (aId) {
|
|
5832
|
+
registry.fail(aId, -1, String(err.message || err));
|
|
5833
|
+
actionAgentIds.delete(actionId);
|
|
5834
|
+
}
|
|
5835
|
+
}
|
|
5836
|
+
return { actionId, milestoneId, exitCode: -1, errorOutput: String(err.message || err), durationMs, startedAt, completedAt, retried: false, attempts: 1 };
|
|
5434
5837
|
});
|
|
5435
5838
|
}
|
|
5436
5839
|
function start() {
|
|
@@ -5445,6 +5848,10 @@ data: ${JSON.stringify(data)}
|
|
|
5445
5848
|
isRunning = true;
|
|
5446
5849
|
stopRequested = false;
|
|
5447
5850
|
results = [];
|
|
5851
|
+
if (registry) {
|
|
5852
|
+
const agent = registry.spawn("pipeline", "manifest", "");
|
|
5853
|
+
pipelineAgentId = agent.id;
|
|
5854
|
+
}
|
|
5448
5855
|
pipelineState = {
|
|
5449
5856
|
currentWave: 0,
|
|
5450
5857
|
totalWaves: waves.length,
|
|
@@ -5497,7 +5904,7 @@ data: ${JSON.stringify(data)}
|
|
|
5497
5904
|
const waveResults = await Promise.all(promises);
|
|
5498
5905
|
for (let ri = 0; ri < waveResults.length; ri++) {
|
|
5499
5906
|
const r = waveResults[ri];
|
|
5500
|
-
if (r.exitCode !== 0 && !stopRequested && isTransientFailure(r.exitCode, r.
|
|
5907
|
+
if (r.exitCode !== 0 && !stopRequested && isTransientFailure(r.exitCode, r.errorOutput)) {
|
|
5501
5908
|
broadcast("action-retry", { actionId: r.actionId, milestoneId: r.milestoneId, attempt: 2, reason: "transient failure detected" });
|
|
5502
5909
|
appendLog(
|
|
5503
5910
|
findMilestoneFolder(path.join(cwd, ".planning"), r.milestoneId) ? path.join(findMilestoneFolder(path.join(cwd, ".planning"), r.milestoneId), "execution.log") : void 0,
|
|
@@ -5557,7 +5964,7 @@ data: ${JSON.stringify(data)}
|
|
|
5557
5964
|
const finalResults = [...results];
|
|
5558
5965
|
isRunning = false;
|
|
5559
5966
|
if (stopRequested) {
|
|
5560
|
-
for (const actionId of
|
|
5967
|
+
for (const actionId of activeControllers.keys()) {
|
|
5561
5968
|
finalState.stoppedActions.push(actionId);
|
|
5562
5969
|
}
|
|
5563
5970
|
}
|
|
@@ -5572,6 +5979,19 @@ data: ${JSON.stringify(data)}
|
|
|
5572
5979
|
results: finalResults,
|
|
5573
5980
|
reportPath: reportPath ? ".planning/execution-report.md" : null
|
|
5574
5981
|
});
|
|
5982
|
+
if (registry && pipelineAgentId) {
|
|
5983
|
+
const failed = finalState.failedActions.length;
|
|
5984
|
+
if (failed === 0 && !stopRequested) {
|
|
5985
|
+
registry.complete(pipelineAgentId, {
|
|
5986
|
+
completed: finalState.completedActions.length,
|
|
5987
|
+
failed: 0,
|
|
5988
|
+
reportPath: reportPath ? ".planning/execution-report.md" : null
|
|
5989
|
+
});
|
|
5990
|
+
} else {
|
|
5991
|
+
registry.fail(pipelineAgentId, 1, stopRequested ? "stopped by user" : `${failed} action(s) failed`);
|
|
5992
|
+
}
|
|
5993
|
+
pipelineAgentId = void 0;
|
|
5994
|
+
}
|
|
5575
5995
|
stopRequested = false;
|
|
5576
5996
|
pausedOnFailure = null;
|
|
5577
5997
|
pipelineState = null;
|
|
@@ -5581,6 +6001,10 @@ data: ${JSON.stringify(data)}
|
|
|
5581
6001
|
pausedOnFailure = null;
|
|
5582
6002
|
pipelineState = null;
|
|
5583
6003
|
persistState();
|
|
6004
|
+
if (registry && pipelineAgentId) {
|
|
6005
|
+
registry.fail(pipelineAgentId, -1, String(_err));
|
|
6006
|
+
pipelineAgentId = void 0;
|
|
6007
|
+
}
|
|
5584
6008
|
broadcast("pipeline-complete", {
|
|
5585
6009
|
completed: [],
|
|
5586
6010
|
failed: [],
|
|
@@ -5600,11 +6024,18 @@ data: ${JSON.stringify(data)}
|
|
|
5600
6024
|
skipResolve("stop");
|
|
5601
6025
|
skipResolve = null;
|
|
5602
6026
|
}
|
|
5603
|
-
for (const [,
|
|
6027
|
+
for (const [actionId, controller] of activeControllers) {
|
|
5604
6028
|
try {
|
|
5605
|
-
|
|
6029
|
+
controller.abort();
|
|
5606
6030
|
} catch (_) {
|
|
5607
6031
|
}
|
|
6032
|
+
if (registry) {
|
|
6033
|
+
const aId = actionAgentIds.get(actionId);
|
|
6034
|
+
if (aId) {
|
|
6035
|
+
registry.fail(aId, -1, "stopped by user");
|
|
6036
|
+
actionAgentIds.delete(actionId);
|
|
6037
|
+
}
|
|
6038
|
+
}
|
|
5608
6039
|
}
|
|
5609
6040
|
persistState();
|
|
5610
6041
|
return { ok: true };
|
|
@@ -5618,7 +6049,7 @@ data: ${JSON.stringify(data)}
|
|
|
5618
6049
|
running: isRunning,
|
|
5619
6050
|
currentWave: pipelineState.currentWave,
|
|
5620
6051
|
totalWaves: pipelineState.totalWaves,
|
|
5621
|
-
activeActions: [...
|
|
6052
|
+
activeActions: [...activeControllers.keys()],
|
|
5622
6053
|
completedActions: pipelineState.completedActions,
|
|
5623
6054
|
failedActions: pipelineState.failedActions,
|
|
5624
6055
|
results
|
|
@@ -5646,7 +6077,7 @@ data: ${JSON.stringify(data)}
|
|
|
5646
6077
|
totalActions: totalActionCount,
|
|
5647
6078
|
completedActions: pipelineState ? pipelineState.completedActions : [],
|
|
5648
6079
|
failedActions: pipelineState ? pipelineState.failedActions : [],
|
|
5649
|
-
activeActions: [...
|
|
6080
|
+
activeActions: [...activeControllers.keys()],
|
|
5650
6081
|
outputBuffers,
|
|
5651
6082
|
pausedOnFailure
|
|
5652
6083
|
};
|
|
@@ -5657,6 +6088,201 @@ data: ${JSON.stringify(data)}
|
|
|
5657
6088
|
}
|
|
5658
6089
|
});
|
|
5659
6090
|
|
|
6091
|
+
// src/server/agent-registry.js
|
|
6092
|
+
var require_agent_registry = __commonJS({
|
|
6093
|
+
"src/server/agent-registry.js"(exports2, module2) {
|
|
6094
|
+
"use strict";
|
|
6095
|
+
var fs = require("node:fs");
|
|
6096
|
+
var path = require("node:path");
|
|
6097
|
+
var RECENT_MAX = 50;
|
|
6098
|
+
var RECENT_MAX_AGE_MS = 30 * 60 * 1e3;
|
|
6099
|
+
function createAgentRegistry(cwd, broadcastFn) {
|
|
6100
|
+
const agents = /* @__PURE__ */ new Map();
|
|
6101
|
+
let recentAgents = [];
|
|
6102
|
+
const statePath = path.join(cwd, ".planning", "agent-state.json");
|
|
6103
|
+
function pruneRecent() {
|
|
6104
|
+
const cutoff = Date.now() - RECENT_MAX_AGE_MS;
|
|
6105
|
+
recentAgents = recentAgents.filter((a) => {
|
|
6106
|
+
const ts = a.completedAt || a.updatedAt;
|
|
6107
|
+
return new Date(ts).getTime() > cutoff;
|
|
6108
|
+
});
|
|
6109
|
+
}
|
|
6110
|
+
function moveToRecent(agentId) {
|
|
6111
|
+
const record = agents.get(agentId);
|
|
6112
|
+
if (!record) return;
|
|
6113
|
+
agents.delete(agentId);
|
|
6114
|
+
recentAgents.push(record);
|
|
6115
|
+
if (recentAgents.length > RECENT_MAX) {
|
|
6116
|
+
recentAgents = recentAgents.slice(recentAgents.length - RECENT_MAX);
|
|
6117
|
+
}
|
|
6118
|
+
}
|
|
6119
|
+
function persistState() {
|
|
6120
|
+
try {
|
|
6121
|
+
const state = {
|
|
6122
|
+
agents: Object.fromEntries(agents),
|
|
6123
|
+
recentAgents,
|
|
6124
|
+
persistedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
6125
|
+
};
|
|
6126
|
+
fs.writeFileSync(statePath, JSON.stringify(state, null, 2), "utf-8");
|
|
6127
|
+
} catch (_) {
|
|
6128
|
+
}
|
|
6129
|
+
}
|
|
6130
|
+
function spawn(type, target, milestoneId) {
|
|
6131
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
6132
|
+
const id = `${type.slice(0, 4)}-${target}-${Date.now()}`;
|
|
6133
|
+
const record = {
|
|
6134
|
+
id,
|
|
6135
|
+
type,
|
|
6136
|
+
target,
|
|
6137
|
+
milestoneId: milestoneId || "",
|
|
6138
|
+
status: "running",
|
|
6139
|
+
startedAt: now,
|
|
6140
|
+
updatedAt: now,
|
|
6141
|
+
completedAt: null,
|
|
6142
|
+
exitCode: null,
|
|
6143
|
+
error: null,
|
|
6144
|
+
result: null
|
|
6145
|
+
};
|
|
6146
|
+
agents.set(id, record);
|
|
6147
|
+
broadcastFn("agent-start", record);
|
|
6148
|
+
persistState();
|
|
6149
|
+
return record;
|
|
6150
|
+
}
|
|
6151
|
+
function update(agentId, patch) {
|
|
6152
|
+
const record = agents.get(agentId);
|
|
6153
|
+
if (!record) return null;
|
|
6154
|
+
Object.assign(record, patch, { updatedAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
6155
|
+
broadcastFn("agent-update", record);
|
|
6156
|
+
persistState();
|
|
6157
|
+
return record;
|
|
6158
|
+
}
|
|
6159
|
+
function complete(agentId, result) {
|
|
6160
|
+
const record = agents.get(agentId);
|
|
6161
|
+
if (!record) return null;
|
|
6162
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
6163
|
+
record.status = "complete";
|
|
6164
|
+
record.completedAt = now;
|
|
6165
|
+
record.updatedAt = now;
|
|
6166
|
+
record.exitCode = 0;
|
|
6167
|
+
record.result = result;
|
|
6168
|
+
moveToRecent(agentId);
|
|
6169
|
+
broadcastFn("agent-complete", record);
|
|
6170
|
+
persistState();
|
|
6171
|
+
return record;
|
|
6172
|
+
}
|
|
6173
|
+
function fail(agentId, exitCode, errorMessage) {
|
|
6174
|
+
const record = agents.get(agentId);
|
|
6175
|
+
if (!record) return null;
|
|
6176
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
6177
|
+
record.status = "failed";
|
|
6178
|
+
record.completedAt = now;
|
|
6179
|
+
record.updatedAt = now;
|
|
6180
|
+
record.exitCode = exitCode;
|
|
6181
|
+
record.error = errorMessage;
|
|
6182
|
+
moveToRecent(agentId);
|
|
6183
|
+
broadcastFn("agent-complete", record);
|
|
6184
|
+
persistState();
|
|
6185
|
+
return record;
|
|
6186
|
+
}
|
|
6187
|
+
function get(agentId) {
|
|
6188
|
+
const active = agents.get(agentId);
|
|
6189
|
+
if (active) return active;
|
|
6190
|
+
return recentAgents.find((a) => a.id === agentId) || null;
|
|
6191
|
+
}
|
|
6192
|
+
function getActive() {
|
|
6193
|
+
return [...agents.values()];
|
|
6194
|
+
}
|
|
6195
|
+
function getRecent(limit = 20) {
|
|
6196
|
+
pruneRecent();
|
|
6197
|
+
return recentAgents.slice(-limit);
|
|
6198
|
+
}
|
|
6199
|
+
function getAll() {
|
|
6200
|
+
return { active: getActive(), recent: getRecent() };
|
|
6201
|
+
}
|
|
6202
|
+
function markInterrupted(agentIds) {
|
|
6203
|
+
for (const id of agentIds) {
|
|
6204
|
+
const record = agents.get(id);
|
|
6205
|
+
if (!record) continue;
|
|
6206
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
6207
|
+
record.status = "interrupted";
|
|
6208
|
+
record.completedAt = now;
|
|
6209
|
+
record.updatedAt = now;
|
|
6210
|
+
moveToRecent(id);
|
|
6211
|
+
}
|
|
6212
|
+
persistState();
|
|
6213
|
+
}
|
|
6214
|
+
function loadFromDisk() {
|
|
6215
|
+
try {
|
|
6216
|
+
const raw = fs.readFileSync(statePath, "utf-8");
|
|
6217
|
+
return JSON.parse(raw);
|
|
6218
|
+
} catch (_) {
|
|
6219
|
+
return null;
|
|
6220
|
+
}
|
|
6221
|
+
}
|
|
6222
|
+
function restoreFromDisk() {
|
|
6223
|
+
const persisted = loadFromDisk();
|
|
6224
|
+
if (!persisted) return { restored: 0, interrupted: 0 };
|
|
6225
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
6226
|
+
let interruptedCount = 0;
|
|
6227
|
+
const seenIds = new Set(recentAgents.map((a) => a.id));
|
|
6228
|
+
const persistedAgents = persisted.agents || {};
|
|
6229
|
+
for (const [id, record] of Object.entries(persistedAgents)) {
|
|
6230
|
+
if (seenIds.has(id)) continue;
|
|
6231
|
+
record.status = "interrupted";
|
|
6232
|
+
record.completedAt = now;
|
|
6233
|
+
record.updatedAt = now;
|
|
6234
|
+
record.exitCode = -1;
|
|
6235
|
+
record.error = "server restarted";
|
|
6236
|
+
recentAgents.push(record);
|
|
6237
|
+
seenIds.add(id);
|
|
6238
|
+
interruptedCount++;
|
|
6239
|
+
}
|
|
6240
|
+
const cutoff = Date.now() - RECENT_MAX_AGE_MS;
|
|
6241
|
+
const persistedRecent = persisted.recentAgents || [];
|
|
6242
|
+
for (const record of persistedRecent) {
|
|
6243
|
+
if (seenIds.has(record.id)) continue;
|
|
6244
|
+
const ts = record.completedAt || record.updatedAt;
|
|
6245
|
+
if (new Date(ts).getTime() <= cutoff) continue;
|
|
6246
|
+
recentAgents.push(record);
|
|
6247
|
+
seenIds.add(record.id);
|
|
6248
|
+
}
|
|
6249
|
+
if (recentAgents.length > RECENT_MAX) {
|
|
6250
|
+
recentAgents = recentAgents.slice(recentAgents.length - RECENT_MAX);
|
|
6251
|
+
}
|
|
6252
|
+
persistState();
|
|
6253
|
+
return { restored: recentAgents.length, interrupted: interruptedCount };
|
|
6254
|
+
}
|
|
6255
|
+
return {
|
|
6256
|
+
spawn,
|
|
6257
|
+
update,
|
|
6258
|
+
complete,
|
|
6259
|
+
fail,
|
|
6260
|
+
get,
|
|
6261
|
+
getActive,
|
|
6262
|
+
getRecent,
|
|
6263
|
+
getAll,
|
|
6264
|
+
markInterrupted,
|
|
6265
|
+
loadFromDisk,
|
|
6266
|
+
restoreFromDisk
|
|
6267
|
+
};
|
|
6268
|
+
}
|
|
6269
|
+
if (require.main === module2) {
|
|
6270
|
+
const reg = createAgentRegistry(".", () => {
|
|
6271
|
+
});
|
|
6272
|
+
const a = reg.spawn("execution", "A-01", "M-01");
|
|
6273
|
+
console.log("spawned:", a.id, a.status);
|
|
6274
|
+
reg.complete(a.id, { path: "test.md" });
|
|
6275
|
+
console.log("completed:", reg.get(a.id).status);
|
|
6276
|
+
console.log("active:", reg.getActive().length);
|
|
6277
|
+
console.log("recent:", reg.getRecent().length);
|
|
6278
|
+
const restored = reg.restoreFromDisk();
|
|
6279
|
+
console.log("restored:", restored.restored, "interrupted:", restored.interrupted);
|
|
6280
|
+
console.log("OK");
|
|
6281
|
+
}
|
|
6282
|
+
module2.exports = { createAgentRegistry };
|
|
6283
|
+
}
|
|
6284
|
+
});
|
|
6285
|
+
|
|
5660
6286
|
// src/server/index.js
|
|
5661
6287
|
var require_server = __commonJS({
|
|
5662
6288
|
"src/server/index.js"(exports2, module2) {
|
|
@@ -5685,7 +6311,9 @@ var require_server = __commonJS({
|
|
|
5685
6311
|
var { computeWorkflowState } = require_workflow_state();
|
|
5686
6312
|
var { createPlayRunner } = require_play();
|
|
5687
6313
|
var { computeReadiness } = require_readiness();
|
|
6314
|
+
var { computeLifecycleStages } = require_lifecycle_stages();
|
|
5688
6315
|
var { createPipelineRunner } = require_pipeline_runner();
|
|
6316
|
+
var { createAgentRegistry } = require_agent_registry();
|
|
5689
6317
|
var MIME_TYPES = {
|
|
5690
6318
|
".html": "text/html; charset=utf-8",
|
|
5691
6319
|
".js": "application/javascript; charset=utf-8",
|
|
@@ -5867,6 +6495,21 @@ var require_server = __commonJS({
|
|
|
5867
6495
|
sendJson(res, 500, { error: String(err) });
|
|
5868
6496
|
}
|
|
5869
6497
|
}
|
|
6498
|
+
function handleLifecycle(res, cwd) {
|
|
6499
|
+
try {
|
|
6500
|
+
const graph = runLoadGraph2(cwd);
|
|
6501
|
+
if ("error" in graph) {
|
|
6502
|
+
sendJson(res, 500, { error: graph.error });
|
|
6503
|
+
return;
|
|
6504
|
+
}
|
|
6505
|
+
const pm = getProcessManager(cwd);
|
|
6506
|
+
const runningIds = new Set(pm.running());
|
|
6507
|
+
const result = computeLifecycleStages(graph, runningIds);
|
|
6508
|
+
sendJson(res, 200, result);
|
|
6509
|
+
} catch (err) {
|
|
6510
|
+
sendJson(res, 500, { error: String(err) });
|
|
6511
|
+
}
|
|
6512
|
+
}
|
|
5870
6513
|
async function handleSaveManifest(req, res, cwd) {
|
|
5871
6514
|
try {
|
|
5872
6515
|
const body = await readJsonBody(req);
|
|
@@ -5977,25 +6620,38 @@ var require_server = __commonJS({
|
|
|
5977
6620
|
}
|
|
5978
6621
|
function handleExecuteAction(res, cwd, actionId) {
|
|
5979
6622
|
try {
|
|
5980
|
-
const
|
|
5981
|
-
if (
|
|
5982
|
-
sendJson(res,
|
|
6623
|
+
const graph = runLoadGraph2(cwd);
|
|
6624
|
+
if ("error" in graph) {
|
|
6625
|
+
sendJson(res, 500, { error: graph.error });
|
|
5983
6626
|
return;
|
|
5984
6627
|
}
|
|
5985
|
-
const
|
|
5986
|
-
|
|
5987
|
-
|
|
5988
|
-
|
|
5989
|
-
|
|
5990
|
-
|
|
5991
|
-
|
|
5992
|
-
|
|
5993
|
-
|
|
5994
|
-
|
|
5995
|
-
}
|
|
6628
|
+
const normalizedId = actionId.toUpperCase();
|
|
6629
|
+
const action = graph.actions.find((a) => a.id.toUpperCase() === normalizedId);
|
|
6630
|
+
if (!action) {
|
|
6631
|
+
sendJson(res, 404, { error: "Action not found" });
|
|
6632
|
+
return;
|
|
6633
|
+
}
|
|
6634
|
+
if (action.reviewState !== "approved") {
|
|
6635
|
+
sendJson(res, 403, {
|
|
6636
|
+
error: "Action not approved for execution",
|
|
6637
|
+
unapproved: [{ id: action.id, title: action.title, reviewState: action.reviewState || "draft" }]
|
|
6638
|
+
});
|
|
6639
|
+
return;
|
|
5996
6640
|
}
|
|
6641
|
+
const milestoneId = (action.causes || []).find((c) => c.startsWith("M-")) || (action.causes || [])[0] || actionId;
|
|
6642
|
+
const milestone = graph.milestones.find((m) => m.id === milestoneId);
|
|
6643
|
+
const declaration = milestone ? (graph.declarations || []).find((d) => (milestone.realizes || []).includes(d.id)) : null;
|
|
6644
|
+
const siblingActions = graph.actions.filter(
|
|
6645
|
+
(a) => a.id !== action.id && (a.causes || []).includes(milestoneId)
|
|
6646
|
+
);
|
|
6647
|
+
const actionContext = {
|
|
6648
|
+
action: { id: action.id, title: action.title, produces: action.produces || "", status: action.status },
|
|
6649
|
+
milestone: milestone ? { id: milestone.id, title: milestone.title, description: milestone.description || "" } : null,
|
|
6650
|
+
declaration: declaration ? { id: declaration.id, statement: declaration.statement || declaration.title || "" } : null,
|
|
6651
|
+
siblingActions: siblingActions.map((a) => ({ id: a.id, title: a.title, produces: a.produces || "", status: a.status }))
|
|
6652
|
+
};
|
|
5997
6653
|
const pm = getProcessManager(cwd);
|
|
5998
|
-
const execResult = pm.execute(actionId,
|
|
6654
|
+
const execResult = pm.execute(actionId, milestoneId, actionContext);
|
|
5999
6655
|
if (execResult.error) {
|
|
6000
6656
|
sendJson(res, execResult.status || 500, { error: execResult.error });
|
|
6001
6657
|
return;
|
|
@@ -6019,24 +6675,35 @@ var require_server = __commonJS({
|
|
|
6019
6675
|
milestones: d.milestones || []
|
|
6020
6676
|
}));
|
|
6021
6677
|
const dr = getDerivationRunner(cwd);
|
|
6022
|
-
const
|
|
6678
|
+
const declarationId = body.declarationId || null;
|
|
6679
|
+
const result = dr.derive(declarationId, declarations);
|
|
6023
6680
|
if (result.error) {
|
|
6024
6681
|
sendJson(res, result.status || 500, { error: result.error });
|
|
6025
6682
|
return;
|
|
6026
6683
|
}
|
|
6027
|
-
sendJson(res, 202, { ok: true, sessionId: result.sessionId });
|
|
6684
|
+
sendJson(res, 202, { ok: true, sessionId: result.sessionId, declarationId });
|
|
6028
6685
|
} catch (err) {
|
|
6029
6686
|
sendJson(res, 400, { error: String(err) });
|
|
6030
6687
|
}
|
|
6031
6688
|
}
|
|
6032
|
-
function handleDeriveStop(res, cwd) {
|
|
6689
|
+
function handleDeriveStop(req, res, cwd) {
|
|
6033
6690
|
const dr = getDerivationRunner(cwd);
|
|
6034
|
-
|
|
6035
|
-
|
|
6036
|
-
|
|
6037
|
-
|
|
6038
|
-
|
|
6039
|
-
|
|
6691
|
+
let sessionId;
|
|
6692
|
+
const chunks = [];
|
|
6693
|
+
req.on("data", (c) => chunks.push(c));
|
|
6694
|
+
req.on("end", () => {
|
|
6695
|
+
try {
|
|
6696
|
+
const body = JSON.parse(Buffer.concat(chunks).toString("utf-8"));
|
|
6697
|
+
sessionId = body.sessionId;
|
|
6698
|
+
} catch (_) {
|
|
6699
|
+
}
|
|
6700
|
+
const result = dr.stop(sessionId);
|
|
6701
|
+
if (result.error) {
|
|
6702
|
+
sendJson(res, result.status || 500, { error: result.error });
|
|
6703
|
+
} else {
|
|
6704
|
+
sendJson(res, 200, { ok: true });
|
|
6705
|
+
}
|
|
6706
|
+
});
|
|
6040
6707
|
}
|
|
6041
6708
|
async function handleDeriveAccept(req, res, cwd) {
|
|
6042
6709
|
try {
|
|
@@ -6056,6 +6723,36 @@ var require_server = __commonJS({
|
|
|
6056
6723
|
sendJson(res, 400, { error: String(err) });
|
|
6057
6724
|
}
|
|
6058
6725
|
}
|
|
6726
|
+
function handleDeriveAll(res, cwd) {
|
|
6727
|
+
try {
|
|
6728
|
+
const graph = runLoadGraph2(cwd);
|
|
6729
|
+
if ("error" in graph) {
|
|
6730
|
+
sendJson(res, 500, { error: graph.error });
|
|
6731
|
+
return;
|
|
6732
|
+
}
|
|
6733
|
+
const declarations = graph.declarations.map((d) => ({
|
|
6734
|
+
id: d.id,
|
|
6735
|
+
statement: d.statement,
|
|
6736
|
+
milestones: d.milestones || []
|
|
6737
|
+
}));
|
|
6738
|
+
const needsPlanning = declarations.filter((d) => !d.milestones || d.milestones.length === 0);
|
|
6739
|
+
if (needsPlanning.length === 0) {
|
|
6740
|
+
sendJson(res, 200, { ok: true, sessions: [], message: "All declarations already have milestones" });
|
|
6741
|
+
return;
|
|
6742
|
+
}
|
|
6743
|
+
const dr = getDerivationRunner(cwd);
|
|
6744
|
+
const results = [];
|
|
6745
|
+
for (const decl of needsPlanning) {
|
|
6746
|
+
const result = dr.derive(decl.id, declarations);
|
|
6747
|
+
if (result.ok) {
|
|
6748
|
+
results.push({ declarationId: decl.id, sessionId: result.sessionId });
|
|
6749
|
+
}
|
|
6750
|
+
}
|
|
6751
|
+
sendJson(res, 202, { ok: true, sessions: results });
|
|
6752
|
+
} catch (err) {
|
|
6753
|
+
sendJson(res, 500, { error: String(err) });
|
|
6754
|
+
}
|
|
6755
|
+
}
|
|
6059
6756
|
function handleActionDerive(res, cwd, milestoneId) {
|
|
6060
6757
|
try {
|
|
6061
6758
|
const graph = runLoadGraph2(cwd);
|
|
@@ -6360,19 +7057,38 @@ var require_server = __commonJS({
|
|
|
6360
7057
|
}
|
|
6361
7058
|
}
|
|
6362
7059
|
var sseClients = /* @__PURE__ */ new Set();
|
|
7060
|
+
var agentRegistry = null;
|
|
7061
|
+
function getAgentRegistry(cwd) {
|
|
7062
|
+
if (!agentRegistry) {
|
|
7063
|
+
agentRegistry = createAgentRegistry(cwd, (event, data) => {
|
|
7064
|
+
const payload = `event: ${event}
|
|
7065
|
+
data: ${JSON.stringify(data)}
|
|
7066
|
+
|
|
7067
|
+
`;
|
|
7068
|
+
for (const client of sseClients) {
|
|
7069
|
+
try {
|
|
7070
|
+
client.write(payload);
|
|
7071
|
+
} catch (_) {
|
|
7072
|
+
sseClients.delete(client);
|
|
7073
|
+
}
|
|
7074
|
+
}
|
|
7075
|
+
});
|
|
7076
|
+
}
|
|
7077
|
+
return agentRegistry;
|
|
7078
|
+
}
|
|
6363
7079
|
var processManager = null;
|
|
6364
7080
|
function getProcessManager(cwd) {
|
|
6365
|
-
if (!processManager) processManager = createProcessManager(sseClients, cwd);
|
|
7081
|
+
if (!processManager) processManager = createProcessManager(sseClients, cwd, getAgentRegistry(cwd));
|
|
6366
7082
|
return processManager;
|
|
6367
7083
|
}
|
|
6368
7084
|
var derivationRunner = null;
|
|
6369
7085
|
function getDerivationRunner(cwd) {
|
|
6370
|
-
if (!derivationRunner) derivationRunner = createDerivationRunner(sseClients, cwd);
|
|
7086
|
+
if (!derivationRunner) derivationRunner = createDerivationRunner(sseClients, cwd, getAgentRegistry(cwd));
|
|
6371
7087
|
return derivationRunner;
|
|
6372
7088
|
}
|
|
6373
7089
|
var actionDerivationRunner = null;
|
|
6374
7090
|
function getActionDerivationRunner(cwd) {
|
|
6375
|
-
if (!actionDerivationRunner) actionDerivationRunner = createActionDerivationRunner(sseClients, cwd);
|
|
7091
|
+
if (!actionDerivationRunner) actionDerivationRunner = createActionDerivationRunner(sseClients, cwd, getAgentRegistry(cwd));
|
|
6376
7092
|
return actionDerivationRunner;
|
|
6377
7093
|
}
|
|
6378
7094
|
var playRunner = null;
|
|
@@ -6382,7 +7098,7 @@ var require_server = __commonJS({
|
|
|
6382
7098
|
}
|
|
6383
7099
|
var pipelineRunnerInstance = null;
|
|
6384
7100
|
function getPipelineRunner(cwd) {
|
|
6385
|
-
if (!pipelineRunnerInstance) pipelineRunnerInstance = createPipelineRunner(sseClients, cwd);
|
|
7101
|
+
if (!pipelineRunnerInstance) pipelineRunnerInstance = createPipelineRunner(sseClients, cwd, getAgentRegistry(cwd));
|
|
6386
7102
|
return pipelineRunnerInstance;
|
|
6387
7103
|
}
|
|
6388
7104
|
var revisionRunner = null;
|
|
@@ -6391,7 +7107,7 @@ var require_server = __commonJS({
|
|
|
6391
7107
|
revisionRunner = createRevisionRunner(sseClients, cwd, (nodeId) => {
|
|
6392
7108
|
setReviewState(cwd, nodeId, "in_review");
|
|
6393
7109
|
broadcastChange();
|
|
6394
|
-
});
|
|
7110
|
+
}, getAgentRegistry(cwd));
|
|
6395
7111
|
}
|
|
6396
7112
|
return revisionRunner;
|
|
6397
7113
|
}
|
|
@@ -6546,7 +7262,7 @@ var require_server = __commonJS({
|
|
|
6546
7262
|
sendJson(res, 200, { ok: true });
|
|
6547
7263
|
}
|
|
6548
7264
|
}
|
|
6549
|
-
var
|
|
7265
|
+
var refineSessions = /* @__PURE__ */ new Map();
|
|
6550
7266
|
function buildRefinePrompt(nodeId, graph, mode, userMessage) {
|
|
6551
7267
|
const id = nodeId.toUpperCase();
|
|
6552
7268
|
const prefix = id.split("-")[0];
|
|
@@ -6586,99 +7302,772 @@ Produces: ${m.produces || "(none)"}`).join("\n\n");
|
|
|
6586
7302
|
const siblingActions = graph.actions.filter((a) => a.id !== action.id && (a.causes || []).some((c) => milestoneIds.includes(c)));
|
|
6587
7303
|
siblingsContent = siblingActions.map((a) => `${a.id}: ${a.title} [${a.status}]`).join("\n");
|
|
6588
7304
|
}
|
|
6589
|
-
const modeInstructions = {
|
|
6590
|
-
write: `The user wants to
|
|
6591
|
-
|
|
6592
|
-
"${userMessage || ""}"
|
|
6593
|
-
|
|
6594
|
-
|
|
6595
|
-
|
|
6596
|
-
-
|
|
6597
|
-
-
|
|
6598
|
-
|
|
6599
|
-
|
|
6600
|
-
|
|
6601
|
-
-
|
|
6602
|
-
-
|
|
6603
|
-
|
|
6604
|
-
|
|
6605
|
-
-
|
|
6606
|
-
-
|
|
6607
|
-
|
|
6608
|
-
|
|
6609
|
-
|
|
6610
|
-
- Could
|
|
6611
|
-
-
|
|
6612
|
-
|
|
6613
|
-
|
|
6614
|
-
|
|
6615
|
-
|
|
6616
|
-
|
|
6617
|
-
-
|
|
6618
|
-
|
|
6619
|
-
|
|
6620
|
-
|
|
6621
|
-
|
|
6622
|
-
|
|
6623
|
-
|
|
6624
|
-
|
|
6625
|
-
|
|
6626
|
-
|
|
6627
|
-
|
|
6628
|
-
|
|
6629
|
-
|
|
6630
|
-
|
|
6631
|
-
|
|
7305
|
+
const modeInstructions = {
|
|
7306
|
+
write: `The user wants to modify this ${nodeType} with specific direction:
|
|
7307
|
+
|
|
7308
|
+
"${userMessage || ""}"
|
|
7309
|
+
|
|
7310
|
+
You have tool access \u2014 directly edit the project files to apply the requested changes. The planning files are at:
|
|
7311
|
+
- ${nodeType === "declaration" ? "FUTURE.md" : nodeType === "milestone" ? "MILESTONES.md" : "the milestone PLAN.md"}
|
|
7312
|
+
- MILESTONES.md (for milestone references)
|
|
7313
|
+
- FUTURE-ARCHIVE.md (if archiving)
|
|
7314
|
+
|
|
7315
|
+
Apply the changes directly. After making edits, summarize what you changed.`,
|
|
7316
|
+
outdated: `Check if this ${nodeType} is still relevant given the current state of its parent and siblings. Some siblings may already be DONE. Consider:
|
|
7317
|
+
- Has progress on siblings made this redundant?
|
|
7318
|
+
- Does the title/statement still accurately describe what's needed?
|
|
7319
|
+
- Should this be reworded to reflect current reality?`,
|
|
7320
|
+
sharpen: `Make this ${nodeType} more specific and actionable. Consider:
|
|
7321
|
+
- Is the title vague or generic? Make it concrete.
|
|
7322
|
+
- Does the statement/produces describe a clear, verifiable outcome?
|
|
7323
|
+
- Could someone read this and know exactly what "done" looks like?`,
|
|
7324
|
+
consolidate: `Check if this ${nodeType} overlaps with its siblings. Consider:
|
|
7325
|
+
- Are any siblings covering the same ground?
|
|
7326
|
+
- Could this be merged with a sibling for clarity?
|
|
7327
|
+
- Is this a subset of another sibling?
|
|
7328
|
+
|
|
7329
|
+
If overlap exists, suggest how to differentiate or merge. If no overlap, say so.`,
|
|
7330
|
+
expand: `This ${nodeType} may be too broad. Consider:
|
|
7331
|
+
- Could it be broken into 2-3 more specific items?
|
|
7332
|
+
- Are there implicit sub-tasks hiding in the description?
|
|
7333
|
+
- Would splitting improve clarity and trackability?
|
|
7334
|
+
|
|
7335
|
+
Suggest a breakdown if warranted. If it's already well-scoped, say so.`
|
|
7336
|
+
};
|
|
7337
|
+
const taskBlock = modeInstructions[mode] || `Analyze this ${nodeType} in the context of its parent and siblings. Consider:
|
|
7338
|
+
- Is it well-scoped? Too broad or too narrow compared to siblings?
|
|
7339
|
+
- Does it overlap with any sibling?
|
|
7340
|
+
- Does it clearly contribute to its parent's goal?
|
|
7341
|
+
- Is the title/statement clear and specific?`;
|
|
7342
|
+
return `You are reviewing a Declare project artifact and suggesting improvements.
|
|
7343
|
+
|
|
7344
|
+
## This ${nodeType}
|
|
7345
|
+
|
|
7346
|
+
${nodeContent}
|
|
7347
|
+
|
|
7348
|
+
## Parent context
|
|
7349
|
+
|
|
7350
|
+
${parentContent}
|
|
7351
|
+
|
|
7352
|
+
## Sibling ${nodeType === "declaration" ? "declarations" : nodeType === "milestone" ? "milestones" : "actions"}
|
|
7353
|
+
|
|
7354
|
+
${siblingsContent || "(none)"}
|
|
7355
|
+
|
|
7356
|
+
## Task
|
|
7357
|
+
|
|
7358
|
+
${taskBlock}
|
|
7359
|
+
|
|
7360
|
+
If improvements are possible, suggest a revised version. Output ONLY the improved text in this format:
|
|
7361
|
+
|
|
7362
|
+
**Title:** <improved title>
|
|
7363
|
+
**Statement:** <improved statement or produces>
|
|
7364
|
+
**Reason:** <one sentence explaining why this is better>
|
|
7365
|
+
|
|
7366
|
+
If the current version is already good, output exactly: LGTM \u2014 no changes needed.`;
|
|
7367
|
+
}
|
|
7368
|
+
async function handleRefine(req, res, cwd, nodeId) {
|
|
7369
|
+
try {
|
|
7370
|
+
const body = await readJsonBody(req).catch(() => ({}));
|
|
7371
|
+
const mode = body.mode || "general";
|
|
7372
|
+
const userMessage = body.message || "";
|
|
7373
|
+
const graph = runLoadGraph2(cwd);
|
|
7374
|
+
if ("error" in graph) {
|
|
7375
|
+
sendJson(res, 500, { error: graph.error });
|
|
7376
|
+
return;
|
|
7377
|
+
}
|
|
7378
|
+
const prompt = buildRefinePrompt(nodeId, graph, mode, userMessage);
|
|
7379
|
+
if (!prompt) {
|
|
7380
|
+
sendJson(res, 404, { error: "Node not found: " + nodeId });
|
|
7381
|
+
return;
|
|
7382
|
+
}
|
|
7383
|
+
const sessionId = `refine-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;
|
|
7384
|
+
const { runAI } = require_ai_runner();
|
|
7385
|
+
const activityFile = path.join(cwd, ".planning", "activity.jsonl");
|
|
7386
|
+
const nId = nodeId.toUpperCase();
|
|
7387
|
+
let refineAgentId;
|
|
7388
|
+
try {
|
|
7389
|
+
const reg = getAgentRegistry(cwd);
|
|
7390
|
+
const agent = reg.spawn("refine", nId, nId);
|
|
7391
|
+
refineAgentId = agent.id;
|
|
7392
|
+
} catch (_) {
|
|
7393
|
+
}
|
|
7394
|
+
const abortController = new AbortController();
|
|
7395
|
+
refineSessions.set(sessionId, { sessionId, nodeId: nId, suggestion: "", abortController, agentId: refineAgentId });
|
|
7396
|
+
const isWriteMode = mode === "write";
|
|
7397
|
+
runAI(prompt, {
|
|
7398
|
+
cwd,
|
|
7399
|
+
model: "haiku",
|
|
7400
|
+
maxTurns: isWriteMode ? 10 : 1,
|
|
7401
|
+
withTools: isWriteMode,
|
|
7402
|
+
abortController,
|
|
7403
|
+
onText: (text) => {
|
|
7404
|
+
const session = refineSessions.get(sessionId);
|
|
7405
|
+
if (session) session.suggestion += text;
|
|
7406
|
+
const payload = `event: refine-output
|
|
7407
|
+
data: ${JSON.stringify({ sessionId, nodeId: nId, text })}
|
|
7408
|
+
|
|
7409
|
+
`;
|
|
7410
|
+
for (const client of sseClients) {
|
|
7411
|
+
try {
|
|
7412
|
+
client.write(payload);
|
|
7413
|
+
} catch (_) {
|
|
7414
|
+
sseClients.delete(client);
|
|
7415
|
+
}
|
|
7416
|
+
}
|
|
7417
|
+
}
|
|
7418
|
+
}).then(({ text, error }) => {
|
|
7419
|
+
const session = refineSessions.get(sessionId);
|
|
7420
|
+
const suggestion = text || (session ? session.suggestion : "");
|
|
7421
|
+
refineSessions.delete(sessionId);
|
|
7422
|
+
const exitCode = error ? 1 : 0;
|
|
7423
|
+
const payload = `event: refine-complete
|
|
7424
|
+
data: ${JSON.stringify({ sessionId, nodeId: nId, exitCode, suggestion, error })}
|
|
7425
|
+
|
|
7426
|
+
`;
|
|
7427
|
+
for (const client of sseClients) {
|
|
7428
|
+
try {
|
|
7429
|
+
client.write(payload);
|
|
7430
|
+
} catch (_) {
|
|
7431
|
+
sseClients.delete(client);
|
|
7432
|
+
}
|
|
7433
|
+
}
|
|
7434
|
+
if (refineAgentId) {
|
|
7435
|
+
try {
|
|
7436
|
+
const reg = getAgentRegistry(cwd);
|
|
7437
|
+
if (!error) reg.complete(refineAgentId, { nodeId: nId });
|
|
7438
|
+
else reg.fail(refineAgentId, 1, error);
|
|
7439
|
+
} catch (_) {
|
|
7440
|
+
}
|
|
7441
|
+
}
|
|
7442
|
+
const phase = error ? "error" : "done";
|
|
7443
|
+
const desc = error ? `Review of ${nId} failed: ${error}` : `Review of ${nId} complete` + (suggestion.includes("LGTM") ? " \u2014 no changes needed" : " \u2014 suggestion ready");
|
|
7444
|
+
try {
|
|
7445
|
+
fs.appendFileSync(activityFile, JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), tool: "Task", phase, agent: "Refine", desc }) + "\n");
|
|
7446
|
+
} catch (_) {
|
|
7447
|
+
}
|
|
7448
|
+
}).catch((err) => {
|
|
7449
|
+
refineSessions.delete(sessionId);
|
|
7450
|
+
const payload = `event: refine-complete
|
|
7451
|
+
data: ${JSON.stringify({ sessionId, nodeId: nId, exitCode: 1, suggestion: "", error: String(err) })}
|
|
7452
|
+
|
|
7453
|
+
`;
|
|
7454
|
+
for (const client of sseClients) {
|
|
7455
|
+
try {
|
|
7456
|
+
client.write(payload);
|
|
7457
|
+
} catch (_) {
|
|
7458
|
+
sseClients.delete(client);
|
|
7459
|
+
}
|
|
7460
|
+
}
|
|
7461
|
+
if (refineAgentId) {
|
|
7462
|
+
try {
|
|
7463
|
+
getAgentRegistry(cwd).fail(refineAgentId, 1, String(err));
|
|
7464
|
+
} catch (_) {
|
|
7465
|
+
}
|
|
7466
|
+
}
|
|
7467
|
+
});
|
|
7468
|
+
const entry = { ts: (/* @__PURE__ */ new Date()).toISOString(), tool: "Task", phase: "start", agent: "Refine", desc: `Analyzing ${nodeId.toUpperCase()} for improvements` };
|
|
7469
|
+
fs.appendFileSync(activityFile, JSON.stringify(entry) + "\n");
|
|
7470
|
+
sendJson(res, 202, { ok: true, sessionId });
|
|
7471
|
+
} catch (err) {
|
|
7472
|
+
sendJson(res, 500, { error: String(err) });
|
|
7473
|
+
}
|
|
7474
|
+
}
|
|
7475
|
+
function handleRefineStop(res, sessionId) {
|
|
7476
|
+
if (sessionId) {
|
|
7477
|
+
const session = refineSessions.get(sessionId);
|
|
7478
|
+
if (!session) {
|
|
7479
|
+
sendJson(res, 404, { error: "Session not found" });
|
|
7480
|
+
return;
|
|
7481
|
+
}
|
|
7482
|
+
try {
|
|
7483
|
+
session.abortController.abort();
|
|
7484
|
+
} catch (_) {
|
|
7485
|
+
}
|
|
7486
|
+
refineSessions.delete(sessionId);
|
|
7487
|
+
sendJson(res, 200, { ok: true });
|
|
7488
|
+
} else {
|
|
7489
|
+
for (const [id, session] of refineSessions) {
|
|
7490
|
+
try {
|
|
7491
|
+
session.abortController.abort();
|
|
7492
|
+
} catch (_) {
|
|
7493
|
+
}
|
|
7494
|
+
}
|
|
7495
|
+
refineSessions.clear();
|
|
7496
|
+
sendJson(res, 200, { ok: true });
|
|
7497
|
+
}
|
|
7498
|
+
}
|
|
7499
|
+
async function handleRefineAccept(req, res, cwd) {
|
|
7500
|
+
try {
|
|
7501
|
+
const body = await readJsonBody(req);
|
|
7502
|
+
const { nodeId, title, statement } = body;
|
|
7503
|
+
if (!nodeId) {
|
|
7504
|
+
sendJson(res, 400, { error: "Missing nodeId" });
|
|
7505
|
+
return;
|
|
7506
|
+
}
|
|
7507
|
+
const id = nodeId.toUpperCase();
|
|
7508
|
+
const prefix = id.split("-")[0];
|
|
7509
|
+
const planningDir = path.join(cwd, ".planning");
|
|
7510
|
+
if (prefix === "D") {
|
|
7511
|
+
const futurePath = path.join(planningDir, "FUTURE.md");
|
|
7512
|
+
const content = fs.readFileSync(futurePath, "utf-8");
|
|
7513
|
+
const declarations = parseFutureFile(content);
|
|
7514
|
+
const decl = declarations.find((d) => d.id === id);
|
|
7515
|
+
if (decl) {
|
|
7516
|
+
if (title) decl.title = title;
|
|
7517
|
+
if (statement) decl.statement = statement;
|
|
7518
|
+
const projectNameMatch = content.match(/^# Future:\\s*(.+)/m);
|
|
7519
|
+
const projectName = projectNameMatch ? projectNameMatch[1].trim() : "Project";
|
|
7520
|
+
fs.writeFileSync(futurePath, writeFutureFile(declarations, projectName), "utf-8");
|
|
7521
|
+
}
|
|
7522
|
+
} else if (prefix === "M") {
|
|
7523
|
+
const msPath = path.join(planningDir, "MILESTONES.md");
|
|
7524
|
+
const content = fs.readFileSync(msPath, "utf-8");
|
|
7525
|
+
const { milestones } = parseMilestonesFile(content);
|
|
7526
|
+
const mile = milestones.find((m) => m.id === id);
|
|
7527
|
+
if (mile && title) {
|
|
7528
|
+
mile.title = title;
|
|
7529
|
+
const projectNameMatch = content.match(/^# Milestones:\\s*(.+)/m);
|
|
7530
|
+
const projectName = projectNameMatch ? projectNameMatch[1].trim() : "Project";
|
|
7531
|
+
fs.writeFileSync(msPath, writeMilestonesFile(milestones, projectName), "utf-8");
|
|
7532
|
+
}
|
|
7533
|
+
}
|
|
7534
|
+
const activityFile = path.join(cwd, ".planning", "activity.jsonl");
|
|
7535
|
+
const entry = { ts: (/* @__PURE__ */ new Date()).toISOString(), tool: "Review", phase: "end", desc: `Refined ${id}`, nodeId: id, reviewState: "approved" };
|
|
7536
|
+
fs.appendFileSync(activityFile, JSON.stringify(entry) + "\n");
|
|
7537
|
+
sendJson(res, 200, { ok: true });
|
|
7538
|
+
} catch (err) {
|
|
7539
|
+
sendJson(res, 500, { error: String(err) });
|
|
7540
|
+
}
|
|
7541
|
+
}
|
|
7542
|
+
var discussSession = null;
|
|
7543
|
+
async function handleDiscuss(req, res, cwd, nodeId) {
|
|
7544
|
+
try {
|
|
7545
|
+
if (discussSession) {
|
|
7546
|
+
sendJson(res, 409, { error: "A discuss session is already running" });
|
|
7547
|
+
return;
|
|
7548
|
+
}
|
|
7549
|
+
const graph = runLoadGraph2(cwd);
|
|
7550
|
+
if ("error" in graph) {
|
|
7551
|
+
sendJson(res, 500, { error: graph.error });
|
|
7552
|
+
return;
|
|
7553
|
+
}
|
|
7554
|
+
const id = nodeId.toUpperCase();
|
|
7555
|
+
const prefix = id.split("-")[0];
|
|
7556
|
+
let nodeContext = "";
|
|
7557
|
+
if (prefix === "D") {
|
|
7558
|
+
const decl = graph.declarations.find((d) => d.id === id);
|
|
7559
|
+
if (!decl) {
|
|
7560
|
+
sendJson(res, 404, { error: "Declaration not found" });
|
|
7561
|
+
return;
|
|
7562
|
+
}
|
|
7563
|
+
nodeContext = `Declaration ${decl.id}: ${decl.title}
|
|
7564
|
+
Statement: ${decl.statement || decl.title}`;
|
|
7565
|
+
const myMiles = graph.milestones.filter((m) => (m.realizes || []).includes(id));
|
|
7566
|
+
if (myMiles.length > 0) {
|
|
7567
|
+
nodeContext += "\n\nExisting milestones:\n" + myMiles.map((m) => `- ${m.id}: ${m.title}`).join("\n");
|
|
7568
|
+
}
|
|
7569
|
+
} else if (prefix === "M") {
|
|
7570
|
+
const mile = graph.milestones.find((m) => m.id === id);
|
|
7571
|
+
if (!mile) {
|
|
7572
|
+
sendJson(res, 404, { error: "Milestone not found" });
|
|
7573
|
+
return;
|
|
7574
|
+
}
|
|
7575
|
+
nodeContext = `Milestone ${mile.id}: ${mile.title}`;
|
|
7576
|
+
const myActions = graph.actions.filter((a) => (a.causes || []).includes(id));
|
|
7577
|
+
if (myActions.length > 0) {
|
|
7578
|
+
nodeContext += "\n\nExisting actions:\n" + myActions.map((a) => `- ${a.id}: ${a.title}`).join("\n");
|
|
7579
|
+
}
|
|
7580
|
+
}
|
|
7581
|
+
const prompt = `You are helping a user plan their software project. You need to identify gray areas and ambiguities that should be resolved BEFORE creating detailed plans.
|
|
7582
|
+
|
|
7583
|
+
Given this project artifact:
|
|
7584
|
+
|
|
7585
|
+
${nodeContext}
|
|
7586
|
+
|
|
7587
|
+
Identify 3-5 important questions that should be answered to avoid wasted work. Focus on:
|
|
7588
|
+
- Ambiguous scope (what's included vs excluded?)
|
|
7589
|
+
- Technical decisions that affect the plan
|
|
7590
|
+
- Dependencies or constraints not mentioned
|
|
7591
|
+
- Success criteria that need clarification
|
|
7592
|
+
|
|
7593
|
+
Output ONLY a JSON array of question objects:
|
|
7594
|
+
[{"question": "The question text", "context": "Why this matters", "options": ["Option A", "Option B"]}]
|
|
7595
|
+
|
|
7596
|
+
The options array is optional \u2014 include it only when there are clear alternatives to choose from.`;
|
|
7597
|
+
const sessionId = `discuss-${Date.now()}`;
|
|
7598
|
+
const { runAI } = require_ai_runner();
|
|
7599
|
+
const activityFile = path.join(cwd, ".planning", "activity.jsonl");
|
|
7600
|
+
let agentId;
|
|
7601
|
+
try {
|
|
7602
|
+
const reg = getAgentRegistry(cwd);
|
|
7603
|
+
const agent = reg.spawn("discuss", id, id);
|
|
7604
|
+
agentId = agent.id;
|
|
7605
|
+
} catch (_) {
|
|
7606
|
+
}
|
|
7607
|
+
const abortController = new AbortController();
|
|
7608
|
+
discussSession = { sessionId, nodeId: id, abortController, agentId };
|
|
7609
|
+
runAI(prompt, {
|
|
7610
|
+
cwd,
|
|
7611
|
+
model: "haiku",
|
|
7612
|
+
maxTurns: 1,
|
|
7613
|
+
abortController,
|
|
7614
|
+
onText: (text) => {
|
|
7615
|
+
const payload = `event: discuss-output
|
|
7616
|
+
data: ${JSON.stringify({ sessionId, nodeId: id, text })}
|
|
7617
|
+
|
|
7618
|
+
`;
|
|
7619
|
+
for (const client of sseClients) {
|
|
7620
|
+
try {
|
|
7621
|
+
client.write(payload);
|
|
7622
|
+
} catch (_) {
|
|
7623
|
+
sseClients.delete(client);
|
|
7624
|
+
}
|
|
7625
|
+
}
|
|
7626
|
+
}
|
|
7627
|
+
}).then(({ text, error }) => {
|
|
7628
|
+
const closingSession = discussSession;
|
|
7629
|
+
discussSession = null;
|
|
7630
|
+
let questions = null;
|
|
7631
|
+
if (!error && text) {
|
|
7632
|
+
try {
|
|
7633
|
+
questions = JSON.parse(text.trim());
|
|
7634
|
+
} catch (_) {
|
|
7635
|
+
const jsonMatch = text.match(/\[[\s\S]*\]/);
|
|
7636
|
+
if (jsonMatch) {
|
|
7637
|
+
try {
|
|
7638
|
+
questions = JSON.parse(jsonMatch[0]);
|
|
7639
|
+
} catch (_2) {
|
|
7640
|
+
}
|
|
7641
|
+
}
|
|
7642
|
+
}
|
|
7643
|
+
}
|
|
7644
|
+
const payload = `event: discuss-complete
|
|
7645
|
+
data: ${JSON.stringify({ sessionId, nodeId: id, questions, error })}
|
|
7646
|
+
|
|
7647
|
+
`;
|
|
7648
|
+
for (const client of sseClients) {
|
|
7649
|
+
try {
|
|
7650
|
+
client.write(payload);
|
|
7651
|
+
} catch (_) {
|
|
7652
|
+
sseClients.delete(client);
|
|
7653
|
+
}
|
|
7654
|
+
}
|
|
7655
|
+
if (closingSession && closingSession.agentId) {
|
|
7656
|
+
try {
|
|
7657
|
+
const reg = getAgentRegistry(cwd);
|
|
7658
|
+
if (!error) reg.complete(closingSession.agentId, { nodeId: id, questionCount: Array.isArray(questions) ? questions.length : 0 });
|
|
7659
|
+
else reg.fail(closingSession.agentId, 1, error);
|
|
7660
|
+
} catch (_) {
|
|
7661
|
+
}
|
|
7662
|
+
}
|
|
7663
|
+
const phase = error ? "error" : "done";
|
|
7664
|
+
const desc = error ? `Discuss ${id} failed: ${error}` : `Discuss ${id}: ${Array.isArray(questions) ? questions.length : 0} questions`;
|
|
7665
|
+
try {
|
|
7666
|
+
fs.appendFileSync(activityFile, JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), tool: "Task", phase, agent: "Discuss", desc }) + "\n");
|
|
7667
|
+
} catch (_) {
|
|
7668
|
+
}
|
|
7669
|
+
}).catch((err) => {
|
|
7670
|
+
discussSession = null;
|
|
7671
|
+
if (agentId) {
|
|
7672
|
+
try {
|
|
7673
|
+
getAgentRegistry(cwd).fail(agentId, 1, String(err));
|
|
7674
|
+
} catch (_) {
|
|
7675
|
+
}
|
|
7676
|
+
}
|
|
7677
|
+
});
|
|
7678
|
+
try {
|
|
7679
|
+
fs.appendFileSync(activityFile, JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), tool: "Task", phase: "start", agent: "Discuss", desc: `Analyzing ${id} for discussion` }) + "\n");
|
|
7680
|
+
} catch (_) {
|
|
7681
|
+
}
|
|
7682
|
+
sendJson(res, 202, { ok: true, sessionId });
|
|
7683
|
+
} catch (err) {
|
|
7684
|
+
sendJson(res, 500, { error: String(err) });
|
|
7685
|
+
}
|
|
7686
|
+
}
|
|
7687
|
+
async function handleDiscussAnswer(req, res, cwd, nodeId) {
|
|
7688
|
+
try {
|
|
7689
|
+
const body = await readJsonBody(req);
|
|
7690
|
+
const answers = body.answers;
|
|
7691
|
+
if (!Array.isArray(answers) || answers.length === 0) {
|
|
7692
|
+
sendJson(res, 400, { error: "Missing answers array" });
|
|
7693
|
+
return;
|
|
7694
|
+
}
|
|
7695
|
+
const id = nodeId.toUpperCase();
|
|
7696
|
+
const prefix = id.split("-")[0];
|
|
7697
|
+
const planningDir = path.join(cwd, ".planning");
|
|
7698
|
+
let contextContent = `# Context: ${id}
|
|
7699
|
+
|
|
7700
|
+
`;
|
|
7701
|
+
contextContent += `Generated: ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
7702
|
+
|
|
7703
|
+
`;
|
|
7704
|
+
for (const item of answers) {
|
|
7705
|
+
contextContent += `## ${item.question}
|
|
7706
|
+
|
|
7707
|
+
${item.answer}
|
|
7708
|
+
|
|
7709
|
+
`;
|
|
7710
|
+
}
|
|
7711
|
+
if (prefix === "M") {
|
|
7712
|
+
const { ensureMilestoneFolder } = require_milestone_folders();
|
|
7713
|
+
const graph = runLoadGraph2(cwd);
|
|
7714
|
+
if ("error" in graph) {
|
|
7715
|
+
sendJson(res, 500, { error: graph.error });
|
|
7716
|
+
return;
|
|
7717
|
+
}
|
|
7718
|
+
const milestone = graph.milestones.find((m) => m.id === id);
|
|
7719
|
+
if (!milestone) {
|
|
7720
|
+
sendJson(res, 404, { error: "Milestone not found" });
|
|
7721
|
+
return;
|
|
7722
|
+
}
|
|
7723
|
+
const folder = ensureMilestoneFolder(planningDir, milestone.id, milestone.title);
|
|
7724
|
+
const contextPath = path.join(folder, "CONTEXT.md");
|
|
7725
|
+
fs.writeFileSync(contextPath, contextContent, "utf-8");
|
|
7726
|
+
} else {
|
|
7727
|
+
const contextPath = path.join(planningDir, `CONTEXT-${id}.md`);
|
|
7728
|
+
fs.writeFileSync(contextPath, contextContent, "utf-8");
|
|
7729
|
+
}
|
|
7730
|
+
sendJson(res, 200, { ok: true });
|
|
7731
|
+
broadcastChange();
|
|
7732
|
+
} catch (err) {
|
|
7733
|
+
sendJson(res, 400, { error: String(err) });
|
|
7734
|
+
}
|
|
7735
|
+
}
|
|
7736
|
+
var commandSession = null;
|
|
7737
|
+
async function handleCommand(req, res, cwd) {
|
|
7738
|
+
try {
|
|
7739
|
+
if (commandSession) {
|
|
7740
|
+
sendJson(res, 409, { error: "A command is already running" });
|
|
7741
|
+
return;
|
|
7742
|
+
}
|
|
7743
|
+
const body = await readJsonBody(req).catch(() => ({}));
|
|
7744
|
+
const message = (body.message || "").trim();
|
|
7745
|
+
const context = body.context || {};
|
|
7746
|
+
if (!message) {
|
|
7747
|
+
sendJson(res, 400, { error: "No message provided" });
|
|
7748
|
+
return;
|
|
7749
|
+
}
|
|
7750
|
+
const sessionId = `cmd-${Date.now()}`;
|
|
7751
|
+
const { runAI } = require_ai_runner();
|
|
7752
|
+
const activityFile = path.join(cwd, ".planning", "activity.jsonl");
|
|
7753
|
+
const graph = runLoadGraph2(cwd);
|
|
7754
|
+
let contextBlock = "";
|
|
7755
|
+
if (!("error" in graph)) {
|
|
7756
|
+
if (context.nodeId) {
|
|
7757
|
+
const allNodes = [...graph.declarations || [], ...graph.milestones || [], ...graph.actions || []];
|
|
7758
|
+
const node = allNodes.find((n) => n.id === context.nodeId);
|
|
7759
|
+
if (node) {
|
|
7760
|
+
contextBlock = `
|
|
7761
|
+
Current view is focused on: ${node.id} \u2014 ${node.title || node.statement || ""} (${node.status || "PENDING"})`;
|
|
7762
|
+
}
|
|
7763
|
+
}
|
|
7764
|
+
if (context.nodeIds && context.nodeIds.length > 0) {
|
|
7765
|
+
const allNodes = [...graph.declarations || [], ...graph.milestones || [], ...graph.actions || []];
|
|
7766
|
+
const visible = context.nodeIds.map((id) => allNodes.find((n) => n.id === id)).filter(Boolean);
|
|
7767
|
+
contextBlock += `
|
|
7768
|
+
Visible nodes:
|
|
7769
|
+
${visible.map((n) => `- ${n.id}: ${n.title || n.statement || ""} (${n.status || "PENDING"})`).join("\n")}`;
|
|
7770
|
+
}
|
|
7771
|
+
if (context.viewDescription) {
|
|
7772
|
+
contextBlock += `
|
|
7773
|
+
View: ${context.viewDescription}`;
|
|
7774
|
+
}
|
|
7775
|
+
}
|
|
7776
|
+
const prompt = `You are working on a Declare project. The project planning files are in ${path.join(cwd, ".planning")}/
|
|
7777
|
+
|
|
7778
|
+
The user is looking at the dashboard and says: "${message}"
|
|
7779
|
+
${contextBlock}
|
|
7780
|
+
|
|
7781
|
+
The planning files:
|
|
7782
|
+
- FUTURE.md \u2014 declarations (the "what" the project declares will be true)
|
|
7783
|
+
- MILESTONES.md \u2014 milestones (checkpoints that realize declarations)
|
|
7784
|
+
- milestones/M-XX-slug/PLAN.md \u2014 action plans per milestone
|
|
7785
|
+
|
|
7786
|
+
Apply the user's request by editing the relevant files. Be concise in your response \u2014 just state what you did.`;
|
|
7787
|
+
let agentId;
|
|
7788
|
+
try {
|
|
7789
|
+
const reg = getAgentRegistry(cwd);
|
|
7790
|
+
const agent = reg.spawn("command", context.nodeId || "project", context.nodeId || "project");
|
|
7791
|
+
agentId = agent.id;
|
|
7792
|
+
} catch (_) {
|
|
7793
|
+
}
|
|
7794
|
+
const abortController = new AbortController();
|
|
7795
|
+
commandSession = { sessionId, abortController, agentId };
|
|
7796
|
+
runAI(prompt, {
|
|
7797
|
+
cwd,
|
|
7798
|
+
model: "sonnet",
|
|
7799
|
+
maxTurns: 15,
|
|
7800
|
+
withTools: true,
|
|
7801
|
+
abortController,
|
|
7802
|
+
onText: (text) => {
|
|
7803
|
+
const payload = `event: command-output
|
|
7804
|
+
data: ${JSON.stringify({ sessionId, text })}
|
|
7805
|
+
|
|
7806
|
+
`;
|
|
7807
|
+
for (const client of sseClients) {
|
|
7808
|
+
try {
|
|
7809
|
+
client.write(payload);
|
|
7810
|
+
} catch (_) {
|
|
7811
|
+
sseClients.delete(client);
|
|
7812
|
+
}
|
|
7813
|
+
}
|
|
7814
|
+
}
|
|
7815
|
+
}).then(({ text, error }) => {
|
|
7816
|
+
commandSession = null;
|
|
7817
|
+
const exitCode = error ? 1 : 0;
|
|
7818
|
+
const payload = `event: command-complete
|
|
7819
|
+
data: ${JSON.stringify({ sessionId, exitCode, result: text, error })}
|
|
7820
|
+
|
|
7821
|
+
`;
|
|
7822
|
+
for (const client of sseClients) {
|
|
7823
|
+
try {
|
|
7824
|
+
client.write(payload);
|
|
7825
|
+
} catch (_) {
|
|
7826
|
+
sseClients.delete(client);
|
|
7827
|
+
}
|
|
7828
|
+
}
|
|
7829
|
+
if (agentId) {
|
|
7830
|
+
try {
|
|
7831
|
+
const reg = getAgentRegistry(cwd);
|
|
7832
|
+
if (!error) reg.complete(agentId, { message: message.slice(0, 50) });
|
|
7833
|
+
else reg.fail(agentId, 1, error);
|
|
7834
|
+
} catch (_) {
|
|
7835
|
+
}
|
|
7836
|
+
}
|
|
7837
|
+
const phase = error ? "error" : "done";
|
|
7838
|
+
const desc = error ? `Command failed: ${error}` : `Command complete: ${text.slice(0, 80)}`;
|
|
7839
|
+
try {
|
|
7840
|
+
fs.appendFileSync(activityFile, JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), tool: "Task", phase, agent: "Command", desc }) + "\n");
|
|
7841
|
+
} catch (_) {
|
|
7842
|
+
}
|
|
7843
|
+
}).catch((err) => {
|
|
7844
|
+
commandSession = null;
|
|
7845
|
+
if (agentId) {
|
|
7846
|
+
try {
|
|
7847
|
+
getAgentRegistry(cwd).fail(agentId, 1, String(err));
|
|
7848
|
+
} catch (_) {
|
|
7849
|
+
}
|
|
7850
|
+
}
|
|
7851
|
+
});
|
|
7852
|
+
try {
|
|
7853
|
+
fs.appendFileSync(activityFile, JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), tool: "Task", phase: "start", agent: "Command", desc: message.slice(0, 100) }) + "\n");
|
|
7854
|
+
} catch (_) {
|
|
7855
|
+
}
|
|
7856
|
+
sendJson(res, 202, { ok: true, sessionId });
|
|
7857
|
+
} catch (err) {
|
|
7858
|
+
sendJson(res, 500, { error: String(err) });
|
|
7859
|
+
}
|
|
7860
|
+
}
|
|
7861
|
+
var onboardSession = null;
|
|
7862
|
+
function persistOnboardSession(cwd) {
|
|
7863
|
+
const filePath = path.join(cwd, ".planning", "onboard-session.json");
|
|
7864
|
+
if (!onboardSession) {
|
|
7865
|
+
try {
|
|
7866
|
+
fs.unlinkSync(filePath);
|
|
7867
|
+
} catch (_) {
|
|
7868
|
+
}
|
|
7869
|
+
return;
|
|
7870
|
+
}
|
|
7871
|
+
try {
|
|
7872
|
+
const data = {
|
|
7873
|
+
prompt: onboardSession.prompt,
|
|
7874
|
+
answers: onboardSession.answers,
|
|
7875
|
+
questions: onboardSession.questions,
|
|
7876
|
+
proposals: onboardSession.proposals,
|
|
7877
|
+
phase: onboardSession.phase,
|
|
7878
|
+
approveIndex: onboardSession.approveIndex || 0,
|
|
7879
|
+
savedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
7880
|
+
};
|
|
7881
|
+
fs.writeFileSync(filePath, JSON.stringify(data, null, 2), "utf-8");
|
|
7882
|
+
} catch (_) {
|
|
7883
|
+
}
|
|
7884
|
+
}
|
|
7885
|
+
function restoreOnboardSession(cwd) {
|
|
7886
|
+
const filePath = path.join(cwd, ".planning", "onboard-session.json");
|
|
7887
|
+
try {
|
|
7888
|
+
if (!fs.existsSync(filePath)) return false;
|
|
7889
|
+
const data = JSON.parse(fs.readFileSync(filePath, "utf-8"));
|
|
7890
|
+
if (data.phase === "questions" && !data.questions) return false;
|
|
7891
|
+
if (data.phase === "proposals" && !data.proposals) return false;
|
|
7892
|
+
onboardSession = {
|
|
7893
|
+
prompt: data.prompt,
|
|
7894
|
+
answers: data.answers,
|
|
7895
|
+
questions: data.questions,
|
|
7896
|
+
proposals: data.proposals,
|
|
7897
|
+
phase: data.phase,
|
|
7898
|
+
approveIndex: data.approveIndex || 0,
|
|
7899
|
+
abortController: null
|
|
7900
|
+
};
|
|
7901
|
+
return true;
|
|
7902
|
+
} catch (_) {
|
|
7903
|
+
return false;
|
|
7904
|
+
}
|
|
7905
|
+
}
|
|
7906
|
+
async function handleOnboard(req, res, cwd) {
|
|
7907
|
+
try {
|
|
7908
|
+
if (onboardSession) {
|
|
7909
|
+
sendJson(res, 409, { error: "An onboarding session is already running" });
|
|
7910
|
+
return;
|
|
7911
|
+
}
|
|
7912
|
+
const body = await readJsonBody(req);
|
|
7913
|
+
const message = (body.message || "").trim();
|
|
7914
|
+
if (!message) {
|
|
7915
|
+
sendJson(res, 400, { error: "No message provided" });
|
|
7916
|
+
return;
|
|
7917
|
+
}
|
|
7918
|
+
const { runAI } = require_ai_runner();
|
|
7919
|
+
const activityFile = path.join(cwd, ".planning", "activity.jsonl");
|
|
7920
|
+
const prompt = `You are helping a user plan their software project. They have described their vision:
|
|
6632
7921
|
|
|
6633
|
-
${
|
|
7922
|
+
"${message}"
|
|
6634
7923
|
|
|
6635
|
-
|
|
7924
|
+
Identify 3-5 important clarification questions that should be answered before breaking this vision into concrete declarations. Focus on:
|
|
7925
|
+
- Scope boundaries (what's included vs excluded?)
|
|
7926
|
+
- Key technical decisions that affect the architecture
|
|
7927
|
+
- Target users and use cases
|
|
7928
|
+
- Priority and sequencing preferences
|
|
7929
|
+
- Success criteria and constraints
|
|
6636
7930
|
|
|
6637
|
-
|
|
7931
|
+
Output ONLY a JSON array of question objects:
|
|
7932
|
+
[{"question": "The question text", "context": "Why this matters for planning", "options": ["Option A", "Option B"]}]
|
|
6638
7933
|
|
|
6639
|
-
|
|
7934
|
+
The options array is optional \u2014 include it only when there are clear alternatives to choose from.`;
|
|
7935
|
+
let agentId;
|
|
7936
|
+
try {
|
|
7937
|
+
const reg = getAgentRegistry(cwd);
|
|
7938
|
+
const agent = reg.spawn("onboard", "project", "");
|
|
7939
|
+
agentId = agent.id;
|
|
7940
|
+
} catch (_) {
|
|
7941
|
+
}
|
|
7942
|
+
const abortController = new AbortController();
|
|
7943
|
+
onboardSession = { prompt: message, answers: null, questions: null, proposals: null, phase: "questions", approveIndex: 0, abortController, agentId };
|
|
7944
|
+
persistOnboardSession(cwd);
|
|
7945
|
+
runAI(prompt, {
|
|
7946
|
+
cwd,
|
|
7947
|
+
model: "haiku",
|
|
7948
|
+
maxTurns: 1,
|
|
7949
|
+
abortController,
|
|
7950
|
+
onText: (text) => {
|
|
7951
|
+
const payload = `event: onboard-output
|
|
7952
|
+
data: ${JSON.stringify({ text })}
|
|
6640
7953
|
|
|
6641
|
-
|
|
6642
|
-
|
|
6643
|
-
|
|
7954
|
+
`;
|
|
7955
|
+
for (const client of sseClients) {
|
|
7956
|
+
try {
|
|
7957
|
+
client.write(payload);
|
|
7958
|
+
} catch (_) {
|
|
7959
|
+
sseClients.delete(client);
|
|
7960
|
+
}
|
|
7961
|
+
}
|
|
7962
|
+
}
|
|
7963
|
+
}).then(({ text, error }) => {
|
|
7964
|
+
let questions = null;
|
|
7965
|
+
if (!error && text) {
|
|
7966
|
+
try {
|
|
7967
|
+
questions = JSON.parse(text.trim());
|
|
7968
|
+
} catch (_) {
|
|
7969
|
+
const jsonMatch = text.match(/\[[\s\S]*\]/);
|
|
7970
|
+
if (jsonMatch) {
|
|
7971
|
+
try {
|
|
7972
|
+
questions = JSON.parse(jsonMatch[0]);
|
|
7973
|
+
} catch (_2) {
|
|
7974
|
+
}
|
|
7975
|
+
}
|
|
7976
|
+
}
|
|
7977
|
+
}
|
|
7978
|
+
if (onboardSession && questions) {
|
|
7979
|
+
onboardSession.questions = questions;
|
|
7980
|
+
persistOnboardSession(cwd);
|
|
7981
|
+
}
|
|
7982
|
+
const payload = `event: onboard-questions-complete
|
|
7983
|
+
data: ${JSON.stringify({ questions, error })}
|
|
6644
7984
|
|
|
6645
|
-
|
|
7985
|
+
`;
|
|
7986
|
+
for (const client of sseClients) {
|
|
7987
|
+
try {
|
|
7988
|
+
client.write(payload);
|
|
7989
|
+
} catch (_) {
|
|
7990
|
+
sseClients.delete(client);
|
|
7991
|
+
}
|
|
7992
|
+
}
|
|
7993
|
+
if (agentId) {
|
|
7994
|
+
try {
|
|
7995
|
+
const reg = getAgentRegistry(cwd);
|
|
7996
|
+
if (!error) reg.complete(agentId, { questionCount: Array.isArray(questions) ? questions.length : 0 });
|
|
7997
|
+
else reg.fail(agentId, 1, error);
|
|
7998
|
+
} catch (_) {
|
|
7999
|
+
}
|
|
8000
|
+
}
|
|
8001
|
+
try {
|
|
8002
|
+
fs.appendFileSync(activityFile, JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), tool: "Task", phase: error ? "error" : "done", agent: "Onboard", desc: error ? `Onboard questions failed: ${error}` : `Onboard: ${Array.isArray(questions) ? questions.length : 0} questions` }) + "\n");
|
|
8003
|
+
} catch (_) {
|
|
8004
|
+
}
|
|
8005
|
+
}).catch((err) => {
|
|
8006
|
+
if (agentId) {
|
|
8007
|
+
try {
|
|
8008
|
+
getAgentRegistry(cwd).fail(agentId, 1, String(err));
|
|
8009
|
+
} catch (_) {
|
|
8010
|
+
}
|
|
8011
|
+
}
|
|
8012
|
+
});
|
|
8013
|
+
try {
|
|
8014
|
+
fs.appendFileSync(activityFile, JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), tool: "Task", phase: "start", agent: "Onboard", desc: "Generating clarification questions" }) + "\n");
|
|
8015
|
+
} catch (_) {
|
|
8016
|
+
}
|
|
8017
|
+
sendJson(res, 202, { ok: true });
|
|
8018
|
+
} catch (err) {
|
|
8019
|
+
sendJson(res, 500, { error: String(err) });
|
|
8020
|
+
}
|
|
6646
8021
|
}
|
|
6647
|
-
async function
|
|
8022
|
+
async function handleOnboardAnswer(req, res, cwd) {
|
|
6648
8023
|
try {
|
|
6649
|
-
if (
|
|
6650
|
-
sendJson(res,
|
|
8024
|
+
if (!onboardSession) {
|
|
8025
|
+
sendJson(res, 400, { error: "No onboarding session active" });
|
|
6651
8026
|
return;
|
|
6652
8027
|
}
|
|
6653
|
-
const body = await readJsonBody(req)
|
|
6654
|
-
const
|
|
6655
|
-
|
|
6656
|
-
|
|
6657
|
-
|
|
6658
|
-
|
|
6659
|
-
|
|
8028
|
+
const body = await readJsonBody(req);
|
|
8029
|
+
const answers = body.answers || [];
|
|
8030
|
+
onboardSession.answers = answers;
|
|
8031
|
+
onboardSession.phase = "proposals";
|
|
8032
|
+
persistOnboardSession(cwd);
|
|
8033
|
+
const { runAI } = require_ai_runner();
|
|
8034
|
+
const activityFile = path.join(cwd, ".planning", "activity.jsonl");
|
|
8035
|
+
let answersBlock = "";
|
|
8036
|
+
if (answers.length > 0) {
|
|
8037
|
+
answersBlock = "\n\nThe user answered these clarification questions:\n" + answers.map((a) => `Q: ${a.question}
|
|
8038
|
+
A: ${a.answer}`).join("\n\n");
|
|
6660
8039
|
}
|
|
6661
|
-
const prompt =
|
|
6662
|
-
|
|
6663
|
-
|
|
6664
|
-
|
|
8040
|
+
const prompt = `You are helping a user break down their software project vision into concrete declarations.
|
|
8041
|
+
|
|
8042
|
+
The user's vision: "${onboardSession.prompt}"${answersBlock}
|
|
8043
|
+
|
|
8044
|
+
In the Declare framework, a "declaration" is a statement about what WILL be true when the project succeeds. Each declaration should be:
|
|
8045
|
+
- A concrete, verifiable outcome (not a task or feature)
|
|
8046
|
+
- Scoped to be achievable independently
|
|
8047
|
+
- Written as a present-tense statement of truth (e.g., "Users can sign in with Google OAuth")
|
|
8048
|
+
|
|
8049
|
+
Propose 4-6 declarations that together cover the user's vision. Each should be distinct and non-overlapping.
|
|
8050
|
+
|
|
8051
|
+
Output ONLY a JSON array:
|
|
8052
|
+
[{"title": "Short declaration title", "statement": "The full declaration statement written as a present-tense truth", "reasoning": "Brief explanation of why this declaration matters"}]`;
|
|
8053
|
+
let agentId;
|
|
8054
|
+
try {
|
|
8055
|
+
const reg = getAgentRegistry(cwd);
|
|
8056
|
+
const agent = reg.spawn("onboard-propose", "project", "");
|
|
8057
|
+
agentId = agent.id;
|
|
8058
|
+
} catch (_) {
|
|
6665
8059
|
}
|
|
6666
|
-
const
|
|
6667
|
-
|
|
6668
|
-
|
|
6669
|
-
|
|
6670
|
-
const proc = spawn("claude", ["-p", prompt, "--output-format", "text"], {
|
|
8060
|
+
const abortController = new AbortController();
|
|
8061
|
+
onboardSession.abortController = abortController;
|
|
8062
|
+
if (agentId) onboardSession.agentId = agentId;
|
|
8063
|
+
runAI(prompt, {
|
|
6671
8064
|
cwd,
|
|
6672
|
-
|
|
6673
|
-
|
|
6674
|
-
|
|
6675
|
-
|
|
6676
|
-
|
|
6677
|
-
|
|
6678
|
-
const text = chunk.toString();
|
|
6679
|
-
if (refineSession) refineSession.suggestion += text;
|
|
6680
|
-
const payload = `event: refine-output
|
|
6681
|
-
data: ${JSON.stringify({ sessionId, nodeId: nodeId.toUpperCase(), text })}
|
|
8065
|
+
model: "sonnet",
|
|
8066
|
+
maxTurns: 1,
|
|
8067
|
+
abortController,
|
|
8068
|
+
onText: (text) => {
|
|
8069
|
+
const payload = `event: onboard-output
|
|
8070
|
+
data: ${JSON.stringify({ text })}
|
|
6682
8071
|
|
|
6683
8072
|
`;
|
|
6684
8073
|
for (const client of sseClients) {
|
|
@@ -6688,21 +8077,28 @@ data: ${JSON.stringify({ sessionId, nodeId: nodeId.toUpperCase(), text })}
|
|
|
6688
8077
|
sseClients.delete(client);
|
|
6689
8078
|
}
|
|
6690
8079
|
}
|
|
6691
|
-
}
|
|
6692
|
-
}
|
|
6693
|
-
|
|
6694
|
-
|
|
6695
|
-
|
|
6696
|
-
|
|
6697
|
-
|
|
6698
|
-
|
|
6699
|
-
|
|
6700
|
-
|
|
6701
|
-
|
|
6702
|
-
|
|
6703
|
-
|
|
6704
|
-
|
|
6705
|
-
|
|
8080
|
+
}
|
|
8081
|
+
}).then(({ text, error }) => {
|
|
8082
|
+
let proposals = null;
|
|
8083
|
+
if (!error && text) {
|
|
8084
|
+
try {
|
|
8085
|
+
proposals = JSON.parse(text.trim());
|
|
8086
|
+
} catch (_) {
|
|
8087
|
+
const jsonMatch = text.match(/\[[\s\S]*\]/);
|
|
8088
|
+
if (jsonMatch) {
|
|
8089
|
+
try {
|
|
8090
|
+
proposals = JSON.parse(jsonMatch[0]);
|
|
8091
|
+
} catch (_2) {
|
|
8092
|
+
}
|
|
8093
|
+
}
|
|
8094
|
+
}
|
|
8095
|
+
}
|
|
8096
|
+
if (onboardSession && proposals) {
|
|
8097
|
+
onboardSession.proposals = proposals;
|
|
8098
|
+
persistOnboardSession(cwd);
|
|
8099
|
+
}
|
|
8100
|
+
const payload = `event: onboard-proposals-complete
|
|
8101
|
+
data: ${JSON.stringify({ proposals, error })}
|
|
6706
8102
|
|
|
6707
8103
|
`;
|
|
6708
8104
|
for (const client of sseClients) {
|
|
@@ -6712,76 +8108,81 @@ data: ${JSON.stringify({ sessionId, nodeId: nId, exitCode, suggestion, error: ex
|
|
|
6712
8108
|
sseClients.delete(client);
|
|
6713
8109
|
}
|
|
6714
8110
|
}
|
|
6715
|
-
|
|
6716
|
-
|
|
6717
|
-
|
|
8111
|
+
if (agentId) {
|
|
8112
|
+
try {
|
|
8113
|
+
const reg = getAgentRegistry(cwd);
|
|
8114
|
+
if (!error) reg.complete(agentId, { proposalCount: Array.isArray(proposals) ? proposals.length : 0 });
|
|
8115
|
+
else reg.fail(agentId, 1, error);
|
|
8116
|
+
} catch (_) {
|
|
8117
|
+
}
|
|
8118
|
+
}
|
|
6718
8119
|
try {
|
|
6719
|
-
fs.appendFileSync(activityFile, JSON.stringify(
|
|
8120
|
+
fs.appendFileSync(activityFile, JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), tool: "Task", phase: error ? "error" : "done", agent: "Onboard", desc: error ? `Onboard proposals failed: ${error}` : `Onboard: ${Array.isArray(proposals) ? proposals.length : 0} proposals` }) + "\n");
|
|
6720
8121
|
} catch (_) {
|
|
6721
8122
|
}
|
|
8123
|
+
}).catch((err) => {
|
|
8124
|
+
if (agentId) {
|
|
8125
|
+
try {
|
|
8126
|
+
getAgentRegistry(cwd).fail(agentId, 1, String(err));
|
|
8127
|
+
} catch (_) {
|
|
8128
|
+
}
|
|
8129
|
+
}
|
|
6722
8130
|
});
|
|
6723
|
-
|
|
6724
|
-
|
|
6725
|
-
|
|
8131
|
+
try {
|
|
8132
|
+
fs.appendFileSync(activityFile, JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), tool: "Task", phase: "start", agent: "Onboard", desc: "Generating declaration proposals" }) + "\n");
|
|
8133
|
+
} catch (_) {
|
|
8134
|
+
}
|
|
8135
|
+
sendJson(res, 202, { ok: true });
|
|
6726
8136
|
} catch (err) {
|
|
6727
8137
|
sendJson(res, 500, { error: String(err) });
|
|
6728
8138
|
}
|
|
6729
8139
|
}
|
|
6730
|
-
function
|
|
6731
|
-
if (!refineSession) {
|
|
6732
|
-
sendJson(res, 404, { error: "No refine session running" });
|
|
6733
|
-
return;
|
|
6734
|
-
}
|
|
6735
|
-
try {
|
|
6736
|
-
refineSession.proc.kill();
|
|
6737
|
-
} catch (_) {
|
|
6738
|
-
}
|
|
6739
|
-
refineSession = null;
|
|
6740
|
-
sendJson(res, 200, { ok: true });
|
|
6741
|
-
}
|
|
6742
|
-
async function handleRefineAccept(req, res, cwd) {
|
|
8140
|
+
async function handleOnboardApprove(req, res, cwd) {
|
|
6743
8141
|
try {
|
|
6744
8142
|
const body = await readJsonBody(req);
|
|
6745
|
-
const {
|
|
6746
|
-
if (!
|
|
6747
|
-
sendJson(res, 400, { error: "Missing
|
|
8143
|
+
const { title, statement } = body;
|
|
8144
|
+
if (!title || !statement) {
|
|
8145
|
+
sendJson(res, 400, { error: "Missing title or statement" });
|
|
6748
8146
|
return;
|
|
6749
8147
|
}
|
|
6750
|
-
const
|
|
6751
|
-
|
|
6752
|
-
|
|
6753
|
-
|
|
6754
|
-
|
|
6755
|
-
|
|
6756
|
-
|
|
6757
|
-
|
|
6758
|
-
|
|
6759
|
-
|
|
6760
|
-
|
|
6761
|
-
|
|
6762
|
-
|
|
6763
|
-
|
|
6764
|
-
|
|
6765
|
-
|
|
6766
|
-
|
|
6767
|
-
|
|
6768
|
-
|
|
6769
|
-
|
|
6770
|
-
|
|
6771
|
-
|
|
6772
|
-
const projectNameMatch = content.match(/^# Milestones:\\s*(.+)/m);
|
|
6773
|
-
const projectName = projectNameMatch ? projectNameMatch[1].trim() : "Project";
|
|
6774
|
-
fs.writeFileSync(msPath, writeMilestonesFile(milestones, projectName), "utf-8");
|
|
8148
|
+
const result = runAddDeclaration2(cwd, ["--title", title, "--statement", statement]);
|
|
8149
|
+
if ("error" in result) {
|
|
8150
|
+
sendJson(res, 400, { error: result.error });
|
|
8151
|
+
return;
|
|
8152
|
+
}
|
|
8153
|
+
setReviewState(cwd, result.id, "approved");
|
|
8154
|
+
broadcastChange();
|
|
8155
|
+
if (onboardSession) {
|
|
8156
|
+
onboardSession.phase = "approving";
|
|
8157
|
+
onboardSession.approveIndex = (onboardSession.approveIndex || 0) + 1;
|
|
8158
|
+
persistOnboardSession(cwd);
|
|
8159
|
+
}
|
|
8160
|
+
try {
|
|
8161
|
+
const graph = runLoadGraph2(cwd);
|
|
8162
|
+
if (!("error" in graph)) {
|
|
8163
|
+
const declarations = graph.declarations.map((d) => ({
|
|
8164
|
+
id: d.id,
|
|
8165
|
+
statement: d.statement,
|
|
8166
|
+
milestones: d.milestones || []
|
|
8167
|
+
}));
|
|
8168
|
+
const dr = getDerivationRunner(cwd);
|
|
8169
|
+
dr.derive(result.id, declarations);
|
|
6775
8170
|
}
|
|
8171
|
+
} catch (_) {
|
|
6776
8172
|
}
|
|
6777
|
-
|
|
6778
|
-
const entry = { ts: (/* @__PURE__ */ new Date()).toISOString(), tool: "Review", phase: "end", desc: `Refined ${id}`, nodeId: id, reviewState: "approved" };
|
|
6779
|
-
fs.appendFileSync(activityFile, JSON.stringify(entry) + "\n");
|
|
6780
|
-
sendJson(res, 200, { ok: true });
|
|
8173
|
+
sendJson(res, 201, result);
|
|
6781
8174
|
} catch (err) {
|
|
6782
8175
|
sendJson(res, 500, { error: String(err) });
|
|
6783
8176
|
}
|
|
6784
8177
|
}
|
|
8178
|
+
function handleOnboardCancel(req, res, cwd) {
|
|
8179
|
+
if (onboardSession && onboardSession.abortController) {
|
|
8180
|
+
onboardSession.abortController.abort();
|
|
8181
|
+
}
|
|
8182
|
+
onboardSession = null;
|
|
8183
|
+
persistOnboardSession(cwd);
|
|
8184
|
+
sendJson(res, 200, { ok: true });
|
|
8185
|
+
}
|
|
6785
8186
|
function handleArchiveNode(res, cwd, nodeId) {
|
|
6786
8187
|
const id = nodeId.toUpperCase();
|
|
6787
8188
|
const prefix = id.split("-")[0];
|
|
@@ -7149,13 +8550,17 @@ data: ${JSON.stringify({ reason: "delete", nodeId: id })}
|
|
|
7149
8550
|
return;
|
|
7150
8551
|
}
|
|
7151
8552
|
if (urlPath === "/api/milestones/derive/stop") {
|
|
7152
|
-
handleDeriveStop(res, cwd);
|
|
8553
|
+
handleDeriveStop(req, res, cwd);
|
|
7153
8554
|
return;
|
|
7154
8555
|
}
|
|
7155
8556
|
if (urlPath === "/api/milestones/derive/accept") {
|
|
7156
8557
|
handleDeriveAccept(req, res, cwd);
|
|
7157
8558
|
return;
|
|
7158
8559
|
}
|
|
8560
|
+
if (urlPath === "/api/declarations/derive-all") {
|
|
8561
|
+
handleDeriveAll(res, cwd);
|
|
8562
|
+
return;
|
|
8563
|
+
}
|
|
7159
8564
|
const actionDeriveMatch = urlPath.match(/^\/api\/milestones\/([^/]+)\/actions\/derive$/);
|
|
7160
8565
|
if (actionDeriveMatch) {
|
|
7161
8566
|
handleActionDerive(res, cwd, actionDeriveMatch[1]);
|
|
@@ -7263,6 +8668,42 @@ data: ${JSON.stringify({ reason: "delete", nodeId: id })}
|
|
|
7263
8668
|
handleRefineAccept(req, res, cwd);
|
|
7264
8669
|
return;
|
|
7265
8670
|
}
|
|
8671
|
+
const discussMatch = urlPath.match(/^\/api\/node\/([^/]+)\/discuss$/);
|
|
8672
|
+
if (discussMatch) {
|
|
8673
|
+
handleDiscuss(req, res, cwd, discussMatch[1]);
|
|
8674
|
+
return;
|
|
8675
|
+
}
|
|
8676
|
+
const discussAnswerMatch = urlPath.match(/^\/api\/node\/([^/]+)\/discuss\/answer$/);
|
|
8677
|
+
if (discussAnswerMatch) {
|
|
8678
|
+
handleDiscussAnswer(req, res, cwd, discussAnswerMatch[1]);
|
|
8679
|
+
return;
|
|
8680
|
+
}
|
|
8681
|
+
if (urlPath === "/api/command") {
|
|
8682
|
+
handleCommand(req, res, cwd);
|
|
8683
|
+
return;
|
|
8684
|
+
}
|
|
8685
|
+
if (urlPath === "/api/onboard/complete") {
|
|
8686
|
+
onboardSession = null;
|
|
8687
|
+
persistOnboardSession(cwd);
|
|
8688
|
+
sendJson(res, 200, { ok: true });
|
|
8689
|
+
return;
|
|
8690
|
+
}
|
|
8691
|
+
if (urlPath === "/api/onboard") {
|
|
8692
|
+
handleOnboard(req, res, cwd);
|
|
8693
|
+
return;
|
|
8694
|
+
}
|
|
8695
|
+
if (urlPath === "/api/onboard/answer") {
|
|
8696
|
+
handleOnboardAnswer(req, res, cwd);
|
|
8697
|
+
return;
|
|
8698
|
+
}
|
|
8699
|
+
if (urlPath === "/api/onboard/approve") {
|
|
8700
|
+
handleOnboardApprove(req, res, cwd);
|
|
8701
|
+
return;
|
|
8702
|
+
}
|
|
8703
|
+
if (urlPath === "/api/onboard/cancel") {
|
|
8704
|
+
handleOnboardCancel(req, res, cwd);
|
|
8705
|
+
return;
|
|
8706
|
+
}
|
|
7266
8707
|
if (urlPath === "/api/execution-manifest") {
|
|
7267
8708
|
handleSaveManifest(req, res, cwd);
|
|
7268
8709
|
return;
|
|
@@ -7332,6 +8773,22 @@ data: ${JSON.stringify({ reason: "delete", nodeId: id })}
|
|
|
7332
8773
|
sendJson(res, 200, { running: pm.running() });
|
|
7333
8774
|
return;
|
|
7334
8775
|
}
|
|
8776
|
+
if (urlPath === "/api/agents") {
|
|
8777
|
+
const reg = getAgentRegistry(cwd);
|
|
8778
|
+
sendJson(res, 200, reg.getAll());
|
|
8779
|
+
return;
|
|
8780
|
+
}
|
|
8781
|
+
const agentMatch = urlPath.match(/^\/api\/agents\/([^/]+)$/);
|
|
8782
|
+
if (agentMatch) {
|
|
8783
|
+
const reg = getAgentRegistry(cwd);
|
|
8784
|
+
const agent = reg.get(decodeURIComponent(agentMatch[1]));
|
|
8785
|
+
if (agent) {
|
|
8786
|
+
sendJson(res, 200, agent);
|
|
8787
|
+
} else {
|
|
8788
|
+
sendJson(res, 404, { error: "Agent not found" });
|
|
8789
|
+
}
|
|
8790
|
+
return;
|
|
8791
|
+
}
|
|
7335
8792
|
if (urlPath === "/api/play/status") {
|
|
7336
8793
|
const pr = getPlayRunner(cwd);
|
|
7337
8794
|
const plr = getPipelineRunner(cwd);
|
|
@@ -7350,7 +8807,28 @@ data: ${JSON.stringify({ reason: "delete", nodeId: id })}
|
|
|
7350
8807
|
}
|
|
7351
8808
|
if (urlPath === "/api/derivation/running") {
|
|
7352
8809
|
const dr = getDerivationRunner(cwd);
|
|
7353
|
-
|
|
8810
|
+
const runningSessions = dr.running();
|
|
8811
|
+
sendJson(res, 200, {
|
|
8812
|
+
running: runningSessions ? runningSessions[0].sessionId : null,
|
|
8813
|
+
sessions: runningSessions
|
|
8814
|
+
});
|
|
8815
|
+
return;
|
|
8816
|
+
}
|
|
8817
|
+
if (urlPath === "/api/onboard/state") {
|
|
8818
|
+
if (!onboardSession) restoreOnboardSession(cwd);
|
|
8819
|
+
if (onboardSession) {
|
|
8820
|
+
sendJson(res, 200, {
|
|
8821
|
+
active: true,
|
|
8822
|
+
prompt: onboardSession.prompt,
|
|
8823
|
+
phase: onboardSession.phase,
|
|
8824
|
+
questions: onboardSession.questions,
|
|
8825
|
+
proposals: onboardSession.proposals,
|
|
8826
|
+
answers: onboardSession.answers,
|
|
8827
|
+
approveIndex: onboardSession.approveIndex || 0
|
|
8828
|
+
});
|
|
8829
|
+
} else {
|
|
8830
|
+
sendJson(res, 200, { active: false });
|
|
8831
|
+
}
|
|
7354
8832
|
return;
|
|
7355
8833
|
}
|
|
7356
8834
|
const actionDeriveRunningMatch = urlPath.match(/^\/api\/milestones\/([^/]+)\/actions\/derive\/running$/);
|
|
@@ -7359,6 +8837,10 @@ data: ${JSON.stringify({ reason: "delete", nodeId: id })}
|
|
|
7359
8837
|
sendJson(res, 200, { running: adr.running() });
|
|
7360
8838
|
return;
|
|
7361
8839
|
}
|
|
8840
|
+
if (urlPath === "/api/lifecycle") {
|
|
8841
|
+
handleLifecycle(res, cwd);
|
|
8842
|
+
return;
|
|
8843
|
+
}
|
|
7362
8844
|
if (urlPath === "/api/workflow/state") {
|
|
7363
8845
|
handleWorkflowState(res, cwd);
|
|
7364
8846
|
return;
|
|
@@ -7432,6 +8914,22 @@ data: ${JSON.stringify({ reason: "delete", nodeId: id })}
|
|
|
7432
8914
|
resolve(void 0);
|
|
7433
8915
|
});
|
|
7434
8916
|
});
|
|
8917
|
+
const portFilePath = require("path").join(cwd, ".planning", "server.port");
|
|
8918
|
+
require("fs").writeFileSync(portFilePath, String(resolvedPort), "utf-8");
|
|
8919
|
+
const deletePortFile = () => {
|
|
8920
|
+
try {
|
|
8921
|
+
require("fs").unlinkSync(portFilePath);
|
|
8922
|
+
} catch (_) {
|
|
8923
|
+
}
|
|
8924
|
+
};
|
|
8925
|
+
server.on("close", deletePortFile);
|
|
8926
|
+
process.on("exit", deletePortFile);
|
|
8927
|
+
const reg = getAgentRegistry(cwd);
|
|
8928
|
+
const restored = reg.restoreFromDisk();
|
|
8929
|
+
if (restored.interrupted > 0) {
|
|
8930
|
+
process.stderr.write(`[declare] Restored agent state: ${restored.interrupted} agent(s) marked as interrupted from previous run
|
|
8931
|
+
`);
|
|
8932
|
+
}
|
|
7435
8933
|
const url = `http://localhost:${resolvedPort}`;
|
|
7436
8934
|
return { server, port: resolvedPort, url };
|
|
7437
8935
|
}
|
|
@@ -7443,6 +8941,7 @@ data: ${JSON.stringify({ reason: "delete", nodeId: id })}
|
|
|
7443
8941
|
var require_serve = __commonJS({
|
|
7444
8942
|
"src/commands/serve.js"(exports2, module2) {
|
|
7445
8943
|
"use strict";
|
|
8944
|
+
var { spawn } = require("child_process");
|
|
7446
8945
|
var { startServer } = require_server();
|
|
7447
8946
|
function parsePortFlag(args) {
|
|
7448
8947
|
const idx = args.indexOf("--port");
|
|
@@ -7453,6 +8952,11 @@ var require_serve = __commonJS({
|
|
|
7453
8952
|
async function runServe2(cwd, args) {
|
|
7454
8953
|
const port = parsePortFlag(args) || parseInt(process.env.PORT || "", 10) || 3847;
|
|
7455
8954
|
const { server, port: resolvedPort, url } = await startServer(cwd, port);
|
|
8955
|
+
const noOpen = args.includes("--no-open") || process.env.DECLARE_NO_OPEN;
|
|
8956
|
+
if (!noOpen) {
|
|
8957
|
+
const opener = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
|
8958
|
+
spawn(opener, [url], { stdio: "ignore", detached: true }).unref();
|
|
8959
|
+
}
|
|
7456
8960
|
process.on("SIGINT", () => {
|
|
7457
8961
|
server.close(() => process.exit(0));
|
|
7458
8962
|
});
|
|
@@ -7473,6 +8977,7 @@ var require_open = __commonJS({
|
|
|
7473
8977
|
var path = require("path");
|
|
7474
8978
|
var http = require("http");
|
|
7475
8979
|
var { spawn } = require("child_process");
|
|
8980
|
+
var { runInit: runInit2 } = require_init();
|
|
7476
8981
|
function checkServer(port) {
|
|
7477
8982
|
return new Promise((resolve) => {
|
|
7478
8983
|
const req = http.get(`http://localhost:${port}/api/graph`, (res) => {
|
|
@@ -7496,16 +9001,11 @@ var require_open = __commonJS({
|
|
|
7496
9001
|
async function runOpen2(cwd, args) {
|
|
7497
9002
|
const planningDir = path.join(cwd, ".planning");
|
|
7498
9003
|
if (!fs.existsSync(planningDir)) {
|
|
7499
|
-
console.log("");
|
|
7500
|
-
|
|
7501
|
-
|
|
7502
|
-
|
|
7503
|
-
|
|
7504
|
-
console.log("");
|
|
7505
|
-
console.log(" Or, if declare-cc is already installed globally:");
|
|
7506
|
-
console.log(" declare-cc");
|
|
7507
|
-
console.log("");
|
|
7508
|
-
process.exit(0);
|
|
9004
|
+
console.log("Initializing Declare project in: " + cwd);
|
|
9005
|
+
const result = runInit2(cwd, []);
|
|
9006
|
+
if (result.created && result.created.length > 0) {
|
|
9007
|
+
console.log("Created: " + result.created.join(", "));
|
|
9008
|
+
}
|
|
7509
9009
|
}
|
|
7510
9010
|
const portFile = path.join(cwd, ".planning", "server.port");
|
|
7511
9011
|
const port = fs.existsSync(portFile) ? parseInt(fs.readFileSync(portFile, "utf8").trim(), 10) : 3847;
|