pi-crew 0.9.68 → 0.10.2

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 (253) hide show
  1. package/CHANGELOG.md +222 -0
  2. package/NOTICE.md +21 -0
  3. package/README.md +44 -2
  4. package/agents/analyst.md +1 -1
  5. package/agents/cold-verifier.md +3 -1
  6. package/agents/critic.md +1 -1
  7. package/agents/executor.md +1 -1
  8. package/agents/explorer.md +1 -1
  9. package/agents/planner.md +1 -1
  10. package/agents/reviewer.md +1 -1
  11. package/agents/security-reviewer.md +1 -1
  12. package/agents/test-engineer.md +1 -1
  13. package/agents/verifier.md +1 -1
  14. package/agents/writer.md +1 -1
  15. package/dist/index.mjs +68113 -60774
  16. package/docs/README.md +2 -0
  17. package/docs/actions-reference.md +31 -0
  18. package/docs/commands-reference.md +17 -6
  19. package/docs/resource-formats.md +13 -0
  20. package/package.json +4 -2
  21. package/schema.json +503 -91
  22. package/scripts/resource-sampler.mjs +36 -2
  23. package/skills/requirements-to-task-packet/SKILL.md +26 -0
  24. package/skills/widget-rendering/SKILL.md +7 -7
  25. package/src/agents/agent-config.ts +2 -1
  26. package/src/agents/discover-agents.ts +23 -14
  27. package/src/config/config-merge.ts +183 -0
  28. package/src/config/config-validation.ts +687 -0
  29. package/src/config/config.ts +22 -864
  30. package/src/config/defaults.ts +43 -2
  31. package/src/config/drift-detector.ts +1 -1
  32. package/src/config/env-vars.ts +691 -0
  33. package/src/config/role-tools.ts +11 -9
  34. package/src/config/sanitize-project-config.ts +172 -0
  35. package/src/config/types.ts +49 -1
  36. package/src/extension/async-notifier.ts +25 -2
  37. package/src/extension/crew-cleanup.ts +13 -0
  38. package/src/extension/crew-vibes/config.ts +2 -1
  39. package/src/extension/crew-vibes/footer.ts +19 -0
  40. package/src/extension/crew-vibes/index.ts +11 -1
  41. package/src/extension/plan-orchestrate.ts +132 -0
  42. package/src/extension/register.ts +8 -0
  43. package/src/extension/registration/command-registration.ts +1 -0
  44. package/src/extension/registration/commands/dashboard.ts +158 -0
  45. package/src/extension/registration/commands/index.ts +35 -0
  46. package/src/extension/registration/commands/manage.ts +303 -0
  47. package/src/extension/registration/commands/run.ts +228 -0
  48. package/src/extension/registration/commands/shared.ts +639 -0
  49. package/src/extension/registration/commands/status.ts +60 -0
  50. package/src/extension/registration/commands.ts +13 -1224
  51. package/src/extension/registration/foreground-run-controller.ts +10 -2
  52. package/src/extension/registration/lifecycle-handlers.ts +178 -17
  53. package/src/extension/registration/runtime-cleanup.ts +23 -5
  54. package/src/extension/registration/subagent-tools.ts +218 -9
  55. package/src/extension/registration/team-tool.ts +5 -1
  56. package/src/extension/registration/ui.ts +5 -4
  57. package/src/extension/rpc-hmac.ts +5 -3
  58. package/src/extension/team-tool/api/heartbeat.ts +47 -10
  59. package/src/extension/team-tool/api/plan-approval.ts +9 -0
  60. package/src/extension/team-tool/api/task-claims.ts +109 -40
  61. package/src/extension/team-tool/cancel.ts +84 -50
  62. package/src/extension/team-tool/dispatch/index.ts +1 -0
  63. package/src/extension/team-tool/dispatch/run.ts +4 -1
  64. package/src/extension/team-tool/doctor.ts +103 -1
  65. package/src/extension/team-tool/orchestrate.ts +66 -1
  66. package/src/extension/team-tool/plans.ts +192 -0
  67. package/src/extension/team-tool/respond.ts +197 -65
  68. package/src/extension/team-tool/run-deadline.ts +35 -3
  69. package/src/extension/team-tool/run-intent.ts +63 -0
  70. package/src/extension/team-tool/run.ts +74 -20
  71. package/src/extension/team-tool/status.ts +84 -26
  72. package/src/extension/team-tool.ts +11 -2
  73. package/src/hooks/registry.ts +1 -6
  74. package/src/i18n.ts +9 -0
  75. package/src/prompt/prompt-runtime.ts +521 -2
  76. package/src/prompt/worker-events-channel.ts +173 -0
  77. package/src/runtime/README.md +8 -8
  78. package/src/runtime/async-runner.ts +7 -3
  79. package/src/runtime/background-runner.ts +42 -14
  80. package/src/runtime/broker/broker-issuer.ts +9 -2
  81. package/src/runtime/broker/crew-broker-tokens.ts +43 -6
  82. package/src/runtime/broker/crew-broker.ts +838 -10
  83. package/src/runtime/broker/wait-status-cache.ts +157 -0
  84. package/src/runtime/budget-enforcement.ts +281 -0
  85. package/src/runtime/child-pi/child-pi-constants.ts +8 -0
  86. package/src/runtime/child-pi/child-pi-spawn.ts +60 -14
  87. package/src/runtime/child-pi/child-pi-streams.ts +21 -1
  88. package/src/runtime/child-pi/child-pi-timers.ts +324 -0
  89. package/src/runtime/child-pi/child-pi.ts +97 -201
  90. package/src/runtime/child-pi/mock-fixtures.ts +16 -2
  91. package/src/runtime/crew-agent-records.ts +259 -14
  92. package/src/runtime/delegate-spawn.ts +148 -0
  93. package/src/runtime/detached-run-results.ts +90 -0
  94. package/src/runtime/deterministic-ast.ts +2 -1
  95. package/src/runtime/dispatch-batch.ts +945 -0
  96. package/src/runtime/finalize-run.ts +557 -0
  97. package/src/runtime/goal-workflow/adaptive-plan.ts +116 -15
  98. package/src/runtime/goal-workflow/dynamic-workflow-runner.ts +8 -3
  99. package/src/runtime/goal-workflow/goal-state-store.ts +1 -1
  100. package/src/runtime/group-join.ts +11 -125
  101. package/src/runtime/live-session/live-session-runtime.ts +26 -1
  102. package/src/runtime/merge-gate.ts +32 -10
  103. package/src/runtime/merge-loop.ts +130 -0
  104. package/src/runtime/model/model-budget-summary.ts +53 -0
  105. package/src/runtime/model/model-fallback.ts +36 -2
  106. package/src/runtime/model/pi-args.ts +10 -0
  107. package/src/runtime/model/provider-extensions.ts +10 -0
  108. package/src/runtime/orphan-worker-registry.ts +1 -1
  109. package/src/runtime/output/output-validator.ts +45 -0
  110. package/src/runtime/parent-guard.ts +3 -1
  111. package/src/runtime/peer-dep.ts +2 -1
  112. package/src/runtime/per-write-validator.ts +0 -5
  113. package/src/runtime/pi-spawn.ts +61 -15
  114. package/src/runtime/plan-approval.ts +125 -0
  115. package/src/runtime/plan-replan.ts +151 -0
  116. package/src/runtime/process-status.ts +16 -1
  117. package/src/runtime/recovery/checkpoint.ts +0 -18
  118. package/src/runtime/recovery/crash-recovery.ts +111 -46
  119. package/src/runtime/run-tracker.ts +77 -10
  120. package/src/runtime/scheduler-context.ts +98 -0
  121. package/src/runtime/scheduling/coalesce-tasks.ts +5 -0
  122. package/src/runtime/scheduling/global-worker-cap.ts +2 -1
  123. package/src/runtime/scheduling/nested-slots.ts +70 -0
  124. package/src/runtime/scheduling/run-coalesced-task-group.ts +64 -13
  125. package/src/runtime/scheduling/task-graph-scheduler.ts +0 -10
  126. package/src/runtime/settings-store.ts +219 -0
  127. package/src/runtime/spawn-policy.ts +217 -0
  128. package/src/runtime/stale-reconciler.ts +87 -6
  129. package/src/runtime/subagent-manager.ts +25 -1
  130. package/src/runtime/task-output-context.ts +230 -9
  131. package/src/runtime/task-packet.ts +23 -1
  132. package/src/runtime/task-runner/child-executor.ts +106 -7
  133. package/src/runtime/task-runner/post-execution.ts +125 -1
  134. package/src/runtime/task-runner/pre-execution.ts +39 -1
  135. package/src/runtime/task-runner/prompt-builder.ts +51 -1
  136. package/src/runtime/task-runner/retrieval-orchestrator.ts +72 -18
  137. package/src/runtime/task-runner/spec-evidence.ts +403 -0
  138. package/src/runtime/task-runner/state-helpers.ts +26 -24
  139. package/src/runtime/task-runner.ts +11 -0
  140. package/src/runtime/team-runner.ts +132 -1673
  141. package/src/runtime/verification/spec-sandbox.ts +255 -0
  142. package/src/runtime/verification/verification-gates.ts +3 -2
  143. package/src/runtime/verification/verification-worktree.ts +2 -1
  144. package/src/runtime/workflow-phase-advance.ts +100 -0
  145. package/src/runtime/workspace-tree.ts +9 -0
  146. package/src/schema/config-schema.ts +66 -26
  147. package/src/schema/sensitive-config-paths.ts +64 -0
  148. package/src/schema/team-tool-schema.ts +13 -3
  149. package/src/state/README.md +4 -10
  150. package/src/state/atomic-write.ts +20 -3
  151. package/src/state/contracts.ts +38 -0
  152. package/src/state/coordination/mailbox.ts +12 -2
  153. package/src/state/event-log/cursor.ts +223 -0
  154. package/src/state/event-log/event-log-rotation.ts +12 -4
  155. package/src/state/event-log/event-log.ts +152 -369
  156. package/src/state/event-log/sequence-cache.ts +373 -0
  157. package/src/state/event-log/worker-atomic-writer.ts +2 -1
  158. package/src/state/stores/active-run-registry.ts +3 -2
  159. package/src/state/stores/manifest-io.ts +237 -0
  160. package/src/state/stores/ownership-map.ts +162 -0
  161. package/src/state/stores/plan-store.ts +241 -0
  162. package/src/state/stores/run-cache.ts +0 -90
  163. package/src/state/stores/spec-store.ts +189 -0
  164. package/src/state/stores/state-store.ts +139 -232
  165. package/src/state/types.ts +199 -0
  166. package/src/ui/dashboard-panes/plan-pane.ts +136 -0
  167. package/src/ui/dashboard-panes/progress-pane.ts +6 -0
  168. package/src/ui/dashboard-panes/transcript-pane.ts +31 -0
  169. package/src/ui/dock-footer.ts +49 -0
  170. package/src/ui/heartbeat-aggregator.ts +9 -1
  171. package/src/ui/inline-panel/agent-pane.ts +375 -0
  172. package/src/ui/inline-panel/agent-transcript.ts +338 -0
  173. package/src/ui/inline-panel/agent-view-overlay.ts +225 -0
  174. package/src/ui/inline-panel/crew-editor.ts +192 -0
  175. package/src/ui/inline-panel/index.ts +290 -0
  176. package/src/ui/inline-panel/panel-rows.ts +37 -0
  177. package/src/ui/inline-panel/panel-selection.ts +157 -0
  178. package/src/ui/inline-panel/panel-store.ts +111 -0
  179. package/src/ui/inline-panel/view-session-store.ts +36 -0
  180. package/src/ui/keybinding-map.ts +54 -13
  181. package/src/ui/pi-ui-compat.ts +9 -0
  182. package/src/ui/powerbar-publisher.ts +52 -1
  183. package/src/ui/run-dashboard.ts +31 -5
  184. package/src/ui/run-snapshot-cache.ts +57 -30
  185. package/src/ui/snapshot-types.ts +6 -1
  186. package/src/ui/widget/index.ts +176 -22
  187. package/src/ui/widget/task-list.ts +198 -0
  188. package/src/ui/widget/widget-formatters.ts +240 -4
  189. package/src/ui/widget/widget-renderer.ts +243 -38
  190. package/src/ui/widget/widget-types.ts +11 -0
  191. package/src/utils/child-process-shield.ts +106 -0
  192. package/src/utils/file-coalescer.ts +0 -4
  193. package/src/utils/fs-errno.ts +66 -0
  194. package/src/utils/fs-watch.ts +1 -1
  195. package/src/utils/internal-error.ts +3 -1
  196. package/src/utils/paths.ts +11 -3
  197. package/src/utils/redaction.ts +7 -0
  198. package/src/utils/safe-abort.ts +45 -0
  199. package/src/utils/task-name-generator.ts +1 -8
  200. package/src/workflows/discover-workflows.ts +20 -2
  201. package/src/workflows/validate-workflow.ts +7 -1
  202. package/src/workflows/workflow-config.ts +17 -0
  203. package/src/workflows/workflow-serializer.ts +3 -0
  204. package/src/worktree/worktree-manager.ts +22 -0
  205. package/workflows/default.workflow.md +36 -26
  206. package/workflows/strict-fast-fix.workflow.md +26 -0
  207. package/src/agents/agent-search.ts +0 -98
  208. package/src/benchmark/benchmark-runner.ts +0 -313
  209. package/src/benchmark/feedback-loop.ts +0 -73
  210. package/src/config/resilient-parser.ts +0 -117
  211. package/src/extension/crew-vibes/cat-frames.ts +0 -18
  212. package/src/extension/result-watcher.ts +0 -139
  213. package/src/observability/exporters/prometheus-exporter.ts +0 -54
  214. package/src/observability/metric-retention.ts +0 -64
  215. package/src/runtime/compaction/compaction-summary.ts +0 -278
  216. package/src/runtime/errors/crew-errors.ts +0 -162
  217. package/src/runtime/live-session/intercom-bridge.ts +0 -187
  218. package/src/runtime/loop-gates.ts +0 -128
  219. package/src/runtime/metric-parser.ts +0 -36
  220. package/src/runtime/output/stream-preview.ts +0 -184
  221. package/src/runtime/output/tool-progress.ts +0 -278
  222. package/src/runtime/phase-tracker.ts +0 -385
  223. package/src/runtime/pipeline-runner.ts +0 -523
  224. package/src/runtime/process/process-lifecycle.ts +0 -491
  225. package/src/runtime/recovery/retry-runner.ts +0 -330
  226. package/src/runtime/run-drift.ts +0 -219
  227. package/src/runtime/task-quality.ts +0 -199
  228. package/src/runtime/task-runner/run-projection.ts +0 -128
  229. package/src/runtime/verification/post-checks.ts +0 -142
  230. package/src/state/coordination/schedule.ts +0 -166
  231. package/src/state/event-log/jsonl-writer.ts +0 -115
  232. package/src/state/hook-instinct-bridge.ts +0 -94
  233. package/src/state/hook-integrations.ts +0 -51
  234. package/src/state/session-state-map.ts +0 -51
  235. package/src/state/stores/blob-store.ts +0 -308
  236. package/src/state/stores/instinct-store.ts +0 -275
  237. package/src/state/stores/observation-store.ts +0 -176
  238. package/src/state/tiered-eval.ts +0 -480
  239. package/src/state/types-eval.ts +0 -58
  240. package/src/tools/safe-bash-extension.ts +0 -54
  241. package/src/tools/safe-bash.ts +0 -505
  242. package/src/ui/agent-management-overlay.ts +0 -160
  243. package/src/ui/crew-footer.ts +0 -102
  244. package/src/ui/crew-select-list.ts +0 -114
  245. package/src/ui/dashboard-panes/capability-pane.ts +0 -77
  246. package/src/ui/transcript-entries.ts +0 -256
  247. package/src/utils/conflict-detect.ts +0 -721
  248. package/src/utils/fingerprint.ts +0 -180
  249. package/src/utils/gh-protocol.ts +0 -556
  250. package/src/utils/project-detector.ts +0 -160
  251. package/src/utils/sse-parser.ts +0 -131
  252. package/src/workflows/cost-estimator.ts +0 -34
  253. package/src/workflows/intermediate-store.ts +0 -166
