agent-lyceum 0.4.9

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.
Files changed (56) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +132 -0
  3. package/README.zh-TW.md +133 -0
  4. package/dist/adapters/claude.d.ts +14 -0
  5. package/dist/adapters/claude.js +155 -0
  6. package/dist/adapters/codex.d.ts +5 -0
  7. package/dist/adapters/codex.js +109 -0
  8. package/dist/adapters/index.d.ts +6 -0
  9. package/dist/adapters/index.js +20 -0
  10. package/dist/adapters/types.d.ts +61 -0
  11. package/dist/adapters/types.js +1 -0
  12. package/dist/cli.d.ts +2 -0
  13. package/dist/cli.js +471 -0
  14. package/dist/config.d.ts +115 -0
  15. package/dist/config.js +253 -0
  16. package/dist/dispatcher.d.ts +37 -0
  17. package/dist/dispatcher.js +467 -0
  18. package/dist/doctor.d.ts +46 -0
  19. package/dist/doctor.js +151 -0
  20. package/dist/format.d.ts +33 -0
  21. package/dist/format.js +100 -0
  22. package/dist/fs-util.d.ts +2 -0
  23. package/dist/fs-util.js +9 -0
  24. package/dist/guard.d.ts +20 -0
  25. package/dist/guard.js +72 -0
  26. package/dist/mailbox.d.ts +97 -0
  27. package/dist/mailbox.js +259 -0
  28. package/dist/message-store.d.ts +91 -0
  29. package/dist/message-store.js +225 -0
  30. package/dist/paths.d.ts +30 -0
  31. package/dist/paths.js +62 -0
  32. package/dist/policy.d.ts +38 -0
  33. package/dist/policy.js +143 -0
  34. package/dist/process-runner.d.ts +21 -0
  35. package/dist/process-runner.js +165 -0
  36. package/dist/project-lock.d.ts +30 -0
  37. package/dist/project-lock.js +186 -0
  38. package/dist/prompt.d.ts +11 -0
  39. package/dist/prompt.js +125 -0
  40. package/dist/run-cleanup.d.ts +51 -0
  41. package/dist/run-cleanup.js +120 -0
  42. package/dist/run-store.d.ts +111 -0
  43. package/dist/run-store.js +119 -0
  44. package/dist/scaffold.d.ts +11 -0
  45. package/dist/scaffold.js +126 -0
  46. package/dist/schema.d.ts +303 -0
  47. package/dist/schema.js +84 -0
  48. package/dist/status.d.ts +142 -0
  49. package/dist/status.js +399 -0
  50. package/dist/task.d.ts +24 -0
  51. package/dist/task.js +71 -0
  52. package/dist/validate.d.ts +13 -0
  53. package/dist/validate.js +116 -0
  54. package/dist/worktree.d.ts +103 -0
  55. package/dist/worktree.js +395 -0
  56. package/package.json +58 -0
