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/src/ui/keybinding-map.ts
CHANGED
|
@@ -13,7 +13,7 @@ export const DASHBOARD_KEYS = {
|
|
|
13
13
|
reload: ["r"],
|
|
14
14
|
progressToggle: ["p"],
|
|
15
15
|
},
|
|
16
|
-
pane: { agents: ["1"], progress: ["2"], mailbox: ["3"], output: ["4"], health: ["5"] },
|
|
16
|
+
pane: { agents: ["1"], progress: ["2"], mailbox: ["3"], output: ["4"], health: ["5"], metrics: ["6"] },
|
|
17
17
|
navigation: { up: ["k", "\u001b[A"], down: ["j", "\u001b[B"] },
|
|
18
18
|
mailbox: { ack: ["A"], nudge: ["N"], compose: ["C"], preview: ["P"], ackAll: ["X"], openDetail: ["\r", "\n"] },
|
|
19
19
|
health: { recovery: ["R"], killStale: ["K"], diagnosticExport: ["D"] },
|
|
@@ -53,6 +53,7 @@ export type DashboardKeyAction =
|
|
|
53
53
|
| "pane-mailbox"
|
|
54
54
|
| "pane-output"
|
|
55
55
|
| "pane-health"
|
|
56
|
+
| "pane-metrics"
|
|
56
57
|
| "up"
|
|
57
58
|
| "down"
|
|
58
59
|
| "mailbox-detail"
|
|
@@ -61,7 +62,7 @@ export type DashboardKeyAction =
|
|
|
61
62
|
| "health-diagnostic-export"
|
|
62
63
|
| "notifications-dismiss";
|
|
63
64
|
|
|
64
|
-
export function dashboardActionForKey(data: string, activePane?: "agents" | "progress" | "mailbox" | "output" | "health"): DashboardKeyAction | undefined {
|
|
65
|
+
export function dashboardActionForKey(data: string, activePane?: "agents" | "progress" | "mailbox" | "output" | "health" | "metrics"): DashboardKeyAction | undefined {
|
|
65
66
|
if (includes(DASHBOARD_KEYS.close, data)) return "close";
|
|
66
67
|
if (activePane === "mailbox" && includes(DASHBOARD_KEYS.mailbox.openDetail, data)) return "mailbox-detail";
|
|
67
68
|
if (activePane === "health") {
|
|
@@ -86,6 +87,7 @@ export function dashboardActionForKey(data: string, activePane?: "agents" | "pro
|
|
|
86
87
|
if (includes(DASHBOARD_KEYS.pane.mailbox, data)) return "pane-mailbox";
|
|
87
88
|
if (includes(DASHBOARD_KEYS.pane.output, data)) return "pane-output";
|
|
88
89
|
if (includes(DASHBOARD_KEYS.pane.health, data)) return "pane-health";
|
|
90
|
+
if (includes(DASHBOARD_KEYS.pane.metrics, data)) return "pane-metrics";
|
|
89
91
|
if (includes(DASHBOARD_KEYS.navigation.up, data)) return "up";
|
|
90
92
|
if (includes(DASHBOARD_KEYS.navigation.down, data)) return "down";
|
|
91
93
|
return undefined;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ExtensionContext } from "@mariozechner/pi-coding-agent";
|
|
2
|
+
import type { MetricRegistry } from "../observability/metric-registry.ts";
|
|
2
3
|
import { handleTeamTool } from "../extension/team-tool.ts";
|
|
3
4
|
import { isToolError, textFromToolResult } from "../extension/tool-result.ts";
|
|
4
5
|
import { loadRunManifestById, saveRunTasks } from "../state/state-store.ts";
|
|
@@ -91,9 +92,9 @@ export async function dispatchKillStaleWorkers(ctx: ExtensionContext, runId: str
|
|
|
91
92
|
}
|
|
92
93
|
}
|
|
93
94
|
|
|
94
|
-
export async function dispatchDiagnosticExport(ctx: ExtensionContext, runId: string): Promise<RunActionResult> {
|
|
95
|
+
export async function dispatchDiagnosticExport(ctx: ExtensionContext, runId: string, options: { registry?: MetricRegistry } = {}): Promise<RunActionResult> {
|
|
95
96
|
try {
|
|
96
|
-
const exported = await exportDiagnostic(ctx, runId);
|
|
97
|
+
const exported = await exportDiagnostic(ctx, runId, options);
|
|
97
98
|
return { ok: true, message: `Diagnostic exported to ${exported.path}`, data: exported.path };
|
|
98
99
|
} catch (error) {
|
|
99
100
|
return err(error);
|
package/src/ui/run-dashboard.ts
CHANGED
|
@@ -17,9 +17,11 @@ import { renderMailboxPane } from "./dashboard-panes/mailbox-pane.ts";
|
|
|
17
17
|
import { renderProgressPane } from "./dashboard-panes/progress-pane.ts";
|
|
18
18
|
import { renderTranscriptPane } from "./dashboard-panes/transcript-pane.ts";
|
|
19
19
|
import { renderHealthPane } from "./dashboard-panes/health-pane.ts";
|
|
20
|
+
import { renderMetricsPane } from "./dashboard-panes/metrics-pane.ts";
|
|
20
21
|
import { dashboardActionForKey } from "./keybinding-map.ts";
|
|
21
22
|
import type { RunSnapshotCache, RunUiSnapshot } from "./snapshot-types.ts";
|
|
22
23
|
import { spinnerBucket, spinnerFrame } from "./spinner.ts";
|
|
24
|
+
import type { MetricRegistry } from "../observability/metric-registry.ts";
|
|
23
25
|
|
|
24
26
|
interface DashboardComponent {
|
|
25
27
|
invalidate(): void;
|
|
@@ -34,6 +36,7 @@ export interface RunDashboardOptions {
|
|
|
34
36
|
showTools?: boolean;
|
|
35
37
|
snapshotCache?: RunSnapshotCache;
|
|
36
38
|
runProvider?: () => TeamRunManifest[];
|
|
39
|
+
registry?: MetricRegistry;
|
|
37
40
|
}
|
|
38
41
|
|
|
39
42
|
export type RunDashboardAction = "status" | "summary" | "artifacts" | "api" | "events" | "agents" | "agent-events" | "agent-output" | "agent-transcript" | "mailbox" | "reload" | "mailbox-detail" | "health-recovery" | "health-kill-stale" | "health-diagnostic-export" | "notifications-dismiss";
|
|
@@ -221,7 +224,7 @@ function countByStatus(runs: TeamRunManifest[], snapshotCache?: RunSnapshotCache
|
|
|
221
224
|
export class RunDashboard implements DashboardComponent {
|
|
222
225
|
private selected = 0;
|
|
223
226
|
private showFullProgress = false;
|
|
224
|
-
private activePane: "agents" | "progress" | "mailbox" | "output" | "health" = "agents";
|
|
227
|
+
private activePane: "agents" | "progress" | "mailbox" | "output" | "health" | "metrics" = "agents";
|
|
225
228
|
private runs: TeamRunManifest[];
|
|
226
229
|
private readonly done: (selection: RunDashboardSelection | undefined) => void;
|
|
227
230
|
private readonly theme: CrewTheme;
|
|
@@ -265,7 +268,8 @@ export class RunDashboard implements DashboardComponent {
|
|
|
265
268
|
if (status === "running" || agents.some((agent) => agent.status === "running")) hasRunning = true;
|
|
266
269
|
return snapshot?.signature ?? `${displayRun.runId}:${displayRun.status}:${displayRun.updatedAt}:${status}`;
|
|
267
270
|
}).join("|");
|
|
268
|
-
|
|
271
|
+
const metricsSig = this.activePane === "metrics" ? `:metrics=${this.options.registry?.snapshot().length ?? 0}:${spinnerBucket()}` : "";
|
|
272
|
+
return `${this.selected}:${this.showFullProgress ? 1 : 0}:${this.activePane}:${statuses}${hasRunning ? `:spin=${spinnerBucket()}` : ""}${metricsSig}`;
|
|
269
273
|
}
|
|
270
274
|
|
|
271
275
|
invalidate(): void {
|
|
@@ -295,7 +299,7 @@ export class RunDashboard implements DashboardComponent {
|
|
|
295
299
|
border("╭", "╮"),
|
|
296
300
|
`│ ${pad(truncate(`${fg("accent", "▐")} ${this.theme.bold(this.options.placement === "right" ? "pi-crew right sidebar (anchored top-right)" : "pi-crew dashboard")}`, innerWidth - 1), innerWidth - 1)}│`,
|
|
297
301
|
`│ ${pad(truncate(`Runs: ${this.runs.length} • ${countByStatus(this.runs, this.options.snapshotCache)}`, innerWidth - 1), innerWidth - 1)}│`,
|
|
298
|
-
`│ ${pad(truncate(`↑/↓ select • 1 agents 2 progress 3 mailbox 4 output 5 health • s/u/a/i actions • R/K/D health • H hush`, innerWidth - 1), innerWidth - 1)}│`,
|
|
302
|
+
`│ ${pad(truncate(`↑/↓ select • 1 agents 2 progress 3 mailbox 4 output 5 health 6 metrics • s/u/a/i actions • R/K/D health • H hush`, innerWidth - 1), innerWidth - 1)}│`,
|
|
299
303
|
border("├", "┤"),
|
|
300
304
|
];
|
|
301
305
|
if (this.runs.length === 0) {
|
|
@@ -340,7 +344,9 @@ export class RunDashboard implements DashboardComponent {
|
|
|
340
344
|
? renderMailboxPane(selectedSnapshot)
|
|
341
345
|
: this.activePane === "health"
|
|
342
346
|
? renderHealthPane(selectedSnapshot, { isForeground: selectedDisplayRun.async ? false : true })
|
|
343
|
-
:
|
|
347
|
+
: this.activePane === "metrics"
|
|
348
|
+
? renderMetricsPane(selectedSnapshot, { registry: this.options.registry })
|
|
349
|
+
: renderTranscriptPane(selectedSnapshot)
|
|
344
350
|
: [
|
|
345
351
|
...readAgentPreview(selectedDisplayRun, this.showFullProgress ? 20 : 8, this.options),
|
|
346
352
|
...readProgressPreview(selectedDisplayRun, this.showFullProgress ? 20 : 5),
|
|
@@ -412,6 +418,7 @@ export class RunDashboard implements DashboardComponent {
|
|
|
412
418
|
else if (action === "pane-mailbox") this.activePane = "mailbox";
|
|
413
419
|
else if (action === "pane-output") this.activePane = "output";
|
|
414
420
|
else if (action === "pane-health") this.activePane = "health";
|
|
421
|
+
else if (action === "pane-metrics") this.activePane = "metrics";
|
|
415
422
|
else if (action === "up") this.selected = Math.max(0, this.selected - 1);
|
|
416
423
|
else if (action === "down") {
|
|
417
424
|
const selectableCount = groupedRuns(this.runs, this.options.snapshotCache).filter((row) => row.run).length;
|