pi-crew 0.9.68 → 0.10.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (253) hide show
  1. package/CHANGELOG.md +222 -0
  2. package/NOTICE.md +21 -0
  3. package/README.md +44 -2
  4. package/agents/analyst.md +1 -1
  5. package/agents/cold-verifier.md +3 -1
  6. package/agents/critic.md +1 -1
  7. package/agents/executor.md +1 -1
  8. package/agents/explorer.md +1 -1
  9. package/agents/planner.md +1 -1
  10. package/agents/reviewer.md +1 -1
  11. package/agents/security-reviewer.md +1 -1
  12. package/agents/test-engineer.md +1 -1
  13. package/agents/verifier.md +1 -1
  14. package/agents/writer.md +1 -1
  15. package/dist/index.mjs +68113 -60774
  16. package/docs/README.md +2 -0
  17. package/docs/actions-reference.md +31 -0
  18. package/docs/commands-reference.md +17 -6
  19. package/docs/resource-formats.md +13 -0
  20. package/package.json +4 -2
  21. package/schema.json +503 -91
  22. package/scripts/resource-sampler.mjs +36 -2
  23. package/skills/requirements-to-task-packet/SKILL.md +26 -0
  24. package/skills/widget-rendering/SKILL.md +7 -7
  25. package/src/agents/agent-config.ts +2 -1
  26. package/src/agents/discover-agents.ts +23 -14
  27. package/src/config/config-merge.ts +183 -0
  28. package/src/config/config-validation.ts +687 -0
  29. package/src/config/config.ts +22 -864
  30. package/src/config/defaults.ts +43 -2
  31. package/src/config/drift-detector.ts +1 -1
  32. package/src/config/env-vars.ts +691 -0
  33. package/src/config/role-tools.ts +11 -9
  34. package/src/config/sanitize-project-config.ts +172 -0
  35. package/src/config/types.ts +49 -1
  36. package/src/extension/async-notifier.ts +25 -2
  37. package/src/extension/crew-cleanup.ts +13 -0
  38. package/src/extension/crew-vibes/config.ts +2 -1
  39. package/src/extension/crew-vibes/footer.ts +19 -0
  40. package/src/extension/crew-vibes/index.ts +11 -1
  41. package/src/extension/plan-orchestrate.ts +132 -0
  42. package/src/extension/register.ts +8 -0
  43. package/src/extension/registration/command-registration.ts +1 -0
  44. package/src/extension/registration/commands/dashboard.ts +158 -0
  45. package/src/extension/registration/commands/index.ts +35 -0
  46. package/src/extension/registration/commands/manage.ts +303 -0
  47. package/src/extension/registration/commands/run.ts +228 -0
  48. package/src/extension/registration/commands/shared.ts +639 -0
  49. package/src/extension/registration/commands/status.ts +60 -0
  50. package/src/extension/registration/commands.ts +13 -1224
  51. package/src/extension/registration/foreground-run-controller.ts +10 -2
  52. package/src/extension/registration/lifecycle-handlers.ts +178 -17
  53. package/src/extension/registration/runtime-cleanup.ts +23 -5
  54. package/src/extension/registration/subagent-tools.ts +218 -9
  55. package/src/extension/registration/team-tool.ts +5 -1
  56. package/src/extension/registration/ui.ts +5 -4
  57. package/src/extension/rpc-hmac.ts +5 -3
  58. package/src/extension/team-tool/api/heartbeat.ts +47 -10
  59. package/src/extension/team-tool/api/plan-approval.ts +9 -0
  60. package/src/extension/team-tool/api/task-claims.ts +109 -40
  61. package/src/extension/team-tool/cancel.ts +84 -50
  62. package/src/extension/team-tool/dispatch/index.ts +1 -0
  63. package/src/extension/team-tool/dispatch/run.ts +4 -1
  64. package/src/extension/team-tool/doctor.ts +103 -1
  65. package/src/extension/team-tool/orchestrate.ts +66 -1
  66. package/src/extension/team-tool/plans.ts +192 -0
  67. package/src/extension/team-tool/respond.ts +197 -65
  68. package/src/extension/team-tool/run-deadline.ts +35 -3
  69. package/src/extension/team-tool/run-intent.ts +63 -0
  70. package/src/extension/team-tool/run.ts +74 -20
  71. package/src/extension/team-tool/status.ts +84 -26
  72. package/src/extension/team-tool.ts +11 -2
  73. package/src/hooks/registry.ts +1 -6
  74. package/src/i18n.ts +9 -0
  75. package/src/prompt/prompt-runtime.ts +521 -2
  76. package/src/prompt/worker-events-channel.ts +173 -0
  77. package/src/runtime/README.md +8 -8
  78. package/src/runtime/async-runner.ts +7 -3
  79. package/src/runtime/background-runner.ts +42 -14
  80. package/src/runtime/broker/broker-issuer.ts +9 -2
  81. package/src/runtime/broker/crew-broker-tokens.ts +43 -6
  82. package/src/runtime/broker/crew-broker.ts +838 -10
  83. package/src/runtime/broker/wait-status-cache.ts +157 -0
  84. package/src/runtime/budget-enforcement.ts +281 -0
  85. package/src/runtime/child-pi/child-pi-constants.ts +8 -0
  86. package/src/runtime/child-pi/child-pi-spawn.ts +60 -14
  87. package/src/runtime/child-pi/child-pi-streams.ts +21 -1
  88. package/src/runtime/child-pi/child-pi-timers.ts +324 -0
  89. package/src/runtime/child-pi/child-pi.ts +97 -201
  90. package/src/runtime/child-pi/mock-fixtures.ts +16 -2
  91. package/src/runtime/crew-agent-records.ts +259 -14
  92. package/src/runtime/delegate-spawn.ts +148 -0
  93. package/src/runtime/detached-run-results.ts +90 -0
  94. package/src/runtime/deterministic-ast.ts +2 -1
  95. package/src/runtime/dispatch-batch.ts +945 -0
  96. package/src/runtime/finalize-run.ts +557 -0
  97. package/src/runtime/goal-workflow/adaptive-plan.ts +116 -15
  98. package/src/runtime/goal-workflow/dynamic-workflow-runner.ts +8 -3
  99. package/src/runtime/goal-workflow/goal-state-store.ts +1 -1
  100. package/src/runtime/group-join.ts +11 -125
  101. package/src/runtime/live-session/live-session-runtime.ts +26 -1
  102. package/src/runtime/merge-gate.ts +32 -10
  103. package/src/runtime/merge-loop.ts +130 -0
  104. package/src/runtime/model/model-budget-summary.ts +53 -0
  105. package/src/runtime/model/model-fallback.ts +36 -2
  106. package/src/runtime/model/pi-args.ts +10 -0
  107. package/src/runtime/model/provider-extensions.ts +10 -0
  108. package/src/runtime/orphan-worker-registry.ts +1 -1
  109. package/src/runtime/output/output-validator.ts +45 -0
  110. package/src/runtime/parent-guard.ts +3 -1
  111. package/src/runtime/peer-dep.ts +2 -1
  112. package/src/runtime/per-write-validator.ts +0 -5
  113. package/src/runtime/pi-spawn.ts +61 -15
  114. package/src/runtime/plan-approval.ts +125 -0
  115. package/src/runtime/plan-replan.ts +151 -0
  116. package/src/runtime/process-status.ts +16 -1
  117. package/src/runtime/recovery/checkpoint.ts +0 -18
  118. package/src/runtime/recovery/crash-recovery.ts +111 -46
  119. package/src/runtime/run-tracker.ts +77 -10
  120. package/src/runtime/scheduler-context.ts +98 -0
  121. package/src/runtime/scheduling/coalesce-tasks.ts +5 -0
  122. package/src/runtime/scheduling/global-worker-cap.ts +2 -1
  123. package/src/runtime/scheduling/nested-slots.ts +70 -0
  124. package/src/runtime/scheduling/run-coalesced-task-group.ts +64 -13
  125. package/src/runtime/scheduling/task-graph-scheduler.ts +0 -10
  126. package/src/runtime/settings-store.ts +219 -0
  127. package/src/runtime/spawn-policy.ts +217 -0
  128. package/src/runtime/stale-reconciler.ts +87 -6
  129. package/src/runtime/subagent-manager.ts +25 -1
  130. package/src/runtime/task-output-context.ts +230 -9
  131. package/src/runtime/task-packet.ts +23 -1
  132. package/src/runtime/task-runner/child-executor.ts +106 -7
  133. package/src/runtime/task-runner/post-execution.ts +125 -1
  134. package/src/runtime/task-runner/pre-execution.ts +39 -1
  135. package/src/runtime/task-runner/prompt-builder.ts +51 -1
  136. package/src/runtime/task-runner/retrieval-orchestrator.ts +72 -18
  137. package/src/runtime/task-runner/spec-evidence.ts +403 -0
  138. package/src/runtime/task-runner/state-helpers.ts +26 -24
  139. package/src/runtime/task-runner.ts +11 -0
  140. package/src/runtime/team-runner.ts +132 -1673
  141. package/src/runtime/verification/spec-sandbox.ts +255 -0
  142. package/src/runtime/verification/verification-gates.ts +3 -2
  143. package/src/runtime/verification/verification-worktree.ts +2 -1
  144. package/src/runtime/workflow-phase-advance.ts +100 -0
  145. package/src/runtime/workspace-tree.ts +9 -0
  146. package/src/schema/config-schema.ts +66 -26
  147. package/src/schema/sensitive-config-paths.ts +64 -0
  148. package/src/schema/team-tool-schema.ts +13 -3
  149. package/src/state/README.md +4 -10
  150. package/src/state/atomic-write.ts +20 -3
  151. package/src/state/contracts.ts +38 -0
  152. package/src/state/coordination/mailbox.ts +12 -2
  153. package/src/state/event-log/cursor.ts +223 -0
  154. package/src/state/event-log/event-log-rotation.ts +12 -4
  155. package/src/state/event-log/event-log.ts +152 -369
  156. package/src/state/event-log/sequence-cache.ts +373 -0
  157. package/src/state/event-log/worker-atomic-writer.ts +2 -1
  158. package/src/state/stores/active-run-registry.ts +3 -2
  159. package/src/state/stores/manifest-io.ts +237 -0
  160. package/src/state/stores/ownership-map.ts +162 -0
  161. package/src/state/stores/plan-store.ts +241 -0
  162. package/src/state/stores/run-cache.ts +0 -90
  163. package/src/state/stores/spec-store.ts +189 -0
  164. package/src/state/stores/state-store.ts +139 -232
  165. package/src/state/types.ts +199 -0
  166. package/src/ui/dashboard-panes/plan-pane.ts +136 -0
  167. package/src/ui/dashboard-panes/progress-pane.ts +6 -0
  168. package/src/ui/dashboard-panes/transcript-pane.ts +31 -0
  169. package/src/ui/dock-footer.ts +49 -0
  170. package/src/ui/heartbeat-aggregator.ts +9 -1
  171. package/src/ui/inline-panel/agent-pane.ts +375 -0
  172. package/src/ui/inline-panel/agent-transcript.ts +338 -0
  173. package/src/ui/inline-panel/agent-view-overlay.ts +225 -0
  174. package/src/ui/inline-panel/crew-editor.ts +192 -0
  175. package/src/ui/inline-panel/index.ts +290 -0
  176. package/src/ui/inline-panel/panel-rows.ts +37 -0
  177. package/src/ui/inline-panel/panel-selection.ts +157 -0
  178. package/src/ui/inline-panel/panel-store.ts +111 -0
  179. package/src/ui/inline-panel/view-session-store.ts +36 -0
  180. package/src/ui/keybinding-map.ts +54 -13
  181. package/src/ui/pi-ui-compat.ts +9 -0
  182. package/src/ui/powerbar-publisher.ts +52 -1
  183. package/src/ui/run-dashboard.ts +31 -5
  184. package/src/ui/run-snapshot-cache.ts +57 -30
  185. package/src/ui/snapshot-types.ts +6 -1
  186. package/src/ui/widget/index.ts +176 -22
  187. package/src/ui/widget/task-list.ts +198 -0
  188. package/src/ui/widget/widget-formatters.ts +240 -4
  189. package/src/ui/widget/widget-renderer.ts +243 -38
  190. package/src/ui/widget/widget-types.ts +11 -0
  191. package/src/utils/child-process-shield.ts +106 -0
  192. package/src/utils/file-coalescer.ts +0 -4
  193. package/src/utils/fs-errno.ts +66 -0
  194. package/src/utils/fs-watch.ts +1 -1
  195. package/src/utils/internal-error.ts +3 -1
  196. package/src/utils/paths.ts +11 -3
  197. package/src/utils/redaction.ts +7 -0
  198. package/src/utils/safe-abort.ts +45 -0
  199. package/src/utils/task-name-generator.ts +1 -8
  200. package/src/workflows/discover-workflows.ts +20 -2
  201. package/src/workflows/validate-workflow.ts +7 -1
  202. package/src/workflows/workflow-config.ts +17 -0
  203. package/src/workflows/workflow-serializer.ts +3 -0
  204. package/src/worktree/worktree-manager.ts +22 -0
  205. package/workflows/default.workflow.md +36 -26
  206. package/workflows/strict-fast-fix.workflow.md +26 -0
  207. package/src/agents/agent-search.ts +0 -98
  208. package/src/benchmark/benchmark-runner.ts +0 -313
  209. package/src/benchmark/feedback-loop.ts +0 -73
  210. package/src/config/resilient-parser.ts +0 -117
  211. package/src/extension/crew-vibes/cat-frames.ts +0 -18
  212. package/src/extension/result-watcher.ts +0 -139
  213. package/src/observability/exporters/prometheus-exporter.ts +0 -54
  214. package/src/observability/metric-retention.ts +0 -64
  215. package/src/runtime/compaction/compaction-summary.ts +0 -278
  216. package/src/runtime/errors/crew-errors.ts +0 -162
  217. package/src/runtime/live-session/intercom-bridge.ts +0 -187
  218. package/src/runtime/loop-gates.ts +0 -128
  219. package/src/runtime/metric-parser.ts +0 -36
  220. package/src/runtime/output/stream-preview.ts +0 -184
  221. package/src/runtime/output/tool-progress.ts +0 -278
  222. package/src/runtime/phase-tracker.ts +0 -385
  223. package/src/runtime/pipeline-runner.ts +0 -523
  224. package/src/runtime/process/process-lifecycle.ts +0 -491
  225. package/src/runtime/recovery/retry-runner.ts +0 -330
  226. package/src/runtime/run-drift.ts +0 -219
  227. package/src/runtime/task-quality.ts +0 -199
  228. package/src/runtime/task-runner/run-projection.ts +0 -128
  229. package/src/runtime/verification/post-checks.ts +0 -142
  230. package/src/state/coordination/schedule.ts +0 -166
  231. package/src/state/event-log/jsonl-writer.ts +0 -115
  232. package/src/state/hook-instinct-bridge.ts +0 -94
  233. package/src/state/hook-integrations.ts +0 -51
  234. package/src/state/session-state-map.ts +0 -51
  235. package/src/state/stores/blob-store.ts +0 -308
  236. package/src/state/stores/instinct-store.ts +0 -275
  237. package/src/state/stores/observation-store.ts +0 -176
  238. package/src/state/tiered-eval.ts +0 -480
  239. package/src/state/types-eval.ts +0 -58
  240. package/src/tools/safe-bash-extension.ts +0 -54
  241. package/src/tools/safe-bash.ts +0 -505
  242. package/src/ui/agent-management-overlay.ts +0 -160
  243. package/src/ui/crew-footer.ts +0 -102
  244. package/src/ui/crew-select-list.ts +0 -114
  245. package/src/ui/dashboard-panes/capability-pane.ts +0 -77
  246. package/src/ui/transcript-entries.ts +0 -256
  247. package/src/utils/conflict-detect.ts +0 -721
  248. package/src/utils/fingerprint.ts +0 -180
  249. package/src/utils/gh-protocol.ts +0 -556
  250. package/src/utils/project-detector.ts +0 -160
  251. package/src/utils/sse-parser.ts +0 -131
  252. package/src/workflows/cost-estimator.ts +0 -34
  253. package/src/workflows/intermediate-store.ts +0 -166
