@narumitw/pi-subagents 2.1.4 → 3.0.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 (312) hide show
  1. package/README.md +187 -1228
  2. package/dist/child-communication-bridge.ts +136 -0
  3. package/dist/child-communication-bridge.ts.map +7 -0
  4. package/dist/chunks/chunk-UKTWUQRM.js +667 -0
  5. package/dist/chunks/chunk-UKTWUQRM.js.map +7 -0
  6. package/dist/index.ts +1448 -1614
  7. package/dist/index.ts.map +4 -4
  8. package/docs/design-principles.md +31 -0
  9. package/docs/ipc-transport-idea.md +103 -0
  10. package/docs/tools.md +109 -0
  11. package/package.json +11 -19
  12. package/src/broker-credentials.ts +71 -0
  13. package/src/child-communication-bridge.ts +153 -0
  14. package/src/child-communication-tools.ts +147 -0
  15. package/src/completion-renderer.ts +60 -0
  16. package/src/index.ts +1 -1
  17. package/src/message-broker.ts +628 -0
  18. package/src/model-output.ts +42 -0
  19. package/src/process.ts +688 -0
  20. package/src/runtime.ts +538 -0
  21. package/src/subagents.ts +33 -34
  22. package/src/tools.ts +416 -0
  23. package/src/types.ts +85 -0
  24. package/src/widget.ts +130 -0
  25. package/dist/chunks/auto-transport-FUUKFDIG.ts +0 -109
  26. package/dist/chunks/auto-transport-FUUKFDIG.ts.map +0 -7
  27. package/dist/chunks/capability-grant-PR72SWWS.ts +0 -19
  28. package/dist/chunks/capability-grant-PR72SWWS.ts.map +0 -7
  29. package/dist/chunks/child-peer-bridge.ts +0 -108
  30. package/dist/chunks/child-peer-bridge.ts.map +0 -7
  31. package/dist/chunks/chunk-2LMJU25E.ts +0 -1641
  32. package/dist/chunks/chunk-2LMJU25E.ts.map +0 -7
  33. package/dist/chunks/chunk-47SK2QYC.ts +0 -56
  34. package/dist/chunks/chunk-47SK2QYC.ts.map +0 -7
  35. package/dist/chunks/chunk-4AQSF7AS.ts +0 -29
  36. package/dist/chunks/chunk-4AQSF7AS.ts.map +0 -7
  37. package/dist/chunks/chunk-5DGRMKNV.ts +0 -126
  38. package/dist/chunks/chunk-5DGRMKNV.ts.map +0 -7
  39. package/dist/chunks/chunk-6H6TBBED.ts +0 -108
  40. package/dist/chunks/chunk-6H6TBBED.ts.map +0 -7
  41. package/dist/chunks/chunk-6KJ34M6S.ts +0 -159
  42. package/dist/chunks/chunk-6KJ34M6S.ts.map +0 -7
  43. package/dist/chunks/chunk-6NSJVPXX.ts +0 -480
  44. package/dist/chunks/chunk-6NSJVPXX.ts.map +0 -7
  45. package/dist/chunks/chunk-7AAJEUSL.ts +0 -238
  46. package/dist/chunks/chunk-7AAJEUSL.ts.map +0 -7
  47. package/dist/chunks/chunk-BFK2Z4ZF.ts +0 -189
  48. package/dist/chunks/chunk-BFK2Z4ZF.ts.map +0 -7
  49. package/dist/chunks/chunk-BOAXY55Y.ts +0 -153
  50. package/dist/chunks/chunk-BOAXY55Y.ts.map +0 -7
  51. package/dist/chunks/chunk-D4CR7T73.ts +0 -353
  52. package/dist/chunks/chunk-D4CR7T73.ts.map +0 -7
  53. package/dist/chunks/chunk-FEVPWRMU.ts +0 -300
  54. package/dist/chunks/chunk-FEVPWRMU.ts.map +0 -7
  55. package/dist/chunks/chunk-G6VZTCFH.ts +0 -70
  56. package/dist/chunks/chunk-G6VZTCFH.ts.map +0 -7
  57. package/dist/chunks/chunk-H3FP6DLR.ts +0 -160
  58. package/dist/chunks/chunk-H3FP6DLR.ts.map +0 -7
  59. package/dist/chunks/chunk-HA36LPCO.ts +0 -130
  60. package/dist/chunks/chunk-HA36LPCO.ts.map +0 -7
  61. package/dist/chunks/chunk-HOP5FXDT.ts +0 -27
  62. package/dist/chunks/chunk-HOP5FXDT.ts.map +0 -7
  63. package/dist/chunks/chunk-IGAMVWFN.ts +0 -139
  64. package/dist/chunks/chunk-IGAMVWFN.ts.map +0 -7
  65. package/dist/chunks/chunk-ILEQ27AL.ts +0 -45
  66. package/dist/chunks/chunk-ILEQ27AL.ts.map +0 -7
  67. package/dist/chunks/chunk-ITVWPNU4.ts +0 -819
  68. package/dist/chunks/chunk-ITVWPNU4.ts.map +0 -7
  69. package/dist/chunks/chunk-IWC32VPY.ts +0 -172
  70. package/dist/chunks/chunk-IWC32VPY.ts.map +0 -7
  71. package/dist/chunks/chunk-JSZIP73U.ts +0 -362
  72. package/dist/chunks/chunk-JSZIP73U.ts.map +0 -7
  73. package/dist/chunks/chunk-JU6LUNLP.ts +0 -380
  74. package/dist/chunks/chunk-JU6LUNLP.ts.map +0 -7
  75. package/dist/chunks/chunk-KMGKCEO4.ts +0 -38
  76. package/dist/chunks/chunk-KMGKCEO4.ts.map +0 -7
  77. package/dist/chunks/chunk-LASD73CM.ts +0 -339
  78. package/dist/chunks/chunk-LASD73CM.ts.map +0 -7
  79. package/dist/chunks/chunk-LEOYDZI3.ts +0 -22
  80. package/dist/chunks/chunk-LEOYDZI3.ts.map +0 -7
  81. package/dist/chunks/chunk-LL4LP2T7.ts +0 -737
  82. package/dist/chunks/chunk-LL4LP2T7.ts.map +0 -7
  83. package/dist/chunks/chunk-N2T5IN4X.ts +0 -18
  84. package/dist/chunks/chunk-N2T5IN4X.ts.map +0 -7
  85. package/dist/chunks/chunk-N7BLVXKK.ts +0 -73
  86. package/dist/chunks/chunk-N7BLVXKK.ts.map +0 -7
  87. package/dist/chunks/chunk-NLT67IZS.ts +0 -322
  88. package/dist/chunks/chunk-NLT67IZS.ts.map +0 -7
  89. package/dist/chunks/chunk-NTRPLF46.ts +0 -63
  90. package/dist/chunks/chunk-NTRPLF46.ts.map +0 -7
  91. package/dist/chunks/chunk-ONDTY4EL.ts +0 -91
  92. package/dist/chunks/chunk-ONDTY4EL.ts.map +0 -7
  93. package/dist/chunks/chunk-OVPHFGKO.ts +0 -1769
  94. package/dist/chunks/chunk-OVPHFGKO.ts.map +0 -7
  95. package/dist/chunks/chunk-P7OH4XMF.ts +0 -60
  96. package/dist/chunks/chunk-P7OH4XMF.ts.map +0 -7
  97. package/dist/chunks/chunk-PBZMBTNJ.ts +0 -422
  98. package/dist/chunks/chunk-PBZMBTNJ.ts.map +0 -7
  99. package/dist/chunks/chunk-PGLSFLYW.ts +0 -52
  100. package/dist/chunks/chunk-PGLSFLYW.ts.map +0 -7
  101. package/dist/chunks/chunk-RRR66UWR.ts +0 -28
  102. package/dist/chunks/chunk-RRR66UWR.ts.map +0 -7
  103. package/dist/chunks/chunk-RSUXZD6S.ts +0 -275
  104. package/dist/chunks/chunk-RSUXZD6S.ts.map +0 -7
  105. package/dist/chunks/chunk-RTCVYIZA.ts +0 -221
  106. package/dist/chunks/chunk-RTCVYIZA.ts.map +0 -7
  107. package/dist/chunks/chunk-RY2AEGMZ.ts +0 -34
  108. package/dist/chunks/chunk-RY2AEGMZ.ts.map +0 -7
  109. package/dist/chunks/chunk-SWGQLFSD.ts +0 -60
  110. package/dist/chunks/chunk-SWGQLFSD.ts.map +0 -7
  111. package/dist/chunks/chunk-TM2R67J3.ts +0 -52
  112. package/dist/chunks/chunk-TM2R67J3.ts.map +0 -7
  113. package/dist/chunks/chunk-TMZRHIIK.ts +0 -497
  114. package/dist/chunks/chunk-TMZRHIIK.ts.map +0 -7
  115. package/dist/chunks/chunk-TZ34IQ3M.ts +0 -59
  116. package/dist/chunks/chunk-TZ34IQ3M.ts.map +0 -7
  117. package/dist/chunks/chunk-UMBPMVJW.ts +0 -22
  118. package/dist/chunks/chunk-UMBPMVJW.ts.map +0 -7
  119. package/dist/chunks/chunk-VDG7LTYE.ts +0 -80
  120. package/dist/chunks/chunk-VDG7LTYE.ts.map +0 -7
  121. package/dist/chunks/chunk-X4NMONPE.ts +0 -73
  122. package/dist/chunks/chunk-X4NMONPE.ts.map +0 -7
  123. package/dist/chunks/chunk-YPJEN6NU.ts +0 -65
  124. package/dist/chunks/chunk-YPJEN6NU.ts.map +0 -7
  125. package/dist/chunks/chunk-YU53SHA7.ts +0 -328
  126. package/dist/chunks/chunk-YU53SHA7.ts.map +0 -7
  127. package/dist/chunks/completion-delivery-RSJU6BXL.ts +0 -17
  128. package/dist/chunks/completion-delivery-RSJU6BXL.ts.map +0 -7
  129. package/dist/chunks/config-status-FKGDECZ3.ts +0 -35
  130. package/dist/chunks/config-status-FKGDECZ3.ts.map +0 -7
  131. package/dist/chunks/config-ui-ABHYNGQ7.ts +0 -1212
  132. package/dist/chunks/config-ui-ABHYNGQ7.ts.map +0 -7
  133. package/dist/chunks/consult-LJU3IQY5.ts +0 -519
  134. package/dist/chunks/consult-LJU3IQY5.ts.map +0 -7
  135. package/dist/chunks/context-QDKLVQXE.ts +0 -11
  136. package/dist/chunks/context-QDKLVQXE.ts.map +0 -7
  137. package/dist/chunks/create-stateful-transport-JWC2EFYL.ts +0 -130
  138. package/dist/chunks/create-stateful-transport-JWC2EFYL.ts.map +0 -7
  139. package/dist/chunks/cwd-policy-NB6XZ5GF.ts +0 -17
  140. package/dist/chunks/cwd-policy-NB6XZ5GF.ts.map +0 -7
  141. package/dist/chunks/delegation-contract-LA56I5DT.ts +0 -24
  142. package/dist/chunks/delegation-contract-LA56I5DT.ts.map +0 -7
  143. package/dist/chunks/discovery-MIFB2U4Y.ts +0 -12
  144. package/dist/chunks/discovery-MIFB2U4Y.ts.map +0 -7
  145. package/dist/chunks/execution-Q2JZLKJA.ts +0 -4037
  146. package/dist/chunks/execution-Q2JZLKJA.ts.map +0 -7
  147. package/dist/chunks/in-process-transport-HJ6TZXC3.ts +0 -39
  148. package/dist/chunks/in-process-transport-HJ6TZXC3.ts.map +0 -7
  149. package/dist/chunks/inspect-UH2TKH6E.ts +0 -634
  150. package/dist/chunks/inspect-UH2TKH6E.ts.map +0 -7
  151. package/dist/chunks/peer-communication-KL36Z6CO.ts +0 -294
  152. package/dist/chunks/peer-communication-KL36Z6CO.ts.map +0 -7
  153. package/dist/chunks/persistence-UY3PY6E5.ts +0 -348
  154. package/dist/chunks/persistence-UY3PY6E5.ts.map +0 -7
  155. package/dist/chunks/registry-BT54L6CY.ts +0 -1445
  156. package/dist/chunks/registry-BT54L6CY.ts.map +0 -7
  157. package/dist/chunks/retained-semantic-state-GM75FZGE.ts +0 -81
  158. package/dist/chunks/retained-semantic-state-GM75FZGE.ts.map +0 -7
  159. package/dist/chunks/rpc-transport-7R7DVCEB.ts +0 -1222
  160. package/dist/chunks/rpc-transport-7R7DVCEB.ts.map +0 -7
  161. package/dist/chunks/runtime-policy-5YELCOVC.ts +0 -15
  162. package/dist/chunks/runtime-policy-5YELCOVC.ts.map +0 -7
  163. package/dist/chunks/semantic-snapshot-WPJ3UAFW.ts +0 -19
  164. package/dist/chunks/semantic-snapshot-WPJ3UAFW.ts.map +0 -7
  165. package/dist/chunks/spawn-idempotency-BNHOSMZW.ts +0 -13
  166. package/dist/chunks/spawn-idempotency-BNHOSMZW.ts.map +0 -7
  167. package/dist/chunks/stateful-lifecycle-JAR6K55H.ts +0 -15
  168. package/dist/chunks/stateful-lifecycle-JAR6K55H.ts.map +0 -7
  169. package/dist/chunks/subprocess-transport-VHZJRBTW.ts +0 -160
  170. package/dist/chunks/subprocess-transport-VHZJRBTW.ts.map +0 -7
  171. package/dist/chunks/usage-recording-store-CW3EH2SZ.ts +0 -164
  172. package/dist/chunks/usage-recording-store-CW3EH2SZ.ts.map +0 -7
  173. package/dist/chunks/workspace-WDKLN72J.ts +0 -11
  174. package/dist/chunks/workspace-WDKLN72J.ts.map +0 -7
  175. package/docs/async-runtime-protocol.md +0 -84
  176. package/docs/implementation-notes/pi-subagents-capability-matrix.md +0 -62
  177. package/docs/implementation-notes/pi-subagents-current-direction.md +0 -99
  178. package/docs/implementation-notes/pi-subagents-rpc-v1.md +0 -139
  179. package/docs/pi-subagents-diagrams.md +0 -183
  180. package/src/adaptive-scheduler.ts +0 -224
  181. package/src/admission-policy.ts +0 -78
  182. package/src/agent-projection.ts +0 -53
  183. package/src/agents/built-ins.ts +0 -71
  184. package/src/agents/catalog.ts +0 -241
  185. package/src/agents/discovery.ts +0 -265
  186. package/src/agents/types.ts +0 -100
  187. package/src/agents.ts +0 -51
  188. package/src/auto-transport.ts +0 -121
  189. package/src/blocking-status.ts +0 -63
  190. package/src/cached-module-loader.ts +0 -18
  191. package/src/capabilities.ts +0 -145
  192. package/src/capability-grant.ts +0 -115
  193. package/src/capability-router.ts +0 -107
  194. package/src/child-peer-bridge.ts +0 -124
  195. package/src/child-peer-tools.ts +0 -132
  196. package/src/completion-delivery.ts +0 -409
  197. package/src/completion-render.ts +0 -190
  198. package/src/completion-requirement.ts +0 -479
  199. package/src/completion-routing.ts +0 -24
  200. package/src/config-registration.ts +0 -114
  201. package/src/config-status.ts +0 -217
  202. package/src/config-ui.ts +0 -983
  203. package/src/consult-policy.ts +0 -15
  204. package/src/consult-registration.ts +0 -125
  205. package/src/consult-render.ts +0 -194
  206. package/src/consult-resources.ts +0 -51
  207. package/src/consult-tool.ts +0 -95
  208. package/src/consult.ts +0 -566
  209. package/src/context.ts +0 -126
  210. package/src/create-stateful-transport.ts +0 -157
  211. package/src/cwd-policy.ts +0 -183
  212. package/src/delegation-contract.ts +0 -463
  213. package/src/execution/budget.ts +0 -56
  214. package/src/execution/runtime-policy.ts +0 -19
  215. package/src/execution-plan.ts +0 -322
  216. package/src/execution-ui.ts +0 -259
  217. package/src/execution.ts +0 -1679
  218. package/src/in-process-transport.ts +0 -941
  219. package/src/inspect-registration.ts +0 -64
  220. package/src/inspect-render.ts +0 -334
  221. package/src/inspect-tool.ts +0 -45
  222. package/src/inspect.ts +0 -769
  223. package/src/integration-controller.ts +0 -98
  224. package/src/limits.ts +0 -75
  225. package/src/orchestration-metrics.ts +0 -116
  226. package/src/outcome.ts +0 -61
  227. package/src/panel-child-group.ts +0 -35
  228. package/src/panel-contract.ts +0 -343
  229. package/src/panel-evidence.ts +0 -59
  230. package/src/panel-execution.ts +0 -769
  231. package/src/panel-failure.ts +0 -56
  232. package/src/panel-planning.ts +0 -175
  233. package/src/panel-presets.ts +0 -3
  234. package/src/panel-prompts.ts +0 -132
  235. package/src/panel-reconciliation.ts +0 -57
  236. package/src/panel-render.ts +0 -103
  237. package/src/parallel-limit-ui.ts +0 -113
  238. package/src/params.ts +0 -278
  239. package/src/peer-communication.ts +0 -352
  240. package/src/peer-transport.ts +0 -49
  241. package/src/persistence.ts +0 -522
  242. package/src/pi-args.ts +0 -43
  243. package/src/pi-invocation.ts +0 -168
  244. package/src/process-control.ts +0 -43
  245. package/src/prompt-resources.ts +0 -38
  246. package/src/prompt-source-safety.ts +0 -48
  247. package/src/protocol.ts +0 -76
  248. package/src/registry-types.ts +0 -204
  249. package/src/registry.ts +0 -1698
  250. package/src/render-common.ts +0 -252
  251. package/src/render.ts +0 -704
  252. package/src/result-contract.ts +0 -431
  253. package/src/retained-semantic-state.ts +0 -100
  254. package/src/rpc-timeout-finalization.ts +0 -207
  255. package/src/rpc-transport-metadata.ts +0 -65
  256. package/src/rpc-transport.ts +0 -1035
  257. package/src/rpc-turn-capture.ts +0 -217
  258. package/src/runner-outcome.ts +0 -31
  259. package/src/runner-result.ts +0 -55
  260. package/src/runner-types.ts +0 -102
  261. package/src/runner-usage.ts +0 -48
  262. package/src/runner.ts +0 -866
  263. package/src/safe-text.ts +0 -67
  264. package/src/semantic-snapshot.ts +0 -214
  265. package/src/session-guidance-contract.ts +0 -399
  266. package/src/settings/inspection.ts +0 -302
  267. package/src/settings/schema.ts +0 -195
  268. package/src/settings-reader.ts +0 -215
  269. package/src/settings.ts +0 -452
  270. package/src/spawn-idempotency.ts +0 -68
  271. package/src/stateful-agent-view.ts +0 -85
  272. package/src/stateful-config.ts +0 -13
  273. package/src/stateful-guidance.ts +0 -29
  274. package/src/stateful-lifecycle.ts +0 -74
  275. package/src/stateful-limit-ui.ts +0 -249
  276. package/src/stateful-limits.ts +0 -96
  277. package/src/stateful-prompt.ts +0 -49
  278. package/src/stateful-registration.ts +0 -1320
  279. package/src/stateful-render.ts +0 -318
  280. package/src/stateful-safety.ts +0 -47
  281. package/src/stateful-tool-params.ts +0 -215
  282. package/src/stateful.ts +0 -12
  283. package/src/subagent-details.ts +0 -43
  284. package/src/subagents-extension.ts +0 -372
  285. package/src/subprocess-transport.ts +0 -145
  286. package/src/supervision.ts +0 -104
  287. package/src/task-path.ts +0 -65
  288. package/src/timeout-checkpoint.ts +0 -305
  289. package/src/timeout-finalization.ts +0 -75
  290. package/src/tool-schema-compatibility.ts +0 -73
  291. package/src/transport-types.ts +0 -74
  292. package/src/transport-ui.ts +0 -135
  293. package/src/transport.ts +0 -43
  294. package/src/turn-budget.ts +0 -109
  295. package/src/usage-format.ts +0 -42
  296. package/src/usage-recording-config.ts +0 -13
  297. package/src/usage-recording-store.ts +0 -183
  298. package/src/usage-recording.ts +0 -478
  299. package/src/verification-harness.ts +0 -516
  300. package/src/verification-policy.ts +0 -67
  301. package/src/verification-receipt.ts +0 -275
  302. package/src/verified-execution-benchmark.ts +0 -86
  303. package/src/verified-execution-contract.ts +0 -191
  304. package/src/verified-execution-schema.ts +0 -32
  305. package/src/work-item-ledger.ts +0 -1404
  306. package/src/work-item-persistence.ts +0 -254
  307. package/src/workflow-completion-controller.ts +0 -397
  308. package/src/workflow-planning.ts +0 -172
  309. package/src/workflow-tree-identity.ts +0 -289
  310. package/src/workflow-ui.ts +0 -69
  311. package/src/workflow-verification.ts +0 -296
  312. package/src/workspace.ts +0 -174
