pi-crew 0.1.51 → 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 (239) hide show
  1. package/CHANGELOG.md +56 -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 +13 -1
  48. package/src/config/drift-detector.ts +211 -0
  49. package/src/config/markers.ts +327 -0
  50. package/src/config/resilient-parser.ts +108 -0
  51. package/src/config/suggestions.ts +74 -0
  52. package/src/extension/cross-extension-rpc.ts +103 -94
  53. package/src/extension/project-init.ts +21 -1
  54. package/src/extension/register.ts +45 -14
  55. package/src/extension/registration/commands.ts +77 -8
  56. package/src/extension/registration/subagent-tools.ts +10 -1
  57. package/src/extension/registration/team-tool.ts +10 -1
  58. package/src/extension/registration/viewers.ts +48 -34
  59. package/src/extension/run-bundle-schema.ts +89 -89
  60. package/src/extension/run-import.ts +25 -1
  61. package/src/extension/run-index.ts +5 -1
  62. package/src/extension/run-maintenance.ts +142 -68
  63. package/src/extension/team-manager-command.ts +10 -1
  64. package/src/extension/team-tool/doctor.ts +28 -3
  65. package/src/extension/team-tool/handle-settings.ts +195 -188
  66. package/src/extension/team-tool/inspect.ts +41 -41
  67. package/src/extension/team-tool/intent-policy.ts +42 -42
  68. package/src/extension/team-tool/lifecycle-actions.ts +27 -8
  69. package/src/extension/team-tool/plan.ts +19 -19
  70. package/src/extension/team-tool/run.ts +12 -1
  71. package/src/extension/team-tool.ts +11 -1
  72. package/src/i18n.ts +184 -184
  73. package/src/observability/exporters/otlp-exporter.ts +92 -77
  74. package/src/prompt/prompt-runtime.ts +72 -72
  75. package/src/runtime/agent-memory.ts +72 -72
  76. package/src/runtime/agent-observability.ts +114 -114
  77. package/src/runtime/async-marker.ts +26 -26
  78. package/src/runtime/attention-events.ts +28 -28
  79. package/src/runtime/auto-resume.ts +100 -0
  80. package/src/runtime/background-runner.ts +11 -1
  81. package/src/runtime/cancellation-token.ts +89 -89
  82. package/src/runtime/cancellation.ts +61 -61
  83. package/src/runtime/capability-inventory.ts +116 -116
  84. package/src/runtime/child-pi.ts +7 -2
  85. package/src/runtime/compaction-summary.ts +271 -0
  86. package/src/runtime/completion-guard.ts +190 -190
  87. package/src/runtime/crash-recovery.ts +33 -0
  88. package/src/runtime/delta-conflict.ts +360 -0
  89. package/src/runtime/direct-run.ts +35 -35
  90. package/src/runtime/foreground-control.ts +82 -82
  91. package/src/runtime/green-contract.ts +46 -46
  92. package/src/runtime/group-join.ts +106 -106
  93. package/src/runtime/heartbeat-gradient.ts +28 -28
  94. package/src/runtime/heartbeat-watcher.ts +124 -124
  95. package/src/runtime/iteration-hooks.ts +262 -0
  96. package/src/runtime/live-agent-control.ts +88 -88
  97. package/src/runtime/live-control-realtime.ts +36 -36
  98. package/src/runtime/live-extension-bridge.ts +150 -150
  99. package/src/runtime/live-irc.ts +92 -92
  100. package/src/runtime/live-session-health.ts +100 -100
  101. package/src/runtime/loop-gates.ts +129 -0
  102. package/src/runtime/metric-parser.ts +40 -0
  103. package/src/runtime/notebook-helpers.ts +90 -90
  104. package/src/runtime/orphan-sentinel.ts +7 -7
  105. package/src/runtime/parallel-research.ts +44 -44
  106. package/src/runtime/phase-progress.ts +217 -0
  107. package/src/runtime/pi-args.ts +38 -11
  108. package/src/runtime/pi-json-output.ts +111 -111
  109. package/src/runtime/pi-spawn.ts +57 -7
  110. package/src/runtime/policy-engine.ts +79 -79
  111. package/src/runtime/post-checks.ts +122 -0
  112. package/src/runtime/progress-event-coalescer.ts +43 -43
  113. package/src/runtime/prose-compressor.ts +164 -164
  114. package/src/runtime/recovery-recipes.ts +74 -74
  115. package/src/runtime/result-extractor.ts +121 -121
  116. package/src/runtime/role-permission.ts +39 -39
  117. package/src/runtime/sensitive-paths.ts +2 -2
  118. package/src/runtime/session-resources.ts +25 -25
  119. package/src/runtime/session-snapshot.ts +59 -59
  120. package/src/runtime/session-usage.ts +79 -79
  121. package/src/runtime/sidechain-output.ts +29 -29
  122. package/src/runtime/stream-preview.ts +177 -177
  123. package/src/runtime/supervisor-contact.ts +59 -59
  124. package/src/runtime/task-display.ts +38 -38
  125. package/src/runtime/task-graph.ts +207 -0
  126. package/src/runtime/task-quality.ts +207 -0
  127. package/src/runtime/task-runner/capabilities.ts +78 -78
  128. package/src/runtime/task-runner/live-executor.ts +7 -1
  129. package/src/runtime/task-runner/progress.ts +119 -119
  130. package/src/runtime/task-runner/prompt-pipeline.ts +64 -64
  131. package/src/runtime/task-runner/result-utils.ts +14 -14
  132. package/src/runtime/task-runner/run-projection.ts +103 -103
  133. package/src/runtime/task-runner/state-helpers.ts +22 -22
  134. package/src/runtime/team-runner.ts +117 -7
  135. package/src/runtime/worker-heartbeat.ts +21 -21
  136. package/src/runtime/worker-startup.ts +57 -57
  137. package/src/runtime/workflow-state.ts +187 -0
  138. package/src/runtime/workspace-tree.ts +298 -298
  139. package/src/schema/config-schema.ts +11 -0
  140. package/src/schema/validation-types.ts +148 -0
  141. package/src/skills/skill-templates.ts +374 -0
  142. package/src/state/active-run-registry.ts +35 -11
  143. package/src/state/atomic-write.ts +33 -26
  144. package/src/state/contracts.ts +1 -0
  145. package/src/state/event-reconstructor.ts +217 -0
  146. package/src/state/locks.ts +2 -13
  147. package/src/state/mailbox.ts +4 -3
  148. package/src/state/state-store.ts +32 -14
  149. package/src/state/task-claims.ts +44 -44
  150. package/src/state/types.ts +9 -0
  151. package/src/state/usage.ts +29 -29
  152. package/src/subagents/async-entry.ts +1 -1
  153. package/src/subagents/index.ts +3 -3
  154. package/src/subagents/live/control.ts +1 -1
  155. package/src/subagents/live/manager.ts +1 -1
  156. package/src/subagents/live/realtime.ts +1 -1
  157. package/src/subagents/live/session-runtime.ts +1 -1
  158. package/src/subagents/manager.ts +1 -1
  159. package/src/subagents/spawn.ts +1 -1
  160. package/src/teams/team-serializer.ts +38 -38
  161. package/src/types/diff.d.ts +18 -18
  162. package/src/ui/crew-footer.ts +101 -101
  163. package/src/ui/crew-select-list.ts +111 -111
  164. package/src/ui/crew-widget.ts +5 -2
  165. package/src/ui/dashboard-panes/cancellation-pane.ts +42 -42
  166. package/src/ui/dashboard-panes/capability-pane.ts +59 -59
  167. package/src/ui/dashboard-panes/mailbox-pane.ts +35 -35
  168. package/src/ui/dashboard-panes/metrics-pane.ts +34 -34
  169. package/src/ui/dashboard-panes/progress-pane.ts +11 -0
  170. package/src/ui/dynamic-border.ts +25 -25
  171. package/src/ui/layout-primitives.ts +106 -106
  172. package/src/ui/loaders.ts +158 -158
  173. package/src/ui/render-coalescer.ts +51 -51
  174. package/src/ui/render-diff.ts +119 -119
  175. package/src/ui/render-scheduler.ts +143 -143
  176. package/src/ui/run-action-dispatcher.ts +10 -1
  177. package/src/ui/spinner.ts +17 -17
  178. package/src/ui/status-colors.ts +58 -58
  179. package/src/ui/syntax-highlight.ts +116 -116
  180. package/src/ui/transcript-entries.ts +258 -258
  181. package/src/utils/completion-dedupe.ts +63 -63
  182. package/src/utils/frontmatter.ts +68 -68
  183. package/src/utils/git.ts +262 -262
  184. package/src/utils/ids.ts +17 -17
  185. package/src/utils/incremental-reader.ts +104 -104
  186. package/src/utils/names.ts +27 -27
  187. package/src/utils/redaction.ts +44 -44
  188. package/src/utils/safe-paths.ts +47 -47
  189. package/src/utils/scan-cache.ts +136 -136
  190. package/src/utils/sleep.ts +40 -26
  191. package/src/utils/task-name-generator.ts +337 -337
  192. package/src/workflows/validate-workflow.ts +40 -40
  193. package/src/worktree/branch-freshness.ts +45 -45
  194. package/teams/default.team.md +12 -12
  195. package/teams/fast-fix.team.md +11 -11
  196. package/teams/implementation.team.md +18 -18
  197. package/teams/parallel-research.team.md +14 -14
  198. package/teams/research.team.md +11 -11
  199. package/teams/review.team.md +12 -12
  200. package/workflows/default.workflow.md +30 -29
  201. package/workflows/fast-fix.workflow.md +23 -22
  202. package/workflows/implementation.workflow.md +43 -43
  203. package/workflows/parallel-research.workflow.md +46 -46
  204. package/workflows/research.workflow.md +22 -22
  205. package/workflows/review.workflow.md +30 -30
  206. package/docs/refactor-tasks-phase3.md +0 -394
  207. package/docs/refactor-tasks-phase4.md +0 -564
  208. package/docs/refactor-tasks-phase5.md +0 -402
  209. package/docs/refactor-tasks-phase6.md +0 -662
  210. package/docs/refactor-tasks.md +0 -1484
  211. package/docs/research/AGENT-EXECUTION-ARCHITECTURE.md +0 -261
  212. package/docs/research/AGENT-LIFECYCLE-COMPARISON.md +0 -111
  213. package/docs/research/AUDIT_OH_MY_PI.md +0 -261
  214. package/docs/research/AUDIT_PI_CREW.md +0 -457
  215. package/docs/research/CAVEMAN-DEEP-RESEARCH.md +0 -281
  216. package/docs/research/COMPARISON_OH_MY_PI_VS_PI_CREW.md +0 -264
  217. package/docs/research/DEEP-RESEARCH-PI-POWERBAR.md +0 -343
  218. package/docs/research/DEEP_RESEARCH_SUBAGENT_ARCHITECTURE.md +0 -480
  219. package/docs/research/GAP_CLOSURE_IMPLEMENTATION_PLAN.md +0 -354
  220. package/docs/research/IMPLEMENTATION_PLAN.md +0 -385
  221. package/docs/research/LIVE-SESSION-PRODUCTION-READY-PLAN.md +0 -502
  222. package/docs/research/OH-MY-PI-DEEP-RESEARCH-v14.7.6.md +0 -266
  223. package/docs/research/REMAINING-GAPS-PLAN.md +0 -363
  224. package/docs/research/SESSION-SUMMARY-2026-05-08.md +0 -146
  225. package/docs/research/UI-RESPONSIVENESS-AUDIT.md +0 -173
  226. package/docs/research-awesome-agent-skills-distillation.md +0 -100
  227. package/docs/research-extension-examples.md +0 -297
  228. package/docs/research-extension-system.md +0 -324
  229. package/docs/research-oh-my-pi-distillation.md +0 -369
  230. package/docs/research-optimization-plan.md +0 -548
  231. package/docs/research-phase10-distillation.md +0 -199
  232. package/docs/research-phase11-distillation.md +0 -201
  233. package/docs/research-phase8-operator-experience-plan.md +0 -819
  234. package/docs/research-phase9-observability-reliability-plan.md +0 -1190
  235. package/docs/research-pi-coding-agent.md +0 -357
  236. package/docs/research-source-pi-crew-reference.md +0 -174
  237. package/docs/research-ui-optimization-plan.md +0 -480
  238. package/docs/source-runtime-refactor-map.md +0 -107
  239. 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"];
@@ -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
+ }