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.
- package/CHANGELOG.md +222 -0
- package/NOTICE.md +21 -0
- package/README.md +44 -2
- package/agents/analyst.md +1 -1
- package/agents/cold-verifier.md +3 -1
- package/agents/critic.md +1 -1
- package/agents/executor.md +1 -1
- package/agents/explorer.md +1 -1
- package/agents/planner.md +1 -1
- package/agents/reviewer.md +1 -1
- package/agents/security-reviewer.md +1 -1
- package/agents/test-engineer.md +1 -1
- package/agents/verifier.md +1 -1
- package/agents/writer.md +1 -1
- package/dist/index.mjs +68113 -60774
- package/docs/README.md +2 -0
- package/docs/actions-reference.md +31 -0
- package/docs/commands-reference.md +17 -6
- package/docs/resource-formats.md +13 -0
- package/package.json +4 -2
- package/schema.json +503 -91
- package/scripts/resource-sampler.mjs +36 -2
- package/skills/requirements-to-task-packet/SKILL.md +26 -0
- package/skills/widget-rendering/SKILL.md +7 -7
- package/src/agents/agent-config.ts +2 -1
- package/src/agents/discover-agents.ts +23 -14
- package/src/config/config-merge.ts +183 -0
- package/src/config/config-validation.ts +687 -0
- package/src/config/config.ts +22 -864
- package/src/config/defaults.ts +43 -2
- package/src/config/drift-detector.ts +1 -1
- package/src/config/env-vars.ts +691 -0
- package/src/config/role-tools.ts +11 -9
- package/src/config/sanitize-project-config.ts +172 -0
- package/src/config/types.ts +49 -1
- package/src/extension/async-notifier.ts +25 -2
- package/src/extension/crew-cleanup.ts +13 -0
- package/src/extension/crew-vibes/config.ts +2 -1
- package/src/extension/crew-vibes/footer.ts +19 -0
- package/src/extension/crew-vibes/index.ts +11 -1
- package/src/extension/plan-orchestrate.ts +132 -0
- package/src/extension/register.ts +8 -0
- package/src/extension/registration/command-registration.ts +1 -0
- package/src/extension/registration/commands/dashboard.ts +158 -0
- package/src/extension/registration/commands/index.ts +35 -0
- package/src/extension/registration/commands/manage.ts +303 -0
- package/src/extension/registration/commands/run.ts +228 -0
- package/src/extension/registration/commands/shared.ts +639 -0
- package/src/extension/registration/commands/status.ts +60 -0
- package/src/extension/registration/commands.ts +13 -1224
- package/src/extension/registration/foreground-run-controller.ts +10 -2
- package/src/extension/registration/lifecycle-handlers.ts +178 -17
- package/src/extension/registration/runtime-cleanup.ts +23 -5
- package/src/extension/registration/subagent-tools.ts +218 -9
- package/src/extension/registration/team-tool.ts +5 -1
- package/src/extension/registration/ui.ts +5 -4
- package/src/extension/rpc-hmac.ts +5 -3
- package/src/extension/team-tool/api/heartbeat.ts +47 -10
- package/src/extension/team-tool/api/plan-approval.ts +9 -0
- package/src/extension/team-tool/api/task-claims.ts +109 -40
- package/src/extension/team-tool/cancel.ts +84 -50
- package/src/extension/team-tool/dispatch/index.ts +1 -0
- package/src/extension/team-tool/dispatch/run.ts +4 -1
- package/src/extension/team-tool/doctor.ts +103 -1
- package/src/extension/team-tool/orchestrate.ts +66 -1
- package/src/extension/team-tool/plans.ts +192 -0
- package/src/extension/team-tool/respond.ts +197 -65
- package/src/extension/team-tool/run-deadline.ts +35 -3
- package/src/extension/team-tool/run-intent.ts +63 -0
- package/src/extension/team-tool/run.ts +74 -20
- package/src/extension/team-tool/status.ts +84 -26
- package/src/extension/team-tool.ts +11 -2
- package/src/hooks/registry.ts +1 -6
- package/src/i18n.ts +9 -0
- package/src/prompt/prompt-runtime.ts +521 -2
- package/src/prompt/worker-events-channel.ts +173 -0
- package/src/runtime/README.md +8 -8
- package/src/runtime/async-runner.ts +7 -3
- package/src/runtime/background-runner.ts +42 -14
- package/src/runtime/broker/broker-issuer.ts +9 -2
- package/src/runtime/broker/crew-broker-tokens.ts +43 -6
- package/src/runtime/broker/crew-broker.ts +838 -10
- package/src/runtime/broker/wait-status-cache.ts +157 -0
- package/src/runtime/budget-enforcement.ts +281 -0
- package/src/runtime/child-pi/child-pi-constants.ts +8 -0
- package/src/runtime/child-pi/child-pi-spawn.ts +60 -14
- package/src/runtime/child-pi/child-pi-streams.ts +21 -1
- package/src/runtime/child-pi/child-pi-timers.ts +324 -0
- package/src/runtime/child-pi/child-pi.ts +97 -201
- package/src/runtime/child-pi/mock-fixtures.ts +16 -2
- package/src/runtime/crew-agent-records.ts +259 -14
- package/src/runtime/delegate-spawn.ts +148 -0
- package/src/runtime/detached-run-results.ts +90 -0
- package/src/runtime/deterministic-ast.ts +2 -1
- package/src/runtime/dispatch-batch.ts +945 -0
- package/src/runtime/finalize-run.ts +557 -0
- package/src/runtime/goal-workflow/adaptive-plan.ts +116 -15
- package/src/runtime/goal-workflow/dynamic-workflow-runner.ts +8 -3
- package/src/runtime/goal-workflow/goal-state-store.ts +1 -1
- package/src/runtime/group-join.ts +11 -125
- package/src/runtime/live-session/live-session-runtime.ts +26 -1
- package/src/runtime/merge-gate.ts +32 -10
- package/src/runtime/merge-loop.ts +130 -0
- package/src/runtime/model/model-budget-summary.ts +53 -0
- package/src/runtime/model/model-fallback.ts +36 -2
- package/src/runtime/model/pi-args.ts +10 -0
- package/src/runtime/model/provider-extensions.ts +10 -0
- package/src/runtime/orphan-worker-registry.ts +1 -1
- package/src/runtime/output/output-validator.ts +45 -0
- package/src/runtime/parent-guard.ts +3 -1
- package/src/runtime/peer-dep.ts +2 -1
- package/src/runtime/per-write-validator.ts +0 -5
- package/src/runtime/pi-spawn.ts +61 -15
- package/src/runtime/plan-approval.ts +125 -0
- package/src/runtime/plan-replan.ts +151 -0
- package/src/runtime/process-status.ts +16 -1
- package/src/runtime/recovery/checkpoint.ts +0 -18
- package/src/runtime/recovery/crash-recovery.ts +111 -46
- package/src/runtime/run-tracker.ts +77 -10
- package/src/runtime/scheduler-context.ts +98 -0
- package/src/runtime/scheduling/coalesce-tasks.ts +5 -0
- package/src/runtime/scheduling/global-worker-cap.ts +2 -1
- package/src/runtime/scheduling/nested-slots.ts +70 -0
- package/src/runtime/scheduling/run-coalesced-task-group.ts +64 -13
- package/src/runtime/scheduling/task-graph-scheduler.ts +0 -10
- package/src/runtime/settings-store.ts +219 -0
- package/src/runtime/spawn-policy.ts +217 -0
- package/src/runtime/stale-reconciler.ts +87 -6
- package/src/runtime/subagent-manager.ts +25 -1
- package/src/runtime/task-output-context.ts +230 -9
- package/src/runtime/task-packet.ts +23 -1
- package/src/runtime/task-runner/child-executor.ts +106 -7
- package/src/runtime/task-runner/post-execution.ts +125 -1
- package/src/runtime/task-runner/pre-execution.ts +39 -1
- package/src/runtime/task-runner/prompt-builder.ts +51 -1
- package/src/runtime/task-runner/retrieval-orchestrator.ts +72 -18
- package/src/runtime/task-runner/spec-evidence.ts +403 -0
- package/src/runtime/task-runner/state-helpers.ts +26 -24
- package/src/runtime/task-runner.ts +11 -0
- package/src/runtime/team-runner.ts +132 -1673
- package/src/runtime/verification/spec-sandbox.ts +255 -0
- package/src/runtime/verification/verification-gates.ts +3 -2
- package/src/runtime/verification/verification-worktree.ts +2 -1
- package/src/runtime/workflow-phase-advance.ts +100 -0
- package/src/runtime/workspace-tree.ts +9 -0
- package/src/schema/config-schema.ts +66 -26
- package/src/schema/sensitive-config-paths.ts +64 -0
- package/src/schema/team-tool-schema.ts +13 -3
- package/src/state/README.md +4 -10
- package/src/state/atomic-write.ts +20 -3
- package/src/state/contracts.ts +38 -0
- package/src/state/coordination/mailbox.ts +12 -2
- package/src/state/event-log/cursor.ts +223 -0
- package/src/state/event-log/event-log-rotation.ts +12 -4
- package/src/state/event-log/event-log.ts +152 -369
- package/src/state/event-log/sequence-cache.ts +373 -0
- package/src/state/event-log/worker-atomic-writer.ts +2 -1
- package/src/state/stores/active-run-registry.ts +3 -2
- package/src/state/stores/manifest-io.ts +237 -0
- package/src/state/stores/ownership-map.ts +162 -0
- package/src/state/stores/plan-store.ts +241 -0
- package/src/state/stores/run-cache.ts +0 -90
- package/src/state/stores/spec-store.ts +189 -0
- package/src/state/stores/state-store.ts +139 -232
- package/src/state/types.ts +199 -0
- package/src/ui/dashboard-panes/plan-pane.ts +136 -0
- package/src/ui/dashboard-panes/progress-pane.ts +6 -0
- package/src/ui/dashboard-panes/transcript-pane.ts +31 -0
- package/src/ui/dock-footer.ts +49 -0
- package/src/ui/heartbeat-aggregator.ts +9 -1
- package/src/ui/inline-panel/agent-pane.ts +375 -0
- package/src/ui/inline-panel/agent-transcript.ts +338 -0
- package/src/ui/inline-panel/agent-view-overlay.ts +225 -0
- package/src/ui/inline-panel/crew-editor.ts +192 -0
- package/src/ui/inline-panel/index.ts +290 -0
- package/src/ui/inline-panel/panel-rows.ts +37 -0
- package/src/ui/inline-panel/panel-selection.ts +157 -0
- package/src/ui/inline-panel/panel-store.ts +111 -0
- package/src/ui/inline-panel/view-session-store.ts +36 -0
- package/src/ui/keybinding-map.ts +54 -13
- package/src/ui/pi-ui-compat.ts +9 -0
- package/src/ui/powerbar-publisher.ts +52 -1
- package/src/ui/run-dashboard.ts +31 -5
- package/src/ui/run-snapshot-cache.ts +57 -30
- package/src/ui/snapshot-types.ts +6 -1
- package/src/ui/widget/index.ts +176 -22
- package/src/ui/widget/task-list.ts +198 -0
- package/src/ui/widget/widget-formatters.ts +240 -4
- package/src/ui/widget/widget-renderer.ts +243 -38
- package/src/ui/widget/widget-types.ts +11 -0
- package/src/utils/child-process-shield.ts +106 -0
- package/src/utils/file-coalescer.ts +0 -4
- package/src/utils/fs-errno.ts +66 -0
- package/src/utils/fs-watch.ts +1 -1
- package/src/utils/internal-error.ts +3 -1
- package/src/utils/paths.ts +11 -3
- package/src/utils/redaction.ts +7 -0
- package/src/utils/safe-abort.ts +45 -0
- package/src/utils/task-name-generator.ts +1 -8
- package/src/workflows/discover-workflows.ts +20 -2
- package/src/workflows/validate-workflow.ts +7 -1
- package/src/workflows/workflow-config.ts +17 -0
- package/src/workflows/workflow-serializer.ts +3 -0
- package/src/worktree/worktree-manager.ts +22 -0
- package/workflows/default.workflow.md +36 -26
- package/workflows/strict-fast-fix.workflow.md +26 -0
- package/src/agents/agent-search.ts +0 -98
- package/src/benchmark/benchmark-runner.ts +0 -313
- package/src/benchmark/feedback-loop.ts +0 -73
- package/src/config/resilient-parser.ts +0 -117
- package/src/extension/crew-vibes/cat-frames.ts +0 -18
- package/src/extension/result-watcher.ts +0 -139
- package/src/observability/exporters/prometheus-exporter.ts +0 -54
- package/src/observability/metric-retention.ts +0 -64
- package/src/runtime/compaction/compaction-summary.ts +0 -278
- package/src/runtime/errors/crew-errors.ts +0 -162
- package/src/runtime/live-session/intercom-bridge.ts +0 -187
- package/src/runtime/loop-gates.ts +0 -128
- package/src/runtime/metric-parser.ts +0 -36
- package/src/runtime/output/stream-preview.ts +0 -184
- package/src/runtime/output/tool-progress.ts +0 -278
- package/src/runtime/phase-tracker.ts +0 -385
- package/src/runtime/pipeline-runner.ts +0 -523
- package/src/runtime/process/process-lifecycle.ts +0 -491
- package/src/runtime/recovery/retry-runner.ts +0 -330
- package/src/runtime/run-drift.ts +0 -219
- package/src/runtime/task-quality.ts +0 -199
- package/src/runtime/task-runner/run-projection.ts +0 -128
- package/src/runtime/verification/post-checks.ts +0 -142
- package/src/state/coordination/schedule.ts +0 -166
- package/src/state/event-log/jsonl-writer.ts +0 -115
- package/src/state/hook-instinct-bridge.ts +0 -94
- package/src/state/hook-integrations.ts +0 -51
- package/src/state/session-state-map.ts +0 -51
- package/src/state/stores/blob-store.ts +0 -308
- package/src/state/stores/instinct-store.ts +0 -275
- package/src/state/stores/observation-store.ts +0 -176
- package/src/state/tiered-eval.ts +0 -480
- package/src/state/types-eval.ts +0 -58
- package/src/tools/safe-bash-extension.ts +0 -54
- package/src/tools/safe-bash.ts +0 -505
- package/src/ui/agent-management-overlay.ts +0 -160
- package/src/ui/crew-footer.ts +0 -102
- package/src/ui/crew-select-list.ts +0 -114
- package/src/ui/dashboard-panes/capability-pane.ts +0 -77
- package/src/ui/transcript-entries.ts +0 -256
- package/src/utils/conflict-detect.ts +0 -721
- package/src/utils/fingerprint.ts +0 -180
- package/src/utils/gh-protocol.ts +0 -556
- package/src/utils/project-detector.ts +0 -160
- package/src/utils/sse-parser.ts +0 -131
- package/src/workflows/cost-estimator.ts +0 -34
- package/src/workflows/intermediate-store.ts +0 -166
package/src/state/types.ts
CHANGED
|
@@ -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;
|