pi-crew 0.6.0 → 0.6.3

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 (135) hide show
  1. package/CHANGELOG.md +225 -0
  2. package/README.md +70 -31
  3. package/docs/issue-29-analysis.md +189 -0
  4. package/docs/superpowers/plans/2026-06-09-fallow-patterns-adoption.md +1268 -0
  5. package/package.json +2 -2
  6. package/src/agents/agent-config.ts +2 -1
  7. package/src/benchmark/feedback-loop.ts +4 -2
  8. package/src/config/config.ts +106 -15
  9. package/src/errors.ts +107 -0
  10. package/src/extension/async-notifier.ts +6 -2
  11. package/src/extension/crew-cleanup.ts +8 -5
  12. package/src/extension/cross-extension-rpc.ts +48 -0
  13. package/src/extension/management.ts +464 -109
  14. package/src/extension/register.ts +194 -34
  15. package/src/extension/registration/commands.ts +4 -3
  16. package/src/extension/registration/subagent-helpers.ts +2 -2
  17. package/src/extension/registration/subagent-tools.ts +3 -1
  18. package/src/extension/registration/team-tool.ts +3 -1
  19. package/src/extension/registration/viewers.ts +3 -2
  20. package/src/extension/run-export.ts +16 -1
  21. package/src/extension/run-import.ts +16 -0
  22. package/src/extension/team-tool/anchor.ts +5 -1
  23. package/src/extension/team-tool/api.ts +12 -7
  24. package/src/extension/team-tool/cancel.ts +3 -3
  25. package/src/extension/team-tool/config-patch.ts +15 -1
  26. package/src/extension/team-tool/explain.ts +1 -1
  27. package/src/extension/team-tool/handle-schedule.ts +40 -0
  28. package/src/extension/team-tool/inspect.ts +3 -3
  29. package/src/extension/team-tool/lifecycle-actions.ts +4 -4
  30. package/src/extension/team-tool/respond.ts +2 -2
  31. package/src/extension/team-tool/run.ts +60 -11
  32. package/src/extension/team-tool/status.ts +1 -1
  33. package/src/extension/team-tool.ts +175 -47
  34. package/src/hooks/registry.ts +84 -12
  35. package/src/hooks/types.ts +12 -3
  36. package/src/i18n.ts +15 -2
  37. package/src/observability/exporters/otlp-exporter.ts +73 -0
  38. package/src/plugins/plugin-define.ts +6 -0
  39. package/src/plugins/plugin-registry.ts +32 -0
  40. package/src/plugins/plugins/index.ts +3 -0
  41. package/src/plugins/plugins/nextjs.ts +19 -0
  42. package/src/plugins/plugins/vite.ts +14 -0
  43. package/src/plugins/plugins/vitest.ts +14 -0
  44. package/src/runtime/adaptive-plan.ts +24 -0
  45. package/src/runtime/agent-control.ts +6 -3
  46. package/src/runtime/async-runner.ts +86 -3
  47. package/src/runtime/background-runner.ts +529 -144
  48. package/src/runtime/chain-parser.ts +5 -1
  49. package/src/runtime/chain-runner.ts +67 -3
  50. package/src/runtime/checkpoint.ts +262 -180
  51. package/src/runtime/child-pi.ts +165 -38
  52. package/src/runtime/crash-recovery.ts +25 -14
  53. package/src/runtime/crew-agent-records.ts +6 -3
  54. package/src/runtime/cross-extension-rpc.ts +34 -8
  55. package/src/runtime/diagnostic-export.ts +4 -5
  56. package/src/runtime/dynamic-script-runner.ts +9 -6
  57. package/src/runtime/foreground-watchdog.ts +3 -3
  58. package/src/runtime/heartbeat-watcher.ts +19 -2
  59. package/src/runtime/intercom-bridge.ts +7 -0
  60. package/src/runtime/iteration-hooks.ts +1 -1
  61. package/src/runtime/live-agent-manager.ts +6 -3
  62. package/src/runtime/live-irc.ts +4 -2
  63. package/src/runtime/manifest-cache.ts +4 -0
  64. package/src/runtime/orphan-worker-registry.ts +444 -0
  65. package/src/runtime/parallel-utils.ts +2 -1
  66. package/src/runtime/parent-guard.ts +70 -16
  67. package/src/runtime/pi-args.ts +437 -14
  68. package/src/runtime/pi-spawn.ts +1 -0
  69. package/src/runtime/post-checks.ts +11 -4
  70. package/src/runtime/retry-runner.ts +9 -3
  71. package/src/runtime/{drift-detectors.ts → run-drift.ts} +1 -1
  72. package/src/runtime/run-tracker.ts +38 -10
  73. package/src/runtime/sandbox.ts +42 -21
  74. package/src/runtime/scheduler.ts +25 -2
  75. package/src/runtime/semaphore.ts +2 -1
  76. package/src/runtime/settings-store.ts +14 -2
  77. package/src/runtime/skill-effectiveness.ts +109 -64
  78. package/src/runtime/skill-instructions.ts +114 -33
  79. package/src/runtime/stale-reconciler.ts +310 -69
  80. package/src/runtime/subagent-manager.ts +265 -53
  81. package/src/runtime/subprocess-tool-registry.ts +2 -2
  82. package/src/runtime/task-health.ts +76 -0
  83. package/src/runtime/task-id.ts +20 -0
  84. package/src/runtime/task-packet.ts +13 -1
  85. package/src/runtime/task-runner/live-executor.ts +1 -1
  86. package/src/runtime/task-runner/progress.ts +1 -1
  87. package/src/runtime/task-runner/state-helpers.ts +110 -6
  88. package/src/runtime/task-runner.ts +98 -67
  89. package/src/runtime/team-runner.ts +186 -20
  90. package/src/runtime/usage-tracker.ts +4 -2
  91. package/src/runtime/verification-gates.ts +36 -9
  92. package/src/schema/team-tool-schema.ts +27 -9
  93. package/src/skills/discover-skills.ts +9 -3
  94. package/src/state/active-run-registry.ts +170 -38
  95. package/src/state/artifact-store.ts +25 -13
  96. package/src/state/atomic-write-v2.ts +86 -0
  97. package/src/state/atomic-write.ts +346 -55
  98. package/src/state/blob-store.ts +178 -10
  99. package/src/state/contracts.ts +2 -1
  100. package/src/state/crew-init.ts +161 -28
  101. package/src/state/decision-ledger.ts +172 -111
  102. package/src/state/event-log-rotation.ts +82 -52
  103. package/src/state/event-log.ts +270 -75
  104. package/src/state/health-store.ts +71 -0
  105. package/src/state/hook-instinct-bridge.ts +2 -1
  106. package/src/state/locks.ts +109 -20
  107. package/src/state/mailbox.ts +45 -7
  108. package/src/state/observation-store.ts +4 -1
  109. package/src/state/run-graph.ts +141 -130
  110. package/src/state/run-metrics.ts +24 -8
  111. package/src/state/state-store.ts +333 -44
  112. package/src/state/task-claims.ts +9 -2
  113. package/src/tools/safe-bash.ts +69 -20
  114. package/src/types/new-api-types.ts +10 -5
  115. package/src/ui/keybinding-map.ts +2 -1
  116. package/src/ui/live-run-sidebar.ts +1 -1
  117. package/src/ui/loaders.ts +4 -0
  118. package/src/ui/overlays/agent-picker-overlay.ts +1 -1
  119. package/src/ui/overlays/mailbox-detail-overlay.ts +1 -1
  120. package/src/ui/run-action-dispatcher.ts +4 -3
  121. package/src/ui/run-snapshot-cache.ts +1 -1
  122. package/src/ui/status-colors.ts +2 -1
  123. package/src/ui/syntax-highlight.ts +2 -1
  124. package/src/ui/tool-render.ts +13 -3
  125. package/src/utils/env-filter.ts +66 -0
  126. package/src/utils/file-coalescer.ts +9 -1
  127. package/src/utils/fs-watch.ts +4 -2
  128. package/src/utils/gh-protocol.ts +2 -1
  129. package/src/utils/paths.ts +80 -5
  130. package/src/utils/redaction.ts +6 -1
  131. package/src/utils/safe-paths.ts +287 -10
  132. package/src/worktree/cleanup.ts +117 -26
  133. package/src/worktree/worktree-manager.ts +128 -15
  134. package/test-bugs-all.mjs +10 -6
  135. package/test-integration-check.ts +4 -0
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Simplifies plugin struct definition.
3
+ */
4
+ export function definePlugin<T extends { name: string; enablers: readonly string[] }>(spec: T): T {
5
+ return spec;
6
+ }
@@ -0,0 +1,32 @@
1
+ export interface Plugin {
2
+ readonly name: string;
3
+ readonly enablers: readonly string[];
4
+ readonly entryPatterns?: readonly string[];
5
+ readonly configPatterns?: readonly string[];
6
+ readonly toolingDependencies?: readonly string[];
7
+ readonly pathAliases?: readonly string[][];
8
+ readonly virtualModulePrefixes?: readonly string[];
9
+ }
10
+
11
+ export class PluginRegistry {
12
+ private plugins: Plugin[] = [];
13
+
14
+ register(plugin: Plugin): void {
15
+ this.plugins.push(plugin);
16
+ }
17
+
18
+ activePlugins(allDeps: string[]): Plugin[] {
19
+ return this.plugins.filter((p) =>
20
+ p.enablers.some((enabler) => {
21
+ if (enabler.endsWith("/")) {
22
+ return allDeps.some((d) => d.startsWith(enabler));
23
+ }
24
+ return allDeps.includes(enabler);
25
+ }),
26
+ );
27
+ }
28
+
29
+ allPlugins(): Plugin[] {
30
+ return [...this.plugins];
31
+ }
32
+ }
@@ -0,0 +1,3 @@
1
+ export { NextJsPlugin } from "./nextjs.ts";
2
+ export { VitestPlugin } from "./vitest.ts";
3
+ export { VitePlugin } from "./vite.ts";
@@ -0,0 +1,19 @@
1
+ import { definePlugin } from "../plugin-define.ts";
2
+
3
+ export const NextJsPlugin = definePlugin({
4
+ name: "nextjs",
5
+ enablers: ["next"],
6
+ entryPatterns: [
7
+ "src/app/**/*.{ts,tsx}",
8
+ "src/pages/**/*.{ts,tsx}",
9
+ "src/app/**/page.{ts,tsx}",
10
+ "src/app/**/layout.{ts,tsx}",
11
+ "src/app/**/route.{ts,tsx}",
12
+ "middleware.{ts,js}",
13
+ "next.config.{ts,js,mjs}",
14
+ ],
15
+ configPatterns: ["next.config.{ts,js,mjs}"],
16
+ toolingDependencies: ["next", "@next/font", "@next/mdx"],
17
+ pathAliases: [["~", "src"]],
18
+ virtualModulePrefixes: ["next:"],
19
+ });
@@ -0,0 +1,14 @@
1
+ import { definePlugin } from "../plugin-define.ts";
2
+
3
+ export const VitePlugin = definePlugin({
4
+ name: "vite",
5
+ enablers: ["vite", "rolldown-vite"],
6
+ entryPatterns: [
7
+ "src/main.{ts,tsx,js,jsx}",
8
+ "src/index.{ts,tsx,js,jsx}",
9
+ "index.html",
10
+ ],
11
+ configPatterns: ["vite.config.{ts,js,mts,mjs}"],
12
+ toolingDependencies: ["vite"],
13
+ virtualModulePrefixes: ["virtual:"],
14
+ });
@@ -0,0 +1,14 @@
1
+ import { definePlugin } from "../plugin-define.ts";
2
+
3
+ export const VitestPlugin = definePlugin({
4
+ name: "vitest",
5
+ enablers: ["vitest"],
6
+ entryPatterns: [
7
+ "**/*.test.{ts,tsx}",
8
+ "**/*.spec.{ts,tsx}",
9
+ "src/**/*.test.{ts,tsx}",
10
+ "src/**/*.spec.{ts,tsx}",
11
+ ],
12
+ configPatterns: ["vitest.config.{ts,js,mjs}", "vite.config.ts"],
13
+ toolingDependencies: ["vitest"],
14
+ });
@@ -22,6 +22,7 @@ import type { TeamConfig } from "../teams/team-config.ts";
22
22
  import type { WorkflowConfig, WorkflowStep } from "../workflows/workflow-config.ts";
