pi-crew 0.9.68 → 0.10.2
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 +222 -0
- package/NOTICE.md +21 -0
- package/README.md +44 -2
- package/agents/analyst.md +1 -1
- package/agents/cold-verifier.md +3 -1
- package/agents/critic.md +1 -1
- package/agents/executor.md +1 -1
- package/agents/explorer.md +1 -1
- package/agents/planner.md +1 -1
- package/agents/reviewer.md +1 -1
- package/agents/security-reviewer.md +1 -1
- package/agents/test-engineer.md +1 -1
- package/agents/verifier.md +1 -1
- package/agents/writer.md +1 -1
- package/dist/index.mjs +68113 -60774
- package/docs/README.md +2 -0
- package/docs/actions-reference.md +31 -0
- package/docs/commands-reference.md +17 -6
- package/docs/resource-formats.md +13 -0
- package/package.json +4 -2
- package/schema.json +503 -91
- package/scripts/resource-sampler.mjs +36 -2
- package/skills/requirements-to-task-packet/SKILL.md +26 -0
- package/skills/widget-rendering/SKILL.md +7 -7
- package/src/agents/agent-config.ts +2 -1
- package/src/agents/discover-agents.ts +23 -14
- package/src/config/config-merge.ts +183 -0
- package/src/config/config-validation.ts +687 -0
- package/src/config/config.ts +22 -864
- package/src/config/defaults.ts +43 -2
- package/src/config/drift-detector.ts +1 -1
- package/src/config/env-vars.ts +691 -0
- package/src/config/role-tools.ts +11 -9
- package/src/config/sanitize-project-config.ts +172 -0
- package/src/config/types.ts +49 -1
- package/src/extension/async-notifier.ts +25 -2
- package/src/extension/crew-cleanup.ts +13 -0
- package/src/extension/crew-vibes/config.ts +2 -1
- package/src/extension/crew-vibes/footer.ts +19 -0
- package/src/extension/crew-vibes/index.ts +11 -1
- package/src/extension/plan-orchestrate.ts +132 -0
- package/src/extension/register.ts +8 -0
- package/src/extension/registration/command-registration.ts +1 -0
- package/src/extension/registration/commands/dashboard.ts +158 -0
- package/src/extension/registration/commands/index.ts +35 -0
- package/src/extension/registration/commands/manage.ts +303 -0
- package/src/extension/registration/commands/run.ts +228 -0
- package/src/extension/registration/commands/shared.ts +639 -0
- package/src/extension/registration/commands/status.ts +60 -0
- package/src/extension/registration/commands.ts +13 -1224
- package/src/extension/registration/foreground-run-controller.ts +10 -2
- package/src/extension/registration/lifecycle-handlers.ts +178 -17
- package/src/extension/registration/runtime-cleanup.ts +23 -5
- package/src/extension/registration/subagent-tools.ts +218 -9
- package/src/extension/registration/team-tool.ts +5 -1
- package/src/extension/registration/ui.ts +5 -4
- package/src/extension/rpc-hmac.ts +5 -3
- package/src/extension/team-tool/api/heartbeat.ts +47 -10
- package/src/extension/team-tool/api/plan-approval.ts +9 -0
- package/src/extension/team-tool/api/task-claims.ts +109 -40
- package/src/extension/team-tool/cancel.ts +84 -50
- package/src/extension/team-tool/dispatch/index.ts +1 -0
- package/src/extension/team-tool/dispatch/run.ts +4 -1
- package/src/extension/team-tool/doctor.ts +103 -1
- package/src/extension/team-tool/orchestrate.ts +66 -1
- package/src/extension/team-tool/plans.ts +192 -0
- package/src/extension/team-tool/respond.ts +197 -65
- package/src/extension/team-tool/run-deadline.ts +35 -3
- package/src/extension/team-tool/run-intent.ts +63 -0
- package/src/extension/team-tool/run.ts +74 -20
- package/src/extension/team-tool/status.ts +84 -26
- package/src/extension/team-tool.ts +11 -2
- package/src/hooks/registry.ts +1 -6
- package/src/i18n.ts +9 -0
- package/src/prompt/prompt-runtime.ts +521 -2
- package/src/prompt/worker-events-channel.ts +173 -0
- package/src/runtime/README.md +8 -8
- package/src/runtime/async-runner.ts +7 -3
- package/src/runtime/background-runner.ts +42 -14
- package/src/runtime/broker/broker-issuer.ts +9 -2
- package/src/runtime/broker/crew-broker-tokens.ts +43 -6
- package/src/runtime/broker/crew-broker.ts +838 -10
- package/src/runtime/broker/wait-status-cache.ts +157 -0
- package/src/runtime/budget-enforcement.ts +281 -0
- package/src/runtime/child-pi/child-pi-constants.ts +8 -0
- package/src/runtime/child-pi/child-pi-spawn.ts +60 -14
- package/src/runtime/child-pi/child-pi-streams.ts +21 -1
- package/src/runtime/child-pi/child-pi-timers.ts +324 -0
- package/src/runtime/child-pi/child-pi.ts +97 -201
- package/src/runtime/child-pi/mock-fixtures.ts +16 -2
- package/src/runtime/crew-agent-records.ts +259 -14
- package/src/runtime/delegate-spawn.ts +148 -0
- package/src/runtime/detached-run-results.ts +90 -0
- package/src/runtime/deterministic-ast.ts +2 -1
- package/src/runtime/dispatch-batch.ts +945 -0
- package/src/runtime/finalize-run.ts +557 -0
- package/src/runtime/goal-workflow/adaptive-plan.ts +116 -15
- package/src/runtime/goal-workflow/dynamic-workflow-runner.ts +8 -3
- package/src/runtime/goal-workflow/goal-state-store.ts +1 -1
- package/src/runtime/group-join.ts +11 -125
- package/src/runtime/live-session/live-session-runtime.ts +26 -1
- package/src/runtime/merge-gate.ts +32 -10
- package/src/runtime/merge-loop.ts +130 -0
- package/src/runtime/model/model-budget-summary.ts +53 -0
- package/src/runtime/model/model-fallback.ts +36 -2
- package/src/runtime/model/pi-args.ts +10 -0
- package/src/runtime/model/provider-extensions.ts +10 -0
- package/src/runtime/orphan-worker-registry.ts +1 -1
- package/src/runtime/output/output-validator.ts +45 -0
- package/src/runtime/parent-guard.ts +3 -1
- package/src/runtime/peer-dep.ts +2 -1
- package/src/runtime/per-write-validator.ts +0 -5
- package/src/runtime/pi-spawn.ts +61 -15
- package/src/runtime/plan-approval.ts +125 -0
- package/src/runtime/plan-replan.ts +151 -0
- package/src/runtime/process-status.ts +16 -1
- package/src/runtime/recovery/checkpoint.ts +0 -18
- package/src/runtime/recovery/crash-recovery.ts +111 -46
- package/src/runtime/run-tracker.ts +77 -10
- package/src/runtime/scheduler-context.ts +98 -0
- package/src/runtime/scheduling/coalesce-tasks.ts +5 -0
- package/src/runtime/scheduling/global-worker-cap.ts +2 -1
- package/src/runtime/scheduling/nested-slots.ts +70 -0
- package/src/runtime/scheduling/run-coalesced-task-group.ts +64 -13
- package/src/runtime/scheduling/task-graph-scheduler.ts +0 -10
- package/src/runtime/settings-store.ts +219 -0
- package/src/runtime/spawn-policy.ts +217 -0
- package/src/runtime/stale-reconciler.ts +87 -6
- package/src/runtime/subagent-manager.ts +25 -1
- package/src/runtime/task-output-context.ts +230 -9
- package/src/runtime/task-packet.ts +23 -1
- package/src/runtime/task-runner/child-executor.ts +106 -7
- package/src/runtime/task-runner/post-execution.ts +125 -1
- package/src/runtime/task-runner/pre-execution.ts +39 -1
- package/src/runtime/task-runner/prompt-builder.ts +51 -1
- package/src/runtime/task-runner/retrieval-orchestrator.ts +72 -18
- package/src/runtime/task-runner/spec-evidence.ts +403 -0
- package/src/runtime/task-runner/state-helpers.ts +26 -24
- package/src/runtime/task-runner.ts +11 -0
- package/src/runtime/team-runner.ts +132 -1673
- package/src/runtime/verification/spec-sandbox.ts +255 -0
- package/src/runtime/verification/verification-gates.ts +3 -2
- package/src/runtime/verification/verification-worktree.ts +2 -1
- package/src/runtime/workflow-phase-advance.ts +100 -0
- package/src/runtime/workspace-tree.ts +9 -0
- package/src/schema/config-schema.ts +66 -26
- package/src/schema/sensitive-config-paths.ts +64 -0
- package/src/schema/team-tool-schema.ts +13 -3
- package/src/state/README.md +4 -10
- package/src/state/atomic-write.ts +20 -3
- package/src/state/contracts.ts +38 -0
- package/src/state/coordination/mailbox.ts +12 -2
- package/src/state/event-log/cursor.ts +223 -0
- package/src/state/event-log/event-log-rotation.ts +12 -4
- package/src/state/event-log/event-log.ts +152 -369
- package/src/state/event-log/sequence-cache.ts +373 -0
- package/src/state/event-log/worker-atomic-writer.ts +2 -1
- package/src/state/stores/active-run-registry.ts +3 -2
- package/src/state/stores/manifest-io.ts +237 -0
- package/src/state/stores/ownership-map.ts +162 -0
- package/src/state/stores/plan-store.ts +241 -0
- package/src/state/stores/run-cache.ts +0 -90
- package/src/state/stores/spec-store.ts +189 -0
- package/src/state/stores/state-store.ts +139 -232
- package/src/state/types.ts +199 -0
- package/src/ui/dashboard-panes/plan-pane.ts +136 -0
- package/src/ui/dashboard-panes/progress-pane.ts +6 -0
- package/src/ui/dashboard-panes/transcript-pane.ts +31 -0
- package/src/ui/dock-footer.ts +49 -0
- package/src/ui/heartbeat-aggregator.ts +9 -1
- package/src/ui/inline-panel/agent-pane.ts +375 -0
- package/src/ui/inline-panel/agent-transcript.ts +338 -0
- package/src/ui/inline-panel/agent-view-overlay.ts +225 -0
- package/src/ui/inline-panel/crew-editor.ts +192 -0
- package/src/ui/inline-panel/index.ts +290 -0
- package/src/ui/inline-panel/panel-rows.ts +37 -0
- package/src/ui/inline-panel/panel-selection.ts +157 -0
- package/src/ui/inline-panel/panel-store.ts +111 -0
- package/src/ui/inline-panel/view-session-store.ts +36 -0
- package/src/ui/keybinding-map.ts +54 -13
- package/src/ui/pi-ui-compat.ts +9 -0
- package/src/ui/powerbar-publisher.ts +52 -1
- package/src/ui/run-dashboard.ts +31 -5
- package/src/ui/run-snapshot-cache.ts +57 -30
- package/src/ui/snapshot-types.ts +6 -1
- package/src/ui/widget/index.ts +176 -22
- package/src/ui/widget/task-list.ts +198 -0
- package/src/ui/widget/widget-formatters.ts +240 -4
- package/src/ui/widget/widget-renderer.ts +243 -38
- package/src/ui/widget/widget-types.ts +11 -0
- package/src/utils/child-process-shield.ts +106 -0
- package/src/utils/file-coalescer.ts +0 -4
- package/src/utils/fs-errno.ts +66 -0
- package/src/utils/fs-watch.ts +1 -1
- package/src/utils/internal-error.ts +3 -1
- package/src/utils/paths.ts +11 -3
- package/src/utils/redaction.ts +7 -0
- package/src/utils/safe-abort.ts +45 -0
- package/src/utils/task-name-generator.ts +1 -8
- package/src/workflows/discover-workflows.ts +20 -2
- package/src/workflows/validate-workflow.ts +7 -1
- package/src/workflows/workflow-config.ts +17 -0
- package/src/workflows/workflow-serializer.ts +3 -0
- package/src/worktree/worktree-manager.ts +22 -0
- package/workflows/default.workflow.md +36 -26
- package/workflows/strict-fast-fix.workflow.md +26 -0
- package/src/agents/agent-search.ts +0 -98
- package/src/benchmark/benchmark-runner.ts +0 -313
- package/src/benchmark/feedback-loop.ts +0 -73
- package/src/config/resilient-parser.ts +0 -117
- package/src/extension/crew-vibes/cat-frames.ts +0 -18
- package/src/extension/result-watcher.ts +0 -139
- package/src/observability/exporters/prometheus-exporter.ts +0 -54
- package/src/observability/metric-retention.ts +0 -64
- package/src/runtime/compaction/compaction-summary.ts +0 -278
- package/src/runtime/errors/crew-errors.ts +0 -162
- package/src/runtime/live-session/intercom-bridge.ts +0 -187
- package/src/runtime/loop-gates.ts +0 -128
- package/src/runtime/metric-parser.ts +0 -36
- package/src/runtime/output/stream-preview.ts +0 -184
- package/src/runtime/output/tool-progress.ts +0 -278
- package/src/runtime/phase-tracker.ts +0 -385
- package/src/runtime/pipeline-runner.ts +0 -523
- package/src/runtime/process/process-lifecycle.ts +0 -491
- package/src/runtime/recovery/retry-runner.ts +0 -330
- package/src/runtime/run-drift.ts +0 -219
- package/src/runtime/task-quality.ts +0 -199
- package/src/runtime/task-runner/run-projection.ts +0 -128
- package/src/runtime/verification/post-checks.ts +0 -142
- package/src/state/coordination/schedule.ts +0 -166
- package/src/state/event-log/jsonl-writer.ts +0 -115
- package/src/state/hook-instinct-bridge.ts +0 -94
- package/src/state/hook-integrations.ts +0 -51
- package/src/state/session-state-map.ts +0 -51
- package/src/state/stores/blob-store.ts +0 -308
- package/src/state/stores/instinct-store.ts +0 -275
- package/src/state/stores/observation-store.ts +0 -176
- package/src/state/tiered-eval.ts +0 -480
- package/src/state/types-eval.ts +0 -58
- package/src/tools/safe-bash-extension.ts +0 -54
- package/src/tools/safe-bash.ts +0 -505
- package/src/ui/agent-management-overlay.ts +0 -160
- package/src/ui/crew-footer.ts +0 -102
- package/src/ui/crew-select-list.ts +0 -114
- package/src/ui/dashboard-panes/capability-pane.ts +0 -77
- package/src/ui/transcript-entries.ts +0 -256
- package/src/utils/conflict-detect.ts +0 -721
- package/src/utils/fingerprint.ts +0 -180
- package/src/utils/gh-protocol.ts +0 -556
- package/src/utils/project-detector.ts +0 -160
- package/src/utils/sse-parser.ts +0 -131
- package/src/workflows/cost-estimator.ts +0 -34
- package/src/workflows/intermediate-store.ts +0 -166
|
@@ -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
|
-
}
|