agent-lyceum 0.4.14 → 0.6.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 +21 -21
- package/README.zh-TW.md +21 -21
- package/dist/adapters/claude.js +2 -0
- package/dist/ask-answer.d.ts +10 -0
- package/dist/ask-answer.js +21 -0
- package/dist/ask-reply.d.ts +65 -0
- package/dist/ask-reply.js +165 -0
- package/dist/assert.d.ts +2 -0
- package/dist/assert.js +6 -0
- package/dist/cli.js +202 -16
- package/dist/config.d.ts +21 -16
- package/dist/config.js +19 -7
- package/dist/dispatcher.d.ts +3 -34
- package/dist/dispatcher.js +4 -464
- package/dist/doctor.js +3 -0
- package/dist/format.js +7 -5
- package/dist/fs-util.d.ts +2 -0
- package/dist/fs-util.js +15 -0
- package/dist/guard.d.ts +8 -0
- package/dist/guard.js +24 -3
- package/dist/mailbox.d.ts +11 -1
- package/dist/mailbox.js +21 -8
- package/dist/memory-tidy.d.ts +115 -0
- package/dist/memory-tidy.js +351 -0
- package/dist/policy.js +3 -3
- package/dist/process-runner.js +1 -2
- package/dist/project-lock.d.ts +10 -1
- package/dist/project-lock.js +39 -4
- package/dist/prompt.d.ts +20 -0
- package/dist/prompt.js +76 -3
- package/dist/run-cleanup.js +4 -3
- package/dist/run-log.d.ts +5 -0
- package/dist/run-log.js +18 -0
- package/dist/run-outcome.d.ts +22 -0
- package/dist/run-outcome.js +28 -0
- package/dist/run-session.d.ts +92 -0
- package/dist/run-session.js +602 -0
- package/dist/run-store.d.ts +3 -1
- package/dist/run-store.js +7 -6
- package/dist/scaffold.js +1 -0
- package/dist/schema.d.ts +72 -239
- package/dist/schema.js +8 -3
- package/dist/status.d.ts +9 -1
- package/dist/status.js +51 -15
- package/dist/validate.js +12 -8
- package/dist/worktree.js +7 -6
- package/package.json +14 -8
package/dist/cli.js
CHANGED
|
@@ -4,6 +4,8 @@ import path from "node:path";
|
|
|
4
4
|
import readline from "node:readline/promises";
|
|
5
5
|
import { Command } from "commander";
|
|
6
6
|
import { ConfigError, findProjectForCwd, flattenResolved, listProjects, resolveProject, resolveProjectWithSources } from "./config.js";
|
|
7
|
+
import { openInEditor, prepareAnswers } from "./ask-answer.js";
|
|
8
|
+
import { restoreMemory, tidyMemory, tidyStamp } from "./memory-tidy.js";
|
|
7
9
|
import { RESULT_FILE, runTeam } from "./dispatcher.js";
|
|
8
10
|
import { executeRunCleanup, planRunCleanup } from "./run-cleanup.js";
|
|
9
11
|
import { diagnoseProject, formatDoctor, preflightRuntimes } from "./doctor.js";
|
|
@@ -15,6 +17,7 @@ import { addProject, initHome, removeProject } from "./scaffold.js";
|
|
|
15
17
|
import { buildStatusReport, buildTaskListReport, formatMonitor, formatRunDetail, formatStatusWithLog, formatTaskList, latestUnfinishedRun, runIsAlive } from "./status.js";
|
|
16
18
|
import { prepareTask, readTaskFile } from "./task.js";
|
|
17
19
|
import { formatEnforcement, validateProject } from "./validate.js";
|
|
20
|
+
import { must } from "./assert.js";
|
|
18
21
|
const program = new Command();
|
|
19
22
|
program
|
|
20
23
|
.name("agent-lyceum")
|
|
@@ -170,9 +173,13 @@ function cancelOnSignals() {
|
|
|
170
173
|
const onTerm = handler("Terminated");
|
|
171
174
|
process.on("SIGINT", onInt);
|
|
172
175
|
process.on("SIGTERM", onTerm);
|
|
173
|
-
return { signal: ac.signal, dispose: () =>
|
|
176
|
+
return { signal: ac.signal, dispose: () => {
|
|
177
|
+
process.off("SIGINT", onInt);
|
|
178
|
+
process.off("SIGTERM", onTerm);
|
|
179
|
+
},
|
|
180
|
+
};
|
|
174
181
|
}
|
|
175
|
-
function
|
|
182
|
+
function printRun(summary, runDir) {
|
|
176
183
|
console.log(`\nRun ${summary.runId} ended: ${summary.endReason}, outcome: ${summary.outcome}, after ${summary.rounds} round(s). Logs: ${path.join(runDir, "log.jsonl")}`);
|
|
177
184
|
if (summary.outcomeNote)
|
|
178
185
|
console.log(`Note: ${summary.outcomeNote}`);
|
|
@@ -182,13 +189,72 @@ function reportRun(summary, runDir) {
|
|
|
182
189
|
console.log(`\nLead's final message — ${summary.doneMessage.subject}\n\n${summary.doneMessage.body}`);
|
|
183
190
|
console.log(`\nResult saved to: ${path.join(runDir, RESULT_FILE)}`);
|
|
184
191
|
}
|
|
192
|
+
}
|
|
193
|
+
function reportRun(summary, runDir) {
|
|
194
|
+
printRun(summary, runDir);
|
|
185
195
|
process.exit(exitCodeForOutcome(summary.outcome));
|
|
186
196
|
}
|
|
197
|
+
/** Take the lock and continue a run; the lock is released when it stops (also when it stops because it waits for answers). */
|
|
198
|
+
async function continueRun(pr, dir, runId) {
|
|
199
|
+
const lease = takeLock(pr, runId);
|
|
200
|
+
const cancel = cancelOnSignals();
|
|
201
|
+
try {
|
|
202
|
+
console.log(`Project ${pr.name} — repo ${pr.dir}`);
|
|
203
|
+
const state = loadRunState(dir);
|
|
204
|
+
console.log(`Resuming run ${state.run_id} (${state.end_reason ?? "interrupted"}) at round ${state.rounds}/${pr.dispatcher.max_rounds}: ${state.task_summary}`);
|
|
205
|
+
// Read the state again under the lock: another process may have changed it while we were checking.
|
|
206
|
+
return await runTeam({ project: pr, resume: state, runDir: dir, signal: cancel.signal });
|
|
207
|
+
}
|
|
208
|
+
finally {
|
|
209
|
+
cancel.dispose();
|
|
210
|
+
lease.release();
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
const interactive = () => !!process.stdin.isTTY && !!process.stdout.isTTY;
|
|
214
|
+
function printWaiting(pr, runId, file, problems) {
|
|
215
|
+
console.log(`\nRun ${runId} is waiting for your answers.\n File: ${file}`);
|
|
216
|
+
for (const p of problems)
|
|
217
|
+
console.log(` - ${p}`);
|
|
218
|
+
console.log(`Answer with: agent-lyceum answer ${runId} -p ${pr.name} (or edit the file, then: agent-lyceum resume ${runId} -p ${pr.name})`);
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Keep going while the run stops to ask and the questions can be settled right now: by --assume-defaults, or (in a terminal)
|
|
222
|
+
* by opening the editor. Otherwise print where to answer and exit 3.
|
|
223
|
+
*/
|
|
224
|
+
async function driveRun(pr, runDir, first, assumeDefaults) {
|
|
225
|
+
let summary = first;
|
|
226
|
+
while (summary.outcome === "waiting") {
|
|
227
|
+
let prep = prepareAnswers(runDir, { assumeDefaults });
|
|
228
|
+
while (!prep.complete && interactive()) {
|
|
229
|
+
console.log(`\nRun ${summary.runId} needs your answers (${prep.path}).`);
|
|
230
|
+
openInEditor(prep.path);
|
|
231
|
+
prep = prepareAnswers(runDir, {});
|
|
232
|
+
if (prep.complete)
|
|
233
|
+
break;
|
|
234
|
+
console.log("Some answers are missing or invalid:");
|
|
235
|
+
for (const p of prep.problems)
|
|
236
|
+
console.log(` - ${p}`);
|
|
237
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
238
|
+
const again = (await rl.question("Edit again? [Y/n] ")).trim().toLowerCase();
|
|
239
|
+
rl.close();
|
|
240
|
+
if (again === "n" || again === "no")
|
|
241
|
+
break;
|
|
242
|
+
}
|
|
243
|
+
if (!prep.complete) {
|
|
244
|
+
printRun(summary, runDir);
|
|
245
|
+
printWaiting(pr, summary.runId, prep.path, prep.problems);
|
|
246
|
+
process.exit(3);
|
|
247
|
+
}
|
|
248
|
+
summary = await continueRun(pr, runDir, summary.runId);
|
|
249
|
+
}
|
|
250
|
+
return reportRun(summary, runDir);
|
|
251
|
+
}
|
|
187
252
|
program
|
|
188
253
|
.command("run [task]")
|
|
189
254
|
.description('Start the dispatcher: give the task to the lead. Use "<task>" or --task-file <path>.')
|
|
190
255
|
.option("-p, --project <name>")
|
|
191
256
|
.option("--task-file <path>", "read the task from a file")
|
|
257
|
+
.option("--assume-defaults", "when the run stops to ask, use the suggested answer for every question that has one")
|
|
192
258
|
.action(async (task, opts) => {
|
|
193
259
|
try {
|
|
194
260
|
const pr = loadProject(opts.project);
|
|
@@ -213,7 +279,7 @@ program
|
|
|
213
279
|
cancel.dispose();
|
|
214
280
|
lease.release();
|
|
215
281
|
}
|
|
216
|
-
|
|
282
|
+
await driveRun(pr, runDir, summary, !!opts.assumeDefaults);
|
|
217
283
|
}
|
|
218
284
|
catch (e) {
|
|
219
285
|
fail(e instanceof ConfigError ? e.message : e.message);
|
|
@@ -223,6 +289,7 @@ program
|
|
|
223
289
|
.command("resume [run-id]")
|
|
224
290
|
.description("Continue an interrupted (or failed) run: same run dir, sessions and round count; the task is not re-sent. Without an id, picks the newest run that is not done or running; with an id, continues that run.")
|
|
225
291
|
.option("-p, --project <name>")
|
|
292
|
+
.option("--assume-defaults", "if the run waits for answers, use the suggested answer for every unanswered question that has one")
|
|
226
293
|
.action(async (runId, opts) => {
|
|
227
294
|
try {
|
|
228
295
|
const pr = loadProject(opts.project);
|
|
@@ -258,20 +325,51 @@ program
|
|
|
258
325
|
}
|
|
259
326
|
if (state.rounds >= pr.dispatcher.max_rounds)
|
|
260
327
|
fail(`Run ${state.run_id} used ${state.rounds}/${pr.dispatcher.max_rounds} rounds; raise dispatcher.max_rounds in project.yaml first.`);
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
// Read the state again under the lock: another process may have changed it while we were checking.
|
|
268
|
-
summary = await runTeam({ project: pr, resume: loadRunState(dir), runDir: dir, signal: cancel.signal });
|
|
328
|
+
if (state.end_reason === "waiting") {
|
|
329
|
+
const prep = prepareAnswers(dir, { assumeDefaults: !!opts.assumeDefaults });
|
|
330
|
+
if (!prep.complete) {
|
|
331
|
+
printWaiting(pr, state.run_id, prep.path, prep.problems);
|
|
332
|
+
process.exit(3);
|
|
333
|
+
}
|
|
269
334
|
}
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
335
|
+
const summary = await continueRun(pr, dir, state.run_id);
|
|
336
|
+
await driveRun(pr, dir, summary, !!opts.assumeDefaults);
|
|
337
|
+
}
|
|
338
|
+
catch (e) {
|
|
339
|
+
fail(e instanceof ConfigError ? e.message : e.message);
|
|
340
|
+
}
|
|
341
|
+
});
|
|
342
|
+
program
|
|
343
|
+
.command("answer <run-id>")
|
|
344
|
+
.description("Answer the questions a waiting run asked: opens ask-reply.md in $EDITOR, checks the answers, then continues the run. With --no-edit it only checks a file you already edited.")
|
|
345
|
+
.option("-p, --project <name>")
|
|
346
|
+
.option("--no-edit", "do not open the editor; check the file as it is")
|
|
347
|
+
.option("--assume-defaults", "use the suggested answer for every unanswered question that has one")
|
|
348
|
+
.action(async (runId, opts) => {
|
|
349
|
+
try {
|
|
350
|
+
assertName("run", runId);
|
|
351
|
+
const pr = loadProject(opts.project);
|
|
352
|
+
const dir = path.join(pr.paths.runs, runId);
|
|
353
|
+
if (!fs.existsSync(path.join(dir, "state.json")))
|
|
354
|
+
fail(`Run "${runId}" not found in ${pr.paths.runs}.`);
|
|
355
|
+
const state = loadRunState(dir);
|
|
356
|
+
if (state.end_reason !== "waiting")
|
|
357
|
+
fail(`Run ${runId} is not waiting for answers (${state.end_reason ?? "not finished"}).`);
|
|
358
|
+
if (opts.edit)
|
|
359
|
+
openInEditor(prepareAnswers(dir, {}).path);
|
|
360
|
+
const prep = prepareAnswers(dir, { assumeDefaults: !!opts.assumeDefaults });
|
|
361
|
+
if (!prep.complete) {
|
|
362
|
+
printWaiting(pr, runId, prep.path, prep.problems);
|
|
363
|
+
process.exit(3);
|
|
273
364
|
}
|
|
274
|
-
|
|
365
|
+
const res = validateProject(pr);
|
|
366
|
+
for (const i of res.issues)
|
|
367
|
+
console.error(`${i.level === "error" ? "ERROR" : "warn "} ${i.message}`);
|
|
368
|
+
if (!res.ok)
|
|
369
|
+
fail("Configuration is invalid; fix the errors above (see `agent-lyceum validate`).");
|
|
370
|
+
await preflight(pr);
|
|
371
|
+
const summary = await continueRun(pr, dir, runId);
|
|
372
|
+
await driveRun(pr, dir, summary, !!opts.assumeDefaults);
|
|
275
373
|
}
|
|
276
374
|
catch (e) {
|
|
277
375
|
fail(e instanceof ConfigError ? e.message : e.message);
|
|
@@ -354,7 +452,7 @@ configCmd
|
|
|
354
452
|
const keys = Object.keys(sources).filter((k) => values[k] !== undefined);
|
|
355
453
|
const w = Math.max(...keys.map((k) => `${k} = ${show(values[k])}`.length));
|
|
356
454
|
for (const k of keys)
|
|
357
|
-
console.log(`${`${k} = ${show(values[k])}`.padEnd(w)} ${where(sources[k])}`);
|
|
455
|
+
console.log(`${`${k} = ${show(values[k])}`.padEnd(w)} ${where(must(sources[k], `source of ${k}`))}`);
|
|
358
456
|
}
|
|
359
457
|
catch (e) {
|
|
360
458
|
fail(e instanceof ConfigError ? e.message : e.message);
|
|
@@ -402,6 +500,94 @@ program
|
|
|
402
500
|
fail(e.message);
|
|
403
501
|
}
|
|
404
502
|
});
|
|
503
|
+
const memoryCmd = program.command("memory").description("Look after the agents' long-term memory");
|
|
504
|
+
function layerOpt(v) {
|
|
505
|
+
if (v === undefined || v === "project")
|
|
506
|
+
return "project";
|
|
507
|
+
if (v === "global")
|
|
508
|
+
return "global";
|
|
509
|
+
return fail(`--layer must be "project" or "global" (task memory is not tidied: \`clear\` deletes it).`);
|
|
510
|
+
}
|
|
511
|
+
memoryCmd
|
|
512
|
+
.command("tidy")
|
|
513
|
+
.description("Have agents tidy their own memory: merge duplicates, fix stale entries, archive what is obsolete (never deletes). Manual only; nothing triggers it automatically.")
|
|
514
|
+
.option("-p, --project <name>")
|
|
515
|
+
.option("--agent <name>", "tidy only this agent (default: every agent)")
|
|
516
|
+
.option("--layer <layer>", "project (default) or global; global memory is shared by every project, so it is never tidied unless you ask")
|
|
517
|
+
.option("--dry-run", "only show the memory sizes and what changed in the project; wake nobody, change nothing")
|
|
518
|
+
.action(async (opts) => {
|
|
519
|
+
try {
|
|
520
|
+
const layer = layerOpt(opts.layer);
|
|
521
|
+
const pr = loadProject(opts.project);
|
|
522
|
+
if (opts.dryRun) {
|
|
523
|
+
const dry = await tidyMemory({ project: pr, layer, agent: opts.agent, dryRun: true });
|
|
524
|
+
if (dry.length === 0)
|
|
525
|
+
console.log(`No agent has ${layer} memory configured. ${layer === "project" ? "The template gives agents global memory only: set `memory.project` for an agent in project.yaml (or use --layer global)." : "Set `memory.global` for an agent."}`);
|
|
526
|
+
return;
|
|
527
|
+
}
|
|
528
|
+
const res = validateProject(pr);
|
|
529
|
+
for (const i of res.issues)
|
|
530
|
+
console.error(`${i.level === "error" ? "ERROR" : "warn "} ${i.message}`);
|
|
531
|
+
if (!res.ok)
|
|
532
|
+
fail("Configuration is invalid; fix the errors above (see `agent-lyceum validate`).");
|
|
533
|
+
await preflight(pr);
|
|
534
|
+
const lease = takeLock(pr, `tidy-${tidyStamp()}`);
|
|
535
|
+
const cancel = cancelOnSignals();
|
|
536
|
+
let results;
|
|
537
|
+
try {
|
|
538
|
+
results = await tidyMemory({ project: pr, layer, agent: opts.agent, signal: cancel.signal });
|
|
539
|
+
}
|
|
540
|
+
finally {
|
|
541
|
+
cancel.dispose();
|
|
542
|
+
lease.release();
|
|
543
|
+
}
|
|
544
|
+
if (results.length === 0)
|
|
545
|
+
console.log(`No agent has ${layer} memory configured. ${layer === "project" ? "The template gives agents global memory only: set `memory.project` for an agent in project.yaml (or tidy global memory with --layer global)." : "Set `memory.global` for an agent."}`);
|
|
546
|
+
for (const r of results) {
|
|
547
|
+
if (r.status === "tidied")
|
|
548
|
+
console.log(` ${r.agent} (${r.layer}): tidied. Archive: ${path.join(r.dir, ".archive", r.archive ?? "")} — undo with: agent-lyceum memory restore ${r.archive} -p ${pr.name} --agent ${r.agent}${r.layer === "global" ? " --layer global" : ""}`);
|
|
549
|
+
else if (r.status === "skipped")
|
|
550
|
+
console.log(` ${r.agent} (${r.layer}): skipped — ${r.problems.join("; ")}`);
|
|
551
|
+
else if (r.status === "failed") {
|
|
552
|
+
console.error(` ${r.agent} (${r.layer}): FAILED, memory restored to how it was`);
|
|
553
|
+
for (const p of r.problems)
|
|
554
|
+
console.error(` - ${p}`);
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
process.exit(results.some((r) => r.status === "failed") ? 1 : 0);
|
|
558
|
+
}
|
|
559
|
+
catch (e) {
|
|
560
|
+
fail(e instanceof ConfigError ? e.message : e.message);
|
|
561
|
+
}
|
|
562
|
+
});
|
|
563
|
+
memoryCmd
|
|
564
|
+
.command("restore <stamp>")
|
|
565
|
+
.description("Move the files of one tidy's archive back (nothing that exists now is overwritten) and re-add them to MEMORY.md")
|
|
566
|
+
.option("-p, --project <name>")
|
|
567
|
+
.requiredOption("--agent <name>")
|
|
568
|
+
.option("--layer <layer>", "project (default) or global")
|
|
569
|
+
.action((stamp, opts) => {
|
|
570
|
+
try {
|
|
571
|
+
const layer = layerOpt(opts.layer);
|
|
572
|
+
const pr = loadProject(opts.project);
|
|
573
|
+
const lease = takeLock(pr, `restore-${tidyStamp()}`);
|
|
574
|
+
try {
|
|
575
|
+
const r = restoreMemory({ project: pr, agent: opts.agent, layer, stamp });
|
|
576
|
+
for (const f of r.restored)
|
|
577
|
+
console.log(` restored ${f}`);
|
|
578
|
+
for (const s of r.skipped)
|
|
579
|
+
console.log(` skipped ${s.rel}: ${s.reason}`);
|
|
580
|
+
if (!r.restored.length && !r.skipped.length)
|
|
581
|
+
console.log(`Archive ${stamp} holds no files to restore.`);
|
|
582
|
+
}
|
|
583
|
+
finally {
|
|
584
|
+
lease.release();
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
catch (e) {
|
|
588
|
+
fail(e.message);
|
|
589
|
+
}
|
|
590
|
+
});
|
|
405
591
|
function useColor() {
|
|
406
592
|
if (process.env.NO_COLOR)
|
|
407
593
|
return false;
|
package/dist/config.d.ts
CHANGED
|
@@ -18,6 +18,8 @@ export interface ResolvedAgent {
|
|
|
18
18
|
resume: boolean;
|
|
19
19
|
canMessage: "all" | string[];
|
|
20
20
|
canEditAgentMd: boolean;
|
|
21
|
+
/** May send `type: ask` mail to the user (the run then waits for the answer). */
|
|
22
|
+
canAskUser: boolean;
|
|
21
23
|
owns: string[];
|
|
22
24
|
/** Where each field's value came from (for `validate` output). */
|
|
23
25
|
sources: Record<string, Source>;
|
|
@@ -44,45 +46,48 @@ export declare function loadGlobal(home: string): {
|
|
|
44
46
|
agents: Record<string, {
|
|
45
47
|
runtime?: "claude-code" | "codex" | undefined;
|
|
46
48
|
model?: string | undefined;
|
|
47
|
-
effort?: "
|
|
49
|
+
effort?: "high" | "low" | "max" | "medium" | "minimal" | "xhigh" | undefined;
|
|
48
50
|
agent_md?: string | undefined;
|
|
49
51
|
memory?: {
|
|
50
52
|
global?: string | undefined;
|
|
51
53
|
project?: string | undefined;
|
|
52
54
|
} | undefined;
|
|
53
55
|
resume?: boolean | undefined;
|
|
54
|
-
can_message?: string[] |
|
|
56
|
+
can_message?: "all" | string[] | undefined;
|
|
55
57
|
can_edit_agent_md?: boolean | undefined;
|
|
58
|
+
can_ask_user?: boolean | undefined;
|
|
56
59
|
owns?: string[] | undefined;
|
|
57
60
|
}>;
|
|
58
61
|
};
|
|
59
62
|
export declare function loadProjectRaw(home: string, name: string): {
|
|
63
|
+
dir: string;
|
|
64
|
+
team: {
|
|
65
|
+
lead: string;
|
|
66
|
+
};
|
|
67
|
+
dispatcher: {
|
|
68
|
+
max_rounds?: number | undefined;
|
|
69
|
+
max_parallel?: number | undefined;
|
|
70
|
+
wake_timeout_sec?: number | undefined;
|
|
71
|
+
retry?: number | undefined;
|
|
72
|
+
strict?: boolean | undefined;
|
|
73
|
+
workspace_mode?: "auto" | "shared" | "worktree" | undefined;
|
|
74
|
+
log_max_bytes?: number | undefined;
|
|
75
|
+
};
|
|
60
76
|
agents: Record<string, {
|
|
61
77
|
runtime?: "claude-code" | "codex" | undefined;
|
|
62
78
|
model?: string | undefined;
|
|
63
|
-
effort?: "
|
|
79
|
+
effort?: "high" | "low" | "max" | "medium" | "minimal" | "xhigh" | undefined;
|
|
64
80
|
agent_md?: string | undefined;
|
|
65
81
|
memory?: {
|
|
66
82
|
global?: string | undefined;
|
|
67
83
|
project?: string | undefined;
|
|
68
84
|
} | undefined;
|
|
69
85
|
resume?: boolean | undefined;
|
|
70
|
-
can_message?: string[] |
|
|
86
|
+
can_message?: "all" | string[] | undefined;
|
|
71
87
|
can_edit_agent_md?: boolean | undefined;
|
|
88
|
+
can_ask_user?: boolean | undefined;
|
|
72
89
|
owns?: string[] | undefined;
|
|
73
90
|
}>;
|
|
74
|
-
dir: string;
|
|
75
|
-
team: {
|
|
76
|
-
lead: string;
|
|
77
|
-
};
|
|
78
|
-
dispatcher: {
|
|
79
|
-
strict?: boolean | undefined;
|
|
80
|
-
max_rounds?: number | undefined;
|
|
81
|
-
max_parallel?: number | undefined;
|
|
82
|
-
wake_timeout_sec?: number | undefined;
|
|
83
|
-
retry?: number | undefined;
|
|
84
|
-
workspace_mode?: "shared" | "auto" | "worktree" | undefined;
|
|
85
|
-
};
|
|
86
91
|
};
|
|
87
92
|
export declare function resolveProject(home: string, name: string): ResolvedProject;
|
|
88
93
|
/** Registered projects: name -> repo dir (best effort; skips broken configs). */
|
package/dist/config.js
CHANGED
|
@@ -112,21 +112,31 @@ export function resolveProject(home, name) {
|
|
|
112
112
|
let runtime = pl.runtime;
|
|
113
113
|
if (runtime !== undefined)
|
|
114
114
|
sources.runtime = "project";
|
|
115
|
-
else
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
115
|
+
else {
|
|
116
|
+
const candidates = [
|
|
117
|
+
[inferRuntime(pl.model), "model"],
|
|
118
|
+
[gl.runtime, "global"],
|
|
119
|
+
[inferRuntime(gl.model), "model"],
|
|
120
|
+
];
|
|
121
|
+
for (const [r, from] of candidates) {
|
|
122
|
+
if (!r)
|
|
123
|
+
continue;
|
|
124
|
+
runtime = r;
|
|
125
|
+
sources.runtime = from;
|
|
126
|
+
break;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
121
129
|
const isLead = agentName === lead;
|
|
122
130
|
const resume = pick("resume");
|
|
123
131
|
const canMessage = pick("can_message");
|
|
124
132
|
const canEdit = pick("can_edit_agent_md");
|
|
133
|
+
const canAsk = pick("can_ask_user");
|
|
125
134
|
const owns = pick("owns");
|
|
126
135
|
for (const [k, v] of [
|
|
127
136
|
["resume", resume],
|
|
128
137
|
["can_message", canMessage],
|
|
129
138
|
["can_edit_agent_md", canEdit],
|
|
139
|
+
["can_ask_user", canAsk],
|
|
130
140
|
["owns", owns],
|
|
131
141
|
]) {
|
|
132
142
|
if (v === undefined)
|
|
@@ -142,6 +152,7 @@ export function resolveProject(home, name) {
|
|
|
142
152
|
resume: resume ?? isLead,
|
|
143
153
|
canMessage: canMessage ?? (isLead ? "all" : [lead]),
|
|
144
154
|
canEditAgentMd: canEdit ?? isLead,
|
|
155
|
+
canAskUser: canAsk ?? isLead,
|
|
145
156
|
owns: owns ?? [],
|
|
146
157
|
sources,
|
|
147
158
|
};
|
|
@@ -190,7 +201,7 @@ export function findProjectForCwd(home, cwd) {
|
|
|
190
201
|
}
|
|
191
202
|
return best?.name;
|
|
192
203
|
}
|
|
193
|
-
const AGENT_FIELDS = ["runtime", "model", "effort", "agent_md", "memory.global", "memory.project", "resume", "can_message", "can_edit_agent_md", "owns"];
|
|
204
|
+
const AGENT_FIELDS = ["runtime", "model", "effort", "agent_md", "memory.global", "memory.project", "resume", "can_message", "can_edit_agent_md", "can_ask_user", "owns"];
|
|
194
205
|
/** The resolved project plus, for every setting, the file and key it was taken from (or "default"). */
|
|
195
206
|
export function resolveProjectWithSources(home, name) {
|
|
196
207
|
const project = resolveProject(home, name);
|
|
@@ -247,6 +258,7 @@ export function flattenResolved(project) {
|
|
|
247
258
|
v[`${p}.resume`] = a.resume;
|
|
248
259
|
v[`${p}.can_message`] = a.canMessage;
|
|
249
260
|
v[`${p}.can_edit_agent_md`] = a.canEditAgentMd;
|
|
261
|
+
v[`${p}.can_ask_user`] = a.canAskUser;
|
|
250
262
|
v[`${p}.owns`] = a.owns;
|
|
251
263
|
}
|
|
252
264
|
return v;
|
package/dist/dispatcher.d.ts
CHANGED
|
@@ -1,37 +1,6 @@
|
|
|
1
|
-
import { type
|
|
2
|
-
import
|
|
3
|
-
import type { RunOutcome } from "./schema.js";
|
|
4
|
-
import { newRunId, type EndReason, type RunState } from "./run-store.js";
|
|
5
|
-
import { type PreparedTask } from "./task.js";
|
|
1
|
+
import { type RunOptions, type RunSummary } from "./run-session.js";
|
|
2
|
+
import { newRunId } from "./run-store.js";
|
|
6
3
|
export { newRunId };
|
|
7
4
|
export type { ActiveWake, EndReason, RunState, SentTopic, WakeRecord, WakeTopic } from "./run-store.js";
|
|
8
|
-
export
|
|
9
|
-
/** How many times a non-conforming `done` goes back to the lead before it is accepted as `partial`. */
|
|
10
|
-
export declare const MAX_DONE_REJECTIONS = 2;
|
|
11
|
-
export interface RunSummary {
|
|
12
|
-
runId: string;
|
|
13
|
-
runDir: string;
|
|
14
|
-
rounds: number;
|
|
15
|
-
endReason: EndReason;
|
|
16
|
-
/** How the work turned out; only "completed" means success. */
|
|
17
|
-
outcome: RunOutcome;
|
|
18
|
-
outcomeNote?: string;
|
|
19
|
-
verification?: string;
|
|
20
|
-
doneMessage?: {
|
|
21
|
-
subject: string;
|
|
22
|
-
body: string;
|
|
23
|
-
};
|
|
24
|
-
}
|
|
25
|
-
export interface RunOptions {
|
|
26
|
-
project: ResolvedProject;
|
|
27
|
-
/** The task for a new run; omit when resuming. */
|
|
28
|
-
task?: PreparedTask;
|
|
29
|
-
/** Continue this earlier run (same run dir, sessions and counters) instead of starting a new one. */
|
|
30
|
-
resume?: RunState;
|
|
31
|
-
runDir: string;
|
|
32
|
-
invoker?: Invoker;
|
|
33
|
-
/** Stop the run when this fires: running agents are killed, unread mail is kept, the run ends `cancelled` and can be resumed. */
|
|
34
|
-
signal?: AbortSignal;
|
|
35
|
-
log?: (line: string) => void;
|
|
36
|
-
}
|
|
5
|
+
export { MAX_DONE_REJECTIONS, RESULT_FILE, type RunOptions, type RunSummary } from "./run-session.js";
|
|
37
6
|
export declare function runTeam(opts: RunOptions): Promise<RunSummary>;
|