pi-crew 0.1.46 → 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 (253) hide show
  1. package/CHANGELOG.md +97 -0
  2. package/agents/analyst.md +11 -11
  3. package/agents/critic.md +11 -11
  4. package/agents/executor.md +11 -11
  5. package/agents/explorer.md +11 -11
  6. package/agents/planner.md +11 -11
  7. package/agents/reviewer.md +11 -11
  8. package/agents/security-reviewer.md +11 -11
  9. package/agents/test-engineer.md +11 -11
  10. package/agents/verifier.md +11 -11
  11. package/agents/writer.md +11 -11
  12. package/docs/next-upgrade-roadmap.md +117 -42
  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/AGENT-EXECUTION-ARCHITECTURE.md +261 -0
  18. package/docs/research/AGENT-LIFECYCLE-COMPARISON.md +111 -0
  19. package/docs/research/AUDIT_OH_MY_PI.md +261 -0
  20. package/docs/research/AUDIT_PI_CREW.md +457 -0
  21. package/docs/research/CAVEMAN-DEEP-RESEARCH.md +281 -0
  22. package/docs/research/COMPARISON_OH_MY_PI_VS_PI_CREW.md +264 -0
  23. package/docs/research/DEEP-RESEARCH-PI-POWERBAR.md +343 -0
  24. package/docs/research/DEEP_RESEARCH_SUBAGENT_ARCHITECTURE.md +480 -0
  25. package/docs/research/GAP_CLOSURE_IMPLEMENTATION_PLAN.md +354 -0
  26. package/docs/research/IMPLEMENTATION_PLAN.md +385 -0
  27. package/docs/research/LIVE-SESSION-PRODUCTION-READY-PLAN.md +502 -0
  28. package/docs/research/OH-MY-PI-DEEP-RESEARCH-v14.7.6.md +266 -0
  29. package/docs/research/REMAINING-GAPS-PLAN.md +363 -0
  30. package/docs/research/SESSION-SUMMARY-2026-05-08.md +146 -0
  31. package/docs/research/UI-RESPONSIVENESS-AUDIT.md +173 -0
  32. package/docs/research-awesome-agent-skills-distillation.md +100 -100
  33. package/docs/research-extension-examples.md +297 -297
  34. package/docs/research-extension-system.md +324 -324
  35. package/docs/research-oh-my-pi-distillation.md +56 -9
  36. package/docs/research-optimization-plan.md +548 -548
  37. package/docs/research-phase10-distillation.md +198 -198
  38. package/docs/research-phase11-distillation.md +201 -201
  39. package/docs/research-pi-coding-agent.md +357 -357
  40. package/docs/research-source-pi-crew-reference.md +174 -174
  41. package/docs/runtime-flow.md +148 -148
  42. package/docs/source-runtime-refactor-map.md +107 -107
  43. package/index.ts +6 -6
  44. package/package.json +99 -98
  45. package/schema.json +8 -0
  46. package/skills/async-worker-recovery/SKILL.md +42 -42
  47. package/skills/context-artifact-hygiene/SKILL.md +52 -52
  48. package/skills/delegation-patterns/SKILL.md +54 -54
  49. package/skills/mailbox-interactive/SKILL.md +40 -40
  50. package/skills/model-routing-context/SKILL.md +39 -39
  51. package/skills/multi-perspective-review/SKILL.md +58 -58
  52. package/skills/observability-reliability/SKILL.md +41 -41
  53. package/skills/orchestration/SKILL.md +157 -0
  54. package/skills/ownership-session-security/SKILL.md +41 -41
  55. package/skills/pi-extension-lifecycle/SKILL.md +39 -39
  56. package/skills/requirements-to-task-packet/SKILL.md +63 -63
  57. package/skills/resource-discovery-config/SKILL.md +41 -41
  58. package/skills/runtime-state-reader/SKILL.md +44 -44
  59. package/skills/secure-agent-orchestration-review/SKILL.md +45 -45
  60. package/skills/state-mutation-locking/SKILL.md +42 -42
  61. package/skills/systematic-debugging/SKILL.md +67 -67
  62. package/skills/ui-render-performance/SKILL.md +39 -39
  63. package/skills/verification-before-done/SKILL.md +57 -57
  64. package/skills/worktree-isolation/SKILL.md +39 -39
  65. package/src/agents/agent-config.ts +6 -0
  66. package/src/agents/agent-search.ts +98 -0
  67. package/src/agents/agent-serializer.ts +4 -0
  68. package/src/agents/discover-agents.ts +17 -4
  69. package/src/config/config.ts +24 -0
  70. package/src/config/defaults.ts +11 -0
  71. package/src/extension/autonomous-policy.ts +26 -33
  72. package/src/extension/cross-extension-rpc.ts +82 -82
  73. package/src/extension/help.ts +1 -0
  74. package/src/extension/management.ts +5 -0
  75. package/src/extension/register.ts +58 -13
  76. package/src/extension/registration/commands.ts +33 -1
  77. package/src/extension/registration/compaction-guard.ts +125 -125
  78. package/src/extension/registration/team-tool.ts +6 -4
  79. package/src/extension/run-bundle-schema.ts +89 -89
  80. package/src/extension/run-index.ts +24 -18
  81. package/src/extension/run-maintenance.ts +68 -62
  82. package/src/extension/team-tool/api.ts +23 -2
  83. package/src/extension/team-tool/cancel.ts +86 -11
  84. package/src/extension/team-tool/context.ts +3 -0
  85. package/src/extension/team-tool/handle-settings.ts +188 -188
  86. package/src/extension/team-tool/inspect.ts +41 -41
  87. package/src/extension/team-tool/intent-policy.ts +42 -0
  88. package/src/extension/team-tool/lifecycle-actions.ts +47 -18
  89. package/src/extension/team-tool/parallel-dispatch.ts +156 -0
  90. package/src/extension/team-tool/plan.ts +19 -19
  91. package/src/extension/team-tool/respond.ts +10 -2
  92. package/src/extension/team-tool/run.ts +3 -2
  93. package/src/extension/team-tool/status.ts +1 -1
  94. package/src/extension/team-tool-types.ts +1 -0
  95. package/src/extension/team-tool.ts +13 -3
  96. package/src/hooks/registry.ts +61 -0
  97. package/src/hooks/types.ts +41 -0
  98. package/src/i18n.ts +184 -184
  99. package/src/observability/exporters/otlp-exporter.ts +77 -77
  100. package/src/prompt/prompt-runtime.ts +72 -72
  101. package/src/runtime/agent-control.ts +108 -2
  102. package/src/runtime/agent-memory.ts +72 -72
  103. package/src/runtime/agent-observability.ts +114 -114
  104. package/src/runtime/async-marker.ts +26 -26
  105. package/src/runtime/async-runner.ts +3 -1
  106. package/src/runtime/attention-events.ts +28 -28
  107. package/src/runtime/background-runner.ts +19 -0
  108. package/src/runtime/cancellation-token.ts +89 -0
  109. package/src/runtime/cancellation.ts +61 -51
  110. package/src/runtime/capability-inventory.ts +116 -0
  111. package/src/runtime/child-pi.ts +2 -1
  112. package/src/runtime/code-summary.ts +247 -0
  113. package/src/runtime/completion-guard.ts +190 -190
  114. package/src/runtime/crash-recovery.ts +181 -0
  115. package/src/runtime/crew-agent-records.ts +35 -7
  116. package/src/runtime/crew-agent-runtime.ts +1 -0
  117. package/src/runtime/custom-tools/irc-tool.ts +201 -0
  118. package/src/runtime/custom-tools/submit-result-tool.ts +90 -0
  119. package/src/runtime/delivery-coordinator.ts +3 -1
  120. package/src/runtime/direct-run.ts +35 -35
  121. package/src/runtime/effectiveness.ts +81 -76
  122. package/src/runtime/event-stream-bridge.ts +90 -0
  123. package/src/runtime/foreground-control.ts +82 -82
  124. package/src/runtime/green-contract.ts +46 -46
  125. package/src/runtime/group-join.ts +106 -106
  126. package/src/runtime/heartbeat-gradient.ts +28 -28
  127. package/src/runtime/heartbeat-watcher.ts +124 -124
  128. package/src/runtime/live-agent-control.ts +88 -88
  129. package/src/runtime/live-agent-manager.ts +78 -2
  130. package/src/runtime/live-control-realtime.ts +36 -36
  131. package/src/runtime/live-extension-bridge.ts +150 -0
  132. package/src/runtime/live-irc.ts +92 -0
  133. package/src/runtime/live-session-health.ts +100 -0
  134. package/src/runtime/live-session-runtime.ts +297 -7
  135. package/src/runtime/mcp-proxy.ts +113 -0
  136. package/src/runtime/notebook-helpers.ts +90 -0
  137. package/src/runtime/orphan-sentinel.ts +7 -0
  138. package/src/runtime/output-validator.ts +187 -0
  139. package/src/runtime/parallel-research.ts +44 -44
  140. package/src/runtime/parallel-utils.ts +57 -0
  141. package/src/runtime/parent-guard.ts +80 -0
  142. package/src/runtime/pi-json-output.ts +111 -111
  143. package/src/runtime/policy-engine.ts +79 -79
  144. package/src/runtime/progress-event-coalescer.ts +43 -43
  145. package/src/runtime/prose-compressor.ts +164 -0
  146. package/src/runtime/recovery-recipes.ts +74 -74
  147. package/src/runtime/result-extractor.ts +121 -0
  148. package/src/runtime/role-permission.ts +39 -39
  149. package/src/runtime/runtime-resolver.ts +1 -4
  150. package/src/runtime/semaphore.ts +131 -0
  151. package/src/runtime/sensitive-paths.ts +92 -0
  152. package/src/runtime/session-resources.ts +25 -25
  153. package/src/runtime/session-snapshot.ts +59 -59
  154. package/src/runtime/session-usage.ts +79 -79
  155. package/src/runtime/sidechain-output.ts +29 -29
  156. package/src/runtime/stream-preview.ts +177 -0
  157. package/src/runtime/subagent-manager.ts +3 -2
  158. package/src/runtime/subprocess-tool-registry.ts +67 -0
  159. package/src/runtime/supervisor-contact.ts +59 -59
  160. package/src/runtime/task-display.ts +38 -38
  161. package/src/runtime/task-output-context.ts +59 -9
  162. package/src/runtime/task-runner/capabilities.ts +78 -78
  163. package/src/runtime/task-runner/live-executor.ts +2 -0
  164. package/src/runtime/task-runner/progress.ts +119 -119
  165. package/src/runtime/task-runner/prompt-builder.ts +70 -8
  166. package/src/runtime/task-runner/prompt-pipeline.ts +64 -64
  167. package/src/runtime/task-runner/result-utils.ts +14 -14
  168. package/src/runtime/task-runner/run-projection.ts +104 -0
  169. package/src/runtime/task-runner/state-helpers.ts +22 -22
  170. package/src/runtime/task-runner.ts +75 -4
  171. package/src/runtime/team-runner.ts +60 -8
  172. package/src/runtime/worker-heartbeat.ts +21 -21
  173. package/src/runtime/worker-startup.ts +57 -57
  174. package/src/runtime/workspace-tree.ts +298 -0
  175. package/src/runtime/yield-handler.ts +189 -0
  176. package/src/schema/config-schema.ts +6 -0
  177. package/src/schema/team-tool-schema.ts +11 -1
  178. package/src/skills/discover-skills.ts +67 -0
  179. package/src/state/active-run-registry.ts +4 -2
  180. package/src/state/artifact-store.ts +4 -1
  181. package/src/state/atomic-write.ts +50 -1
  182. package/src/state/blob-store.ts +117 -0
  183. package/src/state/contracts.ts +1 -0
  184. package/src/state/event-log-rotation.ts +158 -0
  185. package/src/state/event-log.ts +52 -2
  186. package/src/state/mailbox.ts +87 -7
  187. package/src/state/state-store.ts +24 -4
  188. package/src/state/task-claims.ts +44 -44
  189. package/src/state/types.ts +20 -0
  190. package/src/state/usage.ts +29 -29
  191. package/src/subagents/async-entry.ts +1 -1
  192. package/src/subagents/index.ts +3 -3
  193. package/src/subagents/live/control.ts +1 -1
  194. package/src/subagents/live/manager.ts +1 -1
  195. package/src/subagents/live/realtime.ts +1 -1
  196. package/src/subagents/live/session-runtime.ts +1 -1
  197. package/src/subagents/manager.ts +1 -1
  198. package/src/subagents/spawn.ts +1 -1
  199. package/src/teams/team-serializer.ts +38 -38
  200. package/src/types/diff.d.ts +18 -18
  201. package/src/ui/agent-management-overlay.ts +144 -0
  202. package/src/ui/crew-footer.ts +101 -101
  203. package/src/ui/crew-select-list.ts +111 -111
  204. package/src/ui/crew-widget.ts +11 -2
  205. package/src/ui/dashboard-panes/cancellation-pane.ts +43 -0
  206. package/src/ui/dashboard-panes/capability-pane.ts +60 -0
  207. package/src/ui/dashboard-panes/mailbox-pane.ts +35 -11
  208. package/src/ui/dashboard-panes/metrics-pane.ts +34 -34
  209. package/src/ui/dynamic-border.ts +25 -25
  210. package/src/ui/layout-primitives.ts +106 -106
  211. package/src/ui/live-run-sidebar.ts +4 -0
  212. package/src/ui/loaders.ts +158 -158
  213. package/src/ui/powerbar-publisher.ts +77 -15
  214. package/src/ui/render-coalescer.ts +51 -0
  215. package/src/ui/render-diff.ts +119 -119
  216. package/src/ui/render-scheduler.ts +143 -143
  217. package/src/ui/run-dashboard.ts +4 -0
  218. package/src/ui/run-event-bus.ts +209 -0
  219. package/src/ui/run-snapshot-cache.ts +68 -16
  220. package/src/ui/snapshot-types.ts +8 -0
  221. package/src/ui/spinner.ts +17 -17
  222. package/src/ui/status-colors.ts +58 -58
  223. package/src/ui/syntax-highlight.ts +116 -116
  224. package/src/ui/transcript-entries.ts +258 -0
  225. package/src/utils/atomic-write.ts +33 -33
  226. package/src/utils/completion-dedupe.ts +63 -63
  227. package/src/utils/frontmatter.ts +68 -68
  228. package/src/utils/git.ts +262 -262
  229. package/src/utils/ids.ts +17 -12
  230. package/src/utils/incremental-reader.ts +104 -0
  231. package/src/utils/names.ts +27 -27
  232. package/src/utils/redaction.ts +44 -44
  233. package/src/utils/safe-paths.ts +47 -47
  234. package/src/utils/scan-cache.ts +137 -0
  235. package/src/utils/sleep.ts +32 -32
  236. package/src/utils/sse-parser.ts +134 -0
  237. package/src/utils/task-name-generator.ts +337 -0
  238. package/src/utils/visual.ts +33 -2
  239. package/src/workflows/validate-workflow.ts +40 -40
  240. package/src/worktree/branch-freshness.ts +45 -45
  241. package/src/worktree/cleanup.ts +2 -1
  242. package/teams/default.team.md +12 -12
  243. package/teams/fast-fix.team.md +11 -11
  244. package/teams/implementation.team.md +18 -18
  245. package/teams/parallel-research.team.md +14 -14
  246. package/teams/research.team.md +11 -11
  247. package/teams/review.team.md +12 -12
  248. package/workflows/default.workflow.md +29 -29
  249. package/workflows/fast-fix.workflow.md +22 -22
  250. package/workflows/implementation.workflow.md +38 -38
  251. package/workflows/parallel-research.workflow.md +46 -46
  252. package/workflows/research.workflow.md +22 -22
  253. package/workflows/review.workflow.md +30 -30
