agent-lyceum 0.4.14 → 0.6.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.
Files changed (47) hide show
  1. package/README.md +21 -21
  2. package/README.zh-TW.md +21 -21
  3. package/dist/adapters/claude.js +2 -0
  4. package/dist/ask-answer.d.ts +10 -0
  5. package/dist/ask-answer.js +21 -0
  6. package/dist/ask-reply.d.ts +65 -0
  7. package/dist/ask-reply.js +165 -0
  8. package/dist/assert.d.ts +2 -0
  9. package/dist/assert.js +6 -0
  10. package/dist/cli.js +202 -16
  11. package/dist/config.d.ts +21 -16
  12. package/dist/config.js +19 -7
  13. package/dist/dispatcher.d.ts +3 -34
  14. package/dist/dispatcher.js +4 -464
  15. package/dist/doctor.js +3 -0
  16. package/dist/format.js +7 -5
  17. package/dist/fs-util.d.ts +2 -0
  18. package/dist/fs-util.js +15 -0
  19. package/dist/guard.d.ts +8 -0
  20. package/dist/guard.js +24 -3
  21. package/dist/mailbox.d.ts +11 -1
  22. package/dist/mailbox.js +21 -8
  23. package/dist/memory-tidy.d.ts +115 -0
  24. package/dist/memory-tidy.js +351 -0
  25. package/dist/policy.js +3 -3
  26. package/dist/process-runner.js +1 -2
  27. package/dist/project-lock.d.ts +10 -1
  28. package/dist/project-lock.js +39 -4
  29. package/dist/prompt.d.ts +20 -0
  30. package/dist/prompt.js +76 -3
  31. package/dist/run-cleanup.js +4 -3
  32. package/dist/run-log.d.ts +5 -0
  33. package/dist/run-log.js +18 -0
  34. package/dist/run-outcome.d.ts +22 -0
  35. package/dist/run-outcome.js +28 -0
  36. package/dist/run-session.d.ts +92 -0
  37. package/dist/run-session.js +602 -0
  38. package/dist/run-store.d.ts +3 -1
  39. package/dist/run-store.js +7 -6
  40. package/dist/scaffold.js +1 -0
  41. package/dist/schema.d.ts +72 -239
  42. package/dist/schema.js +8 -3
  43. package/dist/status.d.ts +9 -1
  44. package/dist/status.js +51 -15
  45. package/dist/validate.js +12 -8
  46. package/dist/worktree.js +7 -6
  47. package/package.json +14 -8
package/dist/schema.d.ts CHANGED
@@ -9,51 +9,30 @@ export declare const RUNTIME_EFFORTS: Record<Runtime, readonly Effort[]>;
9
9
  /** Guess the runtime from a model name: Claude aliases/IDs -> claude-code, GPT/o-series/codex names -> codex. */
10
10
  export declare function inferRuntime(model: string | undefined): Runtime | undefined;
11
11
  export declare const AgentPartial: z.ZodObject<{
12
- runtime: z.ZodOptional<z.ZodEnum<["claude-code", "codex"]>>;
12
+ runtime: z.ZodOptional<z.ZodEnum<{
13
+ "claude-code": "claude-code";
14
+ codex: "codex";
15
+ }>>;
13
16
  model: z.ZodOptional<z.ZodString>;
14
- effort: z.ZodOptional<z.ZodEnum<["minimal", "low", "medium", "high", "xhigh", "max"]>>;
17
+ effort: z.ZodOptional<z.ZodEnum<{
18
+ high: "high";
19
+ low: "low";
20
+ max: "max";
21
+ medium: "medium";
22
+ minimal: "minimal";
23
+ xhigh: "xhigh";
24
+ }>>;
15
25
  agent_md: z.ZodOptional<z.ZodString>;
16
26
  memory: z.ZodOptional<z.ZodObject<{
17
27
  global: z.ZodOptional<z.ZodString>;
18
28
  project: z.ZodOptional<z.ZodString>;
19
- }, "strict", z.ZodTypeAny, {
20
- global?: string | undefined;
21
- project?: string | undefined;
22
- }, {
23
- global?: string | undefined;
24
- project?: string | undefined;
25
- }>>;
29
+ }, z.core.$strict>>;
26
30
  resume: z.ZodOptional<z.ZodBoolean>;
27
- can_message: z.ZodOptional<z.ZodUnion<[z.ZodLiteral<"all">, z.ZodArray<z.ZodString, "many">]>>;
31
+ can_message: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<"all">, z.ZodArray<z.ZodString>]>>;
28
32
  can_edit_agent_md: z.ZodOptional<z.ZodBoolean>;