23
23
  import type { TeamRunManifest, TeamTaskState } from "../state/types.ts";
24
24
  import { refreshTaskGraphQueues } from "./task-graph-scheduler.ts";
25
+ import { getPlanTemplate, renderPlanTemplate, listPlanTemplates } from "./plan-templates.ts";
25
26
 
26
27
  export interface AdaptivePlanTask {
27
28
  role: string;
@@ -70,6 +71,29 @@ export function extractAdaptivePlanJson(text: string): string | undefined {
70
71
  }
71
72
 
72
73
  export function parseAdaptivePlan(text: string, allowedRoles: string[]): AdaptivePlan | undefined {
74
+ // Check if the text is a plan template reference: "template:<name>" with optional JSON variables
75
+ const templateRefMatch = text.match(/^template:([a-zA-Z0-9_-]+)/);
76
+ if (templateRefMatch?.[1]) {
77
+ const template = getPlanTemplate(templateRefMatch[1]);
78
+ if (template) {
79
+ // Try to extract variables from the remaining text
80
+ let variables: Record<string, string> = {};
81
+ try {
82
+ const varsJson = text.slice(templateRefMatch[0].length).trim();
83
+ if (varsJson) variables = JSON.parse(varsJson);
84
+ } catch { /* use empty variables */ }
85
+ const rendered = renderPlanTemplate(templateRefMatch[1], variables);
86
+ if (rendered) {
87
+ // Convert RenderedPlan → AdaptivePlan
88
+ const phases: AdaptivePlanPhase[] = rendered.phases.map(phase => ({
89
+ name: phase.name,
90
+ tasks: [{ role: phase.role, task: phase.task }],
91
+ }));
92
+ return phases.length ? { phases } : undefined;
93
+ }
94
+ }
95
+ }
96
+
73
97
  const raw = extractAdaptivePlanJson(text);
74
98
  if (!raw) return undefined;
75
99
  let parsed: unknown;
@@ -69,7 +69,8 @@ export function applyAttentionState(manifest: TeamRunManifest, agent: CrewAgentR
69
69
  return updated;
70
70
  }
71
71
 
72
- export function applyLongRunningCheck(
72
+ /** @internal */
73
+ function applyLongRunningCheck(
73
74
  manifest: TeamRunManifest,
74
75
  agent: CrewAgentRecord,
75
76
  config: CrewControlConfig,
@@ -105,7 +106,8 @@ export function applyLongRunningCheck(
105
106
  return updated;
106
107
  }
107
108
 
108
- export function trackConsecutiveToolFailure(
109
+ /** @internal */
110
+ function trackConsecutiveToolFailure(
109
111
  manifest: TeamRunManifest,
110
112
  agent: CrewAgentRecord,
111
113
  toolName: string,
@@ -140,7 +142,8 @@ export function trackConsecutiveToolFailure(
140
142
  return updated;
141
143
  }
142
144
 
143
- export function resetConsecutiveToolFailures(
145
+ /** @internal */
146
+ function resetConsecutiveToolFailures(
144
147
  manifest: TeamRunManifest,
145
148
  agent: CrewAgentRecord,
146
149
  ): void {
@@ -5,6 +5,11 @@ import * as path from "node:path";
5
5
  import { fileURLToPath, pathToFileURL } from "node:url";
6
6
  import { logInternalError } from "../utils/internal-error.ts";
7
7
  import { appendEvent } from "../state/event-log.ts";
8
+ import { sanitizeEnvSecrets } from "../utils/env-filter.ts";
9
+ import {
10
+ registerWorker,
11
+ unregisterWorker,
12
+ } from "./orphan-worker-registry.ts";
8
13
  import type { TeamRunManifest } from "../state/types.ts";
9
14
 
10
15
 
@@ -131,8 +136,71 @@ export async function spawnBackgroundTeamRun(manifest: TeamRunManifest): Promise
131
136
  const logPath = path.join(manifest.stateRoot, "background.log");
132
137
  fs.mkdirSync(manifest.stateRoot, { recursive: true });
133
138
 
134
- // NOTE: Do NOT set PI_CREW_PARENT_PID for the background runner.
135
- const { PI_CREW_PARENT_PID: _, ...envWithoutParentPid } = process.env;
139
+ // SECURITY FIX: Use sanitizeEnvSecrets with same allow-list as child-pi.ts
140
+ // to prevent leaking all env vars (including secrets) to detached background runner.
141
+ // Previously, destructuring only removed PI_CREW_PARENT_PID but kept everything else.
142
+ const filteredEnv = sanitizeEnvSecrets(process.env, {
143
+ allowList: [
144
+ // Model provider API keys (same as child-pi.ts)
145
+ "MINIMAX_API_KEY",
146
+ "MINIMAX_GROUP_ID",
147
+ "OPENAI_API_KEY",
148
+ "OPENAI_ORG_ID",
149
+ "ANTHROPIC_API_KEY",
150
+ "GOOGLE_API_KEY",
151
+ "GOOGLE_GENERATIVE_LANGUAGE_API_KEY",
152
+ "AZURE_OPENAI_API_KEY",
153
+ "AZURE_OPENAI_ENDPOINT",
154
+ "AWS_ACCESS_KEY_ID",
155
+ "AWS_SECRET_ACCESS_KEY",
156
+ "AWS_REGION",
157
+ "ZEU_API_KEY",
158
+ "ZERODEV_API_KEY",
159
+ // Essential non-secret vars
160
+ "PATH",
161
+ "HOME",
162
+ "USER",
163
+ "SHELL",
164
+ "TERM",
165
+ "LANG",
166
+ "LC_ALL",
167
+ "LC_COLLATE",
168
+ "LC_CTYPE",
169
+ "LC_MESSAGES",
170
+ "LC_MONETARY",
171
+ "LC_NUMERIC",
172
+ "LC_TIME",
173
+ "XDG_CONFIG_HOME",
174
+ "XDG_DATA_HOME",
175
+ "XDG_CACHE_HOME",
176
+ "XDG_RUNTIME_DIR",
177
+ "NVM_BIN",
178
+ "NVM_DIR",
179
+ "NVM_INC",
180
+ "NODE_PATH",
181
+ "NODE_DISABLE_COLORS",
182
+ "NODE_EXTRA_CA_CERTS",
183
+ "NPM_CONFIG_REGISTRY",
184
+ "NPM_CONFIG_USERCONFIG",
185
+ "NPM_CONFIG_GLOBALCONFIG",
186
+ // FIX: explicit list matches child-pi.ts to prevent regression.
187
+ // PI_CREW_PARENT_PID is needed for parent-guard (liveness check).
188
+ "PI_CREW_DEPTH",
189
+ "PI_CREW_MAX_DEPTH",
190
+ "PI_CREW_INHERIT_PROJECT_CONTEXT",
191
+ "PI_CREW_INHERIT_SKILLS",
192
+ "PI_CREW_PARENT_PID",
193
+ "PI_TEAMS_DEPTH",
194
+ "PI_TEAMS_MAX_DEPTH",
195
+ "PI_TEAMS_INHERIT_PROJECT_CONTEXT",
196
+ "PI_TEAMS_INHERIT_SKILLS",
197
+ "PI_TEAMS_PI_BIN",
198
+ "PI_TEAMS_MOCK_CHILD_PI",
199
+ "PI_CREW_ALLOW_MOCK",
200
+ ],
201
+ });
202
+ // FIX: removed delete workarounds — with explicit allowlist, these vars
203
+ // are no longer auto-leaked. Matches child-pi.ts.
136
204
 
137
205
  const loader = resolveTypeScriptLoader();
138
206
  if (!loader) {
@@ -159,7 +227,7 @@ export async function spawnBackgroundTeamRun(manifest: TeamRunManifest): Promise
159
227
  detached: true,
160
228
  setsid: true,
161
229
  stdio: ["ignore", "pipe", "pipe"],
162
- env: envWithoutParentPid,
230
+ env: filteredEnv,
163
231
  windowsHide: true,
164
232
  } as unknown as Parameters<typeof spawn>[2];
165
233
  const child = spawn(process.execPath, command.args, spawnOpts);
@@ -168,6 +236,21 @@ export async function spawnBackgroundTeamRun(manifest: TeamRunManifest): Promise
168
236
  });
169
237
  child.unref();
170
238
 
239
+ // Track this worker in the orphan registry so it can be killed on
240
+ // session_start of a future session if the parent pi process is killed.
241
+ if (child.pid) {
242
+ registerWorker(
243
+ child.pid,
244
+ manifest.ownerSessionId ?? "unknown",
245
+ manifest.runId,
246
+ process.pid, // parentPid — used by cleanup to verify session is dead
247
+ );
248
+ // Best-effort: unregister when child exits. Background-runner writes
249
+ // the marker file before it dies, so we unregister on the next
250
+ // cleanup tick. But the child "exit" event won't fire because we
251
+ // unref'd and the stdio is piped + ignored.
252
+ }
253
+
171
254
  return { pid: child.pid, logPath };
172
255
  }
173
256