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
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parallel dispatch handler — accepts an array of independent tasks
|
|
3
|
+
* and spawns them as concurrent background agents.
|
|
4
|
+
*
|
|
5
|
+
* Solves the host-agent limitation of only being able to emit
|
|
6
|
+
* one Agent() call per response turn. By calling `action=parallel`
|
|
7
|
+
* once with multiple tasks, the system handles fanout automatically.
|
|
8
|
+
*/
|
|
9
|
+
import { discoverAgents } from "../../agents/discover-agents.ts";
|
|
10
|
+
import { loadConfig } from "../../config/config.ts";
|
|
11
|
+
import type { TeamToolParamsValue } from "../../schema/team-tool-schema.ts";
|
|
12
|
+
import { createRunManifest } from "../../state/state-store.ts";
|
|
13
|
+
import { appendEvent } from "../../state/event-log.ts";
|
|
14
|
+
import { spawnBackgroundTeamRun } from "../../subagents/async-entry.ts";
|
|
15
|
+
import { resolveCrewRuntime } from "../../runtime/runtime-resolver.ts";
|
|
16
|
+
import { resolveCwdOverride } from "../registration/team-tool.ts";
|
|
17
|
+
import { result, type TeamContext } from "./context.ts";
|
|
18
|
+
import type { PiTeamsToolResult } from "../tool-result.ts";
|
|
19
|
+
import { discoverTeams } from "../../teams/discover-teams.ts";
|
|
20
|
+
import { discoverWorkflows } from "../../workflows/discover-workflows.ts";
|
|
21
|
+
import type { TeamConfig } from "../../teams/team-config.ts";
|
|
22
|
+
import type { WorkflowConfig } from "../../workflows/workflow-config.ts";
|
|
23
|
+
|
|
24
|
+
const MAX_CONCURRENCY = 8;
|
|
25
|
+
const DEFAULT_CONCURRENCY = 4;
|
|
26
|
+
const DEFAULT_TEAM = "fast-fix";
|
|
27
|
+
const DEFAULT_AGENT = "explorer";
|
|
28
|
+
|
|
29
|
+
export async function handleParallel(params: TeamToolParamsValue, ctx: TeamContext): Promise<PiTeamsToolResult> {
|
|
30
|
+
const tasksParam = params.config?.tasks;
|
|
31
|
+
if (!Array.isArray(tasksParam) || tasksParam.length === 0) {
|
|
32
|
+
return result("parallel action requires config.tasks: [{goal, agent?}]", { action: "parallel", status: "error" }, true);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const concurrency = Math.min(
|
|
36
|
+
Math.max(1, Math.floor((params.config?.concurrency as number) ?? DEFAULT_CONCURRENCY)),
|
|
37
|
+
MAX_CONCURRENCY,
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
const config = loadConfig(ctx.cwd);
|
|
41
|
+
const agentsResult = discoverAgents(ctx.cwd);
|
|
42
|
+
const allAgentsList = [...agentsResult.builtin, ...agentsResult.user, ...agentsResult.project];
|
|
43
|
+
const teams = discoverTeams(ctx.cwd);
|
|
44
|
+
const workflows = discoverWorkflows(ctx.cwd);
|
|
45
|
+
|
|
46
|
+
const teamName = (params.config?.team as string) ?? DEFAULT_TEAM;
|
|
47
|
+
const team = teams.builtin.find((t) => t.name === teamName)
|
|
48
|
+
?? teams.user.find((t) => t.name === teamName)
|
|
49
|
+
?? teams.project.find((t) => t.name === teamName);
|
|
50
|
+
if (!team) {
|
|
51
|
+
return result(`Team '${teamName}' not found`, { action: "parallel", status: "error" }, true);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// H2: Use team's defaultWorkflow, fall back to "fast-fix"
|
|
55
|
+
const workflow = workflows.builtin.find((w) => w.name === team.defaultWorkflow)
|
|
56
|
+
?? workflows.builtin.find((w) => w.name === "fast-fix");
|
|
57
|
+
if (!workflow) {
|
|
58
|
+
return result("No suitable workflow found for parallel dispatch", { action: "parallel", status: "error" }, true);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const runtime = await resolveCrewRuntime(config.config);
|
|
62
|
+
|
|
63
|
+
const launched: Array<{ runId: string; goal: string; agent: string }> = [];
|
|
64
|
+
const errors: Array<{ goal: string; error: string }> = [];
|
|
65
|
+
|
|
66
|
+
// C1: Enforce concurrency limit with batched spawning
|
|
67
|
+
for (let batchStart = 0; batchStart < tasksParam.length; batchStart += concurrency) {
|
|
68
|
+
const batch = tasksParam.slice(batchStart, batchStart + concurrency);
|
|
69
|
+
const batchPromises = batch.map((task) => spawnSingleTask(task, ctx, allAgentsList, team, workflow, runtime));
|
|
70
|
+
const batchResults = await Promise.allSettled(batchPromises);
|
|
71
|
+
for (const res of batchResults) {
|
|
72
|
+
if (res.status === "fulfilled" && res.value.ok) {
|
|
73
|
+
launched.push(res.value.value);
|
|
74
|
+
} else if (res.status === "fulfilled" && !res.value.ok) {
|
|
75
|
+
errors.push(res.value.error);
|
|
76
|
+
} else {
|
|
77
|
+
const reason = (res as PromiseRejectedResult).reason;
|
|
78
|
+
errors.push({ goal: "(unknown)", error: reason instanceof Error ? reason.message : String(reason) });
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const lines: string[] = [
|
|
84
|
+
`Parallel dispatch: ${launched.length}/${tasksParam.length} tasks launched (concurrency: ${concurrency})`,
|
|
85
|
+
"",
|
|
86
|
+
];
|
|
87
|
+
for (const l of launched) {
|
|
88
|
+
lines.push(` ✅ ${l.runId} — ${l.agent}: ${l.goal.slice(0, 80)}`);
|
|
89
|
+
}
|
|
90
|
+
for (const e of errors) {
|
|
91
|
+
lines.push(` ❌ ${e.goal.slice(0, 80)}: ${e.error}`);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return result(lines.join("\n"), {
|
|
95
|
+
action: "parallel",
|
|
96
|
+
status: errors.length === tasksParam.length ? "error" : "ok",
|
|
97
|
+
}, errors.length === tasksParam.length);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
type SpawnOk = { ok: true; value: { runId: string; goal: string; agent: string } };
|
|
101
|
+
type SpawnErr = { ok: false; error: { goal: string; error: string } };
|
|
102
|
+
type SpawnResult = SpawnOk | SpawnErr;
|
|
103
|
+
|
|
104
|
+
async function spawnSingleTask(
|
|
105
|
+
task: unknown,
|
|
106
|
+
ctx: TeamContext,
|
|
107
|
+
allAgentsList: Array<{ name: string }>,
|
|
108
|
+
team: TeamConfig,
|
|
109
|
+
workflow: WorkflowConfig,
|
|
110
|
+
runtime: { available: boolean; kind: string },
|
|
111
|
+
): Promise<SpawnResult> {
|
|
112
|
+
try {
|
|
113
|
+
const taskRec = task as Record<string, unknown>;
|
|
114
|
+
const goal = taskRec.goal as string;
|
|
115
|
+
const agentName = (taskRec.agent as string) ?? DEFAULT_AGENT;
|
|
116
|
+
const rawCwd = (taskRec.cwd as string) ?? undefined;
|
|
117
|
+
|
|
118
|
+
if (!goal) {
|
|
119
|
+
return { ok: false, error: { goal: "(missing)", error: "Each task must have a 'goal' field" } };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const agent = allAgentsList.find((a) => a.name === agentName);
|
|
123
|
+
if (!agent) {
|
|
124
|
+
return { ok: false, error: { goal, error: `Agent '${agentName}' not found` } };
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// H1: Validate taskCwd containment against project root
|
|
128
|
+
const cwdResult = resolveCwdOverride(ctx.cwd, rawCwd);
|
|
129
|
+
if (!cwdResult.ok) {
|
|
130
|
+
return { ok: false, error: { goal, error: cwdResult.error } };
|
|
131
|
+
}
|
|
132
|
+
const taskCwd = cwdResult.cwd;
|
|
133
|
+
|
|
134
|
+
const created = createRunManifest({
|
|
135
|
+
cwd: taskCwd,
|
|
136
|
+
team,
|
|
137
|
+
workflow,
|
|
138
|
+
goal,
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
appendEvent(created.manifest.eventsPath, {
|
|
142
|
+
type: "run.started",
|
|
143
|
+
runId: created.manifest.runId,
|
|
144
|
+
message: `Parallel task: ${goal}`,
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
if (runtime.available && runtime.kind === "child-process") {
|
|
148
|
+
spawnBackgroundTeamRun(created.manifest);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return { ok: true, value: { runId: created.manifest.runId, goal, agent: agentName } };
|
|
152
|
+
} catch (error) {
|
|
153
|
+
const goal = ((task as Record<string, unknown>).goal as string) ?? "(unknown)";
|
|
154
|
+
return { ok: false, error: { goal, error: error instanceof Error ? error.message : String(error) } };
|
|
155
|
+
}
|
|
156
|
+
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { TeamToolParamsValue } from "../../schema/team-tool-schema.ts";
|
|
2
2
|
import { withRunLockSync } from "../../state/locks.ts";
|
|
3
|
-
import { loadRunManifestById, saveRunTasks } from "../../state/state-store.ts";
|
|
4
|
-
import {
|
|
3
|
+
import { loadRunManifestById, saveRunTasks, updateRunStatus } from "../../state/state-store.ts";
|
|
4
|
+
import { appendEvent } from "../../state/event-log.ts";
|
|
5
|
+
import { appendMailboxMessage, updateMailboxMessageReply } from "../../state/mailbox.ts";
|
|
5
6
|
import { saveCrewAgents, recordFromTask } from "../../runtime/crew-agent-records.ts";
|
|
6
7
|
import { logInternalError } from "../../utils/internal-error.ts";
|
|
7
8
|
import type { PiTeamsToolResult } from "../tool-result.ts";
|
|
@@ -10,7 +11,7 @@ import { result, type TeamContext } from "./context.ts";
|
|
|
10
11
|
/**
|
|
11
12
|
* Handle `respond` action: send a message to a waiting (interactive) task.
|
|
12
13
|
* The task must be in "waiting" status. The message is stored in the task's
|
|
13
|
-
* mailbox and the task is
|
|
14
|
+
* mailbox and the task is re-queued for durable scheduler resume.
|
|
14
15
|
*/
|
|
15
16
|
export function handleRespond(params: TeamToolParamsValue, ctx: TeamContext): PiTeamsToolResult {
|
|
16
17
|
if (!params.runId) return result("Respond requires runId.", { action: "respond", status: "error" }, true);
|
|
@@ -20,22 +21,28 @@ export function handleRespond(params: TeamToolParamsValue, ctx: TeamContext): Pi
|
|
|
20
21
|
if (!loaded) return result(`Run '${params.runId}' not found.`, { action: "respond", status: "error" }, true);
|
|
21
22
|
|
|
22
23
|
return withRunLockSync(loaded.manifest, () => {
|
|
24
|
+
const fresh = loadRunManifestById(ctx.cwd, params.runId!);
|
|
25
|
+
if (!fresh) return result(`Run '${params.runId}' not found.`, { action: "respond", status: "error" }, true);
|
|
26
|
+
const foreignRun = typeof fresh.manifest.ownerSessionId === "string" && fresh.manifest.ownerSessionId !== ctx.sessionId;
|
|
27
|
+
if (foreignRun) return result(`Run ${fresh.manifest.runId} belongs to another session; not responding.`, { action: "respond", status: "error", runId: fresh.manifest.runId }, true);
|
|
28
|
+
|
|
23
29
|
const taskId = params.taskId;
|
|
24
30
|
const message = params.message ?? "";
|
|
25
31
|
|
|
26
32
|
const targetTasks = taskId
|
|
27
|
-
?
|
|
28
|
-
:
|
|
33
|
+
? fresh.tasks.filter((t) => t.id === taskId && t.status === "waiting")
|
|
34
|
+
: fresh.tasks.filter((t) => t.status === "waiting");
|
|
29
35
|
|
|
30
36
|
if (targetTasks.length === 0) {
|
|
31
|
-
const existing = taskId ?
|
|
37
|
+
const existing = taskId ? fresh.tasks.find((t) => t.id === taskId) : undefined;
|
|
38
|
+
const hint = " Use api operation=follow-up-agent for continuation prompts or api operation=steer-agent to interrupt active work.";
|
|
32
39
|
return result(
|
|
33
|
-
taskId
|
|
40
|
+
(taskId
|
|
34
41
|
? existing
|
|
35
42
|
? `Task '${taskId}' is ${existing.status}, not waiting.`
|
|
36
43
|
: `Task '${taskId}' not found.`
|
|
37
|
-
: `No waiting tasks in run ${
|
|
38
|
-
{ action: "respond", status: "error", runId:
|
|
44
|
+
: `No waiting tasks in run ${fresh.manifest.runId}.`) + hint,
|
|
45
|
+
{ action: "respond", status: "error", runId: fresh.manifest.runId },
|
|
39
46
|
true,
|
|
40
47
|
);
|
|
41
48
|
}
|
|
@@ -43,23 +50,37 @@ export function handleRespond(params: TeamToolParamsValue, ctx: TeamContext): Pi
|
|
|
43
50
|
const resumed = new Set(targetTasks.map((t) => t.id));
|
|
44
51
|
const mailboxIds: string[] = [];
|
|
45
52
|
for (const task of targetTasks) {
|
|
46
|
-
const mailbox = appendMailboxMessage(
|
|
53
|
+
const mailbox = appendMailboxMessage(fresh.manifest, {
|
|
47
54
|
direction: "inbox",
|
|
48
55
|
from: "leader",
|
|
49
56
|
to: task.id,
|
|
50
57
|
taskId: task.id,
|
|
51
58
|
body: message || "(resume)",
|
|
52
|
-
|
|
59
|
+
kind: "response",
|
|
60
|
+
priority: "normal",
|
|
61
|
+
deliveryMode: "next_turn",
|
|
62
|
+
data: { action: "respond", kind: "response" },
|
|
63
|
+
replyTo: params.replyTo,
|
|
64
|
+
replyFrom: params.replyFrom,
|
|
65
|
+
replyDeadline: params.replyDeadline,
|
|
53
66
|
});
|
|
54
67
|
mailboxIds.push(mailbox.id);
|
|
55
68
|
}
|
|
56
69
|
|
|
57
|
-
//
|
|
58
|
-
|
|
70
|
+
// If this respond includes a replyTo, update the original message with reply metadata.
|
|
71
|
+
if (params.replyTo) {
|
|
72
|
+
updateMailboxMessageReply(fresh.manifest, params.replyTo, message || "(resume)");
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Re-queue waiting tasks so durable scheduler/resume can pick them up again.
|
|
76
|
+
const updatedTasks = fresh.tasks.map((task) => {
|
|
59
77
|
if (!resumed.has(task.id)) return task;
|
|
60
78
|
return {
|
|
61
79
|
...task,
|
|
62
|
-
status: "
|
|
80
|
+
status: "queued" as const,
|
|
81
|
+
startedAt: undefined,
|
|
82
|
+
finishedAt: undefined,
|
|
83
|
+
error: undefined,
|
|
63
84
|
adaptive: {
|
|
64
85
|
...task.adaptive,
|
|
65
86
|
phase: "resumed",
|
|
@@ -68,17 +89,24 @@ export function handleRespond(params: TeamToolParamsValue, ctx: TeamContext): Pi
|
|
|
68
89
|
};
|
|
69
90
|
});
|
|
70
91
|
|
|
71
|
-
saveRunTasks(
|
|
92
|
+
saveRunTasks(fresh.manifest, updatedTasks);
|
|
93
|
+
let manifest = fresh.manifest;
|
|
94
|
+
if (manifest.status === "blocked" || manifest.status === "completed" || manifest.status === "failed" || manifest.status === "cancelled") {
|
|
95
|
+
manifest = updateRunStatus(manifest, "running", `Resumed ${resumed.size} waiting task(s).`);
|
|
96
|
+
}
|
|
97
|
+
for (const taskId of resumed) {
|
|
98
|
+
appendEvent(manifest.eventsPath, { type: "task.resumed", runId: manifest.runId, taskId, message: message || "Task re-queued after respond.", data: { mailboxIds } });
|
|
99
|
+
}
|
|
72
100
|
try {
|
|
73
|
-
saveCrewAgents(
|
|
101
|
+
saveCrewAgents(fresh.manifest, updatedTasks.map((task) => recordFromTask(fresh.manifest, task, "child-process")));
|
|
74
102
|
} catch (error) {
|
|
75
|
-
logInternalError("team-tool.handleRespond.crewAgents", error, `runId=${
|
|
103
|
+
logInternalError("team-tool.handleRespond.crewAgents", error, `runId=${fresh.manifest.runId}`);
|
|
76
104
|
}
|
|
77
105
|
|
|
78
106
|
const resumedIds = targetTasks.map((t) => t.id);
|
|
79
107
|
return result(
|
|
80
108
|
`Resumed ${resumedIds.length} waiting task(s): ${resumedIds.join(", ")}. Message: ${message || "(no message)"}`,
|
|
81
|
-
{ action: "respond", status: "ok", runId:
|
|
109
|
+
{ action: "respond", status: "ok", runId: fresh.manifest.runId, resumedIds, mailboxIds, intent: `responding to ${resumedIds.join(", ")} in ${fresh.manifest.runId}` },
|
|
82
110
|
);
|
|
83
111
|
});
|
|
84
112
|
}
|
|
@@ -4,13 +4,15 @@ import { allWorkflows, discoverWorkflows } from "../../workflows/discover-workfl
|
|
|
4
4
|
import { loadConfig } from "../../config/config.ts";
|
|
5
5
|
import type { TeamToolParamsValue } from "../../schema/team-tool-schema.ts";
|
|
6
6
|
import { writeArtifact } from "../../state/artifact-store.ts";
|
|
7
|
+
import { registerActiveRun, unregisterActiveRun } from "../../state/active-run-registry.ts";
|
|
7
8
|
import { createRunManifest, loadRunManifestById, updateRunStatus } from "../../state/state-store.ts";
|
|
8
9
|
import { atomicWriteJson } from "../../state/atomic-write.ts";
|
|
9
10
|
import { validateWorkflowForTeam } from "../../workflows/validate-workflow.ts";
|
|
10
11
|
import { executeTeamRun } from "../../runtime/team-runner.ts";
|
|
11
12
|
import { spawnBackgroundTeamRun } from "../../subagents/async-entry.ts";
|
|
12
13
|
import { appendEvent, readEvents } from "../../state/event-log.ts";
|
|
13
|
-
import { resolveCrewRuntime } from "../../runtime/runtime-resolver.ts";
|
|
14
|
+
import { resolveCrewRuntime, runtimeResolutionState } from "../../runtime/runtime-resolver.ts";
|
|
15
|
+
import { normalizeSkillOverride } from "../../runtime/skill-instructions.ts";
|
|
14
16
|
import { expandParallelResearchWorkflow } from "../../runtime/parallel-research.ts";
|
|
15
17
|
import { checkProcessLiveness, isActiveRunStatus } from "../../runtime/process-status.ts";
|
|
16
18
|
import { hasAsyncStartMarker } from "../../runtime/async-marker.ts";
|
|
@@ -58,6 +60,7 @@ function scheduleBackgroundEarlyExitGuard(cwd: string, runId: string, pid: numbe
|
|
|
58
60
|
export async function handleRun(params: TeamToolParamsValue, ctx: TeamContext): Promise<PiTeamsToolResult> {
|
|
59
61
|
const goal = params.goal ?? params.task;
|
|
60
62
|
if (!goal) return result("Run requires goal or task.", { action: "run", status: "error" }, true);
|
|
63
|
+
const intentPrefix = goal.length > 60 ? `${goal.slice(0, 57)}...` : goal;
|
|
61
64
|
|
|
62
65
|
const teams = allTeams(discoverTeams(ctx.cwd));
|
|
63
66
|
const workflows = allWorkflows(discoverWorkflows(ctx.cwd));
|
|
@@ -91,6 +94,7 @@ export async function handleRun(params: TeamToolParamsValue, ctx: TeamContext):
|
|
|
91
94
|
return result([`Workflow '${workflow.name}' is not valid for team '${team.name}':`, ...validationErrors.map((error) => `- ${error}`)].join("\n"), { action: "run", status: "error" }, true);
|
|
92
95
|
}
|
|
93
96
|
|
|
97
|
+
const skillOverride = normalizeSkillOverride(params.skill);
|
|
94
98
|
const { manifest, tasks, paths } = createRunManifest({
|
|
95
99
|
cwd: ctx.cwd,
|
|
96
100
|
team,
|
|
@@ -105,17 +109,35 @@ export async function handleRun(params: TeamToolParamsValue, ctx: TeamContext):
|
|
|
105
109
|
content: `${goal}\n`,
|
|
106
110
|
producer: "team-tool",
|
|
107
111
|
});
|
|
108
|
-
const updatedManifest = { ...manifest, artifacts: [goalArtifact], summary: "Run manifest created; worker execution is not implemented yet." };
|
|
112
|
+
const updatedManifest = { ...manifest, ...(skillOverride !== undefined ? { skillOverride } : {}), artifacts: [goalArtifact], summary: "Run manifest created; worker execution is not implemented yet." };
|
|
109
113
|
atomicWriteJson(paths.manifestPath, updatedManifest);
|
|
114
|
+
registerActiveRun(updatedManifest);
|
|
110
115
|
|
|
111
116
|
const loadedConfig = loadConfig(ctx.cwd);
|
|
117
|
+
const executedConfig = effectiveRunConfig(loadedConfig.config, params.config);
|
|
118
|
+
const runtime = await resolveCrewRuntime(executedConfig);
|
|
119
|
+
const runtimeResolution = runtimeResolutionState(runtime);
|
|
120
|
+
const executionManifest = { ...updatedManifest, runtimeResolution, runConfig: executedConfig, updatedAt: new Date().toISOString() };
|
|
121
|
+
atomicWriteJson(paths.manifestPath, executionManifest);
|
|
122
|
+
appendEvent(executionManifest.eventsPath, { type: "runtime.resolved", runId: executionManifest.runId, message: `Runtime resolved: ${runtime.kind} safety=${runtime.safety}`, data: { runtimeResolution } });
|
|
112
123
|
const runAsync = params.async ?? loadedConfig.config.asyncByDefault ?? false;
|
|
113
124
|
if (runAsync) {
|
|
114
|
-
|
|
115
|
-
|
|
125
|
+
if (runtime.safety === "blocked") {
|
|
126
|
+
const runningManifest = updateRunStatus(executionManifest, "running", "Checking worker runtime availability.");
|
|
127
|
+
const blocked = updateRunStatus(runningManifest, "blocked", runtime.reason ?? "Child worker execution is disabled; refusing to create no-op scaffold subagents.");
|
|
128
|
+
appendEvent(blocked.eventsPath, { type: "run.blocked", runId: blocked.runId, message: blocked.summary, data: { runtime, runtimeResolution, async: true } });
|
|
129
|
+
unregisterActiveRun(blocked.runId);
|
|
130
|
+
return result([
|
|
131
|
+
`Blocked pi-crew run ${blocked.runId}: real subagent workers are disabled.`,
|
|
132
|
+
`Runtime: ${runtime.kind} (requested ${runtime.requestedMode})`,
|
|
133
|
+
runtime.reason ?? "Child worker execution is disabled.",
|
|
134
|
+
].join("\n"), { action: "run", status: "error", runId: blocked.runId, artifactsRoot: blocked.artifactsRoot }, true);
|
|
135
|
+
}
|
|
136
|
+
const spawned = spawnBackgroundTeamRun(executionManifest);
|
|
137
|
+
const asyncManifest = { ...executionManifest, async: { pid: spawned.pid, logPath: spawned.logPath, spawnedAt: new Date().toISOString() } };
|
|
116
138
|
atomicWriteJson(paths.manifestPath, asyncManifest);
|
|
117
|
-
appendEvent(
|
|
118
|
-
scheduleBackgroundEarlyExitGuard(ctx.cwd,
|
|
139
|
+
appendEvent(executionManifest.eventsPath, { type: "async.spawned", runId: executionManifest.runId, data: { pid: spawned.pid, logPath: spawned.logPath } });
|
|
140
|
+
scheduleBackgroundEarlyExitGuard(ctx.cwd, executionManifest.runId, spawned.pid, spawned.logPath);
|
|
119
141
|
const text = [
|
|
120
142
|
`Started async pi-crew run ${updatedManifest.runId}.`,
|
|
121
143
|
`Team: ${team.name}`,
|
|
@@ -128,16 +150,32 @@ export async function handleRun(params: TeamToolParamsValue, ctx: TeamContext):
|
|
|
128
150
|
"",
|
|
129
151
|
`Check status with: team status runId=${updatedManifest.runId}`,
|
|
130
152
|
].join("\n");
|
|
131
|
-
return result(text, { action: "run", status: "ok", runId: updatedManifest.runId, artifactsRoot: updatedManifest.artifactsRoot });
|
|
153
|
+
return result(text, { action: "run", status: "ok", runId: updatedManifest.runId, artifactsRoot: updatedManifest.artifactsRoot, intent: `running ${team.name}: ${intentPrefix}` });
|
|
132
154
|
}
|
|
133
155
|
|
|
134
|
-
|
|
156
|
+
if (runtime.safety === "blocked") {
|
|
157
|
+
const runningManifest = updateRunStatus(executionManifest, "running", "Checking worker runtime availability.");
|
|
158
|
+
const blocked = updateRunStatus(runningManifest, "blocked", runtime.reason ?? "Child worker execution is disabled; refusing to create no-op scaffold subagents.");
|
|
159
|
+
appendEvent(blocked.eventsPath, { type: "run.blocked", runId: blocked.runId, message: blocked.summary, data: { runtime, runtimeResolution } });
|
|
160
|
+
unregisterActiveRun(blocked.runId);
|
|
161
|
+
return result([
|
|
162
|
+
`Blocked pi-crew run ${blocked.runId}: real subagent workers are disabled.`,
|
|
163
|
+
`Runtime: ${runtime.kind} (requested ${runtime.requestedMode})`,
|
|
164
|
+
runtime.reason ?? "Child worker execution is disabled.",
|
|
165
|
+
"",
|
|
166
|
+
"To run effective subagents, remove executeWorkers=false / PI_CREW_EXECUTE_WORKERS=0 / PI_TEAMS_EXECUTE_WORKERS=0 or set runtime.mode=child-process.",
|
|
167
|
+
"Use runtime.mode=scaffold only for explicit dry-run prompt/artifact generation.",
|
|
168
|
+
].join("\n"), { action: "run", status: "error", runId: blocked.runId, artifactsRoot: blocked.artifactsRoot }, true);
|
|
169
|
+
}
|
|
135
170
|
const executeWorkers = runtime.kind !== "scaffold";
|
|
136
|
-
const executedConfig = effectiveRunConfig(loadedConfig.config, params.config);
|
|
137
171
|
if (executeWorkers && ctx.startForegroundRun) {
|
|
138
172
|
ctx.onRunStarted?.(updatedManifest.runId);
|
|
139
173
|
ctx.startForegroundRun(async (signal) => {
|
|
140
|
-
|
|
174
|
+
try {
|
|
175
|
+
await executeTeamRun({ manifest: executionManifest, tasks, team, workflow, agents, executeWorkers, limits: executedConfig.limits, runtime, runtimeConfig: executedConfig.runtime, parentContext: buildParentContext(ctx), parentModel: ctx.model, modelRegistry: ctx.modelRegistry, modelOverride: params.model, skillOverride, signal, reliability: executedConfig.reliability, metricRegistry: ctx.metricRegistry, onJsonEvent: ctx.onJsonEvent });
|
|
176
|
+
} finally {
|
|
177
|
+
unregisterActiveRun(updatedManifest.runId);
|
|
178
|
+
}
|
|
141
179
|
}, updatedManifest.runId);
|
|
142
180
|
const text = [
|
|
143
181
|
`Started foreground pi-crew run ${updatedManifest.runId}.`,
|
|
@@ -151,9 +189,14 @@ export async function handleRun(params: TeamToolParamsValue, ctx: TeamContext):
|
|
|
151
189
|
"",
|
|
152
190
|
"The run continues in this Pi session without blocking the chat. It will be interrupted on session shutdown. Use /team-dashboard or /team-status to watch it.",
|
|
153
191
|
].join("\n");
|
|
154
|
-
return result(text, { action: "run", status: "ok", runId: updatedManifest.runId, artifactsRoot: updatedManifest.artifactsRoot });
|
|
192
|
+
return result(text, { action: "run", status: "ok", runId: updatedManifest.runId, artifactsRoot: updatedManifest.artifactsRoot, intent: `running ${team.name}: ${intentPrefix}` });
|
|
193
|
+
}
|
|
194
|
+
let executed: Awaited<ReturnType<typeof executeTeamRun>>;
|
|
195
|
+
try {
|
|
196
|
+
executed = await executeTeamRun({ manifest: executionManifest, tasks, team, workflow, agents, executeWorkers, limits: executedConfig.limits, runtime, runtimeConfig: executedConfig.runtime, parentContext: buildParentContext(ctx), parentModel: ctx.model, modelRegistry: ctx.modelRegistry, modelOverride: params.model, skillOverride, signal: ctx.signal, reliability: executedConfig.reliability, metricRegistry: ctx.metricRegistry, onJsonEvent: ctx.onJsonEvent });
|
|
197
|
+
} finally {
|
|
198
|
+
unregisterActiveRun(updatedManifest.runId);
|
|
155
199
|
}
|
|
156
|
-
const executed = await executeTeamRun({ manifest: updatedManifest, tasks, team, workflow, agents, executeWorkers, limits: executedConfig.limits, runtime, runtimeConfig: executedConfig.runtime, parentContext: buildParentContext(ctx), parentModel: ctx.model, modelRegistry: ctx.modelRegistry, modelOverride: params.model, signal: ctx.signal, reliability: executedConfig.reliability, metricRegistry: ctx.metricRegistry, onJsonEvent: ctx.onJsonEvent });
|
|
157
200
|
const text = [
|
|
158
201
|
`Created pi-crew run ${executed.manifest.runId}.`,
|
|
159
202
|
`Team: ${team.name}`,
|
|
@@ -9,6 +9,7 @@ import { readCrewAgents } from "../../runtime/crew-agent-records.ts";
|
|
|
9
9
|
import { checkProcessLiveness, isActiveRunStatus } from "../../runtime/process-status.ts";
|
|
10
10
|
import { formatTaskGraphLines, waitingReason } from "../../runtime/task-display.ts";
|
|
11
11
|
import { verifyTaskCompletion, formatOutputPreview } from "../../runtime/completion-guard.ts";
|
|
12
|
+
import { evaluateRunEffectiveness } from "../../runtime/effectiveness.ts";
|
|
12
13
|
import type { PiTeamsToolResult } from "../tool-result.ts";
|
|
13
14
|
import { result, type TeamContext } from "./context.ts";
|
|
14
15
|
|
|
@@ -51,6 +52,10 @@ export function handleStatus(params: TeamToolParamsValue, ctx: TeamContext): PiT
|
|
|
51
52
|
groupJoinLines.push(`- ${String(message.data?.partial) === "true" ? "partial" : "completed"} request=${requestId} message=${message.id} ack=${timedOut ? "timeout" : ack}`);
|
|
52
53
|
}
|
|
53
54
|
const totalUsage = aggregateUsage(tasks);
|
|
55
|
+
const completedTasks = tasks.filter((task) => task.status === "completed");
|
|
56
|
+
const effectiveness = evaluateRunEffectiveness({ manifest, tasks, executeWorkers: manifest.runtimeResolution?.kind !== "scaffold", runtimeConfig: loadConfig(ctx.cwd).config.runtime });
|
|
57
|
+
const noObservedWorkTasks = effectiveness.noObservedWorkTaskIds.map((id) => tasks.find((task) => task.id === id)).filter((task): task is typeof tasks[number] => task !== undefined);
|
|
58
|
+
const attentionTasks = effectiveness.needsAttentionTaskIds.map((id) => tasks.find((task) => task.id === id)).filter((task): task is typeof tasks[number] => task !== undefined);
|
|
54
59
|
const activeAgents = crewAgents.filter((agent) => agent.status === "running");
|
|
55
60
|
const completedAgents = crewAgents.filter((agent) => agent.status !== "running");
|
|
56
61
|
const waitingTasks = tasks.filter((task) => task.status === "queued" || task.status === "waiting");
|
|
@@ -61,6 +66,7 @@ export function handleStatus(params: TeamToolParamsValue, ctx: TeamContext): PiT
|
|
|
61
66
|
`Workflow: ${manifest.workflow ?? "(none)"}`,
|
|
62
67
|
`Status: ${manifest.status}`,
|
|
63
68
|
`Workspace mode: ${manifest.workspaceMode}`,
|
|
69
|
+
...(manifest.runtimeResolution ? [`Runtime: ${manifest.runtimeResolution.kind}`, `Runtime safety: ${manifest.runtimeResolution.safety}`, `Runtime requested: ${manifest.runtimeResolution.requestedMode}${manifest.runtimeResolution.reason ? ` (${manifest.runtimeResolution.reason})` : ""}`] : []),
|
|
64
70
|
`Goal: ${manifest.goal}`,
|
|
65
71
|
`Created: ${manifest.createdAt}`,
|
|
66
72
|
`Updated: ${manifest.updatedAt}`,
|
|
@@ -72,7 +78,12 @@ export function handleStatus(params: TeamToolParamsValue, ctx: TeamContext): PiT
|
|
|
72
78
|
"Tasks:",
|
|
73
79
|
...(tasks.length ? tasks.map((task) => `- ${task.id} [${task.status}] ${task.role} -> ${task.agent}${task.taskPacket ? ` scope=${task.taskPacket.scope}` : ""}${task.verification ? ` green=${task.verification.observedGreenLevel}/${task.verification.requiredGreenLevel}` : ""}${task.modelAttempts?.length ? ` attempts=${task.modelAttempts.length}` : ""}${task.modelRouting ? ` modelRouting=${task.modelRouting.requested ? `${task.modelRouting.requested}->` : ""}${task.modelRouting.resolved}${task.modelRouting.usedAttempt ? ` attempt=${task.modelRouting.usedAttempt + 1}` : ""}` : ""}${task.agentProgress?.activityState ? ` activityState=${task.agentProgress.activityState}` : ""}${attentionByTask.get(task.id)?.data?.reason ? ` attention=${String(attentionByTask.get(task.id)?.data?.reason)}` : ""}${task.jsonEvents !== undefined ? ` jsonEvents=${task.jsonEvents}` : ""}${task.usage ? ` usage=${JSON.stringify(task.usage)}` : ""}${task.resultArtifact ? ` result=${task.resultArtifact.path}` : ""}${task.transcriptArtifact ? ` transcript=${task.transcriptArtifact.path}` : ""}${task.worktree ? ` worktree=${task.worktree.path}` : ""}${task.error ? ` error=${task.error}` : ""}`) : ["- (none)"]),
|
|
74
80
|
`Task counts: ${[...counts.entries()].map(([status, count]) => `${status}=${count}`).join(", ") || "none"}`,
|
|
75
|
-
"
|
|
81
|
+
"Effectiveness:",
|
|
82
|
+
`- observable=${effectiveness.observable}/${Math.max(1, effectiveness.completed)} completed tasks`,
|
|
83
|
+
`- workerExecution=${effectiveness.workerExecution} guard=${effectiveness.guardMode} severity=${effectiveness.severity}`,
|
|
84
|
+
`- noObservedWork=${effectiveness.noObservedWorkTaskIds.length ? effectiveness.noObservedWorkTaskIds.join(",") : "none"}`,
|
|
85
|
+
`- needsAttention=${effectiveness.needsAttentionTaskIds.length ? effectiveness.needsAttentionTaskIds.join(",") : "none"}`,
|
|
86
|
+
"Completion verification",
|
|
76
87
|
...(tasks.filter((t) => t.status === "completed").length ? tasks.filter((t) => t.status === "completed").map((t) => {
|
|
77
88
|
const guard = verifyTaskCompletion(t, manifest);
|
|
78
89
|
return `- ${t.id} green=${guard.greenLevel}/3${guard.warnings.length ? ` warnings=[${guard.warnings.join(", ")}]` : ""}`;
|
|
@@ -95,5 +106,5 @@ export function handleStatus(params: TeamToolParamsValue, ctx: TeamContext): PiT
|
|
|
95
106
|
"Recent events:",
|
|
96
107
|
...(events.length ? events.map((event) => `- ${event.time} ${event.type}${event.taskId ? ` ${event.taskId}` : ""}${event.message ? `: ${event.message}` : ""}`) : ["- (none)"]),
|
|
97
108
|
];
|
|
98
|
-
return result(lines.join("\n"), { action: "status", status: "ok", runId: manifest.runId, artifactsRoot: manifest.artifactsRoot });
|
|
109
|
+
return result(lines.join("\n"), { action: "status", status: "ok", runId: manifest.runId, artifactsRoot: manifest.artifactsRoot, intent: `status ${manifest.runId}: ${manifest.status}` });
|
|
99
110
|
}
|
|
@@ -29,24 +29,26 @@ import type { ArtifactDescriptor, TeamRunManifest, TeamTaskState } from "../stat
|
|
|
29
29
|
import { executeTeamRun } from "../runtime/team-runner.ts";
|
|
30
30
|
import { checkProcessLiveness, isActiveRunStatus } from "../runtime/process-status.ts";
|
|
31
31
|
import { saveCrewAgents, readCrewAgents, recordFromTask } from "../runtime/crew-agent-records.ts";
|
|
32
|
-
import { resolveCrewRuntime } from "../runtime/runtime-resolver.ts";
|
|
32
|
+
import { resolveCrewRuntime, runtimeResolutionState } from "../runtime/runtime-resolver.ts";
|
|
33
33
|
import { applyAttentionState, formatActivityAge, resolveCrewControlConfig } from "../runtime/agent-control.ts";
|
|
34
34
|
import { writeForegroundInterruptRequest } from "../runtime/foreground-control.ts";
|
|
35
35
|
import { formatTaskGraphLines, waitingReason } from "../runtime/task-display.ts";
|
|
36
36
|
import { directTeamAndWorkflowFromRun } from "../runtime/direct-run.ts";
|
|
37
37
|
import { parsePiJsonOutput } from "../runtime/pi-json-output.ts";
|
|
38
38
|
import { buildParentContext, configRecord, formatScoped, result, type TeamContext } from "./team-tool/context.ts";
|
|
39
|
-
import { autonomousPatchFromConfig, configPatchFromConfig, formatAutonomyStatus } from "./team-tool/config-patch.ts";
|
|
39
|
+
import { autonomousPatchFromConfig, configPatchFromConfig, effectiveRunConfig, formatAutonomyStatus } from "./team-tool/config-patch.ts";
|
|
40
40
|
import { handleApi } from "./team-tool/api.ts";
|
|
41
41
|
import { handleRun } from "./team-tool/run.ts";
|
|
42
42
|
import { handleDoctor } from "./team-tool/doctor.ts";
|
|
43
43
|
import { handleStatus } from "./team-tool/status.ts";
|
|
44
44
|
import { handleArtifacts, handleEvents, handleSummary } from "./team-tool/inspect.ts";
|
|
45
45
|
import { handleCleanup, handleExport, handleForget, handleImport, handleImports, handlePrune, handleWorktrees } from "./team-tool/lifecycle-actions.ts";
|
|
46
|
-
import { handleCancel } from "./team-tool/cancel.ts";
|
|
46
|
+
import { handleCancel, handleRetry } from "./team-tool/cancel.ts";
|
|
47
|
+
import { handleParallel } from "./team-tool/parallel-dispatch.ts";
|
|
47
48
|
import { handleRespond } from "./team-tool/respond.ts";
|
|
48
49
|
import { handlePlan } from "./team-tool/plan.ts";
|
|
49
50
|
import { logInternalError } from "../utils/internal-error.ts";
|
|
51
|
+
import { normalizeSkillOverride } from "../runtime/skill-instructions.ts";
|
|
50
52
|
|
|
51
53
|
export type { TeamToolDetails } from "./team-tool-types.ts";
|
|
52
54
|
export type { TeamContext } from "./team-tool/context.ts";
|
|
@@ -55,7 +57,7 @@ export { handleDoctor } from "./team-tool/doctor.ts";
|
|
|
55
57
|
export { handleStatus } from "./team-tool/status.ts";
|
|
56
58
|
export { handleArtifacts, handleEvents, handleSummary } from "./team-tool/inspect.ts";
|
|
57
59
|
export { handleCleanup, handleExport, handleForget, handleImport, handleImports, handlePrune, handleWorktrees } from "./team-tool/lifecycle-actions.ts";
|
|
58
|
-
export {
|
|
60
|
+
export { handleRetry } from "./team-tool/cancel.ts";
|
|
59
61
|
export { handlePlan } from "./team-tool/plan.ts";
|
|
60
62
|
export { handleApi } from "./team-tool/api.ts";
|
|
61
63
|
|
|
@@ -176,18 +178,44 @@ export async function handleResume(params: TeamToolParamsValue, ctx: TeamContext
|
|
|
176
178
|
const workflow = direct?.workflow ?? allWorkflows(discoverWorkflows(ctx.cwd)).find((candidate) => candidate.name === loaded.manifest.workflow);
|
|
177
179
|
if (!workflow) return result(`Workflow '${loaded.manifest.workflow}' not found.`, { action: "resume", status: "error" }, true);
|
|
178
180
|
return await withRunLock(loaded.manifest, async () => {
|
|
181
|
+
const loadedConfig = loadConfig(ctx.cwd);
|
|
179
182
|
const recovered = recoverCheckpointedTasks(loaded.manifest, loaded.tasks);
|
|
180
183
|
const resumeManifest = recovered.manifest;
|
|
184
|
+
const executedConfig = effectiveRunConfig(loadedConfig.config, params.config);
|
|
185
|
+
// Preserve original manifest scaffold mode when resume has no explicit mode override
|
|
186
|
+
// AND workers are not explicitly disabled. If workers are disabled, let
|
|
187
|
+
// resolveCrewRuntime detect it and return blocked safety.
|
|
188
|
+
if (!executedConfig.runtime?.mode && resumeManifest.runtimeResolution?.safety === "explicit_dry_run") {
|
|
189
|
+
const workersDisabled = executedConfig.executeWorkers === false || process.env.PI_CREW_EXECUTE_WORKERS === "0" || process.env.PI_TEAMS_EXECUTE_WORKERS === "0";
|
|
190
|
+
if (!workersDisabled) executedConfig.runtime = { ...executedConfig.runtime, mode: "scaffold" };
|
|
191
|
+
}
|
|
192
|
+
const runtime = await resolveCrewRuntime(executedConfig);
|
|
193
|
+
const runtimeResolution = runtimeResolutionState(runtime);
|
|
194
|
+
const runtimeManifest = { ...resumeManifest, runtimeResolution, updatedAt: new Date().toISOString() };
|
|
195
|
+
saveRunManifest(runtimeManifest);
|
|
196
|
+
appendEvent(runtimeManifest.eventsPath, { type: "runtime.resolved", runId: runtimeManifest.runId, message: `Runtime resolved for resume: ${runtime.kind} safety=${runtime.safety}`, data: { runtimeResolution, action: "resume" } });
|
|
197
|
+
if (runtime.safety === "blocked") {
|
|
198
|
+
const runningManifest = updateRunStatus(runtimeManifest, "running", "Checking worker runtime availability before resume.");
|
|
199
|
+
const blocked = updateRunStatus(runningManifest, "blocked", runtime.reason ?? "Child worker execution is disabled; refusing to resume with no-op scaffold subagents.");
|
|
200
|
+
appendEvent(blocked.eventsPath, { type: "run.blocked", runId: blocked.runId, message: blocked.summary, data: { runtime, action: "resume" } });
|
|
201
|
+
return result([
|
|
202
|
+
`Blocked resume for pi-crew run ${blocked.runId}: real subagent workers are disabled.`,
|
|
203
|
+
`Runtime: ${runtime.kind} (requested ${runtime.requestedMode})`,
|
|
204
|
+
runtime.reason ?? "Child worker execution is disabled.",
|
|
205
|
+
"",
|
|
206
|
+
"To resume effective subagents, remove executeWorkers=false / PI_CREW_EXECUTE_WORKERS=0 / PI_TEAMS_EXECUTE_WORKERS=0 or set runtime.mode=child-process.",
|
|
207
|
+
"Use runtime.mode=scaffold only for explicit dry-run prompt/artifact generation.",
|
|
208
|
+
].join("\n"), { action: "resume", status: "error", runId: blocked.runId, artifactsRoot: blocked.artifactsRoot }, true);
|
|
209
|
+
}
|
|
181
210
|
const resetTasks = recovered.tasks.map((task) => task.status === "failed" || task.status === "cancelled" || task.status === "skipped" || task.status === "running" ? { ...task, status: "queued" as const, error: undefined, startedAt: undefined, finishedAt: undefined, claim: undefined } : task);
|
|
182
|
-
saveRunTasks(
|
|
183
|
-
const replay = replayPendingMailboxMessages(
|
|
184
|
-
appendEvent(
|
|
185
|
-
if (recovered.recovered.length) appendEvent(
|
|
186
|
-
if (replay.messages.length) appendEvent(
|
|
187
|
-
const loadedConfig = loadConfig(ctx.cwd);
|
|
188
|
-
const runtime = await resolveCrewRuntime(loadedConfig.config);
|
|
211
|
+
saveRunTasks(runtimeManifest, resetTasks);
|
|
212
|
+
const replay = replayPendingMailboxMessages(runtimeManifest);
|
|
213
|
+
appendEvent(runtimeManifest.eventsPath, { type: "run.resume_requested", runId: runtimeManifest.runId, data: { replayedMailboxMessages: replay.messages.length, recoveredCheckpointTasks: recovered.recovered } });
|
|
214
|
+
if (recovered.recovered.length) appendEvent(runtimeManifest.eventsPath, { type: "task.checkpoint_recovered", runId: runtimeManifest.runId, message: `Recovered ${recovered.recovered.length} task(s) from artifact-written checkpoints.`, data: { taskIds: recovered.recovered } });
|
|
215
|
+
if (replay.messages.length) appendEvent(runtimeManifest.eventsPath, { type: "mailbox.replayed", runId: runtimeManifest.runId, message: `Replayed ${replay.messages.length} pending inbox message(s).`, data: { messageIds: replay.messages.map((message) => message.id), taskIds: replay.messages.map((message) => message.taskId).filter(Boolean) } });
|
|
189
216
|
const executeWorkers = runtime.kind !== "scaffold";
|
|
190
|
-
const
|
|
217
|
+
const resumeSkillOverride = normalizeSkillOverride(params.skill) ?? runtimeManifest.skillOverride;
|
|
218
|
+
const executed = await executeTeamRun({ manifest: runtimeManifest, tasks: resetTasks, team, workflow, agents, executeWorkers, limits: executedConfig.limits, runtime, runtimeConfig: executedConfig.runtime, parentContext: buildParentContext(ctx), parentModel: ctx.model, modelRegistry: ctx.modelRegistry, modelOverride: params.model, skillOverride: resumeSkillOverride, signal: ctx.signal, reliability: executedConfig.reliability, metricRegistry: ctx.metricRegistry });
|
|
191
219
|
return result([`Resumed run ${executed.manifest.runId}.`, `Status: ${executed.manifest.status}`, `Tasks: ${executed.tasks.length}`, `Artifacts: ${executed.manifest.artifactsRoot}`].join("\n"), { action: "resume", status: executed.manifest.status === "failed" ? "error" : "ok", runId: executed.manifest.runId, artifactsRoot: executed.manifest.artifactsRoot }, executed.manifest.status === "failed");
|
|
192
220
|
});
|
|
193
221
|
}
|
|
@@ -199,7 +227,7 @@ export async function handleTeamTool(params: TeamToolParamsValue, ctx: TeamConte
|
|
|
199
227
|
case "get": return handleGet(params, ctx);
|
|
200
228
|
case "init": {
|
|
201
229
|
const cfg = configRecord(params.config);
|
|
202
|
-
const initialized = initializeProject(ctx.cwd, { copyBuiltins: cfg.copyBuiltins === true, overwrite: cfg.overwrite === true });
|
|
230
|
+
const initialized = initializeProject(ctx.cwd, { copyBuiltins: cfg.copyBuiltins === true, overwrite: cfg.overwrite === true, configScope: cfg.configScope === "project" || cfg.scope === "project" ? "project" : cfg.configScope === "none" || cfg.scope === "none" ? "none" : "global" });
|
|
203
231
|
return result([
|
|
204
232
|
"Initialized pi-crew project layout.",
|
|
205
233
|
"Directories:",
|
|
@@ -207,6 +235,7 @@ export async function handleTeamTool(params: TeamToolParamsValue, ctx: TeamConte
|
|
|
207
235
|
"Copied builtin files:",
|
|
208
236
|
...(initialized.copiedFiles.length ? initialized.copiedFiles.map((file) => `- ${file}`) : ["- (none)"]),
|
|
209
237
|
...(initialized.skippedFiles.length ? ["Skipped existing files:", ...initialized.skippedFiles.map((file) => `- ${file}`)] : []),
|
|
238
|
+
`Config: ${initialized.configPath || "(none)"} (${initialized.configScope}${initialized.configCreated ? "; created" : initialized.configSkipped ? "; already existed" : "; unchanged"})`,
|
|
210
239
|
`Gitignore: ${initialized.gitignorePath} (${initialized.gitignoreUpdated ? "updated" : "already configured"})`,
|
|
211
240
|
].join("\n"), { action: "init", status: "ok" });
|
|
212
241
|
}
|
|
@@ -274,12 +303,14 @@ export async function handleTeamTool(params: TeamToolParamsValue, ctx: TeamConte
|
|
|
274
303
|
case "import": return handleImport(params, ctx);
|
|
275
304
|
case "imports": return handleImports(params, ctx);
|
|
276
305
|
case "settings": return handleSettings(params, ctx);
|
|
277
|
-
|
|
306
|
+
case "prune": return handlePrune(params, ctx);
|
|
278
307
|
case "forget": return handleForget(params, ctx);
|
|
279
308
|
case "run": return handleRun(params, ctx);
|
|
280
309
|
case "status": return handleStatus(params, ctx);
|
|
281
310
|
case "cancel": return handleCancel(params, ctx);
|
|
311
|
+
case "retry": return handleRetry(params, ctx);
|
|
282
312
|
case "respond": return handleRespond(params, ctx);
|
|
313
|
+
case "parallel": return await handleParallel(params, ctx);
|
|
283
314
|
case "plan": return handlePlan(params, ctx);
|
|
284
315
|
case "resume": return handleResume(params, ctx);
|
|
285
316
|
case "create": return handleCreate(params, ctx);
|