pi-crew 0.9.68 → 0.10.1

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.
Files changed (217) hide show
  1. package/CHANGELOG.md +124 -0
  2. package/agents/analyst.md +1 -1
  3. package/agents/cold-verifier.md +3 -1
  4. package/agents/critic.md +1 -1
  5. package/agents/executor.md +1 -1
  6. package/agents/explorer.md +1 -1
  7. package/agents/planner.md +1 -1
  8. package/agents/reviewer.md +1 -1
  9. package/agents/security-reviewer.md +1 -1
  10. package/agents/test-engineer.md +1 -1
  11. package/agents/verifier.md +1 -1
  12. package/agents/writer.md +1 -1
  13. package/dist/index.mjs +65838 -60331
  14. package/docs/README.md +2 -0
  15. package/docs/actions-reference.md +31 -0
  16. package/docs/commands-reference.md +17 -6
  17. package/docs/resource-formats.md +13 -0
  18. package/package.json +4 -2
  19. package/schema.json +15 -1
  20. package/scripts/resource-sampler.mjs +36 -2
  21. package/skills/requirements-to-task-packet/SKILL.md +26 -0
  22. package/skills/widget-rendering/SKILL.md +7 -7
  23. package/src/agents/agent-config.ts +2 -1
  24. package/src/agents/discover-agents.ts +23 -14
  25. package/src/config/config-merge.ts +183 -0
  26. package/src/config/config-validation.ts +681 -0
  27. package/src/config/config.ts +22 -864
  28. package/src/config/defaults.ts +35 -1
  29. package/src/config/drift-detector.ts +1 -1
  30. package/src/config/env-vars.ts +691 -0
  31. package/src/config/role-tools.ts +8 -8
  32. package/src/config/sanitize-project-config.ts +172 -0
  33. package/src/config/types.ts +30 -0
  34. package/src/extension/async-notifier.ts +25 -2
  35. package/src/extension/crew-vibes/config.ts +2 -1
  36. package/src/extension/plan-orchestrate.ts +132 -0
  37. package/src/extension/registration/commands/dashboard.ts +158 -0
  38. package/src/extension/registration/commands/index.ts +35 -0
  39. package/src/extension/registration/commands/manage.ts +303 -0
  40. package/src/extension/registration/commands/run.ts +214 -0
  41. package/src/extension/registration/commands/shared.ts +631 -0
  42. package/src/extension/registration/commands/status.ts +60 -0
  43. package/src/extension/registration/commands.ts +13 -1224
  44. package/src/extension/registration/lifecycle-handlers.ts +126 -15
  45. package/src/extension/registration/subagent-tools.ts +218 -9
  46. package/src/extension/rpc-hmac.ts +5 -3
  47. package/src/extension/team-tool/api/heartbeat.ts +47 -10
  48. package/src/extension/team-tool/api/plan-approval.ts +9 -0
  49. package/src/extension/team-tool/api/task-claims.ts +109 -40
  50. package/src/extension/team-tool/cancel.ts +84 -50
  51. package/src/extension/team-tool/dispatch/index.ts +1 -0
  52. package/src/extension/team-tool/dispatch/run.ts +4 -1
  53. package/src/extension/team-tool/doctor.ts +103 -1
  54. package/src/extension/team-tool/orchestrate.ts +66 -1
  55. package/src/extension/team-tool/plans.ts +192 -0
  56. package/src/extension/team-tool/respond.ts +197 -65
  57. package/src/extension/team-tool/run-deadline.ts +16 -1
  58. package/src/extension/team-tool/run-intent.ts +63 -0
  59. package/src/extension/team-tool/run.ts +48 -18
  60. package/src/extension/team-tool/status.ts +84 -26
  61. package/src/extension/team-tool.ts +11 -2
  62. package/src/hooks/registry.ts +1 -6
  63. package/src/i18n.ts +9 -0
  64. package/src/prompt/prompt-runtime.ts +521 -2
  65. package/src/prompt/worker-events-channel.ts +173 -0
  66. package/src/runtime/README.md +8 -8
  67. package/src/runtime/async-runner.ts +7 -3
  68. package/src/runtime/background-runner.ts +42 -14
  69. package/src/runtime/broker/broker-issuer.ts +9 -2
  70. package/src/runtime/broker/crew-broker-tokens.ts +43 -6
  71. package/src/runtime/broker/crew-broker.ts +838 -10
  72. package/src/runtime/broker/wait-status-cache.ts +157 -0
  73. package/src/runtime/budget-enforcement.ts +281 -0
  74. package/src/runtime/child-pi/child-pi-spawn.ts +60 -14
  75. package/src/runtime/child-pi/child-pi-timers.ts +324 -0
  76. package/src/runtime/child-pi/child-pi.ts +97 -201
  77. package/src/runtime/child-pi/mock-fixtures.ts +16 -2
  78. package/src/runtime/crew-agent-records.ts +259 -14
  79. package/src/runtime/delegate-spawn.ts +148 -0
  80. package/src/runtime/deterministic-ast.ts +2 -1
  81. package/src/runtime/dispatch-batch.ts +945 -0
  82. package/src/runtime/finalize-run.ts +557 -0
  83. package/src/runtime/goal-workflow/adaptive-plan.ts +87 -13
  84. package/src/runtime/goal-workflow/dynamic-workflow-runner.ts +3 -2
  85. package/src/runtime/goal-workflow/goal-state-store.ts +1 -1
  86. package/src/runtime/group-join.ts +11 -125
  87. package/src/runtime/live-session/live-session-runtime.ts +26 -1
  88. package/src/runtime/merge-gate.ts +7 -1
  89. package/src/runtime/merge-loop.ts +130 -0
  90. package/src/runtime/model/model-budget-summary.ts +53 -0
  91. package/src/runtime/model/model-fallback.ts +32 -2
  92. package/src/runtime/model/pi-args.ts +10 -0
  93. package/src/runtime/model/provider-extensions.ts +10 -0
  94. package/src/runtime/orphan-worker-registry.ts +1 -1
  95. package/src/runtime/output/output-validator.ts +45 -0
  96. package/src/runtime/parent-guard.ts +3 -1
  97. package/src/runtime/peer-dep.ts +2 -1
  98. package/src/runtime/per-write-validator.ts +0 -5
  99. package/src/runtime/pi-spawn.ts +61 -15
  100. package/src/runtime/plan-approval.ts +125 -0
  101. package/src/runtime/plan-replan.ts +151 -0
  102. package/src/runtime/recovery/checkpoint.ts +0 -18
  103. package/src/runtime/recovery/crash-recovery.ts +95 -46
  104. package/src/runtime/scheduler-context.ts +98 -0
  105. package/src/runtime/scheduling/coalesce-tasks.ts +5 -0
  106. package/src/runtime/scheduling/global-worker-cap.ts +2 -1
  107. package/src/runtime/scheduling/nested-slots.ts +70 -0
  108. package/src/runtime/scheduling/run-coalesced-task-group.ts +64 -13
  109. package/src/runtime/scheduling/task-graph-scheduler.ts +0 -10
  110. package/src/runtime/settings-store.ts +219 -0
  111. package/src/runtime/spawn-policy.ts +217 -0
  112. package/src/runtime/stale-reconciler.ts +87 -6
  113. package/src/runtime/subagent-manager.ts +25 -1
  114. package/src/runtime/task-output-context.ts +230 -9
  115. package/src/runtime/task-packet.ts +23 -1
  116. package/src/runtime/task-runner/child-executor.ts +106 -7
  117. package/src/runtime/task-runner/post-execution.ts +125 -1
  118. package/src/runtime/task-runner/pre-execution.ts +39 -1
  119. package/src/runtime/task-runner/prompt-builder.ts +51 -1
  120. package/src/runtime/task-runner/retrieval-orchestrator.ts +72 -18
  121. package/src/runtime/task-runner/spec-evidence.ts +403 -0
  122. package/src/runtime/task-runner/state-helpers.ts +26 -24
  123. package/src/runtime/task-runner.ts +11 -0
  124. package/src/runtime/team-runner.ts +129 -1671
  125. package/src/runtime/verification/spec-sandbox.ts +255 -0
  126. package/src/runtime/verification/verification-gates.ts +3 -2
  127. package/src/runtime/verification/verification-worktree.ts +2 -1
  128. package/src/runtime/workflow-phase-advance.ts +100 -0
  129. package/src/runtime/workspace-tree.ts +9 -0
  130. package/src/schema/config-schema.ts +65 -25
  131. package/src/schema/sensitive-config-paths.ts +64 -0
  132. package/src/schema/team-tool-schema.ts +13 -3
  133. package/src/state/README.md +4 -10
  134. package/src/state/atomic-write.ts +20 -3
  135. package/src/state/contracts.ts +38 -0
  136. package/src/state/coordination/mailbox.ts +12 -2
  137. package/src/state/event-log/cursor.ts +223 -0
  138. package/src/state/event-log/event-log-rotation.ts +12 -4
  139. package/src/state/event-log/event-log.ts +152 -369
  140. package/src/state/event-log/sequence-cache.ts +373 -0
  141. package/src/state/event-log/worker-atomic-writer.ts +2 -1
  142. package/src/state/stores/active-run-registry.ts +3 -2
  143. package/src/state/stores/manifest-io.ts +237 -0
  144. package/src/state/stores/ownership-map.ts +162 -0
  145. package/src/state/stores/plan-store.ts +241 -0
  146. package/src/state/stores/run-cache.ts +0 -90
  147. package/src/state/stores/spec-store.ts +189 -0
  148. package/src/state/stores/state-store.ts +66 -229
  149. package/src/state/types.ts +197 -0
  150. package/src/ui/dashboard-panes/plan-pane.ts +136 -0
  151. package/src/ui/dashboard-panes/progress-pane.ts +6 -0
  152. package/src/ui/dashboard-panes/transcript-pane.ts +31 -0
  153. package/src/ui/heartbeat-aggregator.ts +9 -1
  154. package/src/ui/keybinding-map.ts +54 -13
  155. package/src/ui/powerbar-publisher.ts +52 -1
  156. package/src/ui/run-dashboard.ts +31 -5
  157. package/src/ui/run-snapshot-cache.ts +57 -30
  158. package/src/ui/snapshot-types.ts +6 -1
  159. package/src/ui/widget/widget-renderer.ts +9 -1
  160. package/src/utils/file-coalescer.ts +0 -4
  161. package/src/utils/fs-errno.ts +66 -0
  162. package/src/utils/fs-watch.ts +1 -1
  163. package/src/utils/internal-error.ts +3 -1
  164. package/src/utils/paths.ts +11 -3
  165. package/src/utils/task-name-generator.ts +1 -8
  166. package/src/workflows/discover-workflows.ts +19 -2
  167. package/src/workflows/validate-workflow.ts +7 -1
  168. package/src/workflows/workflow-config.ts +10 -0
  169. package/src/workflows/workflow-serializer.ts +3 -0
  170. package/src/worktree/worktree-manager.ts +22 -0
  171. package/src/agents/agent-search.ts +0 -98
  172. package/src/benchmark/benchmark-runner.ts +0 -313
  173. package/src/benchmark/feedback-loop.ts +0 -73
  174. package/src/config/resilient-parser.ts +0 -117
  175. package/src/extension/crew-vibes/cat-frames.ts +0 -18
  176. package/src/extension/result-watcher.ts +0 -139
  177. package/src/observability/exporters/prometheus-exporter.ts +0 -54
  178. package/src/observability/metric-retention.ts +0 -64
  179. package/src/runtime/compaction/compaction-summary.ts +0 -278
  180. package/src/runtime/errors/crew-errors.ts +0 -162
  181. package/src/runtime/live-session/intercom-bridge.ts +0 -187
  182. package/src/runtime/loop-gates.ts +0 -128
  183. package/src/runtime/metric-parser.ts +0 -36
  184. package/src/runtime/output/stream-preview.ts +0 -184
  185. package/src/runtime/output/tool-progress.ts +0 -278
  186. package/src/runtime/phase-tracker.ts +0 -385
  187. package/src/runtime/pipeline-runner.ts +0 -523
  188. package/src/runtime/process/process-lifecycle.ts +0 -491
  189. package/src/runtime/recovery/retry-runner.ts +0 -330
  190. package/src/runtime/run-drift.ts +0 -219
  191. package/src/runtime/task-quality.ts +0 -199
  192. package/src/runtime/task-runner/run-projection.ts +0 -128
  193. package/src/runtime/verification/post-checks.ts +0 -142
  194. package/src/state/coordination/schedule.ts +0 -166
  195. package/src/state/event-log/jsonl-writer.ts +0 -115
  196. package/src/state/hook-instinct-bridge.ts +0 -94
  197. package/src/state/hook-integrations.ts +0 -51
  198. package/src/state/session-state-map.ts +0 -51
  199. package/src/state/stores/blob-store.ts +0 -308
  200. package/src/state/stores/instinct-store.ts +0 -275
  201. package/src/state/stores/observation-store.ts +0 -176
  202. package/src/state/tiered-eval.ts +0 -480
  203. package/src/state/types-eval.ts +0 -58
  204. package/src/tools/safe-bash-extension.ts +0 -54
  205. package/src/tools/safe-bash.ts +0 -505
  206. package/src/ui/agent-management-overlay.ts +0 -160
  207. package/src/ui/crew-footer.ts +0 -102
  208. package/src/ui/crew-select-list.ts +0 -114
  209. package/src/ui/dashboard-panes/capability-pane.ts +0 -77
  210. package/src/ui/transcript-entries.ts +0 -256
  211. package/src/utils/conflict-detect.ts +0 -721
  212. package/src/utils/fingerprint.ts +0 -180
  213. package/src/utils/gh-protocol.ts +0 -556
  214. package/src/utils/project-detector.ts +0 -160
  215. package/src/utils/sse-parser.ts +0 -131
  216. package/src/workflows/cost-estimator.ts +0 -34
  217. package/src/workflows/intermediate-store.ts +0 -166
