agent-lyceum 0.4.14 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -20
- package/README.zh-TW.md +17 -20
- package/dist/assert.d.ts +2 -0
- package/dist/assert.js +6 -0
- package/dist/cli.js +7 -2
- package/dist/config.d.ts +17 -16
- package/dist/config.js +14 -6
- package/dist/dispatcher.d.ts +3 -34
- package/dist/dispatcher.js +4 -464
- 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 +2 -0
- package/dist/mailbox.js +10 -5
- 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.js +1 -1
- 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 +26 -0
- package/dist/run-session.d.ts +82 -0
- package/dist/run-session.js +505 -0
- package/dist/run-store.js +3 -3
- package/dist/scaffold.js +1 -0
- package/dist/schema.d.ts +67 -237
- package/dist/schema.js +4 -2
- package/dist/status.js +19 -13
- package/dist/validate.js +12 -8
- package/dist/worktree.js +7 -6
- package/package.json +14 -8
package/dist/mailbox.js
CHANGED
|
@@ -6,6 +6,7 @@ import { formatWarning, missingSections, parseSteps } from "./format.js";
|
|
|
6
6
|
import { atomicWrite } from "./fs-util.js";
|
|
7
7
|
import { deterministicId, fault, RouteJournal, sourceIdOf } from "./message-store.js";
|
|
8
8
|
import { inboxDir, outboxDir } from "./policy.js";
|
|
9
|
+
import { must } from "./assert.js";
|
|
9
10
|
export { atomicWrite };
|
|
10
11
|
export const MESSAGE_TYPES = ["task", "reply", "failure", "done"];
|
|
11
12
|
export function newId() {
|
|
@@ -25,17 +26,17 @@ export function parseRaw(text) {
|
|
|
25
26
|
const m = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/.exec(text);
|
|
26
27
|
if (!m)
|
|
27
28
|
throw new Error("missing frontmatter (--- ... ---)");
|
|
28
|
-
const data = YAML.parse(m[1]);
|
|
29
|
+
const data = YAML.parse(m[1] ?? "");
|
|
29
30
|
if (!data || typeof data !== "object" || Array.isArray(data))
|
|
30
31
|
throw new Error("frontmatter is not a mapping");
|
|
31
|
-
return { data: data, body: m[2].trim() };
|
|
32
|
+
return { data: data, body: (m[2] ?? "").trim() };
|
|
32
33
|
}
|
|
33
34
|
export function ensureProjectDirs(project) {
|
|
34
35
|
fs.mkdirSync(path.join(project.paths.shared, "common"), { recursive: true });
|
|
35
36
|
for (const name of Object.keys(project.agents)) {
|
|
36
37
|
fs.mkdirSync(path.join(inboxDir(project, name), "read"), { recursive: true });
|
|
37
38
|
fs.mkdirSync(path.join(outboxDir(project, name), "rejected"), { recursive: true });
|
|
38
|
-
const a = project.agents[name];
|
|
39
|
+
const a = must(project.agents[name], `agent ${name}`);
|
|
39
40
|
for (const m of [a.memory.global, a.memory.project, a.memory.task])
|
|
40
41
|
if (m)
|
|
41
42
|
fs.mkdirSync(m, { recursive: true });
|
|
@@ -109,15 +110,19 @@ export function readMessage(file) {
|
|
|
109
110
|
const meta = data;
|
|
110
111
|
return { meta, body, file };
|
|
111
112
|
}
|
|
112
|
-
|
|
113
|
+
/** Paths of the unread messages in an agent's inbox, oldest name first. Does not open them. */
|
|
114
|
+
export function unreadFiles(project, agent) {
|
|
113
115
|
const dir = inboxDir(project, agent);
|
|
114
116
|
if (!fs.existsSync(dir))
|
|
115
117
|
return [];
|
|
116
|
-
|
|
118
|
+
return fs
|
|
117
119
|
.readdirSync(dir, { withFileTypes: true })
|
|
118
120
|
.filter((e) => e.isFile() && e.name.endsWith(".md") && !e.name.startsWith("."))
|
|
119
121
|
.map((e) => path.join(dir, e.name))
|
|
120
122
|
.sort();
|
|
123
|
+
}
|
|
124
|
+
export function listUnread(project, agent) {
|
|
125
|
+
const files = unreadFiles(project, agent);
|
|
121
126
|
const out = [];
|
|
122
127
|
for (const f of files) {
|
|
123
128
|
try {
|
package/dist/policy.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { spawnSync } from "node:child_process";
|
|
2
1
|
import fs from "node:fs";
|
|
3
2
|
import path from "node:path";
|
|
3
|
+
import { findOnPath } from "./fs-util.js";
|
|
4
4
|
import { globStaticPrefix } from "./paths.js";
|
|
5
5
|
export function commonFile(project) {
|
|
6
6
|
return path.join(project.paths.shared, "common", "COMMON.md");
|
|
@@ -17,7 +17,7 @@ export function repoInstructionFiles(project) {
|
|
|
17
17
|
export function memoryDirs(a) {
|
|
18
18
|
return [a.memory.global, a.memory.project, a.memory.task].filter((x) => !!x);
|
|
19
19
|
}
|
|
20
|
-
const RUN_ENTRIES = ["state.json", "log.jsonl", "result.md", "task.md", "snapshots", "agents", "mail"];
|
|
20
|
+
const RUN_ENTRIES = ["state.json", "log.jsonl", "log.1.jsonl", "result.md", "task.md", "snapshots", "violations", "agents", "mail"];
|
|
21
21
|
const MAIL_ENTRIES = ["inbox", "claims", "attempts", "journal.jsonl"];
|
|
22
22
|
function entries(dir) {
|
|
23
23
|
try {
|
|
@@ -101,7 +101,7 @@ function osSandboxAvailable() {
|
|
|
101
101
|
if (process.platform === "darwin")
|
|
102
102
|
sandboxProbe = true;
|
|
103
103
|
else if (process.platform === "linux")
|
|
104
|
-
sandboxProbe =
|
|
104
|
+
sandboxProbe = findOnPath("bwrap");
|
|
105
105
|
else
|
|
106
106
|
sandboxProbe = false;
|
|
107
107
|
return sandboxProbe;
|
package/dist/process-runner.js
CHANGED
|
@@ -125,8 +125,7 @@ export function runInvocation(inv, opts) {
|
|
|
125
125
|
return;
|
|
126
126
|
let text = partial + chunk.toString("utf8");
|
|
127
127
|
partial = "";
|
|
128
|
-
let nl;
|
|
129
|
-
while ((nl = text.indexOf("\n")) >= 0) {
|
|
128
|
+
for (let nl = text.indexOf("\n"); nl >= 0; nl = text.indexOf("\n")) {
|
|
130
129
|
const line = text.slice(0, nl).replace(/\r$/, "");
|
|
131
130
|
text = text.slice(nl + 1);
|
|
132
131
|
if (skipping)
|
package/dist/project-lock.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ export interface LockInfo {
|
|
|
3
3
|
token: string;
|
|
4
4
|
hostname: string;
|
|
5
5
|
pid: number;
|
|
6
|
-
/**
|
|
6
|
+
/** Start time of `pid` (see `pidStartFrom`), to tell the holder from a different process that reused the pid. */
|
|
7
7
|
pid_started?: string;
|
|
8
8
|
run_id: string;
|
|
9
9
|
acquired_at: string;
|
|
@@ -18,6 +18,15 @@ export interface ProjectLease {
|
|
|
18
18
|
export interface LockOptions {
|
|
19
19
|
heartbeatMs?: number;
|
|
20
20
|
}
|
|
21
|
+
/** Field 22 (starttime, in clock ticks) of a /proc/<pid>/stat line. The command name (field 2) may contain spaces and ")". */
|
|
22
|
+
export declare function parseProcStat(text: string): string | undefined;
|
|
23
|
+
export interface PidStartIo {
|
|
24
|
+
platform: NodeJS.Platform;
|
|
25
|
+
readFile(p: string): string | undefined;
|
|
26
|
+
ps(pid: number): string | undefined;
|
|
27
|
+
}
|
|
28
|
+
/** A start time that identifies one process: `proc:<ticks>` on Linux, the `ps` text elsewhere or when /proc cannot be read. */
|
|
29
|
+
export declare function pidStartFrom(pid: number, io: PidStartIo): string | undefined;
|
|
21
30
|
/** The lock of a project (`root` = the project's home directory), if one is held and readable. */
|
|
22
31
|
export declare function inspectProjectLock(root: string): LockInfo | undefined;
|
|
23
32
|
/**
|
package/dist/project-lock.js
CHANGED
|
@@ -7,11 +7,46 @@ import { atomicWrite } from "./fs-util.js";
|
|
|
7
7
|
export const LOCK_FILE = "lock.json";
|
|
8
8
|
const RECLAIM_FILE = "lock.json.reclaim";
|
|
9
9
|
const HEARTBEAT_MS = 5000;
|
|
10
|
+
/** Field 22 (starttime, in clock ticks) of a /proc/<pid>/stat line. The command name (field 2) may contain spaces and ")". */
|
|
11
|
+
export function parseProcStat(text) {
|
|
12
|
+
const end = text.lastIndexOf(")");
|
|
13
|
+
if (end < 0)
|
|
14
|
+
return undefined;
|
|
15
|
+
const fields = text.slice(end + 2).split(" "); // fields[0] is field 3 (state), so field 22 is fields[19]
|
|
16
|
+
const v = fields[19];
|
|
17
|
+
return v && /^\d+$/.test(v) ? v : undefined;
|
|
18
|
+
}
|
|
19
|
+
/** A start time that identifies one process: `proc:<ticks>` on Linux, the `ps` text elsewhere or when /proc cannot be read. */
|
|
20
|
+
export function pidStartFrom(pid, io) {
|
|
21
|
+
if (io.platform === "linux") {
|
|
22
|
+
const text = io.readFile(`/proc/${pid}/stat`);
|
|
23
|
+
const ticks = text ? parseProcStat(text) : undefined;
|
|
24
|
+
if (ticks)
|
|
25
|
+
return `proc:${ticks}`;
|
|
26
|
+
}
|
|
27
|
+
return io.ps(pid);
|
|
28
|
+
}
|
|
29
|
+
const realIo = {
|
|
30
|
+
platform: process.platform,
|
|
31
|
+
readFile: (p) => {
|
|
32
|
+
try {
|
|
33
|
+
return fs.readFileSync(p, "utf8");
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
ps: (pid) => {
|
|
40
|
+
const r = spawnSync("ps", ["-o", "lstart=", "-p", String(pid)], { encoding: "utf8" });
|
|
41
|
+
const out = r.status === 0 ? r.stdout.trim() : "";
|
|
42
|
+
return out || undefined;
|
|
43
|
+
},
|
|
44
|
+
};
|
|
10
45
|
function pidStart(pid) {
|
|
11
|
-
|
|
12
|
-
const out = r.status === 0 ? r.stdout.trim() : "";
|
|
13
|
-
return out || undefined;
|
|
46
|
+
return pidStartFrom(pid, realIo);
|
|
14
47
|
}
|
|
48
|
+
/** Two start times can only be compared when they were taken the same way. */
|
|
49
|
+
const sameKind = (a, b) => a.startsWith("proc:") === b.startsWith("proc:");
|
|
15
50
|
function readLock(root) {
|
|
16
51
|
let text;
|
|
17
52
|
try {
|
|
@@ -49,7 +84,7 @@ export function lockHolderAlive(info) {
|
|
|
49
84
|
}
|
|
50
85
|
if (info.pid_started) {
|
|
51
86
|
const now = pidStart(info.pid);
|
|
52
|
-
if (now && now !== info.pid_started)
|
|
87
|
+
if (now && sameKind(now, info.pid_started) && now !== info.pid_started)
|
|
53
88
|
return false;
|
|
54
89
|
}
|
|
55
90
|
return true;
|
package/dist/prompt.js
CHANGED
|
@@ -32,7 +32,7 @@ export function buildSystemPrompt(project, agent) {
|
|
|
32
32
|
lines.push(readPersona(agent), "", "---", "", "# Team protocol (injected by agent-lyceum)", "");
|
|
33
33
|
lines.push(`You are **${agent.name}**, a member of the team "${project.name}". You work in the repository \`${repoDirFor(project, agent.name)}\`.`, ...(project.workspaces?.[agent.name]
|
|
34
34
|
? [
|
|
35
|
-
`That directory is your own isolated git worktree (branch \`${project.workspaces[agent.name]
|
|
35
|
+
`That directory is your own isolated git worktree (branch \`${project.workspaces?.[agent.name]?.branch}\`): teammates cannot see your changes while you work, and you cannot see theirs. Do not commit, push, switch branches or touch any other checkout; the dispatcher collects your changes from this directory after you finish.`,
|
|
36
36
|
]
|
|
37
37
|
: []), `The team lead is **${project.lead}**${isLead ? " (that is you)" : ""}. Teammates: ${mates.join(", ") || "(none)"}.`, "", "## Sending mail", "You cannot talk to teammates directly. Mail you receive is shown in your prompt. To send mail, create a Markdown file", `(any name ending in \`.md\`) directly inside \`${outbox}\` with this exact shape:`, "", "```", "---", "to: <agent-name>", "type: reply # task | reply" + (isLead ? " | done" : ""), "subject: <short subject>", "reply_to: <message id> # optional", "---", "", "<body>", "```", "", `You may send to: ${recipients.join(", ") || "(nobody)"}.`, isLead
|
|
38
38
|
? `When the whole job is finished, send a message with \`type: done\` (no \`to\` needed). That ends the run. A summary in your reply text is never delivered and does not end the run: after the last teammate reply is verified you MUST send \`done\` (or a \`task\` for the remaining work), or the run stops as "idle" with no final report.`
|
package/dist/run-cleanup.js
CHANGED
|
@@ -5,6 +5,7 @@ import { assertName } from "./paths.js";
|
|
|
5
5
|
import { loadRunState } from "./run-store.js";
|
|
6
6
|
import { runIsAlive } from "./status.js";
|
|
7
7
|
import { deleteSnapshotRefs, inspectWorkspace, listSnapshotRefs, listWorkspaceAgents, removeAgentWorkspace } from "./worktree.js";
|
|
8
|
+
import { must } from "./assert.js";
|
|
8
9
|
const journalDir = (p) => path.join(p.paths.root, "cleanup");
|
|
9
10
|
const journalFile = (p, runId) => path.join(journalDir(p), `${runId}.json`);
|
|
10
11
|
const lexists = (p) => {
|
|
@@ -58,7 +59,7 @@ export function planRunCleanup(project, runId, opts = {}) {
|
|
|
58
59
|
if (lexists(mem))
|
|
59
60
|
plan.items.push({ kind: "task-memory", path: mem, label: `task memory ${mem}` });
|
|
60
61
|
if (hasRun || lexists(runDir))
|
|
61
|
-
plan.items.push({ kind: "run", path: runDir, label: `run directory ${runDir} (state, log, result, snapshots, mailboxes)` });
|
|
62
|
+
plan.items.push({ kind: "run", path: runDir, label: `run directory ${runDir} (state, log, result, snapshots, kept violations, mailboxes)` });
|
|
62
63
|
return plan;
|
|
63
64
|
}
|
|
64
65
|
/** Remove a path without ever following a symlink, and only inside the project home. */
|
|
@@ -91,7 +92,7 @@ export function executeRunCleanup(plan, hooks = {}) {
|
|
|
91
92
|
const journal = { started_at: new Date().toISOString(), items: plan.items.map((i) => ({ ...i, done: false })) };
|
|
92
93
|
record(journal);
|
|
93
94
|
for (let n = 0; n < plan.items.length; n++) {
|
|
94
|
-
const item = plan.items[n];
|
|
95
|
+
const item = must(plan.items[n], "cleanup item");
|
|
95
96
|
try {
|
|
96
97
|
if (item.kind === "workspace") {
|
|
97
98
|
removeAgentWorkspace(project, runId, item.agent);
|
|
@@ -109,7 +110,7 @@ export function executeRunCleanup(plan, hooks = {}) {
|
|
|
109
110
|
return report; // keep the journal and the run directory: the next `clear` picks up here
|
|
110
111
|
}
|
|
111
112
|
report.done.push(item);
|
|
112
|
-
journal.items[n].done = true;
|
|
113
|
+
must(journal.items[n], "journal item").done = true;
|
|
113
114
|
record(journal);
|
|
114
115
|
hooks.afterItem?.(item);
|
|
115
116
|
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Append-only run log, one JSON object per line. When the next line would push the file past `maxBytes`
|
|
3
|
+
* (0 = no limit) the file becomes `<name>.1.jsonl`, replacing an older one, and a new file starts.
|
|
4
|
+
*/
|
|
5
|
+
export declare function createRunLog(file: string, maxBytes: number): (event: string, data?: Record<string, unknown>) => void;
|
package/dist/run-log.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
/**
|
|
3
|
+
* Append-only run log, one JSON object per line. When the next line would push the file past `maxBytes`
|
|
4
|
+
* (0 = no limit) the file becomes `<name>.1.jsonl`, replacing an older one, and a new file starts.
|
|
5
|
+
*/
|
|
6
|
+
export function createRunLog(file, maxBytes) {
|
|
7
|
+
let size = fs.existsSync(file) ? fs.statSync(file).size : 0;
|
|
8
|
+
return (event, data = {}) => {
|
|
9
|
+
const line = JSON.stringify({ ts: new Date().toISOString(), event, ...data }) + "\n";
|
|
10
|
+
const bytes = Buffer.byteLength(line);
|
|
11
|
+
if (maxBytes > 0 && size > 0 && size + bytes > maxBytes) {
|
|
12
|
+
fs.renameSync(file, file.replace(/\.jsonl$/, ".1.jsonl"));
|
|
13
|
+
size = 0;
|
|
14
|
+
}
|
|
15
|
+
fs.appendFileSync(file, line);
|
|
16
|
+
size += bytes;
|
|
17
|
+
};
|
|
18
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { DoneContract } from "./format.js";
|
|
2
|
+
import type { EndReason } from "./run-store.js";
|
|
3
|
+
import type { RunOutcome } from "./schema.js";
|
|
4
|
+
export interface UnintegratedWork {
|
|
5
|
+
agent: string;
|
|
6
|
+
branch: string;
|
|
7
|
+
report: string;
|
|
8
|
+
}
|
|
9
|
+
/** What a lead's `done` amounts to: its declared outcome, unless the contract is broken or member work never reached the repo. */
|
|
10
|
+
export declare function resolveDoneOutcome(contract: DoneContract, rejections: number, unintegrated: UnintegratedWork[]): {
|
|
11
|
+
outcome: RunOutcome;
|
|
12
|
+
note?: string;
|
|
13
|
+
verification?: string;
|
|
14
|
+
};
|
|
15
|
+
/** How a run turned out, from why it stopped (`done` carries the lead's own verdict). */
|
|
16
|
+
export declare function endOutcome(endReason: EndReason, done?: {
|
|
17
|
+
outcome: RunOutcome;
|
|
18
|
+
note?: string;
|
|
19
|
+
}): {
|
|
20
|
+
outcome: RunOutcome;
|
|
21
|
+
note?: string;
|
|
22
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/** What a lead's `done` amounts to: its declared outcome, unless the contract is broken or member work never reached the repo. */
|
|
2
|
+
export function resolveDoneOutcome(contract, rejections, unintegrated) {
|
|
3
|
+
const ok = contract.missing.length === 0;
|
|
4
|
+
let outcome = ok ? (contract.outcome ?? "partial") : "partial";
|
|
5
|
+
let note = ok ? undefined : `Completion contract not met after ${rejections} reminder(s): missing ${contract.missing.join("; ")}. Reported as partial; the lead's report is kept as written.`;
|
|
6
|
+
if (outcome === "completed" && unintegrated.length) {
|
|
7
|
+
outcome = "blocked";
|
|
8
|
+
note = `Not completed: work of ${unintegrated.map((b) => `${b.agent} (branch ${b.branch})`).join(", ")} was never brought into the repo; see ${unintegrated.map((b) => b.report).join(", ")}.`;
|
|
9
|
+
}
|
|
10
|
+
return { outcome, note, verification: contract.verification };
|
|
11
|
+
}
|
|
12
|
+
/** How a run turned out, from why it stopped (`done` carries the lead's own verdict). */
|
|
13
|
+
export function endOutcome(endReason, done) {
|
|
14
|
+
switch (endReason) {
|
|
15
|
+
case "done":
|
|
16
|
+
return { outcome: done?.outcome ?? "partial", note: done?.note };
|
|
17
|
+
case "lead_failed":
|
|
18
|
+
return { outcome: "failed", note: "The lead's wake-up failed." };
|
|
19
|
+
case "cancelled":
|
|
20
|
+
return { outcome: "cancelled", note: "Cancelled before the lead finished. Unread mail was kept; continue with `agent-lyceum resume`." };
|
|
21
|
+
case "idle":
|
|
22
|
+
return { outcome: "partial", note: "All mailboxes were empty but the lead never sent a done message." };
|
|
23
|
+
case "max_rounds":
|
|
24
|
+
return { outcome: "partial", note: "Stopped at the round limit before the lead sent done." };
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { type Invoker } from "./adapters/index.js";
|
|
2
|
+
import type { ResolvedProject } from "./config.js";
|
|
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";
|
|
6
|
+
export { newRunId };
|
|
7
|
+
export type { ActiveWake, EndReason, RunState, SentTopic, WakeRecord, WakeTopic } from "./run-store.js";
|
|
8
|
+
export declare const RESULT_FILE = "result.md";
|
|
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
|
+
}
|
|
37
|
+
export declare class RunSession {
|
|
38
|
+
private readonly opts;
|
|
39
|
+
private readonly resume;
|
|
40
|
+
private readonly invoke;
|
|
41
|
+
private readonly say;
|
|
42
|
+
private readonly cfg;
|
|
43
|
+
private readonly runId;
|
|
44
|
+
private readonly runDir;
|
|
45
|
+
/** The project bound to this run (own memory dirs and mailboxes). */
|
|
46
|
+
private readonly project;
|
|
47
|
+
private readonly wsMode;
|
|
48
|
+
private readonly log;
|
|
49
|
+
private readonly guard;
|
|
50
|
+
private readonly state;
|
|
51
|
+
private endReason;
|
|
52
|
+
private doneMessage;
|
|
53
|
+
/** The lead's done file; it leaves the outbox only after the result and end state are stored. */
|
|
54
|
+
private pendingDoneFile;
|
|
55
|
+
private doneOutcome;
|
|
56
|
+
private doneNote;
|
|
57
|
+
private doneVerification;
|
|
58
|
+
/** Inbox mail each agent in the current batch is working on; it is marked read only after its output is routed. */
|
|
59
|
+
private readonly claims;
|
|
60
|
+
private readonly noSessionNoted;
|
|
61
|
+
/** Git worktrees of the non-lead agents in the batch being woken (empty in shared mode and for the lead). */
|
|
62
|
+
private batchSpaces;
|
|
63
|
+
/** The attempt that produced each batch member's output; committed together with its claim. */
|
|
64
|
+
private readonly attempts;
|
|
65
|
+
constructor(opts: RunOptions);
|
|
66
|
+
private saveState;
|
|
67
|
+
private journal;
|
|
68
|
+
private note;
|
|
69
|
+
private pendingAgents;
|
|
70
|
+
private pickBatch;
|
|
71
|
+
private wake;
|
|
72
|
+
/**
|
|
73
|
+
* A finished member's files go from its worktree into the repo before anyone reads its mail, so the lead
|
|
74
|
+
* sees them. When that cannot be done, nothing is applied, the member's work stays on its branch, and the lead is told.
|
|
75
|
+
*/
|
|
76
|
+
private integrateMember;
|
|
77
|
+
/** After a batch (or, on resume, before the first one): revert guarded edits, route mail, detect done / lead failure. */
|
|
78
|
+
private settle;
|
|
79
|
+
/** On resume: sort out what the interrupted process left behind before anything is woken. */
|
|
80
|
+
private recover;
|
|
81
|
+
run(): Promise<RunSummary>;
|
|
82
|
+
}
|