pi-crew 0.1.45 → 0.1.46

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (198) hide show
  1. package/README.md +5 -5
  2. package/agents/analyst.md +1 -1
  3. package/agents/critic.md +1 -1
  4. package/agents/executor.md +1 -1
  5. package/agents/explorer.md +1 -1
  6. package/agents/planner.md +1 -1
  7. package/agents/reviewer.md +1 -1
  8. package/agents/security-reviewer.md +1 -1
  9. package/agents/test-engineer.md +1 -1
  10. package/agents/verifier.md +1 -1
  11. package/agents/writer.md +1 -1
  12. package/docs/next-upgrade-roadmap.md +733 -0
  13. package/docs/refactor-tasks-phase3.md +394 -394
  14. package/docs/refactor-tasks-phase4.md +564 -564
  15. package/docs/refactor-tasks-phase5.md +402 -402
  16. package/docs/refactor-tasks-phase6.md +662 -662
  17. package/docs/research-awesome-agent-skills-distillation.md +100 -0
  18. package/docs/research-extension-examples.md +297 -297
  19. package/docs/research-extension-system.md +324 -324
  20. package/docs/research-oh-my-pi-distillation.md +322 -0
  21. package/docs/research-optimization-plan.md +548 -548
  22. package/docs/research-phase10-distillation.md +198 -198
  23. package/docs/research-phase11-distillation.md +201 -201
  24. package/docs/research-pi-coding-agent.md +357 -357
  25. package/docs/research-source-pi-crew-reference.md +174 -174
  26. package/docs/runtime-flow.md +148 -148
  27. package/docs/source-runtime-refactor-map.md +107 -83
  28. package/docs/usage.md +3 -3
  29. package/index.ts +6 -6
  30. package/install.mjs +52 -8
  31. package/package.json +1 -1
  32. package/schema.json +2 -1
  33. package/skills/async-worker-recovery/SKILL.md +42 -0
  34. package/skills/context-artifact-hygiene/SKILL.md +52 -0
  35. package/skills/delegation-patterns/SKILL.md +54 -0
  36. package/skills/mailbox-interactive/SKILL.md +40 -0
  37. package/skills/model-routing-context/SKILL.md +39 -0
  38. package/skills/multi-perspective-review/SKILL.md +58 -0
  39. package/skills/observability-reliability/SKILL.md +41 -0
  40. package/skills/ownership-session-security/SKILL.md +41 -0
  41. package/skills/pi-extension-lifecycle/SKILL.md +39 -0
  42. package/skills/requirements-to-task-packet/SKILL.md +63 -0
  43. package/skills/resource-discovery-config/SKILL.md +41 -0
  44. package/skills/runtime-state-reader/SKILL.md +44 -0
  45. package/skills/secure-agent-orchestration-review/SKILL.md +45 -0
  46. package/skills/state-mutation-locking/SKILL.md +42 -0
  47. package/skills/systematic-debugging/SKILL.md +67 -0
  48. package/skills/ui-render-performance/SKILL.md +39 -0
  49. package/skills/verification-before-done/SKILL.md +57 -0
  50. package/skills/worktree-isolation/SKILL.md +39 -0
  51. package/src/agents/agent-serializer.ts +34 -34
  52. package/src/agents/discover-agents.ts +12 -11
  53. package/src/config/config.ts +48 -24
  54. package/src/config/defaults.ts +14 -0
  55. package/src/extension/cross-extension-rpc.ts +82 -82
  56. package/src/extension/project-init.ts +62 -2
  57. package/src/extension/register.ts +11 -9
  58. package/src/extension/registration/commands.ts +32 -25
  59. package/src/extension/registration/compaction-guard.ts +125 -125
  60. package/src/extension/registration/subagent-helpers.ts +8 -0
  61. package/src/extension/registration/subagent-tools.ts +149 -148
  62. package/src/extension/registration/team-tool.ts +8 -6
  63. package/src/extension/run-bundle-schema.ts +89 -89
  64. package/src/extension/run-index.ts +13 -5
  65. package/src/extension/run-maintenance.ts +62 -43
  66. package/src/extension/team-tool/api.ts +25 -8
  67. package/src/extension/team-tool/cancel.ts +33 -4
  68. package/src/extension/team-tool/context.ts +5 -0
  69. package/src/extension/team-tool/handle-settings.ts +188 -188
  70. package/src/extension/team-tool/inspect.ts +41 -41
  71. package/src/extension/team-tool/lifecycle-actions.ts +91 -79
  72. package/src/extension/team-tool/plan.ts +19 -19
  73. package/src/extension/team-tool/respond.ts +37 -17
  74. package/src/extension/team-tool/run.ts +52 -10
  75. package/src/extension/team-tool/status.ts +12 -1
  76. package/src/extension/team-tool-types.ts +2 -0
  77. package/src/extension/team-tool.ts +32 -11
  78. package/src/i18n.ts +184 -184
  79. package/src/observability/event-to-metric.ts +8 -1
  80. package/src/observability/exporters/otlp-exporter.ts +77 -77
  81. package/src/prompt/prompt-runtime.ts +72 -72
  82. package/src/runtime/agent-control.ts +63 -63
  83. package/src/runtime/agent-memory.ts +72 -72
  84. package/src/runtime/agent-observability.ts +114 -114
  85. package/src/runtime/async-marker.ts +26 -26
  86. package/src/runtime/attention-events.ts +28 -28
  87. package/src/runtime/background-runner.ts +59 -53
  88. package/src/runtime/cancellation.ts +51 -0
  89. package/src/runtime/child-pi.ts +457 -444
  90. package/src/runtime/completion-guard.ts +190 -190
  91. package/src/runtime/crash-recovery.ts +1 -0
  92. package/src/runtime/crew-agent-records.ts +38 -6
  93. package/src/runtime/deadletter.ts +1 -0
  94. package/src/runtime/delivery-coordinator.ts +46 -25
  95. package/src/runtime/direct-run.ts +35 -35
  96. package/src/runtime/effectiveness.ts +76 -0
  97. package/src/runtime/foreground-control.ts +82 -82
  98. package/src/runtime/green-contract.ts +46 -46
  99. package/src/runtime/group-join.ts +106 -106
  100. package/src/runtime/heartbeat-gradient.ts +28 -28
  101. package/src/runtime/heartbeat-watcher.ts +124 -124
  102. package/src/runtime/live-agent-control.ts +88 -87
  103. package/src/runtime/live-agent-manager.ts +103 -85
  104. package/src/runtime/live-control-realtime.ts +36 -36
  105. package/src/runtime/live-session-runtime.ts +309 -305
  106. package/src/runtime/manifest-cache.ts +17 -2
  107. package/src/runtime/model-fallback.ts +6 -4
  108. package/src/runtime/parallel-research.ts +44 -44
  109. package/src/runtime/pi-args.ts +18 -3
  110. package/src/runtime/pi-json-output.ts +111 -111
  111. package/src/runtime/policy-engine.ts +79 -79
  112. package/src/runtime/process-status.ts +5 -1
  113. package/src/runtime/progress-event-coalescer.ts +43 -43
  114. package/src/runtime/recovery-recipes.ts +74 -74
  115. package/src/runtime/retry-executor.ts +81 -64
  116. package/src/runtime/role-permission.ts +39 -39
  117. package/src/runtime/runtime-resolver.ts +22 -6
  118. package/src/runtime/session-resources.ts +25 -25
  119. package/src/runtime/session-snapshot.ts +59 -59
  120. package/src/runtime/session-usage.ts +79 -79
  121. package/src/runtime/sidechain-output.ts +29 -29
  122. package/src/runtime/skill-instructions.ts +222 -0
  123. package/src/runtime/stale-reconciler.ts +4 -14
  124. package/src/runtime/subagent-manager.ts +3 -0
  125. package/src/runtime/supervisor-contact.ts +59 -59
  126. package/src/runtime/task-display.ts +38 -38
  127. package/src/runtime/task-output-context.ts +127 -127
  128. package/src/runtime/task-runner/capabilities.ts +78 -0
  129. package/src/runtime/task-runner/live-executor.ts +105 -101
  130. package/src/runtime/task-runner/progress.ts +119 -119
  131. package/src/runtime/task-runner/prompt-builder.ts +3 -1
  132. package/src/runtime/task-runner/prompt-pipeline.ts +64 -0
  133. package/src/runtime/task-runner/result-utils.ts +14 -14
  134. package/src/runtime/task-runner/state-helpers.ts +22 -22
  135. package/src/runtime/task-runner.ts +44 -5
  136. package/src/runtime/team-runner.ts +78 -15
  137. package/src/runtime/worker-heartbeat.ts +21 -21
  138. package/src/runtime/worker-startup.ts +57 -57
  139. package/src/schema/config-schema.ts +1 -0
  140. package/src/schema/team-tool-schema.ts +3 -3
  141. package/src/state/active-run-registry.ts +165 -0
  142. package/src/state/contracts.ts +1 -1
  143. package/src/state/mailbox.ts +44 -4
  144. package/src/state/state-store.ts +8 -1
  145. package/src/state/task-claims.ts +44 -44
  146. package/src/state/types.ts +44 -2
  147. package/src/state/usage.ts +29 -29
  148. package/src/subagents/async-entry.ts +1 -1
  149. package/src/subagents/index.ts +3 -3
  150. package/src/subagents/live/control.ts +1 -1
  151. package/src/subagents/live/manager.ts +1 -1
  152. package/src/subagents/live/realtime.ts +1 -1
  153. package/src/subagents/live/session-runtime.ts +1 -1
  154. package/src/subagents/manager.ts +1 -1
  155. package/src/subagents/spawn.ts +1 -1
  156. package/src/teams/team-config.ts +1 -0
  157. package/src/teams/team-serializer.ts +38 -38
  158. package/src/types/diff.d.ts +18 -18
  159. package/src/ui/crew-footer.ts +101 -101
  160. package/src/ui/crew-select-list.ts +111 -111
  161. package/src/ui/crew-widget.ts +4 -3
  162. package/src/ui/dashboard-panes/metrics-pane.ts +34 -34
  163. package/src/ui/dashboard-panes/progress-pane.ts +2 -0
  164. package/src/ui/dynamic-border.ts +25 -25
  165. package/src/ui/layout-primitives.ts +106 -106
  166. package/src/ui/loaders.ts +158 -158
  167. package/src/ui/render-diff.ts +119 -119
  168. package/src/ui/render-scheduler.ts +143 -143
  169. package/src/ui/run-snapshot-cache.ts +10 -2
  170. package/src/ui/snapshot-types.ts +2 -0
  171. package/src/ui/spinner.ts +17 -17
  172. package/src/ui/status-colors.ts +58 -58
  173. package/src/ui/syntax-highlight.ts +116 -116
  174. package/src/utils/atomic-write.ts +33 -33
  175. package/src/utils/completion-dedupe.ts +63 -63
  176. package/src/utils/frontmatter.ts +68 -68
  177. package/src/utils/git.ts +262 -262
  178. package/src/utils/ids.ts +12 -12
  179. package/src/utils/names.ts +27 -27
  180. package/src/utils/paths.ts +4 -2
  181. package/src/utils/redaction.ts +44 -44
  182. package/src/utils/safe-paths.ts +47 -47
  183. package/src/utils/sleep.ts +32 -32
  184. package/src/workflows/validate-workflow.ts +40 -40
  185. package/src/workflows/workflow-config.ts +1 -0
  186. package/src/worktree/branch-freshness.ts +45 -45
  187. package/teams/default.team.md +12 -12
  188. package/teams/fast-fix.team.md +11 -11
  189. package/teams/implementation.team.md +18 -18
  190. package/teams/parallel-research.team.md +14 -14
  191. package/teams/research.team.md +11 -11
  192. package/teams/review.team.md +12 -12
  193. package/workflows/default.workflow.md +29 -29
  194. package/workflows/fast-fix.workflow.md +22 -22
  195. package/workflows/implementation.workflow.md +38 -38
  196. package/workflows/parallel-research.workflow.md +46 -46
  197. package/workflows/research.workflow.md +22 -22
  198. package/workflows/review.workflow.md +30 -30
