pi-crew 0.1.45 → 0.1.49
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +97 -0
- package/README.md +5 -5
- package/agents/analyst.md +11 -11
- package/agents/critic.md +11 -11
- package/agents/executor.md +11 -11
- package/agents/explorer.md +11 -11
- package/agents/planner.md +11 -11
- package/agents/reviewer.md +11 -11
- package/agents/security-reviewer.md +11 -11
- package/agents/test-engineer.md +11 -11
- package/agents/verifier.md +11 -11
- package/agents/writer.md +11 -11
- package/docs/next-upgrade-roadmap.md +808 -0
- package/docs/research/AGENT-EXECUTION-ARCHITECTURE.md +261 -0
- package/docs/research/AGENT-LIFECYCLE-COMPARISON.md +111 -0
- package/docs/research/AUDIT_OH_MY_PI.md +261 -0
- package/docs/research/AUDIT_PI_CREW.md +457 -0
- package/docs/research/CAVEMAN-DEEP-RESEARCH.md +281 -0
- package/docs/research/COMPARISON_OH_MY_PI_VS_PI_CREW.md +264 -0
- package/docs/research/DEEP-RESEARCH-PI-POWERBAR.md +343 -0
- package/docs/research/DEEP_RESEARCH_SUBAGENT_ARCHITECTURE.md +480 -0
- package/docs/research/GAP_CLOSURE_IMPLEMENTATION_PLAN.md +354 -0
- package/docs/research/IMPLEMENTATION_PLAN.md +385 -0
- package/docs/research/LIVE-SESSION-PRODUCTION-READY-PLAN.md +502 -0
- package/docs/research/OH-MY-PI-DEEP-RESEARCH-v14.7.6.md +266 -0
- package/docs/research/REMAINING-GAPS-PLAN.md +363 -0
- package/docs/research/SESSION-SUMMARY-2026-05-08.md +146 -0
- package/docs/research/UI-RESPONSIVENESS-AUDIT.md +173 -0
- package/docs/research-awesome-agent-skills-distillation.md +100 -0
- package/docs/research-oh-my-pi-distillation.md +369 -0
- package/docs/source-runtime-refactor-map.md +24 -0
- package/docs/usage.md +3 -3
- package/install.mjs +52 -8
- package/package.json +99 -98
- package/schema.json +10 -1
- package/skills/async-worker-recovery/SKILL.md +42 -0
- package/skills/context-artifact-hygiene/SKILL.md +52 -0
- package/skills/delegation-patterns/SKILL.md +54 -0
- package/skills/mailbox-interactive/SKILL.md +40 -0
- package/skills/model-routing-context/SKILL.md +39 -0
- package/skills/multi-perspective-review/SKILL.md +58 -0
- package/skills/observability-reliability/SKILL.md +41 -0
- package/skills/orchestration/SKILL.md +157 -0
- package/skills/ownership-session-security/SKILL.md +41 -0
- package/skills/pi-extension-lifecycle/SKILL.md +39 -0
- package/skills/requirements-to-task-packet/SKILL.md +63 -0
- package/skills/resource-discovery-config/SKILL.md +41 -0
- package/skills/runtime-state-reader/SKILL.md +44 -0
- package/skills/secure-agent-orchestration-review/SKILL.md +45 -0
- package/skills/state-mutation-locking/SKILL.md +42 -0
- package/skills/systematic-debugging/SKILL.md +67 -0
- package/skills/ui-render-performance/SKILL.md +39 -0
- package/skills/verification-before-done/SKILL.md +57 -0
- package/skills/worktree-isolation/SKILL.md +39 -0
- package/src/agents/agent-config.ts +6 -0
- package/src/agents/agent-search.ts +98 -0
- package/src/agents/agent-serializer.ts +38 -34
- package/src/agents/discover-agents.ts +29 -15
- package/src/config/config.ts +72 -24
- package/src/config/defaults.ts +25 -0
- package/src/extension/autonomous-policy.ts +26 -33
- package/src/extension/help.ts +1 -0
- package/src/extension/management.ts +5 -0
- package/src/extension/project-init.ts +62 -2
- package/src/extension/register.ts +69 -22
- package/src/extension/registration/commands.ts +64 -25
- package/src/extension/registration/compaction-guard.ts +1 -1
- package/src/extension/registration/subagent-helpers.ts +8 -0
- package/src/extension/registration/subagent-tools.ts +149 -148
- package/src/extension/registration/team-tool.ts +14 -10
- package/src/extension/run-index.ts +35 -21
- package/src/extension/run-maintenance.ts +30 -5
- package/src/extension/team-tool/api.ts +47 -9
- package/src/extension/team-tool/cancel.ts +109 -5
- package/src/extension/team-tool/context.ts +8 -0
- package/src/extension/team-tool/intent-policy.ts +42 -0
- package/src/extension/team-tool/lifecycle-actions.ts +120 -79
- package/src/extension/team-tool/parallel-dispatch.ts +156 -0
- package/src/extension/team-tool/respond.ts +46 -18
- package/src/extension/team-tool/run.ts +55 -12
- package/src/extension/team-tool/status.ts +13 -2
- package/src/extension/team-tool-types.ts +3 -0
- package/src/extension/team-tool.ts +45 -14
- package/src/hooks/registry.ts +61 -0
- package/src/hooks/types.ts +41 -0
- package/src/observability/event-to-metric.ts +8 -1
- package/src/runtime/agent-control.ts +169 -63
- package/src/runtime/async-runner.ts +3 -1
- package/src/runtime/background-runner.ts +78 -53
- package/src/runtime/cancellation-token.ts +89 -0
- package/src/runtime/cancellation.ts +61 -0
- package/src/runtime/capability-inventory.ts +116 -0
- package/src/runtime/child-pi.ts +458 -444
- package/src/runtime/code-summary.ts +247 -0
- package/src/runtime/crash-recovery.ts +182 -0
- package/src/runtime/crew-agent-records.ts +70 -10
- package/src/runtime/crew-agent-runtime.ts +1 -0
- package/src/runtime/custom-tools/irc-tool.ts +201 -0
- package/src/runtime/custom-tools/submit-result-tool.ts +90 -0
- package/src/runtime/deadletter.ts +1 -0
- package/src/runtime/delivery-coordinator.ts +48 -25
- package/src/runtime/effectiveness.ts +81 -0
- package/src/runtime/event-stream-bridge.ts +90 -0
- package/src/runtime/live-agent-control.ts +2 -1
- package/src/runtime/live-agent-manager.ts +179 -85
- package/src/runtime/live-control-realtime.ts +1 -1
- package/src/runtime/live-extension-bridge.ts +150 -0
- package/src/runtime/live-irc.ts +92 -0
- package/src/runtime/live-session-health.ts +100 -0
- package/src/runtime/live-session-runtime.ts +599 -305
- package/src/runtime/manifest-cache.ts +17 -2
- package/src/runtime/mcp-proxy.ts +113 -0
- package/src/runtime/model-fallback.ts +6 -4
- package/src/runtime/notebook-helpers.ts +90 -0
- package/src/runtime/orphan-sentinel.ts +7 -0
- package/src/runtime/output-validator.ts +187 -0
- package/src/runtime/parallel-utils.ts +57 -0
- package/src/runtime/parent-guard.ts +80 -0
- package/src/runtime/pi-args.ts +18 -3
- package/src/runtime/process-status.ts +5 -1
- package/src/runtime/prose-compressor.ts +164 -0
- package/src/runtime/result-extractor.ts +121 -0
- package/src/runtime/retry-executor.ts +81 -64
- package/src/runtime/runtime-resolver.ts +23 -10
- package/src/runtime/semaphore.ts +131 -0
- package/src/runtime/sensitive-paths.ts +92 -0
- package/src/runtime/skill-instructions.ts +222 -0
- package/src/runtime/stale-reconciler.ts +4 -14
- package/src/runtime/stream-preview.ts +177 -0
- package/src/runtime/subagent-manager.ts +6 -2
- package/src/runtime/subprocess-tool-registry.ts +67 -0
- package/src/runtime/task-output-context.ts +177 -127
- package/src/runtime/task-runner/capabilities.ts +78 -0
- package/src/runtime/task-runner/live-executor.ts +107 -101
- package/src/runtime/task-runner/prompt-builder.ts +72 -8
- package/src/runtime/task-runner/prompt-pipeline.ts +64 -0
- package/src/runtime/task-runner/run-projection.ts +104 -0
- package/src/runtime/task-runner.ts +115 -5
- package/src/runtime/team-runner.ts +134 -19
- package/src/runtime/workspace-tree.ts +298 -0
- package/src/runtime/yield-handler.ts +189 -0
- package/src/schema/config-schema.ts +7 -0
- package/src/schema/team-tool-schema.ts +14 -4
- package/src/skills/discover-skills.ts +67 -0
- package/src/state/active-run-registry.ts +167 -0
- package/src/state/artifact-store.ts +4 -1
- package/src/state/atomic-write.ts +50 -1
- package/src/state/blob-store.ts +117 -0
- package/src/state/contracts.ts +2 -1
- package/src/state/event-log-rotation.ts +158 -0
- package/src/state/event-log.ts +52 -2
- package/src/state/mailbox.ts +129 -9
- package/src/state/state-store.ts +32 -5
- package/src/state/types.ts +64 -2
- package/src/teams/team-config.ts +1 -0
- package/src/ui/agent-management-overlay.ts +144 -0
- package/src/ui/crew-widget.ts +15 -5
- package/src/ui/dashboard-panes/cancellation-pane.ts +43 -0
- package/src/ui/dashboard-panes/capability-pane.ts +60 -0
- package/src/ui/dashboard-panes/mailbox-pane.ts +35 -11
- package/src/ui/dashboard-panes/progress-pane.ts +2 -0
- package/src/ui/live-run-sidebar.ts +4 -0
- package/src/ui/powerbar-publisher.ts +77 -15
- package/src/ui/render-coalescer.ts +51 -0
- package/src/ui/run-dashboard.ts +4 -0
- package/src/ui/run-event-bus.ts +209 -0
- package/src/ui/run-snapshot-cache.ts +78 -18
- package/src/ui/snapshot-types.ts +10 -0
- package/src/ui/transcript-entries.ts +258 -0
- package/src/utils/ids.ts +5 -0
- package/src/utils/incremental-reader.ts +104 -0
- package/src/utils/paths.ts +4 -2
- package/src/utils/scan-cache.ts +137 -0
- package/src/utils/sse-parser.ts +134 -0
- package/src/utils/task-name-generator.ts +337 -0
- package/src/utils/visual.ts +33 -2
- package/src/workflows/workflow-config.ts +1 -0
- package/src/worktree/cleanup.ts +2 -1
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: worktree-isolation
|
|
3
|
+
description: Conflict-safe git worktree workflow. Use when running parallel implementation workers, isolating risky edits, or cleaning up task worktrees.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# worktree-isolation
|
|
7
|
+
|
|
8
|
+
Use this skill for worktree-based execution or cleanup.
|
|
9
|
+
|
|
10
|
+
## Source patterns distilled
|
|
11
|
+
|
|
12
|
+
- pi-subagents worktree runner and cleanup patterns
|
|
13
|
+
- pi-crew worktrees: `src/worktree/worktree-manager.ts`, `src/worktree/cleanup.ts`, `src/worktree/branch-freshness.ts`
|
|
14
|
+
- Team runner workspace mode: `src/runtime/team-runner.ts`, workflow/team resource fields
|
|
15
|
+
|
|
16
|
+
## Rules
|
|
17
|
+
|
|
18
|
+
- Use worktree mode for parallel or risky code-changing tasks when the repository is clean enough and merge ownership is clear.
|
|
19
|
+
- Assign one owner per file/symbol/migration path to avoid conflict-heavy merges.
|
|
20
|
+
- Name branches/worktrees deterministically from run/task IDs; avoid user-controlled path fragments without sanitization.
|
|
21
|
+
- Before cleanup, check dirty state. Preserve dirty worktrees unless `force` is explicitly set.
|
|
22
|
+
- Record worktree paths and branch metadata in artifacts/events so the operator can inspect or recover.
|
|
23
|
+
- Do not run destructive git operations without explicit confirmation and evidence of target path containment.
|
|
24
|
+
|
|
25
|
+
## Anti-patterns
|
|
26
|
+
|
|
27
|
+
- Parallel editing the same file in multiple worktrees without a merge plan.
|
|
28
|
+
- Force-removing dirty worktrees by default.
|
|
29
|
+
- Reusing stale worktrees after the base branch has moved without freshness checks.
|
|
30
|
+
- Storing worktrees outside the intended contained workspace root.
|
|
31
|
+
|
|
32
|
+
## Verification
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
cd pi-crew
|
|
36
|
+
npx tsc --noEmit
|
|
37
|
+
node --experimental-strip-types --test test/integration/worktree-mode.test.ts test/unit/run-index.test.ts
|
|
38
|
+
npm test
|
|
39
|
+
```
|
|
@@ -25,6 +25,12 @@ export interface AgentConfig {
|
|
|
25
25
|
inheritSkills?: boolean;
|
|
26
26
|
routing?: RoutingMetadata;
|
|
27
27
|
memory?: "user" | "project" | "local";
|
|
28
|
+
/** Tool loading strategy: "essential" = always load all tools, "lean" = only load tools in defaultTools list */
|
|
29
|
+
loadMode?: "essential" | "lean";
|
|
30
|
+
/** Explicit tool list when loadMode is "lean". null means all available tools. */
|
|
31
|
+
defaultTools?: string[] | null;
|
|
32
|
+
/** Context mode: "fresh" = clean start, "fork" = inherit parent session context */
|
|
33
|
+
contextMode?: "fresh" | "fork";
|
|
28
34
|
disabled?: boolean;
|
|
29
35
|
override?: { source: "config"; path: string };
|
|
30
36
|
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import type { AgentConfig } from "./agent-config.ts";
|
|
2
|
+
|
|
3
|
+
// ─── BM25 Agent Search ──────────────────────────────────────────────────────
|
|
4
|
+
// Lightweight BM25 search over agent descriptors for task-to-agent matching.
|
|
5
|
+
// Based on the same BM25 algorithm used in oh-my-pi's tool-index.ts.
|
|
6
|
+
|
|
7
|
+
export interface AgentSearchDocument {
|
|
8
|
+
agent: AgentConfig;
|
|
9
|
+
termFrequencies: Map<string, number>;
|
|
10
|
+
length: number;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface AgentSearchIndex {
|
|
14
|
+
documents: AgentSearchDocument[];
|
|
15
|
+
averageLength: number;
|
|
16
|
+
documentFrequencies: Map<string, number>;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface AgentSearchResult {
|
|
20
|
+
agent: AgentConfig;
|
|
21
|
+
score: number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const BM25_K1 = 1.2;
|
|
25
|
+
const BM25_B = 0.75;
|
|
26
|
+
const FIELD_WEIGHTS = {
|
|
27
|
+
name: 6,
|
|
28
|
+
description: 2,
|
|
29
|
+
role: 3,
|
|
30
|
+
} as const;
|
|
31
|
+
|
|
32
|
+
function tokenize(value: string): string[] {
|
|
33
|
+
return value
|
|
34
|
+
.replace(/([a-z0-9])([A-Z])/g, "$1 $2")
|
|
35
|
+
.replace(/[^a-zA-Z0-9]+/g, " ")
|
|
36
|
+
.toLowerCase()
|
|
37
|
+
.trim()
|
|
38
|
+
.split(/\s+/)
|
|
39
|
+
.filter((token) => token.length > 0);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function addWeightedTokens(termFrequencies: Map<string, number>, value: string | undefined, weight: number): void {
|
|
43
|
+
if (!value) return;
|
|
44
|
+
for (const token of tokenize(value)) {
|
|
45
|
+
termFrequencies.set(token, (termFrequencies.get(token) ?? 0) + weight);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function buildAgentSearchDocument(agent: AgentConfig): AgentSearchDocument {
|
|
50
|
+
const termFrequencies = new Map<string, number>();
|
|
51
|
+
addWeightedTokens(termFrequencies, agent.name, FIELD_WEIGHTS.name);
|
|
52
|
+
addWeightedTokens(termFrequencies, agent.description, FIELD_WEIGHTS.description);
|
|
53
|
+
// Role from agent name heuristic
|
|
54
|
+
const roleHint = agent.name?.replace(/[-_]/g, " ") ?? "";
|
|
55
|
+
addWeightedTokens(termFrequencies, roleHint, FIELD_WEIGHTS.role);
|
|
56
|
+
const length = Array.from(termFrequencies.values()).reduce((sum, value) => sum + value, 0);
|
|
57
|
+
return { agent, termFrequencies, length };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function buildAgentSearchIndex(agents: Iterable<AgentConfig>): AgentSearchIndex {
|
|
61
|
+
const documents = Array.from(agents, buildAgentSearchDocument);
|
|
62
|
+
const averageLength = documents.reduce((sum, document) => sum + document.length, 0) / documents.length || 1;
|
|
63
|
+
const documentFrequencies = new Map<string, number>();
|
|
64
|
+
for (const document of documents) {
|
|
65
|
+
for (const token of new Set(document.termFrequencies.keys())) {
|
|
66
|
+
documentFrequencies.set(token, (documentFrequencies.get(token) ?? 0) + 1);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return { documents, averageLength, documentFrequencies };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function searchAgents(index: AgentSearchIndex, query: string, limit: number): AgentSearchResult[] {
|
|
73
|
+
const queryTokens = tokenize(query);
|
|
74
|
+
if (queryTokens.length === 0) return [];
|
|
75
|
+
if (index.documents.length === 0) return [];
|
|
76
|
+
|
|
77
|
+
const queryTermCounts = new Map<string, number>();
|
|
78
|
+
for (const token of queryTokens) {
|
|
79
|
+
queryTermCounts.set(token, (queryTermCounts.get(token) ?? 0) + 1);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return index.documents
|
|
83
|
+
.map((document) => {
|
|
84
|
+
let score = 0;
|
|
85
|
+
for (const [token, queryTermCount] of queryTermCounts) {
|
|
86
|
+
const termFrequency = document.termFrequencies.get(token) ?? 0;
|
|
87
|
+
if (termFrequency === 0) continue;
|
|
88
|
+
const documentFrequency = index.documentFrequencies.get(token) ?? 0;
|
|
89
|
+
const idf = Math.log(1 + (index.documents.length - documentFrequency + 0.5) / (documentFrequency + 0.5));
|
|
90
|
+
const normalization = BM25_K1 * (1 - BM25_B + BM25_B * (document.length / index.averageLength));
|
|
91
|
+
score += queryTermCount * idf * ((termFrequency * (BM25_K1 + 1)) / (termFrequency + normalization));
|
|
92
|
+
}
|
|
93
|
+
return { agent: document.agent, score };
|
|
94
|
+
})
|
|
95
|
+
.filter((result) => result.score > 0)
|
|
96
|
+
.sort((left, right) => right.score - left.score || left.agent.name.localeCompare(right.agent.name))
|
|
97
|
+
.slice(0, limit);
|
|
98
|
+
}
|
|
@@ -1,34 +1,38 @@
|
|
|
1
|
-
import type { AgentConfig } from "./agent-config.ts";
|
|
2
|
-
|
|
3
|
-
function line(key: string, value: string | boolean | string[] | undefined): string | undefined {
|
|
4
|
-
if (value === undefined) return undefined;
|
|
5
|
-
if (Array.isArray(value)) return `${key}: ${value.join(", ")}`;
|
|
6
|
-
return `${key}: ${String(value)}`;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export function serializeAgent(agent: AgentConfig): string {
|
|
10
|
-
const lines = [
|
|
11
|
-
"---",
|
|
12
|
-
`name: ${agent.name}`,
|
|
13
|
-
`description: ${agent.description}`,
|
|
14
|
-
line("model", agent.model),
|
|
15
|
-
line("fallbackModels", agent.fallbackModels),
|
|
16
|
-
line("thinking", agent.thinking),
|
|
17
|
-
line("tools", agent.tools),
|
|
18
|
-
agent.extensions !== undefined ? line("extensions", agent.extensions) ?? "extensions:" : undefined,
|
|
19
|
-
line("skills", agent.skills),
|
|
20
|
-
line("systemPromptMode", agent.systemPromptMode),
|
|
21
|
-
line("inheritProjectContext", agent.inheritProjectContext),
|
|
22
|
-
line("inheritSkills", agent.inheritSkills),
|
|
23
|
-
line("
|
|
24
|
-
line("
|
|
25
|
-
line("
|
|
26
|
-
line("
|
|
27
|
-
line("
|
|
28
|
-
"
|
|
29
|
-
"",
|
|
30
|
-
agent.
|
|
31
|
-
"",
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
1
|
+
import type { AgentConfig } from "./agent-config.ts";
|
|
2
|
+
|
|
3
|
+
function line(key: string, value: string | boolean | string[] | undefined): string | undefined {
|
|
4
|
+
if (value === undefined) return undefined;
|
|
5
|
+
if (Array.isArray(value)) return `${key}: ${value.join(", ")}`;
|
|
6
|
+
return `${key}: ${String(value)}`;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function serializeAgent(agent: AgentConfig): string {
|
|
10
|
+
const lines = [
|
|
11
|
+
"---",
|
|
12
|
+
`name: ${agent.name}`,
|
|
13
|
+
`description: ${agent.description}`,
|
|
14
|
+
line("model", agent.model),
|
|
15
|
+
line("fallbackModels", agent.fallbackModels),
|
|
16
|
+
line("thinking", agent.thinking),
|
|
17
|
+
line("tools", agent.tools),
|
|
18
|
+
agent.extensions !== undefined ? line("extensions", agent.extensions) ?? "extensions:" : undefined,
|
|
19
|
+
line("skills", agent.skills),
|
|
20
|
+
line("systemPromptMode", agent.systemPromptMode),
|
|
21
|
+
line("inheritProjectContext", agent.inheritProjectContext),
|
|
22
|
+
line("inheritSkills", agent.inheritSkills),
|
|
23
|
+
line("memory", agent.memory),
|
|
24
|
+
line("loadMode", agent.loadMode),
|
|
25
|
+
line("defaultTools", agent.defaultTools ?? undefined),
|
|
26
|
+
line("contextMode", agent.contextMode),
|
|
27
|
+
line("triggers", agent.routing?.triggers),
|
|
28
|
+
line("useWhen", agent.routing?.useWhen),
|
|
29
|
+
line("avoidWhen", agent.routing?.avoidWhen),
|
|
30
|
+
line("cost", agent.routing?.cost),
|
|
31
|
+
line("category", agent.routing?.category),
|
|
32
|
+
"---",
|
|
33
|
+
"",
|
|
34
|
+
agent.systemPrompt.trim(),
|
|
35
|
+
"",
|
|
36
|
+
].filter((entry): entry is string => entry !== undefined);
|
|
37
|
+
return lines.join("\n");
|
|
38
|
+
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import type { AgentConfig, ResourceSource } from "./agent-config.ts";
|
|
4
|
-
import { loadConfig } from "../config/config.ts";
|
|
4
|
+
import { loadConfig, type LoadedPiTeamsConfig } from "../config/config.ts";
|
|
5
5
|
import { parseCsv, parseFrontmatter } from "../utils/frontmatter.ts";
|
|
6
|
+
import { logInternalError } from "../utils/internal-error.ts";
|
|
6
7
|
import { packageRoot, projectCrewRoot, userPiRoot } from "../utils/paths.ts";
|
|
7
8
|
|
|
8
9
|
export interface AgentDiscoveryResult {
|
|
@@ -19,6 +20,14 @@ function parseMemory(value: string | undefined): "user" | "project" | "local" |
|
|
|
19
20
|
return value === "user" || value === "project" || value === "local" ? value : undefined;
|
|
20
21
|
}
|
|
21
22
|
|
|
23
|
+
function parseLoadMode(value: string | undefined): "essential" | "lean" | undefined {
|
|
24
|
+
return value === "essential" || value === "lean" ? value : undefined;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function parseContextMode(value: string | undefined): "fresh" | "fork" | undefined {
|
|
28
|
+
return value === "fresh" || value === "fork" ? value : undefined;
|
|
29
|
+
}
|
|
30
|
+
|
|
22
31
|
function parseAgentFile(filePath: string, source: ResourceSource): AgentConfig | undefined {
|
|
23
32
|
try {
|
|
24
33
|
const content = fs.readFileSync(filePath, "utf-8");
|
|
@@ -36,20 +45,24 @@ function parseAgentFile(filePath: string, source: ResourceSource): AgentConfig |
|
|
|
36
45
|
source,
|
|
37
46
|
filePath,
|
|
38
47
|
systemPrompt: body.trim(),
|
|
39
|
-
model: frontmatter.model || undefined,
|
|
48
|
+
model: frontmatter.model === "false" ? undefined : frontmatter.model || undefined,
|
|
40
49
|
fallbackModels: parseCsv(frontmatter.fallbackModels),
|
|
41
|
-
thinking: frontmatter.thinking || undefined,
|
|
50
|
+
thinking: frontmatter.thinking === "false" ? undefined : frontmatter.thinking || undefined,
|
|
42
51
|
tools: parseCsv(frontmatter.tools),
|
|
43
52
|
extensions: frontmatter.extensions === "" ? [] : parseCsv(frontmatter.extensions),
|
|
44
53
|
skills: parseCsv(frontmatter.skills ?? frontmatter.skill),
|
|
45
54
|
systemPromptMode: frontmatter.systemPromptMode === "append" ? "append" : "replace",
|
|
46
|
-
inheritProjectContext: frontmatter.inheritProjectContext
|
|
47
|
-
inheritSkills: frontmatter.inheritSkills
|
|
55
|
+
inheritProjectContext: frontmatter.inheritProjectContext === "true",
|
|
56
|
+
inheritSkills: frontmatter.inheritSkills === "true",
|
|
48
57
|
memory: parseMemory(frontmatter.memory),
|
|
49
|
-
|
|
58
|
+
loadMode: parseLoadMode(frontmatter.loadMode),
|
|
59
|
+
defaultTools: frontmatter.defaultTools !== undefined ? parseCsv(frontmatter.defaultTools) ?? null : undefined,
|
|
60
|
+
contextMode: parseContextMode(frontmatter.contextMode),
|
|
61
|
+
disabled: frontmatter.disabled === "true" || frontmatter.enabled === "false",
|
|
50
62
|
routing: triggers || useWhen || avoidWhen || cost || category ? { triggers, useWhen, avoidWhen, cost, category } : undefined,
|
|
51
63
|
};
|
|
52
|
-
} catch {
|
|
64
|
+
} catch (error) {
|
|
65
|
+
logInternalError("discoverAgents.parseAgentFile", error, `filePath=${filePath}`);
|
|
53
66
|
return undefined;
|
|
54
67
|
}
|
|
55
68
|
}
|
|
@@ -63,12 +76,12 @@ function readAgentDir(dir: string, source: ResourceSource): AgentConfig[] {
|
|
|
63
76
|
.sort((a, b) => a.name.localeCompare(b.name));
|
|
64
77
|
}
|
|
65
78
|
|
|
66
|
-
function applyAgentOverrides(agents: AgentConfig[], cwd: string): AgentConfig[] {
|
|
67
|
-
const loaded = loadConfig(cwd);
|
|
68
|
-
const
|
|
69
|
-
const overrides =
|
|
79
|
+
function applyAgentOverrides(agents: AgentConfig[], cwd: string, loadedConfig?: LoadedPiTeamsConfig): AgentConfig[] {
|
|
80
|
+
const loaded = loadedConfig ?? loadConfig(cwd);
|
|
81
|
+
const agentsConfig = loaded.config.agents;
|
|
82
|
+
const overrides = agentsConfig?.overrides ?? {};
|
|
70
83
|
return agents
|
|
71
|
-
.filter((agent) => !(
|
|
84
|
+
.filter((agent) => !(agentsConfig?.disableBuiltins && agent.source === "builtin"))
|
|
72
85
|
.map((agent) => {
|
|
73
86
|
const overrideEntry = Object.entries(overrides).find(([name]) => name.toLowerCase() === agent.name.toLowerCase());
|
|
74
87
|
if (!overrideEntry) return agent;
|
|
@@ -87,10 +100,11 @@ function applyAgentOverrides(agents: AgentConfig[], cwd: string): AgentConfig[]
|
|
|
87
100
|
}
|
|
88
101
|
|
|
89
102
|
export function discoverAgents(cwd: string): AgentDiscoveryResult {
|
|
103
|
+
const loaded = loadConfig(cwd);
|
|
90
104
|
return {
|
|
91
|
-
builtin: applyAgentOverrides(readAgentDir(path.join(packageRoot(), "agents"), "builtin"), cwd),
|
|
92
|
-
user: applyAgentOverrides(readAgentDir(path.join(userPiRoot(), "agents"), "user"), cwd),
|
|
93
|
-
project: applyAgentOverrides(readAgentDir(path.join(projectCrewRoot(cwd), "agents"), "project"), cwd),
|
|
105
|
+
builtin: applyAgentOverrides(readAgentDir(path.join(packageRoot(), "agents"), "builtin"), cwd, loaded),
|
|
106
|
+
user: applyAgentOverrides(readAgentDir(path.join(userPiRoot(), "agents"), "user"), cwd, loaded),
|
|
107
|
+
project: applyAgentOverrides(readAgentDir(path.join(projectCrewRoot(cwd), "agents"), "project"), cwd, loaded),
|
|
94
108
|
};
|
|
95
109
|
}
|
|
96
110
|
|
package/src/config/config.ts
CHANGED
|
@@ -4,7 +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
|
+
import { projectCrewRoot, projectPiRoot } from "../utils/paths.ts";
|
|
8
8
|
|
|
9
9
|
export type PiTeamsAutonomyProfile = "manual" | "suggested" | "assisted" | "aggressive";
|
|
10
10
|
|
|
@@ -31,6 +31,7 @@ export interface CrewLimitsConfig {
|
|
|
31
31
|
export type CrewRuntimeMode = "auto" | "scaffold" | "child-process" | "live-session";
|
|
32
32
|
|
|
33
33
|
export type CompletionMutationGuardMode = "off" | "warn" | "fail";
|
|
34
|
+
export type EffectivenessGuardMode = "off" | "warn" | "block" | "fail";
|
|
34
35
|
|
|
35
36
|
export interface CrewRuntimeConfig {
|
|
36
37
|
mode?: CrewRuntimeMode;
|
|
@@ -44,6 +45,8 @@ export interface CrewRuntimeConfig {
|
|
|
44
45
|
groupJoinAckTimeoutMs?: number;
|
|
45
46
|
requirePlanApproval?: boolean;
|
|
46
47
|
completionMutationGuard?: CompletionMutationGuardMode;
|
|
48
|
+
effectivenessGuard?: EffectivenessGuardMode;
|
|
49
|
+
yield?: { enabled?: boolean; maxReminders?: number; reminderPrompt?: string };
|
|
47
50
|
}
|
|
48
51
|
|
|
49
52
|
export interface CrewControlConfig {
|
|
@@ -98,6 +101,11 @@ export interface CrewTelemetryConfig {
|
|
|
98
101
|
enabled?: boolean;
|
|
99
102
|
}
|
|
100
103
|
|
|
104
|
+
export interface CrewPolicyConfig {
|
|
105
|
+
requireIntentForDestructiveActions?: boolean;
|
|
106
|
+
disabledCapabilities?: string[];
|
|
107
|
+
}
|
|
108
|
+
|
|
101
109
|
export type CrewNotificationSeverity = "info" | "warning" | "error" | "critical";
|
|
102
110
|
|
|
103
111
|
export interface CrewNotificationsConfig {
|
|
@@ -150,6 +158,7 @@ export interface PiTeamsConfig {
|
|
|
150
158
|
agents?: CrewAgentsConfig;
|
|
151
159
|
tools?: CrewToolsConfig;
|
|
152
160
|
telemetry?: CrewTelemetryConfig;
|
|
161
|
+
policy?: CrewPolicyConfig;
|
|
153
162
|
notifications?: CrewNotificationsConfig;
|
|
154
163
|
observability?: CrewObservabilityConfig;
|
|
155
164
|
reliability?: CrewReliabilityConfig;
|
|
@@ -182,6 +191,11 @@ export interface UpdateConfigOptions {
|
|
|
182
191
|
}
|
|
183
192
|
|
|
184
193
|
export function configPath(): string {
|
|
194
|
+
const home = process.env.PI_TEAMS_HOME?.trim() || os.homedir();
|
|
195
|
+
return path.join(home, ".pi", "agent", "pi-crew.json");
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export function legacyConfigPath(): string {
|
|
185
199
|
const home = process.env.PI_TEAMS_HOME?.trim() || os.homedir();
|
|
186
200
|
return path.join(home, ".pi", "agent", "extensions", "pi-crew", "config.json");
|
|
187
201
|
}
|
|
@@ -195,7 +209,7 @@ export function projectConfigPath(cwd: string): string {
|
|
|
195
209
|
* This is a convenience path alongside the standard `config.json` in crewRoot.
|
|
196
210
|
*/
|
|
197
211
|
export function projectPiCrewJsonPath(cwd: string): string {
|
|
198
|
-
return path.join(cwd, "
|
|
212
|
+
return path.join(projectPiRoot(cwd), "pi-crew.json");
|
|
199
213
|
}
|
|
200
214
|
|
|
201
215
|
function withoutUndefined<T extends Record<string, unknown>>(value: T): Partial<T> {
|
|
@@ -356,6 +370,12 @@ function mergeConfig(base: PiTeamsConfig, override: PiTeamsConfig): PiTeamsConfi
|
|
|
356
370
|
...withoutUndefined((override.telemetry ?? {}) as Record<string, unknown>),
|
|
357
371
|
};
|
|
358
372
|
}
|
|
373
|
+
if (base.policy || override.policy) {
|
|
374
|
+
merged.policy = {
|
|
375
|
+
...(base.policy ?? {}),
|
|
376
|
+
...withoutUndefined((override.policy ?? {}) as Record<string, unknown>),
|
|
377
|
+
};
|
|
378
|
+
}
|
|
359
379
|
if (base.notifications || override.notifications) {
|
|
360
380
|
merged.notifications = {
|
|
361
381
|
...(base.notifications ?? {}),
|
|
@@ -509,6 +529,7 @@ function parseRuntimeConfig(value: unknown): CrewRuntimeConfig | undefined {
|
|
|
509
529
|
groupJoinAckTimeoutMs: parsePositiveInteger(obj.groupJoinAckTimeoutMs, 86_400_000),
|
|
510
530
|
requirePlanApproval: parseWithSchema(Type.Boolean(), obj.requirePlanApproval),
|
|
511
531
|
completionMutationGuard: parseWithSchema(Type.Union([Type.Literal("off"), Type.Literal("warn"), Type.Literal("fail")]), obj.completionMutationGuard),
|
|
532
|
+
effectivenessGuard: parseWithSchema(Type.Union([Type.Literal("off"), Type.Literal("warn"), Type.Literal("block"), Type.Literal("fail")]), obj.effectivenessGuard),
|
|
512
533
|
};
|
|
513
534
|
return Object.values(runtime).some((entry) => entry !== undefined) ? runtime : undefined;
|
|
514
535
|
}
|
|
@@ -611,6 +632,16 @@ function parseTelemetryConfig(value: unknown): CrewTelemetryConfig | undefined {
|
|
|
611
632
|
return Object.values(telemetry).some((entry) => entry !== undefined) ? telemetry : undefined;
|
|
612
633
|
}
|
|
613
634
|
|
|
635
|
+
function parsePolicyConfig(value: unknown): CrewPolicyConfig | undefined {
|
|
636
|
+
const obj = asRecord(value);
|
|
637
|
+
if (!obj) return undefined;
|
|
638
|
+
const policy: CrewPolicyConfig = {
|
|
639
|
+
requireIntentForDestructiveActions: parseWithSchema(Type.Boolean(), obj.requireIntentForDestructiveActions),
|
|
640
|
+
disabledCapabilities: parseWithSchema(Type.Array(Type.String()), obj.disabledCapabilities),
|
|
641
|
+
};
|
|
642
|
+
return Object.values(policy).some((entry) => entry !== undefined) ? policy : undefined;
|
|
643
|
+
}
|
|
644
|
+
|
|
614
645
|
function parseNotificationsConfig(value: unknown): CrewNotificationsConfig | undefined {
|
|
615
646
|
const obj = asRecord(value);
|
|
616
647
|
if (!obj) return undefined;
|
|
@@ -692,6 +723,7 @@ export function parseConfig(raw: unknown): PiTeamsConfig {
|
|
|
692
723
|
agents: parseAgentsConfig(obj.agents),
|
|
693
724
|
tools: parseToolsConfig(obj.tools),
|
|
694
725
|
telemetry: parseTelemetryConfig(obj.telemetry),
|
|
726
|
+
policy: parsePolicyConfig(obj.policy),
|
|
695
727
|
notifications: parseNotificationsConfig(obj.notifications),
|
|
696
728
|
observability: parseObservabilityConfig(obj.observability),
|
|
697
729
|
reliability: parseReliabilityConfig(obj.reliability),
|
|
@@ -727,35 +759,51 @@ function readConfigRecord(filePath: string): Record<string, unknown> {
|
|
|
727
759
|
return raw as Record<string, unknown>;
|
|
728
760
|
}
|
|
729
761
|
|
|
762
|
+
function readOptionalConfig(filePath: string): { exists: boolean; config: PiTeamsConfig; warnings: string[] } {
|
|
763
|
+
if (!fs.existsSync(filePath)) return { exists: false, config: {}, warnings: [] };
|
|
764
|
+
try {
|
|
765
|
+
const raw = readConfigRecord(filePath);
|
|
766
|
+
const parsed = parseConfigWithWarnings(raw);
|
|
767
|
+
return { exists: true, config: parsed.config, warnings: parsed.warnings.map((warning) => `${filePath}: ${warning}`) };
|
|
768
|
+
} catch (error) {
|
|
769
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
770
|
+
return { exists: true, config: {}, warnings: [`${filePath}: invalid config ignored: ${message}`] };
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
|
|
730
774
|
export function loadConfig(cwd?: string): LoadedPiTeamsConfig {
|
|
731
775
|
const filePath = configPath();
|
|
776
|
+
const legacyPath = legacyConfigPath();
|
|
732
777
|
const paths = cwd ? [filePath, projectConfigPath(cwd)] : [filePath];
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
778
|
+
const warnings: string[] = [];
|
|
779
|
+
const legacyConfig = readOptionalConfig(legacyPath);
|
|
780
|
+
if (legacyConfig.exists && legacyPath !== filePath) {
|
|
781
|
+
warnings.push(...legacyConfig.warnings);
|
|
782
|
+
paths.unshift(legacyPath);
|
|
783
|
+
}
|
|
784
|
+
const userConfig = readOptionalConfig(filePath);
|
|
785
|
+
warnings.push(...userConfig.warnings);
|
|
786
|
+
let config = mergeConfig(legacyConfig.exists && legacyPath !== filePath ? legacyConfig.config : {}, userConfig.config);
|
|
787
|
+
if (cwd) {
|
|
788
|
+
const projectPath = projectConfigPath(cwd);
|
|
789
|
+
const projectConfig = readOptionalConfig(projectPath);
|
|
790
|
+
if (projectConfig.exists) {
|
|
741
791
|
const projectSafeConfig = sanitizeProjectConfig(projectPath, config, projectConfig.config);
|
|
742
|
-
warnings.push(...projectConfig.warnings
|
|
792
|
+
warnings.push(...projectConfig.warnings, ...projectSafeConfig.warnings);
|
|
743
793
|
config = mergeConfig(config, projectSafeConfig.config);
|
|
744
|
-
// Also load .pi/pi-crew.json from project root if it exists
|
|
745
|
-
const piCrewJsonPath = projectPiCrewJsonPath(cwd);
|
|
746
|
-
if (fs.existsSync(piCrewJsonPath)) {
|
|
747
|
-
const piCrewJsonConfig = parseConfigWithWarnings(readConfigRecord(piCrewJsonPath));
|
|
748
|
-
const piCrewJsonSafeConfig = sanitizeProjectConfig(piCrewJsonPath, config, piCrewJsonConfig.config);
|
|
749
|
-
warnings.push(...piCrewJsonConfig.warnings.map((warning) => `${piCrewJsonPath}: ${warning}`), ...piCrewJsonSafeConfig.warnings);
|
|
750
|
-
config = mergeConfig(config, piCrewJsonSafeConfig.config);
|
|
751
|
-
paths.push(piCrewJsonPath);
|
|
752
|
-
}
|
|
753
794
|
}
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
795
|
+
// `.pi/pi-crew.json` is the project-owned override file. If present and valid,
|
|
796
|
+
// it may override all pi-crew config fields, including agents.overrides.
|
|
797
|
+
// If missing or invalid, it is ignored and defaults/user config remain effective.
|
|
798
|
+
const piCrewJsonPath = projectPiCrewJsonPath(cwd);
|
|
799
|
+
const piCrewJsonConfig = readOptionalConfig(piCrewJsonPath);
|
|
800
|
+
if (piCrewJsonConfig.exists) {
|
|
801
|
+
warnings.push(...piCrewJsonConfig.warnings);
|
|
802
|
+
config = mergeConfig(config, piCrewJsonConfig.config);
|
|
803
|
+
paths.push(piCrewJsonPath);
|
|
804
|
+
}
|
|
758
805
|
}
|
|
806
|
+
return { path: filePath, paths, config, warnings: warnings.length > 0 ? warnings : undefined };
|
|
759
807
|
}
|
|
760
808
|
|
|
761
809
|
export function updateConfig(patch: PiTeamsConfig, options: UpdateConfigOptions = {}): SavedPiTeamsConfig {
|
package/src/config/defaults.ts
CHANGED
|
@@ -12,6 +12,17 @@ export const DEFAULT_CHILD_PI = {
|
|
|
12
12
|
maxCompactContentChars: 4096,
|
|
13
13
|
};
|
|
14
14
|
|
|
15
|
+
export const DEFAULT_LIVE_SESSION = {
|
|
16
|
+
/** Maximum wall-clock time for a single live-session task before abort (ms). */
|
|
17
|
+
responseTimeoutMs: 5 * 60_000,
|
|
18
|
+
/** Maximum yield reminder attempts before accepting no-yield. */
|
|
19
|
+
maxYieldRetries: 3,
|
|
20
|
+
/** Polling interval for session idle check during yield enforcement (ms). */
|
|
21
|
+
yieldPollIntervalMs: 500,
|
|
22
|
+
/** Maximum time to wait for session idle after prompt (ms). */
|
|
23
|
+
idleWaitTimeoutMs: 60_000,
|
|
24
|
+
};
|
|
25
|
+
|
|
15
26
|
export const DEFAULT_LOCKS = {
|
|
16
27
|
staleMs: 30_000,
|
|
17
28
|
};
|
|
@@ -53,6 +64,20 @@ export const DEFAULT_UI = {
|
|
|
53
64
|
refreshMs: 1000,
|
|
54
65
|
notifierIntervalMs: 5000,
|
|
55
66
|
widgetDefaultFrameMs: 1000,
|
|
67
|
+
widgetPlacement: "aboveEditor" as const,
|
|
68
|
+
widgetMaxLines: 8,
|
|
69
|
+
powerbar: true,
|
|
70
|
+
dashboardPlacement: "center" as const,
|
|
71
|
+
dashboardWidth: 72,
|
|
72
|
+
dashboardLiveRefreshMs: 1000,
|
|
73
|
+
autoOpenDashboard: false,
|
|
74
|
+
autoOpenDashboardForForegroundRuns: false,
|
|
75
|
+
showModel: true,
|
|
76
|
+
showTokens: true,
|
|
77
|
+
showTools: true,
|
|
78
|
+
transcriptTailBytes: 1024 * 1024,
|
|
79
|
+
mascotStyle: "cat" as const,
|
|
80
|
+
mascotEffect: "random" as const,
|
|
56
81
|
};
|
|
57
82
|
|
|
58
83
|
export const DEFAULT_NOTIFICATIONS = {
|
|
@@ -73,46 +73,39 @@ export function buildAutonomousPolicy(prompt: string, config: PiTeamsAutonomousC
|
|
|
73
73
|
? "Do not suggest worktree mode unless the user explicitly asks for it."
|
|
74
74
|
: "Consider workspaceMode: 'worktree' for parallel or risky code-changing work in clean git repositories.";
|
|
75
75
|
return [
|
|
76
|
-
"# pi-crew
|
|
76
|
+
"# pi-crew Delegation Policy (MANDATORY)",
|
|
77
77
|
"",
|
|
78
78
|
`Autonomy profile: ${effective.profile}.`,
|
|
79
|
-
"You
|
|
79
|
+
"You MUST evaluate EVERY user request against the delegation decision table below BEFORE acting.",
|
|
80
80
|
"",
|
|
81
|
-
"Decision
|
|
82
|
-
"- Treat a user-supplied task list with 2+ actionable bullets/numbered items as a delegation candidate even when no pi-crew keyword appears.",
|
|
83
|
-
"- Prefer `team` when tasks span multiple files/subsystems, require sequencing, combine implementation + tests/docs/review, or need independent exploration before edits.",
|
|
84
|
-
"- If unsure whether subtasks conflict, call `team` with action='recommend' first instead of manually splitting work.",
|
|
85
|
-
"- For assisted/aggressive autonomy and non-trivial multi-task work, prefer a team run or plan over direct single-agent execution.",
|
|
81
|
+
"## Decision Table — Do I delegate?",
|
|
86
82
|
"",
|
|
87
|
-
"
|
|
88
|
-
"
|
|
89
|
-
"
|
|
90
|
-
"
|
|
91
|
-
"
|
|
92
|
-
"
|
|
83
|
+
"| Task characteristic | Delegate? | Method |",
|
|
84
|
+
"|---|---|---|",
|
|
85
|
+
"| Read/edit 1-2 files, simple question | NO | Do it directly |",
|
|
86
|
+
"| User explicitly says \"do it yourself\" / \"no team\" | NO | Do it directly |",
|
|
87
|
+
"| Destructive action (delete/prune/forget) | NO | Ask for confirmation first |",
|
|
88
|
+
"| Research / deep-read / source audit / \"nghiên cứu\" | YES | `team action='research'` |",
|
|
89
|
+
"| Multi-file implementation / feature / refactor | YES | `team action='run', team='implementation'` |",
|
|
90
|
+
"| Small bug fix (1-2 files, clear cause) | YES | `team action='run', team='fast-fix'` |",
|
|
91
|
+
"| Code review / security review | YES | `team action='run', team='review'` |",
|
|
92
|
+
"| Task list with 2+ actionable items | YES | `team action='plan'` or `action='run'` |",
|
|
93
|
+
"| Need exploration before knowing scope | YES | `Agent(explorer)` or `team action='recommend'` |",
|
|
94
|
+
"| Parallel independent subtasks | YES | `team action='parallel'` or multiple `Agent` background |",
|
|
95
|
+
"| Unsure which team/workflow fits | YES | `team action='recommend'` first |",
|
|
93
96
|
"",
|
|
94
|
-
"
|
|
95
|
-
"- The user asks a simple factual question or tiny single-file edit.",
|
|
96
|
-
"- The user explicitly asks you to work directly without delegation.",
|
|
97
|
-
"- The tasks clearly modify the same small file region and can be completed safer by one agent without parallel fanout.",
|
|
98
|
-
"- The action is destructive (`delete`, `forget`, `prune`, forced cleanup) and the user has not explicitly confirmed it.",
|
|
97
|
+
"## Rules",
|
|
99
98
|
"",
|
|
100
|
-
"
|
|
101
|
-
"
|
|
102
|
-
"
|
|
103
|
-
"
|
|
104
|
-
"
|
|
105
|
-
"- Unsure which team/workflow to use -> call the `team` tool with action='recommend' and the user's goal, then follow the suggested plan/run call if appropriate.",
|
|
106
|
-
"- After delegating exploration/research/review, do not duplicate the same search manually. Continue only with non-overlapping work.",
|
|
107
|
-
"- Before claiming delegated work is complete, inspect the run with action='status' or action='summary'.",
|
|
108
|
-
"- Unsure or risky work -> action='plan' first, then run the selected team.",
|
|
99
|
+
"1. If the task needs reading >3 files OR editing >2 files OR has a research/review/planning component → you MUST delegate via `team` or `Agent`. Do not do it yourself.",
|
|
100
|
+
"2. After delegating, do NOT duplicate the same exploration/reading/review manually. Continue only with non-overlapping work.",
|
|
101
|
+
"3. Before claiming delegated work is complete, verify with `team action='status'` or `action='summary'`.",
|
|
102
|
+
"4. If unsure whether subtasks conflict, use `team action='recommend'` first.",
|
|
103
|
+
"5. For parallel implementation, prefer `workspaceMode: 'worktree'` in clean git repos.",
|
|
109
104
|
"",
|
|
110
|
-
"Conflict-safe task splitting
|
|
111
|
-
"- Do not parallelize subtasks that may edit the same file, same symbol, same
|
|
112
|
-
"-
|
|
113
|
-
"-
|
|
114
|
-
"- If workers discover overlap, blockers, missing requirements, or need leader decisions, they must use mailbox/status artifacts to ask the leader/orchestrator and pause risky edits.",
|
|
115
|
-
"- The leader should resolve conflicts by sequencing, narrowing scope, or reassigning ownership before continuing.",
|
|
105
|
+
"## Conflict-safe task splitting",
|
|
106
|
+
"- Do not parallelize subtasks that may edit the same file, same symbol, or same lockfile.",
|
|
107
|
+
"- Assign one owner per file/symbol. Workers must report intended changed files before editing.",
|
|
108
|
+
"- If workers discover overlap, they must pause and ask the leader to resolve.",
|
|
116
109
|
"",
|
|
117
110
|
asyncGuidance,
|
|
118
111
|
worktreeGuidance,
|
package/src/extension/help.ts
CHANGED
|
@@ -6,6 +6,8 @@ import { allAgents, discoverAgents } from "../agents/discover-agents.ts";
|
|
|
6
6
|
import type { TeamToolDetails } from "./team-tool-types.ts";
|
|
7
7
|
import { toolResult, type PiTeamsToolResult } from "./tool-result.ts";
|
|
8
8
|
import type { TeamToolParamsValue } from "../schema/team-tool-schema.ts";
|
|
9
|
+
import type { PiTeamsConfig } from "../config/config.ts";
|
|
10
|
+
import { enforceDestructiveIntent } from "./team-tool/intent-policy.ts";
|
|
9
11
|
import type { TeamConfig, TeamRole } from "../teams/team-config.ts";
|
|
10
12
|
import { serializeTeam } from "../teams/team-serializer.ts";
|
|
11
13
|
import { allTeams, discoverTeams } from "../teams/discover-teams.ts";
|
|
@@ -17,6 +19,7 @@ import { hasOwn, parseConfigObject, requireString, sanitizeName } from "../utils
|
|
|
17
19
|
|
|
18
20
|
interface ManagementContext {
|
|
19
21
|
cwd: string;
|
|
22
|
+
config?: PiTeamsConfig;
|
|
20
23
|
}
|
|
21
24
|
|
|
22
25
|
type MutableSource = "user" | "project";
|
|
@@ -359,6 +362,8 @@ export function handleUpdate(params: TeamToolParamsValue, ctx: ManagementContext
|
|
|
359
362
|
}
|
|
360
363
|
|
|
361
364
|
export function handleDelete(params: TeamToolParamsValue, ctx: ManagementContext): PiTeamsToolResult {
|
|
365
|
+
const intentError = enforceDestructiveIntent("delete", params, ctx.config);
|
|
366
|
+
if (intentError) return intentError;
|
|
362
367
|
if (!params.confirm) return result("delete requires confirm: true.", "error", true);
|
|
363
368
|
const resolved = resolveMutable(ctx, params);
|
|
364
369
|
if (resolved.error) return resolved.error;
|