@@ -1,26 +1,26 @@
1
- import * as fs from "node:fs";
2
- import * as path from "node:path";
3
- import { atomicWriteJson } from "../state/atomic-write.ts";
4
- import type { TeamRunManifest } from "../state/types.ts";
5
-
6
- export interface AsyncStartMarker {
7
- pid: number;
8
- startedAt: string;
9
- }
10
-
11
- export function asyncStartMarkerPath(manifest: Pick<TeamRunManifest, "stateRoot">): string {
12
- return path.join(manifest.stateRoot, "async.pid");
13
- }
14
-
15
- export function writeAsyncStartMarker(manifest: Pick<TeamRunManifest, "stateRoot">, marker: AsyncStartMarker): void {
16
- atomicWriteJson(asyncStartMarkerPath(manifest), marker);
17
- }
18
-
19
- export function hasAsyncStartMarker(manifest: Pick<TeamRunManifest, "stateRoot">): boolean {
20
- try {
21
- const raw = JSON.parse(fs.readFileSync(asyncStartMarkerPath(manifest), "utf-8")) as Partial<AsyncStartMarker>;
22
- return typeof raw.pid === "number" && Number.isInteger(raw.pid) && raw.pid > 0 && typeof raw.startedAt === "string" && raw.startedAt.length > 0;
23
- } catch {
24
- return false;
25
- }
26
- }
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+ import { atomicWriteJson } from "../state/atomic-write.ts";
4
+ import type { TeamRunManifest } from "../state/types.ts";
5
+
6
+ export interface AsyncStartMarker {
7
+ pid: number;
8
+ startedAt: string;
9
+ }
10
+
11
+ export function asyncStartMarkerPath(manifest: Pick<TeamRunManifest, "stateRoot">): string {
12
+ return path.join(manifest.stateRoot, "async.pid");
13
+ }
14
+
15
+ export function writeAsyncStartMarker(manifest: Pick<TeamRunManifest, "stateRoot">, marker: AsyncStartMarker): void {
16
+ atomicWriteJson(asyncStartMarkerPath(manifest), marker);
17
+ }
18
+
19
+ export function hasAsyncStartMarker(manifest: Pick<TeamRunManifest, "stateRoot">): boolean {
20
+ try {
21
+ const raw = JSON.parse(fs.readFileSync(asyncStartMarkerPath(manifest), "utf-8")) as Partial<AsyncStartMarker>;
22
+ return typeof raw.pid === "number" && Number.isInteger(raw.pid) && raw.pid > 0 && typeof raw.startedAt === "string" && raw.startedAt.length > 0;
23
+ } catch {
24
+ return false;
25
+ }
26
+ }
@@ -6,6 +6,7 @@ import { fileURLToPath, pathToFileURL } from "node:url";
6
6
  import { appendEvent } from "../state/event-log.ts";
