pi-crew 0.1.46 → 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/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 +117 -42
- package/docs/refactor-tasks-phase3.md +394 -394
- package/docs/refactor-tasks-phase4.md +564 -564
- package/docs/refactor-tasks-phase5.md +402 -402
- package/docs/refactor-tasks-phase6.md +662 -662
- 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 -100
- package/docs/research-extension-examples.md +297 -297
- package/docs/research-extension-system.md +324 -324
- package/docs/research-oh-my-pi-distillation.md +56 -9
- package/docs/research-optimization-plan.md +548 -548
- package/docs/research-phase10-distillation.md +198 -198
- package/docs/research-phase11-distillation.md +201 -201
- package/docs/research-pi-coding-agent.md +357 -357
- package/docs/research-source-pi-crew-reference.md +174 -174
- package/docs/runtime-flow.md +148 -148
- package/docs/source-runtime-refactor-map.md +107 -107
- package/index.ts +6 -6
- package/package.json +99 -98
- package/schema.json +8 -0
- package/skills/async-worker-recovery/SKILL.md +42 -42
- package/skills/context-artifact-hygiene/SKILL.md +52 -52
- package/skills/delegation-patterns/SKILL.md +54 -54
- package/skills/mailbox-interactive/SKILL.md +40 -40
- package/skills/model-routing-context/SKILL.md +39 -39
- package/skills/multi-perspective-review/SKILL.md +58 -58
- package/skills/observability-reliability/SKILL.md +41 -41
- package/skills/orchestration/SKILL.md +157 -0
- package/skills/ownership-session-security/SKILL.md +41 -41
- package/skills/pi-extension-lifecycle/SKILL.md +39 -39
- package/skills/requirements-to-task-packet/SKILL.md +63 -63
- package/skills/resource-discovery-config/SKILL.md +41 -41
- package/skills/runtime-state-reader/SKILL.md +44 -44
- package/skills/secure-agent-orchestration-review/SKILL.md +45 -45
- package/skills/state-mutation-locking/SKILL.md +42 -42
- package/skills/systematic-debugging/SKILL.md +67 -67
- package/skills/ui-render-performance/SKILL.md +39 -39
- package/skills/verification-before-done/SKILL.md +57 -57
- package/skills/worktree-isolation/SKILL.md +39 -39
- package/src/agents/agent-config.ts +6 -0
- package/src/agents/agent-search.ts +98 -0
- package/src/agents/agent-serializer.ts +4 -0
- package/src/agents/discover-agents.ts +17 -4
- package/src/config/config.ts +24 -0
- package/src/config/defaults.ts +11 -0
- package/src/extension/autonomous-policy.ts +26 -33
- package/src/extension/cross-extension-rpc.ts +82 -82
- package/src/extension/help.ts +1 -0
- package/src/extension/management.ts +5 -0
- package/src/extension/register.ts +58 -13
- package/src/extension/registration/commands.ts +33 -1
- package/src/extension/registration/compaction-guard.ts +125 -125
- package/src/extension/registration/team-tool.ts +6 -4
- package/src/extension/run-bundle-schema.ts +89 -89
- package/src/extension/run-index.ts +24 -18
- package/src/extension/run-maintenance.ts +68 -62
- package/src/extension/team-tool/api.ts +23 -2
- package/src/extension/team-tool/cancel.ts +86 -11
- package/src/extension/team-tool/context.ts +3 -0
- package/src/extension/team-tool/handle-settings.ts +188 -188
- package/src/extension/team-tool/inspect.ts +41 -41
- package/src/extension/team-tool/intent-policy.ts +42 -0
- package/src/extension/team-tool/lifecycle-actions.ts +47 -18
- package/src/extension/team-tool/parallel-dispatch.ts +156 -0
- package/src/extension/team-tool/plan.ts +19 -19
- package/src/extension/team-tool/respond.ts +10 -2
- package/src/extension/team-tool/run.ts +3 -2
- package/src/extension/team-tool/status.ts +1 -1
- package/src/extension/team-tool-types.ts +1 -0
- package/src/extension/team-tool.ts +13 -3
- package/src/hooks/registry.ts +61 -0
- package/src/hooks/types.ts +41 -0
- package/src/i18n.ts +184 -184
- package/src/observability/exporters/otlp-exporter.ts +77 -77
- package/src/prompt/prompt-runtime.ts +72 -72
- package/src/runtime/agent-control.ts +108 -2
- package/src/runtime/agent-memory.ts +72 -72
- package/src/runtime/agent-observability.ts +114 -114
- package/src/runtime/async-marker.ts +26 -26
- package/src/runtime/async-runner.ts +3 -1
- package/src/runtime/attention-events.ts +28 -28
- package/src/runtime/background-runner.ts +19 -0
- package/src/runtime/cancellation-token.ts +89 -0
- package/src/runtime/cancellation.ts +61 -51
- package/src/runtime/capability-inventory.ts +116 -0
- package/src/runtime/child-pi.ts +2 -1
- package/src/runtime/code-summary.ts +247 -0
- package/src/runtime/completion-guard.ts +190 -190
- package/src/runtime/crash-recovery.ts +181 -0
- package/src/runtime/crew-agent-records.ts +35 -7
- 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/delivery-coordinator.ts +3 -1
- package/src/runtime/direct-run.ts +35 -35
- package/src/runtime/effectiveness.ts +81 -76
- package/src/runtime/event-stream-bridge.ts +90 -0
- package/src/runtime/foreground-control.ts +82 -82
- package/src/runtime/green-contract.ts +46 -46
- package/src/runtime/group-join.ts +106 -106
- package/src/runtime/heartbeat-gradient.ts +28 -28
- package/src/runtime/heartbeat-watcher.ts +124 -124
- package/src/runtime/live-agent-control.ts +88 -88
- package/src/runtime/live-agent-manager.ts +78 -2
- package/src/runtime/live-control-realtime.ts +36 -36
- 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 +297 -7
- package/src/runtime/mcp-proxy.ts +113 -0
- 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-research.ts +44 -44
- package/src/runtime/parallel-utils.ts +57 -0
- package/src/runtime/parent-guard.ts +80 -0
- package/src/runtime/pi-json-output.ts +111 -111
- package/src/runtime/policy-engine.ts +79 -79
- package/src/runtime/progress-event-coalescer.ts +43 -43
- package/src/runtime/prose-compressor.ts +164 -0
- package/src/runtime/recovery-recipes.ts +74 -74
- package/src/runtime/result-extractor.ts +121 -0
- package/src/runtime/role-permission.ts +39 -39
- package/src/runtime/runtime-resolver.ts +1 -4
- package/src/runtime/semaphore.ts +131 -0
- package/src/runtime/sensitive-paths.ts +92 -0
- package/src/runtime/session-resources.ts +25 -25
- package/src/runtime/session-snapshot.ts +59 -59
- package/src/runtime/session-usage.ts +79 -79
- package/src/runtime/sidechain-output.ts +29 -29
- package/src/runtime/stream-preview.ts +177 -0
- package/src/runtime/subagent-manager.ts +3 -2
- package/src/runtime/subprocess-tool-registry.ts +67 -0
- package/src/runtime/supervisor-contact.ts +59 -59
- package/src/runtime/task-display.ts +38 -38
- package/src/runtime/task-output-context.ts +59 -9
- package/src/runtime/task-runner/capabilities.ts +78 -78
- package/src/runtime/task-runner/live-executor.ts +2 -0
- package/src/runtime/task-runner/progress.ts +119 -119
- package/src/runtime/task-runner/prompt-builder.ts +70 -8
- package/src/runtime/task-runner/prompt-pipeline.ts +64 -64
- package/src/runtime/task-runner/result-utils.ts +14 -14
- package/src/runtime/task-runner/run-projection.ts +104 -0
- package/src/runtime/task-runner/state-helpers.ts +22 -22
- package/src/runtime/task-runner.ts +75 -4
- package/src/runtime/team-runner.ts +60 -8
- package/src/runtime/worker-heartbeat.ts +21 -21
- package/src/runtime/worker-startup.ts +57 -57
- package/src/runtime/workspace-tree.ts +298 -0
- package/src/runtime/yield-handler.ts +189 -0
- package/src/schema/config-schema.ts +6 -0
- package/src/schema/team-tool-schema.ts +11 -1
- package/src/skills/discover-skills.ts +67 -0
- package/src/state/active-run-registry.ts +4 -2
- 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 +1 -0
- package/src/state/event-log-rotation.ts +158 -0
- package/src/state/event-log.ts +52 -2
- package/src/state/mailbox.ts +87 -7
- package/src/state/state-store.ts +24 -4
- package/src/state/task-claims.ts +44 -44
- package/src/state/types.ts +20 -0
- package/src/state/usage.ts +29 -29
- package/src/subagents/async-entry.ts +1 -1
- package/src/subagents/index.ts +3 -3
- package/src/subagents/live/control.ts +1 -1
- package/src/subagents/live/manager.ts +1 -1
- package/src/subagents/live/realtime.ts +1 -1
- package/src/subagents/live/session-runtime.ts +1 -1
- package/src/subagents/manager.ts +1 -1
- package/src/subagents/spawn.ts +1 -1
- package/src/teams/team-serializer.ts +38 -38
- package/src/types/diff.d.ts +18 -18
- package/src/ui/agent-management-overlay.ts +144 -0
- package/src/ui/crew-footer.ts +101 -101
- package/src/ui/crew-select-list.ts +111 -111
- package/src/ui/crew-widget.ts +11 -2
- 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/metrics-pane.ts +34 -34
- package/src/ui/dynamic-border.ts +25 -25
- package/src/ui/layout-primitives.ts +106 -106
- package/src/ui/live-run-sidebar.ts +4 -0
- package/src/ui/loaders.ts +158 -158
- package/src/ui/powerbar-publisher.ts +77 -15
- package/src/ui/render-coalescer.ts +51 -0
- package/src/ui/render-diff.ts +119 -119
- package/src/ui/render-scheduler.ts +143 -143
- package/src/ui/run-dashboard.ts +4 -0
- package/src/ui/run-event-bus.ts +209 -0
- package/src/ui/run-snapshot-cache.ts +68 -16
- package/src/ui/snapshot-types.ts +8 -0
- package/src/ui/spinner.ts +17 -17
- package/src/ui/status-colors.ts +58 -58
- package/src/ui/syntax-highlight.ts +116 -116
- package/src/ui/transcript-entries.ts +258 -0
- package/src/utils/atomic-write.ts +33 -33
- package/src/utils/completion-dedupe.ts +63 -63
- package/src/utils/frontmatter.ts +68 -68
- package/src/utils/git.ts +262 -262
- package/src/utils/ids.ts +17 -12
- package/src/utils/incremental-reader.ts +104 -0
- package/src/utils/names.ts +27 -27
- package/src/utils/redaction.ts +44 -44
- package/src/utils/safe-paths.ts +47 -47
- package/src/utils/scan-cache.ts +137 -0
- package/src/utils/sleep.ts +32 -32
- 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/validate-workflow.ts +40 -40
- package/src/worktree/branch-freshness.ts +45 -45
- package/src/worktree/cleanup.ts +2 -1
- package/teams/default.team.md +12 -12
- package/teams/fast-fix.team.md +11 -11
- package/teams/implementation.team.md +18 -18
- package/teams/parallel-research.team.md +14 -14
- package/teams/research.team.md +11 -11
- package/teams/review.team.md +12 -12
- package/workflows/default.workflow.md +29 -29
- package/workflows/fast-fix.workflow.md +22 -22
- package/workflows/implementation.workflow.md +38 -38
- package/workflows/parallel-research.workflow.md +46 -46
- package/workflows/research.workflow.md +22 -22
- package/workflows/review.workflow.md +30 -30
|
@@ -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,17 @@ 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
|
+
};
|
|
479
524
|
}
|
|
480
525
|
|
|
481
526
|
function cancellationReasonFromEvents(events: TeamEvent[]): string | undefined {
|
|
@@ -688,6 +733,12 @@ export function createRunSnapshotCache(cwd: string, options: RunSnapshotCacheOpt
|
|
|
688
733
|
}
|
|
689
734
|
}
|
|
690
735
|
|
|
736
|
+
const unsubscribe = runEventBus.onAny((event) => {
|
|
737
|
+
if (entries.has(event.runId)) {
|
|
738
|
+
entries.delete(event.runId);
|
|
739
|
+
}
|
|
740
|
+
});
|
|
741
|
+
|
|
691
742
|
return {
|
|
692
743
|
get(runId: string): RunUiSnapshot | undefined {
|
|
693
744
|
const entry = entries.get(runId);
|
|
@@ -719,6 +770,7 @@ export function createRunSnapshotCache(cwd: string, options: RunSnapshotCacheOpt
|
|
|
719
770
|
return new Map([...entries.entries()].map(([key, entry]) => [key, entry.snapshot]));
|
|
720
771
|
},
|
|
721
772
|
dispose(): void {
|
|
773
|
+
unsubscribe();
|
|
722
774
|
entries.clear();
|
|
723
775
|
},
|
|
724
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
|
}
|
package/src/ui/spinner.ts
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
export const SUBAGENT_SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"] as const;
|
|
2
|
-
export const SUBAGENT_SPINNER_FRAME_MS = 160;
|
|
3
|
-
|
|
4
|
-
export function spinnerBucket(now = Date.now(), frameMs = SUBAGENT_SPINNER_FRAME_MS): number {
|
|
5
|
-
return Math.floor(now / Math.max(1, frameMs));
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
function hashKey(key: string): number {
|
|
9
|
-
let hash = 0;
|
|
10
|
-
for (let index = 0; index < key.length; index += 1) hash = (hash * 31 + key.charCodeAt(index)) >>> 0;
|
|
11
|
-
return hash;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export function spinnerFrame(key = "", now = Date.now()): string {
|
|
15
|
-
const offset = key ? hashKey(key) % SUBAGENT_SPINNER_FRAMES.length : 0;
|
|
16
|
-
return SUBAGENT_SPINNER_FRAMES[(spinnerBucket(now) + offset) % SUBAGENT_SPINNER_FRAMES.length] ?? SUBAGENT_SPINNER_FRAMES[0];
|
|
17
|
-
}
|
|
1
|
+
export const SUBAGENT_SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"] as const;
|
|
2
|
+
export const SUBAGENT_SPINNER_FRAME_MS = 160;
|
|
3
|
+
|
|
4
|
+
export function spinnerBucket(now = Date.now(), frameMs = SUBAGENT_SPINNER_FRAME_MS): number {
|
|
5
|
+
return Math.floor(now / Math.max(1, frameMs));
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function hashKey(key: string): number {
|
|
9
|
+
let hash = 0;
|
|
10
|
+
for (let index = 0; index < key.length; index += 1) hash = (hash * 31 + key.charCodeAt(index)) >>> 0;
|
|
11
|
+
return hash;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function spinnerFrame(key = "", now = Date.now()): string {
|
|
15
|
+
const offset = key ? hashKey(key) % SUBAGENT_SPINNER_FRAMES.length : 0;
|
|
16
|
+
return SUBAGENT_SPINNER_FRAMES[(spinnerBucket(now) + offset) % SUBAGENT_SPINNER_FRAMES.length] ?? SUBAGENT_SPINNER_FRAMES[0];
|
|
17
|
+
}
|
package/src/ui/status-colors.ts
CHANGED
|
@@ -1,58 +1,58 @@
|
|
|
1
|
-
import type { CrewTheme, CrewThemeColor } from "./theme-adapter.ts";
|
|
2
|
-
|
|
3
|
-
export type RunStatus = "queued" | "running" | "completed" | "failed" | "cancelled" | "stopped" | "blocked" | (string & {});
|
|
4
|
-
|
|
5
|
-
export function colorForStatus(status: RunStatus): CrewThemeColor {
|
|
6
|
-
switch (status) {
|
|
7
|
-
case "running":
|
|
8
|
-
return "accent";
|
|
9
|
-
case "waiting":
|
|
10
|
-
return "muted";
|
|
11
|
-
case "completed":
|
|
12
|
-
return "success";
|
|
13
|
-
case "failed":
|
|
14
|
-
case "stale":
|
|
15
|
-
return "error";
|
|
16
|
-
case "cancelled":
|
|
17
|
-
case "blocked":
|
|
18
|
-
case "stopped":
|
|
19
|
-
return "warning";
|
|
20
|
-
case "queued":
|
|
21
|
-
default:
|
|
22
|
-
return "dim";
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export function iconForStatus(status: RunStatus, options?: { runningGlyph?: string }): string {
|
|
27
|
-
const glyph = options?.runningGlyph ?? "▶";
|
|
28
|
-
switch (status) {
|
|
29
|
-
case "completed":
|
|
30
|
-
return "✓";
|
|
31
|
-
case "failed":
|
|
32
|
-
case "stale":
|
|
33
|
-
return "✗";
|
|
34
|
-
case "cancelled":
|
|
35
|
-
case "stopped":
|
|
36
|
-
return "■";
|
|
37
|
-
case "running":
|
|
38
|
-
return glyph;
|
|
39
|
-
case "waiting":
|
|
40
|
-
return "⏳";
|
|
41
|
-
case "queued":
|
|
42
|
-
return "◦";
|
|
43
|
-
case "blocked":
|
|
44
|
-
return "⏸";
|
|
45
|
-
default:
|
|
46
|
-
return "·";
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export function colorForActivity(activityState: string | undefined): CrewThemeColor {
|
|
51
|
-
if (activityState === "needs_attention") return "warning";
|
|
52
|
-
if (activityState === "stale") return "error";
|
|
53
|
-
return "dim";
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export function applyStatusColor(theme: CrewTheme, status: RunStatus, text: string): string {
|
|
57
|
-
return theme.fg(colorForStatus(status), text);
|
|
58
|
-
}
|
|
1
|
+
import type { CrewTheme, CrewThemeColor } from "./theme-adapter.ts";
|
|
2
|
+
|
|
3
|
+
export type RunStatus = "queued" | "running" | "completed" | "failed" | "cancelled" | "stopped" | "blocked" | (string & {});
|
|
4
|
+
|
|
5
|
+
export function colorForStatus(status: RunStatus): CrewThemeColor {
|
|
6
|
+
switch (status) {
|
|
7
|
+
case "running":
|
|
8
|
+
return "accent";
|
|
9
|
+
case "waiting":
|
|
10
|
+
return "muted";
|
|
11
|
+
case "completed":
|
|
12
|
+
return "success";
|
|
13
|
+
case "failed":
|
|
14
|
+
case "stale":
|
|
15
|
+
return "error";
|
|
16
|
+
case "cancelled":
|
|
17
|
+
case "blocked":
|
|
18
|
+
case "stopped":
|
|
19
|
+
return "warning";
|
|
20
|
+
case "queued":
|
|
21
|
+
default:
|
|
22
|
+
return "dim";
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function iconForStatus(status: RunStatus, options?: { runningGlyph?: string }): string {
|
|
27
|
+
const glyph = options?.runningGlyph ?? "▶";
|
|
28
|
+
switch (status) {
|
|
29
|
+
case "completed":
|
|
30
|
+
return "✓";
|
|
31
|
+
case "failed":
|
|
32
|
+
case "stale":
|
|
33
|
+
return "✗";
|
|
34
|
+
case "cancelled":
|
|
35
|
+
case "stopped":
|
|
36
|
+
return "■";
|
|
37
|
+
case "running":
|
|
38
|
+
return glyph;
|
|
39
|
+
case "waiting":
|
|
40
|
+
return "⏳";
|
|
41
|
+
case "queued":
|
|
42
|
+
return "◦";
|
|
43
|
+
case "blocked":
|
|
44
|
+
return "⏸";
|
|
45
|
+
default:
|
|
46
|
+
return "·";
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function colorForActivity(activityState: string | undefined): CrewThemeColor {
|
|
51
|
+
if (activityState === "needs_attention") return "warning";
|
|
52
|
+
if (activityState === "stale") return "error";
|
|
53
|
+
return "dim";
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function applyStatusColor(theme: CrewTheme, status: RunStatus, text: string): string {
|
|
57
|
+
return theme.fg(colorForStatus(status), text);
|
|
58
|
+
}
|