@narumitw/pi-subagents 2.1.3 → 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 (314) hide show
  1. package/README.md +181 -1250
  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 -1598
  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 -153
  179. package/docs/pi-subagents-diagrams.md +0 -183
  180. package/src/adaptive-scheduler.ts +0 -224
  181. package/src/admission-benchmark.ts +0 -95
  182. package/src/admission-policy.ts +0 -78
  183. package/src/agent-projection.ts +0 -53
  184. package/src/agents/built-ins.ts +0 -71
  185. package/src/agents/catalog.ts +0 -241
  186. package/src/agents/discovery.ts +0 -265
  187. package/src/agents/types.ts +0 -100
  188. package/src/agents.ts +0 -51
  189. package/src/async-subagent-benchmark.ts +0 -532
  190. package/src/auto-transport.ts +0 -121
  191. package/src/blocking-status.ts +0 -63
  192. package/src/cached-module-loader.ts +0 -18
  193. package/src/capabilities.ts +0 -145
  194. package/src/capability-grant.ts +0 -115
  195. package/src/capability-router.ts +0 -107
  196. package/src/child-peer-bridge.ts +0 -124
  197. package/src/child-peer-tools.ts +0 -132
  198. package/src/completion-delivery.ts +0 -409
  199. package/src/completion-render.ts +0 -190
  200. package/src/completion-requirement.ts +0 -479
  201. package/src/completion-routing.ts +0 -24
  202. package/src/config-registration.ts +0 -114
  203. package/src/config-status.ts +0 -217
  204. package/src/config-ui.ts +0 -983
  205. package/src/consult-policy.ts +0 -15
  206. package/src/consult-registration.ts +0 -125
  207. package/src/consult-render.ts +0 -194
  208. package/src/consult-resources.ts +0 -51
  209. package/src/consult-tool.ts +0 -95
  210. package/src/consult.ts +0 -566
  211. package/src/context.ts +0 -126
  212. package/src/create-stateful-transport.ts +0 -157
  213. package/src/cwd-policy.ts +0 -183
  214. package/src/delegation-contract.ts +0 -463
  215. package/src/execution/budget.ts +0 -56
  216. package/src/execution/runtime-policy.ts +0 -19
  217. package/src/execution-plan.ts +0 -322
  218. package/src/execution-ui.ts +0 -259
  219. package/src/execution.ts +0 -1679
  220. package/src/in-process-transport.ts +0 -941
  221. package/src/inspect-registration.ts +0 -64
  222. package/src/inspect-render.ts +0 -334
  223. package/src/inspect-tool.ts +0 -45
  224. package/src/inspect.ts +0 -769
  225. package/src/integration-controller.ts +0 -98
  226. package/src/limits.ts +0 -75
  227. package/src/orchestration-metrics.ts +0 -116
  228. package/src/outcome.ts +0 -61
  229. package/src/panel-child-group.ts +0 -35
  230. package/src/panel-contract.ts +0 -343
  231. package/src/panel-evidence.ts +0 -59
  232. package/src/panel-execution.ts +0 -769
  233. package/src/panel-failure.ts +0 -56
  234. package/src/panel-planning.ts +0 -175
  235. package/src/panel-presets.ts +0 -3
  236. package/src/panel-prompts.ts +0 -132
  237. package/src/panel-reconciliation.ts +0 -57
  238. package/src/panel-render.ts +0 -103
  239. package/src/parallel-limit-ui.ts +0 -113
  240. package/src/params.ts +0 -278
  241. package/src/peer-communication.ts +0 -352
  242. package/src/peer-transport.ts +0 -49
  243. package/src/persistence.ts +0 -522
  244. package/src/pi-args.ts +0 -43
  245. package/src/pi-invocation.ts +0 -168
  246. package/src/process-control.ts +0 -43
  247. package/src/prompt-resources.ts +0 -38
  248. package/src/prompt-source-safety.ts +0 -48
  249. package/src/protocol.ts +0 -76
  250. package/src/registry-types.ts +0 -204
  251. package/src/registry.ts +0 -1698
  252. package/src/render-common.ts +0 -252
  253. package/src/render.ts +0 -704
  254. package/src/result-contract.ts +0 -431
  255. package/src/retained-semantic-state.ts +0 -100
  256. package/src/rpc-timeout-finalization.ts +0 -207
  257. package/src/rpc-transport-metadata.ts +0 -65
  258. package/src/rpc-transport.ts +0 -1035
  259. package/src/rpc-turn-capture.ts +0 -217
  260. package/src/runner-outcome.ts +0 -31
  261. package/src/runner-result.ts +0 -55
  262. package/src/runner-types.ts +0 -102
  263. package/src/runner-usage.ts +0 -48
  264. package/src/runner.ts +0 -866
  265. package/src/safe-text.ts +0 -67
  266. package/src/semantic-snapshot.ts +0 -214
  267. package/src/session-guidance-contract.ts +0 -399
  268. package/src/settings/inspection.ts +0 -302
  269. package/src/settings/schema.ts +0 -195
  270. package/src/settings-reader.ts +0 -215
  271. package/src/settings.ts +0 -452
  272. package/src/spawn-idempotency.ts +0 -68
  273. package/src/stateful-agent-view.ts +0 -85
  274. package/src/stateful-config.ts +0 -13
  275. package/src/stateful-guidance.ts +0 -29
  276. package/src/stateful-lifecycle.ts +0 -74
  277. package/src/stateful-limit-ui.ts +0 -249
  278. package/src/stateful-limits.ts +0 -96
  279. package/src/stateful-prompt.ts +0 -49
  280. package/src/stateful-registration.ts +0 -1320
  281. package/src/stateful-render.ts +0 -318
  282. package/src/stateful-safety.ts +0 -47
  283. package/src/stateful-tool-params.ts +0 -215
  284. package/src/stateful.ts +0 -12
  285. package/src/subagent-details.ts +0 -43
  286. package/src/subagents-extension.ts +0 -355
  287. package/src/subprocess-transport.ts +0 -145
  288. package/src/supervision.ts +0 -104
  289. package/src/task-path.ts +0 -65
  290. package/src/timeout-checkpoint.ts +0 -305
  291. package/src/timeout-finalization.ts +0 -75
  292. package/src/tool-schema-compatibility.ts +0 -73
  293. package/src/transport-types.ts +0 -74
  294. package/src/transport-ui.ts +0 -135
  295. package/src/transport.ts +0 -43
  296. package/src/turn-budget.ts +0 -109
  297. package/src/usage-format.ts +0 -42
  298. package/src/usage-recording-config.ts +0 -13
  299. package/src/usage-recording-store.ts +0 -183
  300. package/src/usage-recording.ts +0 -478
  301. package/src/verification-harness.ts +0 -516
  302. package/src/verification-policy.ts +0 -67
  303. package/src/verification-receipt.ts +0 -275
  304. package/src/verified-execution-benchmark.ts +0 -86
  305. package/src/verified-execution-contract.ts +0 -191
  306. package/src/verified-execution-schema.ts +0 -32
  307. package/src/work-item-ledger.ts +0 -1404
  308. package/src/work-item-persistence.ts +0 -254
  309. package/src/workflow-completion-controller.ts +0 -397
  310. package/src/workflow-planning.ts +0 -172
  311. package/src/workflow-tree-identity.ts +0 -289
  312. package/src/workflow-ui.ts +0 -69
  313. package/src/workflow-verification.ts +0 -296
  314. package/src/workspace.ts +0 -174
