@mmerterden/multi-agent-pipeline 20.30.0 → 20.34.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (195) hide show
  1. package/CHANGELOG.md +123 -0
  2. package/README.md +47 -1
  3. package/README.tr.md +61 -1
  4. package/docs/adr/0018-cross-repo-links-not-a-global-graph.md +43 -0
  5. package/docs/adr/README.md +2 -1
  6. package/docs/facts.json +6 -5
  7. package/docs/features.md +1 -1
  8. package/install/_dev-only-files.mjs +1 -0
  9. package/install/_unattended-profile.mjs +61 -0
  10. package/install/catalog-history.json +1 -1
  11. package/install/claude.mjs +16 -2
  12. package/install/index.mjs +10 -1
  13. package/install/templates/claude-hooks.json +23 -1
  14. package/manifest.json +198 -134
  15. package/package.json +4 -1
  16. package/pipeline/agents/code-reviewer.md +9 -0
  17. package/pipeline/agents/explorer.md +4 -0
  18. package/pipeline/agents/plan-critic.md +4 -2
  19. package/pipeline/commands/multi-agent/SKILL.md +17 -136
  20. package/pipeline/commands/multi-agent/analysis/SKILL.md +4 -4
  21. package/pipeline/commands/multi-agent/channels/SKILL.md +15 -297
  22. package/pipeline/commands/multi-agent/graph/SKILL.md +5 -2
  23. package/pipeline/commands/multi-agent/language/SKILL.md +3 -3
  24. package/pipeline/commands/multi-agent/office-on/SKILL.md +8 -7
  25. package/pipeline/commands/multi-agent/office-status/SKILL.md +1 -1
  26. package/pipeline/commands/multi-agent/prune-logs/SKILL.md +4 -1
  27. package/pipeline/commands/multi-agent/resume/SKILL.md +8 -22
  28. package/pipeline/commands/multi-agent/review/SKILL.md +11 -134
  29. package/pipeline/commands/multi-agent/scenario-audit/SKILL.md +4 -2
  30. package/pipeline/commands/multi-agent/setup/SKILL.md +19 -224
  31. package/pipeline/commands/multi-agent/sync/SKILL.md +12 -0
  32. package/pipeline/commands/multi-agent/test-gen/SKILL.md +29 -3
  33. package/pipeline/contract/CHANGELOG.md +46 -0
  34. package/pipeline/contract/README.md +27 -10
  35. package/pipeline/contract/fixtures/error-invalid-request.json +1 -1
  36. package/pipeline/contract/fixtures/error-unauthorized.json +1 -1
  37. package/pipeline/contract/fixtures/error-unsigned.json +1 -1
  38. package/pipeline/contract/fixtures/launch-plan.json +1 -1
  39. package/pipeline/contract/fixtures/runs-awaiting-question.json +6 -1
  40. package/pipeline/contract/fixtures/runs-empty.json +1 -1
  41. package/pipeline/contract/fixtures/runs-failed.json +6 -1
  42. package/pipeline/contract/fixtures/runs-old-schema.json +6 -1
  43. package/pipeline/contract/fixtures/runs-pr-opened-redacted.json +5 -1
  44. package/pipeline/contract/fixtures/runs-pr-opened.json +6 -1
  45. package/pipeline/contract/fixtures/runs-running.json +6 -1
  46. package/pipeline/contract/manifest.json +153 -19
  47. package/pipeline/contract/types/index.d.ts +304 -6
  48. package/pipeline/lib/bg-launch.mjs +106 -0
  49. package/pipeline/lib/code-digest.mjs +49 -0
  50. package/pipeline/lib/credential-resolve.mjs +42 -2
  51. package/pipeline/multi-agent-refs/channels/generator.md +146 -0
  52. package/pipeline/multi-agent-refs/channels/menu.md +55 -0
  53. package/pipeline/multi-agent-refs/channels/multi-repo.md +6 -0
  54. package/pipeline/multi-agent-refs/channels/preview.md +25 -0
  55. package/pipeline/multi-agent-refs/channels/reference.md +98 -0
  56. package/pipeline/multi-agent-refs/features/autopilot-circuit-breaker.md +1 -1
  57. package/pipeline/multi-agent-refs/features/code-graph.md +27 -0
  58. package/pipeline/multi-agent-refs/features/office-threat-model.md +24 -6
  59. package/pipeline/multi-agent-refs/features/plan-critic.md +5 -2
  60. package/pipeline/multi-agent-refs/features/review-delta.md +4 -1
  61. package/pipeline/multi-agent-refs/features/scope-check.md +10 -1
  62. package/pipeline/multi-agent-refs/features/unattended-security.md +1 -1
  63. package/pipeline/multi-agent-refs/help/en.md +2 -2
  64. package/pipeline/multi-agent-refs/help/tr.md +2 -2
  65. package/pipeline/multi-agent-refs/knowledge.md +9 -0
  66. package/pipeline/multi-agent-refs/orchestrator/dispatcher.md +163 -0
  67. package/pipeline/multi-agent-refs/phases/modes.md +7 -7
  68. package/pipeline/multi-agent-refs/phases/phase-1-plan.md +4 -4
  69. package/pipeline/multi-agent-refs/phases/phase-2-dev.md +9 -8
  70. package/pipeline/multi-agent-refs/phases/phase-3-review.md +12 -7
  71. package/pipeline/multi-agent-refs/phases/phase-4-commit.md +7 -3
  72. package/pipeline/multi-agent-refs/phases/phase-5-report.md +19 -10
  73. package/pipeline/multi-agent-refs/picker-contract.md +1 -1
  74. package/pipeline/multi-agent-refs/resume/details.md +35 -0
  75. package/pipeline/multi-agent-refs/review/inputs.md +87 -0
  76. package/pipeline/multi-agent-refs/review/module-guides.md +25 -0
  77. package/pipeline/multi-agent-refs/review/valven.md +42 -0
  78. package/pipeline/multi-agent-refs/review-standalone-tracker.md +37 -0
  79. package/pipeline/multi-agent-refs/setup/accounts.md +27 -0
  80. package/pipeline/multi-agent-refs/setup/app-store-connect.md +5 -0
  81. package/pipeline/multi-agent-refs/setup/firebase.md +5 -0
  82. package/pipeline/multi-agent-refs/setup/google-play.md +4 -0
  83. package/pipeline/multi-agent-refs/setup/hooks-and-plugins.md +21 -0
  84. package/pipeline/multi-agent-refs/setup/language.md +46 -0
  85. package/pipeline/multi-agent-refs/setup/multi-repo-group.md +51 -0
  86. package/pipeline/multi-agent-refs/setup/summaries.md +63 -0
  87. package/pipeline/multi-agent-refs/setup/token-save-flow.md +51 -0
  88. package/pipeline/multi-agent-refs/tracker-contract.md +8 -3
  89. package/pipeline/office/NOTICE.md +4 -1
  90. package/pipeline/office/assets/ARCHIVE_BOXES.png +0 -0
  91. package/pipeline/office/assets/COFFEE_MACHINE.png +0 -0
  92. package/pipeline/office/assets/COUNTER_SINK.png +0 -0
  93. package/pipeline/office/assets/DEVICE_STAND.png +0 -0
  94. package/pipeline/office/assets/FILING_CABINET.png +0 -0
  95. package/pipeline/office/assets/FRIDGE.png +0 -0
  96. package/pipeline/office/assets/MAILBOX.png +0 -0
  97. package/pipeline/office/assets/MOP_BUCKET.png +0 -0
  98. package/pipeline/office/assets/PRINTER.png +0 -0
  99. package/pipeline/office/assets/RECEPTION_DESK.png +0 -0
  100. package/pipeline/office/assets/SERVER_RACK.png +0 -0
  101. package/pipeline/office/assets/SERVER_RACK_2.png +0 -0
  102. package/pipeline/office/assets/TOILET_STALL.png +0 -0
  103. package/pipeline/office/assets/VENDING.png +0 -0
  104. package/pipeline/office/assets/WASHBASIN.png +0 -0
  105. package/pipeline/office/assets/WATER_COOLER.png +0 -0
  106. package/pipeline/office/index.html +3936 -819
  107. package/pipeline/office/toolkit.json +3 -0
  108. package/pipeline/rules/response-shape.md +6 -7
  109. package/pipeline/schemas/activity.schema.json +188 -4
  110. package/pipeline/schemas/agent-state.schema.json +511 -115
  111. package/pipeline/schemas/contract-error.schema.json +12 -1
  112. package/pipeline/schemas/job-change.schema.json +72 -0
  113. package/pipeline/schemas/job-request.schema.json +38 -0
  114. package/pipeline/schemas/launch.json +4 -4
  115. package/pipeline/schemas/office-credentials.schema.json +71 -0
  116. package/pipeline/schemas/office-jobs.json +253 -0
  117. package/pipeline/schemas/office-jobs.schema.json +62 -0
  118. package/pipeline/schemas/office-memory.schema.json +105 -0
  119. package/pipeline/schemas/office-settings-request.schema.json +30 -0
  120. package/pipeline/schemas/office-settings.schema.json +67 -0
  121. package/pipeline/schemas/phases.json +14 -6
  122. package/pipeline/schemas/phases.schema.json +86 -19
  123. package/pipeline/schemas/plan-critique.schema.json +9 -2
  124. package/pipeline/schemas/planning-output.schema.json +25 -2
  125. package/pipeline/schemas/prefs.schema.json +24 -1
  126. package/pipeline/schemas/prs.schema.json +5 -0
  127. package/pipeline/schemas/prune-logs-request.schema.json +26 -0
  128. package/pipeline/schemas/prune-logs.schema.json +52 -0
  129. package/pipeline/schemas/repo-change.schema.json +54 -0
  130. package/pipeline/schemas/repo-request.schema.json +29 -0
  131. package/pipeline/schemas/repos.schema.json +49 -1
  132. package/pipeline/schemas/reviewer-output.schema.json +20 -2
  133. package/pipeline/schemas/runs-index.schema.json +257 -46
  134. package/pipeline/schemas/system-status.schema.json +111 -0
  135. package/pipeline/schemas/token-budget.json +4 -4
  136. package/pipeline/schemas/tools.schema.json +5 -0
  137. package/pipeline/schemas/triage-output.schema.json +20 -2
  138. package/pipeline/schemas/unattended-profile.json +7 -2
  139. package/pipeline/scripts/_active-run.mjs +2 -1
  140. package/pipeline/scripts/_graph-analysis.mjs +270 -0
  141. package/pipeline/scripts/_memory-policy.mjs +165 -0
  142. package/pipeline/scripts/_secret-scan.mjs +105 -0
  143. package/pipeline/scripts/_spawn-async.mjs +9 -5
  144. package/pipeline/scripts/_unified-diff.mjs +2 -2
  145. package/pipeline/scripts/activity-snapshot.mjs +219 -23
  146. package/pipeline/scripts/autopilot-runner.mjs +7 -5
  147. package/pipeline/scripts/capture-flush.sh +12 -2
  148. package/pipeline/scripts/check-read-size.sh +9 -0
  149. package/pipeline/scripts/code-graph-rules/android.json +8 -3
  150. package/pipeline/scripts/contract-server.mjs +575 -28
  151. package/pipeline/scripts/debt-ledger.mjs +323 -0
  152. package/pipeline/scripts/file-context.mjs +261 -0
  153. package/pipeline/scripts/file-context.sh +20 -0
  154. package/pipeline/scripts/gen-mode-dispatch.mjs +3 -1
  155. package/pipeline/scripts/graph-links.mjs +230 -0
  156. package/pipeline/scripts/graph-query.mjs +133 -1
  157. package/pipeline/scripts/graph-report.mjs +65 -0
  158. package/pipeline/scripts/job-launch.mjs +477 -0
  159. package/pipeline/scripts/launch-request.mjs +94 -9
  160. package/pipeline/scripts/learn-from-transcripts.mjs +12 -48
  161. package/pipeline/scripts/learnings-ledger.mjs +143 -24
  162. package/pipeline/scripts/office-build.mjs +148 -11
  163. package/pipeline/scripts/office-control.mjs +34 -7
  164. package/pipeline/scripts/office-credentials.mjs +106 -0
  165. package/pipeline/scripts/office-memory.mjs +200 -0
  166. package/pipeline/scripts/office-prune.mjs +125 -0
  167. package/pipeline/scripts/office-settings.mjs +145 -0
  168. package/pipeline/scripts/phase-tracker.sh +12 -5
  169. package/pipeline/scripts/plan-critique-gate.mjs +11 -2
  170. package/pipeline/scripts/prune-logs.sh +53 -2
  171. package/pipeline/scripts/rework-rulings.mjs +106 -0
  172. package/pipeline/scripts/runs-index.mjs +62 -2
  173. package/pipeline/scripts/scope-check-gate.mjs +149 -2
  174. package/pipeline/scripts/system-status.mjs +196 -0
  175. package/pipeline/scripts/task-mirror.mjs +150 -0
  176. package/pipeline/scripts/task-mirror.sh +20 -0
  177. package/pipeline/scripts/toolkit-tools.mjs +33 -7
  178. package/pipeline/scripts/transcript-index.mjs +4 -1
  179. package/pipeline/scripts/triage-memory.mjs +64 -37
  180. package/pipeline/scripts/validate-planning.mjs +68 -4
  181. package/pipeline/scripts/validate-reviewer.mjs +20 -0
  182. package/pipeline/scripts/validate-triage.mjs +107 -2
  183. package/pipeline/scripts/work-items.mjs +191 -22
  184. package/pipeline/skills/.skill-manifest.json +9 -9
  185. package/pipeline/skills/.skills-index.json +1 -1
  186. package/pipeline/skills/shared/README.md +1 -1
  187. package/pipeline/skills/shared/core/multi-agent-graph/SKILL.md +5 -2
  188. package/pipeline/skills/shared/core/multi-agent-language/SKILL.md +4 -4
  189. package/pipeline/skills/shared/core/multi-agent-office-on/SKILL.md +3 -4
  190. package/pipeline/skills/shared/core/multi-agent-prune-logs/SKILL.md +4 -1
  191. package/pipeline/skills/shared/core/multi-agent-review/SKILL.md +21 -0
  192. package/pipeline/skills/shared/core/multi-agent-scenario-audit/SKILL.md +4 -2
  193. package/pipeline/skills/shared/core/multi-agent-setup/SKILL.md +4 -4
  194. package/pipeline/skills/shared/core/multi-agent-test-gen/SKILL.md +26 -3
  195. package/pipeline/skills/skills-index.md +1 -1