@@ -1,162 +0,0 @@
1
- /**
2
- * crew-errors.ts — Typed error sentinels for pi-crew operations.
3
- *
4
- * Based on oh-my-pi pattern from compaction/errors.ts.
5
- * Uses `instanceof` discrimination instead of string matching.
6
- *
7
- * NOTE: Constructor parameters are explicit properties (not TypeScript
8
- * parameter properties) for strip-types compatibility.
9
- *
10
- * The `name` property is not set on instances because JavaScript's `name`
11
- * on functions is special and can't be overridden. Use `instanceof` checks
12
- * or the `code` property for discrimination.
13
- */
14
-
15
- /** Base class for all pi-crew errors. */
16
- export abstract class CrewError extends Error {
17
- abstract readonly code: string;
18
-
19
- /** Returns the class name. Use `instanceof` for discrimination. */
20
- toString(): string {
21
- return `[${this.constructor.name}] ${this.message}`;
22
- }
23
- }
24
-
25
- /** Raised when a crew run is explicitly cancelled by user or API. */
26
- export class CrewCancelledError extends CrewError {
27
- readonly code = "CREW_CANCELLED";
28
- constructor(message = "Crew run cancelled") {
29
- super(message);
30
- }
31
- }
32
-
33
- /** Raised when a crew run exceeds its configured maximum duration. */
34
- export class CrewTimeoutError extends CrewError {
35
- readonly code = "CREW_TIMEOUT";
36
- readonly maxDurationMs: number;
37
- constructor(maxDurationMs: number, message?: string) {
38
- super(message ?? `Crew run exceeded timeout of ${maxDurationMs}ms`);
39
- this.maxDurationMs = maxDurationMs;
40
- }
41
- }
42
-
43
- /**
44
- * Raised when an individual agent in a crew goes deadletter — i.e. it
45
- * encountered an unrecoverable error that blocks its task from completing.
46
- * Common causes: crash, OOM, unhandled exception in tool execution.
47
- */
48
- export class CrewDeadletterError extends CrewError {
49
- readonly code = "CREW_DEADLETTER";
50
- readonly agentId: string;
51
- readonly reason: string;
52
- constructor(agentId: string, reason: string, message?: string) {
53
- super(message ?? `Agent ${agentId} deadlettered: ${reason}`);
54
- this.agentId = agentId;
55
- this.reason = reason;
56
- }
57
- }
58
-
59
- /** Raised when a crew run is aborted — operator Esc, SIGINT, or similar. */
60
- export class CrewAbortError extends CrewError {
61
- readonly code = "CREW_ABORT";
62
- constructor(message = "Crew run aborted") {
63
- super(message);
64
- }
65
- }
66
-
67
- /** Raised when a manifest or task file is malformed or missing required fields. */
68
- export class CrewManifestError extends CrewError {
69
- readonly code = "CREW_MANIFEST_ERROR";
70
- readonly runId?: string;
71
- readonly taskId?: string;
72
- constructor(message: string, runId?: string, taskId?: string) {
73
- super(message);
74
- this.runId = runId;
75
- this.taskId = taskId;
76
- }
77
- }
78
-
79
- /** Raised when a lock cannot be acquired or is held by a stale process. */
80
- export class CrewLockError extends CrewError {
81
- readonly code = "CREW_LOCK_ERROR";
82
- readonly lockPath?: string;
83
- constructor(message: string, lockPath?: string) {
84
- super(message);
85
- this.lockPath = lockPath;
86
- }
87
- }
88
-
89
- /** Raised when an agent exceeds its turn limit. */
90
- export class CrewTurnLimitError extends CrewError {
91
- readonly code = "CREW_TURN_LIMIT";
92
- readonly agentId: string;
93
- readonly maxTurns: number;
94
- constructor(agentId: string, maxTurns: number) {
95
- super(`Agent ${agentId} exceeded turn limit of ${maxTurns}`);
96
- this.agentId = agentId;
97
- this.maxTurns = maxTurns;
98
- }
99
- }
100
-
101
- /** Raised when a workflow step gate condition is not met. */
102
- export class CrewGateError extends CrewError {
103
- readonly code = "CREW_GATE_ERROR";
104
- readonly gateName: string;
105
- constructor(gateName: string, message: string) {
106
- super(message);
107
- this.gateName = gateName;
108
- }
109
- }
110
-
111
- /** Raised when an agent session fails to initialize (e.g. model not found). */
112
- export class CrewSessionError extends CrewError {
113
- readonly code = "CREW_SESSION_ERROR";
114
- readonly agentId?: string;
115
- constructor(message: string, agentId?: string) {
116
- super(message);
117
- this.agentId = agentId;
118
- }
119
- }
120
-
121
- /**
122
- * Outcome of a crew run attempt.
123
- * Used by RunTracker and health monitoring for outcome classification.
124
- */
125
- export type CrewRunOutcome =
126
- | "ok" // Completed successfully
127
- | "cancelled" // Explicitly cancelled by user or API
128
- | "deadletter" // Agent encountered unrecoverable error
129
- | "failed" // Workflow step failed (gate error, etc.)
130
- | "timeout" // Exceeded max duration
131
- | "aborted"; // Operator interrupt (Esc, SIGINT)
132
-
133
- /**
134
- * Classify a thrown error into a CrewRunOutcome.
135
- * Useful in catch blocks that need to determine run outcome.
136
- */
137
- export function classifyError(error: unknown): CrewRunOutcome {
138
- if (error instanceof CrewCancelledError) return "cancelled";
139
- if (error instanceof CrewTimeoutError) return "timeout";
140
- if (error instanceof CrewDeadletterError) return "deadletter";
141
- if (error instanceof CrewAbortError) return "aborted";
142
- if (error instanceof CrewGateError) return "failed";
143
- if (error instanceof CrewError) return "failed";
144
- // Unknown errors → failed
145
- return "failed";
146
- }
147
-
148
- /**
149
- * Check if an error indicates the run was intentionally interrupted
150
- * (cancelled, aborted, or timed out) vs a genuine failure.
151
- */
152
- export function isInterruptError(error: unknown): boolean {
153
- return error instanceof CrewCancelledError || error instanceof CrewAbortError || error instanceof CrewTimeoutError;
154
- }
155
-
156
- /**
157
- * Check if an error indicates the run failed due to agent malfunction
158
- * rather than workflow logic or user action.
159
- */
160
- export function isAgentError(error: unknown): boolean {
161
- return error instanceof CrewDeadletterError || error instanceof CrewTurnLimitError || error instanceof CrewSessionError;
162
- }
@@ -1,187 +0,0 @@
1
- /**
2
- * Intercom bridge — workers can escalate questions to the orchestrator.
3
- *
4
- * Pattern origin: pi-subagents/src/intercom-bridge.ts — contact_supervisor tool
5
- * for child agents to escalate decisions, report progress, or ask questions.
6
- *
7
- * This module provides the message queue and correlation logic.
8
- * The actual tool registration happens in task-runner.ts.
9
- */
10
-
11
- // ── Types ────────────────────────────────────────────────────────────────
12
-
13
- export type IntercomUrgency = "low" | "medium" | "high" | "critical";
14
- export type IntercomType = "question" | "escalation" | "progress" | "block";
15
-
16
- export interface IntercomMessage {
17
- type: IntercomType;
18
- taskStepId: string;
19
- content: string;
20
- urgency: IntercomUrgency;
21
- timestamp: number;
22
- timeout?: number; // ms to wait for response
23
- }
24
-
25
- export interface IntercomResponse {
26
- answer: string;
27
- source: "orchestrator" | "human" | "timeout";
28
- timestamp: number;
29
- messageId: string;
30
- }
31
-
32
- // ── Message Queue ────────────────────────────────────────────────────────
33
-
34
- interface PendingMessage {
35
- message: IntercomMessage;
36
- id: string;
37
- resolve: (response: IntercomResponse) => void;
38
- timer?: ReturnType<typeof setTimeout>;
39
- }
40
-
41
- const MAX_QUEUE_SIZE = 100;
42
-
43
- /**
44
- * In-process intercom queue for worker→orchestrator communication.
45
- *
46
- * Each message gets a unique ID. Callers await a response via a Promise.
47
- * If no response arrives within the timeout, resolves with source="timeout".
48
- */
49
- export class IntercomQueue {
50
- private pending = new Map<string, PendingMessage>();
51
- private queue: IntercomMessage[] = [];
52
- /** Monotonic sequence counter — guarantees unique IDs even when many
53
- * messages enqueue within the same millisecond (avoids Date.now()
54
- * collision + weak Math.random birthday-paradox flakes under load). */
55
- private seq = 0;
56
-
57
- /**
58
- * Enqueue a message and return a promise that resolves when the
59
- * orchestrator responds (or times out).
60
- */
61
- enqueue(message: IntercomMessage): Promise<IntercomResponse> {
62
- if (this.pending.size >= MAX_QUEUE_SIZE) {
63
- // Evict oldest
64
- const firstKey = this.pending.keys().next().value;
65
- if (firstKey) this.evict(firstKey, "queue_full");
66
- }
67
-
68
- const id = `icm-${Date.now().toString(36)}-${(this.seq++).toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
69
-
70
- return new Promise<IntercomResponse>((resolve) => {
71
- const entry: PendingMessage = { message, id, resolve };
72
-
73
- // Set timeout if specified
74
- if (message.timeout && message.timeout > 0) {
75
- entry.timer = setTimeout(() => {
76
- resolve({
77
- answer: "No response received within timeout",
78
- source: "timeout",
79
- timestamp: Date.now(),
80
- messageId: id,
81
- });
82
- this.pending.delete(id);
83
- }, message.timeout);
84
- // Defense in depth: never let a pending-message timer block
85
- // process exit. The timer is cleared via respond()/evict() in
86
- // the normal case; .unref() ensures shutdown isn't blocked if
87
- // the queue is abandoned.
88
- if (entry.timer && typeof entry.timer.unref === "function") {
89
- entry.timer.unref();
90
- }
91
- }
92
-
93
- this.pending.set(id, entry);
94
- this.queue.push({ ...message });
95
- });
96
- }
97
-
98
- /**
99
- * Respond to a pending message by ID.
100
- */
101
- respond(messageId: string, answer: string, source: "orchestrator" | "human" = "orchestrator"): boolean {
102
- const entry = this.pending.get(messageId);
103
- if (!entry) return false;
104
-
105
- if (entry.timer) clearTimeout(entry.timer);
106
-
107
- entry.resolve({
108
- answer,
109
- source,
110
- timestamp: Date.now(),
111
- messageId,
112
- });
113
-
114
- this.pending.delete(messageId);
115
- return true;
116
- }
117
-
118
- /**
119
- * Get all pending messages (for orchestrator to process).
120
- */
121
- getPending(): Array<IntercomMessage & { id: string }> {
122
- return [...this.pending.entries()].map(([id, entry]) => ({
123
- ...entry.message,
124
- id,
125
- }));
126
- }
127
-
128
- /**
129
- * Number of pending messages awaiting response.
130
- */
131
- get pendingCount(): number {
132
- return this.pending.size;
133
- }
134
-
135
- /**
136
- * Clean up all pending messages (e.g., on run completion).
137
- */
138
- clear(): void {
139
- for (const [id, entry] of this.pending) {
140
- this.evict(id, "run_complete");
141
- }
142
- this.queue = [];
143
- }
144
-
145
- private evict(id: string, reason: string): void {
146
- const entry = this.pending.get(id);
147
- if (!entry) return;
148
-
149
- if (entry.timer) clearTimeout(entry.timer);
150
-
151
- entry.resolve({
152
- answer: `Message evicted: ${reason}`,
153
- source: "timeout",
154
- timestamp: Date.now(),
155
- messageId: id,
156
- });
157
-
158
- this.pending.delete(id);
159
- }
160
- }
161
-
162
- // ── Singleton per run ────────────────────────────────────────────────────
163
-
164
- const queues = new Map<string, IntercomQueue>();
165
-
166
- /**
167
- * Get or create an intercom queue for a run.
168
- */
169
- export function getIntercomQueue(runId: string): IntercomQueue {
170
- let queue = queues.get(runId);
171
- if (!queue) {
172
- queue = new IntercomQueue();
173
- queues.set(runId, queue);
174
- }
175
- return queue;
176
- }
177
-
178
- /**
179
- * Clean up intercom queue for a completed run.
180
- */
181
- export function cleanupIntercomQueue(runId: string): void {
182
- const queue = queues.get(runId);
183
- if (queue) {
184
- queue.clear();
185
- queues.delete(runId);
186
- }
187
- }
@@ -1,128 +0,0 @@
1
- /**
2
- * Dual-gate loop control — determines whether a task should auto-resume
3
- * based on meaningful progress and turn limits.
4
- *
5
- * Gate 1: At least one signal of meaningful progress must be true.
6
- * Gate 2: The auto-resume turn count must be below the maximum.
7
- *
8
- * Distilled from pi-autoresearch's dual-gate loop pattern.
9
- */
10
- import * as fs from "node:fs";
11
- import type { TeamTaskState } from "../state/types.ts";
12
-
13
- /**
14
- * Signal indicating what kind of progress a task has made.
15
- */
16
- export interface TaskProgressSignal {
17
- /** Whether the task has edited files or produced non-empty results. */
18
- editedFiles: boolean;
19
- /** Whether the task has produced artifacts in the artifacts directory. */
20
- producedArtifacts: boolean;
21
- /** Whether the task has run tests (detected via result text). */
22
- ranTests: boolean;
23
- }
24
-
25
- /**
26
- * Runtime state for auto-resume turn tracking.
27
- */
28
- export interface AutoResumeRuntime {
29
- /** Current number of auto-resume turns taken. */
30
- autoResumeTurns: number;
31
- /** Maximum allowed auto-resume turns. */
32
- maxTurns: number;
33
- }
34
-
35
- /** Keywords in result text that indicate tests were run. */
36
- const TEST_KEYWORDS = [
37
- "test passed",
38
- "test failed",
39
- "tests passed",
40
- "tests failed",
41
- "test results",
42
- "jest",
43
- "vitest",
44
- "mocha",
45
- "pytest",
46
- "npx test",
47
- "npm test",
48
- "cargo test",
49
- "all tests",
50
- "test suite",
51
- "✓",
52
- "✗",
53
- "PASS ",
54
- " FAIL ",
55
- ] as const;
56
-
57
- /**
58
- * Determine whether a task should auto-resume based on dual-gate logic.
59
- *
60
- * Gate 1 (meaningful progress): at least one of editedFiles, producedArtifacts,
61
- * or ranTests must be true.
62
- * Gate 2 (turn limit): autoResumeTurns must be strictly less than maxTurns.
63
- *
64
- * Both gates must pass for auto-resume to be allowed.
65
- */
66
- export function shouldAutoResume(runtime: AutoResumeRuntime, taskProgress: TaskProgressSignal): boolean {
67
- // Gate 2: Turn limit check (check first — cheaper)
68
- if (runtime.autoResumeTurns >= runtime.maxTurns) {
69
- return false;
70
- }
71
-
72
- // Gate 1: Meaningful progress check
73
- const hasProgress = taskProgress.editedFiles || taskProgress.producedArtifacts || taskProgress.ranTests;
74
- if (!hasProgress) {
75
- return false;
76
- }
77
-
78
- return true;
79
- }
80
-
81
- /**
82
- * Compute the task progress signal from a task's state and artifacts directory.
83
- *
84
- * - editedFiles: true if task has artifact files or a non-empty result
85
- * - producedArtifacts: true if artifacts directory contains task-specific files
86
- * - ranTests: true if result text contains test-related keywords
87
- *
88
- * @param task - The task state to analyze
89
- * @param artifactsDir - Path to the artifacts directory
90
- * @returns TaskProgressSignal indicating what progress was made
91
- */
92
- export function computeTaskProgressSignal(task: TeamTaskState, artifactsDir: string): TaskProgressSignal {
93
- // editedFiles: check if task has artifact descriptors or a non-empty result
94
- const hasResultArtifact = task.resultArtifact !== undefined;
95
- const hasNonEmptyResult = task.resultArtifact?.path !== undefined;
96
- const editedFiles = hasResultArtifact || hasNonEmptyResult;
97
-
98
- // producedArtifacts: check artifacts directory for task-specific files
99
- let producedArtifacts = false;
100
- if (artifactsDir && fs.existsSync(artifactsDir)) {
101
- try {
102
- const entries = fs.readdirSync(artifactsDir);
103
- // Look for files/directories that START with the task ID prefix.
104
- // Using startsWith instead of includes to avoid false positives
105
- // from short task IDs like "01" matching unrelated entries.
106
- const taskPrefix = task.id.replace(/[^a-zA-Z0-9_-]/g, "");
107
- producedArtifacts = entries.some((entry) => {
108
- const baseName = entry.split(".")[0] ?? entry;
109
- return baseName.startsWith(taskPrefix);
110
- });
111
- } catch {
112
- producedArtifacts = false;
113
- }
114
- }
115
-
116
- // ranTests: check if result-related fields contain test keywords
117
- let ranTests = false;
118
- const textToSearch = [
119
- task.error ?? "",
120
- task.terminalEvidence ? JSON.stringify(task.terminalEvidence) : "",
121
- task.diagnostics ? JSON.stringify(task.diagnostics) : "",
122
- ].join(" ");
123
-
124
- const lowerText = textToSearch.toLowerCase();
125
- ranTests = TEST_KEYWORDS.some((keyword) => lowerText.includes(keyword.toLowerCase()));
126
-
127
- return { editedFiles, producedArtifacts, ranTests };
128
- }
@@ -1,36 +0,0 @@
1
- /**
2
- * Metric names that are denied to prevent prototype pollution.
3
- */
4
- export const DENIED_METRIC_NAMES: ReadonlySet<string> = new Set(["__proto__", "constructor", "prototype"]);
5
-
6
- const METRIC_LINE_RE = /^CREW_METRIC\s+(\w+)=(\S+)$/;
7
-
8
- /**
9
- * Parse CREW_METRIC lines from worker stdout.
10
- *
11
- * Lines must match the pattern: `CREW_METRIC name=value`
12
- * - `name` must be a word character sequence (alphanumeric + underscore)
13
- * - `value` must parse as a valid finite number
14
- * - Denied names (__proto__, constructor, prototype) are silently skipped
15
- *
16
- * @param output - Raw worker stdout text
17
- * @returns Map of metric name → numeric value
18
- */
19
- export function parseMetricLines(output: string): Record<string, number> {
20
- const metrics: Record<string, number> = {};
21
-
22
- for (const line of output.split("\n")) {
23
- const match = METRIC_LINE_RE.exec(line);
24
- if (!match) continue;
25
-
26
- const [, name, rawValue] = match;
27
- if (DENIED_METRIC_NAMES.has(name)) continue;
28
-
29
- const value = Number(rawValue);
30
- if (!Number.isFinite(value)) continue;
31
-
32
- metrics[name] = value;
33
- }
34
-
35
- return metrics;
36
- }
@@ -1,184 +0,0 @@
1
- // ─── Streaming Preview ───────────────────────────────────────────────────────
2
- // Captures incremental worker output during task execution for live preview.
3
- // Used by the UI layer to show partial results before task completion.
4
-
5
- import { TAIL_CAPTURE_STREAM_STAGE } from "../compaction/compact-stages/index.ts";
6
- import type { ParsedPiUsage } from "./pi-json-output.ts";
7
-
8
- export interface ToolCallPreview {
9
- toolName: string;
10
- inputPreview: string;
11
- startedAt: number;
12
- }
13
-
14
- export interface StreamPreview {
15
- taskId: string;
16
- runId: string;
17
- /** Cumulative text output captured so far */
18
- textBuffer: string;
19
- /** Current tool call in progress (if any) */
20
- activeToolCall: ToolCallPreview | null;
21
- /** Completed tool calls count */
22
- toolCallCount: number;
23
- /** Current turn number (incremented per assistant message) */
24
- turnCount: number;
25
- /** Token usage snapshot */
26
- usage: Partial<ParsedPiUsage> | null;
27
- /** Wall-clock start time */
28
- startedAt: number;
29
- /** Last update timestamp */
30
- lastUpdatedAt: number;
31
- /** Whether the task has completed */
32
- finished: boolean;
33
- }
34
-
35
- export function createStreamPreview(taskId: string, runId: string): StreamPreview {
36
- const now = Date.now();
37
- return {
38
- taskId,
39
- runId,
40
- textBuffer: "",
41
- activeToolCall: null,
42
- toolCallCount: 0,
43
- turnCount: 0,
44
- usage: null,
45
- startedAt: now,
46
- lastUpdatedAt: now,
47
- finished: false,
48
- };
49
- }
50
-
51
- /** Max text buffer size — drop oldest content when exceeded */
52
- const MAX_TEXT_BUFFER = 16_384;
53
- /** Max tool input preview length */
54
- const MAX_INPUT_PREVIEW = 512;
55
-
56
- const asRecord = (value: unknown): Record<string, unknown> | undefined =>
57
- value && typeof value === "object" && !Array.isArray(value) ? (value as Record<string, unknown>) : undefined;
58
-
59
- function truncateWithEllipsis(text: string, maxLength: number): string {
60
- if (text.length <= maxLength) return text;
61
- return text.slice(0, maxLength - 3) + "...";
62
- }
63
-
64
- function extractTextFromContent(content: unknown): string {
65
- if (typeof content === "string") return content;
66
- if (!Array.isArray(content)) return "";
67
- const parts: string[] = [];
68
- for (const part of content) {
69
- const obj = asRecord(part);
70
- if (!obj) continue;
71
- if (obj.type === "text" && typeof obj.text === "string") parts.push(obj.text);
72
- else if (typeof obj.content === "string") parts.push(obj.content);
73
- }
74
- return parts.join("");
75
- }
76
-
77
- /**
78
- * Feed a JSON event from Pi's stdout into the preview, updating it in place.
79
- * Returns true if the preview was modified.
80
- */
81
- export function feedJsonEvent(preview: StreamPreview, event: unknown): boolean {
82
- const obj = asRecord(event);
83
- if (!obj) return false;
84
-
85
- let modified = false;
86
- preview.lastUpdatedAt = Date.now();
87
-
88
- // Detect tool calls
89
- if (obj.type === "tool_call" || obj.type === "tool_use" || obj.type === "toolCall") {
90
- const toolName = typeof obj.name === "string" ? obj.name : typeof obj.tool === "string" ? obj.tool : "unknown";
91
- const rawInput = obj.input ?? obj.arguments ?? obj.params ?? "";
92
- const inputStr = typeof rawInput === "string" ? rawInput : JSON.stringify(rawInput);
93
- preview.activeToolCall = {
94
- toolName,
95
- inputPreview: truncateWithEllipsis(inputStr, MAX_INPUT_PREVIEW),
96
- startedAt: Date.now(),
97
- };
98
- preview.toolCallCount++;
99
- modified = true;
100
- }
101
-
102
- // Detect tool results (clear active tool call)
103
- if (obj.type === "tool_result" || obj.type === "toolResult" || obj.type === "result") {
104
- preview.activeToolCall = null;
105
- modified = true;
106
- }
107
-
108
- // Detect assistant text output
109
- const message = asRecord(obj.message);
110
- if (message?.role === "assistant" || obj.role === "assistant") {
111
- preview.turnCount++;
112
- const text = extractTextFromContent(message?.content ?? obj.content);
113
- if (text) {
114
- const appended = preview.textBuffer.length > 0 ? preview.textBuffer + "\n" + text : text;
115
- // Sprint 5: refactored onto the stage-chain. TAIL_CAPTURE_STREAM_STAGE
116
- // is a 16_384-char tail-capture stage with no marker (the UI shows
117
- // raw text without a prefix). It is bit-equivalent to the inline
118
- // `appended.slice(appended.length - MAX_TEXT_BUFFER)` for inputs at
119
- // or below the cap (returns verbatim) and equivalent for over-cap
120
- // inputs (returns last MAX_TEXT_BUFFER chars).
121
- preview.textBuffer = TAIL_CAPTURE_STREAM_STAGE.apply(appended);
122
- }
123
- modified = true;
124
- }
125
-
126
- // Detect direct text/final output
127
- if (typeof obj.text === "string" && obj.text.trim()) {
128
- const appended = preview.textBuffer.length > 0 ? preview.textBuffer + "\n" + obj.text : obj.text;
129
- preview.textBuffer = TAIL_CAPTURE_STREAM_STAGE.apply(appended);
130
- modified = true;
131
- }
132
-
133
- // Detect usage
134
- const rawUsage = obj.usage ?? obj.tokenUsage ?? obj.tokens ?? obj.stats;
135
- if (rawUsage && typeof rawUsage === "object") {
136
- const u = asRecord(rawUsage);
137
- if (u) {
138
- preview.usage = {
139
- input: typeof u.input === "number" ? u.input : preview.usage?.input,
140
- output: typeof u.output === "number" ? u.output : preview.usage?.output,
141
- cacheRead: typeof u.cacheRead === "number" ? u.cacheRead : preview.usage?.cacheRead,
142
- cacheWrite: typeof u.cacheWrite === "number" ? u.cacheWrite : preview.usage?.cacheWrite,
143
- cost: typeof u.cost === "number" ? u.cost : preview.usage?.cost,
144
- turns: typeof u.turns === "number" ? u.turns : preview.usage?.turns,
145
- };
146
- modified = true;
147
- }
148
- }
149
-
150
- return modified;
151
- }
152
-
153
- /** Mark preview as finished */
154
- export function finishStreamPreview(preview: StreamPreview): void {
155
- preview.finished = true;
156
- preview.activeToolCall = null;
157
- preview.lastUpdatedAt = Date.now();
158
- }
159
-
160
- /** Render a compact one-line status for the preview */
161
- export function renderPreviewStatus(preview: StreamPreview): string {
162
- const elapsed = Math.round((Date.now() - preview.startedAt) / 1000);
163
- const parts: string[] = [];
164
-
165
- if (preview.finished) {
166
- parts.push("✓ done");
167
- } else if (preview.activeToolCall) {
168
- parts.push(`⚙ ${preview.activeToolCall.toolName}`);
169
- } else {
170
- parts.push("⟳ thinking");
171
- }
172
-
173
- parts.push(`T${preview.turnCount}`);
174
- parts.push(`${preview.toolCallCount} tools`);
175
-
176
- if (preview.usage?.input) {
177
- const inK = Math.round(preview.usage.input / 1024);
178
- parts.push(`${inK}k in`);
179
- }
180
-
181
- parts.push(`${elapsed}s`);
182
-
183
- return parts.join(" | ");
184
- }