29
- owns: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
30
- }, "strict", z.ZodTypeAny, {
31
- runtime?: "claude-code" | "codex" | undefined;
32
- model?: string | undefined;
33
- effort?: "minimal" | "low" | "medium" | "high" | "xhigh" | "max" | undefined;
34
- agent_md?: string | undefined;
35
- memory?: {
36
- global?: string | undefined;
37
- project?: string | undefined;
38
- } | undefined;
39
- resume?: boolean | undefined;
40
- can_message?: string[] | "all" | undefined;
41
- can_edit_agent_md?: boolean | undefined;
42
- owns?: string[] | undefined;
43
- }, {
44
- runtime?: "claude-code" | "codex" | undefined;
45
- model?: string | undefined;
46
- effort?: "minimal" | "low" | "medium" | "high" | "xhigh" | "max" | undefined;
47
- agent_md?: string | undefined;
48
- memory?: {
49
- global?: string | undefined;
50
- project?: string | undefined;
51
- } | undefined;
52
- resume?: boolean | undefined;
53
- can_message?: string[] | "all" | undefined;
54
- can_edit_agent_md?: boolean | undefined;
55
- owns?: string[] | undefined;
56
- }>;
33
+ can_ask_user: z.ZodOptional<z.ZodBoolean>;
34
+ owns: z.ZodOptional<z.ZodArray<z.ZodString>>;
35
+ }, z.core.$strict>;
57
36
  export type AgentPartialT = z.infer<typeof AgentPartial>;
58
37
  export declare const DispatcherPartial: z.ZodObject<{
59
38
  max_rounds: z.ZodOptional<z.ZodNumber>;
@@ -61,232 +40,84 @@ export declare const DispatcherPartial: z.ZodObject<{
61
40
  wake_timeout_sec: z.ZodOptional<z.ZodNumber>;
62
41
  retry: z.ZodOptional<z.ZodNumber>;
63
42
  strict: z.ZodOptional<z.ZodBoolean>;
64
- workspace_mode: z.ZodOptional<z.ZodEnum<["auto", "shared", "worktree"]>>;
65
- }, "strict", z.ZodTypeAny, {
66
- strict?: boolean | undefined;
67
- max_rounds?: number | undefined;
68
- max_parallel?: number | undefined;
69
- wake_timeout_sec?: number | undefined;
70
- retry?: number | undefined;
71
- workspace_mode?: "shared" | "auto" | "worktree" | undefined;
72
- }, {
73
- strict?: boolean | undefined;
74
- max_rounds?: number | undefined;
75
- max_parallel?: number | undefined;
76
- wake_timeout_sec?: number | undefined;
77
- retry?: number | undefined;
78
- workspace_mode?: "shared" | "auto" | "worktree" | undefined;
79
- }>;
43
+ workspace_mode: z.ZodOptional<z.ZodEnum<{
44
+ auto: "auto";
45
+ shared: "shared";
46
+ worktree: "worktree";
47
+ }>>;
48
+ log_max_bytes: z.ZodOptional<z.ZodNumber>;
49
+ }, z.core.$strict>;
80
50
  export declare const GlobalConfig: z.ZodObject<{
81
51
  agents: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
82
- runtime: z.ZodOptional<z.ZodEnum<["claude-code", "codex"]>>;
52
+ runtime: z.ZodOptional<z.ZodEnum<{
53
+ "claude-code": "claude-code";
54
+ codex: "codex";
55
+ }>>;
83
56
  model: z.ZodOptional<z.ZodString>;
84
- effort: z.ZodOptional<z.ZodEnum<["minimal", "low", "medium", "high", "xhigh", "max"]>>;
57
+ effort: z.ZodOptional<z.ZodEnum<{
58
+ high: "high";
59
+ low: "low";
60
+ max: "max";
61
+ medium: "medium";
62
+ minimal: "minimal";
63
+ xhigh: "xhigh";
64
+ }>>;
85
65
  agent_md: z.ZodOptional<z.ZodString>;
86
66
  memory: z.ZodOptional<z.ZodObject<{
87
67
  global: z.ZodOptional<z.ZodString>;
88
68
  project: z.ZodOptional<z.ZodString>;
89
- }, "strict", z.ZodTypeAny, {
90
- global?: string | undefined;
91
- project?: string | undefined;
92
- }, {
93
- global?: string | undefined;
94
- project?: string | undefined;
95
- }>>;
69
+ }, z.core.$strict>>;
96
70
  resume: z.ZodOptional<z.ZodBoolean>;
