pi-crew 0.1.49 → 0.2.0

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 (249) hide show
  1. package/CHANGELOG.md +74 -1
  2. package/README.md +176 -781
  3. package/agents/analyst.md +11 -11
  4. package/agents/critic.md +11 -11
  5. package/agents/executor.md +11 -11
  6. package/agents/explorer.md +11 -11
  7. package/agents/planner.md +11 -11
  8. package/agents/reviewer.md +11 -11
  9. package/agents/security-reviewer.md +11 -11
  10. package/agents/test-engineer.md +11 -11
  11. package/agents/verifier.md +70 -11
  12. package/agents/writer.md +11 -11
  13. package/docs/actions-reference.md +595 -0
  14. package/docs/commands-reference.md +347 -0
  15. package/docs/runtime-flow.md +148 -148
  16. package/index.ts +6 -6
  17. package/package.json +99 -99
  18. package/skills/async-worker-recovery/SKILL.md +42 -42
  19. package/skills/context-artifact-hygiene/SKILL.md +52 -52
  20. package/skills/delegation-patterns/SKILL.md +54 -54
  21. package/skills/mailbox-interactive/SKILL.md +40 -40
  22. package/skills/model-routing-context/SKILL.md +39 -39
  23. package/skills/multi-perspective-review/SKILL.md +58 -58
  24. package/skills/observability-reliability/SKILL.md +41 -41
  25. package/skills/orchestration/SKILL.md +157 -157
  26. package/skills/ownership-session-security/SKILL.md +41 -41
  27. package/skills/pi-extension-lifecycle/SKILL.md +39 -39
  28. package/skills/requirements-to-task-packet/SKILL.md +63 -63
  29. package/skills/resource-discovery-config/SKILL.md +41 -41
  30. package/skills/runtime-state-reader/SKILL.md +44 -44
  31. package/skills/secure-agent-orchestration-review/SKILL.md +45 -45
  32. package/skills/state-mutation-locking/SKILL.md +42 -42
  33. package/skills/systematic-debugging/SKILL.md +67 -67
  34. package/skills/ui-render-performance/SKILL.md +39 -39
  35. package/skills/verification-before-done/SKILL.md +57 -57
  36. package/skills/worktree-isolation/SKILL.md +39 -39
  37. package/src/adapters/claude-adapter.ts +25 -0
  38. package/src/adapters/codex-adapter.ts +21 -0
  39. package/src/adapters/cursor-adapter.ts +17 -0
  40. package/src/adapters/export-util.ts +137 -0
  41. package/src/adapters/index.ts +15 -0
  42. package/src/adapters/registry.ts +18 -0
  43. package/src/adapters/types.ts +23 -0
  44. package/src/agents/agent-config.ts +2 -0
  45. package/src/agents/agent-search.ts +98 -98
  46. package/src/agents/discover-agents.ts +2 -1
  47. package/src/config/config.ts +14 -1
  48. package/src/config/defaults.ts +5 -5
  49. package/src/config/drift-detector.ts +211 -0
  50. package/src/config/markers.ts +327 -0
  51. package/src/config/resilient-parser.ts +108 -0
  52. package/src/config/suggestions.ts +74 -0
  53. package/src/extension/cross-extension-rpc.ts +103 -82
  54. package/src/extension/project-init.ts +36 -4
  55. package/src/extension/register.ts +67 -22
  56. package/src/extension/registration/commands.ts +77 -8
  57. package/src/extension/registration/subagent-tools.ts +10 -1
  58. package/src/extension/registration/team-tool.ts +10 -1
  59. package/src/extension/registration/viewers.ts +48 -34
  60. package/src/extension/run-bundle-schema.ts +89 -89
  61. package/src/extension/run-export.ts +26 -12
  62. package/src/extension/run-import.ts +25 -1
  63. package/src/extension/run-index.ts +5 -1
  64. package/src/extension/run-maintenance.ts +142 -68
  65. package/src/extension/team-manager-command.ts +10 -1
  66. package/src/extension/team-tool/context.ts +1 -1
  67. package/src/extension/team-tool/doctor.ts +28 -3
  68. package/src/extension/team-tool/handle-settings.ts +195 -188
  69. package/src/extension/team-tool/inspect.ts +41 -41
  70. package/src/extension/team-tool/intent-policy.ts +42 -42
  71. package/src/extension/team-tool/lifecycle-actions.ts +27 -8
  72. package/src/extension/team-tool/plan.ts +19 -19
  73. package/src/extension/team-tool/run.ts +12 -1
  74. package/src/extension/team-tool.ts +14 -3
  75. package/src/i18n.ts +184 -184
  76. package/src/observability/exporters/otlp-exporter.ts +92 -77
  77. package/src/prompt/prompt-runtime.ts +72 -72
  78. package/src/runtime/agent-memory.ts +72 -72
  79. package/src/runtime/agent-observability.ts +114 -114
  80. package/src/runtime/async-marker.ts +26 -26
  81. package/src/runtime/attention-events.ts +28 -28
  82. package/src/runtime/auto-resume.ts +100 -0
  83. package/src/runtime/background-runner.ts +11 -1
  84. package/src/runtime/cancellation-token.ts +89 -89
  85. package/src/runtime/cancellation.ts +61 -61
  86. package/src/runtime/capability-inventory.ts +116 -116
  87. package/src/runtime/child-pi.ts +7 -2
  88. package/src/runtime/compaction-summary.ts +271 -0
  89. package/src/runtime/completion-guard.ts +190 -190
  90. package/src/runtime/concurrency.ts +3 -1
  91. package/src/runtime/crash-recovery.ts +33 -0
  92. package/src/runtime/delta-conflict.ts +360 -0
  93. package/src/runtime/diagnostic-export.ts +3 -1
  94. package/src/runtime/direct-run.ts +35 -35
  95. package/src/runtime/event-stream-bridge.ts +3 -1
  96. package/src/runtime/foreground-control.ts +82 -82
  97. package/src/runtime/green-contract.ts +46 -46
  98. package/src/runtime/group-join.ts +106 -106
  99. package/src/runtime/heartbeat-gradient.ts +28 -28
  100. package/src/runtime/heartbeat-watcher.ts +124 -124
  101. package/src/runtime/iteration-hooks.ts +262 -0
  102. package/src/runtime/live-agent-control.ts +88 -88
  103. package/src/runtime/live-control-realtime.ts +36 -36
  104. package/src/runtime/live-extension-bridge.ts +150 -150
  105. package/src/runtime/live-irc.ts +92 -92
  106. package/src/runtime/live-session-health.ts +100 -100
  107. package/src/runtime/loop-gates.ts +129 -0
  108. package/src/runtime/metric-parser.ts +40 -0
  109. package/src/runtime/notebook-helpers.ts +90 -90
  110. package/src/runtime/orphan-sentinel.ts +7 -7
  111. package/src/runtime/parallel-research.ts +44 -44
  112. package/src/runtime/phase-progress.ts +217 -0
  113. package/src/runtime/pi-args.ts +38 -2
  114. package/src/runtime/pi-json-output.ts +111 -111
  115. package/src/runtime/pi-spawn.ts +74 -6
  116. package/src/runtime/policy-engine.ts +79 -79
  117. package/src/runtime/post-checks.ts +122 -0
  118. package/src/runtime/process-status.ts +14 -1
  119. package/src/runtime/progress-event-coalescer.ts +43 -43
  120. package/src/runtime/prose-compressor.ts +164 -164
  121. package/src/runtime/recovery-recipes.ts +74 -74
  122. package/src/runtime/result-extractor.ts +121 -121
  123. package/src/runtime/role-permission.ts +39 -39
  124. package/src/runtime/sensitive-paths.ts +3 -3
  125. package/src/runtime/session-resources.ts +25 -25
  126. package/src/runtime/session-snapshot.ts +59 -59
  127. package/src/runtime/session-usage.ts +79 -79
  128. package/src/runtime/sidechain-output.ts +29 -29
  129. package/src/runtime/stream-preview.ts +177 -177
  130. package/src/runtime/supervisor-contact.ts +59 -59
  131. package/src/runtime/task-display.ts +38 -38
  132. package/src/runtime/task-graph.ts +207 -0
  133. package/src/runtime/task-quality.ts +207 -0
  134. package/src/runtime/task-runner/capabilities.ts +78 -78
  135. package/src/runtime/task-runner/live-executor.ts +7 -1
  136. package/src/runtime/task-runner/progress.ts +119 -119
  137. package/src/runtime/task-runner/prompt-builder.ts +1 -1
  138. package/src/runtime/task-runner/prompt-pipeline.ts +64 -64
  139. package/src/runtime/task-runner/result-utils.ts +14 -14
  140. package/src/runtime/task-runner/run-projection.ts +103 -103
  141. package/src/runtime/task-runner/state-helpers.ts +22 -22
  142. package/src/runtime/team-runner.ts +126 -7
  143. package/src/runtime/worker-heartbeat.ts +21 -21
  144. package/src/runtime/worker-startup.ts +57 -57
  145. package/src/runtime/workflow-state.ts +187 -0
  146. package/src/runtime/workspace-tree.ts +298 -298
  147. package/src/schema/config-schema.ts +12 -0
  148. package/src/schema/validation-types.ts +148 -0
  149. package/src/skills/skill-templates.ts +374 -0
  150. package/src/state/active-run-registry.ts +35 -11
  151. package/src/state/atomic-write.ts +33 -26
  152. package/src/state/contracts.ts +1 -0
  153. package/src/state/event-reconstructor.ts +217 -0
  154. package/src/state/locks.ts +2 -11
  155. package/src/state/mailbox.ts +4 -3
  156. package/src/state/state-store.ts +32 -14
  157. package/src/state/task-claims.ts +44 -44
  158. package/src/state/types.ts +9 -0
  159. package/src/state/usage.ts +29 -29
  160. package/src/subagents/async-entry.ts +1 -1
  161. package/src/subagents/index.ts +3 -3
  162. package/src/subagents/live/control.ts +1 -1
  163. package/src/subagents/live/manager.ts +1 -1
  164. package/src/subagents/live/realtime.ts +1 -1
  165. package/src/subagents/live/session-runtime.ts +1 -1
  166. package/src/subagents/manager.ts +1 -1
  167. package/src/subagents/spawn.ts +1 -1
  168. package/src/teams/team-serializer.ts +38 -38
  169. package/src/types/diff.d.ts +18 -18
  170. package/src/ui/crew-footer.ts +101 -101
  171. package/src/ui/crew-select-list.ts +111 -111
  172. package/src/ui/crew-widget.ts +9 -4
  173. package/src/ui/dashboard-panes/cancellation-pane.ts +42 -42
  174. package/src/ui/dashboard-panes/capability-pane.ts +59 -59
  175. package/src/ui/dashboard-panes/mailbox-pane.ts +35 -35
  176. package/src/ui/dashboard-panes/metrics-pane.ts +34 -34
  177. package/src/ui/dashboard-panes/progress-pane.ts +11 -0
  178. package/src/ui/dynamic-border.ts +25 -25
  179. package/src/ui/layout-primitives.ts +106 -106
  180. package/src/ui/loaders.ts +158 -158
  181. package/src/ui/powerbar-publisher.ts +6 -0
  182. package/src/ui/render-coalescer.ts +51 -51
  183. package/src/ui/render-diff.ts +119 -119
  184. package/src/ui/render-scheduler.ts +143 -143
  185. package/src/ui/run-action-dispatcher.ts +10 -1
  186. package/src/ui/spinner.ts +17 -17
  187. package/src/ui/status-colors.ts +58 -58
  188. package/src/ui/syntax-highlight.ts +116 -116
  189. package/src/ui/transcript-entries.ts +258 -258
  190. package/src/utils/completion-dedupe.ts +63 -63
  191. package/src/utils/frontmatter.ts +68 -68
  192. package/src/utils/git.ts +262 -262
  193. package/src/utils/ids.ts +17 -17
  194. package/src/utils/incremental-reader.ts +104 -104
  195. package/src/utils/names.ts +27 -27
  196. package/src/utils/redaction.ts +44 -44
  197. package/src/utils/safe-paths.ts +47 -47
  198. package/src/utils/scan-cache.ts +136 -136
  199. package/src/utils/sleep.ts +40 -26
  200. package/src/utils/task-name-generator.ts +337 -337
  201. package/src/workflows/validate-workflow.ts +40 -40
  202. package/src/worktree/branch-freshness.ts +45 -45
  203. package/src/worktree/worktree-manager.ts +11 -3
  204. package/teams/default.team.md +12 -12
  205. package/teams/fast-fix.team.md +11 -11
  206. package/teams/implementation.team.md +18 -18
  207. package/teams/parallel-research.team.md +14 -14
  208. package/teams/research.team.md +11 -11
  209. package/teams/review.team.md +12 -12
  210. package/workflows/default.workflow.md +30 -29
  211. package/workflows/fast-fix.workflow.md +23 -22
  212. package/workflows/implementation.workflow.md +43 -38
  213. package/workflows/parallel-research.workflow.md +46 -46
  214. package/workflows/research.workflow.md +22 -22
  215. package/workflows/review.workflow.md +30 -30
  216. package/docs/refactor-tasks-phase3.md +0 -394
  217. package/docs/refactor-tasks-phase4.md +0 -564
  218. package/docs/refactor-tasks-phase5.md +0 -402
  219. package/docs/refactor-tasks-phase6.md +0 -662
  220. package/docs/refactor-tasks.md +0 -1484
  221. package/docs/research/AGENT-EXECUTION-ARCHITECTURE.md +0 -261
  222. package/docs/research/AGENT-LIFECYCLE-COMPARISON.md +0 -111
  223. package/docs/research/AUDIT_OH_MY_PI.md +0 -261
  224. package/docs/research/AUDIT_PI_CREW.md +0 -457
  225. package/docs/research/CAVEMAN-DEEP-RESEARCH.md +0 -281
  226. package/docs/research/COMPARISON_OH_MY_PI_VS_PI_CREW.md +0 -264
  227. package/docs/research/DEEP-RESEARCH-PI-POWERBAR.md +0 -343
  228. package/docs/research/DEEP_RESEARCH_SUBAGENT_ARCHITECTURE.md +0 -480
  229. package/docs/research/GAP_CLOSURE_IMPLEMENTATION_PLAN.md +0 -354
  230. package/docs/research/IMPLEMENTATION_PLAN.md +0 -385
  231. package/docs/research/LIVE-SESSION-PRODUCTION-READY-PLAN.md +0 -502
  232. package/docs/research/OH-MY-PI-DEEP-RESEARCH-v14.7.6.md +0 -266
  233. package/docs/research/REMAINING-GAPS-PLAN.md +0 -363
  234. package/docs/research/SESSION-SUMMARY-2026-05-08.md +0 -146
  235. package/docs/research/UI-RESPONSIVENESS-AUDIT.md +0 -173
  236. package/docs/research-awesome-agent-skills-distillation.md +0 -100
  237. package/docs/research-extension-examples.md +0 -297
  238. package/docs/research-extension-system.md +0 -324
  239. package/docs/research-oh-my-pi-distillation.md +0 -369
  240. package/docs/research-optimization-plan.md +0 -548
  241. package/docs/research-phase10-distillation.md +0 -199
  242. package/docs/research-phase11-distillation.md +0 -201
  243. package/docs/research-phase8-operator-experience-plan.md +0 -819
  244. package/docs/research-phase9-observability-reliability-plan.md +0 -1190
  245. package/docs/research-pi-coding-agent.md +0 -357
  246. package/docs/research-source-pi-crew-reference.md +0 -174
  247. package/docs/research-ui-optimization-plan.md +0 -480
  248. package/docs/source-runtime-refactor-map.md +0 -107
  249. package/src/utils/atomic-write.ts +0 -33
