agent-lyceum 0.13.1 → 0.15.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 +20 -6
- package/README.zh-TW.md +20 -7
- package/dist/adapters/claude.js +1 -1
- package/dist/ask-reply.d.ts +1 -1
- package/dist/ask-reply.js +10 -10
- package/dist/cli.js +112 -43
- package/dist/fs-util.d.ts +8 -0
- package/dist/fs-util.js +36 -0
- package/dist/guard.js +4 -1
- package/dist/mailbox.js +4 -1
- package/dist/open-pr.d.ts +6 -1
- package/dist/open-pr.js +10 -0
- package/dist/policy.d.ts +9 -0
- package/dist/policy.js +34 -3
- package/dist/prompt.d.ts +2 -1
- package/dist/prompt.js +26 -12
- package/dist/run-session.d.ts +10 -0
- package/dist/run-session.js +82 -13
- package/dist/run-store.d.ts +31 -0
- package/dist/run-store.js +38 -0
- package/dist/status.d.ts +7 -0
- package/dist/status.js +12 -5
- package/dist/validate.js +4 -4
- package/dist/worktree.d.ts +10 -0
- package/dist/worktree.js +25 -0
- package/package.json +1 -1
package/dist/status.js
CHANGED
|
@@ -2,7 +2,7 @@ import fs from "node:fs";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { askReplyPath, readAskReply } from "./ask-reply.js";
|
|
4
4
|
import { RESULT_FILE } from "./dispatcher.js";
|
|
5
|
-
import { bindRunProject, loadRunState, outcomeOf } from "./run-store.js";
|
|
5
|
+
import { bindRunProject, loadRunState, outcomeOf, runLabel } from "./run-store.js";
|
|
6
6
|
import { briefOf, digestOf } from "./format.js";
|
|
7
7
|
import { listUnread } from "./mailbox.js";
|
|
8
8
|
import { tidyAdvice } from "./memory-tidy.js";
|
|
@@ -33,6 +33,10 @@ export function latestRun(project) {
|
|
|
33
33
|
export function latestUnfinishedRun(project) {
|
|
34
34
|
return listRuns(project, Infinity).find((r) => r.state.end_reason !== "done" && (r.state.end_reason || !runIsAlive(r.state, project)));
|
|
35
35
|
}
|
|
36
|
+
/** Runs a follow-up text can reopen: the finished ones (end_reason "done"). Newest first. */
|
|
37
|
+
export function reopenCandidates(project) {
|
|
38
|
+
return listRuns(project, Infinity).filter((r) => r.state.end_reason === "done");
|
|
39
|
+
}
|
|
36
40
|
export function listRuns(project, limit) {
|
|
37
41
|
const root = project.paths.runs;
|
|
38
42
|
if (!fs.existsSync(root))
|
|
@@ -182,6 +186,7 @@ export function buildRunReport(run, project, opts = {}) {
|
|
|
182
186
|
}
|
|
183
187
|
return {
|
|
184
188
|
run_id: s.run_id,
|
|
189
|
+
name: s.name,
|
|
185
190
|
dir: run.dir,
|
|
186
191
|
state: s.end_reason === "waiting" ? "waiting" : s.end_reason ? "ended" : live ? "running" : "interrupted",
|
|
187
192
|
live,
|
|
@@ -279,7 +284,7 @@ function summaryBlock(run, now, c) {
|
|
|
279
284
|
const s = run;
|
|
280
285
|
const live = s.live;
|
|
281
286
|
const lines = [
|
|
282
|
-
`${c.bold("執行 " + s
|
|
287
|
+
`${c.bold("執行 " + runLabel(s))} [${stateColor(c, reportLabel(s))}] 第 ${s.rounds}/${s.max_rounds} 輪`,
|
|
283
288
|
`${L("任務:")}${taskText(s)}`,
|
|
284
289
|
];
|
|
285
290
|
const steps = stepsLine(s.steps, c, s.end_reason === "done");
|
|
@@ -317,7 +322,7 @@ function summaryBlock(run, now, c) {
|
|
|
317
322
|
/** What a run is doing / did, one line per wake. */
|
|
318
323
|
function describeRun(s, now, c, limit = Infinity) {
|
|
319
324
|
const lines = [
|
|
320
|
-
`${c.bold("執行 " + s
|
|
325
|
+
`${c.bold("執行 " + runLabel(s))} [${stateColor(c, reportLabel(s))}] 輪次 ${s.rounds}/${s.max_rounds} 輸出 tokens ${tokens(s.output_tokens)}`,
|
|
321
326
|
` 任務:${taskText(s)}`,
|
|
322
327
|
];
|
|
323
328
|
const steps = stepsLine(s.steps, c);
|
|
@@ -383,10 +388,12 @@ export function formatTaskListReport(report, color = false) {
|
|
|
383
388
|
const head = `${c.bold("專案:")}${report.project.name} ${c.dim(`(共 ${runs.length} 個任務)`)}`;
|
|
384
389
|
if (!runs.length)
|
|
385
390
|
return `${head}\n\n無執行紀錄。`;
|
|
386
|
-
const
|
|
391
|
+
const named = runs.some((s) => s.name);
|
|
392
|
+
const header = [...(named ? ["名稱"] : []), "ID", "狀態", "輪次", "目前步驟", "任務"];
|
|
387
393
|
const rows = [header];
|
|
388
394
|
for (const s of runs) {
|
|
389
395
|
rows.push([
|
|
396
|
+
...(named ? [s.name ?? ""] : []),
|
|
390
397
|
s.run_id,
|
|
391
398
|
reportLabel(s),
|
|
392
399
|
`${s.rounds}/${s.max_rounds}`,
|
|
@@ -398,7 +405,7 @@ export function formatTaskListReport(report, color = false) {
|
|
|
398
405
|
const line = (r, ri) => r
|
|
399
406
|
.map((t, i) => {
|
|
400
407
|
const p = i === r.length - 1 ? t : padCols(t, w[i] ?? 0);
|
|
401
|
-
return ri === 0 ? c.bold(p) : i === 1 ? stateColor(c, t) + " ".repeat(Math.max(0, (w[i] ?? 0) - cols(t))) : p;
|
|
408
|
+
return ri === 0 ? c.bold(p) : i === (named ? 2 : 1) ? stateColor(c, t) + " ".repeat(Math.max(0, (w[i] ?? 0) - cols(t))) : p;
|
|
402
409
|
})
|
|
403
410
|
.join(" ")
|
|
404
411
|
.trimEnd();
|
package/dist/validate.js
CHANGED
|
@@ -100,8 +100,8 @@ export function validateProject(project) {
|
|
|
100
100
|
const enforcement = Object.values(project.agents).map((a) => enforcementFor(project, a));
|
|
101
101
|
if (project.dispatcher.strict) {
|
|
102
102
|
for (const e of enforcement) {
|
|
103
|
-
for (const k of ["memory", "agentMd", "otherContext"]) {
|
|
104
|
-
if (e[k] !== "os")
|
|
103
|
+
for (const k of ["memory", "agentMd", "otherContext", "shared"]) {
|
|
104
|
+
if (e[k] !== "os" && !(k === "shared" && e[k] === "n/a"))
|
|
105
105
|
err(`strict: agent "${e.agent}" ${k} protection is "${e[k]}", not OS-enforced.`);
|
|
106
106
|
}
|
|
107
107
|
}
|
|
@@ -109,10 +109,10 @@ export function validateProject(project) {
|
|
|
109
109
|
return { issues, enforcement, ok: !issues.some((i) => i.level === "error") };
|
|
110
110
|
}
|
|
111
111
|
export function formatEnforcement(list) {
|
|
112
|
-
const header = ["agent", "runtime", "memory", "AGENT.md", "others' ctx", "repo CLAUDE/AGENTS", "owns"];
|
|
112
|
+
const header = ["agent", "runtime", "memory", "AGENT.md", "others' ctx", "handoff", "repo CLAUDE/AGENTS", "owns"];
|
|
113
113
|
const rows = [header];
|
|
114
114
|
for (const e of list)
|
|
115
|
-
rows.push([e.agent, e.runtime, e.memory, e.agentMd, e.otherContext, e.repoInstructions, e.owns]);
|
|
115
|
+
rows.push([e.agent, e.runtime, e.memory, e.agentMd, e.otherContext, e.shared, e.repoInstructions, e.owns]);
|
|
116
116
|
const w = header.map((_, i) => Math.max(...rows.map((r) => (r[i] ?? "").length)));
|
|
117
117
|
const lines = rows.map((r) => r.map((c, i) => c.padEnd(w[i] ?? 0)).join(" ").trimEnd());
|
|
118
118
|
lines.splice(1, 0, w.map((n) => "-".repeat(n)).join(" "));
|
package/dist/worktree.d.ts
CHANGED
|
@@ -162,6 +162,16 @@ export interface ResultBranch {
|
|
|
162
162
|
}
|
|
163
163
|
/** Commit the lead's checkout on its branch (once, at the end of a run) and write what changed as a patch. Never touches your repo's working tree. */
|
|
164
164
|
export declare function finishLeadBranch(ws: AgentWorkspace, message: string, patchFile: string): ResultBranch;
|
|
165
|
+
/**
|
|
166
|
+
* A reopened run whose lead branch was already merged into your HEAD starts its next segment on a fresh branch cut from HEAD,
|
|
167
|
+
* so the merged work is not offered again. Only when nothing would be lost: the branch's last committed head (`resultHead`) is
|
|
168
|
+
* in HEAD, the worktree is still at that head, and it holds no uncommitted work and nothing whose state is unknown.
|
|
169
|
+
* Otherwise the worktree and branch are returned unchanged. Your working tree, index and HEAD are never touched.
|
|
170
|
+
*/
|
|
171
|
+
export declare function recutLeadWorkspace(project: ResolvedProject, runId: string, agent: string, ws: AgentWorkspace, resultHead: string | undefined): {
|
|
172
|
+
ws: AgentWorkspace;
|
|
173
|
+
recut: boolean;
|
|
174
|
+
};
|
|
165
175
|
/** Names of the agents that have a worktree for this run. */
|
|
166
176
|
export declare function listWorkspaceAgents(project: ResolvedProject, runId: string): string[];
|
|
167
177
|
/** The snapshot refs (`refs/agent-lyceum/<run>/...`) of a run. */
|
package/dist/worktree.js
CHANGED
|
@@ -686,6 +686,31 @@ export function finishLeadBranch(ws, message, patchFile) {
|
|
|
686
686
|
fs.writeFileSync(patchFile, agentGit(ws, ["diff", "--binary", "--full-index", ws.base, head]) + "\n");
|
|
687
687
|
return { branch: ws.branch, head, base: ws.base, files: files.length, patch: files.length ? patchFile : undefined };
|
|
688
688
|
}
|
|
689
|
+
/**
|
|
690
|
+
* A reopened run whose lead branch was already merged into your HEAD starts its next segment on a fresh branch cut from HEAD,
|
|
691
|
+
* so the merged work is not offered again. Only when nothing would be lost: the branch's last committed head (`resultHead`) is
|
|
692
|
+
* in HEAD, the worktree is still at that head, and it holds no uncommitted work and nothing whose state is unknown.
|
|
693
|
+
* Otherwise the worktree and branch are returned unchanged. Your working tree, index and HEAD are never touched.
|
|
694
|
+
*/
|
|
695
|
+
export function recutLeadWorkspace(project, runId, agent, ws, resultHead) {
|
|
696
|
+
const keep = { ws, recut: false };
|
|
697
|
+
if (!resultHead)
|
|
698
|
+
return keep;
|
|
699
|
+
if (tryGit(toplevel(project.dir), ["merge-base", "--is-ancestor", resultHead, "HEAD"]) === undefined)
|
|
700
|
+
return keep;
|
|
701
|
+
const work = inspectWorkspace(project, runId, agent, ws);
|
|
702
|
+
if (!work || work.unknown || work.dirty.length)
|
|
703
|
+
return keep;
|
|
704
|
+
try {
|
|
705
|
+
if (agentGit(ws, ["rev-parse", "HEAD"]) !== resultHead)
|
|
706
|
+
return keep;
|
|
707
|
+
}
|
|
708
|
+
catch {
|
|
709
|
+
return keep;
|
|
710
|
+
}
|
|
711
|
+
removeAgentWorkspace(project, runId, agent);
|
|
712
|
+
return { ws: prepareAgentWorkspace(project, runId, agent, snapshotHead(project, runId)), recut: true };
|
|
713
|
+
}
|
|
689
714
|
/** Names of the agents that have a worktree for this run. */
|
|
690
715
|
export function listWorkspaceAgents(project, runId) {
|
|
691
716
|
const dir = path.join(project.paths.root, "worktrees", runId);
|