97
- can_message: z.ZodOptional<z.ZodUnion<[z.ZodLiteral<"all">, z.ZodArray<z.ZodString, "many">]>>;
71
+ can_message: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<"all">, z.ZodArray<z.ZodString>]>>;
98
72
  can_edit_agent_md: z.ZodOptional<z.ZodBoolean>;
99
- owns: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
100
- }, "strict", z.ZodTypeAny, {
101
- runtime?: "claude-code" | "codex" | undefined;
102
- model?: string | undefined;
103
- effort?: "minimal" | "low" | "medium" | "high" | "xhigh" | "max" | undefined;
104
- agent_md?: string | undefined;
105
- memory?: {
106
- global?: string | undefined;
107
- project?: string | undefined;
108
- } | undefined;
109
- resume?: boolean | undefined;
110
- can_message?: string[] | "all" | undefined;
111
- can_edit_agent_md?: boolean | undefined;
112
- owns?: string[] | undefined;
113
- }, {
114
- runtime?: "claude-code" | "codex" | undefined;
115
- model?: string | undefined;
116
- effort?: "minimal" | "low" | "medium" | "high" | "xhigh" | "max" | undefined;
117
- agent_md?: string | undefined;
118
- memory?: {
119
- global?: string | undefined;
120
- project?: string | undefined;
121
- } | undefined;
122
- resume?: boolean | undefined;
123
- can_message?: string[] | "all" | undefined;
124
- can_edit_agent_md?: boolean | undefined;
125
- owns?: string[] | undefined;
126
- }>>>;
127
- }, "strict", z.ZodTypeAny, {
128
- agents: Record<string, {
129
- runtime?: "claude-code" | "codex" | undefined;
130
- model?: string | undefined;
131
- effort?: "minimal" | "low" | "medium" | "high" | "xhigh" | "max" | undefined;
132
- agent_md?: string | undefined;
133
- memory?: {
134
- global?: string | undefined;
135
- project?: string | undefined;
136
- } | undefined;
137
- resume?: boolean | undefined;
138
- can_message?: string[] | "all" | undefined;
139
- can_edit_agent_md?: boolean | undefined;
140
- owns?: string[] | undefined;
141
- }>;
142
- }, {
143
- agents?: Record<string, {
144
- runtime?: "claude-code" | "codex" | undefined;
145
- model?: string | undefined;
146
- effort?: "minimal" | "low" | "medium" | "high" | "xhigh" | "max" | undefined;
147
- agent_md?: string | undefined;
148
- memory?: {
149
- global?: string | undefined;
150
- project?: string | undefined;
151
- } | undefined;
152
- resume?: boolean | undefined;
153
- can_message?: string[] | "all" | undefined;
154
- can_edit_agent_md?: boolean | undefined;
155
- owns?: string[] | undefined;
156
- }> | undefined;
157
- }>;
73
+ can_ask_user: z.ZodOptional<z.ZodBoolean>;
74
+ owns: z.ZodOptional<z.ZodArray<z.ZodString>>;
75
+ }, z.core.$strict>>>;
76
+ }, z.core.$strict>;
158
77
  export declare const ProjectConfig: z.ZodObject<{
159
78
  dir: z.ZodString;
160
79
  team: z.ZodObject<{
161
80
  lead: z.ZodString;
162
- }, "strict", z.ZodTypeAny, {
163
- lead: string;
164
- }, {
165
- lead: string;
166
- }>;
81
+ }, z.core.$strict>;
167
82
  dispatcher: z.ZodDefault<z.ZodObject<{
168
83
  max_rounds: z.ZodOptional<z.ZodNumber>;
169
84
  max_parallel: z.ZodOptional<z.ZodNumber>;
170
85
  wake_timeout_sec: z.ZodOptional<z.ZodNumber>;
171
86
  retry: z.ZodOptional<z.ZodNumber>;
172
87
  strict: z.ZodOptional<z.ZodBoolean>;
173
- workspace_mode: z.ZodOptional<z.ZodEnum<["auto", "shared", "worktree"]>>;
174
- }, "strict", z.ZodTypeAny, {
175
- strict?: boolean | undefined;
176
- max_rounds?: number | undefined;
177
- max_parallel?: number | undefined;
178
- wake_timeout_sec?: number | undefined;
179
- retry?: number | undefined;
180
- workspace_mode?: "shared" | "auto" | "worktree" | undefined;
181
- }, {
182
- strict?: boolean | undefined;
183
- max_rounds?: number | undefined;
184
- max_parallel?: number | undefined;
185
- wake_timeout_sec?: number | undefined;
186
- retry?: number | undefined;
187
- workspace_mode?: "shared" | "auto" | "worktree" | undefined;
188
- }>>;
88
+ workspace_mode: z.ZodOptional<z.ZodEnum<{
89
+ auto: "auto";
90
+ shared: "shared";
91
+ worktree: "worktree";
92
+ }>>;
93
+ log_max_bytes: z.ZodOptional<z.ZodNumber>;
94
+ }, z.core.$strict>>;
189
95
  agents: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