package/src/consult.ts DELETED
@@ -1,566 +0,0 @@
1
- import * as path from "node:path";
2
- import type { AgentToolResult } from "@earendil-works/pi-agent-core";
3
- import type { Usage } from "@earendil-works/pi-ai";
4
- import { CONFIG_DIR_NAME, type ExtensionContext } from "@earendil-works/pi-coding-agent";
5
- import { DEFAULT_AGENT_CATALOG_MAX_ITEMS } from "./agents/catalog.js";
6
- import { type AgentDiscoveryResult, discoverAgents } from "./agents/discovery.js";
7
- import {
8
- type AgentConfig,
9
- type AgentScope,
10
- type ConsultResourcePolicy,
11
- isThinkingLevel,
12
- type SubagentSettings,
13
- type THINKING_LEVELS,
14
- } from "./agents/types.js";
15
- import { resolveConsultTools } from "./consult-policy.js";
16
- import { resolveConsultResourceLaunchPolicy } from "./consult-resources.js";
17
- import type {
18
- ConsultDetails,
19
- ConsultProgress,
20
- ConsultProgressActivity,
21
- SubagentConsultParams,
22
- } from "./consult-tool.js";
23
- import {
24
- assertConsultationTargetAllowed,
25
- type ResolvedSubagentTarget,
26
- resolveSubagentTarget,
27
- } from "./cwd-policy.js";
28
- import {
29
- assertSubagentDepthAllowed,
30
- resolveDefaultSubagentTimeoutMs,
31
- } from "./execution/runtime-policy.js";
32
- import {
33
- DEFAULT_MAX_CONTEXT_BYTES,
34
- DEFAULT_MAX_STDERR_BYTES,
35
- MAX_SUBAGENT_TIMEOUT_MS,
36
- } from "./limits.js";
37
- import { getResultFinalOutput, isResultError, runSingleAgent } from "./runner.js";
38
- import type { ChildLaunchPolicy, SingleResult } from "./runner-types.js";
39
- import { boundedPrivateText, boundText, safeDisplayPath, safeTerminalLine } from "./safe-text.js";
40
- import {
41
- DEFAULT_CONSULT_RESOURCE_POLICY,
42
- DEFAULT_CONSULTATION_CWD_POLICY,
43
- } from "./settings/inspection.js";
44
- import { resolveSubagentThinkingLevel } from "./settings.js";
45
- import type { SubagentDetails } from "./subagent-details.js";
46
-
47
- export { registerSubagentConsult } from "./consult-registration.js";
48
- export type {
49
- ConsultDetails,
50
- ConsultProgress,
51
- ConsultProgressActivity,
52
- } from "./consult-tool.js";
53
- export { SubagentConsultParams } from "./consult-tool.js";
54
-
55
- export interface ConsultChildRequest {
56
- agent: AgentConfig;
57
- task: string;
58
- cwd: string;
59
- agentScope: AgentScope;
60
- thinkingLevel?: (typeof THINKING_LEVELS)[number];
61
- timeoutMs: number;
62
- effectiveTools: string[];
63
- resourcePolicy: ConsultResourcePolicy;
64
- launchPolicy: ChildLaunchPolicy;
65
- signal: AbortSignal;
66
- onUpdate?: (result: SingleResult) => void;
67
- }
68
-
69
- export interface RegisterSubagentConsultOptions {
70
- getSettings(): SubagentSettings | undefined;
71
- runChild?: (request: ConsultChildRequest) => Promise<SingleResult>;
72
- invocationOverride?: { command: string; argsPrefix?: string[] };
73
- resolveResourceLaunchPolicy?: typeof resolveConsultResourceLaunchPolicy;
74
- }
75
-
76
- const READ_ONLY_INSTRUCTION = [
77
- "This is a read-only consultation.",
78
- "Use only the tools made available by the executor to inspect and reason about existing content.",
79
- "Do not claim to edit files, run shell commands, mutate state, or persist a session.",
80
- "If the task asks for implementation, return analysis or instructions instead of claiming changes.",
81
- ].join("\n");
82
-
83
- const MAX_UNKNOWN_AGENT_NAME_BYTES = 128;
84
-
85
- export async function executeSubagentConsult(
86
- params: SubagentConsultParams,
87
- signal: AbortSignal,
88
- onUpdate: ((partial: AgentToolResult<ConsultDetails>) => void) | undefined,
89
- ctx: ExtensionContext,
90
- options: RegisterSubagentConsultOptions,
91
- isCurrent: () => boolean = () => true,
92
- ): Promise<AgentToolResult<ConsultDetails>> {
93
- const operation = validateConsultParams(params);
94
- assertSubagentDepthAllowed();
95
- if (signal.aborted) throw abortError("Subagent consultation was aborted before start");
96
- return executeConsult(
97
- operation,
98
- ctx,
99
- signal,
100
- options,
101
- (partial) => {
102
- if (signal.aborted || !isCurrent()) return;
103
- onUpdate?.(partial);
104
- },
105
- isCurrent,
106
- () => undefined,
107
- );
108
- }
109
-
110
- function formatAvailableConsultAgents(discovery: AgentDiscoveryResult): string {
111
- const listed = discovery.agents.slice(0, DEFAULT_AGENT_CATALOG_MAX_ITEMS);
112
- const labels = listed.map(
113
- (agent) => `${safeTerminalLine(agent.name, MAX_UNKNOWN_AGENT_NAME_BYTES)} (${agent.source})`,
114
- );
115
- const omitted =
116
- discovery.agents.length - listed.length + (discovery.omittedAgentDefinitions ?? 0);
117
- const parts = [labels.join(", ") || "none"];
118
- if (omitted > 0) {
119
- parts.push(`[${omitted} additional agent definition${omitted === 1 ? "" : "s"} omitted.]`);
120
- }
121
- if (discovery.metadataDiscoveryIncomplete) {
122
- parts.push("[Agent metadata discovery was incomplete; some definitions may be unavailable.]");
123
- }
124
- return parts.join(" ");
125
- }
126
-
127
- function validateConsultParams(
128
- params: unknown,
129
- ): Required<Pick<SubagentConsultParams, "agent" | "task" | "agentScope" | "confirmProjectAgents">> &
130
- Pick<SubagentConsultParams, "cwd" | "timeoutMs" | "thinkingLevel"> {
131
- if (!params || typeof params !== "object" || Array.isArray(params)) {
132
- throw new Error("subagent_consult parameters must be an object");
133
- }
134
- const values = params as Record<string, unknown>;
135
- const allowed = [
136
- "agent",
137
- "task",
138
- "agentScope",
139
- "confirmProjectAgents",
140
- "cwd",
141
- "timeoutMs",
142
- "thinkingLevel",
143
- ];
144
- const unexpected = Object.keys(values).find(
145
- (key) => values[key] !== undefined && !allowed.includes(key),
146
- );
147
- if (unexpected) throw new Error(`subagent_consult does not accept ${unexpected}`);
148
- const agent = requiredString(values.agent, "agent");
149
- const task = requiredString(values.task, "task");
150
- if (task.includes("\0")) throw new Error("subagent_consult task must not contain NUL bytes");
151
- if (Buffer.byteLength(task, "utf8") > DEFAULT_MAX_CONTEXT_BYTES) {
152
- throw new Error(
153
- `subagent_consult task must be at most ${DEFAULT_MAX_CONTEXT_BYTES} UTF-8 bytes`,
154
- );
155
- }
156
- const agentScope = optionalScope(values.agentScope);
157
- if (
158
- values.confirmProjectAgents !== undefined &&
159
- typeof values.confirmProjectAgents !== "boolean"
160
- ) {
161
- throw new Error("subagent_consult confirmProjectAgents must be boolean");
162
- }
163
- if (values.cwd !== undefined && (typeof values.cwd !== "string" || !values.cwd.trim())) {
164
- throw new Error("subagent_consult cwd must be a non-empty string");
165
- }
166
- if (
167
- values.timeoutMs !== undefined &&
168
- (typeof values.timeoutMs !== "number" ||
169
- !Number.isFinite(values.timeoutMs) ||
170
- values.timeoutMs < 1 ||
171
- values.timeoutMs > MAX_SUBAGENT_TIMEOUT_MS)
172
- ) {
173
- throw new Error(`subagent_consult timeoutMs must be between 1 and ${MAX_SUBAGENT_TIMEOUT_MS}`);
174
- }
175
- if (values.thinkingLevel !== undefined && !isThinkingLevel(values.thinkingLevel)) {
176
- throw new Error("subagent_consult thinkingLevel is invalid");
177
- }
178
- return {
179
- agent,
180
- task,
181
- agentScope,
182
- confirmProjectAgents: values.confirmProjectAgents !== false,
183
- cwd: values.cwd as string | undefined,
184
- timeoutMs: values.timeoutMs as number | undefined,
185
- thinkingLevel: values.thinkingLevel as (typeof THINKING_LEVELS)[number] | undefined,
186
- };
187
- }
188
-
189
- async function executeConsult(
190
- operation: ReturnType<typeof validateConsultParams>,
191
- ctx: ExtensionContext,
192
- signal: AbortSignal,
193
- options: RegisterSubagentConsultOptions,
194
- emitUpdate: (partial: AgentToolResult<ConsultDetails>) => void,
195
- isCurrent: () => boolean,
196
- trackWork: (work: Promise<unknown>) => void,
197
- ): Promise<AgentToolResult<ConsultDetails>> {
198
- if (
199
- (operation.agentScope === "project" || operation.agentScope === "both") &&
200
- !ctx.isProjectTrusted()
201
- ) {
202
- throw new Error("Project-local subagent definitions require a trusted project");
203
- }
204
- const settings = options.getSettings();
205
- const target = resolveSubagentTarget({
206
- workspace: ctx.cwd,
207
- requestedCwd: operation.cwd,
208
- currentProjectTrusted: ctx.isProjectTrusted(),
209
- });
210
- assertConsultationTargetAllowed(
211
- target,
212
- settings?.cwdPolicy?.consultation ?? DEFAULT_CONSULTATION_CWD_POLICY,
213
- );
214
- const discovery = discoverAgents(ctx.cwd, operation.agentScope, settings);
215
- const agent = discovery.agents.find((candidate) => candidate.name === operation.agent);
216
- if (!agent) {
217
- throw new Error(
218
- `Unknown subagent definition: ${boundedPrivateText(operation.agent, 256)}. ` +
219
- `Available agents for agentScope "${operation.agentScope}": ${formatAvailableConsultAgents(discovery)}`,
220
- );
221
- }
222
- const setupWork = resolveConsultSetup(operation, agent, settings, target, options);
223
- trackWork(setupWork);
224
- const setup = await setupWork;
225
- assertCurrentRequest(signal, isCurrent);
226
-
227
- if (agent.source === "project" && operation.confirmProjectAgents) {
228
- if (!ctx.hasUI) {
229
- throw new Error(
230
- "Project-local subagent confirmation requires UI; pass confirmProjectAgents: false explicitly in a trusted project",
231
- );
232
- }
233
- const approved = await ctx.ui.confirm(
234
- "Run project-local read-only agent?",
235
- `Agent: ${safeTerminalLine(agent.name, 256)}\nSource: ${safeTerminalLine(path.posix.join(CONFIG_DIR_NAME, "agents", path.basename(agent.filePath)))}`,
236
- );
237
- assertCurrentRequest(signal, isCurrent);
238
- if (!approved) {
239
- return {
240
- content: [{ type: "text", text: "Read-only subagent consultation cancelled." }],
241
- details: { ...setup.details, cancelled: true },
242
- };
243
- }
244
- }
245
- assertCurrentRequest(signal, isCurrent);
246
- emitUpdate(consultStartingUpdate(setup.details));
247
- assertCurrentRequest(signal, isCurrent);
248
- const runChild = options.runChild ?? ((request) => runConsultChild(request, options));
249
- const child = runChild({
250
- agent: setup.agent,
251
- task: operation.task,
252
- cwd: setup.cwd,
253
- agentScope: operation.agentScope,
254
- thinkingLevel: setup.thinkingLevel,
255
- timeoutMs: setup.timeoutMs,
256
- effectiveTools: setup.effectiveTools,
257
- resourcePolicy: setup.resourcePolicy,
258
- launchPolicy: setup.launchPolicy,
259
- signal,
260
- onUpdate: (result) => {
261
- if (signal.aborted || !isCurrent()) return;
262
- emitUpdate(consultUpdate(result, setup.details));
263
- },
264
- });
265
- trackWork(child);
266
- const result = await child;
267
- if (!isCurrent()) throw abortError("Subagent consultation owner was replaced");
268
- if (result.aborted && !result.processStarted) {
269
- throw abortError(result.errorMessage || "Subagent consultation was aborted before launch");
270
- }
271
- if (result.launchFailed) {
272
- throw new Error(
273
- boundedPrivateText(
274
- result.errorMessage || result.stderr.trim() || "Subagent consultation failed to launch",
275
- 2 * 1024,
276
- ),
277
- );
278
- }
279
- const error = isResultError(result);
280
- const output = error
281
- ? formatConsultFailure(result)
282
- : getResultFinalOutput(result) || "(no output)";
283
- const bounded = boundText(output);
284
- const details: ConsultDetails = {
285
- ...setup.details,
286
- child: projectChildResult(result),
287
- ...(error ? { isError: true } : {}),
288
- ...(bounded.truncated ? { truncated: true } : {}),
289
- };
290
- return {
291
- content: [{ type: "text", text: bounded.text }],
292
- details,
293
- usage: usageFromResult(result),
294
- };
295
- }
296
-
297
- async function resolveConsultSetup(
298
- operation: ReturnType<typeof validateConsultParams>,
299
- agent: AgentConfig,
300
- settings: SubagentSettings | undefined,
301
- target: ResolvedSubagentTarget,
302
- options: RegisterSubagentConsultOptions,
303
- ) {
304
- const requestedResourcePolicy = settings?.consult?.resources ?? DEFAULT_CONSULT_RESOURCE_POLICY;
305
- const resourcePolicy = target.trust.projectTrusted ? requestedResourcePolicy : "none";
306
- const effectiveTools = resolveConsultTools(agent.tools);
307
- const projectTrusted = target.trust.projectTrusted;
308
- const thinkingLevel = resolveSubagentThinkingLevel([agent], agent.name, operation.thinkingLevel);
309
- const timeoutMs = operation.timeoutMs ?? agent.timeoutMs ?? resolveDefaultSubagentTimeoutMs();
310
- if (!Number.isFinite(timeoutMs) || timeoutMs < 1 || timeoutMs > MAX_SUBAGENT_TIMEOUT_MS) {
311
- throw new Error(
312
- `Subagent consultation timeout must be between 1 and ${MAX_SUBAGENT_TIMEOUT_MS}ms`,
313
- );
314
- }
315
- const resolveResources =
316
- options.resolveResourceLaunchPolicy ?? resolveConsultResourceLaunchPolicy;
317
- const launchPolicy = await resolveResources(resourcePolicy, projectTrusted, target.cwd);
318
- launchPolicy.tools = effectiveTools;
319
- const childAgent: AgentConfig = {
320
- ...agent,
321
- tools: effectiveTools,
322
- systemPrompt: [agent.systemPrompt, READ_ONLY_INSTRUCTION].filter(Boolean).join("\n\n"),
323
- };
324
- const effectiveResources = {
325
- policy: resourcePolicy,
326
- projectResources: projectTrusted && resourcePolicy !== "none",
327
- contextFiles: !launchPolicy.disableContextFiles,
328
- skills: !launchPolicy.disableSkills,
329
- promptTemplates: !launchPolicy.disablePromptTemplates,
330
- };
331
- const details: ConsultDetails = {
332
- agent: boundedPrivateText(agent.name, 256),
333
- agentSource: agent.source,
334
- agentScope: operation.agentScope,
335
- cwd: safeDisplayPath(target.cwd, target.workspace),
336
- model: agent.model ? boundedPrivateText(agent.model, 256) : undefined,
337
- thinkingLevel,
338
- timeoutMs,
339
- policy: {
340
- requestedTools:
341
- agent.tools === undefined
342
- ? null
343
- : agent.tools.slice(0, 100).map((tool) => boundedPrivateText(tool, 256)),
344
- effectiveTools,
345
- cwdBoundary: target.boundary,
346
- targetTrust: {
347
- kind: target.trust.kind,
348
- projectTrusted,
349
- sourcePath: target.trust.sourcePath
350
- ? safeDisplayPath(target.trust.sourcePath, target.workspace)
351
- : undefined,
352
- warning: target.trust.warning,
353
- },
354
- requestedResources: requestedResourcePolicy,
355
- effectiveResources,
356
- ...(resourcePolicy !== requestedResourcePolicy
357
- ? { resourceDowngradeReason: `Target trust is ${target.trust.kind}` }
358
- : {}),
359
- extensions: "disabled",
360
- sessionPersistence: "disabled",
361
- retainedAgent: false,
362
- },
363
- };
364
- return {
365
- agent: childAgent,
366
- cwd: target.cwd,
367
- resourcePolicy,
368
- effectiveTools,
369
- thinkingLevel,
370
- timeoutMs,
371
- launchPolicy,
372
- details,
373
- };
374
- }
375
-
376
- async function runConsultChild(
377
- request: ConsultChildRequest,
378
- options: RegisterSubagentConsultOptions,
379
- ): Promise<SingleResult> {
380
- return runSingleAgent(
381
- request.cwd,
382
- [request.agent],
383
- request.agent.name,
384
- request.task,
385
- request.cwd,
386
- undefined,
387
- request.signal,
388
- request.thinkingLevel,
389
- request.timeoutMs,
390
- (partial) => {
391
- const result = partial.details.results[0];
392
- if (result) request.onUpdate?.(result);
393
- },
394
- (results): SubagentDetails => ({
395
- mode: "single",
396
- agentScope: request.agentScope,
397
- projectAgentsDir: null,
398
- results,
399
- }),
400
- options.invocationOverride,
401
- request.launchPolicy,
402
- );
403
- }
404
-
405
- function consultStartingUpdate(details: ConsultDetails): AgentToolResult<ConsultDetails> {
406
- return {
407
- content: [{ type: "text", text: "Read-only subagent consultation starting." }],
408
- details: {
409
- ...details,
410
- progress: {
411
- phase: "starting",
412
- recentActivity: [],
413
- recentActivityTotal: 0,
414
- usage: emptyProgressUsage(),
415
- },
416
- },
417
- };
418
- }
419
-
420
- function consultUpdate(
421
- result: SingleResult,
422
- details: ConsultDetails,
423
- ): AgentToolResult<ConsultDetails> {
424
- const output = boundText(getResultFinalOutput(result) || "(running...)");
425
- return {
426
- content: [{ type: "text", text: output.text }],
427
- details: {
428
- ...details,
429
- child: projectChildResult(result),
430
- progress: projectConsultProgress(result),
431
- },
432
- };
433
- }
434
-
435
- const CONSULT_ACTIVITY_ARGUMENTS: Record<"read" | "grep" | "find" | "ls", readonly string[]> = {
436
- read: ["path", "file_path", "offset", "limit"],
437
- grep: ["pattern", "path", "glob", "limit"],
438
- find: ["pattern", "path", "limit"],
439
- ls: ["path", "limit"],
440
- };
441
-
442
- function projectConsultProgress(result: SingleResult): ConsultProgress {
443
- const recentActivity: ConsultProgressActivity[] = [];
444
- for (const item of result.recentActivity ?? []) {
445
- if (item.type === "text") {
446
- const text = boundedPrivateText(item.text, 1024).trim();
447
- if (text) recentActivity.push({ type: "text", text });
448
- continue;
449
- }
450
- if (!Object.hasOwn(CONSULT_ACTIVITY_ARGUMENTS, item.name)) continue;
451
- const name = item.name as keyof typeof CONSULT_ACTIVITY_ARGUMENTS;
452
- const args: Record<string, string | number | boolean> = {};
453
- for (const key of CONSULT_ACTIVITY_ARGUMENTS[name]) {
454
- const value = item.args[key];
455
- if (typeof value === "string") args[key] = safeTerminalLine(value, 512);
456
- else if (typeof value === "number" && Number.isFinite(value)) args[key] = value;
457
- else if (typeof value === "boolean") args[key] = value;
458
- }
459
- recentActivity.push({ type: "toolCall", name, args });
460
- }
461
- return {
462
- phase: "running",
463
- recentActivity,
464
- recentActivityTotal: Math.max(recentActivity.length, result.recentActivityTotal ?? 0),
465
- actualProvider: result.actualProvider
466
- ? boundedPrivateText(result.actualProvider, 256)
467
- : undefined,
468
- actualModel: result.actualModel ? boundedPrivateText(result.actualModel, 256) : undefined,
469
- usage: {
470
- input: result.usage.input,
471
- output: result.usage.output,
472
- cacheRead: result.usage.cacheRead,
473
- cacheWrite: result.usage.cacheWrite,
474
- cost: result.usage.cost,
475
- contextTokens: result.usage.contextTokens,
476
- turns: result.usage.turns,
477
- },
478
- };
479
- }
480
-
481
- function emptyProgressUsage(): ConsultProgress["usage"] {
482
- return {
483
- input: 0,
484
- output: 0,
485
- cacheRead: 0,
486
- cacheWrite: 0,
487
- cost: 0,
488
- contextTokens: 0,
489
- turns: 0,
490
- };
491
- }
492
-
493
- function projectChildResult(result: SingleResult): Record<string, unknown> {
494
- return {
495
- exitCode: result.exitCode,
496
- stopReason:
497
- typeof result.stopReason === "string"
498
- ? boundedPrivateText(result.stopReason, 256)
499
- : undefined,
500
- timedOut: result.timedOut,
501
- aborted: result.aborted,
502
- truncated: result.truncated,
503
- malformedEvents: result.malformedEvents,
504
- processStarted: result.processStarted,
505
- actualProvider: result.actualProvider
506
- ? boundedPrivateText(result.actualProvider, 256)
507
- : undefined,
508
- actualModel: result.actualModel ? boundedPrivateText(result.actualModel, 256) : undefined,
509
- partialOutput: result.finalOutput
510
- ? boundedPrivateText(result.finalOutput, 8 * 1024)
511
- : undefined,
512
- error: result.errorMessage ? boundedPrivateText(result.errorMessage, 2 * 1024) : undefined,
513
- usage: { ...result.usage },
514
- };
515
- }
516
-
517
- function formatConsultFailure(result: SingleResult): string {
518
- const rawError = result.errorMessage || result.stderr.trim();
519
- const error = rawError ? boundedPrivateText(rawError, DEFAULT_MAX_STDERR_BYTES) : "";
520
- const output = getResultFinalOutput(result);
521
- return error && output
522
- ? `${error}\n\nPartial output:\n${output}`
523
- : error || output || "(no output)";
524
- }
525
-
526
- function usageFromResult(result: SingleResult): Usage {
527
- return {
528
- input: result.usage.input,
529
- output: result.usage.output,
530
- cacheRead: result.usage.cacheRead,
531
- cacheWrite: result.usage.cacheWrite,
532
- totalTokens:
533
- result.usage.totalTokens ??
534
- result.usage.input + result.usage.output + result.usage.cacheRead + result.usage.cacheWrite,
535
- cost: {
536
- input: result.usage.costInput ?? 0,
537
- output: result.usage.costOutput ?? 0,
538
- cacheRead: result.usage.costCacheRead ?? 0,
539
- cacheWrite: result.usage.costCacheWrite ?? 0,
540
- total: result.usage.cost,
541
- },
542
- };
543
- }
544
-
545
- function assertCurrentRequest(signal: AbortSignal, isCurrent: () => boolean): void {
546
- if (signal.aborted || !isCurrent()) throw abortError("Subagent consultation owner was replaced");
547
- }
548
-
549
- function abortError(message: string): Error {
550
- const error = new Error(message);
551
- error.name = "AbortError";
552
- return error;
553
- }
554
-
555
- function requiredString(value: unknown, name: string): string {
556
- if (typeof value !== "string" || !value.trim()) {
557
- throw new Error(`subagent_consult requires ${name}`);
558
- }
559
- return value;
560
- }
561
-
562
- function optionalScope(value: unknown): AgentScope {
563
- if (value === undefined) return "user";
564
- if (value === "user" || value === "project" || value === "both") return value;
565
- throw new Error("subagent_consult agentScope must be user, project, or both");
566
- }
package/src/context.ts DELETED
@@ -1,126 +0,0 @@
1
- import { createHash } from "node:crypto";
2
- import { DEFAULT_MAX_CONTEXT_BYTES, truncateUtf8, truncateUtf8Tail } from "./limits.js";
3
-
4
- export type ContextMode = "none" | "all" | "summary" | number;
5
-
6
- export interface ContextSnapshot {
7
- text: string;
8
- turns: number;
9
- truncated: boolean;
10
- sourceIds: string[];
11
- }
12
-
13
- function textParts(content: unknown): string {
14
- if (typeof content === "string") return content;
15
- if (!Array.isArray(content)) return "";
16
- return content
17
- .filter((part): part is { type: "text"; text: string } =>
18
- Boolean(
19
- part &&
20
- typeof part === "object" &&
21
- (part as { type?: unknown }).type === "text" &&
22
- typeof (part as { text?: unknown }).text === "string",
23
- ),
24
- )
25
- .map((part) => part.text)
26
- .join("\n");
27
- }
28
-
29
- export function redactPrivateText(text: string): string {
30
- const tagPattern = /<\/?private>/gi;
31
- let redacted = "";
32
- let cursor = 0;
33
- let depth = 0;
34
- for (const match of text.matchAll(tagPattern)) {
35
- const tag = match[0].toLowerCase();
36
- const index = match.index ?? cursor;
37
- if (depth === 0) redacted += text.slice(cursor, index);
38
- if (tag === "<private>") {
39
- if (depth === 0) redacted += "[private content omitted]";
40
- depth++;
41
- } else if (depth > 0) {
42
- depth--;
43
- } else {
44
- redacted += match[0];
45
- }
46
- cursor = index + match[0].length;
47
- }
48
- if (depth === 0) redacted += text.slice(cursor);
49
- return redacted
50
- .split("\n")
51
- .filter((line) => !line.includes("[subagent-private]"))
52
- .join("\n");
53
- }
54
-
55
- export function buildContextSnapshot(
56
- entries: readonly unknown[],
57
- mode: ContextMode,
58
- maxBytes = DEFAULT_MAX_CONTEXT_BYTES,
59
- selectedSourceIds?: readonly string[],
60
- ): ContextSnapshot {
61
- if (mode === "none") return { text: "", turns: 0, truncated: false, sourceIds: [] };
62
- const messages: Array<{ role: "user" | "assistant"; text: string; sourceId: string }> = [];
63
- for (const entry of entries) {
64
- if (!entry || typeof entry !== "object") continue;
65
- const candidate = entry as {
66
- id?: string;
67
- type?: string;
68
- role?: string;
69
- content?: unknown;
70
- message?: { role?: string; content?: unknown };
71
- };
72
- const message: { role?: string; content?: unknown } | undefined =
73
- candidate.type === "message" ? candidate.message : candidate;
74
- if (message?.role !== "user" && message?.role !== "assistant") continue;
75
- const text = redactPrivateText(textParts(message.content));
76
- if (text.trim()) {
77
- const sourceId =
78
- candidate.id ??
79
- createHash("sha256").update(`${message.role}\0${text}`).digest("hex").slice(0, 16);
80
- messages.push({ role: message.role, text, sourceId });
81
- }
82
- }
83
- const seenSourceIds = new Set<string>();
84
- const uniqueMessages = messages.filter((message) => {
85
- if (seenSourceIds.has(message.sourceId)) return false;
86
- seenSourceIds.add(message.sourceId);
87
- return true;
88
- });
89
- const selectedSet = selectedSourceIds ? new Set(selectedSourceIds) : undefined;
90
- const eligible = selectedSet
91
- ? uniqueMessages.filter((message) => selectedSet.has(message.sourceId))
92
- : uniqueMessages;
93
- const turnLimit =
94
- typeof mode === "number" ? Math.max(1, Math.floor(mode)) : Number.POSITIVE_INFINITY;
95
- let userTurns = 0;
96
- let start = eligible.length;
97
- for (let index = eligible.length - 1; index >= 0; index--) {
98
- if (eligible[index].role === "user") userTurns++;
99
- if (userTurns > turnLimit) break;
100
- start = index;
101
- }
102
- const selected = eligible.slice(start);
103
- const raw =
104
- mode === "summary" && selected.length > 4
105
- ? [
106
- `## Earlier context checkpoint\n${
107
- truncateUtf8(
108
- selected
109
- .slice(0, -4)
110
- .map((message) => `${message.role}: ${message.text}`)
111
- .join("\n"),
112
- Math.floor(maxBytes / 3),
113
- ).text
114
- }`,
115
- ...selected.slice(-4).map((message) => `## ${message.role}\n${message.text}`),
116
- ].join("\n\n")
117
- : selected.map((message) => `## ${message.role}\n${message.text}`).join("\n\n");
118
- const bounded =
119
- mode === "summary" ? truncateUtf8Tail(raw, maxBytes) : truncateUtf8(raw, maxBytes);
120
- return {
121
- text: bounded.text,
122
- turns: selected.filter((message) => message.role === "user").length,
123
- truncated: bounded.truncated,
124
- sourceIds: selected.map((message) => message.sourceId),
125
- };
126
- }