agent-lyceum 0.26.0 → 0.28.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 +4 -0
- package/README.zh-TW.md +4 -0
- package/dist/adapters/claude.js +11 -0
- package/dist/adapters/codex.js +2 -0
- package/dist/call.js +5 -2
- package/dist/config.d.ts +37 -1
- package/dist/config.js +69 -4
- package/dist/duplicate-notices.d.ts +9 -0
- package/dist/duplicate-notices.js +34 -0
- package/dist/fallback.d.ts +14 -2
- package/dist/fallback.js +34 -6
- package/dist/guard.d.ts +17 -1
- package/dist/guard.js +37 -6
- package/dist/mailbox.d.ts +14 -0
- package/dist/mailbox.js +67 -23
- package/dist/paths.d.ts +2 -0
- package/dist/paths.js +5 -2
- package/dist/policy.d.ts +33 -0
- package/dist/policy.js +80 -0
- package/dist/prompt.d.ts +9 -0
- package/dist/prompt.js +30 -3
- package/dist/run-session.d.ts +2 -0
- package/dist/run-session.js +74 -11
- package/dist/run-store.d.ts +4 -1
- package/dist/sandbox-invocation.d.ts +35 -0
- package/dist/sandbox-invocation.js +186 -0
- package/dist/scaffold.d.ts +1 -0
- package/dist/scaffold.js +7 -1
- package/dist/schema.d.ts +42 -1
- package/dist/schema.js +24 -1
- package/dist/status.js +26 -1
- package/dist/tiers.d.ts +27 -0
- package/dist/tiers.js +49 -0
- package/dist/validate.js +33 -1
- package/dist/worktree.js +2 -1
- package/package.json +1 -1
package/dist/status.js
CHANGED
|
@@ -319,7 +319,14 @@ export function buildStatusReport(base, runId, now = Date.now()) {
|
|
|
319
319
|
catch {
|
|
320
320
|
/* a hint only: never let it break status */
|
|
321
321
|
}
|
|
322
|
-
|
|
322
|
+
const wakes = run?.state.wakes ?? [];
|
|
323
|
+
const below = new Set();
|
|
324
|
+
for (const a of Object.values(project.agents)) {
|
|
325
|
+
const last = wakes.filter((w) => w.agent === a.name).at(-1);
|
|
326
|
+
if (last?.tier && !last.fallback_from && last.runtime === a.runtime && !candidatesFor(a).some((c) => c.primary && c.key === `${last.runtime}/${(last.model ?? "").trim().toLowerCase()}`))
|
|
327
|
+
below.add(a.name);
|
|
328
|
+
}
|
|
329
|
+
const out = {
|
|
323
330
|
schema_version: 1,
|
|
324
331
|
project: projectInfo(base),
|
|
325
332
|
agents: Object.values(project.agents).map((a) => ({
|
|
@@ -335,6 +342,8 @@ export function buildStatusReport(base, runId, now = Date.now()) {
|
|
|
335
342
|
run: report,
|
|
336
343
|
...(advice.length ? { memory_advice: advice } : {}),
|
|
337
344
|
};
|
|
345
|
+
belowCeiling.set(out, below);
|
|
346
|
+
return out;
|
|
338
347
|
}
|
|
339
348
|
/** Every run of the project, newest first. */
|
|
340
349
|
export function buildTaskListReport(project) {
|
|
@@ -513,6 +522,19 @@ function fallbackLines(agents, c) {
|
|
|
513
522
|
}
|
|
514
523
|
return out;
|
|
515
524
|
}
|
|
525
|
+
/** Agents whose latest wake ran on a tiered model below the ceiling; kept off the report so `status --json` is unchanged. */
|
|
526
|
+
const belowCeiling = new WeakMap();
|
|
527
|
+
/** An agent whose latest wake ran on a model below its ceiling (spec 24.9): one line each, in agent order. */
|
|
528
|
+
function tierLines(report, c) {
|
|
529
|
+
const out = [];
|
|
530
|
+
const wakes = report.run?.wakes ?? [];
|
|
531
|
+
for (const a of report.agents) {
|
|
532
|
+
const last = wakes.filter((w) => w.agent === a.name).at(-1);
|
|
533
|
+
if (last?.tier && last.runtime && belowCeiling.get(report)?.has(a.name))
|
|
534
|
+
out.push(` ${c.cyan(a.name)} ${c.yellow(`tier ${last.tier}`)}(${modelName(`${last.runtime}/${last.model ?? ""}`)})`);
|
|
535
|
+
}
|
|
536
|
+
return out;
|
|
537
|
+
}
|
|
516
538
|
export function formatStatusReport(report, now = Date.now(), color = false) {
|
|
517
539
|
const c = paint(color);
|
|
518
540
|
const run = report.run;
|
|
@@ -543,6 +565,9 @@ export function formatStatusReport(report, now = Date.now(), color = false) {
|
|
|
543
565
|
const fb = fallbackLines(report.agents, c);
|
|
544
566
|
if (fb.length)
|
|
545
567
|
lines.push("", ...fb);
|
|
568
|
+
const tl = tierLines(report, c);
|
|
569
|
+
if (tl.length)
|
|
570
|
+
lines.push(...(fb.length ? [] : [""]), ...tl);
|
|
546
571
|
if (run)
|
|
547
572
|
lines.push("", `輸出 tokens:${tokens(run.output_tokens)}${cacheText(run.cache_read_tokens, run.cache_write_tokens) ? ` ${cacheText(run.cache_read_tokens, run.cache_write_tokens)}` : ""}`);
|
|
548
573
|
if (report.memory_advice?.length)
|
package/dist/tiers.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { type Tier } from "./schema.js";
|
|
2
|
+
export declare function isTier(v: unknown): v is Tier;
|
|
3
|
+
/** Index of `model` on `ladder` (trim + lowercase, like the fallback keys), or -1. */
|
|
4
|
+
export declare function ladderIndex(ladder: readonly string[], model: string | undefined): number;
|
|
5
|
+
/**
|
|
6
|
+
* The tiers an agent can run on: the ladder from its lowest entry up to and including `model` (the ceiling).
|
|
7
|
+
* `undefined` when there is nothing below the ceiling (no ladder, no model, model not on it, or it is the lowest entry).
|
|
8
|
+
*/
|
|
9
|
+
export declare function availableTiers(ladder: readonly string[] | undefined, model: string | undefined): string[] | undefined;
|
|
10
|
+
/** `low` = lowest, `high` = ceiling, `mid` = `tiers[floor(n/2)]` (the higher middle when n is even). */
|
|
11
|
+
export declare function modelForTier(tiers: readonly string[], tier: Tier): string;
|
|
12
|
+
export type TierFlag = "absent" | "invalid" | Tier | {
|
|
13
|
+
ignored: "not-lead" | "not-a-task" | "no-tiers";
|
|
14
|
+
};
|
|
15
|
+
/** Judge the `tier` frontmatter of one outgoing mail, in the order of spec 24.5.2. Not wired anywhere yet. */
|
|
16
|
+
export declare function tierFlag(i: {
|
|
17
|
+
value: unknown;
|
|
18
|
+
senderIsLead: boolean;
|
|
19
|
+
type: string;
|
|
20
|
+
recipientTiers?: string[];
|
|
21
|
+
}): TierFlag;
|
|
22
|
+
/** A mail without a valid tier counts as `high` (the ceiling). */
|
|
23
|
+
export declare function effectiveTier(meta: {
|
|
24
|
+
tier?: unknown;
|
|
25
|
+
}): Tier;
|
|
26
|
+
/** The tier a wake runs at: the highest of the batch when every mail has a valid one, else `undefined` (the ceiling). */
|
|
27
|
+
export declare function wakeTier(tiers: readonly unknown[]): Tier | undefined;
|
package/dist/tiers.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { TIERS } from "./schema.js";
|
|
2
|
+
const norm = (m) => (m ?? "").trim().toLowerCase();
|
|
3
|
+
export function isTier(v) {
|
|
4
|
+
return typeof v === "string" && TIERS.includes(v);
|
|
5
|
+
}
|
|
6
|
+
/** Index of `model` on `ladder` (trim + lowercase, like the fallback keys), or -1. */
|
|
7
|
+
export function ladderIndex(ladder, model) {
|
|
8
|
+
const m = norm(model);
|
|
9
|
+
return m ? ladder.findIndex((e) => norm(e) === m) : -1;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* The tiers an agent can run on: the ladder from its lowest entry up to and including `model` (the ceiling).
|
|
13
|
+
* `undefined` when there is nothing below the ceiling (no ladder, no model, model not on it, or it is the lowest entry).
|
|
14
|
+
*/
|
|
15
|
+
export function availableTiers(ladder, model) {
|
|
16
|
+
if (!ladder?.length)
|
|
17
|
+
return undefined;
|
|
18
|
+
const i = ladderIndex(ladder, model);
|
|
19
|
+
return i >= 1 ? ladder.slice(0, i + 1) : undefined;
|
|
20
|
+
}
|
|
21
|
+
/** `low` = lowest, `high` = ceiling, `mid` = `tiers[floor(n/2)]` (the higher middle when n is even). */
|
|
22
|
+
export function modelForTier(tiers, tier) {
|
|
23
|
+
const n = tiers.length;
|
|
24
|
+
return tiers[tier === "low" ? 0 : tier === "high" ? n - 1 : Math.floor(n / 2)];
|
|
25
|
+
}
|
|
26
|
+
/** Judge the `tier` frontmatter of one outgoing mail, in the order of spec 24.5.2. Not wired anywhere yet. */
|
|
27
|
+
export function tierFlag(i) {
|
|
28
|
+
if (i.value === undefined || i.value === null || i.value === "")
|
|
29
|
+
return "absent";
|
|
30
|
+
if (!i.senderIsLead)
|
|
31
|
+
return { ignored: "not-lead" };
|
|
32
|
+
if (!isTier(i.value))
|
|
33
|
+
return "invalid";
|
|
34
|
+
if (i.type !== "task")
|
|
35
|
+
return { ignored: "not-a-task" };
|
|
36
|
+
if (!i.recipientTiers || i.recipientTiers.length < 2)
|
|
37
|
+
return { ignored: "no-tiers" };
|
|
38
|
+
return i.value;
|
|
39
|
+
}
|
|
40
|
+
/** A mail without a valid tier counts as `high` (the ceiling). */
|
|
41
|
+
export function effectiveTier(meta) {
|
|
42
|
+
return isTier(meta.tier) ? meta.tier : "high";
|
|
43
|
+
}
|
|
44
|
+
/** The tier a wake runs at: the highest of the batch when every mail has a valid one, else `undefined` (the ceiling). */
|
|
45
|
+
export function wakeTier(tiers) {
|
|
46
|
+
if (tiers.length === 0 || !tiers.every(isTier))
|
|
47
|
+
return undefined;
|
|
48
|
+
return TIERS[Math.max(...tiers.map((t) => TIERS.indexOf(t)))];
|
|
49
|
+
}
|
package/dist/validate.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import { enforcementFor, memoryDirs } from "./policy.js";
|
|
3
|
+
import { describeConflict, fallbackCapExcess, sandboxConflictsOf } from "./sandbox-invocation.js";
|
|
3
4
|
import { isInside } from "./paths.js";
|
|
4
|
-
import { inferRuntime, RUNTIME_EFFORTS } from "./schema.js";
|
|
5
|
+
import { inferRuntime, RUNTIME_EFFORTS, RUNTIMES } from "./schema.js";
|
|
6
|
+
import { ladderIndex } from "./tiers.js";
|
|
5
7
|
import { must } from "./assert.js";
|
|
6
8
|
export function validateProject(project) {
|
|
7
9
|
const issues = [];
|
|
@@ -15,7 +17,30 @@ export function validateProject(project) {
|
|
|
15
17
|
if (!fs.existsSync(project.dir) || !fs.statSync(project.dir).isDirectory()) {
|
|
16
18
|
err(`Project dir does not exist: ${project.dir}`);
|
|
17
19
|
}
|
|
20
|
+
for (const rt of RUNTIMES) {
|
|
21
|
+
const ladder = project.modelTiers[rt];
|
|
22
|
+
if (!ladder)
|
|
23
|
+
continue;
|
|
24
|
+
const seen = new Set();
|
|
25
|
+
ladder.forEach((m, i) => {
|
|
26
|
+
const other = inferRuntime(m);
|
|
27
|
+
if (other !== rt)
|
|
28
|
+
err(`model_tiers.${rt}[${i}]: "${m}" is not a ${rt} model (recognised as ${other ?? "nothing"}). List only ${rt} models, lowest first.`);
|
|
29
|
+
const k = m.trim().toLowerCase();
|
|
30
|
+
if (seen.has(k))
|
|
31
|
+
err(`model_tiers.${rt}: "${m}" is listed twice.`);
|
|
32
|
+
seen.add(k);
|
|
33
|
+
});
|
|
34
|
+
}
|
|
18
35
|
for (const a of Object.values(project.agents)) {
|
|
36
|
+
const ladder = a.runtime ? project.modelTiers[a.runtime] : undefined;
|
|
37
|
+
if (a.runtime && ladder) {
|
|
38
|
+
if (!a.model)
|
|
39
|
+
warn(`Agent "${a.name}" has no model, so model_tiers.${a.runtime} does not apply to it: it always runs on its runtime's default model.`);
|
|
40
|
+
else if (ladderIndex(ladder, a.model) < 0) {
|
|
41
|
+
err(`Agent "${a.name}": model "${a.model}" is not on model_tiers.${a.runtime} (${ladder.join(", ")}). Add it to the ladder, or set model_tiers.${a.runtime}: [] in project.yaml to turn the ladder off for this project.`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
19
44
|
if (!a.runtime)
|
|
20
45
|
err(`Agent "${a.name}" has no runtime (set runtime, or a recognizable model such as opus/sonnet/haiku/claude-* or gpt-*/codex-*).`);
|
|
21
46
|
const modelRuntime = inferRuntime(a.model);
|
|
@@ -26,6 +51,10 @@ export function validateProject(project) {
|
|
|
26
51
|
err(`Agent "${a.name}": effort "${a.effort}" is not supported by ${a.runtime} (use ${RUNTIME_EFFORTS[a.runtime].join(", ")}).`);
|
|
27
52
|
}
|
|
28
53
|
validateFallback(project, a, err);
|
|
54
|
+
for (const c of sandboxConflictsOf(project, a, false))
|
|
55
|
+
err(describeConflict(a.name, c, "load"));
|
|
56
|
+
for (const f of fallbackCapExcess(a))
|
|
57
|
+
err(`agent ${a.name}: fallback ${f.label} would apply more sandbox allowance than the primary (${f.excess.join("; ")}).`);
|
|
29
58
|
if (!fs.existsSync(a.agentMd))
|
|
30
59
|
err(`Agent "${a.name}": AGENT.md not found at ${a.agentMd}`);
|
|
31
60
|
if (!a.memory.global && !a.memory.project)
|
|
@@ -39,6 +68,9 @@ export function validateProject(project) {
|
|
|
39
68
|
}
|
|
40
69
|
}
|
|
41
70
|
}
|
|
71
|
+
if (Object.keys(project.modelTiers).length > 0 && !Object.values(project.agents).some((a) => a.name !== project.lead && a.tiers)) {
|
|
72
|
+
warn("model_tiers is set, but no member can run below its model (each member's model is the lowest entry of its ladder): tiers have no effect.");
|
|
73
|
+
}
|
|
42
74
|
// Memory directories must not overlap between agents (or equal each other).
|
|
43
75
|
const mem = [];
|
|
44
76
|
for (const a of Object.values(project.agents))
|
package/dist/worktree.js
CHANGED
|
@@ -4,6 +4,7 @@ import fs from "node:fs";
|
|
|
4
4
|
import os from "node:os";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import { atomicWrite } from "./fs-util.js";
|
|
7
|
+
import { escapesParent } from "./paths.js";
|
|
7
8
|
const IDENTITY = {
|
|
8
9
|
GIT_AUTHOR_NAME: "agent-lyceum",
|
|
9
10
|
GIT_AUTHOR_EMAIL: "agent-lyceum@localhost",
|
|
@@ -474,7 +475,7 @@ export function collectAgentChanges(workspace) {
|
|
|
474
475
|
const resolved = path.posix.normalize(path.posix.join(path.posix.dirname(p), target));
|
|
475
476
|
if (path.posix.isAbsolute(target))
|
|
476
477
|
violations.push(`${f.path}: symlink to an absolute path (${target})`);
|
|
477
|
-
else if (resolved
|
|
478
|
+
else if (escapesParent(resolved, "/"))
|
|
478
479
|
violations.push(`${f.path}: symlink leaves the project directory (${target})`);
|
|
479
480
|
}
|
|
480
481
|
}
|