pi-crew 0.9.67 → 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 (222) hide show
  1. package/CHANGELOG.md +172 -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 +65847 -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 +9 -6
  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 +36 -14
  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/scratchpad/README.md +10 -5
  111. package/src/runtime/scratchpad/guest.ts +103 -2
  112. package/src/runtime/scratchpad/protocol.ts +13 -1
  113. package/src/runtime/scratchpad/transform.ts +206 -12
  114. package/src/runtime/settings-store.ts +219 -0
  115. package/src/runtime/spawn-policy.ts +217 -0
  116. package/src/runtime/stale-reconciler.ts +87 -6
  117. package/src/runtime/subagent-manager.ts +25 -1
  118. package/src/runtime/task-output-context.ts +230 -9
  119. package/src/runtime/task-packet.ts +23 -1
  120. package/src/runtime/task-runner/child-executor.ts +106 -7
  121. package/src/runtime/task-runner/post-execution.ts +125 -1
  122. package/src/runtime/task-runner/pre-execution.ts +39 -1
  123. package/src/runtime/task-runner/prompt-builder.ts +51 -1
  124. package/src/runtime/task-runner/retrieval-orchestrator.ts +72 -18
  125. package/src/runtime/task-runner/spec-evidence.ts +403 -0
  126. package/src/runtime/task-runner/state-helpers.ts +26 -24
  127. package/src/runtime/task-runner.ts +11 -0
  128. package/src/runtime/team-runner.ts +129 -1671
  129. package/src/runtime/verification/spec-sandbox.ts +255 -0
  130. package/src/runtime/verification/verification-gates.ts +3 -2
  131. package/src/runtime/verification/verification-worktree.ts +2 -1
  132. package/src/runtime/workflow-phase-advance.ts +100 -0
  133. package/src/runtime/workspace-tree.ts +9 -0
  134. package/src/schema/config-schema.ts +65 -25
  135. package/src/schema/sensitive-config-paths.ts +64 -0
  136. package/src/schema/team-tool-schema.ts +13 -3
  137. package/src/state/README.md +4 -10
  138. package/src/state/atomic-write.ts +20 -3
  139. package/src/state/contracts.ts +38 -0
  140. package/src/state/coordination/mailbox.ts +12 -2
  141. package/src/state/event-log/cursor.ts +223 -0
  142. package/src/state/event-log/event-log-rotation.ts +12 -4
  143. package/src/state/event-log/event-log.ts +152 -369
  144. package/src/state/event-log/sequence-cache.ts +373 -0
  145. package/src/state/event-log/worker-atomic-writer.ts +2 -1
  146. package/src/state/stores/active-run-registry.ts +3 -2
  147. package/src/state/stores/manifest-io.ts +237 -0
  148. package/src/state/stores/ownership-map.ts +162 -0
  149. package/src/state/stores/plan-store.ts +241 -0
  150. package/src/state/stores/run-cache.ts +0 -90
  151. package/src/state/stores/spec-store.ts +189 -0
  152. package/src/state/stores/state-store.ts +66 -229
  153. package/src/state/types.ts +197 -0
  154. package/src/ui/dashboard-panes/plan-pane.ts +136 -0
  155. package/src/ui/dashboard-panes/progress-pane.ts +6 -0
  156. package/src/ui/dashboard-panes/transcript-pane.ts +31 -0
  157. package/src/ui/heartbeat-aggregator.ts +9 -1
  158. package/src/ui/keybinding-map.ts +54 -13
  159. package/src/ui/powerbar-publisher.ts +52 -1
  160. package/src/ui/run-dashboard.ts +31 -5
  161. package/src/ui/run-snapshot-cache.ts +57 -30
  162. package/src/ui/snapshot-types.ts +6 -1
  163. package/src/ui/widget/widget-renderer.ts +9 -1
  164. package/src/utils/file-coalescer.ts +0 -4
  165. package/src/utils/fs-errno.ts +66 -0
  166. package/src/utils/fs-watch.ts +1 -1
  167. package/src/utils/internal-error.ts +3 -1
  168. package/src/utils/paths.ts +11 -3
  169. package/src/utils/task-name-generator.ts +1 -8
  170. package/src/workflows/discover-workflows.ts +19 -2
  171. package/src/workflows/validate-workflow.ts +7 -1
  172. package/src/workflows/workflow-config.ts +10 -0
  173. package/src/workflows/workflow-serializer.ts +3 -0
  174. package/src/worktree/worktree-manager.ts +22 -0
  175. package/src/agents/agent-search.ts +0 -98
  176. package/src/benchmark/benchmark-runner.ts +0 -313
  177. package/src/benchmark/feedback-loop.ts +0 -73
  178. package/src/config/resilient-parser.ts +0 -117
  179. package/src/extension/crew-vibes/cat-frames.ts +0 -18
  180. package/src/extension/result-watcher.ts +0 -139
  181. package/src/observability/exporters/prometheus-exporter.ts +0 -54
  182. package/src/observability/metric-retention.ts +0 -64
  183. package/src/runtime/compaction/compaction-summary.ts +0 -278
  184. package/src/runtime/errors/crew-errors.ts +0 -162
  185. package/src/runtime/live-session/intercom-bridge.ts +0 -187
  186. package/src/runtime/loop-gates.ts +0 -128
  187. package/src/runtime/metric-parser.ts +0 -36
  188. package/src/runtime/output/stream-preview.ts +0 -184
  189. package/src/runtime/output/tool-progress.ts +0 -278
  190. package/src/runtime/phase-tracker.ts +0 -385
  191. package/src/runtime/pipeline-runner.ts +0 -523
  192. package/src/runtime/process/process-lifecycle.ts +0 -491
  193. package/src/runtime/recovery/retry-runner.ts +0 -330
  194. package/src/runtime/run-drift.ts +0 -219
  195. package/src/runtime/scratchpad/snapshot-hmac.ts +0 -167
  196. package/src/runtime/task-quality.ts +0 -199
  197. package/src/runtime/task-runner/run-projection.ts +0 -128
  198. package/src/runtime/verification/post-checks.ts +0 -142
  199. package/src/state/coordination/schedule.ts +0 -166
  200. package/src/state/event-log/jsonl-writer.ts +0 -115
  201. package/src/state/hook-instinct-bridge.ts +0 -94
  202. package/src/state/hook-integrations.ts +0 -51
  203. package/src/state/session-state-map.ts +0 -51
  204. package/src/state/stores/blob-store.ts +0 -308
  205. package/src/state/stores/instinct-store.ts +0 -275
  206. package/src/state/stores/observation-store.ts +0 -176
  207. package/src/state/tiered-eval.ts +0 -480
  208. package/src/state/types-eval.ts +0 -58
  209. package/src/tools/safe-bash-extension.ts +0 -54
  210. package/src/tools/safe-bash.ts +0 -505
  211. package/src/ui/agent-management-overlay.ts +0 -160
  212. package/src/ui/crew-footer.ts +0 -102
  213. package/src/ui/crew-select-list.ts +0 -114
  214. package/src/ui/dashboard-panes/capability-pane.ts +0 -77
  215. package/src/ui/transcript-entries.ts +0 -256
  216. package/src/utils/conflict-detect.ts +0 -721
  217. package/src/utils/fingerprint.ts +0 -180
  218. package/src/utils/gh-protocol.ts +0 -556
  219. package/src/utils/project-detector.ts +0 -160
  220. package/src/utils/sse-parser.ts +0 -131
  221. package/src/workflows/cost-estimator.ts +0 -34
  222. 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
- }