superagent-claude 0.1.0 → 0.1.1

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.
@@ -13,9 +13,12 @@ import { DatabaseSync } from "node:sqlite";
13
13
  import { checkSync, lockSync } from "proper-lockfile";
14
14
  import { fileURLToPath } from "node:url";
15
15
  // package.json
16
- var version = "0.1.0";
16
+ var version = "0.1.1";
17
17
  // src/infra/env.ts
18
- /** Environment for subprocesses: only the inherited whitelist (PATH, HOME, LANG) plus explicitly named variables; the daemon environment is never passed whole. */
18
+ /**
19
+ * Environment for subprocesses: only the inherited whitelist (PATH, HOME, LANG) plus explicitly named variables.
20
+ * The daemon environment is never passed whole.
21
+ */
19
22
  const INHERITED_ENV = [
20
23
  "PATH",
21
24
  "HOME",
@@ -30,7 +33,7 @@ function subprocessEnv(base, explicit = {}) {
30
33
  return Object.assign(result, explicit);
31
34
  }
32
35
  // src/runtimes/claude/mcp.ts
33
- /** MCP bridge: exposes the host ToolSpecs as an in-process SDK MCP server named `superagent`, so tool names and argument validation match the host tools exactly. */
36
+ /** Exposes the host ToolSpecs as an in-process SDK MCP server named `superagent`, so names and validation match. */
34
37
  const MCP_SERVER = "superagent";
35
38
  const mcpToolName = (name) => `mcp__${MCP_SERVER}__${name}`;
36
39
  function toolHandler(spec, signal) {
@@ -106,7 +109,7 @@ const SUBAGENT_TYPE = `Only these subagent types are allowed: ${SUBAGENT_TYPES.j
106
109
  const SUBAGENT_ISOLATION = "Subagent isolation is not allowed";
107
110
  const SUBAGENT_NESTED = "A subagent cannot start another subagent";
108
111
  const SUBAGENT_HOST_TOOL = "Host tools are only available to the main worker, not to subagents";
109
- /** Turns off the CLI's commit and PR attribution in every session through the flag settings layer; settingSources stays as it is (ADR-34). */
112
+ /** Turns off CLI commit and PR attribution through the flag settings layer; settingSources stays as it is. */
110
113
  const ATTRIBUTION_OFF = {
111
114
  attribution: {
112
115
  commit: "",
@@ -372,18 +375,16 @@ const LIMITS = {
372
375
  }
373
376
  };
374
377
  // src/policy/admission.ts
375
- /** Default admission rules (matching pre-table behavior): unaddressed messages from listed senders are inferred candidates; unlisted senders are ignored. */
376
378
  const ADMISSION_DEFAULTS = {
377
379
  listedUnaddressed: "inferred",
378
380
  unlistedAddressed: "ignore",
379
381
  unlistedUnaddressed: "ignore"
380
382
  };
381
383
  // src/core/model.ts
382
- /** Stable key of a thread across the whole domain. */
383
384
  const threadKey = (ref) => `${ref.source}:${ref.id}`;
384
385
  /**
385
- * Normalizes valid timestamps to `Date#toISOString` form so stored timestamps compare lexicographically in memory and SQL;
386
- * values that do not parse as a timestamp are returned unchanged.
386
+ * Normalizes valid timestamps to `Date#toISOString` form so stored timestamps compare lexicographically in memory and
387
+ * SQL; values that do not parse as a timestamp are returned unchanged.
387
388
  */
388
389
  function isoTime(value) {
389
390
  const time = Date.parse(value);
@@ -391,10 +392,10 @@ function isoTime(value) {
391
392
  }
392
393
  /**
393
394
  * The tickets whose worker decides a person's follow-up: those it reached on their own Threads that can still decide it
394
- * and did not hand it back. A ticket can while it is live, also once a person dropped its Subscription to the Thread, and
395
- * while it is ended, not by a person, with the Trigger waiting to resume it. None once a person sent it to Eval, or while
396
- * an Eval holds it, which an earlier version may have queued. While a ticket owns it, the message goes to no Eval, and the
397
- * main Session may only stop the work on it.
395
+ * and did not hand it back. A ticket can while it is live, also once a person dropped its Subscription to the Thread,
396
+ * and while it is ended, not by a person, with the Trigger waiting to resume it. None once a person sent it to Eval, or
397
+ * while an Eval holds it, which an earlier version may have queued. While a ticket owns it, the message goes to no
398
+ * Eval, and the main Session may only stop the work on it.
398
399
  */
399
400
  function followupOwners(trigger, records) {
400
401
  if (!trigger.human) return [];
@@ -409,22 +410,21 @@ function followupOwners(trigger, records) {
409
410
  });
410
411
  }
411
412
  /**
412
- * Whether a Decision settled what a person's message asks for: a stop on it, taken or refused, decides only that ticket's
413
- * work, routing it to a ticket leaves it to that ticket's worker, and an unavailable one decided nothing.
413
+ * Whether a Decision settled what a person's message asks for: a stop on it, taken or refused, decides only that
414
+ * ticket's work, routing it to a ticket leaves it to that ticket's worker, and an unavailable one decided nothing.
414
415
  */
415
416
  const settled = (decision) => !["stop", "unavailable"].includes(decision.kind) && !decision.reason.some((reason) => [
416
417
  "stopNotExplicit",
417
418
  "stopUnknown",
418
419
  "routed"
419
420
  ].includes(reason.code));
420
- /** Whether a ticket is open: it has not ended. */
421
421
  const liveTicket = (ticket) => !ticket.ended;
422
- /** How a ticket ended. */
423
422
  const ticketOutcome = (ticket) => ticket.ended?.outcome;
424
423
  const runningAssignment = (ticket) => ticket.assignments.find((assignment) => assignment.state === "running");
425
424
  /**
426
- * Whether a Trigger reached a ticket: it started the ticket, came to it as an Input, or is on a Thread the ticket subscribes
427
- * to; with `context` false, not a Thread the ticket follows only for context, where its worker may read but not write.
425
+ * Whether a Trigger reached a ticket: it started the ticket, came to it as an Input, or is on a Thread the
426
+ * ticket subscribes to; with `context` false, not a Thread the ticket follows only for context, where its
427
+ * worker may read but not write.
428
428
  */
429
429
  function reached(ticket, trigger, thread, context = true) {
430
430
  if (ticket.origin.triggerId === trigger.id) return true;
@@ -435,25 +435,28 @@ function reached(ticket, trigger, thread, context = true) {
435
435
  }) || input && (context || !trigger.threads.every((ref) => thread(threadKey(ref))?.context?.includes(ticket.id)));
436
436
  }
437
437
  const latestAssignment = (ticket) => ticket.assignments.at(-1);
438
- /** The latest report any of a ticket's Assignments made, which is what the ticket last said. */
439
438
  const latestReport = (ticket) => ticket.assignments.findLast((assignment) => assignment.report)?.report;
440
439
  /**
441
- * Until when a ticket's worker had a turn to read what reached it: its latest report, but a progress report when `progress`
442
- * is false, or the end of its latest turn, whichever is later.
440
+ * Until when a ticket's worker had a turn to read what reached it: its latest report, but a progress report when
441
+ * `progress` is false, or the end of its latest turn, whichever is later.
443
442
  */
444
443
  function readUntil(ticket, progress = true) {
445
444
  return [(ticket.assignments.findLast((assignment) => assignment.report && (progress || assignment.report.state !== "progress"))?.report)?.at ?? "", ...ticket.assignments.map((assignment) => assignment.idleAt ?? "")].reduce((a, b) => a > b ? a : b);
446
445
  }
447
446
  /**
448
- * Whether a ticket waits for a person: its latest Assignment reported blocked or failed, and no person's message reached it
449
- * since. A later report that is neither, a person's message, or a retry, which starts a new Assignment, lifts it.
447
+ * Whether a ticket waits for a person: its latest Assignment reported blocked or failed, and no person's
448
+ * message reached it since. A later report that is neither, a person's message, or a retry, which starts a
449
+ * new Assignment, lifts it.
450
450
  */
451
451
  function waitsForPerson(ticket) {
452
452
  const report = latestAssignment(ticket)?.report;
453
453
  if (report?.state !== "blocked" && report?.state !== "failed") return false;
454
454
  return !ticket.inputs.some((input) => input.kind === "trigger" && input.human && !input.context && input.at > report.at);
455
455
  }
456
- /** Whether a running Assignment's worker finished with everything delivered to it: a turn ended (idleAt) at or after the last delivery, and no Trigger Input delivered to it is unread or was read after that turn (ADR-32). */
456
+ /**
457
+ * Whether a running Assignment's worker finished with everything delivered to it: a turn ended (idleAt) at or after the
458
+ * last delivery, and no Trigger Input delivered to it is unread or was read after that turn.
459
+ */
457
460
  function caughtUp(ticket, assignment) {
458
461
  const at = assignment.idleAt;
459
462
  if (!at || (assignment.deliveredAt ?? "") > at) return false;
@@ -507,10 +510,7 @@ const USER_ERRORS = {
507
510
  noLabels: ["refused", "This Source has no names to refresh"],
508
511
  nothingToSync: ["refused", "The {source} Source's part of the Policy has nothing to sync from, or the Source is off or waiting for setup"]
509
512
  };
510
- /**
511
- * Structured error thrown by Engine parts and application services when a caller request is invalid or references missing state.
512
- * Tool handlers and HTTP routes inspect its code and missing flag to return appropriate status responses, while the web panel localizes the code.
513
- */
513
+ /** Tool handlers and HTTP routes map `missing` to a status; the web panel localizes `code`. */
514
514
  var UserError = class extends Error {
515
515
  code;
516
516
  values;
@@ -524,15 +524,14 @@ var UserError = class extends Error {
524
524
  }
525
525
  };
526
526
  // src/core/ids.ts
527
- /** Stable content-derived identifiers; equal inputs always produce the same id. */
527
+ /** Equal inputs always produce the same id. */
528
528
  function key(...values) {
529
529
  return createHash("sha256").update(JSON.stringify(values)).digest("hex").slice(0, 32);
530
530
  }
531
531
  // src/core/part.ts
532
- /** Shared base class and configuration for Engine domain parts. */
533
532
  /**
534
- * Base class for Intake, Evals, Tickets, Assignments, and Replies providing shared access to repositories, policy, clock, and event emission.
535
- * Each subclass mutates its own domain aggregates and delegates cross-aggregate changes to sibling parts within the transaction opened by Engine.
533
+ * Subclasses mutate their own aggregates and delegate cross-aggregate changes to sibling parts, within the
534
+ * transaction Engine opens.
536
535
  */
537
536
  var EnginePart = class {
538
537
  repo;
@@ -547,7 +546,6 @@ var EnginePart = class {
547
546
  policy() {
548
547
  return this.options.policy();
549
548
  }
550
- /** The tickets whose worker decides a person's follow-up: see `followupOwners`. */
551
549
  owners(trigger) {
552
550
  return followupOwners(trigger, this.repo);
553
551
  }
@@ -557,9 +555,9 @@ var EnginePart = class {
557
555
  if (owners.length) throw new UserError("workerDecides", { tickets: owners.join(", ") });
558
556
  }
559
557
  /**
560
- * Record that a ticket's worker hands back a Trigger that reached the ticket, with why, and queue it for Eval when the
561
- * ticket decided it and no other ticket still does. One the ticket did not decide, such as a bot's, one a person sent to
562
- * Eval or one on a Thread it follows for context, went to Eval without it.
558
+ * Record that a ticket's worker hands back a Trigger that reached the ticket, with why, and queue it for Eval when
559
+ * the ticket decided it and no other ticket still does. One the ticket did not decide, such as a bot's, one a person
560
+ * sent to Eval or one on a Thread it follows for context, went to Eval without it.
563
561
  * @returns whether it was queued.
564
562
  */
565
563
  handBack(ticketId, trigger, reason) {
@@ -579,7 +577,6 @@ var EnginePart = class {
579
577
  });
580
578
  return decided && !this.owners(trigger).length && this.queueForEval(trigger);
581
579
  }
582
- /** Record that a ticket's worker took a person's message up as within the ticket, by acknowledging or answering it. */
583
580
  takeUp(ticketId, triggerId) {
584
581
  const disposition = this.repo.triggers.disposition(triggerId);
585
582
  if (disposition && !disposition.acked?.includes(ticketId)) this.repo.triggers.saveDisposition(triggerId, {
@@ -587,7 +584,6 @@ var EnginePart = class {
587
584
  acked: [...disposition.acked ?? [], ticketId]
588
585
  });
589
586
  }
590
- /** Queue a Trigger on its primary Thread for the next Eval, unless it waits there already or an Eval holds it. */
591
587
  queueForEval(trigger) {
592
588
  const primary = this.thread(trigger.threads[0]);
593
589
  if (primary.pending.includes(trigger.id) || primary.evaluating?.triggerIds.includes(trigger.id)) return false;
@@ -637,8 +633,7 @@ var EnginePart = class {
637
633
  }
638
634
  };
639
635
  // src/core/stage.ts
640
- /** Ticket stage classification and retry eligibility (docs/003). */
641
- /** Classifies a ticket's stage; the rules follow the table in docs/003 in order, where the first matching row wins. */
636
+ /** Rules are checked in order; the first match wins. */
642
637
  function ticketStage(ticket) {
643
638
  if (ticket.ended) return { stage: "ended" };
644
639
  if (ticket.delivery) return {
@@ -666,28 +661,21 @@ function ticketStage(ticket) {
666
661
  if (report === "waiting" || assignment.state === "ended") return { stage: "idle" };
667
662
  return { stage: "working" };
668
663
  }
669
- /** True when an open ticket is stalled on a blocked/failed report without a newer human follow-up, or has a failed input delivery. */
670
664
  function canRetry(ticket) {
671
665
  return !ticket.ended && (waitsForPerson(ticket) || !!ticket.delivery);
672
666
  }
673
667
  // src/core/tickets.ts
674
- /** Tickets: their lifecycle from an accept to their end, their Subscriptions and their Inputs. */
675
668
  const sameChannel = (a, b) => JSON.stringify(a) === JSON.stringify(b);
676
- /** Whether an Input is a message no person wrote, such as CI's, a bot's, or the main Session's note. */
677
669
  const automatic = (input) => input?.kind === "note" || input?.kind === "trigger" && !input.human;
678
670
  /**
679
- * Whether a ticket's worker read a Trigger Input before `until`, its latest report or turn end: its Session took the message
680
- * into a step before then. One delivered before reads were recorded counts as read once it reached the worker by then.
671
+ * Whether a ticket's worker read a Trigger Input before `until`, its latest report or turn end: its Session
672
+ * took the message into a step before then. One delivered before reads were recorded counts as read once it
673
+ * reached the worker by then.
681
674
  */
682
675
  const readBefore = (input, until) => input.readAt === void 0 ? (input.deliveredAt && input.deliveredAt > input.at ? input.deliveredAt : input.at) <= until : !!input.readAt && input.readAt < until;
683
676
  const STATUS_REQUEST = "Report the current ticket status with orchestrator_report. This is a status-only request: do not resume implementation or perform external actions.";
684
677
  const LIMIT_CONTINUE_NOTE = "Your previous run stopped at its turn or budget limit. A person replied, so the limits start over. Inspect the worktree and remote state, continue the assignment where it stopped, and report its status.";
685
- /**
686
- * Manages each ticket's lifecycle: creates the ticket for an accept decision with its Mandate, Subscriptions, and first
687
- * Assignment, binds and attaches Threads, adds Inputs, resumes, retries, restarts, and ends tickets, and drops Subscriptions.
688
- * Coordinates with Replies for the acknowledgement an accept carries and with Executor to start, deliver to, and stop assignments.
689
- * Intake, Evals, Assignments, and Engine delegate ticket and subscription mutations here.
690
- */
678
+ /** Coordinates with Replies for an accept's acknowledgement, and with the Executor to start, deliver, and stop. */
691
679
  var Tickets = class extends EnginePart {
692
680
  replies;
693
681
  constructor(options, replies) {
@@ -695,9 +683,8 @@ var Tickets = class extends EnginePart {
695
683
  this.replies = replies;
696
684
  }
697
685
  /**
698
- * Create the ticket an accept decided, under `mandate`, with the Channels and Threads of the Eval's Triggers, its first
699
- * Assignment, and the acknowledgement the accept carries, held until its Assignment starts (ADR-32), and queue its launch. Its origin is the admissible Trigger that
700
- * gave the Eval its strength, or the Eval's last Trigger for an Eval recorded before `primary` existed.
686
+ * The acknowledgement an accept carries is held until its Assignment starts. The origin is the admissible Trigger
687
+ * that gave the Eval its strength, or the Eval's last Trigger for an Eval recorded before `primary` existed.
701
688
  */
702
689
  create(id, decisionId, mandate, evaluation, outcome, at) {
703
690
  const { triggerIds } = evaluation;
@@ -762,8 +749,8 @@ var Tickets = class extends EnginePart {
762
749
  return ticket;
763
750
  }
764
751
  /**
765
- * Record that a Thread open tickets follow closed, and end each one done with its Threads (see `followsDone`), unless its
766
- * work has not finished (see `unfinished`); such work ends the ticket once it reports ready.
752
+ * Record that a Thread open tickets follow closed, and end each one done with its Threads (see `followsDone`), unless
753
+ * its work has not finished (see `unfinished`); such work ends the ticket once it reports ready.
767
754
  */
768
755
  threadClosed(thread, outcome, at) {
769
756
  const closed = threadKey(thread);
@@ -782,8 +769,9 @@ var Tickets = class extends EnginePart {
782
769
  }
783
770
  }
784
771
  /**
785
- * How a ticket that follows Threads is done with them: once no Thread it opened (`own`) is still open, or, when it opened none,
786
- * once every one it follows closed. It ends as the latest close among those Threads did; undefined while it is not done.
772
+ * How a ticket that follows Threads is done with them: once no Thread it opened (`own`) is still open, or, when it
773
+ * opened none, once every one it follows closed. It ends as the latest close among those Threads did; undefined while
774
+ * it is not done.
787
775
  */
788
776
  followsDone(ticket) {
789
777
  const follows = ticket.follows ?? [];
@@ -793,8 +781,8 @@ var Tickets = class extends EnginePart {
793
781
  return counted.map((item) => item.closed).sort((a, b) => a.at.localeCompare(b.at)).at(-1);
794
782
  }
795
783
  /**
796
- * The Trigger to attach to a ticket, once the ticket may take it.
797
- * @throws UserError when the trigger or ticket is unknown, a ticket's worker still decides the trigger, a person stopped the ticket, or an ended ticket's Mandate no longer revalidates.
784
+ * @throws UserError when the trigger or ticket is unknown, a ticket's worker still decides the trigger, a person
785
+ * stopped the ticket, or an ended ticket's Mandate no longer revalidates.
798
786
  */
799
787
  attachable(ticketId, triggerId) {
800
788
  const trigger = this.repo.triggers.get(triggerId);
@@ -806,9 +794,9 @@ var Tickets = class extends EnginePart {
806
794
  return trigger;
807
795
  }
808
796
  /**
809
- * Subscribe a ticket to an attached Trigger's Threads and add the Trigger as an Input, with its note. The ticket's worker
810
- * then decides it, as it decides a message that reached the ticket on its own Thread; a worker that does not hold the
811
- * message as its ticket's takes it as a new Input, which wakes an ended ticket as any attached message does.
797
+ * Subscribe a ticket to an attached Trigger's Threads and add the Trigger as an Input, with its note. The ticket's
798
+ * worker then decides it, as it decides a message that reached the ticket on its own Thread; a worker that does not
799
+ * hold the message as its ticket's takes it as a new Input, which wakes an ended ticket as any attached message does.
812
800
  */
813
801
  attach(ticketId, trigger, note) {
814
802
  for (const ref of trigger.threads) this.subscribe(ticketId, ref);
@@ -859,16 +847,14 @@ var Tickets = class extends EnginePart {
859
847
  });
860
848
  return true;
861
849
  }
862
- /**
863
- * Stops a ticket on behalf of a human, ending any active assignments.
864
- * @throws UserError when the ticket is unknown.
865
- */
850
+ /** @throws UserError when the ticket is unknown. */
866
851
  stop(ticketId, reason) {
867
852
  this.end(ticketId, "human", "stopped", this.now(), reason);
868
853
  return this.require(ticketId);
869
854
  }
870
855
  /**
871
- * Ends the stalled assignment of a retryable open ticket (`failed`, `blocked`, or failed input delivery) and starts a new one.
856
+ * Ends the stalled assignment of a retryable open ticket (`failed`, `blocked`, or failed input delivery) and starts a
857
+ * new one.
872
858
  * @throws UserError when the ticket is unknown or not retryable.
873
859
  */
874
860
  retry(ticketId) {
@@ -885,8 +871,9 @@ var Tickets = class extends EnginePart {
885
871
  return ticket;
886
872
  }
887
873
  /**
888
- * Drops a ticket's Subscription to a Thread its worker found unrelated, and hands back, with `reason`, the person's messages there that the ticket decides.
889
- * @throws UserError when the ticket is unknown, or `thread` is an origin thread, a bound `follows` thread, or not subscribed.
874
+ * Drops a ticket's Subscription to a Thread its worker found unrelated, and hands back, with `reason`, the person's
875
+ * messages there that the ticket decides. @throws UserError when the ticket is unknown, or `thread` is an origin
876
+ * thread, a bound `follows` thread, or not subscribed.
890
877
  */
891
878
  markUnrelated(ticketId, thread, reason) {
892
879
  const ticket = this.require(ticketId);
@@ -897,8 +884,8 @@ var Tickets = class extends EnginePart {
897
884
  for (const trigger of this.triggersOf(ticket)) if (trigger.threads.some((ref) => threadKey(ref) === thread) && this.owners(trigger).includes(ticketId)) this.handBack(ticketId, trigger, reason);
898
885
  }
899
886
  /**
900
- * Tell each open ticket a Policy change bears on how it does, oldest first, so their deliveries queue in the order they
901
- * were accepted; the others are left alone.
887
+ * Tell each open ticket a Policy change bears on how it does, oldest first, so their deliveries queue in the order
888
+ * they were accepted; the others are left alone.
902
889
  */
903
890
  policyChanged(from, to, effects) {
904
891
  const at = this.now();
@@ -915,9 +902,9 @@ var Tickets = class extends EnginePart {
915
902
  }
916
903
  }
917
904
  /**
918
- * Drop a Thread's Subscriptions, every ticket's or only `ticketId`'s, and say so; false when none dropped. A ticket that
919
- * followed the Thread follows it no more, so its close no longer ends the ticket. Each ticket keeps deciding the messages
920
- * it received from the Thread.
905
+ * Drop a Thread's Subscriptions, every ticket's or only `ticketId`'s, and say so; false when none dropped. A ticket
906
+ * that followed the Thread follows it no more, so its close no longer ends the ticket. Each ticket keeps deciding the
907
+ * messages it received from the Thread.
921
908
  */
922
909
  dropSubscriptions(thread, by, ticketId, reason) {
923
910
  const current = this.repo.threads.get(thread);
@@ -944,9 +931,9 @@ var Tickets = class extends EnginePart {
944
931
  return true;
945
932
  }
946
933
  /**
947
- * Add an Input once (idempotently) and ask the Executor to deliver it, or to launch again a pending Assignment whose launch
948
- * failed once no person is waited for: a Policy change alone does not launch it again. A message no person wrote waits on a
949
- * ticket that rests on a ready report and goes with the next person's message (see `restsReady`).
934
+ * Add an Input once (idempotently) and ask the Executor to deliver it, or to launch again a pending Assignment whose
935
+ * launch failed once no person is waited for: a Policy change alone does not launch it again. A message no person
936
+ * wrote waits on a ticket that rests on a ready report and goes with the next person's message (see `restsReady`).
950
937
  */
951
938
  addInput(ticket, input) {
952
939
  if (ticket.inputs.some((existing) => existing.id === input.id)) return;
@@ -971,10 +958,11 @@ var Tickets = class extends EnginePart {
971
958
  else if (latest?.state === "pending" && !waitsForPerson(ticket)) this.options.executor.start(ticket, latest);
972
959
  }
973
960
  /**
974
- * Resume an ended ticket under its unchanged Mandate when a person's message on one of its Threads waits for it: no Eval
975
- * and no Decision, since nothing new is authorized. One whose Mandate the current Policy no longer holds stays ended and
976
- * takes nothing (see `refuse`). While processing is paused the message waits, and the first pass after the pause resumes
977
- * the ticket under the Policy then. Reads the ticket afresh, so it is idempotent and leaves an already-resumed ticket alone.
961
+ * Resume an ended ticket under its unchanged Mandate when a person's message on one of its Threads waits for it: no
962
+ * Eval and no Decision, since nothing new is authorized. One whose Mandate the current Policy no longer holds stays
963
+ * ended and takes nothing (see `refuse`). While processing is paused the message waits, and the first pass after the
964
+ * pause resumes the ticket under the Policy then. Reads the ticket afresh, so it is idempotent and leaves an
965
+ * already-resumed ticket alone.
978
966
  */
979
967
  wake(ticketId) {
980
968
  const ticket = this.repo.tickets.get(ticketId);
@@ -1006,43 +994,45 @@ var Tickets = class extends EnginePart {
1006
994
  triggerId: first.triggerId
1007
995
  });
1008
996
  }
1009
- /** The Triggers of a ticket's trigger Inputs, or of those of them whose Input ids are `ids`. */
1010
997
  triggersOf(ticket, ids) {
1011
998
  return ticket.inputs.flatMap((input) => input.kind === "trigger" && (!ids || ids.includes(input.id)) ? this.repo.triggers.get(input.triggerId) ?? [] : []);
1012
999
  }
1013
1000
  /**
1014
- * Whether a ticket's worker took a person's message up, by acknowledging or answering it. A message acknowledged before
1015
- * acknowledgements were recorded on it has only the acknowledgement's message in the outbox.
1001
+ * Whether a ticket's worker took a person's message up, by acknowledging or answering it. A message acknowledged
1002
+ * before acknowledgements were recorded on it has only the acknowledgement's message in the outbox.
1016
1003
  */
1017
1004
  takenUp(ticket, trigger) {
1018
1005
  const acked = this.repo.triggers.disposition(trigger.id)?.acked;
1019
1006
  return acked ? acked.includes(ticket.id) : this.options.outbox.status(key("ack", trigger.id)) !== void 0;
1020
1007
  }
1021
- /** The person's messages among a ticket's Inputs `ids` that it decides and its worker did not take up. */
1022
1008
  untaken(ticket, ids) {
1023
1009
  return this.triggersOf(ticket, ids).filter((trigger) => this.owners(trigger).includes(ticket.id) && !this.takenUp(ticket, trigger));
1024
1010
  }
1025
- /** The person's messages on its own Threads that reached a ticket after its latest report, when that report is `ready`; undefined otherwise. */
1026
1011
  sinceReady(ticket) {
1027
1012
  const report = latestReport(ticket);
1028
1013
  if (report?.state !== "ready") return void 0;
1029
1014
  return this.triggersOf(ticket, ticket.inputs.filter((input) => input.kind === "trigger" && input.human && !input.context && input.at > report.at).map((input) => input.id));
1030
1015
  }
1031
- /** Whether a ticket keeps its `ready` report through a limit stop: its worker took up none of the person's messages since (ADR-24). */
1016
+ /**
1017
+ * Whether a ticket keeps its `ready` report through a limit stop: its worker took up none of the
1018
+ * person's messages since.
1019
+ */
1032
1020
  keepsReady(ticket) {
1033
1021
  const since = this.sinceReady(ticket);
1034
1022
  return !!since && !since.some((trigger) => this.takenUp(ticket, trigger));
1035
1023
  }
1036
- /** Whether a ticket rests on its `ready` report: every person's message since was handed back by it and not taken up (ADR-24). */
1024
+ /**
1025
+ * Whether a ticket rests on its `ready` report: every person's message since was handed back by it and not taken up.
1026
+ */
1037
1027
  restsReady(ticket) {
1038
1028
  const since = this.sinceReady(ticket);
1039
1029
  return !!since && since.every((trigger) => !this.takenUp(ticket, trigger) && !!this.repo.triggers.disposition(trigger.id)?.handbacks?.some((item) => item.ticketId === ticket.id));
1040
1030
  }
1041
1031
  /**
1042
- * A ticket that ended, or cannot resume, lets go of the messages its worker never received, `waiting`, and of the person's
1043
- * messages it decided but may not have taken up, `left`: it no longer counts as having reached them, and each of `left`
1044
- * that no other ticket decides goes to Eval, as a person's message that reached no ticket does, unless a ticket took it
1045
- * up, a Decision settled it, or it is the message a stop ended this ticket on.
1032
+ * A ticket that ended, or cannot resume, lets go of the messages its worker never received, `waiting`, and of the
1033
+ * person's messages it decided but may not have taken up, `left`: it no longer counts as having reached them, and
1034
+ * each of `left` that no other ticket decides goes to Eval, as a person's message that reached no ticket does, unless
1035
+ * a ticket took it up, a Decision settled it, or it is the message a stop ended this ticket on.
1046
1036
  */
1047
1037
  letGo(ticketId, waiting, left) {
1048
1038
  for (const trigger of [...waiting, ...left]) {
@@ -1060,14 +1050,12 @@ var Tickets = class extends EnginePart {
1060
1050
  if (!this.owners(trigger).length && !disposition?.acked?.length && !decided) this.queueForEval(trigger);
1061
1051
  }
1062
1052
  }
1063
- /** An ended ticket that cannot resume takes none of the messages that waited for it (see `letGo`). */
1064
1053
  refuse(ticket) {
1065
1054
  const waiting = this.triggersOf(ticket, ticket.undelivered), left = this.untaken(ticket, ticket.undelivered);
1066
1055
  ticket.undelivered = [];
1067
1056
  this.repo.tickets.save(ticket);
1068
1057
  this.letGo(ticket.id, waiting, left);
1069
1058
  }
1070
- /** Replace a ticket's running Assignment with a new one that receives `note` first, and queue it for launch. */
1071
1059
  restart(ticket, running, reason, note) {
1072
1060
  this.endAssignment(ticket, running, reason);
1073
1061
  this.requeueUntaken(ticket, running);
@@ -1086,10 +1074,10 @@ var Tickets = class extends EnginePart {
1086
1074
  this.options.executor.start(ticket, assignment);
1087
1075
  }
1088
1076
  /**
1089
- * Put back, ahead of what waits, the Trigger Inputs delivered to `replaced` that no step of its worker took: a cancel may
1090
- * have dropped them, or a step that claimed them was cut off, and the new Assignment's launch takes a copy still waiting
1091
- * in the Session out, so its delivery is the only one. An earlier end let go of what earlier Assignments left.
1092
- * A message the ticket handed back is not its work and is not put back.
1077
+ * Put back, ahead of what waits, the Trigger Inputs delivered to `replaced` that no step of its worker took: a cancel
1078
+ * may have dropped them, or a step that claimed them was cut off, and the new Assignment's launch takes a copy still
1079
+ * waiting in the Session out, so its delivery is the only one. An earlier end let go of what earlier Assignments
1080
+ * left. A message the ticket handed back is not its work and is not put back.
1093
1081
  */
1094
1082
  requeueUntaken(ticket, replaced) {
1095
1083
  const untaken = ticket.inputs.filter((input) => input.kind === "trigger" && input.readAt === null && input.deliveredTo === replaced.id && !ticket.undelivered.includes(input.id) && !this.repo.triggers.disposition(input.triggerId)?.handbacks?.some((item) => item.ticketId === ticket.id));
@@ -1097,10 +1085,11 @@ var Tickets = class extends EnginePart {
1097
1085
  }
1098
1086
  /**
1099
1087
  * End a ticket with its outcome. `at` records when the ending fact happened, such as a PR merge, and `reason` says
1100
- * why in words, by default the outcome. The ticket lets go (see `letGo`) of the messages its worker never received, and of
1101
- * the person's messages it decides that its worker did not read before its latest report or turn end, the report that
1102
- * ends the ticket included (see `readBefore`), and neither acknowledged, answered nor handed back: one may have come while
1103
- * the worker wrote the report that ends the ticket, or after its turn ended, and either way it can no longer hand it back.
1088
+ * why in words, by default the outcome. The ticket lets go (see `letGo`) of the messages its worker never received,
1089
+ * and of the person's messages it decides that its worker did not read before its latest report or turn end, the
1090
+ * report that ends the ticket included (see `readBefore`), and neither acknowledged, answered nor handed back: one
1091
+ * may have come while the worker wrote the report that ends the ticket, or after its turn ended, and either way it
1092
+ * can no longer hand it back.
1104
1093
  */
1105
1094
  end(ticketId, by, outcome, at = this.now(), reason) {
1106
1095
  const ticket = this.require(ticketId);
@@ -1135,10 +1124,10 @@ var Tickets = class extends EnginePart {
1135
1124
  for (const assignment of stopping) this.options.executor.stop(ticket, assignment, reason ?? outcome);
1136
1125
  }
1137
1126
  /**
1138
- * Whether a ticket's work has not finished at a close at `at`: its worker took up a person's message the ticket decides and
1139
- * has not reported other than progress, or ended a turn, since its Session took that message into a step, or work resumed
1140
- * after the close, whose Assignment started after it, has no ready report or Input arrived after that report; a Policy
1141
- * change asks for no work.
1127
+ * Whether a ticket's work has not finished at a close at `at`: its worker took up a person's message the ticket
1128
+ * decides and has not reported other than progress, or ended a turn, since its Session took that message into a step,
1129
+ * or work resumed after the close, whose Assignment started after it, has no ready report or Input arrived after that
1130
+ * report; a Policy change asks for no work.
1142
1131
  */
1143
1132
  unfinished(ticket, at) {
1144
1133
  const until = readUntil(ticket, false);
@@ -1171,7 +1160,8 @@ var Tickets = class extends EnginePart {
1171
1160
  });
1172
1161
  }
1173
1162
  /**
1174
- * Give an open ticket's Executor a fact as a note Input, such as what another ticket found; it grants nothing (see `TicketInput`).
1163
+ * Give an open ticket's Executor a fact as a note Input, such as what another ticket found; it grants nothing (see
1164
+ * `TicketInput`).
1175
1165
  * @throws UserError when the ticket is unknown or has ended.
1176
1166
  */
1177
1167
  note(ticketId, text) {
@@ -1187,7 +1177,6 @@ var Tickets = class extends EnginePart {
1187
1177
  return this.require(ticketId);
1188
1178
  }
1189
1179
  /**
1190
- * Subscribe an open ticket to a Thread its Executor found related, for context: see `Thread.context`.
1191
1180
  * @returns false when the ticket follows the Thread already.
1192
1181
  * @throws UserError when the ticket is unknown or has ended.
1193
1182
  */
@@ -1200,7 +1189,6 @@ var Tickets = class extends EnginePart {
1200
1189
  this.repo.threads.save(thread);
1201
1190
  return true;
1202
1191
  }
1203
- /** Subscribe a ticket to a Thread; later Triggers on it reach the ticket as Inputs, as the ticket's own once it was for context. */
1204
1192
  subscribe(ticketId, ref) {
1205
1193
  const thread = this.thread(ref);
1206
1194
  if (!thread.subscriptions.includes(ticketId)) {
@@ -1215,15 +1203,8 @@ var Tickets = class extends EnginePart {
1215
1203
  }
1216
1204
  };
1217
1205
  // src/core/assignments.ts
1218
- /** What the Executor and its workers report about the Assignments that run tickets. */
1219
1206
  const RECOVERY_NOTE = "The host restarted during this ticket. Inspect the current worktree, git history and remote state before continuing. Do not repeat an already completed external operation. Finish the original assignment and report its status.";
1220
- /**
1221
- * Assignments records what the Executor and its workers report about the Assignments that run tickets: one started, Inputs
1222
- * reached it or failed to, its worker's Session took Inputs into a step, a turn of its worker ended, a host restart
1223
- * interrupted it, and a worker's report with its question for a person. It depends on
1224
- * Tickets to restart or end a ticket and on Replies to withdraw a question a report replaced; the Engine routes the
1225
- * Executor's lifecycle callbacks and its workers' reports here.
1226
- */
1207
+ /** Depends on Tickets to restart or end a ticket and on Replies to withdraw a question a report replaced. */
1227
1208
  var Assignments = class extends EnginePart {
1228
1209
  tickets;
1229
1210
  replies;
@@ -1232,7 +1213,6 @@ var Assignments = class extends EnginePart {
1232
1213
  this.tickets = tickets;
1233
1214
  this.replies = replies;
1234
1215
  }
1235
- /** Marks a non-ended assignment running, queues a held accept acknowledgement (ADR-32), queues delivery of waiting undelivered inputs on an open ticket, and emits `assignment.started`. */
1236
1216
  started(ticketId, assignmentId) {
1237
1217
  const ticket = this.require(ticketId);
1238
1218
  const assignment = ticket.assignments.find((item) => item.id === assignmentId);
@@ -1253,10 +1233,7 @@ var Assignments = class extends EnginePart {
1253
1233
  assignmentId
1254
1234
  });
1255
1235
  }
1256
- /**
1257
- * Marks `inputIds` delivered on the ticket's running assignment and clears any prior `delivery` failure; ignored if no
1258
- * longer running. Its worker reads each once its Session takes it into a step (see `read`).
1259
- */
1236
+ /** Reads are recorded later by `read`, once the worker's Session takes the input into a step. */
1260
1237
  delivered(ticketId, assignmentId, inputIds) {
1261
1238
  const ticket = this.running(ticketId, assignmentId);
1262
1239
  if (!ticket) return;
@@ -1270,7 +1247,6 @@ var Assignments = class extends EnginePart {
1270
1247
  delete ticket.delivery;
1271
1248
  this.repo.tickets.save(ticket);
1272
1249
  }
1273
- /** Records when the worker's Session of an open ticket took `inputIds` into a step, from which its model reads them. */
1274
1250
  read(ticketId, inputIds) {
1275
1251
  const ticket = this.require(ticketId);
1276
1252
  if (!liveTicket(ticket)) return;
@@ -1284,9 +1260,9 @@ var Assignments = class extends EnginePart {
1284
1260
  this.repo.tickets.save(ticket);
1285
1261
  }
1286
1262
  /**
1287
- * A worker's Session stopped at its turn or budget limit (`limit`). A ticket whose latest report is `ready` and whose worker
1288
- * took up no person's message since keeps that report: the running Assignment carries it, so the ticket shows as ready for
1289
- * review, and the caller reports nothing (ADR-24). Emits `worker.limit` either way.
1263
+ * A worker's Session stopped at its turn or budget limit (`limit`). A ticket whose latest report is `ready` and whose
1264
+ * worker took up no person's message since keeps that report: the running Assignment carries it, so the ticket shows
1265
+ * as ready for review, and the caller reports nothing. Emits `worker.limit` either way.
1290
1266
  * @returns whether the ticket kept its ready report; false when `assignmentId` no longer runs the open ticket.
1291
1267
  */
1292
1268
  limited(ticketId, assignmentId, limit) {
@@ -1317,9 +1293,9 @@ var Assignments = class extends EnginePart {
1317
1293
  this.repo.tickets.save(ticket);
1318
1294
  }
1319
1295
  /**
1320
- * Handles a running assignment interrupted by a host restart: restarts it with a recovery note unless its latest report is
1321
- * `waiting`, `ready`, `blocked`, or `failed` and no input reached its worker after that report that a later finished turn
1322
- * did not take in (ADR-32), in which case it keeps its session and flushes any waiting undelivered inputs.
1296
+ * Handles a running assignment interrupted by a host restart: restarts it with a recovery note unless its latest
1297
+ * report is `waiting`, `ready`, `blocked`, or `failed` and no input reached its worker after that report that a later
1298
+ * finished turn did not take in, in which case it keeps its session and flushes any waiting undelivered inputs.
1323
1299
  */
1324
1300
  interrupted(ticketId, assignmentId) {
1325
1301
  const ticket = this.running(ticketId, assignmentId);
@@ -1336,10 +1312,8 @@ var Assignments = class extends EnginePart {
1336
1312
  else if (ticket.undelivered.length) this.options.executor.deliver(ticket, running);
1337
1313
  }
1338
1314
  /**
1339
- * Records a worker status report on an open ticket's current assignment, updating `attention`, `answers`, held automated
1340
- * inputs, and ending the ticket when `dropped` or when `ready` and not waiting on followed threads (`followsDone`).
1341
- * @throws UserError for an unknown or ended ticket, an ended assignment, a question combined with a ready result or answer,
1342
- * or a question/answer referencing a non-human or unreached trigger.
1315
+ * @throws UserError for an unknown or ended ticket, an ended assignment, a question combined with a ready result or
1316
+ * answer, or a question/answer referencing a non-human or unreached trigger.
1343
1317
  */
1344
1318
  report(ticketId, report) {
1345
1319
  const ticket = this.require(ticketId);
@@ -1387,7 +1361,10 @@ var Assignments = class extends EnginePart {
1387
1361
  if (outcome) this.tickets.end(ticketId, "executor", outcome, at);
1388
1362
  return this.require(ticketId);
1389
1363
  }
1390
- /** The web address of the ticket's own thread, from its last own follow: recorded at bind time, else the thread's own address. */
1364
+ /**
1365
+ * The web address of the ticket's own thread, from its last own follow: recorded at bind time, else the
1366
+ * thread's own address.
1367
+ */
1391
1368
  ownPullUrl(ticket) {
1392
1369
  const follow = ticket.follows?.findLast((item) => item.own);
1393
1370
  if (!follow) return void 0;
@@ -1395,17 +1372,13 @@ var Assignments = class extends EnginePart {
1395
1372
  const url = this.repo.threads.get(follow.thread)?.meta.url;
1396
1373
  return typeof url === "string" ? url : void 0;
1397
1374
  }
1398
- /** The ticket, while it is open and `assignmentId` runs it. */
1399
1375
  running(ticketId, assignmentId) {
1400
1376
  const ticket = this.require(ticketId);
1401
1377
  return liveTicket(ticket) && runningAssignment(ticket)?.id === assignmentId ? ticket : void 0;
1402
1378
  }
1403
1379
  };
1404
1380
  // src/core/reasons.ts
1405
- /**
1406
- * Structured reason codes and templates for decisions, filtered triggers, and ended assignments.
1407
- * Policy gates and source scopes may also emit their own literal reason codes.
1408
- */
1381
+ /** Policy gates and source scopes may also emit their own literal reason codes. */
1409
1382
  const REASONS = {
1410
1383
  said: "{text}",
1411
1384
  timeout: "Assessment timed out",
@@ -1416,7 +1389,6 @@ const REASONS = {
1416
1389
  routed: "Routed to ticket {ticket}",
1417
1390
  explicitRouted: "Explicit request from an allowed sender routed by the orchestrator",
1418
1391
  humanStop: "Human asked to stop",
1419
- legacyImport: "Imported from the pre-domain-model task record",
1420
1392
  forwarded: "Forwarded to the worker of the Ticket already on this Thread",
1421
1393
  senderNotMember: "The sender is not on the Source's list",
1422
1394
  retried: "Retried by a person",
@@ -1440,18 +1412,11 @@ function reasonText(reasons) {
1440
1412
  }).join("; ");
1441
1413
  }
1442
1414
  // src/core/evals.ts
1443
- /** Evals: the Thread-level assessments of queued Triggers, and the Decisions they end with. */
1444
- /** An outcome's reason: a code it carries, else the words its assessor or person wrote. */
1445
1415
  const reasonOf = (reason) => [typeof reason === "string" ? said(reason) : reason];
1446
1416
  const stronger = (a, b) => a === "explicit" || b === "explicit" ? "explicit" : "inferred";
1447
1417
  /** How many times in a row an expired Eval queues its Triggers again before it is recorded unavailable. */
1448
1418
  const REQUEUES = 2;
1449
- /**
1450
- * Evals runs the Evals of Threads: it batches a Thread's queued Triggers into one Eval, takes its strength, actor, and basis
1451
- * from the gate, asks the assessor, keeps its deadline through a pause, and records the Decision it ends with: accept, stop,
1452
- * ignore, or unavailable. It depends on Tickets to create the ticket an accept decides and to end the one a stop names; the
1453
- * Engine routes the main Session's decisions, people's replays, worker handbacks, and the pass's Eval work here.
1454
- */
1419
+ /** Depends on Tickets to create the ticket an accept decides and to end the one a stop names. */
1455
1420
  var Evals = class extends EnginePart {
1456
1421
  tickets;
1457
1422
  replies;
@@ -1508,7 +1473,6 @@ var Evals = class extends EnginePart {
1508
1473
  this.repo.threads.save(thread);
1509
1474
  this.options.assessor.release?.(id);
1510
1475
  }
1511
- /** Start one Eval of the Triggers queued on a Thread, judged by the gate, and ask the assessor for it. */
1512
1476
  start(threadKeyValue, policy) {
1513
1477
  const thread = this.repo.threads.get(threadKeyValue);
1514
1478
  const at = this.now();
@@ -1591,16 +1555,14 @@ var Evals = class extends EnginePart {
1591
1555
  });
1592
1556
  this.options.assessor.request(this.candidateOf(thread));
1593
1557
  }
1594
- /** The candidate of the Eval that holds `triggerId`, or undefined while no Eval holds it. */
1595
1558
  candidate(triggerId) {
1596
1559
  const thread = this.holding(triggerId);
1597
1560
  return thread && this.candidateOf(thread);
1598
1561
  }
1599
1562
  /**
1600
- * Decide a Trigger: the Eval that holds it, or a direct Eval of it alone when none does.
1601
- * @throws UserError when the Trigger is unknown or not admissible, `outcome.evalId` names an Eval that has finished, a
1602
- * ticket's worker still decides the Trigger and the outcome is not a stop, or an inferred candidate is accepted outside `auto`
1603
- * mode or without enabled matching rules.
1563
+ * @throws UserError when the Trigger is unknown or not admissible, `outcome.evalId` names an Eval that has finished,
1564
+ * a ticket's worker still decides the Trigger and the outcome is not a stop, or an inferred candidate is accepted
1565
+ * outside `auto` mode or without enabled matching rules.
1604
1566
  */
1605
1567
  decide(triggerId, outcome) {
1606
1568
  const trigger = this.repo.triggers.get(triggerId);
@@ -1631,7 +1593,6 @@ var Evals = class extends EnginePart {
1631
1593
  return this.decideEval(thread, evaluation, outcome);
1632
1594
  }
1633
1595
  /**
1634
- * Queue a stored Trigger for a fresh Eval on its primary Thread and emit `trigger.requeued`; returns false while an Eval holds it.
1635
1596
  * @throws UserError when the Trigger is unknown.
1636
1597
  */
1637
1598
  reevaluate(triggerId) {
@@ -1639,7 +1600,6 @@ var Evals = class extends EnginePart {
1639
1600
  this.emit("trigger.requeued", { triggerId });
1640
1601
  return true;
1641
1602
  }
1642
- /** Queue a Trigger for a fresh Eval on its primary Thread; false while an Eval holds it. */
1643
1603
  requeue(triggerId) {
1644
1604
  const trigger = this.repo.triggers.get(triggerId);
1645
1605
  if (!trigger) throw new UserError("unknownTrigger");
@@ -1656,10 +1616,10 @@ var Evals = class extends EnginePart {
1656
1616
  return true;
1657
1617
  }
1658
1618
  /**
1659
- * Record that a ticket's Executor hands back a Trigger that reached it as beyond the ticket, and queue it for a fresh Eval
1660
- * when the ticket decided it and no other ticket still does (see `handBack`): the gate judges it as any message on its
1661
- * Thread, and its candidate carries the reasons, so no requeue notice comes before it. The ticket keeps its Subscription,
1662
- * and hands a Trigger back once.
1619
+ * Record that a ticket's Executor hands back a Trigger that reached it as beyond the ticket, and queue it for a fresh
1620
+ * Eval when the ticket decided it and no other ticket still does (see `handBack`): the gate judges it as any message
1621
+ * on its Thread, and its candidate carries the reasons, so no requeue notice comes before it. The ticket keeps its
1622
+ * Subscription, and hands a Trigger back once.
1663
1623
  * @returns whether it was queued: false while another ticket decides it, and for one the ticket did not decide.
1664
1624
  */
1665
1625
  handback(ticketId, triggerId, reason) {
@@ -1667,10 +1627,7 @@ var Evals = class extends EnginePart {
1667
1627
  if (this.repo.triggers.disposition(triggerId)?.handbacks?.some((item) => item.ticketId === ticketId)) throw new UserError("handedBack");
1668
1628
  return this.handBack(ticketId, this.repo.triggers.get(triggerId), reason);
1669
1629
  }
1670
- /**
1671
- * Marks an `unavailable` Decision dismissed so the overview lists it no more.
1672
- * @throws UserError when the Decision is unknown or not `unavailable`.
1673
- */
1630
+ /** @throws UserError when the Decision is unknown or not `unavailable`. */
1674
1631
  dismiss(decisionId) {
1675
1632
  const decision = this.repo.decisions.get(decisionId);
1676
1633
  if (decision?.kind !== "unavailable") throw new UserError("unknownProblem");
@@ -1686,8 +1643,8 @@ var Evals = class extends EnginePart {
1686
1643
  this.repo.threads.save(thread);
1687
1644
  }
1688
1645
  /**
1689
- * End the Eval that holds a Trigger routed to a ticket, since the ticket has it now: one that holds other Triggers too
1690
- * queues them again for a fresh Eval without it.
1646
+ * End the Eval that holds a Trigger routed to a ticket, since the ticket has it now: one that holds other Triggers
1647
+ * too queues them again for a fresh Eval without it.
1691
1648
  */
1692
1649
  routed(triggerId, ticketId) {
1693
1650
  const held = this.holding(triggerId);
@@ -1704,7 +1661,6 @@ var Evals = class extends EnginePart {
1704
1661
  triggerId
1705
1662
  }, [triggerId]);
1706
1663
  }
1707
- /** Start the Evals in progress again under the new Policy version, and say that it changed. */
1708
1664
  policyChanged(from, to) {
1709
1665
  for (const thread of this.repo.threads.active()) if (thread.evaluating) this.reassess(thread, { code: "policyChanged" });
1710
1666
  this.emit("policy.changed", {
@@ -1712,7 +1668,6 @@ var Evals = class extends EnginePart {
1712
1668
  to
1713
1669
  });
1714
1670
  }
1715
- /** End a Thread's Eval as unavailable for `reason` and queue its Triggers, except `without`, for a new Eval ahead of the others. */
1716
1671
  reassess(thread, reason, without = []) {
1717
1672
  const ids = thread.evaluating.triggerIds.filter((id) => !without.includes(id));
1718
1673
  const decision = this.finish(thread, thread.evaluating, {
@@ -1747,8 +1702,8 @@ var Evals = class extends EnginePart {
1747
1702
  };
1748
1703
  }
1749
1704
  /**
1750
- * Record that the queued Triggers `ids` of a Thread start no Eval, for `reason`, and take them out of its queue. Its id
1751
- * differs from an Eval's, so it never replaces the Decision of an earlier Eval of the same Triggers.
1705
+ * Record that the queued Triggers `ids` of a Thread start no Eval, for `reason`, and take them out of its queue. Its
1706
+ * id differs from an Eval's, so it never replaces the Decision of an earlier Eval of the same Triggers.
1752
1707
  */
1753
1708
  ignore(thread, ids, reason, policy, at) {
1754
1709
  const evaluation = {
@@ -1773,8 +1728,9 @@ var Evals = class extends EnginePart {
1773
1728
  delete thread.expired;
1774
1729
  }
1775
1730
  /**
1776
- * The accept Decisions of the Triggers whose primary Thread is `threadKey`, by Trigger: what each one started. An Eval and
1777
- * its Decision run on its Triggers' primary Thread, so the Thread's Decisions, read by their index, hold them all.
1731
+ * The accept Decisions of the Triggers whose primary Thread is `threadKey`, by Trigger: what each one
1732
+ * started. An Eval and its Decision run on its Triggers' primary Thread, so the Thread's Decisions, read
1733
+ * by their index, hold them all.
1778
1734
  */
1779
1735
  accepted(threadKey) {
1780
1736
  const accepted = /* @__PURE__ */ new Map();
@@ -1785,9 +1741,10 @@ var Evals = class extends EnginePart {
1785
1741
  return accepted;
1786
1742
  }
1787
1743
  /**
1788
- * A gate-only Eval of one Trigger, for an outcome decided without a queued Eval, such as on a follow-up that reached a
1789
- * ticket. It is decided at once, beside an Eval its Thread may have in progress, and takes the Trigger out of the queue.
1790
- * Its id names the outcome's kind, so a stop and an accept of one message at the same moment are two Decisions.
1744
+ * A gate-only Eval of one Trigger, for an outcome decided without a queued Eval, such as on a follow-up
1745
+ * that reached a ticket. It is decided at once, beside an Eval its Thread may have in progress, and takes
1746
+ * the Trigger out of the queue. Its id names the outcome's kind, so a stop and an accept of one message
1747
+ * at the same moment are two Decisions.
1791
1748
  */
1792
1749
  directEval(thread, trigger, kind, ticketId) {
1793
1750
  const policy = this.policy();
@@ -1811,7 +1768,6 @@ var Evals = class extends EnginePart {
1811
1768
  deadline: at
1812
1769
  };
1813
1770
  }
1814
- /** Apply an outcome to an Eval of a Thread: the one in progress, or a gate-only one. */
1815
1771
  decideEval(thread, evaluation, outcome) {
1816
1772
  const audit = outcome.audit ? { audit: outcome.audit } : {};
1817
1773
  if (this.policy().version !== evaluation.policyVersion) {
@@ -1855,14 +1811,16 @@ var Evals = class extends EnginePart {
1855
1811
  }
1856
1812
  return this.accept(thread, evaluation, outcome);
1857
1813
  }
1858
- /** An explicit request a person on the list addressed to the agent, which the assessor ignored in its own words, gets one refusal where it was written, while the Policy allows refusals (ADR-24). */
1814
+ /**
1815
+ * An explicit request a person on the list addressed to the agent, which the assessor ignored in its own words, gets
1816
+ * one refusal where it was written, while the Policy allows refusals.
1817
+ */
1859
1818
  refuse(evaluation, reason) {
1860
1819
  if (evaluation.strength !== "explicit" || evaluation.basis.via !== "addressed" || !this.policy().refuses) return;
1861
1820
  const trigger = evaluation.primary ? this.repo.triggers.get(evaluation.primary) : void 0;
1862
1821
  if (!trigger?.human || !trigger.addressed || !trigger.channel) return;
1863
1822
  this.replies.refuse(trigger, reason);
1864
1823
  }
1865
- /** Record an accept with the Mandate the Eval gives, and have Tickets create its ticket. */
1866
1824
  accept(thread, evaluation, outcome) {
1867
1825
  const at = this.now();
1868
1826
  const basis = {
@@ -1889,7 +1847,6 @@ var Evals = class extends EnginePart {
1889
1847
  this.tickets.create(ticketId, decision.id, mandate, evaluation, outcome, at);
1890
1848
  return decision;
1891
1849
  }
1892
- /** Record an Eval's Decision; the Thread's own Eval ends with it, while a gate-only one leaves the Thread's Eval going. */
1893
1850
  finish(thread, evaluation, outcome) {
1894
1851
  const decision = this.record(thread, evaluation, outcome);
1895
1852
  if (thread.evaluating?.id !== evaluation.id) return decision;
@@ -1936,12 +1893,8 @@ var Evals = class extends EnginePart {
1936
1893
  }
1937
1894
  };
1938
1895
  // src/core/intake.ts
1939
- /** Trigger ingestion, thread correlation, subscription fan-out, and evaluation queuing. */
1940
1896
  /**
1941
- * Ingests incoming triggers from Sources, deduplicating and filtering them against SourceScope before recording thread correlations.
1942
- * Fans listened triggers out to subscribed tickets via Tickets and queues for Eval, on its primary thread, every trigger no
1943
- * ticket's worker decides (see `followupOwners`).
1944
- * Engine delegates trigger reception to this part.
1897
+ * Queues on its primary thread every trigger that no ticket's worker decides (see `followupOwners`).
1945
1898
  */
1946
1899
  var Intake = class extends EnginePart {
1947
1900
  tickets;
@@ -2011,7 +1964,6 @@ var Intake = class extends EnginePart {
2011
1964
  }
2012
1965
  };
2013
1966
  // src/core/replies.ts
2014
- /** The Engine's messages to Channels, each by the rule that allows it. */
2015
1967
  const REFUSAL_PREFIX = "I can't take this on here: ";
2016
1968
  const REFUSAL_FALLBACK = "it is outside what I can do.";
2017
1969
  const INTERNAL = /\b(?:tickets?|workers?|hand(?:ed|s|ing)?[\s-]?backs?|evals?|triggers?|orchestrator|assignments?)\b|\b[0-9a-f]{16,}\b/i;
@@ -2026,7 +1978,11 @@ function readable$1(sentence) {
2026
1978
  const joined = kept.join(", ").replace(/[\s.!?。!?]+$/u, "");
2027
1979
  return joined.charAt(0).toUpperCase() + joined.slice(1) + ".";
2028
1980
  }
2029
- /** The refusal a person gets for an explicit request the assessor ignored: the first sentence of its reason that a person can read: internal terms rewritten (`the trigger` → `the message`) or their clauses dropped; a fixed phrase when nothing is left; at most 200 characters (ADR-24, ADR-25). */
1981
+ /**
1982
+ * The refusal a person gets for an explicit request the assessor ignored: the first sentence of its reason that a
1983
+ * person can read: internal terms rewritten (`the trigger` → `the message`) or their clauses dropped; a fixed phrase
1984
+ * when nothing is left; at most 200 characters.
1985
+ */
2030
1986
  function refusalText(reason) {
2031
1987
  let body = REFUSAL_FALLBACK;
2032
1988
  for (const sentence of reason.trim().split(SENTENCE)) {
@@ -2039,33 +1995,24 @@ function refusalText(reason) {
2039
1995
  const text = REFUSAL_PREFIX + body;
2040
1996
  return text.length <= 200 ? text : text.slice(0, 199).trimEnd() + "…";
2041
1997
  }
2042
- /** The note a person gets once a turn or budget limit stopped the work: which limit, the work so far when there is a pull request, and an invitation to reply (ADR-32). */
2043
1998
  function limitText(limit, pull) {
2044
1999
  return `I stopped before finishing: the ${limit} limit for this task was reached.` + (pull ? ` Work so far: ${pull}.` : "") + " Reply here to continue.";
2045
2000
  }
2046
2001
  /**
2047
- * Replies writes the Engine's messages to Channels through the Outbox port: an acknowledgement, the answer to a person's
2048
- * question, a ticket's result, and its question for a person, each only when its rule allows it, under a deterministic id
2049
- * that keeps it from going out twice (idempotent). The Engine routes `reply` here; Tickets asks it for the acknowledgement
2050
- * an accept carries, and Assignments asks it to withdraw a question a report replaced.
2002
+ * Each message is queued under a deterministic id, so it is never sent twice. Replies to a person go where the person's
2003
+ * message was written, and a result where the latest person's message to the ticket was written.
2051
2004
  */
2052
2005
  var Replies = class extends EnginePart {
2053
2006
  /**
2054
- * Queue a message for the Channel its purpose names: an acknowledgement or an answer goes where that person's message was
2055
- * written, attention where the message that raised the question was (or to the ticket's starting conversation), and a
2056
- * result where the latest person's message to the ticket was written, else to the conversation where the ticket started (`result` is allowed only for a ticket in `ready`, `completed`,
2057
- * `merged`, or `dropped`). Each message is queued at most once (idempotent). An acknowledgement, even one already queued,
2058
- * records that the ticket's worker took the message up.
2059
- * @throws UserError when the ticket or trigger is unknown or the purpose's rule refuses it, such as a result of a ticket
2060
- * that is not ready, completed, merged, or dropped.
2007
+ * An acknowledgement or an answer goes where that person's message was written; attention where the message that
2008
+ * raised the question was, else to the ticket's starting conversation. Each message is queued at most once
2009
+ * (idempotent). An acknowledgement, even one already queued, records that the ticket's worker took the message up.
2010
+ * @throws UserError when the ticket or trigger is unknown or the purpose's rule refuses it, such as a result of a
2011
+ * ticket that is not ready, completed, merged, or dropped.
2061
2012
  */
2062
2013
  reply(request) {
2063
2014
  return request.purpose === "acknowledgement" ? this.acknowledge(request) : this.answer(request);
2064
2015
  }
2065
- /**
2066
- * Queue the acknowledgement an accept carries where the message that started the ticket was written, if it has a Channel
2067
- * and a person wrote it.
2068
- */
2069
2016
  acknowledgeAccepted(ticketId, trigger, text) {
2070
2017
  if (trigger.channel && trigger.human) this.options.outbox.send({
2071
2018
  id: key("ack", trigger.id),
@@ -2075,7 +2022,10 @@ var Replies = class extends EnginePart {
2075
2022
  ticketId
2076
2023
  });
2077
2024
  }
2078
- /** Queue, once per Assignment, the note that a turn or budget limit stopped the work, where the latest person's message to the ticket was written (ADR-23), else where it started; id `limit:<assignmentId>` (ADR-32). */
2025
+ /**
2026
+ * Queue, once per Assignment, the note that a turn or budget limit stopped the work, where the latest person's
2027
+ * message to the ticket was written, else where it started; id `limit:<assignmentId>`.
2028
+ */
2079
2029
  limitStopped(ticket, assignmentId, limit, pull) {
2080
2030
  const channel = this.replyChannel(ticket);
2081
2031
  if (channel) this.options.outbox.send({
@@ -2086,11 +2036,13 @@ var Replies = class extends EnginePart {
2086
2036
  ticketId: ticket.id
2087
2037
  });
2088
2038
  }
2089
- /** Withdraw a ticket's question for a person from the outbox, unless it went out already. */
2090
2039
  withdrawAttention(attentionId) {
2091
2040
  this.options.outbox.withdraw(key("attention-reply", attentionId));
2092
2041
  }
2093
- /** Queue the refusal of a person's explicit request once, where it was written: its id is `refusal:<triggerId>`, so a replay or restart does not send it again. */
2042
+ /**
2043
+ * Queue the refusal of a person's explicit request once, where it was written: its id is `refusal:<triggerId>`, so a
2044
+ * replay or restart does not send it again.
2045
+ */
2094
2046
  refuse(trigger, reason) {
2095
2047
  if (!trigger.channel) return;
2096
2048
  this.options.outbox.send({
@@ -2164,7 +2116,10 @@ var Replies = class extends EnginePart {
2164
2116
  sourceChannel(ticket) {
2165
2117
  return this.source(ticket)?.channel ?? ticket.channels[0];
2166
2118
  }
2167
- /** Where a ticket's result goes: the Channel of the latest person's message that reached it on a Thread it works on (not one it follows for context), else the conversation it started in. */
2119
+ /**
2120
+ * Where a ticket's result goes: the Channel of the latest person's message that reached it on a Thread it works on
2121
+ * (not one it follows for context), else the conversation it started in.
2122
+ */
2168
2123
  replyChannel(ticket) {
2169
2124
  const latest = ticket.inputs.findLast((input) => input.kind === "trigger" && input.human === true && !input.context && this.repo.triggers.get(input.triggerId)?.channel !== void 0);
2170
2125
  return (latest && this.repo.triggers.get(latest.triggerId)?.channel) ?? this.sourceChannel(ticket);
@@ -2173,9 +2128,8 @@ var Replies = class extends EnginePart {
2173
2128
  // src/core/engine.ts
2174
2129
  /** The domain Engine: the single entry point of the core, and the role interfaces its callers receive. */
2175
2130
  /**
2176
- * Single transactional entry point for mutating Triggers, Threads, Decisions, Tickets, and Assignments.
2177
- * Coordinates Intake, Evals, Tickets, Assignments, and Replies inside synchronous repository transactions while queuing side
2178
- * effects through ports. Callers receive role-scoped `Pick` interfaces rather than the full engine.
2131
+ * Mutations run in synchronous repository transactions and queue side effects through ports. Callers receive
2132
+ * role-scoped `Pick` interfaces rather than the full engine.
2179
2133
  */
2180
2134
  var Engine = class {
2181
2135
  repo;
@@ -2195,26 +2149,27 @@ var Engine = class {
2195
2149
  this.intake = new Intake(options, this.tickets);
2196
2150
  }
2197
2151
  /**
2198
- * A Source's request to take in a Trigger: store it once, fan it out to the tickets that subscribe to its Threads (to an
2199
- * ended one only a person's message, which resumes it at once unless processing is paused, and to none a person stopped),
2200
- * and queue it for Eval on its primary Thread unless a ticket's worker decides it (see `followupOwners`).
2152
+ * A Source's request to take in a Trigger: store it once, fan it out to the tickets that subscribe to its Threads (to
2153
+ * an ended one only a person's message, which resumes it at once unless processing is paused, and to none a person
2154
+ * stopped), and queue it for Eval on its primary Thread unless a ticket's worker decides it (see `followupOwners`).
2201
2155
  * Triggers that arrive while an Eval of that Thread runs wait together for the next one.
2202
2156
  */
2203
2157
  receive(trigger) {
2204
2158
  return this.repo.atomic(() => this.intake.receive(trigger));
2205
2159
  }
2206
2160
  /**
2207
- * A Source's request to make an open ticket follow one of its Threads, such as a pull request's, `own` when the ticket
2208
- * opened it: the ticket subscribes to it, and a ready report ends it only once `followsDone` holds (see `Tickets.followsDone`).
2161
+ * A Source's request to make an open ticket follow one of its Threads, such as a pull request's, `own` when the
2162
+ * ticket opened it: the ticket subscribes to it, and a ready report ends it only once `followsDone` holds (see
2163
+ * `Tickets.followsDone`).
2209
2164
  * @throws UserError when the ticket is unknown or has ended.
2210
2165
  */
2211
2166
  bindThread(ticketId, thread, own, url) {
2212
2167
  return this.repo.atomic(() => this.tickets.bind(ticketId, thread, own, url));
2213
2168
  }
2214
2169
  /**
2215
- * A Source's fact that a Thread closed, such as its pull request's merge, at the time it happened. An open ticket that
2216
- * follows it ends once it is done with the Threads it follows, unless work resumed since the close has not finished, or
2217
- * its worker took up a person's message the ticket decides after its latest report.
2170
+ * A Source's fact that a Thread closed, such as its pull request's merge, at the time it happened. An open ticket
2171
+ * that follows it ends once it is done with the Threads it follows, unless work resumed since the close has not
2172
+ * finished, or its worker took up a person's message the ticket decides after its latest report.
2218
2173
  */
2219
2174
  threadClosed(thread, outcome, at) {
2220
2175
  this.repo.atomic(() => {
@@ -2222,10 +2177,11 @@ var Engine = class {
2222
2177
  });
2223
2178
  }
2224
2179
  /**
2225
- * Requeues overdue Evals up to `REQUEUES` times before recording `unavailable` (undelivered Evals never expire), starts the
2226
- * Evals of Threads whose queued Triggers wait, and resumes ended tickets a person's message reached while processing was
2227
- * paused. While processing is paused, nothing expires: each Eval in progress notes when the pause began, and its deadline
2228
- * moves forward by the pause's length once processing resumes. Each Eval and each ticket update takes one transaction.
2180
+ * Requeues overdue Evals up to `REQUEUES` times before recording `unavailable` (undelivered Evals never expire),
2181
+ * starts the Evals of Threads whose queued Triggers wait, and resumes ended tickets a person's message reached while
2182
+ * processing was paused. While processing is paused, nothing expires: each Eval in progress notes when the pause
2183
+ * began, and its deadline moves forward by the pause's length once processing resumes. Each Eval and each ticket
2184
+ * update takes one transaction.
2229
2185
  */
2230
2186
  evaluateDue() {
2231
2187
  const policy = this.options.policy();
@@ -2246,23 +2202,23 @@ var Engine = class {
2246
2202
  }
2247
2203
  /**
2248
2204
  * Decide a Trigger: the Eval that holds it, or a direct Eval of it alone when none does, such as a person's explicit
2249
- * request the main Session routes. A proactive candidate is accepted only in `auto` mode with enabled rules its assessment
2250
- * matched, and a Trigger that already started a ticket returns that ticket's Decision. An invalid stop is recorded as
2251
- * `ignore`; one judged under an older Policy as `unavailable`.
2252
- * @throws UserError when the Trigger is unknown or not admissible, `outcome.evalId` names an Eval that has finished, a
2253
- * ticket's worker still decides the Trigger and the outcome is not a stop, or an inferred candidate is accepted outside `auto`
2254
- * mode or without enabled matching rules.
2205
+ * request the main Session routes. A proactive candidate is accepted only in `auto` mode with enabled rules its
2206
+ * assessment matched, and a Trigger that already started a ticket returns that ticket's Decision. An invalid stop is
2207
+ * recorded as `ignore`; one judged under an older Policy as `unavailable`.
2208
+ * @throws UserError when the Trigger is unknown or not admissible, `outcome.evalId` names an Eval that has finished,
2209
+ * a ticket's worker still decides the Trigger and the outcome is not a stop, or an inferred candidate is accepted
2210
+ * outside `auto` mode or without enabled matching rules.
2255
2211
  */
2256
2212
  decide(triggerId, outcome) {
2257
2213
  return this.repo.atomic(() => this.evals.decide(triggerId, outcome));
2258
2214
  }
2259
2215
  /**
2260
- * Attach a Trigger to a ticket that the message points at: the ticket follows the Trigger's Threads and receives it as an
2261
- * Input with the routing note, and the Eval that holds the Trigger ends, since the ticket has it now; the other Triggers of
2262
- * its batch wait for a fresh Eval. A note for an Input the worker already received goes out as a new Input, and so does a
2263
- * message the ticket let go, read for context only, or had before it ended. An ended ticket resumes under its Mandate, at
2264
- * once unless processing is paused.
2265
- * @throws UserError for a ticket a person stopped, which withdrew its Mandate, or one whose Mandate the Policy no longer holds.
2216
+ * Attach a Trigger to a ticket that the message points at: the ticket follows the Trigger's Threads and receives it
2217
+ * as an Input with the routing note, and the Eval that holds the Trigger ends, since the ticket has it now; the other
2218
+ * Triggers of its batch wait for a fresh Eval. A note for an Input the worker already received goes out as a new
2219
+ * Input, and so does a message the ticket let go, read for context only, or had before it ended. An ended ticket
2220
+ * resumes under its Mandate, at once unless processing is paused. @throws UserError for a ticket a person stopped,
2221
+ * which withdrew its Mandate, or one whose Mandate the Policy no longer holds.
2266
2222
  */
2267
2223
  attach(ticketId, triggerId, note) {
2268
2224
  return this.repo.atomic(() => {
@@ -2284,11 +2240,11 @@ var Engine = class {
2284
2240
  return this.repo.atomic(() => this.tickets.requestStatus(ticketId));
2285
2241
  }
2286
2242
  /**
2287
- * Queue a message for the Channel its purpose names: an acknowledgement or an answer goes where the person's message was
2288
- * written, attention where the message that raised the question was, else, like a result, to the conversation the ticket
2289
- * started in. A message queued before is not queued again (see `Replies.reply`).
2290
- * @throws UserError when the ticket or trigger is unknown or the purpose's rule refuses it, such as a result of a ticket that
2291
- * is not ready, completed, merged, or dropped.
2243
+ * Queue a message for the Channel its purpose names: an acknowledgement or an answer goes where the person's message
2244
+ * was written, attention where the message that raised the question was, else, like a result, to the conversation the
2245
+ * ticket started in. A message queued before is not queued again (see `Replies.reply`).
2246
+ * @throws UserError when the ticket or trigger is unknown or the purpose's rule refuses it, such as a result of a
2247
+ * ticket that is not ready, completed, merged, or dropped.
2292
2248
  */
2293
2249
  reply(request) {
2294
2250
  return this.repo.atomic(() => this.replies.reply(request));
@@ -2345,8 +2301,9 @@ var Engine = class {
2345
2301
  });
2346
2302
  }
2347
2303
  /**
2348
- * The Executor's fact that Inputs reached the Assignment that runs the ticket, which clears a failed delivery. A fact about
2349
- * an Assignment that no longer runs is dropped. Its worker reads each once its Session takes it into a step (`inputsRead`).
2304
+ * The Executor's fact that Inputs reached the Assignment that runs the ticket, which clears a failed
2305
+ * delivery. A fact about an Assignment that no longer runs is dropped. Its worker reads each once its
2306
+ * Session takes it into a step (`inputsRead`).
2350
2307
  */
2351
2308
  inputsDelivered(ticketId, assignmentId, inputIds) {
2352
2309
  this.repo.atomic(() => {
@@ -2354,8 +2311,8 @@ var Engine = class {
2354
2311
  });
2355
2312
  }
2356
2313
  /**
2357
- * The Executor's fact that the worker's Session of an open ticket took the messages that carried Inputs into a step, from
2358
- * which its model reads them: a report or the end of a turn after that counts them as read at the ticket's end.
2314
+ * The Executor's fact that the worker's Session of an open ticket took the messages that carried Inputs into a step,
2315
+ * from which its model reads them: a report or the end of a turn after that counts them as read at the ticket's end.
2359
2316
  * @throws UserError when the ticket is unknown.
2360
2317
  */
2361
2318
  inputsRead(ticketId, inputIds) {
@@ -2369,17 +2326,20 @@ var Engine = class {
2369
2326
  this.assignments.turnEnded(ticketId, assignmentId);
2370
2327
  });
2371
2328
  }
2372
- /** The Executor's fact that delivering Inputs to the Assignment that runs the ticket failed; the ticket shows it until a delivery succeeds. */
2329
+ /**
2330
+ * The Executor's fact that delivering Inputs to the Assignment that runs the ticket failed; the ticket shows it until
2331
+ * a delivery succeeds.
2332
+ */
2373
2333
  deliveryFailed(ticketId, assignmentId, error) {
2374
2334
  this.repo.atomic(() => {
2375
2335
  this.assignments.deliveryFailed(ticketId, assignmentId, error);
2376
2336
  });
2377
2337
  }
2378
2338
  /**
2379
- * A worker's request, through its Executor, to mark a Thread attached to its ticket unrelated, for `reason`: only that
2380
- * ticket's Subscription drops, until a new Decision subscribes it again.
2381
- * @throws UserError when the ticket is unknown, or for the Thread the ticket started on, one a Source bound it to (such as
2382
- * its pull request's), or one it does not follow.
2339
+ * A worker's request, through its Executor, to mark a Thread attached to its ticket unrelated, for `reason`: only
2340
+ * that ticket's Subscription drops, until a new Decision subscribes it again.
2341
+ * @throws UserError when the ticket is unknown, or for the Thread the ticket started on, one a Source bound it to
2342
+ * (such as its pull request's), or one it does not follow.
2383
2343
  */
2384
2344
  markUnrelated(ticketId, thread, reason) {
2385
2345
  this.repo.atomic(() => {
@@ -2387,7 +2347,8 @@ var Engine = class {
2387
2347
  });
2388
2348
  }
2389
2349
  /**
2390
- * An Executor's Subscription to a Thread it found related, for context: see `Tickets.followForContext` and `Thread.context`.
2350
+ * An Executor's Subscription to a Thread it found related, for context: see `Tickets.followForContext` and
2351
+ * `Thread.context`.
2391
2352
  * @returns false when the ticket already follows the Thread.
2392
2353
  * @throws UserError when the ticket is unknown or has ended.
2393
2354
  */
@@ -2403,32 +2364,37 @@ var Engine = class {
2403
2364
  }
2404
2365
  /**
2405
2366
  * An Executor's hand-back of a message that reached its ticket but asks for more than it: see `Evals.handback`.
2406
- * @returns whether it was queued for Eval: false while another ticket still decides it, and for one this ticket did not decide (see `Evals.handback`).
2407
- * @throws UserError when the ticket or trigger is unknown, the trigger did not reach the ticket, or the ticket already handed it back.
2367
+ * @returns whether it was queued for Eval: false while another ticket still decides it, and for one this ticket did
2368
+ * not decide (see `Evals.handback`).
2369
+ * @throws UserError when the ticket or trigger is unknown, the trigger did not reach the ticket, or the ticket
2370
+ * already handed it back.
2408
2371
  */
2409
2372
  handback(ticketId, triggerId, reason) {
2410
2373
  return this.repo.atomic(() => this.evals.handback(ticketId, triggerId, reason));
2411
2374
  }
2412
2375
  /**
2413
2376
  * Record a report on an open ticket's current Assignment. Its question for a person (`attention`) is asked, kept, or
2414
- * cleared (`null` or a `ready` report), and a replaced attention question whose reply has not been sent yet is withdrawn
2415
- * from the outbox. A `blocked` or `failed` report holds back automated inputs until a human message arrives. A `dropped`
2416
- * report ends the ticket, and a `ready` report ends it: `completed` without followed Threads, else with the `followsDone`
2417
- * outcome once `followsDone` holds.
2418
- * @throws UserError for an unknown or ended ticket, an ended assignment, a question that comes with a ready result or an
2419
- * answer, or a question or answer that names a message that is not a person's or did not reach the ticket.
2377
+ * cleared (`null` or a `ready` report), and a replaced attention question whose reply has not been sent yet is
2378
+ * withdrawn from the outbox. A `blocked` or `failed` report holds back automated inputs until a human message
2379
+ * arrives. A `dropped` report ends the ticket, and a `ready` report ends it: `completed` without followed Threads,
2380
+ * else with the `followsDone` outcome once `followsDone` holds.
2381
+ * @throws UserError for an unknown or ended ticket, an ended assignment, a question that comes with a ready result or
2382
+ * an answer, or a question or answer that names a message that is not a person's or did not reach the ticket.
2420
2383
  */
2421
2384
  report(ticketId, report) {
2422
2385
  return this.repo.atomic(() => this.assignments.report(ticketId, report));
2423
2386
  }
2424
- /** A worker's Session stopped at a turn or budget limit: see `Assignments.limited`. @returns whether the ticket kept its ready report, so no blocked report follows. */
2387
+ /**
2388
+ * A worker's Session stopped at a turn or budget limit: see `Assignments.limited`.
2389
+ * @returns whether the ticket kept its ready report, so no blocked report follows.
2390
+ */
2425
2391
  limitReached(ticketId, assignmentId, limit) {
2426
2392
  return this.repo.atomic(() => this.assignments.limited(ticketId, assignmentId, limit));
2427
2393
  }
2428
2394
  /**
2429
- * Handles an assignment that was running when the host stopped: restarts it with a recovery note unless its latest report is
2430
- * `waiting`, `ready`, `blocked`, or `failed` and no Input reached its worker after that report that a later finished turn did
2431
- * not take in (ADR-32), in which case it keeps its Session and state and gets the Inputs that wait for it.
2395
+ * Handles an assignment that was running when the host stopped: restarts it with a recovery note unless its latest
2396
+ * report is `waiting`, `ready`, `blocked`, or `failed` and no Input reached its worker after that report that a later
2397
+ * finished turn did not take in, in which case it keeps its Session and state and gets the Inputs that wait for it.
2432
2398
  */
2433
2399
  assignmentInterrupted(ticketId, assignmentId) {
2434
2400
  this.repo.atomic(() => {
@@ -2436,8 +2402,8 @@ var Engine = class {
2436
2402
  });
2437
2403
  }
2438
2404
  /**
2439
- * The Policy changed: each open ticket in `effects` is told how the change bears on it, and Evals in progress start again
2440
- * under the new version.
2405
+ * The Policy changed: each open ticket in `effects` is told how the change bears on it, and Evals in progress start
2406
+ * again under the new version.
2441
2407
  */
2442
2408
  policyChanged(from, to, effects = /* @__PURE__ */ new Map()) {
2443
2409
  this.repo.atomic(() => {
@@ -2447,16 +2413,11 @@ var Engine = class {
2447
2413
  }
2448
2414
  };
2449
2415
  // src/policy/actors.ts
2450
- /** Compare two logins or repository names without case; no other normalisation (spec A3). */
2416
+ /** Compare two logins or repository names without case; no other normalisation. */
2451
2417
  const sameName = (a, b) => a.toLowerCase() === b.toLowerCase();
2452
2418
  const BOT_SUFFIX = /\[bot\]$/i;
2453
- /** A bot: the Source marked the Trigger as not written by a person (an account type other than a user, CI, or this agent), or the login ends in `[bot]`. */
2454
2419
  const isBot = (trigger) => !trigger.human || BOT_SUFFIX.test(trigger.actor.id);
2455
- /**
2456
- * Judge a Trigger's actor and repository, in order: a repository outside `repositories` (or none) is `rejected-repo`; an empty
2457
- * actor id (a CI transition) is a `source-fact`; an actor not on `allowed` is `rejected-bot` or `rejected-actor`; anyone else,
2458
- * a bot named on the list included, is `allowed` (the gate still treats such a bot as inferred).
2459
- */
2420
+ /** A bot named on the list is `allowed` here; the gate still admits it only as an inferred candidate. */
2460
2421
  function actorVerdict(trigger, rule) {
2461
2422
  const repository = trigger.meta.repository;
2462
2423
  if (typeof repository !== "string" || !rule.repositories.some((name) => sameName(name, repository))) return "rejected-repo";
@@ -2466,8 +2427,8 @@ function actorVerdict(trigger, rule) {
2466
2427
  }
2467
2428
  // src/policy/policy.ts
2468
2429
  /**
2469
- * Runtime settings schemas, assessment validation, and policy gate evaluation.
2470
- * Who may originate work is one global list of logins, compared without case; a bot on it is an inferred candidate at most.
2430
+ * Who may originate work is one global list of logins, compared without case; a bot on it is an inferred
2431
+ * candidate at most.
2471
2432
  */
2472
2433
  const RuleIdSchema = z.string().regex(/^[a-zA-Z0-9_-]{1,80}$/);
2473
2434
  const RuleAssessmentSchema = z.object({
@@ -2484,19 +2445,16 @@ const AdmissionDirectionSchema = z.object({
2484
2445
  requires_design_decision: z.boolean(),
2485
2446
  evidence: z.string().trim().min(1).max(700)
2486
2447
  }).strict();
2487
- /** Check whether an assessment established implementation intent and a settled target without open design decisions. */
2488
2448
  function directionAllowed(value) {
2489
2449
  const result = AdmissionDirectionSchema.safeParse(value);
2490
2450
  return result.success && result.data.implementation_requested && result.data.target_basis !== "undetermined" && !result.data.requires_design_decision;
2491
2451
  }
2492
2452
  /**
2493
- * Validate an assessment against enabled rules and convert it into an engine `AssessmentOutcome`.
2494
- * Acceptance requires a settled direction, at least one matched enabled rule, and `intent_evidence`. Accepts only in `auto` mode;
2495
- * `observe` records `would_accept`, and `off` ignores and records nothing, for explicit and inferred candidates alike.
2496
- * The `strength` argument is kept for caller compatibility and no longer changes the result.
2453
+ * Accepts only in `auto` mode; `observe` records `would_accept`, and `off` ignores and records nothing, for explicit
2454
+ * and inferred candidates alike.
2497
2455
  * @throws when `rule_assessments` does not cover every enabled rule exactly once, or an accept omits `intent_evidence`.
2498
2456
  */
2499
- function assessmentOutcome(assessment, settings, strength) {
2457
+ function assessmentOutcome(assessment, settings) {
2500
2458
  const enabled = settings.policy.rules.filter((rule) => rule.enabled);
2501
2459
  const judged = assessment.rule_assessments;
2502
2460
  const ids = new Set(judged.map((item) => item.rule_id));
@@ -2617,8 +2575,8 @@ function canonical(value) {
2617
2575
  return JSON.stringify(value, (_key, item) => item && typeof item === "object" && !Array.isArray(item) ? Object.fromEntries(Object.entries(item).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0)) : item);
2618
2576
  }
2619
2577
  /**
2620
- * Compute a version hash for settings that affect work authorization.
2621
- * Changes to models, concurrency, pause state, or polling intervals do not invalidate existing decisions.
2578
+ * Hashes only the settings that affect work authorization; model, concurrency, pause and polling changes
2579
+ * keep decisions valid.
2622
2580
  */
2623
2581
  function policyVersion(settings) {
2624
2582
  const { rules, acceptMode, allowed, admission } = settings.policy;
@@ -2629,11 +2587,10 @@ function policyVersion(settings) {
2629
2587
  admission
2630
2588
  }));
2631
2589
  }
2632
- /** The allowed-list entry naming `actor`, compared without case; the list is global, so `actor.source` is not consulted. */
2590
+ /** Compared without case. The list is global, so `actor.source` is not consulted. */
2633
2591
  function allowedSender(settings, actor) {
2634
2592
  return settings.policy.allowed.find((entry) => sameName(entry, actor.id));
2635
2593
  }
2636
- /** Check whether a thread has tickets of its own (excluding context-only subscriptions). */
2637
2594
  const participating = (thread, tickets) => [...thread.subscriptions, ...tickets.map((ticket) => ticket.id)].some((id) => !thread.context?.includes(id));
2638
2595
  const listened = (sources, source, meta) => sources.scope({
2639
2596
  source,
@@ -2650,7 +2607,6 @@ const STRENGTH = {
2650
2607
  inferred: 1,
2651
2608
  explicit: 2
2652
2609
  };
2653
- /** Compute how a settings change from `old` to `now` affects the authorization or matched rules of each open ticket. */
2654
2610
  function policyEffects(tickets, old, now, scope) {
2655
2611
  const before = settingsPolicy(old, scope(old)), after = settingsPolicy(now, scope(now));
2656
2612
  const effects = /* @__PURE__ */ new Map();
@@ -2688,11 +2644,10 @@ function policyEffects(tickets, old, now, scope) {
2688
2644
  }
2689
2645
  return effects;
2690
2646
  }
2691
- /** Build the active Policy for gating incoming Triggers and revalidating existing Ticket Mandates. */
2692
2647
  function settingsPolicy(settings, sources) {
2693
2648
  const version = policyVersion(settings);
2694
2649
  const mode = settings.policy.acceptMode;
2695
- /** Gate non-human triggers: only listed bots (as inferred candidates) and senderless system/CI events may pass; other bots are rejected. */
2650
+ /** Only listed bots (as inferred candidates) and senderless system/CI events pass; other bots are rejected. */
2696
2651
  const machineGate = (trigger) => {
2697
2652
  const bot = trigger.actor.id ? allowedSender(settings, trigger.actor) : void 0;
2698
2653
  if (trigger.actor.id && !bot) return {
@@ -2924,7 +2879,8 @@ const byLogin = (a, b) => {
2924
2879
  return x < y ? -1 : x > y ? 1 : a < b ? -1 : a > b ? 1 : 0;
2925
2880
  };
2926
2881
  /**
2927
- * Map the config policy onto Settings. The allowed list is sorted here (app layer) so that its order does not change the policy version; the case of each login is kept, and `src/policy/**` is not changed (comparison is case-insensitive, the hash covers the given entries).
2882
+ * Maps the config policy onto Settings. The allowed list is sorted here so that its order does not change
2883
+ * the policy version. The case of each login is kept; comparison is case-insensitive.
2928
2884
  */
2929
2885
  function settingsOf(config) {
2930
2886
  const { allowed, acceptMode, rules, admission } = config.policy;
@@ -2937,9 +2893,13 @@ function settingsOf(config) {
2937
2893
  }
2938
2894
  // src/app/credentials.ts
2939
2895
  /**
2940
- * Startup credential and policy checks: resolves the selected auth mode into the environment the runtime may receive, and applies the startup rules that need the config, the agent account or the runtime probe. Names are reported, values never.
2896
+ * Resolves the selected auth mode into the environment the runtime may receive.
2897
+ * Names are reported, values never.
2898
+ */
2899
+ /**
2900
+ * Per mode, the requirements. A requirement is met when every variable of one of its alternatives is set (non-blank).
2901
+ * All names are in AUTH_ENV[mode].
2941
2902
  */
2942
- /** Per mode, the requirements; a requirement is met when every variable of one of its alternatives is set (non-blank). All names are in AUTH_ENV[mode]. */
2943
2903
  const REQUIRED_ENV = {
2944
2904
  "api-key": [[["ANTHROPIC_API_KEY"]]],
2945
2905
  subscription: [[["CLAUDE_CODE_OAUTH_TOKEN"]]],
@@ -3025,13 +2985,12 @@ function assertStartupPolicy(config, credential, agentLogin, probe, now = Date.n
3025
2985
  // src/app/effects.ts
3026
2986
  /**
3027
2987
  * Signals that an effect cannot run right now, such as when processing is paused or all worker slots are full.
3028
- * Effects reschedules the row after the initial retry delay without incrementing its failure count, and the WorkerExecutor throws it from launch and delivery handlers.
2988
+ * Effects reschedules the row after the initial retry delay without incrementing its failure count.
3029
2989
  */
3030
2990
  var Later = class extends Error {};
3031
2991
  /**
3032
- * Durable SQLite-backed queue that persists side effects inside command transactions and dispatches them after commit.
3033
- * Coordinates per-kind handlers, concurrency limits, exponential backoff, and health reporting for the Assessor, WorkerExecutor, RouterHost and Outbox.
3034
- * Changes from the reference: the constructor parameters are declared fields, assigned in the constructor body; a `cleanup` effect kind is added; comments name the Router session and WorkerExecutor.
2992
+ * Durable SQLite-backed queue that persists side effects inside command transactions
2993
+ * and dispatches them after commit.
3035
2994
  */
3036
2995
  var Effects = class {
3037
2996
  handlers = /* @__PURE__ */ new Map();
@@ -3041,7 +3000,7 @@ var Effects = class {
3041
3000
  broken = /* @__PURE__ */ new Set();
3042
3001
  seq;
3043
3002
  scheduled = false;
3044
- /** Effects run only between `start` and `drain`; rows queued before `start` wait, such as while setup is incomplete. */
3003
+ /** Effects run only between `start` and `drain`. Rows queued before `start` wait, e.g. while setup is incomplete. */
3045
3004
  started = false;
3046
3005
  stopped = false;
3047
3006
  store;
@@ -3053,7 +3012,7 @@ var Effects = class {
3053
3012
  this.now = now;
3054
3013
  this.seq = Number(store.db.prepare("SELECT IFNULL(MAX(seq), 0) AS seq FROM effect").get()?.seq ?? 0);
3055
3014
  }
3056
- /** Upserts an effect in the caller's transaction, coalescing with any waiting row of the same kind and key while keeping its queue position. */
3015
+ /** Upserts an effect in the caller's transaction, coalescing with a waiting row of the same kind and key. */
3057
3016
  enqueue(kind, key, payload) {
3058
3017
  this.store.db.prepare(`INSERT INTO effect (kind, key, value, seq, due, since) VALUES (?, ?, ?, ?, ?, ?) ON CONFLICT(kind, key) DO UPDATE
3059
3018
  SET value = excluded.value, generation = generation + 1, attempts = 0, error = '', due = excluded.due`).run(kind, key, JSON.stringify(payload), ++this.seq, this.now(), new Date(this.now()).toISOString());
@@ -3095,7 +3054,10 @@ var Effects = class {
3095
3054
  this.dispatch();
3096
3055
  });
3097
3056
  }
3098
- /** Resolves once no effect of `kind` (or of any kind) is running and none can start because none is due or `limit()` has no room. */
3057
+ /**
3058
+ * Resolves once no effect of `kind` (or of any kind) is running, and none can start
3059
+ * because none is due or `limit()` has no room.
3060
+ */
3099
3061
  async idle(kind) {
3100
3062
  for (;;) {
3101
3063
  this.dispatch();
@@ -3187,7 +3149,10 @@ var Effects = class {
3187
3149
  }
3188
3150
  };
3189
3151
  // src/executor/contract.ts
3190
- /** Applies inputsRead idempotently: `fresh` returns, in order, the ids it has not returned before (duplicates within one call count once). */
3152
+ /**
3153
+ * Idempotent: `fresh` returns, in order, the ids it has not returned before.
3154
+ * Duplicates within one call count once.
3155
+ */
3191
3156
  var ReadLedger = class {
3192
3157
  seen = /* @__PURE__ */ new Set();
3193
3158
  fresh(ids) {
@@ -3201,7 +3166,10 @@ var ReadLedger = class {
3201
3166
  };
3202
3167
  const DRAIN_MS = 2e3;
3203
3168
  const workerKey = (ticketId) => `worker-${ticketId}`;
3204
- /** Mode of a ticket Input (see docs/runtime-contract.md): a person's message steers; notes, legacy reopen markers and a message no person wrote that waited on a ready ticket (`quiet`) are injected; everything else is a followup. */
3169
+ /**
3170
+ * Mode of a ticket Input (see docs/runtime-contract.md): a person's message steers; notes, reopen markers and
3171
+ * a message no person wrote that waited on a ready ticket (`quiet`) are injected; everything else is a followup.
3172
+ */
3205
3173
  function inputMode(input) {
3206
3174
  switch (input.kind) {
3207
3175
  case "trigger": return input.human ? "steer" : input.quiet ? "inject" : "followup";
@@ -3212,7 +3180,10 @@ function inputMode(input) {
3212
3180
  case "resumed": return "followup";
3213
3181
  }
3214
3182
  }
3215
- /** What is left of the limits for an Assignment that used `used` already: the SDK caps of a launch or resume, at least 1 turn and $0.01 (ADR-24). The host still checks the totals (ADR-6). */
3183
+ /**
3184
+ * What is left of the limits for an Assignment that used `used` already: the SDK caps of a launch or resume,
3185
+ * at least 1 turn and $0.01. The host still checks the totals.
3186
+ */
3216
3187
  function remainingLimits(limits, used) {
3217
3188
  if (!used) return { ...limits };
3218
3189
  return {
@@ -3220,7 +3191,7 @@ function remainingLimits(limits, used) {
3220
3191
  maxBudgetUsd: Math.max(.01, Math.round((limits.maxBudgetUsd - used.costUsd) * 1e6) / 1e6)
3221
3192
  };
3222
3193
  }
3223
- /** Worker-facing text of an Input; undefined for a legacy reopen marker, which is marked delivered without being sent. */
3194
+ /** Worker-facing text of an Input; undefined for a reopen marker, which is marked delivered without being sent. */
3224
3195
  function renderInput(input, trigger) {
3225
3196
  switch (input.kind) {
3226
3197
  case "trigger": return trigger ? JSON.stringify({
@@ -3304,11 +3275,10 @@ var WorkerExecutor = class {
3304
3275
  this.halt(ticket.id, assignment.id);
3305
3276
  });
3306
3277
  }
3307
- /** Ticket IDs whose worker session is open now. */
3308
3278
  liveTickets() {
3309
3279
  return [...this.live.keys()];
3310
3280
  }
3311
- /** Injects a host notice into the ticket's open worker session; it is read with the next input (inject never starts a turn). False when no session is open. */
3281
+ /** Injects a host notice, read with the next input; never starts a turn. False when no session is open. */
3312
3282
  notify(ticketId, notice) {
3313
3283
  const live = this.live.get(ticketId);
3314
3284
  if (!live) return false;
@@ -3922,7 +3892,7 @@ var EventMapper = class {
3922
3892
  }
3923
3893
  };
3924
3894
  // src/runtimes/claude/children.ts
3925
- /** Tracks the runtime's child processes in <stateDir>/children.json and stops the ones an earlier host left running (ADR-32). */
3895
+ /** Tracks the runtime's child processes in <stateDir>/children.json and stops the ones an earlier host left running. */
3926
3896
  var ChildRegistry = class {
3927
3897
  file;
3928
3898
  constructor(file) {
@@ -4036,10 +4006,12 @@ async function reapChildren(registry, options = {}) {
4036
4006
  }
4037
4007
  // src/runtimes/claude/runtime.ts
4038
4008
  /**
4039
- * Claude runtime adapter: one SDK query per session fed by a message stream. steer is sent into the running turn with priority next;
4040
- * followup is held until the running turn's result; inject is merged into the next input sent (never sent alone, the SDK shouldQuery
4041
- * flag is unused). Message uuids are adapter UUIDs mapped to input ids. The permission mode is fixed by options.ts.
4042
- * A result suppressed by interrupt does not end the newer turn: busy and held followups wait for that turn's own result.
4009
+ * Claude runtime adapter: one SDK query per session fed by a message stream.
4010
+ * steer is sent into the running turn with priority next; followup is held until the running turn's result.
4011
+ * inject is merged into the next input sent and is never sent alone (the SDK shouldQuery flag is unused).
4012
+ * Message uuids are adapter UUIDs mapped to input ids. The permission mode is fixed by options.ts.
4013
+ * A result suppressed by interrupt does not end the newer turn:
4014
+ * busy and held followups wait for that turn's own result.
4043
4015
  */
4044
4016
  const CLAUDE_SDK_VERSION = "0.3.321";
4045
4017
  const INJECT_SEPARATOR = "\n\n---\n\n";
@@ -4056,7 +4028,7 @@ function bwrapProbeArgs(weakerNested) {
4056
4028
  "true"
4057
4029
  ];
4058
4030
  }
4059
- /** Linux: bwrap and socat on PATH and one bwrap smoke run with the same /proc choice the SDK makes (fresh /proc, or a tmpfs /proc with weakerNested, the mask the SDK also adds). */
4031
+ /** Linux: bwrap and socat on PATH, and a bwrap smoke run with the /proc mount the SDK will use. */
4060
4032
  function sandboxAvailable(platform = process.platform, weakerNested = false, run = (cmd, args) => spawnSync(cmd, args, {
4061
4033
  stdio: "ignore",
4062
4034
  timeout: 1e4
@@ -4498,7 +4470,7 @@ var FakeRuntime = class {
4498
4470
  };
4499
4471
  // src/app/agent.ts
4500
4472
  /**
4501
- * The agent runtime, workspace choice and Router mode for `start`: the Claude runtime with git worktrees and a Router session, or the scripted fake runtime with scratch directories and the deterministic assessor; the adapter env carries only the inherited whitelist and the selected auth mode's variables.
4473
+ * The adapter env carries only the inherited whitelist and the selected auth mode's variables.
4502
4474
  */
4503
4475
  function agentEnv(credential, env) {
4504
4476
  const values = {};
@@ -4552,7 +4524,11 @@ function createAgent(o) {
4552
4524
  };
4553
4525
  }
4554
4526
  // src/infra/commands.ts
4555
- /** Subprocess runner for the hosting API program and git, with timeouts, abort signals, sanitized errors and API paths confined to a root. Changes from the reference: the chat CLI runner and its error rewrite are removed; the API program, the inherited environment, the API-only variables and the process runner are injected options instead of a deployment; subprocesses get a whitelisted environment (subprocessEnv) instead of the daemon environment, and API-only variables reach only the API program; the repository restriction becomes a path root set by at; the JSON reader is call. */
4527
+ /**
4528
+ * Subprocess runner for the hosting API program and git, with timeouts, abort signals and sanitized errors.
4529
+ * API paths are confined to a root. Subprocesses get a whitelisted environment.
4530
+ * API-only variables reach only the API program.
4531
+ */
4556
4532
  const TIMEOUT_MS = 9e4;
4557
4533
  const MAX_BUFFER = 16777216;
4558
4534
  var CliError = class extends Error {
@@ -4588,7 +4564,6 @@ function ended(error) {
4588
4564
  if (typeof code === "number") return `exited with code ${code}`;
4589
4565
  return typeof code === "string" ? `could not run: ${code}` : "failed";
4590
4566
  }
4591
- /** Parses array elements against a schema, skipping invalid items and incrementing `tally.skipped`. */
4592
4567
  function validItems(item, list, tally) {
4593
4568
  const items = list.flatMap((value) => {
4594
4569
  const parsed = item.safeParse(value);
@@ -4650,7 +4625,6 @@ var Commands = class Commands {
4650
4625
  runApi(args) {
4651
4626
  return this.run(this.options.apiProgram, args, void 0, this.options.apiEnv ?? {});
4652
4627
  }
4653
- /** Runs the API program and parses its JSON output. */
4654
4628
  async call(args) {
4655
4629
  return JSON.parse(await this.runApi(args));
4656
4630
  }
@@ -4690,33 +4664,35 @@ var Commands = class Commands {
4690
4664
  // src/app/kv.ts
4691
4665
  /** Define a single typed KV key; external Source plugins use `record` and `family` to type their own keys. */
4692
4666
  const record = (key) => key;
4693
- /** Define a KV key family whose keys join `prefix` and name parts with `:` (or a custom `format`). */
4694
4667
  function family(prefix, format = (...name) => name.join(":")) {
4695
4668
  return Object.assign((...name) => `${prefix}${format(...name)}`, { prefix });
4696
4669
  }
4697
4670
  const KV = {
4698
- /** Runtime health: the HealthBoard snapshot the daemon writes each pass (at most every 5s) and the status CLI reads. */
4671
+ /**
4672
+ * Runtime health: the HealthBoard snapshot the daemon writes each pass (at most every 5 s),
4673
+ * read by the status CLI.
4674
+ */
4699
4675
  health: { snapshot: record("health") },
4700
- /** Store: current database schema version. */
4701
4676
  store: { schema: record("schema") },
4702
- /** Runtime: the Router session's ID and the prompt version it was opened under, and the readiness marker the daemon writes once started and deletes first on stop (health --ready). */
4677
+ /**
4678
+ * Runtime: the Router session's ID and the prompt version it was opened under, and the readiness marker
4679
+ * the daemon writes once started and deletes first on stop (`health --ready`).
4680
+ */
4703
4681
  runtime: {
4704
4682
  masterId: record("master-id"),
4705
4683
  routerPrompt: record("router-prompt-version"),
4706
4684
  ready: record("runtime-ready")
4707
4685
  },
4708
- /** Sessions: prompt revisions each Session has worked under, ordered oldest first; the last entry (`.at(-1)`) is its active revision. */
4686
+ /**
4687
+ * Sessions: prompt revisions each Session has worked under, oldest first.
4688
+ * The last entry (`.at(-1)`) is its active revision.
4689
+ */
4709
4690
  sessions: { prompts: family("session-prompts:") },
4710
4691
  /** Per-Source resolved display names mapping a named ID (such as a chat ID) to its display name. */
4711
4692
  sources: { labels: family("source-labels:") }
4712
4693
  };
4713
4694
  // src/infra/text.ts
4714
- /**
4715
- * Word counting and string formatting utilities for message limits and templates.
4716
- * Changes from the reference: `IM_WORDS`, `checkedImText` and `sameName` are removed; `policy/actors.ts` is the single `sameName`.
4717
- */
4718
4695
  const words = new Intl.Segmenter("zh", { granularity: "word" });
4719
- /** Counts word-like segments across Chinese, English, and mixed text, ignoring punctuation. */
4720
4696
  function wordCount(text) {
4721
4697
  let count = 0;
4722
4698
  for (const segment of words.segment(text)) if (segment.isWordLike) count++;
@@ -4724,14 +4700,22 @@ function wordCount(text) {
4724
4700
  }
4725
4701
  const fill = (template, values) => template.replace(/\{(\w+)\}/g, (_, name) => values[name] ?? "");
4726
4702
  const escapeRegExp = (text) => text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
4727
- /** Lines that credit the AI product as an author (ADR-34): a Co-authored-by trailer whose name has the word Claude or whose address is @anthropic.com, and a "Generated with … Claude" line. Case-insensitive, one line at a time. scripts/check-commits.ts uses the same predicate. */
4703
+ /**
4704
+ * Lines that credit the AI product as an author: a Co-authored-by trailer whose name has the word Claude or whose
4705
+ * address is @anthropic.com, and a "Generated with … Claude" line. Case-insensitive, one line at a time.
4706
+ * scripts/check-commits.ts uses the same predicate.
4707
+ */
4728
4708
  const AI_ATTRIBUTION_PATTERNS = [
4729
4709
  /^\s*co-authored-by:[^<\r\n]*\bclaude\b/i,
4730
4710
  /^\s*co-authored-by:.*@anthropic\.com\b/i,
4731
4711
  /\bgenerated with\b.*\bclaude\b/i
4732
4712
  ];
4733
4713
  const isAiAttributionLine = (line) => AI_ATTRIBUTION_PATTERNS.some((pattern) => pattern.test(line));
4734
- /** Removes AI attribution lines; returns `text` itself when there are none. Blank lines that the removal leaves doubled (or leading) are dropped, trailing blank lines are trimmed, and one final newline is kept when `text` had one. All other lines stay as they were. */
4714
+ /**
4715
+ * Removes AI attribution lines; returns `text` itself when there are none.
4716
+ * Blank lines the removal leaves doubled or leading are dropped; trailing blank lines are trimmed.
4717
+ * One final newline is kept when `text` had one.
4718
+ */
4735
4719
  function stripAiAttribution(text) {
4736
4720
  const lines = text.split("\n");
4737
4721
  if (!lines.some(isAiAttributionLine)) return text;
@@ -4752,7 +4736,7 @@ function stripAiAttribution(text) {
4752
4736
  return body !== "" && text.endsWith("\n") ? body + "\n" : body;
4753
4737
  }
4754
4738
  // src/sources/host.ts
4755
- /** Fixed English health templates (`{name}` placeholders); the panel localizes each `code` in the interface language. */
4739
+ /** Fixed English templates; the panel localizes each `code` in the interface language. */
4756
4740
  const HEALTH_TEXT = {
4757
4741
  sourceOff: "Off",
4758
4742
  pollFailures: "{count} consecutive failed polls; last: {error}"
@@ -4765,10 +4749,7 @@ function health(state, code, values = {}) {
4765
4749
  ...Object.keys(values).length ? { values } : {}
4766
4750
  };
4767
4751
  }
4768
- /**
4769
- * Builds the context for one Source kind. Health reports go to the entries of the registration current when the context
4770
- * is built, becoming no-ops once that registration ends.
4771
- */
4752
+ /** Health reports go to the registration current when the context is built; they become no-ops once it ends. */
4772
4753
  function sourceContext(deps, kind) {
4773
4754
  const { store } = deps;
4774
4755
  const report = deps.sources.reporter(kind);
@@ -4827,19 +4808,24 @@ function sourceContext(deps, kind) {
4827
4808
  };
4828
4809
  }
4829
4810
  // src/sources/poll.ts
4830
- /** Shared polling helpers for subscribed threads, overlap-safe cursors, and delays. Changes from the reference: `pause` takes injectable timers, `pollThreads` takes an injectable clock, and both take only the context members they use. */
4811
+ /** Shared polling helpers for subscribed threads: overlap-safe cursors and delays. */
4831
4812
  /** How long a subscribed Thread with no live Ticket continues to be polled after its last Trigger or Ticket end. */
4832
4813
  const POLL_HORIZON_MS = 12096e5;
4833
- /** True when a subscribed thread has a live ticket or recent activity/completion within `POLL_HORIZON_MS`. */
4834
4814
  function polled(ctx, thread, now = Date.now()) {
4835
4815
  const tickets = thread.subscriptions.map((id) => ctx.tickets.get(id));
4836
4816
  if (tickets.some((ticket) => !!ticket && liveTicket(ticket))) return true;
4837
4817
  const times = [thread.lastAt ?? thread.createdAt, ...tickets.map((ticket) => ticket?.ended?.at ?? "")].map(Date.parse).filter(Number.isFinite);
4838
4818
  return now - Math.max(...times) < POLL_HORIZON_MS;
4839
4819
  }
4840
- /** Steps a poll start timestamp back by 5 seconds so late-arriving remote events are not missed; deterministic trigger IDs deduplicate overlaps. */
4820
+ /**
4821
+ * Steps a poll start timestamp back by 5 seconds so late-arriving remote events are not missed; deterministic trigger
4822
+ * IDs deduplicate overlaps.
4823
+ */
4841
4824
  const cursorAt = (started) => (/* @__PURE__ */ new Date(Date.parse(started) - 5e3)).toISOString();
4842
- /** Polls each subscribed Thread of `source` within the horizon independently; records failures on the Thread as `pollError` (unless `stopped()`) and clears `pollError` on success. */
4825
+ /**
4826
+ * Polls each subscribed Thread of `source` within the horizon independently; records failures on the Thread as
4827
+ * `pollError` (unless `stopped()`) and clears `pollError` on success.
4828
+ */
4843
4829
  async function pollThreads$1(ctx, source, stopped, poll, skip = () => false, now = () => Date.now()) {
4844
4830
  for (const thread of ctx.threads.subscribed(source)) {
4845
4831
  if (stopped()) return;
@@ -4859,7 +4845,7 @@ const systemTimers$1 = {
4859
4845
  clearTimeout(handle);
4860
4846
  }
4861
4847
  };
4862
- /** Waits `ms` or until `signal` aborts, using the injected timers (global timers by default) so tests can drive polling loops. */
4848
+ /** Waits `ms` or until `signal` aborts; the injected timers let tests drive polling loops. */
4863
4849
  function pause(ms, signal, timers = systemTimers$1) {
4864
4850
  return new Promise((resolve) => {
4865
4851
  if (signal.aborted) {
@@ -4878,7 +4864,10 @@ function pause(ms, signal, timers = systemTimers$1) {
4878
4864
  });
4879
4865
  }
4880
4866
  // src/sources/github/trigger.ts
4881
- /** Normalizes GitHub issues, pull requests, comments, reviews, review comments and CI transitions into domain Triggers. Changes from the reference: thread, trigger and channel keys include the repository (Thread id <owner>/<name>#<number>), every Trigger carries meta.repository in the configured spelling, githubAddress parses any configured repository, the bot suffix check ignores case, the CI Trigger takes its time from the caller, default-branch CI is removed, and sameName comes from the policy actors module. */
4867
+ /**
4868
+ * Normalizes GitHub issues, pull requests, comments, reviews, review comments and CI transitions into domain Triggers.
4869
+ * Thread, trigger and channel keys include the repository (Thread id <owner>/<name>#<number>).
4870
+ */
4882
4871
  const SOURCE = "github";
4883
4872
  /** The kind has no config keys; `{}` parses, so the kind is enabled under the Runtime's empty configs reader. */
4884
4873
  const GithubConfigSchema = z.record(z.string(), z.never());
@@ -4902,7 +4891,7 @@ const githubChannel = (repository, number, url) => ({
4902
4891
  ...url ? { url } : {}
4903
4892
  }
4904
4893
  });
4905
- /** Parses a web or API URL for an Issue, PR, or review comment in a configured repository; returns undefined for other URLs or repositories. */
4894
+ /** Parses an Issue, PR, or review-comment URL in a configured repository; undefined for anything else. */
4906
4895
  function githubAddress(url, repositories) {
4907
4896
  const match = /^https:\/\/(?:api\.github\.com\/repos|github\.com)\/([^/]+\/[^/]+)\/(issues|pulls?)\/(\d+)\/?(?:#discussion_r(\d+)|#[\w-]*)?$/i.exec(url);
4908
4897
  if (!match) return void 0;
@@ -4923,10 +4912,7 @@ const UserSchema = z.looseObject({
4923
4912
  login: "ghost",
4924
4913
  type: "User"
4925
4914
  });
4926
- /**
4927
- * A GitHub comment or review; a review carries `state` and `commit_id`, and an inline review comment carries its file,
4928
- * line, diff hunk, and `in_reply_to_id` (the root comment ID of its review thread).
4929
- */
4915
+ /** A GitHub comment or review; `in_reply_to_id` is the root comment ID of its review thread. */
4930
4916
  const CommentSchema$1 = z.looseObject({
4931
4917
  id: z.number(),
4932
4918
  body: z.string().nullish(),
@@ -4943,7 +4929,7 @@ const CommentSchema$1 = z.looseObject({
4943
4929
  diff_hunk: z.string().nullish(),
4944
4930
  in_reply_to_id: z.number().nullish()
4945
4931
  });
4946
- /** An Issue as `issues/<number>` returns it; GitHub sets `pull_request` when the number is a pull request. */
4932
+ /** GitHub sets `pull_request` when the number is a pull request. */
4947
4933
  const IssueSchema = CommentSchema$1.extend({
4948
4934
  number: z.number(),
4949
4935
  title: z.string(),
@@ -4952,12 +4938,8 @@ const IssueSchema = CommentSchema$1.extend({
4952
4938
  html_url: z.string(),
4953
4939
  pull_request: z.looseObject({ url: z.string() }).nullish()
4954
4940
  });
4955
- /**
4956
- * True when GitHub reports the account as a regular user rather than a bot or GitHub App.
4957
- * Review bots running under ordinary user accounts count as human here; the prompts identify which accounts are automated.
4958
- */
4941
+ /** Review bots under ordinary user accounts count as human here; the prompts identify automated accounts. */
4959
4942
  const isHumanLogin = (user) => !/\[bot\]$/i.test(user.login) && (!user.type || user.type === "User");
4960
- /** Formats an inline review comment's file:line and the trailing lines of its diff hunk, which GitHub ends at the commented line. */
4961
4943
  function reviewPlace(comment) {
4962
4944
  const line = comment.line ?? comment.original_line;
4963
4945
  const hunk = (comment.diff_hunk ?? "").split("\n").slice(-3).join("\n");
@@ -4978,7 +4960,7 @@ const author = (user, agent) => ({
4978
4960
  },
4979
4961
  human: isHumanLogin(user) && !sameName(user.login, agent)
4980
4962
  });
4981
- /** Builds a trigger for a comment or review; text is empty for a bodyless comment or a `COMMENTED` review without a body. */
4963
+ /** Text is empty for a bodyless comment or a `COMMENTED` review without a body. */
4982
4964
  function commentTrigger(comment, ref, kind, agent) {
4983
4965
  const at = comment.updated_at ?? comment.submitted_at ?? comment.created_at ?? (/* @__PURE__ */ new Date()).toISOString();
4984
4966
  const version = comment.updated_at ?? comment.submitted_at ?? "";
@@ -5010,10 +4992,7 @@ function commentTrigger(comment, ref, kind, agent) {
5010
4992
  }
5011
4993
  };
5012
4994
  }
5013
- /**
5014
- * Builds the trigger for a newly opened Issue's title and body.
5015
- * The ID is the same as when Issue bodies were stored previously, so already-stored bodies are not ingested again.
5016
- */
4995
+ /** The ID matches the one used when Issue bodies were stored earlier, so stored bodies are not ingested again. */
5017
4996
  function issueTrigger(issue, repository, agent) {
5018
4997
  const body = issue.body ?? "";
5019
4998
  return {
@@ -5037,10 +5016,7 @@ function issueTrigger(issue, repository, agent) {
5037
5016
  }
5038
5017
  };
5039
5018
  }
5040
- /**
5041
- * Builds the trigger for a newly opened PR's title and description.
5042
- * PR and Issue numbers share one sequence in GitHub and never collide, so this reuses `issueTrigger`'s `github-issue` ID format.
5043
- */
5019
+ /** PR and Issue numbers never collide, so the PR trigger reuses `issueTrigger`'s `github-issue` ID. */
5044
5020
  function pullTrigger(pull, repository, agent) {
5045
5021
  return {
5046
5022
  ...issueTrigger(pull, repository, agent),
@@ -5081,7 +5057,10 @@ function ciTrigger(repository, pr, revision, summary, at) {
5081
5057
  };
5082
5058
  }
5083
5059
  // src/sources/github/read.ts
5084
- /** Read-only GitHub repository, conversation, and CI check inspection for assessments and workers. Changes from the reference: readers take commands scoped to one repository (repositoryCommands); the repository file reader is removed; the thread context reader is built for the configured repositories and picks the repository from meta.repository; the default-branch CI context is removed. */
5060
+ /**
5061
+ * Read-only GitHub repository, conversation, and CI check inspection for assessments and workers.
5062
+ * Readers take commands scoped to one repository; the thread context reader picks it from meta.repository.
5063
+ */
5085
5064
  const CheckSchema = z.looseObject({
5086
5065
  name: z.string(),
5087
5066
  status: z.string(),
@@ -5171,7 +5150,10 @@ async function pullChecks(commands, head, tally) {
5171
5150
  })).sort((a, b) => a.context.localeCompare(b.context))
5172
5151
  };
5173
5152
  }
5174
- /** Formats a GitHub comment, review, or issue as a model-facing ContextMessage, marking bots and GitHub Apps because bot messages never authorize work. */
5153
+ /**
5154
+ * Formats a GitHub comment, review, or issue as a model-facing ContextMessage, marking bots and GitHub Apps because
5155
+ * bot messages never authorize work.
5156
+ */
5175
5157
  function githubMessage(item, text, id) {
5176
5158
  const line = item.line ?? item.original_line;
5177
5159
  return {
@@ -5262,7 +5244,7 @@ async function readGithub(commands, resource) {
5262
5244
  ...listed.length === 20 ? { next_resource: `github/${kind}/${number}/${section}/${Number(page) + 1}` } : {}
5263
5245
  };
5264
5246
  }
5265
- /** Reads one page of an Issue or PR conversation (with the Issue/PR description first on page 1), using comment URLs as message IDs. */
5247
+ /** One page of an Issue or PR conversation; page 1 starts with the Issue/PR description. */
5266
5248
  async function conversationPage(commands, number, page) {
5267
5249
  const messages = [];
5268
5250
  if (page === 1) {
@@ -5279,10 +5261,7 @@ async function conversationPage(commands, number, page) {
5279
5261
  ...tally.skipped ? { skipped: tally.skipped } : {}
5280
5262
  };
5281
5263
  }
5282
- /**
5283
- * Context reader for a GitHub Trigger: reads the review thread for an inline review comment (whose first comment is its
5284
- * root and parent), or the Issue/PR conversation by page otherwise (whose first page is its root and parent).
5285
- */
5264
+ /** Reads the review thread of an inline review comment; otherwise the Issue/PR conversation by page. */
5286
5265
  function githubThreadContext(commands, repositories) {
5287
5266
  return {
5288
5267
  messageId: (trigger) => trigger.url ?? "",
@@ -5313,18 +5292,21 @@ function githubThreadContext(commands, repositories) {
5313
5292
  };
5314
5293
  }
5315
5294
  // src/sources/github/channel.ts
5316
- /** GitHub outbound channel: posts issue/PR comments and review thread replies under the agent account. Changes from the reference: the reply goes to the repository named in the address, which must be configured; the commands and the agent login are injected; the retry marker is <!-- superagent-claude:<id> -->; the channel link is removed. */
5295
+ /**
5296
+ * GitHub outbound channel: posts issue/PR comments and review thread replies under the agent account.
5297
+ * The reply goes to the repository named in the address, which must be configured.
5298
+ */
5317
5299
  const CommentSchema = z.looseObject({
5318
5300
  id: z.number(),
5319
5301
  body: z.string().nullish(),
5320
5302
  in_reply_to_id: z.number().nullish(),
5321
5303
  user: z.looseObject({ login: z.string() }).nullish()
5322
5304
  });
5323
- /** True when an issue is permanently deleted (410) or locked (403); 404 is retried because it can indicate temporary permission loss on a private repo. */
5305
+ /** A 410, or a locked 403, means gone; 404 is retried because it can mean lost permission on a private repo. */
5324
5306
  const gone = (error) => error instanceof CliError && (error.code === 410 || error.code === 403 && /locked/i.test(error.message));
5325
5307
  const missing = (error) => error instanceof CliError && (error.code === 404 || error.code === 410);
5326
5308
  const MARKER = (id) => `<!-- superagent-claude:${id} -->`;
5327
- /** Posts a comment or review-thread reply with an embedded HTML marker so retries do not create duplicate comments. */
5309
+ /** Embeds an HTML marker so a retried post does not create a duplicate comment. */
5328
5310
  function githubChannelAdapter(options) {
5329
5311
  return {
5330
5312
  allowed: (address) => !!configured(options.repositories, address.repository ?? "") && Number(address.number) > 0,
@@ -5362,7 +5344,7 @@ function githubChannelAdapter(options) {
5362
5344
  };
5363
5345
  }
5364
5346
  // src/sources/github/pull.ts
5365
- /** Pull request reads keyed by repository, and lifecycle observation with the auto-merge fact. Changes from the reference: pull requests and their Threads are keyed by repository; reads take repository-scoped commands; ticket binding (claiming a PR), agent-branch matching and chat notices are removed (delivery milestones). */
5347
+ /** Pull request reads and lifecycle observation, including the auto-merge fact. */
5366
5348
  const PullSchema = z.looseObject({
5367
5349
  title: z.string().optional(),
5368
5350
  state: z.string(),
@@ -5409,7 +5391,11 @@ function observePull(ctx, pr, now) {
5409
5391
  return ended;
5410
5392
  }
5411
5393
  // src/sources/github/source.ts
5412
- /** GitHub polling Source: the agent account's notifications, the PRs of open tickets with their CI, and subscribed Threads, polled per repository. Changes from the reference: the plugin entry becomes githubSource, which returns the kind and a SourceService; every configured repository is polled in turn with its own cursors and its health entry github:<owner>/<name>; the agent login is read at start with api user, and each repository's access and push permission is checked at start and kept in repo-access:<owner>/<name> (an inaccessible repository is not polled); the interval is an option (default and minimum 60 s) instead of a config key, raised to the API's X-Poll-Interval; the notification read's rate-limit headers are reported in health; repository-wide streams, default-branch CI, observe switches, the policy part, environment variables, display fields and links are removed; CI is polled for the open pull requests of open tickets; poll failures are reported with their consecutive count from the first one. */
5394
+ /**
5395
+ * GitHub polling Source: the agent account's notifications, the PRs of open tickets with their CI,
5396
+ * and subscribed Threads, polled per repository. Each repository has its own cursors and health entry
5397
+ * (github:<owner>/<name>). Access and push permission are checked at start; an inaccessible repository is not polled.
5398
+ */
5413
5399
  const POLL_INTERVAL_MS = 6e4;
5414
5400
  const GITHUB_STATE = {
5415
5401
  access: family("repo-access:"),
@@ -5419,7 +5405,10 @@ const GITHUB_STATE = {
5419
5405
  cursor: family("github-cursor:", (repository, number) => `${repository}#${number}`),
5420
5406
  ci: family("github-ci:", (repository, number) => `${repository}#${number}`)
5421
5407
  };
5422
- /** Reports a failed poll with its consecutive count: `ready` until `FAILURES_BEFORE_ERROR` consecutive failures, `error` from then on. */
5408
+ /**
5409
+ * Reports a failed poll with its consecutive count: `ready` until `FAILURES_BEFORE_ERROR` consecutive failures,
5410
+ * `error` from then on.
5411
+ */
5423
5412
  function pollFailure(failures, error) {
5424
5413
  return health(failures >= 3 ? "error" : "ready", "pollFailures", {
5425
5414
  count: String(failures),
@@ -5432,10 +5421,10 @@ const NotificationSchema = z.looseObject({
5432
5421
  subject: z.looseObject({ url: z.string().nullish() })
5433
5422
  });
5434
5423
  const RETRY_WINDOW_MS = 864e5;
5435
- /** True when GitHub returns HTTP 410 (Gone); HTTP 404 is retried because it can indicate revoked access to a private repository. */
5424
+ /** Only 410 counts as deleted; 404 is retried because it can mean revoked access to a private repository. */
5436
5425
  const deleted = (error) => error instanceof CliError && error.code === 410;
5437
5426
  const stopped = (round) => round.signal.aborted;
5438
- /** Records a failed read on the Thread and in the round; deleted (410) or abandoned (24h+) threads do not fail the round. */
5427
+ /** Deleted or abandoned threads are recorded without failing the round. */
5439
5428
  function failed(ctx, round, number, error, abandoned = false) {
5440
5429
  if (deleted(error)) round.dropped.push(number);
5441
5430
  else if (abandoned) round.abandoned.push(number);
@@ -5478,7 +5467,6 @@ async function pollRepository(ctx, poll) {
5478
5467
  }, poll.repository);
5479
5468
  return interval;
5480
5469
  }
5481
- /** Reads notifications since the cursor and each Issue or PR they name, advancing the cursor atomically with the retry map for failed reads; deleted (410) Issues are dropped once their error is recorded. */
5482
5470
  async function pollNotifications(ctx, round) {
5483
5471
  const { state } = ctx;
5484
5472
  const { repository, login } = round;
@@ -5676,7 +5664,7 @@ function nameThread(ctx, repository, number, title) {
5676
5664
  facts: { number: { value: `#${number}` } }
5677
5665
  });
5678
5666
  }
5679
- /** Polls subscribed Issue and PR Threads of this repository not already read in this round; a deleted (410) Issue is still polled within the horizon (costing one read) and does not fail the round. */
5667
+ /** A deleted (410) Issue is still polled within the horizon and does not fail the round. */
5680
5668
  async function pollThreads(ctx, round) {
5681
5669
  const skip = (thread) => {
5682
5670
  const parsed = parseThreadId(thread.ref.id);
@@ -5913,7 +5901,11 @@ function githubSource(options) {
5913
5901
  };
5914
5902
  }
5915
5903
  // src/sources/github/delivery.ts
5916
- /** Host-side pull request writes under the agent account: open or adopt the PR of a branch, reply in a review thread and resolve it, enable auto-merge (never merge), close a PR, and read the push permission. Only gh api REST and graphql calls. */
5904
+ /**
5905
+ * Host-side pull request writes under the agent account: open or adopt the PR of a branch, reply in a review thread
5906
+ * and resolve it, enable auto-merge (never merge), close a PR, and read the push permission.
5907
+ * Only gh api REST and graphql calls.
5908
+ */
5917
5909
  const PullLite = z.looseObject({
5918
5910
  number: z.number(),
5919
5911
  html_url: z.string(),
@@ -5989,9 +5981,17 @@ function githubPullRequests(commands) {
5989
5981
  };
5990
5982
  }
5991
5983
  // src/app/sources.ts
5992
- /** The Source kinds this build installs: the GitHub Source when SUPERAGENT_CLAUDE_GITHUB_TOKEN is set (the token reaches only the gh calls as GH_TOKEN). The single assembly point that names the hosting service outside its Source plugin; a missing or blank token refuses startup (agent-token), and the account and access checks run through the Source startup hook; the token also authenticates the host's clone and fetch calls for git workspaces and the host's pull request writes (env-scoped header, never in worker sessions or git config). */
5984
+ /**
5985
+ * The Source kinds this build installs: the GitHub Source when SUPERAGENT_CLAUDE_GITHUB_TOKEN is set.
5986
+ * The token reaches only the gh calls, as GH_TOKEN. A missing or blank token refuses startup.
5987
+ * The token also authenticates the host's clone and fetch calls and pull request writes, through an
5988
+ * env-scoped header that never reaches worker sessions or git config.
5989
+ */
5993
5990
  const GITHUB_TOKEN_ENV = "SUPERAGENT_CLAUDE_GITHUB_TOKEN";
5994
- /** Repository access for git workspaces: remote URLs without credentials; the token authenticates only the clone and fetch calls through an env-scoped header. Without a token git tries unauthenticated. */
5991
+ /**
5992
+ * Repository access for git workspaces: remote URLs carry no credentials.
5993
+ * The token authenticates only the clone and fetch calls, through an env-scoped header.
5994
+ */
5995
5995
  function repositoryAccess(env) {
5996
5996
  const token = env[GITHUB_TOKEN_ENV]?.trim();
5997
5997
  const encoded = token ? Buffer.from(`x-access-token:${token}`).toString("base64") : "";
@@ -6044,18 +6044,17 @@ function installedSources(config, env, options = {}) {
6044
6044
  }
6045
6045
  // src/executor/recovery.ts
6046
6046
  /**
6047
- * Restart recovery: every Assignment that was running when the host stopped is ended with `Engine.assignmentInterrupted`,
6048
- * whose restart gives the new Assignment the recovery note as its first Input and queues its launch, unless its latest report
6049
- * settled it (`waiting`, `ready`, `blocked`, `failed`) and its worker caught up with what was delivered since. A settled one keeps
6050
- * its stored session and takes no slot or turn until an Input for it arrives (ADR-32). Pending launches are queued again. Tickets are handled oldest first, and launches coalesce per Assignment, so none is queued twice.
6051
- * Changes from the reference: rewritten as a function over the core ports and the Executor port; stopping the previous
6052
- * process's sessions and background work is dropped because they ended with that process; the note text stays in core.
6047
+ * Restart recovery: a running Assignment is ended with `Engine.assignmentInterrupted`, then relaunched with the
6048
+ * recovery note as its first Input, unless its latest report settled it (`waiting`, `ready`, `blocked`, `failed`)
6049
+ * and its worker caught up. A settled one keeps its session and takes no slot or turn until an Input arrives.
6050
+ * Launches coalesce per Assignment, so none is queued twice.
6051
+ * The previous process's sessions and background work are not stopped here; they ended with that process.
6053
6052
  * `interruptRunning` records the same at a graceful stop (SIGTERM), so the next start only launches what it queued.
6054
6053
  */
6055
6054
  function oldestFirst(tickets) {
6056
6055
  return [...tickets.open()].reverse().sort((a, b) => a.createdAt.localeCompare(b.createdAt));
6057
6056
  }
6058
- /** Recovers open tickets oldest first (`open()` lists newest first, so ties on createdAt keep creation order) and returns their ids in that order. */
6057
+ /** Recovers open tickets oldest first. `open()` lists newest first, so ties on createdAt keep creation order. */
6059
6058
  function recover(host) {
6060
6059
  const tickets = oldestFirst(host.tickets);
6061
6060
  for (const ticket of tickets) {
@@ -6065,7 +6064,10 @@ function recover(host) {
6065
6064
  }
6066
6065
  return tickets.map((ticket) => ticket.id);
6067
6066
  }
6068
- /** At a graceful stop: every running Assignment is handled with `assignmentInterrupted` (oldest ticket first), as recovery would at the next start; returns the ids of the tickets that had one. */
6067
+ /**
6068
+ * At a graceful stop: every running Assignment is handled with `assignmentInterrupted` (oldest ticket first),
6069
+ * as recovery would at the next start. Returns the ids of the tickets that had one.
6070
+ */
6069
6071
  function interruptRunning(host) {
6070
6072
  const interrupted = [];
6071
6073
  for (const ticket of oldestFirst(host.tickets)) {
@@ -6077,7 +6079,6 @@ function interruptRunning(host) {
6077
6079
  return interrupted;
6078
6080
  }
6079
6081
  // src/executor/slots.ts
6080
- /** In-memory worker slot accounting. */
6081
6082
  var Slots = class {
6082
6083
  max;
6083
6084
  held = /* @__PURE__ */ new Map();
@@ -6085,7 +6086,7 @@ var Slots = class {
6085
6086
  constructor(max) {
6086
6087
  this.max = max;
6087
6088
  }
6088
- /** Whether ticketId holds a slot now. A ticket that already holds one gets true without taking another. */
6089
+ /** A ticket that already holds a slot gets true without taking another. */
6089
6090
  acquire(ticketId, kind, human) {
6090
6091
  if (this.held.has(ticketId)) return true;
6091
6092
  let normal = 0;
@@ -6111,16 +6112,15 @@ var Slots = class {
6111
6112
  this.waiting.add(ticketId);
6112
6113
  return false;
6113
6114
  }
6114
- /** Frees ticketId's slot and forgets its waiting wake; idempotent. */
6115
+ /** Idempotent; also forgets the ticket's waiting wake. */
6115
6116
  release(ticketId) {
6116
6117
  this.held.delete(ticketId);
6117
6118
  this.waiting.delete(ticketId);
6118
6119
  }
6119
- /** Whether ticketId currently holds a slot. */
6120
6120
  holds(ticketId) {
6121
6121
  return this.held.has(ticketId);
6122
6122
  }
6123
- /** Number of slots currently held, normal and extra. */
6123
+ /** Held slots, normal and extra. */
6124
6124
  get used() {
6125
6125
  return this.held.size;
6126
6126
  }
@@ -6132,7 +6132,6 @@ var Slots = class {
6132
6132
  }
6133
6133
  };
6134
6134
  // src/executor/worker-store.ts
6135
- /** Reads and writes the worker table. */
6136
6135
  var WorkerStore = class {
6137
6136
  store;
6138
6137
  now;
@@ -6140,7 +6139,6 @@ var WorkerStore = class {
6140
6139
  this.store = store;
6141
6140
  this.now = now;
6142
6141
  }
6143
- /** Returns the worker record of ticketId, if any. */
6144
6142
  get(ticketId) {
6145
6143
  const row = this.store.db.prepare("SELECT * FROM worker WHERE ticket_id = ?").get(ticketId);
6146
6144
  if (row === void 0) return void 0;
@@ -6158,7 +6156,6 @@ var WorkerStore = class {
6158
6156
  updatedAt: String(row.updated_at)
6159
6157
  };
6160
6158
  }
6161
- /** Inserts or replaces the worker record of `record.ticketId` and stamps it with the current time. */
6162
6159
  put(record) {
6163
6160
  this.store.db.prepare(`INSERT INTO worker (ticket_id, assignment_id, runtime, session_id, repository, cwd, branch, push_remote, turns, cost_usd, prompt_version, updated_at)
6164
6161
  VALUES (?,?,?,?,?,?,?,NULL,?,?,?,?)
@@ -6166,19 +6163,19 @@ var WorkerStore = class {
6166
6163
  repository = excluded.repository, cwd = excluded.cwd, branch = excluded.branch, push_remote = excluded.push_remote, turns = excluded.turns,
6167
6164
  cost_usd = excluded.cost_usd, prompt_version = excluded.prompt_version, updated_at = excluded.updated_at`).run(record.ticketId, record.assignmentId, record.runtime, record.sessionId, record.repository, record.cwd, record.branch, record.turns, record.costUsd, record.promptVersion, new Date(this.now()).toISOString());
6168
6165
  }
6169
- /** Deletes the worker record of ticketId. */
6170
6166
  clear(ticketId) {
6171
6167
  this.store.db.prepare("DELETE FROM worker WHERE ticket_id = ?").run(ticketId);
6172
6168
  }
6173
- /** Records the runtime session id of ticketId's worker record. */
6174
6169
  setSession(ticketId, sessionId) {
6175
6170
  this.store.db.prepare("UPDATE worker SET session_id = ?, updated_at = ? WHERE ticket_id = ?").run(sessionId, new Date(this.now()).toISOString(), ticketId);
6176
6171
  }
6177
- /** Records the prompt version the ticket's worker record was opened with. */
6178
6172
  setPromptVersion(ticketId, promptVersion) {
6179
6173
  this.store.db.prepare("UPDATE worker SET prompt_version = ?, updated_at = ? WHERE ticket_id = ?").run(promptVersion, new Date(this.now()).toISOString(), ticketId);
6180
6174
  }
6181
- /** Adds one turn's usage to the record's Assignment; a record of another Assignment starts over from zero under assignmentId. Returns the totals (the given values when there is no record). */
6175
+ /**
6176
+ * A record of another Assignment starts over from zero under assignmentId.
6177
+ * Returns the totals (the given values when there is no record).
6178
+ */
6182
6179
  addUsage(ticketId, assignmentId, turns, costUsd) {
6183
6180
  this.store.db.prepare(`UPDATE worker SET turns = CASE WHEN assignment_id = ? THEN turns + ? ELSE ? END,
6184
6181
  cost_usd = CASE WHEN assignment_id = ? THEN cost_usd + ? ELSE ? END, assignment_id = ?, updated_at = ? WHERE ticket_id = ?`).run(assignmentId, turns, turns, assignmentId, costUsd, costUsd, assignmentId, new Date(this.now()).toISOString(), ticketId);
@@ -6193,7 +6190,13 @@ var WorkerStore = class {
6193
6190
  }
6194
6191
  };
6195
6192
  // src/executor/workspaces.ts
6196
- /** Worker workspaces: git worktrees on agent branches over one shared clone per repository, and scratch directories for fake runs. Clone and fetch get the injected access env (authentication) and errors mask its secrets. Publishing pushes an agent branch without force, adding co-author trailers only to commits not yet pushed. Access errors carry the HTTP status when one is known. A transient push failure is retried up to 3 times (2 s, 5 s). */
6193
+ /**
6194
+ * Worker workspaces: git worktrees on agent branches over one shared clone per repository,
6195
+ * and scratch directories for fake runs.
6196
+ * Clone and fetch get the injected access env (authentication), and errors mask its secrets.
6197
+ * Publishing pushes an agent branch without force, adding co-author trailers only to commits not yet pushed.
6198
+ * Access errors carry the HTTP status when one is known. A transient push failure is retried up to 3 times (2 s, 5 s).
6199
+ */
6197
6200
  var RepositoryAccessError = class extends Error {
6198
6201
  repository;
6199
6202
  reason;
@@ -6206,14 +6209,14 @@ var RepositoryAccessError = class extends Error {
6206
6209
  this.status = status;
6207
6210
  }
6208
6211
  };
6209
- /** The HTTP status of a failed hosting or git call: the API error code, or the status git or the API program printed. */
6212
+ /** The API error code, or the HTTP status printed by git or the API program. */
6210
6213
  function accessStatus(error) {
6211
6214
  if (error instanceof CliError && error.code !== void 0 && error.code >= 100 && error.code <= 599) return error.code;
6212
6215
  const found = /\b(?:HTTP|returned error:)\s*(\d{3})\b/i.exec(error instanceof Error ? error.message : String(error));
6213
6216
  return found ? Number(found[1]) : void 0;
6214
6217
  }
6215
6218
  const PUSH_BACKOFF_MS = [2e3, 5e3];
6216
- /** Anything that is not a non-fast-forward or an access failure is transient (ADR-25). */
6219
+ /** Anything that is not a non-fast-forward or an access failure is transient. */
6217
6220
  function pushFailure(message, status) {
6218
6221
  if (/non-fast-forward|fetch first|\[rejected\]|updates were rejected/i.test(message)) return { kind: "rejected" };
6219
6222
  if (status === 401 || status === 403 || status === 404) return {
@@ -6547,12 +6550,8 @@ var ScratchWorkspaces = class {
6547
6550
  }
6548
6551
  };
6549
6552
  // src/infra/health.ts
6550
- /** Minimum time between two health snapshot writes by a HealthRecorder. */
6551
6553
  const HEALTH_SNAPSHOT_MS = 5e3;
6552
- /**
6553
- * Central registry of component health entries, enforcing single-writer ownership per entry name.
6554
- * Claimed by the Runtime, Effects, WorkerExecutor, RouterHost and Sources, and read by the status CLI.
6555
- */
6554
+ /** Health entries, each with one writer: `claim` throws when the name is already held. */
6556
6555
  var HealthBoard = class {
6557
6556
  entries = {};
6558
6557
  claimed = /* @__PURE__ */ new Set();
@@ -6598,7 +6597,6 @@ var HealthRecorder = class {
6598
6597
  this.now = now;
6599
6598
  this.intervalMs = intervalMs;
6600
6599
  }
6601
- /** Returns true when a snapshot was written. */
6602
6600
  record(force = false) {
6603
6601
  const t = this.now();
6604
6602
  if (!force && this.last !== void 0 && t - this.last < this.intervalMs) return false;
@@ -6609,7 +6607,10 @@ var HealthRecorder = class {
6609
6607
  };
6610
6608
  // src/sources/admission-filter.ts
6611
6609
  /**
6612
- * AdmissionFilter: the check every Trigger passes before Passes.receive. It admits a Trigger whose actor is on the allowed list and whose repository is configured, or a Source fact on a Thread of an open ticket whose mandate actor is allowed; every other Trigger is recorded as rejected and never reaches the Engine. It reads only the actor, the human flag, `meta.repository` and the store, never the text, and takes no authentication mode.
6610
+ * AdmissionFilter: the check every Trigger passes before Passes.receive. It admits a Trigger whose actor is on the
6611
+ * allowed list and whose repository is configured, or a Source fact on a Thread of an open ticket whose mandate actor
6612
+ * is allowed. Other Triggers are recorded as rejected and never reach the Engine. It reads only the actor, the human
6613
+ * flag, `meta.repository` and the store, never the text.
6613
6614
  */
6614
6615
  var AdmissionFilter = class {
6615
6616
  deps;
@@ -6632,7 +6633,6 @@ var AdmissionFilter = class {
6632
6633
  if (this.deps.store.rejected.record(entry)) this.deps.rejected?.(entry);
6633
6634
  return "filtered";
6634
6635
  }
6635
- /** True when some ref of the Trigger's Threads is subscribed by an open ticket whose mandate actor is allowed. */
6636
6636
  followed(trigger, allowed) {
6637
6637
  const { store } = this.deps;
6638
6638
  return trigger.threads.some((ref) => {
@@ -6784,9 +6784,10 @@ var SourceRegistry = class {
6784
6784
  };
6785
6785
  // src/app/outbox.ts
6786
6786
  /**
6787
- * Persists outbound channel messages as receipts and delivers them sequentially through `Effects` and the `OutboxChannels` port.
6788
- * Implements the core `Outbox` port for `Engine` and source contexts, ensuring per-conversation ordering for each ticket until an earlier message fails repeatedly.
6789
- * Messages wait while processing is paused or their source is unavailable, and settle as dropped when the target conversation is ignored or gone.
6787
+ * Persists outbound channel messages as receipts and delivers them through `Effects` and `OutboxChannels`.
6788
+ * Each ticket keeps per-conversation ordering until an earlier message fails repeatedly.
6789
+ * Messages wait while processing is paused or their source is unavailable, and settle as dropped
6790
+ * when the target conversation is ignored or gone.
6790
6791
  */
6791
6792
  var Outbox = class {
6792
6793
  host;
@@ -6797,7 +6798,7 @@ var Outbox = class {
6797
6798
  retry: [5e3, 3e5]
6798
6799
  });
6799
6800
  }
6800
- /** Record a message receipt and enqueue its delivery effect atomically; duplicate message IDs are ignored. */
6801
+ /** Records a message receipt and enqueues its delivery effect atomically; duplicate message IDs are ignored. */
6801
6802
  send(message) {
6802
6803
  const { store, effects } = this.host;
6803
6804
  store.atomic(() => {
@@ -6816,7 +6817,12 @@ var Outbox = class {
6816
6817
  sent() {
6817
6818
  return this.host.effects.idle("reply");
6818
6819
  }
6819
- /** Reply crash rule: the reply effect row is deleted only after the receipt settles, so a process stopped after its Source accepted the message but before `settle` runs this again on restart; each channel must find a message it already sent by its id and return `sent` without sending again (the channel's pre-send marker lookup on the hosting service looks for its id marker before every post). A stop before the send sends it once after restart. */
6820
+ /**
6821
+ * Crash rule: the reply effect row is deleted only after the receipt settles. A process stopped after the
6822
+ * Source accepted the message but before `settle` runs sends it again on restart, so each channel must find
6823
+ * a message it already sent by its id and return `sent` without sending again. A stop before the send
6824
+ * sends it once after restart.
6825
+ */
6820
6826
  async deliver(id) {
6821
6827
  const { store, channels } = this.host;
6822
6828
  const receipt = store.receipts.get(id);
@@ -6841,9 +6847,8 @@ const systemTimers = {
6841
6847
  }
6842
6848
  };
6843
6849
  /**
6844
- * Schedules and serializes orchestration passes that kick due `Effects` and advance `Engine` evaluations.
6845
- * `Runtime` manages its lifecycle, while sources and tools trigger immediate passes when new work arrives or state changes.
6846
- * Updates the runtime `HealthEntry` when a pass fails and clears the error once a subsequent pass succeeds.
6850
+ * Serializes passes; sources and tools request immediate passes. A failed pass sets the health entry to error,
6851
+ * and the next successful pass clears it.
6847
6852
  */
6848
6853
  var Passes = class {
6849
6854
  timer;
@@ -6918,7 +6923,10 @@ var Passes = class {
6918
6923
  this.host.afterPass?.();
6919
6924
  }
6920
6925
  };
6921
- /** True when the ticket ended at a parseable time at least `days` days before `now`. A ticket with no ended time, or one that does not parse, never expires. */
6926
+ /**
6927
+ * True when the ticket ended at a parseable time at least `days` days before `now`.
6928
+ * A ticket with no ended time, or one that does not parse, never expires.
6929
+ */
6922
6930
  function expired(ticket, days, now) {
6923
6931
  const at = ticket.ended ? Date.parse(ticket.ended.at) : NaN;
6924
6932
  return Number.isFinite(at) && now - at >= days * 864e5;
@@ -6931,7 +6939,10 @@ var Retention = class {
6931
6939
  this.now = host.now ?? (() => Date.now());
6932
6940
  host.effects.handle("cleanup", (payload) => this.cleanup(payload.ticketId));
6933
6941
  }
6934
- /** Queues a cleanup for each ended ticket that still has a worker record and has expired, unless one is already waiting. Returns how many were queued. */
6942
+ /**
6943
+ * Queues a cleanup for each expired ended ticket that still has a worker record, unless one is already waiting.
6944
+ * Returns how many were queued.
6945
+ */
6935
6946
  evaluate() {
6936
6947
  const rows = this.host.store.db.prepare("SELECT w.ticket_id AS id, t.value ->> '$.ended.at' AS ended FROM worker w JOIN ticket t ON t.id = w.ticket_id WHERE t.open IS NULL").all();
6937
6948
  let queued = 0;
@@ -6945,7 +6956,10 @@ var Retention = class {
6945
6956
  }
6946
6957
  return queued;
6947
6958
  }
6948
- /** Removes the worker record, then the workspace, of an ended expired ticket. A ticket that is open again, unknown or not expired is left alone. Safe to repeat. */
6959
+ /**
6960
+ * Removes the worker record, then the workspace, of an expired ended ticket.
6961
+ * A ticket that is open again, unknown, or not expired is left alone. Safe to repeat.
6962
+ */
6949
6963
  async cleanup(ticketId) {
6950
6964
  const ticket = this.host.store.tickets.get(ticketId);
6951
6965
  if (!ticket || !expired(ticket, this.host.days(), this.now())) return;
@@ -6955,10 +6969,7 @@ var Retention = class {
6955
6969
  }
6956
6970
  };
6957
6971
  // src/app/store-read.ts
6958
- /**
6959
- * Read-only Store queries for views, tools, and Source polling.
6960
- * Changes from the reference: `openReadOnly` (with `keys`) and `writerActive` are added for the CLI.
6961
- */
6972
+ /** `openReadOnly` and `writerActive` serve the CLI. */
6962
6973
  function storeReads(db) {
6963
6974
  const values = (sql, ...parameters) => db.prepare(sql).all(...parameters).map((row) => JSON.parse(String(row.value)));
6964
6975
  return {
@@ -7064,7 +7075,10 @@ function openReadOnly(path) {
7064
7075
  }
7065
7076
  };
7066
7077
  }
7067
- /** Whether a writer (the daemon) holds the single-writer lock on the database at path; it takes no lock. A lock older than the Store's stale time counts as not held, and so does a lock whose recorded owner process is gone. */
7078
+ /**
7079
+ * Whether a writer (the daemon) holds the single-writer lock on the database at `path`. Takes no lock itself.
7080
+ * A lock older than the Store's stale time counts as not held, and so does a lock whose recorded owner is gone.
7081
+ */
7068
7082
  function writerActive(path) {
7069
7083
  try {
7070
7084
  return checkSync(path, {
@@ -7078,12 +7092,9 @@ function writerActive(path) {
7078
7092
  // src/app/store.ts
7079
7093
  /**
7080
7094
  * SQLite persistence for domain aggregates, key-value state, and the channel outbox.
7081
- * Changes from the reference: settings methods and the upgrade path for older schemas are removed; the schema is fixed at version 1; the Store takes a single-writer lock on the database file; `databasePath` is added; a `worker` table for worker session records is added; a `prompt_version` table records assembled prompt versions; a lock whose recorded owner pid no longer runs is taken over at once.
7082
- */
7083
- /**
7084
- * Generated columns and partial indexes defining active working sets so passes scan only active rows.
7085
- * `schemaSql` declares them in the version 1 tables, and SQLite computes them from the stored JSON rows.
7095
+ * The Store holds a single-writer lock on the database file.
7086
7096
  */
7097
+ /** Generated columns and partial indexes for active working sets, computed by SQLite from the stored JSON rows. */
7087
7098
  const SETS = {
7088
7099
  ticket: {
7089
7100
  open: "INTEGER AS (CASE WHEN value -> '$.ended' IS NULL THEN 1 END)",
@@ -7100,11 +7111,8 @@ const SETS = {
7100
7111
  AND value ->> '$.ticketId' IS NOT NULL THEN (value ->> '$.ticketId') || ' ' || (value ->> '$.channel.thread') END)`
7101
7112
  }
7102
7113
  };
7103
- /** File name of the SQLite database inside the state directory. */
7104
7114
  const DATABASE_FILE = "superagent-claude.db";
7105
- /** Path of the SQLite database file inside `stateDir`. */
7106
7115
  const databasePath = (stateDir) => join(stateDir, DATABASE_FILE);
7107
- /** Table, column and index DDL for schema version 1, with the working-set columns and indexes from `SETS`. */
7108
7116
  function schemaSql() {
7109
7117
  const extra = (table) => Object.entries(SETS[table] ?? {}).map(([name, definition]) => `, ${name} ${definition}`).join("");
7110
7118
  const sets = Object.entries(SETS).flatMap(([table, definitions]) => Object.keys(definitions).map((name) => `CREATE INDEX IF NOT EXISTS ${table}_${name} ON ${table}(${name}) WHERE ${name} IS NOT NULL;`));
@@ -7130,7 +7138,6 @@ function schemaSql() {
7130
7138
  ${sets.join("\n ")}
7131
7139
  `;
7132
7140
  }
7133
- /** Maps a `rejected_trigger` row to its RejectedTrigger. */
7134
7141
  function toRejected(row) {
7135
7142
  return {
7136
7143
  triggerId: String(row.trigger_id),
@@ -7142,7 +7149,10 @@ function toRejected(row) {
7142
7149
  }
7143
7150
  /** The file next to the database that names the pid holding its single-writer lock. */
7144
7151
  const ownerFile = (path) => `${path}.owner`;
7145
- /** The lock owner recorded on `path` (undefined when the file is missing or names no positive pid); a legacy pid-only file has no host. */
7152
+ /**
7153
+ * The recorded lock owner, or undefined when the file is missing or names no positive pid.
7154
+ * An owner without a host is checked as a local process.
7155
+ */
7146
7156
  function lockOwner(path) {
7147
7157
  let text;
7148
7158
  try {
@@ -7159,7 +7169,8 @@ function lockOwner(path) {
7159
7169
  } : { pid };
7160
7170
  }
7161
7171
  /**
7162
- * True when the lock on `path` names an owner pid that no longer runs on this host (it was killed and left its lock behind).
7172
+ * True when the lock on `path` names an owner pid that no longer runs on this host,
7173
+ * i.e. the owner was killed and left its lock behind.
7163
7174
  * A lock recorded on another host is never taken over at once.
7164
7175
  */
7165
7176
  function lockOwnerGone(path) {
@@ -7173,10 +7184,7 @@ function lockOwnerGone(path) {
7173
7184
  return error.code === "ESRCH";
7174
7185
  }
7175
7186
  }
7176
- /**
7177
- * Takes the single-writer lock on `path`; a lock held by another process is reported as a clear error. Returns the release function.
7178
- * A lock left by a killed process is taken over at once when its recorded owner pid no longer runs; otherwise the 30 s stale rule applies.
7179
- */
7187
+ /** A lock whose recorded owner pid no longer runs is taken over at once; otherwise the 30 s stale rule applies. */
7180
7188
  function lockDatabase(path, report) {
7181
7189
  const take = () => lockSync(path, {
7182
7190
  realpath: false,
@@ -7215,18 +7223,20 @@ function lockDatabase(path, report) {
7215
7223
  };
7216
7224
  }
7217
7225
  /**
7218
- * SQLite backing store implementing domain `Repositories`, read queries, outbox receipts and key-value state.
7219
- * Opening it takes a single-writer lock on the database file. The schema starts at version 1, and a database stored at any other version is refused.
7226
+ * Opening takes a single-writer lock on the database file.
7227
+ * A database stored at any version other than `SCHEMA_VERSION` is refused.
7220
7228
  */
7221
7229
  var Store = class {
7222
7230
  db;
7223
7231
  /** Receives errors thrown by `afterCommit` callbacks and lock release without failing the committed transaction. */
7224
7232
  report;
7225
- /** Releases the single-writer lock on the database file. */
7226
7233
  unlock;
7227
7234
  /**
7228
- * Opens or creates the SQLite database at `path` under the single-writer lock. Waits up to 5 s for a lock another connection holds (such as a reader during crash recovery) instead of failing at once.
7229
- * @throws When another process holds the lock, when the stored schema is not `SCHEMA_VERSION`, or when the database cannot be opened; in every case the lock is released and the SQLite handle is closed so a later attempt can open it.
7235
+ * Opens or creates the SQLite database at `path` under the single-writer lock.
7236
+ * Waits up to 5 s for a lock another connection holds (such as a reader during crash recovery).
7237
+ * @throws When another process holds the lock, when the stored schema is not `SCHEMA_VERSION`,
7238
+ * or when the database cannot be opened. In every case the lock is released and the SQLite handle
7239
+ * is closed so a later attempt can open it.
7230
7240
  */
7231
7241
  constructor(path, report = () => {}) {
7232
7242
  this.report = report;
@@ -7257,12 +7267,10 @@ var Store = class {
7257
7267
  throw error;
7258
7268
  }
7259
7269
  }
7260
- /** Closes the underlying SQLite database connection and releases the single-writer lock. */
7261
7270
  close() {
7262
7271
  this.db.close();
7263
7272
  this.unlock();
7264
7273
  }
7265
- /** Parses JSON `value` columns returned by `sql`. */
7266
7274
  values(sql, ...parameters) {
7267
7275
  return this.db.prepare(sql).all(...parameters).map((row) => JSON.parse(String(row.value)));
7268
7276
  }
@@ -7273,11 +7281,13 @@ var Store = class {
7273
7281
  for (const ref of trigger.threads) insert.run(threadKey(ref), trigger.id, trigger.at, trigger.human ? 1 : 0);
7274
7282
  }
7275
7283
  depth = 0;
7276
- /** Callbacks queued to run once the outermost transaction commits, indexed by savepoint level; rolling back a level discards its callbacks. */
7284
+ /** Post-commit callbacks, indexed by savepoint level; rolling back a level discards its callbacks. */
7277
7285
  committed = [];
7278
7286
  /**
7279
- * Executes `operation` in a savepoint transaction (nested inside the current transaction if any); a failure rolls back its changes.
7280
- * Post-commit callbacks run only after the outermost transaction commits, and callback errors are reported without failing the committed transaction.
7287
+ * Executes `operation` in a savepoint transaction (nested inside the current one if any);
7288
+ * a failure rolls back its changes.
7289
+ * Post-commit callbacks run only after the outermost transaction commits. Callback errors are reported
7290
+ * without failing the committed transaction.
7281
7291
  */
7282
7292
  atomic(operation) {
7283
7293
  const level = this.depth++;
@@ -7305,24 +7315,22 @@ var Store = class {
7305
7315
  return value;
7306
7316
  }
7307
7317
  /**
7308
- * Schedules `action` to run once the outermost transaction commits, or immediately if outside a transaction;
7309
- * callbacks queued inside a transaction that rolls back are discarded and never run.
7310
- * Use `afterCommit` for immediate in-memory follow-ups to a committed write (such as refreshing health or cancelling an in-memory turn) that cannot wait for the effect queue and need not survive a restart; use `Effects` for durable side effects that must survive a crash.
7318
+ * Schedules `action` to run once the outermost transaction commits, or immediately if outside a transaction.
7319
+ * Callbacks queued inside a transaction that rolls back are discarded and never run.
7320
+ * Use `afterCommit` only for in-memory follow-ups that need not survive a restart;
7321
+ * use `Effects` for durable side effects.
7311
7322
  */
7312
7323
  afterCommit(action) {
7313
7324
  if (this.depth) this.committed[this.depth - 1].push(action);
7314
7325
  else action();
7315
7326
  }
7316
- /** Reads a typed KV record by its key in `KV`. */
7317
7327
  get(name) {
7318
7328
  const row = this.db.prepare("SELECT value FROM kv WHERE key=?").get(name);
7319
7329
  return row === void 0 ? void 0 : JSON.parse(String(row.value));
7320
7330
  }
7321
- /** Writes a typed KV record under `name`. */
7322
7331
  set(name, value) {
7323
7332
  this.db.prepare("INSERT INTO kv VALUES (?,?) ON CONFLICT(key) DO UPDATE SET value=excluded.value").run(name, JSON.stringify(value));
7324
7333
  }
7325
- /** Deletes the KV record at `name`. */
7326
7334
  delete(name) {
7327
7335
  this.db.prepare("DELETE FROM kv WHERE key=?").run(name);
7328
7336
  }
@@ -7357,7 +7365,10 @@ var Store = class {
7357
7365
  },
7358
7366
  list: () => this.db.prepare("SELECT value FROM thread ORDER BY rowid").all().map((row) => JSON.parse(String(row.value))),
7359
7367
  active: () => this.values("SELECT value FROM thread WHERE active = 1 ORDER BY rowid"),
7360
- /** Updates human-facing thread metadata (`label`, `url`, and `facts`, where `null` removes a fact by ID); no-op if the thread does not exist. */
7368
+ /**
7369
+ * Updates human-facing thread metadata. `null` in `facts` removes that fact by ID.
7370
+ * No-op if the thread does not exist.
7371
+ */
7361
7372
  describe: (keyValue, patch) => {
7362
7373
  const thread = this.threads.get(keyValue);
7363
7374
  if (!thread) return;
@@ -7375,7 +7386,10 @@ var Store = class {
7375
7386
  meta
7376
7387
  });
7377
7388
  },
7378
- /** Atomically sets or removes (`undefined`) source-specific metadata keys on a thread after re-reading it; no-op if the thread does not exist. */
7389
+ /**
7390
+ * Atomically sets or removes (`undefined`) source-specific metadata keys after re-reading the thread.
7391
+ * No-op if the thread does not exist.
7392
+ */
7379
7393
  patchMeta: (keyValue, patch) => this.atomic(() => {
7380
7394
  const thread = this.threads.get(keyValue);
7381
7395
  if (!thread) return;
@@ -7434,7 +7448,10 @@ var Store = class {
7434
7448
  get: (id) => this.values("SELECT value FROM outbox WHERE id = ?", id)[0],
7435
7449
  /** Pending message receipts, oldest first. */
7436
7450
  waiting: () => this.values("SELECT value FROM outbox WHERE value ->> '$.status' = 'pending' ORDER BY rowid"),
7437
- /** True if an earlier message for the same ticket and conversation is still pending with fewer than `failures` consecutive failed `reply` effect attempts. */
7451
+ /**
7452
+ * True if an earlier message for the same ticket and conversation is still pending,
7453
+ * with fewer than `failures` failed `reply` effect attempts.
7454
+ */
7438
7455
  behind: (id, failures) => !!this.db.prepare(`SELECT 1 FROM outbox earlier
7439
7456
  JOIN outbox message ON message.id = ? LEFT JOIN effect ON effect.kind = 'reply' AND effect.key = earlier.id
7440
7457
  WHERE earlier.pending = message.pending AND earlier.rowid < message.rowid AND IFNULL(effect.attempts, 0) < ? LIMIT 1`).get(id, failures),
@@ -7450,8 +7467,8 @@ var Store = class {
7450
7467
  return Number(this.db.prepare("INSERT OR IGNORE INTO outbox (id, value) VALUES (?, ?)").run(message.id, JSON.stringify(receipt)).changes) > 0;
7451
7468
  },
7452
7469
  /**
7453
- * Marks a pending receipt as sent or dropped (with drop reason and timestamp), recreating the receipt if the message
7454
- * was withdrawn while being sent so its ID cannot be queued again.
7470
+ * Marks a pending receipt as sent or dropped (with drop reason and timestamp).
7471
+ * Recreates the receipt if the message was withdrawn while being sent, so its ID cannot be queued again.
7455
7472
  */
7456
7473
  settle: (message, outcome) => {
7457
7474
  const settled = outcome === "sent" ? { status: "sent" } : {
@@ -7480,10 +7497,6 @@ var Store = class {
7480
7497
  };
7481
7498
  };
7482
7499
  // src/infra/paths.ts
7483
- /**
7484
- * Resolves packaged asset paths from either the source tree or the compiled lib/ output.
7485
- * Changes from the reference: the package root is found by the name `superagent-claude`.
7486
- */
7487
7500
  const PACKAGE_NAME = "superagent-claude";
7488
7501
  let cached;
7489
7502
  function packageRoot() {
@@ -7499,7 +7512,11 @@ function packageRoot() {
7499
7512
  throw new Error(`${PACKAGE_NAME} package root not found`);
7500
7513
  }
7501
7514
  // src/app/prompts.ts
7502
- /** Role prompts assembled from host rules, packaged built-in parts, policy rules, the team's three sections and wrapped external text, with immutable versions and a report of built-in default changes. Changes from the reference: parts are ordered host > built-in > rule > team > external data; the team sections are files under <configDir>/prompts/team; overrides of other built-in parts, team skills, the environment and policy-template parts, revisions and the save/preview API are removed; versions are content hashes in the prompt_version table. */
7515
+ /**
7516
+ * Role prompts assembled from host rules, packaged built-in parts, policy rules, the team's three sections,
7517
+ * and wrapped external text. Parts are ordered host > built-in > rule > team > external data.
7518
+ * Versions are immutable content hashes stored in the prompt_version table.
7519
+ */
7503
7520
  const TEAM_SECTIONS = [
7504
7521
  "communication",
7505
7522
  "scope",
@@ -7845,7 +7862,11 @@ function noticeText(previous, next) {
7845
7862
  if (!texts.length) return void 0;
7846
7863
  return NOTICE_HEADER + texts.join("\n\n");
7847
7864
  }
7848
- /** Classifies the change from a stored prompt version to the current assembly: fixed when the role or a host, built-in or skill part changed (a new session is needed), team when only rule or team sections changed (a notice is enough), same otherwise (ADR-18). */
7865
+ /**
7866
+ * Classifies the change from a stored prompt version to the current assembly: `fixed` when the role or a host,
7867
+ * built-in or skill part changed (a new session is needed); `team` when only rule or team sections changed
7868
+ * (a notice is enough); `same` otherwise.
7869
+ */
7849
7870
  function promptChange(stored, current) {
7850
7871
  if (!stored || stored.role !== current.role) return { kind: "fixed" };
7851
7872
  const now = [...current.parts.filter((p) => p.layer === "host" || p.layer === "builtin").map((p) => [p.key, partHash(p.text)]), ["skill/github", current.skillHash]];
@@ -7915,11 +7936,8 @@ var Prompts = class {
7915
7936
  };
7916
7937
  // src/app/cards.ts
7917
7938
  /**
7918
- * Compact, bounded model-facing projections of triggers, tickets, threads, and decisions; full texts stay behind readable resources.
7919
- * Changes from the reference: `external()` wraps every trigger text a card carries as untrusted data; `pr_number` and the PR
7920
- * link are removed (M16 publishes pull requests); the input and batch resource links are removed, so `inputLine`
7921
- * names a trigger, a decision (for the Router only) or a short text, and `ticketDetails` drops the batch, transcript,
7922
- * assignment and result links, carrying the latest report's summary instead; `threadCard` labels a thread by its metadata.
7939
+ * Full texts stay behind readable resources. Every trigger text a card carries is wrapped by `external()`
7940
+ * as untrusted data.
7923
7941
  */
7924
7942
  /** Wraps untrusted text from a source as external data, so the model reads it as data rather than as instructions. */
7925
7943
  function external(source, text) {
@@ -7928,7 +7946,10 @@ function external(source, text) {
7928
7946
  text
7929
7947
  });
7930
7948
  }
7931
- /** Collapses whitespace and truncates text for display previews while keeping code, angle brackets, and links intact for model readers. */
7949
+ /**
7950
+ * Collapses whitespace and truncates text for display previews, keeping code, angle brackets, and links intact
7951
+ * for model readers.
7952
+ */
7932
7953
  function preview(text) {
7933
7954
  return text.replace(/\s+/g, " ").slice(0, 160);
7934
7955
  }
@@ -7979,7 +8000,7 @@ function ticketCard(ticket) {
7979
8000
  };
7980
8001
  }
7981
8002
  const INPUT_TEXT = 80;
7982
- /** Summarizes a ticket input inline: a trigger by its ID, a decision for the Router only, and a short text as written; longer texts by preview. */
8003
+ /** A decision shows for the Router only; a trigger shows by its ID. */
7983
8004
  function inputLine(ticket, input, worker = false) {
7984
8005
  const names = input.kind === "trigger" || input.kind === "resumed" ? { trigger_id: input.triggerId } : input.kind === "reopen" && !worker ? { decision_id: input.decisionId } : input.kind !== "text" ? {} : input.text.length <= INPUT_TEXT ? { text: input.text } : {
7985
8006
  preview: preview(input.text).slice(0, INPUT_TEXT),
@@ -8017,7 +8038,7 @@ function newestPage(items, offset, size, line, next) {
8017
8038
  } : {}
8018
8039
  };
8019
8040
  }
8020
- /** Projects a ticket with its threads and first pages of assignments and inputs, omitting decision IDs for workers. */
8041
+ /** Workers get no decision IDs. */
8021
8042
  function ticketDetails(ticket, threads, worker = false) {
8022
8043
  const { preview: _preview, ...card } = ticketCard(ticket);
8023
8044
  return {
@@ -8081,11 +8102,11 @@ function relatedTickets(tickets) {
8081
8102
  }
8082
8103
  // src/app/resources.ts
8083
8104
  /**
8084
- * Paginated resources that orchestrator_read serves to the Router session and to a ticket's worker, so large bodies stay out of
8085
- * model prompts until they are requested.
8086
- * Changes from the reference: the source-specific, settings, prompt, model, decision and transcript routes are removed; the
8087
- * Router session has `tickets` and the worker has its own ticket only; `github/…` reads go through the configured GitHub reader
8088
- * with the repository checked against the configured list; text bodies of triggers and GitHub originals are wrapped as external data.
8105
+ * Paginated resources that orchestrator_read serves to the Router session and to a ticket's worker,
8106
+ * so large bodies stay out of model prompts until they are requested.
8107
+ * The Router session sees `tickets`; the worker sees its own ticket only. `github/…` reads go through the
8108
+ * configured GitHub reader, with the repository checked against the configured list.
8109
+ * Text bodies of triggers and GitHub originals are wrapped as external data.
8089
8110
  */
8090
8111
  /** Resource descriptors exposed via orchestrator_read; the worker reads only its own ticket and what reached it. */
8091
8112
  const RESOURCES = [
@@ -8130,7 +8151,7 @@ function list(offset, limit, fetch) {
8130
8151
  next_offset: items.length > size ? offset + size : null
8131
8152
  };
8132
8153
  }
8133
- /** Whether a worker's ticket may read a trigger: it reached the ticket, started it, or is on a thread the ticket follows. */
8154
+ /** Whether a worker's ticket may read a trigger: it reached the ticket, started it, or is on a followed thread. */
8134
8155
  const readable = (host, ticket, found) => reached(ticket, found, (key) => host.store.threads.get(key)) || ticket.origin.triggerIds.includes(found.id) || found.threads.some((ref) => ticket.follows?.some((follow) => follow.thread === threadKey(ref)));
8135
8156
  function trigger$1(host, id, ticketId) {
8136
8157
  const found = host.store.triggers.get(id);
@@ -8148,7 +8169,10 @@ const triggerLine = (item) => ({
8148
8169
  chars: item.text.length,
8149
8170
  read: `trigger/${item.id}`
8150
8171
  });
8151
- /** The threads a ticket is tied to: those it is subscribed to (context-only ones marked) and those its own source bound it to. */
8172
+ /**
8173
+ * The threads a ticket is tied to: those it is subscribed to (context-only ones marked),
8174
+ * and those its own source bound it to.
8175
+ */
8152
8176
  function ticketThreads(host, ticket) {
8153
8177
  const bound = new Set((ticket.follows ?? []).map((follow) => follow.thread));
8154
8178
  const subscribed = host.store.threads.list().filter((item) => item.subscriptions.includes(ticket.id));
@@ -8159,7 +8183,10 @@ function ticketThreads(host, ticket) {
8159
8183
  });
8160
8184
  return [...subscribed.map((item) => marked(item.key, !!item.context?.includes(ticket.id))), ...[...bound].filter((thread) => !subscribed.some((item) => item.key === thread)).map((thread) => marked(thread, false))];
8161
8185
  }
8162
- /** Resolves a GitHub resource's repository: explicit in the name, or implicit from the ticket (worker) or the one configured repository (Router). */
8186
+ /**
8187
+ * Resolves a GitHub resource's repository: explicit in the name, implicit from the ticket (worker),
8188
+ * or the one configured repository (Router).
8189
+ */
8163
8190
  function githubRepository(host, resource, ticketId) {
8164
8191
  if (/^github\/(issues|pulls|commits)\//.test(resource)) {
8165
8192
  const ticket = ticketId ? host.store.tickets.get(ticketId) : void 0;
@@ -8178,8 +8205,10 @@ function githubRepository(host, resource, ticketId) {
8178
8205
  };
8179
8206
  }
8180
8207
  /**
8181
- * Read a paginated resource for the Router session (`ticketId` undefined) or a ticket's worker. Unknown names are tool errors.
8182
- * @throws {Error} for a resource the caller may not read or that does not exist; `UserError` for a missing or unreached trigger or ticket.
8208
+ * Reads a paginated resource for the Router session (`ticketId` undefined) or a ticket's worker.
8209
+ * Unknown names are tool errors.
8210
+ * @throws {Error} for a resource the caller may not read or that does not exist;
8211
+ * `UserError` for a missing or unreached trigger or ticket.
8183
8212
  */
8184
8213
  async function readResource(host, resource, offset, limit, ticketId, signal) {
8185
8214
  const worker = ticketId !== void 0;
@@ -8266,7 +8295,6 @@ async function readResource(host, resource, offset, limit, ticketId, signal) {
8266
8295
  if (resource === "skill/github") return text(resource, host.skill(), offset, limit);
8267
8296
  throw new Error(`Unknown resource; use one of: ${resourceGrammar(worker)}`);
8268
8297
  }
8269
- /** Formats a trigger for a worker or the Router: author, thread, timestamp, text cut at TRIGGER_CHARS, and the message it replies to. */
8270
8298
  function triggerMessage(trigger, note, repliesTo) {
8271
8299
  const cut = trigger.text.length > 600 ? `${trigger.text.slice(0, 600)}…` : void 0;
8272
8300
  return {
@@ -8292,14 +8320,13 @@ function repliedTo(host, trigger) {
8292
8320
  const parent = host.sources.repliesTo(trigger);
8293
8321
  return parent === void 0 ? void 0 : host.store.triggers.get(parent);
8294
8322
  }
8295
- /** Finds tickets subscribed to threads referenced in `trigger.text` (such as `#12` or URLs) that are not already in `known`. */
8323
+ /** Finds tickets subscribed to threads that `trigger.text` references (such as `#12` or URLs), excluding `known`. */
8296
8324
  function referencedTickets(host, trigger, known) {
8297
8325
  const threads = (trigger.text.match(/#\d+|https?:\/\/[^\s)>\]]+/g) ?? []).flatMap((reference) => host.sources.find(reference).threads);
8298
8326
  const ids = new Set(threads.flatMap((thread) => host.store.threads.get(thread)?.subscriptions ?? []));
8299
8327
  for (const ticket of known) ids.delete(ticket.id);
8300
8328
  return [...ids].flatMap((id) => host.store.tickets.get(id) ?? []).slice(0, 4);
8301
8329
  }
8302
- /** Builds the Router's candidate card with routing metadata, handback history, and related or referenced tickets. */
8303
8330
  function candidateCard(host, candidate) {
8304
8331
  const primary = candidate.triggers.at(-1);
8305
8332
  const delivered = host.store.triggers.disposition(primary.id)?.fanout ?? [];
@@ -8323,25 +8350,21 @@ function candidateCard(host, candidate) {
8323
8350
  };
8324
8351
  }
8325
8352
  // src/app/notices.ts
8326
- /**
8327
- * Formats background notices and steering relays delivered to the Router session through the effect queue.
8328
- * Changes from the reference: the prompt-configuration and settings notices, and `changedPaths`, are removed (ADR-9); the
8329
- * thread-followed notice is added (R18); a follow-up relay reads the trigger through `triggerMessage`.
8330
- */
8331
8353
  /** Strips the latest report preview from a ticket card when the relay already carries or does not need the summary. */
8332
8354
  function brief(ticket) {
8333
8355
  const { preview: _preview, ...card } = ticketCard(ticket);
8334
8356
  return card;
8335
8357
  }
8336
- /** Creates a one-off receipt notice with a unique `name` key so repeated occurrences of the same event are each delivered instead of coalescing. */
8358
+ /** Creates a one-off receipt notice with a unique `name` key, so each occurrence is delivered instead of coalescing. */
8337
8359
  const receiptNotice = (value, summary) => [
8338
8360
  key("receipt", randomUUID()),
8339
8361
  value,
8340
8362
  summary
8341
8363
  ];
8342
8364
  /**
8343
- * Builds a notice that a person removed a thread's subscriptions at `at`; a later removal of the same thread replaces an unsent
8344
- * waiting notice. The thread key is `<source>:<id>`, such as `github:<owner>/<name>#<n>`.
8365
+ * Builds a notice that a person removed a thread's subscriptions at `at`.
8366
+ * A later removal of the same thread replaces an unsent waiting notice.
8367
+ * The thread key is `<source>:<id>`, such as `github:<owner>/<name>#<n>`.
8345
8368
  */
8346
8369
  const subscriptionNotice = (thread, at) => [
8347
8370
  `subscription:${thread}`,
@@ -8355,8 +8378,8 @@ const subscriptionNotice = (thread, at) => [
8355
8378
  `Removed the subscriptions on thread ${thread}`
8356
8379
  ];
8357
8380
  /**
8358
- * Converts an unsolicited domain event into a Router notice (`ticket.ended`, `trigger.requeued`, or `thread.unsubscribed`
8359
- * by a person or executor); returns `undefined` for events that require no notice.
8381
+ * Converts an unsolicited domain event into a Router notice (`ticket.ended`, `trigger.requeued`, or
8382
+ * `thread.unsubscribed` by a person or executor). Returns `undefined` for events that need no notice.
8360
8383
  */
8361
8384
  function eventNotice(event, ticket = () => void 0) {
8362
8385
  const { at } = event;
@@ -8402,12 +8425,16 @@ const threadFollowedNotice = (ticketId, thread, reason) => receiptNotice({
8402
8425
  },
8403
8426
  note: "For context only: its messages reach this ticket and grant nothing."
8404
8427
  }, `Ticket ${ticketId} follows thread ${thread} for context: ${reason}`);
8405
- /** What a follow-up asks of the Router session: nothing but a stop the person asks for, since its ticket's worker decides it. */
8428
+ /**
8429
+ * What a follow-up asks of the Router session: only a stop that the person asks for,
8430
+ * since its ticket's worker decides it.
8431
+ */
8406
8432
  const FOLLOWUP_NOTE = "Its ticket's worker has it and decides; act only if the person asks to stop the work (orchestrator_route kind stop with this trigger_id).";
8407
8433
  /**
8408
- * Builds a steering relay for a human follow-up trigger that reached at least one live ticket (including an ended ticket
8409
- * that resumed on the follow-up), with the trigger rendered as workers read it, the replied-to message, and the open
8410
- * tickets it reached; returns `undefined` while no ticket's worker decides it (see `followupOwners`) or it reached no live ticket.
8434
+ * Builds a steering relay for a human follow-up trigger that reached at least one live ticket
8435
+ * (including an ended ticket that resumed on the follow-up). The trigger is rendered as workers read it,
8436
+ * with the replied-to message and the open tickets it reached.
8437
+ * Returns `undefined` while no ticket's worker decides it (see `followupOwners`) or it reached no live ticket.
8411
8438
  */
8412
8439
  function followupNotice(store, sources, triggerId) {
8413
8440
  const trigger = store.triggers.get(triggerId);
@@ -8430,8 +8457,9 @@ function followupNotice(store, sources, triggerId) {
8430
8457
  }, trigger)), tickets);
8431
8458
  }
8432
8459
  /**
8433
- * A person's message that reached tickets, as the Router session hears of it: the message as their workers read it, and the
8434
- * tickets it reached with their state. Their workers decide it; the Router session may only stop the work on it.
8460
+ * A person's message that reached tickets, as the Router session hears of it: the message as their workers
8461
+ * read it, and the tickets it reached with their state. Their workers decide it; the Router session may only
8462
+ * stop the work on it.
8435
8463
  */
8436
8464
  function followupRelay(message, tickets) {
8437
8465
  return {
@@ -8444,7 +8472,11 @@ function followupRelay(message, tickets) {
8444
8472
  })
8445
8473
  };
8446
8474
  }
8447
- /** Formats a worker report relay for the Router session (truncating summaries over 600 characters behind `ticket/<id>`); `replyTo` names the human question trigger it answers. */
8475
+ /**
8476
+ * Formats a worker report relay for the Router session.
8477
+ * Summaries over 600 characters are truncated behind `ticket/<id>`.
8478
+ * `replyTo` names the human question trigger it answers.
8479
+ */
8448
8480
  function resultRelay(ticket, state, text, replyTo) {
8449
8481
  const id = key("worker-result", ticket.id, ticket.updatedAt, text);
8450
8482
  const summary = text.length > 600 ? {
@@ -8466,9 +8498,9 @@ function resultRelay(ticket, state, text, replyTo) {
8466
8498
  };
8467
8499
  }
8468
8500
  /**
8469
- * Converts a worker report recorded on `ticket` into a Router relay: a question for a person produces a `worker_attention`
8470
- * relay, and a result, blocker, end, or answer to a question produces a `worker_report` relay, while routine `progress`
8471
- * reports without `answers` stay internal.
8501
+ * Converts a worker report recorded on `ticket` into a Router relay. A question for a person produces a
8502
+ * `worker_attention` relay; a result, blocker, end, or answer to a question produces a `worker_report` relay.
8503
+ * Routine `progress` reports without `answers` stay internal.
8472
8504
  * @returns The steering `Relay`, or `undefined` when the report is not relayed to the Router session.
8473
8505
  */
8474
8506
  function reportRelay(ticket, report) {
@@ -8507,7 +8539,7 @@ const HumanActionSchema = z.string().trim().min(1).max(1e3).superRefine((text, c
8507
8539
  message: `human_action must be one concrete question for a person: ${problem}`
8508
8540
  });
8509
8541
  });
8510
- /** The `orchestrator_read` tool: one page of a resource the grammar lists, read through `read`. */
8542
+ /** The `orchestrator_read` tool: one page of a resource that the grammar lists, read through `read`. */
8511
8543
  function readTool(description, grammar, read) {
8512
8544
  return tool$1({
8513
8545
  name: "orchestrator_read",
@@ -8521,8 +8553,12 @@ function readTool(description, grammar, read) {
8521
8553
  });
8522
8554
  }
8523
8555
  // src/app/tools/delivery.ts
8524
- /** The worker's host-side delivery tools: publish (push + PR, binds its Thread; a new PR's body closes the issue the ticket started from), review-thread reply, auto-merge, close. Policy is checked before any git or GitHub call: live ticket, configured repository, a PR this ticket bound, delivery.allowAutoMerge. Publish first re-checks push permission and returns published: false without any write when it is missing; repository access errors file a failed report. */
8525
- /** The issue a Trigger is on: its meta names a repository and a number and isPr is false (an issue or an issue comment). */
8556
+ /**
8557
+ * Policy is checked before any git or GitHub call: live ticket, configured repository, a PR this ticket bound,
8558
+ * and delivery.allowAutoMerge. Publish re-checks push permission first and returns `published: false`
8559
+ * without any write when it is missing. Repository access errors file a failed report.
8560
+ */
8561
+ /** The issue a Trigger is on: its meta names a repository and a number, and `isPr` is false. */
8526
8562
  function issueOf(trigger) {
8527
8563
  const meta = trigger?.meta;
8528
8564
  return meta && typeof meta.repository === "string" && typeof meta.number === "number" && Number.isInteger(meta.number) && meta.isPr === false ? {
@@ -8531,8 +8567,8 @@ function issueOf(trigger) {
8531
8567
  } : void 0;
8532
8568
  }
8533
8569
  /**
8534
- * A pull request body that closes `issue` on merge: unchanged when the issue is in another repository or the body already
8535
- * has a closing keyword (close/closes/closed, fix/fixes/fixed, resolve/resolves/resolved, any case, optional colon) before
8570
+ * A pull request body that closes `issue` on merge. Unchanged when the issue is in another repository, or when the
8571
+ * body already has a closing keyword (close/fix/resolve and their forms, any case, optional colon) before
8536
8572
  * `#n`, `owner/repo#n` or the issue URL. Otherwise `Closes #n` is appended once, after a blank line.
8537
8573
  */
8538
8574
  function linkIssue(body, repository, issue) {
@@ -8717,10 +8753,8 @@ function deliveryTools(host, ticketId) {
8717
8753
  }
8718
8754
  // src/app/tools/executor.ts
8719
8755
  /**
8720
- * Worker Session tools for acknowledging requests, managing threads, handing back out-of-scope triggers, and reporting status.
8721
- * Changes from the reference: `orchestrator_read` is restored (M14) over the injected reader; the delivery tools are restored (M16) in
8722
- * `delivery.ts`; `pr_number` is removed (publish binds the PR thread) and `sendReplies` is removed; a GitHub thread must name a configured repository; a followed
8723
- * thread is announced to the Router session through `notify` (M14); the report relay is the Runtime's (M14).
8756
+ * A GitHub thread must name a configured repository.
8757
+ * A followed thread is announced to the Router session through `notify`.
8724
8758
  */
8725
8759
  function executorTools(host, ticket) {
8726
8760
  const ticketId = ticket.id;
@@ -8999,7 +9033,6 @@ var RouterHost = class {
8999
9033
  }
9000
9034
  };
9001
9035
  // src/sources/admission-context.ts
9002
- /** Selects the first five thread messages plus five messages on either side of `sourceId`, recording omitted gap counts. */
9003
9036
  function threadWindow(messages, sourceId) {
9004
9037
  const trigger = messages.findIndex((message) => message.id === sourceId);
9005
9038
  const selected = messages.map((message, index) => ({
@@ -9023,8 +9056,8 @@ function threadWindow(messages, sourceId) {
9023
9056
  };
9024
9057
  }
9025
9058
  /**
9026
- * Reads up to `maxPages` of a trigger's thread and builds the bounded message window around the trigger; counts describe
9027
- * only this loaded snapshot (never unread history), and `status` reports fallback modes such as `chat_neighbours`.
9059
+ * Counts describe only the loaded snapshot, never unread history;
9060
+ * `status` reports fallback modes such as `chat_neighbours`.
9028
9061
  */
9029
9062
  async function admissionContext(context, maxPages) {
9030
9063
  const messages = [];
@@ -9055,13 +9088,12 @@ async function admissionContext(context, maxPages) {
9055
9088
  }
9056
9089
  // src/app/assessment.ts
9057
9090
  /**
9058
- * Proactive candidate assessment for the Router session: relays candidate cards, serves bounded context reads, and records outcomes.
9059
- * Changes from the reference: the Router session is the only assessor (the deterministic one stays in the Runtime); the
9060
- * repository, image and file reads are removed (D4; GitHub originals are read through orchestrator_read); the thread window is
9061
- * loaded through the kind's TriggerContext with no command runner; every text a read or the context returns is wrapped as
9062
- * external data; the checked-IM-text step is removed.
9091
+ * Proactive candidate assessment for the Router session: relays candidate cards, serves bounded context reads, and
9092
+ * records outcomes. The Router session is the only assessor; the deterministic one stays in the Runtime. The
9093
+ * repository, image and file reads are not offered; GitHub originals are read through orchestrator_read. The thread
9094
+ * window is loaded through the kind's TriggerContext with no command runner. Every text a read or the context returns
9095
+ * is wrapped as external data. The checked-IM-text step is not performed.
9063
9096
  */
9064
- /** Schema for a short lowercase kebab-case English ticket worktree name (1–4 words, at most 32 characters). */
9065
9097
  const WorktreeNameSchema = z.string().max(32).regex(/^[a-z]+(?:-[a-z0-9]+){0,3}$/, "Use 1–4 short English words in lowercase kebab-case, at most 32 characters").describe("Meaningful short English ticket name, e.g. fix-plan-color. 1–4 lowercase words in kebab-case, at most 32 characters.");
9066
9098
  /** The fields of the Router's assessment decision (`orchestrator_eval_decide`), before the cross-field rule. */
9067
9099
  const AssessmentFields = z.object({
@@ -9079,23 +9111,18 @@ const AssessmentFields = z.object({
9079
9111
  acknowledgement: AcknowledgementSchema.nullish().describe("For acceptance: one sentence in the source language, posted as a reply to the trigger; omit only when a matched rule asks for silence."),
9080
9112
  reason: z.string().min(1).max(500)
9081
9113
  }).strict();
9082
- /** Schema for the Router session's proactive candidate assessment decision (`orchestrator_eval_decide`). */
9083
9114
  const AssessmentSchema = AssessmentFields.refine((value) => value.decision !== "accept" || !directionAllowed(value.direction) || !value.rule_assessments.some((rule) => rule.matched) || !!value.worktree_name, {
9084
9115
  message: "Accepted work requires a short English worktree_name",
9085
9116
  path: ["worktree_name"]
9086
9117
  });
9087
- /** Character limits in the initial thread window: for the trigger itself, its two nearest neighbours, other window messages, and stub previews. */
9088
9118
  const TRIGGER_CHARS = 4e3;
9089
9119
  const NEAR_CHARS = 1200;
9090
9120
  const WINDOW_CHARS = 400;
9091
9121
  const STUB_CHARS = 50;
9092
- /** Pages of the thread loaded for the initial window. */
9093
9122
  const CONTEXT_PAGES = 5;
9094
9123
  const FINISHED = "This assessment has finished: it was decided, timed out or restarted";
9095
9124
  const EXPLICIT = "This is an explicit request, which is routed instead of assessed: use orchestrator_route with its trigger_id and kind accept, attach, stop or ignore";
9096
- /** Message returned when assessing a Trigger whose Source provides no thread context. */
9097
9125
  const NO_CONTEXT = "This Source gives no Thread context; assess the Trigger's own text and the batch.";
9098
- /** Fields of `orchestrator_eval_read`: a message, the root, the parent, or a page of the thread. */
9099
9126
  const EvalReadFields = z.object({
9100
9127
  trigger_id: z.string().min(1).describe("Original candidate ID returned by orchestrator_eval; every reference is scoped to this trigger."),
9101
9128
  resource: z.enum([
@@ -9108,7 +9135,6 @@ const EvalReadFields = z.object({
9108
9135
  cursor: z.string().optional().describe("Only for thread: omit on the first page, then the next_cursor of the window or the cursor of next_read. Never a thread or message ID."),
9109
9136
  offset: z.number().int().min(0).default(0).describe("Text pagination: copy next_offset; keep the other arguments unchanged.")
9110
9137
  }).strict();
9111
- /** Schema for bounded context requests during an active evaluation (`orchestrator_eval_read`). */
9112
9138
  const EvalReadSchema = EvalReadFields.superRefine((read, ctx) => {
9113
9139
  if (read.message_id !== void 0 && read.resource !== "message") ctx.addIssue({
9114
9140
  code: "custom",
@@ -9119,21 +9145,15 @@ const EvalReadSchema = EvalReadFields.superRefine((read, ctx) => {
9119
9145
  message: "cursor is only for thread. First page: {resource:\"thread\"}."
9120
9146
  });
9121
9147
  });
9122
- /** Cuts a text to `limit` characters and wraps it as external data from `source`; the cut length is reported with `chars`. */
9148
+ /** Wraps the text as external data; `chars` reports the full length when it is cut. */
9123
9149
  function cut$1(text, limit, source) {
9124
9150
  return text.length <= limit ? { text: external(source, text) } : {
9125
9151
  text: external(source, `${text.slice(0, limit)}…`),
9126
9152
  chars: text.length
9127
9153
  };
9128
9154
  }
9129
- /**
9130
- * Implements the `Engine` `Assessor` port for proactive candidate evaluation in the Router session.
9131
- * Relays each evaluation's candidate card to the Router session through the durable `assessment` effect, serves bounded thread
9132
- * context reads while the evaluation runs, and converts the Router's assessment into an `AssessmentOutcome` for `Engine.decide`.
9133
- * The Router's `orchestrator_eval*` tools call `context`, `read`, and `decide`.
9134
- */
9155
+ /** The Router's `orchestrator_eval*` tools call `context`, `read`, and `decide`. */
9135
9156
  var MasterAssessor = class {
9136
- /** Per-evaluation read caches and thread contexts, keyed by `evalId`. */
9137
9157
  evals = /* @__PURE__ */ new Map();
9138
9158
  host;
9139
9159
  /** Registers the handler that relays each Eval's candidate card, which a busy Router session may delay. */
@@ -9149,11 +9169,9 @@ var MasterAssessor = class {
9149
9169
  this.host.engine().candidateDelivered(evalId);
9150
9170
  }, { limit: () => host.paused() ? 0 : 4 });
9151
9171
  }
9152
- /** Enqueues an `assessment` effect to deliver `candidate`'s card to the Router session. */
9153
9172
  request(candidate) {
9154
9173
  this.host.effects.enqueue("assessment", candidate.evalId, { triggerId: candidate.triggers[0].id });
9155
9174
  }
9156
- /** Whether `evalId` is still the active evaluation for `trigger`. */
9157
9175
  live(evalId, trigger) {
9158
9176
  return this.host.engine().candidate(trigger.id)?.evalId === evalId;
9159
9177
  }
@@ -9162,7 +9180,10 @@ var MasterAssessor = class {
9162
9180
  this.evals.delete(evalId);
9163
9181
  this.host.effects.cancel("assessment", evalId);
9164
9182
  }
9165
- /** Resolve the active evaluation for a trigger; historical triggers have none, and explicit requests are routed instead. */
9183
+ /**
9184
+ * Resolve the active evaluation for a trigger. Historical triggers have none, and explicit requests
9185
+ * are routed instead.
9186
+ */
9166
9187
  evalFor(triggerId) {
9167
9188
  const candidate = this.host.engine().candidate(triggerId);
9168
9189
  if (!candidate) throw new Error("No assessment is in progress for this trigger; it was already decided, timed out, or never reached assessment");
@@ -9172,7 +9193,7 @@ var MasterAssessor = class {
9172
9193
  trigger: candidate.triggers.find((item) => item.id === triggerId)
9173
9194
  };
9174
9195
  }
9175
- /** Returns or initializes the read cache for `evalId`, seeded with the trigger's own message; throws if `evalId` has finished. */
9196
+ /** @throws when `evalId` has finished. */
9176
9197
  reads(evalId, trigger) {
9177
9198
  let reads = this.evals.get(evalId);
9178
9199
  if (!reads) {
@@ -9187,7 +9208,6 @@ var MasterAssessor = class {
9187
9208
  }
9188
9209
  return reads;
9189
9210
  }
9190
- /** Loads and caches the initial bounded thread window once per evaluation. */
9191
9211
  async seed(evalId, trigger, caller) {
9192
9212
  caller?.throwIfAborted();
9193
9213
  const reads = this.reads(evalId, trigger);
@@ -9220,11 +9240,11 @@ var MasterAssessor = class {
9220
9240
  if (!this.live(evalId, trigger)) throw new Error(FINISHED);
9221
9241
  }
9222
9242
  /**
9223
- * Returns the `orchestrator_eval` payload: the trigger's text once (up to `TRIGGER_CHARS`), batch peers, related tickets on
9224
- * the thread, the Policy version its Eval was built from, and the initial thread window (the first five messages and five on
9225
- * either side of the trigger, cut to `WINDOW_CHARS` or `NEAR_CHARS` for its two nearest neighbours, with other loaded messages
9226
- * as `STUB_CHARS` previews; `orchestrator_eval_read` reads any message in full by ID).
9227
- * Assessment instructions live in the Router's prompt, not in this payload.
9243
+ * Returns the `orchestrator_eval` payload: the trigger's text once (up to `TRIGGER_CHARS`), batch peers, related
9244
+ * tickets on the thread, the Policy version its Eval was built from, and the initial thread window (the first five
9245
+ * messages and five on either side of the trigger, cut to `WINDOW_CHARS` or `NEAR_CHARS` for its two nearest
9246
+ * neighbours, with other loaded messages as `STUB_CHARS` previews; `orchestrator_eval_read` reads any message in full
9247
+ * by ID). Assessment instructions live in the Router's prompt, not in this payload.
9228
9248
  */
9229
9249
  async context(triggerId, signal) {
9230
9250
  const { candidate, trigger } = this.evalFor(triggerId);
@@ -9299,7 +9319,6 @@ var MasterAssessor = class {
9299
9319
  thread
9300
9320
  };
9301
9321
  }
9302
- /** Serves a bounded, paginated read of the evaluation's message, root, parent, or thread page for `orchestrator_eval_read`. */
9303
9322
  async read(read, signal) {
9304
9323
  const { candidate, trigger } = this.evalFor(read.trigger_id);
9305
9324
  await this.seed(candidate.evalId, trigger, signal);
@@ -9349,11 +9368,10 @@ var MasterAssessor = class {
9349
9368
  snapshots.set(cacheKey, body);
9350
9369
  return textPage(read.resource, body, read.offset, 4e3);
9351
9370
  }
9352
- /** Converts the assessment into an outcome under current settings and passes it to `Engine.decide` for policy verification. */
9353
9371
  async decide(triggerId, result, signal) {
9354
9372
  const { candidate } = this.evalFor(triggerId);
9355
9373
  signal?.throwIfAborted();
9356
- const outcome = assessmentOutcome(result, this.host.settings(), candidate.strength);
9374
+ const outcome = assessmentOutcome(result, this.host.settings());
9357
9375
  const decision = this.host.engine().decide(triggerId, {
9358
9376
  ...outcome,
9359
9377
  evalId: candidate.evalId
@@ -9364,20 +9382,16 @@ var MasterAssessor = class {
9364
9382
  reason: reasonText(decision.reason)
9365
9383
  };
9366
9384
  }
9367
- /** Drops every evaluation's context and reads when the runtime stops. */
9368
9385
  stop() {
9369
9386
  this.evals.clear();
9370
9387
  }
9371
9388
  };
9372
9389
  // src/app/tools/main.ts
9373
9390
  /**
9374
- * Router session tools for routing triggers, assessing candidates, sending replies, and managing tickets and threads.
9375
- * Changes from the reference: `orchestrator_route` `accept` honors the host accept mode (observe records what it would accept,
9376
- * off ignores), so no tool bypasses it (D3); the eval reads are `message|root|parent|thread` (D4); the settings, policy-sync and
9377
- * prompt tools are removed (ADR-9); the IM word limit is `HUMAN_WORDS`; an unexpected error is a refusal to retry, not a log line.
9391
+ * `orchestrator_route` `accept` honors the host accept mode (observe records what it would accept, off ignores),
9392
+ * so no tool bypasses it. An unexpected error is a refusal to retry.
9378
9393
  */
9379
9394
  const RoutingNoteSchema = z.string().trim().refine((text) => wordCount(text) <= 30, `Routing note must not exceed 30 words (Chinese uses word segmentation). Shorten it; do not split it across deliveries.`).describe(`Optional routing background or explicit human constraint, at most 30 words. Never add implementation instructions or replace the original source.`);
9380
- /** Schedules an Engine pass to execute the recorded routing decision and formats the tool response. */
9381
9395
  function routed(host, result) {
9382
9396
  host.requestPass();
9383
9397
  const ticket = result.ticketId ? host.store.tickets.get(result.ticketId) : void 0;
@@ -9392,15 +9406,14 @@ const fromDecision = (decision) => ({
9392
9406
  reason: reasonText(decision.reason),
9393
9407
  ticketId: decision.ticketId
9394
9408
  });
9395
- /** A ticket the tool names must exist and still be open. */
9396
9409
  function openTicket(host, id) {
9397
9410
  const ticket = host.store.tickets.get(id);
9398
9411
  if (!ticket) throw new UserError("unknownTicket");
9399
9412
  if (ticket.ended) throw new UserError("ticketEnded");
9400
9413
  }
9401
9414
  /**
9402
- * One routing decision of `orchestrator_route`. The schema holds only its shape: each kind's fields are checked as its item
9403
- * runs (`FIELDS`), so an item that lacks one, or whose value breaks a limit, is refused alone.
9415
+ * One routing decision. The schema holds only its shape; each kind's fields are checked as its item runs (`FIELDS`),
9416
+ * so an item that lacks one, or whose value breaks a limit, is refused alone.
9404
9417
  */
9405
9418
  const RouteItemSchema = z.object({
9406
9419
  kind: z.enum([
@@ -9417,7 +9430,6 @@ const RouteItemSchema = z.object({
9417
9430
  note: z.string().nullish().describe(`accept and attach: ${RoutingNoteSchema.description ?? ""}`),
9418
9431
  reason: z.string().nullish().describe("ignore and stop: why, at most 500 characters; an ignore needs it. An ignore of an explicit request may be posted to its sender, so start with one plain sentence about the request, without internal terms or ids.")
9419
9432
  }).strict();
9420
- /** Each field's check, the kinds whose items read it and those that need it; an item of another kind leaves it unread. */
9421
9433
  const FIELDS = {
9422
9434
  ticket_id: {
9423
9435
  check: z.string(),
@@ -9450,7 +9462,6 @@ const FIELDS = {
9450
9462
  needs: ["ignore"]
9451
9463
  }
9452
9464
  };
9453
- /** Why an item cannot run: a field its kind needs is missing, or one it reads breaks its limit. */
9454
9465
  function invalid(item) {
9455
9466
  for (const [field, { check, reads, needs }] of Object.entries(FIELDS)) {
9456
9467
  const value = item[field];
@@ -9464,8 +9475,8 @@ function invalid(item) {
9464
9475
  }
9465
9476
  }
9466
9477
  /**
9467
- * Carry out one routing decision as its Engine command, in a transaction of its own. `decided` holds the Triggers that this
9468
- * call's earlier items decided, with their batches.
9478
+ * Carries out one routing decision as its Engine command, in a transaction of its own.
9479
+ * `decided` holds the Triggers that this call's earlier items decided, with their batches.
9469
9480
  */
9470
9481
  function route(host, item, decided) {
9471
9482
  const note = item.note ? { note: item.note } : {};
@@ -9546,8 +9557,8 @@ function route(host, item, decided) {
9546
9557
  }
9547
9558
  }
9548
9559
  /**
9549
- * `orchestrator_route`: routing decisions for several triggers in one call, in order. Each runs as its own Engine command; one
9550
- * that is refused, or fails, is answered so, and the others still run.
9560
+ * `orchestrator_route`: routing decisions for several triggers in one call, in order.
9561
+ * Each runs as its own Engine command. One that is refused or fails is answered so; the others still run.
9551
9562
  */
9552
9563
  function routeTool(host) {
9553
9564
  return tool$1({
@@ -9720,8 +9731,7 @@ function mainTools(host) {
9720
9731
  // src/app/cache.ts
9721
9732
  /**
9722
9733
  * In-memory LRU cache bounded by total UTF-16 code units, always retaining the most recently inserted entry.
9723
- * Runtime uses separate instances to cache paginated resource snapshots for `orchestrator_read` and resolved message links for search and cards.
9724
- * Changes from the reference: the constructor parameter is a declared field, assigned in the constructor body.
9734
+ * Runtime uses separate instances for paginated resource snapshots (`orchestrator_read`) and resolved message links.
9725
9735
  */
9726
9736
  var TextCache = class {
9727
9737
  texts = /* @__PURE__ */ new Map();
@@ -9772,7 +9782,10 @@ function jsonLines(write, env) {
9772
9782
  write(mask(JSON.stringify(record)) + "\n");
9773
9783
  };
9774
9784
  }
9775
- /** Executor tools; in deterministic Router mode a ready report also queues its summary as the result, in the same transaction that records the report (so no reader or crash sees the ticket ended without its result). */
9785
+ /**
9786
+ * Executor tools; in deterministic Router mode a ready report also queues its summary as the result, in the same
9787
+ * transaction that records the report (so no reader or crash sees the ticket ended without its result).
9788
+ */
9776
9789
  function workerTools(host, ticket, atomic) {
9777
9790
  const engine = () => {
9778
9791
  const inner = host.engine();
@@ -9842,7 +9855,7 @@ var GateAssessor = class {
9842
9855
  const candidate = this.host.engine().candidate(triggerId);
9843
9856
  if (candidate?.evalId !== evalId) return;
9844
9857
  const settings = this.host.settings();
9845
- const outcome = assessmentOutcome(deterministicAssessment(candidate, settings), settings, candidate.strength);
9858
+ const outcome = assessmentOutcome(deterministicAssessment(candidate, settings), settings);
9846
9859
  try {
9847
9860
  this.host.engine().decide(triggerId, {
9848
9861
  ...outcome,
@@ -10339,13 +10352,22 @@ var Runtime = class {
10339
10352
  });
10340
10353
  this.passes.request();
10341
10354
  }
10342
- /** Re-reads the config file and applies it: the policy part from the next Trigger on (a changed policy version is an input to open tickets), startup-only keys are kept and reported. A refused file changes nothing. `network.allow` and `models.worker` apply to worker sessions opened after the reload; `retention.endedTicketDays` applies from the next retention evaluation. Calls are serialised. */
10355
+ /**
10356
+ * Re-reads the config file and applies it: the policy part from the next Trigger on (a changed policy version is an
10357
+ * input to open tickets), startup-only keys are kept and reported. A refused file changes nothing. `network.allow`
10358
+ * and `models.worker` apply to worker sessions opened after the reload; `retention.endedTicketDays` applies from the
10359
+ * next retention evaluation. Calls are serialised.
10360
+ */
10343
10361
  reload() {
10344
10362
  const next = this.reloads.then(() => this.reloadOnce());
10345
10363
  this.reloads = next.catch(() => void 0);
10346
10364
  return next;
10347
10365
  }
10348
- /** Reassembles the worker prompt; when its team sections or rules changed, queues a notice with the changed sections for every open worker session (same key per ticket: only the latest is delivered). Returns the number queued. M10 calls it on reload. */
10366
+ /**
10367
+ * Reassembles the worker prompt; when its team sections or rules changed, queues a notice with the changed sections
10368
+ * for every open worker session (same key per ticket: only the latest is delivered). Returns the number queued.
10369
+ * Called on reload.
10370
+ */
10349
10371
  refreshPrompts() {
10350
10372
  const routerChange = this.router ? this.prompts.refresh("router") : void 0;
10351
10373
  if (routerChange?.notice) {
@@ -10648,7 +10670,10 @@ function healthCommand(argv, io, now = Date.now) {
10648
10670
  return 0;
10649
10671
  }
10650
10672
  // src/cli/prompts.ts
10651
- /** `superagent-claude prompts diff`: reports built-in default changes of the team sections from the database opened read-only; it takes no lock and needs no running daemon. */
10673
+ /**
10674
+ * `superagent-claude prompts diff`: reports built-in default changes of the team sections from the database opened
10675
+ * read-only; it takes no lock and needs no running daemon.
10676
+ */
10652
10677
  function promptsCommand(argv, io) {
10653
10678
  if (argv[0] !== "diff") {
10654
10679
  io.err(`superagent-claude: unknown prompts command '${argv[0] ?? ""}'\nUsage: superagent-claude prompts diff [--config <path>]
@@ -10697,9 +10722,6 @@ function promptsCommand(argv, io) {
10697
10722
  return 0;
10698
10723
  }
10699
10724
  // src/app/status.ts
10700
- /**
10701
- * Read models of the status and needs CLI over the read-only database: running work, idle sessions and waiting work, recent errors, repository access, consent, and the tickets that need a person; and the masking of secret values in their output.
10702
- */
10703
10725
  /** KV family the repository Source writes per repository: { accessible, push, at, error? }. */
10704
10726
  const REPOSITORY_ACCESS_PREFIX = "repo-access:";
10705
10727
  function rows(reader, sql, ...params) {
@@ -10714,7 +10736,10 @@ function thread(reader, key) {
10714
10736
  const row = reader.db.prepare("SELECT value FROM thread WHERE key = ?").get(key);
10715
10737
  return row ? JSON.parse(String(row.value)) : void 0;
10716
10738
  }
10717
- /** When a running Assignment's Session went idle: its last turn ended (idleAt), nothing waits to be delivered, and no message delivered to it is unread or was read after that. */
10739
+ /**
10740
+ * When a running Assignment's Session went idle: its last turn ended (idleAt), nothing waits to be delivered,
10741
+ * and no message delivered to it is unread or was read after that.
10742
+ */
10718
10743
  function idleSince(ticket, assignment) {
10719
10744
  const at = assignment.idleAt;
10720
10745
  if (!at || ticket.undelivered.length > 0) return void 0;
@@ -10977,7 +11002,10 @@ const FAKE_SCRIPT = ({ index }) => index === 0 ? { tools: [{
10977
11002
  summary: "Done: the fake worker finished"
10978
11003
  }
10979
11004
  }] } : {};
10980
- /** With SUPERAGENT_CLAUDE_FAKE_HOLD=1 (read only with --fake) the fake worker never ends a turn, so a test can stop the daemon while an Assignment runs. */
11005
+ /**
11006
+ * With SUPERAGENT_CLAUDE_FAKE_HOLD=1 (read only with --fake) the fake worker never ends a turn, so a test can stop
11007
+ * the daemon while an Assignment runs.
11008
+ */
10981
11009
  const HOLD_SCRIPT = () => ({ hold: new Promise(() => {}) });
10982
11010
  const fakeScript = (env) => env.SUPERAGENT_CLAUDE_FAKE_HOLD === "1" ? HOLD_SCRIPT : FAKE_SCRIPT;
10983
11011
  const HELP = [