190
- runtime: z.ZodOptional<z.ZodEnum<["claude-code", "codex"]>>;
96
+ runtime: z.ZodOptional<z.ZodEnum<{
97
+ "claude-code": "claude-code";
98
+ codex: "codex";
99
+ }>>;
191
100
  model: z.ZodOptional<z.ZodString>;
192
- effort: z.ZodOptional<z.ZodEnum<["minimal", "low", "medium", "high", "xhigh", "max"]>>;
101
+ effort: z.ZodOptional<z.ZodEnum<{
102
+ high: "high";
103
+ low: "low";
104
+ max: "max";
105
+ medium: "medium";
106
+ minimal: "minimal";
107
+ xhigh: "xhigh";
108
+ }>>;
193
109
  agent_md: z.ZodOptional<z.ZodString>;
194
110
  memory: z.ZodOptional<z.ZodObject<{
195
111
  global: z.ZodOptional<z.ZodString>;
196
112
  project: z.ZodOptional<z.ZodString>;
197
- }, "strict", z.ZodTypeAny, {
198
- global?: string | undefined;
199
- project?: string | undefined;
200
- }, {
201
- global?: string | undefined;
202
- project?: string | undefined;
203
- }>>;
113
+ }, z.core.$strict>>;
204
114
  resume: z.ZodOptional<z.ZodBoolean>;
205
- can_message: z.ZodOptional<z.ZodUnion<[z.ZodLiteral<"all">, z.ZodArray<z.ZodString, "many">]>>;
115
+ can_message: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<"all">, z.ZodArray<z.ZodString>]>>;
206
116
  can_edit_agent_md: z.ZodOptional<z.ZodBoolean>;