@@ -23,7 +23,9 @@ export function defaultWorkflowConcurrency(workflowName: string, workflowMaxConc
23
23
  if (workflowMaxConcurrency !== undefined) return workflowMaxConcurrency;
24
24
  if (workflowName === "parallel-research") return DEFAULT_CONCURRENCY.workflow.parallelResearch;
25
25
  if (workflowName === "research") return DEFAULT_CONCURRENCY.workflow.research;
26
- if (workflowName === "implementation" || workflowName === "review" || workflowName === "default") return DEFAULT_CONCURRENCY.workflow.implementation;
26
+ if (workflowName === "implementation") return DEFAULT_CONCURRENCY.workflow.implementation;
27
+ if (workflowName === "review") return DEFAULT_CONCURRENCY.workflow.review;
28
+ if (workflowName === "default") return DEFAULT_CONCURRENCY.workflow.default;
27
29
  return DEFAULT_CONCURRENCY.fallback;
28
30
  }
29
31
 
@@ -11,6 +11,8 @@ import { checkProcessLiveness } from "./process-status.ts";
11
11
  import { reconcileStaleRun, type ReconcileResult } from "./stale-reconciler.ts";
12
12
  import { executeHook, appendHookEvent } from "../hooks/registry.ts";
13
13
  import { activeRunEntries, unregisterActiveRun, readActiveRunRegistry } from "../state/active-run-registry.ts";
