pi-crew 0.1.46 → 0.1.49
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +97 -0
- package/agents/analyst.md +11 -11
- package/agents/critic.md +11 -11
- package/agents/executor.md +11 -11
- package/agents/explorer.md +11 -11
- package/agents/planner.md +11 -11
- package/agents/reviewer.md +11 -11
- package/agents/security-reviewer.md +11 -11
- package/agents/test-engineer.md +11 -11
- package/agents/verifier.md +11 -11
- package/agents/writer.md +11 -11
- package/docs/next-upgrade-roadmap.md +117 -42
- package/docs/refactor-tasks-phase3.md +394 -394
- package/docs/refactor-tasks-phase4.md +564 -564
- package/docs/refactor-tasks-phase5.md +402 -402
- package/docs/refactor-tasks-phase6.md +662 -662
- package/docs/research/AGENT-EXECUTION-ARCHITECTURE.md +261 -0
- package/docs/research/AGENT-LIFECYCLE-COMPARISON.md +111 -0
- package/docs/research/AUDIT_OH_MY_PI.md +261 -0
- package/docs/research/AUDIT_PI_CREW.md +457 -0
- package/docs/research/CAVEMAN-DEEP-RESEARCH.md +281 -0
- package/docs/research/COMPARISON_OH_MY_PI_VS_PI_CREW.md +264 -0
- package/docs/research/DEEP-RESEARCH-PI-POWERBAR.md +343 -0
- package/docs/research/DEEP_RESEARCH_SUBAGENT_ARCHITECTURE.md +480 -0
- package/docs/research/GAP_CLOSURE_IMPLEMENTATION_PLAN.md +354 -0
- package/docs/research/IMPLEMENTATION_PLAN.md +385 -0
- package/docs/research/LIVE-SESSION-PRODUCTION-READY-PLAN.md +502 -0
- package/docs/research/OH-MY-PI-DEEP-RESEARCH-v14.7.6.md +266 -0
- package/docs/research/REMAINING-GAPS-PLAN.md +363 -0
- package/docs/research/SESSION-SUMMARY-2026-05-08.md +146 -0
- package/docs/research/UI-RESPONSIVENESS-AUDIT.md +173 -0
- package/docs/research-awesome-agent-skills-distillation.md +100 -100
- package/docs/research-extension-examples.md +297 -297
- package/docs/research-extension-system.md +324 -324
- package/docs/research-oh-my-pi-distillation.md +56 -9
- package/docs/research-optimization-plan.md +548 -548
- package/docs/research-phase10-distillation.md +198 -198
- package/docs/research-phase11-distillation.md +201 -201
- package/docs/research-pi-coding-agent.md +357 -357
- package/docs/research-source-pi-crew-reference.md +174 -174
- package/docs/runtime-flow.md +148 -148
- package/docs/source-runtime-refactor-map.md +107 -107
- package/index.ts +6 -6
- package/package.json +99 -98
- package/schema.json +8 -0
- package/skills/async-worker-recovery/SKILL.md +42 -42
- package/skills/context-artifact-hygiene/SKILL.md +52 -52
- package/skills/delegation-patterns/SKILL.md +54 -54
- package/skills/mailbox-interactive/SKILL.md +40 -40
- package/skills/model-routing-context/SKILL.md +39 -39
- package/skills/multi-perspective-review/SKILL.md +58 -58
- package/skills/observability-reliability/SKILL.md +41 -41
- package/skills/orchestration/SKILL.md +157 -0
- package/skills/ownership-session-security/SKILL.md +41 -41
- package/skills/pi-extension-lifecycle/SKILL.md +39 -39
- package/skills/requirements-to-task-packet/SKILL.md +63 -63
- package/skills/resource-discovery-config/SKILL.md +41 -41
- package/skills/runtime-state-reader/SKILL.md +44 -44
- package/skills/secure-agent-orchestration-review/SKILL.md +45 -45
- package/skills/state-mutation-locking/SKILL.md +42 -42
- package/skills/systematic-debugging/SKILL.md +67 -67
- package/skills/ui-render-performance/SKILL.md +39 -39
- package/skills/verification-before-done/SKILL.md +57 -57
- package/skills/worktree-isolation/SKILL.md +39 -39
- package/src/agents/agent-config.ts +6 -0
- package/src/agents/agent-search.ts +98 -0
- package/src/agents/agent-serializer.ts +4 -0
- package/src/agents/discover-agents.ts +17 -4
- package/src/config/config.ts +24 -0
- package/src/config/defaults.ts +11 -0
- package/src/extension/autonomous-policy.ts +26 -33
- package/src/extension/cross-extension-rpc.ts +82 -82
- package/src/extension/help.ts +1 -0
- package/src/extension/management.ts +5 -0
- package/src/extension/register.ts +58 -13
- package/src/extension/registration/commands.ts +33 -1
- package/src/extension/registration/compaction-guard.ts +125 -125
- package/src/extension/registration/team-tool.ts +6 -4
- package/src/extension/run-bundle-schema.ts +89 -89
- package/src/extension/run-index.ts +24 -18
- package/src/extension/run-maintenance.ts +68 -62
- package/src/extension/team-tool/api.ts +23 -2
- package/src/extension/team-tool/cancel.ts +86 -11
- package/src/extension/team-tool/context.ts +3 -0
- package/src/extension/team-tool/handle-settings.ts +188 -188
- package/src/extension/team-tool/inspect.ts +41 -41
- package/src/extension/team-tool/intent-policy.ts +42 -0
- package/src/extension/team-tool/lifecycle-actions.ts +47 -18
- package/src/extension/team-tool/parallel-dispatch.ts +156 -0
- package/src/extension/team-tool/plan.ts +19 -19
- package/src/extension/team-tool/respond.ts +10 -2
- package/src/extension/team-tool/run.ts +3 -2
- package/src/extension/team-tool/status.ts +1 -1
- package/src/extension/team-tool-types.ts +1 -0
- package/src/extension/team-tool.ts +13 -3
- package/src/hooks/registry.ts +61 -0
- package/src/hooks/types.ts +41 -0
- package/src/i18n.ts +184 -184
- package/src/observability/exporters/otlp-exporter.ts +77 -77
- package/src/prompt/prompt-runtime.ts +72 -72
- package/src/runtime/agent-control.ts +108 -2
- package/src/runtime/agent-memory.ts +72 -72
- package/src/runtime/agent-observability.ts +114 -114
- package/src/runtime/async-marker.ts +26 -26
- package/src/runtime/async-runner.ts +3 -1
- package/src/runtime/attention-events.ts +28 -28
- package/src/runtime/background-runner.ts +19 -0
- package/src/runtime/cancellation-token.ts +89 -0
- package/src/runtime/cancellation.ts +61 -51
- package/src/runtime/capability-inventory.ts +116 -0
- package/src/runtime/child-pi.ts +2 -1
- package/src/runtime/code-summary.ts +247 -0
- package/src/runtime/completion-guard.ts +190 -190
- package/src/runtime/crash-recovery.ts +181 -0
- package/src/runtime/crew-agent-records.ts +35 -7
- package/src/runtime/crew-agent-runtime.ts +1 -0
- package/src/runtime/custom-tools/irc-tool.ts +201 -0
- package/src/runtime/custom-tools/submit-result-tool.ts +90 -0
- package/src/runtime/delivery-coordinator.ts +3 -1
- package/src/runtime/direct-run.ts +35 -35
- package/src/runtime/effectiveness.ts +81 -76
- package/src/runtime/event-stream-bridge.ts +90 -0
- package/src/runtime/foreground-control.ts +82 -82
- package/src/runtime/green-contract.ts +46 -46
- package/src/runtime/group-join.ts +106 -106
- package/src/runtime/heartbeat-gradient.ts +28 -28
- package/src/runtime/heartbeat-watcher.ts +124 -124
- package/src/runtime/live-agent-control.ts +88 -88
- package/src/runtime/live-agent-manager.ts +78 -2
- package/src/runtime/live-control-realtime.ts +36 -36
- package/src/runtime/live-extension-bridge.ts +150 -0
- package/src/runtime/live-irc.ts +92 -0
- package/src/runtime/live-session-health.ts +100 -0
- package/src/runtime/live-session-runtime.ts +297 -7
- package/src/runtime/mcp-proxy.ts +113 -0
- package/src/runtime/notebook-helpers.ts +90 -0
- package/src/runtime/orphan-sentinel.ts +7 -0
- package/src/runtime/output-validator.ts +187 -0
- package/src/runtime/parallel-research.ts +44 -44
- package/src/runtime/parallel-utils.ts +57 -0
- package/src/runtime/parent-guard.ts +80 -0
- package/src/runtime/pi-json-output.ts +111 -111
- package/src/runtime/policy-engine.ts +79 -79
- package/src/runtime/progress-event-coalescer.ts +43 -43
- package/src/runtime/prose-compressor.ts +164 -0
- package/src/runtime/recovery-recipes.ts +74 -74
- package/src/runtime/result-extractor.ts +121 -0
- package/src/runtime/role-permission.ts +39 -39
- package/src/runtime/runtime-resolver.ts +1 -4
- package/src/runtime/semaphore.ts +131 -0
- package/src/runtime/sensitive-paths.ts +92 -0
- package/src/runtime/session-resources.ts +25 -25
- package/src/runtime/session-snapshot.ts +59 -59
- package/src/runtime/session-usage.ts +79 -79
- package/src/runtime/sidechain-output.ts +29 -29
- package/src/runtime/stream-preview.ts +177 -0
- package/src/runtime/subagent-manager.ts +3 -2
- package/src/runtime/subprocess-tool-registry.ts +67 -0
- package/src/runtime/supervisor-contact.ts +59 -59
- package/src/runtime/task-display.ts +38 -38
- package/src/runtime/task-output-context.ts +59 -9
- package/src/runtime/task-runner/capabilities.ts +78 -78
- package/src/runtime/task-runner/live-executor.ts +2 -0
- package/src/runtime/task-runner/progress.ts +119 -119
- package/src/runtime/task-runner/prompt-builder.ts +70 -8
- package/src/runtime/task-runner/prompt-pipeline.ts +64 -64
- package/src/runtime/task-runner/result-utils.ts +14 -14
- package/src/runtime/task-runner/run-projection.ts +104 -0
- package/src/runtime/task-runner/state-helpers.ts +22 -22
- package/src/runtime/task-runner.ts +75 -4
- package/src/runtime/team-runner.ts +60 -8
- package/src/runtime/worker-heartbeat.ts +21 -21
- package/src/runtime/worker-startup.ts +57 -57
- package/src/runtime/workspace-tree.ts +298 -0
- package/src/runtime/yield-handler.ts +189 -0
- package/src/schema/config-schema.ts +6 -0
- package/src/schema/team-tool-schema.ts +11 -1
- package/src/skills/discover-skills.ts +67 -0
- package/src/state/active-run-registry.ts +4 -2
- package/src/state/artifact-store.ts +4 -1
- package/src/state/atomic-write.ts +50 -1
- package/src/state/blob-store.ts +117 -0
- package/src/state/contracts.ts +1 -0
- package/src/state/event-log-rotation.ts +158 -0
- package/src/state/event-log.ts +52 -2
- package/src/state/mailbox.ts +87 -7
- package/src/state/state-store.ts +24 -4
- package/src/state/task-claims.ts +44 -44
- package/src/state/types.ts +20 -0
- package/src/state/usage.ts +29 -29
- package/src/subagents/async-entry.ts +1 -1
- package/src/subagents/index.ts +3 -3
- package/src/subagents/live/control.ts +1 -1
- package/src/subagents/live/manager.ts +1 -1
- package/src/subagents/live/realtime.ts +1 -1
- package/src/subagents/live/session-runtime.ts +1 -1
- package/src/subagents/manager.ts +1 -1
- package/src/subagents/spawn.ts +1 -1
- package/src/teams/team-serializer.ts +38 -38
- package/src/types/diff.d.ts +18 -18
- package/src/ui/agent-management-overlay.ts +144 -0
- package/src/ui/crew-footer.ts +101 -101
- package/src/ui/crew-select-list.ts +111 -111
- package/src/ui/crew-widget.ts +11 -2
- package/src/ui/dashboard-panes/cancellation-pane.ts +43 -0
- package/src/ui/dashboard-panes/capability-pane.ts +60 -0
- package/src/ui/dashboard-panes/mailbox-pane.ts +35 -11
- package/src/ui/dashboard-panes/metrics-pane.ts +34 -34
- package/src/ui/dynamic-border.ts +25 -25
- package/src/ui/layout-primitives.ts +106 -106
- package/src/ui/live-run-sidebar.ts +4 -0
- package/src/ui/loaders.ts +158 -158
- package/src/ui/powerbar-publisher.ts +77 -15
- package/src/ui/render-coalescer.ts +51 -0
- package/src/ui/render-diff.ts +119 -119
- package/src/ui/render-scheduler.ts +143 -143
- package/src/ui/run-dashboard.ts +4 -0
- package/src/ui/run-event-bus.ts +209 -0
- package/src/ui/run-snapshot-cache.ts +68 -16
- package/src/ui/snapshot-types.ts +8 -0
- package/src/ui/spinner.ts +17 -17
- package/src/ui/status-colors.ts +58 -58
- package/src/ui/syntax-highlight.ts +116 -116
- package/src/ui/transcript-entries.ts +258 -0
- package/src/utils/atomic-write.ts +33 -33
- package/src/utils/completion-dedupe.ts +63 -63
- package/src/utils/frontmatter.ts +68 -68
- package/src/utils/git.ts +262 -262
- package/src/utils/ids.ts +17 -12
- package/src/utils/incremental-reader.ts +104 -0
- package/src/utils/names.ts +27 -27
- package/src/utils/redaction.ts +44 -44
- package/src/utils/safe-paths.ts +47 -47
- package/src/utils/scan-cache.ts +137 -0
- package/src/utils/sleep.ts +32 -32
- package/src/utils/sse-parser.ts +134 -0
- package/src/utils/task-name-generator.ts +337 -0
- package/src/utils/visual.ts +33 -2
- package/src/workflows/validate-workflow.ts +40 -40
- package/src/worktree/branch-freshness.ts +45 -45
- package/src/worktree/cleanup.ts +2 -1
- package/teams/default.team.md +12 -12
- package/teams/fast-fix.team.md +11 -11
- package/teams/implementation.team.md +18 -18
- package/teams/parallel-research.team.md +14 -14
- package/teams/research.team.md +11 -11
- package/teams/review.team.md +12 -12
- package/workflows/default.workflow.md +29 -29
- package/workflows/fast-fix.workflow.md +22 -22
- package/workflows/implementation.workflow.md +38 -38
- package/workflows/parallel-research.workflow.md +46 -46
- package/workflows/research.workflow.md +22 -22
- package/workflows/review.workflow.md +30 -30
|
@@ -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,19 +1,19 @@
|
|
|
1
|
-
import { allTeams, discoverTeams } from "../../teams/discover-teams.ts";
|
|
2
|
-
import { allWorkflows, discoverWorkflows } from "../../workflows/discover-workflows.ts";
|
|
3
|
-
import { validateWorkflowForTeam } from "../../workflows/validate-workflow.ts";
|
|
4
|
-
import type { TeamToolParamsValue } from "../../schema/team-tool-schema.ts";
|
|
5
|
-
import type { PiTeamsToolResult } from "../tool-result.ts";
|
|
6
|
-
import { result, type TeamContext } from "./context.ts";
|
|
7
|
-
|
|
8
|
-
export function handlePlan(params: TeamToolParamsValue, ctx: TeamContext): PiTeamsToolResult {
|
|
9
|
-
const teamName = params.team ?? "default";
|
|
10
|
-
const team = allTeams(discoverTeams(ctx.cwd)).find((item) => item.name === teamName);
|
|
11
|
-
if (!team) return result(`Team '${teamName}' not found.`, { action: "plan", status: "error" }, true);
|
|
12
|
-
const workflowName = params.workflow ?? team.defaultWorkflow ?? "default";
|
|
13
|
-
const workflow = allWorkflows(discoverWorkflows(ctx.cwd)).find((item) => item.name === workflowName);
|
|
14
|
-
if (!workflow) return result(`Workflow '${workflowName}' not found.`, { action: "plan", status: "error" }, true);
|
|
15
|
-
const errors = validateWorkflowForTeam(workflow, team);
|
|
16
|
-
if (errors.length > 0) return result([`Workflow '${workflow.name}' is not valid for team '${team.name}':`, ...errors.map((error) => `- ${error}`)].join("\n"), { action: "plan", status: "error" }, true);
|
|
17
|
-
const lines = [`Team plan: ${team.name}`, `Workflow: ${workflow.name}`, `Goal: ${params.goal ?? params.task ?? "(not provided)"}`, "", "Steps:", ...workflow.steps.map((step, index) => `${index + 1}. ${step.id} [${step.role}]${step.dependsOn?.length ? ` after ${step.dependsOn.join(", ")}` : ""}`)];
|
|
18
|
-
return result(lines.join("\n"), { action: "plan", status: "ok" });
|
|
19
|
-
}
|
|
1
|
+
import { allTeams, discoverTeams } from "../../teams/discover-teams.ts";
|
|
2
|
+
import { allWorkflows, discoverWorkflows } from "../../workflows/discover-workflows.ts";
|
|
3
|
+
import { validateWorkflowForTeam } from "../../workflows/validate-workflow.ts";
|
|
4
|
+
import type { TeamToolParamsValue } from "../../schema/team-tool-schema.ts";
|
|
5
|
+
import type { PiTeamsToolResult } from "../tool-result.ts";
|
|
6
|
+
import { result, type TeamContext } from "./context.ts";
|
|
7
|
+
|
|
8
|
+
export function handlePlan(params: TeamToolParamsValue, ctx: TeamContext): PiTeamsToolResult {
|
|
9
|
+
const teamName = params.team ?? "default";
|
|
10
|
+
const team = allTeams(discoverTeams(ctx.cwd)).find((item) => item.name === teamName);
|
|
11
|
+
if (!team) return result(`Team '${teamName}' not found.`, { action: "plan", status: "error" }, true);
|
|
12
|
+
const workflowName = params.workflow ?? team.defaultWorkflow ?? "default";
|
|
13
|
+
const workflow = allWorkflows(discoverWorkflows(ctx.cwd)).find((item) => item.name === workflowName);
|
|
14
|
+
if (!workflow) return result(`Workflow '${workflowName}' not found.`, { action: "plan", status: "error" }, true);
|
|
15
|
+
const errors = validateWorkflowForTeam(workflow, team);
|
|
16
|
+
if (errors.length > 0) return result([`Workflow '${workflow.name}' is not valid for team '${team.name}':`, ...errors.map((error) => `- ${error}`)].join("\n"), { action: "plan", status: "error" }, true);
|
|
17
|
+
const lines = [`Team plan: ${team.name}`, `Workflow: ${workflow.name}`, `Goal: ${params.goal ?? params.task ?? "(not provided)"}`, "", "Steps:", ...workflow.steps.map((step, index) => `${index + 1}. ${step.id} [${step.role}]${step.dependsOn?.length ? ` after ${step.dependsOn.join(", ")}` : ""}`)];
|
|
18
|
+
return result(lines.join("\n"), { action: "plan", status: "ok" });
|
|
19
|
+
}
|
|
@@ -2,7 +2,7 @@ import type { TeamToolParamsValue } from "../../schema/team-tool-schema.ts";
|
|
|
2
2
|
import { withRunLockSync } from "../../state/locks.ts";
|
|
3
3
|
import { loadRunManifestById, saveRunTasks, updateRunStatus } from "../../state/state-store.ts";
|
|
4
4
|
import { appendEvent } from "../../state/event-log.ts";
|
|
5
|
-
import { appendMailboxMessage } from "../../state/mailbox.ts";
|
|
5
|
+
import { appendMailboxMessage, updateMailboxMessageReply } from "../../state/mailbox.ts";
|
|
6
6
|
import { saveCrewAgents, recordFromTask } from "../../runtime/crew-agent-records.ts";
|
|
7
7
|
import { logInternalError } from "../../utils/internal-error.ts";
|
|
8
8
|
import type { PiTeamsToolResult } from "../tool-result.ts";
|
|
@@ -60,10 +60,18 @@ export function handleRespond(params: TeamToolParamsValue, ctx: TeamContext): Pi
|
|
|
60
60
|
priority: "normal",
|
|
61
61
|
deliveryMode: "next_turn",
|
|
62
62
|
data: { action: "respond", kind: "response" },
|
|
63
|
+
replyTo: params.replyTo,
|
|
64
|
+
replyFrom: params.replyFrom,
|
|
65
|
+
replyDeadline: params.replyDeadline,
|
|
63
66
|
});
|
|
64
67
|
mailboxIds.push(mailbox.id);
|
|
65
68
|
}
|
|
66
69
|
|
|
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
|
+
|
|
67
75
|
// Re-queue waiting tasks so durable scheduler/resume can pick them up again.
|
|
68
76
|
const updatedTasks = fresh.tasks.map((task) => {
|
|
69
77
|
if (!resumed.has(task.id)) return task;
|
|
@@ -98,7 +106,7 @@ export function handleRespond(params: TeamToolParamsValue, ctx: TeamContext): Pi
|
|
|
98
106
|
const resumedIds = targetTasks.map((t) => t.id);
|
|
99
107
|
return result(
|
|
100
108
|
`Resumed ${resumedIds.length} waiting task(s): ${resumedIds.join(", ")}. Message: ${message || "(no message)"}`,
|
|
101
|
-
{ action: "respond", status: "ok", runId: fresh.manifest.runId, resumedIds, mailboxIds },
|
|
109
|
+
{ action: "respond", status: "ok", runId: fresh.manifest.runId, resumedIds, mailboxIds, intent: `responding to ${resumedIds.join(", ")} in ${fresh.manifest.runId}` },
|
|
102
110
|
);
|
|
103
111
|
});
|
|
104
112
|
}
|
|
@@ -60,6 +60,7 @@ function scheduleBackgroundEarlyExitGuard(cwd: string, runId: string, pid: numbe
|
|
|
60
60
|
export async function handleRun(params: TeamToolParamsValue, ctx: TeamContext): Promise<PiTeamsToolResult> {
|
|
61
61
|
const goal = params.goal ?? params.task;
|
|
62
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;
|
|
63
64
|
|
|
64
65
|
const teams = allTeams(discoverTeams(ctx.cwd));
|
|
65
66
|
const workflows = allWorkflows(discoverWorkflows(ctx.cwd));
|
|
@@ -149,7 +150,7 @@ export async function handleRun(params: TeamToolParamsValue, ctx: TeamContext):
|
|
|
149
150
|
"",
|
|
150
151
|
`Check status with: team status runId=${updatedManifest.runId}`,
|
|
151
152
|
].join("\n");
|
|
152
|
-
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}` });
|
|
153
154
|
}
|
|
154
155
|
|
|
155
156
|
if (runtime.safety === "blocked") {
|
|
@@ -188,7 +189,7 @@ export async function handleRun(params: TeamToolParamsValue, ctx: TeamContext):
|
|
|
188
189
|
"",
|
|
189
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.",
|
|
190
191
|
].join("\n");
|
|
191
|
-
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}` });
|
|
192
193
|
}
|
|
193
194
|
let executed: Awaited<ReturnType<typeof executeTeamRun>>;
|
|
194
195
|
try {
|
|
@@ -106,5 +106,5 @@ export function handleStatus(params: TeamToolParamsValue, ctx: TeamContext): PiT
|
|
|
106
106
|
"Recent events:",
|
|
107
107
|
...(events.length ? events.map((event) => `- ${event.time} ${event.type}${event.taskId ? ` ${event.taskId}` : ""}${event.message ? `: ${event.message}` : ""}`) : ["- (none)"]),
|
|
108
108
|
];
|
|
109
|
-
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}` });
|
|
110
110
|
}
|
|
@@ -43,7 +43,8 @@ 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";
|
|
@@ -56,7 +57,7 @@ export { handleDoctor } from "./team-tool/doctor.ts";
|
|
|
56
57
|
export { handleStatus } from "./team-tool/status.ts";
|
|
57
58
|
export { handleArtifacts, handleEvents, handleSummary } from "./team-tool/inspect.ts";
|
|
58
59
|
export { handleCleanup, handleExport, handleForget, handleImport, handleImports, handlePrune, handleWorktrees } from "./team-tool/lifecycle-actions.ts";
|
|
59
|
-
export {
|
|
60
|
+
export { handleRetry } from "./team-tool/cancel.ts";
|
|
60
61
|
export { handlePlan } from "./team-tool/plan.ts";
|
|
61
62
|
export { handleApi } from "./team-tool/api.ts";
|
|
62
63
|
|
|
@@ -181,6 +182,13 @@ export async function handleResume(params: TeamToolParamsValue, ctx: TeamContext
|
|
|
181
182
|
const recovered = recoverCheckpointedTasks(loaded.manifest, loaded.tasks);
|
|
182
183
|
const resumeManifest = recovered.manifest;
|
|
183
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
|
+
}
|
|
184
192
|
const runtime = await resolveCrewRuntime(executedConfig);
|
|
185
193
|
const runtimeResolution = runtimeResolutionState(runtime);
|
|
186
194
|
const runtimeManifest = { ...resumeManifest, runtimeResolution, updatedAt: new Date().toISOString() };
|
|
@@ -295,12 +303,14 @@ export async function handleTeamTool(params: TeamToolParamsValue, ctx: TeamConte
|
|
|
295
303
|
case "import": return handleImport(params, ctx);
|
|
296
304
|
case "imports": return handleImports(params, ctx);
|
|
297
305
|
case "settings": return handleSettings(params, ctx);
|
|
298
|
-
|
|
306
|
+
case "prune": return handlePrune(params, ctx);
|
|
299
307
|
case "forget": return handleForget(params, ctx);
|
|
300
308
|
case "run": return handleRun(params, ctx);
|
|
301
309
|
case "status": return handleStatus(params, ctx);
|
|
302
310
|
case "cancel": return handleCancel(params, ctx);
|
|
311
|
+
case "retry": return handleRetry(params, ctx);
|
|
303
312
|
case "respond": return handleRespond(params, ctx);
|
|
313
|
+
case "parallel": return await handleParallel(params, ctx);
|
|
304
314
|
case "plan": return handlePlan(params, ctx);
|
|
305
315
|
case "resume": return handleResume(params, ctx);
|
|
306
316
|
case "create": return handleCreate(params, ctx);
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { HookDefinition, HookName, HookContext, HookResult, HookExecutionReport } from "./types.ts";
|
|
2
|
+
import { appendEvent } from "../state/event-log.ts";
|
|
3
|
+
import type { TeamRunManifest } from "../state/types.ts";
|
|
4
|
+
import { runEventBus } from "../ui/run-event-bus.ts";
|
|
5
|
+
|
|
6
|
+
const registry = new Map<HookName, HookDefinition[]>();
|
|
7
|
+
|
|
8
|
+
export function registerHook(definition: HookDefinition): void {
|
|
9
|
+
const hooks = registry.get(definition.name) ?? [];
|
|
10
|
+
hooks.push(definition);
|
|
11
|
+
registry.set(definition.name, hooks);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function clearHooks(): void {
|
|
15
|
+
registry.clear();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function getHooks(name: HookName): HookDefinition[] {
|
|
19
|
+
return registry.get(name) ?? [];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export async function executeHook(name: HookName, ctx: HookContext): Promise<HookExecutionReport> {
|
|
23
|
+
const hooks = getHooks(name);
|
|
24
|
+
if (hooks.length === 0) return { hookName: name, outcome: "allow", durationMs: 0 };
|
|
25
|
+
const start = Date.now();
|
|
26
|
+
const diagnostics: string[] = [];
|
|
27
|
+
let capturedModifications: Record<string, unknown> | undefined;
|
|
28
|
+
for (const hook of hooks) {
|
|
29
|
+
try {
|
|
30
|
+
const result: HookResult = await hook.handler(ctx);
|
|
31
|
+
if (hook.mode === "blocking" && result.outcome === "block") {
|
|
32
|
+
return { hookName: name, outcome: "block", durationMs: Date.now() - start, reason: result.reason };
|
|
33
|
+
}
|
|
34
|
+
if (result.outcome === "modify" && result.data) {
|
|
35
|
+
Object.assign(ctx, result.data);
|
|
36
|
+
capturedModifications = { ...result.data };
|
|
37
|
+
}
|
|
38
|
+
} catch (error) {
|
|
39
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
40
|
+
if (hook.mode === "blocking") {
|
|
41
|
+
return { hookName: name, outcome: "block", durationMs: Date.now() - start, reason: `Hook error: ${message}` };
|
|
42
|
+
}
|
|
43
|
+
// Non-blocking hook errors are accumulated as diagnostics; continue to next hook
|
|
44
|
+
diagnostics.push(message);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
if (diagnostics.length > 0) {
|
|
48
|
+
return { hookName: name, outcome: "diagnostic", durationMs: Date.now() - start, reason: diagnostics.join("; "), modifiedData: capturedModifications };
|
|
49
|
+
}
|
|
50
|
+
return { hookName: name, outcome: "allow", durationMs: Date.now() - start, modifiedData: capturedModifications };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function appendHookEvent(manifest: TeamRunManifest, report: HookExecutionReport): void {
|
|
54
|
+
appendEvent(manifest.eventsPath, {
|
|
55
|
+
type: "hook.executed",
|
|
56
|
+
runId: manifest.runId,
|
|
57
|
+
message: `Hook ${report.hookName} completed with outcome=${report.outcome}${report.reason ? `: ${report.reason}` : ""}`,
|
|
58
|
+
data: { hookName: report.hookName, outcome: report.outcome, durationMs: report.durationMs, reason: report.reason },
|
|
59
|
+
});
|
|
60
|
+
runEventBus.emit({ type: "effectiveness_changed", runId: manifest.runId, data: { hookName: report.hookName, outcome: report.outcome } });
|
|
61
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
export type HookName =
|
|
2
|
+
| "before_run_start"
|
|
3
|
+
| "before_task_start"
|
|
4
|
+
| "task_result"
|
|
5
|
+
| "before_cancel"
|
|
6
|
+
| "before_retry"
|
|
7
|
+
| "before_forget"
|
|
8
|
+
| "before_cleanup"
|
|
9
|
+
| "before_publish"
|
|
10
|
+
| "session_before_switch"
|
|
11
|
+
| "run_recovery";
|
|
12
|
+
|
|
13
|
+
export type HookMode = "blocking" | "non_blocking";
|
|
14
|
+
export type HookOutcome = "allow" | "block" | "modify" | "diagnostic";
|
|
15
|
+
|
|
16
|
+
export interface HookContext {
|
|
17
|
+
runId: string;
|
|
18
|
+
taskId?: string;
|
|
19
|
+
cwd: string;
|
|
20
|
+
[key: string]: unknown;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface HookResult {
|
|
24
|
+
outcome: HookOutcome;
|
|
25
|
+
reason?: string;
|
|
26
|
+
data?: Record<string, unknown>;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface HookDefinition {
|
|
30
|
+
name: HookName;
|
|
31
|
+
mode: HookMode;
|
|
32
|
+
handler: (ctx: HookContext) => HookResult | Promise<HookResult>;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface HookExecutionReport {
|
|
36
|
+
hookName: HookName;
|
|
37
|
+
outcome: HookOutcome;
|
|
38
|
+
durationMs: number;
|
|
39
|
+
reason?: string;
|
|
40
|
+
modifiedData?: Record<string, unknown>;
|
|
41
|
+
}
|