package/dist/config.js ADDED
@@ -0,0 +1,253 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import YAML from "yaml";
4
+ import { absPath, homePaths, projectPaths, } from "./paths.js";
5
+ import { DISPATCHER_DEFAULTS, GlobalConfig, ProjectConfig, inferRuntime, } from "./schema.js";
6
+ export class ConfigError extends Error {
7
+ }
8
+ function readYaml(file, schema, label) {
9
+ let text;
10
+ try {
11
+ text = fs.readFileSync(file, "utf8");
12
+ }
13
+ catch {
14
+ throw new ConfigError(`${label} not found: ${file}`);
15
+ }
16
+ let raw;
17
+ try {
18
+ raw = YAML.parse(text) ?? {};
19
+ }
20
+ catch (e) {
21
+ throw new ConfigError(`${label} is not valid YAML (${file}): ${e.message}`);
22
+ }
23
+ const parsed = schema.safeParse(raw);
24
+ if (!parsed.success) {
25
+ const msg = parsed.error.issues.map((i) => ` - ${i.path.join(".") || "(root)"}: ${i.message}`).join("\n");
26
+ throw new ConfigError(`${label} is invalid (${file}):\n${msg}`);
27
+ }
28
+ return parsed.data;
29
+ }
30
+ export function loadGlobal(home) {
31
+ const file = homePaths(home).globalConfig;
32
+ return readYaml(file, GlobalConfig, "Global config");
33
+ }
34
+ export function loadProjectRaw(home, name) {
35
+ const file = projectPaths(home, name).config;
36
+ return readYaml(file, ProjectConfig, `Project "${name}" config`);
37
+ }
38
+ function resolveLayer(p, base) {
39
+ if (!p)
40
+ return {};
41
+ const out = { ...p };
42
+ if (p.agent_md)
43
+ out.agent_md = absPath(p.agent_md, base);
44
+ if (p.memory) {
45
+ out.memory = {};
46
+ if (p.memory.global)
47
+ out.memory.global = absPath(p.memory.global, base);
48
+ if (p.memory.project)
49
+ out.memory.project = absPath(p.memory.project, base);
50
+ }
51
+ return out;
52
+ }
53
+ export function resolveProject(home, name) {
54
+ const g = loadGlobal(home);
55
+ const p = loadProjectRaw(home, name);
56
+ const paths = projectPaths(home, name);
57
+ const hp = homePaths(home);
58
+ const lead = p.team.lead;
59
+ const dispatcher = { ...DISPATCHER_DEFAULTS, ...stripUndef(p.dispatcher) };
60
+ const agents = {};
61
+ for (const agentName of Object.keys(p.agents)) {
62
+ const gl = resolveLayer(g.agents[agentName], home);
63
+ const pl = resolveLayer(p.agents[agentName], paths.root);
64
+ const sources = {};
65
+ const pick = (key) => {
66
+ if (pl[key] !== undefined) {
67
+ sources[key] = "project";
68
+ return pl[key];
69
+ }
70
+ if (gl[key] !== undefined) {
71
+ sources[key] = "global";
72
+ return gl[key];
73
+ }
74
+ return undefined;
75
+ };
76
+ const projectAgentMd = path.join(paths.agentsDir, agentName, "AGENT.md");
77
+ let agentMd = pick("agent_md");
78
+ if (agentMd === undefined) {
79
+ if (fs.existsSync(projectAgentMd)) {
80
+ agentMd = projectAgentMd;
81
+ sources.agent_md = "project";
82
+ }
83
+ else {
84
+ agentMd = path.join(hp.globalAgentsDir, agentName, "AGENT.md");
85
+ sources.agent_md = "default";
86
+ }
87
+ }
88
+ const memory = {};
89
+ if (pl.memory?.global) {
90
+ memory.global = pl.memory.global;
91
+ sources["memory.global"] = "project";
92
+ }
93
+ else if (gl.memory?.global) {
94
+ memory.global = gl.memory.global;
95
+ sources["memory.global"] = "global";
96
+ }
97
+ if (pl.memory?.project) {
98
+ memory.project = pl.memory.project;
99
+ sources["memory.project"] = "project";
100
+ }
101
+ else if (gl.memory?.project) {
102
+ memory.project = gl.memory.project;
103
+ sources["memory.project"] = "global";
104
+ }
105
+ if (!memory.global && !memory.project) {
106
+ memory.project = path.join(paths.agentsDir, agentName, "memory");
107
+ sources["memory.project"] = "default";
108
+ }
109
+ const model = pick("model");
110
+ // Precedence: project runtime > project model > global runtime > global model.
111
+ // A project-level model must win over an inherited global runtime, or `model: gpt-5` would run on claude.
112
+ let runtime = pl.runtime;
113
+ if (runtime !== undefined)
114
+ sources.runtime = "project";
115
+ else if ((runtime = inferRuntime(pl.model)))
116
+ sources.runtime = "model";
117
+ else if ((runtime = gl.runtime))
118
+ sources.runtime = "global";
119
+ else if ((runtime = inferRuntime(gl.model)))
120
+ sources.runtime = "model";
121
+ const isLead = agentName === lead;
122
+ const resume = pick("resume");
123
+ const canMessage = pick("can_message");
124
+ const canEdit = pick("can_edit_agent_md");
125
+ const owns = pick("owns");
126
+ for (const [k, v] of [
127
+ ["resume", resume],
128
+ ["can_message", canMessage],
129
+ ["can_edit_agent_md", canEdit],
130
+ ["owns", owns],
131
+ ]) {
132
+ if (v === undefined)
133
+ sources[k] = "default";
134
+ }
135
+ agents[agentName] = {
136
+ name: agentName,
137
+ runtime,
138
+ model,
139
+ effort: pick("effort"),
140
+ agentMd,
141
+ memory,
142
+ resume: resume ?? isLead,
143
+ canMessage: canMessage ?? (isLead ? "all" : [lead]),
144
+ canEditAgentMd: canEdit ?? isLead,
145
+ owns: owns ?? [],
146
+ sources,
147
+ };
148
+ }
149
+ return {
150
+ home,
151
+ name,
152
+ dir: absPath(p.dir, paths.root),
153
+ lead,
154
+ dispatcher,
155
+ agents,
156
+ paths,
157
+ };
158
+ }
159
+ function stripUndef(o) {
160
+ return Object.fromEntries(Object.entries(o).filter(([, v]) => v !== undefined));
161
+ }
162
+ /** Registered projects: name -> repo dir (best effort; skips broken configs). */
163
+ export function listProjects(home) {
164
+ const root = homePaths(home).projectsDir;
165
+ if (!fs.existsSync(root))
166
+ return [];
167
+ const out = [];
168
+ for (const entry of fs.readdirSync(root, { withFileTypes: true })) {
169
+ if (!entry.isDirectory())
170
+ continue;
171
+ try {
172
+ const raw = loadProjectRaw(home, entry.name);
173
+ out.push({ name: entry.name, dir: absPath(raw.dir, projectPaths(home, entry.name).root) });
174
+ }
175
+ catch {
176
+ /* ignore broken project */
177
+ }
178
+ }
179
+ return out.sort((a, b) => a.name.localeCompare(b.name));
180
+ }
181
+ /** Pick the registered project whose dir is the longest prefix of `cwd`. */
182
+ export function findProjectForCwd(home, cwd) {
183
+ let best;
184
+ for (const pr of listProjects(home)) {
185
+ const rel = path.relative(pr.dir, cwd);
186
+ if (rel === "" || (!rel.startsWith("..") && !path.isAbsolute(rel))) {
187
+ if (!best || pr.dir.length > best.len)
188
+ best = { name: pr.name, len: pr.dir.length };
189
+ }
190
+ }
191
+ return best?.name;
192
+ }
193
+ const AGENT_FIELDS = ["runtime", "model", "effort", "agent_md", "memory.global", "memory.project", "resume", "can_message", "can_edit_agent_md", "owns"];
194
+ /** The resolved project plus, for every setting, the file and key it was taken from (or "default"). */
195
+ export function resolveProjectWithSources(home, name) {
196
+ const project = resolveProject(home, name);
197
+ const raw = loadProjectRaw(home, name);
198
+ const globalFile = homePaths(home).globalConfig;
199
+ const projectFile = project.paths.config;
200
+ const sources = {
201
+ dir: { file: projectFile, key: "dir" },
202
+ "team.lead": { file: projectFile, key: "team.lead" },
203
+ };
204
+ for (const k of Object.keys(DISPATCHER_DEFAULTS)) {
205
+ sources[`dispatcher.${k}`] = raw.dispatcher[k] !== undefined ? { file: projectFile, key: `dispatcher.${k}` } : { default: true };
206
+ }
207
+ for (const a of Object.values(project.agents)) {
208
+ const at = (src, field) => {
209
+ const key = `agents.${a.name}.${field}`;
210
+ if (src === "project")
211
+ return { file: projectFile, key };
212
+ if (src === "global")
213
+ return { file: globalFile, key };
214
+ return { default: true };
215
+ };
216
+ for (const f of AGENT_FIELDS) {
217
+ const src = a.sources[f];
218
+ if (src === undefined)
219
+ continue; // not set anywhere (e.g. no effort)
220
+ if (src === "model")
221
+ sources[`agents.${a.name}.${f}`] = (() => {
222
+ const m = at(a.sources.model, "model");
223
+ return "file" in m ? { inferred_from: m } : { default: true };
224
+ })();
225
+ // A project-level AGENT.md found by convention is not a setting in project.yaml.
226
+ else if (f === "agent_md" && src === "project" && raw.agents[a.name]?.agent_md === undefined)
227
+ sources[`agents.${a.name}.${f}`] = { default: true };
228
+ else
229
+ sources[`agents.${a.name}.${f}`] = at(src, f);
230
+ }
231
+ }
232
+ return { project, sources };
233
+ }
234
+ /** Every effective setting under the same keys as `resolveProjectWithSources` reports sources for. */
235
+ export function flattenResolved(project) {
236
+ const v = { dir: project.dir, "team.lead": project.lead };
237
+ for (const [k, val] of Object.entries(project.dispatcher))
238
+ v[`dispatcher.${k}`] = val;
239
+ for (const a of Object.values(project.agents)) {
240
+ const p = `agents.${a.name}`;
241
+ v[`${p}.runtime`] = a.runtime;
242
+ v[`${p}.model`] = a.model;
243
+ v[`${p}.effort`] = a.effort;
244
+ v[`${p}.agent_md`] = a.agentMd;
245
+ v[`${p}.memory.global`] = a.memory.global;
246
+ v[`${p}.memory.project`] = a.memory.project;
247
+ v[`${p}.resume`] = a.resume;
248
+ v[`${p}.can_message`] = a.canMessage;
249
+ v[`${p}.can_edit_agent_md`] = a.canEditAgentMd;
250
+ v[`${p}.owns`] = a.owns;
251
+ }
252
+ return v;
253
+ }
@@ -0,0 +1,37 @@
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 function runTeam(opts: RunOptions): Promise<RunSummary>;