@@ -0,0 +1,67 @@
1
+ ---
2
+ name: systematic-debugging
3
+ description: Use when encountering a bug, test failure, blocked run, provider error, stale state, crash, or unexpected behavior before proposing fixes.
4
+ ---
5
+
6
+ # systematic-debugging
7
+
8
+ Core principle: no fixes without root-cause investigation first. Symptom patches create new bugs and hide the real failure.
9
+
10
+ Distilled from detailed reads of systematic-debugging, root-cause tracing, TDD, and error-analysis skill patterns.
11
+
12
+ ## Four Phases
13
+
14
+ ### 1. Root Cause Investigation
15
+
16
+ Before any fix:
17
+
18
+ - read error messages, stack traces, failing assertions, task status, and logs completely;
19
+ - reproduce narrowly and record the exact command/steps;
20
+ - check recent diffs, commits, config changes, dependency changes, and environment differences;
21
+ - trace data/control flow across component boundaries;
22
+ - add temporary diagnostics only when they answer a specific question.
23
+
24
+ For pi-crew, trace:
25
+
26
+ ```text
27
+ user/tool params → config resolution → team/workflow/agent discovery → model/runtime routing → child args/env → state/events/artifacts → status/UI
28
+ ```
29
+
30
+ ### 2. Pattern Analysis
31
+
32
+ - Find a similar working path in the codebase.
33
+ - Compare working vs broken behavior field-by-field.
34
+ - Identify dependencies: config home, project root markers, env vars, locks, stale caches, provider model capabilities.
35
+ - Do not assume small differences are irrelevant.
36
+
37
+ ### 3. Hypothesis and Test
38
+
39
+ - State one hypothesis: “I think X is the root cause because Y.”
40
+ - Test one variable at a time with the smallest read-only probe or targeted test.
41
+ - If wrong, discard the hypothesis instead of piling on fixes.
42
+ - After three failed fixes, question architecture or assumptions before continuing.
43
+
44
+ ### 4. Implementation
45
+
46
+ - Add or identify a failing regression test when practical.
47
+ - Fix the root cause, not the symptom.
48
+ - Avoid “while I’m here” refactors.
49
+ - Verify targeted behavior, then broader gates.
50
+
51
+ ## Evidence to Collect
52
+
53
+ - failing command and exit code;
54
+ - relevant manifest/tasks/events/mailbox files;
55
+ - effective config paths and redacted config;
56
+ - child Pi args/env after redaction;
57
+ - git diff and recent commits;
58
+ - provider/model/thinking resolution;
59
+ - async timing/race indicators.
60
+
61
+ ## Anti-patterns
62
+
63
+ - Fixing before reproducing.
64
+ - Assuming real user global config cannot pollute tests.
65
+ - Treating provider errors as only transient network failures.
66
+ - Removing guards because they reveal a blocked state.
67
+ - Editing unrelated layers before checking the hypothesis.
@@ -0,0 +1,39 @@
1
+ ---
2
+ name: ui-render-performance
3
+ description: Non-blocking Pi TUI render workflow. Use when changing widgets, powerbar/statusbar segments, dashboard panes, overlays, snapshot caches, or live UI refresh behavior.
4
+ ---
5
+
6
+ # ui-render-performance
7
+
8
+ Use this skill for Pi/pi-crew TUI work.
9
+
10
+ ## Source patterns distilled
11
+
12
+ - Pi TUI is synchronous immediate-mode/string rendering: `source/pi-mono/packages/coding-agent/src/modes/interactive/interactive-mode.ts`
13
+ - Pi extension examples use event-driven state updates, not render-time loading.
14
+ - pi-crew UI: `src/extension/register.ts`, `src/ui/run-dashboard.ts`, `src/ui/run-snapshot-cache.ts`, `src/ui/crew-widget.ts`, `src/ui/powerbar-publisher.ts`, `src/ui/render-scheduler.ts`
15
+
16
+ ## Rules
17
+
18
+ - Treat every `render(width)` and widget/powerbar update as a hot synchronous path.
19
+ - Render from in-memory snapshots only. Preload config, manifests, snapshots, agents, and mailbox counts asynchronously.
20
+ - Use `RenderScheduler.schedule()` to coalesce renders; avoid direct repeated rendering.
21
+ - Prefer `snapshotCache.get(runId)` in render paths. If a sync fallback is unavoidable, classify it as first-load/rare and document why.
22
+ - Keep dashboard panes pure: accept a snapshot/model and format strings; do not call `fs.readFileSync`, `fs.readdirSync`, `fs.statSync`, or network APIs from pane render methods.
23
+ - On session switch, cancel timers and ensure in-flight async preloads cannot update stale session UI.
24
+ - Watch TTL interactions: a preload interval shorter than cache TTL prevents render-time refresh gaps.
25
+
26
+ ## Anti-patterns
27
+
28
+ - Do not call `loadConfig()`, `manifestCache.list()`, or `refreshIfStale()` repeatedly inside `renderTick()` unless backed by preloaded frame data.
29
+ - Do not do large JSON parsing or directory scans inside widget render/update functions.
30
+ - Do not show stale health warnings for completed/cancelled/failed runs.
31
+
32
+ ## Verification
33
+
34
+ ```bash
35
+ cd pi-crew
36
+ npx tsc --noEmit
37
+ node --experimental-strip-types --test test/unit/run-snapshot-cache.test.ts test/unit/crew-widget.test.ts test/unit/powerbar-publisher.test.ts test/unit/run-dashboard.test.ts
38
+ npm test
39
+ ```
@@ -0,0 +1,57 @@
1
+ ---
2
+ name: verification-before-done
3
+ description: Use when about to claim work is complete, fixed, passing, reviewed, committed, or ready to hand off.
4
+ ---
5
+
6
+ # verification-before-done
7
+
8
+ Core principle: evidence before claims. A worker report, green-looking log, or previous run is not fresh verification.
9
+
10
+ Distilled from detailed reads of agent-skill patterns for verification-before-completion, TDD, review reception, and QA workflows.
11
+
12
+ ## Gate Function
13
+
14
+ Before any completion claim:
15
+
16
+ 1. Identify the command or inspection that proves the claim.
17
+ 2. Run the full command fresh, or explicitly state why a command cannot be run.
18
+ 3. Read the output, including exit code and failure counts.
19
+ 4. Compare the output to the claim.
20
+ 5. Report the claim only with the evidence.
21
+
22
+ ## Claim-to-Evidence Table
23
+
24
+ | Claim | Requires | Not sufficient |
25
+ |---|---|---|
26
+ | Tests pass | Fresh test output with zero failures | Prior run, “should pass” |
27
+ | Typecheck passes | Typecheck command exit 0 | Lint or targeted tests only |
28
+ | Bug fixed | Original symptom/regression test passes | Code changed |
29
+ | Requirements met | Checklist against request/plan | Generic test success |
30
+ | Agent completed | Worker output plus artifact/diff/state inspection | Worker says DONE |
31
+ | Safe to commit | Relevant checks pass and status reviewed | Partial local confidence |
32
+
33
+ ## Verification Ladder
34
+
35
+ Choose the smallest reliable gate, then escalate when risk requires it:
36
+
37
+ 1. Read-only inspection for plans/reviews.
38
+ 2. Targeted unit test for touched behavior.
39
+ 3. Typecheck for TypeScript/schema/API changes.
40
+ 4. Integration test for runtime, subprocess, state, filesystem, UI, config, or session behavior.
41
+ 5. Full suite before commit/release or broad changes.
42
+ 6. Real Pi smoke only when safe and needed.
43
+
44
+ ## Done Report
45
+
46
+ Include:
47
+
48
+ - changed files or read-only status;
49
+ - commands run and pass/fail result;
50
+ - artifacts, run IDs, logs, or state paths inspected;
51
+ - behavior actually verified;
52
+ - skipped checks and why;
53
+ - risks and rollback notes.
54
+
55
+ ## Red Flags
56
+
57
+ Stop before saying done if you are using words like “should”, “probably”, “looks”, “seems”, “I think”, or if you are trusting an agent report without checking evidence.
@@ -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
+ ```
@@ -1,34 +1,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("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("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,7 +1,7 @@
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
6
  import { packageRoot, projectCrewRoot, userPiRoot } from "../utils/paths.ts";
7
7
 
@@ -36,9 +36,9 @@ function parseAgentFile(filePath: string, source: ResourceSource): AgentConfig |
36
36
  source,
37
37
  filePath,
38
38
  systemPrompt: body.trim(),
39
- model: frontmatter.model || undefined,
39
+ model: frontmatter.model === "false" ? undefined : frontmatter.model || undefined,
40
40
  fallbackModels: parseCsv(frontmatter.fallbackModels),
41
- thinking: frontmatter.thinking || undefined,
41
+ thinking: frontmatter.thinking === "false" ? undefined : frontmatter.thinking || undefined,
42
42
  tools: parseCsv(frontmatter.tools),
43
43
  extensions: frontmatter.extensions === "" ? [] : parseCsv(frontmatter.extensions),
44
44
  skills: parseCsv(frontmatter.skills ?? frontmatter.skill),
@@ -63,12 +63,12 @@ function readAgentDir(dir: string, source: ResourceSource): AgentConfig[] {
63
63
  .sort((a, b) => a.name.localeCompare(b.name));
64
64
  }
65
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 ?? {};
66
+ function applyAgentOverrides(agents: AgentConfig[], cwd: string, loadedConfig?: LoadedPiTeamsConfig): AgentConfig[] {
67
+ const loaded = loadedConfig ?? loadConfig(cwd);
68
+ const agentsConfig = loaded.config.agents;
69
+ const overrides = agentsConfig?.overrides ?? {};
70
70
  return agents
71
- .filter((agent) => !(config?.disableBuiltins && agent.source === "builtin"))
71
+ .filter((agent) => !(agentsConfig?.disableBuiltins && agent.source === "builtin"))
72
72
  .map((agent) => {
73
73
  const overrideEntry = Object.entries(overrides).find(([name]) => name.toLowerCase() === agent.name.toLowerCase());
74
74
  if (!overrideEntry) return agent;
@@ -87,10 +87,11 @@ function applyAgentOverrides(agents: AgentConfig[], cwd: string): AgentConfig[]
87
87
  }
88
88
 
89
89
  export function discoverAgents(cwd: string): AgentDiscoveryResult {
90
+ const loaded = loadConfig(cwd);
90
91
  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),
92
+ builtin: applyAgentOverrides(readAgentDir(path.join(packageRoot(), "agents"), "builtin"), cwd, loaded),
93
+ user: applyAgentOverrides(readAgentDir(path.join(userPiRoot(), "agents"), "user"), cwd, loaded),
94
+ project: applyAgentOverrides(readAgentDir(path.join(projectCrewRoot(cwd), "agents"), "project"), cwd, loaded),
94
95
  };
95
96
  }
96
97
 
@@ -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,7 @@ export interface CrewRuntimeConfig {
44
45
  groupJoinAckTimeoutMs?: number;
45
46
  requirePlanApproval?: boolean;
46
47
  completionMutationGuard?: CompletionMutationGuardMode;
48
+ effectivenessGuard?: EffectivenessGuardMode;
47
49
  }
48
50
 
49
51
  export interface CrewControlConfig {
@@ -182,6 +184,11 @@ export interface UpdateConfigOptions {
182
184
  }
183
185
 
184
186
  export function configPath(): string {
187
+ const home = process.env.PI_TEAMS_HOME?.trim() || os.homedir();
188
+ return path.join(home, ".pi", "agent", "pi-crew.json");
189
+ }
190
+
191
+ export function legacyConfigPath(): string {
185
192
  const home = process.env.PI_TEAMS_HOME?.trim() || os.homedir();
186
193
  return path.join(home, ".pi", "agent", "extensions", "pi-crew", "config.json");
187
194
  }
@@ -195,7 +202,7 @@ export function projectConfigPath(cwd: string): string {
195
202
  * This is a convenience path alongside the standard `config.json` in crewRoot.
196
203
  */
197
204
  export function projectPiCrewJsonPath(cwd: string): string {
198
- return path.join(cwd, ".pi", "pi-crew.json");
205
+ return path.join(projectPiRoot(cwd), "pi-crew.json");
199
206
  }
200
207
 
201
208
  function withoutUndefined<T extends Record<string, unknown>>(value: T): Partial<T> {
@@ -509,6 +516,7 @@ function parseRuntimeConfig(value: unknown): CrewRuntimeConfig | undefined {
509
516
  groupJoinAckTimeoutMs: parsePositiveInteger(obj.groupJoinAckTimeoutMs, 86_400_000),
510
517
  requirePlanApproval: parseWithSchema(Type.Boolean(), obj.requirePlanApproval),
511
518
  completionMutationGuard: parseWithSchema(Type.Union([Type.Literal("off"), Type.Literal("warn"), Type.Literal("fail")]), obj.completionMutationGuard),
519
+ effectivenessGuard: parseWithSchema(Type.Union([Type.Literal("off"), Type.Literal("warn"), Type.Literal("block"), Type.Literal("fail")]), obj.effectivenessGuard),
512
520
  };
513
521
  return Object.values(runtime).some((entry) => entry !== undefined) ? runtime : undefined;
514
522
  }
@@ -727,35 +735,51 @@ function readConfigRecord(filePath: string): Record<string, unknown> {
727
735
  return raw as Record<string, unknown>;
728
736
  }
729
737
 
738
+ function readOptionalConfig(filePath: string): { exists: boolean; config: PiTeamsConfig; warnings: string[] } {
739
+ if (!fs.existsSync(filePath)) return { exists: false, config: {}, warnings: [] };
740
+ try {
741
+ const raw = readConfigRecord(filePath);
742
+ const parsed = parseConfigWithWarnings(raw);
743
+ return { exists: true, config: parsed.config, warnings: parsed.warnings.map((warning) => `${filePath}: ${warning}`) };
744
+ } catch (error) {
745
+ const message = error instanceof Error ? error.message : String(error);
746
+ return { exists: true, config: {}, warnings: [`${filePath}: invalid config ignored: ${message}`] };
747
+ }
748
+ }
749
+
730
750
  export function loadConfig(cwd?: string): LoadedPiTeamsConfig {
731
751
  const filePath = configPath();
752
+ const legacyPath = legacyConfigPath();
732
753
  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));
754
+ const warnings: string[] = [];
755
+ const legacyConfig = readOptionalConfig(legacyPath);
756
+ if (legacyConfig.exists && legacyPath !== filePath) {
757
+ warnings.push(...legacyConfig.warnings);
758
+ paths.unshift(legacyPath);
759
+ }
760
+ const userConfig = readOptionalConfig(filePath);
761
+ warnings.push(...userConfig.warnings);
762
+ let config = mergeConfig(legacyConfig.exists && legacyPath !== filePath ? legacyConfig.config : {}, userConfig.config);
763
+ if (cwd) {
764
+ const projectPath = projectConfigPath(cwd);
765
+ const projectConfig = readOptionalConfig(projectPath);
766
+ if (projectConfig.exists) {
741
767
  const projectSafeConfig = sanitizeProjectConfig(projectPath, config, projectConfig.config);
742
- warnings.push(...projectConfig.warnings.map((warning) => `${projectPath}: ${warning}`), ...projectSafeConfig.warnings);
768
+ warnings.push(...projectConfig.warnings, ...projectSafeConfig.warnings);
743
769
  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
770
  }
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 };
771
+ // `.pi/pi-crew.json` is the project-owned override file. If present and valid,
772
+ // it may override all pi-crew config fields, including agents.overrides.
773
+ // If missing or invalid, it is ignored and defaults/user config remain effective.
774
+ const piCrewJsonPath = projectPiCrewJsonPath(cwd);
775
+ const piCrewJsonConfig = readOptionalConfig(piCrewJsonPath);
776
+ if (piCrewJsonConfig.exists) {
777
+ warnings.push(...piCrewJsonConfig.warnings);
778
+ config = mergeConfig(config, piCrewJsonConfig.config);
779
+ paths.push(piCrewJsonPath);
780
+ }
758
781
  }
782
+ return { path: filePath, paths, config, warnings: warnings.length > 0 ? warnings : undefined };
759
783
  }
760
784
 
761
785
  export function updateConfig(patch: PiTeamsConfig, options: UpdateConfigOptions = {}): SavedPiTeamsConfig {
@@ -53,6 +53,20 @@ export const DEFAULT_UI = {
53
53
  refreshMs: 1000,
54
54
  notifierIntervalMs: 5000,
55
55
  widgetDefaultFrameMs: 1000,
56
+ widgetPlacement: "aboveEditor" as const,
57
+ widgetMaxLines: 8,
58
+ powerbar: true,
59
+ dashboardPlacement: "center" as const,
60
+ dashboardWidth: 72,
61
+ dashboardLiveRefreshMs: 1000,
62
+ autoOpenDashboard: false,
63
+ autoOpenDashboardForForegroundRuns: false,
64
+ showModel: true,
65
+ showTokens: true,
66
+ showTools: true,
67
+ transcriptTailBytes: 1024 * 1024,
68
+ mascotStyle: "cat" as const,
69
+ mascotEffect: "random" as const,
56
70
  };
57
71
 
58
72
  export const DEFAULT_NOTIFICATIONS = {
@@ -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
+ }