@di-code/coding-agent 0.1.6 → 0.1.8

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 (338) hide show
  1. package/README.md +112 -68
  2. package/compositions/base.yml +54 -0
  3. package/compositions/interactive.yml +14 -0
  4. package/compositions/json.yml +6 -0
  5. package/compositions/print.yml +5 -0
  6. package/compositions/rpc.yml +12 -0
  7. package/compositions/webui.yml +9 -0
  8. package/dist/cli.d.ts +22 -1
  9. package/dist/cli.d.ts.map +1 -1
  10. package/dist/cli.js +97 -3
  11. package/dist/cli.js.map +1 -1
  12. package/dist/composition-loader-entry.d.ts +11 -0
  13. package/dist/composition-loader-entry.d.ts.map +1 -0
  14. package/dist/composition-loader-entry.js +12 -0
  15. package/dist/composition-loader-entry.js.map +1 -0
  16. package/dist/compositions.d.ts +32 -0
  17. package/dist/compositions.d.ts.map +1 -0
  18. package/dist/compositions.js +173 -0
  19. package/dist/compositions.js.map +1 -0
  20. package/dist/core/session/session-manager.d.ts +9 -2
  21. package/dist/core/session/session-manager.d.ts.map +1 -1
  22. package/dist/core/session/session-manager.js +28 -0
  23. package/dist/core/session/session-manager.js.map +1 -1
  24. package/dist/core/session/session-storage.d.ts.map +1 -1
  25. package/dist/core/session/session-storage.js +20 -3
  26. package/dist/core/session/session-storage.js.map +1 -1
  27. package/dist/core/session/types.d.ts +11 -2
  28. package/dist/core/session/types.d.ts.map +1 -1
  29. package/dist/core/session/types.js.map +1 -1
  30. package/dist/core/session-tools.d.ts +5 -0
  31. package/dist/core/session-tools.d.ts.map +1 -0
  32. package/dist/core/session-tools.js +17 -0
  33. package/dist/core/session-tools.js.map +1 -0
  34. package/dist/core/session.d.ts +21 -7
  35. package/dist/core/session.d.ts.map +1 -1
  36. package/dist/core/session.js +77 -41
  37. package/dist/core/session.js.map +1 -1
  38. package/dist/core/tools/bash.d.ts +1 -38
  39. package/dist/core/tools/bash.d.ts.map +1 -1
  40. package/dist/core/tools/bash.js +1 -207
  41. package/dist/core/tools/bash.js.map +1 -1
  42. package/dist/core/tools/edit-diff.d.ts +1 -24
  43. package/dist/core/tools/edit-diff.d.ts.map +1 -1
  44. package/dist/core/tools/edit-diff.js +1 -135
  45. package/dist/core/tools/edit-diff.js.map +1 -1
  46. package/dist/core/tools/edit.d.ts +1 -26
  47. package/dist/core/tools/edit.d.ts.map +1 -1
  48. package/dist/core/tools/edit.js +1 -140
  49. package/dist/core/tools/edit.js.map +1 -1
  50. package/dist/core/tools/file-mutation-queue.d.ts +1 -1
  51. package/dist/core/tools/file-mutation-queue.d.ts.map +1 -1
  52. package/dist/core/tools/file-mutation-queue.js +1 -23
  53. package/dist/core/tools/file-mutation-queue.js.map +1 -1
  54. package/dist/core/tools/file-search.d.ts +1 -13
  55. package/dist/core/tools/file-search.d.ts.map +1 -1
  56. package/dist/core/tools/file-search.js +1 -98
  57. package/dist/core/tools/file-search.js.map +1 -1
  58. package/dist/core/tools/glob.d.ts +1 -10
  59. package/dist/core/tools/glob.d.ts.map +1 -1
  60. package/dist/core/tools/glob.js +1 -43
  61. package/dist/core/tools/glob.js.map +1 -1
  62. package/dist/core/tools/grep.d.ts +1 -12
  63. package/dist/core/tools/grep.d.ts.map +1 -1
  64. package/dist/core/tools/grep.js +1 -66
  65. package/dist/core/tools/grep.js.map +1 -1
  66. package/dist/core/tools/load-skill.d.ts +1 -9
  67. package/dist/core/tools/load-skill.d.ts.map +1 -1
  68. package/dist/core/tools/load-skill.js +1 -21
  69. package/dist/core/tools/load-skill.js.map +1 -1
  70. package/dist/core/tools/path-boundary.d.ts +1 -4
  71. package/dist/core/tools/path-boundary.d.ts.map +1 -1
  72. package/dist/core/tools/path-boundary.js +1 -65
  73. package/dist/core/tools/path-boundary.js.map +1 -1
  74. package/dist/core/tools/read.d.ts +1 -16
  75. package/dist/core/tools/read.d.ts.map +1 -1
  76. package/dist/core/tools/read.js +1 -131
  77. package/dist/core/tools/read.js.map +1 -1
  78. package/dist/core/tools/write.d.ts +1 -16
  79. package/dist/core/tools/write.d.ts.map +1 -1
  80. package/dist/core/tools/write.js +1 -44
  81. package/dist/core/tools/write.js.map +1 -1
  82. package/dist/entry.js +30 -28
  83. package/dist/entry.js.map +1 -1
  84. package/dist/i18n.d.ts.map +1 -1
  85. package/dist/i18n.js +2 -0
  86. package/dist/i18n.js.map +1 -1
  87. package/dist/index.d.ts +6 -3
  88. package/dist/index.d.ts.map +1 -1
  89. package/dist/index.js +6 -3
  90. package/dist/index.js.map +1 -1
  91. package/dist/interactive/builtin.d.ts +17 -0
  92. package/dist/interactive/builtin.d.ts.map +1 -0
  93. package/dist/interactive/builtin.js +47 -0
  94. package/dist/interactive/builtin.js.map +1 -0
  95. package/dist/interactive/controller.d.ts +85 -0
  96. package/dist/interactive/controller.d.ts.map +1 -0
  97. package/dist/interactive/controller.js +211 -0
  98. package/dist/interactive/controller.js.map +1 -0
  99. package/dist/interactive/index.d.ts +3 -0
  100. package/dist/interactive/index.d.ts.map +1 -0
  101. package/dist/interactive/index.js +3 -0
  102. package/dist/interactive/index.js.map +1 -0
  103. package/dist/interactive-host-entry.d.ts +7 -0
  104. package/dist/interactive-host-entry.d.ts.map +1 -0
  105. package/dist/interactive-host-entry.js +123 -0
  106. package/dist/interactive-host-entry.js.map +1 -0
  107. package/dist/interactive-profile.d.ts +18 -0
  108. package/dist/interactive-profile.d.ts.map +1 -0
  109. package/dist/interactive-profile.js +290 -0
  110. package/dist/interactive-profile.js.map +1 -0
  111. package/dist/interactive-resources-entry.d.ts +7 -0
  112. package/dist/interactive-resources-entry.d.ts.map +1 -0
  113. package/dist/interactive-resources-entry.js +42 -0
  114. package/dist/interactive-resources-entry.js.map +1 -0
  115. package/dist/legacy-main.d.ts +39 -0
  116. package/dist/legacy-main.d.ts.map +1 -0
  117. package/dist/legacy-main.js +611 -0
  118. package/dist/legacy-main.js.map +1 -0
  119. package/dist/main.d.ts +15 -34
  120. package/dist/main.d.ts.map +1 -1
  121. package/dist/main.js +130 -513
  122. package/dist/main.js.map +1 -1
  123. package/dist/mcp/entries.d.ts +42 -0
  124. package/dist/mcp/entries.d.ts.map +1 -0
  125. package/dist/mcp/entries.js +98 -0
  126. package/dist/mcp/entries.js.map +1 -0
  127. package/dist/mcp-client-entry.d.ts +5 -0
  128. package/dist/mcp-client-entry.d.ts.map +1 -0
  129. package/dist/mcp-client-entry.js +6 -0
  130. package/dist/mcp-client-entry.js.map +1 -0
  131. package/dist/mcp-config-entry.d.ts +5 -0
  132. package/dist/mcp-config-entry.d.ts.map +1 -0
  133. package/dist/mcp-config-entry.js +6 -0
  134. package/dist/mcp-config-entry.js.map +1 -0
  135. package/dist/mcp-tools-entry.d.ts +5 -0
  136. package/dist/mcp-tools-entry.d.ts.map +1 -0
  137. package/dist/mcp-tools-entry.js +6 -0
  138. package/dist/mcp-tools-entry.js.map +1 -0
  139. package/dist/mcp-transport-entry.d.ts +5 -0
  140. package/dist/mcp-transport-entry.d.ts.map +1 -0
  141. package/dist/mcp-transport-entry.js +6 -0
  142. package/dist/mcp-transport-entry.js.map +1 -0
  143. package/dist/modes/interactive-entry.d.ts +18 -0
  144. package/dist/modes/interactive-entry.d.ts.map +1 -0
  145. package/dist/modes/interactive-entry.js +21 -0
  146. package/dist/modes/interactive-entry.js.map +1 -0
  147. package/dist/modes/interactive-state.js +1 -1
  148. package/dist/modes/interactive-state.js.map +1 -1
  149. package/dist/modes/interactive.d.ts +16 -6
  150. package/dist/modes/interactive.d.ts.map +1 -1
  151. package/dist/modes/interactive.js +99 -33
  152. package/dist/modes/interactive.js.map +1 -1
  153. package/dist/modes/json-entry.d.ts +13 -0
  154. package/dist/modes/json-entry.d.ts.map +1 -0
  155. package/dist/modes/json-entry.js +14 -0
  156. package/dist/modes/json-entry.js.map +1 -0
  157. package/dist/modes/json.d.ts +4 -1
  158. package/dist/modes/json.d.ts.map +1 -1
  159. package/dist/modes/json.js +3 -3
  160. package/dist/modes/json.js.map +1 -1
  161. package/dist/modes/print-entry.d.ts +10 -0
  162. package/dist/modes/print-entry.d.ts.map +1 -0
  163. package/dist/modes/print-entry.js +11 -0
  164. package/dist/modes/print-entry.js.map +1 -0
  165. package/dist/modes/tree-selector.d.ts.map +1 -1
  166. package/dist/modes/tree-selector.js +4 -0
  167. package/dist/modes/tree-selector.js.map +1 -1
  168. package/dist/plugin-dump-composition.d.ts +6 -0
  169. package/dist/plugin-dump-composition.d.ts.map +1 -0
  170. package/dist/plugin-dump-composition.js +7 -0
  171. package/dist/plugin-dump-composition.js.map +1 -0
  172. package/dist/plugin-inventory.d.ts +7 -0
  173. package/dist/plugin-inventory.d.ts.map +1 -0
  174. package/dist/plugin-inventory.js +8 -0
  175. package/dist/plugin-inventory.js.map +1 -0
  176. package/dist/plugin-manager.d.ts +6 -0
  177. package/dist/plugin-manager.d.ts.map +1 -0
  178. package/dist/plugin-manager.js +7 -0
  179. package/dist/plugin-manager.js.map +1 -0
  180. package/dist/plugin-trace.d.ts +6 -0
  181. package/dist/plugin-trace.d.ts.map +1 -0
  182. package/dist/plugin-trace.js +7 -0
  183. package/dist/plugin-trace.js.map +1 -0
  184. package/dist/plugins/dynamic-broker.d.ts +76 -0
  185. package/dist/plugins/dynamic-broker.d.ts.map +1 -0
  186. package/dist/plugins/dynamic-broker.js +852 -0
  187. package/dist/plugins/dynamic-broker.js.map +1 -0
  188. package/dist/plugins/profile.d.ts +15 -0
  189. package/dist/plugins/profile.d.ts.map +1 -0
  190. package/dist/plugins/profile.js +34 -0
  191. package/dist/plugins/profile.js.map +1 -0
  192. package/dist/plugins/runtime-host.d.ts +37 -0
  193. package/dist/plugins/runtime-host.d.ts.map +1 -0
  194. package/dist/plugins/runtime-host.js +95 -0
  195. package/dist/plugins/runtime-host.js.map +1 -0
  196. package/dist/plugins/trust.d.ts +8 -0
  197. package/dist/plugins/trust.d.ts.map +1 -0
  198. package/dist/plugins/trust.js +59 -0
  199. package/dist/plugins/trust.js.map +1 -0
  200. package/dist/project-trust-entry.d.ts +15 -0
  201. package/dist/project-trust-entry.d.ts.map +1 -0
  202. package/dist/project-trust-entry.js +15 -0
  203. package/dist/project-trust-entry.js.map +1 -0
  204. package/dist/rpc/client.d.ts +78 -6
  205. package/dist/rpc/client.d.ts.map +1 -1
  206. package/dist/rpc/client.js +172 -4
  207. package/dist/rpc/client.js.map +1 -1
  208. package/dist/rpc/dispatcher.d.ts +121 -0
  209. package/dist/rpc/dispatcher.d.ts.map +1 -0
  210. package/dist/rpc/dispatcher.js +994 -0
  211. package/dist/rpc/dispatcher.js.map +1 -0
  212. package/dist/rpc/index.d.ts +2 -0
  213. package/dist/rpc/index.d.ts.map +1 -1
  214. package/dist/rpc/index.js +2 -0
  215. package/dist/rpc/index.js.map +1 -1
  216. package/dist/rpc/lifecycle.d.ts +6 -0
  217. package/dist/rpc/lifecycle.d.ts.map +1 -0
  218. package/dist/rpc/lifecycle.js +24 -0
  219. package/dist/rpc/lifecycle.js.map +1 -0
  220. package/dist/rpc/protocol.d.ts +112 -31
  221. package/dist/rpc/protocol.d.ts.map +1 -1
  222. package/dist/rpc/protocol.js +569 -117
  223. package/dist/rpc/protocol.js.map +1 -1
  224. package/dist/rpc/server-service.d.ts +7 -0
  225. package/dist/rpc/server-service.d.ts.map +1 -0
  226. package/dist/rpc/server-service.js +3 -0
  227. package/dist/rpc/server-service.js.map +1 -0
  228. package/dist/rpc/server.d.ts +16 -19
  229. package/dist/rpc/server.d.ts.map +1 -1
  230. package/dist/rpc/server.js +59 -115
  231. package/dist/rpc/server.js.map +1 -1
  232. package/dist/rpc-client-sdk-entry.d.ts +12 -0
  233. package/dist/rpc-client-sdk-entry.d.ts.map +1 -0
  234. package/dist/rpc-client-sdk-entry.js +11 -0
  235. package/dist/rpc-client-sdk-entry.js.map +1 -0
  236. package/dist/rpc-entry.js +50 -18
  237. package/dist/rpc-entry.js.map +1 -1
  238. package/dist/rpc-server-entry.d.ts +8 -0
  239. package/dist/rpc-server-entry.d.ts.map +1 -0
  240. package/dist/rpc-server-entry.js +67 -0
  241. package/dist/rpc-server-entry.js.map +1 -0
  242. package/dist/runtime/attachment-store.d.ts +13 -0
  243. package/dist/runtime/attachment-store.d.ts.map +1 -0
  244. package/dist/runtime/attachment-store.js +93 -0
  245. package/dist/runtime/attachment-store.js.map +1 -0
  246. package/dist/runtime/bootstrap.d.ts +14 -0
  247. package/dist/runtime/bootstrap.d.ts.map +1 -0
  248. package/dist/runtime/bootstrap.js +29 -0
  249. package/dist/runtime/bootstrap.js.map +1 -0
  250. package/dist/runtime/interactive-host-service.d.ts +22 -0
  251. package/dist/runtime/interactive-host-service.d.ts.map +1 -0
  252. package/dist/runtime/interactive-host-service.js +2 -0
  253. package/dist/runtime/interactive-host-service.js.map +1 -0
  254. package/dist/runtime/interactive-resource-service.d.ts +11 -0
  255. package/dist/runtime/interactive-resource-service.d.ts.map +1 -0
  256. package/dist/runtime/interactive-resource-service.js +3 -0
  257. package/dist/runtime/interactive-resource-service.js.map +1 -0
  258. package/dist/runtime/plugin-inventory-entry.d.ts +5 -0
  259. package/dist/runtime/plugin-inventory-entry.d.ts.map +1 -0
  260. package/dist/runtime/plugin-inventory-entry.js +18 -0
  261. package/dist/runtime/plugin-inventory-entry.js.map +1 -0
  262. package/dist/runtime/plugin-inventory-service.d.ts +8 -0
  263. package/dist/runtime/plugin-inventory-service.d.ts.map +1 -0
  264. package/dist/runtime/plugin-inventory-service.js +3 -0
  265. package/dist/runtime/plugin-inventory-service.js.map +1 -0
  266. package/dist/runtime/plugin-manager-entry.d.ts +20 -0
  267. package/dist/runtime/plugin-manager-entry.d.ts.map +1 -0
  268. package/dist/runtime/plugin-manager-entry.js +96 -0
  269. package/dist/runtime/plugin-manager-entry.js.map +1 -0
  270. package/dist/runtime/plugin-observability-entry.d.ts +12 -0
  271. package/dist/runtime/plugin-observability-entry.d.ts.map +1 -0
  272. package/dist/runtime/plugin-observability-entry.js +73 -0
  273. package/dist/runtime/plugin-observability-entry.js.map +1 -0
  274. package/dist/runtime/product-host.d.ts +91 -0
  275. package/dist/runtime/product-host.d.ts.map +1 -0
  276. package/dist/runtime/product-host.js +529 -0
  277. package/dist/runtime/product-host.js.map +1 -0
  278. package/dist/runtime/session-factory.d.ts +17 -0
  279. package/dist/runtime/session-factory.d.ts.map +1 -0
  280. package/dist/runtime/session-factory.js +75 -0
  281. package/dist/runtime/session-factory.js.map +1 -0
  282. package/dist/runtime/session-host.d.ts +152 -0
  283. package/dist/runtime/session-host.d.ts.map +1 -0
  284. package/dist/runtime/session-host.js +895 -0
  285. package/dist/runtime/session-host.js.map +1 -0
  286. package/dist/runtime-core-entry.d.ts +11 -0
  287. package/dist/runtime-core-entry.d.ts.map +1 -0
  288. package/dist/runtime-core-entry.js +9 -0
  289. package/dist/runtime-core-entry.js.map +1 -0
  290. package/dist/session-factory-entry.d.ts +7 -0
  291. package/dist/session-factory-entry.d.ts.map +1 -0
  292. package/dist/session-factory-entry.js +9 -0
  293. package/dist/session-factory-entry.js.map +1 -0
  294. package/dist/session-store-jsonl-entry.d.ts +7 -0
  295. package/dist/session-store-jsonl-entry.d.ts.map +1 -0
  296. package/dist/session-store-jsonl-entry.js +33 -0
  297. package/dist/session-store-jsonl-entry.js.map +1 -0
  298. package/dist/startup.d.ts +21 -1
  299. package/dist/startup.d.ts.map +1 -1
  300. package/dist/startup.js +179 -10
  301. package/dist/startup.js.map +1 -1
  302. package/dist/subagents/index.d.ts +2 -0
  303. package/dist/subagents/index.d.ts.map +1 -0
  304. package/dist/subagents/index.js +2 -0
  305. package/dist/subagents/index.js.map +1 -0
  306. package/dist/subagents/service.d.ts +50 -0
  307. package/dist/subagents/service.d.ts.map +1 -0
  308. package/dist/subagents/service.js +521 -0
  309. package/dist/subagents/service.js.map +1 -0
  310. package/dist/ui-host.d.ts +125 -0
  311. package/dist/ui-host.d.ts.map +1 -0
  312. package/dist/ui-host.js +275 -0
  313. package/dist/ui-host.js.map +1 -0
  314. package/dist/web/assets/index-BwfuGMvv.css +1 -0
  315. package/dist/web/assets/index-DPUaJqFJ.js +41 -0
  316. package/dist/web/assets/index-DPUaJqFJ.js.map +1 -0
  317. package/dist/web/index.html +14 -0
  318. package/dist/web-command.d.ts +11 -0
  319. package/dist/web-command.d.ts.map +1 -0
  320. package/dist/web-command.js +114 -0
  321. package/dist/web-command.js.map +1 -0
  322. package/dist/web-frontend.d.ts +73 -0
  323. package/dist/web-frontend.d.ts.map +1 -0
  324. package/dist/web-frontend.js +249 -0
  325. package/dist/web-frontend.js.map +1 -0
  326. package/dist/web.d.ts +254 -0
  327. package/dist/web.d.ts.map +1 -0
  328. package/dist/web.js +740 -0
  329. package/dist/web.js.map +1 -0
  330. package/dist/webui-entry.d.ts +3 -0
  331. package/dist/webui-entry.d.ts.map +1 -0
  332. package/dist/webui-entry.js +73 -0
  333. package/dist/webui-entry.js.map +1 -0
  334. package/dist/webui.d.ts +91 -0
  335. package/dist/webui.d.ts.map +1 -0
  336. package/dist/webui.js +882 -0
  337. package/dist/webui.js.map +1 -0
  338. package/package.json +89 -8