@@ -1,8 +1,8 @@
1
1
  // Generated by pipeline/contract/build.mjs from pipeline/schemas/*.schema.json.
2
2
  // Do not edit: change the schema and run `node pipeline/contract/build.mjs --write`.
3
3
 
4
- export type ContractVersion = "1.4.0";
5
- export type SurfaceId = "runs" | "run" | "commands" | "questions" | "issues" | "worktrees" | "launch-spec" | "repos" | "launch" | "answer" | "run-log" | "resume" | "kill" | "gc" | "autopilot" | "autopilot-off" | "phone-runs" | "phone-run" | "phone-answer" | "phone-launch" | "autopilot-on" | "steer" | "activity" | "activity-stream" | "agent-chat" | "routines" | "routine-delete" | "stack" | "stack-set" | "ledger" | "run-results" | "run-evidence" | "review-verdict" | "issue-maturity" | "prs" | "tools" | "tool-call" | "service-confluence" | "service-crashlytics" | "service-figma" | "permissions";
4
+ export type ContractVersion = "1.6.0";
5
+ export type SurfaceId = "runs" | "run" | "commands" | "questions" | "issues" | "worktrees" | "launch-spec" | "repos" | "repo-add" | "repo-remove" | "launch" | "answer" | "run-log" | "resume" | "kill" | "gc" | "autopilot" | "autopilot-off" | "phone-runs" | "phone-run" | "phone-answer" | "phone-launch" | "autopilot-on" | "steer" | "activity" | "activity-stream" | "agent-chat" | "routines" | "routine-delete" | "jobs" | "job-start" | "stack" | "stack-set" | "ledger" | "system" | "prune-logs" | "settings" | "settings-set" | "credentials" | "memory" | "run-results" | "run-evidence" | "review-verdict" | "issue-maturity" | "prs" | "tools" | "tool-call" | "service-confluence" | "service-crashlytics" | "service-figma" | "permissions";
6
6
 