@@ -1,6 +1,7 @@
1
1
  import type { CrewAgentProgress } from "../runtime/crew-agent-runtime.ts";
2
2
  import type { WorkerHeartbeatState } from "../runtime/heartbeat/worker-heartbeat.ts";
3
3
  import type { CrashClass } from "../runtime/recovery/crash-classification.ts";
4
+ import type { FatalFsCause } from "../utils/fs-errno.ts";
4
5
  import type { TeamRunStatus, TeamTaskStatus } from "./contracts.ts";
5
6
  import type { TaskClaimState } from "./coordination/task-claims.ts";
6
7
  import type { CoherenceMark, RolloutEntry } from "./decision-ledger.ts";
@@ -55,6 +56,73 @@ export interface TaskOutputSchema {
55
56
  example?: string;
56
57
  }
57
58
 
59
+ export type SpecPriority = "must" | "should" | "could";
60
+
61
+ /** T4/R6 (ADR-6 §1): workspace-level spec record — state/specs/<id>.json.
62
+ * Revision machinery mirrors ADR-4 §1 PlanRecord (append-only revision list,
63
+ * copy-forward linkage, stable requirement/acceptance ids). */
64
+ export interface SpecRecord {
65
+ id: string;
66
+ version: number;
67
+ revisionOf?: number;
68
+ title: string;
69
+ requirements: Array<{ id: string; text: string; priority: SpecPriority }>;
70
+ acceptance: Array<{
71
+ id: string;
72
+ requirementId: string;
73
+ /** Free-text description of what counts as evidence (non-strict). */
74
+ check: string;
75
+ /** Strict mode (ADR-6 §4): machine-checkable form. */
76
+ command?: string;
77
+ expectedDigest?: string;
78
+ expectedExitCode?: number;
79
+ idempotent?: boolean;
80
+ }>;
81
+ source: { kind: "manual" | "generated"; by?: string; from?: string };
82
+ /** INFORMATIONAL copy of the store-mint provenance sidecar — the strict
83
+ * gate NEVER trusts this field alone (ADR-6 §4 provenance enforcement). */
84
+ trusted?: boolean;
85
+ }
86
+
87
+ /** Immutable per-task freeze (ADR-6 §1): embedded into the TaskPacket at
88
+ * dispatch; the strict gate executes ONLY snapshot-frozen commands. */
89
+ export interface SpecSnapshotItem {
90
+ requirement: SpecRecord["requirements"][number];
91
+ acceptance: SpecRecord["acceptance"][number];
92
+ }
93
+
94
+ export interface SpecSnapshot {
95
+ specId: string;
96
+ version: number;
97
+ frozenAt: string;
98
+ /** Provenance v2 (ADR-6 §4 + erratum): trust decided ONCE at freeze from
99
+ * the user-store digest sidecar; the strict gate reads only this frozen
100
+ * bit — post-freeze mint/delete cannot affect a running task. */
101
+ trustedAtFreeze: boolean;
102
+ items: SpecSnapshotItem[];
103
+ }
104
+
105
+ /** T4/R6 (ADR-6 §3): non-strict coverage-gate report persisted on the task.
106
+ * Non-strict NEVER blocks — gaps surface as the `unverified` badge only. */
107
+ export interface SpecGateResult {
108
+ /** "coverage" = non-strict (§3); "strict" arrives with the §4 sandbox. */
109
+ mode: "coverage" | "strict";
110
+ /** false for spec-less tasks — gate not applicable (regression guard). */
111
+ applicable: boolean;
112
+ footerPresent: boolean;
113
+ /** Cited ids in citation order (duplicates preserved). */
114
+ citedIds: string[];
115
+ /** Must-acceptance ids NOT cited — mechanically-detectable gap. */
116
+ missingMustIds: string[];
117
+ /** Cited ids that exist in no snapshot — fabrication signal (§2). */
118
+ unknownIds: string[];
119
+ /** Set ONLY on mechanically-detectable gaps (missing footer / missing
120
+ * must-ids / unknown ids). Full-coverage fabrication passes with NO badge. */
121
+ badge?: "unverified";
122
+ /** acceptanceId → one-line evidence text (verifier advisory input, §5). */
123
+ evidence: Record<string, string>;
124
+ }
125
+
58
126
  export interface TaskPacket {
59
127
  objective: string;
60
128
  scope: TaskScope;
@@ -70,6 +138,17 @@ export interface TaskPacket {
70
138
  expectedArtifacts: string[];
71
139
  verification: VerificationContract;
72
140
  outputSchema?: TaskOutputSchema;
141
+ /** T4/R6 (ADR-6): workspace spec ids this task is held to (frozen below). */
142
+ specRefs?: string[];
143
+ /** T4/R6 (ADR-6 §7): strict mode — coverage AND machine-check (§4). */
144
+ specStrict?: boolean;
145
+ /** Declared specRefs that resolved to NO record at freeze (typo / not
146
+ * imported / corrupted). Non-strict: unverified badge. Strict: the gate
147
+ * fails — a strict workflow must never silently degrade to ungated. */
148
+ unresolvedSpecRefs?: string[];
149
+ /** Frozen snapshots embedded at dispatch — later spec edits never rewrite
150
+ * what a running task was held to. */
151
+ specSnapshots?: SpecSnapshot[];
73
152
  }
74
153
 
75
154
  export type PolicyDecisionAction = "retry" | "reassign" | "escalate" | "block" | "notify" | "cleanup" | "closeout" | "fail";
@@ -164,6 +243,61 @@ export interface PlanApprovalState {
164
243
  }
165
244
 
166
245
  export type CrewActivityState = "active" | "active_long_running" | "needs_attention" | "stale";
246
+
247
+ /** T2/R4 (ADR-4 docs/decisions/2026-08-17-plan-object.md §1): status vocabulary
248
+ * shared by plan phases and items. `dropped` marks items removed by a re-plan
249
+ * revision (kept for traceability + diff, never re-dispatched). */
250
+ export type PlanItemStatus = "pending" | "active" | "done" | "dropped";
251
+
252
+ export interface PlanPhaseRecord {
253
+ id: string;
254
+ title: string;
255
+ itemIds: string[];
256
+ status: PlanItemStatus;
257
+ }
258
+
259
+ export interface PlanItemRecord {
260
+ /** Stable across revisions (ADR-4 §3 producer contract) — the scheduler
261
+ * copies carried-over linkage forward at revision switch. */
262
+ id: string;
263
+ /** External reference (e.g. tagged section id in the source plan doc). */
264
+ ref?: string;
265
+ title: string;
266
+ /** Scheduler-owned (single writer, inside the run lock — ADR-4 §3).
267
+ * Producers NEVER set taskIds. */
268
+ taskIds: string[];
269
+ /** R6/T4 forward hook; T2 writes it empty (ADR-4 §1). */
270
+ specIds: string[];
271
+ acceptance: string[];
272
+ status: PlanItemStatus;
273
+ }
274
+
275
+ /** Plan-record side of the approval gate. Vocabulary note (ADR-4 §8): the
276
+ * manifest side keeps `PlanApprovalState` ("cancelled" for deny); the record
277
+ * side uses "rejected" — `plans reject` dual-writes both. */
278
+ export interface PlanApprovalRecord {
279
+ status: "pending" | "approved" | "rejected";
280
+ by?: string;
281
+ at: string;
282
+ planVersion: number;
283
+ }
284
+
285
+ /** One revision in the append-only list persisted at
286
+ * `<stateRoot>/plans/plans.json` (plan-store.ts). History is never mutated in
287
+ * place — EXCEPT the current revision's `items[].taskIds`, the scheduler's
288
+ * single-writer linkage field (ADR-4 §3). */
289
+ export interface PlanRecord {
290
+ id: string;
291
+ runId: string;
292
+ version: number;
293
+ revisionOf?: { id: string; version: number };
294
+ title: string;
295
+ phases: PlanPhaseRecord[];
296
+ items: PlanItemRecord[];
297
+ approval?: PlanApprovalRecord;
298
+ createdAt: string;
299
+ authorTaskId?: string;
300
+ }
167
301
  export type CrewAttentionReason = "idle" | "tool_failures" | "completion_guard" | "heartbeat_stale" | "plan_approval_pending";
168
302
 
169
303
  export interface CrewAttentionEventData {
@@ -226,7 +360,24 @@ export interface TeamRunManifest {
226
360
  eventsPath: string;
227
361
  artifacts: ArtifactDescriptor[];
228
362
  async?: AsyncRunState;
363
+ /** @deprecated-plan-pointer T2/R4 (ADR-4 §2): dual-write era — PlanRecord at
364
+ * `<stateRoot>/plans/plans.json` is authoritative; this field is kept (never
365
+ * dropped) as the pre-v2 fallback and legacy UI surface. Deprecate-only. */
229
366
  planApproval?: PlanApprovalState;
367
+ /** T2/R4 (ADR-4 §2): pointer to the CURRENT plan revision in
368
+ * `<stateRoot>/plans/plans.json`. Plan-record-first readers fall back to
369
+ * `planApproval` above when absent or no record exists (dual-read migration;
370
+ * the manifest field is deprecated, never dropped). */
371
+ plan?: { id: string; version: number };
372
+ /** WP-2/R2 (ADR-0 item 3): run-level park pointer while a worker is blocked
373
+ * in the `ask` tool. Purely additive coordination state — `status` above is
374
+ * NEVER flipped to express waiting (the run stays "running": registry entry,
375
+ * sidebar visibility and live-executor.isCurrent() all preserved). */
376
+ waitState?: {
377
+ taskId: string;
378
+ questionId: string;
379
+ askedAt: string;
380
+ };
230
381
  /** Pi session that created the run, when available. Used to prevent cross-session destructive actions. */
231
382
  ownerSessionId?: string;
232
383
  /** pi-crew skill override selected when the run was created. false disables injected skill instructions. */
@@ -403,6 +554,8 @@ export interface TeamTaskState {
403
554
  role: string;
404
555
  agent: string;
405
556
  title: string;
557
+ /** Full step body (the detailed plan text behind `title`). */
558
+ description?: string;
406
559
  displayName?: string;
407
560
  status: TeamTaskStatus;
408
561
  dependsOn: string[];
@@ -422,6 +575,10 @@ export interface TeamTaskState {
422
575
  jsonEvents?: number;
423
576
  agentProgress?: CrewAgentProgress;
424
577
  error?: string;
578
+ /** Fatal fs failure cause (bug-026 sub-issue B): set when the failure was
579
+ * classified as enospc/edquot/emfile/enfile so operators see "failed
580
+ * (disk full)" instead of a generic timeout diagnostic. */
581
+ failureCause?: FatalFsCause;
425
582
  claim?: TaskClaimState;
426
583
  heartbeat?: WorkerHeartbeatState;
427
584
  checkpoint?: TaskCheckpointState;
@@ -430,11 +587,35 @@ export interface TeamTaskState {
430
587
  terminalEvidence?: OperationTerminalEvidence[];
431
588
  taskPacket?: TaskPacket;
432
589
  verification?: VerificationEvidence;
590
+ /** T4/R6 (ADR-6 §3): SPEC-EVIDENCE coverage report. Present only when the
591
+ task carried specSnapshots; non-strict gaps show as badge:"unverified". */
592
+ specGate?: SpecGateResult;
433
593
  graph?: TaskGraphNode;
434
594
  adaptive?: {
435
595
  phase: string;
436
596
  task: string;
437
597
  };
598
+ /** T2/R4 (ADR-4 §3): the plan item this task implements. Set by producers
599
+ * when they create tasks from PlanRecord items; the scheduler reads it at
600
+ * dispatch to link `items[].taskIds` (single writer, run-locked). */
601
+ planItem?: string;
602
+ /** T2/R4 (ADR-4 §4): set when the wrap-up advisory was delivered because the
603
+ * item was dropped by a re-plan (soft cancel; exactly-once across ticks).
604
+ * Doubles as the terminal marker "cancelled-by-replan". */
605
+ replanDroppedAt?: string;
606
+ /** T3/R5 (ADR-5 §3): delegation depth of THIS task. Workers are depth 1;
607
+ * delegate-spawned grandchildren are depth 2+. The spawn policy computes
608
+ * a grandchild's depth from this record field — NEVER from the requesting
609
+ * worker's env or self-report (design §7 rev-2 P0-2). Additive: absent on
610
+ * pre-v2 records (readers treat absent as depth 1). */
611
+ depth?: number;
612
+ /** T3/R5 (ADR-5 §5): per-task budget allocation for delegate accounting.
613
+ * `tokensGranted` is reserved at delegate admission; `tokensSpent` rolls up
614
+ * grandchild usage as events arrive (single writer, run lock). Additive. */
615
+ allocation?: {
616
+ tokensGranted: number;
617
+ tokensSpent: number;
618
+ };
438
619
  policy?: {
439
620
  retryCount?: number;
440
621
  lastDecision?: PolicyDecision;
@@ -457,6 +638,24 @@ export interface TeamTaskState {
457
638
  /** Steering messages queued before the task's session was ready.
458
639
  * Delivered when the session initializes (mirrors pi-subagents3 pendingSteers pattern). */
459
640
  pendingSteers?: string[];
641
+
642
+ /** WP-2/R2 (ADR-0 docs/decisions/2026-08-17-waiting-producer-ask.md item 3):
643
+ * park marker set while the worker is blocked in the `ask` tool awaiting a
644
+ * leader answer. Purely additive — `status` carries "waiting" for the whole
645
+ * park and the parked tool's terminal report flips it back via the normal
646
+ * task lifecycle. */
647
+ waiting?: {
648
+ /** Correlation id (randomUUID) — matches manifest.waitState.questionId and
649
+ * the mailbox `kind:"response"` entry carrying the answer. */
650
+ questionId: string;
651
+ /** ISO timestamp when the park was accepted (broker wait.request). */
652
+ askedAt: string;
653
+ /** Ms-epoch answer deadline. Server-clamped root-side by the broker to
654
+ * now + min(timeoutSec, 3600) — worker-controlled values never exceed 1h. */
655
+ deadline: number;
656
+ /** Optional answer choices the worker presented with the question. */
657
+ options?: string[];
658
+ };
460
659
  }
461
660
 
462
661
  export interface ControlReservation {
@@ -0,0 +1,136 @@
1
+ /**
2
+ * plan-pane.ts — dashboard pane 7 "Plan" (WP-7 / R7, H4).
3
+ *
4
+ * Tree: phase → item → tasks, with per-item progress derived from linked
5
+ * tasks (deriveItemProgress) and a depth badge on grandchild tasks (depth>1,
6
+ * T3/R5). Approval pending → `A approve · n deny` hint line (same actions the
7
+ * progress pane exposes — plan-approve/plan-deny are pane-scoped to BOTH
8
+ * panes in keybinding-map). `X` toggles the multi-revision diff view
9
+ * (pane-scoped; V collides with root liveConversation, e with root events —
10
+ * X verified free).
11
+ *
12
+ * Flag-off: the dashboard never mounts this pane (key 7 still switches, but
13
+ * the snapshot carries no plans slice — the pane degrades to a hint line).
14
+ * Uncolored by design, mirroring the other dashboard panes.
15
+ */
16
+
17
+ import { isPlanApprovalPending } from "../../runtime/plan-approval.ts";
18
+ import { deriveItemProgress } from "../../state/stores/plan-store.ts";
19
+ import type { PlanItemStatus, TeamTaskState } from "../../state/types.ts";
20
+ import type { RunUiSnapshot } from "../snapshot-types.ts";
21
+
22
+ const ITEM_GLYPH: Record<PlanItemStatus, string> = {
23
+ pending: "○",
24
+ active: "▸",
25
+ done: "✓",
26
+ dropped: "✗",
27
+ };
28
+
29
+ const TASK_GLYPH: Record<string, string> = {
30
+ queued: "○",
31
+ running: "▸",
32
+ waiting: "◷",
33
+ needs_attention: "⚠",
34
+ completed: "✓",
35
+ failed: "✗",
36
+ cancelled: "⊘",
37
+ skipped: "·",
38
+ };
39
+
40
+ function taskLine(task: TeamTaskState, indent: string): string {
41
+ const glyph = TASK_GLYPH[task.status] ?? "?";
42
+ // T3/R5 depth badge: grandchildren (depth 2+) surface explicitly so the
43
+ // tree shows delegation nesting without another pane.
44
+ const depth = typeof task.depth === "number" && task.depth > 1 ? ` d${task.depth}` : "";
45
+ const role = task.displayName ?? task.role;
46
+ return `${indent}${glyph} ${task.id}${depth} ${role} [${task.status}]`;
47
+ }
48
+
49
+ export interface PlanPaneOptions {
50
+ /** X-toggled multi-revision diff view (current vs previous revision). */
51
+ diff?: boolean;
52
+ }
53
+
54
+ /** Item-level diff between the current revision and its predecessor. */
55
+ export function planRevisionDiff(snapshot: RunUiSnapshot): string[] {
56
+ const records = snapshot.plans ?? [];
57
+ // Current = highest-version record in the SNAPSHOT slice (in-memory truth —
58
+ // the pane must not re-read disk for what it already has).
59
+ const current = records.length ? records.reduce((a, b) => (b.version > a.version ? b : a)) : undefined;
60
+ if (!current) return ["Plan diff: no plan records"];
61
+ const prevVersion = current.revisionOf?.version;
62
+ const previous = prevVersion !== undefined ? records.find((r) => r.version === prevVersion) : undefined;
63
+ if (!previous) {
64
+ return [`Plan diff: v${current.version} has no prior revision`];
65
+ }
66
+ const prevItems = new Map(previous.items.map((i) => [i.id, i]));
67
+ const lines = [`Plan diff: v${previous.version} → v${current.version}`];
68
+ for (const item of current.items) {
69
+ const before = prevItems.get(item.id);
70
+ if (!before) {
71
+ lines.push(` + ${item.id} ${item.title} [${item.status}]`);
72
+ continue;
73
+ }
74
+ if (before.status !== item.status || before.taskIds.length !== item.taskIds.length) {
75
+ lines.push(
76
+ ` ~ ${item.id} ${item.title} [${before.status}→${item.status} · ${before.taskIds.length}→${item.taskIds.length} tasks]`,
77
+ );
78
+ }
79
+ prevItems.delete(item.id);
80
+ }
81
+ for (const dropped of prevItems.values()) {
82
+ lines.push(` - ${dropped.id} ${dropped.title} (dropped in v${current.version})`);
83
+ }
84
+ return lines;
85
+ }
86
+
87
+ export function renderPlanPane(snapshot: RunUiSnapshot, options: PlanPaneOptions = {}): string[] {
88
+ const records = snapshot.plans;
89
+ if (!records || records.length === 0) {
90
+ // Flag-off or plan-less run — one honest line, no I/O.
91
+ return ["Plan pane: no plan records (PI_CREW_PLAN_UI=1; plan-producing runs only)"];
92
+ }
93
+ if (options.diff) return planRevisionDiff(snapshot);
94
+
95
+ // Highest-version record in the snapshot slice (no disk re-read).
96
+ const current = records.reduce((a, b) => (b.version > a.version ? b : a));
97
+ if (!current) return ["Plan pane: no plan records"];
98
+ const progress = deriveItemProgress(current, snapshot.tasks);
99
+ const tasksById = new Map(snapshot.tasks.map((t) => [t.id, t]));
100
+ const itemById = new Map(current.items.map((i) => [i.id, i]));
101
+
102
+ const pending = isPlanApprovalPending(snapshot.manifest);
103
+ const header = `Plan pane: ${current.title} @v${current.version} (${current.phases.length} phases · ${current.items.length} items)`;
104
+ const approval = pending ? ["⚠ plan approval pending — A approve · n deny"] : [];
105
+
106
+ const lines: string[] = [header, ...approval];
107
+ for (const phase of current.phases) {
108
+ const glyph = ITEM_GLYPH[phase.status] ?? "?";
109
+ lines.push(`${glyph} ${phase.title}`);
110
+ for (const itemId of phase.itemIds) {
111
+ const item = itemById.get(itemId);
112
+ if (!item) continue;
113
+ const p = progress.get(itemId);
114
+ const counts = p ? ` ${p.done}/${p.total}${p.failed ? ` ✗${p.failed}` : ""}${p.running ? ` ▸${p.running}` : ""}` : "";
115
+ const droppedTag = item.status === "dropped" ? " ✗ dropped" : "";
116
+ lines.push(` ${ITEM_GLYPH[item.status] ?? "?"} ${item.title}${counts}${droppedTag}`);
117
+ for (const taskId of item.taskIds) {
118
+ const task = tasksById.get(taskId);
119
+ if (task) lines.push(taskLine(task, " "));
120
+ }
121
+ }
122
+ }
123
+ // Items not linked to any phase (producer-free-format plans) — still visible.
124
+ const phased = new Set(current.phases.flatMap((p) => p.itemIds));
125
+ const orphans = current.items.filter((i) => !phased.has(i.id));
126
+ if (orphans.length) {
127
+ lines.push("(unphased)");
128
+ for (const item of orphans) {
129
+ const p = progress.get(item.id);
130
+ const droppedTag = item.status === "dropped" ? " ✗ dropped" : "";
131
+ lines.push(` ${ITEM_GLYPH[item.status] ?? "?"} ${item.title}${p ? ` ${p.done}/${p.total}` : ""}${droppedTag}`);
132
+ }
133
+ }
134
+ lines.push("X revision diff");
135
+ return lines;
136
+ }
@@ -1,4 +1,5 @@
1
1
  import { computePhaseProgress, formatPhaseProgressLine } from "../../runtime/phase-progress.ts";
2
+ import { isPlanApprovalPending } from "../../runtime/plan-approval.ts";
2
3
  import { renderDwfPhaseLines } from "../dwf-phase-display.ts";
3
4
  import type { RunUiSnapshot } from "../snapshot-types.ts";
4
5
 
@@ -23,8 +24,13 @@ export function renderProgressPane(snapshot: RunUiSnapshot | undefined): string[
23
24
  // DWF logical phases (round-15 P1-4): derived from dwf.phase_* events.
24
25
  // Null/absent for non-DWF runs → zero visible change.
25
26
  const dwfPhaseLines = snapshot.dwfPhaseState ? renderDwfPhaseLines(snapshot.dwfPhaseState) : [];
27
+ // WP-3 (H4-subset): plan-approval gate banner. Mirrors the health-pane
28
+ // hint pattern — plain foreground text, no color codes (pane output is
29
+ // uncolored by design). One line while the run is parked on approval.
30
+ const planBanner = isPlanApprovalPending(snapshot.manifest) ? ["⚠ plan approval pending — A approve / n deny"] : [];
26
31
  return [
27
32
  `Progress pane: ${progress.completed}/${progress.total} completed · running=${progress.running} queued=${progress.queued} failed=${progress.failed}`,
33
+ ...planBanner,
28
34
  ...dwfPhaseLines,
29
35
  ...phaseHeader,
30
36
  ...cancellationLine,
@@ -1,9 +1,40 @@
1
1
  import type { RunUiSnapshot } from "../snapshot-types.ts";
2
2
 
3
+ /**
4
+ * Transcript pane (pane 4). WP-8 (R8): per-attempt model transparency —
5
+ * tasks that burned fallback attempts show which model each attempt used
6
+ * (`requested ✓` / `failed ✗ → next`), newest first, capped to the pane
7
+ * budget. Rendering stays uncolored (pane convention); status glyphs are
8
+ * colorized by the shared colorizeStatusGlyphs pass.
9
+ */
10
+
11
+ const ATTEMPT_SUMMARY_TASKS = 3;
12
+
13
+ function modelAttemptLines(snapshot: RunUiSnapshot): string[] {
14
+ const withAttempts = snapshot.tasks
15
+ .filter((task) => (task.modelAttempts?.length ?? 0) > 0)
16
+ .slice(-ATTEMPT_SUMMARY_TASKS)
17
+ .reverse();
18
+ if (!withAttempts.length) return [];
19
+ const lines = ["model attempts (newest first):"];
20
+ for (const task of withAttempts) {
21
+ const attempts = (task.modelAttempts ?? [])
22
+ .map(
23
+ (attempt) =>
24
+ `${attempt.model} ${attempt.success ? "✓" : `✗${attempt.exitCode !== undefined ? `(${attempt.exitCode})` : ""}`}`,
25
+ )
26
+ .join(" → ");
27
+ const resolved = task.modelRouting?.resolved ? ` · resolved ${task.modelRouting.resolved}` : "";
28
+ lines.push(` ${task.id} (${task.role})${resolved}: ${attempts}`);
29
+ }
30
+ return lines;
31
+ }
32
+
3
33
  export function renderTranscriptPane(snapshot: RunUiSnapshot | undefined): string[] {
4
34
  if (!snapshot) return ["Output pane: snapshot unavailable"];
5
35
  return [
6
36
  `Output pane: ${snapshot.recentOutputLines.length} recent lines · press v for transcript viewer · o for raw output`,
37
+ ...modelAttemptLines(snapshot),
7
38
  ...snapshot.recentOutputLines.slice(-12).map((line) => `⎿ ${line}`),
8
39
  ...(snapshot.recentOutputLines.length ? [] : ["No recent output"]),
9
40
  ];
@@ -0,0 +1,49 @@
1
+ /**
2
+ * dock-footer.ts — dependency-free bridge between the crew dock and the
3
+ * crew-vibes footer.
4
+ *
5
+ * When `ui.widgetPlacement` is `"bottom"`, the dock's rendered lines are
6
+ * produced by the crew widget but PAINTED by the crew-vibes footer, below the
7
+ * quota/meter lines — the very bottom of the terminal. The two halves are
8
+ * mounted by different modules (widget/index.ts owns the widget lifecycle,
9
+ * crew-vibes/footer.ts owns the footer), so the connection lives here as a
10
+ * tiny observable registry with no imports:
11
+ * - the widget registers a per-render line provider;
12
+ * - crew-vibes flips the "sink" flag when its footer is (un)installed;
13
+ * - the footer pulls the provider's lines on every render.
14
+ *
15
+ * Safety: the widget falls back to pi's `belowEditor` widget slot whenever no
16
+ * footer sink is active (crew-vibes disabled), so `"bottom"` never silently
17
+ * hides the dock.
18
+ */
19
+
20
+ export type FooterDockLinesProvider = (width: number) => string[];
21
+
22
+ let dockProvider: FooterDockLinesProvider | undefined;
23
+ let sinkActive = false;
24
+
25
+ /** Register the dock line provider (widget side). Pass `undefined` to detach. */
26
+ export function setFooterDockProvider(provider: FooterDockLinesProvider | undefined): void {
27
+ dockProvider = provider;
28
+ }
29
+
30
+ /** Current dock line provider, or undefined when the dock is not in the footer. */
31
+ export function getFooterDockProvider(): FooterDockLinesProvider | undefined {
32
+ return dockProvider;
33
+ }
34
+
35
+ /** Mark whether a footer sink (crew-vibes) is currently installed. */
36
+ export function setFooterDockSinkActive(active: boolean): void {
37
+ sinkActive = active;
38
+ }
39
+
40
+ /** True while crew-vibes' custom footer is installed and can host the dock. */
41
+ export function isFooterDockSinkActive(): boolean {
42
+ return sinkActive;
43
+ }
44
+
45
+ /** Test isolation. */
46
+ export function resetFooterDockRegistry(): void {
47
+ dockProvider = undefined;
48
+ sinkActive = false;
49
+ }
@@ -1,5 +1,6 @@
1
1
  import type { MetricRegistry } from "../observability/metric-registry.ts";
2
2
  import { classifyHeartbeat, heartbeatAgeMs } from "../runtime/heartbeat/heartbeat-gradient.ts";
3
+ import { isTerminalRunStatus } from "../state/contracts.ts";
3
4
  import type { TeamTaskState } from "../state/types.ts";
4
5
  import type { RunUiSnapshot } from "./snapshot-types.ts";
5
6
 
@@ -45,8 +46,15 @@ export function summarizeHeartbeats(snapshot: RunUiSnapshot, opts: HeartbeatSumm
45
46
  worstStaleMs: 0,
46
47
  gradient: { healthy: 0, warn: 0, stale: 0, dead: 0 },
47
48
  };
49
+ // bug-026 sub-issue C: a terminal run must never report dead/missing/stale
50
+ // workers. isActiveTask below already skips terminal tasks (the primary
51
+ // task-level gate — locked in by regression tests), but a stale snapshot can
52
+ // carry "running" task statuses that lag the run manifest's terminal
53
+ // transition. Defense-in-depth: skip ALL task counting when the run manifest
54
+ // itself is terminal. Runs with a non-terminal manifest are unaffected.
55
+ const runTerminal = isTerminalRunStatus(snapshot.manifest.status);
48
56
  for (const task of snapshot.tasks) {
49
- if (!isActiveTask(task)) continue;
57
+ if (runTerminal || !isActiveTask(task)) continue;
50
58
  const heartbeat = task.heartbeat;
51
59
  if (!heartbeat) {
52
60
  summary.missing += 1;