pi-crew 0.1.45 → 0.1.46
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -5
- package/agents/analyst.md +1 -1
- package/agents/critic.md +1 -1
- package/agents/executor.md +1 -1
- package/agents/explorer.md +1 -1
- package/agents/planner.md +1 -1
- package/agents/reviewer.md +1 -1
- package/agents/security-reviewer.md +1 -1
- package/agents/test-engineer.md +1 -1
- package/agents/verifier.md +1 -1
- package/agents/writer.md +1 -1
- package/docs/next-upgrade-roadmap.md +733 -0
- package/docs/refactor-tasks-phase3.md +394 -394
- package/docs/refactor-tasks-phase4.md +564 -564
- package/docs/refactor-tasks-phase5.md +402 -402
- package/docs/refactor-tasks-phase6.md +662 -662
- package/docs/research-awesome-agent-skills-distillation.md +100 -0
- package/docs/research-extension-examples.md +297 -297
- package/docs/research-extension-system.md +324 -324
- package/docs/research-oh-my-pi-distillation.md +322 -0
- package/docs/research-optimization-plan.md +548 -548
- package/docs/research-phase10-distillation.md +198 -198
- package/docs/research-phase11-distillation.md +201 -201
- package/docs/research-pi-coding-agent.md +357 -357
- package/docs/research-source-pi-crew-reference.md +174 -174
- package/docs/runtime-flow.md +148 -148
- package/docs/source-runtime-refactor-map.md +107 -83
- package/docs/usage.md +3 -3
- package/index.ts +6 -6
- package/install.mjs +52 -8
- package/package.json +1 -1
- package/schema.json +2 -1
- package/skills/async-worker-recovery/SKILL.md +42 -0
- package/skills/context-artifact-hygiene/SKILL.md +52 -0
- package/skills/delegation-patterns/SKILL.md +54 -0
- package/skills/mailbox-interactive/SKILL.md +40 -0
- package/skills/model-routing-context/SKILL.md +39 -0
- package/skills/multi-perspective-review/SKILL.md +58 -0
- package/skills/observability-reliability/SKILL.md +41 -0
- package/skills/ownership-session-security/SKILL.md +41 -0
- package/skills/pi-extension-lifecycle/SKILL.md +39 -0
- package/skills/requirements-to-task-packet/SKILL.md +63 -0
- package/skills/resource-discovery-config/SKILL.md +41 -0
- package/skills/runtime-state-reader/SKILL.md +44 -0
- package/skills/secure-agent-orchestration-review/SKILL.md +45 -0
- package/skills/state-mutation-locking/SKILL.md +42 -0
- package/skills/systematic-debugging/SKILL.md +67 -0
- package/skills/ui-render-performance/SKILL.md +39 -0
- package/skills/verification-before-done/SKILL.md +57 -0
- package/skills/worktree-isolation/SKILL.md +39 -0
- package/src/agents/agent-serializer.ts +34 -34
- package/src/agents/discover-agents.ts +12 -11
- package/src/config/config.ts +48 -24
- package/src/config/defaults.ts +14 -0
- package/src/extension/cross-extension-rpc.ts +82 -82
- package/src/extension/project-init.ts +62 -2
- package/src/extension/register.ts +11 -9
- package/src/extension/registration/commands.ts +32 -25
- package/src/extension/registration/compaction-guard.ts +125 -125
- package/src/extension/registration/subagent-helpers.ts +8 -0
- package/src/extension/registration/subagent-tools.ts +149 -148
- package/src/extension/registration/team-tool.ts +8 -6
- package/src/extension/run-bundle-schema.ts +89 -89
- package/src/extension/run-index.ts +13 -5
- package/src/extension/run-maintenance.ts +62 -43
- package/src/extension/team-tool/api.ts +25 -8
- package/src/extension/team-tool/cancel.ts +33 -4
- package/src/extension/team-tool/context.ts +5 -0
- package/src/extension/team-tool/handle-settings.ts +188 -188
- package/src/extension/team-tool/inspect.ts +41 -41
- package/src/extension/team-tool/lifecycle-actions.ts +91 -79
- package/src/extension/team-tool/plan.ts +19 -19
- package/src/extension/team-tool/respond.ts +37 -17
- package/src/extension/team-tool/run.ts +52 -10
- package/src/extension/team-tool/status.ts +12 -1
- package/src/extension/team-tool-types.ts +2 -0
- package/src/extension/team-tool.ts +32 -11
- package/src/i18n.ts +184 -184
- package/src/observability/event-to-metric.ts +8 -1
- package/src/observability/exporters/otlp-exporter.ts +77 -77
- package/src/prompt/prompt-runtime.ts +72 -72
- package/src/runtime/agent-control.ts +63 -63
- package/src/runtime/agent-memory.ts +72 -72
- package/src/runtime/agent-observability.ts +114 -114
- package/src/runtime/async-marker.ts +26 -26
- package/src/runtime/attention-events.ts +28 -28
- package/src/runtime/background-runner.ts +59 -53
- package/src/runtime/cancellation.ts +51 -0
- package/src/runtime/child-pi.ts +457 -444
- package/src/runtime/completion-guard.ts +190 -190
- package/src/runtime/crash-recovery.ts +1 -0
- package/src/runtime/crew-agent-records.ts +38 -6
- package/src/runtime/deadletter.ts +1 -0
- package/src/runtime/delivery-coordinator.ts +46 -25
- package/src/runtime/direct-run.ts +35 -35
- package/src/runtime/effectiveness.ts +76 -0
- package/src/runtime/foreground-control.ts +82 -82
- package/src/runtime/green-contract.ts +46 -46
- package/src/runtime/group-join.ts +106 -106
- package/src/runtime/heartbeat-gradient.ts +28 -28
- package/src/runtime/heartbeat-watcher.ts +124 -124
- package/src/runtime/live-agent-control.ts +88 -87
- package/src/runtime/live-agent-manager.ts +103 -85
- package/src/runtime/live-control-realtime.ts +36 -36
- package/src/runtime/live-session-runtime.ts +309 -305
- package/src/runtime/manifest-cache.ts +17 -2
- package/src/runtime/model-fallback.ts +6 -4
- package/src/runtime/parallel-research.ts +44 -44
- package/src/runtime/pi-args.ts +18 -3
- package/src/runtime/pi-json-output.ts +111 -111
- package/src/runtime/policy-engine.ts +79 -79
- package/src/runtime/process-status.ts +5 -1
- package/src/runtime/progress-event-coalescer.ts +43 -43
- package/src/runtime/recovery-recipes.ts +74 -74
- package/src/runtime/retry-executor.ts +81 -64
- package/src/runtime/role-permission.ts +39 -39
- package/src/runtime/runtime-resolver.ts +22 -6
- package/src/runtime/session-resources.ts +25 -25
- package/src/runtime/session-snapshot.ts +59 -59
- package/src/runtime/session-usage.ts +79 -79
- package/src/runtime/sidechain-output.ts +29 -29
- package/src/runtime/skill-instructions.ts +222 -0
- package/src/runtime/stale-reconciler.ts +4 -14
- package/src/runtime/subagent-manager.ts +3 -0
- package/src/runtime/supervisor-contact.ts +59 -59
- package/src/runtime/task-display.ts +38 -38
- package/src/runtime/task-output-context.ts +127 -127
- package/src/runtime/task-runner/capabilities.ts +78 -0
- package/src/runtime/task-runner/live-executor.ts +105 -101
- package/src/runtime/task-runner/progress.ts +119 -119
- package/src/runtime/task-runner/prompt-builder.ts +3 -1
- package/src/runtime/task-runner/prompt-pipeline.ts +64 -0
- package/src/runtime/task-runner/result-utils.ts +14 -14
- package/src/runtime/task-runner/state-helpers.ts +22 -22
- package/src/runtime/task-runner.ts +44 -5
- package/src/runtime/team-runner.ts +78 -15
- package/src/runtime/worker-heartbeat.ts +21 -21
- package/src/runtime/worker-startup.ts +57 -57
- package/src/schema/config-schema.ts +1 -0
- package/src/schema/team-tool-schema.ts +3 -3
- package/src/state/active-run-registry.ts +165 -0
- package/src/state/contracts.ts +1 -1
- package/src/state/mailbox.ts +44 -4
- package/src/state/state-store.ts +8 -1
- package/src/state/task-claims.ts +44 -44
- package/src/state/types.ts +44 -2
- package/src/state/usage.ts +29 -29
- package/src/subagents/async-entry.ts +1 -1
- package/src/subagents/index.ts +3 -3
- package/src/subagents/live/control.ts +1 -1
- package/src/subagents/live/manager.ts +1 -1
- package/src/subagents/live/realtime.ts +1 -1
- package/src/subagents/live/session-runtime.ts +1 -1
- package/src/subagents/manager.ts +1 -1
- package/src/subagents/spawn.ts +1 -1
- package/src/teams/team-config.ts +1 -0
- package/src/teams/team-serializer.ts +38 -38
- package/src/types/diff.d.ts +18 -18
- package/src/ui/crew-footer.ts +101 -101
- package/src/ui/crew-select-list.ts +111 -111
- package/src/ui/crew-widget.ts +4 -3
- package/src/ui/dashboard-panes/metrics-pane.ts +34 -34
- package/src/ui/dashboard-panes/progress-pane.ts +2 -0
- package/src/ui/dynamic-border.ts +25 -25
- package/src/ui/layout-primitives.ts +106 -106
- package/src/ui/loaders.ts +158 -158
- package/src/ui/render-diff.ts +119 -119
- package/src/ui/render-scheduler.ts +143 -143
- package/src/ui/run-snapshot-cache.ts +10 -2
- package/src/ui/snapshot-types.ts +2 -0
- package/src/ui/spinner.ts +17 -17
- package/src/ui/status-colors.ts +58 -58
- package/src/ui/syntax-highlight.ts +116 -116
- package/src/utils/atomic-write.ts +33 -33
- package/src/utils/completion-dedupe.ts +63 -63
- package/src/utils/frontmatter.ts +68 -68
- package/src/utils/git.ts +262 -262
- package/src/utils/ids.ts +12 -12
- package/src/utils/names.ts +27 -27
- package/src/utils/paths.ts +4 -2
- package/src/utils/redaction.ts +44 -44
- package/src/utils/safe-paths.ts +47 -47
- package/src/utils/sleep.ts +32 -32
- package/src/workflows/validate-workflow.ts +40 -40
- package/src/workflows/workflow-config.ts +1 -0
- package/src/worktree/branch-freshness.ts +45 -45
- package/teams/default.team.md +12 -12
- package/teams/fast-fix.team.md +11 -11
- package/teams/implementation.team.md +18 -18
- package/teams/parallel-research.team.md +14 -14
- package/teams/research.team.md +11 -11
- package/teams/review.team.md +12 -12
- package/workflows/default.workflow.md +29 -29
- package/workflows/fast-fix.workflow.md +22 -22
- package/workflows/implementation.workflow.md +38 -38
- package/workflows/parallel-research.workflow.md +46 -46
- package/workflows/research.workflow.md +22 -22
- package/workflows/review.workflow.md +30 -30
|
@@ -1,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";
|
|
@@ -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 },
|
|
@@ -398,7 +399,7 @@ export function registerPiTeams(pi: ExtensionAPI): void {
|
|
|
398
399
|
configureNotifications(ctx);
|
|
399
400
|
configureObservability(ctx);
|
|
400
401
|
configureDeliveryCoordinator();
|
|
401
|
-
const sessionId = (ctx as unknown as Record<string, unknown>).sessionId;
|
|
402
|
+
const sessionId = ctx.sessionManager?.getSessionId?.() ?? (ctx as unknown as Record<string, unknown>).sessionId;
|
|
402
403
|
if (typeof sessionId === "string" && sessionId) deliveryCoordinator?.activate(sessionId);
|
|
403
404
|
tryRegisterSessionCleanup(pi, () => { terminateActiveChildPiProcesses(); cleanupRuntime(); });
|
|
404
405
|
registerPiCrewPowerbarSegments(pi.events, loadedConfig.config.ui);
|
|
@@ -460,7 +461,7 @@ export function registerPiTeams(pi: ExtensionAPI): void {
|
|
|
460
461
|
const snapshotCache = lastFrameSnapshotCache ?? getRunSnapshotCache(currentCtx.cwd);
|
|
461
462
|
const manifests = lastPreloadedManifests.length > 0 ? lastPreloadedManifests : activeCache.list(20);
|
|
462
463
|
if (liveSidebarRunId) {
|
|
463
|
-
const placement = config?.widgetPlacement ??
|
|
464
|
+
const placement = config?.widgetPlacement ?? DEFAULT_UI.widgetPlacement;
|
|
464
465
|
if (widgetState.lastVisibility !== "hidden" || widgetState.lastPlacement !== placement) {
|
|
465
466
|
setExtensionWidget(currentCtx, "pi-crew", undefined, { placement });
|
|
466
467
|
setExtensionWidget(currentCtx, "pi-crew-active", undefined, { placement });
|
|
@@ -500,7 +501,7 @@ export function registerPiTeams(pi: ExtensionAPI): void {
|
|
|
500
501
|
}
|
|
501
502
|
};
|
|
502
503
|
|
|
503
|
-
const fallbackMs = loadedConfig.config.ui?.dashboardLiveRefreshMs ??
|
|
504
|
+
const fallbackMs = loadedConfig.config.ui?.dashboardLiveRefreshMs ?? DEFAULT_UI.refreshMs;
|
|
504
505
|
renderScheduler = new RenderScheduler(pi.events, renderTick, {
|
|
505
506
|
fallbackMs,
|
|
506
507
|
onInvalidate: () => getRunSnapshotCache(ctx.cwd).invalidate(),
|
|
@@ -530,8 +531,9 @@ export function registerPiTeams(pi: ExtensionAPI): void {
|
|
|
530
531
|
// Phase 11a: Dynamic resource discovery — inject pi-crew skill paths.
|
|
531
532
|
try {
|
|
532
533
|
pi.on("resources_discover", () => {
|
|
533
|
-
const
|
|
534
|
-
const
|
|
534
|
+
const sessionCwd = currentCtx?.cwd ?? process.cwd();
|
|
535
|
+
const skillDir = path.resolve(sessionCwd, "skills");
|
|
536
|
+
const extSkillDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "..", "skills");
|
|
535
537
|
const paths: string[] = [];
|
|
536
538
|
if (fs.existsSync(extSkillDir)) paths.push(extSkillDir);
|
|
537
539
|
if (skillDir !== extSkillDir && fs.existsSync(skillDir)) paths.push(skillDir);
|
|
@@ -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";
|
|
@@ -78,6 +80,10 @@ function depsNotify(ctx: ExtensionCommandContext, message: string, level: "info"
|
|
|
78
80
|
ctx.ui.notify(message, level);
|
|
79
81
|
}
|
|
80
82
|
|
|
83
|
+
function teamCommandContext(ctx: ExtensionCommandContext): ExtensionCommandContext & { sessionId?: string } {
|
|
84
|
+
return withSessionId(ctx);
|
|
85
|
+
}
|
|
86
|
+
|
|
81
87
|
async function handleHealthDashboardAction(ctx: ExtensionCommandContext, selection: RunDashboardSelection): Promise<void> {
|
|
82
88
|
const loaded = loadRunManifestById(ctx.cwd, selection.runId);
|
|
83
89
|
if (!loaded) {
|
|
@@ -121,7 +127,7 @@ export function registerTeamCommands(pi: ExtensionAPI, deps: RegisterTeamCommand
|
|
|
121
127
|
pi.registerCommand("teams", {
|
|
122
128
|
description: "List pi-crew teams, workflows, and agents",
|
|
123
129
|
handler: async (_args: string, ctx: ExtensionCommandContext) => {
|
|
124
|
-
const result = await handleTeamTool({ action: "list" }, ctx);
|
|
130
|
+
const result = await handleTeamTool({ action: "list" }, teamCommandContext(ctx));
|
|
125
131
|
await notifyCommandResult(ctx, commandText(result));
|
|
126
132
|
},
|
|
127
133
|
});
|
|
@@ -129,7 +135,7 @@ export function registerTeamCommands(pi: ExtensionAPI, deps: RegisterTeamCommand
|
|
|
129
135
|
pi.registerCommand("team-run", {
|
|
130
136
|
description: "Manually start a pi-crew run (agent may also use the team tool autonomously)",
|
|
131
137
|
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) });
|
|
138
|
+
const result = await handleTeamTool(parseRunArgs(args), { ...teamCommandContext(ctx), metricRegistry: deps.getMetricRegistry?.(), startForegroundRun: (runner, runId) => deps.startForegroundRun(ctx as ExtensionContext, runner, runId), onRunStarted: (runId) => deps.openLiveSidebar(ctx as ExtensionContext, runId) });
|
|
133
139
|
await notifyCommandResult(ctx, commandText(result));
|
|
134
140
|
},
|
|
135
141
|
});
|
|
@@ -146,7 +152,7 @@ export function registerTeamCommands(pi: ExtensionAPI, deps: RegisterTeamCommand
|
|
|
146
152
|
] as const) {
|
|
147
153
|
pi.registerCommand(name, { description, handler: async (args: string, ctx: ExtensionCommandContext) => {
|
|
148
154
|
const runId = args.trim() || undefined;
|
|
149
|
-
const result = await handleTeamTool({ action, runId }, ctx);
|
|
155
|
+
const result = await handleTeamTool({ action, runId }, teamCommandContext(ctx));
|
|
150
156
|
await notifyCommandResult(ctx, commandText(result));
|
|
151
157
|
} });
|
|
152
158
|
}
|
|
@@ -159,7 +165,7 @@ export function registerTeamCommands(pi: ExtensionAPI, deps: RegisterTeamCommand
|
|
|
159
165
|
const taskToken = tokens[0] === "--all" ? tokens.shift() : tokens.shift();
|
|
160
166
|
const taskId = taskToken === "--all" ? undefined : taskToken;
|
|
161
167
|
const message = tokens.join(" ") || undefined;
|
|
162
|
-
const result = await handleTeamTool({ action: "respond", runId, taskId, message }, ctx);
|
|
168
|
+
const result = await handleTeamTool({ action: "respond", runId, taskId, message }, teamCommandContext(ctx));
|
|
163
169
|
await notifyCommandResult(ctx, commandText(result));
|
|
164
170
|
},
|
|
165
171
|
});
|
|
@@ -178,19 +184,19 @@ export function registerTeamCommands(pi: ExtensionAPI, deps: RegisterTeamCommand
|
|
|
178
184
|
const [key, ...rest] = token.split("=");
|
|
179
185
|
if (key) config[key] = parseScalar(rest.join("="));
|
|
180
186
|
}
|
|
181
|
-
const result = await handleTeamTool({ action: "api", runId, config }, ctx);
|
|
187
|
+
const result = await handleTeamTool({ action: "api", runId, config }, teamCommandContext(ctx));
|
|
182
188
|
await notifyCommandResult(ctx, commandText(result));
|
|
183
189
|
},
|
|
184
190
|
});
|
|
185
191
|
|
|
186
192
|
pi.registerCommand("team-metrics", { description: "Show pi-crew metrics snapshot: [filter]", handler: async (args: string, ctx: ExtensionCommandContext) => {
|
|
187
193
|
const filter = args.trim() || undefined;
|
|
188
|
-
const result = await handleTeamTool({ action: "api", config: { operation: "metrics-snapshot", filter } }, { ...ctx, metricRegistry: deps.getMetricRegistry?.() });
|
|
194
|
+
const result = await handleTeamTool({ action: "api", config: { operation: "metrics-snapshot", filter } }, { ...teamCommandContext(ctx), metricRegistry: deps.getMetricRegistry?.() });
|
|
189
195
|
await notifyCommandResult(ctx, commandText(result));
|
|
190
196
|
} });
|
|
191
197
|
|
|
192
198
|
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);
|
|
199
|
+
const result = await handleTeamTool({ action: "imports" }, teamCommandContext(ctx));
|
|
194
200
|
await notifyCommandResult(ctx, commandText(result));
|
|
195
201
|
} });
|
|
196
202
|
|
|
@@ -198,7 +204,7 @@ export function registerTeamCommands(pi: ExtensionAPI, deps: RegisterTeamCommand
|
|
|
198
204
|
const tokens = args.trim().split(/\s+/).filter(Boolean);
|
|
199
205
|
const pathArg = tokens.find((token) => !token.startsWith("--"));
|
|
200
206
|
const scope = tokens.includes("--user") ? "user" : "project";
|
|
201
|
-
const result = await handleTeamTool({ action: "import", config: { path: pathArg, scope } }, ctx);
|
|
207
|
+
const result = await handleTeamTool({ action: "import", config: { path: pathArg, scope } }, teamCommandContext(ctx));
|
|
202
208
|
await notifyCommandResult(ctx, commandText(result));
|
|
203
209
|
} });
|
|
204
210
|
|
|
@@ -206,21 +212,21 @@ export function registerTeamCommands(pi: ExtensionAPI, deps: RegisterTeamCommand
|
|
|
206
212
|
const tokens = args.trim().split(/\s+/).filter(Boolean);
|
|
207
213
|
const keepToken = tokens.find((token) => token.startsWith("--keep="));
|
|
208
214
|
const keep = keepToken ? Number.parseInt(keepToken.slice("--keep=".length), 10) : undefined;
|
|
209
|
-
const result = await handleTeamTool({ action: "prune", keep, confirm: tokens.includes("--confirm") }, ctx);
|
|
215
|
+
const result = await handleTeamTool({ action: "prune", keep, confirm: tokens.includes("--confirm") }, teamCommandContext(ctx));
|
|
210
216
|
await notifyCommandResult(ctx, commandText(result));
|
|
211
217
|
} });
|
|
212
218
|
|
|
213
219
|
pi.registerCommand("team-forget", { description: "Forget a pi-crew run by deleting its state and artifacts", handler: async (args: string, ctx: ExtensionCommandContext) => {
|
|
214
220
|
const tokens = args.trim().split(/\s+/).filter(Boolean);
|
|
215
221
|
const runId = tokens.find((token) => !token.startsWith("--"));
|
|
216
|
-
const result = await handleTeamTool({ action: "forget", runId, force: tokens.includes("--force"), confirm: tokens.includes("--confirm") }, ctx);
|
|
222
|
+
const result = await handleTeamTool({ action: "forget", runId, force: tokens.includes("--force"), confirm: tokens.includes("--confirm") }, teamCommandContext(ctx));
|
|
217
223
|
await notifyCommandResult(ctx, commandText(result));
|
|
218
224
|
} });
|
|
219
225
|
|
|
220
226
|
pi.registerCommand("team-settings", {
|
|
221
227
|
description: "View or update pi-crew settings: [list|get <key>|set <key> <value>|unset <key>|path|scope]",
|
|
222
228
|
handler: async (args: string, ctx: ExtensionCommandContext) => {
|
|
223
|
-
const result = await handleTeamTool({ action: "settings", config: { args: args.trim() } }, ctx);
|
|
229
|
+
const result = await handleTeamTool({ action: "settings", config: { args: args.trim() } }, teamCommandContext(ctx));
|
|
224
230
|
await notifyCommandResult(ctx, commandText(result));
|
|
225
231
|
},
|
|
226
232
|
});
|
|
@@ -233,18 +239,18 @@ export function registerTeamCommands(pi: ExtensionAPI, deps: RegisterTeamCommand
|
|
|
233
239
|
const loaded = selected ? loadRunManifestById(ctx.cwd, selected.runId) : undefined;
|
|
234
240
|
if (ctx.hasUI && loaded) {
|
|
235
241
|
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)";
|
|
242
|
+
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
243
|
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
244
|
return;
|
|
239
245
|
}
|
|
240
|
-
const result = await handleTeamTool({ action: "api", runId, config: { operation: "read-agent-output", agentId: rawTaskId, maxBytes: 64_000 } }, ctx);
|
|
246
|
+
const result = await handleTeamTool({ action: "api", runId, config: { operation: "read-agent-output", agentId: rawTaskId, maxBytes: 64_000 } }, teamCommandContext(ctx));
|
|
241
247
|
await notifyCommandResult(ctx, commandText(result));
|
|
242
248
|
} });
|
|
243
249
|
|
|
244
250
|
pi.registerCommand("team-transcript", { description: "Open a pi-crew transcript viewer: <runId> [taskId]", handler: async (args: string, ctx: ExtensionCommandContext) => {
|
|
245
251
|
const [runId, taskId] = args.trim().split(/\s+/).filter(Boolean);
|
|
246
252
|
if (await openTranscriptViewer(ctx, runId, taskId)) return;
|
|
247
|
-
const result = await handleTeamTool({ action: "api", runId, config: { operation: "read-agent-transcript", agentId: taskId } }, ctx);
|
|
253
|
+
const result = await handleTeamTool({ action: "api", runId, config: { operation: "read-agent-transcript", agentId: taskId } }, teamCommandContext(ctx));
|
|
248
254
|
await notifyCommandResult(ctx, commandText(result));
|
|
249
255
|
} });
|
|
250
256
|
|
|
@@ -252,8 +258,8 @@ export function registerTeamCommands(pi: ExtensionAPI, deps: RegisterTeamCommand
|
|
|
252
258
|
for (;;) {
|
|
253
259
|
const runs = deps.getManifestCache(ctx.cwd).list(50);
|
|
254
260
|
const uiConfig = loadConfig(ctx.cwd).config.ui;
|
|
255
|
-
const rightPanel = uiConfig?.dashboardPlacement
|
|
256
|
-
const width = rightPanel ? Math.min(90, Math.max(40, uiConfig?.dashboardWidth ??
|
|
261
|
+
const rightPanel = (uiConfig?.dashboardPlacement ?? DEFAULT_UI.dashboardPlacement) === "right";
|
|
262
|
+
const width = rightPanel ? Math.min(90, Math.max(40, uiConfig?.dashboardWidth ?? DEFAULT_UI.dashboardWidth)) : "90%";
|
|
257
263
|
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
264
|
if (!selection) return;
|
|
259
265
|
if (selection.action === "reload") continue;
|
|
@@ -273,7 +279,7 @@ export function registerTeamCommands(pi: ExtensionAPI, deps: RegisterTeamCommand
|
|
|
273
279
|
continue;
|
|
274
280
|
}
|
|
275
281
|
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);
|
|
282
|
+
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
283
|
await notifyCommandResult(ctx, commandText(result));
|
|
278
284
|
return;
|
|
279
285
|
}
|
|
@@ -285,14 +291,15 @@ export function registerTeamCommands(pi: ExtensionAPI, deps: RegisterTeamCommand
|
|
|
285
291
|
const uiConfig = loadConfig(ctx.cwd).config.ui;
|
|
286
292
|
const styleArg = tokens.find((t) => t === "cat" || t === "armin");
|
|
287
293
|
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 ??
|
|
294
|
+
const style = (styleArg as "cat" | "armin" | undefined) ?? uiConfig?.mascotStyle ?? DEFAULT_UI.mascotStyle;
|
|
295
|
+
const effect = (effectArg as "random" | "none" | "typewriter" | "scanline" | "rain" | "fade" | "crt" | "glitch" | "dissolve" | undefined) ?? uiConfig?.mascotEffect ?? DEFAULT_UI.mascotEffect;
|
|
290
296
|
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
297
|
} });
|
|
292
298
|
|
|
293
|
-
pi.registerCommand("team-init", { description: "Initialize
|
|
299
|
+
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
300
|
const tokens = args.trim().split(/\s+/).filter(Boolean);
|
|
295
|
-
const
|
|
301
|
+
const configScope = tokens.includes("--project-config") || tokens.includes("--project") ? "project" : tokens.includes("--no-config") ? "none" : "global";
|
|
302
|
+
const result = await handleTeamTool({ action: "init", config: { copyBuiltins: tokens.includes("--copy-builtins"), overwrite: tokens.includes("--overwrite"), configScope } }, teamCommandContext(ctx));
|
|
296
303
|
await notifyCommandResult(ctx, commandText(result));
|
|
297
304
|
} });
|
|
298
305
|
|
|
@@ -300,14 +307,14 @@ export function registerTeamCommands(pi: ExtensionAPI, deps: RegisterTeamCommand
|
|
|
300
307
|
const tokens = args.trim().split(/\s+/).filter(Boolean);
|
|
301
308
|
const mode = tokens[0]?.toLowerCase();
|
|
302
309
|
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);
|
|
310
|
+
const result = await handleTeamTool({ action: "autonomy", config }, teamCommandContext(ctx));
|
|
304
311
|
await notifyCommandResult(ctx, commandText(result));
|
|
305
312
|
} });
|
|
306
313
|
|
|
307
314
|
pi.registerCommand("team-config", { description: "Show or update pi-crew config. Use key=value [--project] to update.", handler: async (args: string, ctx: ExtensionCommandContext) => {
|
|
308
315
|
const tokens = args.trim().split(/\s+/).filter(Boolean);
|
|
309
316
|
if (tokens.length === 0) {
|
|
310
|
-
const result = await handleTeamTool({ action: "config" }, ctx);
|
|
317
|
+
const result = await handleTeamTool({ action: "config" }, teamCommandContext(ctx));
|
|
311
318
|
await notifyCommandResult(ctx, commandText(result));
|
|
312
319
|
return;
|
|
313
320
|
}
|
|
@@ -324,7 +331,7 @@ export function registerTeamCommands(pi: ExtensionAPI, deps: RegisterTeamCommand
|
|
|
324
331
|
if (raw === "unset" || raw === "null") pushUnset(config, key);
|
|
325
332
|
else setNestedConfig(config, key, parseScalar(raw));
|
|
326
333
|
}
|
|
327
|
-
const result = await handleTeamTool({ action: "config", config }, ctx);
|
|
334
|
+
const result = await handleTeamTool({ action: "config", config }, teamCommandContext(ctx));
|
|
328
335
|
await notifyCommandResult(ctx, commandText(result));
|
|
329
336
|
} });
|
|
330
337
|
|
|
@@ -332,7 +339,7 @@ export function registerTeamCommands(pi: ExtensionAPI, deps: RegisterTeamCommand
|
|
|
332
339
|
["team-validate", "validate", "Validate pi-crew agents, teams, and workflows"],
|
|
333
340
|
["team-doctor", "doctor", "Check pi-crew installation and discovery readiness"],
|
|
334
341
|
] as const) pi.registerCommand(name, { description, handler: async (_args: string, ctx: ExtensionCommandContext) => {
|
|
335
|
-
const result = await handleTeamTool({ action }, ctx);
|
|
342
|
+
const result = await handleTeamTool({ action }, teamCommandContext(ctx));
|
|
336
343
|
await notifyCommandResult(ctx, commandText(result));
|
|
337
344
|
} });
|
|
338
345
|
|
|
@@ -1,125 +1,125 @@
|
|
|
1
|
-
import type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent";
|
|
2
|
-
import { listRecentRuns } from "../run-index.ts";
|
|
3
|
-
import type { ArtifactDescriptor, TeamRunManifest } from "../../state/types.ts";
|
|
4
|
-
|
|
5
|
-
export interface RegisterCompactionGuardOptions {
|
|
6
|
-
foregroundControllers: Set<AbortController>;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
const TRIGGER_RATIO = 0.75;
|
|
10
|
-
const HARD_RATIO = 0.95;
|
|
11
|
-
const DEFAULT_CONTEXT_WINDOW = 200_000;
|
|
12
|
-
const MAX_ARTIFACT_INDEX_RUNS = 10;
|
|
13
|
-
const MAX_ARTIFACT_INDEX_ITEMS = 80;
|
|
14
|
-
|
|
15
|
-
function contextWindow(ctx: { model?: { contextWindow?: number } }): number {
|
|
16
|
-
const value = ctx.model?.contextWindow;
|
|
17
|
-
return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : DEFAULT_CONTEXT_WINDOW;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
function usageRatio(ctx: { getContextUsage(): { tokens: number | null } | undefined; model?: { contextWindow?: number } }): number | undefined {
|
|
21
|
-
const tokens = ctx.getContextUsage()?.tokens;
|
|
22
|
-
if (tokens === null || tokens === undefined || !Number.isFinite(tokens)) return undefined;
|
|
23
|
-
return tokens / contextWindow(ctx);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
interface CrewArtifactIndexEntry {
|
|
27
|
-
runId: string;
|
|
28
|
-
status: TeamRunManifest["status"];
|
|
29
|
-
team: string;
|
|
30
|
-
workflow?: string;
|
|
31
|
-
goal: string;
|
|
32
|
-
artifact: Pick<ArtifactDescriptor, "kind" | "path" | "producer" | "sizeBytes" | "createdAt">;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function collectCrewArtifactIndex(cwd: string): CrewArtifactIndexEntry[] {
|
|
36
|
-
const entries: CrewArtifactIndexEntry[] = [];
|
|
37
|
-
for (const run of listRecentRuns(cwd, MAX_ARTIFACT_INDEX_RUNS)) {
|
|
38
|
-
for (const artifact of run.artifacts) {
|
|
39
|
-
entries.push({
|
|
40
|
-
runId: run.runId,
|
|
41
|
-
status: run.status,
|
|
42
|
-
team: run.team,
|
|
43
|
-
workflow: run.workflow,
|
|
44
|
-
goal: run.goal,
|
|
45
|
-
artifact: {
|
|
46
|
-
kind: artifact.kind,
|
|
47
|
-
path: artifact.path,
|
|
48
|
-
producer: artifact.producer,
|
|
49
|
-
sizeBytes: artifact.sizeBytes,
|
|
50
|
-
createdAt: artifact.createdAt,
|
|
51
|
-
},
|
|
52
|
-
});
|
|
53
|
-
if (entries.length >= MAX_ARTIFACT_INDEX_ITEMS) return entries;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
return entries;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function formatCrewArtifactIndex(entries: CrewArtifactIndexEntry[]): string {
|
|
60
|
-
if (!entries.length) return "";
|
|
61
|
-
const lines = ["", "# pi-crew artifact index", "Preserve these run artifact references in the compaction summary:"];
|
|
62
|
-
for (const entry of entries) {
|
|
63
|
-
lines.push(`- ${entry.artifact.kind}: ${entry.artifact.path} (run=${entry.runId}, status=${entry.status}, team=${entry.team}, workflow=${entry.workflow ?? "none"}, producer=${entry.artifact.producer})`);
|
|
64
|
-
}
|
|
65
|
-
return lines.join("\n");
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
export function registerCompactionGuard(pi: ExtensionAPI, options: RegisterCompactionGuardOptions): void {
|
|
69
|
-
let pendingCompactReason: string | null = null;
|
|
70
|
-
let compactionInProgress = false;
|
|
71
|
-
|
|
72
|
-
const startCompact = (ctx: ExtensionContext, reason: string): void => {
|
|
73
|
-
if (compactionInProgress) return;
|
|
74
|
-
compactionInProgress = true;
|
|
75
|
-
const artifactIndex = collectCrewArtifactIndex(ctx.cwd);
|
|
76
|
-
if (artifactIndex.length > 0) {
|
|
77
|
-
pi.appendEntry("crew:artifact-index", {
|
|
78
|
-
reason,
|
|
79
|
-
createdAt: new Date().toISOString(),
|
|
80
|
-
artifacts: artifactIndex,
|
|
81
|
-
});
|
|
82
|
-
}
|
|
83
|
-
ctx.compact({
|
|
84
|
-
customInstructions: `Prioritize keeping pi-crew run state, task results, artifact references, run IDs, and next actions. Keep completed-task detail concise.${formatCrewArtifactIndex(artifactIndex)}`,
|
|
85
|
-
onComplete: () => {
|
|
86
|
-
compactionInProgress = false;
|
|
87
|
-
ctx.ui.notify(reason === "deferred" ? "Deferred compaction completed" : "Auto-compacted context during team run", "info");
|
|
88
|
-
},
|
|
89
|
-
onError: (error) => {
|
|
90
|
-
compactionInProgress = false;
|
|
91
|
-
ctx.ui.notify(`${reason === "deferred" ? "Deferred" : "Auto"} compaction failed: ${error.message}`, "error");
|
|
92
|
-
},
|
|
93
|
-
});
|
|
94
|
-
};
|
|
95
|
-
|
|
96
|
-
// Phase 1.2: Defer compaction during foreground runs unless context is critically full.
|
|
97
|
-
pi.on("session_before_compact", async (_event, ctx) => {
|
|
98
|
-
if (options.foregroundControllers.size === 0) return;
|
|
99
|
-
const ratio = usageRatio(ctx);
|
|
100
|
-
if (ratio !== undefined && ratio >= HARD_RATIO) {
|
|
101
|
-
ctx.ui.notify("Compaction allowed despite foreground run: context is critically full", "warning");
|
|
102
|
-
return;
|
|
103
|
-
}
|
|
104
|
-
pendingCompactReason = "deferred-during-foreground-run";
|
|
105
|
-
ctx.ui.notify("Compaction deferred: foreground team run in progress", "info");
|
|
106
|
-
return { cancel: true };
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
// Phase 2.1: Proactive compaction with dynamic threshold based on model context window.
|
|
110
|
-
pi.on("turn_end", (_event, ctx) => {
|
|
111
|
-
if (compactionInProgress) return;
|
|
112
|
-
if (options.foregroundControllers.size === 0 && pendingCompactReason) {
|
|
113
|
-
pendingCompactReason = null;
|
|
114
|
-
startCompact(ctx, "deferred");
|
|
115
|
-
return;
|
|
116
|
-
}
|
|
117
|
-
const ratio = usageRatio(ctx);
|
|
118
|
-
if (ratio === undefined || ratio < TRIGGER_RATIO) return;
|
|
119
|
-
if (options.foregroundControllers.size > 0) {
|
|
120
|
-
pendingCompactReason = "threshold-during-foreground-run";
|
|
121
|
-
return;
|
|
122
|
-
}
|
|
123
|
-
startCompact(ctx, "threshold");
|
|
124
|
-
});
|
|
125
|
-
}
|
|
1
|
+
import type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent";
|
|
2
|
+
import { listRecentRuns } from "../run-index.ts";
|
|
3
|
+
import type { ArtifactDescriptor, TeamRunManifest } from "../../state/types.ts";
|
|
4
|
+
|
|
5
|
+
export interface RegisterCompactionGuardOptions {
|
|
6
|
+
foregroundControllers: Set<AbortController>;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const TRIGGER_RATIO = 0.75;
|
|
10
|
+
const HARD_RATIO = 0.95;
|
|
11
|
+
const DEFAULT_CONTEXT_WINDOW = 200_000;
|
|
12
|
+
const MAX_ARTIFACT_INDEX_RUNS = 10;
|
|
13
|
+
const MAX_ARTIFACT_INDEX_ITEMS = 80;
|
|
14
|
+
|
|
15
|
+
function contextWindow(ctx: { model?: { contextWindow?: number } }): number {
|
|
16
|
+
const value = ctx.model?.contextWindow;
|
|
17
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : DEFAULT_CONTEXT_WINDOW;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function usageRatio(ctx: { getContextUsage(): { tokens: number | null } | undefined; model?: { contextWindow?: number } }): number | undefined {
|
|
21
|
+
const tokens = ctx.getContextUsage()?.tokens;
|
|
22
|
+
if (tokens === null || tokens === undefined || !Number.isFinite(tokens)) return undefined;
|
|
23
|
+
return tokens / contextWindow(ctx);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
interface CrewArtifactIndexEntry {
|
|
27
|
+
runId: string;
|
|
28
|
+
status: TeamRunManifest["status"];
|
|
29
|
+
team: string;
|
|
30
|
+
workflow?: string;
|
|
31
|
+
goal: string;
|
|
32
|
+
artifact: Pick<ArtifactDescriptor, "kind" | "path" | "producer" | "sizeBytes" | "createdAt">;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function collectCrewArtifactIndex(cwd: string): CrewArtifactIndexEntry[] {
|
|
36
|
+
const entries: CrewArtifactIndexEntry[] = [];
|
|
37
|
+
for (const run of listRecentRuns(cwd, MAX_ARTIFACT_INDEX_RUNS)) {
|
|
38
|
+
for (const artifact of run.artifacts) {
|
|
39
|
+
entries.push({
|
|
40
|
+
runId: run.runId,
|
|
41
|
+
status: run.status,
|
|
42
|
+
team: run.team,
|
|
43
|
+
workflow: run.workflow,
|
|
44
|
+
goal: run.goal,
|
|
45
|
+
artifact: {
|
|
46
|
+
kind: artifact.kind,
|
|
47
|
+
path: artifact.path,
|
|
48
|
+
producer: artifact.producer,
|
|
49
|
+
sizeBytes: artifact.sizeBytes,
|
|
50
|
+
createdAt: artifact.createdAt,
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
if (entries.length >= MAX_ARTIFACT_INDEX_ITEMS) return entries;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return entries;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function formatCrewArtifactIndex(entries: CrewArtifactIndexEntry[]): string {
|
|
60
|
+
if (!entries.length) return "";
|
|
61
|
+
const lines = ["", "# pi-crew artifact index", "Preserve these run artifact references in the compaction summary:"];
|
|
62
|
+
for (const entry of entries) {
|
|
63
|
+
lines.push(`- ${entry.artifact.kind}: ${entry.artifact.path} (run=${entry.runId}, status=${entry.status}, team=${entry.team}, workflow=${entry.workflow ?? "none"}, producer=${entry.artifact.producer})`);
|
|
64
|
+
}
|
|
65
|
+
return lines.join("\n");
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function registerCompactionGuard(pi: ExtensionAPI, options: RegisterCompactionGuardOptions): void {
|
|
69
|
+
let pendingCompactReason: string | null = null;
|
|
70
|
+
let compactionInProgress = false;
|
|
71
|
+
|
|
72
|
+
const startCompact = (ctx: ExtensionContext, reason: string): void => {
|
|
73
|
+
if (compactionInProgress) return;
|
|
74
|
+
compactionInProgress = true;
|
|
75
|
+
const artifactIndex = collectCrewArtifactIndex(ctx.cwd);
|
|
76
|
+
if (artifactIndex.length > 0) {
|
|
77
|
+
pi.appendEntry("crew:artifact-index", {
|
|
78
|
+
reason,
|
|
79
|
+
createdAt: new Date().toISOString(),
|
|
80
|
+
artifacts: artifactIndex,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
ctx.compact({
|
|
84
|
+
customInstructions: `Prioritize keeping pi-crew run state, task results, artifact references, run IDs, and next actions. Keep completed-task detail concise.${formatCrewArtifactIndex(artifactIndex)}`,
|
|
85
|
+
onComplete: () => {
|
|
86
|
+
compactionInProgress = false;
|
|
87
|
+
ctx.ui.notify(reason === "deferred" ? "Deferred compaction completed" : "Auto-compacted context during team run", "info");
|
|
88
|
+
},
|
|
89
|
+
onError: (error) => {
|
|
90
|
+
compactionInProgress = false;
|
|
91
|
+
ctx.ui.notify(`${reason === "deferred" ? "Deferred" : "Auto"} compaction failed: ${error.message}`, "error");
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
// Phase 1.2: Defer compaction during foreground runs unless context is critically full.
|
|
97
|
+
pi.on("session_before_compact", async (_event, ctx) => {
|
|
98
|
+
if (options.foregroundControllers.size === 0) return;
|
|
99
|
+
const ratio = usageRatio(ctx);
|
|
100
|
+
if (ratio !== undefined && ratio >= HARD_RATIO) {
|
|
101
|
+
ctx.ui.notify("Compaction allowed despite foreground run: context is critically full", "warning");
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
pendingCompactReason = "deferred-during-foreground-run";
|
|
105
|
+
ctx.ui.notify("Compaction deferred: foreground team run in progress", "info");
|
|
106
|
+
return { cancel: true };
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
// Phase 2.1: Proactive compaction with dynamic threshold based on model context window.
|
|
110
|
+
pi.on("turn_end", (_event, ctx) => {
|
|
111
|
+
if (compactionInProgress) return;
|
|
112
|
+
if (options.foregroundControllers.size === 0 && pendingCompactReason) {
|
|
113
|
+
pendingCompactReason = null;
|
|
114
|
+
startCompact(ctx, "deferred");
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
const ratio = usageRatio(ctx);
|
|
118
|
+
if (ratio === undefined || ratio < TRIGGER_RATIO) return;
|
|
119
|
+
if (options.foregroundControllers.size > 0) {
|
|
120
|
+
pendingCompactReason = "threshold-during-foreground-run";
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
startCompact(ctx, "threshold");
|
|
124
|
+
});
|
|
125
|
+
}
|