207
- owns: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
208
- }, "strict", z.ZodTypeAny, {
209
- runtime?: "claude-code" | "codex" | undefined;
210
- model?: string | undefined;
211
- effort?: "minimal" | "low" | "medium" | "high" | "xhigh" | "max" | undefined;
212
- agent_md?: string | undefined;
213
- memory?: {
214
- global?: string | undefined;
215
- project?: string | undefined;
216
- } | undefined;
217
- resume?: boolean | undefined;
218
- can_message?: string[] | "all" | undefined;
219
- can_edit_agent_md?: boolean | undefined;
220
- owns?: string[] | undefined;
221
- }, {
222
- runtime?: "claude-code" | "codex" | undefined;
223
- model?: string | undefined;
224
- effort?: "minimal" | "low" | "medium" | "high" | "xhigh" | "max" | undefined;
225
- agent_md?: string | undefined;
226
- memory?: {
227
- global?: string | undefined;
228
- project?: string | undefined;
229
- } | undefined;
230
- resume?: boolean | undefined;
231
- can_message?: string[] | "all" | undefined;
232
- can_edit_agent_md?: boolean | undefined;
233
- owns?: string[] | undefined;
234
- }>>>;
235
- }, "strict", z.ZodTypeAny, {
236
- agents: Record<string, {
237
- runtime?: "claude-code" | "codex" | undefined;
238
- model?: string | undefined;
239
- effort?: "minimal" | "low" | "medium" | "high" | "xhigh" | "max" | undefined;
240
- agent_md?: string | undefined;
241
- memory?: {
242
- global?: string | undefined;
243
- project?: string | undefined;
244
- } | undefined;
245
- resume?: boolean | undefined;
246
- can_message?: string[] | "all" | undefined;
247
- can_edit_agent_md?: boolean | undefined;
248
- owns?: string[] | undefined;
249
- }>;
250
- dir: string;
251
- team: {
252
- lead: string;
253
- };
254
- dispatcher: {
255
- strict?: boolean | undefined;
256
- max_rounds?: number | undefined;
257
- max_parallel?: number | undefined;
258
- wake_timeout_sec?: number | undefined;
259
- retry?: number | undefined;
260
- workspace_mode?: "shared" | "auto" | "worktree" | undefined;
261
- };
262
- }, {
263
- dir: string;
264
- team: {
265
- lead: string;
266
- };
267
- agents?: Record<string, {
268
- runtime?: "claude-code" | "codex" | undefined;
269
- model?: string | undefined;
270
- effort?: "minimal" | "low" | "medium" | "high" | "xhigh" | "max" | undefined;
271
- agent_md?: string | undefined;
272
- memory?: {
273
- global?: string | undefined;
274
- project?: string | undefined;
275
- } | undefined;
276
- resume?: boolean | undefined;
277
- can_message?: string[] | "all" | undefined;
278
- can_edit_agent_md?: boolean | undefined;
279
- owns?: string[] | undefined;
280
- }> | undefined;
281
- dispatcher?: {
282
- strict?: boolean | undefined;
283
- max_rounds?: number | undefined;
284
- max_parallel?: number | undefined;
285
- wake_timeout_sec?: number | undefined;
286
- retry?: number | undefined;
287
- workspace_mode?: "shared" | "auto" | "worktree" | undefined;
288
- } | undefined;
289
- }>;
117
+ can_ask_user: z.ZodOptional<z.ZodBoolean>;
118
+ owns: z.ZodOptional<z.ZodArray<z.ZodString>>;
119
+ }, z.core.$strict>>>;
120
+ }, z.core.$strict>;
290
121
  export interface DispatcherSettings {
291
122
  max_rounds: number;
292
123
  max_parallel: number;
@@ -295,9 +126,11 @@ export interface DispatcherSettings {
295
126
  strict: boolean;
296
127
  /** "auto": a git worktree per non-lead agent when agents can run in parallel, otherwise one shared checkout. */
297
128
  workspace_mode: "auto" | "shared" | "worktree";
129
+ /** log.jsonl is rotated to log.1.jsonl past this size; 0 = never. */
130
+ log_max_bytes: number;
298
131
  }
299
132
  export declare const DISPATCHER_DEFAULTS: DispatcherSettings;
300
133
  /** How a run turned out, independent of why it stopped (`EndReason`). Only "completed" means success. */
301
- export declare const RUN_OUTCOMES: readonly ["completed", "partial", "blocked", "failed", "cancelled"];
134
+ export declare const RUN_OUTCOMES: readonly ["completed", "partial", "blocked", "failed", "waiting", "cancelled"];
302
135
  export type RunOutcome = (typeof RUN_OUTCOMES)[number];
303
- export declare function exitCodeForOutcome(outcome: RunOutcome): 0 | 1 | 2 | 130;
136
+ export declare function exitCodeForOutcome(outcome: RunOutcome): 0 | 1 | 2 | 3 | 130;
package/dist/schema.js CHANGED
@@ -34,6 +34,7 @@ export const AgentPartial = z
34
34
  resume: z.boolean().optional(),
35
35
  can_message: z.union([z.literal("all"), z.array(z.string())]).optional(),
36
36
  can_edit_agent_md: z.boolean().optional(),
37
+ can_ask_user: z.boolean().optional(),
37
38
  owns: z.array(z.string()).optional(),
38
39
  })
39
40
  .strict();
@@ -45,11 +46,12 @@ export const DispatcherPartial = z
45
46
  retry: z.number().int().min(0).optional(),
46
47
  strict: z.boolean().optional(),
47
48
  workspace_mode: z.enum(["auto", "shared", "worktree"]).optional(),
49
+ log_max_bytes: z.number().int().min(0).optional(),
48
50
  })
49
51
  .strict();
50
52
  export const GlobalConfig = z
51
53
  .object({
52
- agents: z.record(AgentPartial).default({}),
54
+ agents: z.record(z.string(), AgentPartial).default({}),
53
55
  })
54
56
  .strict();
55
57
  export const ProjectConfig = z
@@ -57,7 +59,7 @@ export const ProjectConfig = z
57
59
  dir: z.string(),
58
60
  team: z.object({ lead: z.string() }).strict(),
59
61
  dispatcher: DispatcherPartial.default({}),
60
- agents: z.record(AgentPartial).default({}),
62
+ agents: z.record(z.string(), AgentPartial).default({}),
61
63
  })
62
64
  .strict();
