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.
Files changed (178) hide show
  1. package/CHANGELOG.md +97 -0
  2. package/README.md +5 -5
  3. package/agents/analyst.md +11 -11
  4. package/agents/critic.md +11 -11
  5. package/agents/executor.md +11 -11
  6. package/agents/explorer.md +11 -11
  7. package/agents/planner.md +11 -11
  8. package/agents/reviewer.md +11 -11
  9. package/agents/security-reviewer.md +11 -11
  10. package/agents/test-engineer.md +11 -11
  11. package/agents/verifier.md +11 -11
  12. package/agents/writer.md +11 -11
  13. package/docs/next-upgrade-roadmap.md +808 -0
  14. package/docs/research/AGENT-EXECUTION-ARCHITECTURE.md +261 -0
  15. package/docs/research/AGENT-LIFECYCLE-COMPARISON.md +111 -0
  16. package/docs/research/AUDIT_OH_MY_PI.md +261 -0
  17. package/docs/research/AUDIT_PI_CREW.md +457 -0
  18. package/docs/research/CAVEMAN-DEEP-RESEARCH.md +281 -0
  19. package/docs/research/COMPARISON_OH_MY_PI_VS_PI_CREW.md +264 -0
  20. package/docs/research/DEEP-RESEARCH-PI-POWERBAR.md +343 -0
  21. package/docs/research/DEEP_RESEARCH_SUBAGENT_ARCHITECTURE.md +480 -0
  22. package/docs/research/GAP_CLOSURE_IMPLEMENTATION_PLAN.md +354 -0
  23. package/docs/research/IMPLEMENTATION_PLAN.md +385 -0
  24. package/docs/research/LIVE-SESSION-PRODUCTION-READY-PLAN.md +502 -0
  25. package/docs/research/OH-MY-PI-DEEP-RESEARCH-v14.7.6.md +266 -0
  26. package/docs/research/REMAINING-GAPS-PLAN.md +363 -0
  27. package/docs/research/SESSION-SUMMARY-2026-05-08.md +146 -0
  28. package/docs/research/UI-RESPONSIVENESS-AUDIT.md +173 -0
  29. package/docs/research-awesome-agent-skills-distillation.md +100 -0
  30. package/docs/research-oh-my-pi-distillation.md +369 -0
  31. package/docs/source-runtime-refactor-map.md +24 -0
  32. package/docs/usage.md +3 -3
  33. package/install.mjs +52 -8
  34. package/package.json +99 -98
  35. package/schema.json +10 -1
  36. package/skills/async-worker-recovery/SKILL.md +42 -0
  37. package/skills/context-artifact-hygiene/SKILL.md +52 -0
  38. package/skills/delegation-patterns/SKILL.md +54 -0
  39. package/skills/mailbox-interactive/SKILL.md +40 -0
  40. package/skills/model-routing-context/SKILL.md +39 -0
  41. package/skills/multi-perspective-review/SKILL.md +58 -0
  42. package/skills/observability-reliability/SKILL.md +41 -0
  43. package/skills/orchestration/SKILL.md +157 -0
  44. package/skills/ownership-session-security/SKILL.md +41 -0
  45. package/skills/pi-extension-lifecycle/SKILL.md +39 -0
  46. package/skills/requirements-to-task-packet/SKILL.md +63 -0
  47. package/skills/resource-discovery-config/SKILL.md +41 -0
  48. package/skills/runtime-state-reader/SKILL.md +44 -0
  49. package/skills/secure-agent-orchestration-review/SKILL.md +45 -0
  50. package/skills/state-mutation-locking/SKILL.md +42 -0
  51. package/skills/systematic-debugging/SKILL.md +67 -0
  52. package/skills/ui-render-performance/SKILL.md +39 -0
  53. package/skills/verification-before-done/SKILL.md +57 -0
  54. package/skills/worktree-isolation/SKILL.md +39 -0
  55. package/src/agents/agent-config.ts +6 -0
  56. package/src/agents/agent-search.ts +98 -0
  57. package/src/agents/agent-serializer.ts +38 -34
  58. package/src/agents/discover-agents.ts +29 -15
  59. package/src/config/config.ts +72 -24
  60. package/src/config/defaults.ts +25 -0
  61. package/src/extension/autonomous-policy.ts +26 -33
  62. package/src/extension/help.ts +1 -0
  63. package/src/extension/management.ts +5 -0
  64. package/src/extension/project-init.ts +62 -2
  65. package/src/extension/register.ts +69 -22
  66. package/src/extension/registration/commands.ts +64 -25
  67. package/src/extension/registration/compaction-guard.ts +1 -1
  68. package/src/extension/registration/subagent-helpers.ts +8 -0
  69. package/src/extension/registration/subagent-tools.ts +149 -148
  70. package/src/extension/registration/team-tool.ts +14 -10
  71. package/src/extension/run-index.ts +35 -21
  72. package/src/extension/run-maintenance.ts +30 -5
  73. package/src/extension/team-tool/api.ts +47 -9
  74. package/src/extension/team-tool/cancel.ts +109 -5
  75. package/src/extension/team-tool/context.ts +8 -0
  76. package/src/extension/team-tool/intent-policy.ts +42 -0
  77. package/src/extension/team-tool/lifecycle-actions.ts +120 -79
  78. package/src/extension/team-tool/parallel-dispatch.ts +156 -0
  79. package/src/extension/team-tool/respond.ts +46 -18
  80. package/src/extension/team-tool/run.ts +55 -12
  81. package/src/extension/team-tool/status.ts +13 -2
  82. package/src/extension/team-tool-types.ts +3 -0
  83. package/src/extension/team-tool.ts +45 -14
  84. package/src/hooks/registry.ts +61 -0
  85. package/src/hooks/types.ts +41 -0
  86. package/src/observability/event-to-metric.ts +8 -1
  87. package/src/runtime/agent-control.ts +169 -63
  88. package/src/runtime/async-runner.ts +3 -1
  89. package/src/runtime/background-runner.ts +78 -53
  90. package/src/runtime/cancellation-token.ts +89 -0
  91. package/src/runtime/cancellation.ts +61 -0
  92. package/src/runtime/capability-inventory.ts +116 -0
  93. package/src/runtime/child-pi.ts +458 -444
  94. package/src/runtime/code-summary.ts +247 -0
  95. package/src/runtime/crash-recovery.ts +182 -0
  96. package/src/runtime/crew-agent-records.ts +70 -10
  97. package/src/runtime/crew-agent-runtime.ts +1 -0
  98. package/src/runtime/custom-tools/irc-tool.ts +201 -0
  99. package/src/runtime/custom-tools/submit-result-tool.ts +90 -0
  100. package/src/runtime/deadletter.ts +1 -0
  101. package/src/runtime/delivery-coordinator.ts +48 -25
  102. package/src/runtime/effectiveness.ts +81 -0
  103. package/src/runtime/event-stream-bridge.ts +90 -0
  104. package/src/runtime/live-agent-control.ts +2 -1
  105. package/src/runtime/live-agent-manager.ts +179 -85
  106. package/src/runtime/live-control-realtime.ts +1 -1
  107. package/src/runtime/live-extension-bridge.ts +150 -0
  108. package/src/runtime/live-irc.ts +92 -0
  109. package/src/runtime/live-session-health.ts +100 -0
  110. package/src/runtime/live-session-runtime.ts +599 -305
  111. package/src/runtime/manifest-cache.ts +17 -2
  112. package/src/runtime/mcp-proxy.ts +113 -0
  113. package/src/runtime/model-fallback.ts +6 -4
  114. package/src/runtime/notebook-helpers.ts +90 -0
  115. package/src/runtime/orphan-sentinel.ts +7 -0
  116. package/src/runtime/output-validator.ts +187 -0
  117. package/src/runtime/parallel-utils.ts +57 -0
  118. package/src/runtime/parent-guard.ts +80 -0
  119. package/src/runtime/pi-args.ts +18 -3
  120. package/src/runtime/process-status.ts +5 -1
  121. package/src/runtime/prose-compressor.ts +164 -0
  122. package/src/runtime/result-extractor.ts +121 -0
  123. package/src/runtime/retry-executor.ts +81 -64
  124. package/src/runtime/runtime-resolver.ts +23 -10
  125. package/src/runtime/semaphore.ts +131 -0
  126. package/src/runtime/sensitive-paths.ts +92 -0
  127. package/src/runtime/skill-instructions.ts +222 -0
  128. package/src/runtime/stale-reconciler.ts +4 -14
  129. package/src/runtime/stream-preview.ts +177 -0
  130. package/src/runtime/subagent-manager.ts +6 -2
  131. package/src/runtime/subprocess-tool-registry.ts +67 -0
  132. package/src/runtime/task-output-context.ts +177 -127
  133. package/src/runtime/task-runner/capabilities.ts +78 -0
  134. package/src/runtime/task-runner/live-executor.ts +107 -101
  135. package/src/runtime/task-runner/prompt-builder.ts +72 -8
  136. package/src/runtime/task-runner/prompt-pipeline.ts +64 -0
  137. package/src/runtime/task-runner/run-projection.ts +104 -0
  138. package/src/runtime/task-runner.ts +115 -5
  139. package/src/runtime/team-runner.ts +134 -19
  140. package/src/runtime/workspace-tree.ts +298 -0
  141. package/src/runtime/yield-handler.ts +189 -0
  142. package/src/schema/config-schema.ts +7 -0
  143. package/src/schema/team-tool-schema.ts +14 -4
  144. package/src/skills/discover-skills.ts +67 -0
  145. package/src/state/active-run-registry.ts +167 -0
  146. package/src/state/artifact-store.ts +4 -1
  147. package/src/state/atomic-write.ts +50 -1
  148. package/src/state/blob-store.ts +117 -0
  149. package/src/state/contracts.ts +2 -1
  150. package/src/state/event-log-rotation.ts +158 -0
  151. package/src/state/event-log.ts +52 -2
  152. package/src/state/mailbox.ts +129 -9
  153. package/src/state/state-store.ts +32 -5
  154. package/src/state/types.ts +64 -2
  155. package/src/teams/team-config.ts +1 -0
  156. package/src/ui/agent-management-overlay.ts +144 -0
  157. package/src/ui/crew-widget.ts +15 -5
  158. package/src/ui/dashboard-panes/cancellation-pane.ts +43 -0
  159. package/src/ui/dashboard-panes/capability-pane.ts +60 -0
  160. package/src/ui/dashboard-panes/mailbox-pane.ts +35 -11
  161. package/src/ui/dashboard-panes/progress-pane.ts +2 -0
  162. package/src/ui/live-run-sidebar.ts +4 -0
  163. package/src/ui/powerbar-publisher.ts +77 -15
  164. package/src/ui/render-coalescer.ts +51 -0
  165. package/src/ui/run-dashboard.ts +4 -0
  166. package/src/ui/run-event-bus.ts +209 -0
  167. package/src/ui/run-snapshot-cache.ts +78 -18
  168. package/src/ui/snapshot-types.ts +10 -0
  169. package/src/ui/transcript-entries.ts +258 -0
  170. package/src/utils/ids.ts +5 -0
  171. package/src/utils/incremental-reader.ts +104 -0
  172. package/src/utils/paths.ts +4 -2
  173. package/src/utils/scan-cache.ts +137 -0
  174. package/src/utils/sse-parser.ts +134 -0
  175. package/src/utils/task-name-generator.ts +337 -0
  176. package/src/utils/visual.ts +33 -2
  177. package/src/workflows/workflow-config.ts +1 -0
  178. 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("triggers", agent.routing?.triggers),
