pi-crew 0.1.30 → 0.1.32
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 +9 -0
- package/README.md +80 -27
- 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/architecture.md +173 -164
- 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-extension-examples.md +297 -0
- package/docs/research-extension-system.md +324 -0
- package/docs/research-optimization-plan.md +548 -0
- package/docs/research-pi-coding-agent.md +357 -0
- package/docs/resource-formats.md +4 -3
- package/docs/runtime-flow.md +148 -148
- package/docs/source-runtime-refactor-map.md +83 -83
- package/docs/usage.md +3 -3
- package/index.ts +6 -6
- package/package.json +1 -1
- package/schema.json +53 -1
- package/skills/git-master/SKILL.md +24 -19
- package/skills/read-only-explorer/SKILL.md +26 -21
- package/skills/safe-bash/SKILL.md +21 -16
- package/skills/task-packet/SKILL.md +28 -23
- package/skills/verify-evidence/SKILL.md +27 -22
- package/src/agents/agent-serializer.ts +34 -34
- package/src/agents/discover-agents.ts +102 -102
- package/src/config/config.ts +14 -1
- package/src/config/defaults.ts +3 -2
- package/src/extension/cross-extension-rpc.ts +82 -82
- package/src/extension/import-index.ts +4 -3
- package/src/extension/management.ts +2 -2
- package/src/extension/project-init.ts +9 -7
- package/src/extension/register.ts +63 -0
- package/src/extension/registration/artifact-cleanup.ts +15 -14
- package/src/extension/registration/commands.ts +208 -208
- package/src/extension/registration/compaction-guard.ts +125 -0
- package/src/extension/registration/subagent-tools.ts +27 -8
- package/src/extension/registration/team-tool.ts +61 -44
- package/src/extension/result-watcher.ts +98 -98
- package/src/extension/run-import.ts +4 -4
- package/src/extension/run-index.ts +14 -14
- package/src/extension/run-maintenance.ts +24 -24
- package/src/extension/team-tool/api.ts +3 -0
- package/src/extension/team-tool/cancel.ts +31 -31
- package/src/extension/team-tool/doctor.ts +179 -178
- package/src/extension/team-tool/inspect.ts +41 -41
- package/src/extension/team-tool/lifecycle-actions.ts +79 -79
- package/src/extension/team-tool/plan.ts +19 -19
- package/src/extension/team-tool/status.ts +73 -73
- package/src/prompt/prompt-runtime.ts +68 -68
- package/src/runtime/agent-control.ts +64 -64
- package/src/runtime/agent-memory.ts +72 -72
- package/src/runtime/agent-observability.ts +113 -113
- package/src/runtime/async-marker.ts +26 -26
- package/src/runtime/background-runner.ts +53 -53
- package/src/runtime/crew-agent-runtime.ts +58 -58
- package/src/runtime/direct-run.ts +35 -35
- package/src/runtime/foreground-control.ts +82 -82
- package/src/runtime/green-contract.ts +46 -46
- package/src/runtime/group-join.ts +88 -88
- package/src/runtime/live-agent-control.ts +78 -78
- package/src/runtime/live-agent-manager.ts +85 -85
- package/src/runtime/live-control-realtime.ts +36 -36
- package/src/runtime/live-session-runtime.ts +299 -299
- package/src/runtime/manifest-cache.ts +212 -214
- package/src/runtime/model-fallback.ts +261 -261
- package/src/runtime/parallel-research.ts +44 -44
- package/src/runtime/parallel-utils.ts +99 -99
- package/src/runtime/pi-json-output.ts +111 -111
- package/src/runtime/pi-spawn.ts +96 -96
- package/src/runtime/policy-engine.ts +78 -78
- package/src/runtime/post-exit-stdio-guard.ts +86 -86
- package/src/runtime/process-status.ts +56 -56
- package/src/runtime/progress-event-coalescer.ts +43 -43
- package/src/runtime/recovery-recipes.ts +74 -74
- package/src/runtime/role-permission.ts +39 -39
- package/src/runtime/session-usage.ts +79 -79
- package/src/runtime/sidechain-output.ts +28 -28
- package/src/runtime/subagent-manager.ts +29 -2
- package/src/runtime/task-display.ts +38 -38
- package/src/runtime/task-output-context.ts +106 -106
- package/src/runtime/task-packet.ts +84 -84
- package/src/runtime/task-runner/live-executor.ts +98 -98
- package/src/runtime/task-runner/progress.ts +111 -111
- package/src/runtime/task-runner/prompt-builder.ts +72 -72
- package/src/runtime/task-runner/result-utils.ts +14 -14
- package/src/runtime/task-runner/state-helpers.ts +22 -22
- package/src/runtime/worker-heartbeat.ts +21 -21
- package/src/runtime/worker-startup.ts +57 -57
- package/src/schema/config-schema.ts +12 -0
- package/src/schema/team-tool-schema.ts +100 -100
- package/src/state/artifact-store.ts +108 -108
- package/src/state/contracts.ts +105 -105
- package/src/state/jsonl-writer.ts +77 -77
- package/src/state/state-store.ts +8 -9
- package/src/state/task-claims.ts +42 -42
- package/src/state/types.ts +180 -180
- package/src/state/usage.ts +29 -29
- package/src/subagents/async-entry.ts +1 -1
- package/src/subagents/index.ts +3 -3
- package/src/subagents/live/control.ts +1 -1
- package/src/subagents/live/manager.ts +1 -1
- package/src/subagents/live/realtime.ts +1 -1
- package/src/subagents/live/session-runtime.ts +1 -1
- package/src/subagents/manager.ts +1 -1
- package/src/subagents/spawn.ts +1 -1
- package/src/teams/discover-teams.ts +2 -2
- package/src/teams/team-serializer.ts +36 -36
- package/src/types/diff.d.ts +18 -0
- package/src/ui/crew-footer.ts +101 -101
- package/src/ui/crew-select-list.ts +111 -111
- package/src/ui/crew-widget.ts +285 -285
- 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/mascot.ts +441 -441
- package/src/ui/powerbar-publisher.ts +94 -94
- package/src/ui/render-diff.ts +119 -119
- package/src/ui/run-dashboard.ts +372 -372
- package/src/ui/status-colors.ts +54 -54
- package/src/ui/syntax-highlight.ts +116 -116
- package/src/ui/transcript-viewer.ts +302 -302
- package/src/utils/completion-dedupe.ts +63 -63
- package/src/utils/file-coalescer.ts +84 -84
- package/src/utils/frontmatter.ts +36 -36
- package/src/utils/fs-watch.ts +31 -31
- package/src/utils/git.ts +262 -262
- package/src/utils/ids.ts +12 -12
- package/src/utils/names.ts +26 -26
- package/src/utils/paths.ts +34 -7
- package/src/utils/sleep.ts +32 -32
- package/src/utils/timings.ts +31 -31
- package/src/utils/visual.ts +159 -159
- package/src/workflows/discover-workflows.ts +2 -2
- package/src/workflows/validate-workflow.ts +40 -40
- package/src/worktree/branch-freshness.ts +45 -45
- package/src/worktree/cleanup.ts +71 -69
- package/src/worktree/worktree-manager.ts +3 -1
- package/teams/default.team.md +12 -12
- package/teams/fast-fix.team.md +11 -11
- package/teams/implementation.team.md +18 -18
- package/teams/parallel-research.team.md +14 -14
- package/teams/research.team.md +11 -11
- package/teams/review.team.md +12 -12
- package/workflows/default.workflow.md +29 -29
- package/workflows/fast-fix.workflow.md +22 -22
- package/workflows/implementation.workflow.md +38 -38
- package/workflows/parallel-research.workflow.md +46 -46
- package/workflows/research.workflow.md +22 -22
- package/workflows/review.workflow.md +30 -30
|
@@ -1,102 +1,102 @@
|
|
|
1
|
-
import * as fs from "node:fs";
|
|
2
|
-
import * as path from "node:path";
|
|
3
|
-
import type { AgentConfig, ResourceSource } from "./agent-config.ts";
|
|
4
|
-
import { loadConfig } from "../config/config.ts";
|
|
5
|
-
import { parseCsv, parseFrontmatter } from "../utils/frontmatter.ts";
|
|
6
|
-
import { packageRoot,
|
|
7
|
-
|
|
8
|
-
export interface AgentDiscoveryResult {
|
|
9
|
-
builtin: AgentConfig[];
|
|
10
|
-
user: AgentConfig[];
|
|
11
|
-
project: AgentConfig[];
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
function parseCost(value: string | undefined): "free" | "cheap" | "expensive" | undefined {
|
|
15
|
-
return value === "free" || value === "cheap" || value === "expensive" ? value : undefined;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function parseMemory(value: string | undefined): "user" | "project" | "local" | undefined {
|
|
19
|
-
return value === "user" || value === "project" || value === "local" ? value : undefined;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function parseAgentFile(filePath: string, source: ResourceSource): AgentConfig | undefined {
|
|
23
|
-
try {
|
|
24
|
-
const content = fs.readFileSync(filePath, "utf-8");
|
|
25
|
-
const { frontmatter, body } = parseFrontmatter(content);
|
|
26
|
-
const name = frontmatter.name?.trim() || path.basename(filePath, path.extname(filePath));
|
|
27
|
-
const description = frontmatter.description?.trim() || "No description provided.";
|
|
28
|
-
const triggers = parseCsv(frontmatter.triggers ?? frontmatter.trigger);
|
|
29
|
-
const useWhen = parseCsv(frontmatter.useWhen);
|
|
30
|
-
const avoidWhen = parseCsv(frontmatter.avoidWhen);
|
|
31
|
-
const cost = parseCost(frontmatter.cost);
|
|
32
|
-
const category = frontmatter.category?.trim() || undefined;
|
|
33
|
-
return {
|
|
34
|
-
name,
|
|
35
|
-
description,
|
|
36
|
-
source,
|
|
37
|
-
filePath,
|
|
38
|
-
systemPrompt: body.trim(),
|
|
39
|
-
model: frontmatter.model || undefined,
|
|
40
|
-
fallbackModels: parseCsv(frontmatter.fallbackModels),
|
|
41
|
-
thinking: frontmatter.thinking || undefined,
|
|
42
|
-
tools: parseCsv(frontmatter.tools),
|
|
43
|
-
extensions: frontmatter.extensions === "" ? [] : parseCsv(frontmatter.extensions),
|
|
44
|
-
skills: parseCsv(frontmatter.skills ?? frontmatter.skill),
|
|
45
|
-
systemPromptMode: frontmatter.systemPromptMode === "append" ? "append" : "replace",
|
|
46
|
-
inheritProjectContext: frontmatter.inheritProjectContext === "true",
|
|
47
|
-
inheritSkills: frontmatter.inheritSkills === "true",
|
|
48
|
-
memory: parseMemory(frontmatter.memory),
|
|
49
|
-
disabled: frontmatter.disabled === "true" || frontmatter.enabled === "false",
|
|
50
|
-
routing: triggers || useWhen || avoidWhen || cost || category ? { triggers, useWhen, avoidWhen, cost, category } : undefined,
|
|
51
|
-
};
|
|
52
|
-
} catch {
|
|
53
|
-
return undefined;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function readAgentDir(dir: string, source: ResourceSource): AgentConfig[] {
|
|
58
|
-
if (!fs.existsSync(dir)) return [];
|
|
59
|
-
return fs.readdirSync(dir)
|
|
60
|
-
.filter((entry) => entry.endsWith(".md") && !entry.endsWith(".team.md") && !entry.endsWith(".workflow.md"))
|
|
61
|
-
.map((entry) => parseAgentFile(path.join(dir, entry), source))
|
|
62
|
-
.filter((agent): agent is AgentConfig => agent !== undefined)
|
|
63
|
-
.sort((a, b) => a.name.localeCompare(b.name));
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
function applyAgentOverrides(agents: AgentConfig[], cwd: string): AgentConfig[] {
|
|
67
|
-
const loaded = loadConfig(cwd);
|
|
68
|
-
const config = loaded.config.agents;
|
|
69
|
-
const overrides = config?.overrides ?? {};
|
|
70
|
-
return agents
|
|
71
|
-
.filter((agent) => !(config?.disableBuiltins && agent.source === "builtin"))
|
|
72
|
-
.map((agent) => {
|
|
73
|
-
const overrideEntry = Object.entries(overrides).find(([name]) => name.toLowerCase() === agent.name.toLowerCase());
|
|
74
|
-
if (!overrideEntry) return agent;
|
|
75
|
-
const [, override] = overrideEntry;
|
|
76
|
-
return {
|
|
77
|
-
...agent,
|
|
78
|
-
disabled: override.disabled ?? agent.disabled,
|
|
79
|
-
model: override.model === false ? undefined : override.model ?? agent.model,
|
|
80
|
-
fallbackModels: override.fallbackModels === false ? undefined : override.fallbackModels ?? agent.fallbackModels,
|
|
81
|
-
thinking: override.thinking === false ? undefined : override.thinking ?? agent.thinking,
|
|
82
|
-
tools: override.tools === false ? undefined : override.tools ?? agent.tools,
|
|
83
|
-
override: { source: "config", path: loaded.path },
|
|
84
|
-
};
|
|
85
|
-
});
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
export function discoverAgents(cwd: string): AgentDiscoveryResult {
|
|
89
|
-
return {
|
|
90
|
-
builtin: applyAgentOverrides(readAgentDir(path.join(packageRoot(), "agents"), "builtin"), cwd),
|
|
91
|
-
user: applyAgentOverrides(readAgentDir(path.join(userPiRoot(), "agents"), "user"), cwd),
|
|
92
|
-
project: applyAgentOverrides(readAgentDir(path.join(
|
|
93
|
-
};
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
export function allAgents(discovery: AgentDiscoveryResult): AgentConfig[] {
|
|
97
|
-
const byName = new Map<string, AgentConfig>();
|
|
98
|
-
for (const agent of [...discovery.builtin, ...discovery.user, ...discovery.project]) {
|
|
99
|
-
byName.set(agent.name.toLowerCase(), agent);
|
|
100
|
-
}
|
|
101
|
-
return [...byName.values()].filter((agent) => !agent.disabled).sort((a, b) => a.name.localeCompare(b.name));
|
|
102
|
-
}
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import type { AgentConfig, ResourceSource } from "./agent-config.ts";
|
|
4
|
+
import { loadConfig } from "../config/config.ts";
|
|
5
|
+
import { parseCsv, parseFrontmatter } from "../utils/frontmatter.ts";
|
|
6
|
+
import { packageRoot, projectCrewRoot, userPiRoot } from "../utils/paths.ts";
|
|
7
|
+
|
|
8
|
+
export interface AgentDiscoveryResult {
|
|
9
|
+
builtin: AgentConfig[];
|
|
10
|
+
user: AgentConfig[];
|
|
11
|
+
project: AgentConfig[];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function parseCost(value: string | undefined): "free" | "cheap" | "expensive" | undefined {
|
|
15
|
+
return value === "free" || value === "cheap" || value === "expensive" ? value : undefined;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function parseMemory(value: string | undefined): "user" | "project" | "local" | undefined {
|
|
19
|
+
return value === "user" || value === "project" || value === "local" ? value : undefined;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function parseAgentFile(filePath: string, source: ResourceSource): AgentConfig | undefined {
|
|
23
|
+
try {
|
|
24
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
25
|
+
const { frontmatter, body } = parseFrontmatter(content);
|
|
26
|
+
const name = frontmatter.name?.trim() || path.basename(filePath, path.extname(filePath));
|
|
27
|
+
const description = frontmatter.description?.trim() || "No description provided.";
|
|
28
|
+
const triggers = parseCsv(frontmatter.triggers ?? frontmatter.trigger);
|
|
29
|
+
const useWhen = parseCsv(frontmatter.useWhen);
|
|
30
|
+
const avoidWhen = parseCsv(frontmatter.avoidWhen);
|
|
31
|
+
const cost = parseCost(frontmatter.cost);
|
|
32
|
+
const category = frontmatter.category?.trim() || undefined;
|
|
33
|
+
return {
|
|
34
|
+
name,
|
|
35
|
+
description,
|
|
36
|
+
source,
|
|
37
|
+
filePath,
|
|
38
|
+
systemPrompt: body.trim(),
|
|
39
|
+
model: frontmatter.model || undefined,
|
|
40
|
+
fallbackModels: parseCsv(frontmatter.fallbackModels),
|
|
41
|
+
thinking: frontmatter.thinking || undefined,
|
|
42
|
+
tools: parseCsv(frontmatter.tools),
|
|
43
|
+
extensions: frontmatter.extensions === "" ? [] : parseCsv(frontmatter.extensions),
|
|
44
|
+
skills: parseCsv(frontmatter.skills ?? frontmatter.skill),
|
|
45
|
+
systemPromptMode: frontmatter.systemPromptMode === "append" ? "append" : "replace",
|
|
46
|
+
inheritProjectContext: frontmatter.inheritProjectContext === "true",
|
|
47
|
+
inheritSkills: frontmatter.inheritSkills === "true",
|
|
48
|
+
memory: parseMemory(frontmatter.memory),
|
|
49
|
+
disabled: frontmatter.disabled === "true" || frontmatter.enabled === "false",
|
|
50
|
+
routing: triggers || useWhen || avoidWhen || cost || category ? { triggers, useWhen, avoidWhen, cost, category } : undefined,
|
|
51
|
+
};
|
|
52
|
+
} catch {
|
|
53
|
+
return undefined;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function readAgentDir(dir: string, source: ResourceSource): AgentConfig[] {
|
|
58
|
+
if (!fs.existsSync(dir)) return [];
|
|
59
|
+
return fs.readdirSync(dir)
|
|
60
|
+
.filter((entry) => entry.endsWith(".md") && !entry.endsWith(".team.md") && !entry.endsWith(".workflow.md"))
|
|
61
|
+
.map((entry) => parseAgentFile(path.join(dir, entry), source))
|
|
62
|
+
.filter((agent): agent is AgentConfig => agent !== undefined)
|
|
63
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function applyAgentOverrides(agents: AgentConfig[], cwd: string): AgentConfig[] {
|
|
67
|
+
const loaded = loadConfig(cwd);
|
|
68
|
+
const config = loaded.config.agents;
|
|
69
|
+
const overrides = config?.overrides ?? {};
|
|
70
|
+
return agents
|
|
71
|
+
.filter((agent) => !(config?.disableBuiltins && agent.source === "builtin"))
|
|
72
|
+
.map((agent) => {
|
|
73
|
+
const overrideEntry = Object.entries(overrides).find(([name]) => name.toLowerCase() === agent.name.toLowerCase());
|
|
74
|
+
if (!overrideEntry) return agent;
|
|
75
|
+
const [, override] = overrideEntry;
|
|
76
|
+
return {
|
|
77
|
+
...agent,
|
|
78
|
+
disabled: override.disabled ?? agent.disabled,
|
|
79
|
+
model: override.model === false ? undefined : override.model ?? agent.model,
|
|
80
|
+
fallbackModels: override.fallbackModels === false ? undefined : override.fallbackModels ?? agent.fallbackModels,
|
|
81
|
+
thinking: override.thinking === false ? undefined : override.thinking ?? agent.thinking,
|
|
82
|
+
tools: override.tools === false ? undefined : override.tools ?? agent.tools,
|
|
83
|
+
override: { source: "config", path: loaded.path },
|
|
84
|
+
};
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function discoverAgents(cwd: string): AgentDiscoveryResult {
|
|
89
|
+
return {
|
|
90
|
+
builtin: applyAgentOverrides(readAgentDir(path.join(packageRoot(), "agents"), "builtin"), cwd),
|
|
91
|
+
user: applyAgentOverrides(readAgentDir(path.join(userPiRoot(), "agents"), "user"), cwd),
|
|
92
|
+
project: applyAgentOverrides(readAgentDir(path.join(projectCrewRoot(cwd), "agents"), "project"), cwd),
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function allAgents(discovery: AgentDiscoveryResult): AgentConfig[] {
|
|
97
|
+
const byName = new Map<string, AgentConfig>();
|
|
98
|
+
for (const agent of [...discovery.builtin, ...discovery.user, ...discovery.project]) {
|
|
99
|
+
byName.set(agent.name.toLowerCase(), agent);
|
|
100
|
+
}
|
|
101
|
+
return [...byName.values()].filter((agent) => !agent.disabled).sort((a, b) => a.name.localeCompare(b.name));
|
|
102
|
+
}
|
package/src/config/config.ts
CHANGED
|
@@ -4,6 +4,7 @@ import * as fs from "node:fs";
|
|
|
4
4
|
import * as os from "node:os";
|
|
5
5
|
import * as path from "node:path";
|
|
6
6
|
import { PiTeamsAutonomyProfileSchema, PiTeamsConfigSchema } from "../schema/config-schema.ts";
|
|
7
|
+
import { projectCrewRoot } from "../utils/paths.ts";
|
|
7
8
|
|
|
8
9
|
export type PiTeamsAutonomyProfile = "manual" | "suggested" | "assisted" | "aggressive";
|
|
9
10
|
|
|
@@ -80,6 +81,16 @@ export interface CrewAgentsConfig {
|
|
|
80
81
|
overrides?: Record<string, AgentOverrideConfig>;
|
|
81
82
|
}
|
|
82
83
|
|
|
84
|
+
export interface CrewToolsConfig {
|
|
85
|
+
enableClaudeStyleAliases?: boolean;
|
|
86
|
+
enableSteer?: boolean;
|
|
87
|
+
terminateOnForeground?: boolean;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export interface CrewTelemetryConfig {
|
|
91
|
+
enabled?: boolean;
|
|
92
|
+
}
|
|
93
|
+
|
|
83
94
|
export interface PiTeamsConfig {
|
|
84
95
|
asyncByDefault?: boolean;
|
|
85
96
|
executeWorkers?: boolean;
|
|
@@ -91,6 +102,8 @@ export interface PiTeamsConfig {
|
|
|
91
102
|
control?: CrewControlConfig;
|
|
92
103
|
worktree?: CrewWorktreeConfig;
|
|
93
104
|
agents?: CrewAgentsConfig;
|
|
105
|
+
tools?: CrewToolsConfig;
|
|
106
|
+
telemetry?: CrewTelemetryConfig;
|
|
94
107
|
ui?: CrewUiConfig;
|
|
95
108
|
}
|
|
96
109
|
|
|
@@ -124,7 +137,7 @@ export function configPath(): string {
|
|
|
124
137
|
}
|
|
125
138
|
|
|
126
139
|
export function projectConfigPath(cwd: string): string {
|
|
127
|
-
return path.join(cwd, "
|
|
140
|
+
return path.join(projectCrewRoot(cwd), "config.json");
|
|
128
141
|
}
|
|
129
142
|
|
|
130
143
|
function withoutUndefined<T extends Record<string, unknown>>(value: T): Partial<T> {
|
package/src/config/defaults.ts
CHANGED
|
@@ -38,10 +38,11 @@ export const DEFAULT_ARTIFACT_CLEANUP = {
|
|
|
38
38
|
|
|
39
39
|
export const DEFAULT_PATHS = {
|
|
40
40
|
state: {
|
|
41
|
-
projectBase: "teams",
|
|
42
|
-
userBase: "runs",
|
|
43
41
|
runsSubdir: "state/runs",
|
|
44
42
|
artifactsSubdir: "artifacts",
|
|
43
|
+
subagentsSubdir: "state/subagents",
|
|
44
|
+
importsSubdir: "imports",
|
|
45
|
+
worktreesSubdir: "worktrees",
|
|
45
46
|
manifestFile: "manifest.json",
|
|
46
47
|
tasksFile: "tasks.json",
|
|
47
48
|
eventsFile: "events.jsonl",
|
|
@@ -1,82 +1,82 @@
|
|
|
1
|
-
import type { ExtensionContext } from "@mariozechner/pi-coding-agent";
|
|
2
|
-
import type { TeamToolParamsValue } from "../schema/team-tool-schema.ts";
|
|
3
|
-
import { handleTeamTool } from "./team-tool.ts";
|
|
4
|
-
import { parseLiveControlRealtimeMessage, publishLiveControlRealtime } from "../runtime/live-control-realtime.ts";
|
|
5
|
-
|
|
6
|
-
export interface EventBusLike {
|
|
7
|
-
on(event: string, handler: (data: unknown) => void): (() => void) | void;
|
|
8
|
-
emit(event: string, data: unknown): void;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export type RpcReply<T = unknown> = { success: true; data?: T } | { success: false; error: string };
|
|
12
|
-
export const PI_CREW_RPC_VERSION = 1;
|
|
13
|
-
|
|
14
|
-
export interface PiCrewRpcHandle {
|
|
15
|
-
unsubscribe(): void;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function requestId(raw: unknown): string | undefined {
|
|
19
|
-
return raw && typeof raw === "object" && !Array.isArray(raw) && typeof (raw as { requestId?: unknown }).requestId === "string" ? (raw as { requestId: string }).requestId : undefined;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function reply(events: EventBusLike, channel: string, id: string | undefined, payload: RpcReply): void {
|
|
23
|
-
if (!id) return;
|
|
24
|
-
events.emit(`${channel}:reply:${id}`, payload);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function textOf(result: Awaited<ReturnType<typeof handleTeamTool>>): string {
|
|
28
|
-
return result.content?.map((item) => item.type === "text" ? item.text : "").join("\n") ?? "";
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function on(events: EventBusLike, channel: string, handler: (raw: unknown) => void): () => void {
|
|
32
|
-
const unsub = events.on(channel, handler);
|
|
33
|
-
return typeof unsub === "function" ? unsub : () => {};
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export function registerPiCrewRpc(events: EventBusLike | undefined, getCtx: () => ExtensionContext | undefined): PiCrewRpcHandle | undefined {
|
|
37
|
-
if (!events) return undefined;
|
|
38
|
-
const unsubs = [
|
|
39
|
-
on(events, "pi-crew:rpc:ping", (raw) => reply(events, "pi-crew:rpc:ping", requestId(raw), { success: true, data: { version: PI_CREW_RPC_VERSION } })),
|
|
40
|
-
on(events, "pi-crew:rpc:run", async (raw) => {
|
|
41
|
-
const id = requestId(raw);
|
|
42
|
-
try {
|
|
43
|
-
const ctx = getCtx();
|
|
44
|
-
if (!ctx) throw new Error("No active pi-crew session context.");
|
|
45
|
-
const params: TeamToolParamsValue = raw && typeof raw === "object" && !Array.isArray(raw) ? { ...(raw as object), action: "run" } as TeamToolParamsValue : { action: "run" };
|
|
46
|
-
const result = await handleTeamTool(params, ctx);
|
|
47
|
-
reply(events, "pi-crew:rpc:run", id, result.isError ? { success: false, error: textOf(result) } : { success: true, data: result.details });
|
|
48
|
-
} catch (error) {
|
|
49
|
-
reply(events, "pi-crew:rpc:run", id, { success: false, error: error instanceof Error ? error.message : String(error) });
|
|
50
|
-
}
|
|
51
|
-
}),
|
|
52
|
-
on(events, "pi-crew:rpc:status", async (raw) => {
|
|
53
|
-
const id = requestId(raw);
|
|
54
|
-
try {
|
|
55
|
-
const ctx = getCtx();
|
|
56
|
-
if (!ctx) throw new Error("No active pi-crew session context.");
|
|
57
|
-
const runId = raw && typeof raw === "object" && !Array.isArray(raw) ? (raw as { runId?: string }).runId : undefined;
|
|
58
|
-
const result = await handleTeamTool({ action: "status", runId }, ctx);
|
|
59
|
-
reply(events, "pi-crew:rpc:status", id, result.isError ? { success: false, error: textOf(result) } : { success: true, data: { text: textOf(result), details: result.details } });
|
|
60
|
-
} catch (error) {
|
|
61
|
-
reply(events, "pi-crew:rpc:status", id, { success: false, error: error instanceof Error ? error.message : String(error) });
|
|
62
|
-
}
|
|
63
|
-
}),
|
|
64
|
-
on(events, "pi-crew:live-control", (raw) => {
|
|
65
|
-
const request = parseLiveControlRealtimeMessage(raw);
|
|
66
|
-
if (request) publishLiveControlRealtime(request);
|
|
67
|
-
}),
|
|
68
|
-
on(events, "pi-crew:rpc:live-control", async (raw) => {
|
|
69
|
-
const id = requestId(raw);
|
|
70
|
-
try {
|
|
71
|
-
const ctx = getCtx();
|
|
72
|
-
if (!ctx) throw new Error("No active pi-crew session context.");
|
|
73
|
-
const obj = raw && typeof raw === "object" && !Array.isArray(raw) ? raw as Record<string, unknown> : {};
|
|
74
|
-
const result = await handleTeamTool({ action: "api", runId: typeof obj.runId === "string" ? obj.runId : undefined, config: { operation: typeof obj.operation === "string" ? obj.operation : "steer-agent", agentId: obj.agentId, message: obj.message, prompt: obj.prompt } }, ctx);
|
|
75
|
-
reply(events, "pi-crew:rpc:live-control", id, result.isError ? { success: false, error: textOf(result) } : { success: true, data: { text: textOf(result), details: result.details } });
|
|
76
|
-
} catch (error) {
|
|
77
|
-
reply(events, "pi-crew:rpc:live-control", id, { success: false, error: error instanceof Error ? error.message : String(error) });
|
|
78
|
-
}
|
|
79
|
-
}),
|
|
80
|
-
];
|
|
81
|
-
return { unsubscribe: () => unsubs.forEach((unsub) => unsub()) };
|
|
82
|
-
}
|
|
1
|
+
import type { ExtensionContext } from "@mariozechner/pi-coding-agent";
|
|
2
|
+
import type { TeamToolParamsValue } from "../schema/team-tool-schema.ts";
|
|
3
|
+
import { handleTeamTool } from "./team-tool.ts";
|
|
4
|
+
import { parseLiveControlRealtimeMessage, publishLiveControlRealtime } from "../runtime/live-control-realtime.ts";
|
|
5
|
+
|
|
6
|
+
export interface EventBusLike {
|
|
7
|
+
on(event: string, handler: (data: unknown) => void): (() => void) | void;
|
|
8
|
+
emit(event: string, data: unknown): void;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export type RpcReply<T = unknown> = { success: true; data?: T } | { success: false; error: string };
|
|
12
|
+
export const PI_CREW_RPC_VERSION = 1;
|
|
13
|
+
|
|
14
|
+
export interface PiCrewRpcHandle {
|
|
15
|
+
unsubscribe(): void;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function requestId(raw: unknown): string | undefined {
|
|
19
|
+
return raw && typeof raw === "object" && !Array.isArray(raw) && typeof (raw as { requestId?: unknown }).requestId === "string" ? (raw as { requestId: string }).requestId : undefined;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function reply(events: EventBusLike, channel: string, id: string | undefined, payload: RpcReply): void {
|
|
23
|
+
if (!id) return;
|
|
24
|
+
events.emit(`${channel}:reply:${id}`, payload);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function textOf(result: Awaited<ReturnType<typeof handleTeamTool>>): string {
|
|
28
|
+
return result.content?.map((item) => item.type === "text" ? item.text : "").join("\n") ?? "";
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function on(events: EventBusLike, channel: string, handler: (raw: unknown) => void): () => void {
|
|
32
|
+
const unsub = events.on(channel, handler);
|
|
33
|
+
return typeof unsub === "function" ? unsub : () => {};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function registerPiCrewRpc(events: EventBusLike | undefined, getCtx: () => ExtensionContext | undefined): PiCrewRpcHandle | undefined {
|
|
37
|
+
if (!events) return undefined;
|
|
38
|
+
const unsubs = [
|
|
39
|
+
on(events, "pi-crew:rpc:ping", (raw) => reply(events, "pi-crew:rpc:ping", requestId(raw), { success: true, data: { version: PI_CREW_RPC_VERSION } })),
|
|
40
|
+
on(events, "pi-crew:rpc:run", async (raw) => {
|
|
41
|
+
const id = requestId(raw);
|
|
42
|
+
try {
|
|
43
|
+
const ctx = getCtx();
|
|
44
|
+
if (!ctx) throw new Error("No active pi-crew session context.");
|
|
45
|
+
const params: TeamToolParamsValue = raw && typeof raw === "object" && !Array.isArray(raw) ? { ...(raw as object), action: "run" } as TeamToolParamsValue : { action: "run" };
|
|
46
|
+
const result = await handleTeamTool(params, ctx);
|
|
47
|
+
reply(events, "pi-crew:rpc:run", id, result.isError ? { success: false, error: textOf(result) } : { success: true, data: result.details });
|
|
48
|
+
} catch (error) {
|
|
49
|
+
reply(events, "pi-crew:rpc:run", id, { success: false, error: error instanceof Error ? error.message : String(error) });
|
|
50
|
+
}
|
|
51
|
+
}),
|
|
52
|
+
on(events, "pi-crew:rpc:status", async (raw) => {
|
|
53
|
+
const id = requestId(raw);
|
|
54
|
+
try {
|
|
55
|
+
const ctx = getCtx();
|
|
56
|
+
if (!ctx) throw new Error("No active pi-crew session context.");
|
|
57
|
+
const runId = raw && typeof raw === "object" && !Array.isArray(raw) ? (raw as { runId?: string }).runId : undefined;
|
|
58
|
+
const result = await handleTeamTool({ action: "status", runId }, ctx);
|
|
59
|
+
reply(events, "pi-crew:rpc:status", id, result.isError ? { success: false, error: textOf(result) } : { success: true, data: { text: textOf(result), details: result.details } });
|
|
60
|
+
} catch (error) {
|
|
61
|
+
reply(events, "pi-crew:rpc:status", id, { success: false, error: error instanceof Error ? error.message : String(error) });
|
|
62
|
+
}
|
|
63
|
+
}),
|
|
64
|
+
on(events, "pi-crew:live-control", (raw) => {
|
|
65
|
+
const request = parseLiveControlRealtimeMessage(raw);
|
|
66
|
+
if (request) publishLiveControlRealtime(request);
|
|
67
|
+
}),
|
|
68
|
+
on(events, "pi-crew:rpc:live-control", async (raw) => {
|
|
69
|
+
const id = requestId(raw);
|
|
70
|
+
try {
|
|
71
|
+
const ctx = getCtx();
|
|
72
|
+
if (!ctx) throw new Error("No active pi-crew session context.");
|
|
73
|
+
const obj = raw && typeof raw === "object" && !Array.isArray(raw) ? raw as Record<string, unknown> : {};
|
|
74
|
+
const result = await handleTeamTool({ action: "api", runId: typeof obj.runId === "string" ? obj.runId : undefined, config: { operation: typeof obj.operation === "string" ? obj.operation : "steer-agent", agentId: obj.agentId, message: obj.message, prompt: obj.prompt } }, ctx);
|
|
75
|
+
reply(events, "pi-crew:rpc:live-control", id, result.isError ? { success: false, error: textOf(result) } : { success: true, data: { text: textOf(result), details: result.details } });
|
|
76
|
+
} catch (error) {
|
|
77
|
+
reply(events, "pi-crew:rpc:live-control", id, { success: false, error: error instanceof Error ? error.message : String(error) });
|
|
78
|
+
}
|
|
79
|
+
}),
|
|
80
|
+
];
|
|
81
|
+
return { unsubscribe: () => unsubs.forEach((unsub) => unsub()) };
|
|
82
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
|
-
import {
|
|
3
|
+
import { projectCrewRoot, userCrewRoot } from "../utils/paths.ts";
|
|
4
|
+
import { DEFAULT_PATHS } from "../config/defaults.ts";
|
|
4
5
|
|
|
5
6
|
export interface ImportedRunIndexEntry {
|
|
6
7
|
runId: string;
|
|
@@ -45,8 +46,8 @@ function collect(root: string, scope: "project" | "user"): ImportedRunIndexEntry
|
|
|
45
46
|
}
|
|
46
47
|
|
|
47
48
|
export function listImportedRuns(cwd: string): ImportedRunIndexEntry[] {
|
|
48
|
-
const projectRoot = path.join(
|
|
49
|
-
const userRoot = path.join(
|
|
49
|
+
const projectRoot = path.join(projectCrewRoot(cwd), DEFAULT_PATHS.state.importsSubdir);
|
|
50
|
+
const userRoot = path.join(userCrewRoot(), DEFAULT_PATHS.state.importsSubdir);
|
|
50
51
|
return [...collect(userRoot, "user"), ...collect(projectRoot, "project")]
|
|
51
52
|
.sort((a, b) => (b.importedAt ?? "").localeCompare(a.importedAt ?? ""));
|
|
52
53
|
}
|
|
@@ -12,7 +12,7 @@ import { allTeams, discoverTeams } from "../teams/discover-teams.ts";
|
|
|
12
12
|
import type { WorkflowConfig, WorkflowStep } from "../workflows/workflow-config.ts";
|
|
13
13
|
import { serializeWorkflow } from "../workflows/workflow-serializer.ts";
|
|
14
14
|
import { allWorkflows, discoverWorkflows } from "../workflows/discover-workflows.ts";
|
|
15
|
-
import {
|
|
15
|
+
import { projectCrewRoot, userPiRoot } from "../utils/paths.ts";
|
|
16
16
|
import { hasOwn, parseConfigObject, requireString, sanitizeName } from "../utils/names.ts";
|
|
17
17
|
|
|
18
18
|
interface ManagementContext {
|
|
@@ -28,7 +28,7 @@ function result(text: string, status: TeamToolDetails["status"] = "ok", isError
|
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
function scopeDir(ctx: ManagementContext, resource: "agent" | "team" | "workflow", scope: MutableSource): string {
|
|
31
|
-
const base = scope === "user" ? userPiRoot() :
|
|
31
|
+
const base = scope === "user" ? userPiRoot() : projectCrewRoot(ctx.cwd);
|
|
32
32
|
if (resource === "agent") return path.join(base, "agents");
|
|
33
33
|
if (resource === "team") return path.join(base, "teams");
|
|
34
34
|
return path.join(base, "workflows");
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
|
-
import { packageRoot } from "../utils/paths.ts";
|
|
3
|
+
import { packageRoot, projectCrewRoot } from "../utils/paths.ts";
|
|
4
4
|
|
|
5
5
|
export interface ProjectInitOptions {
|
|
6
6
|
copyBuiltins?: boolean;
|
|
@@ -44,14 +44,16 @@ export function initializeProject(cwd: string, options: ProjectInitOptions = {})
|
|
|
44
44
|
const createdDirs: string[] = [];
|
|
45
45
|
const copiedFiles: string[] = [];
|
|
46
46
|
const skippedFiles: string[] = [];
|
|
47
|
-
const
|
|
48
|
-
const
|
|
49
|
-
const
|
|
50
|
-
const
|
|
47
|
+
const crewRoot = projectCrewRoot(cwd);
|
|
48
|
+
const usingLegacyPi = path.basename(crewRoot) === "teams" && path.basename(path.dirname(crewRoot)) === ".pi";
|
|
49
|
+
const ignorePrefix = usingLegacyPi ? ".pi/teams" : ".crew";
|
|
50
|
+
const agentsDir = path.join(crewRoot, "agents");
|
|
51
|
+
const teamsDir = path.join(crewRoot, "teams");
|
|
52
|
+
const workflowsDir = path.join(crewRoot, "workflows");
|
|
51
53
|
ensureDir(agentsDir, createdDirs);
|
|
52
54
|
ensureDir(teamsDir, createdDirs);
|
|
53
55
|
ensureDir(workflowsDir, createdDirs);
|
|
54
|
-
ensureDir(path.join(
|
|
56
|
+
ensureDir(path.join(crewRoot, "imports"), createdDirs);
|
|
55
57
|
|
|
56
58
|
if (options.copyBuiltins) {
|
|
57
59
|
copyBuiltinDir("agents", agentsDir, options.overwrite === true, copiedFiles, skippedFiles);
|
|
@@ -60,7 +62,7 @@ export function initializeProject(cwd: string, options: ProjectInitOptions = {})
|
|
|
60
62
|
}
|
|
61
63
|
|
|
62
64
|
const gitignorePath = path.join(cwd, ".gitignore");
|
|
63
|
-
const desired = [
|
|
65
|
+
const desired = [`${ignorePrefix}/state/`, `${ignorePrefix}/artifacts/`, `${ignorePrefix}/worktrees/`, `${ignorePrefix}/imports/`];
|
|
64
66
|
const existing = fs.existsSync(gitignorePath) ? fs.readFileSync(gitignorePath, "utf-8") : "";
|
|
65
67
|
const missing = desired.filter((entry) => !existing.split(/\r?\n/).includes(entry));
|
|
66
68
|
let gitignoreUpdated = false;
|
|
@@ -19,6 +19,7 @@ import { registerTeamCommands } from "./registration/commands.ts";
|
|
|
19
19
|
import { registerSubagentTools } from "./registration/subagent-tools.ts";
|
|
20
20
|
import { runArtifactCleanup } from "./registration/artifact-cleanup.ts";
|
|
21
21
|
import { registerTeamTool } from "./registration/team-tool.ts";
|
|
22
|
+
import { registerCompactionGuard } from "./registration/compaction-guard.ts";
|
|
22
23
|
|
|
23
24
|
export { __test__subagentSpawnParams };
|
|
24
25
|
|
|
@@ -47,10 +48,24 @@ export function registerPiTeams(pi: ExtensionAPI): void {
|
|
|
47
48
|
manifestCache = createManifestCache(cwd);
|
|
48
49
|
return manifestCache;
|
|
49
50
|
};
|
|
51
|
+
const telemetryEnabled = (): boolean => loadConfig(currentCtx?.cwd ?? process.cwd()).config.telemetry?.enabled !== false;
|
|
50
52
|
const widgetState: CrewWidgetState = { frame: 0 };
|
|
51
53
|
const subagentManager = new SubagentManager(
|
|
52
54
|
4,
|
|
53
55
|
(record) => {
|
|
56
|
+
// Phase 1.3 + 1.6: Emit public crew.subagent.completed event with telemetry.
|
|
57
|
+
// Users can opt out with config.telemetry.enabled=false.
|
|
58
|
+
if (telemetryEnabled()) {
|
|
59
|
+
pi.events?.emit?.("crew.subagent.completed", {
|
|
60
|
+
id: record.id,
|
|
61
|
+
runId: record.runId,
|
|
62
|
+
type: record.type,
|
|
63
|
+
status: record.status,
|
|
64
|
+
turnCount: record.turnCount,
|
|
65
|
+
terminated: record.terminated ?? false,
|
|
66
|
+
durationMs: record.durationMs,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
54
69
|
if (!record.background || record.resultConsumed) return;
|
|
55
70
|
if (record.status === "completed" || record.status === "failed" || record.status === "cancelled" || record.status === "blocked" || record.status === "error") {
|
|
56
71
|
sendFollowUp(pi, [`pi-crew subagent ${record.id} ${record.status}.`, record.runId ? `Run: ${record.runId}` : undefined, `Use get_subagent_result with agent_id=${record.id} for output.`].filter((line): line is string => Boolean(line)).join("\n"));
|
|
@@ -108,6 +123,10 @@ export function registerPiTeams(pi: ExtensionAPI): void {
|
|
|
108
123
|
const startForegroundRun = (ctx: ExtensionContext, runner: (signal?: AbortSignal) => Promise<void>, runId?: string): void => {
|
|
109
124
|
const controller = new AbortController();
|
|
110
125
|
foregroundControllers.add(controller);
|
|
126
|
+
if (ctx.hasUI) {
|
|
127
|
+
(ctx.ui as { setWorkingIndicator?: (options?: { frames?: string[]; intervalMs?: number }) => void }).setWorkingIndicator?.({ frames: ["⣾", "⣽", "⣻", "⢿", "⡿", "⣟", "⣯", "⣷"], intervalMs: 80 });
|
|
128
|
+
ctx.ui.setWorkingMessage(runId ? `pi-crew foreground run ${runId}...` : "pi-crew foreground run...");
|
|
129
|
+
}
|
|
111
130
|
setImmediate(() => {
|
|
112
131
|
void runner(controller.signal)
|
|
113
132
|
.catch((error) => {
|
|
@@ -124,11 +143,37 @@ export function registerPiTeams(pi: ExtensionAPI): void {
|
|
|
124
143
|
})
|
|
125
144
|
.finally(() => {
|
|
126
145
|
foregroundControllers.delete(controller);
|
|
146
|
+
if (ctx.hasUI) {
|
|
147
|
+
(ctx.ui as { setWorkingIndicator?: (options?: { frames?: string[]; intervalMs?: number }) => void }).setWorkingIndicator?.();
|
|
148
|
+
ctx.ui.setWorkingMessage();
|
|
149
|
+
}
|
|
127
150
|
if (runId) {
|
|
128
151
|
const loaded = loadRunManifestById(ctx.cwd, runId);
|
|
129
152
|
const status = loaded?.manifest.status ?? "finished";
|
|
130
153
|
const level = status === "failed" || status === "blocked" ? "error" : status === "cancelled" ? "warning" : "info";
|
|
131
154
|
ctx.ui.notify(`pi-crew run ${runId} ${status}. Use /team-summary ${runId} or /team-status ${runId}.`, level as "info" | "warning" | "error");
|
|
155
|
+
// Phase 2.3: Persist run completion reference into the Pi session.
|
|
156
|
+
pi.appendEntry("crew:run-completed", {
|
|
157
|
+
runId,
|
|
158
|
+
team: loaded?.manifest.team,
|
|
159
|
+
workflow: loaded?.manifest.workflow,
|
|
160
|
+
goal: loaded?.manifest.goal,
|
|
161
|
+
status,
|
|
162
|
+
taskCount: loaded?.tasks.length,
|
|
163
|
+
timestamp: Date.now(),
|
|
164
|
+
});
|
|
165
|
+
// Phase 1.3: Emit public crew.run.* events
|
|
166
|
+
const eventType = status === "completed" ? "crew.run.completed" : status === "failed" || status === "blocked" ? "crew.run.failed" : status === "cancelled" ? "crew.run.cancelled" : undefined;
|
|
167
|
+
if (eventType) {
|
|
168
|
+
pi.events?.emit?.(eventType, {
|
|
169
|
+
runId,
|
|
170
|
+
team: loaded?.manifest.team,
|
|
171
|
+
workflow: loaded?.manifest.workflow,
|
|
172
|
+
status,
|
|
173
|
+
taskCount: loaded?.tasks.length,
|
|
174
|
+
goal: loaded?.manifest.goal,
|
|
175
|
+
});
|
|
176
|
+
}
|
|
132
177
|
}
|
|
133
178
|
if (currentCtx) {
|
|
134
179
|
const config = loadConfig(currentCtx.cwd).config.ui;
|
|
@@ -189,6 +234,24 @@ export function registerPiTeams(pi: ExtensionAPI): void {
|
|
|
189
234
|
pi.on("session_before_switch", () => stopSessionBoundSubagents());
|
|
190
235
|
pi.on("session_shutdown", () => cleanupRuntime());
|
|
191
236
|
|
|
237
|
+
registerCompactionGuard(pi, { foregroundControllers });
|
|
238
|
+
|
|
239
|
+
// Phase 1.4: Permission gate for destructive team actions.
|
|
240
|
+
// AGENTS.md requires confirm=true for management deletes.
|
|
241
|
+
pi.on("tool_call", async (event, ctx) => {
|
|
242
|
+
if (event.toolName !== "team") return;
|
|
243
|
+
const input = (event as { input?: Record<string, unknown> }).input;
|
|
244
|
+
if (!input) return;
|
|
245
|
+
const action = typeof input.action === "string" ? input.action : undefined;
|
|
246
|
+
const destructiveActions = new Set(["delete", "forget", "prune", "cleanup"]);
|
|
247
|
+
if (!action || !destructiveActions.has(action)) return;
|
|
248
|
+
if (input.confirm === true || input.force === true) return;
|
|
249
|
+
return {
|
|
250
|
+
block: true,
|
|
251
|
+
reason: `Destructive action '${action}' requires confirm=true (or force=true to bypass reference checks).`,
|
|
252
|
+
};
|
|
253
|
+
});
|
|
254
|
+
|
|
192
255
|
registerTeamTool(pi, { foregroundControllers, startForegroundRun, openLiveSidebar, getManifestCache, widgetState });
|
|
193
256
|
registerSubagentTools(pi, subagentManager);
|
|
194
257
|
time("register.tools");
|
|
@@ -1,14 +1,15 @@
|
|
|
1
|
-
import * as path from "node:path";
|
|
2
|
-
import { DEFAULT_ARTIFACT_CLEANUP } from "../../config/defaults.ts";
|
|
3
|
-
import { CLEANUP_MARKER_FILE, cleanupOldArtifacts } from "../../state/artifact-store.ts";
|
|
4
|
-
import { logInternalError } from "../../utils/internal-error.ts";
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
cleanupOldArtifacts(path.join(
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
}
|
|
1
|
+
import * as path from "node:path";
|
|
2
|
+
import { DEFAULT_ARTIFACT_CLEANUP } from "../../config/defaults.ts";
|
|
3
|
+
import { CLEANUP_MARKER_FILE, cleanupOldArtifacts } from "../../state/artifact-store.ts";
|
|
4
|
+
import { logInternalError } from "../../utils/internal-error.ts";
|
|
5
|
+
import { projectCrewRoot, userCrewRoot } from "../../utils/paths.ts";
|
|
6
|
+
import { DEFAULT_PATHS } from "../../config/defaults.ts";
|
|
7
|
+
|
|
8
|
+
export function runArtifactCleanup(cwd: string): void {
|
|
9
|
+
try {
|
|
10
|
+
cleanupOldArtifacts(path.join(userCrewRoot(), DEFAULT_PATHS.state.artifactsSubdir), { maxAgeDays: DEFAULT_ARTIFACT_CLEANUP.maxAgeDays, markerFile: CLEANUP_MARKER_FILE });
|
|
11
|
+
cleanupOldArtifacts(path.join(projectCrewRoot(cwd), DEFAULT_PATHS.state.artifactsSubdir), { maxAgeDays: DEFAULT_ARTIFACT_CLEANUP.maxAgeDays, markerFile: CLEANUP_MARKER_FILE });
|
|
12
|
+
} catch (error) {
|
|
13
|
+
logInternalError("register.artifact-cleanup", error, `cwd=${cwd}`);
|
|
14
|
+
}
|
|
15
|
+
}
|