@code-yeongyu/senpi-agent-core 2026.9.29 → 2026.10.1-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 (285) hide show
  1. package/README.md +49 -20
  2. package/dist/agent-loop.d.ts +27 -3
  3. package/dist/agent-loop.js +192 -56
  4. package/dist/agent.d.ts +21 -7
  5. package/dist/agent.js +38 -18
  6. package/dist/harness/execution/tools.d.ts +1 -1
  7. package/dist/harness/execution/tools.js +0 -2
  8. package/dist/harness/messages.js +1 -0
  9. package/dist/harness/pico3/bash.d.ts +10 -0
  10. package/dist/harness/pico3/bash.js +25 -0
  11. package/dist/harness/pico3/bounded.d.ts +21 -0
  12. package/dist/harness/pico3/bounded.js +95 -0
  13. package/dist/harness/pico3/chord.d.ts +41 -0
  14. package/dist/harness/pico3/chord.js +147 -0
  15. package/dist/harness/pico3/context.d.ts +16 -0
  16. package/dist/harness/pico3/context.js +88 -0
  17. package/dist/harness/pico3/harness.d.ts +198 -0
  18. package/dist/harness/pico3/harness.js +645 -0
  19. package/dist/harness/pico3/hooks.d.ts +10 -0
  20. package/dist/harness/pico3/hooks.js +33 -0
  21. package/dist/harness/pico3/index.d.ts +19 -0
  22. package/dist/harness/pico3/index.js +15 -0
  23. package/dist/harness/pico3/jsonl.d.ts +58 -0
  24. package/dist/harness/pico3/jsonl.js +322 -0
  25. package/dist/harness/pico3/kinds/collapse.d.ts +49 -0
  26. package/dist/harness/pico3/kinds/collapse.js +191 -0
  27. package/dist/harness/pico3/kinds/entries.d.ts +78 -0
  28. package/dist/harness/pico3/kinds/entries.js +13 -0
  29. package/dist/harness/pico3/kinds/frames.d.ts +9 -0
  30. package/dist/harness/pico3/kinds/frames.js +75 -0
  31. package/dist/harness/pico3/kinds/generation.d.ts +94 -0
  32. package/dist/harness/pico3/kinds/generation.js +504 -0
  33. package/dist/harness/pico3/kinds/job.d.ts +44 -0
  34. package/dist/harness/pico3/kinds/job.js +143 -0
  35. package/dist/harness/pico3/kinds/plugin.d.ts +15 -0
  36. package/dist/harness/pico3/kinds/plugin.js +35 -0
  37. package/dist/harness/pico3/kinds/post-tools.d.ts +22 -0
  38. package/dist/harness/pico3/kinds/post-tools.js +145 -0
  39. package/dist/harness/pico3/kinds/task-api.d.ts +3 -0
  40. package/dist/harness/pico3/kinds/task-api.js +45 -0
  41. package/dist/harness/pico3/kinds/tool.d.ts +43 -0
  42. package/dist/harness/pico3/kinds/tool.js +373 -0
  43. package/dist/harness/pico3/legacy-tracker.d.ts +11 -0
  44. package/dist/harness/pico3/legacy-tracker.js +39 -0
  45. package/dist/harness/pico3/membrane.d.ts +23 -0
  46. package/dist/harness/pico3/membrane.js +140 -0
  47. package/dist/harness/pico3/memory.d.ts +53 -0
  48. package/dist/harness/pico3/memory.js +265 -0
  49. package/dist/harness/pico3/scheduler.d.ts +49 -0
  50. package/dist/harness/pico3/scheduler.js +437 -0
  51. package/dist/harness/pico3/session.d.ts +264 -0
  52. package/dist/harness/pico3/session.js +1328 -0
  53. package/dist/harness/pico3/system.d.ts +126 -0
  54. package/dist/harness/pico3/system.js +244 -0
  55. package/dist/harness/pico3/types.d.ts +973 -0
  56. package/dist/harness/pico3/types.js +94 -0
  57. package/dist/harness/pico3/view.d.ts +34 -0
  58. package/dist/harness/pico3/view.js +404 -0
  59. package/dist/harness/runtime/drive/tool-placement.js +2 -27
  60. package/dist/harness/telemetry.d.ts +36 -36
  61. package/dist/harness/tools/image.js +1 -1
  62. package/dist/harness/utils/read-folders/index.d.ts +2 -2
  63. package/dist/harness/utils/read-folders/index.js +2 -2
  64. package/dist/harness/utils/read-folders/tree-sitter/grammar-assets.js +1 -1
  65. package/dist/proxy.d.ts +2 -2
  66. package/dist/types.d.ts +111 -34
  67. package/package.json +10 -5
  68. package/dist/agent-loop.d.ts.map +0 -1
  69. package/dist/agent-loop.js.map +0 -1
  70. package/dist/agent.d.ts.map +0 -1
  71. package/dist/agent.js.map +0 -1
  72. package/dist/assistant-terminal-state.d.ts.map +0 -1
  73. package/dist/assistant-terminal-state.js.map +0 -1
  74. package/dist/empty-assistant-recovery.d.ts.map +0 -1
  75. package/dist/empty-assistant-recovery.js.map +0 -1
  76. package/dist/harness/agent-harness.d.ts.map +0 -1
  77. package/dist/harness/agent-harness.js.map +0 -1
  78. package/dist/harness/compaction/branch-summarization.d.ts.map +0 -1
  79. package/dist/harness/compaction/branch-summarization.js.map +0 -1
  80. package/dist/harness/compaction/compaction.d.ts.map +0 -1
  81. package/dist/harness/compaction/compaction.js.map +0 -1
  82. package/dist/harness/compaction/utils.d.ts.map +0 -1
  83. package/dist/harness/compaction/utils.js.map +0 -1
  84. package/dist/harness/config.d.ts.map +0 -1
  85. package/dist/harness/config.js.map +0 -1
  86. package/dist/harness/context.d.ts.map +0 -1
  87. package/dist/harness/context.js.map +0 -1
  88. package/dist/harness/env/nodejs.d.ts.map +0 -1
  89. package/dist/harness/env/nodejs.js.map +0 -1
  90. package/dist/harness/events.d.ts.map +0 -1
  91. package/dist/harness/events.js.map +0 -1
  92. package/dist/harness/execution/assistant.d.ts.map +0 -1
  93. package/dist/harness/execution/assistant.js.map +0 -1
  94. package/dist/harness/execution/effect-gate.d.ts.map +0 -1
  95. package/dist/harness/execution/effect-gate.js.map +0 -1
  96. package/dist/harness/execution/tools.d.ts.map +0 -1
  97. package/dist/harness/execution/tools.js.map +0 -1
  98. package/dist/harness/hooks.d.ts.map +0 -1
  99. package/dist/harness/hooks.js.map +0 -1
  100. package/dist/harness/messages.d.ts.map +0 -1
  101. package/dist/harness/messages.js.map +0 -1
  102. package/dist/harness/prompt-templates.d.ts.map +0 -1
  103. package/dist/harness/prompt-templates.js.map +0 -1
  104. package/dist/harness/result.d.ts.map +0 -1
  105. package/dist/harness/result.js.map +0 -1
  106. package/dist/harness/runtime/drive/boundary.d.ts.map +0 -1
  107. package/dist/harness/runtime/drive/boundary.js.map +0 -1
  108. package/dist/harness/runtime/drive/checkpoint.d.ts.map +0 -1
  109. package/dist/harness/runtime/drive/checkpoint.js.map +0 -1
  110. package/dist/harness/runtime/drive/deferred.d.ts.map +0 -1
  111. package/dist/harness/runtime/drive/deferred.js.map +0 -1
  112. package/dist/harness/runtime/drive/generation.d.ts.map +0 -1
  113. package/dist/harness/runtime/drive/generation.js.map +0 -1
  114. package/dist/harness/runtime/drive/reconcile.d.ts.map +0 -1
  115. package/dist/harness/runtime/drive/reconcile.js.map +0 -1
  116. package/dist/harness/runtime/drive/recovery.d.ts.map +0 -1
  117. package/dist/harness/runtime/drive/recovery.js.map +0 -1
  118. package/dist/harness/runtime/drive/response.d.ts.map +0 -1
  119. package/dist/harness/runtime/drive/response.js.map +0 -1
  120. package/dist/harness/runtime/drive/retry.d.ts.map +0 -1
  121. package/dist/harness/runtime/drive/retry.js.map +0 -1
  122. package/dist/harness/runtime/drive/structural.d.ts.map +0 -1
  123. package/dist/harness/runtime/drive/structural.js.map +0 -1
  124. package/dist/harness/runtime/drive/terminal.d.ts.map +0 -1
  125. package/dist/harness/runtime/drive/terminal.js.map +0 -1
  126. package/dist/harness/runtime/drive/tool-placement.d.ts.map +0 -1
  127. package/dist/harness/runtime/drive/tool-placement.js.map +0 -1
  128. package/dist/harness/runtime/drive/tools.d.ts.map +0 -1
  129. package/dist/harness/runtime/drive/tools.js.map +0 -1
  130. package/dist/harness/runtime/drive.d.ts.map +0 -1
  131. package/dist/harness/runtime/drive.js.map +0 -1
  132. package/dist/harness/runtime/harness.d.ts.map +0 -1
  133. package/dist/harness/runtime/harness.js.map +0 -1
  134. package/dist/harness/runtime/index.d.ts.map +0 -1
  135. package/dist/harness/runtime/index.js.map +0 -1
  136. package/dist/harness/runtime/lane.d.ts.map +0 -1
  137. package/dist/harness/runtime/lane.js.map +0 -1
  138. package/dist/harness/runtime/progress.d.ts.map +0 -1
  139. package/dist/harness/runtime/progress.js.map +0 -1
  140. package/dist/harness/runtime/reducer.d.ts.map +0 -1
  141. package/dist/harness/runtime/reducer.js.map +0 -1
  142. package/dist/harness/runtime/restore.d.ts.map +0 -1
  143. package/dist/harness/runtime/restore.js.map +0 -1
  144. package/dist/harness/runtime/transcript.d.ts.map +0 -1
  145. package/dist/harness/runtime/transcript.js.map +0 -1
  146. package/dist/harness/runtime/types.d.ts.map +0 -1
  147. package/dist/harness/runtime/types.js.map +0 -1
  148. package/dist/harness/session/commit.d.ts.map +0 -1
  149. package/dist/harness/session/commit.js.map +0 -1
  150. package/dist/harness/session/context.d.ts.map +0 -1
  151. package/dist/harness/session/context.js.map +0 -1
  152. package/dist/harness/session/fork-policy.d.ts.map +0 -1
  153. package/dist/harness/session/fork-policy.js.map +0 -1
  154. package/dist/harness/session/fork.d.ts.map +0 -1
  155. package/dist/harness/session/fork.js.map +0 -1
  156. package/dist/harness/session/in-memory-storage-state.d.ts.map +0 -1
  157. package/dist/harness/session/in-memory-storage-state.js.map +0 -1
  158. package/dist/harness/session/index.d.ts.map +0 -1
  159. package/dist/harness/session/index.js.map +0 -1
  160. package/dist/harness/session/jsonl/codec.d.ts.map +0 -1
  161. package/dist/harness/session/jsonl/codec.js.map +0 -1
  162. package/dist/harness/session/jsonl/fork.d.ts.map +0 -1
  163. package/dist/harness/session/jsonl/fork.js.map +0 -1
  164. package/dist/harness/session/jsonl/index.d.ts.map +0 -1
  165. package/dist/harness/session/jsonl/index.js.map +0 -1
  166. package/dist/harness/session/jsonl/io.d.ts.map +0 -1
  167. package/dist/harness/session/jsonl/io.js.map +0 -1
  168. package/dist/harness/session/jsonl/legacy-v3.d.ts.map +0 -1
  169. package/dist/harness/session/jsonl/legacy-v3.js.map +0 -1
  170. package/dist/harness/session/jsonl/repo.d.ts.map +0 -1
  171. package/dist/harness/session/jsonl/repo.js.map +0 -1
  172. package/dist/harness/session/jsonl/storage.d.ts.map +0 -1
  173. package/dist/harness/session/jsonl/storage.js.map +0 -1
  174. package/dist/harness/session/jsonl/types.d.ts.map +0 -1
  175. package/dist/harness/session/jsonl/types.js.map +0 -1
  176. package/dist/harness/session/memory.d.ts.map +0 -1
  177. package/dist/harness/session/memory.js.map +0 -1
  178. package/dist/harness/session/mutation-line.d.ts.map +0 -1
  179. package/dist/harness/session/mutation-line.js.map +0 -1
  180. package/dist/harness/session/session.d.ts.map +0 -1
  181. package/dist/harness/session/session.js.map +0 -1
  182. package/dist/harness/session/testing/benchmark/datasets.d.ts.map +0 -1
  183. package/dist/harness/session/testing/benchmark/datasets.js.map +0 -1
  184. package/dist/harness/session/testing/benchmark/session-repo.d.ts.map +0 -1
  185. package/dist/harness/session/testing/benchmark/session-repo.js.map +0 -1
  186. package/dist/harness/session/testing/benchmark/storage.d.ts.map +0 -1
  187. package/dist/harness/session/testing/benchmark/storage.js.map +0 -1
  188. package/dist/harness/session/testing/conformance/session-repo.d.ts.map +0 -1
  189. package/dist/harness/session/testing/conformance/session-repo.js.map +0 -1
  190. package/dist/harness/session/testing/conformance/storage.d.ts.map +0 -1
  191. package/dist/harness/session/testing/conformance/storage.js.map +0 -1
  192. package/dist/harness/session/testing/gating-storage.d.ts.map +0 -1
  193. package/dist/harness/session/testing/gating-storage.js.map +0 -1
  194. package/dist/harness/session/testing/index.d.ts.map +0 -1
  195. package/dist/harness/session/testing/index.js.map +0 -1
  196. package/dist/harness/session/testing/instrumented-storage.d.ts.map +0 -1
  197. package/dist/harness/session/testing/instrumented-storage.js.map +0 -1
  198. package/dist/harness/session/testing/storage-decorator.d.ts.map +0 -1
  199. package/dist/harness/session/testing/storage-decorator.js.map +0 -1
  200. package/dist/harness/session/testing/types.d.ts.map +0 -1
  201. package/dist/harness/session/testing/types.js.map +0 -1
  202. package/dist/harness/session/types.d.ts.map +0 -1
  203. package/dist/harness/session/types.js.map +0 -1
  204. package/dist/harness/session/values.d.ts.map +0 -1
  205. package/dist/harness/session/values.js.map +0 -1
  206. package/dist/harness/skills.d.ts.map +0 -1
  207. package/dist/harness/skills.js.map +0 -1
  208. package/dist/harness/system-prompt.d.ts.map +0 -1
  209. package/dist/harness/system-prompt.js.map +0 -1
  210. package/dist/harness/telemetry.d.ts.map +0 -1
  211. package/dist/harness/telemetry.js.map +0 -1
  212. package/dist/harness/tools/bash.d.ts.map +0 -1
  213. package/dist/harness/tools/bash.js.map +0 -1
  214. package/dist/harness/tools/edit-diff.d.ts.map +0 -1
  215. package/dist/harness/tools/edit-diff.js.map +0 -1
  216. package/dist/harness/tools/edit.d.ts.map +0 -1
  217. package/dist/harness/tools/edit.js.map +0 -1
  218. package/dist/harness/tools/file-mutation-queue.d.ts.map +0 -1
  219. package/dist/harness/tools/file-mutation-queue.js.map +0 -1
  220. package/dist/harness/tools/image.d.ts.map +0 -1
  221. package/dist/harness/tools/image.js.map +0 -1
  222. package/dist/harness/tools/index.d.ts.map +0 -1
  223. package/dist/harness/tools/index.js.map +0 -1
  224. package/dist/harness/tools/path-utils.d.ts.map +0 -1
  225. package/dist/harness/tools/path-utils.js.map +0 -1
  226. package/dist/harness/tools/post-mutate.d.ts.map +0 -1
  227. package/dist/harness/tools/post-mutate.js.map +0 -1
  228. package/dist/harness/tools/read.d.ts.map +0 -1
  229. package/dist/harness/tools/read.js.map +0 -1
  230. package/dist/harness/tools/tool-context.d.ts.map +0 -1
  231. package/dist/harness/tools/tool-context.js.map +0 -1
  232. package/dist/harness/tools/write.d.ts.map +0 -1
  233. package/dist/harness/tools/write.js.map +0 -1
  234. package/dist/harness/types.d.ts.map +0 -1
  235. package/dist/harness/types.js.map +0 -1
  236. package/dist/harness/utils/adaptive-publisher.d.ts.map +0 -1
  237. package/dist/harness/utils/adaptive-publisher.js.map +0 -1
  238. package/dist/harness/utils/output-capture.d.ts.map +0 -1
  239. package/dist/harness/utils/output-capture.js.map +0 -1
  240. package/dist/harness/utils/read-folders/brace-scanner.d.ts.map +0 -1
  241. package/dist/harness/utils/read-folders/brace-scanner.js.map +0 -1
  242. package/dist/harness/utils/read-folders/compose.d.ts.map +0 -1
  243. package/dist/harness/utils/read-folders/compose.js.map +0 -1
  244. package/dist/harness/utils/read-folders/header-protection.d.ts.map +0 -1
  245. package/dist/harness/utils/read-folders/header-protection.js.map +0 -1
  246. package/dist/harness/utils/read-folders/index.d.ts.map +0 -1
  247. package/dist/harness/utils/read-folders/index.js.map +0 -1
  248. package/dist/harness/utils/read-folders/lexical-context.d.ts.map +0 -1
  249. package/dist/harness/utils/read-folders/lexical-context.js.map +0 -1
  250. package/dist/harness/utils/read-folders/lexical-spans.d.ts.map +0 -1
  251. package/dist/harness/utils/read-folders/lexical-spans.js.map +0 -1
  252. package/dist/harness/utils/read-folders/prepare.d.ts.map +0 -1
  253. package/dist/harness/utils/read-folders/prepare.js.map +0 -1
  254. package/dist/harness/utils/read-folders/tree-sitter/engine.d.ts.map +0 -1
  255. package/dist/harness/utils/read-folders/tree-sitter/engine.js.map +0 -1
  256. package/dist/harness/utils/read-folders/tree-sitter/grammar-assets.d.ts.map +0 -1
  257. package/dist/harness/utils/read-folders/tree-sitter/grammar-assets.js.map +0 -1
  258. package/dist/harness/utils/read-folders/tree-sitter/syntax.d.ts.map +0 -1
  259. package/dist/harness/utils/read-folders/tree-sitter/syntax.js.map +0 -1
  260. package/dist/harness/utils/read-folders/types.d.ts.map +0 -1
  261. package/dist/harness/utils/read-folders/types.js.map +0 -1
  262. package/dist/harness/utils/segmented-read-view.d.ts.map +0 -1
  263. package/dist/harness/utils/segmented-read-view.js.map +0 -1
  264. package/dist/harness/utils/shell-output.d.ts.map +0 -1
  265. package/dist/harness/utils/shell-output.js.map +0 -1
  266. package/dist/harness/utils/truncate.d.ts.map +0 -1
  267. package/dist/harness/utils/truncate.js.map +0 -1
  268. package/dist/harness/utils/usage.d.ts.map +0 -1
  269. package/dist/harness/utils/usage.js.map +0 -1
  270. package/dist/index.d.ts.map +0 -1
  271. package/dist/index.js.map +0 -1
  272. package/dist/node.d.ts.map +0 -1
  273. package/dist/node.js.map +0 -1
  274. package/dist/proxy.d.ts.map +0 -1
  275. package/dist/proxy.js.map +0 -1
  276. package/dist/search/index.d.ts.map +0 -1
  277. package/dist/search/index.js.map +0 -1
  278. package/dist/stream-fn.d.ts.map +0 -1
  279. package/dist/stream-fn.js.map +0 -1
  280. package/dist/tool-arguments.d.ts.map +0 -1
  281. package/dist/tool-arguments.js.map +0 -1
  282. package/dist/tool-name-alias.d.ts.map +0 -1
  283. package/dist/tool-name-alias.js.map +0 -1
  284. package/dist/types.d.ts.map +0 -1
  285. package/dist/types.js.map +0 -1
