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,79 +1,91 @@
|
|
|
1
|
-
import * as fs from "node:fs";
|
|
2
|
-
import type { TeamToolParamsValue } from "../../schema/team-tool-schema.ts";
|
|
3
|
-
import { appendEvent } from "../../state/event-log.ts";
|
|
4
|
-
import { loadRunManifestById } from "../../state/state-store.ts";
|
|
5
|
-
import { cleanupRunWorktrees } from "../../worktree/cleanup.ts";
|
|
6
|
-
import { listImportedRuns } from "../import-index.ts";
|
|
7
|
-
import { exportRunBundle } from "../run-export.ts";
|
|
8
|
-
import { importRunBundle } from "../run-import.ts";
|
|
9
|
-
import { pruneFinishedRuns } from "../run-maintenance.ts";
|
|
10
|
-
import type { PiTeamsToolResult } from "../tool-result.ts";
|
|
11
|
-
import { configRecord, result, type TeamContext } from "./context.ts";
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
const
|
|
16
|
-
if (
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
const
|
|
53
|
-
if (!
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
return result([`
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
export function
|
|
60
|
-
|
|
61
|
-
if (!params.confirm) return result("
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
if (!
|
|
73
|
-
const
|
|
74
|
-
if (!
|
|
75
|
-
const
|
|
76
|
-
appendEvent(loaded.manifest.eventsPath, { type: "
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
}
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import type { TeamToolParamsValue } from "../../schema/team-tool-schema.ts";
|
|
3
|
+
import { appendEvent } from "../../state/event-log.ts";
|
|
4
|
+
import { loadRunManifestById } from "../../state/state-store.ts";
|
|
5
|
+
import { cleanupRunWorktrees } from "../../worktree/cleanup.ts";
|
|
6
|
+
import { listImportedRuns } from "../import-index.ts";
|
|
7
|
+
import { exportRunBundle } from "../run-export.ts";
|
|
8
|
+
import { importRunBundle } from "../run-import.ts";
|
|
9
|
+
import { pruneFinishedRuns } from "../run-maintenance.ts";
|
|
10
|
+
import type { PiTeamsToolResult } from "../tool-result.ts";
|
|
11
|
+
import { configRecord, result, type TeamContext } from "./context.ts";
|
|
12
|
+
|
|
13
|
+
function intentFromParams(params: TeamToolParamsValue): string | undefined {
|
|
14
|
+
const cfg = configRecord(params.config);
|
|
15
|
+
const rawIntent = cfg.intent ?? cfg._intent;
|
|
16
|
+
if (typeof rawIntent !== "string") return undefined;
|
|
17
|
+
const intent = rawIntent.replace(/\s+/g, " ").trim();
|
|
18
|
+
return intent ? intent.slice(0, 500) : undefined;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function handleWorktrees(params: TeamToolParamsValue, ctx: TeamContext): PiTeamsToolResult {
|
|
22
|
+
if (!params.runId) return result("Worktrees requires runId.", { action: "worktrees", status: "error" }, true);
|
|
23
|
+
const loaded = loadRunManifestById(ctx.cwd, params.runId);
|
|
24
|
+
if (!loaded) return result(`Run '${params.runId}' not found.`, { action: "worktrees", status: "error" }, true);
|
|
25
|
+
const withWorktrees = loaded.tasks.filter((task) => task.worktree);
|
|
26
|
+
const lines = [`Worktrees for ${loaded.manifest.runId}:`, ...(withWorktrees.length ? withWorktrees.map((task) => `- ${task.id}: ${task.worktree!.path} branch=${task.worktree!.branch} reused=${task.worktree!.reused ? "true" : "false"}`) : ["- (none)"])];
|
|
27
|
+
return result(lines.join("\n"), { action: "worktrees", status: "ok", runId: loaded.manifest.runId, artifactsRoot: loaded.manifest.artifactsRoot });
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function handleImports(_params: TeamToolParamsValue, ctx: TeamContext): PiTeamsToolResult {
|
|
31
|
+
const imports = listImportedRuns(ctx.cwd);
|
|
32
|
+
const lines = ["Imported pi-crew runs:", ...(imports.length ? imports.map((entry) => `- ${entry.runId} (${entry.scope})${entry.status ? ` [${entry.status}]` : ""} ${entry.team ?? "unknown"}/${entry.workflow ?? "none"}: ${entry.goal ?? ""}\n Bundle: ${entry.bundlePath}\n Summary: ${entry.summaryPath}`) : ["- (none)"])];
|
|
33
|
+
return result(lines.join("\n"), { action: "imports", status: "ok" });
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function handleImport(params: TeamToolParamsValue, ctx: TeamContext): PiTeamsToolResult {
|
|
37
|
+
const cfg = configRecord(params.config);
|
|
38
|
+
const bundlePath = typeof cfg.path === "string" ? cfg.path : typeof cfg.bundlePath === "string" ? cfg.bundlePath : undefined;
|
|
39
|
+
if (!bundlePath) return result("Import requires config.path pointing at run-export.json.", { action: "import", status: "error" }, true);
|
|
40
|
+
const scope = cfg.scope === "user" ? "user" : "project";
|
|
41
|
+
try {
|
|
42
|
+
const imported = importRunBundle(ctx.cwd, bundlePath, scope);
|
|
43
|
+
return result([`Imported run bundle ${imported.runId}.`, `Bundle: ${imported.bundlePath}`, `Summary: ${imported.summaryPath}`].join("\n"), { action: "import", status: "ok" });
|
|
44
|
+
} catch (error) {
|
|
45
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
46
|
+
return result(`Import failed: ${message}`, { action: "import", status: "error" }, true);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function handleExport(params: TeamToolParamsValue, ctx: TeamContext): PiTeamsToolResult {
|
|
51
|
+
if (!params.runId) return result("Export requires runId.", { action: "export", status: "error" }, true);
|
|
52
|
+
const loaded = loadRunManifestById(ctx.cwd, params.runId);
|
|
53
|
+
if (!loaded) return result(`Run '${params.runId}' not found.`, { action: "export", status: "error" }, true);
|
|
54
|
+
const exported = exportRunBundle(loaded.manifest, loaded.tasks);
|
|
55
|
+
appendEvent(loaded.manifest.eventsPath, { type: "run.exported", runId: loaded.manifest.runId, data: exported });
|
|
56
|
+
return result([`Exported run ${loaded.manifest.runId}.`, `JSON: ${exported.jsonPath}`, `Markdown: ${exported.markdownPath}`].join("\n"), { action: "export", status: "ok", runId: loaded.manifest.runId, artifactsRoot: loaded.manifest.artifactsRoot });
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function handlePrune(params: TeamToolParamsValue, ctx: TeamContext): PiTeamsToolResult {
|
|
60
|
+
const keep = params.keep ?? 20;
|
|
61
|
+
if (!params.confirm) return result("prune requires confirm: true.", { action: "prune", status: "error" }, true);
|
|
62
|
+
if (keep < 0 || !Number.isInteger(keep)) return result("keep must be an integer >= 0.", { action: "prune", status: "error" }, true);
|
|
63
|
+
const intent = intentFromParams(params);
|
|
64
|
+
const pruned = pruneFinishedRuns(ctx.cwd, keep, { intent });
|
|
65
|
+
return result([`Pruned finished pi-crew runs.`, `Kept: ${pruned.kept.length}`, `Removed: ${pruned.removed.length}`, ...(pruned.auditPath ? [`Audit: ${pruned.auditPath}`] : []), ...(pruned.removed.length ? ["Removed runs:", ...pruned.removed.map((runId) => `- ${runId}`)] : [])].join("\n"), { action: "prune", status: "ok", intent });
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function handleForget(params: TeamToolParamsValue, ctx: TeamContext): PiTeamsToolResult {
|
|
69
|
+
if (!params.runId) return result("Forget requires runId.", { action: "forget", status: "error" }, true);
|
|
70
|
+
if (!params.confirm) return result("forget requires confirm: true.", { action: "forget", status: "error" }, true);
|
|
71
|
+
const loaded = loadRunManifestById(ctx.cwd, params.runId);
|
|
72
|
+
if (!loaded) return result(`Run '${params.runId}' not found.`, { action: "forget", status: "error" }, true);
|
|
73
|
+
const cleanup = cleanupRunWorktrees(loaded.manifest, { force: params.force });
|
|
74
|
+
if (cleanup.preserved.length > 0 && !params.force) return result([`Run '${params.runId}' has preserved worktrees. Use force: true to forget anyway.`, ...cleanup.preserved.map((item) => `- ${item.path}: ${item.reason}`)].join("\n"), { action: "forget", status: "error", runId: loaded.manifest.runId, artifactsRoot: loaded.manifest.artifactsRoot }, true);
|
|
75
|
+
const intent = intentFromParams(params);
|
|
76
|
+
appendEvent(loaded.manifest.eventsPath, { type: "run.forget_requested", runId: loaded.manifest.runId, message: "Run state and artifacts are being forgotten.", data: { force: params.force === true, removedWorktrees: cleanup.removed, preservedWorktrees: cleanup.preserved, intent } });
|
|
77
|
+
fs.rmSync(loaded.manifest.stateRoot, { recursive: true, force: true });
|
|
78
|
+
fs.rmSync(loaded.manifest.artifactsRoot, { recursive: true, force: true });
|
|
79
|
+
return result([`Forgot run ${loaded.manifest.runId}.`, `Removed state: ${loaded.manifest.stateRoot}`, `Removed artifacts: ${loaded.manifest.artifactsRoot}`, ...(cleanup.removed.length ? ["Removed worktrees:", ...cleanup.removed.map((item) => `- ${item}`)] : [])].join("\n"), { action: "forget", status: "ok", runId: loaded.manifest.runId, intent });
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function handleCleanup(params: TeamToolParamsValue, ctx: TeamContext): PiTeamsToolResult {
|
|
83
|
+
if (!params.runId) return result("Cleanup requires runId.", { action: "cleanup", status: "error" }, true);
|
|
84
|
+
const loaded = loadRunManifestById(ctx.cwd, params.runId);
|
|
85
|
+
if (!loaded) return result(`Run '${params.runId}' not found.`, { action: "cleanup", status: "error" }, true);
|
|
86
|
+
const cleanup = cleanupRunWorktrees(loaded.manifest, { force: params.force });
|
|
87
|
+
const intent = intentFromParams(params);
|
|
88
|
+
appendEvent(loaded.manifest.eventsPath, { type: "worktree.cleanup", runId: loaded.manifest.runId, data: { removed: cleanup.removed, preserved: cleanup.preserved, artifacts: cleanup.artifactPaths, intent } });
|
|
89
|
+
const lines = [`Worktree cleanup for ${loaded.manifest.runId}:`, "Removed:", ...(cleanup.removed.length ? cleanup.removed.map((item) => `- ${item}`) : ["- (none)"]), "Preserved:", ...(cleanup.preserved.length ? cleanup.preserved.map((item) => `- ${item.path}: ${item.reason}`) : ["- (none)"]), "Artifacts:", ...(cleanup.artifactPaths.length ? cleanup.artifactPaths.map((item) => `- ${item}`) : ["- (none)"])];
|
|
90
|
+
return result(lines.join("\n"), { action: "cleanup", status: "ok", runId: loaded.manifest.runId, artifactsRoot: loaded.manifest.artifactsRoot, intent });
|
|
91
|
+
}
|
|
@@ -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
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
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";
|
|
3
|
+
import { loadRunManifestById, saveRunTasks, updateRunStatus } from "../../state/state-store.ts";
|
|
4
|
+
import { appendEvent } from "../../state/event-log.ts";
|
|
4
5
|
import { appendMailboxMessage } from "../../state/mailbox.ts";
|
|
5
6
|
import { saveCrewAgents, recordFromTask } from "../../runtime/crew-agent-records.ts";
|
|
6
7
|
import { logInternalError } from "../../utils/internal-error.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,29 @@ 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" },
|
|
53
63
|
});
|
|
54
64
|
mailboxIds.push(mailbox.id);
|
|
55
65
|
}
|
|
56
66
|
|
|
57
|
-
//
|
|
58
|
-
const updatedTasks =
|
|
67
|
+
// Re-queue waiting tasks so durable scheduler/resume can pick them up again.
|
|
68
|
+
const updatedTasks = fresh.tasks.map((task) => {
|
|
59
69
|
if (!resumed.has(task.id)) return task;
|
|
60
70
|
return {
|
|
61
71
|
...task,
|
|
62
|
-
status: "
|
|
72
|
+
status: "queued" as const,
|
|
73
|
+
startedAt: undefined,
|
|
74
|
+
finishedAt: undefined,
|
|
75
|
+
error: undefined,
|
|
63
76
|
adaptive: {
|
|
64
77
|
...task.adaptive,
|
|
65
78
|
phase: "resumed",
|
|
@@ -68,17 +81,24 @@ export function handleRespond(params: TeamToolParamsValue, ctx: TeamContext): Pi
|
|
|
68
81
|
};
|
|
69
82
|
});
|
|
70
83
|
|
|
71
|
-
saveRunTasks(
|
|
84
|
+
saveRunTasks(fresh.manifest, updatedTasks);
|
|
85
|
+
let manifest = fresh.manifest;
|
|
86
|
+
if (manifest.status === "blocked" || manifest.status === "completed" || manifest.status === "failed" || manifest.status === "cancelled") {
|
|
87
|
+
manifest = updateRunStatus(manifest, "running", `Resumed ${resumed.size} waiting task(s).`);
|
|
88
|
+
}
|
|
89
|
+
for (const taskId of resumed) {
|
|
90
|
+
appendEvent(manifest.eventsPath, { type: "task.resumed", runId: manifest.runId, taskId, message: message || "Task re-queued after respond.", data: { mailboxIds } });
|
|
91
|
+
}
|
|
72
92
|
try {
|
|
73
|
-
saveCrewAgents(
|
|
93
|
+
saveCrewAgents(fresh.manifest, updatedTasks.map((task) => recordFromTask(fresh.manifest, task, "child-process")));
|
|
74
94
|
} catch (error) {
|
|
75
|
-
logInternalError("team-tool.handleRespond.crewAgents", error, `runId=${
|
|
95
|
+
logInternalError("team-tool.handleRespond.crewAgents", error, `runId=${fresh.manifest.runId}`);
|
|
76
96
|
}
|
|
77
97
|
|
|
78
98
|
const resumedIds = targetTasks.map((t) => t.id);
|
|
79
99
|
return result(
|
|
80
100
|
`Resumed ${resumedIds.length} waiting task(s): ${resumedIds.join(", ")}. Message: ${message || "(no message)"}`,
|
|
81
|
-
{ action: "respond", status: "ok", runId:
|
|
101
|
+
{ action: "respond", status: "ok", runId: fresh.manifest.runId, resumedIds, mailboxIds },
|
|
82
102
|
);
|
|
83
103
|
});
|
|
84
104
|
}
|
|
@@ -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";
|
|
@@ -91,6 +93,7 @@ export async function handleRun(params: TeamToolParamsValue, ctx: TeamContext):
|
|
|
91
93
|
return result([`Workflow '${workflow.name}' is not valid for team '${team.name}':`, ...validationErrors.map((error) => `- ${error}`)].join("\n"), { action: "run", status: "error" }, true);
|
|
92
94
|
}
|
|
93
95
|
|
|
96
|
+
const skillOverride = normalizeSkillOverride(params.skill);
|
|
94
97
|
const { manifest, tasks, paths } = createRunManifest({
|
|
95
98
|
cwd: ctx.cwd,
|
|
96
99
|
team,
|
|
@@ -105,17 +108,35 @@ export async function handleRun(params: TeamToolParamsValue, ctx: TeamContext):
|
|
|
105
108
|
content: `${goal}\n`,
|
|
106
109
|
producer: "team-tool",
|
|
107
110
|
});
|
|
108
|
-
const updatedManifest = { ...manifest, artifacts: [goalArtifact], summary: "Run manifest created; worker execution is not implemented yet." };
|
|
111
|
+
const updatedManifest = { ...manifest, ...(skillOverride !== undefined ? { skillOverride } : {}), artifacts: [goalArtifact], summary: "Run manifest created; worker execution is not implemented yet." };
|
|
109
112
|
atomicWriteJson(paths.manifestPath, updatedManifest);
|
|
113
|
+
registerActiveRun(updatedManifest);
|
|
110
114
|
|
|
111
115
|
const loadedConfig = loadConfig(ctx.cwd);
|
|
116
|
+
const executedConfig = effectiveRunConfig(loadedConfig.config, params.config);
|
|
117
|
+
const runtime = await resolveCrewRuntime(executedConfig);
|
|
118
|
+
const runtimeResolution = runtimeResolutionState(runtime);
|
|
119
|
+
const executionManifest = { ...updatedManifest, runtimeResolution, runConfig: executedConfig, updatedAt: new Date().toISOString() };
|
|
120
|
+
atomicWriteJson(paths.manifestPath, executionManifest);
|
|
121
|
+
appendEvent(executionManifest.eventsPath, { type: "runtime.resolved", runId: executionManifest.runId, message: `Runtime resolved: ${runtime.kind} safety=${runtime.safety}`, data: { runtimeResolution } });
|
|
112
122
|
const runAsync = params.async ?? loadedConfig.config.asyncByDefault ?? false;
|
|
113
123
|
if (runAsync) {
|
|
114
|
-
|
|
115
|
-
|
|
124
|
+
if (runtime.safety === "blocked") {
|
|
125
|
+
const runningManifest = updateRunStatus(executionManifest, "running", "Checking worker runtime availability.");
|
|
126
|
+
const blocked = updateRunStatus(runningManifest, "blocked", runtime.reason ?? "Child worker execution is disabled; refusing to create no-op scaffold subagents.");
|
|
127
|
+
appendEvent(blocked.eventsPath, { type: "run.blocked", runId: blocked.runId, message: blocked.summary, data: { runtime, runtimeResolution, async: true } });
|
|
128
|
+
unregisterActiveRun(blocked.runId);
|
|
129
|
+
return result([
|
|
130
|
+
`Blocked pi-crew run ${blocked.runId}: real subagent workers are disabled.`,
|
|
131
|
+
`Runtime: ${runtime.kind} (requested ${runtime.requestedMode})`,
|
|
132
|
+
runtime.reason ?? "Child worker execution is disabled.",
|
|
133
|
+
].join("\n"), { action: "run", status: "error", runId: blocked.runId, artifactsRoot: blocked.artifactsRoot }, true);
|
|
134
|
+
}
|
|
135
|
+
const spawned = spawnBackgroundTeamRun(executionManifest);
|
|
136
|
+
const asyncManifest = { ...executionManifest, async: { pid: spawned.pid, logPath: spawned.logPath, spawnedAt: new Date().toISOString() } };
|
|
116
137
|
atomicWriteJson(paths.manifestPath, asyncManifest);
|
|
117
|
-
appendEvent(
|
|
118
|
-
scheduleBackgroundEarlyExitGuard(ctx.cwd,
|
|
138
|
+
appendEvent(executionManifest.eventsPath, { type: "async.spawned", runId: executionManifest.runId, data: { pid: spawned.pid, logPath: spawned.logPath } });
|
|
139
|
+
scheduleBackgroundEarlyExitGuard(ctx.cwd, executionManifest.runId, spawned.pid, spawned.logPath);
|
|
119
140
|
const text = [
|
|
120
141
|
`Started async pi-crew run ${updatedManifest.runId}.`,
|
|
121
142
|
`Team: ${team.name}`,
|
|
@@ -131,13 +152,29 @@ export async function handleRun(params: TeamToolParamsValue, ctx: TeamContext):
|
|
|
131
152
|
return result(text, { action: "run", status: "ok", runId: updatedManifest.runId, artifactsRoot: updatedManifest.artifactsRoot });
|
|
132
153
|
}
|
|
133
154
|
|
|
134
|
-
|
|
155
|
+
if (runtime.safety === "blocked") {
|
|
156
|
+
const runningManifest = updateRunStatus(executionManifest, "running", "Checking worker runtime availability.");
|
|
157
|
+
const blocked = updateRunStatus(runningManifest, "blocked", runtime.reason ?? "Child worker execution is disabled; refusing to create no-op scaffold subagents.");
|
|
158
|
+
appendEvent(blocked.eventsPath, { type: "run.blocked", runId: blocked.runId, message: blocked.summary, data: { runtime, runtimeResolution } });
|
|
159
|
+
unregisterActiveRun(blocked.runId);
|
|
160
|
+
return result([
|
|
161
|
+
`Blocked pi-crew run ${blocked.runId}: real subagent workers are disabled.`,
|
|
162
|
+
`Runtime: ${runtime.kind} (requested ${runtime.requestedMode})`,
|
|
163
|
+
runtime.reason ?? "Child worker execution is disabled.",
|
|
164
|
+
"",
|
|
165
|
+
"To run effective subagents, remove executeWorkers=false / PI_CREW_EXECUTE_WORKERS=0 / PI_TEAMS_EXECUTE_WORKERS=0 or set runtime.mode=child-process.",
|
|
166
|
+
"Use runtime.mode=scaffold only for explicit dry-run prompt/artifact generation.",
|
|
167
|
+
].join("\n"), { action: "run", status: "error", runId: blocked.runId, artifactsRoot: blocked.artifactsRoot }, true);
|
|
168
|
+
}
|
|
135
169
|
const executeWorkers = runtime.kind !== "scaffold";
|
|
136
|
-
const executedConfig = effectiveRunConfig(loadedConfig.config, params.config);
|
|
137
170
|
if (executeWorkers && ctx.startForegroundRun) {
|
|
138
171
|
ctx.onRunStarted?.(updatedManifest.runId);
|
|
139
172
|
ctx.startForegroundRun(async (signal) => {
|
|
140
|
-
|
|
173
|
+
try {
|
|
174
|
+
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 });
|
|
175
|
+
} finally {
|
|
176
|
+
unregisterActiveRun(updatedManifest.runId);
|
|
177
|
+
}
|
|
141
178
|
}, updatedManifest.runId);
|
|
142
179
|
const text = [
|
|
143
180
|
`Started foreground pi-crew run ${updatedManifest.runId}.`,
|
|
@@ -153,7 +190,12 @@ export async function handleRun(params: TeamToolParamsValue, ctx: TeamContext):
|
|
|
153
190
|
].join("\n");
|
|
154
191
|
return result(text, { action: "run", status: "ok", runId: updatedManifest.runId, artifactsRoot: updatedManifest.artifactsRoot });
|
|
155
192
|
}
|
|
156
|
-
|
|
193
|
+
let executed: Awaited<ReturnType<typeof executeTeamRun>>;
|
|
194
|
+
try {
|
|
195
|
+
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 });
|
|
196
|
+
} finally {
|
|
197
|
+
unregisterActiveRun(updatedManifest.runId);
|
|
198
|
+
}
|
|
157
199
|
const text = [
|
|
158
200
|
`Created pi-crew run ${executed.manifest.runId}.`,
|
|
159
201
|
`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(", ")}]` : ""}`;
|
|
@@ -29,14 +29,14 @@ 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";
|
|
@@ -47,6 +47,7 @@ import { handleCancel } from "./team-tool/cancel.ts";
|
|
|
47
47
|
import { handleRespond } from "./team-tool/respond.ts";
|
|
48
48
|
import { handlePlan } from "./team-tool/plan.ts";
|
|
49
49
|
import { logInternalError } from "../utils/internal-error.ts";
|
|
50
|
+
import { normalizeSkillOverride } from "../runtime/skill-instructions.ts";
|
|
50
51
|
|
|
51
52
|
export type { TeamToolDetails } from "./team-tool-types.ts";
|
|
52
53
|
export type { TeamContext } from "./team-tool/context.ts";
|
|
@@ -176,18 +177,37 @@ export async function handleResume(params: TeamToolParamsValue, ctx: TeamContext
|
|
|
176
177
|
const workflow = direct?.workflow ?? allWorkflows(discoverWorkflows(ctx.cwd)).find((candidate) => candidate.name === loaded.manifest.workflow);
|
|
177
178
|
if (!workflow) return result(`Workflow '${loaded.manifest.workflow}' not found.`, { action: "resume", status: "error" }, true);
|
|
178
179
|
return await withRunLock(loaded.manifest, async () => {
|
|
180
|
+
const loadedConfig = loadConfig(ctx.cwd);
|
|
179
181
|
const recovered = recoverCheckpointedTasks(loaded.manifest, loaded.tasks);
|
|
180
182
|
const resumeManifest = recovered.manifest;
|
|
183
|
+
const executedConfig = effectiveRunConfig(loadedConfig.config, params.config);
|
|
184
|
+
const runtime = await resolveCrewRuntime(executedConfig);
|
|
185
|
+
const runtimeResolution = runtimeResolutionState(runtime);
|
|
186
|
+
const runtimeManifest = { ...resumeManifest, runtimeResolution, updatedAt: new Date().toISOString() };
|
|
187
|
+
saveRunManifest(runtimeManifest);
|
|
188
|
+
appendEvent(runtimeManifest.eventsPath, { type: "runtime.resolved", runId: runtimeManifest.runId, message: `Runtime resolved for resume: ${runtime.kind} safety=${runtime.safety}`, data: { runtimeResolution, action: "resume" } });
|
|
189
|
+
if (runtime.safety === "blocked") {
|
|
190
|
+
const runningManifest = updateRunStatus(runtimeManifest, "running", "Checking worker runtime availability before resume.");
|
|
191
|
+
const blocked = updateRunStatus(runningManifest, "blocked", runtime.reason ?? "Child worker execution is disabled; refusing to resume with no-op scaffold subagents.");
|
|
192
|
+
appendEvent(blocked.eventsPath, { type: "run.blocked", runId: blocked.runId, message: blocked.summary, data: { runtime, action: "resume" } });
|
|
193
|
+
return result([
|
|
194
|
+
`Blocked resume for pi-crew run ${blocked.runId}: real subagent workers are disabled.`,
|
|
195
|
+
`Runtime: ${runtime.kind} (requested ${runtime.requestedMode})`,
|
|
196
|
+
runtime.reason ?? "Child worker execution is disabled.",
|
|
197
|
+
"",
|
|
198
|
+
"To resume effective subagents, remove executeWorkers=false / PI_CREW_EXECUTE_WORKERS=0 / PI_TEAMS_EXECUTE_WORKERS=0 or set runtime.mode=child-process.",
|
|
199
|
+
"Use runtime.mode=scaffold only for explicit dry-run prompt/artifact generation.",
|
|
200
|
+
].join("\n"), { action: "resume", status: "error", runId: blocked.runId, artifactsRoot: blocked.artifactsRoot }, true);
|
|
201
|
+
}
|
|
181
202
|
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);
|
|
203
|
+
saveRunTasks(runtimeManifest, resetTasks);
|
|
204
|
+
const replay = replayPendingMailboxMessages(runtimeManifest);
|
|
205
|
+
appendEvent(runtimeManifest.eventsPath, { type: "run.resume_requested", runId: runtimeManifest.runId, data: { replayedMailboxMessages: replay.messages.length, recoveredCheckpointTasks: recovered.recovered } });
|
|
206
|
+
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 } });
|
|
207
|
+
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
208
|
const executeWorkers = runtime.kind !== "scaffold";
|
|
190
|
-
const
|
|
209
|
+
const resumeSkillOverride = normalizeSkillOverride(params.skill) ?? runtimeManifest.skillOverride;
|
|
210
|
+
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
211
|
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
212
|
});
|
|
193
213
|
}
|
|
@@ -199,7 +219,7 @@ export async function handleTeamTool(params: TeamToolParamsValue, ctx: TeamConte
|
|
|
199
219
|
case "get": return handleGet(params, ctx);
|
|
200
220
|
case "init": {
|
|
201
221
|
const cfg = configRecord(params.config);
|
|
202
|
-
const initialized = initializeProject(ctx.cwd, { copyBuiltins: cfg.copyBuiltins === true, overwrite: cfg.overwrite === true });
|
|
222
|
+
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
223
|
return result([
|
|
204
224
|
"Initialized pi-crew project layout.",
|
|
205
225
|
"Directories:",
|
|
@@ -207,6 +227,7 @@ export async function handleTeamTool(params: TeamToolParamsValue, ctx: TeamConte
|
|
|
207
227
|
"Copied builtin files:",
|
|
208
228
|
...(initialized.copiedFiles.length ? initialized.copiedFiles.map((file) => `- ${file}`) : ["- (none)"]),
|
|
209
229
|
...(initialized.skippedFiles.length ? ["Skipped existing files:", ...initialized.skippedFiles.map((file) => `- ${file}`)] : []),
|
|
230
|
+
`Config: ${initialized.configPath || "(none)"} (${initialized.configScope}${initialized.configCreated ? "; created" : initialized.configSkipped ? "; already existed" : "; unchanged"})`,
|
|
210
231
|
`Gitignore: ${initialized.gitignorePath} (${initialized.gitignoreUpdated ? "updated" : "already configured"})`,
|
|
211
232
|
].join("\n"), { action: "init", status: "ok" });
|
|
212
233
|
}
|