@@ -1,199 +0,0 @@
1
- /**
2
- * Task quality scoring — simple additive heuristic for evaluating task
3
- * completion quality based on diagnostics, metrics, artifacts, and duration.
4
- *
5
- * Distilled from pi-autoresearch's quality scoring pattern.
6
- */
7
- import * as fs from "node:fs";
8
- import * as path from "node:path";
9
- import type { TeamTaskState } from "../state/types.ts";
10
-
11
- /** Letter grade for task quality. */
12
- export type QualityGrade = "A" | "B" | "C" | "D";
13
-
14
- /** Breakdown of individual quality criteria. */
15
- export interface QualityBreakdown {
16
- /** Task has a non-empty diagnostics object. */
17
- hasDiagnostics: boolean;
18
- /** Task has a non-empty metrics object. */
19
- hasMetrics: boolean;
20
- /** Task produced files in the artifacts directory. */
21
- producedArtifacts: boolean;
22
- /** Task has a non-empty result/description. */
23
- hasDescription: boolean;
24
- /** Task duration is reasonable (> 0 and < 1 hour). */
25
- durationReasonable: boolean;
26
- }
27
-
28
- /** Scored quality result for a task. */
29
- export interface TaskQualityScore {
30
- /** Numeric score (0–5). */
31
- score: number;
32
- /** Individual criterion breakdown. */
33
- breakdown: QualityBreakdown;
34
- /** Letter grade based on score thresholds. */
35
- grade: QualityGrade;
36
- }
37
-
38
- /** One hour in milliseconds. */
39
- const ONE_HOUR_MS = 3_600_000;
40
-
41
- /**
42
- * Determine the letter grade for a given numeric score.
43
- *
44
- * A: 4–5, B: 3, C: 2, D: 0–1
45
- */
46
- function scoreToGrade(score: number): QualityGrade {
47
- if (score >= 4) return "A";
48
- if (score === 3) return "B";
49
- if (score === 2) return "C";
50
- return "D";
51
- }
52
-
53
- /**
54
- * Check whether the artifacts directory contains files for the given task.
55
- *
56
- * Looks for a subdirectory named after the task ID, or files containing
57
- * the task ID prefix in the artifacts directory.
58
- */
59
- function hasTaskArtifacts(taskId: string, artifactsDir: string): boolean {
60
- try {
61
- if (!fs.existsSync(artifactsDir)) return false;
62
-
63
- // Check for a task-specific subdirectory
64
- const taskDir = path.join(artifactsDir, taskId);
65
- if (fs.existsSync(taskDir)) {
66
- const stat = fs.statSync(taskDir);
67
- if (stat.isDirectory()) {
68
- const entries = fs.readdirSync(taskDir);
69
- return entries.length > 0;
70
- }
71
- }
72
-
73
- // Check for files containing the task ID prefix
74
- const entries = fs.readdirSync(artifactsDir);
75
- const safePrefix = taskId.replace(/[^a-zA-Z0-9_-]/g, "");
76
- return entries.some((entry) => entry.includes(safePrefix));
77
- } catch {
78
- return false;
79
- }
80
- }
81
-
82
- /**
83
- * Check if a task result string is a non-empty description.
84
- *
85
- * A result is considered descriptive if any of these sources have non-empty content:
86
- * - task.resultArtifact exists with a path
87
- * - task.error is a non-empty string (workers often set this with result info)
88
- * - task.verification.satisfied is true
89
- * - task.diagnostics contains a 'result' string
90
- */
91
- function isResultDescriptive(task: TeamTaskState): boolean {
92
- // Check resultArtifact — presence of a result artifact indicates output was produced
93
- if (task.resultArtifact?.path) return true;
94
-
95
- // Check error field — workers often put result info here
96
- if (typeof task.error === "string" && task.error.trim().length > 0) return true;
97
-
98
- // Check verification — satisfied verification indicates meaningful output
99
- if (task.verification?.satisfied) return true;
100
-
101
- // Check diagnostics for an explicit result string
102
- if (
103
- task.diagnostics &&
104
- typeof task.diagnostics === "object" &&
105
- typeof task.diagnostics.result === "string" &&
106
- (task.diagnostics.result as string).trim().length > 0
107
- )
108
- return true;
109
-
110
- return false;
111
- }
112
-
113
- /**
114
- * Check if the task duration is reasonable (started, finished, > 0, < 1 hour).
115
- */
116
- function isDurationReasonable(task: TeamTaskState): boolean {
117
- if (!task.startedAt || !task.finishedAt) return false;
118
-
119
- const started = new Date(task.startedAt).getTime();
120
- const finished = new Date(task.finishedAt).getTime();
121
-
122
- if (Number.isNaN(started) || Number.isNaN(finished)) return false;
123
-
124
- const duration = finished - started;
125
- return duration > 0 && duration < ONE_HOUR_MS;
126
- }
127
-
128
- /**
129
- * Compute the quality score for a completed task.
130
- *
131
- * Uses simple additive scoring across 5 criteria:
132
- * - hasDiagnostics: +1 if task.diagnostics exists and has keys
133
- * - hasMetrics: +1 if task.metrics exists and has keys
134
- * - producedArtifacts: +1 if artifactsDir has files for this task
135
- * - hasDescription: +1 if task has a non-empty result/description
136
- * - durationReasonable: +1 if task has both startedAt and finishedAt, duration > 0 and < 1 hour
137
- *
138
- * @param task - The task state to evaluate
139
- * @param artifactsDir - Optional path to the run artifacts directory
140
- * @returns TaskQualityScore with numeric score, breakdown, and letter grade
141
- */
142
- export function computeTaskQuality(task: TeamTaskState, artifactsDir?: string): TaskQualityScore {
143
- const hasDiagnostics =
144
- task.diagnostics !== undefined && typeof task.diagnostics === "object" && Object.keys(task.diagnostics).length > 0;
145
-
146
- const hasMetrics = task.metrics !== undefined && typeof task.metrics === "object" && Object.keys(task.metrics).length > 0;
147
-
148
- const producedArtifacts = artifactsDir !== undefined && hasTaskArtifacts(task.id, artifactsDir);
149
-
150
- const hasDescription = isResultDescriptive(task);
151
-
152
- const durationReasonable = isDurationReasonable(task);
153
-
154
- const breakdown: QualityBreakdown = {
155
- hasDiagnostics,
156
- hasMetrics,
157
- producedArtifacts,
158
- hasDescription,
159
- durationReasonable,
160
- };
161
-
162
- const score =
163
- (hasDiagnostics ? 1 : 0) +
164
- (hasMetrics ? 1 : 0) +
165
- (producedArtifacts ? 1 : 0) +
166
- (hasDescription ? 1 : 0) +
167
- (durationReasonable ? 1 : 0);
168
-
169
- return {
170
- score,
171
- breakdown,
172
- grade: scoreToGrade(score),
173
- };
174
- }
175
-
176
- /** Human-readable labels for each quality criterion. */
177
- const CRITERION_LABELS: Record<keyof QualityBreakdown, string> = {
178
- hasDiagnostics: "diagnostics",
179
- hasMetrics: "metrics",
180
- producedArtifacts: "artifacts",
181
- hasDescription: "description",
182
- durationReasonable: "duration",
183
- };
184
-
185
- /**
186
- * Format a quality score as a human-readable one-line string.
187
- *
188
- * Format: "Quality: B (3/5: diagnostics, metrics, description)"
189
- *
190
- * @param score - The quality score to format
191
- * @returns Formatted string
192
- */
193
- export function formatQualityScore(score: TaskQualityScore): string {
194
- const metCriteria = Object.entries(score.breakdown)
195
- .filter(([, met]) => met)
196
- .map(([key]) => CRITERION_LABELS[key as keyof QualityBreakdown]);
197
-
198
- return `Quality: ${score.grade} (${score.score}/5${metCriteria.length > 0 ? `: ${metCriteria.join(", ")}` : ""})`;
199
- }
@@ -1,128 +0,0 @@
1
- import type { MailboxMessage } from "../../state/coordination/mailbox.ts";
2
- import type { ArtifactDescriptor, TeamRunManifest, TeamTaskState } from "../../state/types.ts";
3
- import { sanitizeTaskText } from "../task-packet.ts";
4
-
5
- export interface RunProjectionSource {
6
- kind: "events" | "mailbox" | "artifacts" | "ui_metadata" | "runtime_metadata";
7
- bounded: boolean;
8
- reference?: string;
9
- }
10
-
11
- export interface RunProjectionResult {
12
- sources: RunProjectionSource[];
13
- summary: string;
14
- injectedAsContext: boolean;
15
- }
16
-
17
- /**
18
- * Transform run context before a worker starts.
19
- * Builds a bounded projection of durable history that will be available
20
- * to the worker as reference context, not as instructions.
21
- *
22
- * Rules:
23
- * - Durable history retains events, mailbox, artifacts, UI/runtime metadata.
24
- * - Worker prompt gets a bounded projection (truncated/summarized).
25
- * - UI/runtime events are not prompt text unless explicitly selected.
26
- */
27
- export function transformRunContextBeforeWorkerStart(input: {
28
- manifest: TeamRunManifest;
29
- tasks: TeamTaskState[];
30
- pendingMailbox: MailboxMessage[];
31
- artifacts: ArtifactDescriptor[];
32
- maxEvents?: number;
33
- maxMailboxMessages?: number;
34
- maxArtifactRefs?: number;
35
- }): RunProjectionResult {
36
- const maxEvents = input.maxEvents ?? 20;
37
- const maxMailbox = input.maxMailboxMessages ?? 10;
38
- const maxArtifacts = input.maxArtifactRefs ?? 15;
39
-
40
- const sources: RunProjectionSource[] = [];
41
- const lines: string[] = [];
42
-
43
- // Project a bounded slice of task history
44
- const completedTasks = input.tasks.filter((t) => t.status === "completed" || t.status === "failed");
45
- if (completedTasks.length > 0) {
46
- const tasks = completedTasks.slice(0, maxEvents);
47
- sources.push({
48
- kind: "events",
49
- bounded: true,
50
- reference: `tasks:${tasks.length}/${completedTasks.length}`,
51
- });
52
- lines.push(`Previous tasks (${tasks.length}/${completedTasks.length}):`);
53
- for (const task of tasks) {
54
- lines.push(`- ${task.id}: ${task.status}${task.error ? ` (${task.error})` : ""}`);
55
- }
56
- }
57
-
58
- // Project pending mailbox that is relevant to this worker
59
- if (input.pendingMailbox.length > 0) {
60
- const messages = input.pendingMailbox.slice(0, maxMailbox);
61
- sources.push({
62
- kind: "mailbox",
63
- bounded: true,
64
- reference: `mailbox:${messages.length}/${input.pendingMailbox.length}`,
65
- });
66
- lines.push(`Pending messages (${messages.length}/${input.pendingMailbox.length}):`);
67
- for (const msg of messages) {
68
- const safeBody = sanitizeTaskText(msg.body.slice(0, 100));
69
- // VULN-3: mailbox bodies are cross-worker controllable — give the model a
70
- // structural signal that this is untrusted content (sanitizeTaskText catches
71
- // directive injection; the demarcation frames natural-language injection).
72
- lines.push(`- ${msg.kind ?? "message"}: <untrusted_data source="mailbox">${safeBody}</untrusted_data>`);
73
- }
74
- }
75
-
76
- // Project artifact references (not content)
77
- if (input.artifacts.length > 0) {
78
- const artifacts = input.artifacts.slice(0, maxArtifacts);
79
- sources.push({
80
- kind: "artifacts",
81
- bounded: true,
82
- reference: `artifacts:${artifacts.length}/${input.artifacts.length}`,
83
- });
84
- lines.push(`Available artifacts (${artifacts.length}/${input.artifacts.length}):`);
85
- for (const art of artifacts) {
86
- lines.push(`- ${art.kind} (${art.producer})`);
87
- }
88
- }
89
-
90
- // Metadata markers — not injected as prompt instructions
91
- sources.push({
92
- kind: "ui_metadata",
93
- bounded: false,
94
- reference: "excluded_from_prompt",
95
- });
96
- sources.push({
97
- kind: "runtime_metadata",
98
- bounded: false,
99
- reference: "excluded_from_prompt",
100
- });
101
-
102
- return {
103
- sources,
104
- summary: lines.join("\n"),
105
- injectedAsContext: true,
106
- };
107
- }
108
-
109
- /**
110
- * Convert run history to a bounded worker prompt section.
111
- * Same logic as transformRunContextBeforeWorkerStart but returns
112
- * the prompt text directly for embedding in the worker prompt.
113
- */
114
- export function convertRunHistoryToWorkerPrompt(input: {
115
- manifest: TeamRunManifest;
116
- tasks: TeamTaskState[];
117
- pendingMailbox: MailboxMessage[];
118
- artifacts: ArtifactDescriptor[];
119
- }): string {
120
- const projection = transformRunContextBeforeWorkerStart(input);
121
- if (!projection.summary) return "";
122
- return [
123
- "## Run Context (bounded projection)",
124
- projection.summary,
125
- "",
126
- `Projection sources: ${projection.sources.map((s) => s.kind).join(", ")}`,
127
- ].join("\n");
128
- }
@@ -1,142 +0,0 @@
1
- /**
2
- * Post-task backpressure checks — runs a configurable shell script after
3
- * task completion to verify workspace health before proceeding.
4
- *
5
- * Distilled from pi-autoresearch's post-check / backpressure pattern.
6
- */
7
- import { execFileSync } from "node:child_process";
8
- import { WINDOWS_ESSENTIAL_ENV_VARS } from "../../utils/env-allowlist.ts";
9
- import { sanitizeEnvSecrets } from "../../utils/env-filter.ts";
10
- import { resolveShellForScript } from "../../utils/resolve-shell.ts";
11
- import { resolveRealContainedPath } from "../../utils/safe-paths.ts";
12
-
13
- /** Default timeout for post-check scripts (5 minutes). */
14
- const DEFAULT_TIMEOUT_MS = 300_000;
15
-
16
- /** Environment variable name for the post-check script path. */
17
- const POST_CHECK_SCRIPT_ENV = "PI_CREW_POST_CHECK_SCRIPT";
18
-
19
- /**
20
- * Configuration for a post-task check.
21
- */
22
- export interface PostCheckConfig {
23
- /** Path to the shell script to execute. */
24
- scriptPath: string;
25
- /** Timeout in milliseconds. Defaults to 300000 (5 minutes). */
26
- timeoutMs: number;
27
- }
28
-
29
- /**
30
- * Result of running a post-task check.
31
- */
32
- export interface PostCheckResult {
33
- /** Whether the check passed (exit code 0). */
34
- passed: boolean;
35
- /** Combined stdout + stderr from the script. */
36
- output: string;
37
- /** Wall-clock duration of the check in milliseconds. */
38
- durationMs: number;
39
- /** Whether the check timed out. */
40
- timedOut: boolean;
41
- }
42
-
43
- /**
44
- * Resolve the effective post-check script path.
45
- * Prefers config.scriptPath; falls back to the PI_CREW_POST_CHECK_SCRIPT env var.
46
- */
47
- function resolveScriptPath(config: PostCheckConfig): string | undefined {
48
- if (config.scriptPath && config.scriptPath.length > 0) {
49
- return config.scriptPath;
50
- }
51
- return process.env[POST_CHECK_SCRIPT_ENV];
52
- }
53
-
54
- /**
55
- * Run a post-task backpressure check script.
56
- *
57
- * Executes the configured bash script and returns a structured result.
58
- * If no script path is available (neither config nor env var), the check
59
- * passes by default with a note.
60
- *
61
- * **Security note:** The script path is validated to stay within `cwd`.
62
- * Scripts that escape the working directory are rejected.
63
- *
64
- * @param config - Post-check configuration (script path and timeout)
65
- * @param cwd - Working directory for script execution
66
- * @returns PostCheckResult with pass/fail status, output, and timing
67
- */
68
- export async function runPostCheck(config: PostCheckConfig, cwd: string): Promise<PostCheckResult> {
69
- const scriptPath = resolveScriptPath(config);
70
- const timeoutMs = config.timeoutMs > 0 ? config.timeoutMs : DEFAULT_TIMEOUT_MS;
71
-
72
- if (!scriptPath) {
73
- return {
74
- passed: true,
75
- output: "No post-check script configured; skipping.",
76
- durationMs: 0,
77
- timedOut: false,
78
- };
79
- }
80
-
81
- // M-1 fix (code-review 2026-06-23): use the project's safe-path primitive instead
82
- // of a hand-rolled path.resolve + startsWith check. The lexical check passed a
83
- // symlinked cwd/scripts -> /usr/local/bin, but execFileSync followed the symlink
84
- // and executed a script outside the working directory. resolveRealContainedPath
85
- // walks ancestors with O_NOFOLLOW and re-validates after realpath, defeating
86
- // symlink-traversal attacks. Throws if the resolved path escapes cwd.
87
- resolveRealContainedPath(cwd, scriptPath);
88
-
89
- const startTime = Date.now();
90
-
91
- return new Promise<PostCheckResult>((resolve) => {
92
- try {
93
- const { command, args } = resolveShellForScript(scriptPath);
94
- const output = execFileSync(command, args, {
95
- cwd,
96
- timeout: timeoutMs,
97
- encoding: "utf-8",
98
- maxBuffer: 10 * 1024 * 1024, // 10 MB
99
- env: {
100
- ...sanitizeEnvSecrets(process.env, {
101
- allowList: ["PATH", "HOME", "USER", ...WINDOWS_ESSENTIAL_ENV_VARS, "TMPDIR", "LANG", "LC_ALL", "PI_CREW_*"],
102
- }),
103
- PI_CREW_POST_CHECK: "1",
104
- },
105
- });
106
-
107
- const durationMs = Date.now() - startTime;
108
- resolve({
109
- passed: true,
110
- output: output.trim(),
111
- durationMs,
112
- timedOut: false,
113
- });
114
- } catch (error: unknown) {
115
- const durationMs = Date.now() - startTime;
116
-
117
- // Determine if this was a timeout
118
- // execFileSync throws with code 'ETIMEDOUT' or sets killed:true on timeout
119
- const isTimedOut =
120
- Boolean((error as NodeJS.ErrnoException & { killed?: boolean }).killed) ||
121
- (error as NodeJS.ErrnoException).code === "ETIMEDOUT";
122
-
123
- let output = "";
124
- if (error instanceof Error) {
125
- const execError = error as NodeJS.ErrnoException & {
126
- stdout?: string;
127
- stderr?: string;
128
- };
129
- output = [execError.stdout ?? "", execError.stderr ?? "", execError.message ?? ""].join("\n").trim();
130
- } else {
131
- output = String(error);
132
- }
133
-
134
- resolve({
135
- passed: false,
136
- output,
137
- durationMs,
138
- timedOut: isTimedOut,
139
- });
140
- }
141
- });
142
- }
@@ -1,166 +0,0 @@
1
- /**
2
- * schedule.ts — Schedule detection and parsing utilities.
3
- *
4
- * Mirrors pi-subagents3's SubagentScheduler static methods:
5
- * - detectSchedule(): sniff cron / interval / one-shot from string
6
- * - validateCronExpression(): 6-field cron validation
7
- * - parseRelativeTime(): "+10m" → ISO timestamp
8
- * - parseInterval(): "5m" → milliseconds
9
- */
10
- import * as fs from "node:fs";
11
- import * as path from "node:path";
12
- import { logInternalError } from "../../utils/internal-error.ts";
13
- import { atomicWriteJson } from "../atomic-write.ts";
14
-
15
- import type { ScheduledTask, ScheduleStoreData } from "../types.ts";
16
-
17
- export type DetectedSchedule =
18
- | { type: "cron"; normalized: string }
19
- | { type: "interval"; intervalMs: number; normalized: string }
20
- | { type: "once"; normalized: string };
21
-
22
- /** "+10s"/"+5m"/"+1h"/"+2d" → ISO timestamp or null if not a relative time. */
23
- export function parseRelativeTime(s: string): string | null {
24
- const m = s.trim().match(/^\+(\d+)(s|m|h|d)$/);
25
- if (!m) return null;
26
- const ms = parseInt(m[1], 10) * { s: 1000, m: 60_000, h: 3_600_000, d: 86_400_000 }[m[2] as "s" | "m" | "h" | "d"];
27
- return new Date(Date.now() + ms).toISOString();
28
- }
29
-
30
- /** "10s"/"5m"/"1h"/"2d" → milliseconds or null if not an interval. */
31
- export function parseInterval(s: string): number | null {
32
- const m = s.trim().match(/^(\d+)(s|m|h|d)$/);
33
- if (!m) return null;
34
- return parseInt(m[1], 10) * { s: 1000, m: 60_000, h: 3_600_000, d: 86_400_000 }[m[2] as "s" | "m" | "h" | "d"];
35
- }
36
-
37
- /** 6-field cron validation ("second minute hour dom month dow"). */
38
- export function validateCronExpression(expr: string): {
39
- valid: boolean;
40
- error?: string;
41
- } {
42
- const fields = expr.trim().split(/\s+/);
43
- if (fields.length !== 6) {
44
- return {
45
- valid: false,
46
- error: `Cron must have 6 fields (second minute hour dom month dow), got ${fields.length}. Example: "0 0 9 * * 1" for 9am every Monday.`,
47
- };
48
- }
49
- // Basic format check: all fields must be non-empty
50
- if (!fields.every((f) => f.length > 0)) {
51
- return {
52
- valid: false,
53
- error: "Cron expression contains empty fields.",
54
- };
55
- }
56
- // Accept any cron pattern — fail silently for malformed expressions
57
- // (the croner library will reject at execution time)
58
- return { valid: true };
59
- }
60
-
61
- /**
62
- * Sniff a schedule string and tag its type. Throws on invalid input.
63
- * Order matters: relative ("+10m") and interval ("5m") both match digit+unit;
64
- * relative requires the leading "+" to disambiguate.
65
- */
66
- export function detectSchedule(s: string): DetectedSchedule {
67
- const trimmed = s.trim();
68
- // "+10m" — relative one-shot
69
- const rel = parseRelativeTime(trimmed);
70
- if (rel !== null) return { type: "once", normalized: rel };
71
- // "5m" — interval
72
- const ivl = parseInterval(trimmed);
73
- if (ivl !== null) return { type: "interval", intervalMs: ivl, normalized: trimmed };
74
- // ISO timestamp — one-shot. Reject past timestamps.
75
- if (/^\d{4}-\d{2}-\d{2}T/.test(trimmed)) {
76
- const d = new Date(trimmed);
77
- if (!Number.isNaN(d.getTime())) {
78
- if (d.getTime() <= Date.now()) {
79
- throw new Error(`Scheduled time ${d.toISOString()} is in the past.`);
80
- }
81
- return { type: "once", normalized: d.toISOString() };
82
- }
83
- }
84
- // Cron — 6-field
85
- const cronCheck = validateCronExpression(trimmed);
86
- if (cronCheck.valid) return { type: "cron", normalized: trimmed };
87
- throw new Error(
88
- `Invalid schedule "${s}". Use 6-field cron (e.g. "0 0 9 * * 1" — 9am every Monday), interval ("5m"/"1h"), or one-shot ("+10m" / ISO).`,
89
- );
90
- }
91
-
92
- /** ScheduleStore: PID-locked, session-scoped, atomic JSON persistence. */
93
- export class ScheduleStore {
94
- private readonly path: string;
95
- private data: ScheduleStoreData;
96
-
97
- constructor(path: string) {
98
- this.path = path;
99
- this.data = { version: 1, jobs: [] };
100
- try {
101
- if (fs.existsSync(path)) {
102
- const content = fs.readFileSync(path, "utf-8");
103
- const parsed = JSON.parse(content);
104
- if (parsed && typeof parsed === "object" && "version" in parsed && "jobs" in parsed) {
105
- this.data = parsed as ScheduleStoreData;
106
- }
107
- }
108
- } catch {
109
- // Corrupt or missing file — start fresh
110
- }
111
- }
112
-
113
- private save(): void {
114
- try {
115
- fs.mkdirSync(path.dirname(this.path), { recursive: true });
116
- // Atomic (temp + rename + fsync) — matches the class docstring's "atomic JSON
117
- // persistence" claim. A raw writeFileSync could truncate schedules.json on
118
- // crash and the constructor silently resets to {jobs:[]} on parse failure.
119
- // NOTE: this class is currently not instantiated in production (scheduling
120
- // persists via settings-store updateCrewSettings); kept consistent for safety.
121
- atomicWriteJson(this.path, this.data);
122
- } catch (error) {
123
- // FIX (Round 21, L1): Use logInternalError for consistency with
124
- // the rest of the codebase. Previously console.warn may not be
125
- // visible in all environments (e.g. JSON-RPC mode, redirected
126
- // stderr). Also import the dependency properly at the top of
127
- // the file (this method used the legacy require() pattern).
128
- logInternalError("schedule.save", error, `path=${this.path}`);
129
- }
130
- }
131
-
132
- list(): ScheduledTask[] {
133
- return [...this.data.jobs];
134
- }
135
-
136
- hasName(name: string): boolean {
137
- return this.data.jobs.some((j) => j.name === name);
138
- }
139
-
140
- get(id: string): ScheduledTask | undefined {
141
- return this.data.jobs.find((j) => j.id === id);
142
- }
143
-
144
- add(job: ScheduledTask): void {
145
- this.data.jobs.push(job);
146
- this.save();
147
- }
148
-
149
- update(id: string, patch: Partial<ScheduledTask>): ScheduledTask | undefined {
150
- const idx = this.data.jobs.findIndex((j) => j.id === id);
151
- if (idx === -1) return undefined;
152
- this.data.jobs[idx] = { ...this.data.jobs[idx], ...patch };
153
- this.save();
154
- return this.data.jobs[idx];
155
- }
156
-
157
- remove(id: string): boolean {
158
- const before = this.data.jobs.length;
159
- this.data.jobs = this.data.jobs.filter((j) => j.id !== id);
160
- if (this.data.jobs.length !== before) {
161
- this.save();
162
- return true;
163
- }
164
- return false;
165
- }
166
- }