package/dist/agent.js CHANGED
@@ -3,7 +3,10 @@ import { buildProviderContext as buildProviderContextFromAgentContext, runAgentL
3
3
  import { ProviderRetryWatchdogAbortError } from "./assistant-terminal-state.js";
4
4
  import { getDefaultStreamFn } from "./stream-fn.js";
5
5
  function defaultConvertToLlm(messages) {
6
- return messages.filter((message) => message.role === "user" || message.role === "assistant" || message.role === "toolResult");
6
+ return messages.filter((message) => message.role === "system" ||
7
+ message.role === "user" ||
8
+ message.role === "assistant" ||
9
+ message.role === "toolResult");
7
10
  }
8
11
  const EMPTY_USAGE = {
9
12
  input: 0,
@@ -68,18 +71,16 @@ class PendingMessageQueue {
68
71
  getClearGeneration() {
69
72
  return this.clearGeneration;
70
73
  }
71
- drain() {
72
- if (this.mode === "all") {
73
- const drained = this.messages.slice();
74
- this.messages = [];
75
- return drained;
76
- }
74
+ peek() {
75
+ if (this.mode === "all")
76
+ return this.messages.slice();
77
77
  const first = this.messages[0];
78
- if (!first) {
79
- return [];
80
- }
81
- this.messages = this.messages.slice(1);
82
- return [first];
78
+ return first ? [first] : [];
79
+ }
80
+ drain() {
81
+ const drained = this.peek();
82
+ this.messages = this.messages.slice(drained.length);
83
+ return drained;
83
84
  }
84
85
  prepend(messages) {
85
86
  this.messages = [...messages, ...this.messages];
@@ -110,9 +111,11 @@ export class Agent {
110
111
  this.getApiKey = runtimeOptions.getApiKey;
111
112
  this.onPayload = runtimeOptions.onPayload;
112
113
  this.onResponse = runtimeOptions.onResponse;
114
+ this.onProviderStreamEvent = runtimeOptions.onProviderStreamEvent;
113
115
  this.beforeToolCall = runtimeOptions.beforeToolCall;
114
116
  this.afterToolCall = runtimeOptions.afterToolCall;
115
- this.shouldStopAfterTurn = runtimeOptions.shouldStopAfterTurn;
117
+ this.finishTurn = runtimeOptions.finishTurn;
118
+ this.prepareRequest = runtimeOptions.prepareRequest;
116
119
  this.prepareNextTurn = runtimeOptions.prepareNextTurn;
117
120
  this.prepareNextTurnWithContext = runtimeOptions.prepareNextTurnWithContext;
118
121
  this.steeringQueue = new PendingMessageQueue(runtimeOptions.steeringMode ?? "one-at-a-time");
@@ -190,6 +193,11 @@ export class Agent {
190
193
  hasQueuedMessages() {
191
194
  return this.steeringQueue.hasItems() || this.followUpQueue.hasItems();
192
195
  }
196
+ /** Preview the messages selected for the next turn without consuming them. */
197
+ peekQueuedMessages() {
198
+ const steering = this.steeringQueue.peek();
199
+ return steering.length > 0 ? steering : this.followUpQueue.peek();
200
+ }
193
201
  /** Active abort signal for the current run, if any. */
194
202
  get signal() {
195
203
  return this.activeRun?.abortController.signal;
@@ -217,7 +225,10 @@ export class Agent {
217
225
  waitForIdle() {
218
226
  return this.activeRun?.promise ?? Promise.resolve();
219
227
  }
220
- /** Clear transcript state, runtime state, and queued messages. */
228
+ /**
229
+ * Clear transcript state, runtime state, and queued messages. Fork: the prompt/tool baseline is agent state
230
+ * (`systemPrompt`, `tools`), so transcript system messages are conversation content and are cleared too.
231
+ */
221
232
  reset() {
222
233
  if (this.activeRun) {
223
234
  throw new Error("Agent is already processing. Wait for completion before resetting.");
@@ -280,7 +291,7 @@ export class Agent {
280
291
  throw new Error("Agent is already processing. Wait for completion before continuing.");
281
292
  }
282
293
  const lastMessage = this._state.messages[this._state.messages.length - 1];
283
- if (!lastMessage) {
294
+ if (!lastMessage || this._state.messages.every((message) => message.role === "system")) {
284
295
  throw new Error("No messages to continue from");
285
296
  }
286
297
  if (lastMessage.role === "assistant") {
@@ -344,7 +355,7 @@ export class Agent {
344
355
  let skipInitialSteeringPoll = options.skipInitialSteeringPoll === true;
345
356
  let steeringQueueGeneration = this.steeringQueue.getClearGeneration();
346
357
  let followUpQueueGeneration = this.followUpQueue.getClearGeneration();
347
- const shouldStopAfterTurn = this.shouldStopAfterTurn;
358
+ const finishTurn = this.finishTurn;
348
359
  const reasoning = this._state.reasoningBaseline ?? this._state.thinkingLevel;
349
360
  return {
350
361
  model: this._state.model,
@@ -353,6 +364,7 @@ export class Agent {
353
364
  sessionId: this.sessionId,
354
365
  onPayload: this.onPayload,
355
366
  onResponse: this.onResponse,
367
+ onProviderStreamEvent: this.onProviderStreamEvent,
356
368
  transport: this.transport,
357
369
  thinkingBudgets: this.thinkingBudgets,
358
370
  timeoutMs: this.timeoutMs,
@@ -367,9 +379,17 @@ export class Agent {
367
379
  resolveUnknownToolCall: this.resolveUnknownToolCall,
368
380
  beforeToolCall: this.beforeToolCall,
369
381
  afterToolCall: this.afterToolCall,
370
- shouldStopAfterTurn: shouldStopAfterTurn
371
- ? async (context) => await shouldStopAfterTurn(context, this.signal)
382
+ // `{ action: "end" }` ends the run without polling queues, so the fork's post-run queue drain is
383
+ // suppressed for this run as well: queued input stays for its owner.
384
+ finishTurn: finishTurn
385
+ ? async (turn, signal) => {
386
+ const decision = (await finishTurn(turn, signal)) ?? undefined;
387
+ if (decision?.action === "end")
388
+ this.suppressQueuedMessageDrain();
389
+ return decision;
390
+ }
372
391
  : undefined,
392
+ prepareRequest: this.prepareRequest,
373
393
  prepareNextTurn: this.prepareNextTurnWithContext || this.prepareNextTurn
374
394
  ? async (context) => {
375
395
  if (this.prepareNextTurnWithContext) {
@@ -61,7 +61,7 @@ export declare function executeToolCall<TContext extends object | undefined>(cal
61
61
  /** Apply an after-tool patch field by field. */
62
62
  export declare function finalizeToolCall<TContext extends object | undefined>(call: ClearedToolCall<TContext>, executed: ExecutedToolCall, patch: AfterToolPatch | undefined): FinalizedToolCall;
63
63
  /** Reconstruct the canonical tool result represented by a staged transcript message. */
64
- export declare function toolResultFromMessage(message: ToolResultMessage<unknown>, terminate: boolean): AgentToolResult<unknown>;
64
+ export declare function toolResultFromMessage(message: ToolResultMessage, terminate: boolean): AgentToolResult;
65
65
  /** Convert finalized tool output to the provider-facing transcript message. */
66
66
  export declare function createToolResultMessage(call: FinalizedToolCall): ToolResultMessage;
67
67
  //# sourceMappingURL=tools.d.ts.map
@@ -100,7 +100,6 @@ export function toolResultFromMessage(message, terminate) {
100
100
  content: message.content,
101
101
  details: message.details,
102
102
  ...(message.usage === undefined ? {} : { usage: message.usage }),
103
- ...(message.addedToolNames === undefined ? {} : { addedToolNames: message.addedToolNames }),
104
103
  ...(terminate ? { terminate: true } : {}),
105
104
  };
106
105
  }
@@ -113,7 +112,6 @@ export function createToolResultMessage(call) {
113
112
  content: call.result.content ?? [],
114
113
  ...(call.result.details === undefined ? {} : { details: call.result.details }),
115
114
  ...(call.result.usage === undefined ? {} : { usage: call.result.usage }),
116
- ...(call.result.addedToolNames?.length ? { addedToolNames: call.result.addedToolNames } : {}),
117
115
  isError: call.isError,
118
116
  timestamp: Date.now(),
119
117
  };
@@ -90,6 +90,7 @@ export function convertToLlm(messages) {
90
90
  ],
91
91
  timestamp: m.timestamp,
92
92
  };
93
+ case "system":
93
94
  case "user":
94
95
  case "assistant":
95
96
  case "toolResult":
@@ -0,0 +1,10 @@
1
+ import { Type } from "typebox";
2
+ import type { ToolDeclaration } from "./types.ts";
3
+ declare const parameters: Type.TObject<{
4
+ command: Type.TString;
5
+ cwd: Type.TOptional<Type.TString>;
6
+ }>;
7
+ /** Run a shell command. Output is piped to the kernel; bounds come from `output`. */
8
+ export declare function bashTool(output?: ToolDeclaration["output"]): ToolDeclaration<typeof parameters>;
9
+ export {};
10
+ //# sourceMappingURL=bash.d.ts.map
@@ -0,0 +1,25 @@
1
+ import { spawn } from "node:child_process";
2
+ import { Type } from "typebox";
3
+ const parameters = Type.Object({ command: Type.String(), cwd: Type.Optional(Type.String()) });
4
+ /** Run a shell command. Output is piped to the kernel; bounds come from `output`. */
5
+ export function bashTool(output = {}) {
6
+ return {
7
+ name: "bash",
8
+ description: "Run a shell command",
9
+ parameters,
10
+ replay: "unsafe",
11
+ output,
12
+ async execute({ command, cwd }, api, ctx) {
13
+ const started = Date.now();
14
+ const child = spawn("bash", ["-c", command], { cwd, stdio: ["ignore", "pipe", "pipe"] });
15
+ const onAbort = () => child.kill("SIGKILL");
16
+ ctx.abortSignal?.addEventListener("abort", onAbort);
17
+ child.stdout.on("data", (c) => api.stream(c));
18
+ child.stderr.on("data", (c) => api.stream(c));
19
+ const { code, signal } = await new Promise((r) => child.on("close", (code, signal) => r({ code, signal })));
20
+ ctx.abortSignal?.removeEventListener("abort", onAbort);
21
+ return { isError: code !== 0, details: { exitCode: code, signal, ms: Date.now() - started } };
22
+ },
23
+ };
24
+ }
25
+ //# sourceMappingURL=bash.js.map
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Bounded byte collector. It retains a prefix or suffix constrained by both a byte
3
+ * budget and a newline budget, and accounts for every discarded byte/newline.
4
+ */
5
+ export declare class Bounded {
6
+ private bytes;
7
+ private readonly maxBytes;
8
+ private readonly maxLines;
9
+ private readonly retain;
10
+ droppedBytes: number;
11
+ droppedLines: number;
12
+ total: number;
13
+ constructor(maxBytes: number, maxLines: number, retain: "head" | "tail");
14
+ push(chunk: Uint8Array): void;
15
+ private pushHead;
16
+ private pushTail;
17
+ private drop;
18
+ get dropped(): number;
19
+ text(): string;
20
+ }
21
+ //# sourceMappingURL=bounded.d.ts.map
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Bounded byte collector. It retains a prefix or suffix constrained by both a byte
3
+ * budget and a newline budget, and accounts for every discarded byte/newline.
4
+ */
5
+ export class Bounded {
6
+ constructor(maxBytes, maxLines, retain) {
7
+ this.bytes = new Uint8Array();
8
+ this.droppedBytes = 0;
9
+ this.droppedLines = 0;
10
+ this.total = 0;
11
+ this.maxBytes = Math.max(0, maxBytes);
12
+ this.maxLines = Math.max(0, maxLines);
13
+ this.retain = retain;
14
+ }
15
+ push(chunk) {
16
+ this.total += chunk.length;
17
+ if (chunk.length === 0)
18
+ return;
19
+ if (this.maxBytes === 0 || this.maxLines === 0) {
20
+ this.drop(chunk);
21
+ return;
22
+ }
23
+ if (this.retain === "head") {
24
+ this.pushHead(chunk);
25
+ return;
26
+ }
27
+ this.pushTail(chunk);
28
+ }
29
+ pushHead(chunk) {
30
+ const remainingBytes = this.maxBytes - this.bytes.length;
31
+ const remainingLines = this.maxLines - countNewlines(this.bytes);
32
+ if (remainingBytes <= 0 || remainingLines <= 0) {
33
+ this.drop(chunk);
34
+ return;
35
+ }
36
+ let take = Math.min(chunk.length, remainingBytes);
37
+ let lines = 0;
38
+ for (let i = 0; i < take; i++) {
39
+ if (chunk[i] !== 0x0a)
40
+ continue;
41
+ lines++;
42
+ if (lines === remainingLines) {
43
+ take = i + 1;
44
+ break;
45
+ }
46
+ }
47
+ this.bytes = concat(this.bytes, chunk.subarray(0, take));
48
+ this.drop(chunk.subarray(take));
49
+ }
50
+ pushTail(chunk) {
51
+ const incomingStart = tailStart(chunk, this.maxBytes, this.maxLines);
52
+ this.drop(chunk.subarray(0, incomingStart));
53
+ const combined = concat(this.bytes, chunk.subarray(incomingStart));
54
+ const start = tailStart(combined, this.maxBytes, this.maxLines);
55
+ this.drop(combined.subarray(0, start));
56
+ this.bytes = combined.slice(start);
57
+ }
58
+ drop(bytes) {
59
+ this.droppedBytes += bytes.length;
60
+ this.droppedLines += countNewlines(bytes);
61
+ }
62
+ get dropped() {
63
+ return this.droppedBytes;
64
+ }
65
+ text() {
66
+ return new TextDecoder().decode(this.bytes);
67
+ }
68
+ }
69
+ function tailStart(bytes, maxBytes, maxLines) {
70
+ let start = Math.max(0, bytes.length - maxBytes);
71
+ let excessLines = countNewlines(bytes.subarray(start)) - maxLines;
72
+ for (; start < bytes.length && excessLines > 0; start++) {
73
+ if (bytes[start] === 0x0a)
74
+ excessLines--;
75
+ }
76
+ return start;
77
+ }
78
+ function concat(a, b) {
79
+ if (a.length === 0)
80
+ return b.slice();
81
+ if (b.length === 0)
82
+ return a;
83
+ const out = new Uint8Array(a.length + b.length);
84
+ out.set(a);
85
+ out.set(b, a.length);
86
+ return out;
87
+ }
88
+ function countNewlines(bytes) {
89
+ let count = 0;
90
+ for (const byte of bytes)
91
+ if (byte === 0x0a)
92
+ count++;
93
+ return count;
94
+ }
95
+ //# sourceMappingURL=bounded.js.map
@@ -0,0 +1,41 @@
1
+ import { type Context, type MutableReplicatedState, type ReplicatedState } from "@earendil-works/chord";
2
+ import type { ConversationHandle, Harness } from "./harness.ts";
3
+ import type { ConversationSpec, ConversationView, Entry, EntryScan, Id, JsonObject, NewEntry, SendInput, ViewEvent } from "./types.ts";
4
+ export type PublishedConversationView = ConversationView & {
5
+ commit: {
6
+ events: ViewEvent[];
7
+ };
8
+ };
9
+ export interface PicoConversationService {
10
+ readonly view: ReplicatedState<PublishedConversationView>;
11
+ send(input: SendInput, ctx: Context): Promise<Id>;
12
+ write(entry: NewEntry, ctx: Context): Promise<Id>;
13
+ inputAbort(id: Id, ctx: Context): Promise<"aborted" | "already_placed" | "not_found">;
14
+ configSet(patch: JsonObject, ctx: Context): Promise<void>;
15
+ abort(ctx: Context): Promise<void>;
16
+ reset(handoff: string | null, ctx: Context): Promise<void>;
17
+ collapse(instructions: string | null, ctx: Context): Promise<Id>;
18
+ fork(at: Id | "start", spec: Omit<ConversationSpec, "parent">, ctx: Context): Promise<Id>;
19
+ entries(scan: EntryScan, ctx: Context): Promise<Entry[]>;
20
+ }
21
+ /** Local registration/control plane and remote keyed conversation contract used by Chord facets. */
22
+ export declare const PicoHarnessService: import("@earendil-works/chord").Service<Harness<[]>>;
23
+ export declare const PicoConversationService: import("@earendil-works/chord").Service<PicoConversationService>;
24
+ export declare function createPicoConversationService<Cfg extends object>(harness: Pick<Harness, "abortInput" | "entries">, conversation: ConversationHandle<Cfg>, view: ReplicatedState<PublishedConversationView>): PicoConversationService;
25
+ export interface ChordViewBridge {
26
+ readonly view: MutableReplicatedState<PublishedConversationView>;
27
+ readonly closed: boolean;
28
+ close(): void;
29
+ }
30
+ export interface ChordViewBridgeOptions {
31
+ capacity?: number;
32
+ /** Called after the raw watch is closed. The owner should close and respawn its keyed service instance. */
33
+ onFailure?: (error: Error) => void;
34
+ }
35
+ /**
36
+ * Adapt Pico's commit-granular envelopes to one Chord publication per commit.
37
+ * The raw listener only enqueues. Applying ops and publishing happen together,
38
+ * off the Session line, with no await between them.
39
+ */
40
+ export declare function attachChordView(conversation: Pick<ConversationHandle, "watch">, createState: (initial: PublishedConversationView) => MutableReplicatedState<PublishedConversationView>, ctx: Context, opts?: ChordViewBridgeOptions): Promise<ChordViewBridge>;
41
+ //# sourceMappingURL=chord.d.ts.map
@@ -0,0 +1,147 @@
1
+ import { defineService, } from "@earendil-works/chord";
2
+ import { WATCH_CAPACITY } from "./view.js";
3
+ /** Local registration/control plane and remote keyed conversation contract used by Chord facets. */
4
+ export const PicoHarnessService = defineService("pi.harness", { local: true });
5
+ export const PicoConversationService = defineService("pi.conversation");
6
+ export function createPicoConversationService(harness, conversation, view) {
7
+ return {
8
+ view,
9
+ send: (input, ctx) => conversation.send(input, ctx).then((handle) => handle.id),
10
+ write: (entry, ctx) => conversation.write(entry, ctx),
11
+ inputAbort: (id, ctx) => harness.abortInput(id, ctx, conversation.id),
12
+ configSet: (patch, ctx) => conversation.config.set(patch, ctx),
13
+ abort: (ctx) => conversation.abort(ctx),
14
+ reset: (handoff, ctx) => conversation.reset(handoff ?? undefined, ctx),
15
+ collapse: (instructions, ctx) => conversation.collapse(instructions ?? undefined, ctx),
16
+ fork: (at, spec, ctx) => conversation.fork(at, spec, ctx).then((child) => child.id),
17
+ entries: (scan, ctx) => harness.entries({ ...scan, conversationId: conversation.id }, ctx),
18
+ };
19
+ }
20
+ /**
21
+ * Adapt Pico's commit-granular envelopes to one Chord publication per commit.
22
+ * The raw listener only enqueues. Applying ops and publishing happen together,
23
+ * off the Session line, with no await between them.
24
+ */
25
+ export async function attachChordView(conversation, createState, ctx, opts = {}) {
26
+ const watch = await conversation.watch(ctx);
27
+ const view = createState({ ...structuredClone(watch.view), commit: { events: [] } });
28
+ return bridgeWatch(watch, view, ctx, opts);
29
+ }
30
+ function bridgeWatch(watch, view, ctx, opts) {
31
+ const capacity = opts.capacity ?? WATCH_CAPACITY;
32
+ if (!Number.isSafeInteger(capacity) || capacity < 1)
33
+ throw new RangeError("Chord view queue capacity must be positive");
34
+ const queue = [];
35
+ let scheduled = false;
36
+ let closed = false;
37
+ const fail = (cause) => {
38
+ if (closed)
39
+ return;
40
+ closed = true;
41
+ queue.length = 0;
42
+ watch.stop();
43
+ try {
44
+ opts.onFailure?.(cause instanceof Error ? cause : new Error(String(cause)));
45
+ }
46
+ catch { }
47
+ };
48
+ const drain = () => {
49
+ scheduled = false;
50
+ while (!closed && queue.length > 0) {
51
+ const envelope = queue.shift();
52
+ try {
53
+ view.change(ctx, (draft) => {
54
+ applyTracked(draft, envelope.ops);
55
+ draft.commit.events = envelope.events;
56
+ });
57
+ }
58
+ catch (error) {
59
+ fail(error);
60
+ }
61
+ }
62
+ };
63
+ watch.start((envelope) => {
64
+ if (closed)
65
+ return;
66
+ if (queue.length >= capacity) {
67
+ fail(new Error(`Pico-to-Chord view queue exceeded ${capacity} envelopes`));
68
+ return;
69
+ }
70
+ queue.push(envelope);
71
+ if (scheduled)
72
+ return;
73
+ scheduled = true;
74
+ queueMicrotask(drain);
75
+ });
76
+ return {
77
+ view,
78
+ get closed() {
79
+ return closed;
80
+ },
81
+ close() {
82
+ if (closed)
83
+ return;
84
+ closed = true;
85
+ queue.length = 0;
86
+ watch.stop();
87
+ },
88
+ };
89
+ }
90
+ function applyTracked(root, ops) {
91
+ for (const op of ops) {
92
+ if (op[0] === "r")
93
+ throw new Error("live Pico envelope unexpectedly replaced the view root");
94
+ const path = op[1];
95
+ if (op[0] === "p") {
96
+ const target = resolve(root, path);
97
+ if (!Array.isArray(target))
98
+ throw new Error(`Pico splice path is not an array: ${path.join(".")}`);
99
+ target.splice(op[2], op[3], ...op[4]);
100
+ continue;
101
+ }
102
+ if (op[0] === "m") {
103
+ const target = resolve(root, path);
104
+ if (!Array.isArray(target) || target.length !== op[2].length) {
105
+ throw new Error(`Pico permutation path is not a matching array: ${path.join(".")}`);
106
+ }
107
+ const array = target;
108
+ const previous = array.slice();
109
+ const rank = new Map();
110
+ for (let index = 0; index < op[2].length; index++) {
111
+ const value = previous[op[2][index]];
112
+ if (!rank.has(value))
113
+ rank.set(value, index);
114
+ }
115
+ array.sort((left, right) => rank.get(left) - rank.get(right));
116
+ continue;
117
+ }
118
+ const parent = resolve(root, path.slice(0, -1));
119
+ if (parent === null || typeof parent !== "object")
120
+ throw new Error(`Pico operation parent is not an object: ${path.join(".")}`);
121
+ const key = path[path.length - 1];
122
+ if (op[0] === "d") {
123
+ if (Array.isArray(parent))
124
+ parent.splice(key, 1);
125
+ else
126
+ delete parent[key];
127
+ continue;
128
+ }
129
+ const record = parent;
130
+ if (op[0] === "s")
131
+ record[key] = op[2];
132
+ else if (op[0] === "a")
133
+ record[key] = `${String(record[key])}${op[2]}`;
134
+ else
135
+ record[key] = String(record[key]).slice(op[2]);
136
+ }
137
+ }
138
+ function resolve(root, path) {
139
+ let value = root;
140
+ for (const segment of path) {
141
+ if (value === null || typeof value !== "object")
142
+ return undefined;
143
+ value = value[segment];
144
+ }
145
+ return value;
146
+ }
147
+ //# sourceMappingURL=chord.js.map
@@ -0,0 +1,16 @@
1
+ import type { Context } from "@earendil-works/chord";
2
+ import type { ContextView, Id, Storage } from "./types.ts";
3
+ /**
4
+ * pico §1.3, verbatim:
5
+ * H = newest fork-visible entry at or before T with a head
6
+ * from = H ? H.head : transcript start
7
+ * range = fork-visible entries from `from` through T
8
+ * edits = per target, newest edit in range wins
9
+ * entries = H ? [H, ...range without any head entries] : range
10
+ * model = concat(entries.map(e => edits[e.id] ? apply : e.model)), then reorder tool results
11
+ *
12
+ * Display-only entries (aborted/error assistants, pi.usage, model-less plugin entries) have no
13
+ * `model` and contribute nothing. Nothing here inspects an error string.
14
+ */
15
+ export declare function deriveContext(storage: Storage, conversationId: Id, at: Id | undefined, ctx: Context): Promise<ContextView>;
16
+ //# sourceMappingURL=context.d.ts.map
@@ -0,0 +1,88 @@
1
+ /**
2
+ * pico §1.3, verbatim:
3
+ * H = newest fork-visible entry at or before T with a head
4
+ * from = H ? H.head : transcript start
5
+ * range = fork-visible entries from `from` through T
6
+ * edits = per target, newest edit in range wins
7
+ * entries = H ? [H, ...range without any head entries] : range
8
+ * model = concat(entries.map(e => edits[e.id] ? apply : e.model)), then reorder tool results
9
+ *
10
+ * Display-only entries (aborted/error assistants, pi.usage, model-less plugin entries) have no
11
+ * `model` and contribute nothing. Nothing here inspects an error string.
12
+ */
13
+ export async function deriveContext(storage, conversationId, at, ctx) {
14
+ const [head] = await storage.scanEntries({ conversationId, withHead: true, ...(at === undefined ? {} : { before: at + 1 }), limit: 1 }, ctx);
15
+ const from = head?.head;
16
+ // Walk newest-first until we pass `from`.
17
+ const range = [];
18
+ let before = at === undefined ? undefined : at + 1;
19
+ for (;;) {
20
+ const page = await storage.scanEntries({ conversationId, ...(before === undefined ? {} : { before }), limit: 256 }, ctx);
21
+ let done = page.length < 256;
22
+ for (const e of page) {
23
+ if (from !== undefined && e.id < from) {
24
+ done = true;
25
+ break;
26
+ }
27
+ range.push(e);
28
+ }
29
+ if (done)
30
+ break;
31
+ before = page[page.length - 1].id;
32
+ }
33
+ range.reverse();
34
+ const edits = new Map();
35
+ for (const e of range)
36
+ for (const edit of e.edits ?? [])
37
+ edits.set(edit.target, edit); // later wins by iteration order
38
+ const entries = head === undefined ? range : [head, ...range.filter((e) => e.head === undefined)];
39
+ const messages = [];
40
+ for (const e of entries) {
41
+ const edit = edits.get(e.id);
42
+ if (edit?.action === "omit")
43
+ continue;
44
+ if (edit?.action === "replace") {
45
+ messages.push(...(edit.messages ?? []));
46
+ continue;
47
+ }
48
+ if (e.model)
49
+ messages.push(...e.model);
50
+ }
51
+ return { head, entries, messages: reorderToolResults(messages) };
52
+ }
53
+ function reorderToolResults(messages) {
54
+ const out = [];
55
+ for (let i = 0; i < messages.length; i++) {
56
+ const m = messages[i];
57
+ out.push(m);
58
+ if (m.role !== "assistant")
59
+ continue;
60
+ const calls = m.content.filter((c) => c.type === "toolCall");
61
+ if (calls.length === 0)
62
+ continue;
63
+ const results = new Map();
64
+ let j = i + 1;
65
+ while (j < messages.length && messages[j].role === "toolResult") {
66
+ const r = messages[j];
67
+ results.set(r.toolCallId, r);
68
+ j++;
69
+ }
70
+ for (const call of calls) {
71
+ out.push(results.get(call.id) ??
72
+ {
73
+ role: "toolResult",
74
+ toolCallId: call.id,
75
+ toolName: call.name,
76
+ content: [
77
+ { type: "text", text: "Tool result unavailable: history ends before this call completed." },
78
+ ],
79
+ isError: true,
80
+ details: { reason: "missing_after_fork" },
81
+ timestamp: m.timestamp,
82
+ });
83
+ }
84
+ i = j - 1;
85
+ }
86
+ return out;
87
+ }
88
+ //# sourceMappingURL=context.js.map