pi-crew 0.2.3 → 0.2.5
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/AGENTS.md +57 -32
- package/CHANGELOG.md +466 -448
- package/LICENSE +21 -21
- package/NOTICE.md +16 -16
- package/README.md +323 -323
- package/docs/FEATURE_INTAKE.md +126 -0
- package/docs/HARNESS.md +86 -0
- package/docs/HARNESS_BACKLOG.md +41 -0
- package/docs/TEST_MATRIX.md +49 -0
- package/docs/actions-reference.md +595 -595
- package/docs/architecture.md +180 -180
- package/docs/code-review-2026-05-11.md +592 -592
- package/docs/commands-reference.md +347 -347
- package/docs/comparison-pi-subagents-vs-pi-crew.md +303 -0
- package/docs/decisions/0001-durable-state.md +41 -0
- package/docs/decisions/0002-child-process-for-async.md +42 -0
- package/docs/decisions/0003-depth-guard.md +36 -0
- package/docs/decisions/0004-execfile-over-exec.md +34 -0
- package/docs/decisions/0005-no-parameter-properties.md +49 -0
- package/docs/decisions/0006-publish-bundled-esm.md +63 -0
- package/docs/decisions/0007-active-run-binary-index.md +54 -0
- package/docs/decisions/0008-child-pi-warm-pool.md +61 -0
- package/docs/decisions/README.md +23 -0
- package/docs/followup-review-round4-2026-05-13.md +107 -0
- package/docs/implementation-plan-top3.md +333 -0
- package/docs/live-mailbox-runtime.md +36 -36
- package/docs/next-upgrade-roadmap.md +808 -808
- package/docs/oh-my-pi-research.md +509 -0
- package/docs/perf/baseline-2026-05.md +113 -0
- package/docs/perf/final-report-2026-05.md +206 -0
- package/docs/perf/sprint-1-report.md +71 -0
- package/docs/perf/sprint-2-report.md +81 -0
- package/docs/perf/sprint-2.5-report.md +53 -0
- package/docs/perf/sprint-3-report.md +36 -0
- package/docs/perf/sprint-4-report.md +47 -0
- package/docs/perf/sprint-5-report.md +51 -0
- package/docs/perf/sprint-6-report.md +94 -0
- package/docs/perf/sprint-7-report.md +74 -0
- package/docs/perf/upgrade-plan-2026-05.md +147 -0
- package/docs/pi-subagents3-deep-analysis.md +508 -0
- package/docs/product/README.md +31 -0
- package/docs/product/platform.md +27 -0
- package/docs/product/runtime-safety.md +37 -0
- package/docs/product/team-run.md +39 -0
- package/docs/product/team-tool.md +37 -0
- package/docs/publishing.md +65 -65
- package/docs/resource-formats.md +134 -134
- package/docs/runtime-analysis-child-vs-live.md +171 -0
- package/docs/runtime-flow.md +148 -148
- package/docs/runtime-migration-in-process-analysis.md +250 -0
- package/docs/stories/README.md +30 -0
- package/docs/stories/backlog.md +36 -0
- package/docs/templates/decision.md +27 -0
- package/docs/templates/story.md +44 -0
- package/docs/templates/validation-report.md +32 -0
- package/docs/usage.md +238 -238
- package/index.ts +7 -6
- package/install.mjs +65 -65
- package/package.json +107 -100
- package/schema.json +222 -222
- package/skills/child-pi-spawning/SKILL.md +213 -0
- package/skills/context-artifact-hygiene/SKILL.md +32 -0
- package/skills/event-log-tracing/SKILL.md +299 -0
- package/skills/git-master/SKILL.md +225 -24
- package/skills/live-agent-lifecycle/SKILL.md +192 -0
- package/skills/mailbox-interactive/SKILL.md +300 -19
- package/skills/model-routing-context/SKILL.md +94 -0
- package/skills/multi-perspective-review/SKILL.md +88 -0
- package/skills/read-only-explorer/SKILL.md +250 -26
- package/skills/safe-bash/SKILL.md +307 -21
- package/skills/verification-before-done/SKILL.md +11 -2
- package/skills/widget-rendering/SKILL.md +258 -0
- package/skills/workspace-isolation/SKILL.md +202 -0
- package/skills/worktree-isolation/SKILL.md +202 -18
- package/src/adapters/claude-adapter.ts +25 -25
- package/src/adapters/codex-adapter.ts +21 -21
- package/src/adapters/cursor-adapter.ts +17 -17
- package/src/adapters/export-util.ts +137 -137
- package/src/adapters/index.ts +15 -15
- package/src/adapters/registry.ts +18 -18
- package/src/adapters/types.ts +23 -23
- package/src/agents/agent-config.ts +38 -38
- package/src/agents/agent-serializer.ts +38 -38
- package/src/agents/discover-agents.ts +121 -118
- package/src/config/config.ts +740 -858
- package/src/config/defaults.ts +96 -96
- package/src/config/drift-detector.ts +211 -211
- package/src/config/markers.ts +327 -327
- package/src/config/resilient-parser.ts +109 -108
- package/src/config/suggestions.ts +74 -74
- package/src/config/types.ts +199 -0
- package/src/extension/async-notifier.ts +123 -89
- package/src/extension/autonomous-policy.ts +169 -169
- package/src/extension/cross-extension-rpc.ts +104 -104
- package/src/extension/help.ts +47 -47
- package/src/extension/import-index.ts +69 -69
- package/src/extension/management.ts +395 -382
- package/src/extension/notification-router.ts +116 -116
- package/src/extension/notification-sink.ts +51 -51
- package/src/extension/project-init.ts +168 -168
- package/src/extension/register.ts +859 -668
- package/src/extension/registration/artifact-cleanup.ts +15 -15
- package/src/extension/registration/command-utils.ts +54 -54
- package/src/extension/registration/commands.ts +559 -452
- package/src/extension/registration/compaction-guard.ts +125 -125
- package/src/extension/registration/subagent-helpers.ts +102 -102
- package/src/extension/registration/subagent-tools.ts +220 -159
- package/src/extension/registration/team-tool.ts +159 -99
- package/src/extension/registration/viewers.ts +29 -0
- package/src/extension/result-watcher.ts +128 -128
- package/src/extension/run-bundle-schema.ts +89 -89
- package/src/extension/run-export.ts +73 -73
- package/src/extension/run-import.ts +84 -84
- package/src/extension/run-index.ts +94 -94
- package/src/extension/run-maintenance.ts +142 -142
- package/src/extension/session-summary.ts +8 -8
- package/src/extension/team-manager-command.ts +96 -96
- package/src/extension/team-recommendation.ts +188 -188
- package/src/extension/team-tool/api.ts +5 -2
- package/src/extension/team-tool/cancel.ts +224 -209
- package/src/extension/team-tool/config-patch.ts +36 -36
- package/src/extension/team-tool/context.ts +60 -60
- package/src/extension/team-tool/doctor.ts +242 -242
- package/src/extension/team-tool/handle-settings.ts +421 -195
- package/src/extension/team-tool/inspect.ts +41 -41
- package/src/extension/team-tool/lifecycle-actions.ts +139 -139
- package/src/extension/team-tool/parallel-dispatch.ts +156 -156
- package/src/extension/team-tool/plan.ts +19 -19
- package/src/extension/team-tool/respond.ts +112 -111
- package/src/extension/team-tool/run.ts +246 -229
- package/src/extension/team-tool/status.ts +110 -110
- package/src/extension/team-tool-types.ts +13 -13
- package/src/extension/team-tool.ts +344 -344
- package/src/extension/tool-result.ts +16 -16
- package/src/extension/validate-resources.ts +77 -77
- package/src/hooks/registry.ts +61 -61
- package/src/hooks/types.ts +40 -40
- package/src/i18n.ts +184 -184
- package/src/observability/correlation.ts +35 -35
- package/src/observability/event-to-metric.ts +68 -68
- package/src/observability/exporters/adapter.ts +30 -30
- package/src/observability/exporters/otlp-exporter.ts +106 -92
- package/src/observability/exporters/prometheus-exporter.ts +54 -54
- package/src/observability/metric-registry.ts +87 -87
- package/src/observability/metric-retention.ts +54 -54
- package/src/observability/metric-sink.ts +81 -56
- package/src/observability/metrics-primitives.ts +167 -167
- package/src/prompt/prompt-runtime.ts +72 -72
- package/src/runtime/adaptive-plan.ts +338 -0
- package/src/runtime/agent-control.ts +169 -169
- package/src/runtime/agent-memory.ts +72 -72
- package/src/runtime/agent-observability.ts +114 -114
- package/src/runtime/async-marker.ts +26 -26
- package/src/runtime/async-runner.ts +153 -153
- package/src/runtime/attention-events.ts +28 -28
- package/src/runtime/auto-resume.ts +100 -100
- package/src/runtime/background-runner.ts +122 -89
- package/src/runtime/cancellation.ts +61 -61
- package/src/runtime/capability-inventory.ts +116 -116
- package/src/runtime/child-pi-pool.ts +68 -0
- package/src/runtime/child-pi.ts +541 -461
- package/src/runtime/code-summary.ts +247 -247
- package/src/runtime/compaction-summary.ts +271 -271
- package/src/runtime/concurrency.ts +58 -58
- package/src/runtime/crash-recovery.ts +317 -301
- package/src/runtime/crew-agent-records.ts +379 -281
- package/src/runtime/crew-agent-runtime.ts +60 -60
- package/src/runtime/cross-extension-rpc.ts +72 -0
- package/src/runtime/custom-tools/irc-tool.ts +201 -201
- package/src/runtime/custom-tools/submit-result-tool.ts +90 -90
- package/src/runtime/deadletter.ts +47 -47
- package/src/runtime/delivery-coordinator.ts +176 -176
- package/src/runtime/delta-conflict.ts +360 -360
- package/src/runtime/diagnostic-export.ts +102 -102
- package/src/runtime/direct-run.ts +35 -35
- package/src/runtime/effectiveness.ts +82 -81
- package/src/runtime/errors/crew-errors.ts +166 -0
- package/src/runtime/event-stream-bridge.ts +92 -92
- package/src/runtime/foreground-control.ts +82 -82
- package/src/runtime/green-contract.ts +46 -46
- package/src/runtime/group-join.ts +234 -106
- package/src/runtime/heartbeat-watcher.ts +145 -124
- package/src/runtime/iteration-hooks.ts +267 -267
- package/src/runtime/live-agent-control.ts +88 -88
- package/src/runtime/live-agent-manager.ts +377 -179
- package/src/runtime/live-control-realtime.ts +36 -36
- package/src/runtime/live-session-runtime.ts +676 -600
- package/src/runtime/loop-gates.ts +129 -129
- package/src/runtime/manifest-cache.ts +263 -263
- package/src/runtime/mcp-proxy.ts +113 -113
- package/src/runtime/metric-parser.ts +40 -40
- package/src/runtime/model-fallback.ts +282 -274
- package/src/runtime/model-resolver.ts +118 -0
- package/src/runtime/output-validator.ts +187 -187
- package/src/runtime/overflow-recovery.ts +175 -175
- package/src/runtime/parallel-research.ts +44 -44
- package/src/runtime/parallel-utils.ts +156 -156
- package/src/runtime/parent-guard.ts +80 -80
- package/src/runtime/phase-progress.ts +217 -217
- package/src/runtime/pi-args.ts +165 -165
- package/src/runtime/pi-json-output.ts +111 -111
- package/src/runtime/pi-spawn.ts +167 -167
- package/src/runtime/policy-engine.ts +79 -79
- package/src/runtime/post-checks.ts +125 -125
- package/src/runtime/post-exit-stdio-guard.ts +86 -86
- package/src/runtime/process-status.ts +97 -73
- package/src/runtime/progress-event-coalescer.ts +43 -43
- package/src/runtime/recovery-recipes.ts +74 -74
- package/src/runtime/retry-executor.ts +81 -81
- package/src/runtime/role-permission.ts +39 -39
- package/src/runtime/run-tracker.ts +99 -0
- package/src/runtime/runtime-policy.ts +21 -0
- package/src/runtime/runtime-resolver.ts +94 -91
- package/src/runtime/scheduler.ts +294 -0
- package/src/runtime/semaphore.ts +131 -131
- package/src/runtime/sensitive-paths.ts +92 -92
- package/src/runtime/session-usage.ts +79 -79
- package/src/runtime/settings-store.ts +103 -0
- package/src/runtime/sidechain-output.ts +29 -29
- package/src/runtime/skill-instructions.ts +222 -222
- package/src/runtime/stale-reconciler.ts +198 -189
- package/src/runtime/streaming-output.ts +47 -0
- package/src/runtime/subagent-manager.ts +404 -400
- package/src/runtime/subprocess-tool-registry.ts +67 -67
- package/src/runtime/task-display.ts +38 -38
- package/src/runtime/task-graph-scheduler.ts +122 -122
- package/src/runtime/task-graph.ts +207 -207
- package/src/runtime/task-output-context.ts +177 -177
- package/src/runtime/task-packet.ts +93 -93
- package/src/runtime/task-quality.ts +207 -207
- package/src/runtime/task-runner/capabilities.ts +78 -78
- package/src/runtime/task-runner/live-executor.ts +131 -113
- package/src/runtime/task-runner/progress.ts +119 -119
- package/src/runtime/task-runner/prompt-builder.ts +139 -139
- package/src/runtime/task-runner/prompt-pipeline.ts +64 -64
- package/src/runtime/task-runner/result-utils.ts +14 -14
- package/src/runtime/task-runner/run-projection.ts +103 -103
- package/src/runtime/task-runner/state-helpers.ts +22 -22
- package/src/runtime/task-runner.ts +469 -459
- package/src/runtime/team-runner.ts +693 -945
- package/src/runtime/usage-tracker.ts +71 -0
- package/src/runtime/worker-heartbeat.ts +21 -21
- package/src/runtime/worker-startup.ts +57 -57
- package/src/runtime/workflow-state.ts +187 -187
- package/src/runtime/yield-handler.ts +190 -190
- package/src/schema/config-schema.ts +172 -168
- package/src/schema/team-tool-schema.ts +126 -126
- package/src/schema/validation-types.ts +151 -148
- package/src/skills/discover-skills.ts +67 -67
- package/src/skills/skill-templates.ts +374 -374
- package/src/state/active-run-registry.ts +227 -191
- package/src/state/artifact-store.ts +130 -129
- package/src/state/atomic-write.ts +262 -195
- package/src/state/blob-store.ts +116 -116
- package/src/state/contracts.ts +111 -111
- package/src/state/event-log-rotation.ts +161 -158
- package/src/state/event-log.ts +383 -303
- package/src/state/event-reconstructor.ts +217 -217
- package/src/state/jsonl-writer.ts +82 -82
- package/src/state/locks.ts +146 -146
- package/src/state/mailbox.ts +446 -405
- package/src/state/state-store.ts +364 -351
- package/src/state/task-claims.ts +44 -44
- package/src/state/types.ts +285 -285
- package/src/state/usage.ts +29 -29
- package/src/subagents/async-entry.ts +1 -1
- package/src/subagents/index.ts +3 -3
- package/src/subagents/live/control.ts +1 -1
- package/src/subagents/live/manager.ts +1 -1
- package/src/subagents/live/realtime.ts +1 -1
- package/src/subagents/live/session-runtime.ts +1 -1
- package/src/subagents/manager.ts +1 -1
- package/src/subagents/spawn.ts +1 -1
- package/src/teams/discover-teams.ts +116 -116
- package/src/teams/team-config.ts +27 -27
- package/src/teams/team-serializer.ts +38 -38
- package/src/types/diff.d.ts +18 -18
- package/src/ui/agent-management-overlay.ts +144 -144
- package/src/ui/crew-widget.ts +487 -370
- package/src/ui/dashboard-panes/agents-pane.ts +109 -28
- package/src/ui/dashboard-panes/cancellation-pane.ts +42 -42
- package/src/ui/dashboard-panes/capability-pane.ts +59 -59
- package/src/ui/dashboard-panes/health-pane.ts +30 -30
- package/src/ui/dashboard-panes/mailbox-pane.ts +35 -35
- package/src/ui/dashboard-panes/progress-pane.ts +30 -30
- package/src/ui/dashboard-panes/transcript-pane.ts +10 -10
- package/src/ui/heartbeat-aggregator.ts +63 -63
- package/src/ui/keybinding-map.ts +97 -94
- package/src/ui/live-conversation-overlay.ts +152 -0
- package/src/ui/live-run-sidebar.ts +180 -180
- package/src/ui/mascot.ts +442 -442
- package/src/ui/overlays/agent-picker-overlay.ts +57 -57
- package/src/ui/overlays/confirm-overlay.ts +58 -58
- package/src/ui/overlays/mailbox-compose-overlay.ts +144 -144
- package/src/ui/overlays/mailbox-compose-preview.ts +63 -63
- package/src/ui/overlays/mailbox-detail-overlay.ts +122 -122
- package/src/ui/pi-ui-compat.ts +57 -57
- package/src/ui/powerbar-publisher.ts +221 -197
- package/src/ui/render-scheduler.ts +216 -143
- package/src/ui/run-action-dispatcher.ts +118 -118
- package/src/ui/run-dashboard.ts +526 -464
- package/src/ui/run-event-bus.ts +208 -208
- package/src/ui/run-snapshot-cache.ts +826 -777
- package/src/ui/settings-overlay.ts +721 -0
- package/src/ui/snapshot-types.ts +86 -70
- package/src/ui/theme-adapter.ts +190 -190
- package/src/ui/tool-progress-formatter.ts +89 -0
- package/src/ui/transcript-cache.ts +94 -94
- package/src/ui/transcript-viewer.ts +335 -335
- package/src/utils/conflict-detect.ts +662 -0
- package/src/utils/file-coalescer.ts +86 -86
- package/src/utils/frontmatter.ts +68 -68
- package/src/utils/fs-watch.ts +88 -31
- package/src/utils/gh-protocol.ts +479 -0
- package/src/utils/ids.ts +17 -17
- package/src/utils/incremental-reader.ts +104 -104
- package/src/utils/internal-error.ts +6 -6
- package/src/utils/names.ts +27 -27
- package/src/utils/paths.ts +102 -63
- package/src/utils/redaction.ts +44 -44
- package/src/utils/safe-paths.ts +47 -47
- package/src/utils/scan-cache.ts +136 -136
- package/src/utils/sse-parser.ts +134 -134
- package/src/utils/task-name-generator.ts +337 -337
- package/src/utils/timings.ts +33 -33
- package/src/utils/visual.ts +243 -198
- package/src/workflows/discover-workflows.ts +139 -139
- package/src/workflows/validate-workflow.ts +40 -40
- package/src/workflows/workflow-config.ts +26 -26
- package/src/workflows/workflow-serializer.ts +32 -32
- package/src/worktree/branch-freshness.ts +45 -45
- package/src/worktree/cleanup.ts +75 -75
- package/src/worktree/worktree-manager.ts +188 -188
- package/teams/default.team.md +12 -12
- package/teams/fast-fix.team.md +11 -11
- package/teams/implementation.team.md +18 -18
- package/teams/parallel-research.team.md +14 -14
- package/teams/research.team.md +11 -11
- package/teams/review.team.md +12 -12
- package/tsconfig.json +19 -19
- package/workflows/default.workflow.md +30 -30
- package/workflows/fast-fix.workflow.md +23 -23
- package/workflows/implementation.workflow.md +43 -43
- package/workflows/parallel-research.workflow.md +46 -46
- package/workflows/research.workflow.md +22 -22
- package/workflows/review.workflow.md +30 -30
- package/skills/task-packet/SKILL.md +0 -28
- package/skills/verify-evidence/SKILL.md +0 -27
|
@@ -1,143 +1,216 @@
|
|
|
1
|
-
import { logInternalError } from "../utils/internal-error.ts";
|
|
2
|
-
|
|
3
|
-
export interface RenderSchedulerEventBus {
|
|
4
|
-
on?: (event: string, handler: (payload: unknown) => void) => (() => void) | void;
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
export interface RenderSchedulerOptions {
|
|
8
|
-
debounceMs?: number;
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
private
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
this.
|
|
111
|
-
this.
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
1
|
+
import { logInternalError } from "../utils/internal-error.ts";
|
|
2
|
+
|
|
3
|
+
export interface RenderSchedulerEventBus {
|
|
4
|
+
on?: (event: string, handler: (payload: unknown) => void) => (() => void) | void;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export interface RenderSchedulerOptions {
|
|
8
|
+
debounceMs?: number;
|
|
9
|
+
/**
|
|
10
|
+
* Maximum interval (ms) between auto-refreshes when no event arrives.
|
|
11
|
+
* Can be a function returning a dynamic interval — useful to tick fast
|
|
12
|
+
* while a run is active (e.g., 160ms aligned with spinner) and slow
|
|
13
|
+
* down when idle (e.g., 1000ms) without burning CPU.
|
|
14
|
+
*/
|
|
15
|
+
fallbackMs?: number | (() => number);
|
|
16
|
+
events?: string[];
|
|
17
|
+
onInvalidate?: (payload: unknown) => void;
|
|
18
|
+
/**
|
|
19
|
+
* 1.9: coalesce `onInvalidate` calls per-runId within this window so that
|
|
20
|
+
* bursts of `crew.subagent.completed`-style events touching the same run
|
|
21
|
+
* trigger a single cache invalidate instead of N. Set to 0 to disable.
|
|
22
|
+
*/
|
|
23
|
+
invalidateCoalesceMs?: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const DEFAULT_EVENTS = [
|
|
27
|
+
"crew.run.created",
|
|
28
|
+
"crew.run.completed",
|
|
29
|
+
"crew.run.failed",
|
|
30
|
+
"crew.run.cancelled",
|
|
31
|
+
"crew.subagent.completed",
|
|
32
|
+
"crew.subagent.failed",
|
|
33
|
+
"crew.mailbox.updated",
|
|
34
|
+
"crew.mailbox.message",
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Coordinates UI renders with debounce + fallback polling.
|
|
39
|
+
*
|
|
40
|
+
* Critical: uses recursive setTimeout instead of setInterval + a rendering
|
|
41
|
+
* guard (`rendering` / `pendingRender`) so that when render() takes longer
|
|
42
|
+
* than the fallback interval, callbacks do NOT pile up and storm the event
|
|
43
|
+
* loop. Instead, overlapping schedules are collapsed into a single deferred
|
|
44
|
+
* re-render.
|
|
45
|
+
*/
|
|
46
|
+
export class RenderScheduler {
|
|
47
|
+
private readonly render: () => void;
|
|
48
|
+
private readonly onInvalidate?: (payload: unknown) => void;
|
|
49
|
+
private readonly debounceMs: number;
|
|
50
|
+
private readonly fallbackProvider: () => number;
|
|
51
|
+
private readonly fallbackMs: number;
|
|
52
|
+
private readonly invalidateCoalesceMs: number;
|
|
53
|
+
private debounceTimer: ReturnType<typeof setTimeout> | undefined;
|
|
54
|
+
private fallbackTimer: ReturnType<typeof setTimeout> | undefined;
|
|
55
|
+
private invalidateTimer: ReturnType<typeof setTimeout> | undefined;
|
|
56
|
+
/** runId → most recent payload to forward when the coalesce window flushes. */
|
|
57
|
+
private invalidateBuffer = new Map<string, unknown>();
|
|
58
|
+
private disposed = false;
|
|
59
|
+
private lastEventAt = 0;
|
|
60
|
+
private rendering = false;
|
|
61
|
+
private pendingRender = false;
|
|
62
|
+
private readonly unsubs: Array<() => void> = [];
|
|
63
|
+
|
|
64
|
+
constructor(events: RenderSchedulerEventBus | undefined, render: () => void, options: RenderSchedulerOptions = {}) {
|
|
65
|
+
this.render = render;
|
|
66
|
+
this.onInvalidate = options.onInvalidate;
|
|
67
|
+
this.debounceMs = options.debounceMs ?? 75;
|
|
68
|
+
const fallback = options.fallbackMs ?? 750;
|
|
69
|
+
this.fallbackProvider = typeof fallback === "function" ? fallback : () => fallback;
|
|
70
|
+
this.fallbackMs = typeof fallback === "number" ? fallback : 750;
|
|
71
|
+
this.invalidateCoalesceMs = options.invalidateCoalesceMs ?? 50;
|
|
72
|
+
for (const event of options.events ?? DEFAULT_EVENTS) this.subscribe(events, event);
|
|
73
|
+
this.fallbackTimer = setTimeout(() => this.fallbackLoop(), this.currentFallbackMs());
|
|
74
|
+
this.fallbackTimer.unref();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
private currentFallbackMs(): number {
|
|
78
|
+
try {
|
|
79
|
+
const value = this.fallbackProvider();
|
|
80
|
+
return Number.isFinite(value) && value > 0 ? value : 750;
|
|
81
|
+
} catch (error) {
|
|
82
|
+
logInternalError("render-scheduler.fallbackProvider", error);
|
|
83
|
+
return 750;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
private subscribe(events: RenderSchedulerEventBus | undefined, event: string): void {
|
|
88
|
+
if (!events?.on) return;
|
|
89
|
+
const handler = (payload: unknown): void => this.schedule(payload);
|
|
90
|
+
try {
|
|
91
|
+
const unsub = events.on(event, handler);
|
|
92
|
+
if (typeof unsub === "function") this.unsubs.push(unsub);
|
|
93
|
+
} catch (error) {
|
|
94
|
+
logInternalError("render-scheduler.subscribe", error, event);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** Recursive setTimeout — avoids setInterval timer storms. */
|
|
99
|
+
private fallbackLoop(): void {
|
|
100
|
+
if (this.disposed) return;
|
|
101
|
+
const fallbackMs = this.currentFallbackMs();
|
|
102
|
+
if (Date.now() - this.lastEventAt < fallbackMs) {
|
|
103
|
+
if (this.disposed) return;
|
|
104
|
+
this.fallbackTimer = setTimeout(() => this.fallbackLoop(), fallbackMs);
|
|
105
|
+
this.fallbackTimer.unref();
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
this.schedule();
|
|
109
|
+
if (this.disposed) return;
|
|
110
|
+
this.fallbackTimer = setTimeout(() => this.fallbackLoop(), this.currentFallbackMs());
|
|
111
|
+
this.fallbackTimer.unref();
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
schedule(payload?: unknown): void {
|
|
115
|
+
if (this.disposed) return;
|
|
116
|
+
this.lastEventAt = Date.now();
|
|
117
|
+
this.invalidate(payload);
|
|
118
|
+
if (this.debounceTimer) clearTimeout(this.debounceTimer);
|
|
119
|
+
this.debounceTimer = setTimeout(() => {
|
|
120
|
+
this.debounceTimer = undefined;
|
|
121
|
+
this.flush();
|
|
122
|
+
}, this.debounceMs);
|
|
123
|
+
this.debounceTimer.unref();
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* 1.9: forward `onInvalidate` immediately when the payload has no `runId`
|
|
128
|
+
* (we cannot dedup it). When it carries a runId, buffer the latest payload
|
|
129
|
+
* per runId and flush on a coalesce timer so high-frequency event bursts
|
|
130
|
+
* (`crew.subagent.completed` for parallel tasks) collapse into one
|
|
131
|
+
* invalidate per affected run.
|
|
132
|
+
*/
|
|
133
|
+
private invalidate(payload: unknown): void {
|
|
134
|
+
try {
|
|
135
|
+
const runId = typeof payload === "object" && payload !== null && "runId" in payload && typeof (payload as { runId: unknown }).runId === "string"
|
|
136
|
+
? (payload as { runId: string }).runId
|
|
137
|
+
: undefined;
|
|
138
|
+
if (runId === undefined || this.invalidateCoalesceMs <= 0) {
|
|
139
|
+
this.onInvalidate?.(payload);
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
this.invalidateBuffer.set(runId, payload);
|
|
143
|
+
if (!this.invalidateTimer) {
|
|
144
|
+
this.invalidateTimer = setTimeout(() => this.flushInvalidate(), this.invalidateCoalesceMs);
|
|
145
|
+
this.invalidateTimer.unref();
|
|
146
|
+
}
|
|
147
|
+
} catch (error) {
|
|
148
|
+
logInternalError("render-scheduler.invalidate", error);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
private flushInvalidate(): void {
|
|
153
|
+
this.invalidateTimer = undefined;
|
|
154
|
+
if (this.disposed) {
|
|
155
|
+
this.invalidateBuffer.clear();
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
const buffered = this.invalidateBuffer;
|
|
159
|
+
this.invalidateBuffer = new Map();
|
|
160
|
+
for (const payload of buffered.values()) {
|
|
161
|
+
try {
|
|
162
|
+
this.onInvalidate?.(payload);
|
|
163
|
+
} catch (error) {
|
|
164
|
+
logInternalError("render-scheduler.invalidate.flush", error);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Flush a render. If a render is already in progress the request is
|
|
171
|
+
* collapsed: `pendingRender` is set and the caller that holds
|
|
172
|
+
* `rendering==true` will loop one more time after finishing.
|
|
173
|
+
*/
|
|
174
|
+
flush(): void {
|
|
175
|
+
if (this.disposed) return;
|
|
176
|
+
if (this.rendering) {
|
|
177
|
+
this.pendingRender = true;
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
this.rendering = true;
|
|
181
|
+
this.pendingRender = false;
|
|
182
|
+
let iterations = 0;
|
|
183
|
+
try {
|
|
184
|
+
do {
|
|
185
|
+
this.pendingRender = false;
|
|
186
|
+
this.render();
|
|
187
|
+
iterations += 1;
|
|
188
|
+
// Safety valve: 5 re-renders max per flush to prevent infinite loops
|
|
189
|
+
// if render() itself calls flush() synchronously.
|
|
190
|
+
} while (this.pendingRender && !this.disposed && iterations < 5);
|
|
191
|
+
} catch (error) {
|
|
192
|
+
logInternalError("render-scheduler.render", error);
|
|
193
|
+
} finally {
|
|
194
|
+
this.rendering = false;
|
|
195
|
+
// If we hit the iteration cap, schedule one more render to drain.
|
|
196
|
+
if (iterations >= 5 && this.pendingRender && !this.disposed) {
|
|
197
|
+
this.schedule();
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
dispose(): void {
|
|
203
|
+
if (this.disposed) return;
|
|
204
|
+
this.disposed = true;
|
|
205
|
+
if (this.debounceTimer) clearTimeout(this.debounceTimer);
|
|
206
|
+
if (this.fallbackTimer) clearTimeout(this.fallbackTimer);
|
|
207
|
+
if (this.invalidateTimer) clearTimeout(this.invalidateTimer);
|
|
208
|
+
this.debounceTimer = undefined;
|
|
209
|
+
this.fallbackTimer = undefined;
|
|
210
|
+
this.invalidateTimer = undefined;
|
|
211
|
+
this.invalidateBuffer.clear();
|
|
212
|
+
for (const unsub of this.unsubs.splice(0)) {
|
|
213
|
+
try { unsub(); } catch (error) { logInternalError("render-scheduler.unsubscribe", error); }
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
@@ -1,118 +1,118 @@
|
|
|
1
|
-
import type { ExtensionContext } from "@mariozechner/pi-coding-agent";
|
|
2
|
-
import type { MetricRegistry } from "../observability/metric-registry.ts";
|
|
3
|
-
// Lazy-loaded: team-tool.ts pulls in entire runtime chain.
|
|
4
|
-
import type { handleTeamTool as HandleTeamToolFn } from "../extension/team-tool.ts";
|
|
5
|
-
let _cachedHandleTeamTool: typeof HandleTeamToolFn | undefined;
|
|
6
|
-
async function handleTeamTool(params: Parameters<typeof HandleTeamToolFn>[0], ctx: Parameters<typeof HandleTeamToolFn>[1]): Promise<Awaited<ReturnType<typeof HandleTeamToolFn>>> {
|
|
7
|
-
if (!_cachedHandleTeamTool) {
|
|
8
|
-
// LAZY: avoid pulling team-tool.ts (and its entire runtime chain) into module load.
|
|
9
|
-
const mod = await import("../extension/team-tool.ts");
|
|
10
|
-
_cachedHandleTeamTool = mod.handleTeamTool;
|
|
11
|
-
}
|
|
12
|
-
return _cachedHandleTeamTool(params, ctx);
|
|
13
|
-
}
|
|
14
|
-
import { isToolError, textFromToolResult } from "../extension/tool-result.ts";
|
|
15
|
-
import { loadRunManifestById, saveRunTasks } from "../state/state-store.ts";
|
|
16
|
-
import { appendEvent } from "../state/event-log.ts";
|
|
17
|
-
import { readCrewAgents } from "../runtime/crew-agent-records.ts";
|
|
18
|
-
import { exportDiagnostic } from "../runtime/diagnostic-export.ts";
|
|
19
|
-
import type { MailboxDirection, MailboxMessage } from "../state/mailbox.ts";
|
|
20
|
-
|
|
21
|
-
export interface RunActionResult {
|
|
22
|
-
ok: boolean;
|
|
23
|
-
message: string;
|
|
24
|
-
data?: unknown;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function okFromTool(result: Awaited<ReturnType<typeof handleTeamTool>>): RunActionResult {
|
|
28
|
-
return { ok: !isToolError(result), message: textFromToolResult(result), data: result };
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function err(error: unknown): RunActionResult {
|
|
32
|
-
return { ok: false, message: error instanceof Error ? error.message : String(error) };
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
async function dispatchApi(ctx: ExtensionContext, runId: string, config: Record<string, unknown>): Promise<RunActionResult> {
|
|
36
|
-
try {
|
|
37
|
-
return okFromTool(await handleTeamTool({ action: "api", runId, config }, ctx));
|
|
38
|
-
} catch (error) {
|
|
39
|
-
return err(error);
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
function parseMailboxMessages(text: string): MailboxMessage[] {
|
|
44
|
-
try {
|
|
45
|
-
const parsed = JSON.parse(text) as unknown;
|
|
46
|
-
if (!Array.isArray(parsed)) return [];
|
|
47
|
-
return parsed.filter((item): item is MailboxMessage => Boolean(item) && typeof item === "object" && !Array.isArray(item) && typeof (item as { id?: unknown }).id === "string");
|
|
48
|
-
} catch {
|
|
49
|
-
return [];
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export function dispatchMailboxAck(ctx: ExtensionContext, runId: string, messageId: string): Promise<RunActionResult> {
|
|
54
|
-
return dispatchApi(ctx, runId, { operation: "ack-message", messageId });
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
export function dispatchMailboxNudge(ctx: ExtensionContext, runId: string, agentId: string, message: string): Promise<RunActionResult> {
|
|
58
|
-
return dispatchApi(ctx, runId, { operation: "nudge-agent", agentId, message });
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
export function dispatchMailboxCompose(ctx: ExtensionContext, runId: string, payload: { from: string; to: string; body: string; taskId?: string; direction: MailboxDirection }): Promise<RunActionResult> {
|
|
62
|
-
return dispatchApi(ctx, runId, { operation: "send-message", ...payload });
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
export async function dispatchMailboxAckAll(ctx: ExtensionContext, runId: string): Promise<RunActionResult> {
|
|
66
|
-
const listed = await dispatchApi(ctx, runId, { operation: "read-mailbox", direction: "inbox" });
|
|
67
|
-
if (!listed.ok) return listed;
|
|
68
|
-
const messages = parseMailboxMessages(listed.message).filter((message) => message.status !== "acknowledged");
|
|
69
|
-
let count = 0;
|
|
70
|
-
for (const message of messages) {
|
|
71
|
-
const acked = await dispatchMailboxAck(ctx, runId, message.id);
|
|
72
|
-
if (!acked.ok) return { ok: false, message: `Acknowledged ${count}/${messages.length}; failed ${message.id}: ${acked.message}` };
|
|
73
|
-
count += 1;
|
|
74
|
-
}
|
|
75
|
-
return { ok: true, message: `Acknowledged ${count} messages.`, data: { count } };
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
export function dispatchHealthRecovery(ctx: ExtensionContext, runId: string): Promise<RunActionResult> {
|
|
79
|
-
return dispatchApi(ctx, runId, { operation: "foreground-interrupt", reason: "operator health recovery" });
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
export async function dispatchKillStaleWorkers(ctx: ExtensionContext, runId: string): Promise<RunActionResult> {
|
|
83
|
-
try {
|
|
84
|
-
const loaded = loadRunManifestById(ctx.cwd, runId);
|
|
85
|
-
if (!loaded) return { ok: false, message: `Run '${runId}' not found.` };
|
|
86
|
-
const currentMs = Date.now();
|
|
87
|
-
const staleMs = 60_000;
|
|
88
|
-
const now = new Date(currentMs).toISOString();
|
|
89
|
-
let count = 0;
|
|
90
|
-
const tasks = loaded.tasks.map((task) => {
|
|
91
|
-
if ((task.status !== "running" && task.status !== "queued") || !task.heartbeat || task.heartbeat.alive === false) return task;
|
|
92
|
-
const lastSeenMs = Date.parse(task.heartbeat.lastSeenAt);
|
|
93
|
-
if (!Number.isFinite(lastSeenMs) || currentMs - lastSeenMs <= staleMs) return task;
|
|
94
|
-
count += 1;
|
|
95
|
-
return { ...task, heartbeat: { ...task.heartbeat, alive: false, lastSeenAt: now } };
|
|
96
|
-
});
|
|
97
|
-
saveRunTasks(loaded.manifest, tasks);
|
|
98
|
-
appendEvent(loaded.manifest.eventsPath, { type: "worker.kill_stale", runId, message: `Marked ${count} stale worker heartbeat(s) dead.`, data: { count } });
|
|
99
|
-
return { ok: true, message: `Marked ${count} stale worker heartbeat(s) dead.`, data: { count } };
|
|
100
|
-
} catch (error) {
|
|
101
|
-
return err(error);
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
export async function dispatchDiagnosticExport(ctx: ExtensionContext, runId: string, options: { registry?: MetricRegistry } = {}): Promise<RunActionResult> {
|
|
106
|
-
try {
|
|
107
|
-
const exported = await exportDiagnostic(ctx, runId, options);
|
|
108
|
-
return { ok: true, message: `Diagnostic exported to ${exported.path}`, data: exported.path };
|
|
109
|
-
} catch (error) {
|
|
110
|
-
return err(error);
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
export function defaultNudgeAgentId(ctx: Pick<ExtensionContext, "cwd">, runId: string): string | undefined {
|
|
115
|
-
const loaded = loadRunManifestById(ctx.cwd, runId);
|
|
116
|
-
if (!loaded) return undefined;
|
|
117
|
-
return readCrewAgents(loaded.manifest).find((agent) => agent.status === "running" || agent.status === "queued")?.taskId;
|
|
118
|
-
}
|
|
1
|
+
import type { ExtensionContext } from "@mariozechner/pi-coding-agent";
|
|
2
|
+
import type { MetricRegistry } from "../observability/metric-registry.ts";
|
|
3
|
+
// Lazy-loaded: team-tool.ts pulls in entire runtime chain.
|
|
4
|
+
import type { handleTeamTool as HandleTeamToolFn } from "../extension/team-tool.ts";
|
|
5
|
+
let _cachedHandleTeamTool: typeof HandleTeamToolFn | undefined;
|
|
6
|
+
async function handleTeamTool(params: Parameters<typeof HandleTeamToolFn>[0], ctx: Parameters<typeof HandleTeamToolFn>[1]): Promise<Awaited<ReturnType<typeof HandleTeamToolFn>>> {
|
|
7
|
+
if (!_cachedHandleTeamTool) {
|
|
8
|
+
// LAZY: avoid pulling team-tool.ts (and its entire runtime chain) into module load.
|
|
9
|
+
const mod = await import("../extension/team-tool.ts");
|
|
10
|
+
_cachedHandleTeamTool = mod.handleTeamTool;
|
|
11
|
+
}
|
|
12
|
+
return _cachedHandleTeamTool(params, ctx);
|
|
13
|
+
}
|
|
14
|
+
import { isToolError, textFromToolResult } from "../extension/tool-result.ts";
|
|
15
|
+
import { loadRunManifestById, saveRunTasks } from "../state/state-store.ts";
|
|
16
|
+
import { appendEvent } from "../state/event-log.ts";
|
|
17
|
+
import { readCrewAgents } from "../runtime/crew-agent-records.ts";
|
|
18
|
+
import { exportDiagnostic } from "../runtime/diagnostic-export.ts";
|
|
19
|
+
import type { MailboxDirection, MailboxMessage } from "../state/mailbox.ts";
|
|
20
|
+
|
|
21
|
+
export interface RunActionResult {
|
|
22
|
+
ok: boolean;
|
|
23
|
+
message: string;
|
|
24
|
+
data?: unknown;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function okFromTool(result: Awaited<ReturnType<typeof handleTeamTool>>): RunActionResult {
|
|
28
|
+
return { ok: !isToolError(result), message: textFromToolResult(result), data: result };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function err(error: unknown): RunActionResult {
|
|
32
|
+
return { ok: false, message: error instanceof Error ? error.message : String(error) };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async function dispatchApi(ctx: ExtensionContext, runId: string, config: Record<string, unknown>): Promise<RunActionResult> {
|
|
36
|
+
try {
|
|
37
|
+
return okFromTool(await handleTeamTool({ action: "api", runId, config }, ctx));
|
|
38
|
+
} catch (error) {
|
|
39
|
+
return err(error);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function parseMailboxMessages(text: string): MailboxMessage[] {
|
|
44
|
+
try {
|
|
45
|
+
const parsed = JSON.parse(text) as unknown;
|
|
46
|
+
if (!Array.isArray(parsed)) return [];
|
|
47
|
+
return parsed.filter((item): item is MailboxMessage => Boolean(item) && typeof item === "object" && !Array.isArray(item) && typeof (item as { id?: unknown }).id === "string");
|
|
48
|
+
} catch {
|
|
49
|
+
return [];
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function dispatchMailboxAck(ctx: ExtensionContext, runId: string, messageId: string): Promise<RunActionResult> {
|
|
54
|
+
return dispatchApi(ctx, runId, { operation: "ack-message", messageId });
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function dispatchMailboxNudge(ctx: ExtensionContext, runId: string, agentId: string, message: string): Promise<RunActionResult> {
|
|
58
|
+
return dispatchApi(ctx, runId, { operation: "nudge-agent", agentId, message });
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function dispatchMailboxCompose(ctx: ExtensionContext, runId: string, payload: { from: string; to: string; body: string; taskId?: string; direction: MailboxDirection }): Promise<RunActionResult> {
|
|
62
|
+
return dispatchApi(ctx, runId, { operation: "send-message", ...payload });
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export async function dispatchMailboxAckAll(ctx: ExtensionContext, runId: string): Promise<RunActionResult> {
|
|
66
|
+
const listed = await dispatchApi(ctx, runId, { operation: "read-mailbox", direction: "inbox" });
|
|
67
|
+
if (!listed.ok) return listed;
|
|
68
|
+
const messages = parseMailboxMessages(listed.message).filter((message) => message.status !== "acknowledged");
|
|
69
|
+
let count = 0;
|
|
70
|
+
for (const message of messages) {
|
|
71
|
+
const acked = await dispatchMailboxAck(ctx, runId, message.id);
|
|
72
|
+
if (!acked.ok) return { ok: false, message: `Acknowledged ${count}/${messages.length}; failed ${message.id}: ${acked.message}` };
|
|
73
|
+
count += 1;
|
|
74
|
+
}
|
|
75
|
+
return { ok: true, message: `Acknowledged ${count} messages.`, data: { count } };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function dispatchHealthRecovery(ctx: ExtensionContext, runId: string): Promise<RunActionResult> {
|
|
79
|
+
return dispatchApi(ctx, runId, { operation: "foreground-interrupt", reason: "operator health recovery" });
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export async function dispatchKillStaleWorkers(ctx: ExtensionContext, runId: string): Promise<RunActionResult> {
|
|
83
|
+
try {
|
|
84
|
+
const loaded = loadRunManifestById(ctx.cwd, runId);
|
|
85
|
+
if (!loaded) return { ok: false, message: `Run '${runId}' not found.` };
|
|
86
|
+
const currentMs = Date.now();
|
|
87
|
+
const staleMs = 60_000;
|
|
88
|
+
const now = new Date(currentMs).toISOString();
|
|
89
|
+
let count = 0;
|
|
90
|
+
const tasks = loaded.tasks.map((task) => {
|
|
91
|
+
if ((task.status !== "running" && task.status !== "queued") || !task.heartbeat || task.heartbeat.alive === false) return task;
|
|
92
|
+
const lastSeenMs = Date.parse(task.heartbeat.lastSeenAt);
|
|
93
|
+
if (!Number.isFinite(lastSeenMs) || currentMs - lastSeenMs <= staleMs) return task;
|
|
94
|
+
count += 1;
|
|
95
|
+
return { ...task, heartbeat: { ...task.heartbeat, alive: false, lastSeenAt: now } };
|
|
96
|
+
});
|
|
97
|
+
saveRunTasks(loaded.manifest, tasks);
|
|
98
|
+
appendEvent(loaded.manifest.eventsPath, { type: "worker.kill_stale", runId, message: `Marked ${count} stale worker heartbeat(s) dead.`, data: { count } });
|
|
99
|
+
return { ok: true, message: `Marked ${count} stale worker heartbeat(s) dead.`, data: { count } };
|
|
100
|
+
} catch (error) {
|
|
101
|
+
return err(error);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export async function dispatchDiagnosticExport(ctx: ExtensionContext, runId: string, options: { registry?: MetricRegistry } = {}): Promise<RunActionResult> {
|
|
106
|
+
try {
|
|
107
|
+
const exported = await exportDiagnostic(ctx, runId, options);
|
|
108
|
+
return { ok: true, message: `Diagnostic exported to ${exported.path}`, data: exported.path };
|
|
109
|
+
} catch (error) {
|
|
110
|
+
return err(error);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function defaultNudgeAgentId(ctx: Pick<ExtensionContext, "cwd">, runId: string): string | undefined {
|
|
115
|
+
const loaded = loadRunManifestById(ctx.cwd, runId);
|
|
116
|
+
if (!loaded) return undefined;
|
|
117
|
+
return readCrewAgents(loaded.manifest).find((agent) => agent.status === "running" || agent.status === "queued")?.taskId;
|
|
118
|
+
}
|