7
7
  /** What `runs-index.mjs --json` prints: every run under the log root and the unattended log root (one record per task id), grouped per status/SKILL.md 3b. Fields are only ever added - a decoder ignores keys it does not know. `--redact` drops every x-sensitivity local field (and `logsRoot`) and scrubs the free text in the title, `now`, sub-step names, task subjects and the pending question. */
8
8
  export interface RunsIndex {
@@ -51,6 +51,8 @@ export interface RunsIndexRun {
51
51
  };
52
52
  estUsd: number;
53
53
  unlockedWrites: number;
54
+ /** agent-state.json mode as the run recorded it (analysis, test-gen, review...), so a reader can name the command; null when none was recorded. */
55
+ mode?: string | null;
54
56
  /** autopilot when the run is in autopilot mode; analysis for an analysis-family run (agent-state mode analysis, or a tracker with no agent state); development otherwise. Added in 1.2.0. */
55
57
  runType?: "analysis" | "development" | "autopilot";
56
58
  /** The issue summary Phase 0 recorded (agent-state title), else the input the run was launched with (launchRequest.input), else null. Scrubbed under --redact. Added in 1.2.0. */
@@ -64,6 +66,27 @@ export interface RunsIndexRun {
64
66
  subject: string;
65
67
  activeForm: string | null;
66
68
  } | null;
69
+ /** Where the work came from: a Jira key, a GitHub issue (owner/repo#N) or free text; a GitHub issue that got a Jira has both. Null when the run recorded neither. Added in 1.3.0. */
70
+ source?: {
71
+ kind: "jira" | "github" | "text";
72
+ jira: string | null;
73
+ issue: string | null;
74
+ } | null;
75
+ /** The pull request's number, from the state or its URL; the URL itself is prUrl (local). Added in 1.3.0. */
76
+ prNumber?: number | null;
77
+ /** Where Phase 5 sent the report, per adapter (state.channelResults); empty before then. Added in 1.3.0. */
78
+ channels?: Array<{
79
+ channel: "pr" | "jira" | "confluence" | "wiki" | "issue";
80
+ status: "posted" | "skipped" | "failed";
81
+ }>;
82
+ /** The address each adapter posted to, https only; dropped from a redacted answer. Added in 1.3.0. */
83
+ channelUrls?: {
84
+ pr?: string;
85
+ jira?: string;
86
+ confluence?: string;
87
+ wiki?: string;
88
+ issue?: string;
89
+ };
67
90
  }
