pi-crew 0.1.46 → 0.1.49
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +97 -0
- package/agents/analyst.md +11 -11
- package/agents/critic.md +11 -11
- package/agents/executor.md +11 -11
- package/agents/explorer.md +11 -11
- package/agents/planner.md +11 -11
- package/agents/reviewer.md +11 -11
- package/agents/security-reviewer.md +11 -11
- package/agents/test-engineer.md +11 -11
- package/agents/verifier.md +11 -11
- package/agents/writer.md +11 -11
- package/docs/next-upgrade-roadmap.md +117 -42
- package/docs/refactor-tasks-phase3.md +394 -394
- package/docs/refactor-tasks-phase4.md +564 -564
- package/docs/refactor-tasks-phase5.md +402 -402
- package/docs/refactor-tasks-phase6.md +662 -662
- package/docs/research/AGENT-EXECUTION-ARCHITECTURE.md +261 -0
- package/docs/research/AGENT-LIFECYCLE-COMPARISON.md +111 -0
- package/docs/research/AUDIT_OH_MY_PI.md +261 -0
- package/docs/research/AUDIT_PI_CREW.md +457 -0
- package/docs/research/CAVEMAN-DEEP-RESEARCH.md +281 -0
- package/docs/research/COMPARISON_OH_MY_PI_VS_PI_CREW.md +264 -0
- package/docs/research/DEEP-RESEARCH-PI-POWERBAR.md +343 -0
- package/docs/research/DEEP_RESEARCH_SUBAGENT_ARCHITECTURE.md +480 -0
- package/docs/research/GAP_CLOSURE_IMPLEMENTATION_PLAN.md +354 -0
- package/docs/research/IMPLEMENTATION_PLAN.md +385 -0
- package/docs/research/LIVE-SESSION-PRODUCTION-READY-PLAN.md +502 -0
- package/docs/research/OH-MY-PI-DEEP-RESEARCH-v14.7.6.md +266 -0
- package/docs/research/REMAINING-GAPS-PLAN.md +363 -0
- package/docs/research/SESSION-SUMMARY-2026-05-08.md +146 -0
- package/docs/research/UI-RESPONSIVENESS-AUDIT.md +173 -0
- package/docs/research-awesome-agent-skills-distillation.md +100 -100
- package/docs/research-extension-examples.md +297 -297
- package/docs/research-extension-system.md +324 -324
- package/docs/research-oh-my-pi-distillation.md +56 -9
- package/docs/research-optimization-plan.md +548 -548
- package/docs/research-phase10-distillation.md +198 -198
- package/docs/research-phase11-distillation.md +201 -201
- package/docs/research-pi-coding-agent.md +357 -357
- package/docs/research-source-pi-crew-reference.md +174 -174
- package/docs/runtime-flow.md +148 -148
- package/docs/source-runtime-refactor-map.md +107 -107
- package/index.ts +6 -6
- package/package.json +99 -98
- package/schema.json +8 -0
- package/skills/async-worker-recovery/SKILL.md +42 -42
- package/skills/context-artifact-hygiene/SKILL.md +52 -52
- package/skills/delegation-patterns/SKILL.md +54 -54
- package/skills/mailbox-interactive/SKILL.md +40 -40
- package/skills/model-routing-context/SKILL.md +39 -39
- package/skills/multi-perspective-review/SKILL.md +58 -58
- package/skills/observability-reliability/SKILL.md +41 -41
- package/skills/orchestration/SKILL.md +157 -0
- package/skills/ownership-session-security/SKILL.md +41 -41
- package/skills/pi-extension-lifecycle/SKILL.md +39 -39
- package/skills/requirements-to-task-packet/SKILL.md +63 -63
- package/skills/resource-discovery-config/SKILL.md +41 -41
- package/skills/runtime-state-reader/SKILL.md +44 -44
- package/skills/secure-agent-orchestration-review/SKILL.md +45 -45
- package/skills/state-mutation-locking/SKILL.md +42 -42
- package/skills/systematic-debugging/SKILL.md +67 -67
- package/skills/ui-render-performance/SKILL.md +39 -39
- package/skills/verification-before-done/SKILL.md +57 -57
- package/skills/worktree-isolation/SKILL.md +39 -39
- package/src/agents/agent-config.ts +6 -0
- package/src/agents/agent-search.ts +98 -0
- package/src/agents/agent-serializer.ts +4 -0
- package/src/agents/discover-agents.ts +17 -4
- package/src/config/config.ts +24 -0
- package/src/config/defaults.ts +11 -0
- package/src/extension/autonomous-policy.ts +26 -33
- package/src/extension/cross-extension-rpc.ts +82 -82
- package/src/extension/help.ts +1 -0
- package/src/extension/management.ts +5 -0
- package/src/extension/register.ts +58 -13
- package/src/extension/registration/commands.ts +33 -1
- package/src/extension/registration/compaction-guard.ts +125 -125
- package/src/extension/registration/team-tool.ts +6 -4
- package/src/extension/run-bundle-schema.ts +89 -89
- package/src/extension/run-index.ts +24 -18
- package/src/extension/run-maintenance.ts +68 -62
- package/src/extension/team-tool/api.ts +23 -2
- package/src/extension/team-tool/cancel.ts +86 -11
- package/src/extension/team-tool/context.ts +3 -0
- package/src/extension/team-tool/handle-settings.ts +188 -188
- package/src/extension/team-tool/inspect.ts +41 -41
- package/src/extension/team-tool/intent-policy.ts +42 -0
- package/src/extension/team-tool/lifecycle-actions.ts +47 -18
- package/src/extension/team-tool/parallel-dispatch.ts +156 -0
- package/src/extension/team-tool/plan.ts +19 -19
- package/src/extension/team-tool/respond.ts +10 -2
- package/src/extension/team-tool/run.ts +3 -2
- package/src/extension/team-tool/status.ts +1 -1
- package/src/extension/team-tool-types.ts +1 -0
- package/src/extension/team-tool.ts +13 -3
- package/src/hooks/registry.ts +61 -0
- package/src/hooks/types.ts +41 -0
- package/src/i18n.ts +184 -184
- package/src/observability/exporters/otlp-exporter.ts +77 -77
- package/src/prompt/prompt-runtime.ts +72 -72
- package/src/runtime/agent-control.ts +108 -2
- package/src/runtime/agent-memory.ts +72 -72
- package/src/runtime/agent-observability.ts +114 -114
- package/src/runtime/async-marker.ts +26 -26
- package/src/runtime/async-runner.ts +3 -1
- package/src/runtime/attention-events.ts +28 -28
- package/src/runtime/background-runner.ts +19 -0
- package/src/runtime/cancellation-token.ts +89 -0
- package/src/runtime/cancellation.ts +61 -51
- package/src/runtime/capability-inventory.ts +116 -0
- package/src/runtime/child-pi.ts +2 -1
- package/src/runtime/code-summary.ts +247 -0
- package/src/runtime/completion-guard.ts +190 -190
- package/src/runtime/crash-recovery.ts +181 -0
- package/src/runtime/crew-agent-records.ts +35 -7
- package/src/runtime/crew-agent-runtime.ts +1 -0
- package/src/runtime/custom-tools/irc-tool.ts +201 -0
- package/src/runtime/custom-tools/submit-result-tool.ts +90 -0
- package/src/runtime/delivery-coordinator.ts +3 -1
- package/src/runtime/direct-run.ts +35 -35
- package/src/runtime/effectiveness.ts +81 -76
- package/src/runtime/event-stream-bridge.ts +90 -0
- package/src/runtime/foreground-control.ts +82 -82
- package/src/runtime/green-contract.ts +46 -46
- package/src/runtime/group-join.ts +106 -106
- package/src/runtime/heartbeat-gradient.ts +28 -28
- package/src/runtime/heartbeat-watcher.ts +124 -124
- package/src/runtime/live-agent-control.ts +88 -88
- package/src/runtime/live-agent-manager.ts +78 -2
- package/src/runtime/live-control-realtime.ts +36 -36
- package/src/runtime/live-extension-bridge.ts +150 -0
- package/src/runtime/live-irc.ts +92 -0
- package/src/runtime/live-session-health.ts +100 -0
- package/src/runtime/live-session-runtime.ts +297 -7
- package/src/runtime/mcp-proxy.ts +113 -0
- package/src/runtime/notebook-helpers.ts +90 -0
- package/src/runtime/orphan-sentinel.ts +7 -0
- package/src/runtime/output-validator.ts +187 -0
- package/src/runtime/parallel-research.ts +44 -44
- package/src/runtime/parallel-utils.ts +57 -0
- package/src/runtime/parent-guard.ts +80 -0
- package/src/runtime/pi-json-output.ts +111 -111
- package/src/runtime/policy-engine.ts +79 -79
- package/src/runtime/progress-event-coalescer.ts +43 -43
- package/src/runtime/prose-compressor.ts +164 -0
- package/src/runtime/recovery-recipes.ts +74 -74
- package/src/runtime/result-extractor.ts +121 -0
- package/src/runtime/role-permission.ts +39 -39
- package/src/runtime/runtime-resolver.ts +1 -4
- package/src/runtime/semaphore.ts +131 -0
- package/src/runtime/sensitive-paths.ts +92 -0
- package/src/runtime/session-resources.ts +25 -25
- package/src/runtime/session-snapshot.ts +59 -59
- package/src/runtime/session-usage.ts +79 -79
- package/src/runtime/sidechain-output.ts +29 -29
- package/src/runtime/stream-preview.ts +177 -0
- package/src/runtime/subagent-manager.ts +3 -2
- package/src/runtime/subprocess-tool-registry.ts +67 -0
- package/src/runtime/supervisor-contact.ts +59 -59
- package/src/runtime/task-display.ts +38 -38
- package/src/runtime/task-output-context.ts +59 -9
- package/src/runtime/task-runner/capabilities.ts +78 -78
- package/src/runtime/task-runner/live-executor.ts +2 -0
- package/src/runtime/task-runner/progress.ts +119 -119
- package/src/runtime/task-runner/prompt-builder.ts +70 -8
- package/src/runtime/task-runner/prompt-pipeline.ts +64 -64
- package/src/runtime/task-runner/result-utils.ts +14 -14
- package/src/runtime/task-runner/run-projection.ts +104 -0
- package/src/runtime/task-runner/state-helpers.ts +22 -22
- package/src/runtime/task-runner.ts +75 -4
- package/src/runtime/team-runner.ts +60 -8
- package/src/runtime/worker-heartbeat.ts +21 -21
- package/src/runtime/worker-startup.ts +57 -57
- package/src/runtime/workspace-tree.ts +298 -0
- package/src/runtime/yield-handler.ts +189 -0
- package/src/schema/config-schema.ts +6 -0
- package/src/schema/team-tool-schema.ts +11 -1
- package/src/skills/discover-skills.ts +67 -0
- package/src/state/active-run-registry.ts +4 -2
- package/src/state/artifact-store.ts +4 -1
- package/src/state/atomic-write.ts +50 -1
- package/src/state/blob-store.ts +117 -0
- package/src/state/contracts.ts +1 -0
- package/src/state/event-log-rotation.ts +158 -0
- package/src/state/event-log.ts +52 -2
- package/src/state/mailbox.ts +87 -7
- package/src/state/state-store.ts +24 -4
- package/src/state/task-claims.ts +44 -44
- package/src/state/types.ts +20 -0
- package/src/state/usage.ts +29 -29
- package/src/subagents/async-entry.ts +1 -1
- package/src/subagents/index.ts +3 -3
- package/src/subagents/live/control.ts +1 -1
- package/src/subagents/live/manager.ts +1 -1
- package/src/subagents/live/realtime.ts +1 -1
- package/src/subagents/live/session-runtime.ts +1 -1
- package/src/subagents/manager.ts +1 -1
- package/src/subagents/spawn.ts +1 -1
- package/src/teams/team-serializer.ts +38 -38
- package/src/types/diff.d.ts +18 -18
- package/src/ui/agent-management-overlay.ts +144 -0
- package/src/ui/crew-footer.ts +101 -101
- package/src/ui/crew-select-list.ts +111 -111
- package/src/ui/crew-widget.ts +11 -2
- package/src/ui/dashboard-panes/cancellation-pane.ts +43 -0
- package/src/ui/dashboard-panes/capability-pane.ts +60 -0
- package/src/ui/dashboard-panes/mailbox-pane.ts +35 -11
- package/src/ui/dashboard-panes/metrics-pane.ts +34 -34
- package/src/ui/dynamic-border.ts +25 -25
- package/src/ui/layout-primitives.ts +106 -106
- package/src/ui/live-run-sidebar.ts +4 -0
- package/src/ui/loaders.ts +158 -158
- package/src/ui/powerbar-publisher.ts +77 -15
- package/src/ui/render-coalescer.ts +51 -0
- package/src/ui/render-diff.ts +119 -119
- package/src/ui/render-scheduler.ts +143 -143
- package/src/ui/run-dashboard.ts +4 -0
- package/src/ui/run-event-bus.ts +209 -0
- package/src/ui/run-snapshot-cache.ts +68 -16
- package/src/ui/snapshot-types.ts +8 -0
- package/src/ui/spinner.ts +17 -17
- package/src/ui/status-colors.ts +58 -58
- package/src/ui/syntax-highlight.ts +116 -116
- package/src/ui/transcript-entries.ts +258 -0
- package/src/utils/atomic-write.ts +33 -33
- package/src/utils/completion-dedupe.ts +63 -63
- package/src/utils/frontmatter.ts +68 -68
- package/src/utils/git.ts +262 -262
- package/src/utils/ids.ts +17 -12
- package/src/utils/incremental-reader.ts +104 -0
- package/src/utils/names.ts +27 -27
- package/src/utils/redaction.ts +44 -44
- package/src/utils/safe-paths.ts +47 -47
- package/src/utils/scan-cache.ts +137 -0
- package/src/utils/sleep.ts +32 -32
- package/src/utils/sse-parser.ts +134 -0
- package/src/utils/task-name-generator.ts +337 -0
- package/src/utils/visual.ts +33 -2
- package/src/workflows/validate-workflow.ts +40 -40
- package/src/worktree/branch-freshness.ts +45 -45
- package/src/worktree/cleanup.ts +2 -1
- package/teams/default.team.md +12 -12
- package/teams/fast-fix.team.md +11 -11
- package/teams/implementation.team.md +18 -18
- package/teams/parallel-research.team.md +14 -14
- package/teams/research.team.md +11 -11
- package/teams/review.team.md +12 -12
- package/workflows/default.workflow.md +29 -29
- package/workflows/fast-fix.workflow.md +22 -22
- package/workflows/implementation.workflow.md +38 -38
- package/workflows/parallel-research.workflow.md +46 -46
- package/workflows/research.workflow.md +22 -22
- package/workflows/review.workflow.md +30 -30
|
@@ -1,22 +1,22 @@
|
|
|
1
|
-
import type { TaskCheckpointState, TeamRunManifest, TeamTaskState } from "../../state/types.ts";
|
|
2
|
-
import { loadRunManifestById, saveRunTasks } from "../../state/state-store.ts";
|
|
3
|
-
import { recordFromTask, upsertCrewAgent } from "../crew-agent-records.ts";
|
|
4
|
-
|
|
5
|
-
export function updateTask(tasks: TeamTaskState[], updated: TeamTaskState): TeamTaskState[] {
|
|
6
|
-
return tasks.map((task) => task.id === updated.id ? updated : task);
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export function persistSingleTaskUpdate(manifest: TeamRunManifest, fallbackTasks: TeamTaskState[], updated: TeamTaskState): TeamTaskState[] {
|
|
10
|
-
const latest = loadRunManifestById(manifest.cwd, manifest.runId)?.tasks ?? fallbackTasks;
|
|
11
|
-
const merged = updateTask(latest, updated);
|
|
12
|
-
saveRunTasks(manifest, merged);
|
|
13
|
-
return merged;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export function checkpointTask(manifest: TeamRunManifest, tasks: TeamTaskState[], task: TeamTaskState, phase: TaskCheckpointState["phase"], childPid?: number): { task: TeamTaskState; tasks: TeamTaskState[] } {
|
|
17
|
-
const checkpoint: TaskCheckpointState = { phase, updatedAt: new Date().toISOString(), ...(childPid ? { childPid } : task.checkpoint?.childPid ? { childPid: task.checkpoint.childPid } : {}) };
|
|
18
|
-
const nextTask = { ...task, checkpoint };
|
|
19
|
-
const nextTasks = persistSingleTaskUpdate(manifest, updateTask(tasks, nextTask), nextTask);
|
|
20
|
-
upsertCrewAgent(manifest, recordFromTask(manifest, nextTask, "child-process"));
|
|
21
|
-
return { task: nextTask, tasks: nextTasks };
|
|
22
|
-
}
|
|
1
|
+
import type { TaskCheckpointState, TeamRunManifest, TeamTaskState } from "../../state/types.ts";
|
|
2
|
+
import { loadRunManifestById, saveRunTasks } from "../../state/state-store.ts";
|
|
3
|
+
import { recordFromTask, upsertCrewAgent } from "../crew-agent-records.ts";
|
|
4
|
+
|
|
5
|
+
export function updateTask(tasks: TeamTaskState[], updated: TeamTaskState): TeamTaskState[] {
|
|
6
|
+
return tasks.map((task) => task.id === updated.id ? updated : task);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function persistSingleTaskUpdate(manifest: TeamRunManifest, fallbackTasks: TeamTaskState[], updated: TeamTaskState): TeamTaskState[] {
|
|
10
|
+
const latest = loadRunManifestById(manifest.cwd, manifest.runId)?.tasks ?? fallbackTasks;
|
|
11
|
+
const merged = updateTask(latest, updated);
|
|
12
|
+
saveRunTasks(manifest, merged);
|
|
13
|
+
return merged;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function checkpointTask(manifest: TeamRunManifest, tasks: TeamTaskState[], task: TeamTaskState, phase: TaskCheckpointState["phase"], childPid?: number): { task: TeamTaskState; tasks: TeamTaskState[] } {
|
|
17
|
+
const checkpoint: TaskCheckpointState = { phase, updatedAt: new Date().toISOString(), ...(childPid ? { childPid } : task.checkpoint?.childPid ? { childPid: task.checkpoint.childPid } : {}) };
|
|
18
|
+
const nextTask = { ...task, checkpoint };
|
|
19
|
+
const nextTasks = persistSingleTaskUpdate(manifest, updateTask(tasks, nextTask), nextTask);
|
|
20
|
+
upsertCrewAgent(manifest, recordFromTask(manifest, nextTask, "child-process"));
|
|
21
|
+
return { task: nextTask, tasks: nextTasks };
|
|
22
|
+
}
|
|
@@ -13,11 +13,13 @@ import { buildConfiguredModelRouting, formatModelAttemptNote, isRetryableModelFa
|
|
|
13
13
|
import { parsePiJsonOutput, type ParsedPiJsonOutput } from "./pi-json-output.ts";
|
|
14
14
|
import { runChildPi } from "./child-pi.ts";
|
|
15
15
|
import { buildTaskPacket } from "./task-packet.ts";
|
|
16
|
+
import { executeHook, appendHookEvent } from "../hooks/registry.ts";
|
|
16
17
|
import { createVerificationEvidence } from "./green-contract.ts";
|
|
17
18
|
import { createStartupEvidence } from "./worker-startup.ts";
|
|
18
19
|
import { permissionForRole } from "./role-permission.ts";
|
|
19
20
|
import { collectDependencyOutputContext, renderDependencyOutputContext, writeTaskInputsArtifact, writeTaskSharedOutput } from "./task-output-context.ts";
|
|
20
21
|
import { appendCrewAgentEvent, appendCrewAgentOutput, emptyCrewAgentProgress, recordFromTask, upsertCrewAgent } from "./crew-agent-records.ts";
|
|
22
|
+
import { reserveControlChannel } from "./agent-control.ts";
|
|
21
23
|
import { parseSessionUsage } from "./session-usage.ts";
|
|
22
24
|
import type { CrewAgentProgress, CrewRuntimeKind } from "./crew-agent-runtime.ts";
|
|
23
25
|
import { shouldAppendProgressEventUpdate, type ProgressEventSummary } from "./progress-event-coalescer.ts";
|
|
@@ -28,10 +30,16 @@ import { applyAgentProgressEvent, applyUsageToProgress, progressEventSummary, sh
|
|
|
28
30
|
import { checkpointTask, persistSingleTaskUpdate, updateTask } from "./task-runner/state-helpers.ts";
|
|
29
31
|
import { cleanResultText, isFinalChildEvent } from "./task-runner/result-utils.ts";
|
|
30
32
|
import { evaluateCompletionMutationGuard } from "./completion-guard.ts";
|
|
31
|
-
import { cancellationReasonFromSignal } from "./cancellation.ts";
|
|
33
|
+
import { cancellationReasonFromSignal, buildSyntheticTerminalEvidence } from "./cancellation.ts";
|
|
32
34
|
import { appendTaskAttentionEvent } from "./attention-events.ts";
|
|
33
35
|
import { parseSupervisorContactFromLine, recordSupervisorContact } from "./supervisor-contact.ts";
|
|
36
|
+
import { registerStreamBridge, bridgeEventFromJsonEvent } from "./event-stream-bridge.ts";
|
|
34
37
|
import { renderSkillInstructions } from "./skill-instructions.ts";
|
|
38
|
+
import { DEFAULT_YIELD_CONFIG, extractYieldResult, hasYieldInOutput, isYieldEvent, registerYieldTool, type YieldResult } from "./yield-handler.ts";
|
|
39
|
+
import { validateWorkerOutput, type OutputValidationResult } from "./output-validator.ts";
|
|
40
|
+
|
|
41
|
+
// Register the submit_result tool handler so subprocess events can extract yield data.
|
|
42
|
+
registerYieldTool();
|
|
35
43
|
|
|
36
44
|
export interface TaskRunnerInput {
|
|
37
45
|
manifest: TeamRunManifest;
|
|
@@ -61,6 +69,10 @@ export interface TaskRunnerInput {
|
|
|
61
69
|
|
|
62
70
|
export async function runTeamTask(input: TaskRunnerInput): Promise<{ manifest: TeamRunManifest; tasks: TeamTaskState[] }> {
|
|
63
71
|
let manifest = input.manifest;
|
|
72
|
+
// H4: registerStreamBridge inside try so dispose() in finally is safe
|
|
73
|
+
let streamBridge: ReturnType<typeof registerStreamBridge> | undefined;
|
|
74
|
+
try {
|
|
75
|
+
streamBridge = registerStreamBridge(manifest.runId);
|
|
64
76
|
const workspace = prepareTaskWorkspace(manifest, input.task);
|
|
65
77
|
const worktree = workspace.worktreePath && workspace.branch ? { path: workspace.worktreePath, branch: workspace.branch, reused: workspace.reused ?? false } : input.task.worktree;
|
|
66
78
|
const taskPacket = buildTaskPacket({ manifest, step: input.step, taskId: input.task.id, cwd: workspace.cwd, worktreePath: worktree?.path });
|
|
@@ -77,6 +89,8 @@ export async function runTeamTask(input: TaskRunnerInput): Promise<{ manifest: T
|
|
|
77
89
|
heartbeat: createWorkerHeartbeat(input.task.id),
|
|
78
90
|
agentProgress: input.task.agentProgress ?? emptyCrewAgentProgress(),
|
|
79
91
|
...(dependencyContextText ? { dependencyContextText } : {}),
|
|
92
|
+
// Reserve control channel before spawn so cancel/steer can target this task immediately
|
|
93
|
+
controlReservation: reserveControlChannel(input.task.id, manifest.runId),
|
|
80
94
|
} as TeamTaskState;
|
|
81
95
|
let tasks = updateTask(input.tasks, task);
|
|
82
96
|
const runtimeKind = input.runtimeKind ?? (input.executeWorkers ? "child-process" : "scaffold");
|
|
@@ -84,13 +98,17 @@ export async function runTeamTask(input: TaskRunnerInput): Promise<{ manifest: T
|
|
|
84
98
|
if (runtimeKind === "child-process") ({ task, tasks } = checkpointTask(manifest, tasks, task, "started"));
|
|
85
99
|
upsertCrewAgent(manifest, recordFromTask(manifest, task, runtimeKind));
|
|
86
100
|
appendEvent(manifest.eventsPath, { type: "task.started", runId: manifest.runId, taskId: task.id, data: { role: task.role, agent: task.agent, runtime: runtimeKind, cwd: task.cwd, worktreePath: workspace.worktreePath, worktreeBranch: workspace.branch, worktreeReused: workspace.reused } });
|
|
101
|
+
// Emit immediate UI notification so widget shows agent as "running" within ~100ms
|
|
102
|
+
// instead of waiting for child process first JSON event (2-5s delay).
|
|
103
|
+
streamBridge?.handler({ runId: manifest.runId, taskId: task.id, eventType: "task.started", timestamp: Date.now() });
|
|
87
104
|
const permissionMode = permissionForRole(task.role);
|
|
88
105
|
const renderedSkills = input.skillBlock === undefined ? renderSkillInstructions({ cwd: task.cwd, role: task.role, agent: input.agent, teamRole: { skills: input.teamRoleSkills }, step: input.step, override: input.skillOverride }) : undefined;
|
|
89
106
|
const skillBlock = input.skillBlock ?? renderedSkills?.block;
|
|
90
107
|
const skillNames = input.skillNames ?? renderedSkills?.names;
|
|
91
108
|
const skillPaths = input.skillPaths ?? renderedSkills?.paths;
|
|
92
109
|
|
|
93
|
-
const
|
|
110
|
+
const promptResult = await renderTaskPrompt(manifest, input.step, task, input.agent, skillBlock);
|
|
111
|
+
const prompt = promptResult.full;
|
|
94
112
|
const promptArtifact = writeArtifact(manifest.artifactsRoot, {
|
|
95
113
|
kind: "prompt",
|
|
96
114
|
relativePath: `prompts/${task.id}.md`,
|
|
@@ -108,6 +126,7 @@ export async function runTeamTask(input: TaskRunnerInput): Promise<{ manifest: T
|
|
|
108
126
|
let finalStdout = "";
|
|
109
127
|
let transcriptPath: string | undefined;
|
|
110
128
|
let terminalEvidence: OperationTerminalEvidence[] = [];
|
|
129
|
+
const collectedJsonEvents: Record<string, unknown>[] = [];
|
|
111
130
|
|
|
112
131
|
let startupEvidence = createStartupEvidence({ command: runtimeKind === "child-process" ? "pi" : runtimeKind === "live-session" ? "live-session" : "safe-scaffold", startedAt: new Date(task.startedAt ?? new Date().toISOString()), finishedAt: new Date(), promptSentAt: new Date(task.startedAt ?? new Date().toISOString()), promptAccepted: true, exitCode: 0 });
|
|
113
132
|
const inputsArtifact = writeTaskInputsArtifact(manifest, task, dependencyContext);
|
|
@@ -187,9 +206,15 @@ export async function runTeamTask(input: TaskRunnerInput): Promise<{ manifest: T
|
|
|
187
206
|
},
|
|
188
207
|
onJsonEvent: (event) => {
|
|
189
208
|
appendCrewAgentEvent(manifest, task.id, event);
|
|
209
|
+
if (event && typeof event === "object" && !Array.isArray(event)) collectedJsonEvents.push(event as Record<string, unknown>);
|
|
190
210
|
persistHeartbeat();
|
|
191
211
|
task = { ...task, agentProgress: applyAgentProgressEvent(task.agentProgress ?? emptyCrewAgentProgress(), event, task.startedAt) };
|
|
192
212
|
tasks = updateTask(tasks, task);
|
|
213
|
+
// Bridge event to UI event bus for near-instant updates
|
|
214
|
+
try {
|
|
215
|
+
const bridgeEvent = bridgeEventFromJsonEvent(manifest.runId, task.id, event);
|
|
216
|
+
if (bridgeEvent) streamBridge?.handler(bridgeEvent);
|
|
217
|
+
} catch { /* bridge errors should not affect task */ }
|
|
193
218
|
// Feed overflow recovery tracker
|
|
194
219
|
if (input.onJsonEvent) {
|
|
195
220
|
try {
|
|
@@ -205,7 +230,11 @@ export async function runTeamTask(input: TaskRunnerInput): Promise<{ manifest: T
|
|
|
205
230
|
});
|
|
206
231
|
const evidenceStatus = childResult.exitStatus?.cancelled ? "cancelled" : childResult.error || (childResult.exitCode && childResult.exitCode !== 0) ? "failed" : "completed";
|
|
207
232
|
terminalEvidence = [...terminalEvidence, { operation: "worker", status: evidenceStatus, startedAt: attemptStartedAt.toISOString(), finishedAt: new Date().toISOString(), ...(input.signal?.aborted ? { reason: cancellationReasonFromSignal(input.signal) } : {}), ...(childResult.exitStatus ? { exitStatus: childResult.exitStatus } : {}) }];
|
|
208
|
-
if (evidenceStatus === "cancelled")
|
|
233
|
+
if (evidenceStatus === "cancelled") {
|
|
234
|
+
const cancelReason = input.signal?.aborted ? cancellationReasonFromSignal(input.signal) : { code: "caller_cancelled" as const, message: "Worker cancelled." };
|
|
235
|
+
terminalEvidence.push(buildSyntheticTerminalEvidence("tool", cancelReason, attemptStartedAt.toISOString()));
|
|
236
|
+
appendEvent(manifest.eventsPath, { type: "worker.cancelled", runId: manifest.runId, taskId: task.id, message: cancelReason.message, data: { terminalEvidence: terminalEvidence.at(-1) } });
|
|
237
|
+
}
|
|
209
238
|
startupEvidence = createStartupEvidence({ command: "pi", startedAt: attemptStartedAt, finishedAt: new Date(), promptSentAt: attemptStartedAt, promptAccepted: childResult.exitCode === 0 && !childResult.error, stderr: childResult.stderr, error: childResult.error, exitCode: childResult.exitCode });
|
|
210
239
|
exitCode = childResult.exitCode;
|
|
211
240
|
finalStdout = childResult.stdout;
|
|
@@ -287,6 +316,20 @@ export async function runTeamTask(input: TaskRunnerInput): Promise<{ manifest: T
|
|
|
287
316
|
});
|
|
288
317
|
}
|
|
289
318
|
|
|
319
|
+
// --- Yield-based completion contract ---
|
|
320
|
+
let yieldResult: YieldResult | undefined;
|
|
321
|
+
const yieldEnabled = input.runtimeConfig?.yield?.enabled ?? DEFAULT_YIELD_CONFIG.enabled;
|
|
322
|
+
if (yieldEnabled && collectedJsonEvents.length > 0) {
|
|
323
|
+
if (hasYieldInOutput(collectedJsonEvents)) {
|
|
324
|
+
const yieldEvent = collectedJsonEvents.find((e) => isYieldEvent(e));
|
|
325
|
+
if (yieldEvent) {
|
|
326
|
+
yieldResult = extractYieldResult(yieldEvent);
|
|
327
|
+
}
|
|
328
|
+
} else if (!error) {
|
|
329
|
+
appendEvent(manifest.eventsPath, { type: "task.attention", runId: manifest.runId, taskId: task.id, message: "Worker completed without calling submit_result tool.", data: { activityState: "needs_attention", reason: "no_yield" } });
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
290
333
|
const diffArtifact = workspace.worktreePath ? writeArtifact(manifest.artifactsRoot, {
|
|
291
334
|
kind: "diff",
|
|
292
335
|
relativePath: `diffs/${task.id}.diff`,
|
|
@@ -320,6 +363,22 @@ export async function runTeamTask(input: TaskRunnerInput): Promise<{ manifest: T
|
|
|
320
363
|
tasks = updateTask(tasks, task);
|
|
321
364
|
}
|
|
322
365
|
|
|
366
|
+
// --- Output format validation (caveman Phase 4) ---
|
|
367
|
+
// Validate worker output against the role's output contract.
|
|
368
|
+
// On failure: emit attention event but don't fail the task.
|
|
369
|
+
let outputValidation: OutputValidationResult | undefined;
|
|
370
|
+
if (!error) {
|
|
371
|
+
const outputText = parsedOutput?.finalText ?? finalStdout;
|
|
372
|
+
if (outputText) {
|
|
373
|
+
outputValidation = validateWorkerOutput(task.role, outputText);
|
|
374
|
+
if (!outputValidation.valid) {
|
|
375
|
+
appendEvent(manifest.eventsPath, { type: "task.output_validation", runId: manifest.runId, taskId: task.id, data: { valid: false, formatMatch: outputValidation.formatMatch, structurePreserved: outputValidation.structurePreserved, issues: outputValidation.issues } });
|
|
376
|
+
task = { ...task, agentProgress: { ...(task.agentProgress ?? emptyCrewAgentProgress()), activityState: "needs_attention" } };
|
|
377
|
+
tasks = updateTask(tasks, task);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
323
382
|
task = {
|
|
324
383
|
...task,
|
|
325
384
|
status: error ? "failed" : "completed",
|
|
@@ -378,10 +437,22 @@ export async function runTeamTask(input: TaskRunnerInput): Promise<{ manifest: T
|
|
|
378
437
|
content: `${JSON.stringify(buildWorkerPromptPipeline({ artifactsRoot: manifest.artifactsRoot, taskId: task.id, promptArtifact, inputsArtifact, skillArtifact, capabilityArtifact, coordinationArtifact, skillInstructionCount: skillNames?.length ?? 0, skillsDisabled: input.skillOverride === false || input.teamRoleSkills === false }), null, 2)}\n`,
|
|
379
438
|
producer: task.id,
|
|
380
439
|
});
|
|
381
|
-
|
|
440
|
+
const outputValidationArtifact = outputValidation ? writeArtifact(manifest.artifactsRoot, {
|
|
441
|
+
kind: "metadata",
|
|
442
|
+
relativePath: `metadata/${task.id}.output-validation.json`,
|
|
443
|
+
content: `${JSON.stringify(outputValidation, null, 2)}\n`,
|
|
444
|
+
producer: task.id,
|
|
445
|
+
}) : undefined;
|
|
446
|
+
manifest = { ...manifest, updatedAt: new Date().toISOString(), artifacts: [...manifest.artifacts, promptArtifact, resultArtifact, inputsArtifact, coordinationArtifact, ...(skillArtifact ? [skillArtifact] : []), packetArtifact, verificationArtifact, startupArtifact, permissionArtifact, capabilityArtifact, promptPipelineArtifact, ...(outputValidationArtifact ? [outputValidationArtifact] : []), ...(sharedOutputArtifact ? [sharedOutputArtifact] : []), ...(logArtifact ? [logArtifact] : []), ...(transcriptArtifact ? [transcriptArtifact] : []), ...(diffArtifact ? [diffArtifact] : []), ...(diffStatArtifact ? [diffStatArtifact] : [])] };
|
|
382
447
|
saveRunManifest(manifest);
|
|
383
448
|
tasks = persistSingleTaskUpdate(manifest, tasks, task);
|
|
384
449
|
upsertCrewAgent(manifest, recordFromTask(manifest, task, runtimeKind));
|
|
450
|
+
// Execute task_result hook before emitting terminal event
|
|
451
|
+
const hookReport = await executeHook("task_result", { runId: manifest.runId, taskId: task.id, cwd: manifest.cwd });
|
|
452
|
+
appendHookEvent(manifest, hookReport);
|
|
385
453
|
appendEvent(manifest.eventsPath, { type: error ? "task.failed" : "task.completed", runId: manifest.runId, taskId: task.id, message: error });
|
|
386
454
|
return { manifest, tasks };
|
|
455
|
+
} finally {
|
|
456
|
+
streamBridge?.dispose();
|
|
457
|
+
}
|
|
387
458
|
}
|
|
@@ -3,6 +3,7 @@ import type { AgentConfig } from "../agents/agent-config.ts";
|
|
|
3
3
|
import type { CrewLimitsConfig, CrewRuntimeConfig, CrewReliabilityConfig } from "../config/config.ts";
|
|
4
4
|
import type { CrewRuntimeCapabilities } from "./runtime-resolver.ts";
|
|
5
5
|
import { writeArtifact } from "../state/artifact-store.ts";
|
|
6
|
+
import { executeHook, appendHookEvent } from "../hooks/registry.ts";
|
|
6
7
|
import { appendEvent } from "../state/event-log.ts";
|
|
7
8
|
import type { TeamConfig } from "../teams/team-config.ts";
|
|
8
9
|
import type { ArtifactDescriptor, PolicyDecision, TeamRunManifest, TaskAttemptState, TeamTaskState } from "../state/types.ts";
|
|
@@ -25,7 +26,7 @@ import { childCorrelation, withCorrelation } from "../observability/correlation.
|
|
|
25
26
|
import { resolveBatchConcurrency } from "./concurrency.ts";
|
|
26
27
|
import { mapConcurrent } from "./parallel-utils.ts";
|
|
27
28
|
import { permissionForRole } from "./role-permission.ts";
|
|
28
|
-
import { CrewCancellationError, cancellationReasonFromSignal } from "./cancellation.ts";
|
|
29
|
+
import { CrewCancellationError, buildSyntheticTerminalEvidence, cancellationReasonFromSignal } from "./cancellation.ts";
|
|
29
30
|
import { effectivenessPolicyDecision, evaluateRunEffectiveness, formatRunEffectivenessLines } from "./effectiveness.ts";
|
|
30
31
|
|
|
31
32
|
export interface ExecuteTeamRunInput {
|
|
@@ -506,6 +507,39 @@ function hasPendingMutatingAdaptiveTask(tasks: TeamTaskState[]): boolean {
|
|
|
506
507
|
export async function executeTeamRun(input: ExecuteTeamRunInput): Promise<{ manifest: TeamRunManifest; tasks: TeamTaskState[] }> {
|
|
507
508
|
let workflow = input.workflow;
|
|
508
509
|
let manifest = updateRunStatus(input.manifest, "running", input.executeWorkers ? "Executing team workflow." : "Creating workflow prompts and placeholder results.");
|
|
510
|
+
|
|
511
|
+
try {
|
|
512
|
+
return await executeTeamRunCore(input, manifest, workflow);
|
|
513
|
+
} catch (error) {
|
|
514
|
+
// P1: Catch unhandled errors — ensure manifest is set to "failed" so it doesn't stay "running" forever.
|
|
515
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
516
|
+
try {
|
|
517
|
+
manifest = updateRunStatus(manifest, "failed", `Unhandled error in team runner: ${message}`);
|
|
518
|
+
await saveRunManifestAsync(manifest);
|
|
519
|
+
} catch {
|
|
520
|
+
// Best-effort — state write may also fail
|
|
521
|
+
}
|
|
522
|
+
const tasks = refreshTaskGraphQueues(input.tasks).map((task) =>
|
|
523
|
+
task.status === "running" || task.status === "queued" || task.status === "waiting"
|
|
524
|
+
? { ...task, status: "failed" as const, finishedAt: new Date().toISOString(), error: message }
|
|
525
|
+
: task,
|
|
526
|
+
);
|
|
527
|
+
return { manifest, tasks };
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
async function executeTeamRunCore(
|
|
532
|
+
input: ExecuteTeamRunInput,
|
|
533
|
+
manifest: TeamRunManifest,
|
|
534
|
+
workflow: WorkflowConfig,
|
|
535
|
+
): Promise<{ manifest: TeamRunManifest; tasks: TeamTaskState[] }> {
|
|
536
|
+
// Execute before_run_start hook (non-blocking by default)
|
|
537
|
+
const beforeRunReport = await executeHook("before_run_start", { runId: manifest.runId, cwd: manifest.cwd });
|
|
538
|
+
appendHookEvent(manifest, beforeRunReport);
|
|
539
|
+
if (beforeRunReport.outcome === "block") {
|
|
540
|
+
manifest = updateRunStatus(manifest, "blocked", beforeRunReport.reason ?? "before_run_start hook blocked the run.");
|
|
541
|
+
return { manifest, tasks: input.tasks };
|
|
542
|
+
}
|
|
509
543
|
let tasks = refreshTaskGraphQueues(input.tasks);
|
|
510
544
|
let queueIndex = buildTaskGraphIndex(tasks);
|
|
511
545
|
const canInjectAdaptivePlan = workflow.name === "implementation";
|
|
@@ -552,7 +586,11 @@ export async function executeTeamRun(input: ExecuteTeamRunInput): Promise<{ mani
|
|
|
552
586
|
tasks = tasks.map((task) => {
|
|
553
587
|
if (task.status !== "queued" && task.status !== "running" && task.status !== "waiting") return task;
|
|
554
588
|
cancelledTaskIds.push(task.id);
|
|
555
|
-
|
|
589
|
+
const base = { ...task, status: "cancelled" as const, finishedAt: new Date().toISOString(), error: message };
|
|
590
|
+
if (task.status === "running") {
|
|
591
|
+
return { ...base, terminalEvidence: [...(task.terminalEvidence ?? []), buildSyntheticTerminalEvidence("worker", cancelReason, task.startedAt)] };
|
|
592
|
+
}
|
|
593
|
+
return base;
|
|
556
594
|
});
|
|
557
595
|
await saveRunTasksAsync(manifest, tasks);
|
|
558
596
|
for (const taskId of cancelledTaskIds) appendEvent(manifest.eventsPath, { type: "task.cancelled", runId: manifest.runId, taskId, message, data: { reason: cancelReason.code } });
|
|
@@ -594,8 +632,18 @@ export async function executeTeamRun(input: ExecuteTeamRunInput): Promise<{ mani
|
|
|
594
632
|
}
|
|
595
633
|
|
|
596
634
|
appendEvent(manifest.eventsPath, { type: "task.progress", runId: manifest.runId, message: `Starting ready batch with ${readyBatch.length} task(s).`, data: { taskIds: readyBatch.map((task) => task.id), readyCount: snapshot.ready.length, blockedCount: snapshot.blocked.length, runningCount: snapshot.running.length, doneCount: snapshot.done.length, selectedCount: readyBatch.length, maxConcurrent: concurrency.maxConcurrent, defaultConcurrency: concurrency.defaultConcurrency, concurrencyReason: approvalPending ? `${concurrency.reason};plan-approval-read-only` : concurrency.reason } });
|
|
635
|
+
// Execute before_task_start hooks for the batch
|
|
636
|
+
for (const task of readyBatch) {
|
|
637
|
+
const taskReport = await executeHook("before_task_start", { runId: manifest.runId, taskId: task.id, cwd: manifest.cwd });
|
|
638
|
+
appendHookEvent(manifest, taskReport);
|
|
639
|
+
if (taskReport.outcome === "block") {
|
|
640
|
+
tasks = tasks.map((t) => t.id === task.id ? { ...t, status: "skipped" as const, error: taskReport.reason ?? "before_task_start hook blocked execution." } : t);
|
|
641
|
+
manifest = updateRunStatus(manifest, manifest.status, `Task '${task.id}' blocked by hook.`);
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
const batchTasks = readyBatch.filter((task) => tasks.find((t) => t.id === task.id && t.status !== "skipped"));
|
|
597
645
|
const results = await mapConcurrent(
|
|
598
|
-
|
|
646
|
+
batchTasks,
|
|
599
647
|
concurrency.selectedCount,
|
|
600
648
|
async (task) => {
|
|
601
649
|
const step = findStep(workflow, task);
|
|
@@ -604,6 +652,7 @@ export async function executeTeamRun(input: ExecuteTeamRunInput): Promise<{ mani
|
|
|
604
652
|
const baseInput = { manifest, tasks, task, step, agent, signal: input.signal, executeWorkers: input.executeWorkers, runtimeKind: input.runtime?.kind, runtimeConfig: input.runtimeConfig, parentContext: input.parentContext, parentModel: input.parentModel, modelRegistry: input.modelRegistry, modelOverride: input.modelOverride, teamRoleModel: teamRole?.model, teamRoleSkills: teamRole?.skills, skillOverride: input.skillOverride, limits: input.limits, onJsonEvent: input.onJsonEvent };
|
|
605
653
|
if (input.reliability?.autoRetry !== true) return withCorrelation(childCorrelation(manifest.runId, task.id), () => runTeamTask(baseInput));
|
|
606
654
|
let lastFailed: { manifest: TeamRunManifest; tasks: TeamTaskState[] } | undefined;
|
|
655
|
+
let lastAttemptId: string | undefined;
|
|
607
656
|
const attemptsSoFar: TaskAttemptState[] = [...(task.attempts ?? [])];
|
|
608
657
|
const policy = retryPolicyFromConfig(input.reliability);
|
|
609
658
|
try {
|
|
@@ -634,10 +683,12 @@ export async function executeTeamRun(input: ExecuteTeamRunInput): Promise<{ mani
|
|
|
634
683
|
signal: input.signal,
|
|
635
684
|
attemptId: (attempt) => `${manifest.runId}:${task.id}:attempt-${attempt}`,
|
|
636
685
|
onAttemptFailed: (attempt, error, delayMs, info) => {
|
|
637
|
-
|
|
686
|
+
lastAttemptId = info.attemptId;
|
|
687
|
+
appendEvent(manifest.eventsPath, { type: "crew.task.retry_attempt", runId: manifest.runId, taskId: task.id, message: error.message, data: { attempt, attemptId: info.attemptId, delayMs }, metadata: { attemptId: info.attemptId } });
|
|
638
688
|
input.metricRegistry?.histogram("crew.task.retry_delay_ms", "Retry backoff delay, milliseconds").observe({ runId: manifest.runId, taskId: task.id }, delayMs);
|
|
639
689
|
},
|
|
640
690
|
onRetryGivenUp: (attempts, error, info) => {
|
|
691
|
+
lastAttemptId = info.attemptId;
|
|
641
692
|
appendDeadletter(manifest, { runId: manifest.runId, taskId: task.id, reason: "max-retries", attempts, attemptId: info.attemptId, lastError: error.message, timestamp: new Date().toISOString() });
|
|
642
693
|
input.metricRegistry?.counter("crew.task.deadletter_total", "Deadletter triggers by reason").inc({ reason: "max-retries" });
|
|
643
694
|
input.metricRegistry?.histogram("crew.task.retry_count", "Retries per task", [0, 1, 2, 3, 5, 10]).observe({ runId: manifest.runId, team: input.team.name }, Math.max(0, attempts - 1));
|
|
@@ -650,7 +701,7 @@ export async function executeTeamRun(input: ExecuteTeamRunInput): Promise<{ mani
|
|
|
650
701
|
const freshManifest = fresh?.manifest ?? manifest;
|
|
651
702
|
const freshTasks = fresh?.tasks ?? tasks;
|
|
652
703
|
const cancelledTasks = freshTasks.map((item) => item.id === task.id && (item.status === "queued" || item.status === "running") ? { ...item, status: "cancelled" as const, finishedAt: new Date().toISOString(), error: `${reason.message} (${reason.code})` } : item);
|
|
653
|
-
appendEvent(freshManifest.eventsPath, { type: "task.cancelled", runId: freshManifest.runId, taskId: task.id, message: reason.message, data: { reason, phase: "retry" } });
|
|
704
|
+
appendEvent(freshManifest.eventsPath, { type: "task.cancelled", runId: freshManifest.runId, taskId: task.id, message: reason.message, data: { reason, phase: "retry" }, metadata: lastAttemptId ? { attemptId: lastAttemptId } : undefined });
|
|
654
705
|
return { manifest: updateRunStatus(freshManifest, "cancelled", reason.message), tasks: cancelledTasks };
|
|
655
706
|
}
|
|
656
707
|
if (lastFailed) return lastFailed;
|
|
@@ -663,6 +714,7 @@ export async function executeTeamRun(input: ExecuteTeamRunInput): Promise<{ mani
|
|
|
663
714
|
}
|
|
664
715
|
},
|
|
665
716
|
);
|
|
717
|
+
if (results.length === 0) break;
|
|
666
718
|
manifest = { ...results.at(-1)!.manifest, artifacts: mergeArtifacts([manifest.artifacts, ...results.map((item) => item.manifest.artifacts)].flat()) };
|
|
667
719
|
tasks = __test__mergeTaskUpdates(tasks, results);
|
|
668
720
|
const cancelledResult = results.find((item) => item.manifest.status === "cancelled");
|
|
@@ -701,14 +753,14 @@ export async function executeTeamRun(input: ExecuteTeamRunInput): Promise<{ mani
|
|
|
701
753
|
}
|
|
702
754
|
await saveRunTasksAsync(manifest, tasks);
|
|
703
755
|
saveCrewAgents(manifest, recordsForMaterializedTasks(manifest, tasks, runtimeKind));
|
|
704
|
-
const completedBatch =
|
|
756
|
+
const completedBatch = batchTasks.map((task) => tasks.find((item) => item.id === task.id) ?? task);
|
|
705
757
|
const batchArtifact = writeArtifact(manifest.artifactsRoot, {
|
|
706
758
|
kind: "summary",
|
|
707
|
-
relativePath: `batches/${
|
|
759
|
+
relativePath: `batches/${batchTasks.map((task) => task.id).join("+")}.md`,
|
|
708
760
|
producer: "team-runner",
|
|
709
761
|
content: aggregateTaskOutputs(completedBatch, manifest),
|
|
710
762
|
});
|
|
711
|
-
const groupDelivery = deliverGroupJoin({ manifest, mode: resolveGroupJoinMode(input.runtimeConfig), batch:
|
|
763
|
+
const groupDelivery = deliverGroupJoin({ manifest, mode: resolveGroupJoinMode(input.runtimeConfig), batch: batchTasks, allTasks: tasks });
|
|
712
764
|
manifest = { ...manifest, artifacts: mergeArtifacts([...manifest.artifacts, batchArtifact, ...(groupDelivery?.artifact ? [groupDelivery.artifact] : [])]) };
|
|
713
765
|
manifest = writeProgress(manifest, tasks, "team-runner", input.executeWorkers, input.runtimeConfig);
|
|
714
766
|
await saveRunManifestAsync(manifest);
|
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
export interface WorkerHeartbeatState {
|
|
2
|
-
workerId: string;
|
|
3
|
-
pid?: number;
|
|
4
|
-
lastSeenAt: string;
|
|
5
|
-
lastStdoutAt?: string;
|
|
6
|
-
lastEventAt?: string;
|
|
7
|
-
turnCount?: number;
|
|
8
|
-
alive?: boolean;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export function createWorkerHeartbeat(workerId: string, pid?: number, now = new Date()): WorkerHeartbeatState {
|
|
12
|
-
return { workerId, pid, lastSeenAt: now.toISOString(), alive: true };
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export function touchWorkerHeartbeat(heartbeat: WorkerHeartbeatState, updates: Partial<Omit<WorkerHeartbeatState, "workerId">> = {}, now = new Date()): WorkerHeartbeatState {
|
|
16
|
-
return { ...heartbeat, ...updates, lastSeenAt: now.toISOString() };
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export function isWorkerHeartbeatStale(heartbeat: WorkerHeartbeatState, staleMs: number, now = new Date()): boolean {
|
|
20
|
-
return now.getTime() - Date.parse(heartbeat.lastSeenAt) > staleMs;
|
|
21
|
-
}
|
|
1
|
+
export interface WorkerHeartbeatState {
|
|
2
|
+
workerId: string;
|
|
3
|
+
pid?: number;
|
|
4
|
+
lastSeenAt: string;
|
|
5
|
+
lastStdoutAt?: string;
|
|
6
|
+
lastEventAt?: string;
|
|
7
|
+
turnCount?: number;
|
|
8
|
+
alive?: boolean;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function createWorkerHeartbeat(workerId: string, pid?: number, now = new Date()): WorkerHeartbeatState {
|
|
12
|
+
return { workerId, pid, lastSeenAt: now.toISOString(), alive: true };
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function touchWorkerHeartbeat(heartbeat: WorkerHeartbeatState, updates: Partial<Omit<WorkerHeartbeatState, "workerId">> = {}, now = new Date()): WorkerHeartbeatState {
|
|
16
|
+
return { ...heartbeat, ...updates, lastSeenAt: now.toISOString() };
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function isWorkerHeartbeatStale(heartbeat: WorkerHeartbeatState, staleMs: number, now = new Date()): boolean {
|
|
20
|
+
return now.getTime() - Date.parse(heartbeat.lastSeenAt) > staleMs;
|
|
21
|
+
}
|
|
@@ -1,57 +1,57 @@
|
|
|
1
|
-
export type WorkerLifecycleState = "spawning" | "trust_required" | "ready_for_prompt" | "running" | "finished" | "failed";
|
|
2
|
-
export type StartupFailureClassification = "trust_required" | "prompt_misdelivery" | "prompt_acceptance_timeout" | "transport_dead" | "worker_crashed" | "unknown";
|
|
3
|
-
|
|
4
|
-
export interface WorkerStartupEvidence {
|
|
5
|
-
lastLifecycleState: WorkerLifecycleState;
|
|
6
|
-
command: string;
|
|
7
|
-
promptSentAt?: string;
|
|
8
|
-
promptAccepted: boolean;
|
|
9
|
-
trustPromptDetected: boolean;
|
|
10
|
-
transportHealthy: boolean;
|
|
11
|
-
childProcessAlive: boolean;
|
|
12
|
-
elapsedMs: number;
|
|
13
|
-
classification: StartupFailureClassification;
|
|
14
|
-
stderrPreview?: string;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export function detectTrustPrompt(text: string): boolean {
|
|
18
|
-
const lowered = text.toLowerCase();
|
|
19
|
-
return lowered.includes("do you trust") || lowered.includes("trust this") || lowered.includes("untrusted") || lowered.includes("workspace trust") || lowered.includes("allow this folder");
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export function classifyStartupFailure(evidence: Omit<WorkerStartupEvidence, "classification">): StartupFailureClassification {
|
|
23
|
-
if (!evidence.transportHealthy) return "transport_dead";
|
|
24
|
-
if (evidence.trustPromptDetected || evidence.lastLifecycleState === "trust_required") return "trust_required";
|
|
25
|
-
if (evidence.promptSentAt && !evidence.promptAccepted && evidence.childProcessAlive) return "prompt_acceptance_timeout";
|
|
26
|
-
if (evidence.promptSentAt && !evidence.promptAccepted && !evidence.childProcessAlive) return "worker_crashed";
|
|
27
|
-
if (evidence.stderrPreview?.toLowerCase().includes("command not found") || evidence.stderrPreview?.toLowerCase().includes("not recognized")) return "prompt_misdelivery";
|
|
28
|
-
if (!evidence.childProcessAlive && evidence.lastLifecycleState !== "finished") return "worker_crashed";
|
|
29
|
-
return "unknown";
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export function createStartupEvidence(input: {
|
|
33
|
-
command: string;
|
|
34
|
-
startedAt: Date;
|
|
35
|
-
finishedAt?: Date;
|
|
36
|
-
promptSentAt?: Date;
|
|
37
|
-
promptAccepted?: boolean;
|
|
38
|
-
stderr?: string;
|
|
39
|
-
error?: string;
|
|
40
|
-
exitCode?: number | null;
|
|
41
|
-
}): WorkerStartupEvidence {
|
|
42
|
-
const stderrPreview = (input.error || input.stderr || "").slice(0, 500) || undefined;
|
|
43
|
-
const trustPromptDetected = detectTrustPrompt(stderrPreview ?? "");
|
|
44
|
-
const childProcessAlive = input.exitCode === undefined || input.exitCode === null ? !input.finishedAt : false;
|
|
45
|
-
const base: Omit<WorkerStartupEvidence, "classification"> = {
|
|
46
|
-
lastLifecycleState: input.error || (input.exitCode !== undefined && input.exitCode !== null && input.exitCode !== 0) ? "failed" : input.finishedAt ? "finished" : "running",
|
|
47
|
-
command: input.command,
|
|
48
|
-
promptSentAt: input.promptSentAt?.toISOString(),
|
|
49
|
-
promptAccepted: input.promptAccepted ?? !input.error,
|
|
50
|
-
trustPromptDetected,
|
|
51
|
-
transportHealthy: !input.error || !/enoent|spawn|transport/i.test(input.error),
|
|
52
|
-
childProcessAlive,
|
|
53
|
-
elapsedMs: Math.max(0, (input.finishedAt ?? new Date()).getTime() - input.startedAt.getTime()),
|
|
54
|
-
stderrPreview,
|
|
55
|
-
};
|
|
56
|
-
return { ...base, classification: classifyStartupFailure(base) };
|
|
57
|
-
}
|
|
1
|
+
export type WorkerLifecycleState = "spawning" | "trust_required" | "ready_for_prompt" | "running" | "finished" | "failed";
|
|
2
|
+
export type StartupFailureClassification = "trust_required" | "prompt_misdelivery" | "prompt_acceptance_timeout" | "transport_dead" | "worker_crashed" | "unknown";
|
|
3
|
+
|
|
4
|
+
export interface WorkerStartupEvidence {
|
|
5
|
+
lastLifecycleState: WorkerLifecycleState;
|
|
6
|
+
command: string;
|
|
7
|
+
promptSentAt?: string;
|
|
8
|
+
promptAccepted: boolean;
|
|
9
|
+
trustPromptDetected: boolean;
|
|
10
|
+
transportHealthy: boolean;
|
|
11
|
+
childProcessAlive: boolean;
|
|
12
|
+
elapsedMs: number;
|
|
13
|
+
classification: StartupFailureClassification;
|
|
14
|
+
stderrPreview?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function detectTrustPrompt(text: string): boolean {
|
|
18
|
+
const lowered = text.toLowerCase();
|
|
19
|
+
return lowered.includes("do you trust") || lowered.includes("trust this") || lowered.includes("untrusted") || lowered.includes("workspace trust") || lowered.includes("allow this folder");
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function classifyStartupFailure(evidence: Omit<WorkerStartupEvidence, "classification">): StartupFailureClassification {
|
|
23
|
+
if (!evidence.transportHealthy) return "transport_dead";
|
|
24
|
+
if (evidence.trustPromptDetected || evidence.lastLifecycleState === "trust_required") return "trust_required";
|
|
25
|
+
if (evidence.promptSentAt && !evidence.promptAccepted && evidence.childProcessAlive) return "prompt_acceptance_timeout";
|
|
26
|
+
if (evidence.promptSentAt && !evidence.promptAccepted && !evidence.childProcessAlive) return "worker_crashed";
|
|
27
|
+
if (evidence.stderrPreview?.toLowerCase().includes("command not found") || evidence.stderrPreview?.toLowerCase().includes("not recognized")) return "prompt_misdelivery";
|
|
28
|
+
if (!evidence.childProcessAlive && evidence.lastLifecycleState !== "finished") return "worker_crashed";
|
|
29
|
+
return "unknown";
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function createStartupEvidence(input: {
|
|
33
|
+
command: string;
|
|
34
|
+
startedAt: Date;
|
|
35
|
+
finishedAt?: Date;
|
|
36
|
+
promptSentAt?: Date;
|
|
37
|
+
promptAccepted?: boolean;
|
|
38
|
+
stderr?: string;
|
|
39
|
+
error?: string;
|
|
40
|
+
exitCode?: number | null;
|
|
41
|
+
}): WorkerStartupEvidence {
|
|
42
|
+
const stderrPreview = (input.error || input.stderr || "").slice(0, 500) || undefined;
|
|
43
|
+
const trustPromptDetected = detectTrustPrompt(stderrPreview ?? "");
|
|
44
|
+
const childProcessAlive = input.exitCode === undefined || input.exitCode === null ? !input.finishedAt : false;
|
|
45
|
+
const base: Omit<WorkerStartupEvidence, "classification"> = {
|
|
46
|
+
lastLifecycleState: input.error || (input.exitCode !== undefined && input.exitCode !== null && input.exitCode !== 0) ? "failed" : input.finishedAt ? "finished" : "running",
|
|
47
|
+
command: input.command,
|
|
48
|
+
promptSentAt: input.promptSentAt?.toISOString(),
|
|
49
|
+
promptAccepted: input.promptAccepted ?? !input.error,
|
|
50
|
+
trustPromptDetected,
|
|
51
|
+
transportHealthy: !input.error || !/enoent|spawn|transport/i.test(input.error),
|
|
52
|
+
childProcessAlive,
|
|
53
|
+
elapsedMs: Math.max(0, (input.finishedAt ?? new Date()).getTime() - input.startedAt.getTime()),
|
|
54
|
+
stderrPreview,
|
|
55
|
+
};
|
|
56
|
+
return { ...base, classification: classifyStartupFailure(base) };
|
|
57
|
+
}
|