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
@@ -1,121 +1,121 @@
1
- /**
2
- * Structured Result Extractor — attempts to extract structured data from worker output.
3
- * Tries multiple extraction strategies before falling back to raw text.
4
- */
5
- export interface ExtractedResult {
6
- /** Whether structured data was successfully extracted */
7
- structured: boolean;
8
- /** Parsed structured data (if structured=true) */
9
- data: unknown;
10
- /** Raw text output (always available) */
11
- rawText: string;
12
- /** Error message if extraction was attempted but failed */
13
- error?: string;
14
- }
15
-
16
- /**
17
- * Extract structured result from raw worker output text.
18
- * Tries strategies in order: direct JSON, fenced JSON, key-value markers.
19
- */
20
- export function extractStructuredResult(raw: string, _schema?: Record<string, unknown>): ExtractedResult {
21
- const trimmed = raw.trim();
22
- if (!trimmed) {
23
- return { structured: false, data: null, rawText: raw };
24
- }
25
-
26
- // Strategy 1: Direct JSON parse (entire output is JSON)
27
- const directResult = tryDirectJson(trimmed);
28
- if (directResult !== undefined) {
29
- return { structured: true, data: directResult, rawText: raw };
30
- }
31
-
32
- // Strategy 2: Extract from ```json ... ``` fence
33
- const fencedResult = tryFencedJson(trimmed);
34
- if (fencedResult !== undefined) {
35
- return { structured: true, data: fencedResult, rawText: raw };
36
- }
37
-
38
- // Strategy 3: Extract from markers like "RESULT:" or "OUTPUT:"
39
- const markerResult = tryMarkerExtraction(trimmed);
40
- if (markerResult !== undefined) {
41
- return { structured: true, data: markerResult, rawText: raw };
42
- }
43
-
44
- return { structured: false, data: null, rawText: raw };
45
- }
46
-
47
- function tryDirectJson(text: string): unknown | undefined {
48
- if (!text.startsWith("{") && !text.startsWith("[")) return undefined;
49
- try {
50
- return JSON.parse(text);
51
- } catch {
52
- return undefined;
53
- }
54
- }
55
-
56
- function tryFencedJson(text: string): unknown | undefined {
57
- const match = text.match(/```json\s*\n([\s\S]*?)\n\s*```/);
58
- if (!match?.[1]) return undefined;
59
- try {
60
- return JSON.parse(match[1].trim());
61
- } catch {
62
- return undefined;
63
- }
64
- }
65
-
66
- function tryMarkerExtraction(text: string): unknown | undefined {
67
- // Try to find JSON after common markers
68
- const markers = ["RESULT:", "OUTPUT:", "ANSWER:", "### Result\n", "## Output\n"];
69
- for (const marker of markers) {
70
- const idx = text.indexOf(marker);
71
- if (idx === -1) continue;
72
- const after = text.slice(idx + marker.length).trim();
73
- // Try JSON parse on text after marker
74
- if (after.startsWith("{") || after.startsWith("[")) {
75
- try {
76
- return JSON.parse(after);
77
- } catch {
78
- // Try to find just the JSON object/array
79
- const jsonEnd = findMatchingBracket(after);
80
- if (jsonEnd > 0) {
81
- try {
82
- return JSON.parse(after.slice(0, jsonEnd));
83
- } catch {
84
- continue;
85
- }
86
- }
87
- }
88
- }
89
- }
90
- return undefined;
91
- }
92
-
93
- function findMatchingBracket(text: string): number {
94
- const openChar = text[0];
95
- const closeChar = openChar === "{" ? "}" : "]";
96
- let depth = 0;
97
- let inString = false;
98
- let escape = false;
99
- for (let i = 0; i < text.length; i++) {
100
- const ch = text[i];
101
- if (escape) {
102
- escape = false;
103
- continue;
104
- }
105
- if (ch === "\\") {
106
- escape = true;
107
- continue;
108
- }
109
- if (ch === '"') {
110
- inString = !inString;
111
- continue;
112
- }
113
- if (inString) continue;
114
- if (ch === openChar) depth++;
115
- if (ch === closeChar) {
116
- depth--;
117
- if (depth === 0) return i + 1;
118
- }
119
- }
120
- return -1;
121
- }
1
+ /**
2
+ * Structured Result Extractor — attempts to extract structured data from worker output.
3
+ * Tries multiple extraction strategies before falling back to raw text.
4
+ */
5
+ export interface ExtractedResult {
6
+ /** Whether structured data was successfully extracted */
7
+ structured: boolean;
8
+ /** Parsed structured data (if structured=true) */
9
+ data: unknown;
10
+ /** Raw text output (always available) */
11
+ rawText: string;
12
+ /** Error message if extraction was attempted but failed */
13
+ error?: string;
14
+ }
15
+
16
+ /**
17
+ * Extract structured result from raw worker output text.
18
+ * Tries strategies in order: direct JSON, fenced JSON, key-value markers.
19
+ */
20
+ export function extractStructuredResult(raw: string, _schema?: Record<string, unknown>): ExtractedResult {
21
+ const trimmed = raw.trim();
22
+ if (!trimmed) {
23
+ return { structured: false, data: null, rawText: raw };
24
+ }
25
+
26
+ // Strategy 1: Direct JSON parse (entire output is JSON)
27
+ const directResult = tryDirectJson(trimmed);
28
+ if (directResult !== undefined) {
29
+ return { structured: true, data: directResult, rawText: raw };
30
+ }
31
+
32
+ // Strategy 2: Extract from ```json ... ``` fence
33
+ const fencedResult = tryFencedJson(trimmed);
34
+ if (fencedResult !== undefined) {
35
+ return { structured: true, data: fencedResult, rawText: raw };
36
+ }
37
+
38
+ // Strategy 3: Extract from markers like "RESULT:" or "OUTPUT:"
39
+ const markerResult = tryMarkerExtraction(trimmed);
40
+ if (markerResult !== undefined) {
41
+ return { structured: true, data: markerResult, rawText: raw };
42
+ }
43
+
44
+ return { structured: false, data: null, rawText: raw };
45
+ }
46
+
47
+ function tryDirectJson(text: string): unknown | undefined {
48
+ if (!text.startsWith("{") && !text.startsWith("[")) return undefined;
49
+ try {
50
+ return JSON.parse(text);
51
+ } catch {
52
+ return undefined;
53
+ }
54
+ }
55
+
56
+ function tryFencedJson(text: string): unknown | undefined {
57
+ const match = text.match(/```json\s*\n([\s\S]*?)\n\s*```/);
58
+ if (!match?.[1]) return undefined;
59
+ try {
60
+ return JSON.parse(match[1].trim());
61
+ } catch {
62
+ return undefined;
63
+ }
64
+ }
65
+
66
+ function tryMarkerExtraction(text: string): unknown | undefined {
67
+ // Try to find JSON after common markers
68
+ const markers = ["RESULT:", "OUTPUT:", "ANSWER:", "### Result\n", "## Output\n"];
69
+ for (const marker of markers) {
70
+ const idx = text.indexOf(marker);
71
+ if (idx === -1) continue;
72
+ const after = text.slice(idx + marker.length).trim();
73
+ // Try JSON parse on text after marker
74
+ if (after.startsWith("{") || after.startsWith("[")) {
75
+ try {
76
+ return JSON.parse(after);
77
+ } catch {
78
+ // Try to find just the JSON object/array
79
+ const jsonEnd = findMatchingBracket(after);
80
+ if (jsonEnd > 0) {
81
+ try {
82
+ return JSON.parse(after.slice(0, jsonEnd));
83
+ } catch {
84
+ continue;
85
+ }
86
+ }
87
+ }
88
+ }
89
+ }
90
+ return undefined;
91
+ }
92
+
93
+ function findMatchingBracket(text: string): number {
94
+ const openChar = text[0];
95
+ const closeChar = openChar === "{" ? "}" : "]";
96
+ let depth = 0;
97
+ let inString = false;
98
+ let escape = false;
99
+ for (let i = 0; i < text.length; i++) {
100
+ const ch = text[i];
101
+ if (escape) {
102
+ escape = false;
103
+ continue;
104
+ }
105
+ if (ch === "\\") {
106
+ escape = true;
107
+ continue;
108
+ }
109
+ if (ch === '"') {
110
+ inString = !inString;
111
+ continue;
112
+ }
113
+ if (inString) continue;
114
+ if (ch === openChar) depth++;
115
+ if (ch === closeChar) {
116
+ depth--;
117
+ if (depth === 0) return i + 1;
118
+ }
119
+ }
120
+ return -1;
121
+ }
@@ -1,39 +1,39 @@
1
- export type RolePermissionMode = "read_only" | "workspace_write" | "danger_full_access" | "explicit_confirm";
2
-
3
- const READ_ONLY_ROLES = new Set(["explorer", "reviewer", "security-reviewer", "verifier", "analyst", "critic", "planner", "writer"]);
4
- const WRITE_ROLES = new Set(["executor", "test-engineer"]);
5
- const READ_ONLY_COMMANDS = new Set(["cat", "head", "tail", "less", "more", "wc", "ls", "find", "grep", "rg", "awk", "sed", "echo", "printf", "which", "where", "whoami", "pwd", "env", "printenv", "date", "df", "du", "uname", "file", "stat", "diff", "sort", "uniq", "tr", "cut", "paste", "test", "true", "false", "type", "readlink", "realpath", "basename", "dirname", "sha256sum", "md5sum", "xxd", "hexdump", "od", "strings", "tree", "jq", "git", "gh"]);
6
-
7
- export interface PermissionCheckResult {
8
- allowed: boolean;
9
- mode: RolePermissionMode;
10
- reason?: string;
11
- }
12
-
13
- export function permissionForRole(role: string): RolePermissionMode {
14
- if (READ_ONLY_ROLES.has(role)) return "read_only";
15
- if (WRITE_ROLES.has(role)) return "workspace_write";
16
- return "workspace_write";
17
- }
18
-
19
- export function isReadOnlyCommand(command: string): boolean {
20
- const first = command.trim().split(/\s+/)[0]?.split(/[\\/]/).pop() ?? "";
21
- return READ_ONLY_COMMANDS.has(first) && !/\s(-i|--in-place)\b|\s>{1,2}\s|\brm\b|\bmv\b|\bcp\b|\b(?:npm|pnpm|yarn|bun)\s+(install|add|ci|remove)\b|\bgit\s+(commit|push|merge|rebase|reset|checkout|clean)\b/.test(command);
22
- }
23
-
24
- export function checkRolePermission(role: string, command: string): PermissionCheckResult {
25
- const mode = permissionForRole(role);
26
- if (mode === "read_only" && !isReadOnlyCommand(command)) return { allowed: false, mode, reason: `Role '${role}' is read-only and command may modify state.` };
27
- return { allowed: true, mode };
28
- }
29
-
30
- export function currentCrewRole(env: NodeJS.ProcessEnv = process.env): string | undefined {
31
- return env.PI_CREW_ROLE?.trim() || env.PI_TEAMS_ROLE?.trim() || undefined;
32
- }
33
-
34
- export function checkSubagentSpawnPermission(role: string | undefined): PermissionCheckResult {
35
- if (!role) return { allowed: true, mode: "workspace_write" };
36
- const mode = permissionForRole(role);
37
- if (mode === "read_only") return { allowed: false, mode, reason: `Role '${role}' is read-only and cannot spawn additional subagents.` };
38
- return { allowed: true, mode };
39
- }
1
+ export type RolePermissionMode = "read_only" | "workspace_write" | "danger_full_access" | "explicit_confirm";
2
+
3
+ const READ_ONLY_ROLES = new Set(["explorer", "reviewer", "security-reviewer", "verifier", "analyst", "critic", "planner", "writer"]);
4
+ const WRITE_ROLES = new Set(["executor", "test-engineer"]);
5
+ const READ_ONLY_COMMANDS = new Set(["cat", "head", "tail", "less", "more", "wc", "ls", "find", "grep", "rg", "awk", "sed", "echo", "printf", "which", "where", "whoami", "pwd", "env", "printenv", "date", "df", "du", "uname", "file", "stat", "diff", "sort", "uniq", "tr", "cut", "paste", "test", "true", "false", "type", "readlink", "realpath", "basename", "dirname", "sha256sum", "md5sum", "xxd", "hexdump", "od", "strings", "tree", "jq", "git", "gh"]);
6
+
7
+ export interface PermissionCheckResult {
8
+ allowed: boolean;
9
+ mode: RolePermissionMode;
10
+ reason?: string;
11
+ }
12
+
13
+ export function permissionForRole(role: string): RolePermissionMode {
14
+ if (READ_ONLY_ROLES.has(role)) return "read_only";
15
+ if (WRITE_ROLES.has(role)) return "workspace_write";
16
+ return "workspace_write";
17
+ }
18
+
19
+ export function isReadOnlyCommand(command: string): boolean {
20
+ const first = command.trim().split(/\s+/)[0]?.split(/[\\/]/).pop() ?? "";
21
+ return READ_ONLY_COMMANDS.has(first) && !/\s(-i|--in-place)\b|\s>{1,2}\s|\brm\b|\bmv\b|\bcp\b|\b(?:npm|pnpm|yarn|bun)\s+(install|add|ci|remove)\b|\bgit\s+(commit|push|merge|rebase|reset|checkout|clean)\b/.test(command);
22
+ }
23
+
24
+ export function checkRolePermission(role: string, command: string): PermissionCheckResult {
25
+ const mode = permissionForRole(role);
26
+ if (mode === "read_only" && !isReadOnlyCommand(command)) return { allowed: false, mode, reason: `Role '${role}' is read-only and command may modify state.` };
27
+ return { allowed: true, mode };
28
+ }
29
+
30
+ export function currentCrewRole(env: NodeJS.ProcessEnv = process.env): string | undefined {
31
+ return env.PI_CREW_ROLE?.trim() || env.PI_TEAMS_ROLE?.trim() || undefined;
32
+ }
33
+
34
+ export function checkSubagentSpawnPermission(role: string | undefined): PermissionCheckResult {
35
+ if (!role) return { allowed: true, mode: "workspace_write" };
36
+ const mode = permissionForRole(role);
37
+ if (mode === "read_only") return { allowed: false, mode, reason: `Role '${role}' is read-only and cannot spawn additional subagents.` };
38
+ return { allowed: true, mode };
39
+ }
@@ -12,10 +12,10 @@ import * as path from "node:path";
12
12
 