68
91
 
69
92
  export interface RunsIndexQuestion {
@@ -490,7 +513,7 @@ export interface AutopilotOff {
490
513
  stoppedInFlight: boolean;
491
514
  }
492
515
 
493
- /** What GET /v1/repos returns and `launch-request.mjs repos` prints: the repositories POST /v1/launch?repo= accepts, by real path. source autopilot is a configured autopilot repo (config.json repos[], named by nameWithOwner); registered is one added with `launch-request.mjs register-repo` (launch-repos.json, named by its directory). path is dropped under redact. */
516
+ /** What GET /v1/repos returns and `launch-request.mjs repos` prints: the repositories POST /v1/launch?repo= accepts, by real path. source autopilot is a configured autopilot repo (config.json repos[], named by nameWithOwner); registered is one added with `launch-request.mjs register-repo` (launch-repos.json, named by its directory). path is dropped under redact. With --all (?all=1): suggested lists checkouts earlier runs worked in that launch does not accept yet, and missing lists registered entries that are no longer a directory; each carries an id that POST /v1/repos and DELETE /v1/repos/{id} take. */
494
517
  export interface Repos {
495
518
  contractVersion: string;
496
519
  repos: Array<{
@@ -498,6 +521,97 @@ export interface Repos {
498
521
  name: string;
499
522
  source: "autopilot" | "registered";
500
523
  }>;
524
+ suggested?: Array<{
525
+ id: string;
526
+ path?: string;
527
+ name: string;
528
+ }>;
529
+ missing?: Array<{
530
+ id: string;
531
+ path?: string;
532
+ name: string;
533
+ }>;
534
+ }
535
+
536
+ /** The body of POST /v1/repos. The first request names suggestion (an id from GET /v1/repos?all=1) or path (an absolute path to a git checkout); the second sends only confirmToken. */
537
+ export interface RepoRequest {
538
+ contractVersion: string;
539
+ suggestion?: string;
540
+ path?: string;
541
+ confirmToken?: string;
542
+ }
543
+
544
+ /** What POST /v1/repos and DELETE /v1/repos/{id} return. Without a token: preview (the repository's name) with confirmToken and expiresAt. With it: registered or removed, by name. Paths never leave the server here. */
545
+ export interface RepoChange {
546
+ contractVersion: string;
547
+ preview?: {
548
+ name: string;
549
+ };
550
+ confirmToken?: string;
551
+ expiresAt?: string;
552
+ registered?: {
553
+ name: string;
554
+ };
555
+ removed?: {
556
+ name: string;
557
+ };
558
+ }
559
+
560
+ /** The catalog of jobs the office can start (office-jobs.json), read by job-launch.mjs and served by GET /v1/jobs. */
561
+ export interface OfficeJobs {
562
+ $schema?: string;
563
+ contractVersion: string;
564
+ description?: string;
565
+ jobs: Array<{
566
+ id: string;
567
+ command: string;
568
+ role: string;
569
+ team: boolean;
570
+ launch?: "development" | "analysis" | "autopilot";
571
+ available?: boolean;
572
+ reason?: string | null;
573
+ params: Array<{
574
+ name: string;
575
+ kind: "work-item" | "pull-request" | "branch" | "run-id" | "jira-key" | "github-issue" | "enum" | "repo-path";
576
+ values?: Array<string>;
577
+ required: boolean;
578
+ }>;
579
+ writes: "none" | "branch" | "branch-pr" | "pr-comments" | "issue-comment";
580
+ }>;
581
+ }
582
+
583
+ /** The body of POST /v1/jobs. The first request names jobId (from GET /v1/jobs), repo (a name from GET /v1/repos or its path) and args (by parameter name), plus answers for a pipeline run; the second sends only confirmToken. */
584
+ export interface JobRequest {
585
+ contractVersion: string;
586
+ jobId?: string;
587
+ repo?: string;
588
+ args?: {
589
+ [key: string]: string;
590
+ };
591
+ answers?: Record<string, unknown>;
592
+ confirmToken?: string;
593
+ }
594
+
595
+ /** What POST /v1/jobs returns. Without a token: preview (the job, the role that does it, team or solo, the repository by name, the exact prompt the session receives and what it writes outside the machine) with confirmToken and expiresAt. With it: started, the session the server began. */
596
+ export interface JobChange {
597
+ contractVersion: string;
598
+ preview?: {
599
+ jobId: string;
600
+ role: string;
601
+ team: boolean;
602
+ repo: string;
603
+ prompt: string;
604
+ writes: "none" | "branch" | "branch-pr" | "pr-comments" | "issue-comment";
605
+ };
606
+ confirmToken?: string;
607
+ expiresAt?: string;
608
+ started?: {
609
+ sessionId: string;
610
+ jobId: string;
611
+ role: string;
612
+ team: boolean;
613
+ repo: string;
614
+ };
501
615
  }
502
616
 
503
617
  /** What GET /v1/activity returns and `activity-snapshot.mjs --json` prints, and what each GET /v1/activity/stream event carries: one room per repository, one table per run (its team; a session no run claims sits at a table of its own), one seat per agent seen in the recent window, the orchestrator at the head. Derived from disk on every call. No field is machine-local: there are no paths, seat labels come from allow-listed fields only (tool name, file basename, sidecar description), and every free-text string has been through the redactor, so the redacted and the full view are the same document. */
@@ -526,8 +640,12 @@ export interface Activity {
526
640
 
527
641
  export interface ActivityTable {
528
642
  id: string;
529
- /** run: a pipeline run; session: a session no run claims (outside the pipeline); unmatched: a session more than one run could claim, kept apart rather than guessed. */
530
- kind: "run" | "session" | "unmatched";
643
+ /** run: a pipeline run; session: a session no run claims (outside the pipeline); unmatched: a session more than one run could claim, kept apart rather than guessed. solo: a session the office started for a one-person job, seated at a desk. */
644
+ kind: "run" | "session" | "solo" | "unmatched";
645
+ /** The command the table runs, for its room sign: /multi-agent, /multi-agent:analysis, /multi-agent:test-gen. Null for a session no command owns. */
646
+ command: string | null;
647
+ /** Which pipeline the table runs: /multi-agent (development), /multi-agent:analysis, or an autopilot run. Null for a session no pipeline owns. The office names the room after it. */
648
+ runType: "development" | "analysis" | "autopilot" | null;
531
649
  taskId: string | null;
532
650
  title: string | null;
533
651
  host: "claude" | "copilot" | "codex";
@@ -543,6 +661,9 @@ export interface ActivityTable {
543
661
  /** The last phase id, for an n of last display. */
544
662
  last: number;
545
663
  } | null;
664
+ /** The task in progress in the phase in progress, from the mirrored task list. */
665
+ currentTask: string | null;
666
+ progress: number | null;
546
667
  now: string | null;
547
668
  status: string | null;
548
669
  waitingForYou: boolean;
@@ -570,6 +691,13 @@ export interface ActivityTable {
570
691
  branch: string | null;
571
692
  phaseHistory: Array<{
572
693
  id: number | null;
694
+ /** How far the phase is, from its mirrored task list; null when it has none. */
695
+ progress: number | null;
696
+ /** The phase's task list as the host task widget shows it (mirrored by phase-tracker.sh task), redacted and cut short. */
697
+ tasks: Array<{
698
+ subject: string;
699
+ status: "pending" | "in_progress" | "completed";
700
+ }>;
573
701
  name: string;
574
702
  status: string;
575
703
  startedAt: string | null;
@@ -582,9 +710,45 @@ export interface ActivityTable {
582
710
  /** The last seat of an earlier phase, for drawing the handoff. */
583
711
  previousHolder: string | null;
584
712
  seats: Array<ActivitySeat>;
713
+ /** The office job a session was started for (run tables carry none). */
714
+ job?: null | {
715
+ id: string;
716
+ role: string;
717
+ team: boolean;
718
+ };
719
+ /** Where the run's work came from, for the table sign: a Jira key, a GitHub issue or free text. Null for a session or a run that recorded neither. */
720
+ source: {
721
+ kind: "jira" | "github" | "text";
722
+ jira: string | null;
723
+ issue: string | null;
724
+ } | null;
725
+ /** The pull request the run opened, by number only. */
726
+ pr: {
727
+ number: number;
728
+ } | null;
729
+ /** What a waiting run waits for: a step it paused at (the run's waitingFor), or terminal when its session holds a question open in its own window. */
730
+ waitingFor: "maturity" | "user-channels-choice" | "local-test" | "attended-reply" | "question" | "terminal" | null;
731
+ /** Where the run's report went, per adapter, for the run window; empty for a session or before Phase 5. */
732
+ channels: Array<{
733
+ channel: "pr" | "jira" | "confluence" | "wiki" | "issue";
734
+ status: "posted" | "skipped" | "failed";
735
+ }>;
585
736
  }
586
737
 
587
738
  export interface ActivitySeat {
739
+ /** The desk's own task list from its transcript (TaskCreate / TaskUpdate / TodoWrite), phase tiles left out, redacted and cut short. */
740
+ tasks: Array<{
741
+ subject: string;
742
+ status: "pending" | "in_progress" | "completed";
743
+ }>;
744
+ /** The desk's last MCP calls, newest last: server, tool, whether it is still running, failed or answered, and how long it took. No input or result. */
745
+ mcp: Array<{
746
+ server: string;
747
+ tool: string;
748
+ state: "running" | "ok" | "error";
749
+ ms: number | null;
750
+ at: string | null;
751
+ }>;
588
752
  id: string;
589
753
  sessionId: string | null;
590
754
  /** null for the session's own (orchestrator) desk. */
@@ -837,6 +1001,105 @@ export interface LedgerWindow {
837
1001
  byPhase: Array<LedgerRow>;
838
1002
  }
839
1003
 
1004
+ /** What GET /v1/system returns and `system-status.mjs --json` prints: CPU load, memory, free disk and what the pipeline itself uses. Numbers only: no path, process argument, user name or host name. A value that could not be measured is null, never 0. */
1005
+ export interface SystemStatus {
1006
+ contractVersion: string;
1007
+ generatedAt: string;
1008
+ cpu: {
1009
+ cores: number;
1010
+ /** Busy share of all cores over a short sampling window. */
1011
+ usage: number | null;
1012
+ /** Load average over 1, 5 and 15 minutes. */
1013
+ load: Array<number>;
1014
+ };
1015
+ memory: {
1016
+ totalMb: number;
1017
+ /** Total minus what the OS reports free; on macOS that counts the file cache as used. */
1018
+ usedMb: number;
1019
+ };
1020
+ disk: {
1021
+ home: SystemStatusDisk;
1022
+ /** The disk the run logs live on, when it is not the home disk; null when it is the same disk. */
1023
+ logs: SystemStatusDisk;
1024
+ };
1025
+ pipeline: {
1026
+ /** Claude Code processes running. */
1027
+ sessions: number | null;
1028
+ /** multi-agent-toolkit server processes running. */
1029
+ toolkit: number | null;
1030
+ /** Their resident memory together. */
1031
+ rssMb: number | null;
1032
+ /** Size of the run log folder; null when it could not be measured in time. */
1033
+ logsMb: number | null;
1034
+ };
1035
+ /** disk-low: under 10 percent of the home disk is free. cpu-busy: the 5-minute load is above the core count. */
1036
+ warnings: (Array<"disk-low" | "cpu-busy">);
1037
+ }
1038
+
1039
+ export type SystemStatusDisk = {
1040
+ totalMb: number;
1041
+ freeMb: number;
1042
+ } | null;
1043
+
1044
+ /** What GET /v1/credentials returns and `office-credentials.mjs --json` prints: for each credential the pipeline knows by logical name, whether the store holds it and, with probe, whether its service accepted it. Never a value: lib/credential-inventory.sh reads each through the store with stdout discarded. Renewal stays in the terminal (/multi-agent:setup). */
1045
+ export interface OfficeCredentials {
1046
+ contractVersion: string;
1047
+ generatedAt: string;
1048
+ probed: boolean;
1049
+ /** False when the inventory could not run on this host. */
1050
+ available: boolean;
1051
+ credentials: Array<{
1052
+ logical: string;
1053
+ state: "present" | "mapped-but-missing" | "unmapped";
1054
+ reachability: "not-probed" | "reachable" | "auth-rejected" | "unreachable" | "no-host-configured" | "well-formed" | "not-probeable" | "malformed" | "no-credential" | "probe-error" | "server-error" | "tier-1-ready" | "tier-2-ready" | "tier-1-no-grant" | "unknown";
1055
+ capability: string | null;
1056
+ }>;
1057
+ }
1058
+
1059
+ /** What GET /v1/memory returns and `office-memory.mjs --json` prints: per repository, the code graph's age and size and how many learnings, past review findings and session notes the learning loop holds, with the newest learnings redacted and cut short. An empty shelf is reported, never left out. */
1060
+ export interface OfficeMemory {
1061
+ contractVersion: string;
1062
+ generatedAt: string;
1063
+ shelves: Array<{
1064
+ repo: string;
1065
+ graph: null | {
1066
+ builtAt: string | null;
1067
+ ageDays: number;
1068
+ sizeKb: number;
1069
+ };
1070
+ learnings: number;
1071
+ findings: number;
1072
+ sessionNotes: number;
1073
+ recent: Array<{
1074
+ kind: string | null;
1075
+ statement: string;
1076
+ at: string | null;
1077
+ }>;
1078
+ lastWrite: string | null;
1079
+ }>;
1080
+ }
1081
+
1082
+ /** What POST /v1/prune-logs returns. The preview (body {olderThanDays}) is prune-logs.sh's dry run for that age with a confirm token; the confirmation (body {confirmToken}) runs it again with the same age and --yes. The audit trail, the metrics corpus and task counters are never removed, and a folder written in the last minutes is spared. */
1083
+ export interface PruneLogs {
1084
+ contractVersion: string;
1085
+ olderThanDays: 7 | 30 | 90;
1086
+ applied: boolean;
1087
+ count: number;
1088
+ freedMb: number;
1089
+ /** project/task folders, at most 200 listed. */
1090
+ folders: Array<string>;
1091
+ confirmToken?: string;
1092
+ expiresAt?: string;
1093
+ }
1094
+
1095
+ /** The body of POST /v1/prune-logs. Step 1 names the age in days (7, 30 or 90); step 2 sends only the confirmToken step 1 returned. */
1096
+ export interface PruneLogsRequest {
1097
+ contractVersion: string;
1098
+ olderThanDays?: 7 | 30 | 90;
1099
+ /** The confirmToken the preview returned. Single use, bound to the route. */
1100
+ confirmToken?: string;
1101
+ }
1102
+
840
1103
  /** What GET /v1/runs/{id}/results returns (`run-results.mjs <id> --json`): what a run produced, read from its own files. diff from agent-state diffRisk or .review-diff.txt; findings from triage-output.json or agent-state review; tests from the test-summary gate and uiTest; evidence the screenshot names under .pipeline/evidence; analysis the documents an analysis run wrote. A part with no source is null. Text is scrubbed and redacted; paths are reduced to file names. */
841
1104
  export interface RunResults {
842
1105
  contractVersion: string;
@@ -921,6 +1184,8 @@ export interface Prs {
921
1184
  contractVersion: string;
922
1185
  items: Array<{
923
1186
  source: "github" | "bitbucket";
1187
+ /** The GitHub login the pull request was listed under, when gh is logged in to more than one account (personal and corporate). Null for Bitbucket. */
1188
+ account?: string | null;
924
1189
  repo: string;
925
1190
  number: number;
926
1191
  title: string;
@@ -973,6 +1238,8 @@ export interface Tools {
973
1238
  allowed: boolean;
974
1239
  reason: string | null;
975
1240
  }>;
1241
+ /** The exact toolkit version the panel starts: the registration's own when it names one, else the version the install recorded from the release. */
1242
+ version?: string | null;
976
1243
  }
977
1244
 
978
1245
  /** The body of POST /v1/tools/{name}/call: the tool's arguments, which must satisfy its inputSchema. At most 64 KiB. */
@@ -1030,10 +1297,41 @@ export interface Permissions {
1030
1297
  /** Every non-2xx body contract-server.mjs sends, on the desktop and the phone routes alike. `error` is a fixed code a client switches on; `message` is for a person and names no path, host or token. `errors` lists schema or rule violations for invalid-body and invalid-request; `supported` lists the majors ?v= accepts for unsupported-version. run-active and not-resumable answer POST /v1/runs/{id}/resume; confirm-expired and confirm-mismatch answer the confirming request of a kill or gc. */
1031
1298
  export interface ContractError {
1032
1299
  contractVersion: string;
1033
- error: "unauthorized" | "forbidden-host" | "forbidden-origin" | "not-found" | "method-not-allowed" | "invalid-query" | "unsupported-version" | "unsupported-media-type" | "invalid-body" | "invalid-request" | "no-launch-shape" | "not-waiting" | "wrong-question" | "wrong-arity" | "not-an-option" | "state-moved" | "unsigned" | "unknown-device" | "revoked-device" | "bad-signature" | "stale-request" | "replayed" | "replay-store-full" | "unknown-verb" | "forbidden-scope" | "launch-disabled" | "repo-not-allowed" | "run-active" | "not-resumable" | "confirm-expired" | "confirm-mismatch" | "unattended-profile-missing" | "workspace-not-trusted" | "no-selection" | "repo-not-configured" | "invalid-config" | "not-installed" | "repo-ambiguous" | "unreadable-settings" | "not-removable" | "not-steerable" | "too-many-streams" | "no-review" | "upstream-failed" | "tool-not-allowed" | "busy" | "review-incomplete" | "host-not-allowed" | "internal";
1300
+ error: "unauthorized" | "forbidden-host" | "forbidden-origin" | "not-found" | "method-not-allowed" | "invalid-query" | "unsupported-version" | "unsupported-media-type" | "invalid-body" | "invalid-request" | "no-launch-shape" | "not-waiting" | "wrong-question" | "wrong-arity" | "not-an-option" | "state-moved" | "unsigned" | "unknown-device" | "revoked-device" | "bad-signature" | "stale-request" | "replayed" | "replay-store-full" | "unknown-verb" | "forbidden-scope" | "launch-disabled" | "repo-not-allowed" | "run-active" | "not-resumable" | "confirm-expired" | "confirm-mismatch" | "unattended-profile-missing" | "workspace-not-trusted" | "no-selection" | "repo-not-configured" | "invalid-config" | "not-installed" | "repo-ambiguous" | "unreadable-settings" | "not-removable" | "not-steerable" | "too-many-streams" | "no-review" | "upstream-failed" | "tool-not-allowed" | "busy" | "review-incomplete" | "host-not-allowed" | "internal" | "unknown-job" | "command-not-installed" | "invalid-repo" | "invalid-args" | "room-busy" | "sandbox-unavailable" | "launch-failed" | "guard-missing" | "name-taken" | "already-registered" | "not-registered";
1034
1301
  message: string;
1035
1302
  errors?: Array<string>;
1036
1303
  supported?: Array<number>;
1037
1304
  /** What fixes the refusal: for unattended-profile-missing the installer step that writes the unattended permission profile; for workspace-not-trusted opening Claude Code once in the repository to accept the trust prompt (the path is left out under redact). */
1038
1305
  remedy?: string;
1039
1306
  }
1307
+
1308
+ /** What GET /v1/settings returns and POST /v1/settings returns after a change: the output language, the fable rung and model routing. A preview (the first POST) adds the change it would make and a confirmToken. */
1309
+ export interface OfficeSettings {
1310
+ contractVersion: string;
1311
+ language: "en" | "tr";
1312
+ /** Whether the fable rung is in play. */
1313
+ model: "on" | "off";
1314
+ /** Whether model routing is on. */
1315
+ route: "on" | "off";
1316
+ change?: {
1317
+ setting: "language" | "model" | "route";
1318
+ from: string;
1319
+ to: string;
1320
+ /** Present when turning routing on brings back a saved free provider chain: the confirmation names it. */
1321
+ freeChain?: {
1322
+ enabled: true;
1323
+ allowTraining: boolean;
1324
+ };
1325
+ };
1326
+ confirmToken?: string;
1327
+ expiresAt?: string;
1328
+ }
1329
+
1330
+ /** The body of POST /v1/settings. Step 1 names the setting and its new value; step 2 sends only the confirmToken step 1 returned. */
1331
+ export interface OfficeSettingsRequest {
1332
+ contractVersion: string;
1333
+ setting?: "language" | "model" | "route";
1334
+ value?: "en" | "tr" | "on" | "off";
1335
+ /** The confirmToken the preview returned. Single use, bound to the route. */
1336
+ confirmToken?: string;
1337
+ }
@@ -0,0 +1,106 @@
1
+ /**
2
+ * bg-launch.mjs - start one unattended `claude --bg` session.
3
+ *
4
+ * The autopilot runner and the office both start background sessions, and
5
+ * both go through here so there is one way to do it: the caller's
6
+ * precondition (the unattended sandbox check) runs first and a failing one
7
+ * starts nothing; the binary is started without a shell, with the argv and
8
+ * environment the caller built; `claude --bg` prints the session and returns,
9
+ * so a bounded synchronous wait is enough.
10
+ *
11
+ * @module pipeline/lib/bg-launch
12
+ */
13
+
14
+ import { spawn, spawnSync } from "node:child_process";
15
+
16
+ import { resolveClaudeBin } from "./claude-sessions.mjs";
17
+
18
+ export const LAUNCH_TIMEOUT_MS = 2 * 60 * 1000;
19
+
20
+ /**
21
+ * @param {{argv: string[], cwd: string, env: Record<string,string|undefined>,
22
+ * precondition?: (o: {cwd: string}) => {ok: boolean, reason?: string},
23
+ * bin?: string, timeoutMs?: number}} args
24
+ * @returns {{status: number|null, stdout?: string, stderr?: string, error?: Error, blocked?: string}}
25
+ */
26
+ export function startBackground({
27
+ argv,
28
+ cwd,
29
+ env,
30
+ precondition,
31
+ bin = resolveClaudeBin(env),
32
+ timeoutMs = LAUNCH_TIMEOUT_MS,
33
+ }) {
34
+ if (!Array.isArray(argv) || !argv.every((a) => typeof a === "string")) {
35
+ throw new TypeError("argv is a list of strings");
36
+ }
37
+ if (precondition) {
38
+ const check = precondition({ cwd });
39
+ if (!check.ok) return { status: 1, stderr: check.reason, blocked: check.reason };
40
+ }
41
+ return spawnSync(bin, argv, {
42
+ cwd,
43
+ env,
44
+ encoding: "utf-8",
45
+ stdio: ["ignore", "pipe", "pipe"],
46
+ timeout: timeoutMs,
47
+ shell: false,
48
+ });
49
+ }
50
+
51
+ /**
52
+ * startBackground for a caller that must not block, such as the contract
53
+ * server answering other requests meanwhile. It settles when the launcher
54
+ * exits rather than when its output pipes close, and keeps draining them
55
+ * afterwards, so a process left holding a pipe never writes into a closed one.
56
+ *
57
+ * @param {Parameters<typeof startBackground>[0]} args
58
+ * @returns {Promise<ReturnType<typeof startBackground>>}
59
+ */
60
+ export function startBackgroundAsync({
61
+ argv,
62
+ cwd,
63
+ env,
64
+ precondition,
65
+ bin = resolveClaudeBin(env),
66
+ timeoutMs = LAUNCH_TIMEOUT_MS,
67
+ }) {
68
+ if (!Array.isArray(argv) || !argv.every((a) => typeof a === "string")) {
69
+ throw new TypeError("argv is a list of strings");
70
+ }
71
+ if (precondition) {
72
+ const check = precondition({ cwd });
73
+ if (!check.ok)
74
+ return Promise.resolve({ status: 1, stderr: check.reason, blocked: check.reason });
75
+ }
76
+ return new Promise((resolve) => {
77
+ let stdout = "";
78
+ let stderr = "";
79
+ let settled = false;
80
+ const child = spawn(bin, argv, { cwd, env, stdio: ["ignore", "pipe", "pipe"], shell: false });
81
+ const done = (result) => {
82
+ if (settled) return;
83
+ settled = true;
84
+ clearTimeout(timer);
85
+ for (const s of [child.stdout, child.stderr]) {
86
+ s?.removeAllListeners("data");
87
+ s?.resume();
88
+ }
89
+ resolve(result);
90
+ };
91
+ const timer = setTimeout(() => {
92
+ child.kill("SIGTERM");
93
+ done({
94
+ status: null,
95
+ stdout,
96
+ stderr,
97
+ error: new Error("the launcher timed out"),
98
+ timedOut: true,
99
+ });
100
+ }, timeoutMs);
101
+ child.stdout.setEncoding("utf-8").on("data", (d) => (stdout += d));
102
+ child.stderr.setEncoding("utf-8").on("data", (d) => (stderr += d));
103
+ child.on("error", (error) => done({ status: null, stdout, stderr, error }));
104
+ child.on("exit", (status) => setImmediate(() => done({ status, stdout, stderr })));
105
+ });
106
+ }
@@ -0,0 +1,49 @@
1
+ /**
2
+ * code-digest.mjs - one digest for the code a long-lived server loaded.
3
+ *
4
+ * The contract server imports modules from scripts/ and lib/, and an ES module
5
+ * keeps the copy it loaded at start. A digest of the server file alone misses
6
+ * a release that changed only an imported module, so this hashes every .mjs
7
+ * file in the server's directory and in the lib/ directory beside it, by name
8
+ * and content. office-control compares it with the digest the running server
9
+ * recorded; the server compares it with its own start-up value for
10
+ * /office/version.
11
+ *
12
+ * @module pipeline/lib/code-digest
13
+ */
14
+
15
+ import { createHash } from "node:crypto";
16
+ import { readFileSync, readdirSync } from "node:fs";
17
+ import { dirname, join } from "node:path";
18
+
19
+ /**
20
+ * @param {string} scriptsDir the directory holding contract-server.mjs
21
+ * @returns {string|null} 16 hex characters, or null when nothing could be read
22
+ */
23
+ export function codeDigest(scriptsDir) {
24
+ const hash = createHash("sha256");
25
+ let read = 0;
26
+ for (const dir of [scriptsDir, join(dirname(scriptsDir), "lib")]) {
27
+ let names;
28
+ try {
29
+ names = readdirSync(dir)
30
+ .filter((n) => n.endsWith(".mjs"))
31
+ .sort();
32
+ } catch {
33
+ continue;
34
+ }
35
+ for (const n of names) {
36
+ try {
37
+ const body = readFileSync(join(dir, n));
38
+ hash
39
+ .update(`${dir === scriptsDir ? "scripts" : "lib"}/${n}\0`)
40
+ .update(body)
41
+ .update("\0");
42
+ read += 1;
43
+ } catch {
44
+ // A file that vanished mid-read changes the digest by its absence.
45
+ }
46
+ }
47
+ }
48
+ return read ? hash.digest("hex").slice(0, 16) : null;
49
+ }
@@ -10,7 +10,7 @@
10
10
  */
11
11
 
12
12
  import { existsSync, readFileSync } from "node:fs";
13
- import { spawnSync } from "node:child_process";
13
+ import { execFile, spawnSync } from "node:child_process";
14
14
  import { homedir } from "node:os";
15
15
  import { join } from "node:path";
16
16
 
@@ -27,7 +27,7 @@ export function resolveToken(name, envVar) {
27
27
  try {
28
28
  const key = JSON.parse(readFileSync(prefsPath, "utf8"))?.global?.keychainMapping?.[name];
29
29
  if (key) {
30
- const r = spawnSync("bash", [store, "get", key], { encoding: "utf8" });
30
+ const r = spawnSync("bash", [store, "get", key], { encoding: "utf8", timeout: 10_000 });
31
31
  const token = (r.stdout || "").trim();
32
32
  if (r.status === 0 && token) return token;
33
33
  }
@@ -37,3 +37,43 @@ export function resolveToken(name, envVar) {
37
37
  }
38
38
  return process.env[envVar] || null;
39
39
  }
40
+
41
+ /**
42
+ * resolveToken for a request path: the store is read without blocking the
43
+ * event loop and given a deadline, so a store waiting on an unlock prompt
44
+ * yields "no token" instead of stalling the caller.
45
+ *
46
+ * @param {string} name
47
+ * @param {string} envVar
48
+ * @param {{timeoutMs?: number}} [opts]
49
+ * @returns {Promise<string|null>}
50
+ */
51
+ export async function resolveTokenAsync(
52
+ name,
53
+ envVar,
54
+ { timeoutMs = 5000, env = process.env } = {},
55
+ ) {
56
+ const home = env.HOME || homedir();
57
+ const store = join(home, ".claude", "lib", "credential-store.sh");
58
+ const prefsPath = env.MULTI_AGENT_PREFS || join(home, ".claude", "multi-agent-preferences.json");
59
+ if (existsSync(store) && existsSync(prefsPath)) {
60
+ let key = null;
61
+ try {
62
+ key = JSON.parse(readFileSync(prefsPath, "utf8"))?.global?.keychainMapping?.[name] ?? null;
63
+ } catch {
64
+ key = null;
65
+ }
66
+ if (key) {
67
+ const token = await new Promise((resolve) => {
68
+ execFile(
69
+ "bash",
70
+ [store, "get", key],
71
+ { encoding: "utf8", timeout: timeoutMs, killSignal: "SIGKILL", env },
72
+ (err, stdout) => resolve(err ? "" : String(stdout || "").trim()),
73
+ );
74
+ });
75
+ if (token) return token;
76
+ }
77
+ }
78
+ return env[envVar] || null;
79
+ }