24
- line("useWhen", agent.routing?.useWhen),
25
- line("avoidWhen", agent.routing?.avoidWhen),
26
- line("cost", agent.routing?.cost),
27
- line("category", agent.routing?.category),
28
- "---",
29
- "",
30
- agent.systemPrompt.trim(),
31
- "",
32
- ].filter((entry): entry is string => entry !== undefined);
33
- return lines.join("\n");
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 as unknown === true || frontmatter.inheritProjectContext === "true",
47
- inheritSkills: frontmatter.inheritSkills as unknown === true || frontmatter.inheritSkills === "true",
55
+ inheritProjectContext: frontmatter.inheritProjectContext === "true",
56
+ inheritSkills: frontmatter.inheritSkills === "true",
48
57
  memory: parseMemory(frontmatter.memory),
49
- disabled: frontmatter.disabled as unknown === true || frontmatter.disabled === "true" || frontmatter.enabled as unknown === false || frontmatter.enabled === "false",
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 config = loaded.config.agents;
69
- const overrides = config?.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) => !(config?.disableBuiltins && agent.source === "builtin"))
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
 
@@ -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, ".pi", "pi-crew.json");
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
- try {
734
- const userRaw = readConfigRecord(filePath);
735
- const userConfig = parseConfigWithWarnings(userRaw);
736
- let config = userConfig.config;
737
- const warnings: string[] = userConfig.warnings.map((warning) => `${filePath}: ${warning}`);
738
- if (cwd) {
739
- const projectPath = projectConfigPath(cwd);
740
- const projectConfig = parseConfigWithWarnings(readConfigRecord(projectPath));
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.map((warning) => `${projectPath}: ${warning}`), ...projectSafeConfig.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
- return { path: filePath, paths, config, warnings: warnings.length > 0 ? warnings : undefined };
755
- } catch (error) {
756
- const message = error instanceof Error ? error.message : String(error);
757
- return { path: filePath, paths, config: {}, error: message };
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 {
@@ -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 Autonomous Delegation Policy",
76
+ "# pi-crew Delegation Policy (MANDATORY)",
77
77
  "",
78
78
  `Autonomy profile: ${effective.profile}.`,
79
- "You have access to the `team` tool for coordinated multi-agent work. Use it proactively when the task benefits from specialized roles, planning, review, verification, durable artifacts, async execution, or worktree isolation.",
79
+ "You MUST evaluate EVERY user request against the delegation decision table below BEFORE acting.",
80
80
  "",
81
- "Decision framework (not keyword-only):",
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
- "Use `team` automatically when:",
88
- "- The task spans multiple files, subsystems, or unclear code areas.",
89
- "- The prompt contains a non-trivial task list, roadmap, checklist, migration plan, or ordered implementation plan.",
90
- "- The task requires planning before implementation.",
91
- "- The task asks for implementation plus tests, review, verification, migration, architecture, security review, or debugging.",
92
- "- The task would benefit from explorer/planner/executor/reviewer/verifier roles.",
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
- "Do not use `team` when:",
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
- "Recommended mappings:",
101
- "- Complex feature/refactor/migration -> action='run', team='implementation'.",
102
- "- Small bug fix -> action='run', team='fast-fix'.",
103
- "- Code/security review -> action='run', team='review'.",
104
- "- Research or documentation synthesis -> action='run', team='research'.",
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 migration path, package manifest, lockfile, or generated schema unless a planner explicitly sequences them.",
112
- "- For potential overlap, use plan/recommend first, assign one owner per file/symbol, and require workers to report intended changed files before editing.",
113
- "- Prefer workspaceMode: 'worktree' for parallel implementation in clean git repositories, but still avoid merging overlapping edits without review.",
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,
@@ -11,6 +11,7 @@ export function piTeamsHelp(): string {
11
11
  "- /team-summary <runId>",
12
12
  "- /team-resume <runId>",
13
13
  "- /team-cancel <runId>",
14
+ "- /team-retry <runId> [taskId]",
14
15
  "",
15
16
  "Inspection:",
16
17
  "- /team-events <runId>",
@@ -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;