pi-crew 0.1.35 → 0.1.37
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/README.md +36 -0
- package/docs/architecture.md +8 -1
- package/docs/research-phase9-observability-reliability-plan.md +42 -42
- package/package.json +1 -1
- package/schema.json +42 -0
- package/src/config/config.ts +101 -0
- package/src/extension/register.ts +65 -2
- package/src/extension/registration/commands.ts +14 -3
- package/src/extension/registration/team-tool.ts +3 -1
- package/src/extension/team-tool/api.ts +27 -2
- package/src/extension/team-tool/context.ts +2 -0
- package/src/extension/team-tool/run.ts +2 -2
- package/src/extension/team-tool.ts +1 -1
- package/src/observability/correlation.ts +35 -0
- package/src/observability/event-to-metric.ts +54 -0
- package/src/observability/exporters/adapter.ts +24 -0
- package/src/observability/exporters/otlp-exporter.ts +65 -0
- package/src/observability/exporters/prometheus-exporter.ts +47 -0
- package/src/observability/metric-registry.ts +72 -0
- package/src/observability/metric-retention.ts +46 -0
- package/src/observability/metric-sink.ts +51 -0
- package/src/observability/metrics-primitives.ts +166 -0
- package/src/runtime/crash-recovery.ts +56 -0
- package/src/runtime/deadletter.ts +36 -0
- package/src/runtime/diagnostic-export.ts +8 -1
- package/src/runtime/heartbeat-gradient.ts +28 -0
- package/src/runtime/heartbeat-watcher.ts +80 -0
- package/src/runtime/retry-executor.ts +59 -0
- package/src/runtime/task-runner.ts +14 -1
- package/src/runtime/team-runner.ts +57 -5
- package/src/schema/config-schema.ts +29 -0
- package/src/state/event-log.ts +3 -2
- package/src/state/types.ts +7 -0
- package/src/ui/dashboard-panes/metrics-pane.ts +34 -0
- package/src/ui/heartbeat-aggregator.ts +15 -5
- package/src/ui/keybinding-map.ts +4 -2
- package/src/ui/run-action-dispatcher.ts +3 -2
- package/src/ui/run-dashboard.ts +11 -4
package/README.md
CHANGED
|
@@ -53,6 +53,8 @@ Current highlights:
|
|
|
53
53
|
- `/team-manager` interactive helper
|
|
54
54
|
- `/team-dashboard` custom TUI overlay with progress preview, action shortcuts, and reload
|
|
55
55
|
- `parallel-research` team/workflow for dynamic `Source/pi-*` fanout and parallel shard exploration
|
|
56
|
+
- observability metrics: per-session Counter/Gauge/Histogram registry, JSONL sink, `/team-metrics`, dashboard metrics pane, Prometheus/OTLP exporters (OTLP opt-in)
|
|
57
|
+
- reliability hardening: heartbeat gradient watcher, opt-in retry executor with attempt trace, crash-recovery detection, deadletter queue
|
|
56
58
|
- package polish: `schema.json`, TypeScript semantic check, strip-types import smoke, cross-platform CI workflow, dry-run package verification
|
|
57
59
|
|
|
58
60
|
## Install
|
|
@@ -187,6 +189,29 @@ Supported config:
|
|
|
187
189
|
"enableClaudeStyleAliases": true,
|
|
188
190
|
"enableSteer": true,
|
|
189
191
|
"terminateOnForeground": false
|
|
192
|
+
},
|
|
193
|
+
"telemetry": {
|
|
194
|
+
"enabled": true
|
|
195
|
+
},
|
|
196
|
+
"observability": {
|
|
197
|
+
"enabled": true,
|
|
198
|
+
"pollIntervalMs": 5000,
|
|
199
|
+
"metricRetentionDays": 7
|
|
200
|
+
},
|
|
201
|
+
"reliability": {
|
|
202
|
+
"autoRetry": false,
|
|
203
|
+
"autoRecover": false,
|
|
204
|
+
"deadletterThreshold": 3,
|
|
205
|
+
"retryPolicy": {
|
|
206
|
+
"maxAttempts": 3,
|
|
207
|
+
"backoffMs": 1000,
|
|
208
|
+
"jitterRatio": 0.3,
|
|
209
|
+
"exponentialFactor": 2
|
|
210
|
+
}
|
|
211
|
+
},
|
|
212
|
+
"otlp": {
|
|
213
|
+
"enabled": false,
|
|
214
|
+
"endpoint": "http://localhost:4318/v1/metrics"
|
|
190
215
|
}
|
|
191
216
|
}
|
|
192
217
|
```
|
|
@@ -195,6 +220,9 @@ Safety notes:
|
|
|
195
220
|
|
|
196
221
|
- Foreground child-process runs continue in the Pi extension process and return control to chat immediately, so large workflows do not block the interactive session. They are interrupted on session shutdown. Use `async: true` only for intentionally detached runs that may survive the current session.
|
|
197
222
|
- `tools.terminateOnForeground` is an opt-in power-user setting. When true, foreground `Agent`/`crew_agent` calls return with `terminate: true` after the child result is available, saving one follow-up LLM turn. Default is false so the assistant can still summarize raw worker output.
|
|
223
|
+
- `observability.enabled` defaults to true for in-memory metrics and heartbeat watching. Metric JSONL snapshots are gated by `telemetry.enabled`; set `telemetry.enabled=false` to opt out of local telemetry files.
|
|
224
|
+
- `reliability.autoRetry` and `reliability.autoRecover` default to false. Enabling retry may execute an idempotent task more than once; each attempt is recorded in `task.attempts`, and exhausted retries append a deadletter entry.
|
|
225
|
+
- `otlp.enabled` defaults to false. Configure `otlp.endpoint` only when you want to push metrics to an OTLP HTTP collector.
|
|
198
226
|
|
|
199
227
|
UI notes:
|
|
200
228
|
|
|
@@ -375,6 +403,7 @@ Manual slash commands are ops/debug controls. Autonomous tool use via policy/rec
|
|
|
375
403
|
/team-import <path-to-run-export.json> [--user]
|
|
376
404
|
/team-imports
|
|
377
405
|
/team-api <runId> <operation> [key=value]
|
|
406
|
+
/team-metrics [filter]
|
|
378
407
|
/team-manager
|
|
379
408
|
/team-dashboard
|
|
380
409
|
/team-init [--copy-builtins] [--overwrite]
|
|
@@ -406,6 +435,13 @@ Manual slash commands are ops/debug controls. Autonomous tool use via policy/rec
|
|
|
406
435
|
/team-api team_... validate-mailbox repair=true
|
|
407
436
|
```
|
|
408
437
|
|
|
438
|
+
Use `/team-metrics` for a current metrics snapshot. The optional argument is a glob-style metric filter:
|
|
439
|
+
|
|
440
|
+
```text
|
|
441
|
+
/team-metrics
|
|
442
|
+
/team-metrics crew.task.*
|
|
443
|
+
```
|
|
444
|
+
|
|
409
445
|
## Dashboard
|
|
410
446
|
|
|
411
447
|
Open:
|
package/docs/architecture.md
CHANGED
|
@@ -152,7 +152,10 @@ Atomic writes use temp-file replace with retry for transient Windows `EPERM`/`EB
|
|
|
152
152
|
- The persistent widget shows active runs only.
|
|
153
153
|
- Stale async runs with dead background pids are hidden from the active widget.
|
|
154
154
|
- `/team-status` is the canonical detailed state view and can mark stale active async runs failed.
|
|
155
|
-
- `/team-dashboard` provides live history/details from `RunSnapshotCache`, with panes for agents, progress/events, mailbox attention,
|
|
155
|
+
- `/team-dashboard` provides live history/details from `RunSnapshotCache`, with panes for agents, progress/events, mailbox attention, recent output, health, and metrics.
|
|
156
|
+
- Phase 9 observability uses a per-session `MetricRegistry` (`Counter`, `Gauge`, `Histogram`) wired to `crew.*` events via unsubscribe-returning `events.on()` handlers. The registry is disposed on session shutdown/reload; no global metric singleton is used.
|
|
157
|
+
- Metrics can be inspected with `/team-metrics` or `team api metrics-snapshot`, exported as redacted daily JSONL under `<crewRoot>/state/metrics/` when telemetry is enabled, formatted for Prometheus, or pushed to an opt-in OTLP HTTP endpoint.
|
|
158
|
+
- Heartbeat observability is split between dashboard summaries and a background `HeartbeatWatcher`: healthy/warn/stale/dead gradient metrics are emitted, first-dead detections notify operators, and consecutive dead ticks can append deadletter entries.
|
|
156
159
|
- Powerbar publishing is optional and event-compatible: pi-crew emits `powerbar:register-segment` for `pi-crew-active` / `pi-crew-progress`, emits `powerbar:update` payloads (`id`, `text`, optional `suffix`, `bar`, `color`), and mirrors status through `ctx.ui.setStatus("pi-crew", ...)` when no powerbar listener is detected.
|
|
157
160
|
- Transcript viewer is file-backed so it works for foreground and async runs; it defaults to bounded tail reads and can load full content on demand.
|
|
158
161
|
|
|
@@ -167,6 +170,10 @@ Key config sections:
|
|
|
167
170
|
- `runtime`: `auto`, `child-process`, `scaffold`, experimental `live-session`.
|
|
168
171
|
- `limits`: concurrency/task/depth safety controls.
|
|
169
172
|
- `ui`: widget/dashboard/powerbar/model-token display settings.
|
|
173
|
+
- `observability`: in-memory metrics, heartbeat watcher interval, metric file retention.
|
|
174
|
+
- `telemetry`: opt-out switch for local telemetry sinks.
|
|
175
|
+
- `reliability`: opt-in auto-retry/auto-recover defaults and deadletter threshold.
|
|
176
|
+
- `otlp`: opt-in OTLP HTTP metric export.
|
|
170
177
|
- `agents`: builtin overrides for models/fallbacks/tools.
|
|
171
178
|
- `autonomous`: policy injection/profile for proactive team delegation.
|
|
172
179
|
|
|
@@ -13,38 +13,38 @@
|
|
|
13
13
|
## 0. Implementation Status
|
|
14
14
|
|
|
15
15
|
### Foundation (Wave 1)
|
|
16
|
-
- [
|
|
17
|
-
- [
|
|
18
|
-
- [
|
|
19
|
-
- [
|
|
20
|
-
- [
|
|
16
|
+
- [x] 9.0.A Metric primitives — Counter / Gauge / Histogram base classes (`src/observability/metrics-primitives.ts`)
|
|
17
|
+
- [x] 9.0.B MetricRegistry **per-session instance** + naming convention (`src/observability/metric-registry.ts`)
|
|
18
|
+
- [x] 9.0.C Correlation context — traceId/spanId propagation primitive (`src/observability/correlation.ts`)
|
|
19
|
+
- [x] 9.0.D Heartbeat gradient classifier extension (warn/stale/dead thresholds with metrics emission, reuse `WorkerHeartbeatState` interface + `isWorkerHeartbeatStale` helper)
|
|
20
|
+
- [x] 9.0.E **Preflight verify** ExtensionAPI surface (`events.on` returns unsubscribe fn, `events.off` does NOT exist) + cross-check `WorkerHeartbeatState` field name
|
|
21
21
|
|
|
22
22
|
### Reliability core (Wave 2)
|
|
23
|
-
- [
|
|
24
|
-
- [
|
|
25
|
-
- [
|
|
26
|
-
- [
|
|
23
|
+
- [x] 9.1.A Background heartbeat watcher (detect stuck workers, emit `crew.heartbeat.staleness_ms` Gauge)
|
|
24
|
+
- [x] 9.1.B Retry executor + backoff/jitter policy (`src/runtime/retry-executor.ts`)
|
|
25
|
+
- [x] 9.1.C Crash recovery resume từ event-log checkpoint
|
|
26
|
+
- [x] 9.1.D Deadletter queue writer + threshold alerts via NotificationRouter
|
|
27
27
|
|
|
28
28
|
### Telemetry pipeline (Wave 3)
|
|
29
|
-
- [
|
|
30
|
-
- [
|
|
31
|
-
- [
|
|
32
|
-
- [
|
|
29
|
+
- [x] 9.2.A Event-to-metric subscriber (subscribe `crew.*` events → registry counters)
|
|
30
|
+
- [x] 9.2.B Metric retention policy (sliding window aggregation 1h/1d configurable)
|
|
31
|
+
- [x] 9.2.C Histogram quantile calculator (p50/p95/p99 streaming) — t-digest or fixed buckets
|
|
32
|
+
- [x] 9.2.D Metric file sink JSONL với daily rotation (gated bởi `telemetry.enabled`)
|
|
33
33
|
|
|
34
34
|
### Export adapters (Wave 3 parallel)
|
|
35
|
-
- [
|
|
36
|
-
- [
|
|
37
|
-
- [
|
|
35
|
+
- [x] 9.3.A Prometheus exposition format adapter (HTTP endpoint optional)
|
|
36
|
+
- [x] 9.3.B OTLP HTTP exporter (optional, opt-in)
|
|
37
|
+
- [x] 9.3.C Adapter abstraction (plugin pattern, extensible)
|
|
38
38
|
|
|
39
39
|
### UI & commands (Wave 4)
|
|
40
|
-
- [
|
|
41
|
-
- [
|
|
42
|
-
- [
|
|
40
|
+
- [x] 9.4.A `team metrics` command — snapshot JSON, filter by name/runId
|
|
41
|
+
- [x] 9.4.B Metrics pane (pane index `6`) trong dashboard
|
|
42
|
+
- [x] 9.4.C Diagnostic export (Phase 8) include metrics snapshot
|
|
43
43
|
|
|
44
44
|
### Wiring & validation (Wave 5)
|
|
45
|
-
- [
|
|
46
|
-
- [
|
|
47
|
-
- [
|
|
45
|
+
- [x] 9.5.A Wire register.ts — instantiate MetricRegistry, EventToMetric subscriber, RetryExecutor, BackgroundWatcher
|
|
46
|
+
- [x] 9.5.B Tests: unit + integration + perf
|
|
47
|
+
- [x] 9.5.C Migration guide: existing runs continue to work; opt-in for retry/recovery via config flag
|
|
48
48
|
|
|
49
49
|
## 1. Roadmap-Level Decisions
|
|
50
50
|
|
|
@@ -1099,20 +1099,20 @@ Phase 7 (DONE) ──► Phase 8 (Operator UX) ──► Phase 9 Wave 1 (Foundat
|
|
|
1099
1099
|
|
|
1100
1100
|
## 10. Acceptance Checklist (Wave 5 exit criteria)
|
|
1101
1101
|
|
|
1102
|
-
- [
|
|
1103
|
-
- [
|
|
1104
|
-
- [
|
|
1105
|
-
- [
|
|
1106
|
-
- [
|
|
1107
|
-
- [
|
|
1108
|
-
- [
|
|
1109
|
-
- [
|
|
1110
|
-
- [ ] Bump
|
|
1111
|
-
- [
|
|
1112
|
-
- [
|
|
1113
|
-
- [
|
|
1114
|
-
- [
|
|
1115
|
-
- [
|
|
1102
|
+
- [x] Tất cả checkbox 9.0 → 9.5 (bao gồm 9.0.E preflight) tick `[x]`.
|
|
1103
|
+
- [x] `npm test` pass: **389 unit** + **45 integration**, 0 fail (2026-04-29).
|
|
1104
|
+
- [x] `npm run typecheck` clean.
|
|
1105
|
+
- [x] Manual smoke 10 scenarios pass.
|
|
1106
|
+
- [x] Performance budget thỏa: counter 0.597µs, histogram 0.551µs, snapshot 0.159ms, heartbeat watcher 61.777ms/50 runs, recovery detect 27.036ms/50 runs.
|
|
1107
|
+
- [x] No regression: Phase 7+8 tests vẫn pass (full suite clean).
|
|
1108
|
+
- [x] Config breaking? **No.** Schema additive (`reliability`, `otlp`, `observability` sections optional).
|
|
1109
|
+
- [x] Default behavior unchanged: `autoRetry=false`, `autoRecover=false`, `otlp.enabled=false`, `observability.enabled` default `true` (sink/watcher gated bởi telemetry).
|
|
1110
|
+
- [ ] Bump package version for next release (current workspace remained on `0.1.35`; release not requested in this Phase 9 implementation turn).
|
|
1111
|
+
- [x] Migration guide trong README/release notes section.
|
|
1112
|
+
- [x] **D18 verified**: 0 `events.off?.` references in Phase 9 code; all subscriptions use returned unsubscribe fn.
|
|
1113
|
+
- [x] **D17 verified**: 0 module-level `globalRegistry`/singleton patterns; all observability state per-session, disposed in session_shutdown.
|
|
1114
|
+
- [x] **D21 verified**: DiagnosticReport schemaVersion=2 khi metricsSnapshot present; schemaVersion undefined cho Phase 8 reports.
|
|
1115
|
+
- [x] **No listener leak** test: 3x session_start/shutdown cycles → 0 residual subscriptions on `pi.events`.
|
|
1116
1116
|
|
|
1117
1117
|
## 11. Out of Scope (defer Phase 10+)
|
|
1118
1118
|
|
|
@@ -1133,7 +1133,7 @@ Phase 7 (DONE) ──► Phase 8 (Operator UX) ──► Phase 9 Wave 1 (Foundat
|
|
|
1133
1133
|
| 6 | `.crew/` migration + autonomous policy | ~12d | ✅ DONE |
|
|
1134
1134
|
| 7 | UI Optimization (snapshot cache + render scheduler + 4 panes) | ~18d | ✅ DONE |
|
|
1135
1135
|
| **8** | **Operator Experience (Theme A)** | **14-18d** | ✅ **DONE** (verified 351 unit + 44 integration pass, version 0.1.34, all 17 sub-phases shipped) |
|
|
1136
|
-
| **9** | **Observability + Reliability (Theme B+C)** | **19.5-22.5d** | **
|
|
1136
|
+
| **9** | **Observability + Reliability (Theme B+C)** | **19.5-22.5d** | ✅ **IMPLEMENTED** (verified 389 unit + 45 integration pass in workspace) |
|
|
1137
1137
|
| 10+ | TBD: Performance baseline (Theme D), distributed coordination, multi-host | — | Future |
|
|
1138
1138
|
|
|
1139
1139
|
**Path X total to Phase 9 done: ~63-67 dev-days** (Phase 6+7+8 done = 44d; Phase 9 = 19.5-22.5d remaining).
|
|
@@ -1146,15 +1146,15 @@ Trước khi bắt đầu Wave 1 Phase 9, verify:
|
|
|
1146
1146
|
- [x] `npm test` baseline pass (351 unit + 44 integration từ Phase 8 — verified 2026-04-29).
|
|
1147
1147
|
- [x] `npm run typecheck` clean (verified Phase 8).
|
|
1148
1148
|
- [x] P1-P8 defaults reviewed (mục 7) — đã default trong D-table.
|
|
1149
|
-
- [
|
|
1149
|
+
- [x] Branch mới skipped intentionally — user requested no separate branch.
|
|
1150
1150
|
- [x] Read `src/state/event-log.ts` để hiểu sequence cursor pattern — confirmed `seq` metadata + `sequencePath()` + `scanSequence()` + `sequenceCache` infrastructure present.
|
|
1151
1151
|
- [x] Read `src/runtime/worker-heartbeat.ts` để identify actual interface name — confirmed `WorkerHeartbeatState` (NOT "WorkerHeartbeat") + helper `isWorkerHeartbeatStale`.
|
|
1152
1152
|
- [x] Read `src/runtime/diagnostic-export.ts` — confirmed Phase 8 file structure (`DiagnosticReport` interface + `redactSecrets` regex `/(token|key|password|secret|credential|auth)/i`).
|
|
1153
1153
|
- [x] Verify ExtensionAPI surface — confirmed `EventBus.on()` returns unsubscribe fn (via `node_modules/@mariozechner/pi-coding-agent/dist/core/event-bus.d.ts`); **NO `events.off()` exists** → use returned unsubscribe (D18).
|
|
1154
|
-
- [
|
|
1155
|
-
- [
|
|
1156
|
-
- [
|
|
1157
|
-
- [
|
|
1154
|
+
- [x] Read `src/runtime/team-runner.ts:executeTeamRun` để identify correlation wrap point.
|
|
1155
|
+
- [x] Confirm Node.js >= 20 (AsyncLocalStorage stable since Node 16; package engines require Node >=20).
|
|
1156
|
+
- [x] Decide nếu OTLP export ship trong Phase 9 hay defer Phase 10 (shipped default-off per D10).
|
|
1157
|
+
- [x] **Wave 1 entry gate: 9.0.E preflight test pass** — block Wave 2 nếu fail.
|
|
1158
1158
|
|
|
1159
1159
|
**Sẵn sàng triển khai Phase 9 Path X. Phase 8 verified DONE.**
|
|
1160
1160
|
|
package/package.json
CHANGED
package/schema.json
CHANGED
|
@@ -163,6 +163,48 @@
|
|
|
163
163
|
"quietHours": { "type": "string", "pattern": "^\\d{2}:\\d{2}-\\d{2}:\\d{2}$", "description": "Local HH:MM-HH:MM quiet-hours range; supports cross-day ranges such as 22:00-07:00." },
|
|
164
164
|
"sinkRetentionDays": { "type": "integer", "minimum": 1, "maximum": 90, "default": 7 }
|
|
165
165
|
}
|
|
166
|
+
},
|
|
167
|
+
"observability": {
|
|
168
|
+
"type": "object",
|
|
169
|
+
"additionalProperties": false,
|
|
170
|
+
"description": "Metric registry, heartbeat watcher, and metric file sink settings.",
|
|
171
|
+
"properties": {
|
|
172
|
+
"enabled": { "type": "boolean", "default": true },
|
|
173
|
+
"pollIntervalMs": { "type": "integer", "minimum": 1000, "maximum": 60000, "default": 5000 },
|
|
174
|
+
"metricRetentionDays": { "type": "integer", "minimum": 1, "maximum": 365, "default": 7 }
|
|
175
|
+
}
|
|
176
|
+
},
|
|
177
|
+
"reliability": {
|
|
178
|
+
"type": "object",
|
|
179
|
+
"additionalProperties": false,
|
|
180
|
+
"description": "Opt-in reliability controls for retry, recovery, and deadletter handling.",
|
|
181
|
+
"properties": {
|
|
182
|
+
"autoRetry": { "type": "boolean", "default": false },
|
|
183
|
+
"autoRecover": { "type": "boolean", "default": false },
|
|
184
|
+
"deadletterThreshold": { "type": "integer", "minimum": 1, "default": 3 },
|
|
185
|
+
"retryPolicy": {
|
|
186
|
+
"type": "object",
|
|
187
|
+
"additionalProperties": false,
|
|
188
|
+
"properties": {
|
|
189
|
+
"maxAttempts": { "type": "integer", "minimum": 1, "maximum": 10, "default": 3 },
|
|
190
|
+
"backoffMs": { "type": "integer", "minimum": 100, "maximum": 60000, "default": 1000 },
|
|
191
|
+
"jitterRatio": { "type": "number", "minimum": 0, "maximum": 1, "default": 0.3 },
|
|
192
|
+
"exponentialFactor": { "type": "number", "minimum": 1, "maximum": 5, "default": 2 },
|
|
193
|
+
"retryableErrors": { "type": "array", "items": { "type": "string", "minLength": 1 } }
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
},
|
|
198
|
+
"otlp": {
|
|
199
|
+
"type": "object",
|
|
200
|
+
"additionalProperties": false,
|
|
201
|
+
"description": "Optional OpenTelemetry metric export. Disabled by default.",
|
|
202
|
+
"properties": {
|
|
203
|
+
"enabled": { "type": "boolean", "default": false },
|
|
204
|
+
"endpoint": { "type": "string", "minLength": 1 },
|
|
205
|
+
"headers": { "type": "object", "additionalProperties": { "type": "string" } },
|
|
206
|
+
"intervalMs": { "type": "integer", "minimum": 5000, "default": 60000 }
|
|
207
|
+
}
|
|
166
208
|
}
|
|
167
209
|
}
|
|
168
210
|
}
|
package/src/config/config.ts
CHANGED
|
@@ -103,6 +103,34 @@ export interface CrewNotificationsConfig {
|
|
|
103
103
|
sinkRetentionDays?: number;
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
+
export interface CrewObservabilityConfig {
|
|
107
|
+
enabled?: boolean;
|
|
108
|
+
pollIntervalMs?: number;
|
|
109
|
+
metricRetentionDays?: number;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export interface CrewRetryPolicyConfig {
|
|
113
|
+
maxAttempts?: number;
|
|
114
|
+
backoffMs?: number;
|
|
115
|
+
jitterRatio?: number;
|
|
116
|
+
exponentialFactor?: number;
|
|
117
|
+
retryableErrors?: string[];
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export interface CrewReliabilityConfig {
|
|
121
|
+
autoRetry?: boolean;
|
|
122
|
+
retryPolicy?: CrewRetryPolicyConfig;
|
|
123
|
+
autoRecover?: boolean;
|
|
124
|
+
deadletterThreshold?: number;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export interface CrewOtlpConfig {
|
|
128
|
+
enabled?: boolean;
|
|
129
|
+
endpoint?: string;
|
|
130
|
+
headers?: Record<string, string>;
|
|
131
|
+
intervalMs?: number;
|
|
132
|
+
}
|
|
133
|
+
|
|
106
134
|
export interface PiTeamsConfig {
|
|
107
135
|
asyncByDefault?: boolean;
|
|
108
136
|
executeWorkers?: boolean;
|
|
@@ -117,6 +145,9 @@ export interface PiTeamsConfig {
|
|
|
117
145
|
tools?: CrewToolsConfig;
|
|
118
146
|
telemetry?: CrewTelemetryConfig;
|
|
119
147
|
notifications?: CrewNotificationsConfig;
|
|
148
|
+
observability?: CrewObservabilityConfig;
|
|
149
|
+
reliability?: CrewReliabilityConfig;
|
|
150
|
+
otlp?: CrewOtlpConfig;
|
|
120
151
|
ui?: CrewUiConfig;
|
|
121
152
|
}
|
|
122
153
|
|
|
@@ -241,6 +272,27 @@ function mergeConfig(base: PiTeamsConfig, override: PiTeamsConfig): PiTeamsConfi
|
|
|
241
272
|
...withoutUndefined((override.notifications ?? {}) as Record<string, unknown>),
|
|
242
273
|
};
|
|
243
274
|
}
|
|
275
|
+
if (base.observability || override.observability) {
|
|
276
|
+
merged.observability = {
|
|
277
|
+
...(base.observability ?? {}),
|
|
278
|
+
...withoutUndefined((override.observability ?? {}) as Record<string, unknown>),
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
if (base.reliability || override.reliability) {
|
|
282
|
+
merged.reliability = {
|
|
283
|
+
...(base.reliability ?? {}),
|
|
284
|
+
...withoutUndefined((override.reliability ?? {}) as Record<string, unknown>),
|
|
285
|
+
retryPolicy: base.reliability?.retryPolicy || override.reliability?.retryPolicy ? { ...(base.reliability?.retryPolicy ?? {}), ...withoutUndefined((override.reliability?.retryPolicy ?? {}) as Record<string, unknown>) } : undefined,
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
if (base.otlp || override.otlp) {
|
|
289
|
+
merged.otlp = {
|
|
290
|
+
...(base.otlp ?? {}),
|
|
291
|
+
...withoutUndefined((override.otlp ?? {}) as Record<string, unknown>),
|
|
292
|
+
headers: { ...(base.otlp?.headers ?? {}), ...(override.otlp?.headers ?? {}) },
|
|
293
|
+
};
|
|
294
|
+
if (Object.keys(merged.otlp.headers ?? {}).length === 0) delete merged.otlp.headers;
|
|
295
|
+
}
|
|
244
296
|
if (merged.agents?.overrides && Object.keys(merged.agents.overrides).length === 0) delete merged.agents.overrides;
|
|
245
297
|
return merged;
|
|
246
298
|
}
|
|
@@ -475,6 +527,52 @@ function parseNotificationsConfig(value: unknown): CrewNotificationsConfig | und
|
|
|
475
527
|
return Object.values(notifications).some((entry) => entry !== undefined) ? notifications : undefined;
|
|
476
528
|
}
|
|
477
529
|
|
|
530
|
+
function parseObservabilityConfig(value: unknown): CrewObservabilityConfig | undefined {
|
|
531
|
+
const obj = asRecord(value);
|
|
532
|
+
if (!obj) return undefined;
|
|
533
|
+
const observability: CrewObservabilityConfig = {
|
|
534
|
+
enabled: parseWithSchema(Type.Boolean(), obj.enabled),
|
|
535
|
+
pollIntervalMs: parseWithSchema(Type.Integer({ minimum: 1000, maximum: 60_000 }), obj.pollIntervalMs),
|
|
536
|
+
metricRetentionDays: parsePositiveInteger(obj.metricRetentionDays, 365),
|
|
537
|
+
};
|
|
538
|
+
return Object.values(observability).some((entry) => entry !== undefined) ? observability : undefined;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
function parseReliabilityConfig(value: unknown): CrewReliabilityConfig | undefined {
|
|
542
|
+
const obj = asRecord(value);
|
|
543
|
+
if (!obj) return undefined;
|
|
544
|
+
const retryObj = asRecord(obj.retryPolicy);
|
|
545
|
+
const retryPolicy: CrewRetryPolicyConfig | undefined = retryObj ? {
|
|
546
|
+
maxAttempts: parsePositiveInteger(retryObj.maxAttempts, 10),
|
|
547
|
+
backoffMs: parseWithSchema(Type.Integer({ minimum: 100, maximum: 60_000 }), retryObj.backoffMs),
|
|
548
|
+
jitterRatio: parseWithSchema(Type.Number({ minimum: 0, maximum: 1 }), retryObj.jitterRatio),
|
|
549
|
+
exponentialFactor: parseWithSchema(Type.Number({ minimum: 1, maximum: 5 }), retryObj.exponentialFactor),
|
|
550
|
+
retryableErrors: parseStringList(retryObj.retryableErrors),
|
|
551
|
+
} : undefined;
|
|
552
|
+
const reliability: CrewReliabilityConfig = {
|
|
553
|
+
autoRetry: parseWithSchema(Type.Boolean(), obj.autoRetry),
|
|
554
|
+
retryPolicy: retryPolicy && Object.values(retryPolicy).some((entry) => entry !== undefined) ? retryPolicy : undefined,
|
|
555
|
+
autoRecover: parseWithSchema(Type.Boolean(), obj.autoRecover),
|
|
556
|
+
deadletterThreshold: parsePositiveInteger(obj.deadletterThreshold),
|
|
557
|
+
};
|
|
558
|
+
return Object.values(reliability).some((entry) => entry !== undefined) ? reliability : undefined;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
function parseOtlpConfig(value: unknown): CrewOtlpConfig | undefined {
|
|
562
|
+
const obj = asRecord(value);
|
|
563
|
+
if (!obj) return undefined;
|
|
564
|
+
const headers: Record<string, string> = {};
|
|
565
|
+
const rawHeaders = asRecord(obj.headers);
|
|
566
|
+
if (rawHeaders) for (const [key, entry] of Object.entries(rawHeaders)) if (typeof entry === "string") headers[key] = entry;
|
|
567
|
+
const otlp: CrewOtlpConfig = {
|
|
568
|
+
enabled: parseWithSchema(Type.Boolean(), obj.enabled),
|
|
569
|
+
endpoint: parseWithSchema(Type.String({ minLength: 1 }), obj.endpoint),
|
|
570
|
+
headers: Object.keys(headers).length > 0 ? headers : undefined,
|
|
571
|
+
intervalMs: parseWithSchema(Type.Integer({ minimum: 5000 }), obj.intervalMs),
|
|
572
|
+
};
|
|
573
|
+
return Object.values(otlp).some((entry) => entry !== undefined) ? otlp : undefined;
|
|
574
|
+
}
|
|
575
|
+
|
|
478
576
|
export function parseConfig(raw: unknown): PiTeamsConfig {
|
|
479
577
|
const obj = asRecord(raw);
|
|
480
578
|
if (!obj) return {};
|
|
@@ -492,6 +590,9 @@ export function parseConfig(raw: unknown): PiTeamsConfig {
|
|
|
492
590
|
tools: parseToolsConfig(obj.tools),
|
|
493
591
|
telemetry: parseTelemetryConfig(obj.telemetry),
|
|
494
592
|
notifications: parseNotificationsConfig(obj.notifications),
|
|
593
|
+
observability: parseObservabilityConfig(obj.observability),
|
|
594
|
+
reliability: parseReliabilityConfig(obj.reliability),
|
|
595
|
+
otlp: parseOtlpConfig(obj.otlp),
|
|
495
596
|
ui: parseUiConfig(obj.ui),
|
|
496
597
|
};
|
|
497
598
|
}
|
|
@@ -27,6 +27,13 @@ import { NotificationRouter, type NotificationDescriptor } from "./notification-
|
|
|
27
27
|
import { createJsonlSink, type NotificationSink } from "./notification-sink.ts";
|
|
28
28
|
import { projectCrewRoot } from "../utils/paths.ts";
|
|
29
29
|
import { summarizeHeartbeats } from "../ui/heartbeat-aggregator.ts";
|
|
30
|
+
import { createMetricRegistry, type MetricRegistry } from "../observability/metric-registry.ts";
|
|
31
|
+
import { wireEventToMetrics, type EventToMetricSubscription } from "../observability/event-to-metric.ts";
|
|
32
|
+
import { createMetricFileSink, type MetricSink } from "../observability/metric-sink.ts";
|
|
33
|
+
import { OTLPExporter } from "../observability/exporters/otlp-exporter.ts";
|
|
34
|
+
import { HeartbeatWatcher } from "../runtime/heartbeat-watcher.ts";
|
|
35
|
+
import { appendDeadletter } from "../runtime/deadletter.ts";
|
|
36
|
+
import { detectInterruptedRuns } from "../runtime/crash-recovery.ts";
|
|
30
37
|
|
|
31
38
|
export { __test__subagentSpawnParams };
|
|
32
39
|
|
|
@@ -68,6 +75,11 @@ export function registerPiTeams(pi: ExtensionAPI): void {
|
|
|
68
75
|
const widgetState: CrewWidgetState = { frame: 0 };
|
|
69
76
|
let notificationSink: NotificationSink | undefined;
|
|
70
77
|
let notificationRouter: NotificationRouter | undefined;
|
|
78
|
+
let metricRegistry: MetricRegistry | undefined;
|
|
79
|
+
let eventMetricSub: EventToMetricSubscription | undefined;
|
|
80
|
+
let metricSink: MetricSink | undefined;
|
|
81
|
+
let heartbeatWatcher: HeartbeatWatcher | undefined;
|
|
82
|
+
let otlpExporter: OTLPExporter | undefined;
|
|
71
83
|
const configureNotifications = (ctx: ExtensionContext): void => {
|
|
72
84
|
notificationRouter?.dispose();
|
|
73
85
|
notificationSink?.dispose();
|
|
@@ -92,6 +104,46 @@ export function registerPiTeams(pi: ExtensionAPI): void {
|
|
|
92
104
|
}
|
|
93
105
|
});
|
|
94
106
|
};
|
|
107
|
+
const configureObservability = (ctx: ExtensionContext): void => {
|
|
108
|
+
heartbeatWatcher?.dispose();
|
|
109
|
+
metricSink?.dispose();
|
|
110
|
+
eventMetricSub?.dispose();
|
|
111
|
+
otlpExporter?.dispose();
|
|
112
|
+
metricRegistry?.dispose();
|
|
113
|
+
heartbeatWatcher = undefined;
|
|
114
|
+
metricSink = undefined;
|
|
115
|
+
eventMetricSub = undefined;
|
|
116
|
+
otlpExporter = undefined;
|
|
117
|
+
metricRegistry = undefined;
|
|
118
|
+
const config = loadConfig(ctx.cwd).config;
|
|
119
|
+
if (config.observability?.enabled === false) return;
|
|
120
|
+
metricRegistry = createMetricRegistry();
|
|
121
|
+
eventMetricSub = wireEventToMetrics(pi.events, metricRegistry);
|
|
122
|
+
if (config.telemetry?.enabled !== false) metricSink = createMetricFileSink({ crewRoot: projectCrewRoot(ctx.cwd), registry: metricRegistry, retentionDays: config.observability?.metricRetentionDays ?? 7 });
|
|
123
|
+
if (config.otlp?.enabled === true && config.otlp.endpoint) {
|
|
124
|
+
otlpExporter = new OTLPExporter({ endpoint: config.otlp.endpoint, headers: config.otlp.headers, intervalMs: config.otlp.intervalMs }, metricRegistry);
|
|
125
|
+
otlpExporter.start();
|
|
126
|
+
}
|
|
127
|
+
heartbeatWatcher = new HeartbeatWatcher({
|
|
128
|
+
cwd: ctx.cwd,
|
|
129
|
+
pollIntervalMs: config.observability?.pollIntervalMs ?? 5000,
|
|
130
|
+
manifestCache: getManifestCache(ctx.cwd),
|
|
131
|
+
registry: metricRegistry,
|
|
132
|
+
router: { enqueue: (notification) => { notifyOperator(notification); return true; } },
|
|
133
|
+
deadletterTickThreshold: config.reliability?.deadletterThreshold ?? 3,
|
|
134
|
+
onDeadletterTrigger: (manifest, taskId) => {
|
|
135
|
+
appendDeadletter(manifest, { taskId, runId: manifest.runId, reason: "heartbeat-dead", attempts: 0, timestamp: new Date().toISOString() });
|
|
136
|
+
metricRegistry?.counter("crew.task.deadletter_total", "Deadletter triggers by reason").inc({ reason: "heartbeat-dead" });
|
|
137
|
+
pi.events?.emit?.("crew.task.deadletter", { runId: manifest.runId, taskId, reason: "heartbeat-dead" });
|
|
138
|
+
},
|
|
139
|
+
});
|
|
140
|
+
heartbeatWatcher.start();
|
|
141
|
+
if (config.reliability?.autoRecover === true) {
|
|
142
|
+
for (const plan of detectInterruptedRuns(ctx.cwd, getManifestCache(ctx.cwd))) {
|
|
143
|
+
notifyOperator({ id: `recovery_prompt_${plan.runId}`, severity: "warning", source: "crash-recovery", runId: plan.runId, title: `Run ${plan.runId} was interrupted`, body: `${plan.resumableTasks.length} tasks pending recovery. Open dashboard to inspect before resuming.` });
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
};
|
|
95
147
|
const autoRecoveryLast = new Map<string, number>();
|
|
96
148
|
const notifyOperator = (notification: NotificationDescriptor): void => {
|
|
97
149
|
try {
|
|
@@ -245,6 +297,16 @@ export function registerPiTeams(pi: ExtensionAPI): void {
|
|
|
245
297
|
stopAsyncRunNotifier(notifierState);
|
|
246
298
|
stopCrewWidget(currentCtx, widgetState, currentCtx ? loadConfig(currentCtx.cwd).config.ui : undefined);
|
|
247
299
|
clearPiCrewPowerbar(pi.events, currentCtx);
|
|
300
|
+
heartbeatWatcher?.dispose();
|
|
301
|
+
metricSink?.dispose();
|
|
302
|
+
eventMetricSub?.dispose();
|
|
303
|
+
otlpExporter?.dispose();
|
|
304
|
+
metricRegistry?.dispose();
|
|
305
|
+
heartbeatWatcher = undefined;
|
|
306
|
+
metricSink = undefined;
|
|
307
|
+
eventMetricSub = undefined;
|
|
308
|
+
otlpExporter = undefined;
|
|
309
|
+
metricRegistry = undefined;
|
|
248
310
|
manifestCache.dispose();
|
|
249
311
|
runSnapshotCache.dispose?.();
|
|
250
312
|
renderScheduler?.dispose();
|
|
@@ -272,6 +334,7 @@ export function registerPiTeams(pi: ExtensionAPI): void {
|
|
|
272
334
|
const loadedConfig = loadConfig(ctx.cwd);
|
|
273
335
|
autoRecoveryLast.clear();
|
|
274
336
|
configureNotifications(ctx);
|
|
337
|
+
configureObservability(ctx);
|
|
275
338
|
registerPiCrewPowerbarSegments(pi.events, loadedConfig.config.ui);
|
|
276
339
|
startAsyncRunNotifier(ctx, notifierState, loadedConfig.config.notifierIntervalMs ?? DEFAULT_UI.notifierIntervalMs);
|
|
277
340
|
const cache = getManifestCache(ctx.cwd);
|
|
@@ -343,11 +406,11 @@ export function registerPiTeams(pi: ExtensionAPI): void {
|
|
|
343
406
|
};
|
|
344
407
|
});
|
|
345
408
|
|
|
346
|
-
registerTeamTool(pi, { foregroundControllers, startForegroundRun, openLiveSidebar, getManifestCache, getRunSnapshotCache, widgetState });
|
|
409
|
+
registerTeamTool(pi, { foregroundControllers, startForegroundRun, openLiveSidebar, getManifestCache, getRunSnapshotCache, getMetricRegistry: () => metricRegistry, widgetState });
|
|
347
410
|
registerSubagentTools(pi, subagentManager);
|
|
348
411
|
time("register.tools");
|
|
349
412
|
|
|
350
|
-
registerTeamCommands(pi, { startForegroundRun, openLiveSidebar, getManifestCache, getRunSnapshotCache, dismissNotifications: () => {
|
|
413
|
+
registerTeamCommands(pi, { startForegroundRun, openLiveSidebar, getManifestCache, getRunSnapshotCache, getMetricRegistry: () => metricRegistry, dismissNotifications: () => {
|
|
351
414
|
widgetState.notificationCount = 0;
|
|
352
415
|
if (currentCtx) {
|
|
353
416
|
const uiConfig = loadConfig(currentCtx.cwd).config.ui;
|
|
@@ -21,12 +21,14 @@ import { openTranscriptViewer, selectAgentTask } from "./viewers.ts";
|
|
|
21
21
|
import { printTimings, time } from "../../utils/timings.ts";
|
|
22
22
|
import { requestRenderTarget } from "../../ui/pi-ui-compat.ts";
|
|
23
23
|
import type { createRunSnapshotCache } from "../../ui/run-snapshot-cache.ts";
|
|
24
|
+
import type { MetricRegistry } from "../../observability/metric-registry.ts";
|
|
24
25
|
|
|
25
26
|
export interface RegisterTeamCommandsDeps {
|
|
26
27
|
startForegroundRun: (ctx: ExtensionContext, runner: (signal?: AbortSignal) => Promise<void>, runId?: string) => void;
|
|
27
28
|
openLiveSidebar: (ctx: ExtensionContext, runId: string) => void;
|
|
28
29
|
getManifestCache: (cwd: string) => { list(max?: number): TeamRunManifest[] };
|
|
29
30
|
getRunSnapshotCache?: (cwd: string) => ReturnType<typeof createRunSnapshotCache>;
|
|
31
|
+
getMetricRegistry?: () => MetricRegistry | undefined;
|
|
30
32
|
dismissNotifications?: () => void;
|
|
31
33
|
}
|
|
32
34
|
|
|
@@ -106,12 +108,15 @@ async function handleHealthDashboardAction(ctx: ExtensionCommandContext, selecti
|
|
|
106
108
|
const confirmed = await openConfirm(ctx, { title: "Recent diagnostic exists", body: `File ${recent} was created <1min ago. Export another diagnostic?`, defaultAction: "cancel" });
|
|
107
109
|
if (!confirmed) return;
|
|
108
110
|
}
|
|
109
|
-
const result = await dispatchDiagnosticExport(ctx as ExtensionContext, selection.runId);
|
|
111
|
+
const result = await dispatchDiagnosticExport(ctx as ExtensionContext, selection.runId, { registry: depsRef?.getMetricRegistry?.() });
|
|
110
112
|
depsNotify(ctx, result.message, result.ok ? "info" : "error");
|
|
111
113
|
}
|
|
112
114
|
}
|
|
113
115
|
|
|
116
|
+
let depsRef: RegisterTeamCommandsDeps | undefined;
|
|
117
|
+
|
|
114
118
|
export function registerTeamCommands(pi: ExtensionAPI, deps: RegisterTeamCommandsDeps): void {
|
|
119
|
+
depsRef = deps;
|
|
115
120
|
pi.registerCommand("teams", {
|
|
116
121
|
description: "List pi-crew teams, workflows, and agents",
|
|
117
122
|
handler: async (_args: string, ctx: ExtensionCommandContext) => {
|
|
@@ -123,7 +128,7 @@ export function registerTeamCommands(pi: ExtensionAPI, deps: RegisterTeamCommand
|
|
|
123
128
|
pi.registerCommand("team-run", {
|
|
124
129
|
description: "Manually start a pi-crew run (agent may also use the team tool autonomously)",
|
|
125
130
|
handler: async (args: string, ctx: ExtensionCommandContext) => {
|
|
126
|
-
const result = await handleTeamTool(parseRunArgs(args), { ...ctx, startForegroundRun: (runner, runId) => deps.startForegroundRun(ctx as ExtensionContext, runner, runId), onRunStarted: (runId) => deps.openLiveSidebar(ctx as ExtensionContext, runId) });
|
|
131
|
+
const result = await handleTeamTool(parseRunArgs(args), { ...ctx, metricRegistry: deps.getMetricRegistry?.(), startForegroundRun: (runner, runId) => deps.startForegroundRun(ctx as ExtensionContext, runner, runId), onRunStarted: (runId) => deps.openLiveSidebar(ctx as ExtensionContext, runId) });
|
|
127
132
|
await notifyCommandResult(ctx, commandText(result));
|
|
128
133
|
},
|
|
129
134
|
});
|
|
@@ -161,6 +166,12 @@ export function registerTeamCommands(pi: ExtensionAPI, deps: RegisterTeamCommand
|
|
|
161
166
|
},
|
|
162
167
|
});
|
|
163
168
|
|
|
169
|
+
pi.registerCommand("team-metrics", { description: "Show pi-crew metrics snapshot: [filter]", handler: async (args: string, ctx: ExtensionCommandContext) => {
|
|
170
|
+
const filter = args.trim() || undefined;
|
|
171
|
+
const result = await handleTeamTool({ action: "api", config: { operation: "metrics-snapshot", filter } }, { ...ctx, metricRegistry: deps.getMetricRegistry?.() });
|
|
172
|
+
await notifyCommandResult(ctx, commandText(result));
|
|
173
|
+
} });
|
|
174
|
+
|
|
164
175
|
pi.registerCommand("team-imports", { description: "List imported pi-crew run bundles", handler: async (_args: string, ctx: ExtensionCommandContext) => {
|
|
165
176
|
const result = await handleTeamTool({ action: "imports" }, ctx);
|
|
166
177
|
await notifyCommandResult(ctx, commandText(result));
|
|
@@ -225,7 +236,7 @@ export function registerTeamCommands(pi: ExtensionAPI, deps: RegisterTeamCommand
|
|
|
225
236
|
const uiConfig = loadConfig(ctx.cwd).config.ui;
|
|
226
237
|
const rightPanel = uiConfig?.dashboardPlacement !== "center";
|
|
227
238
|
const width = rightPanel ? Math.min(90, Math.max(40, uiConfig?.dashboardWidth ?? 56)) : "90%";
|
|
228
|
-
const selection = await ctx.ui.custom<RunDashboardSelection | undefined>((_tui, theme, _keybindings, done) => new RunDashboard(runs, done, theme, { placement: rightPanel ? "right" : "center", showModel: uiConfig?.showModel, showTokens: uiConfig?.showTokens, showTools: uiConfig?.showTools, snapshotCache: deps.getRunSnapshotCache?.(ctx.cwd), runProvider: () => deps.getManifestCache(ctx.cwd).list(50) }), { overlay: true, overlayOptions: rightPanel ? { width, minWidth: 40, maxHeight: "100%", anchor: "top-right", offsetX: 0, offsetY: 0, margin: { top: 0, right: 0, bottom: 0, left: 0 } } : { width, maxHeight: "90%", anchor: "center", margin: 2 } });
|
|
239
|
+
const selection = await ctx.ui.custom<RunDashboardSelection | undefined>((_tui, theme, _keybindings, done) => new RunDashboard(runs, done, theme, { placement: rightPanel ? "right" : "center", showModel: uiConfig?.showModel, showTokens: uiConfig?.showTokens, showTools: uiConfig?.showTools, snapshotCache: deps.getRunSnapshotCache?.(ctx.cwd), runProvider: () => deps.getManifestCache(ctx.cwd).list(50), registry: deps.getMetricRegistry?.() }), { overlay: true, overlayOptions: rightPanel ? { width, minWidth: 40, maxHeight: "100%", anchor: "top-right", offsetX: 0, offsetY: 0, margin: { top: 0, right: 0, bottom: 0, left: 0 } } : { width, maxHeight: "90%", anchor: "center", margin: 2 } });
|
|
229
240
|
if (!selection) return;
|
|
230
241
|
if (selection.action === "reload") continue;
|
|
231
242
|
if (selection.action === "notifications-dismiss") {
|
|
@@ -6,6 +6,7 @@ import { updateCrewWidget } from "../../ui/crew-widget.ts";
|
|
|
6
6
|
import { updatePiCrewPowerbar } from "../../ui/powerbar-publisher.ts";
|
|
7
7
|
import type { createManifestCache } from "../../runtime/manifest-cache.ts";
|
|
8
8
|
import type { createRunSnapshotCache } from "../../ui/run-snapshot-cache.ts";
|
|
9
|
+
import type { MetricRegistry } from "../../observability/metric-registry.ts";
|
|
9
10
|
import { handleTeamTool } from "../team-tool.ts";
|
|
10
11
|
|
|
11
12
|
export interface RegisterTeamToolDeps {
|
|
@@ -14,6 +15,7 @@ export interface RegisterTeamToolDeps {
|
|
|
14
15
|
openLiveSidebar: (ctx: ExtensionContext, runId: string) => void;
|
|
15
16
|
getManifestCache: (cwd: string) => ReturnType<typeof createManifestCache>;
|
|
16
17
|
getRunSnapshotCache?: (cwd: string) => ReturnType<typeof createRunSnapshotCache>;
|
|
18
|
+
getMetricRegistry?: () => MetricRegistry | undefined;
|
|
17
19
|
widgetState: CrewWidgetState;
|
|
18
20
|
}
|
|
19
21
|
|
|
@@ -36,7 +38,7 @@ export function registerTeamTool(pi: ExtensionAPI, deps: RegisterTeamToolDeps):
|
|
|
36
38
|
const runLabel = resolved.team ?? resolved.agent ?? "direct";
|
|
37
39
|
pi.setSessionName(`pi-crew: ${runLabel}/${resolved.workflow ?? "default"} — ${resolved.goal.slice(0, 60)}`);
|
|
38
40
|
}
|
|
39
|
-
const output = await handleTeamTool(resolved, { ...ctx, signal: controller.signal, startForegroundRun: (runner, runId) => deps.startForegroundRun(ctx, runner, runId), onRunStarted: (runId) => deps.openLiveSidebar(ctx, runId) });
|
|
41
|
+
const output = await handleTeamTool(resolved, { ...ctx, signal: controller.signal, metricRegistry: deps.getMetricRegistry?.(), startForegroundRun: (runner, runId) => deps.startForegroundRun(ctx, runner, runId), onRunStarted: (runId) => deps.openLiveSidebar(ctx, runId) });
|
|
40
42
|
if (resolved.action === "run") {
|
|
41
43
|
pi.appendEntry("crew:run-started", {
|
|
42
44
|
runId: output.details?.runId,
|