@aria-cli/cli 1.0.57 → 1.0.58

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 (319) hide show
  1. package/bin/aria.mjs +2 -2
  2. package/package.json +17 -76
  3. package/dist/.tsbuildinfo +0 -1
  4. package/dist/attached-local-control-client.js +0 -826
  5. package/dist/bootstrap-local-control-client.js +0 -2
  6. package/dist/capability-aware-method-proxy.js +0 -42
  7. package/dist/cli-context.js +0 -160
  8. package/dist/commands/arions.js +0 -174
  9. package/dist/commands/auth.js +0 -123
  10. package/dist/commands/daemon.js +0 -367
  11. package/dist/commands/definitions.js +0 -176
  12. package/dist/commands/index.js +0 -80
  13. package/dist/commands/login-handler.js +0 -1108
  14. package/dist/commands/logout-handler.js +0 -92
  15. package/dist/commands/memory-handlers.js +0 -89
  16. package/dist/commands/pairing.js +0 -60
  17. package/dist/commands/runtime-cutover-reset-command.js +0 -12
  18. package/dist/commands/runtime-cutover-reset.js +0 -265
  19. package/dist/commands/terminal-setup.js +0 -84
  20. package/dist/config/aria-config.js +0 -238
  21. package/dist/config/index.js +0 -3
  22. package/dist/config/loader.js +0 -97
  23. package/dist/config.js +0 -142
  24. package/dist/daemon-info.js +0 -10
  25. package/dist/ensure-daemon.js +0 -128
  26. package/dist/entrypoints/command-mode.js +0 -5
  27. package/dist/entrypoints/daemon.js +0 -50
  28. package/dist/entrypoints/headless-stdio.js +0 -25
  29. package/dist/entrypoints/interactive.js +0 -80
  30. package/dist/event-loop-watchdog.js +0 -73
  31. package/dist/headless/auth-orchestrator.js +0 -508
  32. package/dist/headless/auth-service.js +0 -43
  33. package/dist/headless/bootstrap-fast-path.js +0 -112
  34. package/dist/headless/call-command.js +0 -143
  35. package/dist/headless/daemon-service.js +0 -318
  36. package/dist/headless/hook-actions.js +0 -235
  37. package/dist/headless/hook-service.js +0 -42
  38. package/dist/headless/kernel-services.js +0 -216
  39. package/dist/headless/kernel.js +0 -785
  40. package/dist/headless/operations/arion.js +0 -119
  41. package/dist/headless/operations/auth.js +0 -45
  42. package/dist/headless/operations/client.js +0 -31
  43. package/dist/headless/operations/config.js +0 -69
  44. package/dist/headless/operations/daemon.js +0 -47
  45. package/dist/headless/operations/hook.js +0 -56
  46. package/dist/headless/operations/index.js +0 -11
  47. package/dist/headless/operations/memory.js +0 -102
  48. package/dist/headless/operations/message.js +0 -279
  49. package/dist/headless/operations/model.js +0 -100
  50. package/dist/headless/operations/peer.js +0 -56
  51. package/dist/headless/operations/run.js +0 -24
  52. package/dist/headless/operations/session.js +0 -90
  53. package/dist/headless/operations/system.js +0 -19
  54. package/dist/headless/operations/utils.js +0 -35
  55. package/dist/headless/run-orchestrator.js +0 -703
  56. package/dist/headless/stdio-server.js +0 -439
  57. package/dist/history/SessionHistory.js +0 -8
  58. package/dist/history/SessionHistoryClient.js +0 -186
  59. package/dist/history/conversation-message.js +0 -112
  60. package/dist/history/index.js +0 -8
  61. package/dist/history/jsonl-replay.js +0 -154
  62. package/dist/history/repair-tool-pairing.js +0 -84
  63. package/dist/history/stall-phase-bridge.js +0 -11
  64. package/dist/history/turn-accumulator.js +0 -427
  65. package/dist/index.js +0 -7
  66. package/dist/ink-repl.js +0 -4183
  67. package/dist/local-control-bootstrap.js +0 -26
  68. package/dist/local-control-client.js +0 -2
  69. package/dist/local-control-error-reporting.js +0 -34
  70. package/dist/local-control-http-client.js +0 -362
  71. package/dist/local-control-lazy-wrapper.js +0 -363
  72. package/dist/local-control-manager.js +0 -146
  73. package/dist/main.js +0 -62
  74. package/dist/network-security.js +0 -62
  75. package/dist/networking-server.js +0 -38
  76. package/dist/peer-identity.js +0 -23
  77. package/dist/polling-subscription.js +0 -34
  78. package/dist/relaunch.js +0 -617
  79. package/dist/release-notes.js +0 -35
  80. package/dist/repl-cleanup.js +0 -47
  81. package/dist/runtime/configure-bun-sqlite.js +0 -3
  82. package/dist/runtime/crash-handlers.js +0 -111
  83. package/dist/runtime/interactive-invocation.js +0 -39
  84. package/dist/runtime/internal-mode.js +0 -14
  85. package/dist/runtime/launch-spec.js +0 -64
  86. package/dist/runtime/owner-lease.js +0 -44
  87. package/dist/runtime/public-mode.js +0 -20
  88. package/dist/runtime/run-internal-mode.js +0 -18
  89. package/dist/runtime/runtime-kind.js +0 -32
  90. package/dist/runtime/spawn-aria.js +0 -38
  91. package/dist/selectable-client.js +0 -2
  92. package/dist/selectable-peer.js +0 -2
  93. package/dist/session.js +0 -203
  94. package/dist/slash-commands.js +0 -80
  95. package/dist/sounds.js +0 -210
  96. package/dist/ui/App.js +0 -526
  97. package/dist/ui/components/AnthropicMethodPicker.js +0 -6
  98. package/dist/ui/components/ArionPrompt.js +0 -15
  99. package/dist/ui/components/AutocompleteDropdown.js +0 -23
  100. package/dist/ui/components/AutonomySelector.js +0 -55
  101. package/dist/ui/components/Banner.js +0 -98
  102. package/dist/ui/components/ConversationHistory.js +0 -175
  103. package/dist/ui/components/CopilotDeviceLoginFlow.js +0 -88
  104. package/dist/ui/components/CopilotSourcePicker.js +0 -50
  105. package/dist/ui/components/Cost.js +0 -10
  106. package/dist/ui/components/CustomSelect/option-map.js +0 -30
  107. package/dist/ui/components/CustomSelect/select-option.js +0 -13
  108. package/dist/ui/components/CustomSelect/select.js +0 -42
  109. package/dist/ui/components/CustomSelect/use-select-state.js +0 -179
  110. package/dist/ui/components/CustomSelect/use-select.js +0 -15
  111. package/dist/ui/components/ErrorDisplay.js +0 -35
  112. package/dist/ui/components/FallbackToolUseRejectedMessage.js +0 -7
  113. package/dist/ui/components/FileEditToolUpdatedMessage.js +0 -57
  114. package/dist/ui/components/HandoffMarker.js +0 -18
  115. package/dist/ui/components/HighlightedCode.js +0 -21
  116. package/dist/ui/components/InputArea.js +0 -187
  117. package/dist/ui/components/Message.js +0 -25
  118. package/dist/ui/components/OAuthLoginFlow.js +0 -113
  119. package/dist/ui/components/OutputTruncation.js +0 -35
  120. package/dist/ui/components/PermissionPrompt.js +0 -79
  121. package/dist/ui/components/PipelineTimingPanel.js +0 -15
  122. package/dist/ui/components/ProviderMethodPicker.js +0 -61
  123. package/dist/ui/components/ProviderPicker.js +0 -63
  124. package/dist/ui/components/RenderItemView.js +0 -71
  125. package/dist/ui/components/Spinner.js +0 -46
  126. package/dist/ui/components/StatusBar.js +0 -95
  127. package/dist/ui/components/StreamingIndicator.js +0 -55
  128. package/dist/ui/components/StructuredDiff.js +0 -168
  129. package/dist/ui/components/TextInputOverlay.js +0 -43
  130. package/dist/ui/components/ThinkingBlock.js +0 -82
  131. package/dist/ui/components/ToolCost.js +0 -17
  132. package/dist/ui/components/ToolExecution.js +0 -61
  133. package/dist/ui/components/ToolHeader.js +0 -51
  134. package/dist/ui/components/ToolRenderLayoutContext.js +0 -14
  135. package/dist/ui/components/ToolResultWrapper.js +0 -6
  136. package/dist/ui/components/ToolUseLoader.js +0 -35
  137. package/dist/ui/components/TraceWaterfall.js +0 -91
  138. package/dist/ui/components/index.js +0 -33
  139. package/dist/ui/components/messages/AssistantTextMessage.js +0 -25
  140. package/dist/ui/components/messages/UserImageMessage.js +0 -12
  141. package/dist/ui/components/messages/UserTextMessage.js +0 -12
  142. package/dist/ui/components/overlays/ArionSelector.js +0 -68
  143. package/dist/ui/components/overlays/ClientSelector.js +0 -62
  144. package/dist/ui/components/overlays/CommandPalette.js +0 -67
  145. package/dist/ui/components/overlays/DaemonControl.js +0 -87
  146. package/dist/ui/components/overlays/InviteShareOverlay.js +0 -15
  147. package/dist/ui/components/overlays/JoinInviteOverlay.js +0 -32
  148. package/dist/ui/components/overlays/MemoryBrowser.js +0 -100
  149. package/dist/ui/components/overlays/MessageSelector.js +0 -123
  150. package/dist/ui/components/overlays/ModelSelector.js +0 -211
  151. package/dist/ui/components/overlays/PairRequestOverlay.js +0 -42
  152. package/dist/ui/components/overlays/PeerSelector.js +0 -84
  153. package/dist/ui/components/overlays/SessionSelector.js +0 -102
  154. package/dist/ui/components/overlays/SoundSelector.js +0 -86
  155. package/dist/ui/components/overlays/ThemeSelector.js +0 -139
  156. package/dist/ui/components/overlays/index.js +0 -15
  157. package/dist/ui/components/permissions/BashPermissionRequest/BashPermissionRequest.js +0 -53
  158. package/dist/ui/components/permissions/FallbackPermissionRequest.js +0 -56
  159. package/dist/ui/components/permissions/FileEditPermissionRequest/FileEditPermissionRequest.js +0 -76
  160. package/dist/ui/components/permissions/FileEditPermissionRequest/FileEditToolDiff.js +0 -18
  161. package/dist/ui/components/permissions/FileWritePermissionRequest/FileWritePermissionRequest.js +0 -64
  162. package/dist/ui/components/permissions/FileWritePermissionRequest/FileWriteToolDiff.js +0 -26
  163. package/dist/ui/components/permissions/FilesystemPermissionRequest/FilesystemPermissionRequest.js +0 -141
  164. package/dist/ui/components/permissions/PermissionRequest.js +0 -70
  165. package/dist/ui/components/permissions/PermissionRequestTitle.js +0 -41
  166. package/dist/ui/components/permissions/hooks.js +0 -10
  167. package/dist/ui/components/permissions/toolUseOptions.js +0 -68
  168. package/dist/ui/components/permissions/utils.js +0 -10
  169. package/dist/ui/components/text-input/Cursor.js +0 -326
  170. package/dist/ui/components/text-input/TextInput.js +0 -231
  171. package/dist/ui/components/text-input/imagePaste.js +0 -28
  172. package/dist/ui/components/text-input/index.js +0 -6
  173. package/dist/ui/components/text-input/useDoublePress.js +0 -30
  174. package/dist/ui/components/text-input/useTextInput.js +0 -245
  175. package/dist/ui/components/tool-types.js +0 -9
  176. package/dist/ui/constants/figures.js +0 -4
  177. package/dist/ui/constants/index.js +0 -3
  178. package/dist/ui/display-mode.js +0 -93
  179. package/dist/ui/display-policy.js +0 -19
  180. package/dist/ui/hooks/index.js +0 -6
  181. package/dist/ui/hooks/useCommandAutocomplete.js +0 -93
  182. package/dist/ui/hooks/useDoublePress.js +0 -37
  183. package/dist/ui/hooks/useIndicatorState.js +0 -55
  184. package/dist/ui/hooks/useInterval.js +0 -23
  185. package/dist/ui/hooks/useKeyboardShortcuts.js +0 -127
  186. package/dist/ui/hooks/useTerminalSize.js +0 -55
  187. package/dist/ui/hooks/useUnifiedMessages.js +0 -117
  188. package/dist/ui/indicator-state.js +0 -44
  189. package/dist/ui/markdown/highlight.js +0 -44
  190. package/dist/ui/markdown/index.js +0 -1460
  191. package/dist/ui/markdown/tokenizer.js +0 -24
  192. package/dist/ui/render-item.js +0 -5
  193. package/dist/ui/screens/REPL.js +0 -119
  194. package/dist/ui/screens/approval-lifecycle.js +0 -38
  195. package/dist/ui/status-line.js +0 -72
  196. package/dist/ui/theme/index.js +0 -51
  197. package/dist/ui/theme/themes/claude-dark-daltonized.js +0 -51
  198. package/dist/ui/theme/themes/claude-dark.js +0 -50
  199. package/dist/ui/theme/themes/claude-light-daltonized.js +0 -51
  200. package/dist/ui/theme/themes/claude-light.js +0 -50
  201. package/dist/ui/theme/themes/dark-accessible.js +0 -18
  202. package/dist/ui/theme/themes/dark.js +0 -49
  203. package/dist/ui/theme/themes/light-accessible.js +0 -18
  204. package/dist/ui/theme/themes/light.js +0 -49
  205. package/dist/ui/theme/types.js +0 -3
  206. package/dist/ui/theme.js +0 -142
  207. package/dist/ui/to-render-items.js +0 -145
  208. package/dist/ui/tools/AgentTool/index.js +0 -30
  209. package/dist/ui/tools/ArchitectTool/index.js +0 -31
  210. package/dist/ui/tools/AskUserTool/index.js +0 -46
  211. package/dist/ui/tools/BashTool/BashToolResultMessage.js +0 -11
  212. package/dist/ui/tools/BashTool/OutputLine.js +0 -21
  213. package/dist/ui/tools/BashTool/index.js +0 -91
  214. package/dist/ui/tools/BrowseTool/index.js +0 -43
  215. package/dist/ui/tools/BrowserTool/index.js +0 -47
  216. package/dist/ui/tools/CbmTool/index.js +0 -188
  217. package/dist/ui/tools/CheckDelegationTool/index.js +0 -46
  218. package/dist/ui/tools/CheckMessagesTool/index.js +0 -85
  219. package/dist/ui/tools/CreateQuipTool/index.js +0 -30
  220. package/dist/ui/tools/CreateSkillTool/index.js +0 -22
  221. package/dist/ui/tools/CreateToolTool/index.js +0 -31
  222. package/dist/ui/tools/DelegateRemoteTool/index.js +0 -42
  223. package/dist/ui/tools/DeployTool/index.js +0 -47
  224. package/dist/ui/tools/FffTool/index.js +0 -103
  225. package/dist/ui/tools/FileEditTool/index.js +0 -67
  226. package/dist/ui/tools/FileReadTool/index.js +0 -68
  227. package/dist/ui/tools/FileWriteTool/index.js +0 -61
  228. package/dist/ui/tools/ForkTool/index.js +0 -47
  229. package/dist/ui/tools/FrgTool/index.js +0 -96
  230. package/dist/ui/tools/GetThreadTool/index.js +0 -39
  231. package/dist/ui/tools/GlobTool/index.js +0 -50
  232. package/dist/ui/tools/GrepTool/index.js +0 -84
  233. package/dist/ui/tools/HatchArionTool/index.js +0 -36
  234. package/dist/ui/tools/LearnSkillTool/index.js +0 -22
  235. package/dist/ui/tools/LearnTool/index.js +0 -43
  236. package/dist/ui/tools/LearnToolTool/index.js +0 -22
  237. package/dist/ui/tools/ListClientsTool/index.js +0 -39
  238. package/dist/ui/tools/LspTool/index.js +0 -261
  239. package/dist/ui/tools/MCPTool/index.js +0 -33
  240. package/dist/ui/tools/ManageNetworkTool/index.js +0 -53
  241. package/dist/ui/tools/MemoryReadTool/index.js +0 -64
  242. package/dist/ui/tools/MemoryWriteTool/index.js +0 -20
  243. package/dist/ui/tools/NotebookEditTool/index.js +0 -33
  244. package/dist/ui/tools/NotebookReadTool/index.js +0 -25
  245. package/dist/ui/tools/OutlookReadTool/index.js +0 -66
  246. package/dist/ui/tools/OutlookReplyTool/index.js +0 -49
  247. package/dist/ui/tools/OutlookSendTool/index.js +0 -49
  248. package/dist/ui/tools/PauseDelegationTool/index.js +0 -35
  249. package/dist/ui/tools/ProbeTool/index.js +0 -121
  250. package/dist/ui/tools/ProcessTool/index.js +0 -66
  251. package/dist/ui/tools/QuestListTool/index.js +0 -46
  252. package/dist/ui/tools/QuestReportTool/index.js +0 -49
  253. package/dist/ui/tools/QuestUpdateTool/index.js +0 -87
  254. package/dist/ui/tools/QuipCommentTool/index.js +0 -69
  255. package/dist/ui/tools/QuipReadTool/index.js +0 -71
  256. package/dist/ui/tools/RestArionTool/index.js +0 -32
  257. package/dist/ui/tools/RestartTool/index.js +0 -35
  258. package/dist/ui/tools/ResumeDelegationTool/index.js +0 -35
  259. package/dist/ui/tools/RetireArionTool/index.js +0 -32
  260. package/dist/ui/tools/RgTool/index.js +0 -73
  261. package/dist/ui/tools/SearchKnowledgeTool/index.js +0 -43
  262. package/dist/ui/tools/SearchMessagesTool/index.js +0 -43
  263. package/dist/ui/tools/SelfDiagnoseTool/index.js +0 -61
  264. package/dist/ui/tools/SendMessageTool/index.js +0 -45
  265. package/dist/ui/tools/SerenaTool/index.js +0 -124
  266. package/dist/ui/tools/SessionHistoryTool/index.js +0 -52
  267. package/dist/ui/tools/SgTool/index.js +0 -80
  268. package/dist/ui/tools/SlackReactTool/index.js +0 -41
  269. package/dist/ui/tools/SlackReadTool/index.js +0 -48
  270. package/dist/ui/tools/SlackSendTool/index.js +0 -45
  271. package/dist/ui/tools/SpawnWorkerTool/index.js +0 -33
  272. package/dist/ui/tools/StickerRequestTool/index.js +0 -19
  273. package/dist/ui/tools/ThinkTool/index.js +0 -17
  274. package/dist/ui/tools/UgTool/index.js +0 -108
  275. package/dist/ui/tools/UseSkillTool/index.js +0 -22
  276. package/dist/ui/tools/WakeArionTool/index.js +0 -32
  277. package/dist/ui/tools/WebFetchTool/index.js +0 -56
  278. package/dist/ui/tools/WebSearchTool/index.js +0 -44
  279. package/dist/ui/tools/lsTool/index.js +0 -58
  280. package/dist/ui/tools/registry.js +0 -197
  281. package/dist/ui/tools/tool-renderer.js +0 -11
  282. package/dist/ui/tools/truncation.js +0 -35
  283. package/dist/ui/types/anthropic.js +0 -4
  284. package/dist/ui/types/index.js +0 -2
  285. package/dist/ui/types/message.js +0 -3
  286. package/dist/ui/types/tool.js +0 -4
  287. package/dist/ui/utils/array.js +0 -4
  288. package/dist/ui/utils/cursor.js +0 -131
  289. package/dist/ui/utils/diff.js +0 -120
  290. package/dist/ui/utils/format.js +0 -42
  291. package/dist/ui/utils/fuzzy.js +0 -59
  292. package/dist/ui/utils/index.js +0 -11
  293. package/dist/ui/utils/keys.js +0 -8
  294. package/dist/ui/utils/patch.js +0 -17
  295. package/dist/ui/utils/risk.js +0 -114
  296. package/dist/ui/utils/terminal-image.js +0 -70
  297. package/dist/ui/utils/validation.js +0 -48
  298. package/dist/ui/verb-pairs.js +0 -248
  299. package/dist/ui.js +0 -131
  300. package/src/entrypoints/command-mode.ts +0 -5
  301. package/src/entrypoints/daemon.ts +0 -54
  302. package/src/entrypoints/headless-stdio.ts +0 -27
  303. package/src/entrypoints/interactive.ts +0 -112
  304. package/src/main.ts +0 -72
  305. package/src/runtime/configure-bun-sqlite.ts +0 -3
  306. package/src/runtime/crash-handlers.ts +0 -128
  307. package/src/runtime/interactive-invocation.test.ts +0 -42
  308. package/src/runtime/interactive-invocation.ts +0 -51
  309. package/src/runtime/internal-mode.test.ts +0 -19
  310. package/src/runtime/internal-mode.ts +0 -24
  311. package/src/runtime/launch-spec.test.ts +0 -26
  312. package/src/runtime/launch-spec.ts +0 -84
  313. package/src/runtime/owner-lease.ts +0 -52
  314. package/src/runtime/public-mode.test.ts +0 -18
  315. package/src/runtime/public-mode.ts +0 -19
  316. package/src/runtime/run-internal-mode.ts +0 -19
  317. package/src/runtime/runtime-kind.test.ts +0 -23
  318. package/src/runtime/runtime-kind.ts +0 -41
  319. package/src/runtime/spawn-aria.ts +0 -62
