pi-crew 0.2.2 → 0.2.4
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 -413
- 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 -0
- 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-plan-2026-05-12.md +463 -0
- package/docs/followup-review-2026-05-12.md +297 -0
- package/docs/followup-review-round3-2026-05-12.md +342 -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 -99
- 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 -103
- 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 -158
- package/src/extension/registration/team-tool.ts +159 -98
- 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 -95
- 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 -228
- package/src/extension/team-tool/status.ts +110 -110
- package/src/extension/team-tool-types.ts +13 -13
- package/src/extension/team-tool.ts +16 -4
- 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 -79
- package/src/runtime/attention-events.ts +28 -28
- package/src/runtime/auto-resume.ts +100 -100
- package/src/runtime/background-runner.ts +122 -88
- 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 -463
- 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 -264
- 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 -599
- 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 -122
- 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 -90
- 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 -395
- 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 -458
- 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 -189
- package/src/schema/config-schema.ts +172 -168
- package/src/schema/team-tool-schema.ts +126 -125
- 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 -178
- 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 -240
- package/src/state/event-reconstructor.ts +217 -217
- package/src/state/jsonl-writer.ts +82 -82
- package/src/state/locks.ts +146 -148
- 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 -117
- 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/env-filter.ts +30 -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/resolve-shell.ts +34 -0
- package/src/utils/safe-paths.ts +47 -47
- package/src/utils/scan-cache.ts +136 -136
- package/src/utils/sleep.ts +2 -1
- 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 -72
- package/src/worktree/worktree-manager.ts +188 -146
- 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,98 +1,159 @@
|
|
|
1
|
-
import * as fs from "node:fs";
|
|
2
|
-
import type { ExtensionAPI, ExtensionContext, ToolDefinition } from "@mariozechner/pi-coding-agent";
|
|
3
|
-
import { loadConfig } from "../../config/config.ts";
|
|
4
|
-
import { TeamToolParams, type TeamToolParamsValue } from "../../schema/team-tool-schema.ts";
|
|
5
|
-
import type { CrewWidgetState } from "../../ui/crew-widget.ts";
|
|
6
|
-
import { updateCrewWidget } from "../../ui/crew-widget.ts";
|
|
7
|
-
import { updatePiCrewPowerbar } from "../../ui/powerbar-publisher.ts";
|
|
8
|
-
import type { createManifestCache } from "../../runtime/manifest-cache.ts";
|
|
9
|
-
import type { createRunSnapshotCache } from "../../ui/run-snapshot-cache.ts";
|
|
10
|
-
import type { MetricRegistry } from "../../observability/metric-registry.ts";
|
|
11
|
-
import { resolveRealContainedPath } from "../../utils/safe-paths.ts";
|
|
12
|
-
// Team tool handler — lazy-loaded because team-tool.ts imports many modules
|
|
13
|
-
import type { handleTeamTool as HandleTeamToolFn } from "../team-tool.ts";
|
|
14
|
-
let _cachedHandleTeamTool: typeof HandleTeamToolFn | undefined;
|
|
15
|
-
async function handleTeamTool(params: Parameters<typeof HandleTeamToolFn>[0], ctx: Parameters<typeof HandleTeamToolFn>[1]): Promise<ReturnType<typeof HandleTeamToolFn>> {
|
|
16
|
-
if (!_cachedHandleTeamTool) {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
import {
|
|
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
|
-
|
|
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
|
-
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import type { ExtensionAPI, ExtensionContext, ToolDefinition } from "@mariozechner/pi-coding-agent";
|
|
3
|
+
import { loadConfig } from "../../config/config.ts";
|
|
4
|
+
import { TeamToolParams, type TeamToolParamsValue } from "../../schema/team-tool-schema.ts";
|
|
5
|
+
import type { CrewWidgetState } from "../../ui/crew-widget.ts";
|
|
6
|
+
import { updateCrewWidget } from "../../ui/crew-widget.ts";
|
|
7
|
+
import { updatePiCrewPowerbar } from "../../ui/powerbar-publisher.ts";
|
|
8
|
+
import type { createManifestCache } from "../../runtime/manifest-cache.ts";
|
|
9
|
+
import type { createRunSnapshotCache } from "../../ui/run-snapshot-cache.ts";
|
|
10
|
+
import type { MetricRegistry } from "../../observability/metric-registry.ts";
|
|
11
|
+
import { resolveRealContainedPath } from "../../utils/safe-paths.ts";
|
|
12
|
+
// Team tool handler — lazy-loaded because team-tool.ts imports many modules
|
|
13
|
+
import type { handleTeamTool as HandleTeamToolFn } from "../team-tool.ts";
|
|
14
|
+
let _cachedHandleTeamTool: typeof HandleTeamToolFn | undefined;
|
|
15
|
+
async function handleTeamTool(params: Parameters<typeof HandleTeamToolFn>[0], ctx: Parameters<typeof HandleTeamToolFn>[1]): Promise<ReturnType<typeof HandleTeamToolFn>> {
|
|
16
|
+
if (!_cachedHandleTeamTool) {
|
|
17
|
+
// LAZY: team-tool.ts imports many modules — defer until first use.
|
|
18
|
+
const mod = await import("../team-tool.ts");
|
|
19
|
+
_cachedHandleTeamTool = mod.handleTeamTool;
|
|
20
|
+
}
|
|
21
|
+
return _cachedHandleTeamTool(params, ctx);
|
|
22
|
+
}
|
|
23
|
+
import { withSessionId } from "../team-tool/context.ts";
|
|
24
|
+
import { toolResult } from "../tool-result.ts";
|
|
25
|
+
import { loadRunManifestById } from "../../state/state-store.ts";
|
|
26
|
+
import { readCrewAgents } from "../../runtime/crew-agent-records.ts";
|
|
27
|
+
import { formatCompactToolProgress } from "../../ui/tool-progress-formatter.ts";
|
|
28
|
+
import { logInternalError } from "../../utils/internal-error.ts";
|
|
29
|
+
|
|
30
|
+
const TEAM_TOOL_PROGRESS_TICK_MS = 1000;
|
|
31
|
+
|
|
32
|
+
type OnUpdate = (chunk: { content: { type: "text"; text: string }[] }) => void;
|
|
33
|
+
|
|
34
|
+
export interface RegisterTeamToolDeps {
|
|
35
|
+
foregroundControllers: Map<string | symbol, AbortController>;
|
|
36
|
+
startForegroundRun: (ctx: ExtensionContext, runner: (signal?: AbortSignal) => Promise<void>, runId?: string) => void;
|
|
37
|
+
abortForegroundRun: (runId: string) => boolean;
|
|
38
|
+
openLiveSidebar: (ctx: ExtensionContext, runId: string) => void;
|
|
39
|
+
getManifestCache: (cwd: string) => ReturnType<typeof createManifestCache>;
|
|
40
|
+
getRunSnapshotCache?: (cwd: string) => ReturnType<typeof createRunSnapshotCache>;
|
|
41
|
+
getMetricRegistry?: () => MetricRegistry | undefined;
|
|
42
|
+
widgetState: CrewWidgetState;
|
|
43
|
+
onJsonEvent?: (taskId: string, runId: string, event: unknown) => void;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function resolveCwdOverride(baseCwd: string, override: string | undefined): { ok: true; cwd: string } | { ok: false; error: string } {
|
|
47
|
+
if (!override) return { ok: true, cwd: baseCwd };
|
|
48
|
+
try {
|
|
49
|
+
const resolved = resolveRealContainedPath(baseCwd, override);
|
|
50
|
+
const stat = fs.statSync(resolved);
|
|
51
|
+
if (!stat.isDirectory()) return { ok: false, error: `cwd override is not a directory: ${resolved}` };
|
|
52
|
+
return { ok: true, cwd: resolved };
|
|
53
|
+
} catch (error) {
|
|
54
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
55
|
+
return { ok: false, error: `Invalid cwd override: ${message}` };
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function registerTeamTool(pi: ExtensionAPI, deps: RegisterTeamToolDeps): void {
|
|
60
|
+
const tool: ToolDefinition = {
|
|
61
|
+
name: "team",
|
|
62
|
+
label: "Team",
|
|
63
|
+
description: "Coordinate Pi teams. Use proactively for complex multi-file work, planning, implementation, tests, reviews, security audits, research, async/background runs, and worktree-isolated execution. Use action='recommend' when unsure which team/workflow to choose. Destructive actions require explicit user confirmation.",
|
|
64
|
+
promptSnippet: "Use the team tool proactively for coordinated multi-agent work. If unsure, call { action: 'recommend', goal } first, then run or plan with the suggested team/workflow.",
|
|
65
|
+
parameters: TeamToolParams as never,
|
|
66
|
+
async execute(_id, params, signal, onUpdate, ctx) {
|
|
67
|
+
const controller = new AbortController();
|
|
68
|
+
const toolKey = Symbol();
|
|
69
|
+
deps.foregroundControllers.set(toolKey, controller);
|
|
70
|
+
const abort = (): void => controller.abort();
|
|
71
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
72
|
+
const stopProgress = startTeamToolProgressBinder(onUpdate as OnUpdate | undefined);
|
|
73
|
+
try {
|
|
74
|
+
const resolved = params as TeamToolParamsValue;
|
|
75
|
+
const cwdOverride = resolveCwdOverride(ctx.cwd, resolved.cwd);
|
|
76
|
+
if (!cwdOverride.ok) return toolResult(cwdOverride.error, { action: resolved.action ?? "list", status: "error" }, true);
|
|
77
|
+
const toolCtx = withSessionId({ ...ctx, cwd: cwdOverride.cwd });
|
|
78
|
+
// Phase 1.5: Auto-set session name from team run context
|
|
79
|
+
if (resolved.action === "run" && resolved.goal && !pi.getSessionName()) {
|
|
80
|
+
const runLabel = resolved.team ?? resolved.agent ?? "direct";
|
|
81
|
+
pi.setSessionName(`pi-crew: ${runLabel}/${resolved.workflow ?? "default"} — ${resolved.goal.slice(0, 60)}`);
|
|
82
|
+
}
|
|
83
|
+
const output = await handleTeamTool(resolved, { ...toolCtx, signal: controller.signal, metricRegistry: deps.getMetricRegistry?.(), startForegroundRun: (runner, runId) => deps.startForegroundRun(toolCtx, runner, runId), abortForegroundRun: deps.abortForegroundRun, onRunStarted: (runId) => { stopProgress.attach(toolCtx.cwd, runId); deps.openLiveSidebar(toolCtx, runId); }, onJsonEvent: deps.onJsonEvent });
|
|
84
|
+
if (resolved.action === "run" && !output.isError && typeof output.details?.runId === "string") {
|
|
85
|
+
pi.appendEntry("crew:run-started", {
|
|
86
|
+
runId: output.details.runId,
|
|
87
|
+
team: resolved.team,
|
|
88
|
+
workflow: resolved.workflow,
|
|
89
|
+
agent: resolved.agent,
|
|
90
|
+
goal: resolved.goal,
|
|
91
|
+
status: output.details?.status,
|
|
92
|
+
timestamp: Date.now(),
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
const config = loadConfig(toolCtx.cwd).config.ui;
|
|
96
|
+
const cache = deps.getManifestCache(toolCtx.cwd);
|
|
97
|
+
const snapshotCache = deps.getRunSnapshotCache?.(toolCtx.cwd);
|
|
98
|
+
updateCrewWidget(toolCtx, deps.widgetState, config, cache, snapshotCache);
|
|
99
|
+
updatePiCrewPowerbar(pi.events, toolCtx.cwd, config, cache, snapshotCache, toolCtx);
|
|
100
|
+
return output;
|
|
101
|
+
} finally {
|
|
102
|
+
signal?.removeEventListener("abort", abort);
|
|
103
|
+
deps.foregroundControllers.delete(toolKey);
|
|
104
|
+
stopProgress.stop();
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
pi.registerTool(tool);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
interface TeamToolProgressBinder {
|
|
112
|
+
attach: (cwd: string, runId: string) => void;
|
|
113
|
+
stop: () => void;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function startTeamToolProgressBinder(onUpdate: OnUpdate | undefined): TeamToolProgressBinder {
|
|
117
|
+
if (!onUpdate) {
|
|
118
|
+
return { attach: () => {}, stop: () => {} };
|
|
119
|
+
}
|
|
120
|
+
const startedAt = Date.now();
|
|
121
|
+
let cwd: string | undefined;
|
|
122
|
+
let runId: string | undefined;
|
|
123
|
+
const tick = (): void => {
|
|
124
|
+
try {
|
|
125
|
+
if (!cwd || !runId) {
|
|
126
|
+
const elapsed = Math.max(0, Math.round((Date.now() - startedAt) / 1000));
|
|
127
|
+
onUpdate({ content: [{ type: "text", text: `team status=starting elapsed=${elapsed}s` }] });
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
const loaded = loadRunManifestById(cwd, runId);
|
|
131
|
+
if (!loaded) {
|
|
132
|
+
const elapsed = Math.max(0, Math.round((Date.now() - startedAt) / 1000));
|
|
133
|
+
onUpdate({ content: [{ type: "text", text: `team run=${runId} elapsed=${elapsed}s (manifest pending)` }] });
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
let agents;
|
|
137
|
+
try { agents = readCrewAgents(loaded.manifest); } catch { /* ignore */ }
|
|
138
|
+
const text = formatCompactToolProgress({
|
|
139
|
+
agentId: runId,
|
|
140
|
+
status: loaded.manifest.status,
|
|
141
|
+
runId,
|
|
142
|
+
startedAt,
|
|
143
|
+
manifest: loaded.manifest,
|
|
144
|
+
tasks: loaded.tasks,
|
|
145
|
+
agents,
|
|
146
|
+
});
|
|
147
|
+
onUpdate({ content: [{ type: "text", text }] });
|
|
148
|
+
} catch (error) {
|
|
149
|
+
logInternalError("team-tool.progress", error, `runId=${runId ?? ""}`);
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
tick();
|
|
153
|
+
const timer = setInterval(tick, TEAM_TOOL_PROGRESS_TICK_MS);
|
|
154
|
+
if (typeof timer.unref === "function") timer.unref();
|
|
155
|
+
return {
|
|
156
|
+
attach: (boundCwd, boundRunId) => { cwd = boundCwd; runId = boundRunId; tick(); },
|
|
157
|
+
stop: () => clearInterval(timer),
|
|
158
|
+
};
|
|
159
|
+
}
|
|
@@ -2,6 +2,9 @@ import type { ExtensionCommandContext } from "@mariozechner/pi-coding-agent";
|
|
|
2
2
|
import { loadRunManifestById } from "../../state/state-store.ts";
|
|
3
3
|
import { readCrewAgents } from "../../runtime/crew-agent-records.ts";
|
|
4
4
|
import { loadConfig } from "../../config/config.ts";
|
|
5
|
+
import { listLiveAgents, type LiveAgentHandle } from "../../runtime/live-agent-manager.ts";
|
|
6
|
+
import { LiveConversationOverlay } from "../../ui/live-conversation-overlay.ts";
|
|
7
|
+
import { asCrewTheme } from "../../ui/theme-adapter.ts";
|
|
5
8
|
// Lazy-loaded: DurableTranscriptViewer is 658ms — only needed for /crew transcript command
|
|
6
9
|
import type { DurableTranscriptViewer as DurableTranscriptViewerType } from "../../ui/transcript-viewer.ts";
|
|
7
10
|
let _cachedViewer: typeof DurableTranscriptViewerType | undefined;
|
|
@@ -46,3 +49,29 @@ export async function openTranscriptViewer(ctx: ExtensionCommandContext, initial
|
|
|
46
49
|
});
|
|
47
50
|
return true;
|
|
48
51
|
}
|
|
52
|
+
|
|
53
|
+
/** R2: Open live conversation overlay for a running live-session agent. */
|
|
54
|
+
export async function openLiveConversation(ctx: ExtensionCommandContext, initialRunId: string | undefined, initialTaskId?: string): Promise<boolean> {
|
|
55
|
+
const selected = await selectAgentTask(ctx, initialRunId, initialTaskId);
|
|
56
|
+
if (!selected || !ctx.hasUI) return false;
|
|
57
|
+
const liveAgents = listLiveAgents();
|
|
58
|
+
const handle = liveAgents.find((h) => h.runId === selected.runId && (selected.taskId ? h.taskId === selected.taskId : true));
|
|
59
|
+
if (!handle) return false;
|
|
60
|
+
const theme = asCrewTheme({});
|
|
61
|
+
await ctx.ui.custom<undefined>((tui: any, _theme: any, _keybindings: any, done: (result: undefined) => void) => {
|
|
62
|
+
const columns = tui?.terminal?.columns ?? 80;
|
|
63
|
+
const rows = tui?.terminal?.rows ?? 24;
|
|
64
|
+
const overlay = new LiveConversationOverlay(handle, theme, columns, rows);
|
|
65
|
+
return {
|
|
66
|
+
render(width: number) { return overlay.render(width); },
|
|
67
|
+
handleInput(data: string) {
|
|
68
|
+
if (data === "\x1b" || data === "q") { overlay.close(); done(undefined); }
|
|
69
|
+
},
|
|
70
|
+
invalidate() { /* overlay polls */ },
|
|
71
|
+
};
|
|
72
|
+
}, {
|
|
73
|
+
overlay: true,
|
|
74
|
+
overlayOptions: { width: "90%", maxHeight: "85%", anchor: "center" },
|
|
75
|
+
});
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
@@ -1,128 +1,128 @@
|
|
|
1
|
-
import * as fs from "node:fs";
|
|
2
|
-
import * as path from "node:path";
|
|
3
|
-
import { buildCompletionKey, getGlobalSeenMap, markSeenWithTtl } from "../utils/completion-dedupe.ts";
|
|
4
|
-
import { closeWatcher, watchWithErrorHandler } from "../utils/fs-watch.ts";
|
|
5
|
-
import { createFileCoalescer } from "../utils/file-coalescer.ts";
|
|
6
|
-
import { logInternalError } from "../utils/internal-error.ts";
|
|
7
|
-
|
|
8
|
-
export interface ResultWatcherEvents {
|
|
9
|
-
emit(event: string, data: unknown): void;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export interface ResultWatcherHandle {
|
|
13
|
-
start(): void;
|
|
14
|
-
prime(): void;
|
|
15
|
-
stop(): void;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
interface ResultWatcherDependencies {
|
|
19
|
-
watch?: typeof watchWithErrorHandler;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export interface ResultWatcherOptions extends ResultWatcherDependencies {
|
|
23
|
-
eventName?: string;
|
|
24
|
-
completionTtlMs?: number;
|
|
25
|
-
isCurrent?: () => boolean;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const RESULT_WATCHER_RESTART_MS = 3000;
|
|
29
|
-
const RESULT_WATCHER_POLL_MS = 1000;
|
|
30
|
-
|
|
31
|
-
function shouldFallBackToPolling(error: unknown): boolean {
|
|
32
|
-
const code = error && typeof error === "object" ? (error as { code?: unknown }).code : undefined;
|
|
33
|
-
return code === "EMFILE" || code === "ENOSPC" || code === "EPERM";
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function readJson(filePath: string): unknown | undefined {
|
|
37
|
-
try {
|
|
38
|
-
return JSON.parse(fs.readFileSync(filePath, "utf-8")) as unknown;
|
|
39
|
-
} catch (error) {
|
|
40
|
-
logInternalError("result-watcher.parse", error, `filePath=${filePath}`);
|
|
41
|
-
return undefined;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export function createResultWatcher(events: ResultWatcherEvents, resultsDir: string, eventNameOrOptions: string | ResultWatcherOptions = "pi-crew:run-result"): ResultWatcherHandle {
|
|
46
|
-
const options: ResultWatcherOptions = typeof eventNameOrOptions === "string" ? { eventName: eventNameOrOptions } : eventNameOrOptions;
|
|
47
|
-
const eventName = options.eventName ?? "pi-crew:run-result";
|
|
48
|
-
const completionTtlMs = options.completionTtlMs ?? 5 * 60_000;
|
|
49
|
-
const watch = options.watch ?? watchWithErrorHandler;
|
|
50
|
-
const isCurrent = options.isCurrent ?? (() => true);
|
|
51
|
-
const seen = getGlobalSeenMap("pi-crew.result-watcher");
|
|
52
|
-
let watcher: fs.FSWatcher | null | undefined;
|
|
53
|
-
let restartTimer: ReturnType<typeof setTimeout> | undefined;
|
|
54
|
-
let pollTimer: ReturnType<typeof setInterval> | undefined;
|
|
55
|
-
const coalescer = createFileCoalescer((file) => {
|
|
56
|
-
if (!isCurrent()) return;
|
|
57
|
-
const filePath = path.join(resultsDir, file);
|
|
58
|
-
if (!file.endsWith(".json") || !fs.existsSync(filePath)) return;
|
|
59
|
-
const payload = readJson(filePath);
|
|
60
|
-
if (payload === undefined) {
|
|
61
|
-
coalescer.schedule(file, RESULT_WATCHER_POLL_MS);
|
|
62
|
-
return;
|
|
63
|
-
}
|
|
64
|
-
const key = buildCompletionKey(payload && typeof payload === "object" && !Array.isArray(payload) ? payload as Record<string, unknown> : {}, `file:${file}`);
|
|
65
|
-
if (!markSeenWithTtl(seen, key, Date.now(), completionTtlMs)) {
|
|
66
|
-
events.emit(eventName, payload);
|
|
67
|
-
}
|
|
68
|
-
try {
|
|
69
|
-
fs.unlinkSync(filePath);
|
|
70
|
-
} catch (error) {
|
|
71
|
-
logInternalError("result-watcher.unlink", error, `filePath=${filePath}`);
|
|
72
|
-
}
|
|
73
|
-
}, 50);
|
|
74
|
-
const poll = () => {
|
|
75
|
-
if (!isCurrent() || !fs.existsSync(resultsDir)) return;
|
|
76
|
-
for (const file of fs.readdirSync(resultsDir).filter((entry) => entry.endsWith(".json"))) coalescer.schedule(file, 0);
|
|
77
|
-
};
|
|
78
|
-
const startPolling = () => {
|
|
79
|
-
if (pollTimer) return;
|
|
80
|
-
pollTimer = setInterval(poll, RESULT_WATCHER_POLL_MS);
|
|
81
|
-
pollTimer.unref();
|
|
82
|
-
poll();
|
|
83
|
-
};
|
|
84
|
-
const stopPolling = () => {
|
|
85
|
-
if (pollTimer) clearInterval(pollTimer);
|
|
86
|
-
pollTimer = undefined;
|
|
87
|
-
};
|
|
88
|
-
const scheduleRestart = (error?: unknown) => {
|
|
89
|
-
if (shouldFallBackToPolling(error)) startPolling();
|
|
90
|
-
if (restartTimer) clearTimeout(restartTimer);
|
|
91
|
-
restartTimer = setTimeout(() => {
|
|
92
|
-
restartTimer = undefined;
|
|
93
|
-
try {
|
|
94
|
-
if (!isCurrent()) return;
|
|
95
|
-
fs.mkdirSync(resultsDir, { recursive: true });
|
|
96
|
-
handle.start();
|
|
97
|
-
} catch (error) {
|
|
98
|
-
logInternalError("result-watcher.restart", error, `resultsDir=${resultsDir}`);
|
|
99
|
-
}
|
|
100
|
-
}, RESULT_WATCHER_RESTART_MS);
|
|
101
|
-
restartTimer.unref();
|
|
102
|
-
};
|
|
103
|
-
const handle: ResultWatcherHandle = {
|
|
104
|
-
start() {
|
|
105
|
-
if (!isCurrent()) return;
|
|
106
|
-
fs.mkdirSync(resultsDir, { recursive: true });
|
|
107
|
-
if (watcher) closeWatcher(watcher);
|
|
108
|
-
watcher = watch(resultsDir, (event, fileName) => {
|
|
109
|
-
if (event !== "rename" || !fileName) return;
|
|
110
|
-
coalescer.schedule(fileName.toString());
|
|
111
|
-
}, scheduleRestart);
|
|
112
|
-
if (watcher) stopPolling();
|
|
113
|
-
watcher?.unref();
|
|
114
|
-
},
|
|
115
|
-
prime() {
|
|
116
|
-
poll();
|
|
117
|
-
},
|
|
118
|
-
stop() {
|
|
119
|
-
if (restartTimer) clearTimeout(restartTimer);
|
|
120
|
-
restartTimer = undefined;
|
|
121
|
-
closeWatcher(watcher);
|
|
122
|
-
watcher = undefined;
|
|
123
|
-
stopPolling();
|
|
124
|
-
coalescer.clear();
|
|
125
|
-
},
|
|
126
|
-
};
|
|
127
|
-
return handle;
|
|
128
|
-
}
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { buildCompletionKey, getGlobalSeenMap, markSeenWithTtl } from "../utils/completion-dedupe.ts";
|
|
4
|
+
import { closeWatcher, watchWithErrorHandler } from "../utils/fs-watch.ts";
|
|
5
|
+
import { createFileCoalescer } from "../utils/file-coalescer.ts";
|
|
6
|
+
import { logInternalError } from "../utils/internal-error.ts";
|
|
7
|
+
|
|
8
|
+
export interface ResultWatcherEvents {
|
|
9
|
+
emit(event: string, data: unknown): void;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface ResultWatcherHandle {
|
|
13
|
+
start(): void;
|
|
14
|
+
prime(): void;
|
|
15
|
+
stop(): void;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface ResultWatcherDependencies {
|
|
19
|
+
watch?: typeof watchWithErrorHandler;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface ResultWatcherOptions extends ResultWatcherDependencies {
|
|
23
|
+
eventName?: string;
|
|
24
|
+
completionTtlMs?: number;
|
|
25
|
+
isCurrent?: () => boolean;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const RESULT_WATCHER_RESTART_MS = 3000;
|
|
29
|
+
const RESULT_WATCHER_POLL_MS = 1000;
|
|
30
|
+
|
|
31
|
+
function shouldFallBackToPolling(error: unknown): boolean {
|
|
32
|
+
const code = error && typeof error === "object" ? (error as { code?: unknown }).code : undefined;
|
|
33
|
+
return code === "EMFILE" || code === "ENOSPC" || code === "EPERM";
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function readJson(filePath: string): unknown | undefined {
|
|
37
|
+
try {
|
|
38
|
+
return JSON.parse(fs.readFileSync(filePath, "utf-8")) as unknown;
|
|
39
|
+
} catch (error) {
|
|
40
|
+
logInternalError("result-watcher.parse", error, `filePath=${filePath}`);
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function createResultWatcher(events: ResultWatcherEvents, resultsDir: string, eventNameOrOptions: string | ResultWatcherOptions = "pi-crew:run-result"): ResultWatcherHandle {
|
|
46
|
+
const options: ResultWatcherOptions = typeof eventNameOrOptions === "string" ? { eventName: eventNameOrOptions } : eventNameOrOptions;
|
|
47
|
+
const eventName = options.eventName ?? "pi-crew:run-result";
|
|
48
|
+
const completionTtlMs = options.completionTtlMs ?? 5 * 60_000;
|
|
49
|
+
const watch = options.watch ?? watchWithErrorHandler;
|
|
50
|
+
const isCurrent = options.isCurrent ?? (() => true);
|
|
51
|
+
const seen = getGlobalSeenMap("pi-crew.result-watcher");
|
|
52
|
+
let watcher: fs.FSWatcher | null | undefined;
|
|
53
|
+
let restartTimer: ReturnType<typeof setTimeout> | undefined;
|
|
54
|
+
let pollTimer: ReturnType<typeof setInterval> | undefined;
|
|
55
|
+
const coalescer = createFileCoalescer((file) => {
|
|
56
|
+
if (!isCurrent()) return;
|
|
57
|
+
const filePath = path.join(resultsDir, file);
|
|
58
|
+
if (!file.endsWith(".json") || !fs.existsSync(filePath)) return;
|
|
59
|
+
const payload = readJson(filePath);
|
|
60
|
+
if (payload === undefined) {
|
|
61
|
+
coalescer.schedule(file, RESULT_WATCHER_POLL_MS);
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
const key = buildCompletionKey(payload && typeof payload === "object" && !Array.isArray(payload) ? payload as Record<string, unknown> : {}, `file:${file}`);
|
|
65
|
+
if (!markSeenWithTtl(seen, key, Date.now(), completionTtlMs)) {
|
|
66
|
+
events.emit(eventName, payload);
|
|
67
|
+
}
|
|
68
|
+
try {
|
|
69
|
+
fs.unlinkSync(filePath);
|
|
70
|
+
} catch (error) {
|
|
71
|
+
logInternalError("result-watcher.unlink", error, `filePath=${filePath}`);
|
|
72
|
+
}
|
|
73
|
+
}, 50);
|
|
74
|
+
const poll = () => {
|
|
75
|
+
if (!isCurrent() || !fs.existsSync(resultsDir)) return;
|
|
76
|
+
for (const file of fs.readdirSync(resultsDir).filter((entry) => entry.endsWith(".json"))) coalescer.schedule(file, 0);
|
|
77
|
+
};
|
|
78
|
+
const startPolling = () => {
|
|
79
|
+
if (pollTimer) return;
|
|
80
|
+
pollTimer = setInterval(poll, RESULT_WATCHER_POLL_MS);
|
|
81
|
+
pollTimer.unref();
|
|
82
|
+
poll();
|
|
83
|
+
};
|
|
84
|
+
const stopPolling = () => {
|
|
85
|
+
if (pollTimer) clearInterval(pollTimer);
|
|
86
|
+
pollTimer = undefined;
|
|
87
|
+
};
|
|
88
|
+
const scheduleRestart = (error?: unknown) => {
|
|
89
|
+
if (shouldFallBackToPolling(error)) startPolling();
|
|
90
|
+
if (restartTimer) clearTimeout(restartTimer);
|
|
91
|
+
restartTimer = setTimeout(() => {
|
|
92
|
+
restartTimer = undefined;
|
|
93
|
+
try {
|
|
94
|
+
if (!isCurrent()) return;
|
|
95
|
+
fs.mkdirSync(resultsDir, { recursive: true });
|
|
96
|
+
handle.start();
|
|
97
|
+
} catch (error) {
|
|
98
|
+
logInternalError("result-watcher.restart", error, `resultsDir=${resultsDir}`);
|
|
99
|
+
}
|
|
100
|
+
}, RESULT_WATCHER_RESTART_MS);
|
|
101
|
+
restartTimer.unref();
|
|
102
|
+
};
|
|
103
|
+
const handle: ResultWatcherHandle = {
|
|
104
|
+
start() {
|
|
105
|
+
if (!isCurrent()) return;
|
|
106
|
+
fs.mkdirSync(resultsDir, { recursive: true });
|
|
107
|
+
if (watcher) closeWatcher(watcher);
|
|
108
|
+
watcher = watch(resultsDir, (event, fileName) => {
|
|
109
|
+
if (event !== "rename" || !fileName) return;
|
|
110
|
+
coalescer.schedule(fileName.toString());
|
|
111
|
+
}, scheduleRestart);
|
|
112
|
+
if (watcher) stopPolling();
|
|
113
|
+
watcher?.unref();
|
|
114
|
+
},
|
|
115
|
+
prime() {
|
|
116
|
+
poll();
|
|
117
|
+
},
|
|
118
|
+
stop() {
|
|
119
|
+
if (restartTimer) clearTimeout(restartTimer);
|
|
120
|
+
restartTimer = undefined;
|
|
121
|
+
closeWatcher(watcher);
|
|
122
|
+
watcher = undefined;
|
|
123
|
+
stopPolling();
|
|
124
|
+
coalescer.clear();
|
|
125
|
+
},
|
|
126
|
+
};
|
|
127
|
+
return handle;
|
|
128
|
+
}
|