63
65
  export const DISPATCHER_DEFAULTS = {
@@ -67,15 +69,18 @@ export const DISPATCHER_DEFAULTS = {
67
69
  retry: 1,
68
70
  strict: false,
69
71
  workspace_mode: "auto",
72
+ log_max_bytes: 8 * 1024 * 1024,
70
73
  };
71
74
  /** How a run turned out, independent of why it stopped (`EndReason`). Only "completed" means success. */
72
- export const RUN_OUTCOMES = ["completed", "partial", "blocked", "failed", "cancelled"];
75
+ export const RUN_OUTCOMES = ["completed", "partial", "blocked", "failed", "waiting", "cancelled"];
73
76
  export function exitCodeForOutcome(outcome) {
74
77
  switch (outcome) {
75
78
  case "completed":
76
79
  return 0;
77
80
  case "failed":
78
81
  return 1;
82
+ case "waiting":
83
+ return 3;
79
84
  case "cancelled":
80
85
  return 130;
81
86
  default:
package/dist/status.d.ts CHANGED
@@ -44,7 +44,7 @@ export interface RunReport {
44
44
  run_id: string;
45
45
  dir: string;
46
46
  /** running: its dispatcher is alive; interrupted: it died mid-run; ended: it stopped (see end_reason). */
47
- state: "running" | "interrupted" | "ended";
47
+ state: "running" | "interrupted" | "ended" | "waiting";
48
48
  live: boolean;
49
49
  end_reason?: EndReason;
50
50
  outcome?: RunOutcome;
@@ -83,6 +83,12 @@ export interface RunReport {
83
83
  ok: boolean;
84
84
  error?: string;
85
85
  }>;
86
+ /** Present while the run waits for the user: the file to fill in and how many questions are still open. */
87
+ ask?: {
88
+ path: string;
89
+ total: number;
90
+ unanswered: number;
91
+ };
86
92
  }
87
93
  export interface AgentReport {
88
94
  name: string;
@@ -104,6 +110,8 @@ export interface StatusReport {
104
110
  };
105
111
  agents: AgentReport[];
106
112
  run?: RunReport;
113
+ /** Hints that a `memory tidy` might help. Only a suggestion; nothing runs it. */
114
+ memory_advice?: string[];
107
115
  }
108
116
  export interface TaskListReport {
109
117
  schema_version: 1;
package/dist/status.js CHANGED
@@ -1,8 +1,10 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
+ import { askReplyPath, readAskReply } from "./ask-reply.js";
3
4
  import { RESULT_FILE } from "./dispatcher.js";
4
5
  import { bindRunProject, loadRunState, outcomeOf } from "./run-store.js";
5
6
  import { listUnread } from "./mailbox.js";
7
+ import { tidyAdvice } from "./memory-tidy.js";
6
8
  import { inspectProjectLock, lockHolderAlive } from "./project-lock.js";
7
9
  export function latestRun(project) {
8
10
  const root = project.paths.runs;
@@ -76,8 +78,10 @@ function alive(pid) {
76
78
  return e.code === "EPERM";
77
79
  }
78
80
  }
79
- const OUTCOME_ZH = { completed: "完成", partial: "部分完成", blocked: "受阻", failed: "失敗", cancelled: "已取消" };
81
+ const OUTCOME_ZH = { completed: "完成", partial: "部分完成", blocked: "受阻", failed: "失敗", waiting: "等待回答", cancelled: "已取消" };
80
82
  export function runStateLabel(s, live = alive(s.pid)) {
83
+ if (s.end_reason === "waiting")
84
+ return "等待回答";
81
85
  if (s.end_reason) {
82
86
  const o = outcomeOf(s);
83
87
  // Runs from before outcomes existed: keep what they recorded, but never present it as a checked success.
@@ -137,7 +141,7 @@ function stepsLine(steps, c, finished = false) {
137
141
  function queueOf(project) {
138
142
  const rows = Object.values(project.agents)
139
143
  .map((a) => ({ agent: a.name, msg: listUnread(project, a.name)[0] }))
140
- .filter((r) => !!r.msg);
144
+ .flatMap((r) => (r.msg ? [{ agent: r.agent, msg: r.msg }] : []));
141
145
  rows.sort((x, y) => {
142
146
  if ((x.agent === project.lead) !== (y.agent === project.lead))
143
147
  return x.agent === project.lead ? -1 : 1;
@@ -155,10 +159,23 @@ export function buildRunReport(run, project, opts = {}) {
155
159
  const result = path.join(run.dir, RESULT_FILE);
156
160
  const hasResult = detail && s.end_reason === "done" && fs.existsSync(result);
157
161
  const cur = s.steps?.find((x) => !x.done);
162
+ let ask;
163
+ if (s.end_reason === "waiting") {
164
+ try {
165
+ const r = readAskReply(run.dir);
166
+ if (r) {
167
+ const open = r.questions.filter((q) => !q.delivered);
168
+ ask = { path: askReplyPath(run.dir), total: open.length, unanswered: open.filter((q) => !q.answer?.value.trim()).length };
169
+ }
170
+ }
171
+ catch {
172
+ ask = { path: askReplyPath(run.dir), total: 0, unanswered: 0 };
173
+ }
174
+ }
158
175
  return {
159
176
  run_id: s.run_id,
160
177
  dir: run.dir,
161
- state: s.end_reason ? "ended" : live ? "running" : "interrupted",
178
+ state: s.end_reason === "waiting" ? "waiting" : s.end_reason ? "ended" : live ? "running" : "interrupted",
162
179
  live,
163
180
  end_reason: s.end_reason,
164
181
  outcome: o?.outcome,
@@ -186,6 +203,7 @@ export function buildRunReport(run, project, opts = {}) {
186
203
  mail_layout: s.mail_layout,
187
204
  workspace_mode: s.workspace_mode,
188
205
  last_wake: s.last_wake,
206
+ ...(ask ? { ask } : {}),
189
207
  };
190
208
  }
191
209
  const projectInfo = (p) => ({ name: p.name, dir: p.dir, lead: p.lead });
@@ -203,6 +221,13 @@ export function buildStatusReport(base, runId) {
203
221
  // Mail is per run: count the unread mail of the run being shown.
204
222
  const project = run ? bindRunProject(base, run.dir, run.state.mail_layout) : base;
205
223
  const report = run ? buildRunReport(run, base) : undefined;
224
+ let advice = [];
225
+ try {
226
+ advice = tidyAdvice(base).map((a) => `${a.agent}: ${a.reason}`);
227
+ }
228
+ catch {
229
+ /* a hint only: never let it break status */
230
+ }
206
231
  return {
207
232
  schema_version: 1,
208
233
  project: projectInfo(base),
@@ -214,6 +239,7 @@ export function buildStatusReport(base, runId) {
214
239
  last_wake: run?.state.last_wake[a.name],
215
240
  })),
216
241
  run: report,
242
+ ...(advice.length ? { memory_advice: advice } : {}),
217
243
  };
218
244
  }
219
245
  /** Every run of the project, newest first. */
@@ -251,13 +277,16 @@ function summaryBlock(run, now, c) {
251
277
  lines.push(`${L("進度:")}${steps ?? `無清單;已用 ${s.rounds}/${s.max_rounds} 輪(lead 可在信中加入 "## Steps" 清單)`}`);
252
278
  const active = Object.entries(s.active ?? {});
253
279
  if (active.length) {
254
- active.forEach(([agent, a], i) => lines.push(`${i === 0 ? L("現在:") : " "}${c.cyan(agent)} ${live ? c.green(`工作中 ${since(a.since, now)}`) : c.yellow("曾在工作(已中斷)")},處理 ${handlingText(a.handling)}`));
280
+ for (const [i, [agent, a]] of active.entries()) {
281
+ lines.push(`${i === 0 ? L("現在:") : " "}${c.cyan(agent)} ${live ? c.green(`工作中 ${since(a.since, now)}`) : c.yellow("曾在工作(已中斷)")},處理 ${handlingText(a.handling)}`);
282
+ }
255
283
  }
256
284
  else
257
285
  lines.push(`${L("現在:")}${c.dim(`閒置${s.end_reason ? `(執行已結束:${s.end_reason})` : live ? "" : "(執行未在運作)"}`)}`);
258
286
  const queue = s.queue;
259
287
  if (queue.length) {
260
- queue.slice(0, 3).forEach((q, i) => lines.push(`${i === 0 ? L("下一個:") : " "}${c.cyan(q.agent)} ← ${mailText(q)}`));
288
+ for (const [i, q] of queue.slice(0, 3).entries())
289
+ lines.push(`${i === 0 ? L("下一個:") : " "}${c.cyan(q.agent)} ← ${mailText(q)}`);
261
290
  if (queue.length > 3)
262
291
  lines.push(c.dim(` … 還有 ${queue.length - 3} 筆`));
263
292
  }
@@ -271,6 +300,8 @@ function summaryBlock(run, now, c) {
271
300
  const lastWake = (s.wakes ?? []).at(-1);
272
301
  if (lastWake)
273
302
  lines.push(`${L("上一次:")}#${lastWake.round} ${c.cyan(lastWake.agent)} ${okText(c, lastWake.ok)} → ${sentText(lastWake.sent)}`);
303
+ if (s.ask)
304
+ lines.push(`${L("等你回答:")}${s.ask.unanswered}/${s.ask.total} 題未答 → ${s.ask.path}`, c.dim(` 填完後執行 agent-lyceum answer ${s.run_id}(或 resume)`));
274
305
  lines.push(`${L("流程:")}${flowLine(s, c)}`);
275
306
  return lines;
276
307
  }
@@ -343,7 +374,8 @@ export function formatTaskListReport(report, color = false) {
343
374
  const head = `${c.bold("專案:")}${report.project.name} ${c.dim(`(共 ${runs.length} 個任務)`)}`;
344
375
  if (!runs.length)
345
376
  return `${head}\n\n無執行紀錄。`;
346
- const rows = [["ID", "狀態", "輪次", "目前步驟", "任務"]];
377
+ const header = ["ID", "狀態", "輪次", "目前步驟", "任務"];
378
+ const rows = [header];
347
379
  for (const s of runs) {
348
380
  rows.push([
349
381
  s.run_id,
@@ -353,11 +385,11 @@ export function formatTaskListReport(report, color = false) {
353
385
  clip(taskText(s), 60),
354
386
  ]);
355
387
  }
356
- const w = rows[0].map((_, i) => Math.max(...rows.map((r) => cols(r[i]))));
388
+ const w = header.map((_, i) => Math.max(...rows.map((r) => cols(r[i] ?? ""))));
357
389
  const line = (r, ri) => r
358
390
  .map((t, i) => {
359
- const p = i === r.length - 1 ? t : padCols(t, w[i]);
360
- return ri === 0 ? c.bold(p) : i === 1 ? stateColor(c, t) + " ".repeat(Math.max(0, w[i] - cols(t))) : p;
391
+ const p = i === r.length - 1 ? t : padCols(t, w[i] ?? 0);
392
+ return ri === 0 ? c.bold(p) : i === 1 ? stateColor(c, t) + " ".repeat(Math.max(0, (w[i] ?? 0) - cols(t))) : p;
361
393
  })
362
394
  .join(" ")
363
395
  .trimEnd();
@@ -371,23 +403,25 @@ export function formatStatus(base, now = Date.now(), color = false) {
371
403
  export function formatStatusReport(report, now = Date.now(), color = false) {
372
404
  const c = paint(color);
373
405
  const run = report.run;
374
- const rows = [["成員", "執行環境", "未讀", "上次喚醒"]];
406
+ const header = ["成員", "執行環境", "未讀", "上次喚醒"];
407
+ const rows = [header];
375
408
  for (const a of report.agents) {
376
409
  const lw = a.last_wake;
377
410
  rows.push([a.name + (a.lead ? " (lead)" : ""), a.runtime ?? "?", String(a.unread), lw ? `${lw.ok ? "成功" : "失敗"} ${lw.at}` : "-"]);
378
411
  }
379
- const w = rows[0].map((_, i) => Math.max(...rows.map((r) => cols(r[i]))));
412
+ const w = header.map((_, i) => Math.max(...rows.map((r) => cols(r[i] ?? ""))));
380
413
  // Pad on the plain text first so ANSI codes never skew the column widths.
381
414
  const cell = (r, ri, i) => {
382
- const t = i === r.length - 1 ? r[i] : padCols(r[i], w[i]); // no trailing padding, as in plain output
415
+ const text = r[i] ?? "";
416
+ const t = i === r.length - 1 ? text : padCols(text, w[i] ?? 0); // no trailing padding, as in plain output
383
417
  if (ri === 0)
384
418
  return c.bold(t);
385
419
  if (i === 0)
386
- return r[0].endsWith("(lead)") ? c.cyan(t) : t;
420
+ return text.endsWith("(lead)") ? c.cyan(t) : t;
387
421
  if (i === 2)
388
- return r[2] === "0" ? c.dim(t) : c.yellow(t);
422
+ return text === "0" ? c.dim(t) : c.yellow(t);
389
423
  if (i === 3)
390
- return r[3].startsWith("失敗") ? c.red(t) : r[3].startsWith("成功") ? c.green(t) : c.dim(t);
424
+ return text.startsWith("失敗") ? c.red(t) : text.startsWith("成功") ? c.green(t) : c.dim(t);
391
425
  return t;
392
426
  };
393
427
  const table = rows.map((r, ri) => r.map((_, i) => cell(r, ri, i)).join(" ").trimEnd());
@@ -395,5 +429,7 @@ export function formatStatusReport(report, now = Date.now(), color = false) {
395
429
  const lines = [`${c.bold("專案:")}${report.project.name} ${c.dim(`(repo:${report.project.dir})`)}`, "", ...summaryBlock(run, now, c), "", ...table];
396
430
  if (run)
397
431
  lines.push("", `輸出 tokens:${tokens(run.output_tokens)}`);
432
+ if (report.memory_advice?.length)
433
+ lines.push("", `記憶:${report.memory_advice.join(";")}(建議執行 agent-lyceum memory tidy -p ${report.project.name};status 只提示,不會自動整理)`);
398
434
  return lines.join("\n");
399
435
  }