pi-crew 0.1.45 → 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/README.md +5 -5
- 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 +808 -0
- 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 -0
- package/docs/research-oh-my-pi-distillation.md +369 -0
- package/docs/source-runtime-refactor-map.md +24 -0
- package/docs/usage.md +3 -3
- package/install.mjs +52 -8
- package/package.json +99 -98
- package/schema.json +10 -1
- package/skills/async-worker-recovery/SKILL.md +42 -0
- package/skills/context-artifact-hygiene/SKILL.md +52 -0
- package/skills/delegation-patterns/SKILL.md +54 -0
- package/skills/mailbox-interactive/SKILL.md +40 -0
- package/skills/model-routing-context/SKILL.md +39 -0
- package/skills/multi-perspective-review/SKILL.md +58 -0
- package/skills/observability-reliability/SKILL.md +41 -0
- package/skills/orchestration/SKILL.md +157 -0
- package/skills/ownership-session-security/SKILL.md +41 -0
- package/skills/pi-extension-lifecycle/SKILL.md +39 -0
- package/skills/requirements-to-task-packet/SKILL.md +63 -0
- package/skills/resource-discovery-config/SKILL.md +41 -0
- package/skills/runtime-state-reader/SKILL.md +44 -0
- package/skills/secure-agent-orchestration-review/SKILL.md +45 -0
- package/skills/state-mutation-locking/SKILL.md +42 -0
- package/skills/systematic-debugging/SKILL.md +67 -0
- package/skills/ui-render-performance/SKILL.md +39 -0
- package/skills/verification-before-done/SKILL.md +57 -0
- package/skills/worktree-isolation/SKILL.md +39 -0
- package/src/agents/agent-config.ts +6 -0
- package/src/agents/agent-search.ts +98 -0
- package/src/agents/agent-serializer.ts +38 -34
- package/src/agents/discover-agents.ts +29 -15
- package/src/config/config.ts +72 -24
- package/src/config/defaults.ts +25 -0
- package/src/extension/autonomous-policy.ts +26 -33
- package/src/extension/help.ts +1 -0
- package/src/extension/management.ts +5 -0
- package/src/extension/project-init.ts +62 -2
- package/src/extension/register.ts +69 -22
- package/src/extension/registration/commands.ts +64 -25
- package/src/extension/registration/compaction-guard.ts +1 -1
- package/src/extension/registration/subagent-helpers.ts +8 -0
- package/src/extension/registration/subagent-tools.ts +149 -148
- package/src/extension/registration/team-tool.ts +14 -10
- package/src/extension/run-index.ts +35 -21
- package/src/extension/run-maintenance.ts +30 -5
- package/src/extension/team-tool/api.ts +47 -9
- package/src/extension/team-tool/cancel.ts +109 -5
- package/src/extension/team-tool/context.ts +8 -0
- package/src/extension/team-tool/intent-policy.ts +42 -0
- package/src/extension/team-tool/lifecycle-actions.ts +120 -79
- package/src/extension/team-tool/parallel-dispatch.ts +156 -0
- package/src/extension/team-tool/respond.ts +46 -18
- package/src/extension/team-tool/run.ts +55 -12
- package/src/extension/team-tool/status.ts +13 -2
- package/src/extension/team-tool-types.ts +3 -0
- package/src/extension/team-tool.ts +45 -14
- package/src/hooks/registry.ts +61 -0
- package/src/hooks/types.ts +41 -0
- package/src/observability/event-to-metric.ts +8 -1
- package/src/runtime/agent-control.ts +169 -63
- package/src/runtime/async-runner.ts +3 -1
- package/src/runtime/background-runner.ts +78 -53
- package/src/runtime/cancellation-token.ts +89 -0
- package/src/runtime/cancellation.ts +61 -0
- package/src/runtime/capability-inventory.ts +116 -0
- package/src/runtime/child-pi.ts +458 -444
- package/src/runtime/code-summary.ts +247 -0
- package/src/runtime/crash-recovery.ts +182 -0
- package/src/runtime/crew-agent-records.ts +70 -10
- 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/deadletter.ts +1 -0
- package/src/runtime/delivery-coordinator.ts +48 -25
- package/src/runtime/effectiveness.ts +81 -0
- package/src/runtime/event-stream-bridge.ts +90 -0
- package/src/runtime/live-agent-control.ts +2 -1
- package/src/runtime/live-agent-manager.ts +179 -85
- package/src/runtime/live-control-realtime.ts +1 -1
- 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 +599 -305
- package/src/runtime/manifest-cache.ts +17 -2
- package/src/runtime/mcp-proxy.ts +113 -0
- package/src/runtime/model-fallback.ts +6 -4
- 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-utils.ts +57 -0
- package/src/runtime/parent-guard.ts +80 -0
- package/src/runtime/pi-args.ts +18 -3
- package/src/runtime/process-status.ts +5 -1
- package/src/runtime/prose-compressor.ts +164 -0
- package/src/runtime/result-extractor.ts +121 -0
- package/src/runtime/retry-executor.ts +81 -64
- package/src/runtime/runtime-resolver.ts +23 -10
- package/src/runtime/semaphore.ts +131 -0
- package/src/runtime/sensitive-paths.ts +92 -0
- package/src/runtime/skill-instructions.ts +222 -0
- package/src/runtime/stale-reconciler.ts +4 -14
- package/src/runtime/stream-preview.ts +177 -0
- package/src/runtime/subagent-manager.ts +6 -2
- package/src/runtime/subprocess-tool-registry.ts +67 -0
- package/src/runtime/task-output-context.ts +177 -127
- package/src/runtime/task-runner/capabilities.ts +78 -0
- package/src/runtime/task-runner/live-executor.ts +107 -101
- package/src/runtime/task-runner/prompt-builder.ts +72 -8
- package/src/runtime/task-runner/prompt-pipeline.ts +64 -0
- package/src/runtime/task-runner/run-projection.ts +104 -0
- package/src/runtime/task-runner.ts +115 -5
- package/src/runtime/team-runner.ts +134 -19
- package/src/runtime/workspace-tree.ts +298 -0
- package/src/runtime/yield-handler.ts +189 -0
- package/src/schema/config-schema.ts +7 -0
- package/src/schema/team-tool-schema.ts +14 -4
- package/src/skills/discover-skills.ts +67 -0
- package/src/state/active-run-registry.ts +167 -0
- 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 +2 -1
- package/src/state/event-log-rotation.ts +158 -0
- package/src/state/event-log.ts +52 -2
- package/src/state/mailbox.ts +129 -9
- package/src/state/state-store.ts +32 -5
- package/src/state/types.ts +64 -2
- package/src/teams/team-config.ts +1 -0
- package/src/ui/agent-management-overlay.ts +144 -0
- package/src/ui/crew-widget.ts +15 -5
- 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/progress-pane.ts +2 -0
- package/src/ui/live-run-sidebar.ts +4 -0
- package/src/ui/powerbar-publisher.ts +77 -15
- package/src/ui/render-coalescer.ts +51 -0
- package/src/ui/run-dashboard.ts +4 -0
- package/src/ui/run-event-bus.ts +209 -0
- package/src/ui/run-snapshot-cache.ts +78 -18
- package/src/ui/snapshot-types.ts +10 -0
- package/src/ui/transcript-entries.ts +258 -0
- package/src/utils/ids.ts +5 -0
- package/src/utils/incremental-reader.ts +104 -0
- package/src/utils/paths.ts +4 -2
- package/src/utils/scan-cache.ts +137 -0
- 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/workflow-config.ts +1 -0
- package/src/worktree/cleanup.ts +2 -1
|
@@ -1,9 +1,24 @@
|
|
|
1
1
|
import type { AgentConfig } from "../../agents/agent-config.ts";
|
|
2
|
-
import type { TeamRunManifest, TeamTaskState } from "../../state/types.ts";
|
|
2
|
+
import type { TeamRunManifest, TeamTaskState, TaskOutputSchema } from "../../state/types.ts";
|
|
3
3
|
import type { WorkflowStep } from "../../workflows/workflow-config.ts";
|
|
4
4
|
import { buildMemoryBlock } from "../agent-memory.ts";
|
|
5
5
|
import { permissionForRole } from "../role-permission.ts";
|
|
6
6
|
import { renderTaskPacket } from "../task-packet.ts";
|
|
7
|
+
import { buildWorkspaceTree } from "../workspace-tree.ts";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* When loadMode is "lean", emit a tool guidance block that tells the worker
|
|
11
|
+
* which tools to prefer. This is a prompt-level hint only — actual tool
|
|
12
|
+
* filtering at the Pi level is a future optimisation (Phase 3.2+).
|
|
13
|
+
*/
|
|
14
|
+
export function toolGuidanceBlock(agent?: AgentConfig): string {
|
|
15
|
+
if (!agent || agent.loadMode !== "lean" || !agent.defaultTools?.length) return "";
|
|
16
|
+
return [
|
|
17
|
+
"# Tool Guidance",
|
|
18
|
+
`This role uses a focused tool set. Preferred tools: ${agent.defaultTools.join(", ")}.`,
|
|
19
|
+
"Other tools are available but should only be used when explicitly needed for the task.",
|
|
20
|
+
].join("\n");
|
|
21
|
+
}
|
|
7
22
|
|
|
8
23
|
function readOnlyRoleInstructions(role: string): string {
|
|
9
24
|
if (permissionForRole(role) !== "read_only") return "";
|
|
@@ -36,9 +51,45 @@ function inputDependencyContext(task: TeamTaskState): string {
|
|
|
36
51
|
return (task as TeamTaskState & { dependencyContextText?: string }).dependencyContextText ?? "";
|
|
37
52
|
}
|
|
38
53
|
|
|
39
|
-
export function
|
|
54
|
+
export function renderOutputSchemaBlock(outputSchema: TaskOutputSchema): string {
|
|
55
|
+
const lines: string[] = ["## Expected Output Format"];
|
|
56
|
+
lines.push(`Your final output must be ${outputSchema.format}.`);
|
|
57
|
+
if (outputSchema.description) {
|
|
58
|
+
lines.push(outputSchema.description);
|
|
59
|
+
}
|
|
60
|
+
if (outputSchema.format === "json" && outputSchema.schema) {
|
|
61
|
+
lines.push("The output must match this schema:");
|
|
62
|
+
lines.push("```json");
|
|
63
|
+
lines.push(JSON.stringify(outputSchema.schema, null, 2));
|
|
64
|
+
lines.push("```");
|
|
65
|
+
}
|
|
66
|
+
if (outputSchema.example) {
|
|
67
|
+
lines.push("Example output:");
|
|
68
|
+
lines.push("```");
|
|
69
|
+
lines.push(outputSchema.example);
|
|
70
|
+
lines.push("```");
|
|
71
|
+
}
|
|
72
|
+
return lines.join("\n");
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export interface RenderedTaskPrompt {
|
|
76
|
+
/** Stable sections that rarely change between tasks of the same role/cwd. */
|
|
77
|
+
stablePrefix: string;
|
|
78
|
+
/** Dynamic sections that change per-task (goal, task packet, skills, dependency context). */
|
|
79
|
+
dynamicSuffix: string;
|
|
80
|
+
/** Full rendered prompt (stablePrefix + dynamicSuffix). */
|
|
81
|
+
full: string;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export async function renderTaskPrompt(manifest: TeamRunManifest, step: WorkflowStep, task: TeamTaskState, agent?: AgentConfig, skillBlock = ""): Promise<RenderedTaskPrompt> {
|
|
40
85
|
const memoryBlock = agent?.memory ? buildMemoryBlock(agent.name, agent.memory, task.cwd, Boolean(agent.tools?.some((tool) => tool === "write" || tool === "edit"))) : "";
|
|
41
|
-
|
|
86
|
+
|
|
87
|
+
// Build workspace tree for stable context
|
|
88
|
+
const tree = await buildWorkspaceTree(task.cwd);
|
|
89
|
+
const treeBlock = tree.rendered ? `# Workspace Structure\n${tree.rendered}` : "";
|
|
90
|
+
|
|
91
|
+
// Stable prefix: role instructions, coordination, workspace tree — rarely changes
|
|
92
|
+
const stablePrefix = [
|
|
42
93
|
"# pi-crew Worker Runtime Context",
|
|
43
94
|
`Run ID: ${manifest.runId}`,
|
|
44
95
|
`Team: ${manifest.team}`,
|
|
@@ -50,11 +101,6 @@ export function renderTaskPrompt(manifest: TeamRunManifest, step: WorkflowStep,
|
|
|
50
101
|
`Task cwd: ${task.cwd}`,
|
|
51
102
|
`Workspace mode: ${manifest.workspaceMode}`,
|
|
52
103
|
"",
|
|
53
|
-
`Goal:\n${manifest.goal}`,
|
|
54
|
-
"",
|
|
55
|
-
`Step: ${step.id}`,
|
|
56
|
-
`Role: ${step.role}`,
|
|
57
|
-
"",
|
|
58
104
|
"Protocol:",
|
|
59
105
|
"- Stay within the task scope unless the prompt explicitly says otherwise.",
|
|
60
106
|
"- Report blockers and verification evidence in the final result.",
|
|
@@ -65,11 +111,29 @@ export function renderTaskPrompt(manifest: TeamRunManifest, step: WorkflowStep,
|
|
|
65
111
|
"",
|
|
66
112
|
coordinationBridgeInstructions(task),
|
|
67
113
|
"",
|
|
114
|
+
treeBlock,
|
|
115
|
+
"",
|
|
116
|
+
toolGuidanceBlock(agent),
|
|
117
|
+
].filter(Boolean).join("\n");
|
|
118
|
+
|
|
119
|
+
// Dynamic suffix: goal, step, skills, task packet, dependency context, memory — changes per task
|
|
120
|
+
const dynamicSuffix = [
|
|
121
|
+
`Goal:\n${manifest.goal}`,
|
|
122
|
+
"",
|
|
123
|
+
`Step: ${step.id}`,
|
|
124
|
+
`Role: ${step.role}`,
|
|
125
|
+
"",
|
|
126
|
+
skillBlock,
|
|
127
|
+
"",
|
|
68
128
|
task.taskPacket ? renderTaskPacket(task.taskPacket) : "",
|
|
69
129
|
"",
|
|
70
130
|
(inputDependencyContext(task) || ""),
|
|
71
131
|
memoryBlock,
|
|
132
|
+
task.taskPacket?.outputSchema ? renderOutputSchemaBlock(task.taskPacket.outputSchema) : "",
|
|
72
133
|
"Task:",
|
|
73
134
|
step.task.replaceAll("{goal}", manifest.goal),
|
|
74
135
|
].join("\n");
|
|
136
|
+
|
|
137
|
+
const full = [stablePrefix, "", dynamicSuffix].join("\n");
|
|
138
|
+
return { stablePrefix, dynamicSuffix, full };
|
|
75
139
|
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import * as path from "node:path";
|
|
2
|
+
import type { ArtifactDescriptor } from "../../state/types.ts";
|
|
3
|
+
|
|
4
|
+
export type WorkerPromptPipelineStageName =
|
|
5
|
+
| "task-packet-built"
|
|
6
|
+
| "dependency-context-collected"
|
|
7
|
+
| "skills-rendered-or-disabled"
|
|
8
|
+
| "capability-inventory-recorded"
|
|
9
|
+
| "coordination-bridge-attached"
|
|
10
|
+
| "prompt-rendered"
|
|
11
|
+
| "prompt-artifact-written";
|
|
12
|
+
|
|
13
|
+
export interface WorkerPromptPipelineStage {
|
|
14
|
+
name: WorkerPromptPipelineStageName;
|
|
15
|
+
references: string[];
|
|
16
|
+
details?: Record<string, string | number | boolean>;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface WorkerPromptPipelineArtifact {
|
|
20
|
+
schemaVersion: 1;
|
|
21
|
+
taskId: string;
|
|
22
|
+
stages: WorkerPromptPipelineStage[];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function artifactReference(artifactsRoot: string, artifact?: ArtifactDescriptor): string | undefined {
|
|
26
|
+
if (!artifact) return undefined;
|
|
27
|
+
const root = path.resolve(artifactsRoot);
|
|
28
|
+
const target = path.resolve(artifact.path);
|
|
29
|
+
const relative = path.relative(root, target);
|
|
30
|
+
if (!relative || relative.startsWith("..") || path.isAbsolute(relative)) return undefined;
|
|
31
|
+
return relative.replaceAll("\\", "/");
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface BuildWorkerPromptPipelineInput {
|
|
35
|
+
artifactsRoot: string;
|
|
36
|
+
taskId: string;
|
|
37
|
+
promptArtifact: ArtifactDescriptor;
|
|
38
|
+
inputsArtifact: ArtifactDescriptor;
|
|
39
|
+
skillArtifact?: ArtifactDescriptor;
|
|
40
|
+
capabilityArtifact: ArtifactDescriptor;
|
|
41
|
+
coordinationArtifact: ArtifactDescriptor;
|
|
42
|
+
skillInstructionCount: number;
|
|
43
|
+
skillsDisabled: boolean;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function buildWorkerPromptPipeline(input: BuildWorkerPromptPipelineInput): WorkerPromptPipelineArtifact {
|
|
47
|
+
return {
|
|
48
|
+
schemaVersion: 1,
|
|
49
|
+
taskId: input.taskId,
|
|
50
|
+
stages: [
|
|
51
|
+
{ name: "task-packet-built", references: [`metadata/${input.taskId}.task-packet.json`] },
|
|
52
|
+
{ name: "dependency-context-collected", references: [artifactReference(input.artifactsRoot, input.inputsArtifact) ?? `metadata/${input.taskId}.inputs.json`] },
|
|
53
|
+
{
|
|
54
|
+
name: "skills-rendered-or-disabled",
|
|
55
|
+
references: input.skillArtifact ? [artifactReference(input.artifactsRoot, input.skillArtifact) ?? `metadata/${input.taskId}.skills.md`] : [],
|
|
56
|
+
details: { disabled: input.skillsDisabled, skillInstructionCount: input.skillInstructionCount },
|
|
57
|
+
},
|
|
58
|
+
{ name: "capability-inventory-recorded", references: [artifactReference(input.artifactsRoot, input.capabilityArtifact) ?? `metadata/${input.taskId}.capabilities.json`] },
|
|
59
|
+
{ name: "coordination-bridge-attached", references: [artifactReference(input.artifactsRoot, input.coordinationArtifact) ?? `metadata/${input.taskId}.coordination-bridge.md`] },
|
|
60
|
+
{ name: "prompt-rendered", references: [] },
|
|
61
|
+
{ name: "prompt-artifact-written", references: [artifactReference(input.artifactsRoot, input.promptArtifact) ?? `prompts/${input.taskId}.md`] },
|
|
62
|
+
],
|
|
63
|
+
};
|
|
64
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import type { TeamRunManifest, TeamTaskState } from "../../state/types.ts";
|
|
2
|
+
import type { MailboxMessage } from "../../state/mailbox.ts";
|
|
3
|
+
import type { ArtifactDescriptor } from "../../state/types.ts";
|
|
4
|
+
|
|
5
|
+
export interface RunProjectionSource {
|
|
6
|
+
kind: "events" | "mailbox" | "artifacts" | "ui_metadata" | "runtime_metadata";
|
|
7
|
+
bounded: boolean;
|
|
8
|
+
reference?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface RunProjectionResult {
|
|
12
|
+
sources: RunProjectionSource[];
|
|
13
|
+
summary: string;
|
|
14
|
+
injectedAsContext: boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Transform run context before a worker starts.
|
|
19
|
+
* Builds a bounded projection of durable history that will be available
|
|
20
|
+
* to the worker as reference context, not as instructions.
|
|
21
|
+
*
|
|
22
|
+
* Rules:
|
|
23
|
+
* - Durable history retains events, mailbox, artifacts, UI/runtime metadata.
|
|
24
|
+
* - Worker prompt gets a bounded projection (truncated/summarized).
|
|
25
|
+
* - UI/runtime events are not prompt text unless explicitly selected.
|
|
26
|
+
*/
|
|
27
|
+
export function transformRunContextBeforeWorkerStart(input: {
|
|
28
|
+
manifest: TeamRunManifest;
|
|
29
|
+
tasks: TeamTaskState[];
|
|
30
|
+
pendingMailbox: MailboxMessage[];
|
|
31
|
+
artifacts: ArtifactDescriptor[];
|
|
32
|
+
maxEvents?: number;
|
|
33
|
+
maxMailboxMessages?: number;
|
|
34
|
+
maxArtifactRefs?: number;
|
|
35
|
+
}): RunProjectionResult {
|
|
36
|
+
const maxEvents = input.maxEvents ?? 20;
|
|
37
|
+
const maxMailbox = input.maxMailboxMessages ?? 10;
|
|
38
|
+
const maxArtifacts = input.maxArtifactRefs ?? 15;
|
|
39
|
+
|
|
40
|
+
const sources: RunProjectionSource[] = [];
|
|
41
|
+
const lines: string[] = [];
|
|
42
|
+
|
|
43
|
+
// Project a bounded slice of task history
|
|
44
|
+
const completedTasks = input.tasks.filter((t) => t.status === "completed" || t.status === "failed");
|
|
45
|
+
if (completedTasks.length > 0) {
|
|
46
|
+
const tasks = completedTasks.slice(0, maxEvents);
|
|
47
|
+
sources.push({ kind: "events", bounded: true, reference: `tasks:${tasks.length}/${completedTasks.length}` });
|
|
48
|
+
lines.push(`Previous tasks (${tasks.length}/${completedTasks.length}):`);
|
|
49
|
+
for (const task of tasks) {
|
|
50
|
+
lines.push(`- ${task.id}: ${task.status}${task.error ? ` (${task.error})` : ""}`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Project pending mailbox that is relevant to this worker
|
|
55
|
+
if (input.pendingMailbox.length > 0) {
|
|
56
|
+
const messages = input.pendingMailbox.slice(0, maxMailbox);
|
|
57
|
+
sources.push({ kind: "mailbox", bounded: true, reference: `mailbox:${messages.length}/${input.pendingMailbox.length}` });
|
|
58
|
+
lines.push(`Pending messages (${messages.length}/${input.pendingMailbox.length}):`);
|
|
59
|
+
for (const msg of messages) {
|
|
60
|
+
lines.push(`- ${msg.kind ?? "message"}: ${msg.body.slice(0, 100)}`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Project artifact references (not content)
|
|
65
|
+
if (input.artifacts.length > 0) {
|
|
66
|
+
const artifacts = input.artifacts.slice(0, maxArtifacts);
|
|
67
|
+
sources.push({ kind: "artifacts", bounded: true, reference: `artifacts:${artifacts.length}/${input.artifacts.length}` });
|
|
68
|
+
lines.push(`Available artifacts (${artifacts.length}/${input.artifacts.length}):`);
|
|
69
|
+
for (const art of artifacts) {
|
|
70
|
+
lines.push(`- ${art.kind} (${art.producer})`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Metadata markers — not injected as prompt instructions
|
|
75
|
+
sources.push({ kind: "ui_metadata", bounded: false, reference: "excluded_from_prompt" });
|
|
76
|
+
sources.push({ kind: "runtime_metadata", bounded: false, reference: "excluded_from_prompt" });
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
sources,
|
|
80
|
+
summary: lines.join("\n"),
|
|
81
|
+
injectedAsContext: true,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Convert run history to a bounded worker prompt section.
|
|
87
|
+
* Same logic as transformRunContextBeforeWorkerStart but returns
|
|
88
|
+
* the prompt text directly for embedding in the worker prompt.
|
|
89
|
+
*/
|
|
90
|
+
export function convertRunHistoryToWorkerPrompt(input: {
|
|
91
|
+
manifest: TeamRunManifest;
|
|
92
|
+
tasks: TeamTaskState[];
|
|
93
|
+
pendingMailbox: MailboxMessage[];
|
|
94
|
+
artifacts: ArtifactDescriptor[];
|
|
95
|
+
}): string {
|
|
96
|
+
const projection = transformRunContextBeforeWorkerStart(input);
|
|
97
|
+
if (!projection.summary) return "";
|
|
98
|
+
return [
|
|
99
|
+
"## Run Context (bounded projection)",
|
|
100
|
+
projection.summary,
|
|
101
|
+
"",
|
|
102
|
+
`Projection sources: ${projection.sources.map((s) => s.kind).join(", ")}`,
|
|
103
|
+
].join("\n");
|
|
104
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import type { AgentConfig } from "../agents/agent-config.ts";
|
|
3
3
|
import type { CrewLimitsConfig, CrewRuntimeConfig } from "../config/config.ts";
|
|
4
|
-
import type { ArtifactDescriptor, TeamRunManifest, TeamTaskState, UsageState } from "../state/types.ts";
|
|
4
|
+
import type { ArtifactDescriptor, OperationTerminalEvidence, TeamRunManifest, TeamTaskState, UsageState } from "../state/types.ts";
|
|
5
5
|
import { writeArtifact } from "../state/artifact-store.ts";
|
|
6
6
|
import { appendEvent } from "../state/event-log.ts";
|
|
7
7
|
import { saveRunManifest } from "../state/state-store.ts";
|
|
@@ -13,21 +13,33 @@ 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";
|
|
24
26
|
import { coordinationBridgeInstructions, renderTaskPrompt } from "./task-runner/prompt-builder.ts";
|
|
27
|
+
import { buildWorkerPromptPipeline } from "./task-runner/prompt-pipeline.ts";
|
|
28
|
+
import { buildWorkerCapabilityInventory } from "./task-runner/capabilities.ts";
|
|
25
29
|
import { applyAgentProgressEvent, applyUsageToProgress, progressEventSummary, shouldFlushProgressEvent } from "./task-runner/progress.ts";
|
|
26
30
|
import { checkpointTask, persistSingleTaskUpdate, updateTask } from "./task-runner/state-helpers.ts";
|
|
27
31
|
import { cleanResultText, isFinalChildEvent } from "./task-runner/result-utils.ts";
|
|
28
32
|
import { evaluateCompletionMutationGuard } from "./completion-guard.ts";
|
|
33
|
+
import { cancellationReasonFromSignal, buildSyntheticTerminalEvidence } from "./cancellation.ts";
|
|
29
34
|
import { appendTaskAttentionEvent } from "./attention-events.ts";
|
|
30
35
|
import { parseSupervisorContactFromLine, recordSupervisorContact } from "./supervisor-contact.ts";
|
|
36
|
+
import { registerStreamBridge, bridgeEventFromJsonEvent } from "./event-stream-bridge.ts";
|
|
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();
|
|
31
43
|
|
|
32
44
|
export interface TaskRunnerInput {
|
|
33
45
|
manifest: TeamRunManifest;
|
|
@@ -43,14 +55,24 @@ export interface TaskRunnerInput {
|
|
|
43
55
|
parentModel?: unknown;
|
|
44
56
|
modelRegistry?: unknown;
|
|
45
57
|
modelOverride?: string;
|
|
58
|
+
teamRoleModel?: string;
|
|
59
|
+
teamRoleSkills?: string[] | false;
|
|
60
|
+
skillOverride?: string[] | false;
|
|
46
61
|
limits?: CrewLimitsConfig;
|
|
47
62
|
dependencyContextText?: string;
|
|
63
|
+
skillBlock?: string;
|
|
64
|
+
skillNames?: string[];
|
|
65
|
+
skillPaths?: string[];
|
|
48
66
|
/** Optional callback for JSON events from child Pi. Used for overflow recovery tracking. */
|
|
49
67
|
onJsonEvent?: (taskId: string, runId: string, event: unknown) => void;
|
|
50
68
|
}
|
|
51
69
|
|
|
52
70
|
export async function runTeamTask(input: TaskRunnerInput): Promise<{ manifest: TeamRunManifest; tasks: TeamTaskState[] }> {
|
|
53
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);
|
|
54
76
|
const workspace = prepareTaskWorkspace(manifest, input.task);
|
|
55
77
|
const worktree = workspace.worktreePath && workspace.branch ? { path: workspace.worktreePath, branch: workspace.branch, reused: workspace.reused ?? false } : input.task.worktree;
|
|
56
78
|
const taskPacket = buildTaskPacket({ manifest, step: input.step, taskId: input.task.id, cwd: workspace.cwd, worktreePath: worktree?.path });
|
|
@@ -67,6 +89,8 @@ export async function runTeamTask(input: TaskRunnerInput): Promise<{ manifest: T
|
|
|
67
89
|
heartbeat: createWorkerHeartbeat(input.task.id),
|
|
68
90
|
agentProgress: input.task.agentProgress ?? emptyCrewAgentProgress(),
|
|
69
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),
|
|
70
94
|
} as TeamTaskState;
|
|
71
95
|
let tasks = updateTask(input.tasks, task);
|
|
72
96
|
const runtimeKind = input.runtimeKind ?? (input.executeWorkers ? "child-process" : "scaffold");
|
|
@@ -74,9 +98,17 @@ export async function runTeamTask(input: TaskRunnerInput): Promise<{ manifest: T
|
|
|
74
98
|
if (runtimeKind === "child-process") ({ task, tasks } = checkpointTask(manifest, tasks, task, "started"));
|
|
75
99
|
upsertCrewAgent(manifest, recordFromTask(manifest, task, runtimeKind));
|
|
76
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() });
|
|
77
104
|
const permissionMode = permissionForRole(task.role);
|
|
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;
|
|
106
|
+
const skillBlock = input.skillBlock ?? renderedSkills?.block;
|
|
107
|
+
const skillNames = input.skillNames ?? renderedSkills?.names;
|
|
108
|
+
const skillPaths = input.skillPaths ?? renderedSkills?.paths;
|
|
78
109
|
|
|
79
|
-
const
|
|
110
|
+
const promptResult = await renderTaskPrompt(manifest, input.step, task, input.agent, skillBlock);
|
|
111
|
+
const prompt = promptResult.full;
|
|
80
112
|
const promptArtifact = writeArtifact(manifest.artifactsRoot, {
|
|
81
113
|
kind: "prompt",
|
|
82
114
|
relativePath: `prompts/${task.id}.md`,
|
|
@@ -93,9 +125,17 @@ export async function runTeamTask(input: TaskRunnerInput): Promise<{ manifest: T
|
|
|
93
125
|
let parsedOutput: ParsedPiJsonOutput | undefined;
|
|
94
126
|
let finalStdout = "";
|
|
95
127
|
let transcriptPath: string | undefined;
|
|
128
|
+
let terminalEvidence: OperationTerminalEvidence[] = [];
|
|
129
|
+
const collectedJsonEvents: Record<string, unknown>[] = [];
|
|
96
130
|
|
|
97
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 });
|
|
98
132
|
const inputsArtifact = writeTaskInputsArtifact(manifest, task, dependencyContext);
|
|
133
|
+
const skillArtifact = skillBlock ? writeArtifact(manifest.artifactsRoot, {
|
|
134
|
+
kind: "metadata",
|
|
135
|
+
relativePath: `metadata/${task.id}.skills.md`,
|
|
136
|
+
content: [`Selected skills: ${skillNames?.join(", ") ?? "(none)"}`, `Skill paths passed to child Pi: ${(skillPaths ?? []).length}`, "", skillBlock, ""].join("\n"),
|
|
137
|
+
producer: task.id,
|
|
138
|
+
}) : undefined;
|
|
99
139
|
const coordinationArtifact = writeArtifact(manifest.artifactsRoot, {
|
|
100
140
|
kind: "metadata",
|
|
101
141
|
relativePath: `metadata/${task.id}.coordination-bridge.md`,
|
|
@@ -103,7 +143,7 @@ export async function runTeamTask(input: TaskRunnerInput): Promise<{ manifest: T
|
|
|
103
143
|
producer: task.id,
|
|
104
144
|
});
|
|
105
145
|
if (runtimeKind === "child-process") {
|
|
106
|
-
const modelRoutingPlan = buildConfiguredModelRouting({ overrideModel: input.modelOverride, stepModel: input.step.model, agentModel: input.agent.model, fallbackModels: input.agent.fallbackModels, parentModel: input.parentModel, modelRegistry: input.modelRegistry, cwd:
|
|
146
|
+
const modelRoutingPlan = buildConfiguredModelRouting({ overrideModel: input.modelOverride, stepModel: input.step.model, teamRoleModel: input.teamRoleModel, agentModel: input.agent.model, fallbackModels: input.agent.fallbackModels, parentModel: input.parentModel, modelRegistry: input.modelRegistry, cwd: task.cwd });
|
|
107
147
|
const candidates = modelRoutingPlan.candidates;
|
|
108
148
|
const attemptModels = candidates.length > 0 ? candidates : [undefined];
|
|
109
149
|
const logs: string[] = [];
|
|
@@ -151,6 +191,7 @@ export async function runTeamTask(input: TaskRunnerInput): Promise<{ manifest: T
|
|
|
151
191
|
signal: input.signal,
|
|
152
192
|
transcriptPath,
|
|
153
193
|
maxDepth: input.limits?.maxTaskDepth,
|
|
194
|
+
skillPaths,
|
|
154
195
|
onSpawn: (pid) => {
|
|
155
196
|
({ task, tasks } = checkpointTask(manifest, tasks, task, "child-spawned", pid));
|
|
156
197
|
},
|
|
@@ -165,9 +206,15 @@ export async function runTeamTask(input: TaskRunnerInput): Promise<{ manifest: T
|
|
|
165
206
|
},
|
|
166
207
|
onJsonEvent: (event) => {
|
|
167
208
|
appendCrewAgentEvent(manifest, task.id, event);
|
|
209
|
+
if (event && typeof event === "object" && !Array.isArray(event)) collectedJsonEvents.push(event as Record<string, unknown>);
|
|
168
210
|
persistHeartbeat();
|
|
169
211
|
task = { ...task, agentProgress: applyAgentProgressEvent(task.agentProgress ?? emptyCrewAgentProgress(), event, task.startedAt) };
|
|
170
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 */ }
|
|
171
218
|
// Feed overflow recovery tracker
|
|
172
219
|
if (input.onJsonEvent) {
|
|
173
220
|
try {
|
|
@@ -181,6 +228,13 @@ export async function runTeamTask(input: TaskRunnerInput): Promise<{ manifest: T
|
|
|
181
228
|
persistChildProgress(event);
|
|
182
229
|
},
|
|
183
230
|
});
|
|
231
|
+
const evidenceStatus = childResult.exitStatus?.cancelled ? "cancelled" : childResult.error || (childResult.exitCode && childResult.exitCode !== 0) ? "failed" : "completed";
|
|
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 } : {}) }];
|
|
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
|
+
}
|
|
184
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 });
|
|
185
239
|
exitCode = childResult.exitCode;
|
|
186
240
|
finalStdout = childResult.stdout;
|
|
@@ -238,7 +292,7 @@ export async function runTeamTask(input: TaskRunnerInput): Promise<{ manifest: T
|
|
|
238
292
|
({ task, tasks } = checkpointTask(manifest, tasks, task, "artifact-written"));
|
|
239
293
|
} else if (runtimeKind === "live-session") {
|
|
240
294
|
const { runLiveTask } = await import("./task-runner/live-executor.ts");
|
|
241
|
-
const live = await runLiveTask({ manifest, tasks, task, step: input.step, agent: input.agent, prompt, signal: input.signal, runtimeConfig: input.runtimeConfig, parentContext: input.parentContext, parentModel: input.parentModel, modelRegistry: input.modelRegistry });
|
|
295
|
+
const live = await runLiveTask({ manifest, tasks, task, step: input.step, agent: input.agent, prompt, signal: input.signal, runtimeConfig: input.runtimeConfig, parentContext: input.parentContext, parentModel: input.parentModel, modelRegistry: input.modelRegistry, modelOverride: input.modelOverride, teamRoleModel: input.teamRoleModel });
|
|
242
296
|
task = live.task;
|
|
243
297
|
tasks = live.tasks;
|
|
244
298
|
startupEvidence = live.startupEvidence;
|
|
@@ -262,6 +316,20 @@ export async function runTeamTask(input: TaskRunnerInput): Promise<{ manifest: T
|
|
|
262
316
|
});
|
|
263
317
|
}
|
|
264
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
|
+
|
|
265
333
|
const diffArtifact = workspace.worktreePath ? writeArtifact(manifest.artifactsRoot, {
|
|
266
334
|
kind: "diff",
|
|
267
335
|
relativePath: `diffs/${task.id}.diff`,
|
|
@@ -295,6 +363,22 @@ export async function runTeamTask(input: TaskRunnerInput): Promise<{ manifest: T
|
|
|
295
363
|
tasks = updateTask(tasks, task);
|
|
296
364
|
}
|
|
297
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
|
+
|
|
298
382
|
task = {
|
|
299
383
|
...task,
|
|
300
384
|
status: error ? "failed" : "completed",
|
|
@@ -310,6 +394,8 @@ export async function runTeamTask(input: TaskRunnerInput): Promise<{ manifest: T
|
|
|
310
394
|
resultArtifact,
|
|
311
395
|
claim: undefined,
|
|
312
396
|
heartbeat: touchWorkerHeartbeat(task.heartbeat ?? createWorkerHeartbeat(task.id), { alive: false }),
|
|
397
|
+
workerExitStatus: terminalEvidence.at(-1)?.exitStatus,
|
|
398
|
+
terminalEvidence: terminalEvidence.length ? [...(task.terminalEvidence ?? []), ...terminalEvidence] : task.terminalEvidence,
|
|
313
399
|
...(logArtifact ? { logArtifact } : {}),
|
|
314
400
|
...(transcriptArtifact ? { transcriptArtifact } : {}),
|
|
315
401
|
};
|
|
@@ -339,10 +425,34 @@ export async function runTeamTask(input: TaskRunnerInput): Promise<{ manifest: T
|
|
|
339
425
|
content: `${JSON.stringify({ role: task.role, permissionMode }, null, 2)}\n`,
|
|
340
426
|
producer: task.id,
|
|
341
427
|
});
|
|
342
|
-
|
|
428
|
+
const capabilityArtifact = writeArtifact(manifest.artifactsRoot, {
|
|
429
|
+
kind: "metadata",
|
|
430
|
+
relativePath: `metadata/${task.id}.capabilities.json`,
|
|
431
|
+
content: `${JSON.stringify(buildWorkerCapabilityInventory({ taskId: task.id, role: task.role, agent: input.agent, runtime: runtimeKind, permissionMode, skillNames, skillPaths, skillsDisabled: input.skillOverride === false || input.teamRoleSkills === false, modelOverride: input.modelOverride, teamRoleModel: input.teamRoleModel, stepModel: input.step.model }), null, 2)}\n`,
|
|
432
|
+
producer: task.id,
|
|
433
|
+
});
|
|
434
|
+
const promptPipelineArtifact = writeArtifact(manifest.artifactsRoot, {
|
|
435
|
+
kind: "metadata",
|
|
436
|
+
relativePath: `metadata/${task.id}.prompt-pipeline.json`,
|
|
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`,
|
|
438
|
+
producer: task.id,
|
|
439
|
+
});
|
|
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] : [])] };
|
|
343
447
|
saveRunManifest(manifest);
|
|
344
448
|
tasks = persistSingleTaskUpdate(manifest, tasks, task);
|
|
345
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);
|
|
346
453
|
appendEvent(manifest.eventsPath, { type: error ? "task.failed" : "task.completed", runId: manifest.runId, taskId: task.id, message: error });
|
|
347
454
|
return { manifest, tasks };
|
|
455
|
+
} finally {
|
|
456
|
+
streamBridge?.dispose();
|
|
457
|
+
}
|
|
348
458
|
}
|