@@ -0,0 +1,895 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { constants as fsConstants } from "node:fs";
3
+ import { access, lstat, mkdir, open, readdir, readFile, realpath, rename, unlink } from "node:fs/promises";
4
+ import { basename, dirname, extname, join, resolve } from "node:path";
5
+ import { agentSessionKey, providerRegistryKey, runtimeSelectionKey, sessionStoreRegistryKey, } from "@di-code/builtins";
6
+ import { SessionManager } from "../core/session/session-manager.js";
7
+ import { AgentSession, } from "../core/session.js";
8
+ import { workspaceStorageKey } from "../core/user-data.js";
9
+ import { mcpClientServiceKey, mcpConfigServiceKey, mcpToolServiceKey } from "../mcp/entries.js";
10
+ import { interactiveResourceServiceKey } from "./interactive-resource-service.js";
11
+ const RETRY_PLUGIN_ID = "di-code.retry";
12
+ const RETRY_PLUGIN_VERSION = "1";
13
+ const RETRY_PLUGIN_SCHEMA_VERSION = 1;
14
+ export class SessionHostError extends Error {
15
+ code;
16
+ constructor(code, message, options) {
17
+ super(message, { cause: options?.cause });
18
+ this.name = "SessionHostError";
19
+ this.code = code;
20
+ }
21
+ }
22
+ async function validateDataRoot(path) {
23
+ const requested = resolve(path);
24
+ await mkdir(requested, { recursive: true });
25
+ const stats = await lstat(requested);
26
+ if (!stats.isDirectory() || stats.isSymbolicLink()) {
27
+ throw new SessionHostError("INVALID_WORKSPACE", `Agent data root must be a real directory: "${requested}".`);
28
+ }
29
+ const root = await realpath(requested);
30
+ await access(root, fsConstants.R_OK | fsConstants.W_OK | fsConstants.X_OK);
31
+ return root;
32
+ }
33
+ async function ensureRealChildDirectory(parent, name) {
34
+ const path = join(parent, name);
35
+ try {
36
+ const stats = await lstat(path);
37
+ if (!stats.isDirectory() || stats.isSymbolicLink()) {
38
+ throw new SessionHostError("INVALID_WORKSPACE", `Managed data path must be a real directory: "${path}".`);
39
+ }
40
+ }
41
+ catch (cause) {
42
+ if (!(cause instanceof Error && "code" in cause && cause.code === "ENOENT"))
43
+ throw cause;
44
+ await mkdir(path);
45
+ }
46
+ const real = await realpath(path);
47
+ if (dirname(real) !== parent)
48
+ throw new SessionHostError("INVALID_WORKSPACE", `Managed data path escaped its parent: "${path}".`);
49
+ return real;
50
+ }
51
+ function asId(value) {
52
+ return value;
53
+ }
54
+ function asRequestId(value) {
55
+ return value;
56
+ }
57
+ function messageText(message) {
58
+ if (message.role !== "user")
59
+ return "";
60
+ return message.content
61
+ .filter((item) => item.type === "text")
62
+ .map((item) => item.text)
63
+ .join(" ")
64
+ .trim();
65
+ }
66
+ function sessionLabel(manager) {
67
+ for (const entry of [...manager.entries].reverse()) {
68
+ if (entry.type !== "plugin" || entry.pluginId !== "di-code.session-label")
69
+ continue;
70
+ const data = entry.data;
71
+ if (typeof data === "object" &&
72
+ data !== null &&
73
+ !Array.isArray(data) &&
74
+ typeof data.label === "string") {
75
+ const label = data.label;
76
+ if (label.trim())
77
+ return label;
78
+ }
79
+ }
80
+ const first = manager.entries.find((entry) => entry.type === "message" && entry.message.role === "user");
81
+ return first?.type === "message"
82
+ ? messageText(first.message) || basename(manager.filePath, extname(manager.filePath))
83
+ : basename(manager.filePath, extname(manager.filePath));
84
+ }
85
+ async function validateWorkspace(cwd) {
86
+ const requested = resolve(cwd);
87
+ let root;
88
+ try {
89
+ const stats = await lstat(requested);
90
+ if (!stats.isDirectory() || stats.isSymbolicLink())
91
+ throw new Error("Workspace must be a real directory.");
92
+ root = await realpath(requested);
93
+ await access(root, fsConstants.R_OK | fsConstants.W_OK | fsConstants.X_OK);
94
+ }
95
+ catch (cause) {
96
+ throw new SessionHostError("INVALID_WORKSPACE", `Workspace is not an accessible real directory: "${requested}".`, {
97
+ cause,
98
+ });
99
+ }
100
+ return root;
101
+ }
102
+ async function lockSession(filePath) {
103
+ const lockPath = `${filePath}.host.lock`;
104
+ const token = `${process.pid}:${randomUUID()}`;
105
+ try {
106
+ const handle = await open(lockPath, "wx");
107
+ await handle.writeFile(token, "utf8");
108
+ return async () => {
109
+ let releaseError;
110
+ try {
111
+ const owner = await readFile(lockPath, "utf8");
112
+ if (owner !== token)
113
+ releaseError = new Error(`Session lock ownership changed: "${lockPath}".`);
114
+ }
115
+ catch (cause) {
116
+ if (!(cause instanceof Error && "code" in cause && cause.code === "ENOENT"))
117
+ releaseError = cause;
118
+ }
119
+ try {
120
+ await handle.close();
121
+ }
122
+ catch (cause) {
123
+ releaseError = cause;
124
+ }
125
+ if (releaseError === undefined) {
126
+ try {
127
+ await unlink(lockPath);
128
+ }
129
+ catch (cause) {
130
+ if (!(cause instanceof Error && "code" in cause && cause.code === "ENOENT"))
131
+ releaseError = cause;
132
+ }
133
+ }
134
+ if (releaseError !== undefined)
135
+ throw releaseError;
136
+ };
137
+ }
138
+ catch (cause) {
139
+ if (cause instanceof Error && "code" in cause && cause.code === "EEXIST") {
140
+ try {
141
+ const owner = await readFile(lockPath, "utf8");
142
+ const ownerPid = Number.parseInt(owner.split(":", 1)[0] ?? "", 10);
143
+ if (Number.isSafeInteger(ownerPid) && ownerPid > 0)
144
+ process.kill(ownerPid, 0);
145
+ }
146
+ catch (ownerCause) {
147
+ if (ownerCause instanceof Error && "code" in ownerCause && ownerCause.code === "ESRCH") {
148
+ const stalePath = `${lockPath}.stale-${randomUUID()}`;
149
+ try {
150
+ await rename(lockPath, stalePath);
151
+ await unlink(stalePath);
152
+ return await lockSession(filePath);
153
+ }
154
+ catch (reclaimCause) {
155
+ if (reclaimCause instanceof Error && "code" in reclaimCause && reclaimCause.code === "ENOENT")
156
+ return await lockSession(filePath);
157
+ throw new SessionHostError("INTERNAL", `Unable to reclaim stale Session lock: "${filePath}".`, {
158
+ cause: reclaimCause,
159
+ });
160
+ }
161
+ }
162
+ }
163
+ throw new SessionHostError("SESSION_IN_USE", `Session is already open: "${filePath}".`, { cause });
164
+ }
165
+ throw new SessionHostError("INTERNAL", `Unable to lock session: "${filePath}".`, { cause });
166
+ }
167
+ }
168
+ async function createBootstrap(context, options) {
169
+ const workspace = await validateWorkspace(options.cwd);
170
+ const agentDir = await validateDataRoot(options.agentDir);
171
+ const sessionsRoot = await ensureRealChildDirectory(agentDir, "sessions");
172
+ // Preserve the CLI's historical workspace hash (which uses the user-supplied path casing/alias).
173
+ const sessionDirectory = await ensureRealChildDirectory(sessionsRoot, workspaceStorageKey(options.cwd));
174
+ const resources = await context.require(interactiveResourceServiceKey).load({
175
+ cwd: workspace,
176
+ agentDir,
177
+ projectTrusted: options.projectTrusted ?? false,
178
+ noSkills: options.noSkills,
179
+ noContextFiles: options.noContextFiles,
180
+ skillPaths: options.skillPaths,
181
+ });
182
+ const runtime = options.provider && options.model
183
+ ? { provider: options.provider, model: options.model }
184
+ : context.require(runtimeSelectionKey).selected();
185
+ const mcpConfig = context.require(mcpConfigServiceKey);
186
+ const mcpClient = context.require(mcpClientServiceKey);
187
+ const configurations = await mcpConfig.load({ cwd: workspace, projectTrusted: options.projectTrusted ?? false });
188
+ const mcp = await mcpClient.connect(configurations, { signal: options.signal });
189
+ let externalTools;
190
+ try {
191
+ externalTools = context
192
+ .require(mcpToolServiceKey)
193
+ .create(mcp.servers, ["read", "write", "edit", "glob", "grep", "bash", "load_skill"]);
194
+ }
195
+ catch (cause) {
196
+ await mcpClient.close(mcp.manager);
197
+ throw cause;
198
+ }
199
+ return {
200
+ workspace,
201
+ sessionDirectory,
202
+ provider: runtime.provider,
203
+ model: runtime.model,
204
+ skills: resources.resources.skills,
205
+ systemPrompt: resources.systemPrompt,
206
+ externalTools,
207
+ closeMcp: () => mcpClient.close(mcp.manager),
208
+ };
209
+ }
210
+ export async function createSessionHost(context, options) {
211
+ let bootstrap = await createBootstrap(context, options);
212
+ try {
213
+ await mkdir(bootstrap.sessionDirectory, { recursive: true });
214
+ }
215
+ catch (cause) {
216
+ await bootstrap.closeMcp();
217
+ throw cause;
218
+ }
219
+ const store = context.require(sessionStoreRegistryKey).get("jsonl");
220
+ if (!store) {
221
+ await bootstrap.closeMcp();
222
+ throw new SessionHostError("INTERNAL", "JSONL SessionStore is unavailable.");
223
+ }
224
+ const sessions = new Map();
225
+ const operations = new Map();
226
+ const listeners = new Set();
227
+ let activeId;
228
+ const failedPrompts = new Map();
229
+ let resourceProjectTrusted = options.projectTrusted;
230
+ let disposed = false;
231
+ const ensureOpen = () => {
232
+ if (disposed)
233
+ throw new SessionHostError("DISPOSED", "SessionHost has been disposed.");
234
+ };
235
+ const current = () => {
236
+ if (!activeId)
237
+ throw new SessionHostError("NOT_FOUND", "No Session is open.");
238
+ const value = sessions.get(activeId);
239
+ if (!value)
240
+ throw new SessionHostError("NOT_FOUND", "No Session is open.");
241
+ return value;
242
+ };
243
+ const emit = (event) => {
244
+ for (const listener of listeners) {
245
+ queueMicrotask(() => {
246
+ try {
247
+ Promise.resolve(listener(structuredClone(event))).catch(() => undefined);
248
+ }
249
+ catch {
250
+ // Observers cannot fail the Agent loop or persistence path.
251
+ }
252
+ });
253
+ }
254
+ };
255
+ const infoFor = (manager) => ({
256
+ id: asId(manager.header.id),
257
+ displayId: basename(manager.filePath, extname(manager.filePath)),
258
+ cwd: manager.header.cwd,
259
+ label: sessionLabel(manager),
260
+ modifiedAt: Date.parse(manager.header.timestamp),
261
+ stats: statsFor(manager),
262
+ });
263
+ const statsFor = (manager) => {
264
+ const entries = manager.entries;
265
+ const parentIds = new Set(entries.map((entry) => entry.parentId));
266
+ return {
267
+ entryCount: entries.length,
268
+ messageCount: entries.filter((entry) => entry.type === "message").length,
269
+ branchCount: [...parentIds].filter((parent) => entries.filter((entry) => entry.parentId === parent).length > 1)
270
+ .length,
271
+ };
272
+ };
273
+ const retryState = (manager) => {
274
+ for (const entry of [...manager.getBranch()].reverse()) {
275
+ if (entry.type !== "plugin" ||
276
+ entry.pluginId !== RETRY_PLUGIN_ID ||
277
+ entry.schemaVersion !== RETRY_PLUGIN_SCHEMA_VERSION)
278
+ continue;
279
+ const data = entry.data;
280
+ if (typeof data !== "object" || data === null || Array.isArray(data))
281
+ return undefined;
282
+ const value = data;
283
+ if (value.active !== true || typeof value.text !== "string" || typeof value.requestId !== "string")
284
+ return undefined;
285
+ return { requestId: value.requestId, text: value.text };
286
+ }
287
+ return undefined;
288
+ };
289
+ const persistRetryState = async (manager, state) => {
290
+ await manager.appendPlugin({
291
+ pluginId: RETRY_PLUGIN_ID,
292
+ pluginVersion: RETRY_PLUGIN_VERSION,
293
+ schemaVersion: RETRY_PLUGIN_SCHEMA_VERSION,
294
+ data: state ? { active: true, requestId: state.requestId, text: state.text } : { active: false },
295
+ });
296
+ };
297
+ const createAgent = async (manager, resources = bootstrap) => {
298
+ const created = await context.require(agentSessionKey).create({
299
+ // The host owns the workspace boundary. Reading the root from the shared
300
+ // composition context would pin every WebUI actor to the startup folder.
301
+ allowedRoot: resources.workspace,
302
+ provider: resources.provider,
303
+ model: resources.model,
304
+ systemPrompt: resources.systemPrompt,
305
+ skills: resources.skills,
306
+ sessionManager: manager,
307
+ externalTools: resources.externalTools,
308
+ compaction: options.compaction,
309
+ toolApproval: options.toolApproval,
310
+ });
311
+ if (!(created instanceof AgentSession))
312
+ throw new SessionHostError("INTERNAL", "SessionFactory returned an incompatible session.");
313
+ return created;
314
+ };
315
+ const assertIdle = (kind) => {
316
+ if (operations.size > 0)
317
+ throw new SessionHostError("BUSY", `Cannot ${kind} while another Session operation is running.`);
318
+ };
319
+ const withOperation = async (kind, requestId, signal, action) => {
320
+ ensureOpen();
321
+ const id = asRequestId(requestId?.trim() || randomUUID());
322
+ if (operations.has(id))
323
+ throw new SessionHostError("BUSY", `Request ID is already active: "${id}".`);
324
+ const controller = new AbortController();
325
+ const abort = () => controller.abort(signal?.reason);
326
+ if (signal?.aborted)
327
+ abort();
328
+ else
329
+ signal?.addEventListener("abort", abort, { once: true });
330
+ const operation = { controller, kind };
331
+ operations.set(id, operation);
332
+ const promise = (async () => {
333
+ try {
334
+ return await action(controller.signal);
335
+ }
336
+ catch (cause) {
337
+ if (cause instanceof SessionHostError)
338
+ throw cause;
339
+ if (cause instanceof Error && (cause.name === "AbortError" || controller.signal.aborted))
340
+ throw cause;
341
+ throw new SessionHostError("INTERNAL", cause instanceof Error ? cause.message : String(cause), { cause });
342
+ }
343
+ finally {
344
+ operations.delete(id);
345
+ signal?.removeEventListener("abort", abort);
346
+ }
347
+ })();
348
+ operation.promise = promise;
349
+ return await promise;
350
+ };
351
+ const openManager = async (manager) => {
352
+ let sessionWorkspace;
353
+ try {
354
+ sessionWorkspace = await realpath(manager.header.cwd);
355
+ }
356
+ catch (cause) {
357
+ throw new SessionHostError("INVALID_WORKSPACE", "Session workspace is unavailable.", { cause });
358
+ }
359
+ if (sessionWorkspace !== bootstrap.workspace)
360
+ throw new SessionHostError("INVALID_WORKSPACE", "Session workspace does not match the Host workspace.");
361
+ const id = asId(manager.header.id);
362
+ const unlock = await lockSession(manager.filePath);
363
+ try {
364
+ const session = await createAgent(manager);
365
+ const info = infoFor(manager);
366
+ const previous = activeId;
367
+ if (previous !== undefined) {
368
+ const old = sessions.get(previous);
369
+ if (old) {
370
+ old.unsubscribe();
371
+ await old.session.dispose();
372
+ await old.unlock();
373
+ }
374
+ sessions.delete(previous);
375
+ }
376
+ const unsubscribe = session.subscribeSession(emit);
377
+ sessions.set(id, { manager, session, unsubscribe, unlock, info });
378
+ failedPrompts.set(id, retryState(manager));
379
+ activeId = id;
380
+ emit({ type: "session_changed", session: info });
381
+ return info;
382
+ }
383
+ catch (cause) {
384
+ await unlock();
385
+ throw cause;
386
+ }
387
+ };
388
+ const findSessionPath = async (id) => {
389
+ let entries;
390
+ try {
391
+ entries = await readdir(bootstrap.sessionDirectory, { withFileTypes: true, encoding: "utf8" });
392
+ }
393
+ catch (cause) {
394
+ if (cause instanceof Error && "code" in cause && cause.code === "ENOENT")
395
+ throw new SessionHostError("NOT_FOUND", `Session not found: "${id}".`);
396
+ throw cause;
397
+ }
398
+ const managedDirectory = await realpath(bootstrap.sessionDirectory);
399
+ for (const entry of entries) {
400
+ if (!entry.isFile() || extname(entry.name) !== ".jsonl")
401
+ continue;
402
+ const path = join(bootstrap.sessionDirectory, entry.name);
403
+ if (dirname(await realpath(path)) !== managedDirectory)
404
+ continue;
405
+ try {
406
+ const manager = await store.open(path);
407
+ if (manager instanceof SessionManager && manager.header.id === id)
408
+ return path;
409
+ }
410
+ catch {
411
+ // Damaged sessions remain listable only through their file diagnostics; they cannot match an opaque ID.
412
+ }
413
+ }
414
+ throw new SessionHostError("NOT_FOUND", `Session not found: "${id}".`);
415
+ };
416
+ const api = {
417
+ state: () => ({
418
+ disposed,
419
+ workspace: bootstrap.workspace,
420
+ activeSession: activeId ? sessions.get(activeId)?.info : undefined,
421
+ busy: operations.size > 0,
422
+ operations: [...operations].map(([requestId, op]) => ({ requestId, kind: op.kind })),
423
+ }),
424
+ listSessions: async () => {
425
+ ensureOpen();
426
+ let entries = [];
427
+ try {
428
+ entries = await readdir(bootstrap.sessionDirectory, { withFileTypes: true, encoding: "utf8" });
429
+ }
430
+ catch (cause) {
431
+ if (cause instanceof Error && "code" in cause && cause.code === "ENOENT")
432
+ return [];
433
+ throw cause;
434
+ }
435
+ const result = [];
436
+ for (const entry of entries) {
437
+ if (!entry.isFile() || extname(entry.name) !== ".jsonl")
438
+ continue;
439
+ const path = join(bootstrap.sessionDirectory, entry.name);
440
+ const real = await realpath(path);
441
+ if (dirname(real) !== (await realpath(bootstrap.sessionDirectory)))
442
+ throw new SessionHostError("INVALID_WORKSPACE", "Session path escaped its managed directory.");
443
+ const manager = await store.open(path);
444
+ if (!(manager instanceof SessionManager))
445
+ continue;
446
+ let managerWorkspace;
447
+ try {
448
+ managerWorkspace = await realpath(manager.header.cwd);
449
+ }
450
+ catch {
451
+ continue;
452
+ }
453
+ if (managerWorkspace.toLowerCase() !== bootstrap.workspace.toLowerCase())
454
+ continue;
455
+ result.push(infoFor(manager));
456
+ }
457
+ return result.sort((left, right) => {
458
+ const rightModified = right.modifiedAt ?? 0;
459
+ const leftModified = left.modifiedAt ?? 0;
460
+ return rightModified - leftModified || right.id.localeCompare(left.id);
461
+ });
462
+ },
463
+ createSession: async () => {
464
+ ensureOpen();
465
+ assertIdle("create a Session");
466
+ const manager = await store.create({
467
+ filePath: join(bootstrap.sessionDirectory, `${new Date().toISOString().replaceAll(/[:.]/g, "-")}-${randomUUID()}.jsonl`),
468
+ cwd: bootstrap.workspace,
469
+ deferCreate: true,
470
+ });
471
+ if (!(manager instanceof SessionManager))
472
+ throw new SessionHostError("INTERNAL", "JSONL SessionStore returned an incompatible SessionManager.");
473
+ return await openManager(manager);
474
+ },
475
+ openSession: async (id) => {
476
+ ensureOpen();
477
+ assertIdle("open a Session");
478
+ if (!/^[A-Za-z0-9](?:[A-Za-z0-9._-]*[A-Za-z0-9])?$/.test(id))
479
+ throw new SessionHostError("INVALID_INPUT", "Session ID is invalid.");
480
+ if (activeId === id)
481
+ return current().info;
482
+ const path = await findSessionPath(id);
483
+ try {
484
+ if ((await lstat(path)).isSymbolicLink())
485
+ throw new Error("symlink");
486
+ }
487
+ catch (cause) {
488
+ if (cause instanceof Error && "code" in cause && cause.code === "ENOENT")
489
+ throw new SessionHostError("NOT_FOUND", `Session not found: "${id}".`);
490
+ throw new SessionHostError("INVALID_WORKSPACE", "Session path is not a regular file.", { cause });
491
+ }
492
+ const managedDirectory = await realpath(bootstrap.sessionDirectory);
493
+ if (dirname(await realpath(path)) !== managedDirectory)
494
+ throw new SessionHostError("INVALID_WORKSPACE", "Session path escaped its managed directory.");
495
+ const manager = await store.open(path);
496
+ if (!(manager instanceof SessionManager))
497
+ throw new SessionHostError("INTERNAL", "JSONL SessionStore returned an incompatible SessionManager.");
498
+ return await openManager(manager);
499
+ },
500
+ inspectSession: async (id) => {
501
+ ensureOpen();
502
+ const path = await findSessionPath(id);
503
+ const manager = await store.open(path);
504
+ if (!(manager instanceof SessionManager))
505
+ throw new SessionHostError("INTERNAL", "Invalid SessionManager.");
506
+ const info = infoFor(manager);
507
+ return {
508
+ session: info,
509
+ transcript: manager.messages,
510
+ tree: manager.getTree(),
511
+ stats: statsFor(manager),
512
+ readOnly: true,
513
+ };
514
+ },
515
+ renameSession: async (id, label) => {
516
+ ensureOpen();
517
+ assertIdle("rename a Session");
518
+ if (!label.trim() || label.length > 200)
519
+ throw new SessionHostError("INVALID_INPUT", "Session label is invalid.");
520
+ const path = await findSessionPath(id);
521
+ const manager = await store.open(path);
522
+ if (!(manager instanceof SessionManager))
523
+ throw new SessionHostError("INTERNAL", "Invalid SessionManager.");
524
+ const active = sessions.get(asId(id));
525
+ const unlock = active ? undefined : await lockSession(path);
526
+ try {
527
+ await manager.appendPlugin({
528
+ pluginId: "di-code.session-label",
529
+ pluginVersion: "1",
530
+ schemaVersion: 1,
531
+ data: { label: label.trim() },
532
+ });
533
+ }
534
+ finally {
535
+ if (unlock)
536
+ await unlock();
537
+ }
538
+ const info = infoFor(manager);
539
+ const existing = sessions.get(asId(id));
540
+ if (existing)
541
+ sessions.set(asId(id), { ...existing, info });
542
+ return info;
543
+ },
544
+ deleteSession: async (id, confirmation) => {
545
+ ensureOpen();
546
+ assertIdle("delete a Session");
547
+ if (confirmation !== id)
548
+ throw new SessionHostError("INVALID_INPUT", "Session deletion requires confirmation.");
549
+ if (activeId === id)
550
+ await api.closeSession();
551
+ const path = await findSessionPath(id);
552
+ const unlock = await lockSession(path);
553
+ try {
554
+ await unlink(path);
555
+ }
556
+ finally {
557
+ await unlock();
558
+ }
559
+ },
560
+ branchSession: async (id, entryId) => {
561
+ ensureOpen();
562
+ assertIdle("branch a Session");
563
+ const sourceId = id ?? activeId;
564
+ if (!sourceId)
565
+ throw new SessionHostError("NOT_FOUND", "No Session is open.");
566
+ const sourcePath = await findSessionPath(sourceId);
567
+ const source = await store.open(sourcePath);
568
+ if (!(source instanceof SessionManager))
569
+ throw new SessionHostError("INTERNAL", "Invalid SessionManager.");
570
+ const branch = await store.create({
571
+ filePath: join(bootstrap.sessionDirectory, `${new Date().toISOString().replaceAll(/[:.]/g, "-")}-${randomUUID()}.jsonl`),
572
+ cwd: bootstrap.workspace,
573
+ deferCreate: true,
574
+ });
575
+ if (!(branch instanceof SessionManager))
576
+ throw new SessionHostError("INTERNAL", "Invalid SessionManager.");
577
+ const selected = entryId ? source.getEntry(entryId) : undefined;
578
+ if (entryId && !selected)
579
+ throw new SessionHostError("NOT_FOUND", "Session tree entry was not found.");
580
+ const messages = source
581
+ .getBranch(entryId ?? source.leafId)
582
+ .filter((entry) => entry.type === "message")
583
+ .map((entry) => entry.message);
584
+ for (const message of messages)
585
+ await branch.appendMessage(message);
586
+ return await openManager(branch);
587
+ },
588
+ closeSession: async () => {
589
+ ensureOpen();
590
+ assertIdle("close the Session");
591
+ if (!activeId)
592
+ return;
593
+ const value = current();
594
+ sessions.delete(activeId);
595
+ activeId = undefined;
596
+ value.unsubscribe();
597
+ await value.session.dispose();
598
+ await value.unlock();
599
+ emit({ type: "session_changed" });
600
+ },
601
+ prompt: (input, signal) => {
602
+ const parsed = typeof input === "string" ? { text: input } : input;
603
+ if (!parsed.text?.trim())
604
+ return Promise.reject(new SessionHostError("INVALID_INPUT", "Prompt text must not be empty."));
605
+ assertIdle("start a prompt");
606
+ return withOperation("prompt", parsed.requestId, signal, async (operationSignal) => {
607
+ const value = current();
608
+ try {
609
+ const result = await value.session.prompt(parsed.text, operationSignal);
610
+ const failed = result.stopReason === "error" || result.stopReason === "aborted";
611
+ failedPrompts.set(value.info.id, failed ? { requestId: parsed.requestId ?? "", text: parsed.text } : undefined);
612
+ await persistRetryState(value.manager, failed ? failedPrompts.get(value.info.id) : undefined);
613
+ return result;
614
+ }
615
+ catch (cause) {
616
+ const failed = { requestId: parsed.requestId ?? "", text: parsed.text };
617
+ failedPrompts.set(value.info.id, failed);
618
+ await persistRetryState(value.manager, failed).catch(() => undefined);
619
+ throw cause;
620
+ }
621
+ });
622
+ },
623
+ promptWithImages: (text, images, signal) => {
624
+ if (!text?.trim())
625
+ return Promise.reject(new SessionHostError("INVALID_INPUT", "Prompt text must not be empty."));
626
+ assertIdle("start a prompt");
627
+ return withOperation("prompt", undefined, signal, async (operationSignal) => {
628
+ const value = current();
629
+ try {
630
+ const result = await value.session.promptWithImages(text, images, operationSignal);
631
+ const failed = result.stopReason === "error" || result.stopReason === "aborted";
632
+ const state = failed ? { requestId: randomUUID(), text } : undefined;
633
+ failedPrompts.set(value.info.id, state);
634
+ await persistRetryState(value.manager, state);
635
+ return result;
636
+ }
637
+ catch (cause) {
638
+ const failed = { requestId: randomUUID(), text };
639
+ failedPrompts.set(value.info.id, failed);
640
+ await persistRetryState(value.manager, failed).catch(() => undefined);
641
+ throw cause;
642
+ }
643
+ });
644
+ },
645
+ steer: (input, signal) => {
646
+ const parsed = typeof input === "string" ? { text: input } : input;
647
+ if (!parsed.text?.trim())
648
+ return Promise.reject(new SessionHostError("INVALID_INPUT", "Steering text must not be empty."));
649
+ return withOperation("steer", parsed.requestId, signal, async (operationSignal) => {
650
+ await current().session.steer(parsed.text, operationSignal);
651
+ });
652
+ },
653
+ steerWithImages: (text, images, signal) => withOperation("steer", undefined, signal, async (operationSignal) => {
654
+ await current().session.steerWithImages(text, images, operationSignal);
655
+ }),
656
+ retry: (input, signal) => {
657
+ ensureOpen();
658
+ assertIdle("retry");
659
+ const target = typeof input === "object" && input !== null && !(input instanceof AbortSignal)
660
+ ? input.targetRequestId
661
+ : undefined;
662
+ const operationSignal = input instanceof AbortSignal ? input : signal;
663
+ const value = current();
664
+ const retry = failedPrompts.get(value.info.id);
665
+ if (!retry || (target !== undefined && target !== retry.requestId))
666
+ return Promise.reject(new SessionHostError("INVALID_INPUT", "No failed prompt is available to retry."));
667
+ return withOperation("retry", target, operationSignal, async (operationSignal) => {
668
+ const result = await value.session.prompt(retry.text, operationSignal);
669
+ if (result.stopReason === "error" || result.stopReason === "aborted") {
670
+ await persistRetryState(value.manager, retry);
671
+ }
672
+ else {
673
+ failedPrompts.set(value.info.id, undefined);
674
+ await persistRetryState(value.manager, undefined);
675
+ }
676
+ return result;
677
+ });
678
+ },
679
+ cancel: (id) => {
680
+ const operation = operations.get(asRequestId(id));
681
+ if (!operation)
682
+ return false;
683
+ operation.controller.abort();
684
+ return true;
685
+ },
686
+ transcript: () => structuredClone(current().session.transcript),
687
+ tree: () => structuredClone(current().session.sessionTree),
688
+ navigateTree: (entryId) => {
689
+ ensureOpen();
690
+ assertIdle("navigate the Session tree");
691
+ return withOperation("tree", undefined, undefined, async () => current().session.navigateTree(entryId));
692
+ },
693
+ setModel: (modelId) => {
694
+ ensureOpen();
695
+ assertIdle("change model");
696
+ return current().session.setModel(modelId);
697
+ },
698
+ setRuntime: (providerId, modelId) => {
699
+ ensureOpen();
700
+ assertIdle("change runtime");
701
+ try {
702
+ const selection = context.require(providerRegistryKey).select(providerId, modelId);
703
+ return api.setRuntimeValue(selection.provider, selection.model);
704
+ }
705
+ catch (cause) {
706
+ const provider = bootstrap.provider;
707
+ const model = provider.id === providerId ? provider.models.find((candidate) => candidate.id === modelId) : undefined;
708
+ if (model)
709
+ return api.setRuntimeValue(provider, model);
710
+ throw cause;
711
+ }
712
+ },
713
+ setRuntimeValue: (provider, model) => {
714
+ ensureOpen();
715
+ assertIdle("change runtime");
716
+ return current().session.setRuntime(provider, model);
717
+ },
718
+ setThinkingLevel: (level) => {
719
+ ensureOpen();
720
+ assertIdle("change thinking level");
721
+ return current().session.setThinkingLevel(level);
722
+ },
723
+ cycleThinkingLevel: () => {
724
+ ensureOpen();
725
+ assertIdle("change thinking level");
726
+ return current().session.cycleThinkingLevel();
727
+ },
728
+ compact: (signal) => {
729
+ ensureOpen();
730
+ assertIdle("compact the Session");
731
+ return withOperation("compact", undefined, signal, async (operationSignal) => current().session.compact(operationSignal));
732
+ },
733
+ setCompactionEnabled: (enabled) => {
734
+ ensureOpen();
735
+ assertIdle("change compaction");
736
+ return current().session.setCompactionEnabled(enabled);
737
+ },
738
+ refreshResources: (projectTrusted, signal) => {
739
+ ensureOpen();
740
+ assertIdle("refresh product resources");
741
+ return withOperation("session", undefined, signal, async () => {
742
+ const next = await createBootstrap(context, {
743
+ ...options,
744
+ projectTrusted: projectTrusted ?? resourceProjectTrusted,
745
+ });
746
+ if (next.workspace !== bootstrap.workspace) {
747
+ await next.closeMcp();
748
+ throw new SessionHostError("INVALID_WORKSPACE", "Product resources changed the Host workspace.");
749
+ }
750
+ const active = activeId === undefined ? undefined : current();
751
+ if (!active) {
752
+ const previous = bootstrap;
753
+ bootstrap = next;
754
+ resourceProjectTrusted = projectTrusted ?? resourceProjectTrusted;
755
+ await previous.closeMcp();
756
+ return;
757
+ }
758
+ try {
759
+ const session = await createAgent(active.manager, next);
760
+ const unsubscribe = session.subscribeSession(emit);
761
+ const previous = bootstrap;
762
+ bootstrap = next;
763
+ resourceProjectTrusted = projectTrusted ?? resourceProjectTrusted;
764
+ sessions.set(active.info.id, { ...active, session, unsubscribe });
765
+ active.unsubscribe();
766
+ await active.session.dispose();
767
+ await previous.closeMcp();
768
+ }
769
+ catch (cause) {
770
+ await next.closeMcp();
771
+ throw cause;
772
+ }
773
+ });
774
+ },
775
+ usage: () => current().session.usage,
776
+ ui: () => {
777
+ const session = current().session;
778
+ return {
779
+ get allowedRoot() {
780
+ return session.allowedRoot;
781
+ },
782
+ get modelId() {
783
+ return session.modelId;
784
+ },
785
+ get providerId() {
786
+ return session.providerId;
787
+ },
788
+ get thinkingLevel() {
789
+ return session.thinkingLevel;
790
+ },
791
+ get availableModels() {
792
+ return session.availableModels;
793
+ },
794
+ get availableSkills() {
795
+ return session.availableSkills;
796
+ },
797
+ get compactionEnabled() {
798
+ return session.compactionEnabled;
799
+ },
800
+ get sessionFile() {
801
+ return session.sessionFile;
802
+ },
803
+ get sessionTree() {
804
+ return session.sessionTree;
805
+ },
806
+ get sessionLeafId() {
807
+ return session.sessionLeafId;
808
+ },
809
+ get transcript() {
810
+ return session.transcript;
811
+ },
812
+ get usage() {
813
+ return session.usage;
814
+ },
815
+ promptWithImages: api.promptWithImages,
816
+ retry: api.retry,
817
+ steerWithImages: api.steerWithImages,
818
+ subscribeSession: (listener) => api.subscribe((event) => {
819
+ if (event.type !== "session_changed")
820
+ return listener(event);
821
+ }),
822
+ setModel: api.setModel,
823
+ setRuntime: (provider, model) => api.setRuntime(provider.id, model.id),
824
+ cycleThinkingLevel: api.cycleThinkingLevel,
825
+ setCompactionEnabled: api.setCompactionEnabled,
826
+ navigateTree: api.navigateTree,
827
+ compact: api.compact,
828
+ };
829
+ },
830
+ subscribe: (listener) => {
831
+ listeners.add(listener);
832
+ return () => listeners.delete(listener);
833
+ },
834
+ dispose: async () => {
835
+ if (disposed)
836
+ return;
837
+ disposed = true;
838
+ const pending = [...operations.values()]
839
+ .map((operation) => operation.promise)
840
+ .filter((promise) => promise !== undefined);
841
+ for (const operation of operations.values())
842
+ operation.controller.abort();
843
+ await Promise.allSettled(pending);
844
+ for (const value of sessions.values()) {
845
+ value.unsubscribe();
846
+ await value.session.dispose();
847
+ await value.unlock();
848
+ }
849
+ sessions.clear();
850
+ failedPrompts.clear();
851
+ activeId = undefined;
852
+ listeners.clear();
853
+ await bootstrap.closeMcp();
854
+ },
855
+ };
856
+ if (options.initialSessionPath !== undefined) {
857
+ const initialPath = resolve(options.initialSessionPath);
858
+ if (dirname(await realpath(initialPath)) !== (await realpath(bootstrap.sessionDirectory)))
859
+ throw new SessionHostError("INVALID_WORKSPACE", "Initial Session path escaped its managed directory.");
860
+ const initial = await store.open(initialPath);
861
+ if (!(initial instanceof SessionManager))
862
+ throw new SessionHostError("INTERNAL", "JSONL SessionStore returned an incompatible SessionManager.");
863
+ await openManager(initial);
864
+ }
865
+ return api;
866
+ }
867
+ export class HostManager {
868
+ actors = new Map();
869
+ disposed = false;
870
+ context;
871
+ constructor(context) {
872
+ this.context = context;
873
+ }
874
+ async get(options) {
875
+ if (this.disposed)
876
+ throw new SessionHostError("DISPOSED", "HostManager has been disposed.");
877
+ const workspace = await validateWorkspace(options.cwd);
878
+ const key = `${options.principal}\n${workspace}`;
879
+ const existing = this.actors.get(key);
880
+ if (existing)
881
+ return existing;
882
+ const host = await createSessionHost(this.context, { ...options, cwd: workspace });
883
+ const actor = Object.assign(host, { principal: options.principal, workspace });
884
+ this.actors.set(key, actor);
885
+ return actor;
886
+ }
887
+ async dispose() {
888
+ if (this.disposed)
889
+ return;
890
+ this.disposed = true;
891
+ await Promise.all([...this.actors.values()].map((actor) => actor.dispose()));
892
+ this.actors.clear();
893
+ }
894
+ }
895
+ //# sourceMappingURL=session-host.js.map