13
13
  /** Basenames that almost certainly hold secrets or PII */
14
14
  const SENSITIVE_BASENAMES = /\.(?:env|pem|key|p12|pfx|crt|cer|jks|keystore|asc|gpg)(?:\..+)?$/i;
15
- const SENSITIVE_EXACT = /^(?:\.env|\.netrc|\.npmrc|\.pypirc|credentials|secrets?|passwords?|id_(?:rsa|dsa|ecdsa|ed25519)(?:\.pub)?|authorized_keys|known_hosts)$/i;
15
+ const SENSITIVE_EXACT = /^(?:\.env|\.netrc|\.npmrc|\.pypirc|credentials|secrets?|passwords?|id_(?:rsa|dsa|ecdsa|ed25519)(?:\.pub)?|authorized_keys|known_hosts|jwt\.json|session\.cookie|\.token)$/i;
16
16
 
17
17
  /** Path components that indicate sensitive directories */
18
- const SENSITIVE_DIRS = new Set([".ssh", ".aws", ".gnupg", ".kube", ".docker", ".config/gcloud"]);
18
+ const SENSITIVE_DIRS = new Set([".ssh", ".aws", ".gnupg", ".kube", ".docker", ".config/gcloud", ".config/gh"]);
19
19
 
20
20
  /** Name tokens that suggest sensitive content */
