agent-lyceum 0.18.0 → 0.20.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 +1 -0
- package/README.zh-TW.md +1 -0
- package/dist/adapters/claude.d.ts +12 -0
- package/dist/adapters/claude.js +38 -1
- package/dist/adapters/codex.d.ts +7 -0
- package/dist/adapters/codex.js +42 -1
- package/dist/adapters/types.d.ts +5 -0
- package/dist/call.d.ts +5 -0
- package/dist/call.js +34 -11
- package/dist/cli.js +3 -0
- package/dist/config.d.ts +26 -1
- package/dist/config.js +53 -3
- package/dist/doctor.d.ts +1 -1
- package/dist/doctor.js +14 -5
- package/dist/fallback.d.ts +111 -0
- package/dist/fallback.js +157 -0
- package/dist/policy.d.ts +2 -0
- package/dist/policy.js +4 -0
- package/dist/process-runner.d.ts +10 -0
- package/dist/process-runner.js +126 -14
- package/dist/run-outcome.d.ts +3 -11
- package/dist/run-outcome.js +5 -0
- package/dist/run-session.d.ts +8 -6
- package/dist/run-session.js +164 -83
- package/dist/run-store.d.ts +31 -6
- package/dist/run-store.js +23 -1
- package/dist/schema.d.ts +105 -0
- package/dist/schema.js +14 -0
- package/dist/status.d.ts +16 -7
- package/dist/status.js +49 -1
- package/dist/validate.js +36 -0
- package/package.json +1 -1
package/dist/status.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type ActiveWake, type EndReason, type RunState, type WakeRecord } from "./run-store.js";
|
|
1
|
+
import { type ActiveWake, type EndReason, type RunFailure, type RunState, type WakeRecord } from "./run-store.js";
|
|
2
2
|
import type { RunOutcome } from "./schema.js";
|
|
3
3
|
import type { ResolvedProject } from "./config.js";
|
|
4
4
|
import { type DigestSection, type Step } from "./format.js";
|
|
@@ -72,11 +72,7 @@ export interface RunReport {
|
|
|
72
72
|
/** 0 = no limit. */
|
|
73
73
|
max_rounds: number;
|
|
74
74
|
/** Set when an agent kept failing and the run stopped: `resume` after fixing the cause. */
|
|
75
|
-
failure?:
|
|
76
|
-
agent: string;
|
|
77
|
-
error?: string;
|
|
78
|
-
attempts: number;
|
|
79
|
-
};
|
|
75
|
+
failure?: RunFailure;
|
|
80
76
|
output_tokens: number;
|
|
81
77
|
task_source: "text" | "file";
|
|
82
78
|
task_path?: string;
|
|
@@ -123,6 +119,19 @@ export interface AgentReport {
|
|
|
123
119
|
ok: boolean;
|
|
124
120
|
error?: string;
|
|
125
121
|
};
|
|
122
|
+
/** Only for an agent with a fallback chain: the model it last ran on (or its own, before any wake). */
|
|
123
|
+
model?: string;
|
|
124
|
+
/** Only when the agent's latest wake ran on a backup: what it replaced and why. */
|
|
125
|
+
fallback?: {
|
|
126
|
+
from: string;
|
|
127
|
+
kind: string;
|
|
128
|
+
};
|
|
129
|
+
/** Only for an agent with a fallback chain: its models that are cooling down now (`model` is `<runtime>/<model>`). */
|
|
130
|
+
cooldowns?: {
|
|
131
|
+
model: string;
|
|
132
|
+
kind: string;
|
|
133
|
+
remaining_sec: number;
|
|
134
|
+
}[];
|
|
126
135
|
}
|
|
127
136
|
export interface StatusReport {
|
|
128
137
|
schema_version: 1;
|
|
@@ -153,7 +162,7 @@ export declare function buildRunReport(run: {
|
|
|
153
162
|
detail?: boolean;
|
|
154
163
|
}): RunReport;
|
|
155
164
|
/** The status of a project: its agents and one run (the newest running one, else the newest, or the one named by `runId`). */
|
|
156
|
-
export declare function buildStatusReport(base: ResolvedProject, runId?: string): StatusReport;
|
|
165
|
+
export declare function buildStatusReport(base: ResolvedProject, runId?: string, now?: number): StatusReport;
|
|
157
166
|
/** Every run of the project, newest first. */
|
|
158
167
|
export declare function buildTaskListReport(project: ResolvedProject): TaskListReport;
|
|
159
168
|
/** Live view: summary and agent table, plus the latest run and any run still going, wake by wake. */
|
package/dist/status.js
CHANGED
|
@@ -3,6 +3,7 @@ import path from "node:path";
|
|
|
3
3
|
import { askReplyPath, readAskReply } from "./ask-reply.js";
|
|
4
4
|
import { RESULT_FILE } from "./dispatcher.js";
|
|
5
5
|
import { bindRunProject, loadRunState, outcomeOf, roundsLabel, runLabel } from "./run-store.js";
|
|
6
|
+
import { candidatesFor } from "./fallback.js";
|
|
6
7
|
import { briefOf, digestOf } from "./format.js";
|
|
7
8
|
import { listUnread } from "./mailbox.js";
|
|
8
9
|
import { tidyAdvice } from "./memory-tidy.js";
|
|
@@ -226,8 +227,37 @@ export function buildRunReport(run, project, opts = {}) {
|
|
|
226
227
|
};
|
|
227
228
|
}
|
|
228
229
|
const projectInfo = (p) => ({ name: p.name, dir: p.dir, lead: p.lead });
|
|
230
|
+
/** The fallback part of an agent's report: nothing at all for an agent without a chain. */
|
|
231
|
+
function fallbackView(a, state, now) {
|
|
232
|
+
if (!a.fallback.length || !a.runtime)
|
|
233
|
+
return {};
|
|
234
|
+
const lw = state?.last_wake[a.name];
|
|
235
|
+
const ran = lw?.runtime ? lw : undefined;
|
|
236
|
+
const model = ran ? ran.model : a.model;
|
|
237
|
+
const mine = (state?.wakes ?? []).filter((w) => w.agent === a.name);
|
|
238
|
+
const last = mine.at(-1);
|
|
239
|
+
let fallback;
|
|
240
|
+
if (last?.fallback_from) {
|
|
241
|
+
// The reason is on the failed wake just before: the nearest earlier wake of this agent that has a kind.
|
|
242
|
+
let kind = "unknown";
|
|
243
|
+
for (let i = mine.length - 2; i >= 0; i--) {
|
|
244
|
+
const k = mine[i].kind;
|
|
245
|
+
if (k) {
|
|
246
|
+
kind = k;
|
|
247
|
+
break;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
fallback = { from: last.fallback_from, kind };
|
|
251
|
+
}
|
|
252
|
+
const keys = new Set(candidatesFor(a).map((c) => c.key));
|
|
253
|
+
const cooldowns = Object.entries(state?.cooldowns ?? {})
|
|
254
|
+
.filter(([k]) => keys.has(k))
|
|
255
|
+
.map(([k, v]) => ({ model: k, kind: v.kind, remaining_sec: Math.ceil((Date.parse(v.until) - now) / 1000) }))
|
|
256
|
+
.filter((c) => Number.isFinite(c.remaining_sec) && c.remaining_sec > 0);
|
|
257
|
+
return { runtime: ran?.runtime ?? a.runtime, ...(model ? { model } : {}), ...(fallback ? { fallback } : {}), ...(cooldowns.length ? { cooldowns } : {}) };
|
|
258
|
+
}
|
|
229
259
|
/** The status of a project: its agents and one run (the newest running one, else the newest, or the one named by `runId`). */
|
|
230
|
-
export function buildStatusReport(base, runId) {
|
|
260
|
+
export function buildStatusReport(base, runId, now = Date.now()) {
|
|
231
261
|
let run;
|
|
232
262
|
if (runId) {
|
|
233
263
|
const dir = path.join(base.paths.runs, runId);
|
|
@@ -257,6 +287,7 @@ export function buildStatusReport(base, runId) {
|
|
|
257
287
|
unread: listUnread(project, a.name).length,
|
|
258
288
|
can_message: (a.canMessage === "all" ? Object.keys(project.agents) : a.canMessage).filter((n) => n !== a.name),
|
|
259
289
|
last_wake: run?.state.last_wake[a.name],
|
|
290
|
+
...fallbackView(a, run?.state, now),
|
|
260
291
|
})),
|
|
261
292
|
run: report,
|
|
262
293
|
...(advice.length ? { memory_advice: advice } : {}),
|
|
@@ -425,6 +456,20 @@ export function formatTaskListReport(report, color = false) {
|
|
|
425
456
|
export function formatStatus(base, now = Date.now(), color = false) {
|
|
426
457
|
return formatStatusReport(buildStatusReport(base), now, color);
|
|
427
458
|
}
|
|
459
|
+
const shortSec = (sec) => (sec < 60 ? `${sec}s` : `${Math.floor(sec / 60)}m${String(sec % 60).padStart(2, "0")}s`);
|
|
460
|
+
/** `runtime/model`, or the runtime alone for its default model (cooldown keys end in `/` then). */
|
|
461
|
+
const modelName = (key) => key.replace(/\/$/, "");
|
|
462
|
+
/** Agents running on a backup and models cooling down; empty for a run without fallback. */
|
|
463
|
+
function fallbackLines(agents, c) {
|
|
464
|
+
const out = [];
|
|
465
|
+
for (const a of agents) {
|
|
466
|
+
if (a.fallback)
|
|
467
|
+
out.push(` ${c.cyan(a.name)} ${c.yellow(`備援中 fallback: ${a.fallback.kind}`)} (${modelName(`${a.runtime ?? "?"}/${a.model ?? ""}`)})`);
|
|
468
|
+
for (const k of a.cooldowns ?? [])
|
|
469
|
+
out.push(` ${c.cyan(a.name)} ${c.yellow(`冷卻中:${modelName(k.model)} 剩 ${shortSec(k.remaining_sec)}`)}(${k.kind})`);
|
|
470
|
+
}
|
|
471
|
+
return out;
|
|
472
|
+
}
|
|
428
473
|
export function formatStatusReport(report, now = Date.now(), color = false) {
|
|
429
474
|
const c = paint(color);
|
|
430
475
|
const run = report.run;
|
|
@@ -452,6 +497,9 @@ export function formatStatusReport(report, now = Date.now(), color = false) {
|
|
|
452
497
|
const table = rows.map((r, ri) => r.map((_, i) => cell(r, ri, i)).join(" ").trimEnd());
|
|
453
498
|
table.splice(1, 0, c.dim(w.map((n) => "-".repeat(n)).join(" ")));
|
|
454
499
|
const lines = [`${c.bold("專案:")}${report.project.name} ${c.dim(`(repo:${report.project.dir})`)}`, "", ...summaryBlock(run, now, c), "", ...table];
|
|
500
|
+
const fb = fallbackLines(report.agents, c);
|
|
501
|
+
if (fb.length)
|
|
502
|
+
lines.push("", ...fb);
|
|
455
503
|
if (run)
|
|
456
504
|
lines.push("", `輸出 tokens:${tokens(run.output_tokens)}`);
|
|
457
505
|
if (report.memory_advice?.length)
|
package/dist/validate.js
CHANGED
|
@@ -25,6 +25,7 @@ export function validateProject(project) {
|
|
|
25
25
|
if (a.effort && a.runtime && !RUNTIME_EFFORTS[a.runtime].includes(a.effort)) {
|
|
26
26
|
err(`Agent "${a.name}": effort "${a.effort}" is not supported by ${a.runtime} (use ${RUNTIME_EFFORTS[a.runtime].join(", ")}).`);
|
|
27
27
|
}
|
|
28
|
+
validateFallback(project, a, err);
|
|
28
29
|
if (!fs.existsSync(a.agentMd))
|
|
29
30
|
err(`Agent "${a.name}": AGENT.md not found at ${a.agentMd}`);
|
|
30
31
|
if (!a.memory.global && !a.memory.project)
|
|
@@ -75,6 +76,41 @@ export function validateProject(project) {
|
|
|
75
76
|
}
|
|
76
77
|
return { issues, enforcement, ok: !issues.some((i) => i.level === "error") };
|
|
77
78
|
}
|
|
79
|
+
const PARITY_FIELDS = ["memory", "agentMd", "otherContext", "shared", "repoInstructions"];
|
|
80
|
+
const norm = (m) => (m ?? "").trim().toLowerCase();
|
|
81
|
+
/** Load-time (fail closed) checks of one agent's fallback chain: well-formed, not a no-op, and no weaker at write-scope than the primary. */
|
|
82
|
+
function validateFallback(project, a, err) {
|
|
83
|
+
for (const bad of a.fallbackInvalid) {
|
|
84
|
+
err(bad.reason === "empty"
|
|
85
|
+
? `agent ${a.name}: fallback #${bad.index + 1} needs a model or a runtime.`
|
|
86
|
+
: `agent ${a.name}: fallback #${bad.index + 1} has no recognizable runtime (set runtime, or use a recognizable model).`);
|
|
87
|
+
}
|
|
88
|
+
const primary = a.runtime ? enforcementFor(project, a) : undefined;
|
|
89
|
+
for (const f of a.fallback) {
|
|
90
|
+
const label = `${f.runtime}/${f.model || "(default model)"}`;
|
|
91
|
+
if (f.runtime === a.runtime && norm(f.model) === norm(a.model)) {
|
|
92
|
+
// A chain shared through dispatcher.fallback may already contain this agent's primary: just skip it then.
|
|
93
|
+
if (!a.fallbackShared)
|
|
94
|
+
err(`agent ${a.name}: fallback ${label} is the same as the primary runtime and model.`);
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
if (f.effort && !RUNTIME_EFFORTS[f.runtime].includes(f.effort)) {
|
|
98
|
+
err(`agent ${a.name}: fallback ${label}: effort "${f.effort}" is not supported by ${f.runtime} (use ${RUNTIME_EFFORTS[f.runtime].join(", ")}).`);
|
|
99
|
+
}
|
|
100
|
+
if (!primary)
|
|
101
|
+
continue;
|
|
102
|
+
// `prompt-only` is the one level that is no enforcement at all; everything else (os, tool-rules, post-hoc) still holds the line.
|
|
103
|
+
const swapped = enforcementFor(project, { ...a, runtime: f.runtime, model: f.model || undefined, effort: f.effort });
|
|
104
|
+
for (const k of PARITY_FIELDS) {
|
|
105
|
+
const from = primary[k];
|
|
106
|
+
const to = swapped[k];
|
|
107
|
+
if (from === "n/a" || to === "n/a")
|
|
108
|
+
continue;
|
|
109
|
+
if (to === "prompt-only" && from !== "prompt-only")
|
|
110
|
+
err(`agent ${a.name}: fallback ${label} cannot enforce ${k} (${from} -> ${to})`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
78
114
|
export function formatEnforcement(list) {
|
|
79
115
|
const header = ["agent", "runtime", "memory", "AGENT.md", "others' ctx", "handoff", "repo CLAUDE/AGENTS"];
|
|
80
116
|
const rows = [header];
|