@@ -1,56 +0,0 @@
1
- import type { SingleResult } from "./runner-types.js";
2
-
3
- export const PANEL_FAILURE_KINDS = [
4
- "transient-launch",
5
- "transient-transport",
6
- "invalid-contract",
7
- "semantic-stall",
8
- "permission-denied",
9
- "budget-exhausted",
10
- "cancelled",
11
- "task-failed",
12
- ] as const;
13
-
14
- export type PanelFailureKind = (typeof PANEL_FAILURE_KINDS)[number];
15
-
16
- export interface PanelFailure {
17
- reviewerId?: string;
18
- kind: PanelFailureKind;
19
- retryable: boolean;
20
- message?: string;
21
- }
22
-
23
- export function classifyPanelFailure(
24
- result: Partial<
25
- Pick<
26
- SingleResult,
27
- | "launchFailed"
28
- | "resultContractInvalid"
29
- | "stopReason"
30
- | "timedOut"
31
- | "aborted"
32
- | "errorMessage"
33
- | "stderr"
34
- | "termination"
35
- >
36
- >,
37
- ): Omit<PanelFailure, "reviewerId" | "message"> {
38
- if (result.launchFailed) return { kind: "transient-launch", retryable: true };
39
- if (result.resultContractInvalid) return { kind: "invalid-contract", retryable: false };
40
- if (result.stopReason === "semantic-stall" || result.termination?.reason === "idle_timeout") {
41
- return { kind: "semantic-stall", retryable: false };
42
- }
43
- if (result.aborted || result.stopReason === "aborted")
44
- return { kind: "cancelled", retryable: false };
45
- if (result.timedOut || result.stopReason === "timeout") {
46
- return { kind: "budget-exhausted", retryable: false };
47
- }
48
- const message = `${result.errorMessage ?? ""}\n${result.stderr ?? ""}`;
49
- if (/permission|denied|not allowed|unauthori[sz]ed/iu.test(message)) {
50
- return { kind: "permission-denied", retryable: false };
51
- }
52
- if (/transport|socket|connection reset|temporar|econnreset|eai_again/iu.test(message)) {
53
- return { kind: "transient-transport", retryable: true };
54
- }
55
- return { kind: "task-failed", retryable: false };
56
- }
@@ -1,175 +0,0 @@
1
- import type { AgentConfig } from "./agents/types.js";
2
- import { DEFAULT_MAX_CONTEXT_BYTES, truncateUtf8 } from "./limits.js";
3
- import type { PanelPreset } from "./panel-presets.js";
4
- import { type WorkItemDefinition, WorkItemLedger } from "./work-item-ledger.js";
5
-
6
- export type { PanelPreset } from "./panel-presets.js";
7
-
8
- export interface PanelReviewerRequest {
9
- id: string;
10
- agent: string;
11
- focus?: string;
12
- timeoutMs?: number;
13
- idleTimeoutMs?: number;
14
- maxTurns?: number;
15
- maxToolCalls?: number;
16
- thinkingLevel?: string;
17
- }
18
-
19
- export interface PanelSynthesizerRequest {
20
- agent: string;
21
- timeoutMs?: number;
22
- idleTimeoutMs?: number;
23
- maxTurns?: number;
24
- maxToolCalls?: number;
25
- thinkingLevel?: string;
26
- }
27
-
28
- export interface PanelRequestLike {
29
- id?: string;
30
- preset?: PanelPreset;
31
- task: string;
32
- context?: string;
33
- reviewers: PanelReviewerRequest[];
34
- synthesizer: PanelSynthesizerRequest;
35
- minValidReviews?: number;
36
- }
37
-
38
- export interface PanelPhaseBudgets {
39
- totalMs: number;
40
- reviewMs: number;
41
- finalizationMs: number;
42
- synthesisMs: number;
43
- cleanupMs: number;
44
- reviewerTimeoutMs: number;
45
- }
46
-
47
- const MIN_PHASE_MS = 1;
48
- const MAX_ID_BYTES = 256;
49
- const MAX_FOCUS_BYTES = 8 * 1024;
50
-
51
- export function planPanelBudgets(totalMs: number, _reviewerCount: number): PanelPhaseBudgets {
52
- const normalized = Math.floor(totalMs);
53
- if (!Number.isSafeInteger(normalized) || normalized < 4 * MIN_PHASE_MS) {
54
- throw new Error(
55
- "Panel total timeout is too small to reserve review, finalization, synthesis, and cleanup phases",
56
- );
57
- }
58
- const cleanupMs = Math.max(MIN_PHASE_MS, Math.floor(normalized * 0.05));
59
- const synthesisMs = Math.max(MIN_PHASE_MS, Math.floor(normalized * 0.2));
60
- const finalizationMs = Math.max(MIN_PHASE_MS, Math.floor(normalized * 0.1));
61
- const reviewMs = normalized - cleanupMs - synthesisMs - finalizationMs;
62
- if (reviewMs < MIN_PHASE_MS) {
63
- throw new Error("Panel total timeout is too small for the review phase");
64
- }
65
- return {
66
- totalMs: normalized,
67
- reviewMs,
68
- finalizationMs,
69
- synthesisMs,
70
- cleanupMs,
71
- reviewerTimeoutMs: reviewMs,
72
- };
73
- }
74
-
75
- export function createPanelWorkLedger(
76
- panelId: string,
77
- panel: PanelRequestLike,
78
- agents: readonly AgentConfig[],
79
- ): WorkItemLedger {
80
- const taskPreview = truncateUtf8(panel.task, 256).text;
81
- const reviewerItems: WorkItemDefinition[] = panel.reviewers.map((reviewer) => {
82
- const agent = agents.find((candidate) => candidate.name === reviewer.agent);
83
- return {
84
- id: `review:${reviewer.id}`,
85
- objective: `Panel review ${reviewer.id}: ${taskPreview}`,
86
- dependencies: [],
87
- inputArtifacts: [],
88
- inputArtifactVersions: {},
89
- requiredCapabilities: ["independent-review"],
90
- requiredTools: [],
91
- selectedAgentName: reviewer.agent,
92
- sideEffectPolicy:
93
- agent?.capabilityManifest?.authority?.filesystem === "read" ? "read-only" : "mutating",
94
- readPaths: [],
95
- writePaths: [],
96
- ownershipKeys: [],
97
- acceptanceCriteria: ["Return one valid panel-review:v1 evidence artifact"],
98
- };
99
- });
100
- return WorkItemLedger.create({
101
- workflowId: panelId,
102
- items: [
103
- ...reviewerItems,
104
- {
105
- id: "synthesis",
106
- objective: "Reconcile valid panel evidence while preserving objections and dissent",
107
- dependencies: reviewerItems.map((item) => item.id),
108
- dependencyPolicy: "settled",
109
- inputArtifacts: [],
110
- inputArtifactVersions: {},
111
- requiredCapabilities: ["evidence-review"],
112
- requiredTools: [],
113
- selectedAgentName: panel.synthesizer.agent,
114
- sideEffectPolicy: "read-only",
115
- readPaths: [],
116
- writePaths: [],
117
- ownershipKeys: [],
118
- acceptanceCriteria: [
119
- "Preserve every blocking objection and disagreement with evidence-backed resolution state",
120
- ],
121
- integrationOwner: true,
122
- },
123
- ],
124
- });
125
- }
126
-
127
- export function validatePanelRequest(panel: PanelRequestLike, maxReviewers = 64): void {
128
- if (
129
- panel.id !== undefined &&
130
- (!/^[A-Za-z0-9][A-Za-z0-9._:-]*$/u.test(panel.id) ||
131
- Buffer.byteLength(panel.id, "utf8") > MAX_ID_BYTES)
132
- ) {
133
- throw new Error("Panel id must use 1-256 safe identifier bytes");
134
- }
135
- if (!panel.task.trim()) throw new Error("Panel task must not be empty");
136
- if (Buffer.byteLength(panel.task, "utf8") > DEFAULT_MAX_CONTEXT_BYTES) {
137
- throw new Error("Panel task exceeds the bounded context limit");
138
- }
139
- if (panel.context && Buffer.byteLength(panel.context, "utf8") > DEFAULT_MAX_CONTEXT_BYTES) {
140
- throw new Error("Panel shared context exceeds the bounded context limit");
141
- }
142
- if (panel.reviewers.length < 2) throw new Error("Panel mode requires at least two reviewers");
143
- if (panel.reviewers.length > maxReviewers) {
144
- throw new Error(
145
- `Panel has too many reviewers (${panel.reviewers.length}); configured max is ${maxReviewers}`,
146
- );
147
- }
148
- const ids = panel.reviewers.map((reviewer) => reviewer.id.trim());
149
- if (
150
- ids.some(
151
- (id) =>
152
- !id ||
153
- Buffer.byteLength(id, "utf8") > MAX_ID_BYTES ||
154
- !/^[A-Za-z0-9][A-Za-z0-9._:-]*$/u.test(id),
155
- )
156
- ) {
157
- throw new Error("Panel reviewer ids must use 1-256 safe identifier bytes");
158
- }
159
- if (new Set(ids).size !== ids.length) throw new Error("Panel reviewer ids must be unique");
160
- if (panel.reviewers.some((reviewer) => !reviewer.agent.trim())) {
161
- throw new Error("Every panel reviewer must name an agent");
162
- }
163
- if (
164
- panel.reviewers.some(
165
- (reviewer) => reviewer.focus && Buffer.byteLength(reviewer.focus, "utf8") > MAX_FOCUS_BYTES,
166
- )
167
- ) {
168
- throw new Error("Panel reviewer focus exceeds the bounded field limit");
169
- }
170
- if (!panel.synthesizer.agent.trim()) throw new Error("Panel synthesizer must name an agent");
171
- const minValid = panel.minValidReviews ?? 2;
172
- if (!Number.isInteger(minValid) || minValid < 2 || minValid > panel.reviewers.length) {
173
- throw new Error("minValidReviews must be between two and the reviewer count");
174
- }
175
- }
@@ -1,3 +0,0 @@
1
- export const PANEL_PRESETS = ["code-review", "research", "security-review", "custom"] as const;
2
-
3
- export type PanelPreset = (typeof PANEL_PRESETS)[number];
@@ -1,132 +0,0 @@
1
- import { DEFAULT_MAX_CONTEXT_BYTES, truncateUtf8 } from "./limits.js";
2
- import {
3
- type PanelReview,
4
- panelReviewInstruction,
5
- panelSynthesisInstruction,
6
- } from "./panel-contract.js";
7
- import type { PanelFailure } from "./panel-failure.js";
8
- import type { PanelPreset } from "./panel-planning.js";
9
-
10
- const PRESET_GUIDANCE: Record<PanelPreset, string> = {
11
- "code-review":
12
- "Review correctness, regressions, baseline security, maintainability, tests, and integration risk.",
13
- research:
14
- "Assess source quality, evidence traceability, contradictory evidence, uncertainty, and unsupported claims.",
15
- "security-review":
16
- "Review trust boundaries, authority, injection, data exposure, unsafe side effects, and missing mitigations.",
17
- custom: "Apply the shared task and your assigned focus without inventing unstated requirements.",
18
- };
19
-
20
- const PROMPT_SEPARATOR = "\n\n";
21
- const MAX_REVIEWER_SUFFIX_BYTES = 12 * 1024;
22
- const REVIEWER_SHARED_BYTES = DEFAULT_MAX_CONTEXT_BYTES - MAX_REVIEWER_SUFFIX_BYTES;
23
-
24
- export function buildPanelReviewerPrompt(input: {
25
- panelId: string;
26
- preset: PanelPreset;
27
- task: string;
28
- context?: string;
29
- reviewerId: string;
30
- focus?: string;
31
- }): string {
32
- const shared = buildReviewerSharedBlock(input);
33
- const reviewer = [
34
- `Reviewer id: ${input.reviewerId}`,
35
- input.focus ? `Reviewer focus:\n${input.focus}` : undefined,
36
- "Work independently and do not assume another reviewer will catch or resolve a problem.",
37
- `Panel review contract:\n${panelReviewInstruction(input.reviewerId)}`,
38
- ]
39
- .filter((part): part is string => Boolean(part))
40
- .join(PROMPT_SEPARATOR);
41
- return joinWithRequiredSuffix(shared, reviewer);
42
- }
43
-
44
- export function buildPanelSynthesisPrompt(input: {
45
- panelId: string;
46
- task: string;
47
- reviews: readonly PanelReview[];
48
- failures: readonly PanelFailure[];
49
- }): string {
50
- const validIds = input.reviews.map((review) => review.reviewerId);
51
- const failedIds = input.failures
52
- .map((failure) => failure.reviewerId)
53
- .filter((id): id is string => Boolean(id));
54
- const artifacts = input.reviews.map((review) => ({
55
- reviewerId: review.reviewerId,
56
- disposition: review.disposition,
57
- blocking: review.blocking,
58
- findings: review.findings,
59
- missingChecks: review.missingChecks,
60
- limitations: review.limitations,
61
- provenance: review.provenance,
62
- }));
63
- const requiredSuffix = [
64
- "Reconcile evidence without majority voting and without erasing dissent.",
65
- `Panel synthesis contract:\n${panelSynthesisInstruction(validIds, failedIds)}`,
66
- ].join(PROMPT_SEPARATOR);
67
- const fixedPrefixParts = [
68
- `Panel: ${input.panelId}`,
69
- "Shared task:\n",
70
- `Panel evidence artifacts:\n${JSON.stringify(artifacts)}`,
71
- `Failed or invalid reviewers:\n${JSON.stringify(input.failures)}`,
72
- ];
73
- const taskBudget = Math.max(
74
- 0,
75
- DEFAULT_MAX_CONTEXT_BYTES -
76
- byteLength(fixedPrefixParts.join(PROMPT_SEPARATOR)) -
77
- byteLength(PROMPT_SEPARATOR) -
78
- byteLength(requiredSuffix),
79
- );
80
- const prefix = [
81
- fixedPrefixParts[0],
82
- `Shared task:\n${truncateUtf8(input.task, taskBudget).text}`,
83
- fixedPrefixParts[2],
84
- fixedPrefixParts[3],
85
- ].join(PROMPT_SEPARATOR);
86
- return joinWithRequiredSuffix(prefix, requiredSuffix);
87
- }
88
-
89
- function buildReviewerSharedBlock(input: {
90
- panelId: string;
91
- preset: PanelPreset;
92
- task: string;
93
- context?: string;
94
- }): string {
95
- const context = input.context || undefined;
96
- const hasContext = context !== undefined;
97
- const fixedParts = [
98
- `Panel: ${input.panelId}`,
99
- `Preset: ${input.preset}`,
100
- "Shared task:\n",
101
- hasContext ? "Shared context:\n" : undefined,
102
- `Preset guidance:\n${PRESET_GUIDANCE[input.preset]}`,
103
- ].filter((part): part is string => part !== undefined);
104
- const contentBudget = Math.max(
105
- 0,
106
- REVIEWER_SHARED_BYTES - byteLength(fixedParts.join(PROMPT_SEPARATOR)),
107
- );
108
- const taskBudget = hasContext ? Math.floor(contentBudget / 2) : contentBudget;
109
- const contextBudget = contentBudget - taskBudget;
110
- return [
111
- fixedParts[0],
112
- fixedParts[1],
113
- `Shared task:\n${truncateUtf8(input.task, taskBudget).text}`,
114
- hasContext ? `Shared context:\n${truncateUtf8(context, contextBudget).text}` : undefined,
115
- fixedParts.at(-1),
116
- ]
117
- .filter((part): part is string => part !== undefined)
118
- .join(PROMPT_SEPARATOR);
119
- }
120
-
121
- function joinWithRequiredSuffix(prefix: string, suffix: string): string {
122
- const suffixBytes = byteLength(PROMPT_SEPARATOR) + byteLength(suffix);
123
- if (suffixBytes > DEFAULT_MAX_CONTEXT_BYTES) {
124
- throw new Error("Panel prompt contract exceeds the bounded context limit");
125
- }
126
- const boundedPrefix = truncateUtf8(prefix, DEFAULT_MAX_CONTEXT_BYTES - suffixBytes).text;
127
- return `${boundedPrefix}${PROMPT_SEPARATOR}${suffix}`;
128
- }
129
-
130
- function byteLength(value: string): number {
131
- return Buffer.byteLength(value, "utf8");
132
- }
@@ -1,57 +0,0 @@
1
- import type { PanelFinding, PanelReview } from "./panel-contract.js";
2
- import type { PanelFailure } from "./panel-failure.js";
3
-
4
- export interface PanelBlockingObjection {
5
- reviewerId: string;
6
- finding: PanelFinding;
7
- }
8
-
9
- export type PanelReconciliation =
10
- | {
11
- kind: "ready-for-synthesis";
12
- reviews: PanelReview[];
13
- failures: PanelFailure[];
14
- blockingObjections: PanelBlockingObjection[];
15
- verified: false;
16
- }
17
- | {
18
- kind: "insufficient-panel";
19
- partialReviews: PanelReview[];
20
- failures: PanelFailure[];
21
- blockingObjections: PanelBlockingObjection[];
22
- consensus: false;
23
- };
24
-
25
- export function reconcilePanel(input: {
26
- reviews: PanelReview[];
27
- failures: PanelFailure[];
28
- minValidReviews: number;
29
- }): PanelReconciliation {
30
- const reviews = [...input.reviews].sort((left, right) =>
31
- left.reviewerId.localeCompare(right.reviewerId),
32
- );
33
- const failures = [...input.failures].sort((left, right) =>
34
- (left.reviewerId ?? "").localeCompare(right.reviewerId ?? ""),
35
- );
36
- const blockingObjections = reviews.flatMap((review) =>
37
- review.blocking
38
- ? review.findings.map((finding) => ({ reviewerId: review.reviewerId, finding }))
39
- : [],
40
- );
41
- if (reviews.length < input.minValidReviews) {
42
- return {
43
- kind: "insufficient-panel",
44
- partialReviews: reviews,
45
- failures,
46
- blockingObjections,
47
- consensus: false,
48
- };
49
- }
50
- return {
51
- kind: "ready-for-synthesis",
52
- reviews,
53
- failures,
54
- blockingObjections,
55
- verified: false,
56
- };
57
- }
@@ -1,103 +0,0 @@
1
- import type { Theme } from "@earendil-works/pi-coding-agent";
2
- import { Text } from "@earendil-works/pi-tui";
3
- import type { SubagentParams } from "./params.js";
4
- import { safeBlock, safeLine } from "./render-common.js";
5
- import type { SubagentDetails } from "./subagent-details.js";
6
-
7
- export function renderPanelCall(args: SubagentParams, theme: Theme): Text | undefined {
8
- const panel = args.panel;
9
- if (!panel) return undefined;
10
- const scope = args.agentScope ?? "user";
11
- let text =
12
- theme.fg("toolTitle", theme.bold("subagent ")) +
13
- theme.fg("accent", `panel ${panel.preset ?? "custom"} (${panel.reviewers.length} reviewers)`) +
14
- theme.fg("muted", ` [${scope}]`);
15
- text += `\n ${theme.fg("dim", preview(panel.task, 80))}`;
16
- for (const reviewer of panel.reviewers.slice(0, 3)) {
17
- text += `\n ${theme.fg("muted", `${safeLine(reviewer.id, "reviewer", 256)}:`)} ${theme.fg("accent", safeLine(reviewer.agent, "agent", 256))}`;
18
- if (reviewer.focus) text += theme.fg("dim", ` ${preview(reviewer.focus, 40)}`);
19
- }
20
- if (panel.reviewers.length > 3) {
21
- text += `\n ${theme.fg("muted", `... +${panel.reviewers.length - 3} more`)}`;
22
- }
23
- text += `\n ${theme.fg("muted", "synthesis → ")}${theme.fg("accent", safeLine(panel.synthesizer.agent, "agent", 256))}`;
24
- return new Text(text, 0, 0);
25
- }
26
-
27
- export function renderPanelResult(
28
- details: SubagentDetails,
29
- expanded: boolean,
30
- isPartial: boolean,
31
- theme: Theme,
32
- ): Text | undefined {
33
- const panel = details.panel;
34
- if (details.mode !== "panel" || !panel) return undefined;
35
- const running = isPartial || panel.state === "running";
36
- const status = running ? "running" : panel.state;
37
- const color =
38
- status === "completed"
39
- ? "success"
40
- : status === "running" || status === "degraded"
41
- ? "warning"
42
- : "error";
43
- let text = `${theme.fg(color, status)} ${theme.fg("accent", safeLine(panel.preset, "panel", 128))}`;
44
- text += theme.fg(
45
- "muted",
46
- ` · valid ${panel.validReviewCount}/${panel.reviewerIds.length} · failed ${panel.failedReviewCount} · blockers ${panel.blockingObjectionCount} · dissent ${panel.dissentCount}`,
47
- );
48
- if (panel.synthesis) {
49
- text += `\n${theme.fg("toolOutput", safeBlock(panel.synthesis.summary, "", 8 * 1024))}`;
50
- } else if (panel.state === "insufficient-panel") {
51
- text += `\n${theme.fg("warning", "Synthesis skipped because too few valid reviews remained.")}`;
52
- }
53
- if (!expanded) return new Text(text, 0, 0);
54
-
55
- text += `\n\n${theme.fg("muted", "Shared task: ")}${theme.fg("dim", safeBlock(panel.sharedTaskPreview, "", 2 * 1024))}`;
56
- text += `\n\n${theme.fg("muted", "Reviewers:")}`;
57
- for (const reviewerId of panel.reviewerIds) {
58
- const artifact = panel.evidence.find((candidate) => candidate.reviewerId === reviewerId);
59
- const failure = panel.failures.find((candidate) => candidate.reviewerId === reviewerId);
60
- const result = details.results.find(
61
- (candidate) => candidate.step === panel.reviewerIds.indexOf(reviewerId) + 1,
62
- );
63
- const model = result?.actualModel ?? result?.model;
64
- text += `\n ${theme.fg("accent", safeLine(reviewerId, "reviewer", 256))}`;
65
- if (model) text += theme.fg("muted", ` · ${safeLine(model, "", 256)}`);
66
- if (artifact) {
67
- text += theme.fg(
68
- "muted",
69
- ` · ${artifact.review.disposition} · revision ${artifact.revision}`,
70
- );
71
- for (const finding of artifact.review.findings) {
72
- text += `\n ${theme.fg(finding.severity === "critical" || finding.severity === "high" ? "error" : "warning", `[${finding.severity}]`)} ${safeLine(finding.title, "finding", 512)}`;
73
- }
74
- } else if (failure) {
75
- text += theme.fg("error", ` · ${safeLine(failure.kind, "failed", 128)}`);
76
- }
77
- }
78
- if (panel.synthesis?.disagreements.length) {
79
- text += `\n\n${theme.fg("muted", "Disagreements:")}`;
80
- for (const disagreement of panel.synthesis.disagreements) {
81
- text += `\n ${safeLine(disagreement.summary, "disagreement", 1024)}`;
82
- }
83
- }
84
- if (panel.synthesis?.objections.length) {
85
- text += `\n\n${theme.fg("muted", "Blocking objections:")}`;
86
- for (const objection of panel.synthesis.objections) {
87
- text += `\n ${safeLine(`${objection.reviewerId}/${objection.findingId}: ${objection.resolution}`, "objection", 1024)}`;
88
- }
89
- }
90
- const limitations = panel.synthesis?.limitations ?? [];
91
- if (limitations.length) {
92
- text += `\n\n${theme.fg("muted", "Limitations:")}`;
93
- for (const limitation of limitations) text += `\n ${safeLine(limitation, "", 1024)}`;
94
- }
95
- text += `\n\n${theme.fg("muted", `Budget: review ${panel.budgets.reviewMs}ms · finalization ${panel.budgets.finalizationMs}ms · synthesis ${panel.budgets.synthesisMs}ms · cleanup ${panel.budgets.cleanupMs}ms`)}`;
96
- text += `\n${theme.fg("muted", `Cleanup: ${panel.cleanupComplete ? "complete" : "pending"}`)}`;
97
- return new Text(text, 0, 0);
98
- }
99
-
100
- function preview(value: unknown, maxLength: number): string {
101
- const text = safeLine(value, "", 2 * 1024);
102
- return text.length > maxLength ? `${text.slice(0, maxLength)}...` : text;
103
- }
@@ -1,113 +0,0 @@
1
- import type { ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
2
- import { MAX_BLOCKING_PARALLEL_CONCURRENCY, MAX_CONFIGURABLE_PARALLEL_TASKS } from "./limits.js";
3
- import {
4
- inspectBlockingParallelLimitSettings,
5
- updateBlockingMaxParallelTasksSetting,
6
- } from "./settings.js";
7
-
8
- export interface BlockingParallelLimitRuntime {
9
- getMaxParallelTasks(): number;
10
- setMaxParallelTasks(value: number): void;
11
- }
12
-
13
- export function blockingParallelLimitScreen(runtime: BlockingParallelLimitRuntime) {
14
- const limit = inspectBlockingParallelLimitSettings();
15
- return {
16
- kind: "input" as const,
17
- title: "Blocking Worker Limit",
18
- lines: [
19
- "This applies only to requests that make Pi wait for delegated work.",
20
- `Current: ${runtime.getMaxParallelTasks()} subagents per blocking request`,
21
- `Allowed: 1-${MAX_CONFIGURABLE_PARALLEL_TASKS}`,
22
- `Pi still starts at most ${MAX_BLOCKING_PARALLEL_CONCURRENCY} of them at once.`,
23
- safeTerminalText(limit.path),
24
- ],
25
- placeholder: "Enter a whole number",
26
- action: "set-parallel-limit" as const,
27
- hint: "back" as const,
28
- };
29
- }
30
-
31
- export function applyBlockingParallelLimitSetting(
32
- value: string | undefined,
33
- ctx: ExtensionCommandContext,
34
- runtime: BlockingParallelLimitRuntime,
35
- ) {
36
- const normalized = value?.trim() ?? "";
37
- if (!/^\d+$/u.test(normalized)) {
38
- notifyValidationError(ctx);
39
- return { kind: "rejected" as const };
40
- }
41
- const next = Number(normalized);
42
- if (!Number.isSafeInteger(next) || next < 1 || next > MAX_CONFIGURABLE_PARALLEL_TASKS) {
43
- notifyValidationError(ctx);
44
- return { kind: "rejected" as const };
45
- }
46
- const configured = inspectBlockingParallelLimitSettings();
47
- if (configured.error) {
48
- ctx.ui.notify(
49
- `Subagent settings cannot be edited: ${safeTerminalText(configured.error)}`,
50
- "error",
51
- );
52
- return { kind: "rejected" as const };
53
- }
54
- const previousRuntime = runtime.getMaxParallelTasks();
55
- const runtimeChanged = next !== previousRuntime;
56
- const settingsChanged = next !== configured.value;
57
- if (!runtimeChanged && !settingsChanged) return { kind: "back" as const };
58
-
59
- if (runtimeChanged) {
60
- try {
61
- runtime.setMaxParallelTasks(next);
62
- } catch (error) {
63
- ctx.ui.notify(
64
- `Parallel-worker limit was not applied; user settings are unchanged: ${formatError(error)}`,
65
- "error",
66
- );
67
- return { kind: "rejected" as const };
68
- }
69
- }
70
- if (settingsChanged) {
71
- try {
72
- updateBlockingMaxParallelTasksSetting(next);
73
- } catch (saveError) {
74
- if (runtimeChanged) {
75
- try {
76
- runtime.setMaxParallelTasks(previousRuntime);
77
- } catch (rollbackError) {
78
- ctx.ui.notify(
79
- `Subagent settings were not saved, and the runtime rollback failed: ${formatError(new AggregateError([saveError, rollbackError]))}`,
80
- "error",
81
- );
82
- return { kind: "rejected" as const };
83
- }
84
- }
85
- ctx.ui.notify(
86
- "Subagent settings were not saved; the previous runtime limit was restored.",
87
- "error",
88
- );
89
- return { kind: "rejected" as const };
90
- }
91
- }
92
- ctx.ui.notify(
93
- `${settingsChanged ? "Saved and " : ""}applied: maximum ${next} subagents per blocking request.`,
94
- "info",
95
- );
96
- return { kind: "back" as const };
97
- }
98
-
99
- function notifyValidationError(ctx: ExtensionCommandContext): void {
100
- ctx.ui.notify(
101
- `Blocking worker limit must be a whole number from 1 to ${MAX_CONFIGURABLE_PARALLEL_TASKS}.`,
102
- "warning",
103
- );
104
- }
105
-
106
- function safeTerminalText(value: string): string {
107
- // biome-ignore lint/suspicious/noControlCharactersInRegex: Escape untrusted terminal controls.
108
- return value.replace(/[\u0000-\u001f\u007f-\u009f]/gu, "?");
109
- }
110
-
111
- function formatError(error: unknown): string {
112
- return safeTerminalText(error instanceof Error ? error.message : String(error));
113
- }