pi-crew 0.1.45 → 0.1.49
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +97 -0
- package/README.md +5 -5
- package/agents/analyst.md +11 -11
- package/agents/critic.md +11 -11
- package/agents/executor.md +11 -11
- package/agents/explorer.md +11 -11
- package/agents/planner.md +11 -11
- package/agents/reviewer.md +11 -11
- package/agents/security-reviewer.md +11 -11
- package/agents/test-engineer.md +11 -11
- package/agents/verifier.md +11 -11
- package/agents/writer.md +11 -11
- package/docs/next-upgrade-roadmap.md +808 -0
- package/docs/research/AGENT-EXECUTION-ARCHITECTURE.md +261 -0
- package/docs/research/AGENT-LIFECYCLE-COMPARISON.md +111 -0
- package/docs/research/AUDIT_OH_MY_PI.md +261 -0
- package/docs/research/AUDIT_PI_CREW.md +457 -0
- package/docs/research/CAVEMAN-DEEP-RESEARCH.md +281 -0
- package/docs/research/COMPARISON_OH_MY_PI_VS_PI_CREW.md +264 -0
- package/docs/research/DEEP-RESEARCH-PI-POWERBAR.md +343 -0
- package/docs/research/DEEP_RESEARCH_SUBAGENT_ARCHITECTURE.md +480 -0
- package/docs/research/GAP_CLOSURE_IMPLEMENTATION_PLAN.md +354 -0
- package/docs/research/IMPLEMENTATION_PLAN.md +385 -0
- package/docs/research/LIVE-SESSION-PRODUCTION-READY-PLAN.md +502 -0
- package/docs/research/OH-MY-PI-DEEP-RESEARCH-v14.7.6.md +266 -0
- package/docs/research/REMAINING-GAPS-PLAN.md +363 -0
- package/docs/research/SESSION-SUMMARY-2026-05-08.md +146 -0
- package/docs/research/UI-RESPONSIVENESS-AUDIT.md +173 -0
- package/docs/research-awesome-agent-skills-distillation.md +100 -0
- package/docs/research-oh-my-pi-distillation.md +369 -0
- package/docs/source-runtime-refactor-map.md +24 -0
- package/docs/usage.md +3 -3
- package/install.mjs +52 -8
- package/package.json +99 -98
- package/schema.json +10 -1
- package/skills/async-worker-recovery/SKILL.md +42 -0
- package/skills/context-artifact-hygiene/SKILL.md +52 -0
- package/skills/delegation-patterns/SKILL.md +54 -0
- package/skills/mailbox-interactive/SKILL.md +40 -0
- package/skills/model-routing-context/SKILL.md +39 -0
- package/skills/multi-perspective-review/SKILL.md +58 -0
- package/skills/observability-reliability/SKILL.md +41 -0
- package/skills/orchestration/SKILL.md +157 -0
- package/skills/ownership-session-security/SKILL.md +41 -0
- package/skills/pi-extension-lifecycle/SKILL.md +39 -0
- package/skills/requirements-to-task-packet/SKILL.md +63 -0
- package/skills/resource-discovery-config/SKILL.md +41 -0
- package/skills/runtime-state-reader/SKILL.md +44 -0
- package/skills/secure-agent-orchestration-review/SKILL.md +45 -0
- package/skills/state-mutation-locking/SKILL.md +42 -0
- package/skills/systematic-debugging/SKILL.md +67 -0
- package/skills/ui-render-performance/SKILL.md +39 -0
- package/skills/verification-before-done/SKILL.md +57 -0
- package/skills/worktree-isolation/SKILL.md +39 -0
- package/src/agents/agent-config.ts +6 -0
- package/src/agents/agent-search.ts +98 -0
- package/src/agents/agent-serializer.ts +38 -34
- package/src/agents/discover-agents.ts +29 -15
- package/src/config/config.ts +72 -24
- package/src/config/defaults.ts +25 -0
- package/src/extension/autonomous-policy.ts +26 -33
- package/src/extension/help.ts +1 -0
- package/src/extension/management.ts +5 -0
- package/src/extension/project-init.ts +62 -2
- package/src/extension/register.ts +69 -22
- package/src/extension/registration/commands.ts +64 -25
- package/src/extension/registration/compaction-guard.ts +1 -1
- package/src/extension/registration/subagent-helpers.ts +8 -0
- package/src/extension/registration/subagent-tools.ts +149 -148
- package/src/extension/registration/team-tool.ts +14 -10
- package/src/extension/run-index.ts +35 -21
- package/src/extension/run-maintenance.ts +30 -5
- package/src/extension/team-tool/api.ts +47 -9
- package/src/extension/team-tool/cancel.ts +109 -5
- package/src/extension/team-tool/context.ts +8 -0
- package/src/extension/team-tool/intent-policy.ts +42 -0
- package/src/extension/team-tool/lifecycle-actions.ts +120 -79
- package/src/extension/team-tool/parallel-dispatch.ts +156 -0
- package/src/extension/team-tool/respond.ts +46 -18
- package/src/extension/team-tool/run.ts +55 -12
- package/src/extension/team-tool/status.ts +13 -2
- package/src/extension/team-tool-types.ts +3 -0
- package/src/extension/team-tool.ts +45 -14
- package/src/hooks/registry.ts +61 -0
- package/src/hooks/types.ts +41 -0
- package/src/observability/event-to-metric.ts +8 -1
- package/src/runtime/agent-control.ts +169 -63
- package/src/runtime/async-runner.ts +3 -1
- package/src/runtime/background-runner.ts +78 -53
- package/src/runtime/cancellation-token.ts +89 -0
- package/src/runtime/cancellation.ts +61 -0
- package/src/runtime/capability-inventory.ts +116 -0
- package/src/runtime/child-pi.ts +458 -444
- package/src/runtime/code-summary.ts +247 -0
- package/src/runtime/crash-recovery.ts +182 -0
- package/src/runtime/crew-agent-records.ts +70 -10
- package/src/runtime/crew-agent-runtime.ts +1 -0
- package/src/runtime/custom-tools/irc-tool.ts +201 -0
- package/src/runtime/custom-tools/submit-result-tool.ts +90 -0
- package/src/runtime/deadletter.ts +1 -0
- package/src/runtime/delivery-coordinator.ts +48 -25
- package/src/runtime/effectiveness.ts +81 -0
- package/src/runtime/event-stream-bridge.ts +90 -0
- package/src/runtime/live-agent-control.ts +2 -1
- package/src/runtime/live-agent-manager.ts +179 -85
- package/src/runtime/live-control-realtime.ts +1 -1
- package/src/runtime/live-extension-bridge.ts +150 -0
- package/src/runtime/live-irc.ts +92 -0
- package/src/runtime/live-session-health.ts +100 -0
- package/src/runtime/live-session-runtime.ts +599 -305
- package/src/runtime/manifest-cache.ts +17 -2
- package/src/runtime/mcp-proxy.ts +113 -0
- package/src/runtime/model-fallback.ts +6 -4
- package/src/runtime/notebook-helpers.ts +90 -0
- package/src/runtime/orphan-sentinel.ts +7 -0
- package/src/runtime/output-validator.ts +187 -0
- package/src/runtime/parallel-utils.ts +57 -0
- package/src/runtime/parent-guard.ts +80 -0
- package/src/runtime/pi-args.ts +18 -3
- package/src/runtime/process-status.ts +5 -1
- package/src/runtime/prose-compressor.ts +164 -0
- package/src/runtime/result-extractor.ts +121 -0
- package/src/runtime/retry-executor.ts +81 -64
- package/src/runtime/runtime-resolver.ts +23 -10
- package/src/runtime/semaphore.ts +131 -0
- package/src/runtime/sensitive-paths.ts +92 -0
- package/src/runtime/skill-instructions.ts +222 -0
- package/src/runtime/stale-reconciler.ts +4 -14
- package/src/runtime/stream-preview.ts +177 -0
- package/src/runtime/subagent-manager.ts +6 -2
- package/src/runtime/subprocess-tool-registry.ts +67 -0
- package/src/runtime/task-output-context.ts +177 -127
- package/src/runtime/task-runner/capabilities.ts +78 -0
- package/src/runtime/task-runner/live-executor.ts +107 -101
- package/src/runtime/task-runner/prompt-builder.ts +72 -8
- package/src/runtime/task-runner/prompt-pipeline.ts +64 -0
- package/src/runtime/task-runner/run-projection.ts +104 -0
- package/src/runtime/task-runner.ts +115 -5
- package/src/runtime/team-runner.ts +134 -19
- package/src/runtime/workspace-tree.ts +298 -0
- package/src/runtime/yield-handler.ts +189 -0
- package/src/schema/config-schema.ts +7 -0
- package/src/schema/team-tool-schema.ts +14 -4
- package/src/skills/discover-skills.ts +67 -0
- package/src/state/active-run-registry.ts +167 -0
- package/src/state/artifact-store.ts +4 -1
- package/src/state/atomic-write.ts +50 -1
- package/src/state/blob-store.ts +117 -0
- package/src/state/contracts.ts +2 -1
- package/src/state/event-log-rotation.ts +158 -0
- package/src/state/event-log.ts +52 -2
- package/src/state/mailbox.ts +129 -9
- package/src/state/state-store.ts +32 -5
- package/src/state/types.ts +64 -2
- package/src/teams/team-config.ts +1 -0
- package/src/ui/agent-management-overlay.ts +144 -0
- package/src/ui/crew-widget.ts +15 -5
- package/src/ui/dashboard-panes/cancellation-pane.ts +43 -0
- package/src/ui/dashboard-panes/capability-pane.ts +60 -0
- package/src/ui/dashboard-panes/mailbox-pane.ts +35 -11
- package/src/ui/dashboard-panes/progress-pane.ts +2 -0
- package/src/ui/live-run-sidebar.ts +4 -0
- package/src/ui/powerbar-publisher.ts +77 -15
- package/src/ui/render-coalescer.ts +51 -0
- package/src/ui/run-dashboard.ts +4 -0
- package/src/ui/run-event-bus.ts +209 -0
- package/src/ui/run-snapshot-cache.ts +78 -18
- package/src/ui/snapshot-types.ts +10 -0
- package/src/ui/transcript-entries.ts +258 -0
- package/src/utils/ids.ts +5 -0
- package/src/utils/incremental-reader.ts +104 -0
- package/src/utils/paths.ts +4 -2
- package/src/utils/scan-cache.ts +137 -0
- package/src/utils/sse-parser.ts +134 -0
- package/src/utils/task-name-generator.ts +337 -0
- package/src/utils/visual.ts +33 -2
- package/src/workflows/workflow-config.ts +1 -0
- package/src/worktree/cleanup.ts +2 -1
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
|
-
import {
|
|
3
|
+
import { configPath as globalConfigPath } from "../config/config.ts";
|
|
4
|
+
import { DEFAULT_UI } from "../config/defaults.ts";
|
|
5
|
+
import { packageRoot, projectCrewRoot, projectPiRoot } from "../utils/paths.ts";
|
|
4
6
|
|
|
5
7
|
export interface ProjectInitOptions {
|
|
6
8
|
copyBuiltins?: boolean;
|
|
7
9
|
overwrite?: boolean;
|
|
10
|
+
configScope?: "global" | "project" | "none";
|
|
8
11
|
}
|
|
9
12
|
|
|
10
13
|
export interface ProjectInitResult {
|
|
@@ -13,6 +16,10 @@ export interface ProjectInitResult {
|
|
|
13
16
|
skippedFiles: string[];
|
|
14
17
|
gitignorePath: string;
|
|
15
18
|
gitignoreUpdated: boolean;
|
|
19
|
+
configPath: string;
|
|
20
|
+
configScope: "global" | "project" | "none";
|
|
21
|
+
configCreated: boolean;
|
|
22
|
+
configSkipped: boolean;
|
|
16
23
|
}
|
|
17
24
|
|
|
18
25
|
function ensureDir(dir: string, createdDirs: string[]): void {
|
|
@@ -24,6 +31,44 @@ function ensureDir(dir: string, createdDirs: string[]): void {
|
|
|
24
31
|
}
|
|
25
32
|
}
|
|
26
33
|
|
|
34
|
+
const DEFAULT_PI_CREW_CONFIG = {
|
|
35
|
+
// Keep generated config non-invasive: do not set runtime/limits defaults here.
|
|
36
|
+
// Those are provided by pi-crew internals and should not make a normal workflow block.
|
|
37
|
+
autonomous: {
|
|
38
|
+
enabled: true,
|
|
39
|
+
injectPolicy: true,
|
|
40
|
+
preferAsyncForLongTasks: false,
|
|
41
|
+
allowWorktreeSuggestion: true,
|
|
42
|
+
},
|
|
43
|
+
agents: {
|
|
44
|
+
overrides: {
|
|
45
|
+
explorer: { model: false, thinking: "off" },
|
|
46
|
+
writer: { model: false, thinking: "off" },
|
|
47
|
+
planner: { model: false, thinking: "medium" },
|
|
48
|
+
analyst: { model: false, thinking: "off" },
|
|
49
|
+
critic: { model: false, thinking: "low" },
|
|
50
|
+
executor: { model: false, thinking: "medium" },
|
|
51
|
+
reviewer: { model: false, thinking: "off" },
|
|
52
|
+
"security-reviewer": { model: false, thinking: "medium" },
|
|
53
|
+
"test-engineer": { model: false, thinking: "low" },
|
|
54
|
+
verifier: { model: false, thinking: "off" },
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
ui: {
|
|
58
|
+
widgetPlacement: DEFAULT_UI.widgetPlacement,
|
|
59
|
+
widgetMaxLines: DEFAULT_UI.widgetMaxLines,
|
|
60
|
+
powerbar: DEFAULT_UI.powerbar,
|
|
61
|
+
dashboardPlacement: DEFAULT_UI.dashboardPlacement,
|
|
62
|
+
dashboardWidth: DEFAULT_UI.dashboardWidth,
|
|
63
|
+
dashboardLiveRefreshMs: DEFAULT_UI.dashboardLiveRefreshMs,
|
|
64
|
+
autoOpenDashboard: DEFAULT_UI.autoOpenDashboard,
|
|
65
|
+
autoOpenDashboardForForegroundRuns: DEFAULT_UI.autoOpenDashboardForForegroundRuns,
|
|
66
|
+
showModel: DEFAULT_UI.showModel,
|
|
67
|
+
showTokens: DEFAULT_UI.showTokens,
|
|
68
|
+
showTools: DEFAULT_UI.showTools,
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
|
|
27
72
|
function copyBuiltinDir(kind: "agents" | "teams" | "workflows", targetDir: string, overwrite: boolean, copiedFiles: string[], skippedFiles: string[]): void {
|
|
28
73
|
const sourceDir = path.join(packageRoot(), kind);
|
|
29
74
|
if (!fs.existsSync(sourceDir)) return;
|
|
@@ -50,11 +95,26 @@ export function initializeProject(cwd: string, options: ProjectInitOptions = {})
|
|
|
50
95
|
const agentsDir = path.join(crewRoot, "agents");
|
|
51
96
|
const teamsDir = path.join(crewRoot, "teams");
|
|
52
97
|
const workflowsDir = path.join(crewRoot, "workflows");
|
|
98
|
+
const configScope = options.configScope ?? "global";
|
|
99
|
+
const configPath = configScope === "project" ? path.join(projectPiRoot(cwd), "pi-crew.json") : configScope === "global" ? globalConfigPath() : "";
|
|
53
100
|
ensureDir(agentsDir, createdDirs);
|
|
54
101
|
ensureDir(teamsDir, createdDirs);
|
|
55
102
|
ensureDir(workflowsDir, createdDirs);
|
|
56
103
|
ensureDir(path.join(crewRoot, "imports"), createdDirs);
|
|
57
104
|
|
|
105
|
+
let configCreated = false;
|
|
106
|
+
let configSkipped = false;
|
|
107
|
+
if (configPath) {
|
|
108
|
+
if (configScope === "project") ensureDir(path.dirname(configPath), createdDirs);
|
|
109
|
+
else fs.mkdirSync(path.dirname(configPath), { recursive: true });
|
|
110
|
+
if (!fs.existsSync(configPath) || options.overwrite === true) {
|
|
111
|
+
fs.writeFileSync(configPath, `${JSON.stringify(DEFAULT_PI_CREW_CONFIG, null, 2)}\n`, "utf-8");
|
|
112
|
+
configCreated = true;
|
|
113
|
+
} else {
|
|
114
|
+
configSkipped = true;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
58
118
|
if (options.copyBuiltins) {
|
|
59
119
|
copyBuiltinDir("agents", agentsDir, options.overwrite === true, copiedFiles, skippedFiles);
|
|
60
120
|
copyBuiltinDir("teams", teamsDir, options.overwrite === true, copiedFiles, skippedFiles);
|
|
@@ -72,5 +132,5 @@ export function initializeProject(cwd: string, options: ProjectInitOptions = {})
|
|
|
72
132
|
gitignoreUpdated = true;
|
|
73
133
|
}
|
|
74
134
|
|
|
75
|
-
return { createdDirs, copiedFiles, skippedFiles, gitignorePath, gitignoreUpdated };
|
|
135
|
+
return { createdDirs, copiedFiles, skippedFiles, gitignorePath, gitignoreUpdated, configPath, configScope, configCreated, configSkipped };
|
|
76
136
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent";
|
|
2
2
|
import * as fs from "node:fs";
|
|
3
3
|
import * as path from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
4
5
|
import { loadConfig } from "../config/config.ts";
|
|
5
6
|
import { registerAutonomousPolicy } from "./autonomous-policy.ts";
|
|
6
7
|
import { startAsyncRunNotifier, stopAsyncRunNotifier, type AsyncNotifierState } from "./async-notifier.ts";
|
|
@@ -8,7 +9,7 @@ import { notifyActiveRuns } from "./session-summary.ts";
|
|
|
8
9
|
import { LiveRunSidebar } from "../ui/live-run-sidebar.ts";
|
|
9
10
|
import { registerPiCrewRpc, type PiCrewRpcHandle } from "./cross-extension-rpc.ts";
|
|
10
11
|
import { stopCrewWidget, updateCrewWidget, type CrewWidgetState } from "../ui/crew-widget.ts";
|
|
11
|
-
import { clearPiCrewPowerbar, registerPiCrewPowerbarSegments, updatePiCrewPowerbar } from "../ui/powerbar-publisher.ts";
|
|
12
|
+
import { clearPiCrewPowerbar, disposePowerbarCoalescer, registerPiCrewPowerbarSegments, requestPowerbarUpdate, updatePiCrewPowerbar } from "../ui/powerbar-publisher.ts";
|
|
12
13
|
import { loadRunManifestById, updateRunStatus } from "../state/state-store.ts";
|
|
13
14
|
import type { TeamRunManifest } from "../state/types.ts";
|
|
14
15
|
import { terminateActiveChildPiProcesses } from "../subagents/spawn.ts";
|
|
@@ -36,7 +37,7 @@ import { createMetricFileSink, type MetricSink } from "../observability/metric-s
|
|
|
36
37
|
import { OTLPExporter } from "../observability/exporters/otlp-exporter.ts";
|
|
37
38
|
import { HeartbeatWatcher } from "../runtime/heartbeat-watcher.ts";
|
|
38
39
|
import { appendDeadletter } from "../runtime/deadletter.ts";
|
|
39
|
-
import { detectInterruptedRuns } from "../runtime/crash-recovery.ts";
|
|
40
|
+
import { cancelOrphanedRuns, detectInterruptedRuns, purgeStaleActiveRunIndex } from "../runtime/crash-recovery.ts";
|
|
40
41
|
import { DeliveryCoordinator } from "../runtime/delivery-coordinator.ts";
|
|
41
42
|
import { OverflowRecoveryTracker } from "../runtime/overflow-recovery.ts";
|
|
42
43
|
import { tryRegisterSessionCleanup } from "../runtime/session-resources.ts";
|
|
@@ -112,7 +113,7 @@ export function registerPiTeams(pi: ExtensionAPI): void {
|
|
|
112
113
|
if (currentCtx) {
|
|
113
114
|
const uiConfig = loadConfig(currentCtx.cwd).config.ui;
|
|
114
115
|
updateCrewWidget(currentCtx, widgetState, uiConfig, getManifestCache(currentCtx.cwd), getRunSnapshotCache(currentCtx.cwd));
|
|
115
|
-
|
|
116
|
+
requestPowerbarUpdate(pi.events, currentCtx.cwd, uiConfig, getManifestCache(currentCtx.cwd), getRunSnapshotCache(currentCtx.cwd), currentCtx, widgetState.notificationCount ?? 0);
|
|
116
117
|
}
|
|
117
118
|
});
|
|
118
119
|
};
|
|
@@ -235,12 +236,12 @@ export function registerPiTeams(pi: ExtensionAPI): void {
|
|
|
235
236
|
pi.events?.emit?.(event, payload);
|
|
236
237
|
},
|
|
237
238
|
);
|
|
238
|
-
const foregroundControllers = new
|
|
239
|
+
const foregroundControllers = new Map<string | symbol, AbortController>();
|
|
239
240
|
let liveSidebarRunId: string | undefined;
|
|
240
241
|
let renderScheduler: RenderScheduler | undefined;
|
|
241
242
|
let preloadTimer: ReturnType<typeof setTimeout> | undefined;
|
|
242
243
|
const stopSessionBoundSubagents = (): void => {
|
|
243
|
-
for (const controller of foregroundControllers) controller.abort();
|
|
244
|
+
for (const controller of foregroundControllers.values()) controller.abort();
|
|
244
245
|
foregroundControllers.clear();
|
|
245
246
|
subagentManager.abortAll();
|
|
246
247
|
terminateActiveChildPiProcesses();
|
|
@@ -253,18 +254,18 @@ export function registerPiTeams(pi: ExtensionAPI): void {
|
|
|
253
254
|
const openLiveSidebar = (ctx: ExtensionContext, runId: string): void => {
|
|
254
255
|
const uiConfig = loadConfig(ctx.cwd).config.ui;
|
|
255
256
|
const autoOpen = uiConfig?.autoOpenDashboard === true;
|
|
256
|
-
const foregroundAutoOpen = uiConfig?.autoOpenDashboardForForegroundRuns
|
|
257
|
-
if (!ctx.hasUI || !autoOpen || !foregroundAutoOpen || (uiConfig?.dashboardPlacement ??
|
|
257
|
+
const foregroundAutoOpen = uiConfig?.autoOpenDashboardForForegroundRuns ?? DEFAULT_UI.autoOpenDashboardForForegroundRuns;
|
|
258
|
+
if (!ctx.hasUI || !autoOpen || !foregroundAutoOpen || (uiConfig?.dashboardPlacement ?? DEFAULT_UI.dashboardPlacement) !== "right") return;
|
|
258
259
|
if (liveSidebarRunId === runId) return;
|
|
259
260
|
liveSidebarRunId = runId;
|
|
260
|
-
const widgetPlacement = uiConfig?.widgetPlacement ??
|
|
261
|
+
const widgetPlacement = uiConfig?.widgetPlacement ?? DEFAULT_UI.widgetPlacement;
|
|
261
262
|
setExtensionWidget(ctx, "pi-crew", undefined, { placement: widgetPlacement });
|
|
262
263
|
setExtensionWidget(ctx, "pi-crew-active", undefined, { placement: widgetPlacement });
|
|
263
264
|
widgetState.lastVisibility = "hidden";
|
|
264
265
|
widgetState.lastPlacement = widgetPlacement;
|
|
265
266
|
widgetState.lastKey = "pi-crew-active";
|
|
266
267
|
widgetState.model = undefined;
|
|
267
|
-
const width = Math.min(90, Math.max(40, uiConfig?.dashboardWidth ??
|
|
268
|
+
const width = Math.min(90, Math.max(40, uiConfig?.dashboardWidth ?? DEFAULT_UI.dashboardWidth));
|
|
268
269
|
void showCustom<undefined>(ctx, (_tui, theme, _keybindings, done) => new LiveRunSidebar({ cwd: ctx.cwd, runId, done, theme, config: uiConfig, snapshotCache: getRunSnapshotCache(ctx.cwd) }), {
|
|
269
270
|
overlay: true,
|
|
270
271
|
overlayOptions: { width, minWidth: 40, maxHeight: "100%", anchor: "top-right", offsetX: 0, offsetY: 0, margin: { top: 0, right: 0, bottom: 0, left: 0 }, visible: (termWidth: number) => termWidth >= 100 },
|
|
@@ -276,7 +277,8 @@ export function registerPiTeams(pi: ExtensionAPI): void {
|
|
|
276
277
|
const startForegroundRun = (ctx: ExtensionContext, runner: (signal?: AbortSignal) => Promise<void>, runId?: string): void => {
|
|
277
278
|
const ownerGeneration = captureSessionGeneration();
|
|
278
279
|
const controller = new AbortController();
|
|
279
|
-
|
|
280
|
+
const key = runId ?? Symbol();
|
|
281
|
+
foregroundControllers.set(key, controller);
|
|
280
282
|
if (ctx.hasUI) {
|
|
281
283
|
setWorkingIndicator(ctx, { frames: ["⣾", "⣽", "⣻", "⢿", "⡿", "⣟", "⣯", "⣷"], intervalMs: 80 });
|
|
282
284
|
ctx.ui.setWorkingMessage(runId ? `pi-crew foreground run ${runId}...` : "pi-crew foreground run...");
|
|
@@ -294,9 +296,10 @@ export function registerPiTeams(pi: ExtensionAPI): void {
|
|
|
294
296
|
}
|
|
295
297
|
}
|
|
296
298
|
if (isContextCurrent(ctx, ownerGeneration)) ctx.ui.notify(`pi-crew foreground run failed: ${message}`, "error");
|
|
299
|
+
else logInternalError("register.foreground-run-failure", error, `runId=${runId} context disposed`);
|
|
297
300
|
})
|
|
298
301
|
.finally(() => {
|
|
299
|
-
foregroundControllers.delete(
|
|
302
|
+
foregroundControllers.delete(key);
|
|
300
303
|
const ownerCurrent = isContextCurrent(ctx, ownerGeneration);
|
|
301
304
|
if (ownerCurrent && ctx.hasUI) {
|
|
302
305
|
setWorkingIndicator(ctx);
|
|
@@ -333,7 +336,7 @@ export function registerPiTeams(pi: ExtensionAPI): void {
|
|
|
333
336
|
if (ownerCurrent && currentCtx) {
|
|
334
337
|
const config = loadConfig(currentCtx.cwd).config.ui;
|
|
335
338
|
updateCrewWidget(currentCtx, widgetState, config, getManifestCache(currentCtx.cwd), getRunSnapshotCache(currentCtx.cwd));
|
|
336
|
-
|
|
339
|
+
requestPowerbarUpdate(pi.events, currentCtx.cwd, config, getManifestCache(currentCtx.cwd), getRunSnapshotCache(currentCtx.cwd), currentCtx, widgetState.notificationCount ?? 0);
|
|
337
340
|
}
|
|
338
341
|
});
|
|
339
342
|
});
|
|
@@ -349,8 +352,20 @@ export function registerPiTeams(pi: ExtensionAPI): void {
|
|
|
349
352
|
if (preloadTimer) { clearTimeout(preloadTimer); preloadTimer = undefined; }
|
|
350
353
|
stopSessionBoundSubagents();
|
|
351
354
|
stopAsyncRunNotifier(notifierState);
|
|
355
|
+
|
|
356
|
+
// P0: Purge all stale active-run-index entries on session cleanup.
|
|
357
|
+
// This handles: normal exit, SIGTERM, Ctrl+C — any case where cleanupRuntime fires.
|
|
358
|
+
// For SIGKILL / crash / SIGHUP (where cleanupRuntime does NOT fire),
|
|
359
|
+
// purgeStaleActiveRunIndex() runs at next session_start instead.
|
|
360
|
+
try {
|
|
361
|
+
purgeStaleActiveRunIndex();
|
|
362
|
+
} catch (error) {
|
|
363
|
+
logInternalError("register.cleanupRuntime.purgeStale", error);
|
|
364
|
+
}
|
|
365
|
+
|
|
352
366
|
stopCrewWidget(currentCtx, widgetState, currentCtx ? loadConfig(currentCtx.cwd).config.ui : undefined);
|
|
353
367
|
clearPiCrewPowerbar(pi.events, currentCtx);
|
|
368
|
+
disposePowerbarCoalescer();
|
|
354
369
|
heartbeatWatcher?.dispose();
|
|
355
370
|
metricSink?.dispose();
|
|
356
371
|
eventMetricSub?.dispose();
|
|
@@ -393,12 +408,36 @@ export function registerPiTeams(pi: ExtensionAPI): void {
|
|
|
393
408
|
if (widgetState.interval) clearInterval(widgetState.interval);
|
|
394
409
|
widgetState.interval = undefined;
|
|
395
410
|
notifyActiveRuns(ctx);
|
|
411
|
+
|
|
412
|
+
// Auto-cancel orphaned runs from dead sessions
|
|
413
|
+
const currentSessionId = (ctx as unknown as Record<string, unknown>).sessionId as string | undefined;
|
|
414
|
+
if (currentSessionId) {
|
|
415
|
+
try {
|
|
416
|
+
const { cancelled } = cancelOrphanedRuns(ctx.cwd, getManifestCache(ctx.cwd), currentSessionId);
|
|
417
|
+
if (cancelled.length > 0) {
|
|
418
|
+
notifyOperator({ id: `orphan_cleanup`, severity: "info", source: "crash-recovery", title: `Cleaned up ${cancelled.length} orphaned run(s)`, body: `Runs from previous sessions were auto-cancelled: ${cancelled.join(", ")}` });
|
|
419
|
+
}
|
|
420
|
+
} catch (error) {
|
|
421
|
+
logInternalError("register.sessionStart.orphanCleanup", error);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// Global purge of stale active-run-index entries (temp dirs, dead workers, etc.)
|
|
426
|
+
try {
|
|
427
|
+
const { purged } = purgeStaleActiveRunIndex();
|
|
428
|
+
if (purged.length > 0) {
|
|
429
|
+
notifyOperator({ id: `active_index_purge`, severity: "info", source: "crash-recovery", title: `Purged ${purged.length} stale active-run-index entr${purged.length === 1 ? "y" : "ies"}`, body: `Cleaned up global active run index` });
|
|
430
|
+
}
|
|
431
|
+
} catch (error) {
|
|
432
|
+
logInternalError("register.sessionStart.globalIndexPurge", error);
|
|
433
|
+
}
|
|
434
|
+
|
|
396
435
|
const loadedConfig = loadConfig(ctx.cwd);
|
|
397
436
|
autoRecoveryLast.clear();
|
|
398
437
|
configureNotifications(ctx);
|
|
399
438
|
configureObservability(ctx);
|
|
400
439
|
configureDeliveryCoordinator();
|
|
401
|
-
const sessionId = (ctx as unknown as Record<string, unknown>).sessionId;
|
|
440
|
+
const sessionId = ctx.sessionManager?.getSessionId?.() ?? (ctx as unknown as Record<string, unknown>).sessionId;
|
|
402
441
|
if (typeof sessionId === "string" && sessionId) deliveryCoordinator?.activate(sessionId);
|
|
403
442
|
tryRegisterSessionCleanup(pi, () => { terminateActiveChildPiProcesses(); cleanupRuntime(); });
|
|
404
443
|
registerPiCrewPowerbarSegments(pi.events, loadedConfig.config.ui);
|
|
@@ -460,7 +499,7 @@ export function registerPiTeams(pi: ExtensionAPI): void {
|
|
|
460
499
|
const snapshotCache = lastFrameSnapshotCache ?? getRunSnapshotCache(currentCtx.cwd);
|
|
461
500
|
const manifests = lastPreloadedManifests.length > 0 ? lastPreloadedManifests : activeCache.list(20);
|
|
462
501
|
if (liveSidebarRunId) {
|
|
463
|
-
const placement = config?.widgetPlacement ??
|
|
502
|
+
const placement = config?.widgetPlacement ?? DEFAULT_UI.widgetPlacement;
|
|
464
503
|
if (widgetState.lastVisibility !== "hidden" || widgetState.lastPlacement !== placement) {
|
|
465
504
|
setExtensionWidget(currentCtx, "pi-crew", undefined, { placement });
|
|
466
505
|
setExtensionWidget(currentCtx, "pi-crew-active", undefined, { placement });
|
|
@@ -473,7 +512,7 @@ export function registerPiTeams(pi: ExtensionAPI): void {
|
|
|
473
512
|
} else {
|
|
474
513
|
updateCrewWidget(currentCtx, widgetState, config, activeCache, snapshotCache, manifests);
|
|
475
514
|
}
|
|
476
|
-
|
|
515
|
+
requestPowerbarUpdate(pi.events, currentCtx.cwd, config, activeCache, snapshotCache, currentCtx, widgetState.notificationCount ?? 0, manifests);
|
|
477
516
|
// Health notifications: only warn about genuinely running runs
|
|
478
517
|
const now = Date.now();
|
|
479
518
|
for (const run of manifests) {
|
|
@@ -500,7 +539,7 @@ export function registerPiTeams(pi: ExtensionAPI): void {
|
|
|
500
539
|
}
|
|
501
540
|
};
|
|
502
541
|
|
|
503
|
-
const fallbackMs = loadedConfig.config.ui?.dashboardLiveRefreshMs ??
|
|
542
|
+
const fallbackMs = loadedConfig.config.ui?.dashboardLiveRefreshMs ?? DEFAULT_UI.refreshMs;
|
|
504
543
|
renderScheduler = new RenderScheduler(pi.events, renderTick, {
|
|
505
544
|
fallbackMs,
|
|
506
545
|
onInvalidate: () => getRunSnapshotCache(ctx.cwd).invalidate(),
|
|
@@ -530,8 +569,9 @@ export function registerPiTeams(pi: ExtensionAPI): void {
|
|
|
530
569
|
// Phase 11a: Dynamic resource discovery — inject pi-crew skill paths.
|
|
531
570
|
try {
|
|
532
571
|
pi.on("resources_discover", () => {
|
|
533
|
-
const
|
|
534
|
-
const
|
|
572
|
+
const sessionCwd = currentCtx?.cwd ?? process.cwd();
|
|
573
|
+
const skillDir = path.resolve(sessionCwd, "skills");
|
|
574
|
+
const extSkillDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "..", "skills");
|
|
535
575
|
const paths: string[] = [];
|
|
536
576
|
if (fs.existsSync(extSkillDir)) paths.push(extSkillDir);
|
|
537
577
|
if (skillDir !== extSkillDir && fs.existsSync(skillDir)) paths.push(skillDir);
|
|
@@ -539,6 +579,12 @@ export function registerPiTeams(pi: ExtensionAPI): void {
|
|
|
539
579
|
});
|
|
540
580
|
} catch { /* older Pi without resources_discover */ }
|
|
541
581
|
|
|
582
|
+
const abortForegroundRun = (runId: string): boolean => {
|
|
583
|
+
const controller = foregroundControllers.get(runId);
|
|
584
|
+
if (!controller) return false;
|
|
585
|
+
controller.abort();
|
|
586
|
+
return true;
|
|
587
|
+
};
|
|
542
588
|
registerCompactionGuard(pi, { foregroundControllers });
|
|
543
589
|
|
|
544
590
|
// Phase 1.4: Permission gate for destructive team actions.
|
|
@@ -550,14 +596,15 @@ export function registerPiTeams(pi: ExtensionAPI): void {
|
|
|
550
596
|
const action = typeof input.action === "string" ? input.action : undefined;
|
|
551
597
|
const destructiveActions = new Set(["delete", "forget", "prune", "cleanup"]);
|
|
552
598
|
if (!action || !destructiveActions.has(action)) return;
|
|
553
|
-
|
|
599
|
+
const forceBypassesReferenceChecks = action === "delete" && input.force === true;
|
|
600
|
+
if (input.confirm === true || forceBypassesReferenceChecks) return;
|
|
554
601
|
return {
|
|
555
602
|
block: true,
|
|
556
|
-
reason: `Destructive action '${action}' requires confirm=true (or force=true to bypass reference checks).`,
|
|
603
|
+
reason: `Destructive action '${action}' requires confirm=true${action === "delete" ? " (or force=true to bypass reference checks)" : ""}.`,
|
|
557
604
|
};
|
|
558
605
|
});
|
|
559
606
|
|
|
560
|
-
registerTeamTool(pi, { foregroundControllers, startForegroundRun, openLiveSidebar, getManifestCache, getRunSnapshotCache, getMetricRegistry: () => metricRegistry, widgetState, onJsonEvent: (taskId, runId, event) => {
|
|
607
|
+
registerTeamTool(pi, { foregroundControllers, startForegroundRun, abortForegroundRun, openLiveSidebar, getManifestCache, getRunSnapshotCache, getMetricRegistry: () => metricRegistry, widgetState, onJsonEvent: (taskId, runId, event) => {
|
|
561
608
|
const record = event as Record<string, unknown>;
|
|
562
609
|
const eventType = typeof record.type === "string" ? record.type : undefined;
|
|
563
610
|
if (eventType) overflowTracker?.feedEvent(taskId, runId, eventType);
|
|
@@ -565,7 +612,7 @@ export function registerPiTeams(pi: ExtensionAPI): void {
|
|
|
565
612
|
registerSubagentTools(pi, subagentManager, { ownerSessionGeneration: captureSessionGeneration });
|
|
566
613
|
time("register.tools");
|
|
567
614
|
|
|
568
|
-
registerTeamCommands(pi, { startForegroundRun, openLiveSidebar, getManifestCache, getRunSnapshotCache, getMetricRegistry: () => metricRegistry, dismissNotifications: () => {
|
|
615
|
+
registerTeamCommands(pi, { startForegroundRun, abortForegroundRun, openLiveSidebar, getManifestCache, getRunSnapshotCache, getMetricRegistry: () => metricRegistry, dismissNotifications: () => {
|
|
569
616
|
widgetState.notificationCount = 0;
|
|
570
617
|
if (currentCtx) {
|
|
571
618
|
const uiConfig = loadConfig(currentCtx.cwd).config.ui;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { ExtensionAPI, ExtensionCommandContext, ExtensionContext } from "@mariozechner/pi-coding-agent";
|
|
2
2
|
import { loadConfig } from "../../config/config.ts";
|
|
3
3
|
import { handleTeamTool } from "../team-tool.ts";
|
|
4
|
+
import { withSessionId } from "../team-tool/context.ts";
|
|
4
5
|
import { piTeamsHelp } from "../help.ts";
|
|
5
6
|
import { handleTeamManagerCommand } from "../team-manager-command.ts";
|
|
6
7
|
import { loadRunManifestById } from "../../state/state-store.ts";
|
|
@@ -15,6 +16,7 @@ import { MailboxDetailOverlay, type MailboxAction } from "../../ui/overlays/mail
|
|
|
15
16
|
import { MailboxComposeOverlay, type MailboxComposeResult } from "../../ui/overlays/mailbox-compose-overlay.ts";
|
|
16
17
|
import { AgentPickerOverlay } from "../../ui/overlays/agent-picker-overlay.ts";
|
|
17
18
|
import { dispatchDiagnosticExport, dispatchHealthRecovery, dispatchKillStaleWorkers, dispatchMailboxAck, dispatchMailboxAckAll, dispatchMailboxCompose, dispatchMailboxNudge } from "../../ui/run-action-dispatcher.ts";
|
|
19
|
+
import { DEFAULT_UI } from "../../config/defaults.ts";
|
|
18
20
|
import { listRecentDiagnostic } from "../../runtime/diagnostic-export.ts";
|
|
19
21
|
import { commandText, notifyCommandResult, parseRunArgs, parseScalar, pushUnset, setNestedConfig } from "./command-utils.ts";
|
|
20
22
|
import { openTranscriptViewer, selectAgentTask } from "./viewers.ts";
|
|
@@ -25,6 +27,7 @@ import type { MetricRegistry } from "../../observability/metric-registry.ts";
|
|
|
25
27
|
|
|
26
28
|
export interface RegisterTeamCommandsDeps {
|
|
27
29
|
startForegroundRun: (ctx: ExtensionContext, runner: (signal?: AbortSignal) => Promise<void>, runId?: string) => void;
|
|
30
|
+
abortForegroundRun: (runId: string) => boolean;
|
|
28
31
|
openLiveSidebar: (ctx: ExtensionContext, runId: string) => void;
|
|
29
32
|
getManifestCache: (cwd: string) => { list(max?: number): TeamRunManifest[] };
|
|
30
33
|
getRunSnapshotCache?: (cwd: string) => ReturnType<typeof createRunSnapshotCache>;
|
|
@@ -78,6 +81,10 @@ function depsNotify(ctx: ExtensionCommandContext, message: string, level: "info"
|
|
|
78
81
|
ctx.ui.notify(message, level);
|
|
79
82
|
}
|
|
80
83
|
|
|
84
|
+
function teamCommandContext(ctx: ExtensionCommandContext): ExtensionCommandContext & { sessionId?: string } {
|
|
85
|
+
return withSessionId(ctx);
|
|
86
|
+
}
|
|
87
|
+
|
|
81
88
|
async function handleHealthDashboardAction(ctx: ExtensionCommandContext, selection: RunDashboardSelection): Promise<void> {
|
|
82
89
|
const loaded = loadRunManifestById(ctx.cwd, selection.runId);
|
|
83
90
|
if (!loaded) {
|
|
@@ -121,7 +128,7 @@ export function registerTeamCommands(pi: ExtensionAPI, deps: RegisterTeamCommand
|
|
|
121
128
|
pi.registerCommand("teams", {
|
|
122
129
|
description: "List pi-crew teams, workflows, and agents",
|
|
123
130
|
handler: async (_args: string, ctx: ExtensionCommandContext) => {
|
|
124
|
-
const result = await handleTeamTool({ action: "list" }, ctx);
|
|
131
|
+
const result = await handleTeamTool({ action: "list" }, teamCommandContext(ctx));
|
|
125
132
|
await notifyCommandResult(ctx, commandText(result));
|
|
126
133
|
},
|
|
127
134
|
});
|
|
@@ -129,7 +136,7 @@ export function registerTeamCommands(pi: ExtensionAPI, deps: RegisterTeamCommand
|
|
|
129
136
|
pi.registerCommand("team-run", {
|
|
130
137
|
description: "Manually start a pi-crew run (agent may also use the team tool autonomously)",
|
|
131
138
|
handler: async (args: string, ctx: ExtensionCommandContext) => {
|
|
132
|
-
const result = await handleTeamTool(parseRunArgs(args), { ...ctx, metricRegistry: deps.getMetricRegistry?.(), startForegroundRun: (runner, runId) => deps.startForegroundRun(ctx as ExtensionContext, runner, runId), onRunStarted: (runId) => deps.openLiveSidebar(ctx as ExtensionContext, runId) });
|
|
139
|
+
const result = await handleTeamTool(parseRunArgs(args), { ...teamCommandContext(ctx), metricRegistry: deps.getMetricRegistry?.(), startForegroundRun: (runner, runId) => deps.startForegroundRun(ctx as ExtensionContext, runner, runId), abortForegroundRun: deps.abortForegroundRun, onRunStarted: (runId) => deps.openLiveSidebar(ctx as ExtensionContext, runId) });
|
|
133
140
|
await notifyCommandResult(ctx, commandText(result));
|
|
134
141
|
},
|
|
135
142
|
});
|
|
@@ -146,11 +153,26 @@ export function registerTeamCommands(pi: ExtensionAPI, deps: RegisterTeamCommand
|
|
|
146
153
|
] as const) {
|
|
147
154
|
pi.registerCommand(name, { description, handler: async (args: string, ctx: ExtensionCommandContext) => {
|
|
148
155
|
const runId = args.trim() || undefined;
|
|
149
|
-
const result = await handleTeamTool({ action, runId }, ctx);
|
|
156
|
+
const result = await handleTeamTool({ action, runId }, teamCommandContext(ctx));
|
|
150
157
|
await notifyCommandResult(ctx, commandText(result));
|
|
151
158
|
} });
|
|
152
159
|
}
|
|
153
160
|
|
|
161
|
+
pi.registerCommand("team-retry", {
|
|
162
|
+
description: "Retry failed/cancelled pi-crew tasks: <runId> [taskId]",
|
|
163
|
+
handler: async (args: string, ctx: ExtensionCommandContext) => {
|
|
164
|
+
const tokens = args.trim().split(/\s+/).filter(Boolean);
|
|
165
|
+
const runId = tokens.shift();
|
|
166
|
+
const taskId = tokens.shift();
|
|
167
|
+
if (!runId) {
|
|
168
|
+
await notifyCommandResult(ctx, "Usage: /team-retry <runId> [taskId]");
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
const retryResult = await handleTeamTool({ action: "retry", runId, taskId }, teamCommandContext(ctx));
|
|
172
|
+
await notifyCommandResult(ctx, commandText(retryResult));
|
|
173
|
+
},
|
|
174
|
+
});
|
|
175
|
+
|
|
154
176
|
pi.registerCommand("team-respond", {
|
|
155
177
|
description: "Respond to a waiting pi-crew task: <runId> <taskId|--all> <message>",
|
|
156
178
|
handler: async (args: string, ctx: ExtensionCommandContext) => {
|
|
@@ -159,7 +181,23 @@ export function registerTeamCommands(pi: ExtensionAPI, deps: RegisterTeamCommand
|
|
|
159
181
|
const taskToken = tokens[0] === "--all" ? tokens.shift() : tokens.shift();
|
|
160
182
|
const taskId = taskToken === "--all" ? undefined : taskToken;
|
|
161
183
|
const message = tokens.join(" ") || undefined;
|
|
162
|
-
const result = await handleTeamTool({ action: "respond", runId, taskId, message }, ctx);
|
|
184
|
+
const result = await handleTeamTool({ action: "respond", runId, taskId, message }, teamCommandContext(ctx));
|
|
185
|
+
await notifyCommandResult(ctx, commandText(result));
|
|
186
|
+
},
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
pi.registerCommand("team-follow-up", {
|
|
190
|
+
description: "Send a follow-up prompt to a pi-crew task: <runId> <taskId> <prompt>",
|
|
191
|
+
handler: async (args: string, ctx: ExtensionCommandContext) => {
|
|
192
|
+
const tokens = args.trim().split(/\s+/).filter(Boolean);
|
|
193
|
+
const runId = tokens.shift();
|
|
194
|
+
const taskId = tokens.shift();
|
|
195
|
+
const prompt = tokens.join(" ") || undefined;
|
|
196
|
+
if (!runId || !taskId || !prompt) {
|
|
197
|
+
await notifyCommandResult(ctx, "Usage: /team-follow-up <runId> <taskId> <prompt>. Use /team-respond for waiting-task replies.");
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
const result = await handleTeamTool({ action: "api", runId, config: { operation: "follow-up-agent", taskId, prompt } }, teamCommandContext(ctx));
|
|
163
201
|
await notifyCommandResult(ctx, commandText(result));
|
|
164
202
|
},
|
|
165
203
|
});
|
|
@@ -178,19 +216,19 @@ export function registerTeamCommands(pi: ExtensionAPI, deps: RegisterTeamCommand
|
|
|
178
216
|
const [key, ...rest] = token.split("=");
|
|
179
217
|
if (key) config[key] = parseScalar(rest.join("="));
|
|
180
218
|
}
|
|
181
|
-
const result = await handleTeamTool({ action: "api", runId, config }, ctx);
|
|
219
|
+
const result = await handleTeamTool({ action: "api", runId, config }, teamCommandContext(ctx));
|
|
182
220
|
await notifyCommandResult(ctx, commandText(result));
|
|
183
221
|
},
|
|
184
222
|
});
|
|
185
223
|
|
|
186
224
|
pi.registerCommand("team-metrics", { description: "Show pi-crew metrics snapshot: [filter]", handler: async (args: string, ctx: ExtensionCommandContext) => {
|
|
187
225
|
const filter = args.trim() || undefined;
|
|
188
|
-
const result = await handleTeamTool({ action: "api", config: { operation: "metrics-snapshot", filter } }, { ...ctx, metricRegistry: deps.getMetricRegistry?.() });
|
|
226
|
+
const result = await handleTeamTool({ action: "api", config: { operation: "metrics-snapshot", filter } }, { ...teamCommandContext(ctx), metricRegistry: deps.getMetricRegistry?.() });
|
|
189
227
|
await notifyCommandResult(ctx, commandText(result));
|
|
190
228
|
} });
|
|
191
229
|
|
|
192
230
|
pi.registerCommand("team-imports", { description: "List imported pi-crew run bundles", handler: async (_args: string, ctx: ExtensionCommandContext) => {
|
|
193
|
-
const result = await handleTeamTool({ action: "imports" }, ctx);
|
|
231
|
+
const result = await handleTeamTool({ action: "imports" }, teamCommandContext(ctx));
|
|
194
232
|
await notifyCommandResult(ctx, commandText(result));
|
|
195
233
|
} });
|
|
196
234
|
|
|
@@ -198,7 +236,7 @@ export function registerTeamCommands(pi: ExtensionAPI, deps: RegisterTeamCommand
|
|
|
198
236
|
const tokens = args.trim().split(/\s+/).filter(Boolean);
|
|
199
237
|
const pathArg = tokens.find((token) => !token.startsWith("--"));
|
|
200
238
|
const scope = tokens.includes("--user") ? "user" : "project";
|
|
201
|
-
const result = await handleTeamTool({ action: "import", config: { path: pathArg, scope } }, ctx);
|
|
239
|
+
const result = await handleTeamTool({ action: "import", config: { path: pathArg, scope } }, teamCommandContext(ctx));
|
|
202
240
|
await notifyCommandResult(ctx, commandText(result));
|
|
203
241
|
} });
|
|
204
242
|
|
|
@@ -206,21 +244,21 @@ export function registerTeamCommands(pi: ExtensionAPI, deps: RegisterTeamCommand
|
|
|
206
244
|
const tokens = args.trim().split(/\s+/).filter(Boolean);
|
|
207
245
|
const keepToken = tokens.find((token) => token.startsWith("--keep="));
|
|
208
246
|
const keep = keepToken ? Number.parseInt(keepToken.slice("--keep=".length), 10) : undefined;
|
|
209
|
-
const result = await handleTeamTool({ action: "prune", keep, confirm: tokens.includes("--confirm") }, ctx);
|
|
247
|
+
const result = await handleTeamTool({ action: "prune", keep, confirm: tokens.includes("--confirm") }, teamCommandContext(ctx));
|
|
210
248
|
await notifyCommandResult(ctx, commandText(result));
|
|
211
249
|
} });
|
|
212
250
|
|
|
213
251
|
pi.registerCommand("team-forget", { description: "Forget a pi-crew run by deleting its state and artifacts", handler: async (args: string, ctx: ExtensionCommandContext) => {
|
|
214
252
|
const tokens = args.trim().split(/\s+/).filter(Boolean);
|
|
215
253
|
const runId = tokens.find((token) => !token.startsWith("--"));
|
|
216
|
-
const result = await handleTeamTool({ action: "forget", runId, force: tokens.includes("--force"), confirm: tokens.includes("--confirm") }, ctx);
|
|
254
|
+
const result = await handleTeamTool({ action: "forget", runId, force: tokens.includes("--force"), confirm: tokens.includes("--confirm") }, teamCommandContext(ctx));
|
|
217
255
|
await notifyCommandResult(ctx, commandText(result));
|
|
218
256
|
} });
|
|
219
257
|
|
|
220
258
|
pi.registerCommand("team-settings", {
|
|
221
259
|
description: "View or update pi-crew settings: [list|get <key>|set <key> <value>|unset <key>|path|scope]",
|
|
222
260
|
handler: async (args: string, ctx: ExtensionCommandContext) => {
|
|
223
|
-
const result = await handleTeamTool({ action: "settings", config: { args: args.trim() } }, ctx);
|
|
261
|
+
const result = await handleTeamTool({ action: "settings", config: { args: args.trim() } }, teamCommandContext(ctx));
|
|
224
262
|
await notifyCommandResult(ctx, commandText(result));
|
|
225
263
|
},
|
|
226
264
|
});
|
|
@@ -233,18 +271,18 @@ export function registerTeamCommands(pi: ExtensionAPI, deps: RegisterTeamCommand
|
|
|
233
271
|
const loaded = selected ? loadRunManifestById(ctx.cwd, selected.runId) : undefined;
|
|
234
272
|
if (ctx.hasUI && loaded) {
|
|
235
273
|
const agent = readCrewAgents(loaded.manifest).find((item) => item.taskId === selected?.taskId || item.id === selected?.taskId) ?? readCrewAgents(loaded.manifest)[0];
|
|
236
|
-
const resultText = agent?.resultArtifactPath ? commandText(await handleTeamTool({ action: "api", runId: selected?.runId ?? "", config: { operation: "read-agent-output", agentId: agent.taskId, maxBytes: 64_000 } }, ctx)) : "(no result)";
|
|
274
|
+
const resultText = agent?.resultArtifactPath ? commandText(await handleTeamTool({ action: "api", runId: selected?.runId ?? "", config: { operation: "read-agent-output", agentId: agent.taskId, maxBytes: 64_000 } }, teamCommandContext(ctx))) : "(no result)";
|
|
237
275
|
await ctx.ui.custom<undefined>((_tui, theme, _keybindings, done) => new DurableTextViewer("pi-crew result", `${selected?.runId ?? ""}:${agent?.taskId ?? "unknown"}`, resultText.split(/\r?\n/), theme, done), { overlay: true, overlayOptions: { width: "90%", maxHeight: "85%", anchor: "center" } });
|
|
238
276
|
return;
|
|
239
277
|
}
|
|
240
|
-
const result = await handleTeamTool({ action: "api", runId, config: { operation: "read-agent-output", agentId: rawTaskId, maxBytes: 64_000 } }, ctx);
|
|
278
|
+
const result = await handleTeamTool({ action: "api", runId, config: { operation: "read-agent-output", agentId: rawTaskId, maxBytes: 64_000 } }, teamCommandContext(ctx));
|
|
241
279
|
await notifyCommandResult(ctx, commandText(result));
|
|
242
280
|
} });
|
|
243
281
|
|
|
244
282
|
pi.registerCommand("team-transcript", { description: "Open a pi-crew transcript viewer: <runId> [taskId]", handler: async (args: string, ctx: ExtensionCommandContext) => {
|
|
245
283
|
const [runId, taskId] = args.trim().split(/\s+/).filter(Boolean);
|
|
246
284
|
if (await openTranscriptViewer(ctx, runId, taskId)) return;
|
|
247
|
-
const result = await handleTeamTool({ action: "api", runId, config: { operation: "read-agent-transcript", agentId: taskId } }, ctx);
|
|
285
|
+
const result = await handleTeamTool({ action: "api", runId, config: { operation: "read-agent-transcript", agentId: taskId } }, teamCommandContext(ctx));
|
|
248
286
|
await notifyCommandResult(ctx, commandText(result));
|
|
249
287
|
} });
|
|
250
288
|
|
|
@@ -252,8 +290,8 @@ export function registerTeamCommands(pi: ExtensionAPI, deps: RegisterTeamCommand
|
|
|
252
290
|
for (;;) {
|
|
253
291
|
const runs = deps.getManifestCache(ctx.cwd).list(50);
|
|
254
292
|
const uiConfig = loadConfig(ctx.cwd).config.ui;
|
|
255
|
-
const rightPanel = uiConfig?.dashboardPlacement
|
|
256
|
-
const width = rightPanel ? Math.min(90, Math.max(40, uiConfig?.dashboardWidth ??
|
|
293
|
+
const rightPanel = (uiConfig?.dashboardPlacement ?? DEFAULT_UI.dashboardPlacement) === "right";
|
|
294
|
+
const width = rightPanel ? Math.min(90, Math.max(40, uiConfig?.dashboardWidth ?? DEFAULT_UI.dashboardWidth)) : "90%";
|
|
257
295
|
const selection = await ctx.ui.custom<RunDashboardSelection | undefined>((_tui, theme, _keybindings, done) => new RunDashboard(runs, done, theme, { placement: rightPanel ? "right" : "center", showModel: uiConfig?.showModel, showTokens: uiConfig?.showTokens, showTools: uiConfig?.showTools, snapshotCache: deps.getRunSnapshotCache?.(ctx.cwd), runProvider: () => deps.getManifestCache(ctx.cwd).list(50), registry: deps.getMetricRegistry?.() }), { overlay: true, overlayOptions: rightPanel ? { width, minWidth: 40, maxHeight: "100%", anchor: "top-right", offsetX: 0, offsetY: 0, margin: { top: 0, right: 0, bottom: 0, left: 0 } } : { width, maxHeight: "90%", anchor: "center", margin: 2 } });
|
|
258
296
|
if (!selection) return;
|
|
259
297
|
if (selection.action === "reload") continue;
|
|
@@ -273,7 +311,7 @@ export function registerTeamCommands(pi: ExtensionAPI, deps: RegisterTeamCommand
|
|
|
273
311
|
continue;
|
|
274
312
|
}
|
|
275
313
|
if (selection.action === "agent-transcript" && await openTranscriptViewer(ctx, selection.runId)) continue;
|
|
276
|
-
const result = selection.action === "api" ? await handleTeamTool({ action: "api", runId: selection.runId, config: { operation: "read-manifest" } }, ctx) : selection.action === "agents" ? await handleTeamTool({ action: "api", runId: selection.runId, config: { operation: "agent-dashboard" } }, ctx) : selection.action === "mailbox" ? await handleTeamTool({ action: "api", runId: selection.runId, config: { operation: "read-mailbox" } }, ctx) : selection.action === "agent-events" ? await handleTeamTool({ action: "api", runId: selection.runId, config: { operation: "read-agent-events", limit: 50 } }, ctx) : selection.action === "agent-output" ? await handleTeamTool({ action: "api", runId: selection.runId, config: { operation: "read-agent-output", maxBytes: 32_000 } }, ctx) : selection.action === "agent-transcript" ? await handleTeamTool({ action: "api", runId: selection.runId, config: { operation: "read-agent-transcript" } }, ctx) : await handleTeamTool({ action: selection.action, runId: selection.runId }, ctx);
|
|
314
|
+
const result = selection.action === "api" ? await handleTeamTool({ action: "api", runId: selection.runId, config: { operation: "read-manifest" } }, teamCommandContext(ctx)) : selection.action === "agents" ? await handleTeamTool({ action: "api", runId: selection.runId, config: { operation: "agent-dashboard" } }, teamCommandContext(ctx)) : selection.action === "mailbox" ? await handleTeamTool({ action: "api", runId: selection.runId, config: { operation: "read-mailbox" } }, teamCommandContext(ctx)) : selection.action === "agent-events" ? await handleTeamTool({ action: "api", runId: selection.runId, config: { operation: "read-agent-events", limit: 50 } }, teamCommandContext(ctx)) : selection.action === "agent-output" ? await handleTeamTool({ action: "api", runId: selection.runId, config: { operation: "read-agent-output", maxBytes: 32_000 } }, teamCommandContext(ctx)) : selection.action === "agent-transcript" ? await handleTeamTool({ action: "api", runId: selection.runId, config: { operation: "read-agent-transcript" } }, teamCommandContext(ctx)) : await handleTeamTool({ action: selection.action, runId: selection.runId }, teamCommandContext(ctx));
|
|
277
315
|
await notifyCommandResult(ctx, commandText(result));
|
|
278
316
|
return;
|
|
279
317
|
}
|
|
@@ -285,14 +323,15 @@ export function registerTeamCommands(pi: ExtensionAPI, deps: RegisterTeamCommand
|
|
|
285
323
|
const uiConfig = loadConfig(ctx.cwd).config.ui;
|
|
286
324
|
const styleArg = tokens.find((t) => t === "cat" || t === "armin");
|
|
287
325
|
const effectArg = tokens.find((t) => ["random", "none", "typewriter", "scanline", "rain", "fade", "crt", "glitch", "dissolve"].includes(t));
|
|
288
|
-
const style = (styleArg as "cat" | "armin" | undefined) ?? uiConfig?.mascotStyle ??
|
|
289
|
-
const effect = (effectArg as "random" | "none" | "typewriter" | "scanline" | "rain" | "fade" | "crt" | "glitch" | "dissolve" | undefined) ?? uiConfig?.mascotEffect ??
|
|
326
|
+
const style = (styleArg as "cat" | "armin" | undefined) ?? uiConfig?.mascotStyle ?? DEFAULT_UI.mascotStyle;
|
|
327
|
+
const effect = (effectArg as "random" | "none" | "typewriter" | "scanline" | "rain" | "fade" | "crt" | "glitch" | "dissolve" | undefined) ?? uiConfig?.mascotEffect ?? DEFAULT_UI.mascotEffect;
|
|
290
328
|
await ctx.ui.custom<undefined>((tui, theme, _keybindings, done) => new AnimatedMascot(theme, () => done(undefined), { frameIntervalMs: style === "armin" ? 33 : 180, autoCloseMs: 7000, requestRender: () => requestRenderTarget(tui), style, effect }), { overlay: true, overlayOptions: { width: style === "armin" ? 48 : 62, maxHeight: "85%", anchor: "center" } });
|
|
291
329
|
} });
|
|
292
330
|
|
|
293
|
-
pi.registerCommand("team-init", { description: "Initialize
|
|
331
|
+
pi.registerCommand("team-init", { description: "Initialize pi-crew layout and global config. Use --project-config to write .pi/pi-crew.json.", handler: async (args: string, ctx: ExtensionCommandContext) => {
|
|
294
332
|
const tokens = args.trim().split(/\s+/).filter(Boolean);
|
|
295
|
-
const
|
|
333
|
+
const configScope = tokens.includes("--project-config") || tokens.includes("--project") ? "project" : tokens.includes("--no-config") ? "none" : "global";
|
|
334
|
+
const result = await handleTeamTool({ action: "init", config: { copyBuiltins: tokens.includes("--copy-builtins"), overwrite: tokens.includes("--overwrite"), configScope } }, teamCommandContext(ctx));
|
|
296
335
|
await notifyCommandResult(ctx, commandText(result));
|
|
297
336
|
} });
|
|
298
337
|
|
|
@@ -300,14 +339,14 @@ export function registerTeamCommands(pi: ExtensionAPI, deps: RegisterTeamCommand
|
|
|
300
339
|
const tokens = args.trim().split(/\s+/).filter(Boolean);
|
|
301
340
|
const mode = tokens[0]?.toLowerCase();
|
|
302
341
|
const config = mode === "on" ? { profile: "suggested", enabled: true, injectPolicy: true } : mode === "off" ? { profile: "manual", enabled: false } : mode === "manual" || mode === "suggested" || mode === "assisted" || mode === "aggressive" ? { profile: mode, enabled: mode !== "manual", injectPolicy: mode !== "manual" } : { preferAsyncForLongTasks: tokens.includes("--prefer-async") ? true : undefined, allowWorktreeSuggestion: tokens.includes("--no-worktree-suggest") ? false : undefined };
|
|
303
|
-
const result = await handleTeamTool({ action: "autonomy", config }, ctx);
|
|
342
|
+
const result = await handleTeamTool({ action: "autonomy", config }, teamCommandContext(ctx));
|
|
304
343
|
await notifyCommandResult(ctx, commandText(result));
|
|
305
344
|
} });
|
|
306
345
|
|
|
307
346
|
pi.registerCommand("team-config", { description: "Show or update pi-crew config. Use key=value [--project] to update.", handler: async (args: string, ctx: ExtensionCommandContext) => {
|
|
308
347
|
const tokens = args.trim().split(/\s+/).filter(Boolean);
|
|
309
348
|
if (tokens.length === 0) {
|
|
310
|
-
const result = await handleTeamTool({ action: "config" }, ctx);
|
|
349
|
+
const result = await handleTeamTool({ action: "config" }, teamCommandContext(ctx));
|
|
311
350
|
await notifyCommandResult(ctx, commandText(result));
|
|
312
351
|
return;
|
|
313
352
|
}
|
|
@@ -324,7 +363,7 @@ export function registerTeamCommands(pi: ExtensionAPI, deps: RegisterTeamCommand
|
|
|
324
363
|
if (raw === "unset" || raw === "null") pushUnset(config, key);
|
|
325
364
|
else setNestedConfig(config, key, parseScalar(raw));
|
|
326
365
|
}
|
|
327
|
-
const result = await handleTeamTool({ action: "config", config }, ctx);
|
|
366
|
+
const result = await handleTeamTool({ action: "config", config }, teamCommandContext(ctx));
|
|
328
367
|
await notifyCommandResult(ctx, commandText(result));
|
|
329
368
|
} });
|
|
330
369
|
|
|
@@ -332,7 +371,7 @@ export function registerTeamCommands(pi: ExtensionAPI, deps: RegisterTeamCommand
|
|
|
332
371
|
["team-validate", "validate", "Validate pi-crew agents, teams, and workflows"],
|
|
333
372
|
["team-doctor", "doctor", "Check pi-crew installation and discovery readiness"],
|
|
334
373
|
] as const) pi.registerCommand(name, { description, handler: async (_args: string, ctx: ExtensionCommandContext) => {
|
|
335
|
-
const result = await handleTeamTool({ action }, ctx);
|
|
374
|
+
const result = await handleTeamTool({ action }, teamCommandContext(ctx));
|
|
336
375
|
await notifyCommandResult(ctx, commandText(result));
|
|
337
376
|
} });
|
|
338
377
|
|
|
@@ -3,7 +3,7 @@ import { listRecentRuns } from "../run-index.ts";
|
|
|
3
3
|
import type { ArtifactDescriptor, TeamRunManifest } from "../../state/types.ts";
|
|
4
4
|
|
|
5
5
|
export interface RegisterCompactionGuardOptions {
|
|
6
|
-
foregroundControllers:
|
|
6
|
+
foregroundControllers: Map<string | symbol, AbortController>;
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
const TRIGGER_RATIO = 0.75;
|
|
@@ -81,6 +81,13 @@ export function subagentToolResult(text: string, details: Record<string, unknown
|
|
|
81
81
|
return { content: [{ type: "text" as const, text }], details, isError };
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
+
function parseSkillParam(value: unknown): string | string[] | false | undefined {
|
|
85
|
+
if (value === false) return false;
|
|
86
|
+
if (typeof value === "string") return value;
|
|
87
|
+
if (Array.isArray(value) && value.every((entry) => typeof entry === "string")) return value;
|
|
88
|
+
return undefined;
|
|
89
|
+
}
|
|
90
|
+
|
|
84
91
|
export function __test__subagentSpawnParams(params: Record<string, unknown>, ctx: Pick<ExtensionContext, "cwd">): SubagentSpawnOptions {
|
|
85
92
|
return {
|
|
86
93
|
cwd: ctx.cwd,
|
|
@@ -89,6 +96,7 @@ export function __test__subagentSpawnParams(params: Record<string, unknown>, ctx
|
|
|
89
96
|
prompt: typeof params.prompt === "string" ? params.prompt : "",
|
|
90
97
|
background: params.run_in_background === true,
|
|
91
98
|
model: typeof params.model === "string" && params.model.trim() ? params.model.trim() : undefined,
|
|
99
|
+
skill: parseSkillParam(params.skill),
|
|
92
100
|
maxTurns: typeof params.max_turns === "number" && Number.isFinite(params.max_turns) ? params.max_turns : undefined,
|
|
93
101
|
};
|
|
94
102
|
}
|