@schilderlabs/pitown-core 0.1.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +252 -1
- package/dist/index.mjs +820 -18
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -4
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { basename, dirname, join, resolve } from "node:path";
|
|
3
3
|
import { homedir, hostname } from "node:os";
|
|
4
4
|
import { createHash, randomUUID } from "node:crypto";
|
|
@@ -20,6 +20,120 @@ function readJsonl(filePath) {
|
|
|
20
20
|
}
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
//#endregion
|
|
24
|
+
//#region src/agents.ts
|
|
25
|
+
function writeJson$3(path, value) {
|
|
26
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
27
|
+
writeFileSync(path, `${JSON.stringify(value, null, 2)}\n`, "utf-8");
|
|
28
|
+
}
|
|
29
|
+
function ensureMailbox(path) {
|
|
30
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
31
|
+
if (!existsSync(path)) writeFileSync(path, "", "utf-8");
|
|
32
|
+
}
|
|
33
|
+
function getAgentsDir(artifactsDir) {
|
|
34
|
+
return join(artifactsDir, "agents");
|
|
35
|
+
}
|
|
36
|
+
function getAgentDir(artifactsDir, agentId) {
|
|
37
|
+
return join(getAgentsDir(artifactsDir), agentId);
|
|
38
|
+
}
|
|
39
|
+
function getAgentStatePath(artifactsDir, agentId) {
|
|
40
|
+
return join(getAgentDir(artifactsDir, agentId), "state.json");
|
|
41
|
+
}
|
|
42
|
+
function getAgentSessionPath(artifactsDir, agentId) {
|
|
43
|
+
return join(getAgentDir(artifactsDir, agentId), "session.json");
|
|
44
|
+
}
|
|
45
|
+
function getAgentMailboxPath(artifactsDir, agentId, box) {
|
|
46
|
+
return join(getAgentDir(artifactsDir, agentId), `${box}.jsonl`);
|
|
47
|
+
}
|
|
48
|
+
function getAgentSessionsDir(artifactsDir, agentId) {
|
|
49
|
+
return join(getAgentDir(artifactsDir, agentId), "sessions");
|
|
50
|
+
}
|
|
51
|
+
function getSessionIdFromPath(sessionPath) {
|
|
52
|
+
if (!sessionPath) return null;
|
|
53
|
+
return /_([0-9a-f-]+)\.jsonl$/i.exec(sessionPath)?.[1] ?? null;
|
|
54
|
+
}
|
|
55
|
+
function createAgentSessionRecord(input) {
|
|
56
|
+
return {
|
|
57
|
+
runtime: "pi",
|
|
58
|
+
persisted: true,
|
|
59
|
+
sessionDir: input?.sessionDir ?? null,
|
|
60
|
+
sessionId: input?.sessionId ?? null,
|
|
61
|
+
sessionPath: input?.sessionPath ?? null,
|
|
62
|
+
lastAttachedAt: input?.lastAttachedAt ?? null
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
function createAgentState(input) {
|
|
66
|
+
return {
|
|
67
|
+
agentId: input.agentId,
|
|
68
|
+
role: input.role,
|
|
69
|
+
status: input.status,
|
|
70
|
+
taskId: input.taskId ?? null,
|
|
71
|
+
task: input.task ?? null,
|
|
72
|
+
branch: input.branch ?? null,
|
|
73
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
74
|
+
lastMessage: input.lastMessage ?? null,
|
|
75
|
+
waitingOn: input.waitingOn ?? null,
|
|
76
|
+
blocked: input.blocked ?? false,
|
|
77
|
+
runId: input.runId ?? null,
|
|
78
|
+
session: input.session ?? createAgentSessionRecord()
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
function writeAgentState(artifactsDir, state) {
|
|
82
|
+
mkdirSync(getAgentDir(artifactsDir, state.agentId), { recursive: true });
|
|
83
|
+
ensureMailbox(getAgentMailboxPath(artifactsDir, state.agentId, "inbox"));
|
|
84
|
+
ensureMailbox(getAgentMailboxPath(artifactsDir, state.agentId, "outbox"));
|
|
85
|
+
writeJson$3(getAgentStatePath(artifactsDir, state.agentId), state);
|
|
86
|
+
writeJson$3(getAgentSessionPath(artifactsDir, state.agentId), state.session);
|
|
87
|
+
}
|
|
88
|
+
function readAgentState(artifactsDir, agentId) {
|
|
89
|
+
const statePath = getAgentStatePath(artifactsDir, agentId);
|
|
90
|
+
try {
|
|
91
|
+
return JSON.parse(readFileSync(statePath, "utf-8"));
|
|
92
|
+
} catch {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
function listAgentStates(artifactsDir) {
|
|
97
|
+
const agentsDir = getAgentsDir(artifactsDir);
|
|
98
|
+
let entries;
|
|
99
|
+
try {
|
|
100
|
+
entries = readdirSync(agentsDir);
|
|
101
|
+
} catch {
|
|
102
|
+
return [];
|
|
103
|
+
}
|
|
104
|
+
return entries.map((entry) => readAgentState(artifactsDir, entry)).filter((state) => state !== null).sort((left, right) => left.agentId.localeCompare(right.agentId));
|
|
105
|
+
}
|
|
106
|
+
function appendAgentMessage(input) {
|
|
107
|
+
const record = {
|
|
108
|
+
box: input.box,
|
|
109
|
+
from: input.from,
|
|
110
|
+
body: input.body,
|
|
111
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
112
|
+
};
|
|
113
|
+
appendJsonl(getAgentMailboxPath(input.artifactsDir, input.agentId, input.box), record);
|
|
114
|
+
return record;
|
|
115
|
+
}
|
|
116
|
+
function readAgentMessages(artifactsDir, agentId, box) {
|
|
117
|
+
return readJsonl(getAgentMailboxPath(artifactsDir, agentId, box));
|
|
118
|
+
}
|
|
119
|
+
function getLatestAgentSession(artifactsDir, agentId) {
|
|
120
|
+
const sessionDir = getAgentSessionsDir(artifactsDir, agentId);
|
|
121
|
+
let entries;
|
|
122
|
+
try {
|
|
123
|
+
entries = readdirSync(sessionDir);
|
|
124
|
+
} catch {
|
|
125
|
+
return createAgentSessionRecord({ sessionDir });
|
|
126
|
+
}
|
|
127
|
+
const latestSessionPath = entries.filter((entry) => entry.endsWith(".jsonl")).sort().at(-1) ?? null;
|
|
128
|
+
if (latestSessionPath === null) return createAgentSessionRecord({ sessionDir });
|
|
129
|
+
const sessionPath = join(sessionDir, latestSessionPath);
|
|
130
|
+
return createAgentSessionRecord({
|
|
131
|
+
sessionDir,
|
|
132
|
+
sessionPath,
|
|
133
|
+
sessionId: getSessionIdFromPath(sessionPath)
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
|
|
23
137
|
//#endregion
|
|
24
138
|
//#region src/lease.ts
|
|
25
139
|
function sanitize$1(value) {
|
|
@@ -120,6 +234,16 @@ function computeMetrics(input) {
|
|
|
120
234
|
};
|
|
121
235
|
}
|
|
122
236
|
|
|
237
|
+
//#endregion
|
|
238
|
+
//#region src/pi.ts
|
|
239
|
+
function detectPiAuthFailure(stderr, stdout) {
|
|
240
|
+
const output = `${stdout}\n${stderr}`.toLowerCase();
|
|
241
|
+
return output.includes("no models available") || output.includes("not authenticated") || output.includes("authentication") || output.includes("/login") || output.includes("api key");
|
|
242
|
+
}
|
|
243
|
+
function createPiAuthHelpMessage() {
|
|
244
|
+
return "Pi appears to be installed but not authenticated or configured. Verify Pi works first: pi -p \"hello\". Either set an API key or run `pi` and use `/login`.";
|
|
245
|
+
}
|
|
246
|
+
|
|
123
247
|
//#endregion
|
|
124
248
|
//#region src/shell.ts
|
|
125
249
|
function runCommandSync(command, args, options) {
|
|
@@ -136,6 +260,22 @@ function runCommandSync(command, args, options) {
|
|
|
136
260
|
exitCode: result.status ?? 1
|
|
137
261
|
};
|
|
138
262
|
}
|
|
263
|
+
function assertCommandAvailable(command) {
|
|
264
|
+
const result = spawnSync(command, ["--help"], {
|
|
265
|
+
encoding: "utf-8",
|
|
266
|
+
stdio: "ignore"
|
|
267
|
+
});
|
|
268
|
+
if (result.error instanceof Error) throw new Error(result.error.message);
|
|
269
|
+
}
|
|
270
|
+
function runCommandInteractive(command, args, options) {
|
|
271
|
+
const result = spawnSync(command, args, {
|
|
272
|
+
cwd: options?.cwd,
|
|
273
|
+
env: options?.env,
|
|
274
|
+
stdio: "inherit"
|
|
275
|
+
});
|
|
276
|
+
if (result.error instanceof Error) throw new Error(result.error.message);
|
|
277
|
+
return result.status ?? 1;
|
|
278
|
+
}
|
|
139
279
|
function assertSuccess(result, context) {
|
|
140
280
|
if (result.exitCode === 0) return;
|
|
141
281
|
const details = [result.stdout.trim(), result.stderr.trim()].filter(Boolean).join("\n");
|
|
@@ -196,7 +336,17 @@ function createRepoSlug(repoId, repoRoot) {
|
|
|
196
336
|
function createRunId() {
|
|
197
337
|
return `run-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}`;
|
|
198
338
|
}
|
|
199
|
-
function
|
|
339
|
+
function createPiInvocationArgs$1(input) {
|
|
340
|
+
const args = [];
|
|
341
|
+
if (input.extensionPath) args.push("--extension", input.extensionPath);
|
|
342
|
+
if (input.appendedSystemPrompt) args.push("--append-system-prompt", input.appendedSystemPrompt);
|
|
343
|
+
if (input.sessionPath) args.push("--session", input.sessionPath);
|
|
344
|
+
else if (input.sessionDir) args.push("--session-dir", input.sessionDir);
|
|
345
|
+
else throw new Error("Pi invocation requires a session path or session directory");
|
|
346
|
+
args.push("-p", input.prompt);
|
|
347
|
+
return args;
|
|
348
|
+
}
|
|
349
|
+
function writeJson$2(path, value) {
|
|
200
350
|
writeFileSync(path, `${JSON.stringify(value, null, 2)}\n`, "utf-8");
|
|
201
351
|
}
|
|
202
352
|
function writeText(path, value) {
|
|
@@ -205,6 +355,9 @@ function writeText(path, value) {
|
|
|
205
355
|
function createPiPrompt(input) {
|
|
206
356
|
const goal = input.goal ?? "continue from current scaffold state";
|
|
207
357
|
if (input.planPath) return [
|
|
358
|
+
"You are the Pi Town leader agent for this repository.",
|
|
359
|
+
"Coordinate the next bounded unit of work, keep updates concise, and leave a durable artifact trail.",
|
|
360
|
+
"",
|
|
208
361
|
"Read the private plans in:",
|
|
209
362
|
`- ${input.planPath}`,
|
|
210
363
|
"",
|
|
@@ -216,6 +369,9 @@ function createPiPrompt(input) {
|
|
|
216
369
|
"Keep any persisted run artifacts high-signal and avoid copying private plan contents into them."
|
|
217
370
|
].join("\n");
|
|
218
371
|
return [
|
|
372
|
+
"You are the Pi Town leader agent for this repository.",
|
|
373
|
+
"Coordinate the next bounded unit of work, keep updates concise, and leave a durable artifact trail.",
|
|
374
|
+
"",
|
|
219
375
|
`Work in the repository at: ${input.repoRoot}`,
|
|
220
376
|
`Goal: ${goal}`,
|
|
221
377
|
"No private plan path is configured for this run.",
|
|
@@ -223,6 +379,23 @@ function createPiPrompt(input) {
|
|
|
223
379
|
"Continue from the current scaffold state."
|
|
224
380
|
].join("\n");
|
|
225
381
|
}
|
|
382
|
+
function assertPiRuntimeAvailable(piCommand) {
|
|
383
|
+
try {
|
|
384
|
+
assertCommandAvailable(piCommand);
|
|
385
|
+
} catch (error) {
|
|
386
|
+
if (piCommand === "pi") throw new Error([
|
|
387
|
+
"Pi Town requires the `pi` CLI to run `pitown run`.",
|
|
388
|
+
"Install Pi: npm install -g @mariozechner/pi-coding-agent",
|
|
389
|
+
"Then authenticate Pi and verify it works: pi -p \"hello\"",
|
|
390
|
+
`Details: ${error.message}`
|
|
391
|
+
].join("\n"));
|
|
392
|
+
throw new Error([
|
|
393
|
+
`Pi Town could not execute the configured Pi command: ${piCommand}`,
|
|
394
|
+
"Make sure the command exists on PATH or points to an executable file.",
|
|
395
|
+
`Details: ${error.message}`
|
|
396
|
+
].join("\n"));
|
|
397
|
+
}
|
|
398
|
+
}
|
|
226
399
|
function createManifest(input) {
|
|
227
400
|
return {
|
|
228
401
|
runId: input.runId,
|
|
@@ -248,12 +421,13 @@ function createManifest(input) {
|
|
|
248
421
|
function createSummary(input) {
|
|
249
422
|
const success = input.exitCode === 0;
|
|
250
423
|
const recommendation = input.recommendedPlanDir === null ? "" : ` No plan path was configured. Recommended private plans location: ${input.recommendedPlanDir}.`;
|
|
424
|
+
const authHelp = success || !detectPiAuthFailure(input.stderr, input.stdout) ? "" : ` ${createPiAuthHelpMessage()}`;
|
|
251
425
|
return {
|
|
252
426
|
runId: input.runId,
|
|
253
427
|
mode: input.mode,
|
|
254
428
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
255
429
|
success,
|
|
256
|
-
message: success ? `Pi invocation completed.${recommendation}` : `Pi invocation failed.${recommendation}`,
|
|
430
|
+
message: success ? `Pi invocation completed.${recommendation}` : `Pi invocation failed.${authHelp}${recommendation}`,
|
|
257
431
|
piExitCode: input.exitCode,
|
|
258
432
|
recommendedPlanDir: input.recommendedPlanDir
|
|
259
433
|
};
|
|
@@ -281,14 +455,33 @@ function runController(options) {
|
|
|
281
455
|
goal,
|
|
282
456
|
recommendedPlanDir
|
|
283
457
|
});
|
|
458
|
+
const existingLeaderState = readAgentState(artifactsDir, "leader");
|
|
459
|
+
const existingLeaderSession = existingLeaderState?.session.sessionPath || existingLeaderState?.session.sessionDir ? existingLeaderState.session : getLatestAgentSession(artifactsDir, "leader");
|
|
460
|
+
const leaderSessionDir = existingLeaderSession.sessionDir ?? getAgentSessionsDir(artifactsDir, "leader");
|
|
461
|
+
const leaderState = createAgentState({
|
|
462
|
+
agentId: "leader",
|
|
463
|
+
role: "leader",
|
|
464
|
+
status: "starting",
|
|
465
|
+
task: goal,
|
|
466
|
+
branch,
|
|
467
|
+
lastMessage: goal ? `Starting leader run for goal: ${goal}` : "Starting leader run",
|
|
468
|
+
runId,
|
|
469
|
+
session: createAgentSessionRecord({
|
|
470
|
+
sessionDir: leaderSessionDir,
|
|
471
|
+
sessionId: existingLeaderSession.sessionId,
|
|
472
|
+
sessionPath: existingLeaderSession.sessionPath
|
|
473
|
+
})
|
|
474
|
+
});
|
|
475
|
+
assertPiRuntimeAvailable(piCommand);
|
|
284
476
|
mkdirSync(runDir, { recursive: true });
|
|
285
477
|
mkdirSync(latestDir, { recursive: true });
|
|
286
478
|
writeText(join(runDir, "questions.jsonl"), "");
|
|
287
479
|
writeText(join(runDir, "interventions.jsonl"), "");
|
|
288
|
-
writeJson(join(runDir, "agent-state.json"), {
|
|
480
|
+
writeJson$2(join(runDir, "agent-state.json"), {
|
|
289
481
|
status: "starting",
|
|
290
482
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
291
483
|
});
|
|
484
|
+
writeAgentState(artifactsDir, leaderState);
|
|
292
485
|
const lease = acquireRepoLease(runId, repoId, branch);
|
|
293
486
|
try {
|
|
294
487
|
const manifest = createManifest({
|
|
@@ -318,15 +511,23 @@ function runController(options) {
|
|
|
318
511
|
command: piCommand,
|
|
319
512
|
createdAt: piStartedAt
|
|
320
513
|
});
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
"
|
|
324
|
-
|
|
325
|
-
|
|
514
|
+
writeAgentState(artifactsDir, createAgentState({
|
|
515
|
+
...leaderState,
|
|
516
|
+
status: "running",
|
|
517
|
+
lastMessage: goal ? `Leader working on: ${goal}` : "Leader working"
|
|
518
|
+
}));
|
|
519
|
+
const piResult = runCommandSync(piCommand, createPiInvocationArgs$1({
|
|
520
|
+
sessionDir: leaderState.session.sessionPath === null ? leaderSessionDir : null,
|
|
521
|
+
sessionPath: leaderState.session.sessionPath,
|
|
522
|
+
prompt,
|
|
523
|
+
appendedSystemPrompt: options.appendedSystemPrompt,
|
|
524
|
+
extensionPath: options.extensionPath
|
|
525
|
+
}), {
|
|
326
526
|
cwd: repoRoot,
|
|
327
527
|
env: process.env
|
|
328
528
|
});
|
|
329
529
|
const piEndedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
530
|
+
const latestLeaderSession = getLatestAgentSession(artifactsDir, "leader");
|
|
330
531
|
writeText(stdoutPath, piResult.stdout);
|
|
331
532
|
writeText(stderrPath, piResult.stderr);
|
|
332
533
|
const piInvocation = {
|
|
@@ -335,6 +536,9 @@ function runController(options) {
|
|
|
335
536
|
repoRoot,
|
|
336
537
|
planPath,
|
|
337
538
|
goal,
|
|
539
|
+
sessionDir: latestLeaderSession.sessionDir,
|
|
540
|
+
sessionId: latestLeaderSession.sessionId,
|
|
541
|
+
sessionPath: latestLeaderSession.sessionPath,
|
|
338
542
|
startedAt: piStartedAt,
|
|
339
543
|
endedAt: piEndedAt,
|
|
340
544
|
exitCode: piResult.exitCode,
|
|
@@ -342,7 +546,7 @@ function runController(options) {
|
|
|
342
546
|
stderrPath,
|
|
343
547
|
promptSummary: planPath ? "Read private plan path and continue from current scaffold state." : "Continue from current scaffold state without a configured private plan path."
|
|
344
548
|
};
|
|
345
|
-
writeJson(join(runDir, "pi-invocation.json"), piInvocation);
|
|
549
|
+
writeJson$2(join(runDir, "pi-invocation.json"), piInvocation);
|
|
346
550
|
appendJsonl(join(runDir, "events.jsonl"), {
|
|
347
551
|
type: "pi_invocation_finished",
|
|
348
552
|
runId,
|
|
@@ -358,6 +562,8 @@ function runController(options) {
|
|
|
358
562
|
runId,
|
|
359
563
|
mode,
|
|
360
564
|
exitCode: piInvocation.exitCode,
|
|
565
|
+
stdout: piResult.stdout,
|
|
566
|
+
stderr: piResult.stderr,
|
|
361
567
|
recommendedPlanDir
|
|
362
568
|
});
|
|
363
569
|
const finalManifest = {
|
|
@@ -366,17 +572,29 @@ function runController(options) {
|
|
|
366
572
|
stopReason: piInvocation.exitCode === 0 ? "pi invocation completed" : `pi invocation exited with code ${piInvocation.exitCode}`,
|
|
367
573
|
piExitCode: piInvocation.exitCode
|
|
368
574
|
};
|
|
369
|
-
writeJson(join(runDir, "manifest.json"), finalManifest);
|
|
370
|
-
writeJson(join(runDir, "metrics.json"), metrics);
|
|
371
|
-
writeJson(join(runDir, "run-summary.json"), summary);
|
|
372
|
-
writeJson(join(runDir, "agent-state.json"), {
|
|
575
|
+
writeJson$2(join(runDir, "manifest.json"), finalManifest);
|
|
576
|
+
writeJson$2(join(runDir, "metrics.json"), metrics);
|
|
577
|
+
writeJson$2(join(runDir, "run-summary.json"), summary);
|
|
578
|
+
writeJson$2(join(runDir, "agent-state.json"), {
|
|
373
579
|
status: summary.success ? "completed" : "failed",
|
|
374
580
|
updatedAt: piEndedAt,
|
|
375
581
|
exitCode: piInvocation.exitCode
|
|
376
582
|
});
|
|
377
|
-
writeJson(join(latestDir, "manifest.json"), finalManifest);
|
|
378
|
-
writeJson(join(latestDir, "metrics.json"), metrics);
|
|
379
|
-
writeJson(join(latestDir, "run-summary.json"), summary);
|
|
583
|
+
writeJson$2(join(latestDir, "manifest.json"), finalManifest);
|
|
584
|
+
writeJson$2(join(latestDir, "metrics.json"), metrics);
|
|
585
|
+
writeJson$2(join(latestDir, "run-summary.json"), summary);
|
|
586
|
+
writeAgentState(artifactsDir, createAgentState({
|
|
587
|
+
...leaderState,
|
|
588
|
+
status: piInvocation.exitCode === 0 ? "idle" : "blocked",
|
|
589
|
+
lastMessage: piInvocation.exitCode === 0 ? "Leader run completed and is ready for the next instruction" : `Leader run stopped with exit code ${piInvocation.exitCode}`,
|
|
590
|
+
blocked: piInvocation.exitCode !== 0,
|
|
591
|
+
waitingOn: piInvocation.exitCode === 0 ? null : "human-or-follow-up-run",
|
|
592
|
+
session: createAgentSessionRecord({
|
|
593
|
+
sessionDir: latestLeaderSession.sessionDir,
|
|
594
|
+
sessionId: latestLeaderSession.sessionId,
|
|
595
|
+
sessionPath: latestLeaderSession.sessionPath
|
|
596
|
+
})
|
|
597
|
+
}));
|
|
380
598
|
appendJsonl(join(runDir, "events.jsonl"), {
|
|
381
599
|
type: "run_finished",
|
|
382
600
|
runId,
|
|
@@ -421,5 +639,589 @@ function resolveInterrupt(interrupt, options) {
|
|
|
421
639
|
}
|
|
422
640
|
|
|
423
641
|
//#endregion
|
|
424
|
-
|
|
642
|
+
//#region src/tasks.ts
|
|
643
|
+
function writeJson$1(path, value) {
|
|
644
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
645
|
+
writeFileSync(path, `${JSON.stringify(value, null, 2)}\n`, "utf-8");
|
|
646
|
+
}
|
|
647
|
+
function getTasksDir(artifactsDir) {
|
|
648
|
+
return join(artifactsDir, "tasks");
|
|
649
|
+
}
|
|
650
|
+
function getTaskPath(artifactsDir, taskId) {
|
|
651
|
+
return join(getTasksDir(artifactsDir), `${taskId}.json`);
|
|
652
|
+
}
|
|
653
|
+
function createTaskRecord(input) {
|
|
654
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
655
|
+
return {
|
|
656
|
+
taskId: input.taskId,
|
|
657
|
+
title: input.title,
|
|
658
|
+
status: input.status,
|
|
659
|
+
role: input.role,
|
|
660
|
+
assignedAgentId: input.assignedAgentId,
|
|
661
|
+
createdBy: input.createdBy,
|
|
662
|
+
createdAt: now,
|
|
663
|
+
updatedAt: now
|
|
664
|
+
};
|
|
665
|
+
}
|
|
666
|
+
function writeTaskRecord(artifactsDir, task) {
|
|
667
|
+
writeJson$1(getTaskPath(artifactsDir, task.taskId), task);
|
|
668
|
+
}
|
|
669
|
+
function updateTaskRecordStatus(artifactsDir, taskId, status) {
|
|
670
|
+
const task = readTaskRecord(artifactsDir, taskId);
|
|
671
|
+
if (task === null) return null;
|
|
672
|
+
const updatedTask = {
|
|
673
|
+
...task,
|
|
674
|
+
status,
|
|
675
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
676
|
+
};
|
|
677
|
+
writeTaskRecord(artifactsDir, updatedTask);
|
|
678
|
+
return updatedTask;
|
|
679
|
+
}
|
|
680
|
+
function readTaskRecord(artifactsDir, taskId) {
|
|
681
|
+
const path = getTaskPath(artifactsDir, taskId);
|
|
682
|
+
try {
|
|
683
|
+
return JSON.parse(readFileSync(path, "utf-8"));
|
|
684
|
+
} catch {
|
|
685
|
+
return null;
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
function listTaskRecords(artifactsDir) {
|
|
689
|
+
const tasksDir = getTasksDir(artifactsDir);
|
|
690
|
+
let entries;
|
|
691
|
+
try {
|
|
692
|
+
entries = readdirSync(tasksDir);
|
|
693
|
+
} catch {
|
|
694
|
+
return [];
|
|
695
|
+
}
|
|
696
|
+
return entries.filter((entry) => entry.endsWith(".json")).map((entry) => readTaskRecord(artifactsDir, entry.replace(/\.json$/, ""))).filter((task) => task !== null).sort((left, right) => left.taskId.localeCompare(right.taskId));
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
//#endregion
|
|
700
|
+
//#region src/loop.ts
|
|
701
|
+
const DEFAULT_MAX_ITERATIONS = 10;
|
|
702
|
+
const DEFAULT_MAX_WALL_TIME_MS = 36e5;
|
|
703
|
+
function createLoopId() {
|
|
704
|
+
return `loop-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}`;
|
|
705
|
+
}
|
|
706
|
+
function writeJson(path, value) {
|
|
707
|
+
writeFileSync(path, `${JSON.stringify(value, null, 2)}\n`, "utf-8");
|
|
708
|
+
}
|
|
709
|
+
function snapshotBoard(artifactsDir) {
|
|
710
|
+
const tasks = listTaskRecords(artifactsDir);
|
|
711
|
+
const agents = listAgentStates(artifactsDir);
|
|
712
|
+
return {
|
|
713
|
+
tasks: tasks.map((task) => ({
|
|
714
|
+
taskId: task.taskId,
|
|
715
|
+
status: task.status
|
|
716
|
+
})),
|
|
717
|
+
agents: agents.map((agent) => ({
|
|
718
|
+
agentId: agent.agentId,
|
|
719
|
+
status: agent.status,
|
|
720
|
+
blocked: agent.blocked
|
|
721
|
+
})),
|
|
722
|
+
allTasksCompleted: tasks.length > 0 && tasks.every((task) => task.status === "completed"),
|
|
723
|
+
allRemainingTasksBlocked: tasks.length > 0 && tasks.every((task) => task.status === "completed" || task.status === "blocked"),
|
|
724
|
+
leaderBlocked: agents.find((agent) => agent.agentId === "leader")?.blocked === true,
|
|
725
|
+
hasQueuedOrRunningWork: agents.some((agent) => agent.status === "queued" || agent.status === "running" || agent.status === "starting") || tasks.some((task) => task.status === "queued" || task.status === "running")
|
|
726
|
+
};
|
|
727
|
+
}
|
|
728
|
+
function evaluateStopCondition(input) {
|
|
729
|
+
if (input.iteration >= input.maxIterations) return {
|
|
730
|
+
stopReason: "max-iterations-reached",
|
|
731
|
+
continueReason: null
|
|
732
|
+
};
|
|
733
|
+
if (input.elapsedMs >= input.maxWallTimeMs) return {
|
|
734
|
+
stopReason: "max-wall-time-reached",
|
|
735
|
+
continueReason: null
|
|
736
|
+
};
|
|
737
|
+
if (input.stopOnPiFailure && input.piExitCode !== 0) return {
|
|
738
|
+
stopReason: "pi-exit-nonzero",
|
|
739
|
+
continueReason: null
|
|
740
|
+
};
|
|
741
|
+
if (input.board.allTasksCompleted) return {
|
|
742
|
+
stopReason: "all-tasks-completed",
|
|
743
|
+
continueReason: null
|
|
744
|
+
};
|
|
745
|
+
if (input.board.leaderBlocked) return {
|
|
746
|
+
stopReason: "leader-blocked",
|
|
747
|
+
continueReason: null
|
|
748
|
+
};
|
|
749
|
+
if (input.board.allRemainingTasksBlocked) return {
|
|
750
|
+
stopReason: "all-remaining-tasks-blocked",
|
|
751
|
+
continueReason: null
|
|
752
|
+
};
|
|
753
|
+
if (input.interruptRateThreshold !== null && input.metrics.interruptRate > input.interruptRateThreshold) return {
|
|
754
|
+
stopReason: "high-interrupt-rate",
|
|
755
|
+
continueReason: null
|
|
756
|
+
};
|
|
757
|
+
const reasons = [];
|
|
758
|
+
if (input.board.hasQueuedOrRunningWork) reasons.push("queued or running work remains");
|
|
759
|
+
if (input.board.tasks.length === 0) reasons.push("no tasks tracked yet");
|
|
760
|
+
if (reasons.length === 0) reasons.push("leader idle, no stop condition met");
|
|
761
|
+
return {
|
|
762
|
+
stopReason: null,
|
|
763
|
+
continueReason: reasons.join("; ")
|
|
764
|
+
};
|
|
765
|
+
}
|
|
766
|
+
function aggregateMetrics(iterations) {
|
|
767
|
+
if (iterations.length === 0) return computeMetrics({
|
|
768
|
+
taskAttempts: [],
|
|
769
|
+
interrupts: []
|
|
770
|
+
});
|
|
771
|
+
let totalTaskAttempts = 0;
|
|
772
|
+
let totalCompletedTasks = 0;
|
|
773
|
+
let totalInterrupts = 0;
|
|
774
|
+
let totalObservedInterruptCategories = 0;
|
|
775
|
+
let totalCoveredInterruptCategories = 0;
|
|
776
|
+
let interruptRateSum = 0;
|
|
777
|
+
let autonomousCompletionRateSum = 0;
|
|
778
|
+
let contextCoverageScoreSum = 0;
|
|
779
|
+
let mttcValues = [];
|
|
780
|
+
let ftdValues = [];
|
|
781
|
+
for (const iter of iterations) {
|
|
782
|
+
const m = iter.metrics;
|
|
783
|
+
totalTaskAttempts += m.totals.taskAttempts;
|
|
784
|
+
totalCompletedTasks += m.totals.completedTasks;
|
|
785
|
+
totalInterrupts += m.totals.interrupts;
|
|
786
|
+
totalObservedInterruptCategories += m.totals.observedInterruptCategories;
|
|
787
|
+
totalCoveredInterruptCategories += m.totals.coveredInterruptCategories;
|
|
788
|
+
interruptRateSum += m.interruptRate;
|
|
789
|
+
autonomousCompletionRateSum += m.autonomousCompletionRate;
|
|
790
|
+
contextCoverageScoreSum += m.contextCoverageScore;
|
|
791
|
+
if (m.meanTimeToCorrectHours !== null) mttcValues.push(m.meanTimeToCorrectHours);
|
|
792
|
+
if (m.feedbackToDemoCycleTimeHours !== null) ftdValues.push(m.feedbackToDemoCycleTimeHours);
|
|
793
|
+
}
|
|
794
|
+
const count = iterations.length;
|
|
795
|
+
const round = (v) => Math.round(v * 1e3) / 1e3;
|
|
796
|
+
const avg = (values) => values.length === 0 ? null : round(values.reduce((s, v) => s + v, 0) / values.length);
|
|
797
|
+
return {
|
|
798
|
+
interruptRate: round(interruptRateSum / count),
|
|
799
|
+
autonomousCompletionRate: round(autonomousCompletionRateSum / count),
|
|
800
|
+
contextCoverageScore: round(contextCoverageScoreSum / count),
|
|
801
|
+
meanTimeToCorrectHours: avg(mttcValues),
|
|
802
|
+
feedbackToDemoCycleTimeHours: avg(ftdValues),
|
|
803
|
+
totals: {
|
|
804
|
+
taskAttempts: totalTaskAttempts,
|
|
805
|
+
completedTasks: totalCompletedTasks,
|
|
806
|
+
interrupts: totalInterrupts,
|
|
807
|
+
observedInterruptCategories: totalObservedInterruptCategories,
|
|
808
|
+
coveredInterruptCategories: totalCoveredInterruptCategories
|
|
809
|
+
}
|
|
810
|
+
};
|
|
811
|
+
}
|
|
812
|
+
function runLoop(options) {
|
|
813
|
+
const maxIterations = options.maxIterations ?? DEFAULT_MAX_ITERATIONS;
|
|
814
|
+
const maxWallTimeMs = options.maxWallTimeMs ?? DEFAULT_MAX_WALL_TIME_MS;
|
|
815
|
+
const stopOnPiFailure = options.stopOnPiFailure ?? true;
|
|
816
|
+
const interruptRateThreshold = options.interruptRateThreshold ?? null;
|
|
817
|
+
const loopId = createLoopId();
|
|
818
|
+
const artifactsDir = options.runOptions.artifactsDir;
|
|
819
|
+
const loopDir = join(artifactsDir, "loops", loopId);
|
|
820
|
+
mkdirSync(loopDir, { recursive: true });
|
|
821
|
+
const loopStartedAt = Date.now();
|
|
822
|
+
const iterations = [];
|
|
823
|
+
let finalStopReason = "max-iterations-reached";
|
|
824
|
+
appendJsonl(join(loopDir, "events.jsonl"), {
|
|
825
|
+
type: "loop_started",
|
|
826
|
+
loopId,
|
|
827
|
+
maxIterations,
|
|
828
|
+
maxWallTimeMs,
|
|
829
|
+
stopOnPiFailure,
|
|
830
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
831
|
+
});
|
|
832
|
+
for (let iteration = 1; iteration <= maxIterations; iteration++) {
|
|
833
|
+
const iterationStart = Date.now();
|
|
834
|
+
let controllerResult;
|
|
835
|
+
try {
|
|
836
|
+
controllerResult = runController(options.runOptions);
|
|
837
|
+
} catch (error) {
|
|
838
|
+
appendJsonl(join(loopDir, "events.jsonl"), {
|
|
839
|
+
type: "loop_iteration_error",
|
|
840
|
+
loopId,
|
|
841
|
+
iteration,
|
|
842
|
+
error: error.message,
|
|
843
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
844
|
+
});
|
|
845
|
+
finalStopReason = "pi-exit-nonzero";
|
|
846
|
+
break;
|
|
847
|
+
}
|
|
848
|
+
const iterationElapsedMs = Date.now() - iterationStart;
|
|
849
|
+
const totalElapsedMs = Date.now() - loopStartedAt;
|
|
850
|
+
const board = snapshotBoard(artifactsDir);
|
|
851
|
+
const metrics = controllerResult.metrics;
|
|
852
|
+
const { stopReason, continueReason } = evaluateStopCondition({
|
|
853
|
+
iteration,
|
|
854
|
+
maxIterations,
|
|
855
|
+
elapsedMs: totalElapsedMs,
|
|
856
|
+
maxWallTimeMs,
|
|
857
|
+
piExitCode: controllerResult.piInvocation.exitCode,
|
|
858
|
+
stopOnPiFailure,
|
|
859
|
+
board,
|
|
860
|
+
metrics,
|
|
861
|
+
interruptRateThreshold
|
|
862
|
+
});
|
|
863
|
+
const iterationResult = {
|
|
864
|
+
iteration,
|
|
865
|
+
controllerResult,
|
|
866
|
+
boardSnapshot: board,
|
|
867
|
+
metrics,
|
|
868
|
+
elapsedMs: iterationElapsedMs,
|
|
869
|
+
continueReason,
|
|
870
|
+
stopReason
|
|
871
|
+
};
|
|
872
|
+
iterations.push(iterationResult);
|
|
873
|
+
writeJson(join(loopDir, `iteration-${iteration}.json`), {
|
|
874
|
+
iteration,
|
|
875
|
+
runId: controllerResult.runId,
|
|
876
|
+
boardSnapshot: board,
|
|
877
|
+
metrics,
|
|
878
|
+
elapsedMs: iterationElapsedMs,
|
|
879
|
+
continueReason,
|
|
880
|
+
stopReason
|
|
881
|
+
});
|
|
882
|
+
appendJsonl(join(loopDir, "events.jsonl"), {
|
|
883
|
+
type: "loop_iteration_completed",
|
|
884
|
+
loopId,
|
|
885
|
+
iteration,
|
|
886
|
+
runId: controllerResult.runId,
|
|
887
|
+
piExitCode: controllerResult.piInvocation.exitCode,
|
|
888
|
+
stopReason,
|
|
889
|
+
continueReason,
|
|
890
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
891
|
+
});
|
|
892
|
+
if (options.onIterationComplete) options.onIterationComplete(iterationResult);
|
|
893
|
+
if (stopReason !== null) {
|
|
894
|
+
finalStopReason = stopReason;
|
|
895
|
+
break;
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
const totalElapsedMs = Date.now() - loopStartedAt;
|
|
899
|
+
const finalBoard = iterations.length > 0 ? iterations[iterations.length - 1].boardSnapshot : snapshotBoard(artifactsDir);
|
|
900
|
+
const aggregate = aggregateMetrics(iterations);
|
|
901
|
+
const loopResult = {
|
|
902
|
+
loopId,
|
|
903
|
+
iterations,
|
|
904
|
+
stopReason: finalStopReason,
|
|
905
|
+
totalIterations: iterations.length,
|
|
906
|
+
totalElapsedMs,
|
|
907
|
+
finalBoardSnapshot: finalBoard,
|
|
908
|
+
aggregateMetrics: aggregate
|
|
909
|
+
};
|
|
910
|
+
writeJson(join(loopDir, "loop-summary.json"), {
|
|
911
|
+
loopId,
|
|
912
|
+
stopReason: finalStopReason,
|
|
913
|
+
totalIterations: iterations.length,
|
|
914
|
+
totalElapsedMs,
|
|
915
|
+
finalBoardSnapshot: finalBoard,
|
|
916
|
+
aggregateMetrics: aggregate
|
|
917
|
+
});
|
|
918
|
+
appendJsonl(join(loopDir, "events.jsonl"), {
|
|
919
|
+
type: "loop_finished",
|
|
920
|
+
loopId,
|
|
921
|
+
stopReason: finalStopReason,
|
|
922
|
+
totalIterations: iterations.length,
|
|
923
|
+
totalElapsedMs,
|
|
924
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
925
|
+
});
|
|
926
|
+
return loopResult;
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
//#endregion
|
|
930
|
+
//#region src/orchestration.ts
|
|
931
|
+
function createPiInvocationArgs(input) {
|
|
932
|
+
const args = [];
|
|
933
|
+
if (input.extensionPath) args.push("--extension", input.extensionPath);
|
|
934
|
+
if (input.appendedSystemPrompt) args.push("--append-system-prompt", input.appendedSystemPrompt);
|
|
935
|
+
if (input.sessionPath) args.push("--session", input.sessionPath);
|
|
936
|
+
else if (input.sessionDir) args.push("--session-dir", input.sessionDir);
|
|
937
|
+
else throw new Error("Pi invocation requires a session path or session directory");
|
|
938
|
+
if (input.prompt) args.push("-p", input.prompt);
|
|
939
|
+
return args;
|
|
940
|
+
}
|
|
941
|
+
function createRolePrompt(input) {
|
|
942
|
+
const task = input.task ?? "pick the next bounded task from the current repo context";
|
|
943
|
+
switch (input.role) {
|
|
944
|
+
case "leader": return [
|
|
945
|
+
"You are the Pi Town leader.",
|
|
946
|
+
"You coordinate work for this repository and act as the primary human-facing agent.",
|
|
947
|
+
"",
|
|
948
|
+
`Repository: ${input.repoRoot}`,
|
|
949
|
+
`Task: ${task}`,
|
|
950
|
+
"Keep updates concise, choose bounded next steps, and leave a durable artifact trail."
|
|
951
|
+
].join("\n");
|
|
952
|
+
case "reviewer": return [
|
|
953
|
+
"You are the Pi Town reviewer.",
|
|
954
|
+
"You review work for correctness, safety, and completeness.",
|
|
955
|
+
"",
|
|
956
|
+
`Repository: ${input.repoRoot}`,
|
|
957
|
+
`Task: ${task}`,
|
|
958
|
+
"Focus on validation confidence, regressions, and whether the output is ready for a human handoff."
|
|
959
|
+
].join("\n");
|
|
960
|
+
case "docs-keeper": return [
|
|
961
|
+
"You are the Pi Town docs keeper.",
|
|
962
|
+
"You summarize outcomes, blockers, and continuity in compact factual language.",
|
|
963
|
+
"",
|
|
964
|
+
`Repository: ${input.repoRoot}`,
|
|
965
|
+
`Task: ${task}`,
|
|
966
|
+
"Keep the output concise and useful for the next run or human review."
|
|
967
|
+
].join("\n");
|
|
968
|
+
default: return [
|
|
969
|
+
"You are the Pi Town worker.",
|
|
970
|
+
"You implement one bounded task at a time.",
|
|
971
|
+
"",
|
|
972
|
+
`Repository: ${input.repoRoot}`,
|
|
973
|
+
`Task: ${task}`,
|
|
974
|
+
"Keep scope tight, prefer explicit validations, and summarize what changed and what still needs follow-up."
|
|
975
|
+
].join("\n");
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
function resolveAgentSession(agentId, artifactsDir) {
|
|
979
|
+
const state = readAgentState(artifactsDir, agentId);
|
|
980
|
+
if (state === null) throw new Error(`Unknown agent: ${agentId}`);
|
|
981
|
+
const latestSession = getLatestAgentSession(artifactsDir, agentId);
|
|
982
|
+
const sessionPath = state.session.sessionPath ?? latestSession.sessionPath;
|
|
983
|
+
const sessionId = state.session.sessionId ?? latestSession.sessionId;
|
|
984
|
+
const sessionDir = state.session.sessionDir ?? latestSession.sessionDir ?? getAgentSessionsDir(artifactsDir, agentId);
|
|
985
|
+
if (sessionPath === null) throw new Error(`Agent ${agentId} does not have a persisted Pi session yet.`);
|
|
986
|
+
return {
|
|
987
|
+
state,
|
|
988
|
+
session: createAgentSessionRecord({
|
|
989
|
+
sessionDir,
|
|
990
|
+
sessionId,
|
|
991
|
+
sessionPath,
|
|
992
|
+
lastAttachedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
993
|
+
})
|
|
994
|
+
};
|
|
995
|
+
}
|
|
996
|
+
function queueAgentMessage(input) {
|
|
997
|
+
const state = readAgentState(input.artifactsDir, input.agentId);
|
|
998
|
+
if (state === null) throw new Error(`Unknown agent: ${input.agentId}`);
|
|
999
|
+
appendAgentMessage({
|
|
1000
|
+
artifactsDir: input.artifactsDir,
|
|
1001
|
+
agentId: input.agentId,
|
|
1002
|
+
box: "inbox",
|
|
1003
|
+
from: input.from,
|
|
1004
|
+
body: input.body
|
|
1005
|
+
});
|
|
1006
|
+
writeAgentState(input.artifactsDir, createAgentState({
|
|
1007
|
+
...state,
|
|
1008
|
+
status: state.status === "idle" ? "queued" : state.status,
|
|
1009
|
+
lastMessage: input.body,
|
|
1010
|
+
waitingOn: null,
|
|
1011
|
+
blocked: false,
|
|
1012
|
+
session: createAgentSessionRecord({
|
|
1013
|
+
sessionDir: state.session.sessionDir ?? getAgentSessionsDir(input.artifactsDir, input.agentId),
|
|
1014
|
+
sessionId: state.session.sessionId,
|
|
1015
|
+
sessionPath: state.session.sessionPath,
|
|
1016
|
+
lastAttachedAt: state.session.lastAttachedAt
|
|
1017
|
+
})
|
|
1018
|
+
}));
|
|
1019
|
+
}
|
|
1020
|
+
function updateAgentStatus(input) {
|
|
1021
|
+
const state = readAgentState(input.artifactsDir, input.agentId);
|
|
1022
|
+
if (state === null) throw new Error(`Unknown agent: ${input.agentId}`);
|
|
1023
|
+
if (state.taskId) {
|
|
1024
|
+
const taskStatus = input.status === "completed" ? "completed" : input.status === "blocked" || input.status === "failed" ? "blocked" : input.status === "running" || input.status === "queued" ? "running" : null;
|
|
1025
|
+
if (taskStatus) updateTaskRecordStatus(input.artifactsDir, state.taskId, taskStatus);
|
|
1026
|
+
}
|
|
1027
|
+
writeAgentState(input.artifactsDir, createAgentState({
|
|
1028
|
+
...state,
|
|
1029
|
+
status: input.status,
|
|
1030
|
+
lastMessage: input.lastMessage ?? state.lastMessage,
|
|
1031
|
+
waitingOn: input.waitingOn ?? state.waitingOn,
|
|
1032
|
+
blocked: input.blocked ?? state.blocked,
|
|
1033
|
+
session: state.session
|
|
1034
|
+
}));
|
|
1035
|
+
}
|
|
1036
|
+
function spawnAgentRun(options) {
|
|
1037
|
+
const sessionDir = getAgentSessionsDir(options.artifactsDir, options.agentId);
|
|
1038
|
+
if (readAgentState(options.artifactsDir, options.agentId) !== null) throw new Error(`Agent already exists: ${options.agentId}`);
|
|
1039
|
+
assertCommandAvailable("pi");
|
|
1040
|
+
const state = createAgentState({
|
|
1041
|
+
agentId: options.agentId,
|
|
1042
|
+
role: options.role,
|
|
1043
|
+
status: "queued",
|
|
1044
|
+
taskId: options.taskId ?? null,
|
|
1045
|
+
task: options.task,
|
|
1046
|
+
lastMessage: options.task ? `Spawned with task: ${options.task}` : `Spawned ${options.role} agent`,
|
|
1047
|
+
session: createAgentSessionRecord({ sessionDir })
|
|
1048
|
+
});
|
|
1049
|
+
writeAgentState(options.artifactsDir, state);
|
|
1050
|
+
if (options.task) appendAgentMessage({
|
|
1051
|
+
artifactsDir: options.artifactsDir,
|
|
1052
|
+
agentId: options.agentId,
|
|
1053
|
+
box: "inbox",
|
|
1054
|
+
from: "system",
|
|
1055
|
+
body: options.task
|
|
1056
|
+
});
|
|
1057
|
+
writeAgentState(options.artifactsDir, createAgentState({
|
|
1058
|
+
...state,
|
|
1059
|
+
status: "running",
|
|
1060
|
+
lastMessage: options.task ? `Running ${options.role} task: ${options.task}` : `Running ${options.role} agent`
|
|
1061
|
+
}));
|
|
1062
|
+
const piArgs = createPiInvocationArgs({
|
|
1063
|
+
sessionDir,
|
|
1064
|
+
prompt: createRolePrompt({
|
|
1065
|
+
role: options.role,
|
|
1066
|
+
task: options.task,
|
|
1067
|
+
repoRoot: options.repoRoot
|
|
1068
|
+
}),
|
|
1069
|
+
appendedSystemPrompt: options.appendedSystemPrompt,
|
|
1070
|
+
extensionPath: options.extensionPath
|
|
1071
|
+
});
|
|
1072
|
+
const piResult = runCommandSync("pi", piArgs, {
|
|
1073
|
+
cwd: options.repoRoot,
|
|
1074
|
+
env: process.env
|
|
1075
|
+
});
|
|
1076
|
+
const latestSession = getLatestAgentSession(options.artifactsDir, options.agentId);
|
|
1077
|
+
const agentArtifactsDir = getAgentDir(options.artifactsDir, options.agentId);
|
|
1078
|
+
writeFileSync(`${agentArtifactsDir}/latest-stdout.txt`, piResult.stdout, "utf-8");
|
|
1079
|
+
writeFileSync(`${agentArtifactsDir}/latest-stderr.txt`, piResult.stderr, "utf-8");
|
|
1080
|
+
writeFileSync(`${agentArtifactsDir}/latest-invocation.json`, `${JSON.stringify({
|
|
1081
|
+
command: "pi",
|
|
1082
|
+
args: piArgs,
|
|
1083
|
+
exitCode: piResult.exitCode,
|
|
1084
|
+
sessionDir,
|
|
1085
|
+
sessionPath: latestSession.sessionPath,
|
|
1086
|
+
sessionId: latestSession.sessionId
|
|
1087
|
+
}, null, 2)}\n`, "utf-8");
|
|
1088
|
+
const completionMessage = piResult.stdout.trim() || (piResult.exitCode === 0 ? `${options.role} run completed` : `${options.role} run exited with code ${piResult.exitCode}`);
|
|
1089
|
+
appendAgentMessage({
|
|
1090
|
+
artifactsDir: options.artifactsDir,
|
|
1091
|
+
agentId: options.agentId,
|
|
1092
|
+
box: "outbox",
|
|
1093
|
+
from: options.agentId,
|
|
1094
|
+
body: completionMessage
|
|
1095
|
+
});
|
|
1096
|
+
writeAgentState(options.artifactsDir, createAgentState({
|
|
1097
|
+
...state,
|
|
1098
|
+
status: piResult.exitCode === 0 ? "idle" : "blocked",
|
|
1099
|
+
lastMessage: completionMessage,
|
|
1100
|
+
blocked: piResult.exitCode !== 0,
|
|
1101
|
+
waitingOn: piResult.exitCode === 0 ? null : "human-or-follow-up-run",
|
|
1102
|
+
session: createAgentSessionRecord({
|
|
1103
|
+
sessionDir: latestSession.sessionDir,
|
|
1104
|
+
sessionId: latestSession.sessionId,
|
|
1105
|
+
sessionPath: latestSession.sessionPath
|
|
1106
|
+
})
|
|
1107
|
+
}));
|
|
1108
|
+
return {
|
|
1109
|
+
piResult,
|
|
1110
|
+
latestSession,
|
|
1111
|
+
completionMessage
|
|
1112
|
+
};
|
|
1113
|
+
}
|
|
1114
|
+
function runAgentTurn(options) {
|
|
1115
|
+
assertCommandAvailable("pi");
|
|
1116
|
+
const resolved = resolveAgentSession(options.agentId, options.artifactsDir);
|
|
1117
|
+
const messageSource = options.from ?? "human";
|
|
1118
|
+
writeAgentState(options.artifactsDir, createAgentState({
|
|
1119
|
+
...resolved.state,
|
|
1120
|
+
status: "running",
|
|
1121
|
+
lastMessage: `Responding to ${messageSource}: ${options.message}`,
|
|
1122
|
+
waitingOn: null,
|
|
1123
|
+
blocked: false,
|
|
1124
|
+
session: resolved.session
|
|
1125
|
+
}));
|
|
1126
|
+
const piArgs = options.runtimeArgs && options.runtimeArgs.length > 0 ? options.runtimeArgs : createPiInvocationArgs({
|
|
1127
|
+
sessionPath: resolved.session.sessionPath,
|
|
1128
|
+
prompt: options.message
|
|
1129
|
+
});
|
|
1130
|
+
const piResult = runCommandSync("pi", piArgs, {
|
|
1131
|
+
cwd: options.repoRoot,
|
|
1132
|
+
env: process.env
|
|
1133
|
+
});
|
|
1134
|
+
const latestSession = getLatestAgentSession(options.artifactsDir, options.agentId);
|
|
1135
|
+
const agentArtifactsDir = getAgentDir(options.artifactsDir, options.agentId);
|
|
1136
|
+
writeFileSync(`${agentArtifactsDir}/latest-stdout.txt`, piResult.stdout, "utf-8");
|
|
1137
|
+
writeFileSync(`${agentArtifactsDir}/latest-stderr.txt`, piResult.stderr, "utf-8");
|
|
1138
|
+
writeFileSync(`${agentArtifactsDir}/latest-invocation.json`, `${JSON.stringify({
|
|
1139
|
+
command: "pi",
|
|
1140
|
+
args: piArgs,
|
|
1141
|
+
exitCode: piResult.exitCode,
|
|
1142
|
+
sessionDir: latestSession.sessionDir,
|
|
1143
|
+
sessionPath: latestSession.sessionPath,
|
|
1144
|
+
sessionId: latestSession.sessionId
|
|
1145
|
+
}, null, 2)}\n`, "utf-8");
|
|
1146
|
+
const completionMessage = piResult.stdout.trim() || (piResult.exitCode === 0 ? `${resolved.state.role} turn completed` : `${resolved.state.role} turn exited with code ${piResult.exitCode}`);
|
|
1147
|
+
appendAgentMessage({
|
|
1148
|
+
artifactsDir: options.artifactsDir,
|
|
1149
|
+
agentId: options.agentId,
|
|
1150
|
+
box: "outbox",
|
|
1151
|
+
from: options.agentId,
|
|
1152
|
+
body: completionMessage
|
|
1153
|
+
});
|
|
1154
|
+
writeAgentState(options.artifactsDir, createAgentState({
|
|
1155
|
+
...resolved.state,
|
|
1156
|
+
status: piResult.exitCode === 0 ? "idle" : "blocked",
|
|
1157
|
+
lastMessage: completionMessage,
|
|
1158
|
+
waitingOn: piResult.exitCode === 0 ? null : "human-or-follow-up-run",
|
|
1159
|
+
blocked: piResult.exitCode !== 0,
|
|
1160
|
+
session: createAgentSessionRecord({
|
|
1161
|
+
sessionDir: latestSession.sessionDir,
|
|
1162
|
+
sessionId: latestSession.sessionId,
|
|
1163
|
+
sessionPath: latestSession.sessionPath
|
|
1164
|
+
})
|
|
1165
|
+
}));
|
|
1166
|
+
return {
|
|
1167
|
+
piResult,
|
|
1168
|
+
latestSession,
|
|
1169
|
+
completionMessage
|
|
1170
|
+
};
|
|
1171
|
+
}
|
|
1172
|
+
function delegateTask(options) {
|
|
1173
|
+
if (readAgentState(options.artifactsDir, options.fromAgentId) === null) throw new Error(`Unknown delegating agent: ${options.fromAgentId}`);
|
|
1174
|
+
const agentId = options.agentId ?? `${options.role}-${Date.now()}`;
|
|
1175
|
+
const task = createTaskRecord({
|
|
1176
|
+
taskId: `task-${Date.now()}`,
|
|
1177
|
+
title: options.task,
|
|
1178
|
+
status: "queued",
|
|
1179
|
+
role: options.role,
|
|
1180
|
+
assignedAgentId: agentId,
|
|
1181
|
+
createdBy: options.fromAgentId
|
|
1182
|
+
});
|
|
1183
|
+
writeTaskRecord(options.artifactsDir, task);
|
|
1184
|
+
appendAgentMessage({
|
|
1185
|
+
artifactsDir: options.artifactsDir,
|
|
1186
|
+
agentId: options.fromAgentId,
|
|
1187
|
+
box: "outbox",
|
|
1188
|
+
from: options.fromAgentId,
|
|
1189
|
+
body: `Delegated ${task.taskId} to ${agentId}: ${options.task}`
|
|
1190
|
+
});
|
|
1191
|
+
const { piResult, latestSession } = spawnAgentRun({
|
|
1192
|
+
repoRoot: options.repoRoot,
|
|
1193
|
+
artifactsDir: options.artifactsDir,
|
|
1194
|
+
role: options.role,
|
|
1195
|
+
agentId,
|
|
1196
|
+
appendedSystemPrompt: options.appendedSystemPrompt,
|
|
1197
|
+
extensionPath: options.extensionPath,
|
|
1198
|
+
task: options.task,
|
|
1199
|
+
taskId: task.taskId
|
|
1200
|
+
});
|
|
1201
|
+
appendAgentMessage({
|
|
1202
|
+
artifactsDir: options.artifactsDir,
|
|
1203
|
+
agentId,
|
|
1204
|
+
box: "inbox",
|
|
1205
|
+
from: options.fromAgentId,
|
|
1206
|
+
body: `Delegated by ${options.fromAgentId} as ${task.taskId}: ${options.task}`
|
|
1207
|
+
});
|
|
1208
|
+
writeTaskRecord(options.artifactsDir, {
|
|
1209
|
+
...task,
|
|
1210
|
+
status: piResult.exitCode === 0 ? "completed" : "blocked",
|
|
1211
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1212
|
+
});
|
|
1213
|
+
return {
|
|
1214
|
+
task: {
|
|
1215
|
+
...task,
|
|
1216
|
+
status: piResult.exitCode === 0 ? "completed" : "blocked",
|
|
1217
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1218
|
+
},
|
|
1219
|
+
agentId,
|
|
1220
|
+
piResult,
|
|
1221
|
+
latestSession
|
|
1222
|
+
};
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
//#endregion
|
|
1226
|
+
export { acquireRepoLease, appendAgentMessage, appendJsonl, assertCommandAvailable, assertSuccess, computeAutonomousCompletionRate, computeContextCoverageScore, computeFeedbackToDemoCycleTime, computeInterruptRate, computeMeanTimeToCorrect, computeMetrics, createAgentSessionRecord, createAgentState, createInterruptRecord, createPiAuthHelpMessage, createRepoSlug, createRolePrompt, createTaskRecord, delegateTask, detectPiAuthFailure, evaluateStopCondition, getAgentDir, getAgentMailboxPath, getAgentSessionPath, getAgentSessionsDir, getAgentStatePath, getAgentsDir, getCurrentBranch, getLatestAgentSession, getRepoIdentity, getRepoRoot, getSessionIdFromPath, getTaskPath, getTasksDir, isGitRepo, listAgentStates, listTaskRecords, queueAgentMessage, readAgentMessages, readAgentState, readJsonl, readTaskRecord, resolveAgentSession, resolveInterrupt, runAgentTurn, runCommandInteractive, runCommandSync, runController, runLoop, snapshotBoard, spawnAgentRun, updateAgentStatus, updateTaskRecordStatus, writeAgentState, writeTaskRecord };
|
|
425
1227
|
//# sourceMappingURL=index.mjs.map
|