pi-crew 0.2.3 → 0.2.5
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/AGENTS.md +57 -32
- package/CHANGELOG.md +466 -448
- package/LICENSE +21 -21
- package/NOTICE.md +16 -16
- package/README.md +323 -323
- package/docs/FEATURE_INTAKE.md +126 -0
- package/docs/HARNESS.md +86 -0
- package/docs/HARNESS_BACKLOG.md +41 -0
- package/docs/TEST_MATRIX.md +49 -0
- package/docs/actions-reference.md +595 -595
- package/docs/architecture.md +180 -180
- package/docs/code-review-2026-05-11.md +592 -592
- package/docs/commands-reference.md +347 -347
- package/docs/comparison-pi-subagents-vs-pi-crew.md +303 -0
- package/docs/decisions/0001-durable-state.md +41 -0
- package/docs/decisions/0002-child-process-for-async.md +42 -0
- package/docs/decisions/0003-depth-guard.md +36 -0
- package/docs/decisions/0004-execfile-over-exec.md +34 -0
- package/docs/decisions/0005-no-parameter-properties.md +49 -0
- package/docs/decisions/0006-publish-bundled-esm.md +63 -0
- package/docs/decisions/0007-active-run-binary-index.md +54 -0
- package/docs/decisions/0008-child-pi-warm-pool.md +61 -0
- package/docs/decisions/README.md +23 -0
- package/docs/followup-review-round4-2026-05-13.md +107 -0
- package/docs/implementation-plan-top3.md +333 -0
- package/docs/live-mailbox-runtime.md +36 -36
- package/docs/next-upgrade-roadmap.md +808 -808
- package/docs/oh-my-pi-research.md +509 -0
- package/docs/perf/baseline-2026-05.md +113 -0
- package/docs/perf/final-report-2026-05.md +206 -0
- package/docs/perf/sprint-1-report.md +71 -0
- package/docs/perf/sprint-2-report.md +81 -0
- package/docs/perf/sprint-2.5-report.md +53 -0
- package/docs/perf/sprint-3-report.md +36 -0
- package/docs/perf/sprint-4-report.md +47 -0
- package/docs/perf/sprint-5-report.md +51 -0
- package/docs/perf/sprint-6-report.md +94 -0
- package/docs/perf/sprint-7-report.md +74 -0
- package/docs/perf/upgrade-plan-2026-05.md +147 -0
- package/docs/pi-subagents3-deep-analysis.md +508 -0
- package/docs/product/README.md +31 -0
- package/docs/product/platform.md +27 -0
- package/docs/product/runtime-safety.md +37 -0
- package/docs/product/team-run.md +39 -0
- package/docs/product/team-tool.md +37 -0
- package/docs/publishing.md +65 -65
- package/docs/resource-formats.md +134 -134
- package/docs/runtime-analysis-child-vs-live.md +171 -0
- package/docs/runtime-flow.md +148 -148
- package/docs/runtime-migration-in-process-analysis.md +250 -0
- package/docs/stories/README.md +30 -0
- package/docs/stories/backlog.md +36 -0
- package/docs/templates/decision.md +27 -0
- package/docs/templates/story.md +44 -0
- package/docs/templates/validation-report.md +32 -0
- package/docs/usage.md +238 -238
- package/index.ts +7 -6
- package/install.mjs +65 -65
- package/package.json +107 -100
- package/schema.json +222 -222
- package/skills/child-pi-spawning/SKILL.md +213 -0
- package/skills/context-artifact-hygiene/SKILL.md +32 -0
- package/skills/event-log-tracing/SKILL.md +299 -0
- package/skills/git-master/SKILL.md +225 -24
- package/skills/live-agent-lifecycle/SKILL.md +192 -0
- package/skills/mailbox-interactive/SKILL.md +300 -19
- package/skills/model-routing-context/SKILL.md +94 -0
- package/skills/multi-perspective-review/SKILL.md +88 -0
- package/skills/read-only-explorer/SKILL.md +250 -26
- package/skills/safe-bash/SKILL.md +307 -21
- package/skills/verification-before-done/SKILL.md +11 -2
- package/skills/widget-rendering/SKILL.md +258 -0
- package/skills/workspace-isolation/SKILL.md +202 -0
- package/skills/worktree-isolation/SKILL.md +202 -18
- package/src/adapters/claude-adapter.ts +25 -25
- package/src/adapters/codex-adapter.ts +21 -21
- package/src/adapters/cursor-adapter.ts +17 -17
- package/src/adapters/export-util.ts +137 -137
- package/src/adapters/index.ts +15 -15
- package/src/adapters/registry.ts +18 -18
- package/src/adapters/types.ts +23 -23
- package/src/agents/agent-config.ts +38 -38
- package/src/agents/agent-serializer.ts +38 -38
- package/src/agents/discover-agents.ts +121 -118
- package/src/config/config.ts +740 -858
- package/src/config/defaults.ts +96 -96
- package/src/config/drift-detector.ts +211 -211
- package/src/config/markers.ts +327 -327
- package/src/config/resilient-parser.ts +109 -108
- package/src/config/suggestions.ts +74 -74
- package/src/config/types.ts +199 -0
- package/src/extension/async-notifier.ts +123 -89
- package/src/extension/autonomous-policy.ts +169 -169
- package/src/extension/cross-extension-rpc.ts +104 -104
- package/src/extension/help.ts +47 -47
- package/src/extension/import-index.ts +69 -69
- package/src/extension/management.ts +395 -382
- package/src/extension/notification-router.ts +116 -116
- package/src/extension/notification-sink.ts +51 -51
- package/src/extension/project-init.ts +168 -168
- package/src/extension/register.ts +859 -668
- package/src/extension/registration/artifact-cleanup.ts +15 -15
- package/src/extension/registration/command-utils.ts +54 -54
- package/src/extension/registration/commands.ts +559 -452
- package/src/extension/registration/compaction-guard.ts +125 -125
- package/src/extension/registration/subagent-helpers.ts +102 -102
- package/src/extension/registration/subagent-tools.ts +220 -159
- package/src/extension/registration/team-tool.ts +159 -99
- package/src/extension/registration/viewers.ts +29 -0
- package/src/extension/result-watcher.ts +128 -128
- package/src/extension/run-bundle-schema.ts +89 -89
- package/src/extension/run-export.ts +73 -73
- package/src/extension/run-import.ts +84 -84
- package/src/extension/run-index.ts +94 -94
- package/src/extension/run-maintenance.ts +142 -142
- package/src/extension/session-summary.ts +8 -8
- package/src/extension/team-manager-command.ts +96 -96
- package/src/extension/team-recommendation.ts +188 -188
- package/src/extension/team-tool/api.ts +5 -2
- package/src/extension/team-tool/cancel.ts +224 -209
- package/src/extension/team-tool/config-patch.ts +36 -36
- package/src/extension/team-tool/context.ts +60 -60
- package/src/extension/team-tool/doctor.ts +242 -242
- package/src/extension/team-tool/handle-settings.ts +421 -195
- package/src/extension/team-tool/inspect.ts +41 -41
- package/src/extension/team-tool/lifecycle-actions.ts +139 -139
- package/src/extension/team-tool/parallel-dispatch.ts +156 -156
- package/src/extension/team-tool/plan.ts +19 -19
- package/src/extension/team-tool/respond.ts +112 -111
- package/src/extension/team-tool/run.ts +246 -229
- package/src/extension/team-tool/status.ts +110 -110
- package/src/extension/team-tool-types.ts +13 -13
- package/src/extension/team-tool.ts +344 -344
- package/src/extension/tool-result.ts +16 -16
- package/src/extension/validate-resources.ts +77 -77
- package/src/hooks/registry.ts +61 -61
- package/src/hooks/types.ts +40 -40
- package/src/i18n.ts +184 -184
- package/src/observability/correlation.ts +35 -35
- package/src/observability/event-to-metric.ts +68 -68
- package/src/observability/exporters/adapter.ts +30 -30
- package/src/observability/exporters/otlp-exporter.ts +106 -92
- package/src/observability/exporters/prometheus-exporter.ts +54 -54
- package/src/observability/metric-registry.ts +87 -87
- package/src/observability/metric-retention.ts +54 -54
- package/src/observability/metric-sink.ts +81 -56
- package/src/observability/metrics-primitives.ts +167 -167
- package/src/prompt/prompt-runtime.ts +72 -72
- package/src/runtime/adaptive-plan.ts +338 -0
- package/src/runtime/agent-control.ts +169 -169
- 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 +153 -153
- package/src/runtime/attention-events.ts +28 -28
- package/src/runtime/auto-resume.ts +100 -100
- package/src/runtime/background-runner.ts +122 -89
- package/src/runtime/cancellation.ts +61 -61
- package/src/runtime/capability-inventory.ts +116 -116
- package/src/runtime/child-pi-pool.ts +68 -0
- package/src/runtime/child-pi.ts +541 -461
- package/src/runtime/code-summary.ts +247 -247
- package/src/runtime/compaction-summary.ts +271 -271
- package/src/runtime/concurrency.ts +58 -58
- package/src/runtime/crash-recovery.ts +317 -301
- package/src/runtime/crew-agent-records.ts +379 -281
- package/src/runtime/crew-agent-runtime.ts +60 -60
- package/src/runtime/cross-extension-rpc.ts +72 -0
- package/src/runtime/custom-tools/irc-tool.ts +201 -201
- package/src/runtime/custom-tools/submit-result-tool.ts +90 -90
- package/src/runtime/deadletter.ts +47 -47
- package/src/runtime/delivery-coordinator.ts +176 -176
- package/src/runtime/delta-conflict.ts +360 -360
- package/src/runtime/diagnostic-export.ts +102 -102
- package/src/runtime/direct-run.ts +35 -35
- package/src/runtime/effectiveness.ts +82 -81
- package/src/runtime/errors/crew-errors.ts +166 -0
- package/src/runtime/event-stream-bridge.ts +92 -92
- package/src/runtime/foreground-control.ts +82 -82
- package/src/runtime/green-contract.ts +46 -46
- package/src/runtime/group-join.ts +234 -106
- package/src/runtime/heartbeat-watcher.ts +145 -124
- package/src/runtime/iteration-hooks.ts +267 -267
- package/src/runtime/live-agent-control.ts +88 -88
- package/src/runtime/live-agent-manager.ts +377 -179
- package/src/runtime/live-control-realtime.ts +36 -36
- package/src/runtime/live-session-runtime.ts +676 -600
- package/src/runtime/loop-gates.ts +129 -129
- package/src/runtime/manifest-cache.ts +263 -263
- package/src/runtime/mcp-proxy.ts +113 -113
- package/src/runtime/metric-parser.ts +40 -40
- package/src/runtime/model-fallback.ts +282 -274
- package/src/runtime/model-resolver.ts +118 -0
- package/src/runtime/output-validator.ts +187 -187
- package/src/runtime/overflow-recovery.ts +175 -175
- package/src/runtime/parallel-research.ts +44 -44
- package/src/runtime/parallel-utils.ts +156 -156
- package/src/runtime/parent-guard.ts +80 -80
- package/src/runtime/phase-progress.ts +217 -217
- package/src/runtime/pi-args.ts +165 -165
- package/src/runtime/pi-json-output.ts +111 -111
- package/src/runtime/pi-spawn.ts +167 -167
- package/src/runtime/policy-engine.ts +79 -79
- package/src/runtime/post-checks.ts +125 -125
- package/src/runtime/post-exit-stdio-guard.ts +86 -86
- package/src/runtime/process-status.ts +97 -73
- package/src/runtime/progress-event-coalescer.ts +43 -43
- package/src/runtime/recovery-recipes.ts +74 -74
- package/src/runtime/retry-executor.ts +81 -81
- package/src/runtime/role-permission.ts +39 -39
- package/src/runtime/run-tracker.ts +99 -0
- package/src/runtime/runtime-policy.ts +21 -0
- package/src/runtime/runtime-resolver.ts +94 -91
- package/src/runtime/scheduler.ts +294 -0
- package/src/runtime/semaphore.ts +131 -131
- package/src/runtime/sensitive-paths.ts +92 -92
- package/src/runtime/session-usage.ts +79 -79
- package/src/runtime/settings-store.ts +103 -0
- package/src/runtime/sidechain-output.ts +29 -29
- package/src/runtime/skill-instructions.ts +222 -222
- package/src/runtime/stale-reconciler.ts +198 -189
- package/src/runtime/streaming-output.ts +47 -0
- package/src/runtime/subagent-manager.ts +404 -400
- package/src/runtime/subprocess-tool-registry.ts +67 -67
- package/src/runtime/task-display.ts +38 -38
- package/src/runtime/task-graph-scheduler.ts +122 -122
- package/src/runtime/task-graph.ts +207 -207
- package/src/runtime/task-output-context.ts +177 -177
- package/src/runtime/task-packet.ts +93 -93
- package/src/runtime/task-quality.ts +207 -207
- package/src/runtime/task-runner/capabilities.ts +78 -78
- package/src/runtime/task-runner/live-executor.ts +131 -113
- package/src/runtime/task-runner/progress.ts +119 -119
- package/src/runtime/task-runner/prompt-builder.ts +139 -139
- 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 +103 -103
- package/src/runtime/task-runner/state-helpers.ts +22 -22
- package/src/runtime/task-runner.ts +469 -459
- package/src/runtime/team-runner.ts +693 -945
- package/src/runtime/usage-tracker.ts +71 -0
- package/src/runtime/worker-heartbeat.ts +21 -21
- package/src/runtime/worker-startup.ts +57 -57
- package/src/runtime/workflow-state.ts +187 -187
- package/src/runtime/yield-handler.ts +190 -190
- package/src/schema/config-schema.ts +172 -168
- package/src/schema/team-tool-schema.ts +126 -126
- package/src/schema/validation-types.ts +151 -148
- package/src/skills/discover-skills.ts +67 -67
- package/src/skills/skill-templates.ts +374 -374
- package/src/state/active-run-registry.ts +227 -191
- package/src/state/artifact-store.ts +130 -129
- package/src/state/atomic-write.ts +262 -195
- package/src/state/blob-store.ts +116 -116
- package/src/state/contracts.ts +111 -111
- package/src/state/event-log-rotation.ts +161 -158
- package/src/state/event-log.ts +383 -303
- package/src/state/event-reconstructor.ts +217 -217
- package/src/state/jsonl-writer.ts +82 -82
- package/src/state/locks.ts +146 -146
- package/src/state/mailbox.ts +446 -405
- package/src/state/state-store.ts +364 -351
- package/src/state/task-claims.ts +44 -44
- package/src/state/types.ts +285 -285
- 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/discover-teams.ts +116 -116
- package/src/teams/team-config.ts +27 -27
- package/src/teams/team-serializer.ts +38 -38
- package/src/types/diff.d.ts +18 -18
- package/src/ui/agent-management-overlay.ts +144 -144
- package/src/ui/crew-widget.ts +487 -370
- package/src/ui/dashboard-panes/agents-pane.ts +109 -28
- package/src/ui/dashboard-panes/cancellation-pane.ts +42 -42
- package/src/ui/dashboard-panes/capability-pane.ts +59 -59
- package/src/ui/dashboard-panes/health-pane.ts +30 -30
- package/src/ui/dashboard-panes/mailbox-pane.ts +35 -35
- package/src/ui/dashboard-panes/progress-pane.ts +30 -30
- package/src/ui/dashboard-panes/transcript-pane.ts +10 -10
- package/src/ui/heartbeat-aggregator.ts +63 -63
- package/src/ui/keybinding-map.ts +97 -94
- package/src/ui/live-conversation-overlay.ts +152 -0
- package/src/ui/live-run-sidebar.ts +180 -180
- package/src/ui/mascot.ts +442 -442
- package/src/ui/overlays/agent-picker-overlay.ts +57 -57
- package/src/ui/overlays/confirm-overlay.ts +58 -58
- package/src/ui/overlays/mailbox-compose-overlay.ts +144 -144
- package/src/ui/overlays/mailbox-compose-preview.ts +63 -63
- package/src/ui/overlays/mailbox-detail-overlay.ts +122 -122
- package/src/ui/pi-ui-compat.ts +57 -57
- package/src/ui/powerbar-publisher.ts +221 -197
- package/src/ui/render-scheduler.ts +216 -143
- package/src/ui/run-action-dispatcher.ts +118 -118
- package/src/ui/run-dashboard.ts +526 -464
- package/src/ui/run-event-bus.ts +208 -208
- package/src/ui/run-snapshot-cache.ts +826 -777
- package/src/ui/settings-overlay.ts +721 -0
- package/src/ui/snapshot-types.ts +86 -70
- package/src/ui/theme-adapter.ts +190 -190
- package/src/ui/tool-progress-formatter.ts +89 -0
- package/src/ui/transcript-cache.ts +94 -94
- package/src/ui/transcript-viewer.ts +335 -335
- package/src/utils/conflict-detect.ts +662 -0
- package/src/utils/file-coalescer.ts +86 -86
- package/src/utils/frontmatter.ts +68 -68
- package/src/utils/fs-watch.ts +88 -31
- package/src/utils/gh-protocol.ts +479 -0
- package/src/utils/ids.ts +17 -17
- package/src/utils/incremental-reader.ts +104 -104
- package/src/utils/internal-error.ts +6 -6
- package/src/utils/names.ts +27 -27
- package/src/utils/paths.ts +102 -63
- package/src/utils/redaction.ts +44 -44
- package/src/utils/safe-paths.ts +47 -47
- package/src/utils/scan-cache.ts +136 -136
- package/src/utils/sse-parser.ts +134 -134
- package/src/utils/task-name-generator.ts +337 -337
- package/src/utils/timings.ts +33 -33
- package/src/utils/visual.ts +243 -198
- package/src/workflows/discover-workflows.ts +139 -139
- package/src/workflows/validate-workflow.ts +40 -40
- package/src/workflows/workflow-config.ts +26 -26
- package/src/workflows/workflow-serializer.ts +32 -32
- package/src/worktree/branch-freshness.ts +45 -45
- package/src/worktree/cleanup.ts +75 -75
- package/src/worktree/worktree-manager.ts +188 -188
- 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/tsconfig.json +19 -19
- package/workflows/default.workflow.md +30 -30
- package/workflows/fast-fix.workflow.md +23 -23
- package/workflows/implementation.workflow.md +43 -43
- package/workflows/parallel-research.workflow.md +46 -46
- package/workflows/research.workflow.md +22 -22
- package/workflows/review.workflow.md +30 -30
- package/skills/task-packet/SKILL.md +0 -28
- package/skills/verify-evidence/SKILL.md +0 -27
|
@@ -1,96 +1,96 @@
|
|
|
1
|
-
import type { ExtensionCommandContext } from "@mariozechner/pi-coding-agent";
|
|
2
|
-
import { listRuns } from "./run-index.ts";
|
|
3
|
-
// Lazy-loaded: team-tool.ts pulls in entire runtime chain.
|
|
4
|
-
import type { handleTeamTool as HandleTeamToolFn } from "./team-tool.ts";
|
|
5
|
-
let _cachedHandleTeamTool: typeof HandleTeamToolFn | undefined;
|
|
6
|
-
async function handleTeamTool(params: Parameters<typeof HandleTeamToolFn>[0], ctx: Parameters<typeof HandleTeamToolFn>[1]): Promise<Awaited<ReturnType<typeof HandleTeamToolFn>>> {
|
|
7
|
-
if (!_cachedHandleTeamTool) {
|
|
8
|
-
// LAZY: team-tool.ts pulls in the entire runtime chain.
|
|
9
|
-
const mod = await import("./team-tool.ts");
|
|
10
|
-
_cachedHandleTeamTool = mod.handleTeamTool;
|
|
11
|
-
}
|
|
12
|
-
return _cachedHandleTeamTool(params, ctx);
|
|
13
|
-
}
|
|
14
|
-
import { isToolError, textFromToolResult } from "./tool-result.ts";
|
|
15
|
-
|
|
16
|
-
async function notifyResult(ctx: ExtensionCommandContext, result: Awaited<ReturnType<typeof handleTeamTool>>): Promise<void> {
|
|
17
|
-
const text = textFromToolResult(result);
|
|
18
|
-
ctx.ui.notify(text.length > 1000 ? `${text.slice(0, 997)}...` : text, isToolError(result) ? "error" : "info");
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export async function handleTeamManagerCommand(_args: string, ctx: ExtensionCommandContext): Promise<void> {
|
|
22
|
-
const action = await ctx.ui.select("pi-crew", [
|
|
23
|
-
"List teams/workflows/agents/runs",
|
|
24
|
-
"Run team",
|
|
25
|
-
"Show run status",
|
|
26
|
-
"Cleanup run worktrees",
|
|
27
|
-
"Create routed resource",
|
|
28
|
-
"Update routed resource",
|
|
29
|
-
"Doctor",
|
|
30
|
-
]);
|
|
31
|
-
if (!action) return;
|
|
32
|
-
|
|
33
|
-
if (action.startsWith("List")) {
|
|
34
|
-
await notifyResult(ctx, await handleTeamTool({ action: "list" }, ctx));
|
|
35
|
-
return;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
if (action === "Doctor") {
|
|
39
|
-
await notifyResult(ctx, await handleTeamTool({ action: "doctor" }, ctx));
|
|
40
|
-
return;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
if (action === "Create routed resource" || action === "Update routed resource") {
|
|
44
|
-
const isUpdate = action === "Update routed resource";
|
|
45
|
-
const resource = await ctx.ui.select("Resource type", ["agent", "team"]);
|
|
46
|
-
if (resource !== "agent" && resource !== "team") return;
|
|
47
|
-
const name = await ctx.ui.input("Name", resource === "agent" ? "custom-agent" : "custom-team");
|
|
48
|
-
if (!name) return;
|
|
49
|
-
const description = await ctx.ui.input("Description", "When to use this resource");
|
|
50
|
-
if (!description) return;
|
|
51
|
-
const triggers = await ctx.ui.input("Triggers (comma-separated)", "");
|
|
52
|
-
const useWhen = await ctx.ui.input("Use when (comma-separated)", "");
|
|
53
|
-
const avoidWhen = await ctx.ui.input("Avoid when (comma-separated)", "");
|
|
54
|
-
const cost = await ctx.ui.select("Cost", ["cheap", "free", "expensive"]);
|
|
55
|
-
const category = await ctx.ui.input("Category", "custom");
|
|
56
|
-
const baseConfig = { name, description, scope: "project", triggers, useWhen, avoidWhen, cost, category };
|
|
57
|
-
if (resource === "agent") {
|
|
58
|
-
const systemPrompt = isUpdate ? undefined : `You are ${name}.`;
|
|
59
|
-
await notifyResult(ctx, await handleTeamTool({ action: isUpdate ? "update" : "create", resource, agent: name, config: { ...baseConfig, systemPrompt } }, ctx));
|
|
60
|
-
return;
|
|
61
|
-
}
|
|
62
|
-
const agent = await ctx.ui.input("Role agent", "executor");
|
|
63
|
-
await notifyResult(ctx, await handleTeamTool({ action: isUpdate ? "update" : "create", resource, team: name, config: { ...baseConfig, roles: [{ name: "executor", agent: agent || "executor" }] } }, ctx));
|
|
64
|
-
return;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
if (action === "Run team") {
|
|
68
|
-
const team = await ctx.ui.input("Team name", "default");
|
|
69
|
-
if (team === undefined) return;
|
|
70
|
-
const goal = await ctx.ui.input("Goal", "Describe the team objective");
|
|
71
|
-
if (!goal) return;
|
|
72
|
-
const asyncRun = await ctx.ui.confirm("Async run?", "Run in detached background mode?");
|
|
73
|
-
const worktree = await ctx.ui.confirm("Worktree mode?", "Use git worktrees for task workspaces? Requires a clean repo by default.");
|
|
74
|
-
await notifyResult(ctx, await handleTeamTool({ action: "run", team: team || "default", goal, async: asyncRun, workspaceMode: worktree ? "worktree" : "single" }, ctx));
|
|
75
|
-
return;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
const runs = listRuns(ctx.cwd).slice(0, 20);
|
|
79
|
-
if (runs.length === 0) {
|
|
80
|
-
ctx.ui.notify("No pi-crew runs found.", "info");
|
|
81
|
-
return;
|
|
82
|
-
}
|
|
83
|
-
const selected = await ctx.ui.select("Select run", runs.map((run) => `${run.runId} [${run.status}] ${run.team}/${run.workflow ?? "none"}`));
|
|
84
|
-
if (!selected) return;
|
|
85
|
-
const runId = selected.split(" ")[0];
|
|
86
|
-
if (!runId) return;
|
|
87
|
-
|
|
88
|
-
if (action === "Show run status") {
|
|
89
|
-
await notifyResult(ctx, await handleTeamTool({ action: "status", runId }, ctx));
|
|
90
|
-
return;
|
|
91
|
-
}
|
|
92
|
-
if (action === "Cleanup run worktrees") {
|
|
93
|
-
const force = await ctx.ui.confirm("Force cleanup?", "Force may remove dirty worktrees. Choose false to preserve dirty worktrees and capture cleanup diffs.");
|
|
94
|
-
await notifyResult(ctx, await handleTeamTool({ action: "cleanup", runId, force }, ctx));
|
|
95
|
-
}
|
|
96
|
-
}
|
|
1
|
+
import type { ExtensionCommandContext } from "@mariozechner/pi-coding-agent";
|
|
2
|
+
import { listRuns } from "./run-index.ts";
|
|
3
|
+
// Lazy-loaded: team-tool.ts pulls in entire runtime chain.
|
|
4
|
+
import type { handleTeamTool as HandleTeamToolFn } from "./team-tool.ts";
|
|
5
|
+
let _cachedHandleTeamTool: typeof HandleTeamToolFn | undefined;
|
|
6
|
+
async function handleTeamTool(params: Parameters<typeof HandleTeamToolFn>[0], ctx: Parameters<typeof HandleTeamToolFn>[1]): Promise<Awaited<ReturnType<typeof HandleTeamToolFn>>> {
|
|
7
|
+
if (!_cachedHandleTeamTool) {
|
|
8
|
+
// LAZY: team-tool.ts pulls in the entire runtime chain.
|
|
9
|
+
const mod = await import("./team-tool.ts");
|
|
10
|
+
_cachedHandleTeamTool = mod.handleTeamTool;
|
|
11
|
+
}
|
|
12
|
+
return _cachedHandleTeamTool(params, ctx);
|
|
13
|
+
}
|
|
14
|
+
import { isToolError, textFromToolResult } from "./tool-result.ts";
|
|
15
|
+
|
|
16
|
+
async function notifyResult(ctx: ExtensionCommandContext, result: Awaited<ReturnType<typeof handleTeamTool>>): Promise<void> {
|
|
17
|
+
const text = textFromToolResult(result);
|
|
18
|
+
ctx.ui.notify(text.length > 1000 ? `${text.slice(0, 997)}...` : text, isToolError(result) ? "error" : "info");
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export async function handleTeamManagerCommand(_args: string, ctx: ExtensionCommandContext): Promise<void> {
|
|
22
|
+
const action = await ctx.ui.select("pi-crew", [
|
|
23
|
+
"List teams/workflows/agents/runs",
|
|
24
|
+
"Run team",
|
|
25
|
+
"Show run status",
|
|
26
|
+
"Cleanup run worktrees",
|
|
27
|
+
"Create routed resource",
|
|
28
|
+
"Update routed resource",
|
|
29
|
+
"Doctor",
|
|
30
|
+
]);
|
|
31
|
+
if (!action) return;
|
|
32
|
+
|
|
33
|
+
if (action.startsWith("List")) {
|
|
34
|
+
await notifyResult(ctx, await handleTeamTool({ action: "list" }, ctx));
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (action === "Doctor") {
|
|
39
|
+
await notifyResult(ctx, await handleTeamTool({ action: "doctor" }, ctx));
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (action === "Create routed resource" || action === "Update routed resource") {
|
|
44
|
+
const isUpdate = action === "Update routed resource";
|
|
45
|
+
const resource = await ctx.ui.select("Resource type", ["agent", "team"]);
|
|
46
|
+
if (resource !== "agent" && resource !== "team") return;
|
|
47
|
+
const name = await ctx.ui.input("Name", resource === "agent" ? "custom-agent" : "custom-team");
|
|
48
|
+
if (!name) return;
|
|
49
|
+
const description = await ctx.ui.input("Description", "When to use this resource");
|
|
50
|
+
if (!description) return;
|
|
51
|
+
const triggers = await ctx.ui.input("Triggers (comma-separated)", "");
|
|
52
|
+
const useWhen = await ctx.ui.input("Use when (comma-separated)", "");
|
|
53
|
+
const avoidWhen = await ctx.ui.input("Avoid when (comma-separated)", "");
|
|
54
|
+
const cost = await ctx.ui.select("Cost", ["cheap", "free", "expensive"]);
|
|
55
|
+
const category = await ctx.ui.input("Category", "custom");
|
|
56
|
+
const baseConfig = { name, description, scope: "project", triggers, useWhen, avoidWhen, cost, category };
|
|
57
|
+
if (resource === "agent") {
|
|
58
|
+
const systemPrompt = isUpdate ? undefined : `You are ${name}.`;
|
|
59
|
+
await notifyResult(ctx, await handleTeamTool({ action: isUpdate ? "update" : "create", resource, agent: name, config: { ...baseConfig, systemPrompt } }, ctx));
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
const agent = await ctx.ui.input("Role agent", "executor");
|
|
63
|
+
await notifyResult(ctx, await handleTeamTool({ action: isUpdate ? "update" : "create", resource, team: name, config: { ...baseConfig, roles: [{ name: "executor", agent: agent || "executor" }] } }, ctx));
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (action === "Run team") {
|
|
68
|
+
const team = await ctx.ui.input("Team name", "default");
|
|
69
|
+
if (team === undefined) return;
|
|
70
|
+
const goal = await ctx.ui.input("Goal", "Describe the team objective");
|
|
71
|
+
if (!goal) return;
|
|
72
|
+
const asyncRun = await ctx.ui.confirm("Async run?", "Run in detached background mode?");
|
|
73
|
+
const worktree = await ctx.ui.confirm("Worktree mode?", "Use git worktrees for task workspaces? Requires a clean repo by default.");
|
|
74
|
+
await notifyResult(ctx, await handleTeamTool({ action: "run", team: team || "default", goal, async: asyncRun, workspaceMode: worktree ? "worktree" : "single" }, ctx));
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const runs = listRuns(ctx.cwd).slice(0, 20);
|
|
79
|
+
if (runs.length === 0) {
|
|
80
|
+
ctx.ui.notify("No pi-crew runs found.", "info");
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
const selected = await ctx.ui.select("Select run", runs.map((run) => `${run.runId} [${run.status}] ${run.team}/${run.workflow ?? "none"}`));
|
|
84
|
+
if (!selected) return;
|
|
85
|
+
const runId = selected.split(" ")[0];
|
|
86
|
+
if (!runId) return;
|
|
87
|
+
|
|
88
|
+
if (action === "Show run status") {
|
|
89
|
+
await notifyResult(ctx, await handleTeamTool({ action: "status", runId }, ctx));
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
if (action === "Cleanup run worktrees") {
|
|
93
|
+
const force = await ctx.ui.confirm("Force cleanup?", "Force may remove dirty worktrees. Choose false to preserve dirty worktrees and capture cleanup diffs.");
|
|
94
|
+
await notifyResult(ctx, await handleTeamTool({ action: "cleanup", runId, force }, ctx));
|
|
95
|
+
}
|
|
96
|
+
}
|
|
@@ -1,188 +1,188 @@
|
|
|
1
|
-
import { detectTeamIntent } from "./autonomous-policy.ts";
|
|
2
|
-
import type { AgentConfig } from "../agents/agent-config.ts";
|
|
3
|
-
import type { TeamConfig } from "../teams/team-config.ts";
|
|
4
|
-
import type { PiTeamsAutonomousConfig } from "../config/config.ts";
|
|
5
|
-
|
|
6
|
-
export type DecompositionStrategy = "numbered" | "bulleted" | "conjunction" | "atomic";
|
|
7
|
-
|
|
8
|
-
export interface RecommendedSubtask {
|
|
9
|
-
subject: string;
|
|
10
|
-
description: string;
|
|
11
|
-
role: string;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export interface TeamRecommendation {
|
|
15
|
-
team: string;
|
|
16
|
-
workflow: string;
|
|
17
|
-
action: "plan" | "run";
|
|
18
|
-
async: boolean;
|
|
19
|
-
workspaceMode: "single" | "worktree";
|
|
20
|
-
confidence: "low" | "medium" | "high";
|
|
21
|
-
decomposition: { strategy: DecompositionStrategy; subtasks: RecommendedSubtask[]; fanout: number };
|
|
22
|
-
reasons: string[];
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const REVIEW_TERMS = ["review", "audit", "security", "vulnerability", "diff", "pr", "pull request"];
|
|
26
|
-
const RESEARCH_TERMS = ["research", "investigate", "compare", "analyze", "document", "docs", "explain", "architecture", "đọc sâu", "source", "projects"];
|
|
27
|
-
const PARALLEL_RESEARCH_RE = /(?:đọc sâu|deep read|deep research|source audit|multiple projects|các project|pi-\*|source\/|@source)/i;
|
|
28
|
-
const FAST_FIX_TERMS = ["quick fix", "fast-fix", "small bug", "typo", "one-line", "minor", "lint"];
|
|
29
|
-
const IMPLEMENTATION_TERMS = ["implement", "refactor", "migrate", "feature", "tests", "test", "integration", "upgrade", "build", "create", "add", "fix", "update", "sửa", "thêm", "cập nhật", "kiểm thử"];
|
|
30
|
-
const RISKY_TERMS = ["migration", "refactor", "large", "multiple", "parallel", "concurrent", "risky", "critical", "nhiều file", "nhiều task"];
|
|
31
|
-
const NUMBERED_LINE_RE = /^\s*\d+[.)]\s+(.+)$/;
|
|
32
|
-
const BULLETED_LINE_RE = /^\s*[-*•]\s+(.+)$/;
|
|
33
|
-
const CONJUNCTION_SPLIT_RE = /\s+(?:and|,\s*and|,)\s+/i;
|
|
34
|
-
const FILE_REF_RE = /\b\S+\.\w{1,8}\b/g;
|
|
35
|
-
const CODE_SYMBOL_RE = /`[^`]+`/g;
|
|
36
|
-
|
|
37
|
-
function includesAny(text: string, terms: string[]): string[] {
|
|
38
|
-
return terms.filter((term) => text.includes(term));
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function wordCount(text: string): number {
|
|
42
|
-
return text.trim().split(/\s+/).filter(Boolean).length;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function recommendRole(text: string): string {
|
|
46
|
-
const lower = text.toLowerCase();
|
|
47
|
-
if (includesAny(lower, ["test", "spec", "coverage", "verify"]).length > 0) return "test-engineer";
|
|
48
|
-
if (includesAny(lower, ["security", "vulnerability", "auth", "owasp"]).length > 0) return "security-reviewer";
|
|
49
|
-
if (includesAny(lower, ["review", "audit", "diff"]).length > 0) return "reviewer";
|
|
50
|
-
if (includesAny(lower, ["doc", "readme", "guide", "write"]).length > 0) return "writer";
|
|
51
|
-
if (includesAny(lower, ["research", "investigate", "explore", "find", "trace"]).length > 0) return "explorer";
|
|
52
|
-
if (includesAny(lower, ["plan", "design", "architecture"]).length > 0) return "planner";
|
|
53
|
-
return "executor";
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function makeSubtask(text: string): RecommendedSubtask {
|
|
57
|
-
const subject = text.trim().slice(0, 80) || "Task";
|
|
58
|
-
return { subject, description: text.trim(), role: recommendRole(text) };
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
export function decomposeGoal(goal: string): { strategy: DecompositionStrategy; subtasks: RecommendedSubtask[]; fanout: number } {
|
|
62
|
-
const lines = goal.split("\n").map((line) => line.trim()).filter(Boolean);
|
|
63
|
-
const fileRefs = goal.match(FILE_REF_RE)?.length ?? 0;
|
|
64
|
-
const codeSymbols = goal.match(CODE_SYMBOL_RE)?.length ?? 0;
|
|
65
|
-
const hasParallelKeyword = /\b(?:parallel|concurrently|simultaneously|independently)\b/i.test(goal);
|
|
66
|
-
if (fileRefs >= 3 || codeSymbols >= 3 || hasParallelKeyword) {
|
|
67
|
-
const subtask = makeSubtask(goal);
|
|
68
|
-
return { strategy: "atomic", subtasks: [subtask], fanout: 1 };
|
|
69
|
-
}
|
|
70
|
-
const numberedLines = lines.map((line) => line.match(NUMBERED_LINE_RE)?.[1]).filter((line): line is string => line !== undefined);
|
|
71
|
-
if (numberedLines.length >= 2 && numberedLines.length >= lines.length - 1) {
|
|
72
|
-
const subtasks = numberedLines.map((line) => makeSubtask(line));
|
|
73
|
-
return { strategy: "numbered", subtasks, fanout: subtasks.length };
|
|
74
|
-
}
|
|
75
|
-
const bulletedLines = lines.map((line) => line.match(BULLETED_LINE_RE)?.[1]).filter((line): line is string => line !== undefined);
|
|
76
|
-
if (bulletedLines.length >= 2 && bulletedLines.length >= lines.length - 1) {
|
|
77
|
-
const subtasks = bulletedLines.map((line) => makeSubtask(line));
|
|
78
|
-
return { strategy: "bulleted", subtasks, fanout: subtasks.length };
|
|
79
|
-
}
|
|
80
|
-
if (lines.length === 1) {
|
|
81
|
-
const parts = lines[0].split(CONJUNCTION_SPLIT_RE).map((part) => part.trim()).filter(Boolean);
|
|
82
|
-
if (parts.length >= 2) {
|
|
83
|
-
const subtasks = parts.map((part) => makeSubtask(part));
|
|
84
|
-
return { strategy: "conjunction", subtasks, fanout: subtasks.length };
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
const subtask = makeSubtask(goal);
|
|
88
|
-
return { strategy: "atomic", subtasks: [subtask], fanout: 1 };
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
function metadataMatches(goal: string, values: string[] | undefined): string[] {
|
|
92
|
-
const lower = goal.toLowerCase();
|
|
93
|
-
return (values ?? []).filter((value) => lower.includes(value.toLowerCase()));
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
export function recommendTeam(goal: string, config: PiTeamsAutonomousConfig = {}, resources?: { teams?: TeamConfig[]; agents?: AgentConfig[] }): TeamRecommendation {
|
|
97
|
-
const normalized = goal.toLowerCase();
|
|
98
|
-
const intents = detectTeamIntent(goal, config);
|
|
99
|
-
const decomposition = decomposeGoal(goal);
|
|
100
|
-
const reasons: string[] = [];
|
|
101
|
-
let team: TeamRecommendation["team"] = "default";
|
|
102
|
-
let workflow: TeamRecommendation["workflow"] = "default";
|
|
103
|
-
let action: TeamRecommendation["action"] = "run";
|
|
104
|
-
let confidence: TeamRecommendation["confidence"] = "medium";
|
|
105
|
-
|
|
106
|
-
if (intents.length > 0) reasons.push(`Matched explicit intent keyword(s): ${intents.join(", ")}.`);
|
|
107
|
-
|
|
108
|
-
const metadataTeamMatches = (resources?.teams ?? [])
|
|
109
|
-
.map((candidate) => ({ team: candidate, matches: [...metadataMatches(goal, candidate.routing?.triggers), ...metadataMatches(goal, candidate.routing?.useWhen)] }))
|
|
110
|
-
.filter((candidate) => candidate.matches.length > 0)
|
|
111
|
-
.sort((a, b) => b.matches.length - a.matches.length);
|
|
112
|
-
|
|
113
|
-
const reviewMatches = includesAny(normalized, REVIEW_TERMS);
|
|
114
|
-
const researchMatches = includesAny(normalized, RESEARCH_TERMS);
|
|
115
|
-
const fastFixMatches = includesAny(normalized, FAST_FIX_TERMS);
|
|
116
|
-
const implementationMatches = includesAny(normalized, IMPLEMENTATION_TERMS);
|
|
117
|
-
const riskyMatches = includesAny(normalized, RISKY_TERMS);
|
|
118
|
-
|
|
119
|
-
if (metadataTeamMatches[0]) {
|
|
120
|
-
team = metadataTeamMatches[0].team.name as TeamRecommendation["team"];
|
|
121
|
-
workflow = (metadataTeamMatches[0].team.defaultWorkflow ?? metadataTeamMatches[0].team.name) as TeamRecommendation["workflow"];
|
|
122
|
-
confidence = "high";
|
|
123
|
-
reasons.push(`Matched team routing metadata for '${metadataTeamMatches[0].team.name}': ${metadataTeamMatches[0].matches.join(", ")}.`);
|
|
124
|
-
} else if (intents.includes("review") || reviewMatches.length >= 2 || normalized.includes("security review")) {
|
|
125
|
-
team = "review";
|
|
126
|
-
workflow = "review";
|
|
127
|
-
confidence = "high";
|
|
128
|
-
reasons.push(`Review/audit terms detected: ${reviewMatches.join(", ") || "explicit review intent"}.`);
|
|
129
|
-
} else if (PARALLEL_RESEARCH_RE.test(goal) || (researchMatches.length >= 2 && (normalized.includes("multiple") || normalized.includes("source") || normalized.includes("project") || normalized.includes("pi-")))) {
|
|
130
|
-
team = "parallel-research";
|
|
131
|
-
workflow = "parallel-research";
|
|
132
|
-
confidence = "high";
|
|
133
|
-
reasons.push("Deep/multi-source research detected; use parallel shard exploration.");
|
|
134
|
-
} else if (intents.includes("research") || (researchMatches.length > 0 && implementationMatches.length === 0)) {
|
|
135
|
-
team = "research";
|
|
136
|
-
workflow = "research";
|
|
137
|
-
confidence = researchMatches.length >= 2 ? "high" : "medium";
|
|
138
|
-
reasons.push(`Research/analysis terms detected: ${researchMatches.join(", ")}.`);
|
|
139
|
-
} else if (intents.includes("fastFix") || fastFixMatches.length > 0) {
|
|
140
|
-
team = "fast-fix";
|
|
141
|
-
workflow = "fast-fix";
|
|
142
|
-
confidence = "high";
|
|
143
|
-
reasons.push(`Small fix terms detected: ${fastFixMatches.join(", ") || "fast-fix intent"}.`);
|
|
144
|
-
} else if (intents.includes("taskList")) {
|
|
145
|
-
team = "implementation";
|
|
146
|
-
workflow = "implementation";
|
|
147
|
-
confidence = "high";
|
|
148
|
-
reasons.push(`Actionable multi-item task list detected (${decomposition.fanout} bullet${decomposition.fanout === 1 ? "" : "s"}); use coordinated implementation planning.`);
|
|
149
|
-
} else if (intents.includes("implementation") || implementationMatches.length > 0) {
|
|
150
|
-
team = "implementation";
|
|
151
|
-
workflow = "implementation";
|
|
152
|
-
confidence = implementationMatches.length >= 2 || riskyMatches.length > 0 || decomposition.fanout >= 2 ? "high" : "medium";
|
|
153
|
-
reasons.push(`Implementation terms detected: ${implementationMatches.join(", ") || "implementation intent"}.`);
|
|
154
|
-
} else {
|
|
155
|
-
action = "plan";
|
|
156
|
-
confidence = wordCount(goal) < 8 ? "low" : "medium";
|
|
157
|
-
reasons.push("No strong team-specific intent detected; start with planning/default discovery.");
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
if (decomposition.strategy !== "atomic") reasons.push(`Goal decomposes into ${decomposition.subtasks.length} subtasks using ${decomposition.strategy} parsing.`);
|
|
162
|
-
const async = config.preferAsyncForLongTasks === true && (wordCount(goal) > 24 || riskyMatches.length > 0 || implementationMatches.length >= 2 || decomposition.fanout >= 3);
|
|
163
|
-
const workspaceMode = config.allowWorktreeSuggestion === false ? "single" : (riskyMatches.length > 0 && team === "implementation" ? "worktree" : "single");
|
|
164
|
-
if (async) reasons.push("Task appears long/risky and config prefers async for long tasks.");
|
|
165
|
-
if (workspaceMode === "worktree") reasons.push(`Risk/isolation terms detected: ${riskyMatches.join(", ")}.`);
|
|
166
|
-
|
|
167
|
-
return { team, workflow, action, async, workspaceMode, confidence, decomposition, reasons };
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
export function formatRecommendation(goal: string, recommendation: TeamRecommendation): string {
|
|
171
|
-
return [
|
|
172
|
-
"pi-crew recommendation:",
|
|
173
|
-
`Goal: ${goal}`,
|
|
174
|
-
`Action: ${recommendation.action}`,
|
|
175
|
-
`Team: ${recommendation.team}`,
|
|
176
|
-
`Workflow: ${recommendation.workflow}`,
|
|
177
|
-
`Async: ${recommendation.async}`,
|
|
178
|
-
`Workspace mode: ${recommendation.workspaceMode}`,
|
|
179
|
-
`Confidence: ${recommendation.confidence}`,
|
|
180
|
-
`Decomposition: ${recommendation.decomposition.strategy} (${recommendation.decomposition.fanout} lane${recommendation.decomposition.fanout === 1 ? "" : "s"})`,
|
|
181
|
-
"Subtasks:",
|
|
182
|
-
...recommendation.decomposition.subtasks.map((task, index) => `- ${index + 1}. [${task.role}] ${task.subject}`),
|
|
183
|
-
"Reasons:",
|
|
184
|
-
...recommendation.reasons.map((reason) => `- ${reason}`),
|
|
185
|
-
"Suggested tool call:",
|
|
186
|
-
JSON.stringify({ action: recommendation.action, team: recommendation.team, workflow: recommendation.workflow, goal, async: recommendation.async, workspaceMode: recommendation.workspaceMode }, null, 2),
|
|
187
|
-
].join("\n");
|
|
188
|
-
}
|
|
1
|
+
import { detectTeamIntent } from "./autonomous-policy.ts";
|
|
2
|
+
import type { AgentConfig } from "../agents/agent-config.ts";
|
|
3
|
+
import type { TeamConfig } from "../teams/team-config.ts";
|
|
4
|
+
import type { PiTeamsAutonomousConfig } from "../config/config.ts";
|
|
5
|
+
|
|
6
|
+
export type DecompositionStrategy = "numbered" | "bulleted" | "conjunction" | "atomic";
|
|
7
|
+
|
|
8
|
+
export interface RecommendedSubtask {
|
|
9
|
+
subject: string;
|
|
10
|
+
description: string;
|
|
11
|
+
role: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface TeamRecommendation {
|
|
15
|
+
team: string;
|
|
16
|
+
workflow: string;
|
|
17
|
+
action: "plan" | "run";
|
|
18
|
+
async: boolean;
|
|
19
|
+
workspaceMode: "single" | "worktree";
|
|
20
|
+
confidence: "low" | "medium" | "high";
|
|
21
|
+
decomposition: { strategy: DecompositionStrategy; subtasks: RecommendedSubtask[]; fanout: number };
|
|
22
|
+
reasons: string[];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const REVIEW_TERMS = ["review", "audit", "security", "vulnerability", "diff", "pr", "pull request"];
|
|
26
|
+
const RESEARCH_TERMS = ["research", "investigate", "compare", "analyze", "document", "docs", "explain", "architecture", "đọc sâu", "source", "projects"];
|
|
27
|
+
const PARALLEL_RESEARCH_RE = /(?:đọc sâu|deep read|deep research|source audit|multiple projects|các project|pi-\*|source\/|@source)/i;
|
|
28
|
+
const FAST_FIX_TERMS = ["quick fix", "fast-fix", "small bug", "typo", "one-line", "minor", "lint"];
|
|
29
|
+
const IMPLEMENTATION_TERMS = ["implement", "refactor", "migrate", "feature", "tests", "test", "integration", "upgrade", "build", "create", "add", "fix", "update", "sửa", "thêm", "cập nhật", "kiểm thử"];
|
|
30
|
+
const RISKY_TERMS = ["migration", "refactor", "large", "multiple", "parallel", "concurrent", "risky", "critical", "nhiều file", "nhiều task"];
|
|
31
|
+
const NUMBERED_LINE_RE = /^\s*\d+[.)]\s+(.+)$/;
|
|
32
|
+
const BULLETED_LINE_RE = /^\s*[-*•]\s+(.+)$/;
|
|
33
|
+
const CONJUNCTION_SPLIT_RE = /\s+(?:and|,\s*and|,)\s+/i;
|
|
34
|
+
const FILE_REF_RE = /\b\S+\.\w{1,8}\b/g;
|
|
35
|
+
const CODE_SYMBOL_RE = /`[^`]+`/g;
|
|
36
|
+
|
|
37
|
+
function includesAny(text: string, terms: string[]): string[] {
|
|
38
|
+
return terms.filter((term) => text.includes(term));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function wordCount(text: string): number {
|
|
42
|
+
return text.trim().split(/\s+/).filter(Boolean).length;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function recommendRole(text: string): string {
|
|
46
|
+
const lower = text.toLowerCase();
|
|
47
|
+
if (includesAny(lower, ["test", "spec", "coverage", "verify"]).length > 0) return "test-engineer";
|
|
48
|
+
if (includesAny(lower, ["security", "vulnerability", "auth", "owasp"]).length > 0) return "security-reviewer";
|
|
49
|
+
if (includesAny(lower, ["review", "audit", "diff"]).length > 0) return "reviewer";
|
|
50
|
+
if (includesAny(lower, ["doc", "readme", "guide", "write"]).length > 0) return "writer";
|
|
51
|
+
if (includesAny(lower, ["research", "investigate", "explore", "find", "trace"]).length > 0) return "explorer";
|
|
52
|
+
if (includesAny(lower, ["plan", "design", "architecture"]).length > 0) return "planner";
|
|
53
|
+
return "executor";
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function makeSubtask(text: string): RecommendedSubtask {
|
|
57
|
+
const subject = text.trim().slice(0, 80) || "Task";
|
|
58
|
+
return { subject, description: text.trim(), role: recommendRole(text) };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function decomposeGoal(goal: string): { strategy: DecompositionStrategy; subtasks: RecommendedSubtask[]; fanout: number } {
|
|
62
|
+
const lines = goal.split("\n").map((line) => line.trim()).filter(Boolean);
|
|
63
|
+
const fileRefs = goal.match(FILE_REF_RE)?.length ?? 0;
|
|
64
|
+
const codeSymbols = goal.match(CODE_SYMBOL_RE)?.length ?? 0;
|
|
65
|
+
const hasParallelKeyword = /\b(?:parallel|concurrently|simultaneously|independently)\b/i.test(goal);
|
|
66
|
+
if (fileRefs >= 3 || codeSymbols >= 3 || hasParallelKeyword) {
|
|
67
|
+
const subtask = makeSubtask(goal);
|
|
68
|
+
return { strategy: "atomic", subtasks: [subtask], fanout: 1 };
|
|
69
|
+
}
|
|
70
|
+
const numberedLines = lines.map((line) => line.match(NUMBERED_LINE_RE)?.[1]).filter((line): line is string => line !== undefined);
|
|
71
|
+
if (numberedLines.length >= 2 && numberedLines.length >= lines.length - 1) {
|
|
72
|
+
const subtasks = numberedLines.map((line) => makeSubtask(line));
|
|
73
|
+
return { strategy: "numbered", subtasks, fanout: subtasks.length };
|
|
74
|
+
}
|
|
75
|
+
const bulletedLines = lines.map((line) => line.match(BULLETED_LINE_RE)?.[1]).filter((line): line is string => line !== undefined);
|
|
76
|
+
if (bulletedLines.length >= 2 && bulletedLines.length >= lines.length - 1) {
|
|
77
|
+
const subtasks = bulletedLines.map((line) => makeSubtask(line));
|
|
78
|
+
return { strategy: "bulleted", subtasks, fanout: subtasks.length };
|
|
79
|
+
}
|
|
80
|
+
if (lines.length === 1) {
|
|
81
|
+
const parts = lines[0].split(CONJUNCTION_SPLIT_RE).map((part) => part.trim()).filter(Boolean);
|
|
82
|
+
if (parts.length >= 2) {
|
|
83
|
+
const subtasks = parts.map((part) => makeSubtask(part));
|
|
84
|
+
return { strategy: "conjunction", subtasks, fanout: subtasks.length };
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
const subtask = makeSubtask(goal);
|
|
88
|
+
return { strategy: "atomic", subtasks: [subtask], fanout: 1 };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function metadataMatches(goal: string, values: string[] | undefined): string[] {
|
|
92
|
+
const lower = goal.toLowerCase();
|
|
93
|
+
return (values ?? []).filter((value) => lower.includes(value.toLowerCase()));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function recommendTeam(goal: string, config: PiTeamsAutonomousConfig = {}, resources?: { teams?: TeamConfig[]; agents?: AgentConfig[] }): TeamRecommendation {
|
|
97
|
+
const normalized = goal.toLowerCase();
|
|
98
|
+
const intents = detectTeamIntent(goal, config);
|
|
99
|
+
const decomposition = decomposeGoal(goal);
|
|
100
|
+
const reasons: string[] = [];
|
|
101
|
+
let team: TeamRecommendation["team"] = "default";
|
|
102
|
+
let workflow: TeamRecommendation["workflow"] = "default";
|
|
103
|
+
let action: TeamRecommendation["action"] = "run";
|
|
104
|
+
let confidence: TeamRecommendation["confidence"] = "medium";
|
|
105
|
+
|
|
106
|
+
if (intents.length > 0) reasons.push(`Matched explicit intent keyword(s): ${intents.join(", ")}.`);
|
|
107
|
+
|
|
108
|
+
const metadataTeamMatches = (resources?.teams ?? [])
|
|
109
|
+
.map((candidate) => ({ team: candidate, matches: [...metadataMatches(goal, candidate.routing?.triggers), ...metadataMatches(goal, candidate.routing?.useWhen)] }))
|
|
110
|
+
.filter((candidate) => candidate.matches.length > 0)
|
|
111
|
+
.sort((a, b) => b.matches.length - a.matches.length);
|
|
112
|
+
|
|
113
|
+
const reviewMatches = includesAny(normalized, REVIEW_TERMS);
|
|
114
|
+
const researchMatches = includesAny(normalized, RESEARCH_TERMS);
|
|
115
|
+
const fastFixMatches = includesAny(normalized, FAST_FIX_TERMS);
|
|
116
|
+
const implementationMatches = includesAny(normalized, IMPLEMENTATION_TERMS);
|
|
117
|
+
const riskyMatches = includesAny(normalized, RISKY_TERMS);
|
|
118
|
+
|
|
119
|
+
if (metadataTeamMatches[0]) {
|
|
120
|
+
team = metadataTeamMatches[0].team.name as TeamRecommendation["team"];
|
|
121
|
+
workflow = (metadataTeamMatches[0].team.defaultWorkflow ?? metadataTeamMatches[0].team.name) as TeamRecommendation["workflow"];
|
|
122
|
+
confidence = "high";
|
|
123
|
+
reasons.push(`Matched team routing metadata for '${metadataTeamMatches[0].team.name}': ${metadataTeamMatches[0].matches.join(", ")}.`);
|
|
124
|
+
} else if (intents.includes("review") || reviewMatches.length >= 2 || normalized.includes("security review")) {
|
|
125
|
+
team = "review";
|
|
126
|
+
workflow = "review";
|
|
127
|
+
confidence = "high";
|
|
128
|
+
reasons.push(`Review/audit terms detected: ${reviewMatches.join(", ") || "explicit review intent"}.`);
|
|
129
|
+
} else if (PARALLEL_RESEARCH_RE.test(goal) || (researchMatches.length >= 2 && (normalized.includes("multiple") || normalized.includes("source") || normalized.includes("project") || normalized.includes("pi-")))) {
|
|
130
|
+
team = "parallel-research";
|
|
131
|
+
workflow = "parallel-research";
|
|
132
|
+
confidence = "high";
|
|
133
|
+
reasons.push("Deep/multi-source research detected; use parallel shard exploration.");
|
|
134
|
+
} else if (intents.includes("research") || (researchMatches.length > 0 && implementationMatches.length === 0)) {
|
|
135
|
+
team = "research";
|
|
136
|
+
workflow = "research";
|
|
137
|
+
confidence = researchMatches.length >= 2 ? "high" : "medium";
|
|
138
|
+
reasons.push(`Research/analysis terms detected: ${researchMatches.join(", ")}.`);
|
|
139
|
+
} else if (intents.includes("fastFix") || fastFixMatches.length > 0) {
|
|
140
|
+
team = "fast-fix";
|
|
141
|
+
workflow = "fast-fix";
|
|
142
|
+
confidence = "high";
|
|
143
|
+
reasons.push(`Small fix terms detected: ${fastFixMatches.join(", ") || "fast-fix intent"}.`);
|
|
144
|
+
} else if (intents.includes("taskList")) {
|
|
145
|
+
team = "implementation";
|
|
146
|
+
workflow = "implementation";
|
|
147
|
+
confidence = "high";
|
|
148
|
+
reasons.push(`Actionable multi-item task list detected (${decomposition.fanout} bullet${decomposition.fanout === 1 ? "" : "s"}); use coordinated implementation planning.`);
|
|
149
|
+
} else if (intents.includes("implementation") || implementationMatches.length > 0) {
|
|
150
|
+
team = "implementation";
|
|
151
|
+
workflow = "implementation";
|
|
152
|
+
confidence = implementationMatches.length >= 2 || riskyMatches.length > 0 || decomposition.fanout >= 2 ? "high" : "medium";
|
|
153
|
+
reasons.push(`Implementation terms detected: ${implementationMatches.join(", ") || "implementation intent"}.`);
|
|
154
|
+
} else {
|
|
155
|
+
action = "plan";
|
|
156
|
+
confidence = wordCount(goal) < 8 ? "low" : "medium";
|
|
157
|
+
reasons.push("No strong team-specific intent detected; start with planning/default discovery.");
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
if (decomposition.strategy !== "atomic") reasons.push(`Goal decomposes into ${decomposition.subtasks.length} subtasks using ${decomposition.strategy} parsing.`);
|
|
162
|
+
const async = config.preferAsyncForLongTasks === true && (wordCount(goal) > 24 || riskyMatches.length > 0 || implementationMatches.length >= 2 || decomposition.fanout >= 3);
|
|
163
|
+
const workspaceMode = config.allowWorktreeSuggestion === false ? "single" : (riskyMatches.length > 0 && team === "implementation" ? "worktree" : "single");
|
|
164
|
+
if (async) reasons.push("Task appears long/risky and config prefers async for long tasks.");
|
|
165
|
+
if (workspaceMode === "worktree") reasons.push(`Risk/isolation terms detected: ${riskyMatches.join(", ")}.`);
|
|
166
|
+
|
|
167
|
+
return { team, workflow, action, async, workspaceMode, confidence, decomposition, reasons };
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export function formatRecommendation(goal: string, recommendation: TeamRecommendation): string {
|
|
171
|
+
return [
|
|
172
|
+
"pi-crew recommendation:",
|
|
173
|
+
`Goal: ${goal}`,
|
|
174
|
+
`Action: ${recommendation.action}`,
|
|
175
|
+
`Team: ${recommendation.team}`,
|
|
176
|
+
`Workflow: ${recommendation.workflow}`,
|
|
177
|
+
`Async: ${recommendation.async}`,
|
|
178
|
+
`Workspace mode: ${recommendation.workspaceMode}`,
|
|
179
|
+
`Confidence: ${recommendation.confidence}`,
|
|
180
|
+
`Decomposition: ${recommendation.decomposition.strategy} (${recommendation.decomposition.fanout} lane${recommendation.decomposition.fanout === 1 ? "" : "s"})`,
|
|
181
|
+
"Subtasks:",
|
|
182
|
+
...recommendation.decomposition.subtasks.map((task, index) => `- ${index + 1}. [${task.role}] ${task.subject}`),
|
|
183
|
+
"Reasons:",
|
|
184
|
+
...recommendation.reasons.map((reason) => `- ${reason}`),
|
|
185
|
+
"Suggested tool call:",
|
|
186
|
+
JSON.stringify({ action: recommendation.action, team: recommendation.team, workflow: recommendation.workflow, goal, async: recommendation.async, workspaceMode: recommendation.workspaceMode }, null, 2),
|
|
187
|
+
].join("\n");
|
|
188
|
+
}
|
|
@@ -12,9 +12,10 @@ import { probeLiveSessionRuntime } from "../../subagents/live/session-runtime.ts
|
|
|
12
12
|
import { currentCrewRole, permissionForRole } from "../../runtime/role-permission.ts";
|
|
13
13
|
import { touchWorkerHeartbeat } from "../../runtime/worker-heartbeat.ts";
|
|
14
14
|
import { agentOutputPath, readCrewAgentEventsCursor, readCrewAgentStatus, readCrewAgents } from "../../runtime/crew-agent-records.ts";
|
|
15
|
+
import { terminateLiveAgentsForRun } from "../../runtime/live-agent-manager.ts";
|
|
15
16
|
import { buildAgentDashboard, readAgentOutput } from "../../runtime/agent-observability.ts";
|
|
16
17
|
import { readForegroundControlStatus, writeForegroundInterruptRequest } from "../../runtime/foreground-control.ts";
|
|
17
|
-
import { followUpLiveAgent, getLiveAgent,
|
|
18
|
+
import { followUpLiveAgent, getLiveAgent, listActiveLiveAgents, resumeLiveAgent, steerLiveAgent, stopLiveAgent } from "../../subagents/live/manager.ts";
|
|
18
19
|
import { appendLiveAgentControlRequest } from "../../subagents/live/control.ts";
|
|
19
20
|
import { liveControlRealtimeMessage, publishLiveControlRealtime } from "../../subagents/live/realtime.ts";
|
|
20
21
|
import { buildCapabilityInventory } from "../../runtime/capability-inventory.ts";
|
|
@@ -121,6 +122,7 @@ export async function handleApi(params: TeamToolParamsValue, ctx: TeamContext):
|
|
|
121
122
|
saveRunTasks(manifest, tasks);
|
|
122
123
|
appendEvent(manifest.eventsPath, { type: "plan.cancelled", runId: manifest.runId, taskId: approval.planTaskId, message: "Adaptive implementation plan was cancelled.", metadata: { provenance: "api" } });
|
|
123
124
|
manifest = updateRunStatus(manifest, "cancelled", "Plan approval was cancelled.");
|
|
125
|
+
void terminateLiveAgentsForRun(manifest.runId, "cancelled", appendEvent, manifest.eventsPath).catch(() => {});
|
|
124
126
|
return result(JSON.stringify({ planApproval: manifest.planApproval, cancelledTasks: tasks.filter((task) => task.status === "cancelled").map((task) => task.id) }, null, 2), { action: "api", status: "ok", runId: manifest.runId, artifactsRoot: manifest.artifactsRoot });
|
|
125
127
|
});
|
|
126
128
|
} catch (error) {
|
|
@@ -223,7 +225,7 @@ export async function handleApi(params: TeamToolParamsValue, ctx: TeamContext):
|
|
|
223
225
|
return result(JSON.stringify({ agentId: agent.id, mailboxMessage: message }, null, 2), { action: "api", status: "ok", runId: loaded.manifest.runId, artifactsRoot: loaded.manifest.artifactsRoot });
|
|
224
226
|
}
|
|
225
227
|
if (operation === "list-live-agents") {
|
|
226
|
-
return result(JSON.stringify(
|
|
228
|
+
return result(JSON.stringify(listActiveLiveAgents().filter((agent) => agent.runId === loaded.manifest.runId), null, 2), { action: "api", status: "ok", runId: loaded.manifest.runId, artifactsRoot: loaded.manifest.artifactsRoot });
|
|
227
229
|
}
|
|
228
230
|
if (operation === "steer-agent" || operation === "follow-up-agent" || operation === "stop-agent" || operation === "resume-agent" || operation === "interrupt-agent") {
|
|
229
231
|
const agentId = typeof cfg.agentId === "string" ? cfg.agentId : undefined;
|
|
@@ -233,6 +235,7 @@ export async function handleApi(params: TeamToolParamsValue, ctx: TeamContext):
|
|
|
233
235
|
try {
|
|
234
236
|
const live = getLiveAgent(agentId);
|
|
235
237
|
if (live && live.runId !== loaded.manifest.runId) return result(`Live agent '${agentId}' does not belong to run ${loaded.manifest.runId}.`, { action: "api", status: "error", runId: loaded.manifest.runId }, true);
|
|
238
|
+
if (live && live.workspaceId !== loaded.manifest.cwd) return result(`Live agent '${agentId}' does not belong to workspace ${loaded.manifest.cwd}.`, { action: "api", status: "error", runId: loaded.manifest.runId }, true);
|
|
236
239
|
if (!live && (operation === "steer-agent" || operation === "follow-up-agent")) throw new Error(`Live agent '${agentId}' not found.`);
|
|
237
240
|
const liveTaskId = live?.taskId;
|
|
238
241
|
if ((operation === "steer-agent" || operation === "follow-up-agent") && !liveTaskId) throw new Error(`Live agent '${agentId}' not found.`);
|