pi-crew 0.1.45 → 0.1.46
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/README.md +5 -5
- package/agents/analyst.md +1 -1
- package/agents/critic.md +1 -1
- package/agents/executor.md +1 -1
- package/agents/explorer.md +1 -1
- package/agents/planner.md +1 -1
- package/agents/reviewer.md +1 -1
- package/agents/security-reviewer.md +1 -1
- package/agents/test-engineer.md +1 -1
- package/agents/verifier.md +1 -1
- package/agents/writer.md +1 -1
- package/docs/next-upgrade-roadmap.md +733 -0
- 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-awesome-agent-skills-distillation.md +100 -0
- package/docs/research-extension-examples.md +297 -297
- package/docs/research-extension-system.md +324 -324
- package/docs/research-oh-my-pi-distillation.md +322 -0
- 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 -83
- package/docs/usage.md +3 -3
- package/index.ts +6 -6
- package/install.mjs +52 -8
- package/package.json +1 -1
- package/schema.json +2 -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/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-serializer.ts +34 -34
- package/src/agents/discover-agents.ts +12 -11
- package/src/config/config.ts +48 -24
- package/src/config/defaults.ts +14 -0
- package/src/extension/cross-extension-rpc.ts +82 -82
- package/src/extension/project-init.ts +62 -2
- package/src/extension/register.ts +11 -9
- package/src/extension/registration/commands.ts +32 -25
- package/src/extension/registration/compaction-guard.ts +125 -125
- 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 +8 -6
- package/src/extension/run-bundle-schema.ts +89 -89
- package/src/extension/run-index.ts +13 -5
- package/src/extension/run-maintenance.ts +62 -43
- package/src/extension/team-tool/api.ts +25 -8
- package/src/extension/team-tool/cancel.ts +33 -4
- package/src/extension/team-tool/context.ts +5 -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/lifecycle-actions.ts +91 -79
- package/src/extension/team-tool/plan.ts +19 -19
- package/src/extension/team-tool/respond.ts +37 -17
- package/src/extension/team-tool/run.ts +52 -10
- package/src/extension/team-tool/status.ts +12 -1
- package/src/extension/team-tool-types.ts +2 -0
- package/src/extension/team-tool.ts +32 -11
- package/src/i18n.ts +184 -184
- package/src/observability/event-to-metric.ts +8 -1
- package/src/observability/exporters/otlp-exporter.ts +77 -77
- package/src/prompt/prompt-runtime.ts +72 -72
- package/src/runtime/agent-control.ts +63 -63
- 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/attention-events.ts +28 -28
- package/src/runtime/background-runner.ts +59 -53
- package/src/runtime/cancellation.ts +51 -0
- package/src/runtime/child-pi.ts +457 -444
- package/src/runtime/completion-guard.ts +190 -190
- package/src/runtime/crash-recovery.ts +1 -0
- package/src/runtime/crew-agent-records.ts +38 -6
- package/src/runtime/deadletter.ts +1 -0
- package/src/runtime/delivery-coordinator.ts +46 -25
- package/src/runtime/direct-run.ts +35 -35
- package/src/runtime/effectiveness.ts +76 -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 -87
- package/src/runtime/live-agent-manager.ts +103 -85
- package/src/runtime/live-control-realtime.ts +36 -36
- package/src/runtime/live-session-runtime.ts +309 -305
- package/src/runtime/manifest-cache.ts +17 -2
- package/src/runtime/model-fallback.ts +6 -4
- package/src/runtime/parallel-research.ts +44 -44
- package/src/runtime/pi-args.ts +18 -3
- package/src/runtime/pi-json-output.ts +111 -111
- package/src/runtime/policy-engine.ts +79 -79
- package/src/runtime/process-status.ts +5 -1
- package/src/runtime/progress-event-coalescer.ts +43 -43
- package/src/runtime/recovery-recipes.ts +74 -74
- package/src/runtime/retry-executor.ts +81 -64
- package/src/runtime/role-permission.ts +39 -39
- package/src/runtime/runtime-resolver.ts +22 -6
- 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/skill-instructions.ts +222 -0
- package/src/runtime/stale-reconciler.ts +4 -14
- package/src/runtime/subagent-manager.ts +3 -0
- package/src/runtime/supervisor-contact.ts +59 -59
- package/src/runtime/task-display.ts +38 -38
- package/src/runtime/task-output-context.ts +127 -127
- package/src/runtime/task-runner/capabilities.ts +78 -0
- package/src/runtime/task-runner/live-executor.ts +105 -101
- package/src/runtime/task-runner/progress.ts +119 -119
- package/src/runtime/task-runner/prompt-builder.ts +3 -1
- package/src/runtime/task-runner/prompt-pipeline.ts +64 -0
- package/src/runtime/task-runner/result-utils.ts +14 -14
- package/src/runtime/task-runner/state-helpers.ts +22 -22
- package/src/runtime/task-runner.ts +44 -5
- package/src/runtime/team-runner.ts +78 -15
- package/src/runtime/worker-heartbeat.ts +21 -21
- package/src/runtime/worker-startup.ts +57 -57
- package/src/schema/config-schema.ts +1 -0
- package/src/schema/team-tool-schema.ts +3 -3
- package/src/state/active-run-registry.ts +165 -0
- package/src/state/contracts.ts +1 -1
- package/src/state/mailbox.ts +44 -4
- package/src/state/state-store.ts +8 -1
- package/src/state/task-claims.ts +44 -44
- package/src/state/types.ts +44 -2
- 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-config.ts +1 -0
- package/src/teams/team-serializer.ts +38 -38
- package/src/types/diff.d.ts +18 -18
- package/src/ui/crew-footer.ts +101 -101
- package/src/ui/crew-select-list.ts +111 -111
- package/src/ui/crew-widget.ts +4 -3
- package/src/ui/dashboard-panes/metrics-pane.ts +34 -34
- package/src/ui/dashboard-panes/progress-pane.ts +2 -0
- package/src/ui/dynamic-border.ts +25 -25
- package/src/ui/layout-primitives.ts +106 -106
- package/src/ui/loaders.ts +158 -158
- package/src/ui/render-diff.ts +119 -119
- package/src/ui/render-scheduler.ts +143 -143
- package/src/ui/run-snapshot-cache.ts +10 -2
- package/src/ui/snapshot-types.ts +2 -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/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 +12 -12
- package/src/utils/names.ts +27 -27
- package/src/utils/paths.ts +4 -2
- package/src/utils/redaction.ts +44 -44
- package/src/utils/safe-paths.ts +47 -47
- package/src/utils/sleep.ts +32 -32
- package/src/workflows/validate-workflow.ts +40 -40
- package/src/workflows/workflow-config.ts +1 -0
- package/src/worktree/branch-freshness.ts +45 -45
- 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,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";
|
|
@@ -22,12 +22,16 @@ import { parseSessionUsage } from "./session-usage.ts";
|
|
|
22
22
|
import type { CrewAgentProgress, CrewRuntimeKind } from "./crew-agent-runtime.ts";
|
|
23
23
|
import { shouldAppendProgressEventUpdate, type ProgressEventSummary } from "./progress-event-coalescer.ts";
|
|
24
24
|
import { coordinationBridgeInstructions, renderTaskPrompt } from "./task-runner/prompt-builder.ts";
|
|
25
|
+
import { buildWorkerPromptPipeline } from "./task-runner/prompt-pipeline.ts";
|
|
26
|
+
import { buildWorkerCapabilityInventory } from "./task-runner/capabilities.ts";
|
|
25
27
|
import { applyAgentProgressEvent, applyUsageToProgress, progressEventSummary, shouldFlushProgressEvent } from "./task-runner/progress.ts";
|
|
26
28
|
import { checkpointTask, persistSingleTaskUpdate, updateTask } from "./task-runner/state-helpers.ts";
|
|
27
29
|
import { cleanResultText, isFinalChildEvent } from "./task-runner/result-utils.ts";
|
|
28
30
|
import { evaluateCompletionMutationGuard } from "./completion-guard.ts";
|
|
31
|
+
import { cancellationReasonFromSignal } from "./cancellation.ts";
|
|
29
32
|
import { appendTaskAttentionEvent } from "./attention-events.ts";
|
|
30
33
|
import { parseSupervisorContactFromLine, recordSupervisorContact } from "./supervisor-contact.ts";
|
|
34
|
+
import { renderSkillInstructions } from "./skill-instructions.ts";
|
|
31
35
|
|
|
32
36
|
export interface TaskRunnerInput {
|
|
33
37
|
manifest: TeamRunManifest;
|
|
@@ -43,8 +47,14 @@ export interface TaskRunnerInput {
|
|
|
43
47
|
parentModel?: unknown;
|
|
44
48
|
modelRegistry?: unknown;
|
|
45
49
|
modelOverride?: string;
|
|
50
|
+
teamRoleModel?: string;
|
|
51
|
+
teamRoleSkills?: string[] | false;
|
|
52
|
+
skillOverride?: string[] | false;
|
|
46
53
|
limits?: CrewLimitsConfig;
|
|
47
54
|
dependencyContextText?: string;
|
|
55
|
+
skillBlock?: string;
|
|
56
|
+
skillNames?: string[];
|
|
57
|
+
skillPaths?: string[];
|
|
48
58
|
/** Optional callback for JSON events from child Pi. Used for overflow recovery tracking. */
|
|
49
59
|
onJsonEvent?: (taskId: string, runId: string, event: unknown) => void;
|
|
50
60
|
}
|
|
@@ -75,8 +85,12 @@ export async function runTeamTask(input: TaskRunnerInput): Promise<{ manifest: T
|
|
|
75
85
|
upsertCrewAgent(manifest, recordFromTask(manifest, task, runtimeKind));
|
|
76
86
|
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 } });
|
|
77
87
|
const permissionMode = permissionForRole(task.role);
|
|
88
|
+
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
|
+
const skillBlock = input.skillBlock ?? renderedSkills?.block;
|
|
90
|
+
const skillNames = input.skillNames ?? renderedSkills?.names;
|
|
91
|
+
const skillPaths = input.skillPaths ?? renderedSkills?.paths;
|
|
78
92
|
|
|
79
|
-
const prompt = renderTaskPrompt(manifest, input.step, task, input.agent);
|
|
93
|
+
const prompt = renderTaskPrompt(manifest, input.step, task, input.agent, skillBlock);
|
|
80
94
|
const promptArtifact = writeArtifact(manifest.artifactsRoot, {
|
|
81
95
|
kind: "prompt",
|
|
82
96
|
relativePath: `prompts/${task.id}.md`,
|
|
@@ -93,9 +107,16 @@ export async function runTeamTask(input: TaskRunnerInput): Promise<{ manifest: T
|
|
|
93
107
|
let parsedOutput: ParsedPiJsonOutput | undefined;
|
|
94
108
|
let finalStdout = "";
|
|
95
109
|
let transcriptPath: string | undefined;
|
|
110
|
+
let terminalEvidence: OperationTerminalEvidence[] = [];
|
|
96
111
|
|
|
97
112
|
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
113
|
const inputsArtifact = writeTaskInputsArtifact(manifest, task, dependencyContext);
|
|
114
|
+
const skillArtifact = skillBlock ? writeArtifact(manifest.artifactsRoot, {
|
|
115
|
+
kind: "metadata",
|
|
116
|
+
relativePath: `metadata/${task.id}.skills.md`,
|
|
117
|
+
content: [`Selected skills: ${skillNames?.join(", ") ?? "(none)"}`, `Skill paths passed to child Pi: ${(skillPaths ?? []).length}`, "", skillBlock, ""].join("\n"),
|
|
118
|
+
producer: task.id,
|
|
119
|
+
}) : undefined;
|
|
99
120
|
const coordinationArtifact = writeArtifact(manifest.artifactsRoot, {
|
|
100
121
|
kind: "metadata",
|
|
101
122
|
relativePath: `metadata/${task.id}.coordination-bridge.md`,
|
|
@@ -103,7 +124,7 @@ export async function runTeamTask(input: TaskRunnerInput): Promise<{ manifest: T
|
|
|
103
124
|
producer: task.id,
|
|
104
125
|
});
|
|
105
126
|
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:
|
|
127
|
+
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
128
|
const candidates = modelRoutingPlan.candidates;
|
|
108
129
|
const attemptModels = candidates.length > 0 ? candidates : [undefined];
|
|
109
130
|
const logs: string[] = [];
|
|
@@ -151,6 +172,7 @@ export async function runTeamTask(input: TaskRunnerInput): Promise<{ manifest: T
|
|
|
151
172
|
signal: input.signal,
|
|
152
173
|
transcriptPath,
|
|
153
174
|
maxDepth: input.limits?.maxTaskDepth,
|
|
175
|
+
skillPaths,
|
|
154
176
|
onSpawn: (pid) => {
|
|
155
177
|
({ task, tasks } = checkpointTask(manifest, tasks, task, "child-spawned", pid));
|
|
156
178
|
},
|
|
@@ -181,6 +203,9 @@ export async function runTeamTask(input: TaskRunnerInput): Promise<{ manifest: T
|
|
|
181
203
|
persistChildProgress(event);
|
|
182
204
|
},
|
|
183
205
|
});
|
|
206
|
+
const evidenceStatus = childResult.exitStatus?.cancelled ? "cancelled" : childResult.error || (childResult.exitCode && childResult.exitCode !== 0) ? "failed" : "completed";
|
|
207
|
+
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") appendEvent(manifest.eventsPath, { type: "worker.cancelled", runId: manifest.runId, taskId: task.id, message: input.signal?.aborted ? cancellationReasonFromSignal(input.signal).message : "Worker cancelled.", data: { terminalEvidence: terminalEvidence.at(-1) } });
|
|
184
209
|
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
210
|
exitCode = childResult.exitCode;
|
|
186
211
|
finalStdout = childResult.stdout;
|
|
@@ -238,7 +263,7 @@ export async function runTeamTask(input: TaskRunnerInput): Promise<{ manifest: T
|
|
|
238
263
|
({ task, tasks } = checkpointTask(manifest, tasks, task, "artifact-written"));
|
|
239
264
|
} else if (runtimeKind === "live-session") {
|
|
240
265
|
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 });
|
|
266
|
+
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
267
|
task = live.task;
|
|
243
268
|
tasks = live.tasks;
|
|
244
269
|
startupEvidence = live.startupEvidence;
|
|
@@ -310,6 +335,8 @@ export async function runTeamTask(input: TaskRunnerInput): Promise<{ manifest: T
|
|
|
310
335
|
resultArtifact,
|
|
311
336
|
claim: undefined,
|
|
312
337
|
heartbeat: touchWorkerHeartbeat(task.heartbeat ?? createWorkerHeartbeat(task.id), { alive: false }),
|
|
338
|
+
workerExitStatus: terminalEvidence.at(-1)?.exitStatus,
|
|
339
|
+
terminalEvidence: terminalEvidence.length ? [...(task.terminalEvidence ?? []), ...terminalEvidence] : task.terminalEvidence,
|
|
313
340
|
...(logArtifact ? { logArtifact } : {}),
|
|
314
341
|
...(transcriptArtifact ? { transcriptArtifact } : {}),
|
|
315
342
|
};
|
|
@@ -339,7 +366,19 @@ export async function runTeamTask(input: TaskRunnerInput): Promise<{ manifest: T
|
|
|
339
366
|
content: `${JSON.stringify({ role: task.role, permissionMode }, null, 2)}\n`,
|
|
340
367
|
producer: task.id,
|
|
341
368
|
});
|
|
342
|
-
|
|
369
|
+
const capabilityArtifact = writeArtifact(manifest.artifactsRoot, {
|
|
370
|
+
kind: "metadata",
|
|
371
|
+
relativePath: `metadata/${task.id}.capabilities.json`,
|
|
372
|
+
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`,
|
|
373
|
+
producer: task.id,
|
|
374
|
+
});
|
|
375
|
+
const promptPipelineArtifact = writeArtifact(manifest.artifactsRoot, {
|
|
376
|
+
kind: "metadata",
|
|
377
|
+
relativePath: `metadata/${task.id}.prompt-pipeline.json`,
|
|
378
|
+
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
|
+
producer: task.id,
|
|
380
|
+
});
|
|
381
|
+
manifest = { ...manifest, updatedAt: new Date().toISOString(), artifacts: [...manifest.artifacts, promptArtifact, resultArtifact, inputsArtifact, coordinationArtifact, ...(skillArtifact ? [skillArtifact] : []), packetArtifact, verificationArtifact, startupArtifact, permissionArtifact, capabilityArtifact, promptPipelineArtifact, ...(sharedOutputArtifact ? [sharedOutputArtifact] : []), ...(logArtifact ? [logArtifact] : []), ...(transcriptArtifact ? [transcriptArtifact] : []), ...(diffArtifact ? [diffArtifact] : []), ...(diffStatArtifact ? [diffStatArtifact] : [])] };
|
|
343
382
|
saveRunManifest(manifest);
|
|
344
383
|
tasks = persistSingleTaskUpdate(manifest, tasks, task);
|
|
345
384
|
upsertCrewAgent(manifest, recordFromTask(manifest, task, runtimeKind));
|
|
@@ -25,6 +25,8 @@ import { childCorrelation, withCorrelation } from "../observability/correlation.
|
|
|
25
25
|
import { resolveBatchConcurrency } from "./concurrency.ts";
|
|
26
26
|
import { mapConcurrent } from "./parallel-utils.ts";
|
|
27
27
|
import { permissionForRole } from "./role-permission.ts";
|
|
28
|
+
import { CrewCancellationError, cancellationReasonFromSignal } from "./cancellation.ts";
|
|
29
|
+
import { effectivenessPolicyDecision, evaluateRunEffectiveness, formatRunEffectivenessLines } from "./effectiveness.ts";
|
|
28
30
|
|
|
29
31
|
export interface ExecuteTeamRunInput {
|
|
30
32
|
manifest: TeamRunManifest;
|
|
@@ -43,6 +45,8 @@ export interface ExecuteTeamRunInput {
|
|
|
43
45
|
signal?: AbortSignal;
|
|
44
46
|
reliability?: CrewReliabilityConfig;
|
|
45
47
|
metricRegistry?: MetricRegistry;
|
|
48
|
+
/** Skill override from the team tool. false disables skill injection for this run. */
|
|
49
|
+
skillOverride?: string[] | false;
|
|
46
50
|
/** Optional callback for JSON events from child Pi. Used for overflow recovery tracking. */
|
|
47
51
|
onJsonEvent?: (taskId: string, runId: string, event: unknown) => void;
|
|
48
52
|
}
|
|
@@ -380,7 +384,11 @@ function formatTaskProgress(task: TeamTaskState): string {
|
|
|
380
384
|
return `- ${task.id}: ${task.status} (${task.role} -> ${task.agent})${task.taskPacket ? ` scope=${task.taskPacket.scope}` : ""}${task.verification ? ` green=${task.verification.observedGreenLevel}/${task.verification.requiredGreenLevel}` : ""}${task.error ? ` - ${task.error}` : ""}`;
|
|
381
385
|
}
|
|
382
386
|
|
|
383
|
-
function
|
|
387
|
+
function runEffectivenessLines(manifest: TeamRunManifest, tasks: TeamTaskState[], executeWorkers: boolean, runtimeConfig?: CrewRuntimeConfig): string[] {
|
|
388
|
+
return formatRunEffectivenessLines(evaluateRunEffectiveness({ manifest, tasks, executeWorkers, runtimeConfig }));
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
function writeProgress(manifest: TeamRunManifest, tasks: TeamTaskState[], producer: string, executeWorkers = true, runtimeConfig?: CrewRuntimeConfig): TeamRunManifest {
|
|
384
392
|
const counts = new Map<string, number>();
|
|
385
393
|
for (const task of tasks) counts.set(task.status, (counts.get(task.status) ?? 0) + 1);
|
|
386
394
|
const queue = taskGraphSnapshot(tasks);
|
|
@@ -401,6 +409,9 @@ function writeProgress(manifest: TeamRunManifest, tasks: TeamTaskState[], produc
|
|
|
401
409
|
"## Tasks",
|
|
402
410
|
...tasks.map(formatTaskProgress),
|
|
403
411
|
"",
|
|
412
|
+
"## Effectiveness",
|
|
413
|
+
...runEffectivenessLines(manifest, tasks, executeWorkers, runtimeConfig),
|
|
414
|
+
"",
|
|
404
415
|
].join("\n"),
|
|
405
416
|
});
|
|
406
417
|
return { ...manifest, updatedAt: new Date().toISOString(), artifacts: [...manifest.artifacts.filter((artifact) => !(artifact.kind === "progress" && artifact.path === progress.path)), progress] };
|
|
@@ -528,16 +539,24 @@ export async function executeTeamRun(input: ExecuteTeamRunInput): Promise<{ mani
|
|
|
528
539
|
manifest = updateRunStatus(manifest, "cancelled", "Plan approval was cancelled.");
|
|
529
540
|
return { manifest, tasks };
|
|
530
541
|
}
|
|
531
|
-
manifest = writeProgress(manifest, tasks, "team-runner");
|
|
542
|
+
manifest = writeProgress(manifest, tasks, "team-runner", input.executeWorkers, input.runtimeConfig);
|
|
532
543
|
await saveRunManifestAsync(manifest);
|
|
533
544
|
const runtimeKind = input.runtime?.kind ?? (input.executeWorkers ? "child-process" : "scaffold");
|
|
534
545
|
saveCrewAgents(manifest, recordsForMaterializedTasks(manifest, tasks, runtimeKind));
|
|
535
546
|
|
|
536
547
|
while (tasks.some((task) => task.status === "queued")) {
|
|
537
548
|
if (input.signal?.aborted) {
|
|
538
|
-
|
|
549
|
+
const cancelReason = cancellationReasonFromSignal(input.signal);
|
|
550
|
+
const message = `${cancelReason.message} (${cancelReason.code})`;
|
|
551
|
+
const cancelledTaskIds: string[] = [];
|
|
552
|
+
tasks = tasks.map((task) => {
|
|
553
|
+
if (task.status !== "queued" && task.status !== "running" && task.status !== "waiting") return task;
|
|
554
|
+
cancelledTaskIds.push(task.id);
|
|
555
|
+
return { ...task, status: "cancelled", finishedAt: new Date().toISOString(), error: message };
|
|
556
|
+
});
|
|
539
557
|
await saveRunTasksAsync(manifest, tasks);
|
|
540
|
-
|
|
558
|
+
for (const taskId of cancelledTaskIds) appendEvent(manifest.eventsPath, { type: "task.cancelled", runId: manifest.runId, taskId, message, data: { reason: cancelReason.code } });
|
|
559
|
+
manifest = updateRunStatus(manifest, "cancelled", message, { data: { reason: cancelReason.code, cancelledTaskIds } });
|
|
541
560
|
return { manifest, tasks };
|
|
542
561
|
}
|
|
543
562
|
|
|
@@ -581,25 +600,27 @@ export async function executeTeamRun(input: ExecuteTeamRunInput): Promise<{ mani
|
|
|
581
600
|
async (task) => {
|
|
582
601
|
const step = findStep(workflow, task);
|
|
583
602
|
const agent = findAgent(input.agents, task);
|
|
584
|
-
const
|
|
603
|
+
const teamRole = input.team.roles.find((role) => role.name === task.role);
|
|
604
|
+
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 };
|
|
585
605
|
if (input.reliability?.autoRetry !== true) return withCorrelation(childCorrelation(manifest.runId, task.id), () => runTeamTask(baseInput));
|
|
586
606
|
let lastFailed: { manifest: TeamRunManifest; tasks: TeamTaskState[] } | undefined;
|
|
587
607
|
const attemptsSoFar: TaskAttemptState[] = [...(task.attempts ?? [])];
|
|
588
608
|
const policy = retryPolicyFromConfig(input.reliability);
|
|
589
609
|
try {
|
|
590
|
-
return await executeWithRetry(async (attempt) => {
|
|
610
|
+
return await executeWithRetry(async (attempt, info) => {
|
|
591
611
|
const startedAt = new Date().toISOString();
|
|
592
|
-
const inFlightAttempts: TaskAttemptState[] = [...attemptsSoFar, { startedAt }];
|
|
612
|
+
const inFlightAttempts: TaskAttemptState[] = [...attemptsSoFar, { attemptId: info.attemptId, startedAt }];
|
|
593
613
|
input.metricRegistry?.counter("crew.task.retry_attempt_total", "Retry attempts by run and task").inc({ runId: manifest.runId, taskId: task.id });
|
|
594
614
|
const fresh = loadRunManifestById(manifest.cwd, manifest.runId);
|
|
595
615
|
const freshManifest = fresh?.manifest ?? manifest;
|
|
596
616
|
const freshTasks = fresh?.tasks ?? tasks;
|
|
597
617
|
const freshTask = freshTasks.find((item) => item.id === task.id) ?? task;
|
|
618
|
+
if (freshTask.status !== "queued" && freshTask.status !== "running") return { manifest: freshManifest, tasks: freshTasks };
|
|
598
619
|
const taskWithAttempt: TeamTaskState = { ...freshTask, attempts: inFlightAttempts };
|
|
599
620
|
const result = await withCorrelation(childCorrelation(freshManifest.runId, task.id), () => runTeamTask({ ...baseInput, manifest: freshManifest, tasks: freshTasks, task: taskWithAttempt }));
|
|
600
621
|
const failed = failedTaskFrom(result, task.id);
|
|
601
622
|
const endedAt = new Date().toISOString();
|
|
602
|
-
const finishedAttempt: TaskAttemptState = { startedAt, endedAt, ...(failed?.error ? { error: failed.error } : {}) };
|
|
623
|
+
const finishedAttempt: TaskAttemptState = { attemptId: info.attemptId, startedAt, endedAt, ...(failed?.error ? { error: failed.error } : {}) };
|
|
603
624
|
attemptsSoFar.push(finishedAttempt);
|
|
604
625
|
const withAttempt = result.tasks.map((item) => item.id === task.id ? { ...item, attempts: [...attemptsSoFar] } : item);
|
|
605
626
|
const enriched = { manifest: result.manifest, tasks: withAttempt };
|
|
@@ -611,28 +632,51 @@ export async function executeTeamRun(input: ExecuteTeamRunInput): Promise<{ mani
|
|
|
611
632
|
return enriched;
|
|
612
633
|
}, policy, {
|
|
613
634
|
signal: input.signal,
|
|
614
|
-
|
|
615
|
-
|
|
635
|
+
attemptId: (attempt) => `${manifest.runId}:${task.id}:attempt-${attempt}`,
|
|
636
|
+
onAttemptFailed: (attempt, error, delayMs, info) => {
|
|
637
|
+
appendEvent(manifest.eventsPath, { type: "crew.task.retry_attempt", runId: manifest.runId, taskId: task.id, message: error.message, data: { attempt, attemptId: info.attemptId, delayMs } });
|
|
616
638
|
input.metricRegistry?.histogram("crew.task.retry_delay_ms", "Retry backoff delay, milliseconds").observe({ runId: manifest.runId, taskId: task.id }, delayMs);
|
|
617
639
|
},
|
|
618
|
-
onRetryGivenUp: (attempts, error) => {
|
|
619
|
-
appendDeadletter(manifest, { runId: manifest.runId, taskId: task.id, reason: "max-retries", attempts, lastError: error.message, timestamp: new Date().toISOString() });
|
|
640
|
+
onRetryGivenUp: (attempts, error, info) => {
|
|
641
|
+
appendDeadletter(manifest, { runId: manifest.runId, taskId: task.id, reason: "max-retries", attempts, attemptId: info.attemptId, lastError: error.message, timestamp: new Date().toISOString() });
|
|
620
642
|
input.metricRegistry?.counter("crew.task.deadletter_total", "Deadletter triggers by reason").inc({ reason: "max-retries" });
|
|
621
643
|
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));
|
|
622
644
|
},
|
|
623
645
|
});
|
|
624
|
-
} catch {
|
|
646
|
+
} catch (retryError) {
|
|
647
|
+
if (retryError instanceof CrewCancellationError || input.signal?.aborted) {
|
|
648
|
+
const reason = retryError instanceof CrewCancellationError ? retryError.reason : cancellationReasonFromSignal(input.signal);
|
|
649
|
+
const fresh = loadRunManifestById(manifest.cwd, manifest.runId);
|
|
650
|
+
const freshManifest = fresh?.manifest ?? manifest;
|
|
651
|
+
const freshTasks = fresh?.tasks ?? tasks;
|
|
652
|
+
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" } });
|
|
654
|
+
return { manifest: updateRunStatus(freshManifest, "cancelled", reason.message), tasks: cancelledTasks };
|
|
655
|
+
}
|
|
625
656
|
if (lastFailed) return lastFailed;
|
|
626
657
|
const fresh = loadRunManifestById(manifest.cwd, manifest.runId);
|
|
627
658
|
const freshManifest = fresh?.manifest ?? manifest;
|
|
628
659
|
const freshTasks = fresh?.tasks ?? tasks;
|
|
629
660
|
const freshTask = freshTasks.find((item) => item.id === task.id) ?? task;
|
|
661
|
+
if (freshTask.status !== "queued" && freshTask.status !== "running") return { manifest: freshManifest, tasks: freshTasks };
|
|
630
662
|
return withCorrelation(childCorrelation(freshManifest.runId, task.id), () => runTeamTask({ ...baseInput, manifest: freshManifest, tasks: freshTasks, task: freshTask }));
|
|
631
663
|
}
|
|
632
664
|
},
|
|
633
665
|
);
|
|
634
666
|
manifest = { ...results.at(-1)!.manifest, artifacts: mergeArtifacts([manifest.artifacts, ...results.map((item) => item.manifest.artifacts)].flat()) };
|
|
635
667
|
tasks = __test__mergeTaskUpdates(tasks, results);
|
|
668
|
+
const cancelledResult = results.find((item) => item.manifest.status === "cancelled");
|
|
669
|
+
if (cancelledResult || input.signal?.aborted) {
|
|
670
|
+
const reason = input.signal?.aborted ? cancellationReasonFromSignal(input.signal) : undefined;
|
|
671
|
+
const message = reason?.message ?? cancelledResult?.manifest.summary ?? "Run cancelled during task execution.";
|
|
672
|
+
manifest = { ...manifest, status: "running" };
|
|
673
|
+
manifest = updateRunStatus(manifest, "cancelled", message);
|
|
674
|
+
await saveRunTasksAsync(manifest, tasks);
|
|
675
|
+
saveCrewAgents(manifest, recordsForMaterializedTasks(manifest, tasks, runtimeKind));
|
|
676
|
+
await saveRunManifestAsync(manifest);
|
|
677
|
+
appendEvent(manifest.eventsPath, { type: "run.cancelled", runId: manifest.runId, message, data: { reason, phase: "task-batch", cancelledResultRunId: cancelledResult?.manifest.runId } });
|
|
678
|
+
return { manifest, tasks };
|
|
679
|
+
}
|
|
636
680
|
queueIndex = buildTaskGraphIndex(tasks);
|
|
637
681
|
const injectedAfterBatch = attemptAdaptivePlan();
|
|
638
682
|
if (injectedAfterBatch.missing) {
|
|
@@ -666,21 +710,37 @@ export async function executeTeamRun(input: ExecuteTeamRunInput): Promise<{ mani
|
|
|
666
710
|
});
|
|
667
711
|
const groupDelivery = deliverGroupJoin({ manifest, mode: resolveGroupJoinMode(input.runtimeConfig), batch: readyBatch, allTasks: tasks });
|
|
668
712
|
manifest = { ...manifest, artifacts: mergeArtifacts([...manifest.artifacts, batchArtifact, ...(groupDelivery?.artifact ? [groupDelivery.artifact] : [])]) };
|
|
669
|
-
manifest = writeProgress(manifest, tasks, "team-runner");
|
|
713
|
+
manifest = writeProgress(manifest, tasks, "team-runner", input.executeWorkers, input.runtimeConfig);
|
|
670
714
|
await saveRunManifestAsync(manifest);
|
|
671
715
|
}
|
|
672
716
|
|
|
673
717
|
const failed = tasks.find((task) => task.status === "failed");
|
|
718
|
+
const waiting = tasks.find((task) => task.status === "waiting");
|
|
719
|
+
const running = tasks.find((task) => task.status === "running");
|
|
674
720
|
manifest = applyPolicy(manifest, tasks, input.limits);
|
|
721
|
+
const effectiveness = evaluateRunEffectiveness({ manifest, tasks, executeWorkers: input.executeWorkers, runtimeConfig: input.runtimeConfig });
|
|
722
|
+
const effectivenessDecision = effectivenessPolicyDecision(effectiveness);
|
|
723
|
+
if (effectivenessDecision) {
|
|
724
|
+
manifest = { ...manifest, policyDecisions: [...(manifest.policyDecisions ?? []), effectivenessDecision], updatedAt: new Date().toISOString() };
|
|
725
|
+
appendEvent(manifest.eventsPath, { type: "run.effectiveness", runId: manifest.runId, message: effectivenessDecision.message, data: { effectiveness, policyDecision: effectivenessDecision } });
|
|
726
|
+
}
|
|
675
727
|
const blockingDecision = manifest.policyDecisions?.find((item) => item.action === "block" || item.action === "escalate");
|
|
676
728
|
if (failed) {
|
|
677
729
|
manifest = updateRunStatus(manifest, "failed", `Failed at task '${failed.id}'.`);
|
|
730
|
+
} else if (waiting) {
|
|
731
|
+
manifest = updateRunStatus(manifest, "blocked", `Waiting for response to task '${waiting.id}'.`);
|
|
732
|
+
} else if (running) {
|
|
733
|
+
manifest = updateRunStatus(manifest, "blocked", `Task '${running.id}' is still running.`);
|
|
734
|
+
} else if (effectiveness.severity === "failed") {
|
|
735
|
+
manifest = updateRunStatus(manifest, "failed", effectivenessDecision?.message ?? "Run effectiveness guard failed.");
|
|
736
|
+
} else if (effectiveness.severity === "blocked") {
|
|
737
|
+
manifest = updateRunStatus(manifest, "blocked", effectivenessDecision?.message ?? "Run effectiveness guard blocked completion.");
|
|
678
738
|
} else if (blockingDecision) {
|
|
679
739
|
manifest = updateRunStatus(manifest, "blocked", blockingDecision.message);
|
|
680
740
|
} else {
|
|
681
741
|
manifest = updateRunStatus(manifest, "completed", input.executeWorkers ? "Team workflow completed." : "Team workflow scaffold completed without launching child workers.");
|
|
682
742
|
}
|
|
683
|
-
manifest = writeProgress(manifest, tasks, "team-runner");
|
|
743
|
+
manifest = writeProgress(manifest, tasks, "team-runner", input.executeWorkers, input.runtimeConfig);
|
|
684
744
|
await saveRunManifestAsync(manifest);
|
|
685
745
|
const usage = aggregateUsage(tasks);
|
|
686
746
|
const summaryArtifact = writeArtifact(manifest.artifactsRoot, {
|
|
@@ -699,6 +759,9 @@ export async function executeTeamRun(input: ExecuteTeamRunInput): Promise<{ mani
|
|
|
699
759
|
"## Tasks",
|
|
700
760
|
...tasks.map(formatTaskProgress),
|
|
701
761
|
"",
|
|
762
|
+
"## Effectiveness",
|
|
763
|
+
...runEffectivenessLines(manifest, tasks, input.executeWorkers, input.runtimeConfig),
|
|
764
|
+
"",
|
|
702
765
|
"## Policy decisions",
|
|
703
766
|
...(manifest.policyDecisions?.length ? summarizePolicyDecisions(manifest.policyDecisions) : ["- (none)"]),
|
|
704
767
|
"",
|
|
@@ -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
|
+
}
|
|
@@ -39,6 +39,7 @@ export const PiTeamsRuntimeConfigSchema = Type.Object({
|
|
|
39
39
|
groupJoinAckTimeoutMs: Type.Optional(Type.Integer({ minimum: 1 })),
|
|
40
40
|
requirePlanApproval: Type.Optional(Type.Boolean()),
|
|
41
41
|
completionMutationGuard: Type.Optional(Type.Union([Type.Literal("off"), Type.Literal("warn"), Type.Literal("fail")])),
|
|
42
|
+
effectivenessGuard: Type.Optional(Type.Union([Type.Literal("off"), Type.Literal("warn"), Type.Literal("block"), Type.Literal("fail")])),
|
|
42
43
|
}, { additionalProperties: false });
|
|
43
44
|
|
|
44
45
|
export const PiTeamsControlConfigSchema = Type.Object({
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { Type } from "typebox";
|
|
2
2
|
|
|
3
3
|
const SkillOverride = Type.Unsafe({
|
|
4
|
-
description: "Skill name(s) to
|
|
4
|
+
description: "Skill name(s) to add to role/default skills, an array of skill names, or false to disable all injected skills for this run.",
|
|
5
5
|
anyOf: [
|
|
6
|
-
{ type: "string" },
|
|
7
|
-
{ type: "array", items: { type: "string" } },
|
|
6
|
+
{ type: "string", maxLength: 2048 },
|
|
7
|
+
{ type: "array", maxItems: 32, items: { type: "string", maxLength: 80 } },
|
|
8
8
|
{ type: "boolean" },
|
|
9
9
|
],
|
|
10
10
|
});
|