14
+ import { resolveRealContainedPath } from "../utils/safe-paths.ts";
15
+ import { projectCrewRoot, userCrewRoot } from "../utils/paths.ts";
14
16
 
15
17
  export interface RecoveryPlan {
16
18
  runId: string;
@@ -168,6 +170,32 @@ export function cancelOrphanedRuns(
168
170
  * This is the **global** cleanup that cancelOrphanedRuns (project-scoped)
169
171
  * cannot reach.
170
172
  */
173
+ /**
174
+ * Best-effort removal of stateRoot and artifactsRoot directories for a purged run.
175
+ * Uses resolveRealContainedPath to ensure we only delete paths that are safely
176
+ * contained within a known crew root (project or user level).
177
+ */
178
+ function tryRemoveRunDirectories(entry: { stateRoot: string; cwd: string }): void {
179
+ const roots = [projectCrewRoot(entry.cwd), userCrewRoot()];
180
+ for (const root of roots) {
181
+ try {
182
+ resolveRealContainedPath(root, entry.stateRoot);
183
+ // If we get here, stateRoot is safely contained — remove it
184
+ fs.rmSync(entry.stateRoot, { recursive: true, force: true });
185
+ break;
186
+ } catch {
187
+ // Not contained in this root, try next
188
+ }
189
+ }
190
+ // NOTE: artifactsRoot is shared across runs and cleaned up by pruneFinishedRuns/pruneUserLevelRuns — not deleted here.
191
+ }
192
+
193
+ /**
194
+ * Purge the global active-run-index of entries whose manifest is no longer active.
195
+ *
196
+ * Note: This function only cleans user-level active run entries.
197
+ * Project-level stale runs are handled by session_start auto-prune triggered during run creation.
198
+ */
171
199
  export function purgeStaleActiveRunIndex(staleThresholdMs = 300_000, now = Date.now()): { purged: string[]; kept: string[] } {
172
200
  const purged: string[] = [];
173
201
  const kept: string[] = [];
@@ -177,6 +205,7 @@ export function purgeStaleActiveRunIndex(staleThresholdMs = 300_000, now = Date.
177
205
  // 1. Manifest file gone → definitely stale
178
206
  if (!fs.existsSync(entry.manifestPath)) {
179
207
  unregisterActiveRun(entry.runId);
208
+ tryRemoveRunDirectories(entry);
180
209
  purged.push(entry.runId);
181
210
  continue;
182
211
  }
@@ -184,6 +213,7 @@ export function purgeStaleActiveRunIndex(staleThresholdMs = 300_000, now = Date.
184
213
  // 2. CWD gone → temp dir cleaned up
185
214
  if (!fs.existsSync(entry.cwd)) {
186
215
  unregisterActiveRun(entry.runId);
216
+ tryRemoveRunDirectories(entry);
187
217
  purged.push(entry.runId);
188
218
  continue;
189
219
  }
@@ -194,6 +224,7 @@ export function purgeStaleActiveRunIndex(staleThresholdMs = 300_000, now = Date.
194
224
  manifest = JSON.parse(fs.readFileSync(entry.manifestPath, "utf-8"));
195
225
  } catch {
196
226
  unregisterActiveRun(entry.runId);
227
+ tryRemoveRunDirectories(entry);
197
228
  purged.push(entry.runId);
198
229
  continue;
199
230
  }
@@ -202,6 +233,7 @@ export function purgeStaleActiveRunIndex(staleThresholdMs = 300_000, now = Date.
202
233
  const terminalStatuses = new Set(["completed", "failed", "cancelled", "blocked"]);
203
234
  if (manifest && terminalStatuses.has(manifest.status ?? "")) {
204
235
  unregisterActiveRun(entry.runId);
236
+ tryRemoveRunDirectories(entry);
205
237
  purged.push(entry.runId);
206
238
  continue;
207
239
  }
@@ -231,6 +263,7 @@ export function purgeStaleActiveRunIndex(staleThresholdMs = 300_000, now = Date.
231
263
  // Best-effort manifest cleanup
232
264
  }
233
265
  unregisterActiveRun(entry.runId);
266
+ tryRemoveRunDirectories(entry);
234
267
  purged.push(entry.runId);
235
268
  continue;
236
269
  }
@@ -0,0 +1,360 @@
1
+ /**
2
+ * Delta conflict detection for pi-crew import and resume operations.
3
+ *
4
+ * Compares incoming bundles against existing state to surface conflicts
5
+ * (file overwrites, status mismatches, schema drift, deleted resources)
6
+ * without blocking the operation — only reporting for user awareness.
7
+ */
8
+
9
+ // ── Types ──────────────────────────────────────────────────────────────
10
+
11
+ export type ConflictKind = "file_overwrite" | "state_mismatch" | "schema_drift" | "resource_deleted";
12
+
13
+ export interface Conflict {
14
+ kind: ConflictKind;
15
+ /** File or resource path that is in conflict. */
16
+ path: string;
17
+ /** Current value or summary (optional). */
18
+ existing?: string;
19
+ /** Incoming value or summary (optional). */
20
+ incoming?: string;
21
+ severity: "error" | "warning";
22
+ autoResolvable: boolean;
23
+ }
24
+
25
+ export interface ConflictReport {
26
+ hasConflicts: boolean;
27
+ conflicts: Conflict[];
28
+ summary: { errors: number; warnings: number; autoResolvable: number };
29
+ }
30
+
31
+ export type ConflictStrategy = "skip" | "overwrite" | "merge";
32
+
33
+ export interface ConflictResolution {
34
+ resolved: boolean;
35
+ action: string;
36
+ }
37
+
38
+ // ── Helpers ────────────────────────────────────────────────────────────
39
+
40
+ interface TaskLike {
41
+ id: string;
42
+ status: string;
43
+ role?: string;
44
+ agent?: string;
45
+ }
46
+
47
+ function buildReport(conflicts: Conflict[]): ConflictReport {
48
+ const errors = conflicts.filter((c) => c.severity === "error").length;
49
+ const warnings = conflicts.filter((c) => c.severity === "warning").length;
50
+ const autoResolvable = conflicts.filter((c) => c.autoResolvable).length;
51
+ return {
52
+ hasConflicts: conflicts.length > 0,
53
+ conflicts,
54
+ summary: { errors, warnings, autoResolvable },
55
+ };
56
+ }
57
+
58
+ function isRecord(value: unknown): value is Record<string, unknown> {
59
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
60
+ }
61
+
62
+ /**
63
+ * Extract task-like objects from an unknown array, filtering out non-records.
64
+ */
65
+ function extractTaskLikes(tasks: unknown[]): TaskLike[] {
66
+ return tasks
67
+ .filter(isRecord)
68
+ .map((t) => ({
69
+ id: typeof t.id === "string" ? t.id : "",
70
+ status: typeof t.status === "string" ? t.status : "",
71
+ role: typeof t.role === "string" ? t.role : undefined,
72
+ agent: typeof t.agent === "string" ? t.agent : undefined,
73
+ }))
74
+ .filter((t) => t.id !== "");
75
+ }
76
+
77
+ // ── Import Conflict Detection ──────────────────────────────────────────
78
+
79
+ export interface ImportBundle {
80
+ manifest: Record<string, unknown>;
81
+ tasks: unknown[];
82
+ events?: unknown[];
83
+ }
84
+
85
+ export interface ExistingState {
86
+ manifest?: Record<string, unknown>;
87
+ tasks?: unknown[];
88
+ }
89
+
90
+ /**
91
+ * Detect conflicts between an import bundle and existing run state.
92
+ *
93
+ * Checks:
94
+ * - **schema_drift**: manifest `schemaVersion` differs between import and existing.
95
+ * - **file_overwrite**: artifact paths in the import bundle that already exist
96
+ * in the current manifest's artifact list.
97
+ * - **state_mismatch**: task statuses differ between import and existing tasks.
98
+ * - **resource_deleted**: referenced agent/team/workflow in import does not exist
99
+ * in the current state.
100
+ */
101
+ export function detectImportConflicts(
102
+ importBundle: ImportBundle,
103
+ existingState: ExistingState,
104
+ ): ConflictReport {
105
+ const conflicts: Conflict[] = [];
106
+ const { manifest: incoming, tasks: incomingTasks } = importBundle;
107
+ const { manifest: current, tasks: currentTasks } = existingState;
108
+
109
+ // ── Schema drift ─────────────────────────────────────────────────
110
+ if (current) {
111
+ const incomingVersion = incoming.schemaVersion;
112
+ const currentVersion = current.schemaVersion;
113
+ if (
114
+ typeof incomingVersion !== "undefined" &&
115
+ typeof currentVersion !== "undefined" &&
116
+ incomingVersion !== currentVersion
117
+ ) {
118
+ conflicts.push({
119
+ kind: "schema_drift",
120
+ path: "manifest.schemaVersion",
121
+ existing: String(currentVersion),
122
+ incoming: String(incomingVersion),
123
+ severity: "warning",
124
+ autoResolvable: true,
125
+ });
126
+ }
127
+ }
128
+
129
+ // ── File overwrite (artifact collision) ──────────────────────────
130
+ const incomingArtifacts = Array.isArray(incoming.artifacts) ? incoming.artifacts : [];
131
+ const currentArtifacts = Array.isArray(current?.artifacts) ? current?.artifacts : [];
132
+
133
+ if (current && currentArtifacts.length > 0) {
134
+ const currentPaths = new Set(
135
+ currentArtifacts
136
+ .filter(isRecord)
137
+ .map((a) => (typeof a.path === "string" ? a.path : ""))
138
+ .filter((p) => p !== ""),
139
+ );
140
+
141
+ for (const artifact of incomingArtifacts) {
142
+ if (!isRecord(artifact)) continue;
143
+ const artifactPath = typeof artifact.path === "string" ? artifact.path : "";
144
+ if (artifactPath !== "" && currentPaths.has(artifactPath)) {
145
+ conflicts.push({
146
+ kind: "file_overwrite",
147
+ path: artifactPath,
148
+ existing: "present in current run",
149
+ incoming: "present in import bundle",
150
+ severity: "warning",
151
+ autoResolvable: true,
152
+ });
153
+ }
154
+ }
155
+ }
156
+
157
+ // ── State mismatch (task status differences) ─────────────────────
158
+ if (currentTasks && currentTasks.length > 0) {
159
+ const currentTaskMap = new Map(
160
+ extractTaskLikes(currentTasks).map((t) => [t.id, t]),
161
+ );
162
+ const incomingTaskList = extractTaskLikes(incomingTasks);
163
+
164
+ for (const inTask of incomingTaskList) {
165
+ const curTask = currentTaskMap.get(inTask.id);
166
+ if (curTask && curTask.status !== inTask.status) {
167
+ conflicts.push({
168
+ kind: "state_mismatch",
169
+ path: `tasks/${inTask.id}`,
170
+ existing: curTask.status,
171
+ incoming: inTask.status,
172
+ severity: "error",
173
+ autoResolvable: false,
174
+ });
175
+ }
176
+ }
177
+ }
178
+
179
+ // ── Resource deleted (agent/team/workflow no longer in current) ──
180
+ if (current) {
181
+ const currentTeam = typeof current.team === "string" ? current.team : undefined;
182
+ const currentWorkflow = typeof current.workflow === "string" ? current.workflow : undefined;
183
+ const incomingTeam = typeof incoming.team === "string" ? incoming.team : undefined;
184
+ const incomingWorkflow = typeof incoming.workflow === "string" ? incoming.workflow : undefined;
185
+
186
+ if (incomingTeam && currentTeam && incomingTeam !== currentTeam) {
187
+ conflicts.push({
188
+ kind: "resource_deleted",
189
+ path: `team/${incomingTeam}`,
190
+ existing: currentTeam,
191
+ incoming: incomingTeam,
192
+ severity: "warning",
193
+ autoResolvable: true,
194
+ });
195
+ }
196
+
197
+ if (incomingWorkflow && currentWorkflow && incomingWorkflow !== currentWorkflow) {
198
+ conflicts.push({
199
+ kind: "resource_deleted",
200
+ path: `workflow/${incomingWorkflow}`,
201
+ existing: currentWorkflow,
202
+ incoming: incomingWorkflow,
203
+ severity: "warning",
204
+ autoResolvable: true,
205
+ });
206
+ }
207
+
208
+ // Check agent references from tasks vs current tasks
209
+ if (currentTasks && currentTasks.length > 0) {
210
+ const currentAgents = new Set(
211
+ extractTaskLikes(currentTasks)
212
+ .map((t) => t.agent)
213
+ .filter((a): a is string => a !== undefined),
214
+ );
215
+ const incomingTaskList = extractTaskLikes(incomingTasks);
216
+ for (const inTask of incomingTaskList) {
217
+ if (
218
+ inTask.agent &&
219
+ inTask.id !== "" &&
220
+ currentAgents.size > 0 &&
221
+ !currentAgents.has(inTask.agent) &&
222
+ // Only flag if there's a matching task id (agent was reassigned)
223
+ extractTaskLikes(currentTasks).some((ct) => ct.id === inTask.id)
224
+ ) {
225
+ conflicts.push({
226
+ kind: "resource_deleted",
227
+ path: `agent/${inTask.agent}`,
228
+ existing: "not in current run",
229
+ incoming: inTask.agent,
230
+ severity: "warning",
231
+ autoResolvable: true,
232
+ });
233
+ }
234
+ }
235
+ }
236
+ }
237
+
238
+ return buildReport(conflicts);
239
+ }
240
+
241
+ // ── Resume Conflict Detection ──────────────────────────────────────────
242
+
243
+ export interface SuspendedState {
244
+ tasks: unknown[];
245
+ artifacts: string[];
246
+ }
247
+
248
+ export interface CurrentState {
249
+ changedFiles: string[];
250
+ taskStatuses: Record<string, string>;
251
+ }
252
+
253
+ /**
254
+ * Detect conflicts when resuming a suspended run against current filesystem state.
255
+ *
256
+ * Checks:
257
+ * - **file_overwrite**: files changed since suspension.
258
+ * - **state_mismatch**: task statuses changed externally.
259
+ */
260
+ export function detectResumeConflicts(
261
+ suspendedState: SuspendedState,
262
+ currentState: CurrentState,
263
+ ): ConflictReport {
264
+ const conflicts: Conflict[] = [];
265
+ const { tasks: suspendedTasks, artifacts: suspendedArtifacts } = suspendedState;
266
+ const { changedFiles, taskStatuses } = currentState;
267
+
268
+ // ── File overwrite ───────────────────────────────────────────────
269
+ const changedSet = new Set(changedFiles);
270
+ for (const artifactPath of suspendedArtifacts) {
271
+ if (changedSet.has(artifactPath)) {
272
+ conflicts.push({
273
+ kind: "file_overwrite",
274
+ path: artifactPath,
275
+ existing: "modified since suspension",
276
+ incoming: "expected unchanged",
277
+ severity: "error",
278
+ autoResolvable: false,
279
+ });
280
+ }
281
+ }
282
+
283
+ // ── State mismatch ───────────────────────────────────────────────
284
+ const suspendedTaskList = extractTaskLikes(suspendedTasks);
285
+ for (const task of suspendedTaskList) {
286
+ const currentStatus = taskStatuses[task.id];
287
+ if (currentStatus !== undefined && currentStatus !== task.status) {
288
+ conflicts.push({
289
+ kind: "state_mismatch",
290
+ path: `tasks/${task.id}`,
291
+ existing: currentStatus,
292
+ incoming: task.status,
293
+ severity: "error",
294
+ autoResolvable: false,
295
+ });
296
+ }
297
+ }
298
+
299
+ return buildReport(conflicts);
300
+ }
301
+
302
+ // ── Conflict Resolution ────────────────────────────────────────────────
303
+
304
+ /**
305
+ * Apply a resolution strategy to a conflict.
306
+ *
307
+ * - **skip**: skip the conflicting item (always resolves).
308
+ * - **overwrite**: replace existing with incoming (resolves for `file_overwrite`
309
+ * and `schema_drift`; does not resolve `state_mismatch` or `resource_deleted`).
310
+ * - **merge**: attempt merge — resolves `state_mismatch` with merged status;
311
+ * resolves others conditionally.
312
+ */
313
+ export function resolveConflict(
314
+ conflict: Conflict,
315
+ strategy: ConflictStrategy,
316
+ ): ConflictResolution {
317
+ switch (strategy) {
318
+ case "skip":
319
+ return { resolved: true, action: `Skipped ${conflict.path}` };
320
+
321
+ case "overwrite":
322
+ if (conflict.kind === "file_overwrite" || conflict.kind === "schema_drift") {
323
+ return { resolved: true, action: `Overwritten ${conflict.path} with incoming value` };
324
+ }
325
+ return {
326
+ resolved: false,
327
+ action: `Cannot overwrite ${conflict.kind} at ${conflict.path}; manual resolution required`,
328
+ };
329
+
330
+ case "merge":
331
+ if (conflict.kind === "state_mismatch") {
332
+ return {
333
+ resolved: true,
334
+ action: `Merged ${conflict.path}: kept existing=${conflict.existing ?? "?"}, incoming=${conflict.incoming ?? "?"}`,
335
+ };
336
+ }
337
+ if (conflict.kind === "resource_deleted") {
338
+ return {
339
+ resolved: true,
340
+ action: `Merged ${conflict.path}: using incoming resource reference`,
341
+ };
342
+ }
343
+ if (conflict.kind === "file_overwrite") {
344
+ return {
345
+ resolved: true,
346
+ action: `Merged ${conflict.path}: kept both versions`,
347
+ };
348
+ }
349
+ if (conflict.kind === "schema_drift") {
350
+ return {
351
+ resolved: true,
352
+ action: `Merged ${conflict.path}: using incoming schema version`,
353
+ };
354
+ }
355
+ return {
356
+ resolved: false,
357
+ action: `Cannot merge ${conflict.kind} at ${conflict.path}`,
358
+ };
359
+ }
360
+ }
@@ -26,12 +26,14 @@ export interface DiagnosticReport {
26
26
  }
27
27
 
28
28
  const SECRET_KEY_PATTERN = /(token|key|password|secret|credential|auth)/i;
29
+ const ENV_DEBUG_ALLOWLIST = /^(PI_CREW_|PI_TEAMS_|PI_.*HOME|NODE_ENV|NODE_VERSION|OS|PROCESSOR|TERM|LANG|HOME|USERPROFILE|APPDATA|PLATFORM|ARCH|WIN32|DOCKER|CI|VERBOSE|DEBUG|NO_COLOR|FORCE_COLOR|NPM_CONFIG|npm_)/i;
29
30
 
30
31
  function envRedacted(): Record<string, string> {
31
32
  const output: Record<string, string> = {};
32
33
  for (const [key, value] of Object.entries(process.env)) {
33
34
  if (SECRET_KEY_PATTERN.test(key)) output[key] = "***";
34
- else if (typeof value === "string") output[key] = value;
35
+ else if (typeof value === "string" && ENV_DEBUG_ALLOWLIST.test(key)) output[key] = value;
36
+ // All other env vars are omitted to prevent leaking sensitive paths or system topology.
35
37
  }
36
38
  return output;
37
39
  }
@@ -1,35 +1,35 @@
1
- import type { AgentConfig } from "../agents/agent-config.ts";
2
- import type { TeamRunManifest, TeamTaskState } from "../state/types.ts";
3
- import type { TeamConfig } from "../teams/team-config.ts";
4
- import type { WorkflowConfig } from "../workflows/workflow-config.ts";
5
-
6
- export function isDirectRun(manifest: Pick<TeamRunManifest, "team" | "workflow">): boolean {
7
- return manifest.workflow === "direct-agent";
8
- }
9
-
10
- export function directTeamAndWorkflowFromRun(manifest: TeamRunManifest, tasks: TeamTaskState[], agents: AgentConfig[]): { team: TeamConfig; workflow: WorkflowConfig } | undefined {
11
- if (!isDirectRun(manifest)) return undefined;
12
- const firstTask = tasks[0];
13
- const agentName = firstTask?.agent ?? (manifest.team.replace(/^direct-/, "") || "executor");
14
- const agent = agents.find((candidate) => candidate.name === agentName);
15
- const role = firstTask?.role ?? "agent";
16
- const stepId = firstTask?.stepId ?? "01_agent";
17
- return {
18
- team: {
19
- name: manifest.team,
20
- description: `Direct subagent run for ${agentName}`,
21
- source: "builtin",
22
- filePath: "<generated>",
23
- roles: [{ name: role, agent: agentName, description: agent?.description }],
24
- defaultWorkflow: "direct-agent",
25
- workspaceMode: manifest.workspaceMode,
26
- },
27
- workflow: {
28
- name: manifest.workflow ?? "direct-agent",
29
- description: `Direct task for ${agentName}`,
30
- source: "builtin",
31
- filePath: "<generated>",
32
- steps: [{ id: stepId, role, task: "{goal}", model: firstTask?.model }],
33
- },
34
- };
35
- }
1
+ import type { AgentConfig } from "../agents/agent-config.ts";
2
+ import type { TeamRunManifest, TeamTaskState } from "../state/types.ts";
3
+ import type { TeamConfig } from "../teams/team-config.ts";
4
+ import type { WorkflowConfig } from "../workflows/workflow-config.ts";
5
+
6
+ export function isDirectRun(manifest: Pick<TeamRunManifest, "team" | "workflow">): boolean {
7
+ return manifest.workflow === "direct-agent";
8
+ }
9
+
10
+ export function directTeamAndWorkflowFromRun(manifest: TeamRunManifest, tasks: TeamTaskState[], agents: AgentConfig[]): { team: TeamConfig; workflow: WorkflowConfig } | undefined {
11
+ if (!isDirectRun(manifest)) return undefined;
12
+ const firstTask = tasks[0];
13
+ const agentName = firstTask?.agent ?? (manifest.team.replace(/^direct-/, "") || "executor");
14
+ const agent = agents.find((candidate) => candidate.name === agentName);
15
+ const role = firstTask?.role ?? "agent";
16
+ const stepId = firstTask?.stepId ?? "01_agent";
17
+ return {
18
+ team: {
19
+ name: manifest.team,
20
+ description: `Direct subagent run for ${agentName}`,
21
+ source: "builtin",
22
+ filePath: "<generated>",
23
+ roles: [{ name: role, agent: agentName, description: agent?.description }],
24
+ defaultWorkflow: "direct-agent",
25
+ workspaceMode: manifest.workspaceMode,
26
+ },
27
+ workflow: {
28
+ name: manifest.workflow ?? "direct-agent",
29
+ description: `Direct task for ${agentName}`,
30
+ source: "builtin",
31
+ filePath: "<generated>",
32
+ steps: [{ id: stepId, role, task: "{goal}", model: firstTask?.model }],
33
+ },
34
+ };
35
+ }
@@ -53,7 +53,9 @@ export function bridgeEventFromJsonEvent(runId: string, taskId: string, event: u
53
53
  if (typeof record.toolName === "string") result.toolName = record.toolName;
54
54
  if (record.args && typeof record.args === "object") {
55
55
  try {
56
- result.toolArgs = JSON.stringify(record.args).slice(0, 200);
56
+ const json = JSON.stringify(record.args);
57
+ // Truncate at a JSON boundary to avoid breaking structure
58
+ result.toolArgs = json.length > 200 ? json.slice(0, 197) + "..." : json;
57
59
  } catch {
58
60
  /* skip */
59
61
  }