pi-crew 0.1.45 → 0.1.49
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +97 -0
- package/README.md +5 -5
- package/agents/analyst.md +11 -11
- package/agents/critic.md +11 -11
- package/agents/executor.md +11 -11
- package/agents/explorer.md +11 -11
- package/agents/planner.md +11 -11
- package/agents/reviewer.md +11 -11
- package/agents/security-reviewer.md +11 -11
- package/agents/test-engineer.md +11 -11
- package/agents/verifier.md +11 -11
- package/agents/writer.md +11 -11
- package/docs/next-upgrade-roadmap.md +808 -0
- package/docs/research/AGENT-EXECUTION-ARCHITECTURE.md +261 -0
- package/docs/research/AGENT-LIFECYCLE-COMPARISON.md +111 -0
- package/docs/research/AUDIT_OH_MY_PI.md +261 -0
- package/docs/research/AUDIT_PI_CREW.md +457 -0
- package/docs/research/CAVEMAN-DEEP-RESEARCH.md +281 -0
- package/docs/research/COMPARISON_OH_MY_PI_VS_PI_CREW.md +264 -0
- package/docs/research/DEEP-RESEARCH-PI-POWERBAR.md +343 -0
- package/docs/research/DEEP_RESEARCH_SUBAGENT_ARCHITECTURE.md +480 -0
- package/docs/research/GAP_CLOSURE_IMPLEMENTATION_PLAN.md +354 -0
- package/docs/research/IMPLEMENTATION_PLAN.md +385 -0
- package/docs/research/LIVE-SESSION-PRODUCTION-READY-PLAN.md +502 -0
- package/docs/research/OH-MY-PI-DEEP-RESEARCH-v14.7.6.md +266 -0
- package/docs/research/REMAINING-GAPS-PLAN.md +363 -0
- package/docs/research/SESSION-SUMMARY-2026-05-08.md +146 -0
- package/docs/research/UI-RESPONSIVENESS-AUDIT.md +173 -0
- package/docs/research-awesome-agent-skills-distillation.md +100 -0
- package/docs/research-oh-my-pi-distillation.md +369 -0
- package/docs/source-runtime-refactor-map.md +24 -0
- package/docs/usage.md +3 -3
- package/install.mjs +52 -8
- package/package.json +99 -98
- package/schema.json +10 -1
- package/skills/async-worker-recovery/SKILL.md +42 -0
- package/skills/context-artifact-hygiene/SKILL.md +52 -0
- package/skills/delegation-patterns/SKILL.md +54 -0
- package/skills/mailbox-interactive/SKILL.md +40 -0
- package/skills/model-routing-context/SKILL.md +39 -0
- package/skills/multi-perspective-review/SKILL.md +58 -0
- package/skills/observability-reliability/SKILL.md +41 -0
- package/skills/orchestration/SKILL.md +157 -0
- package/skills/ownership-session-security/SKILL.md +41 -0
- package/skills/pi-extension-lifecycle/SKILL.md +39 -0
- package/skills/requirements-to-task-packet/SKILL.md +63 -0
- package/skills/resource-discovery-config/SKILL.md +41 -0
- package/skills/runtime-state-reader/SKILL.md +44 -0
- package/skills/secure-agent-orchestration-review/SKILL.md +45 -0
- package/skills/state-mutation-locking/SKILL.md +42 -0
- package/skills/systematic-debugging/SKILL.md +67 -0
- package/skills/ui-render-performance/SKILL.md +39 -0
- package/skills/verification-before-done/SKILL.md +57 -0
- package/skills/worktree-isolation/SKILL.md +39 -0
- package/src/agents/agent-config.ts +6 -0
- package/src/agents/agent-search.ts +98 -0
- package/src/agents/agent-serializer.ts +38 -34
- package/src/agents/discover-agents.ts +29 -15
- package/src/config/config.ts +72 -24
- package/src/config/defaults.ts +25 -0
- package/src/extension/autonomous-policy.ts +26 -33
- package/src/extension/help.ts +1 -0
- package/src/extension/management.ts +5 -0
- package/src/extension/project-init.ts +62 -2
- package/src/extension/register.ts +69 -22
- package/src/extension/registration/commands.ts +64 -25
- package/src/extension/registration/compaction-guard.ts +1 -1
- package/src/extension/registration/subagent-helpers.ts +8 -0
- package/src/extension/registration/subagent-tools.ts +149 -148
- package/src/extension/registration/team-tool.ts +14 -10
- package/src/extension/run-index.ts +35 -21
- package/src/extension/run-maintenance.ts +30 -5
- package/src/extension/team-tool/api.ts +47 -9
- package/src/extension/team-tool/cancel.ts +109 -5
- package/src/extension/team-tool/context.ts +8 -0
- package/src/extension/team-tool/intent-policy.ts +42 -0
- package/src/extension/team-tool/lifecycle-actions.ts +120 -79
- package/src/extension/team-tool/parallel-dispatch.ts +156 -0
- package/src/extension/team-tool/respond.ts +46 -18
- package/src/extension/team-tool/run.ts +55 -12
- package/src/extension/team-tool/status.ts +13 -2
- package/src/extension/team-tool-types.ts +3 -0
- package/src/extension/team-tool.ts +45 -14
- package/src/hooks/registry.ts +61 -0
- package/src/hooks/types.ts +41 -0
- package/src/observability/event-to-metric.ts +8 -1
- package/src/runtime/agent-control.ts +169 -63
- package/src/runtime/async-runner.ts +3 -1
- package/src/runtime/background-runner.ts +78 -53
- package/src/runtime/cancellation-token.ts +89 -0
- package/src/runtime/cancellation.ts +61 -0
- package/src/runtime/capability-inventory.ts +116 -0
- package/src/runtime/child-pi.ts +458 -444
- package/src/runtime/code-summary.ts +247 -0
- package/src/runtime/crash-recovery.ts +182 -0
- package/src/runtime/crew-agent-records.ts +70 -10
- package/src/runtime/crew-agent-runtime.ts +1 -0
- package/src/runtime/custom-tools/irc-tool.ts +201 -0
- package/src/runtime/custom-tools/submit-result-tool.ts +90 -0
- package/src/runtime/deadletter.ts +1 -0
- package/src/runtime/delivery-coordinator.ts +48 -25
- package/src/runtime/effectiveness.ts +81 -0
- package/src/runtime/event-stream-bridge.ts +90 -0
- package/src/runtime/live-agent-control.ts +2 -1
- package/src/runtime/live-agent-manager.ts +179 -85
- package/src/runtime/live-control-realtime.ts +1 -1
- package/src/runtime/live-extension-bridge.ts +150 -0
- package/src/runtime/live-irc.ts +92 -0
- package/src/runtime/live-session-health.ts +100 -0
- package/src/runtime/live-session-runtime.ts +599 -305
- package/src/runtime/manifest-cache.ts +17 -2
- package/src/runtime/mcp-proxy.ts +113 -0
- package/src/runtime/model-fallback.ts +6 -4
- package/src/runtime/notebook-helpers.ts +90 -0
- package/src/runtime/orphan-sentinel.ts +7 -0
- package/src/runtime/output-validator.ts +187 -0
- package/src/runtime/parallel-utils.ts +57 -0
- package/src/runtime/parent-guard.ts +80 -0
- package/src/runtime/pi-args.ts +18 -3
- package/src/runtime/process-status.ts +5 -1
- package/src/runtime/prose-compressor.ts +164 -0
- package/src/runtime/result-extractor.ts +121 -0
- package/src/runtime/retry-executor.ts +81 -64
- package/src/runtime/runtime-resolver.ts +23 -10
- package/src/runtime/semaphore.ts +131 -0
- package/src/runtime/sensitive-paths.ts +92 -0
- package/src/runtime/skill-instructions.ts +222 -0
- package/src/runtime/stale-reconciler.ts +4 -14
- package/src/runtime/stream-preview.ts +177 -0
- package/src/runtime/subagent-manager.ts +6 -2
- package/src/runtime/subprocess-tool-registry.ts +67 -0
- package/src/runtime/task-output-context.ts +177 -127
- package/src/runtime/task-runner/capabilities.ts +78 -0
- package/src/runtime/task-runner/live-executor.ts +107 -101
- package/src/runtime/task-runner/prompt-builder.ts +72 -8
- package/src/runtime/task-runner/prompt-pipeline.ts +64 -0
- package/src/runtime/task-runner/run-projection.ts +104 -0
- package/src/runtime/task-runner.ts +115 -5
- package/src/runtime/team-runner.ts +134 -19
- package/src/runtime/workspace-tree.ts +298 -0
- package/src/runtime/yield-handler.ts +189 -0
- package/src/schema/config-schema.ts +7 -0
- package/src/schema/team-tool-schema.ts +14 -4
- package/src/skills/discover-skills.ts +67 -0
- package/src/state/active-run-registry.ts +167 -0
- package/src/state/artifact-store.ts +4 -1
- package/src/state/atomic-write.ts +50 -1
- package/src/state/blob-store.ts +117 -0
- package/src/state/contracts.ts +2 -1
- package/src/state/event-log-rotation.ts +158 -0
- package/src/state/event-log.ts +52 -2
- package/src/state/mailbox.ts +129 -9
- package/src/state/state-store.ts +32 -5
- package/src/state/types.ts +64 -2
- package/src/teams/team-config.ts +1 -0
- package/src/ui/agent-management-overlay.ts +144 -0
- package/src/ui/crew-widget.ts +15 -5
- package/src/ui/dashboard-panes/cancellation-pane.ts +43 -0
- package/src/ui/dashboard-panes/capability-pane.ts +60 -0
- package/src/ui/dashboard-panes/mailbox-pane.ts +35 -11
- package/src/ui/dashboard-panes/progress-pane.ts +2 -0
- package/src/ui/live-run-sidebar.ts +4 -0
- package/src/ui/powerbar-publisher.ts +77 -15
- package/src/ui/render-coalescer.ts +51 -0
- package/src/ui/run-dashboard.ts +4 -0
- package/src/ui/run-event-bus.ts +209 -0
- package/src/ui/run-snapshot-cache.ts +78 -18
- package/src/ui/snapshot-types.ts +10 -0
- package/src/ui/transcript-entries.ts +258 -0
- package/src/utils/ids.ts +5 -0
- package/src/utils/incremental-reader.ts +104 -0
- package/src/utils/paths.ts +4 -2
- package/src/utils/scan-cache.ts +137 -0
- package/src/utils/sse-parser.ts +134 -0
- package/src/utils/task-name-generator.ts +337 -0
- package/src/utils/visual.ts +33 -2
- package/src/workflows/workflow-config.ts +1 -0
- package/src/worktree/cleanup.ts +2 -1
|
@@ -10,6 +10,7 @@ import { logInternalError } from "../utils/internal-error.ts";
|
|
|
10
10
|
import type { ManifestCache } from "../runtime/manifest-cache.ts";
|
|
11
11
|
import type { RunSnapshotCache, RunUiSnapshot } from "./snapshot-types.ts";
|
|
12
12
|
import { notificationBadge } from "./crew-widget.ts";
|
|
13
|
+
import { RenderCoalescer } from "./render-coalescer.ts";
|
|
13
14
|
|
|
14
15
|
type EventBus = { emit?: (event: string, data: unknown) => void; listenerCount?: (event: string) => number } | undefined;
|
|
15
16
|
type StatusContext = { hasUI?: boolean; ui?: { setStatus?: (key: string, text: string | undefined) => void } } | undefined;
|
|
@@ -84,6 +85,8 @@ export function updatePiCrewPowerbar(events: EventBus, cwd: string, config?: Cre
|
|
|
84
85
|
return { run, agents, tasks: readTasks(run.tasksPath), snapshot };
|
|
85
86
|
}).filter((item) => isDisplayActiveRun(item.run, item.agents));
|
|
86
87
|
if (!active.length) {
|
|
88
|
+
lastEmittedActive = undefined;
|
|
89
|
+
lastEmittedProgress = undefined;
|
|
87
90
|
safeEmit(events, "powerbar:update", { id: "pi-crew-active" });
|
|
88
91
|
safeEmit(events, "powerbar:update", { id: "pi-crew-progress" });
|
|
89
92
|
if (useStatusFallback) setStatusFallback(ctx, undefined);
|
|
@@ -104,25 +107,84 @@ export function updatePiCrewPowerbar(events: EventBus, cwd: string, config?: Cre
|
|
|
104
107
|
const activeText = `crew ${running}a/${waiting}w${notificationBadge(notificationCount)}`;
|
|
105
108
|
const activeSuffix = [model, tokenText].filter(Boolean).join(" · ") || undefined;
|
|
106
109
|
const progressSuffix = `${completed}/${total}${tokenText ? ` · ${tokenText}` : ""}`;
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
110
|
+
const activeKey = `${activeText}|${activeSuffix ?? ""}|${running}`;
|
|
111
|
+
const progressKey = `${(active[0]?.run as TeamRunManifest)?.team ?? "crew"}|${completed}/${total}|${tokenText ?? ""}`;
|
|
112
|
+
const changed = activeKey !== lastEmittedActive || progressKey !== lastEmittedProgress;
|
|
113
|
+
if (changed) {
|
|
114
|
+
lastEmittedActive = activeKey;
|
|
115
|
+
lastEmittedProgress = progressKey;
|
|
116
|
+
safeEmit(events, "powerbar:update", {
|
|
117
|
+
id: "pi-crew-active",
|
|
118
|
+
icon: "⚙",
|
|
119
|
+
text: activeText,
|
|
120
|
+
suffix: activeSuffix,
|
|
121
|
+
color: running ? "accent" : "warning",
|
|
122
|
+
});
|
|
123
|
+
safeEmit(events, "powerbar:update", {
|
|
124
|
+
id: "pi-crew-progress",
|
|
125
|
+
text: (active[0]?.run as TeamRunManifest)?.team ?? "crew",
|
|
126
|
+
bar: Math.round((completed / total) * 100),
|
|
127
|
+
suffix: progressSuffix,
|
|
128
|
+
color: completed === total ? "success" : "accent",
|
|
129
|
+
barSegments: 8,
|
|
130
|
+
});
|
|
131
|
+
}
|
|
122
132
|
if (useStatusFallback) setStatusFallback(ctx, `${activeText}${activeSuffix ? ` · ${activeSuffix}` : ""} · ${progressSuffix}`);
|
|
123
133
|
}
|
|
124
134
|
|
|
135
|
+
// --- Dedup state: skip emit if segment data unchanged ---
|
|
136
|
+
let lastEmittedActive: string | undefined;
|
|
137
|
+
let lastEmittedProgress: string | undefined;
|
|
138
|
+
|
|
139
|
+
// --- Coalesced powerbar update ---
|
|
140
|
+
|
|
141
|
+
interface PowerbarUpdateArgs {
|
|
142
|
+
events: EventBus;
|
|
143
|
+
cwd: string;
|
|
144
|
+
config?: CrewUiConfig;
|
|
145
|
+
manifestCache?: ManifestCache;
|
|
146
|
+
snapshotCache?: RunSnapshotCache;
|
|
147
|
+
ctx?: StatusContext;
|
|
148
|
+
notificationCount: number;
|
|
149
|
+
preloadedManifests?: TeamRunManifest[];
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
let latestArgs: PowerbarUpdateArgs | null = null;
|
|
153
|
+
|
|
154
|
+
const powerbarCoalescer = new RenderCoalescer(() => {
|
|
155
|
+
if (!latestArgs) return;
|
|
156
|
+
const a = latestArgs;
|
|
157
|
+
latestArgs = null;
|
|
158
|
+
updatePiCrewPowerbar(a.events, a.cwd, a.config, a.manifestCache, a.snapshotCache, a.ctx, a.notificationCount, a.preloadedManifests);
|
|
159
|
+
}, 200);
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Request a coalesced powerbar update. Multiple rapid calls are batched into a single
|
|
163
|
+
* render pass within 200ms, preventing UI flicker from event bursts.
|
|
164
|
+
*/
|
|
165
|
+
export function requestPowerbarUpdate(
|
|
166
|
+
events: EventBus,
|
|
167
|
+
cwd: string,
|
|
168
|
+
config?: CrewUiConfig,
|
|
169
|
+
manifestCache?: ManifestCache,
|
|
170
|
+
snapshotCache?: RunSnapshotCache,
|
|
171
|
+
ctx?: StatusContext,
|
|
172
|
+
notificationCount = 0,
|
|
173
|
+
preloadedManifests?: TeamRunManifest[],
|
|
174
|
+
): void {
|
|
175
|
+
if (config?.powerbar === false) return;
|
|
176
|
+
latestArgs = { events, cwd, config, manifestCache, snapshotCache, ctx, notificationCount, preloadedManifests };
|
|
177
|
+
powerbarCoalescer.request();
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/** Dispose the powerbar coalescer. Call during extension cleanup. */
|
|
181
|
+
export function disposePowerbarCoalescer(): void {
|
|
182
|
+
powerbarCoalescer.dispose();
|
|
183
|
+
}
|
|
184
|
+
|
|
125
185
|
export function clearPiCrewPowerbar(events: EventBus, ctx?: StatusContext): void {
|
|
186
|
+
lastEmittedActive = undefined;
|
|
187
|
+
lastEmittedProgress = undefined;
|
|
126
188
|
safeEmit(events, "powerbar:update", { id: "pi-crew-active" });
|
|
127
189
|
safeEmit(events, "powerbar:update", { id: "pi-crew-progress" });
|
|
128
190
|
setStatusFallback(ctx, undefined);
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RenderCoalescer — batches multiple render requests into single render passes.
|
|
3
|
+
* Prevents UI flicker when many events arrive in quick succession.
|
|
4
|
+
* Inspired by oh-my-pi's PROGRESS_COALESCE_MS (150ms) pattern.
|
|
5
|
+
*/
|
|
6
|
+
export class RenderCoalescer {
|
|
7
|
+
#pending = false;
|
|
8
|
+
#timerId: ReturnType<typeof setTimeout> | null = null;
|
|
9
|
+
#callback: () => void;
|
|
10
|
+
#intervalMs: number;
|
|
11
|
+
|
|
12
|
+
constructor(callback: () => void, intervalMs = 32) {
|
|
13
|
+
this.#callback = callback;
|
|
14
|
+
this.#intervalMs = intervalMs;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Request a render. Will be coalesced with other requests within the interval. */
|
|
18
|
+
request(): void {
|
|
19
|
+
if (this.#pending) return;
|
|
20
|
+
this.#pending = true;
|
|
21
|
+
this.#timerId = setTimeout(() => {
|
|
22
|
+
this.#pending = false;
|
|
23
|
+
this.#timerId = null;
|
|
24
|
+
this.#callback();
|
|
25
|
+
}, this.#intervalMs);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Force an immediate render, bypassing coalescing. */
|
|
29
|
+
flush(): void {
|
|
30
|
+
if (this.#timerId !== null) {
|
|
31
|
+
clearTimeout(this.#timerId);
|
|
32
|
+
this.#timerId = null;
|
|
33
|
+
}
|
|
34
|
+
this.#pending = false;
|
|
35
|
+
this.#callback();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Check if a render is pending. */
|
|
39
|
+
get pending(): boolean {
|
|
40
|
+
return this.#pending;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Clean up timers. Call when the coalescer is no longer needed. */
|
|
44
|
+
dispose(): void {
|
|
45
|
+
if (this.#timerId !== null) {
|
|
46
|
+
clearTimeout(this.#timerId);
|
|
47
|
+
this.#timerId = null;
|
|
48
|
+
}
|
|
49
|
+
this.#pending = false;
|
|
50
|
+
}
|
|
51
|
+
}
|
package/src/ui/run-dashboard.ts
CHANGED
|
@@ -24,6 +24,7 @@ import type { RunSnapshotCache, RunUiSnapshot } from "./snapshot-types.ts";
|
|
|
24
24
|
import { spinnerBucket, spinnerFrame } from "./spinner.ts";
|
|
25
25
|
import type { MetricRegistry } from "../observability/metric-registry.ts";
|
|
26
26
|
import { resolveRealContainedPath } from "../utils/safe-paths.ts";
|
|
27
|
+
import { runEventBus } from "./run-event-bus.ts";
|
|
27
28
|
|
|
28
29
|
interface DashboardComponent {
|
|
29
30
|
invalidate(): void;
|
|
@@ -253,6 +254,7 @@ export class RunDashboard implements DashboardComponent {
|
|
|
253
254
|
private cachedVersion = "";
|
|
254
255
|
private cachedLines: string[] = [];
|
|
255
256
|
private readonly unsubscribeTheme: () => void;
|
|
257
|
+
private readonly unsubscribeEventBus: () => void;
|
|
256
258
|
|
|
257
259
|
constructor(
|
|
258
260
|
runs: TeamRunManifest[],
|
|
@@ -265,6 +267,7 @@ export class RunDashboard implements DashboardComponent {
|
|
|
265
267
|
this.theme = asCrewTheme(theme);
|
|
266
268
|
this.options = options;
|
|
267
269
|
this.unsubscribeTheme = subscribeThemeChange(theme, () => this.invalidate());
|
|
270
|
+
this.unsubscribeEventBus = runEventBus.onAny(() => this.invalidate());
|
|
268
271
|
}
|
|
269
272
|
|
|
270
273
|
private refreshRuns(): void {
|
|
@@ -301,6 +304,7 @@ export class RunDashboard implements DashboardComponent {
|
|
|
301
304
|
|
|
302
305
|
dispose(): void {
|
|
303
306
|
this.unsubscribeTheme();
|
|
307
|
+
this.unsubscribeEventBus();
|
|
304
308
|
}
|
|
305
309
|
|
|
306
310
|
private selectedRunId(): string | undefined {
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import type { TeamEvent } from "../state/event-log.ts";
|
|
2
|
+
|
|
3
|
+
export type RunEventType =
|
|
4
|
+
| "task_started"
|
|
5
|
+
| "task_completed"
|
|
6
|
+
| "task_failed"
|
|
7
|
+
| "task_cancelled"
|
|
8
|
+
| "worker_status"
|
|
9
|
+
| "mailbox_updated"
|
|
10
|
+
| "effectiveness_changed"
|
|
11
|
+
| "run_started"
|
|
12
|
+
| "run_completed"
|
|
13
|
+
| "run_blocked"
|
|
14
|
+
| "run_cancelled";
|
|
15
|
+
|
|
16
|
+
/** Typed channel names for category-based event subscription. */
|
|
17
|
+
export type EventChannel =
|
|
18
|
+
| "worker:progress"
|
|
19
|
+
| "worker:lifecycle"
|
|
20
|
+
| "worker:stream"
|
|
21
|
+
| "run:state"
|
|
22
|
+
| "ui:invalidate";
|
|
23
|
+
|
|
24
|
+
/** Sets used by classifyEventChannel for O(1) lookup. */
|
|
25
|
+
const WORKER_PROGRESS_TYPES = new Set([
|
|
26
|
+
"tool_execution_start", "tool_result", "agent_progress", "worker_status",
|
|
27
|
+
]);
|
|
28
|
+
const WORKER_LIFECYCLE_TYPES = new Set([
|
|
29
|
+
"task.started", "task.completed", "task.failed",
|
|
30
|
+
"task_started", "task_completed", "task_failed", "task_cancelled",
|
|
31
|
+
"run.started", "run.completed", "run.cancelled", "run.failed",
|
|
32
|
+
"run_started", "run_completed", "run_cancelled", "run_blocked",
|
|
33
|
+
]);
|
|
34
|
+
const WORKER_STREAM_TYPES = new Set([
|
|
35
|
+
"stdout_chunk", "stderr_chunk", "stream",
|
|
36
|
+
]);
|
|
37
|
+
const RUN_STATE_TYPES = new Set([
|
|
38
|
+
"manifest.saved", "task.claimed", "task.unclaimed", "mailbox_updated",
|
|
39
|
+
]);
|
|
40
|
+
const UI_INVALIDATE_TYPES = new Set([
|
|
41
|
+
"effectiveness_changed", "snapshot_stale",
|
|
42
|
+
]);
|
|
43
|
+
|
|
44
|
+
/** Classify an event type string into a typed channel. */
|
|
45
|
+
export function classifyEventChannel(type: string): EventChannel {
|
|
46
|
+
if (WORKER_PROGRESS_TYPES.has(type)) return "worker:progress";
|
|
47
|
+
if (WORKER_LIFECYCLE_TYPES.has(type)) return "worker:lifecycle";
|
|
48
|
+
if (WORKER_STREAM_TYPES.has(type)) return "worker:stream";
|
|
49
|
+
if (RUN_STATE_TYPES.has(type)) return "run:state";
|
|
50
|
+
if (UI_INVALIDATE_TYPES.has(type)) return "ui:invalidate";
|
|
51
|
+
return "worker:progress"; // default fallback
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface RunEventPayload {
|
|
55
|
+
type: RunEventType;
|
|
56
|
+
runId: string;
|
|
57
|
+
taskId?: string;
|
|
58
|
+
timestamp?: string;
|
|
59
|
+
data?: unknown;
|
|
60
|
+
channel?: EventChannel;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export type RunEventCallback = (event: RunEventPayload) => void;
|
|
64
|
+
|
|
65
|
+
class RunEventBus {
|
|
66
|
+
#listeners = new Map<string, Set<RunEventCallback>>();
|
|
67
|
+
#globalListeners = new Set<RunEventCallback>();
|
|
68
|
+
#channelListeners = new Map<EventChannel, Set<RunEventCallback>>();
|
|
69
|
+
#channelRunListeners = new Map<string, Map<EventChannel, Set<RunEventCallback>>>();
|
|
70
|
+
|
|
71
|
+
on(runId: string, callback: RunEventCallback): () => void {
|
|
72
|
+
const listeners = this.#listeners.get(runId) ?? new Set();
|
|
73
|
+
listeners.add(callback);
|
|
74
|
+
this.#listeners.set(runId, listeners);
|
|
75
|
+
return () => { listeners.delete(callback); if (listeners.size === 0) this.#listeners.delete(runId); };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
onAny(callback: RunEventCallback): () => void {
|
|
79
|
+
this.#globalListeners.add(callback);
|
|
80
|
+
return () => { this.#globalListeners.delete(callback); };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
off(runId: string, callback: RunEventCallback): void {
|
|
84
|
+
const listeners = this.#listeners.get(runId);
|
|
85
|
+
if (listeners) {
|
|
86
|
+
listeners.delete(callback);
|
|
87
|
+
if (listeners.size === 0) this.#listeners.delete(runId);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** Subscribe to all events on a specific channel. */
|
|
92
|
+
onChannel(channel: EventChannel, callback: RunEventCallback): () => void {
|
|
93
|
+
const listeners = this.#channelListeners.get(channel) ?? new Set();
|
|
94
|
+
listeners.add(callback);
|
|
95
|
+
this.#channelListeners.set(channel, listeners);
|
|
96
|
+
return () => {
|
|
97
|
+
listeners.delete(callback);
|
|
98
|
+
if (listeners.size === 0) this.#channelListeners.delete(channel);
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** Subscribe to events on a specific channel for a given runId. */
|
|
103
|
+
onChannelForRun(channel: EventChannel, runId: string, callback: RunEventCallback): () => void {
|
|
104
|
+
const runKey = `${channel}::${runId}`;
|
|
105
|
+
const runMap = this.#channelRunListeners.get(runKey) ?? new Map();
|
|
106
|
+
const listeners = runMap.get(channel) ?? new Set();
|
|
107
|
+
listeners.add(callback);
|
|
108
|
+
runMap.set(channel, listeners);
|
|
109
|
+
this.#channelRunListeners.set(runKey, runMap);
|
|
110
|
+
return () => {
|
|
111
|
+
listeners.delete(callback);
|
|
112
|
+
if (listeners.size === 0) runMap.delete(channel);
|
|
113
|
+
if (runMap.size === 0) this.#channelRunListeners.delete(runKey);
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
emit(event: RunEventPayload): void {
|
|
118
|
+
// Auto-classify channel if not already set.
|
|
119
|
+
// M2: Use local variable for routing, but also set on event
|
|
120
|
+
// for subscriber API contract (listeners read event.channel).
|
|
121
|
+
if (!event.channel) {
|
|
122
|
+
(event as { channel?: EventChannel }).channel = classifyEventChannel(event.type);
|
|
123
|
+
}
|
|
124
|
+
const channel = event.channel!;
|
|
125
|
+
|
|
126
|
+
// Existing: runId-specific listeners
|
|
127
|
+
const listeners = this.#listeners.get(event.runId);
|
|
128
|
+
if (listeners) {
|
|
129
|
+
for (const cb of listeners) {
|
|
130
|
+
try { cb(event); } catch { /* subscriber errors are non-fatal */ }
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Existing: global listeners
|
|
135
|
+
for (const cb of this.#globalListeners) {
|
|
136
|
+
try { cb(event); } catch { /* subscriber errors are non-fatal */ }
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// New: channel listeners
|
|
140
|
+
const channelListeners = this.#channelListeners.get(channel);
|
|
141
|
+
if (channelListeners) {
|
|
142
|
+
for (const cb of channelListeners) {
|
|
143
|
+
try { cb(event); } catch { /* subscriber errors are non-fatal */ }
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// New: channel+runId listeners
|
|
148
|
+
const runKey = `${channel}::${event.runId}`;
|
|
149
|
+
const runMap = this.#channelRunListeners.get(runKey);
|
|
150
|
+
if (runMap) {
|
|
151
|
+
const runChannelListeners = runMap.get(channel);
|
|
152
|
+
if (runChannelListeners) {
|
|
153
|
+
for (const cb of runChannelListeners) {
|
|
154
|
+
try { cb(event); } catch { /* subscriber errors are non-fatal */ }
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** Dispose all subscriptions including channel-based ones. */
|
|
161
|
+
dispose(): void {
|
|
162
|
+
this.#listeners.clear();
|
|
163
|
+
this.#globalListeners.clear();
|
|
164
|
+
this.#channelListeners.clear();
|
|
165
|
+
this.#channelRunListeners.clear();
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
listenerCount(runId?: string): number {
|
|
169
|
+
if (runId) return this.#listeners.get(runId)?.size ?? 0;
|
|
170
|
+
let total = this.#globalListeners.size;
|
|
171
|
+
for (const set of this.#listeners.values()) total += set.size;
|
|
172
|
+
for (const set of this.#channelListeners.values()) total += set.size;
|
|
173
|
+
for (const runMap of this.#channelRunListeners.values()) {
|
|
174
|
+
for (const set of runMap.values()) total += set.size;
|
|
175
|
+
}
|
|
176
|
+
return total;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/** Global singleton run event bus for UI-first event delivery. */
|
|
181
|
+
export const runEventBus = new RunEventBus();
|
|
182
|
+
|
|
183
|
+
/** Derive a RunEventType from a TeamEvent. */
|
|
184
|
+
export function teamEventToRunEventType(event: TeamEvent): RunEventType | undefined {
|
|
185
|
+
const type = event.type;
|
|
186
|
+
if (type === "task.started") return "task_started";
|
|
187
|
+
if (type === "task.completed") return "task_completed";
|
|
188
|
+
if (type === "task.failed") return "task_failed";
|
|
189
|
+
if (type === "run.completed") return "run_completed";
|
|
190
|
+
if (type === "run.blocked") return "run_blocked";
|
|
191
|
+
if (type === "run.running") return "run_started";
|
|
192
|
+
if (type === "run.cancelled") return "run_cancelled";
|
|
193
|
+
if (type === "task.progress" || type === "mailbox.message_queued" || type === "mailbox.message_delivered") return "mailbox_updated";
|
|
194
|
+
if (type === "run.effectiveness" || type === "task.attention") return "effectiveness_changed";
|
|
195
|
+
return undefined;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/** Emit a run event from a TeamEvent. */
|
|
199
|
+
export function emitFromTeamEvent(event: TeamEvent): void {
|
|
200
|
+
const type = teamEventToRunEventType(event);
|
|
201
|
+
if (!type) return;
|
|
202
|
+
runEventBus.emit({
|
|
203
|
+
type,
|
|
204
|
+
runId: event.runId,
|
|
205
|
+
taskId: event.taskId,
|
|
206
|
+
timestamp: event.time,
|
|
207
|
+
data: event.data,
|
|
208
|
+
});
|
|
209
|
+
}
|
|
@@ -9,6 +9,7 @@ import type { MailboxMessageStatus } from "../state/mailbox.ts";
|
|
|
9
9
|
import { loadRunManifestById, loadRunManifestByIdAsync } from "../state/state-store.ts";
|
|
10
10
|
import type { TeamRunManifest, TeamTaskState } from "../state/types.ts";
|
|
11
11
|
import type { RunSnapshotCache as RunSnapshotCacheBase, RunUiGroupJoin, RunUiMailbox, RunUiProgress, RunUiSnapshot, RunUiUsage } from "./snapshot-types.ts";
|
|
12
|
+
import { runEventBus } from "./run-event-bus.ts";
|
|
12
13
|
|
|
13
14
|
export interface RunSnapshotCache extends RunSnapshotCacheBase {
|
|
14
15
|
preloadStale(runId: string): Promise<RunUiSnapshot | undefined>;
|
|
@@ -378,6 +379,13 @@ interface MailboxCount {
|
|
|
378
379
|
approximate: boolean;
|
|
379
380
|
}
|
|
380
381
|
|
|
382
|
+
interface MailboxKindCount extends MailboxCount {
|
|
383
|
+
steer: number;
|
|
384
|
+
followUp: number;
|
|
385
|
+
response: number;
|
|
386
|
+
message: number;
|
|
387
|
+
}
|
|
388
|
+
|
|
381
389
|
function tailApproximate(filePath: string): boolean {
|
|
382
390
|
try {
|
|
383
391
|
return fs.statSync(filePath).size > MAX_TAIL_BYTES;
|
|
@@ -394,34 +402,54 @@ async function tailApproximateAsync(filePath: string): Promise<boolean> {
|
|
|
394
402
|
}
|
|
395
403
|
}
|
|
396
404
|
|
|
397
|
-
function readMailboxCounts(filePath: string, delivery: Record<string, MailboxMessageStatus>):
|
|
405
|
+
function readMailboxCounts(filePath: string, delivery: Record<string, MailboxMessageStatus>): MailboxKindCount {
|
|
406
|
+
const kindCounts = { steer: 0, followUp: 0, response: 0, message: 0 };
|
|
398
407
|
const items = tailJsonlLines(filePath, MAX_TAIL_LINES, (line) => {
|
|
399
408
|
try {
|
|
400
409
|
const parsed = JSON.parse(line) as unknown;
|
|
401
410
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return 0;
|
|
402
|
-
const
|
|
403
|
-
if (typeof
|
|
404
|
-
|
|
411
|
+
const msg = parsed as { id?: unknown; status?: unknown; kind?: unknown; data?: unknown };
|
|
412
|
+
if (typeof msg.id !== "string" || !isMailboxStatus(msg.status)) return 0;
|
|
413
|
+
if (msg.status !== "acknowledged" && delivery[msg.id] !== "acknowledged") {
|
|
414
|
+
const kind = typeof msg.kind === "string" ? msg.kind : typeof (msg.data as Record<string, unknown>)?.kind === "string" ? (msg.data as Record<string, unknown>).kind as string : undefined;
|
|
415
|
+
if (kind === "steer") kindCounts.steer++;
|
|
416
|
+
else if (kind === "follow-up") kindCounts.followUp++;
|
|
417
|
+
else if (kind === "response") kindCounts.response++;
|
|
418
|
+
else kindCounts.message++;
|
|
419
|
+
return 1;
|
|
420
|
+
}
|
|
421
|
+
return 0;
|
|
405
422
|
} catch {
|
|
406
423
|
return 0;
|
|
407
424
|
}
|
|
408
425
|
}) as number[];
|
|
409
|
-
|
|
426
|
+
const count = items.reduce((sum, val) => sum + val, 0);
|
|
427
|
+
return { count, approximate: tailApproximate(filePath), steer: kindCounts.steer, followUp: kindCounts.followUp, response: kindCounts.response, message: kindCounts.message };
|
|
410
428
|
}
|
|
411
429
|
|
|
412
|
-
async function readMailboxCountsAsync(filePath: string, delivery: Record<string, MailboxMessageStatus>): Promise<
|
|
430
|
+
async function readMailboxCountsAsync(filePath: string, delivery: Record<string, MailboxMessageStatus>): Promise<MailboxKindCount> {
|
|
431
|
+
const kindCounts = { steer: 0, followUp: 0, response: 0, message: 0 };
|
|
413
432
|
const items = await tailJsonlLinesAsync(filePath, MAX_TAIL_LINES, (line) => {
|
|
414
433
|
try {
|
|
415
434
|
const parsed = JSON.parse(line) as unknown;
|
|
416
435
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return 0;
|
|
417
|
-
const
|
|
418
|
-
if (typeof
|
|
419
|
-
|
|
436
|
+
const msg = parsed as { id?: unknown; status?: unknown; kind?: unknown; data?: unknown };
|
|
437
|
+
if (typeof msg.id !== "string" || !isMailboxStatus(msg.status)) return 0;
|
|
438
|
+
if (msg.status !== "acknowledged" && delivery[msg.id] !== "acknowledged") {
|
|
439
|
+
const kind = typeof msg.kind === "string" ? msg.kind : typeof (msg.data as Record<string, unknown>)?.kind === "string" ? (msg.data as Record<string, unknown>).kind as string : undefined;
|
|
440
|
+
if (kind === "steer") kindCounts.steer++;
|
|
441
|
+
else if (kind === "follow-up") kindCounts.followUp++;
|
|
442
|
+
else if (kind === "response") kindCounts.response++;
|
|
443
|
+
else kindCounts.message++;
|
|
444
|
+
return 1;
|
|
445
|
+
}
|
|
446
|
+
return 0;
|
|
420
447
|
} catch {
|
|
421
448
|
return 0;
|
|
422
449
|
}
|
|
423
450
|
}) as number[];
|
|
424
|
-
|
|
451
|
+
const count = items.reduce((sum, val) => sum + val, 0);
|
|
452
|
+
return { count, approximate: await tailApproximateAsync(filePath), steer: kindCounts.steer, followUp: kindCounts.followUp, response: kindCounts.response, message: kindCounts.message };
|
|
425
453
|
}
|
|
426
454
|
|
|
427
455
|
function groupJoinsFrom(manifest: TeamRunManifest): RunUiGroupJoin[] {
|
|
@@ -436,6 +464,17 @@ async function groupJoinsFromAsync(manifest: TeamRunManifest): Promise<RunUiGrou
|
|
|
436
464
|
return (await readGroupJoinMailboxAsync(path.join(root, "outbox.jsonl"), delivery)).slice(-5);
|
|
437
465
|
}
|
|
438
466
|
|
|
467
|
+
function mergeKindCounts(a: MailboxKindCount, b: MailboxKindCount): MailboxKindCount {
|
|
468
|
+
return {
|
|
469
|
+
count: a.count + b.count,
|
|
470
|
+
approximate: a.approximate || b.approximate,
|
|
471
|
+
steer: a.steer + b.steer,
|
|
472
|
+
followUp: a.followUp + b.followUp,
|
|
473
|
+
response: a.response + b.response,
|
|
474
|
+
message: a.message + b.message,
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
|
|
439
478
|
function mailboxFrom(manifest: TeamRunManifest, agents: CrewAgentRecord[]): RunUiMailbox {
|
|
440
479
|
const root = path.join(manifest.stateRoot, "mailbox");
|
|
441
480
|
const delivery = readDeliveryMessages(path.join(root, "delivery.json"));
|
|
@@ -447,14 +486,17 @@ function mailboxFrom(manifest: TeamRunManifest, agents: CrewAgentRecord[]): RunU
|
|
|
447
486
|
if (!entry.isDirectory()) continue;
|
|
448
487
|
const taskInbox = readMailboxCounts(path.join(tasksRoot, entry.name, "inbox.jsonl"), delivery);
|
|
449
488
|
const taskOutbox = readMailboxCounts(path.join(tasksRoot, entry.name, "outbox.jsonl"), delivery);
|
|
450
|
-
inbox =
|
|
451
|
-
outbox =
|
|
489
|
+
inbox = mergeKindCounts(inbox, taskInbox);
|
|
490
|
+
outbox = mergeKindCounts(outbox, taskOutbox);
|
|
452
491
|
}
|
|
453
492
|
} catch {
|
|
454
493
|
// No task mailboxes yet.
|
|
455
494
|
}
|
|
456
495
|
const attentionAgents = agents.filter((agent) => agent.progress?.activityState === "needs_attention").length;
|
|
457
|
-
return {
|
|
496
|
+
return {
|
|
497
|
+
inboxUnread: inbox.count, outboxPending: outbox.count, needsAttention: inbox.count + attentionAgents, approximate: inbox.approximate || outbox.approximate,
|
|
498
|
+
steerUnread: inbox.steer + outbox.steer, followUpUnread: inbox.followUp + outbox.followUp, responseUnread: inbox.response + outbox.response, messageUnread: inbox.message + outbox.message,
|
|
499
|
+
};
|
|
458
500
|
}
|
|
459
501
|
|
|
460
502
|
async function mailboxFromAsync(manifest: TeamRunManifest, agents: CrewAgentRecord[]): Promise<RunUiMailbox> {
|
|
@@ -468,14 +510,21 @@ async function mailboxFromAsync(manifest: TeamRunManifest, agents: CrewAgentReco
|
|
|
468
510
|
if (!entry.isDirectory()) continue;
|
|
469
511
|
const taskInbox = await readMailboxCountsAsync(path.join(tasksRoot, entry.name, "inbox.jsonl"), delivery);
|
|
470
512
|
const taskOutbox = await readMailboxCountsAsync(path.join(tasksRoot, entry.name, "outbox.jsonl"), delivery);
|
|
471
|
-
inbox =
|
|
472
|
-
outbox =
|
|
513
|
+
inbox = mergeKindCounts(inbox, taskInbox);
|
|
514
|
+
outbox = mergeKindCounts(outbox, taskOutbox);
|
|
473
515
|
}
|
|
474
516
|
} catch {
|
|
475
517
|
// No task mailboxes yet.
|
|
476
518
|
}
|
|
477
519
|
const attentionAgents = agents.filter((agent) => agent.progress?.activityState === "needs_attention").length;
|
|
478
|
-
return {
|
|
520
|
+
return {
|
|
521
|
+
inboxUnread: inbox.count, outboxPending: outbox.count, needsAttention: inbox.count + attentionAgents, approximate: inbox.approximate || outbox.approximate,
|
|
522
|
+
steerUnread: inbox.steer + outbox.steer, followUpUnread: inbox.followUp + outbox.followUp, responseUnread: inbox.response + outbox.response, messageUnread: inbox.message + outbox.message,
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
function cancellationReasonFromEvents(events: TeamEvent[]): string | undefined {
|
|
527
|
+
return [...events].reverse().find((event) => event.type === "run.cancelled" && typeof event.data?.reason === "string")?.data?.reason as string | undefined;
|
|
479
528
|
}
|
|
480
529
|
|
|
481
530
|
function signatureFor(input: Omit<RunUiSnapshot, "signature" | "fetchedAt">, stamps: SnapshotStamps): string {
|
|
@@ -489,7 +538,8 @@ function signatureFor(input: Omit<RunUiSnapshot, "signature" | "fetchedAt">, sta
|
|
|
489
538
|
usage: input.usage,
|
|
490
539
|
mailbox: input.mailbox,
|
|
491
540
|
groupJoins: input.groupJoins,
|
|
492
|
-
events: input.recentEvents.map((event) => [event.metadata?.seq, event.time, event.type, event.taskId, event.message]),
|
|
541
|
+
events: input.recentEvents.map((event) => [event.metadata?.seq, event.time, event.type, event.taskId, event.message, event.data?.reason]),
|
|
542
|
+
cancellationReason: input.cancellationReason,
|
|
493
543
|
output: input.recentOutputLines,
|
|
494
544
|
stamps,
|
|
495
545
|
}));
|
|
@@ -571,6 +621,7 @@ export function createRunSnapshotCache(cwd: string, options: RunSnapshotCacheOpt
|
|
|
571
621
|
}
|
|
572
622
|
const mailbox = mailboxFrom(loaded.manifest, agents);
|
|
573
623
|
const groupJoins = groupJoinsFrom(loaded.manifest);
|
|
624
|
+
const recentEvents = safeRecentEvents(loaded.manifest.eventsPath, recentEventsLimit);
|
|
574
625
|
const base = {
|
|
575
626
|
runId: loaded.manifest.runId,
|
|
576
627
|
cwd: loaded.manifest.cwd,
|
|
@@ -581,7 +632,8 @@ export function createRunSnapshotCache(cwd: string, options: RunSnapshotCacheOpt
|
|
|
581
632
|
usage: usageFrom(tasks, agents),
|
|
582
633
|
mailbox,
|
|
583
634
|
groupJoins,
|
|
584
|
-
|
|
635
|
+
cancellationReason: cancellationReasonFromEvents(recentEvents),
|
|
636
|
+
recentEvents,
|
|
585
637
|
recentOutputLines: recentOutputLines(loaded.manifest, agents, recentOutputLimit),
|
|
586
638
|
};
|
|
587
639
|
const stamps = stampsFor(loaded.manifest, agents);
|
|
@@ -626,6 +678,7 @@ export function createRunSnapshotCache(cwd: string, options: RunSnapshotCacheOpt
|
|
|
626
678
|
usage: usageFrom(tasks, agents),
|
|
627
679
|
mailbox,
|
|
628
680
|
groupJoins,
|
|
681
|
+
cancellationReason: cancellationReasonFromEvents(recentEvents),
|
|
629
682
|
recentEvents,
|
|
630
683
|
recentOutputLines: recentOutput,
|
|
631
684
|
};
|
|
@@ -680,6 +733,12 @@ export function createRunSnapshotCache(cwd: string, options: RunSnapshotCacheOpt
|
|
|
680
733
|
}
|
|
681
734
|
}
|
|
682
735
|
|
|
736
|
+
const unsubscribe = runEventBus.onAny((event) => {
|
|
737
|
+
if (entries.has(event.runId)) {
|
|
738
|
+
entries.delete(event.runId);
|
|
739
|
+
}
|
|
740
|
+
});
|
|
741
|
+
|
|
683
742
|
return {
|
|
684
743
|
get(runId: string): RunUiSnapshot | undefined {
|
|
685
744
|
const entry = entries.get(runId);
|
|
@@ -711,6 +770,7 @@ export function createRunSnapshotCache(cwd: string, options: RunSnapshotCacheOpt
|
|
|
711
770
|
return new Map([...entries.entries()].map(([key, entry]) => [key, entry.snapshot]));
|
|
712
771
|
},
|
|
713
772
|
dispose(): void {
|
|
773
|
+
unsubscribe();
|
|
714
774
|
entries.clear();
|
|
715
775
|
},
|
|
716
776
|
};
|
package/src/ui/snapshot-types.ts
CHANGED
|
@@ -23,6 +23,14 @@ export interface RunUiMailbox {
|
|
|
23
23
|
inboxUnread: number;
|
|
24
24
|
outboxPending: number;
|
|
25
25
|
needsAttention: number;
|
|
26
|
+
/** Urgent steering messages count. Default 0. */
|
|
27
|
+
steerUnread?: number;
|
|
28
|
+
/** Follow-up / continuation messages count. Default 0. */
|
|
29
|
+
followUpUnread?: number;
|
|
30
|
+
/** Response / reply messages count. Default 0. */
|
|
31
|
+
responseUnread?: number;
|
|
32
|
+
/** Generic messages count. Default 0. */
|
|
33
|
+
messageUnread?: number;
|
|
26
34
|
/** True when counts come from bounded tail reads and older messages may be omitted. */
|
|
27
35
|
approximate?: boolean;
|
|
28
36
|
}
|
|
@@ -46,6 +54,8 @@ export interface RunUiSnapshot {
|
|
|
46
54
|
usage: RunUiUsage;
|
|
47
55
|
mailbox: RunUiMailbox;
|
|
48
56
|
groupJoins?: RunUiGroupJoin[];
|
|
57
|
+
/** Structured cancellation reason from run.cancelled event data, when available. */
|
|
58
|
+
cancellationReason?: string;
|
|
49
59
|
recentEvents: TeamEvent[];
|
|
50
60
|
recentOutputLines: string[];
|
|
51
61
|
}
|