7
7
  import type { TeamRunManifest } from "../state/types.ts";
8
8
 
9
+
9
10
  export type FileExists = (filePath: string) => boolean;
10
11
 
11
12
  const requireFromHere = createRequire(import.meta.url);
@@ -49,7 +50,7 @@ export function buildBackgroundSpawnOptions(manifest: TeamRunManifest, logFd: nu
49
50
  cwd: manifest.cwd,
50
51
  detached: true,
51
52
  stdio: ["ignore", logFd, logFd],
52
- env: { ...process.env },
53
+ env: { ...process.env, PI_CREW_PARENT_PID: String(process.pid) },
53
54
  windowsHide: true,
54
55
  };
55
56
  }
@@ -70,6 +71,7 @@ export function spawnBackgroundTeamRun(manifest: TeamRunManifest): SpawnBackgrou
70
71
  fs.appendFileSync(logPath, `[pi-crew] background loader=${command.loader}\n`, "utf-8");
71
72
  const child = spawn(process.execPath, command.args, buildBackgroundSpawnOptions(manifest, logFd));
72
73
  child.unref();
74
+
73
75
  return { pid: child.pid, logPath };
74
76
  } finally {
75
77
  fs.closeSync(logFd);
@@ -1,28 +1,28 @@
1
- import { appendEvent, readEvents } from "../state/event-log.ts";
2
- import type { CrewAttentionEventData, TeamRunManifest } from "../state/types.ts";
3
-
4
- export interface AppendTaskAttentionInput {
5
- manifest: TeamRunManifest;
6
- taskId?: string;
7
- message: string;
8
- data: CrewAttentionEventData;
9
- }
10
-
11
- export function appendTaskAttentionEvent(input: AppendTaskAttentionInput): boolean {
12
- const recent = readEvents(input.manifest.eventsPath).slice(-200);
13
- const dedupKey = `${input.taskId ?? ""}:${input.data.reason}:${input.data.activityState}`;
14
- const duplicate = recent.some(
15
- (event) =>
16
- event.type === "task.attention" &&
17
- `${event.taskId ?? ""}:${event.data?.reason ?? ""}:${event.data?.activityState ?? ""}` === dedupKey,
18
- );
19
- if (duplicate) return false;
20
- appendEvent(input.manifest.eventsPath, {
21
- type: "task.attention",
22
- runId: input.manifest.runId,
23
- taskId: input.taskId,
24
- message: input.message,
25
- data: { ...input.data },
26
- });
27
- return true;
28
- }
1
+ import { appendEvent, readEvents } from "../state/event-log.ts";
2
+ import type { CrewAttentionEventData, TeamRunManifest } from "../state/types.ts";
3
+
4
+ export interface AppendTaskAttentionInput {
5
+ manifest: TeamRunManifest;
6
+ taskId?: string;
7
+ message: string;
8
+ data: CrewAttentionEventData;
9
+ }
10
+
11
+ export function appendTaskAttentionEvent(input: AppendTaskAttentionInput): boolean {
12
+ const recent = readEvents(input.manifest.eventsPath).slice(-200);
13
+ const dedupKey = `${input.taskId ?? ""}:${input.data.reason}:${input.data.activityState}`;
14
+ const duplicate = recent.some(
15
+ (event) =>
16
+ event.type === "task.attention" &&
17
+ `${event.taskId ?? ""}:${event.data?.reason ?? ""}:${event.data?.activityState ?? ""}` === dedupKey,
18
+ );
19
+ if (duplicate) return false;
20
+ appendEvent(input.manifest.eventsPath, {
21
+ type: "task.attention",
22
+ runId: input.manifest.runId,
23
+ taskId: input.taskId,
24
+ message: input.message,
25
+ data: { ...input.data },
26
+ });
27
+ return true;
28
+ }
@@ -9,6 +9,16 @@ import { resolveCrewRuntime, runtimeResolutionState } from "./runtime-resolver.t
9
9
  import { directTeamAndWorkflowFromRun } from "./direct-run.ts";
10
10
  import { expandParallelResearchWorkflow } from "./parallel-research.ts";
11
11
  import { writeAsyncStartMarker } from "./async-marker.ts";
12
+ import { startParentGuard, stopParentGuard } from "./parent-guard.ts";
13
+
14
+ /**
15
+ * Remove macOS malloc-stack-logging vars that get inherited by child shells.
16
+ * Without this, every subprocess prints "MallocStackLogging: can't turn off..." to stderr.
17
+ */
18
+ function scrubProcessEnv(): void {
19
+ delete process.env.MallocStackLogging;
20
+ delete process.env.MallocStackLoggingNoCompact;
21
+ }
12
22
 
13
23
  function argValue(name: string): string | undefined {
14
24
  const index = process.argv.indexOf(name);
@@ -17,6 +27,13 @@ function argValue(name: string): string | undefined {
17
27
  }
18
28
 
19
29
  async function main(): Promise<void> {
30
+ // Scrub macOS malloc vars BEFORE anything else — must be clean for all child processes
31
+ scrubProcessEnv();
32
+
33
+ // Start parent guard FIRST — if parent is already dead, exit immediately
34
+ const parentPid = Number(process.env.PI_CREW_PARENT_PID);
35
+ if (parentPid > 0) startParentGuard(parentPid);
36
+
20
37
  const cwd = argValue("--cwd");
21
38
  const runId = argValue("--run-id");
22
39
  if (!cwd || !runId) throw new Error("Usage: background-runner.ts --cwd <cwd> --run-id <runId>");
@@ -53,6 +70,8 @@ async function main(): Promise<void> {
53
70
  manifest = updateRunStatus(manifest, "failed", message);
54
71
  appendEvent(manifest.eventsPath, { type: "async.failed", runId: manifest.runId, message });
55
72
  process.exitCode = 1;
73
+ } finally {
74
+ stopParentGuard();
56
75
  }
57
76
  }
58
77
 
@@ -0,0 +1,89 @@
1
+ import { CrewCancellationError, type CancellationReason, cancellationReasonFromUnknown } from "./cancellation.ts";
2
+
3
+ export interface CancellationTokenState {
4
+ aborted: boolean;
5
+ reason?: CancellationReason;
6
+ lastHeartbeatAt?: string;
7
+ lastHeartbeatStage?: string;
8
+ }
9
+
10
+ export interface CancellationTokenOptions {
11
+ signal?: AbortSignal;
12
+ onHeartbeat?: (state: CancellationTokenState) => void;
13
+ now?: () => Date;
14
+ }
15
+
16
+ export class CancellationToken {
17
+ readonly #controller = new AbortController();
18
+ readonly #onHeartbeat?: (state: CancellationTokenState) => void;
19
+ readonly #now: () => Date;
20
+ #reason?: CancellationReason;
21
+ #lastHeartbeatAt?: string;
22
+ #lastHeartbeatStage?: string;
23
+
24
+ constructor(options: CancellationTokenOptions = {}) {
25
+ this.#onHeartbeat = options.onHeartbeat;
26
+ this.#now = options.now ?? (() => new Date());
27
+ if (options.signal?.aborted) this.abort(options.signal.reason);
28
+ else if (options.signal) options.signal.addEventListener("abort", () => this.abort(options.signal?.reason), { once: true });
29
+ }
30
+
31
+ get signal(): AbortSignal { return this.#controller.signal; }
32
+ get aborted(): boolean { return this.#controller.signal.aborted; }
33
+ get reason(): CancellationReason | undefined { return this.#reason; }
34
+ get lastHeartbeatAt(): string | undefined { return this.#lastHeartbeatAt; }
35
+ get lastHeartbeatStage(): string | undefined { return this.#lastHeartbeatStage; }
36
+
37
+ heartbeat(stage?: string): CancellationTokenState {
38
+ this.throwIfCancelled();
39
+ this.#lastHeartbeatAt = this.#now().toISOString();
40
+ this.#lastHeartbeatStage = stage;
41
+ const state = this.state();
42
+ this.#onHeartbeat?.(state);
43
+ return state;
44
+ }
45
+
46
+ throwIfCancelled(): void {
47
+ if (this.aborted) throw new CrewCancellationError(this.#reason ?? cancellationReasonFromUnknown(this.#controller.signal.reason));
48
+ }
49
+
50
+ abort(reason?: unknown): void {
51
+ if (this.aborted) return;
52
+ this.#reason = cancellationReasonFromUnknown(reason);
53
+ this.#controller.abort(this.#reason);
54
+ }
55
+
56
+ wait(ms: number): Promise<void> {
57
+ this.throwIfCancelled();
58
+ if (ms <= 0) return Promise.resolve();
59
+ return new Promise((resolve, reject) => {
60
+ let timeout: NodeJS.Timeout | undefined;
61
+ const cleanup = (): void => {
62
+ if (timeout) clearTimeout(timeout);
63
+ this.signal.removeEventListener("abort", onAbort);
64
+ };
65
+ const onAbort = (): void => {
66
+ cleanup();
67
+ reject(new CrewCancellationError(this.#reason ?? cancellationReasonFromUnknown(this.signal.reason)));
68
+ };
69
+ timeout = setTimeout(() => {
70
+ cleanup();
71
+ resolve();
72
+ }, ms);
73
+ this.signal.addEventListener("abort", onAbort, { once: true });
74
+ });
75
+ }
76
+
77
+ state(): CancellationTokenState {
78
+ return {
79
+ aborted: this.aborted,
80
+ ...(this.#reason ? { reason: this.#reason } : {}),
81
+ ...(this.#lastHeartbeatAt ? { lastHeartbeatAt: this.#lastHeartbeatAt } : {}),
82
+ ...(this.#lastHeartbeatStage ? { lastHeartbeatStage: this.#lastHeartbeatStage } : {}),
83
+ };
84
+ }
85
+ }
86
+
87
+ export function createCancellationToken(options: CancellationTokenOptions = {}): CancellationToken {
88
+ return new CancellationToken(options);
89
+ }
@@ -1,51 +1,61 @@
1
- export type CancellationReasonCode = "caller_cancelled" | "leader_interrupted" | "provider_timeout" | "worker_timeout" | "tool_timeout" | "shutdown" | "unknown";
2
-
3
- export interface CancellationReason {
4
- code: CancellationReasonCode;
5
- message: string;
6
- cause?: unknown;
7
- }
8
-
9
- const KNOWN_CODES: ReadonlySet<string> = new Set(["caller_cancelled", "leader_interrupted", "provider_timeout", "worker_timeout", "tool_timeout", "shutdown", "unknown"]);
10
-
11
- export class CrewCancellationError extends Error {
12
- readonly reason: CancellationReason;
13
-
14
- constructor(reason: CancellationReason) {
15
- super(reason.message);
16
- this.name = "CrewCancellationError";
17
- this.reason = reason;
18
- }
19
- }
20
-
21
- function reasonFromString(value: string): CancellationReason {
22
- const trimmed = value.trim();
23
- if (KNOWN_CODES.has(trimmed)) return { code: trimmed as CancellationReasonCode, message: `Cancelled: ${trimmed}` };
24
- return { code: "caller_cancelled", message: trimmed || "Cancelled by caller." };
25
- }
26
-
27
- export function cancellationReasonFromUnknown(value: unknown): CancellationReason {
28
- if (value instanceof CrewCancellationError) return value.reason;
29
- if (value instanceof Error) return { code: "caller_cancelled", message: value.message || "Cancelled by caller.", cause: value };
30
- if (typeof value === "string") return reasonFromString(value);
31
- if (value && typeof value === "object" && !Array.isArray(value)) {
32
- const record = value as { code?: unknown; reason?: unknown; message?: unknown; cause?: unknown };
33
- const rawCode = typeof record.code === "string" ? record.code : typeof record.reason === "string" ? record.reason : undefined;
34
- const code = rawCode && KNOWN_CODES.has(rawCode) ? rawCode as CancellationReasonCode : "caller_cancelled";
35
- const message = typeof record.message === "string" && record.message.trim() ? record.message.trim() : `Cancelled: ${code}`;
36
- return { code, message, cause: record.cause ?? value };
37
- }
38
- return { code: "caller_cancelled", message: "Cancelled by caller." };
39
- }
40
-
41
- export function cancellationReasonFromSignal(signal: AbortSignal | undefined): CancellationReason {
42
- return cancellationReasonFromUnknown(signal?.reason);
43
- }
44
-
45
- export function cancellationErrorFromSignal(signal: AbortSignal | undefined): CrewCancellationError {
46
- return new CrewCancellationError(cancellationReasonFromSignal(signal));
47
- }
48
-
49
- export function throwIfCancelled(signal: AbortSignal | undefined): void {
50
- if (signal?.aborted) throw cancellationErrorFromSignal(signal);
51
- }
1
+ import type { OperationTerminalEvidence } from "../state/types.ts";
2
+
3
+ export type CancellationReasonCode = "caller_cancelled" | "leader_interrupted" | "provider_timeout" | "worker_timeout" | "tool_timeout" | "shutdown" | "unknown";
4
+
5
+ export interface CancellationReason {
6
+ code: CancellationReasonCode;
7
+ message: string;
8
+ cause?: unknown;
9
+ }
10
+
11
+ export function buildSyntheticTerminalEvidence(
12
+ operation: "worker" | "tool" | "model",
13
+ reason: CancellationReason,
14
+ startedAt?: string,
15
+ ): OperationTerminalEvidence {
16
+ return { operation, status: "cancelled", startedAt, finishedAt: new Date().toISOString(), reason };
17
+ }
18
+
19
+ const KNOWN_CODES: ReadonlySet<string> = new Set(["caller_cancelled", "leader_interrupted", "provider_timeout", "worker_timeout", "tool_timeout", "shutdown", "unknown"]);
20
+
21
+ export class CrewCancellationError extends Error {
22
+ readonly reason: CancellationReason;
23
+
24
+ constructor(reason: CancellationReason) {
25
+ super(reason.message);
26
+ this.name = "CrewCancellationError";
27
+ this.reason = reason;
28
+ }
29
+ }
30
+
31
+ function reasonFromString(value: string): CancellationReason {
32
+ const trimmed = value.trim();
33
+ if (KNOWN_CODES.has(trimmed)) return { code: trimmed as CancellationReasonCode, message: `Cancelled: ${trimmed}` };
34
+ return { code: "caller_cancelled", message: trimmed || "Cancelled by caller." };
35
+ }
36
+
37
+ export function cancellationReasonFromUnknown(value: unknown): CancellationReason {
38
+ if (value instanceof CrewCancellationError) return value.reason;
39
+ if (value instanceof Error) return { code: "caller_cancelled", message: value.message || "Cancelled by caller.", cause: value };
40
+ if (typeof value === "string") return reasonFromString(value);
41
+ if (value && typeof value === "object" && !Array.isArray(value)) {
42
+ const record = value as { code?: unknown; reason?: unknown; message?: unknown; cause?: unknown };
43
+ const rawCode = typeof record.code === "string" ? record.code : typeof record.reason === "string" ? record.reason : undefined;
44
+ const code = rawCode && KNOWN_CODES.has(rawCode) ? rawCode as CancellationReasonCode : "caller_cancelled";
45
+ const message = typeof record.message === "string" && record.message.trim() ? record.message.trim() : `Cancelled: ${code}`;
46
+ return { code, message, cause: record.cause ?? value };
47
+ }
48
+ return { code: "caller_cancelled", message: "Cancelled by caller." };
49
+ }
50
+
51
+ export function cancellationReasonFromSignal(signal: AbortSignal | undefined): CancellationReason {
52
+ return cancellationReasonFromUnknown(signal?.reason);
53
+ }
54
+
55
+ export function cancellationErrorFromSignal(signal: AbortSignal | undefined): CrewCancellationError {
56
+ return new CrewCancellationError(cancellationReasonFromSignal(signal));
57
+ }
58
+
59
+ export function throwIfCancelled(signal: AbortSignal | undefined): void {
60
+ if (signal?.aborted) throw cancellationErrorFromSignal(signal);
61
+ }
@@ -0,0 +1,116 @@
1
+ import type { AgentConfig, ResourceSource } from "../agents/agent-config.ts";
2
+ import { discoverAgents } from "../agents/discover-agents.ts";
3
+ import { discoverTeams } from "../teams/discover-teams.ts";
4
+ import { discoverWorkflows } from "../workflows/discover-workflows.ts";
5
+ import { discoverSkills } from "../skills/discover-skills.ts";
6
+ import type { PiTeamsConfig } from "../config/config.ts";
7
+
8
+ export type CapabilityKind = "team" | "workflow" | "agent" | "skill" | "tool" | "runtime";
9
+ export type CapabilitySource = "builtin" | "project" | "user" | "package" | "git";
10
+ export type CapabilityState = "active" | "disabled" | "shadowed" | "missing";
11
+
12
+ export interface CapabilityItem {
13
+ id: string;
14
+ kind: CapabilityKind;
15
+ name: string;
16
+ description: string;
17
+ source: CapabilitySource;
18
+ path?: string;
19
+ state: CapabilityState;
20
+ disabledReason?: string;
21
+ shadowedBy?: string;
22
+ }
23
+
24
+ function normalizeAgents(agents: AgentConfig[], source: CapabilitySource, disabledIds: Set<string>): CapabilityItem[] {
25
+ return agents.map((agent) => {
26
+ const id = `agent:${agent.name}`;
27
+ const configDisabled = disabledIds.has(id);
28
+ const agentDisabled = agent.disabled || configDisabled;
29
+ return {
30
+ id,
31
+ kind: "agent" as const,
32
+ name: agent.name,
33
+ description: agent.description,
34
+ source,
35
+ path: agent.filePath,
36
+ state: agentDisabled ? "disabled" : "active",
37
+ disabledReason: configDisabled ? "disabled by policy" : agent.disabled ? "disabled in config" : undefined,
38
+ };
39
+ });
40
+ }
41
+
42
+ function normalizeSkills(cwd: string, disabledIds: Set<string>): CapabilityItem[] {
43
+ const skills = discoverSkills(cwd);
44
+ return skills.map((skill) => {
45
+ const id = `skill:${skill.name}`;
46
+ const configDisabled = disabledIds.has(id);
47
+ return {
48
+ id,
49
+ kind: "skill" as const,
50
+ name: skill.name,
51
+ description: skill.description,
52
+ source: skill.source as CapabilitySource,
53
+ path: skill.path,
54
+ state: configDisabled ? "disabled" : "active",
55
+ disabledReason: configDisabled ? "disabled by policy" : undefined,
56
+ };
57
+ });
58
+ }
59
+
60
+ function normalizeTeams(cwd: string, disabledIds: Set<string>): CapabilityItem[] {
61
+ const result = discoverTeams(cwd);
62
+ return [...result.builtin, ...result.user, ...result.project].map((team) => {
63
+ const id = `team:${team.name}`;
64
+ const configDisabled = disabledIds.has(id);
65
+ return {
66
+ id,
67
+ kind: "team" as const,
68
+ name: team.name,
69
+ description: team.description,
70
+ source: team.source as CapabilitySource,
71
+ path: team.filePath,
72
+ state: configDisabled ? "disabled" : "active",
73
+ disabledReason: configDisabled ? "disabled by policy" : undefined,
74
+ };
75
+ });
76
+ }
77
+
78
+ function normalizeWorkflows(cwd: string, disabledIds: Set<string>): CapabilityItem[] {
79
+ const result = discoverWorkflows(cwd);
80
+ return [...result.builtin, ...result.user, ...result.project].map((workflow) => {
81
+ const id = `workflow:${workflow.name}`;
82
+ const configDisabled = disabledIds.has(id);
83
+ return {
84
+ id,
85
+ kind: "workflow" as const,
86
+ name: workflow.name,
87
+ description: workflow.description,
88
+ source: workflow.source as CapabilitySource,
89
+ path: workflow.filePath,
90
+ state: configDisabled ? "disabled" : "active",
91
+ disabledReason: configDisabled ? "disabled by policy" : undefined,
92
+ };
93
+ });
94
+ }
95
+
96
+ export function buildCapabilityInventory(cwd: string, config?: PiTeamsConfig): CapabilityItem[] {
97
+ const disabledIds = new Set<string>(config?.policy?.disabledCapabilities ?? []);
98
+ const agents = discoverAgents(cwd);
99
+ const items = [
100
+ ...normalizeTeams(cwd, disabledIds),
101
+ ...normalizeWorkflows(cwd, disabledIds),
102
+ ...normalizeAgents([...agents.builtin, ...agents.user, ...agents.project], "builtin", disabledIds),
103
+ ...normalizeSkills(cwd, disabledIds),
104
+ ];
105
+
106
+ // Mark shadowed resources: project/user items with same kind:name as a builtin
107
+ const builtinNames = new Set(items.filter((item) => item.source === "builtin" || item.source === "package").map((item) => `${item.kind}:${item.name}`));
108
+ for (const item of items) {
109
+ if (item.source !== "builtin" && item.source !== "package" && builtinNames.has(`${item.kind}:${item.name}`)) {
110
+ item.state = "shadowed";
111
+ item.shadowedBy = `builtin:${item.kind}:${item.name}`;
112
+ }
113
+ }
114
+
115
+ return items.sort((a, b) => a.id.localeCompare(b.id));
116
+ }
@@ -112,7 +112,7 @@ export interface ChildPiRunResult {
112
112
  export function buildChildPiSpawnOptions(cwd: string, env: NodeJS.ProcessEnv): SpawnOptions {
113
113
  return {
114
114
  cwd,
115
- env,
115
+ env: { ...env, PI_CREW_PARENT_PID: String(process.pid) },
116
116
  stdio: ["ignore", "pipe", "pipe"],
117
117
  detached: process.platform !== "win32",
118
118
  windowsHide: true,
@@ -243,6 +243,7 @@ export class ChildPiLineObserver {
243
243
  }
244
244
  }
245
245
 
246
+ /** Mock-only path — real code path reuses a single observer. */
246
247
  function observeStdoutChunk(input: ChildPiRunInput, text: string): void {
247
248
  const observer = new ChildPiLineObserver(input);
248
249
  observer.observe(text);