21
21
  const SENSITIVE_TOKENS = ["secret", "credential", "password", "passwd", "apikey", "accesskey", "token", "privatekey"];
@@ -50,7 +50,7 @@ export function isSensitivePath(filePath: string): boolean {
50
50
  // any token matches. For substring matching in the normalized form,
51
51
  // we require the token to end at a segment boundary or string end.
52
52
  // This matches 'secret', 'secrets' but NOT 'secretary'.
53
- const words = lower.split(/[_\-\s.]+/).filter(Boolean);
53
+ const words = lower.split(/[_\-\s.\W]+/).filter(Boolean);
54
54
  const normalized = lower.replace(/[_\-\s.]/g, "");
55
55
  for (const token of SENSITIVE_TOKENS) {
56
56
  // Check individual words — exact match or token is prefix and word is <= token+2 chars
@@ -1,25 +1,25 @@
1
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
2
- import { logInternalError } from "../utils/internal-error.ts";
3
-
4
- /**
5
- * Try to register a cleanup function with Pi's session resource cleanup API (v0.72+).
6
- * Falls back to returning undefined if the API is not available.
7
- *
8
- * The returned function (if defined) can be called to unregister the cleanup.
9
- */
10
- export function tryRegisterSessionCleanup(pi: ExtensionAPI, cleanup: () => void): (() => void) | undefined {
11
- const api = pi as unknown as Record<string, unknown>;
12
- const registerFn = api["registerSessionResourceCleanup"];
13
- if (typeof registerFn === "function") {
14
- try {
15
- const unregister = (registerFn as (fn: () => void) => (() => void) | void)(cleanup);
16
- if (typeof unregister === "function") return unregister;
17
- // API returned void — cleanup is registered but cannot be unregistered
18
- return undefined;
19
- } catch (error) {
20
- logInternalError("session-resources.register", error);
21
- return undefined;
22
- }
23
- }
24
- return undefined;
25
- }
1
+ import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
2
+ import { logInternalError } from "../utils/internal-error.ts";
3
+
4
+ /**
5
+ * Try to register a cleanup function with Pi's session resource cleanup API (v0.72+).
6
+ * Falls back to returning undefined if the API is not available.
7
+ *
8
+ * The returned function (if defined) can be called to unregister the cleanup.
9
+ */
10
+ export function tryRegisterSessionCleanup(pi: ExtensionAPI, cleanup: () => void): (() => void) | undefined {
11
+ const api = pi as unknown as Record<string, unknown>;
12
+ const registerFn = api["registerSessionResourceCleanup"];
13
+ if (typeof registerFn === "function") {
14
+ try {
15
+ const unregister = (registerFn as (fn: () => void) => (() => void) | void)(cleanup);
16
+ if (typeof unregister === "function") return unregister;
17
+ // API returned void — cleanup is registered but cannot be unregistered
18
+ return undefined;
19
+ } catch (error) {
20
+ logInternalError("session-resources.register", error);
21
+ return undefined;
22
+ }
23
+ }
24
+ return undefined;
25
+ }
@@ -1,59 +1,59 @@
1
- import type { TeamRunManifest, TeamTaskState } from "../state/types.ts";
2
-
3
- /**
4
- * Creates a lightweight snapshot of task state for event emission.
5
- * Prevents mutation-during-callback issues by copying relevant fields.
6
- */
7
- export function snapshotTaskState(task: TeamTaskState): Readonly<TeamTaskState> {
8
- return {
9
- ...task,
10
- dependsOn: [...task.dependsOn],
11
- usage: task.usage ? { ...task.usage } : undefined,
12
- agentProgress: task.agentProgress ? { ...task.agentProgress } : undefined,
13
- heartbeat: task.heartbeat ? { ...task.heartbeat } : undefined,
14
- modelAttempts: task.modelAttempts?.map((a) => ({ ...a })),
15
- modelRouting: task.modelRouting ? { ...task.modelRouting } : undefined,
16
- claim: task.claim ? { ...task.claim } : undefined,
17
- checkpoint: task.checkpoint ? { ...task.checkpoint } : undefined,
18
- attempts: task.attempts?.map((a) => ({ ...a })),
19
- worktree: task.worktree ? { ...task.worktree } : undefined,
20
- };
21
- }
22
-
23
- /**
24
- * Session state snapshot for persistence before session switches.
25
- * Captures the minimal set of data needed to resume operations.
26
- */
27
- export interface SessionStateSnapshot {
28
- /** ISO timestamp of the snapshot */
29
- capturedAt: string;
30
- /** Active run IDs at time of snapshot */
31
- activeRunIds: string[];
32
- /** Number of pending deliveries */
33
- pendingDeliveryCount: number;
34
- /** Session generation counter */
35
- sessionGeneration: number;
36
- /** Summary of active tasks by status */
37
- taskSummary: Record<string, number>;
38
- }
39
-
40
- /**
41
- * Create a session state snapshot for pre-switch persistence.
42
- */
43
- export function createSessionSnapshot(
44
- activeRuns: TeamRunManifest[],
45
- pendingDeliveryCount: number,
46
- sessionGeneration: number,
47
- ): SessionStateSnapshot {
48
- const taskSummary: Record<string, number> = {};
49
- for (const run of activeRuns) {
50
- taskSummary[run.status] = (taskSummary[run.status] ?? 0) + 1;
51
- }
52
- return {
53
- capturedAt: new Date().toISOString(),
54
- activeRunIds: activeRuns.map((r) => r.runId),
55
- pendingDeliveryCount,
56
- sessionGeneration,
57
- taskSummary,
58
- };
59
- }
1
+ import type { TeamRunManifest, TeamTaskState } from "../state/types.ts";
2
+
3
+ /**
4
+ * Creates a lightweight snapshot of task state for event emission.
5
+ * Prevents mutation-during-callback issues by copying relevant fields.
6
+ */
7
+ export function snapshotTaskState(task: TeamTaskState): Readonly<TeamTaskState> {
8
+ return {
9
+ ...task,
10
+ dependsOn: [...task.dependsOn],
11
+ usage: task.usage ? { ...task.usage } : undefined,
12
+ agentProgress: task.agentProgress ? { ...task.agentProgress } : undefined,
13
+ heartbeat: task.heartbeat ? { ...task.heartbeat } : undefined,
14
+ modelAttempts: task.modelAttempts?.map((a) => ({ ...a })),
15
+ modelRouting: task.modelRouting ? { ...task.modelRouting } : undefined,
16
+ claim: task.claim ? { ...task.claim } : undefined,
17
+ checkpoint: task.checkpoint ? { ...task.checkpoint } : undefined,
18
+ attempts: task.attempts?.map((a) => ({ ...a })),
19
+ worktree: task.worktree ? { ...task.worktree } : undefined,
20
+ };
21
+ }
22
+
23
+ /**
24
+ * Session state snapshot for persistence before session switches.
25
+ * Captures the minimal set of data needed to resume operations.
26
+ */
27
+ export interface SessionStateSnapshot {
28
+ /** ISO timestamp of the snapshot */
29
+ capturedAt: string;
30
+ /** Active run IDs at time of snapshot */
31
+ activeRunIds: string[];
32
+ /** Number of pending deliveries */
33
+ pendingDeliveryCount: number;
34
+ /** Session generation counter */
35
+ sessionGeneration: number;
36
+ /** Summary of active tasks by status */
37
+ taskSummary: Record<string, number>;
38
+ }
39
+
40
+ /**
41
+ * Create a session state snapshot for pre-switch persistence.
42
+ */
43
+ export function createSessionSnapshot(
44
+ activeRuns: TeamRunManifest[],
45
+ pendingDeliveryCount: number,
46
+ sessionGeneration: number,
47
+ ): SessionStateSnapshot {
48
+ const taskSummary: Record<string, number> = {};
49
+ for (const run of activeRuns) {
50
+ taskSummary[run.status] = (taskSummary[run.status] ?? 0) + 1;
51
+ }
52
+ return {
53
+ capturedAt: new Date().toISOString(),
54
+ activeRunIds: activeRuns.map((r) => r.runId),
55
+ pendingDeliveryCount,
56
+ sessionGeneration,
57
+ taskSummary,
58
+ };
59
+ }