@llm4ts/runner 2.52.0 → 2.54.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/src/AgentTree.ts CHANGED
@@ -26,8 +26,12 @@ export type TreeInput =
26
26
 
27
27
  export type LaneStatus = "running" | "done" | "failed"
28
28
 
29
+ /** What a lane stands for: a story's coder (ADR 0022), a roundtable attendee (ADR 0046), or a taskforce specialist (ADR 0047). */
30
+ export type LaneKind = "story" | "attendee" | "specialist"
31
+
29
32
  export interface TreeLane {
30
33
  readonly id: string
34
+ readonly kind: LaneKind
31
35
  /** The lane's open stages below `story <id>`, innermost last. */
32
36
  readonly stages: ReadonlyArray<string>
33
37
  readonly executor: string | undefined
@@ -68,6 +72,24 @@ export interface TreeLane {
68
72
  readonly pause: { readonly label: string; readonly since: number } | undefined
69
73
  /** Tasks running beside the lane's coder, each on a coder of its own (ADR 0034). */
70
74
  readonly taskCoders: ReadonlyArray<TreeTaskCoder>
75
+ /** A specialist's last report (ADR 0047); other lanes have none. */
76
+ readonly report:
77
+ | {
78
+ readonly brief: string
79
+ readonly status: string
80
+ readonly summary: string
81
+ readonly attempt: number
82
+ }
83
+ | undefined
84
+ /** An attendee's last turn (ADR 0046); a story lane has none. */
85
+ readonly turn:
86
+ | {
87
+ readonly id: number
88
+ readonly kind: string
89
+ readonly summary: string
90
+ readonly blocking: boolean
91
+ }
92
+ | undefined
71
93
  }
72
94
 
73
95
  /** A task running beside a story's coder, in its own worktree (ADR 0034). */
@@ -257,7 +279,8 @@ const logged = (state: TreeState, entry: TreeLogEntry): TreeState => ({
257
279
  log: [...state.log, entry]
258
280
  })
259
281
 
260
- const storyStage = /^story (\S+)$/u
282
+ /** The stage that opens a lane: a story's, a roundtable attendee's or a taskforce specialist's (names may hold spaces). */
283
+ const laneStage = /^(story|attendee|specialist) (.+)$/u
261
284
  const expandedTools = 20
262
285
 
263
286
  const updateLane = (
@@ -401,10 +424,18 @@ const reduceEvent = (state: TreeState, at: number, event: FlowEvent): TreeState
401
424
  }
402
425
  switch (event._tag) {
403
426
  case "StageStarted": {
404
- const story = storyStage.exec(event.stage)?.[1]
427
+ const opened = laneStage.exec(event.stage)
428
+ const story = opened?.[2]
405
429
  if (story !== undefined && event.lane === story) {
430
+ const kind: LaneKind =
431
+ opened?.[1] === "attendee"
432
+ ? "attendee"
433
+ : opened?.[1] === "specialist"
434
+ ? "specialist"
435
+ : "story"
406
436
  const fresh: TreeLane = {
407
437
  id: story,
438
+ kind,
408
439
  stages: [],
409
440
  executor: event.executor,
410
441
  startedAt: at,
@@ -423,7 +454,9 @@ const reduceEvent = (state: TreeState, at: number, event: FlowEvent): TreeState
423
454
  task: undefined,
424
455
  children: [],
425
456
  pause: undefined,
426
- taskCoders: []
457
+ taskCoders: [],
458
+ report: undefined,
459
+ turn: undefined
427
460
  }
428
461
  return {
429
462
  ...current,
@@ -444,7 +477,7 @@ const reduceEvent = (state: TreeState, at: number, event: FlowEvent): TreeState
444
477
  }
445
478
  case "StageCompleted":
446
479
  case "StageFailed": {
447
- if (lane !== undefined && event.stage === `story ${lane.id}`) {
480
+ if (lane !== undefined && event.stage === `${lane.kind} ${lane.id}`) {
448
481
  const status: LaneStatus = event._tag === "StageFailed" ? "failed" : "done"
449
482
  const closed = updateLane(current, lane.id, (open) => ({
450
483
  ...open,
@@ -542,6 +575,43 @@ const reduceEvent = (state: TreeState, at: number, event: FlowEvent): TreeState
542
575
  : {})
543
576
  }))
544
577
  }
578
+ case "RoundtableTurn": {
579
+ const what = `#${event.turn} ${event.kind}${event.blocking ? " (blocking)" : ""}${
580
+ event.answers.length === 0 ? "" : ` → ${event.answers.map((id) => `#${id}`).join(", ")}`
581
+ }: ${event.summary}`
582
+ if (lane === undefined) {
583
+ // The Chair and the Owner have no seat at the table: their words are the run's.
584
+ return logged(current, { at, source: "run", who: event.lane, what })
585
+ }
586
+ const spoken = updateLane(current, lane.id, (open) => ({
587
+ ...open,
588
+ stages: [`item ${event.item} r${event.round}`],
589
+ turn: {
590
+ id: event.turn,
591
+ kind: event.kind,
592
+ summary: event.summary,
593
+ blocking: event.blocking
594
+ }
595
+ }))
596
+ return logged(spoken, { at, source: "lane", who: lane.id, what })
597
+ }
598
+ case "BriefReported": {
599
+ const what = `${event.brief} ${event.status}${event.attempt > 1 ? ` (attempt ${event.attempt})` : ""}: ${event.summary}`
600
+ if (lane === undefined) {
601
+ // A name without a lane of its own: its report is the run's.
602
+ return logged(current, { at, source: "run", who: event.lane, what })
603
+ }
604
+ const handed = updateLane(current, lane.id, (open) => ({
605
+ ...open,
606
+ report: {
607
+ brief: event.brief,
608
+ status: event.status,
609
+ summary: event.summary,
610
+ attempt: event.attempt
611
+ }
612
+ }))
613
+ return logged(handed, { at, source: "lane", who: lane.id, what })
614
+ }
545
615
  case "StoryJudged": {
546
616
  const verdict: TreeVerdict = {
547
617
  lane: event.lane,
@@ -1266,6 +1336,21 @@ const statusMark = (lane: TreeLane, now: number | undefined, idleAfterMs: number
1266
1336
  }
1267
1337
 
1268
1338
  /** `3/5 wire session cookie`, or `2/5 tasks` between tasks, or nothing before the plan. */
1339
+ /** An attendee's last turn as one line: `#4 objection (blocking): …`. */
1340
+ const turnText = (lane: TreeLane): string =>
1341
+ lane.turn === undefined
1342
+ ? ""
1343
+ : `#${lane.turn.id} ${lane.turn.kind}${lane.turn.blocking ? " (blocking)" : ""}: ${lane.turn.summary}`
1344
+
1345
+ /** A specialist's last report as one line: `B3 PASS: …`. */
1346
+ const reportText = (lane: TreeLane): string =>
1347
+ lane.report === undefined
1348
+ ? ""
1349
+ : `${lane.report.brief} ${lane.report.status}${lane.report.attempt > 1 ? ` (attempt ${lane.report.attempt})` : ""}: ${lane.report.summary}`
1350
+
1351
+ /** A lane without a coder of its own: an attendee borrows the room's seat, a specialist its role's. */
1352
+ const seatless = (lane: TreeLane): boolean => lane.kind === "attendee" || lane.kind === "specialist"
1353
+
1269
1354
  const taskText = (lane: TreeLane): string =>
1270
1355
  lane.task !== undefined
1271
1356
  ? `${lane.task.index}/${lane.task.count} ${lane.task.title}`
@@ -1287,14 +1372,22 @@ const laneLine = (
1287
1372
  selected: boolean,
1288
1373
  idleAfterMs: number
1289
1374
  ): Line => {
1290
- const task = taskText(lane)
1291
- const stage = lane.stages.at(-1) ?? "starting"
1375
+ const task =
1376
+ lane.kind === "attendee"
1377
+ ? turnText(lane)
1378
+ : lane.kind === "specialist"
1379
+ ? reportText(lane)
1380
+ : taskText(lane)
1381
+ const stage =
1382
+ lane.stages.at(-1) ??
1383
+ (lane.kind === "attendee" ? "listening" : lane.kind === "specialist" ? "idle" : "starting")
1292
1384
  const head: Line = [
1293
1385
  span(selected ? "▸ " : " "),
1294
1386
  statusMark(lane, now, idleAfterMs),
1295
1387
  span(" "),
1296
1388
  span(lane.id, "bold"),
1297
- span(` ${cloneName(lane.executor ?? "(leasing)", lane.clone)}`),
1389
+ // An attendee borrows the room's one seat, a specialist its role's: no executor of its own to show.
1390
+ ...(seatless(lane) ? [] : [span(` ${cloneName(lane.executor ?? "(leasing)", lane.clone)}`)]),
1298
1391
  ...(task.length === 0 ? [] : [span(" · "), span(task)]),
1299
1392
  // A task is a stage of its own name: say it once.
1300
1393
  ...(stage === lane.task?.title ? [] : [span(" · "), span(stage, "dim")]),
@@ -1416,8 +1509,9 @@ const detailBox = (
1416
1509
  expanded: boolean
1417
1510
  ): Array<Line> => {
1418
1511
  const inner = width - 4
1419
- const checklist: Array<Line> =
1420
- lane.tasks.length === 0
1512
+ const checklist: Array<Line> = seatless(lane)
1513
+ ? []
1514
+ : lane.tasks.length === 0
1421
1515
  ? [[span("no task plan yet", "dim")]]
1422
1516
  : lane.tasks.map((task): Line => {
1423
1517
  const beside = lane.taskCoders.find((coder) => coder.index === task.index)
@@ -1447,9 +1541,14 @@ const detailBox = (
1447
1541
  : lane.tools.length === 0
1448
1542
  ? []
1449
1543
  : [[span("tools "), span(lane.tools.slice(-3).join(" · "), "dim")]]
1450
- const title = `${lane.id} · ${cloneName(lane.executor ?? "(leasing)", lane.clone)}${
1451
- lane.task === undefined ? "" : ` · task ${lane.task.index}/${lane.task.count}`
1452
- }`
1544
+ const title =
1545
+ lane.kind === "attendee"
1546
+ ? `${lane.id} · ${lane.turn === undefined ? "listening" : turnText(lane)}`
1547
+ : lane.kind === "specialist"
1548
+ ? `${lane.id} · ${lane.report === undefined ? "idle" : reportText(lane)}`
1549
+ : `${lane.id} · ${cloneName(lane.executor ?? "(leasing)", lane.clone)}${
1550
+ lane.task === undefined ? "" : ` · task ${lane.task.index}/${lane.task.count}`
1551
+ }`
1453
1552
  return box(
1454
1553
  width,
1455
1554
  "running",
package/src/Terminal.ts CHANGED
@@ -42,6 +42,8 @@ export const rendersEvent = (verbosity: Verbosity, event: FlowEvent): boolean =>
42
42
  case "UsageProgress":
43
43
  case "ExecutorReleased":
44
44
  case "StoryJudged":
45
+ case "RoundtableTurn":
46
+ case "BriefReported":
45
47
  case "Timed":
46
48
  case "TasksPlanned":
47
49
  case "TaskStarted":
@@ -253,6 +255,8 @@ export const terminalLine = (
253
255
  case "BlackboardRun":
254
256
  case "UsageProgress":
255
257
  case "StoryJudged":
258
+ case "RoundtableTurn":
259
+ case "BriefReported":
256
260
  case "Timed":
257
261
  case "TasksPlanned":
258
262
  case "TaskStarted":
package/src/index.ts CHANGED
@@ -42,6 +42,6 @@ export { makeChat } from "@llm4ts/flow/Chat"
42
42
  export { makePlanStore } from "@llm4ts/flow/Persistence"
43
43
  export { defaultPlanInstructions, planFrom, writeBrief } from "@llm4ts/flow/Planner"
44
44
  export { defaultPlanPath, parsePlan, Plan } from "@llm4ts/flow/Plan"
45
- export { AssistantMessage, Info } from "@llm4ts/flow/FlowEvents"
45
+ export { AssistantMessage, BriefReported, Info, RoundtableTurn } from "@llm4ts/flow/FlowEvents"
46
46
  export { FlowAborted, FlowLlmError } from "@llm4ts/flow/FlowError"
47
47
  export { loadPack } from "@llm4ts/flow/Pack"