@@ -1,826 +0,0 @@
1
- import * as net from "node:net";
2
- import { appendFileSync, mkdirSync } from "node:fs";
3
- import { join as pathJoin } from "node:path";
4
- import { randomUUID } from "node:crypto";
5
- import { createRuntimeSocketAttachedLocalControlClient, createRuntimeSocketLocalControlClient, createTrustedRuntimeError, AttachedClientLeaseGrantSchema, LocalControlSocketAttachClientRequestSchema, LocalControlSocketAttachClientResponseSchema, LocalControlSocketRequestSchema, LocalControlSocketResponseSchema, } from "@aria-cli/tools";
6
- import { assertAttachableRuntimeBootstrap } from "./local-control-bootstrap.js";
7
- import { reportTrustedLocalControlFailure } from "./local-control-error-reporting.js";
8
- async function sleep(ms) {
9
- await new Promise((resolve) => setTimeout(resolve, Math.max(ms, 0)));
10
- }
11
- const EXISTING_RUNTIME_ATTACH_TIMEOUT_MS = 10_000;
12
- const EXISTING_RUNTIME_ATTACH_RETRY_MS = 250;
13
- let hostSupervisorModulePromise;
14
- let nodeMetadataModulePromise;
15
- let nodeStoreModulePromise;
16
- function loadHostSupervisorModule() {
17
- hostSupervisorModulePromise ??= import("@aria-cli/server/runtime/host-supervisor");
18
- return hostSupervisorModulePromise;
19
- }
20
- function loadNodeMetadataModule() {
21
- nodeMetadataModulePromise ??= import("@aria-cli/server/runtime/node-metadata");
22
- return nodeMetadataModulePromise;
23
- }
24
- function loadNodeStoreModule() {
25
- nodeStoreModulePromise ??= import("@aria-cli/server/runtime/node-store");
26
- return nodeStoreModulePromise;
27
- }
28
- async function resolveOrCreateLocalNode(ariaHome) {
29
- const { resolveOrCreateNode } = await loadNodeMetadataModule();
30
- return resolveOrCreateNode({ ariaHome });
31
- }
32
- async function resolveLocalRuntimeOwnerRecord(ariaHome, nodeId) {
33
- const { NodeStore } = await loadNodeStoreModule();
34
- const nodeStore = new NodeStore({ ariaHome });
35
- try {
36
- return nodeStore.readRuntimeOwnerRecord(nodeId);
37
- }
38
- finally {
39
- nodeStore.close();
40
- }
41
- }
42
- function isPublishedRuntimeUnavailableError(error) {
43
- if (!(error instanceof Error)) {
44
- return false;
45
- }
46
- const message = error.message;
47
- return (message.includes("connect ENOENT") ||
48
- message.includes("ECONNREFUSED") ||
49
- message.includes("Missing runtime owner record") ||
50
- message.includes("not attachable") ||
51
- message.includes("not control-ready"));
52
- }
53
- async function connectToPublishedRuntimeSocket(options) {
54
- const ownerRecord = await resolveLocalRuntimeOwnerRecord(options.ariaHome, options.nodeId);
55
- if (!ownerRecord) {
56
- throw new Error(`[local-control-client] Missing runtime owner record for node ${options.nodeId}`);
57
- }
58
- const control = createSocketLocalControlClient({
59
- runtimeSocket: ownerRecord.runtimeSocket,
60
- pollIntervalMs: options.pollIntervalMs,
61
- });
62
- const bootstrap = assertAttachableRuntimeBootstrap(options.nodeId, await control.getRuntimeBootstrap());
63
- let externalClientId;
64
- let externalClientAuthToken;
65
- let resilientClient;
66
- if (options.clientKind) {
67
- resilientClient = await createResilientAttachedClient({
68
- runtimeSocket: ownerRecord.runtimeSocket,
69
- clientKind: options.clientKind,
70
- logDir: pathJoin(options.ariaHome, "logs"),
71
- });
72
- externalClientId = resilientClient.getClientId();
73
- externalClientAuthToken = resilientClient.getClientAuthToken();
74
- }
75
- let releasePromise = null;
76
- return {
77
- nodeId: options.nodeId,
78
- runtimeId: bootstrap.runtimeId,
79
- port: bootstrap.controlEndpoint.port,
80
- ownership: options.ownership,
81
- ...(externalClientId ? { attachedClientId: externalClientId } : {}),
82
- ...(externalClientAuthToken ? { attachedClientAuthToken: externalClientAuthToken } : {}),
83
- control: resilientClient ? resilientClient.api : control,
84
- getConnectionState: resilientClient
85
- ? (() => {
86
- const rc = resilientClient;
87
- return () => rc.getState();
88
- })()
89
- : undefined,
90
- onConnectionStateChange: resilientClient
91
- ? (() => {
92
- const rc = resilientClient;
93
- return (listener) => rc.onStateChange(listener);
94
- })()
95
- : undefined,
96
- release: async (releaseOptions) => {
97
- if (releasePromise) {
98
- return releasePromise;
99
- }
100
- releasePromise = (async () => {
101
- const rc = resilientClient;
102
- resilientClient = undefined;
103
- externalClientId = undefined;
104
- externalClientAuthToken = undefined;
105
- await rc?.release().catch(() => { });
106
- await options.release(releaseOptions);
107
- })();
108
- return releasePromise;
109
- },
110
- };
111
- }
112
- export async function requestRuntimeSocketLease(runtimeSocket, payload) {
113
- const request = LocalControlSocketRequestSchema.parse({
114
- id: randomUUID(),
115
- method: "attachClient",
116
- payload,
117
- });
118
- return new Promise((resolve, reject) => {
119
- const socket = net.createConnection(runtimeSocket);
120
- let buffer = "";
121
- let settled = false;
122
- let closed = false;
123
- let closeResolve;
124
- const closePromise = new Promise((resolveClose) => {
125
- closeResolve = resolveClose;
126
- });
127
- const markClosed = () => {
128
- if (closed) {
129
- return;
130
- }
131
- closed = true;
132
- closeResolve?.();
133
- if (!settled) {
134
- settled = true;
135
- reject(new Error("Local control socket closed before establishing an attached-client lease"));
136
- }
137
- };
138
- socket.setEncoding("utf8");
139
- socket.once("connect", () => {
140
- socket.write(`${JSON.stringify(request)}\n`);
141
- });
142
- socket.once("error", (error) => {
143
- if (!settled) {
144
- settled = true;
145
- reject(error);
146
- return;
147
- }
148
- markClosed();
149
- });
150
- socket.once("end", markClosed);
151
- socket.once("close", markClosed);
152
- socket.on("data", async (chunk) => {
153
- buffer += chunk;
154
- const newlineIndex = buffer.indexOf("\n");
155
- if (newlineIndex === -1 || settled) {
156
- return;
157
- }
158
- try {
159
- const response = LocalControlSocketResponseSchema.parse(JSON.parse(buffer.slice(0, newlineIndex)));
160
- if (response.id !== request.id) {
161
- settled = true;
162
- socket.destroy();
163
- reject(new Error("Local control socket response ID mismatch"));
164
- return;
165
- }
166
- if (!response.ok) {
167
- const trustedError = createTrustedRuntimeError(response.error, response.diagnostic);
168
- await reportTrustedLocalControlFailure(trustedError, {
169
- endpoint: "attachClient",
170
- responseStatus: null,
171
- payload: response,
172
- transport: "runtime_socket_attach",
173
- });
174
- settled = true;
175
- socket.destroy();
176
- reject(trustedError);
177
- return;
178
- }
179
- const attached = AttachedClientLeaseGrantSchema.parse(LocalControlSocketAttachClientResponseSchema.parse(response.payload));
180
- settled = true;
181
- resolve({
182
- clientId: attached.clientId,
183
- clientAuthToken: attached.clientAuthToken,
184
- release: async () => {
185
- if (!closed) {
186
- socket.destroy();
187
- }
188
- await closePromise;
189
- },
190
- });
191
- }
192
- catch (error) {
193
- settled = true;
194
- socket.destroy();
195
- reject(error);
196
- }
197
- });
198
- });
199
- }
200
- export async function attachExistingLocalControlClient(options) {
201
- const resolvedNode = await resolveOrCreateLocalNode(options.ariaHome);
202
- if (!(await resolveLocalRuntimeOwnerRecord(options.ariaHome, resolvedNode.nodeId))) {
203
- return null;
204
- }
205
- const deadline = Date.now() + EXISTING_RUNTIME_ATTACH_TIMEOUT_MS;
206
- while (Date.now() < deadline) {
207
- try {
208
- return await connectToPublishedRuntimeSocket({
209
- ariaHome: options.ariaHome,
210
- nodeId: resolvedNode.nodeId,
211
- ownership: "reattached",
212
- release: async () => { },
213
- pollIntervalMs: options.pollIntervalMs,
214
- clientKind: options.clientKind,
215
- });
216
- }
217
- catch (error) {
218
- if (!isPublishedRuntimeUnavailableError(error)) {
219
- throw error;
220
- }
221
- if (!(await resolveLocalRuntimeOwnerRecord(options.ariaHome, resolvedNode.nodeId))) {
222
- return null;
223
- }
224
- await sleep(EXISTING_RUNTIME_ATTACH_RETRY_MS);
225
- }
226
- }
227
- return null;
228
- }
229
- function createSocketLocalControlClient(options) {
230
- if (options.auth) {
231
- return createRuntimeSocketAttachedLocalControlClient({
232
- runtimeSocket: options.runtimeSocket,
233
- pollIntervalMs: options.pollIntervalMs,
234
- auth: options.auth,
235
- });
236
- }
237
- return createRuntimeSocketLocalControlClient(options);
238
- }
239
- export async function attachLocalControlClient(options) {
240
- const attachToPublishedRuntime = async (nodeId, clientKind) => {
241
- const retryIntervalMs = options.pollIntervalMs ?? 100;
242
- const deadline = Date.now() + Math.max(retryIntervalMs * 20, 2_000);
243
- let lastError = null;
244
- while (Date.now() < deadline) {
245
- if (options.signal?.aborted) {
246
- throw new Error(`[local-control-client] Attach-only client ${clientKind} aborted while waiting for a live runtime owner on node ${nodeId}`);
247
- }
248
- try {
249
- const ownerRecord = await resolveLocalRuntimeOwnerRecord(options.ariaHome, nodeId);
250
- if (!ownerRecord) {
251
- lastError = new Error(`[local-control-client] No live runtime owner available for attach-only client ${clientKind} on node ${nodeId}`);
252
- }
253
- else {
254
- return await connectToPublishedRuntimeSocket({
255
- ariaHome: options.ariaHome,
256
- nodeId,
257
- ownership: "reattached",
258
- release: async () => { },
259
- pollIntervalMs: options.pollIntervalMs,
260
- clientKind,
261
- });
262
- }
263
- }
264
- catch (error) {
265
- if (!isPublishedRuntimeUnavailableError(error)) {
266
- throw error;
267
- }
268
- lastError = error;
269
- }
270
- await sleep(retryIntervalMs);
271
- }
272
- throw (lastError ??
273
- new Error(`[local-control-client] No live runtime owner available for attach-only client ${clientKind} on node ${nodeId}`));
274
- };
275
- const { getHostSupervisor, HostSupervisorSplitBrainError } = await loadHostSupervisorModule();
276
- const supervisor = getHostSupervisor();
277
- let attachment;
278
- try {
279
- attachment = await supervisor.attach({
280
- ariaHome: options.ariaHome,
281
- arionName: options.arionName,
282
- clientKind: options.clientKind,
283
- memoriaFactory: options.memoriaFactory,
284
- router: options.router,
285
- authResolver: options.authResolver,
286
- runSessionConfig: options.runSessionConfig,
287
- mcpServers: options.mcpServers,
288
- daemonSafetyPolicy: options.daemonSafetyPolicy,
289
- autonomousIntervalMs: options.autonomousIntervalMs,
290
- signal: options.signal,
291
- silent: options.silent,
292
- runtimeLifecycle: options.runtimeLifecycle,
293
- port: options.port,
294
- });
295
- }
296
- catch (error) {
297
- if (error instanceof HostSupervisorSplitBrainError) {
298
- return connectToPublishedRuntimeSocket({
299
- ariaHome: options.ariaHome,
300
- nodeId: error.nodeId,
301
- ownership: "reattached",
302
- release: async () => { },
303
- pollIntervalMs: options.pollIntervalMs,
304
- clientKind: options.clientKind,
305
- });
306
- }
307
- throw error;
308
- }
309
- try {
310
- await attachment.runtime.waitForInitialDiscovery();
311
- return await connectToPublishedRuntimeSocket({
312
- ariaHome: options.ariaHome,
313
- nodeId: attachment.nodeId,
314
- ownership: attachment.ownership,
315
- pollIntervalMs: options.pollIntervalMs,
316
- clientKind: options.clientKind,
317
- release: (releaseOptions) => attachment.release(releaseOptions),
318
- });
319
- }
320
- catch (error) {
321
- await attachment.release();
322
- throw error;
323
- }
324
- }
325
- export async function resolveLocalControlClient(options) {
326
- const resolvedNode = await resolveOrCreateLocalNode(options.ariaHome);
327
- try {
328
- const attached = await connectToPublishedRuntimeSocket({
329
- ariaHome: options.ariaHome,
330
- nodeId: resolvedNode.nodeId,
331
- ownership: "reattached",
332
- release: async () => { },
333
- pollIntervalMs: options.pollIntervalMs,
334
- });
335
- return attached.control;
336
- }
337
- catch (error) {
338
- if (isPublishedRuntimeUnavailableError(error)) {
339
- return null;
340
- }
341
- throw error;
342
- }
343
- }
344
- export function resolveLocalControlClientSync(options) {
345
- void options;
346
- // Published runtime liveness is an async property established by a bootstrap round-trip.
347
- // A synchronous raw-socket fallback would recreate the stale-owner bypass.
348
- return null;
349
- }
350
- // ---------------------------------------------------------------------------
351
- // Resilient attached client — THE canonical way to get a client that survives
352
- // daemon restarts. Fuses lease acquisition, auth, reattach, background
353
- // reconnection, and diagnostic logging at the lowest level so callers cannot
354
- // get it wrong.
355
- //
356
- // Design (modeled after ioredis / node-redis / gRPC channel semantics):
357
- //
358
- // 1. **Background reconnect loop** — runs forever with capped exponential
359
- // backoff + jitter. As soon as the daemon is reachable the loop
360
- // acquires a fresh lease and flips the state to `connected`.
361
- //
362
- // 2. **Connection state** — `connected | disconnected | reconnecting`
363
- // with an EventEmitter so the REPL / overlay can react in real time.
364
- //
365
- // 3. **Per-call waiting** — when the connection is down a call does NOT
366
- // fail immediately. It waits for the background loop to reconnect
367
- // (up to `callTimeoutMs`, default 5 minutes) then retries. This
368
- // means a daemon restart that takes 1-2 minutes is fully transparent.
369
- //
370
- // 4. **Jitter** — prevents thundering-herd reconnect storms when many
371
- // clients try to reconnect at the same time.
372
- // ---------------------------------------------------------------------------
373
- /** Backoff parameters (all in milliseconds). */
374
- const INITIAL_BACKOFF_MS = 250;
375
- const MAX_BACKOFF_MS = 30_000;
376
- const DEFAULT_CALL_TIMEOUT_MS = 5 * 60 * 1000; // 5 minutes
377
- /**
378
- * Methods whose streams are safe to re-issue after reconnect (subscriptions).
379
- * Streams NOT in this set are treated as non-idempotent executions and will
380
- * NOT be silently restarted — the error propagates so callers can handle it.
381
- */
382
- const RESUBSCRIBABLE_STREAMS = new Set([
383
- "subscribeRuntimeEvents",
384
- "subscribeInbox",
385
- "subscribePeers",
386
- "subscribeDirectClientInbox",
387
- ]);
388
- function isRecoverableError(error) {
389
- if (!(error instanceof Error))
390
- return false;
391
- const msg = error.message;
392
- return (msg.includes("attached-local-client-only") ||
393
- msg.includes("ECONNRESET") ||
394
- msg.includes("ECONNREFUSED") ||
395
- msg.includes("EPIPE") ||
396
- msg.includes("connect ENOENT") ||
397
- msg.includes("socket hang up") ||
398
- msg.includes("closed before"));
399
- }
400
- function jitter(ms) {
401
- return ms + Math.floor(Math.random() * ms * 0.3);
402
- }
403
- function backoffSleep(ms) {
404
- if (ms <= 0)
405
- return Promise.resolve();
406
- return new Promise((resolve) => {
407
- const timer = setTimeout(resolve, ms);
408
- timer.unref?.();
409
- });
410
- }
411
- /**
412
- * Acquire a one-shot lease from the daemon socket. Returns credentials
413
- * without creating a background reconnect loop. Use this when you only
414
- * need clientId/token (e.g. for resolveCredentials) but do NOT need a
415
- * long-lived resilient client.
416
- */
417
- export async function requestOneShotLease(runtimeSocket, clientKind = "local-api") {
418
- const lease = await requestRuntimeSocketLease(runtimeSocket, LocalControlSocketAttachClientRequestSchema.parse({
419
- clientKind,
420
- lease: true,
421
- pid: process.pid,
422
- }));
423
- return {
424
- clientId: lease.clientId,
425
- clientAuthToken: lease.clientAuthToken,
426
- release: () => lease.release(),
427
- };
428
- }
429
- /**
430
- * Creates a self-healing AttachedLocalControlApi that survives daemon
431
- * restarts — including long outages of minutes or hours.
432
- *
433
- * Architecture:
434
- *
435
- * ┌────────────────────────────────┐
436
- * │ Background Reconnect Loop │ runs forever until release()
437
- * │ exponential backoff + jitter │ capped at 30 s
438
- * │ acquires fresh lease on │
439
- * │ success → state = connected │
440
- * └──────────────┬─────────────────┘
441
- * │
442
- * ▼
443
- * ┌────────────────────────────────┐
444
- * │ Per-Call Path │
445
- * │ connected → execute │
446
- * │ disconnected → wait for │
447
- * │ reconnect (up to 5 min) │
448
- * │ then retry │
449
- * └────────────────────────────────┘
450
- */
451
- export async function createResilientAttachedClient(options) {
452
- const { runtimeSocket, clientKind = "local-api" } = options;
453
- const callTimeoutMs = options.callTimeoutMs ?? DEFAULT_CALL_TIMEOUT_MS;
454
- let released = false;
455
- let state = "disconnected";
456
- /** Internal flag: true when the reconnect loop should attempt reconnection
457
- * even though the external `state` has not been changed to `disconnected`.
458
- * This lets transient errors trigger reconnection without flashing the UI. */
459
- let needsReconnect = false;
460
- let currentClientId;
461
- let currentAuthToken;
462
- let inner = null;
463
- let currentReleaseLease = null;
464
- // --- State management + listeners ---
465
- const listeners = new Set();
466
- function setState(next) {
467
- if (next === state)
468
- return;
469
- state = next;
470
- for (const fn of listeners) {
471
- try {
472
- fn(next);
473
- }
474
- catch {
475
- /* never break the client */
476
- }
477
- }
478
- }
479
- // --- "connected" promise that per-call waiters block on ---
480
- // Resolves every time the background loop transitions to `connected`.
481
- let connectedResolve = null;
482
- let connectedPromise = new Promise((r) => {
483
- connectedResolve = r;
484
- });
485
- function signalConnected() {
486
- connectedResolve?.();
487
- // Reset for next disconnect cycle
488
- connectedPromise = new Promise((r) => {
489
- connectedResolve = r;
490
- });
491
- }
492
- // --- Logging ---
493
- const log = (event) => {
494
- if (!options.logDir)
495
- return;
496
- try {
497
- mkdirSync(options.logDir, { recursive: true });
498
- appendFileSync(pathJoin(options.logDir, "reattach.jsonl"), JSON.stringify({ ts: new Date().toISOString(), pid: process.pid, ...event }) + "\n");
499
- }
500
- catch {
501
- /* never break the client */
502
- }
503
- };
504
- // --- Lease acquisition ---
505
- async function acquireLease() {
506
- if (currentReleaseLease) {
507
- await currentReleaseLease().catch(() => { });
508
- currentReleaseLease = null;
509
- }
510
- const lease = await requestRuntimeSocketLease(runtimeSocket, LocalControlSocketAttachClientRequestSchema.parse({
511
- clientKind,
512
- lease: true,
513
- pid: process.pid,
514
- displayName: options.displayName,
515
- }));
516
- currentClientId = lease.clientId;
517
- currentAuthToken = lease.clientAuthToken;
518
- currentReleaseLease = () => lease.release();
519
- inner = createRuntimeSocketAttachedLocalControlClient({
520
- runtimeSocket,
521
- auth: { clientId: lease.clientId, clientAuthToken: lease.clientAuthToken },
522
- });
523
- log({ event: "lease_acquired", clientId: lease.clientId });
524
- }
525
- // --- Disconnect trigger ---
526
- // Event-driven: the reconnect loop sleeps on this promise instead of
527
- // polling. Resolved by triggerReconnect() so the loop wakes immediately.
528
- let disconnectResolve = null;
529
- let disconnectPromise = new Promise((r) => {
530
- disconnectResolve = r;
531
- });
532
- // Release signal: resolved by release() to unblock all pending waiters.
533
- // The .catch() suppresses the unhandled-rejection when no waiter is active.
534
- let releaseReject = null;
535
- const releasePromise = new Promise((_, reject) => {
536
- releaseReject = (err) => reject(err);
537
- });
538
- releasePromise.catch(() => { });
539
- /**
540
- * Internal: wake the background reconnect loop. Does NOT immediately
541
- * change external state — the reconnect loop decides when to surface
542
- * `disconnected` vs `reconnecting` based on whether fast-reattach
543
- * succeeds or fails. This prevents transient blips from flashing
544
- * "disconnected" in the UI.
545
- */
546
- function triggerReconnect() {
547
- disconnectResolve?.();
548
- disconnectPromise = new Promise((r) => {
549
- disconnectResolve = r;
550
- });
551
- }
552
- /**
553
- * Try to acquire a fresh lease immediately (fast-path recovery for
554
- * transient errors). Returns true if successful. On failure it wakes
555
- * the background reconnect loop but does NOT change external state —
556
- * that is the reconnect loop's responsibility. This prevents brief
557
- * transient errors from flashing "disconnected" in the UI when the
558
- * daemon is actually fine.
559
- */
560
- async function tryFastReattach() {
561
- try {
562
- await acquireLease();
563
- needsReconnect = false;
564
- log({ event: "fast_reattach_success" });
565
- return true;
566
- }
567
- catch (error) {
568
- log({ event: "fast_reattach_failed", error: error.message });
569
- // Set the internal flag so the reconnect loop knows to attempt
570
- // reconnection, then wake it. External state is NOT changed here —
571
- // the reconnect loop decides when to surface "disconnected" to the UI.
572
- needsReconnect = true;
573
- triggerReconnect();
574
- return false;
575
- }
576
- }
577
- // --- Background reconnect loop ---
578
- // Runs forever. Sleeps on disconnectPromise while connected (no polling).
579
- // When disconnected it retries with capped exponential backoff + jitter
580
- // until the daemon comes back or the client is released/aborted.
581
- async function reconnectLoop() {
582
- let backoff = INITIAL_BACKOFF_MS;
583
- while (!released && !options.signal?.aborted) {
584
- if (state === "connected" && !needsReconnect) {
585
- // Event-driven wait — no polling. Wakes only when triggerReconnect()
586
- // is called or the abort signal fires.
587
- const loopAc = new AbortController();
588
- try {
589
- await Promise.race([
590
- disconnectPromise,
591
- ...(options.signal
592
- ? [
593
- new Promise((r) => {
594
- options.signal.addEventListener("abort", () => r(), {
595
- once: true,
596
- signal: loopAc.signal,
597
- });
598
- }),
599
- ]
600
- : []),
601
- ]);
602
- }
603
- finally {
604
- loopAc.abort();
605
- }
606
- continue;
607
- }
608
- // Attempt reconnection. Don't change external state to
609
- // "reconnecting" / "disconnected" on the first attempt — give the
610
- // daemon a chance to respond before alarming the UI.
611
- const firstAttempt = state === "connected" && needsReconnect;
612
- if (!firstAttempt) {
613
- setState("reconnecting");
614
- }
615
- try {
616
- await acquireLease();
617
- backoff = INITIAL_BACKOFF_MS;
618
- needsReconnect = false;
619
- setState("connected");
620
- signalConnected();
621
- log({ event: "reconnect_success" });
622
- }
623
- catch (error) {
624
- const delay = jitter(backoff);
625
- log({
626
- event: "reconnect_failed",
627
- error: error.message,
628
- nextRetryMs: delay,
629
- });
630
- // NOW surface the disconnect — reconnect genuinely failed.
631
- needsReconnect = true;
632
- setState("disconnected");
633
- backoff = Math.min(backoff * 2, MAX_BACKOFF_MS);
634
- await backoffSleep(delay);
635
- }
636
- }
637
- }
638
- // --- Per-call waiting ---
639
- // If disconnected, wait for the background loop to reconnect (up to
640
- // callTimeoutMs). Supports AbortSignal for cancellation.
641
- // All racing promises are cleaned up after the race resolves to prevent
642
- // timer leaks and unhandled rejections.
643
- async function waitForConnection(perCallSignal) {
644
- if (state === "connected" && !needsReconnect)
645
- return;
646
- if (released)
647
- throw new Error("ResilientAttachedClient has been released");
648
- if (options.signal?.aborted)
649
- throw new Error("ResilientAttachedClient aborted");
650
- if (perCallSignal?.aborted)
651
- throw new Error("Call cancelled");
652
- // Cleanup handles so we can teardown all racing promises after the race.
653
- let timeoutTimer = null;
654
- const ac = new AbortController(); // internal signal to clean up listeners
655
- try {
656
- const racers = [connectedPromise, releasePromise];
657
- // Per-call timeout
658
- racers.push(new Promise((_, reject) => {
659
- timeoutTimer = setTimeout(() => reject(new Error("Timed out waiting for daemon reconnection")), callTimeoutMs);
660
- timeoutTimer.unref?.();
661
- }));
662
- // Client-level abort signal
663
- if (options.signal) {
664
- racers.push(new Promise((_, reject) => {
665
- const onAbort = () => reject(new Error("ResilientAttachedClient aborted"));
666
- options.signal.addEventListener("abort", onAbort, { once: true, signal: ac.signal });
667
- }));
668
- }
669
- // Per-call abort signal
670
- if (perCallSignal) {
671
- racers.push(new Promise((_, reject) => {
672
- const onAbort = () => reject(new Error("Call cancelled"));
673
- perCallSignal.addEventListener("abort", onAbort, { once: true, signal: ac.signal });
674
- }));
675
- }
676
- await Promise.race(racers);
677
- }
678
- finally {
679
- // Cleanup: cancel timeout and remove abort listeners to prevent leaks.
680
- if (timeoutTimer !== null)
681
- clearTimeout(timeoutTimer);
682
- ac.abort();
683
- }
684
- }
685
- // --- Call / stream wrappers ---
686
- async function callWithReattach(methodName, fn) {
687
- if (released)
688
- throw new Error("ResilientAttachedClient has been released");
689
- // Wait for a live connection (may resolve instantly or wait minutes)
690
- await waitForConnection();
691
- try {
692
- return await fn(inner);
693
- }
694
- catch (error) {
695
- if (!isRecoverableError(error))
696
- throw error;
697
- log({
698
- event: "call_failed_recoverable",
699
- method: methodName,
700
- error: error.message,
701
- });
702
- // Try fast reattach first — if it succeeds, no UI disruption.
703
- if (await tryFastReattach()) {
704
- // Retry with the freshly-reattached connection.
705
- return await fn(inner);
706
- }
707
- // Fast reattach failed — state is now disconnected, background loop
708
- // is running. Wait for it to reconnect then retry.
709
- await waitForConnection();
710
- return await fn(inner);
711
- }
712
- }
713
- async function* streamWithReattach(methodName, fn, resubscribable) {
714
- if (released)
715
- throw new Error("ResilientAttachedClient has been released");
716
- await waitForConnection();
717
- try {
718
- yield* fn(inner);
719
- return;
720
- }
721
- catch (error) {
722
- if (!isRecoverableError(error))
723
- throw error;
724
- log({
725
- event: "stream_failed_recoverable",
726
- method: methodName,
727
- resubscribable,
728
- error: error.message,
729
- });
730
- if (!resubscribable) {
731
- // Non-idempotent stream (e.g. streamRun) — do NOT silently restart.
732
- // Try fast reattach so the background loop is primed, then propagate
733
- // the error so the caller (run orchestrator) can handle it properly
734
- // via its own abort/cleanup/session-ledger mechanism.
735
- await tryFastReattach();
736
- throw error;
737
- }
738
- // Resubscribable stream (subscriptions) — try fast reattach first.
739
- if (await tryFastReattach()) {
740
- yield* fn(inner);
741
- return;
742
- }
743
- // Fast reattach failed — wait for background reconnect.
744
- await waitForConnection();
745
- yield* fn(inner);
746
- }
747
- }
748
- // Static set of methods that return AsyncIterable (streaming).
749
- const STREAMING_METHODS = new Set([
750
- "streamRun",
751
- "subscribeRuntimeEvents",
752
- "subscribeInbox",
753
- "subscribePeers",
754
- "subscribeDirectClientInbox",
755
- ]);
756
- // Build the proxy — every method call goes through reattach logic.
757
- // IMPORTANT: we must NOT call the inner method to probe its return type,
758
- // because that fires a real network request that's never awaited.
759
- const api = new Proxy({}, {
760
- get(_target, prop) {
761
- if (prop === "then" || prop === "catch" || prop === "finally")
762
- return undefined;
763
- // Local wrapper-only methods: NOT RPC, never forward into the daemon API.
764
- if (prop === "getConnectionState") {
765
- return () => state;
766
- }
767
- if (prop === "onConnectionStateChange") {
768
- return (listener) => {
769
- listeners.add(listener);
770
- return () => {
771
- listeners.delete(listener);
772
- };
773
- };
774
- }
775
- if (prop === "getClientId") {
776
- return () => currentClientId;
777
- }
778
- if (prop === "getClientAuthToken") {
779
- return () => currentAuthToken;
780
- }
781
- if (STREAMING_METHODS.has(prop)) {
782
- return (...args) => streamWithReattach(prop,
783
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
784
- (client) => client[prop](...args), RESUBSCRIBABLE_STREAMS.has(prop));
785
- }
786
- return (...args) =>
787
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
788
- callWithReattach(prop, (client) => client[prop](...args));
789
- },
790
- });
791
- // --- Bootstrap ---
792
- // Acquire initial lease, then start the background loop.
793
- await acquireLease();
794
- setState("connected");
795
- signalConnected();
796
- // Fire-and-forget the background reconnect loop.
797
- // It runs for the lifetime of this client.
798
- void reconnectLoop();
799
- return {
800
- api,
801
- getClientId: () => currentClientId,
802
- setSessionCredentialOverlay: (input) => api.setSessionCredentialOverlay?.(input),
803
- clearSessionCredentialOverlay: () => api.clearSessionCredentialOverlay?.(),
804
- getClientAuthToken: () => currentAuthToken,
805
- getState: () => state,
806
- onStateChange: (listener) => {
807
- listeners.add(listener);
808
- return () => {
809
- listeners.delete(listener);
810
- };
811
- },
812
- release: async () => {
813
- released = true;
814
- const releaseStack = new Error("release() caller").stack;
815
- if (currentReleaseLease) {
816
- await currentReleaseLease().catch(() => { });
817
- currentReleaseLease = null;
818
- }
819
- // Unblock any pending waitForConnection() calls.
820
- releaseReject?.(new Error("ResilientAttachedClient has been released"));
821
- setState("disconnected");
822
- log({ event: "released", clientId: currentClientId, releaseStack });
823
- },
824
- };
825
- }
826
- //# sourceMappingURL=attached-local-control-client.js.map