@jini-ai/agent-runtime 0.3.9 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (200) hide show
  1. package/CHANGELOG.md +103 -0
  2. package/README.md +34 -17
  3. package/dist/acp-model-probe.d.ts +6 -4
  4. package/dist/acp-model-probe.js +3 -3
  5. package/dist/agent-protocol/acp/account-failure.d.ts +4 -2
  6. package/dist/agent-protocol/acp/account-failure.js +1 -1
  7. package/dist/agent-protocol/acp/json.d.ts +6 -6
  8. package/dist/agent-protocol/acp/json.js +3 -3
  9. package/dist/agent-protocol/acp/models.d.ts +10 -5
  10. package/dist/agent-protocol/acp/models.js +32 -31
  11. package/dist/agent-protocol/acp/session-params.d.ts +3 -1
  12. package/dist/agent-protocol/acp/session-params.js +2 -1
  13. package/dist/agent-protocol/acp/session.d.ts +28 -22
  14. package/dist/agent-protocol/acp/session.js +181 -147
  15. package/dist/agent-protocol/acp/text-suppression.d.ts +1 -1
  16. package/dist/agent-protocol/acp/text-suppression.js +1 -1
  17. package/dist/agent-protocol/acp/types.d.ts +3 -2
  18. package/dist/agent-protocol/acp/updates.d.ts +11 -11
  19. package/dist/agent-protocol/acp/updates.js +2 -2
  20. package/dist/agent-protocol/core/json-line-stream.d.ts +24 -8
  21. package/dist/agent-protocol/core/json-line-stream.js +8 -8
  22. package/dist/agent-protocol/pi-rpc/events.d.ts +5 -1
  23. package/dist/agent-protocol/pi-rpc/events.js +36 -36
  24. package/dist/agent-protocol/pi-rpc/internal.d.ts +4 -1
  25. package/dist/agent-protocol/pi-rpc/models.d.ts +3 -1
  26. package/dist/agent-protocol/pi-rpc/models.js +1 -1
  27. package/dist/agent-protocol/pi-rpc/session.d.ts +1 -1
  28. package/dist/agent-protocol/pi-rpc/session.js +33 -32
  29. package/dist/amr-model-cache.d.ts +12 -4
  30. package/dist/amr-model-cache.js +17 -21
  31. package/dist/amr-profile-resolver.d.ts +3 -1
  32. package/dist/anthropic-live-models.js +0 -0
  33. package/dist/artifact-taxonomy.d.ts +6 -2
  34. package/dist/auth.d.ts +52 -15
  35. package/dist/auth.js +36 -36
  36. package/dist/claude-code-models.js +2 -2
  37. package/dist/claude-stream.d.ts +22 -3
  38. package/dist/claude-stream.js +24 -8
  39. package/dist/copilot-stream.d.ts +7 -3
  40. package/dist/copilot-stream.js +3 -3
  41. package/dist/defs/aider.d.ts +8 -1
  42. package/dist/defs/aider.js +2 -2
  43. package/dist/defs/amp.d.ts +8 -1
  44. package/dist/defs/amp.js +2 -2
  45. package/dist/defs/amr.d.ts +22 -6
  46. package/dist/defs/amr.js +23 -23
  47. package/dist/defs/antigravity.d.ts +15 -4
  48. package/dist/defs/antigravity.js +9 -8
  49. package/dist/defs/auggie.d.ts +4 -1
  50. package/dist/defs/auggie.js +1 -1
  51. package/dist/defs/claude.d.ts +16 -2
  52. package/dist/defs/claude.js +22 -4
  53. package/dist/defs/cline.d.ts +4 -1
  54. package/dist/defs/cline.js +1 -1
  55. package/dist/defs/codebuddy.d.ts +8 -1
  56. package/dist/defs/codebuddy.js +2 -2
  57. package/dist/defs/codex.d.ts +18 -4
  58. package/dist/defs/codex.js +7 -7
  59. package/dist/defs/copilot.d.ts +8 -1
  60. package/dist/defs/copilot.js +2 -2
  61. package/dist/defs/crush.d.ts +8 -1
  62. package/dist/defs/crush.js +1 -1
  63. package/dist/defs/cursor-agent.d.ts +14 -3
  64. package/dist/defs/cursor-agent.js +5 -5
  65. package/dist/defs/deepseek.d.ts +8 -1
  66. package/dist/defs/deepseek.js +2 -2
  67. package/dist/defs/devin.d.ts +12 -2
  68. package/dist/defs/devin.js +3 -3
  69. package/dist/defs/droid.d.ts +8 -1
  70. package/dist/defs/droid.js +1 -1
  71. package/dist/defs/gemini.d.ts +8 -1
  72. package/dist/defs/gemini.js +1 -1
  73. package/dist/defs/goose.d.ts +4 -1
  74. package/dist/defs/goose.js +1 -1
  75. package/dist/defs/grok-build.d.ts +11 -2
  76. package/dist/defs/grok-build.js +3 -3
  77. package/dist/defs/hermes.d.ts +4 -1
  78. package/dist/defs/hermes.js +2 -2
  79. package/dist/defs/kilo.d.ts +4 -1
  80. package/dist/defs/kilo.js +2 -2
  81. package/dist/defs/kimi.d.ts +4 -1
  82. package/dist/defs/kimi.js +2 -2
  83. package/dist/defs/kiro.d.ts +4 -1
  84. package/dist/defs/kiro.js +2 -2
  85. package/dist/defs/mimo.d.ts +8 -1
  86. package/dist/defs/mimo.js +2 -2
  87. package/dist/defs/opencode.d.ts +9 -2
  88. package/dist/defs/opencode.js +2 -2
  89. package/dist/defs/pi.d.ts +12 -2
  90. package/dist/defs/pi.js +9 -9
  91. package/dist/defs/qoder.d.ts +8 -1
  92. package/dist/defs/qoder.js +2 -2
  93. package/dist/defs/qwen.d.ts +8 -1
  94. package/dist/defs/qwen.js +2 -2
  95. package/dist/defs/reasonix.d.ts +4 -1
  96. package/dist/defs/reasonix.js +3 -3
  97. package/dist/defs/shared.d.ts +4 -2
  98. package/dist/defs/shared.js +2 -2
  99. package/dist/defs/trae-cli.d.ts +12 -2
  100. package/dist/defs/trae-cli.js +3 -3
  101. package/dist/defs/vibe.d.ts +4 -1
  102. package/dist/defs/vibe.js +2 -2
  103. package/dist/detection.d.ts +38 -3
  104. package/dist/detection.js +77 -37
  105. package/dist/diagnostics.d.ts +14 -3
  106. package/dist/diagnostics.js +5 -5
  107. package/dist/env.d.ts +16 -4
  108. package/dist/env.js +7 -7
  109. package/dist/executables.d.ts +24 -8
  110. package/dist/executables.js +13 -13
  111. package/dist/index.d.ts +228 -4
  112. package/dist/index.js +230 -6
  113. package/dist/invocation.d.ts +6 -1
  114. package/dist/invocation.js +2 -2
  115. package/dist/json-event-stream.d.ts +7 -2
  116. package/dist/json-event-stream.js +3 -3
  117. package/dist/launch.d.ts +12 -2
  118. package/dist/launch.js +3 -3
  119. package/dist/log-file.d.ts +4 -1
  120. package/dist/log-file.js +2 -2
  121. package/dist/mcp.d.ts +6 -3
  122. package/dist/mcp.js +1 -1
  123. package/dist/metadata.d.ts +7 -3
  124. package/dist/metadata.js +1 -1
  125. package/dist/mmd-routes.d.ts +22 -6
  126. package/dist/mmd-routes.js +14 -14
  127. package/dist/model-catalog-cache.d.ts +50 -0
  128. package/dist/model-catalog-cache.js +61 -0
  129. package/dist/model-loading-state.d.ts +10 -0
  130. package/dist/model-loading-state.js +23 -0
  131. package/dist/model-registry.d.ts +28 -7
  132. package/dist/model-registry.js +11 -8
  133. package/dist/models.d.ts +31 -6
  134. package/dist/models.js +6 -6
  135. package/dist/opencode-log.d.ts +12 -4
  136. package/dist/opencode-log.js +8 -8
  137. package/dist/paths.d.ts +6 -2
  138. package/dist/paths.js +3 -3
  139. package/dist/pi-models.d.ts +3 -1
  140. package/dist/pi-models.js +2 -2
  141. package/dist/prompt-budget.d.ts +17 -4
  142. package/dist/prompt-budget.js +3 -3
  143. package/dist/prompt-file.d.ts +5 -1
  144. package/dist/prompt-file.js +1 -1
  145. package/dist/providers/aihubmix.d.ts +34 -9
  146. package/dist/providers/aihubmix.js +26 -26
  147. package/dist/providers/anthropic-messages.d.ts +77 -20
  148. package/dist/providers/anthropic-messages.js +53 -50
  149. package/dist/providers/azure-chat.d.ts +21 -7
  150. package/dist/providers/azure-chat.js +18 -16
  151. package/dist/providers/connection-guard.d.ts +45 -31
  152. package/dist/providers/connection-guard.js +40 -143
  153. package/dist/providers/connection-test.d.ts +1 -1
  154. package/dist/providers/connection-test.js +13 -30
  155. package/dist/providers/elevenlabs.d.ts +7 -2
  156. package/dist/providers/elevenlabs.js +4 -4
  157. package/dist/providers/google-messages.d.ts +44 -10
  158. package/dist/providers/google-messages.js +36 -34
  159. package/dist/providers/google-schema.d.ts +26 -0
  160. package/dist/providers/google-schema.js +253 -0
  161. package/dist/providers/google.d.ts +21 -6
  162. package/dist/providers/google.js +10 -10
  163. package/dist/providers/index.d.ts +3 -2
  164. package/dist/providers/index.js +3 -2
  165. package/dist/providers/model-catalog.d.ts +1 -1
  166. package/dist/providers/model-catalog.js +14 -27
  167. package/dist/providers/oauth-callback-server.d.ts +1 -1
  168. package/dist/providers/oauth-callback-server.js +3 -2
  169. package/dist/providers/oauth-credentials.d.ts +7 -1
  170. package/dist/providers/oauth-credentials.js +5 -5
  171. package/dist/providers/oauth-provider.d.ts +2 -2
  172. package/dist/providers/oauth-provider.js +20 -25
  173. package/dist/providers/oauth-tokens.d.ts +26 -6
  174. package/dist/providers/oauth-tokens.js +9 -9
  175. package/dist/providers/ollama-chat.d.ts +66 -31
  176. package/dist/providers/ollama-chat.js +64 -61
  177. package/dist/providers/openai-chat.d.ts +77 -22
  178. package/dist/providers/openai-chat.js +71 -67
  179. package/dist/providers/pkce.d.ts +33 -20
  180. package/dist/providers/pkce.js +75 -146
  181. package/dist/providers/sse-decode.d.ts +3 -1
  182. package/dist/providers/sse-decode.js +1 -1
  183. package/dist/providers/token-params.d.ts +16 -5
  184. package/dist/providers/token-params.js +6 -6
  185. package/dist/providers/tool-turn-types.d.ts +89 -0
  186. package/dist/providers/tool-turn-types.js +2 -0
  187. package/dist/providers/tool-turn.d.ts +10 -0
  188. package/dist/providers/tool-turn.js +252 -0
  189. package/dist/qoder-stream.d.ts +7 -3
  190. package/dist/qoder-stream.js +3 -3
  191. package/dist/registry.d.ts +6 -2
  192. package/dist/registry.js +4 -4
  193. package/dist/resolution.d.ts +5 -1
  194. package/dist/resolution.js +3 -3
  195. package/dist/role-marker-guard.d.ts +9 -5
  196. package/dist/role-marker-guard.js +5 -5
  197. package/dist/terminal-launch.d.ts +6 -1
  198. package/dist/terminal-launch.js +2 -2
  199. package/dist/types.d.ts +37 -6
  200. package/package.json +43 -4
package/dist/auth.js CHANGED
@@ -11,12 +11,12 @@
11
11
  * `antigravityQuotaGuidance`, `reasonixAuthGuidance`, `claudeAuthGuidance`)
12
12
  * and `genericAuthGuidance` took an implicit product name baked into nine
13
13
  * literal strings (e.g. "expose CURSOR_API_KEY in the <product>'s process
14
- * environment" — see `source-map.md` for the exact original text). Each
14
+ * environment" — see `archived provenance ledger` for the exact original text). Each
15
15
  * now takes an optional `hostName`
16
16
  * parameter (default `'the host application'`) so the guidance text is
17
17
  * product-neutral by default and a consumer can substitute its own name.
18
18
  * `classifyAgentAuthFailure`/`probeAgentAuthStatus` thread `hostName`
19
- * through to whichever guidance function they call. See `source-map.md`.
19
+ * through to whichever guidance function they call. See `archived provenance ledger`.
20
20
  */
21
21
  import { execAgentFile } from './invocation.js';
22
22
  const DEFAULT_HOST_NAME = 'the host application';
@@ -66,28 +66,28 @@ function claudeAuthGuidanceText(hostName) {
66
66
  function geminiAuthGuidanceText(hostName) {
67
67
  return `Gemini CLI is installed but has no usable sign-in. Google no longer serves Gemini CLI's "Login with Google" for free, Google AI Pro or Ultra accounts. Use a Gemini API key instead: run \`gemini\` in a terminal, type /auth, choose "Use Gemini API key", and make GEMINI_API_KEY available to ${hostName}'s process environment (or configure Vertex AI). Google's replacement for individual accounts is Antigravity (\`agy\`), which you can pick here instead.`;
68
68
  }
69
- export function cursorAuthGuidance(hostName = DEFAULT_HOST_NAME) {
69
+ export function cursorAuthGuidance({}, { hostName = DEFAULT_HOST_NAME } = {}) {
70
70
  return cursorAuthGuidanceText(hostName);
71
71
  }
72
- export function deepseekAuthGuidance(hostName = DEFAULT_HOST_NAME) {
72
+ export function deepseekAuthGuidance({}, { hostName = DEFAULT_HOST_NAME } = {}) {
73
73
  return deepseekAuthGuidanceText(hostName);
74
74
  }
75
- export function antigravityAuthGuidance(hostName = DEFAULT_HOST_NAME) {
75
+ export function antigravityAuthGuidance({}, { hostName = DEFAULT_HOST_NAME } = {}) {
76
76
  return antigravityAuthGuidanceText(hostName);
77
77
  }
78
78
  export function antigravityQuotaGuidance() {
79
79
  return antigravityQuotaGuidanceText();
80
80
  }
81
- export function reasonixAuthGuidance(hostName = DEFAULT_HOST_NAME) {
81
+ export function reasonixAuthGuidance({}, { hostName = DEFAULT_HOST_NAME } = {}) {
82
82
  return reasonixAuthGuidanceText(hostName);
83
83
  }
84
- export function claudeAuthGuidance(hostName = DEFAULT_HOST_NAME) {
84
+ export function claudeAuthGuidance({}, { hostName = DEFAULT_HOST_NAME } = {}) {
85
85
  return claudeAuthGuidanceText(hostName);
86
86
  }
87
- export function geminiAuthGuidance(hostName = DEFAULT_HOST_NAME) {
87
+ export function geminiAuthGuidance({}, { hostName = DEFAULT_HOST_NAME } = {}) {
88
88
  return geminiAuthGuidanceText(hostName);
89
89
  }
90
- export function isCursorAuthFailureText(text) {
90
+ export function isCursorAuthFailureText({ text }) {
91
91
  const value = String(text || '');
92
92
  if (!value.trim())
93
93
  return false;
@@ -107,7 +107,7 @@ export function isCursorAuthFailureText(text) {
107
107
  // "error getting token source: You are not logged into Antigravity"
108
108
  // Any of these is sufficient signal — match conservatively so the regex
109
109
  // doesn't fire on prose containing the word "authentication" by accident.
110
- export function isAntigravityAuthFailureText(text) {
110
+ export function isAntigravityAuthFailureText({ text }) {
111
111
  const value = String(text || '');
112
112
  if (!value.trim())
113
113
  return false;
@@ -116,7 +116,7 @@ export function isAntigravityAuthFailureText(text) {
116
116
  /not logged into antigravity/i.test(value) ||
117
117
  /accounts\.google\.com\/o\/oauth2\/auth.*antigravity/i.test(value));
118
118
  }
119
- export function isDeepSeekAuthFailureText(text) {
119
+ export function isDeepSeekAuthFailureText({ text }) {
120
120
  const value = String(text || '');
121
121
  if (!value.trim())
122
122
  return false;
@@ -126,7 +126,7 @@ export function isDeepSeekAuthFailureText(text) {
126
126
  (/DEEPSEEK_API_KEY/i.test(value) &&
127
127
  /auth|api[_ -]?key|missing|not set|required|unauthorized/i.test(value)));
128
128
  }
129
- export function isReasonixAuthFailureText(text) {
129
+ export function isReasonixAuthFailureText({ text }) {
130
130
  const value = String(text || '');
131
131
  if (!value.trim())
132
132
  return false;
@@ -135,7 +135,7 @@ export function isReasonixAuthFailureText(text) {
135
135
  (/DEEPSEEK_API_KEY/i.test(value) &&
136
136
  /auth|missing|not set|required|unauthorized|invalid/i.test(value)));
137
137
  }
138
- export function isClaudeAuthFailureText(text) {
138
+ export function isClaudeAuthFailureText({ text }) {
139
139
  const value = String(text || '');
140
140
  if (!value.trim())
141
141
  return false;
@@ -159,7 +159,7 @@ export function isClaudeAuthFailureText(text) {
159
159
  /authentication required/i.test(value) ||
160
160
  /please (?:sign|log)[ _-]?in/i.test(value));
161
161
  }
162
- export function isGeminiAuthFailureText(text) {
162
+ export function isGeminiAuthFailureText({ text }) {
163
163
  const value = String(text || '');
164
164
  if (!value.trim())
165
165
  return false;
@@ -169,36 +169,36 @@ export function isGeminiAuthFailureText(text) {
169
169
  /error authenticating/i.test(value) ||
170
170
  /api key not valid/i.test(value));
171
171
  }
172
- export function classifyAgentAuthFailure(agentId, text, hostName = DEFAULT_HOST_NAME) {
172
+ export function classifyAgentAuthFailure({ agentId, text }, { hostName = DEFAULT_HOST_NAME } = {}) {
173
173
  if (agentId === 'claude') {
174
- if (!isClaudeAuthFailureText(text))
174
+ if (!isClaudeAuthFailureText({ text: text }))
175
175
  return null;
176
- return { status: 'missing', message: claudeAuthGuidance(hostName) };
176
+ return { status: 'missing', message: claudeAuthGuidance({}, { hostName: hostName }) };
177
177
  }
178
178
  if (agentId === 'cursor-agent') {
179
- if (!isCursorAuthFailureText(text))
179
+ if (!isCursorAuthFailureText({ text: text }))
180
180
  return null;
181
- return { status: 'missing', message: cursorAuthGuidance(hostName) };
181
+ return { status: 'missing', message: cursorAuthGuidance({}, { hostName: hostName }) };
182
182
  }
183
183
  if (agentId === 'deepseek') {
184
- if (!isDeepSeekAuthFailureText(text))
184
+ if (!isDeepSeekAuthFailureText({ text: text }))
185
185
  return null;
186
- return { status: 'missing', message: deepseekAuthGuidance(hostName) };
186
+ return { status: 'missing', message: deepseekAuthGuidance({}, { hostName: hostName }) };
187
187
  }
188
188
  if (agentId === 'antigravity') {
189
- if (!isAntigravityAuthFailureText(text))
189
+ if (!isAntigravityAuthFailureText({ text: text }))
190
190
  return null;
191
- return { status: 'missing', message: antigravityAuthGuidance(hostName) };
191
+ return { status: 'missing', message: antigravityAuthGuidance({}, { hostName: hostName }) };
192
192
  }
193
193
  if (agentId === 'reasonix') {
194
- if (!isReasonixAuthFailureText(text))
194
+ if (!isReasonixAuthFailureText({ text: text }))
195
195
  return null;
196
- return { status: 'missing', message: reasonixAuthGuidance(hostName) };
196
+ return { status: 'missing', message: reasonixAuthGuidance({}, { hostName: hostName }) };
197
197
  }
198
198
  if (agentId === 'gemini') {
199
- if (!isGeminiAuthFailureText(text))
199
+ if (!isGeminiAuthFailureText({ text: text }))
200
200
  return null;
201
- return { status: 'missing', message: geminiAuthGuidance(hostName) };
201
+ return { status: 'missing', message: geminiAuthGuidance({}, { hostName: hostName }) };
202
202
  }
203
203
  return null;
204
204
  }
@@ -228,7 +228,7 @@ const AGENT_UPSTREAM_FAILURE_RE = new RegExp(`(\\b(overloaded(?:_error)?|service
228
228
  // process failure. Auth is checked before rate/upstream so a `401` is never
229
229
  // misread as a `5xx`. Pure text match — no agent-specific assumptions — so it
230
230
  // applies uniformly to any CLI agent.
231
- export function classifyAgentServiceFailure(text) {
231
+ export function classifyAgentServiceFailure({ text }) {
232
232
  const value = String(text || '');
233
233
  if (!value.trim())
234
234
  return null;
@@ -303,9 +303,9 @@ function hasProbeSatisfyingApiKey(def, env) {
303
303
  // regexes.
304
304
  function classifyProbedAuthFailure(def, text, hostName) {
305
305
  if (TAILORED_AUTH_AGENTS.has(def.id)) {
306
- return classifyAgentAuthFailure(def.id, text, hostName);
306
+ return classifyAgentAuthFailure({ agentId: def.id, text: text }, { hostName: hostName });
307
307
  }
308
- if (classifyAgentServiceFailure(text) === 'AGENT_AUTH_REQUIRED') {
308
+ if (classifyAgentServiceFailure({ text: text }) === 'AGENT_AUTH_REQUIRED') {
309
309
  return { status: 'missing', message: genericAuthGuidance(def.name || def.id, hostName) };
310
310
  }
311
311
  return null;
@@ -314,18 +314,18 @@ function classifyProbedAuthFailure(def, text, hostName) {
314
314
  // status/whoami command) and classify the result. Returns null when the
315
315
  // adapter declares no `authProbe` — those agents are never actively probed;
316
316
  // their auth status is inferred only from a real chat failure's error text.
317
- export async function probeAgentAuthStatus(def, resolvedBin, env, hostName = DEFAULT_HOST_NAME) {
317
+ export async function probeAgentAuthStatus({ def, resolvedBin, env }, { hostName = DEFAULT_HOST_NAME } = {}) {
318
318
  const probe = def.authProbe;
319
319
  if (!probe)
320
320
  return null;
321
321
  if (hasProbeSatisfyingApiKey(def, env))
322
322
  return { status: 'ok' };
323
323
  try {
324
- const { stdout, stderr } = await execAgentFile(resolvedBin, probe.args, {
325
- env: env,
326
- timeout: probe.timeoutMs ?? 5000,
327
- maxBuffer: 1024 * 1024,
328
- });
324
+ const { stdout, stderr } = await execAgentFile({ command: resolvedBin, args: probe.args }, { options: {
325
+ env: env,
326
+ timeout: probe.timeoutMs ?? 5000,
327
+ maxBuffer: 1024 * 1024,
328
+ } });
329
329
  const stdoutText = typeof stdout === 'string' ? stdout : '';
330
330
  const stderrText = typeof stderr === 'string' ? stderr : '';
331
331
  const output = `${stdoutText}\n${stderrText}`;
@@ -126,7 +126,7 @@ function initializeEntryRows(entry) {
126
126
  if (typeof raw !== 'string')
127
127
  continue;
128
128
  const id = normalizeClaudePickerModelId(raw);
129
- if (id === 'default' || sanitizeCustomModel(id) !== id)
129
+ if (id === 'default' || sanitizeCustomModel({ id: id }) !== id)
130
130
  continue;
131
131
  const isAlias = !id.startsWith('claude-');
132
132
  rows.push({ id, label: isAlias && displayName ? `${displayName} (alias)` : id });
@@ -221,7 +221,7 @@ async function discoverClaudeCodeModels(bin, env) {
221
221
  if (!cached?.ok)
222
222
  return null;
223
223
  return cached.ids
224
- .filter((id) => sanitizeCustomModel(id) === id)
224
+ .filter((id) => sanitizeCustomModel({ id: id }) === id)
225
225
  .map((id) => ({ id, label: id }));
226
226
  }
227
227
  catch {
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * Ported verbatim from OD's `apps/daemon/src/runtimes/claude-stream.ts`
5
5
  * (only the `role-marker-guard` import path changed — it now resolves
6
- * within this package instead of two directories up). See `source-map.md`.
6
+ * within this package instead of two directories up). See `archived provenance ledger`.
7
7
  *
8
8
  * Parses Claude Code's `--output-format stream-json --verbose` JSONL stream
9
9
  * (with or without `--include-partial-messages`) into a small set of
@@ -57,6 +57,8 @@ export type ClaudeStreamEvent = {
57
57
  model?: unknown;
58
58
  sessionId?: unknown;
59
59
  ttftMs?: number;
60
+ /** Init frames only: each MCP server's connection state as the CLI reported it (see {@link initMcpServers}). */
61
+ mcpServers?: McpServerStatus[];
60
62
  } | {
61
63
  type: 'text_delta' | 'thinking_delta';
62
64
  delta: string;
@@ -139,6 +141,19 @@ export declare function isUnstreamedTextBlock(block: Record<string, unknown>, al
139
141
  export declare function isUnstreamedThinkingBlock(block: Record<string, unknown>, alreadyStreamed: boolean): block is Record<string, unknown> & {
140
142
  thinking: string;
141
143
  };
144
+ /** One MCP server's state from an init frame's `mcp_servers`, e.g. `{ name: 'jini', status: 'failed' }`. */
145
+ export interface McpServerStatus {
146
+ readonly name: string;
147
+ readonly status: string;
148
+ }
149
+ /**
150
+ * The init frame's `mcp_servers`, reduced to `{name, status}`, or `undefined` when the frame has no
151
+ * such list. Kept on the event so a host can tell "its bridge did not connect" from "no bridge was
152
+ * configured": Claude Code times a dead MCP server out, reports it `failed` here, and carries on
153
+ * without its tools (the 2026-10-01 desktop incident). Entries without a string name and status are
154
+ * dropped rather than guessed at.
155
+ */
156
+ export declare function initMcpServers(obj: Record<string, unknown>): McpServerStatus[] | undefined;
142
157
  export declare function handleSystemMessage(obj: Record<string, unknown>, onEvent: EventSink): void;
143
158
  export declare function stringifyToolResult(content: unknown): string;
144
159
  export declare function handleUserMessage(obj: Record<string, unknown>, onEvent: EventSink): void;
@@ -147,8 +162,12 @@ export declare function emitAssistantErrorIfPresent(obj: Record<string, unknown>
147
162
  interface ClaudeStreamHandlerOptions {
148
163
  suppressHtmlArtifactsAfterFileWrite?: boolean;
149
164
  }
150
- export declare function createClaudeStreamHandler(onEvent: EventSink, options?: ClaudeStreamHandlerOptions): {
151
- feed: (chunk: string) => void;
165
+ export declare function createClaudeStreamHandler({ onEvent }: {
166
+ onEvent: EventSink;
167
+ }, options?: ClaudeStreamHandlerOptions): {
168
+ feed: ({ chunk }: {
169
+ chunk: string;
170
+ }) => void;
152
171
  flush: () => void;
153
172
  };
154
173
  export {};
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * Ported verbatim from OD's `apps/daemon/src/runtimes/claude-stream.ts`
5
5
  * (only the `role-marker-guard` import path changed — it now resolves
6
- * within this package instead of two directories up). See `source-map.md`.
6
+ * within this package instead of two directories up). See `archived provenance ledger`.
7
7
  *
8
8
  * Parses Claude Code's `--output-format stream-json --verbose` JSONL stream
9
9
  * (with or without `--include-partial-messages`) into a small set of
@@ -86,7 +86,7 @@ function nextGeneratedRuntimeTaskId(counter) {
86
86
  // increases, it can never equal an id already present in the map at
87
87
  // this point. Removed per the coverage skill's Phase 6.5 "dead
88
88
  // branch" classification rather than fake a test for an unreachable
89
- // loop; see source-map.md.
89
+ // loop; see archived provenance ledger.
90
90
  const id = String(counter.next);
91
91
  counter.next += 1;
92
92
  return id;
@@ -161,7 +161,7 @@ export function emitCanonicalTaskSnapshot(toolUseId, name, input, registry, onEv
161
161
  // reaching here (so the "changed" flag those returns guarded was
162
162
  // always true here). Removed the dead double-guard per the coverage
163
163
  // skill's Phase 6.5 "dead branch" classification rather than fake a
164
- // test for an unreachable condition; see source-map.md.
164
+ // test for an unreachable condition; see archived provenance ledger.
165
165
  registry.seenToolUseIds.add(toolUseId);
166
166
  onEvent({
167
167
  type: 'tool_use',
@@ -197,7 +197,7 @@ export function isFileWriteToolUse(name, input) {
197
197
  // already made. Removed the runtime early-return per the coverage
198
198
  // skill's Phase 6.5 "dead branch" classification rather than fake a
199
199
  // test with a non-record `input` neither function can ever actually
200
- // receive (see source-map.md), replaced by a one-line type assertion
200
+ // receive (see archived provenance ledger), replaced by a one-line type assertion
201
201
  // (classification 4: TS-required, no real runtime path) so the
202
202
  // parameter can stay `unknown` without disturbing the call sites.
203
203
  export function fileWriteContent(input) {
@@ -260,13 +260,29 @@ export function isUnstreamedTextBlock(block, alreadyStreamed) {
260
260
  export function isUnstreamedThinkingBlock(block, alreadyStreamed) {
261
261
  return (!alreadyStreamed && block.type === 'thinking' && typeof block.thinking === 'string' && block.thinking.length > 0);
262
262
  }
263
+ /**
264
+ * The init frame's `mcp_servers`, reduced to `{name, status}`, or `undefined` when the frame has no
265
+ * such list. Kept on the event so a host can tell "its bridge did not connect" from "no bridge was
266
+ * configured": Claude Code times a dead MCP server out, reports it `failed` here, and carries on
267
+ * without its tools (the 2026-10-01 desktop incident). Entries without a string name and status are
268
+ * dropped rather than guessed at.
269
+ */
270
+ export function initMcpServers(obj) {
271
+ if (!Array.isArray(obj.mcp_servers))
272
+ return undefined;
273
+ return obj.mcp_servers.flatMap((server) => isRecord(server) && typeof server.name === 'string' && typeof server.status === 'string'
274
+ ? [{ name: server.name, status: server.status }]
275
+ : []);
276
+ }
263
277
  export function handleSystemMessage(obj, onEvent) {
264
278
  if (obj.subtype === 'init') {
279
+ const mcpServers = initMcpServers(obj);
265
280
  onEvent({
266
281
  type: 'status',
267
282
  label: 'initializing',
268
283
  model: obj.model ?? null,
269
284
  sessionId: obj.session_id ?? null,
285
+ ...(mcpServers !== undefined ? { mcpServers } : {}),
270
286
  });
271
287
  return;
272
288
  }
@@ -324,7 +340,7 @@ export function emitAssistantErrorIfPresent(obj, content, onEvent) {
324
340
  code: obj.error,
325
341
  });
326
342
  }
327
- export function createClaudeStreamHandler(onEvent, options = {}) {
343
+ export function createClaudeStreamHandler({ onEvent }, options = {}) {
328
344
  let buffer = '';
329
345
  // Per-content-block scratch, keyed by `${messageId}:${blockIndex}`.
330
346
  const blocks = new Map();
@@ -431,7 +447,7 @@ export function createClaudeStreamHandler(onEvent, options = {}) {
431
447
  function getOrCreateRoleGuard(msgId) {
432
448
  let guard = roleGuards.get(msgId);
433
449
  if (!guard) {
434
- guard = createRoleMarkerGuard(msgId);
450
+ guard = createRoleMarkerGuard({ messageId: msgId });
435
451
  roleGuards.set(msgId, guard);
436
452
  }
437
453
  return guard;
@@ -449,7 +465,7 @@ export function createClaudeStreamHandler(onEvent, options = {}) {
449
465
  const guard = getOrCreateRoleGuard(msgId);
450
466
  if (guard.contaminated)
451
467
  return;
452
- const safe = guard.feedText(text);
468
+ const safe = guard.feedText({ text: text });
453
469
  if (safe.length > 0) {
454
470
  onEvent({ type: eventType, delta: safe });
455
471
  }
@@ -523,7 +539,7 @@ export function createClaudeStreamHandler(onEvent, options = {}) {
523
539
  }
524
540
  return startDuplicateArtifactCandidate(current, openIndex);
525
541
  }
526
- function feed(chunk) {
542
+ function feed({ chunk }) {
527
543
  buffer += chunk;
528
544
  let nl;
529
545
  while ((nl = buffer.indexOf('\n')) !== -1) {
@@ -4,7 +4,7 @@
4
4
  * Ported verbatim from OD's top-level `apps/daemon/src/copilot-stream.ts`
5
5
  * (relocated under `runtimes/` in Jini's daemon per r1's recon; grouped
6
6
  * with the other stream parsers here). Self-contained — no imports, no
7
- * product coupling. See `source-map.md`.
7
+ * product coupling. See `archived provenance ledger`.
8
8
  *
9
9
  * Parses GitHub Copilot CLI's `--output-format json` JSONL stream into the
10
10
  * same UI-friendly events that claude-stream.js emits, so the chat panel
@@ -58,8 +58,12 @@ export type CopilotStreamEvent = {
58
58
  durationMs: unknown;
59
59
  };
60
60
  type EventSink = (event: CopilotStreamEvent) => void;
61
- export declare function createCopilotStreamHandler(onEvent: EventSink): {
62
- feed: (chunk: string) => void;
61
+ export declare function createCopilotStreamHandler({ onEvent }: {
62
+ onEvent: EventSink;
63
+ }): {
64
+ feed: ({ chunk }: {
65
+ chunk: string;
66
+ }) => void;
63
67
  flush: () => void;
64
68
  };
65
69
  export {};
@@ -4,7 +4,7 @@
4
4
  * Ported verbatim from OD's top-level `apps/daemon/src/copilot-stream.ts`
5
5
  * (relocated under `runtimes/` in Jini's daemon per r1's recon; grouped
6
6
  * with the other stream parsers here). Self-contained — no imports, no
7
- * product coupling. See `source-map.md`.
7
+ * product coupling. See `archived provenance ledger`.
8
8
  *
9
9
  * Parses GitHub Copilot CLI's `--output-format json` JSONL stream into the
10
10
  * same UI-friendly events that claude-stream.js emits, so the chat panel
@@ -30,9 +30,9 @@
30
30
  function isRecord(value) {
31
31
  return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
32
32
  }
33
- export function createCopilotStreamHandler(onEvent) {
33
+ export function createCopilotStreamHandler({ onEvent }) {
34
34
  let buffer = '';
35
- function feed(chunk) {
35
+ function feed({ chunk }) {
36
36
  buffer += chunk;
37
37
  let nl;
38
38
  while ((nl = buffer.indexOf('\n')) !== -1) {
@@ -5,7 +5,14 @@ export declare const aiderAgentDef: {
5
5
  versionArgs: string[];
6
6
  fallbackModels: import("../types.js").RuntimeModelOption[];
7
7
  fallbackModelsAssertedAt: string;
8
- buildArgs: (prompt: string, _imagePaths: string[], _extra: string[] | undefined, options?: import("../types.js").RuntimeBuildOptions | undefined) => string[];
8
+ buildArgs: ({ prompt, imagePaths: _imagePaths }: {
9
+ prompt: string;
10
+ imagePaths: string[];
11
+ }, { extraAllowedDirs: _extra, options }?: {
12
+ extraAllowedDirs?: string[];
13
+ options?: import("../types.js").RuntimeBuildOptions;
14
+ runtimeContext?: import("../types.js").RuntimeContext;
15
+ } | undefined) => string[];
9
16
  maxPromptArgBytes: number;
10
17
  streamFormat: string;
11
18
  installUrl: string;
@@ -1,4 +1,4 @@
1
- /** Ported from OD's `apps/daemon/src/runtimes/defs/aider.ts` (one de-branded comment; see `source-map.md`). */
1
+ /** Ported from OD's `apps/daemon/src/runtimes/defs/aider.ts` (one de-branded comment; see `archived provenance ledger`). */
2
2
  import { DEFAULT_MODEL_OPTION } from './shared.js';
3
3
  export const aiderAgentDef = {
4
4
  id: 'aider',
@@ -49,7 +49,7 @@ export const aiderAgentDef = {
49
49
  // --no-show-model-warnings — suppresses model-compat banners
50
50
  // that would otherwise prefix every
51
51
  // run with noise
52
- buildArgs: (prompt, _imagePaths, _extra, options = {}) => {
52
+ buildArgs: ({ prompt, imagePaths: _imagePaths }, { extraAllowedDirs: _extra, options = {} } = {}) => {
53
53
  const args = [
54
54
  // See `RuntimeBuildOptions.permissionMode`'s doc: bypass is the default (unchanged
55
55
  // behavior) unless a caller explicitly opts into a restricted run. Without
@@ -5,7 +5,14 @@ export declare const ampAgentDef: {
5
5
  versionArgs: string[];
6
6
  fallbackModels: import("../types.js").RuntimeModelOption[];
7
7
  supportsCustomModel: false;
8
- buildArgs: (_prompt: string, _imagePaths: string[], _extraAllowedDirs?: string[] | undefined, options?: import("../types.js").RuntimeBuildOptions | undefined) => string[];
8
+ buildArgs: ({ prompt: _prompt, imagePaths: _imagePaths }: {
9
+ prompt: string;
10
+ imagePaths: string[];
11
+ }, { extraAllowedDirs: _extraAllowedDirs, options }?: {
12
+ extraAllowedDirs?: string[];
13
+ options?: import("../types.js").RuntimeBuildOptions;
14
+ runtimeContext?: import("../types.js").RuntimeContext;
15
+ } | undefined) => string[];
9
16
  promptViaStdin: true;
10
17
  streamFormat: string;
11
18
  };
package/dist/defs/amp.js CHANGED
@@ -1,4 +1,4 @@
1
- /** Ported verbatim from OD's `apps/daemon/src/runtimes/defs/amp.ts` (import path adjusted only). See `source-map.md`. */
1
+ /** Ported verbatim from OD's `apps/daemon/src/runtimes/defs/amp.ts` (import path adjusted only). See `archived provenance ledger`. */
2
2
  import { DEFAULT_MODEL_OPTION } from './shared.js';
3
3
  // Amp CLI (https://ampcode.com) is a full agentic coding CLI, argv-shaped
4
4
  // much like Claude Code: it has a headless execute mode and emits a
@@ -43,7 +43,7 @@ export const ampAgentDef = {
43
43
  { id: 'rush', label: 'Rush (mode)' },
44
44
  ],
45
45
  supportsCustomModel: false,
46
- buildArgs: (_prompt, _imagePaths, _extraAllowedDirs = [], options = {}) => {
46
+ buildArgs: ({ prompt: _prompt, imagePaths: _imagePaths }, { extraAllowedDirs: _extraAllowedDirs = [], options = {} } = {}) => {
47
47
  // See `RuntimeBuildOptions.permissionMode`'s doc: bypass is the default (unchanged
48
48
  // behavior) unless a caller explicitly opts into a restricted run, in which case Amp
49
49
  // blocks on tool calls that would need approval rather than auto-running them.
@@ -1,10 +1,23 @@
1
1
  import type { RuntimeModelOption } from '../types.js';
2
2
  export type VelaModelJsonSource = 'preset' | 'remote';
3
- export declare function normalizeVelaModelId(rawId: string): string | null;
4
- export declare function parseVelaModels(stdout: string): RuntimeModelOption[];
5
- export declare function parseVelaModelJson(stdout: string, expectedSource: VelaModelJsonSource): RuntimeModelOption[];
6
- export declare function fetchVelaPresetModels(resolvedBin: string, env: NodeJS.ProcessEnv): Promise<RuntimeModelOption[]>;
7
- export declare function fetchVelaRemoteModelsWithRetry(resolvedBin: string, env: NodeJS.ProcessEnv): Promise<RuntimeModelOption[]>;
3
+ export declare function normalizeVelaModelId({ rawId }: {
4
+ rawId: string;
5
+ }): string | null;
6
+ export declare function parseVelaModels({ stdout }: {
7
+ stdout: string;
8
+ }): RuntimeModelOption[];
9
+ export declare function parseVelaModelJson({ stdout, expectedSource }: {
10
+ stdout: string;
11
+ expectedSource: VelaModelJsonSource;
12
+ }): RuntimeModelOption[];
13
+ export declare function fetchVelaPresetModels({ resolvedBin, env }: {
14
+ resolvedBin: string;
15
+ env: NodeJS.ProcessEnv;
16
+ }): Promise<RuntimeModelOption[]>;
17
+ export declare function fetchVelaRemoteModelsWithRetry({ resolvedBin, env }: {
18
+ resolvedBin: string;
19
+ env: NodeJS.ProcessEnv;
20
+ }): Promise<RuntimeModelOption[]>;
8
21
  /** Live account fields parsed from `vela billing summary --format json`. */
9
22
  export interface VelaBillingSummary {
10
23
  /** Real subscription tier (e.g. "max"); absent for free accounts. */
@@ -18,7 +31,10 @@ export interface VelaBillingSummary {
18
31
  * contract rather than a separate HTTP call. Returns total available balance
19
32
  * and the real membership tier.
20
33
  */
21
- export declare function fetchVelaBillingSummary(resolvedBin: string, env: NodeJS.ProcessEnv): Promise<VelaBillingSummary>;
34
+ export declare function fetchVelaBillingSummary({ resolvedBin, env }: {
35
+ resolvedBin: string;
36
+ env: NodeJS.ProcessEnv;
37
+ }): Promise<VelaBillingSummary>;
22
38
  export declare const amrAgentDef: {
23
39
  id: string;
24
40
  name: string;
package/dist/defs/amr.js CHANGED
@@ -1,4 +1,4 @@
1
- /** Ported from OD's `apps/daemon/src/runtimes/defs/amr.ts` (two de-branded comments; see `source-map.md`). */
1
+ /** Ported from OD's `apps/daemon/src/runtimes/defs/amr.ts` (two de-branded comments; see `archived provenance ledger`). */
2
2
  import { execAgentFile } from './shared.js';
3
3
  const AMR_MODELS_TIMEOUT_MS = 10_000;
4
4
  const AMR_MODELS_RETRY_DELAYS_MS = [250, 750];
@@ -11,7 +11,7 @@ const PREFERRED_AMR_CHAT_MODEL_ORDER = [
11
11
  const PREFERRED_AMR_CHAT_MODEL_RANK = new Map(PREFERRED_AMR_CHAT_MODEL_ORDER.map((id, index) => [id, index]));
12
12
  // AMR is the vela CLI's ACP stdio mode. `vela agent run --runtime opencode`
13
13
  // starts a private OpenCode server and forwards stream-json over ACP JSON-RPC.
14
- // Required env (set on the daemon process or via Settings → CLI env):
14
+ // Required env (supplied by the host process environment):
15
15
  // VELA_RUNTIME_KEY — OpenRouter (or compatible) API key
16
16
  // VELA_LINK_URL — OpenAI-compatible endpoint, e.g. https://openrouter.ai/api/v1
17
17
  // VELA_OPENCODE_BIN — optional; absolute path to opencode when not on PATH
@@ -30,7 +30,7 @@ const PREFERRED_AMR_CHAT_MODEL_RANK = new Map(PREFERRED_AMR_CHAT_MODEL_ORDER.map
30
30
  // the link-facing slug (`deepseek-v3.2` / `glm-5.1`), so this adapter
31
31
  // normalizes those public ids at the spawn boundary until Vela exposes
32
32
  // canonical ACP ids directly.
33
- export function normalizeVelaModelId(rawId) {
33
+ export function normalizeVelaModelId({ rawId }) {
34
34
  const trimmed = rawId.trim();
35
35
  if (!trimmed)
36
36
  return null;
@@ -108,7 +108,7 @@ function isVelaChatModelId(modelId) {
108
108
  return false;
109
109
  return true;
110
110
  }
111
- export function parseVelaModels(stdout) {
111
+ export function parseVelaModels({ stdout }) {
112
112
  const seen = new Set();
113
113
  const models = [];
114
114
  for (const line of String(stdout || '').split('\n')) {
@@ -118,7 +118,7 @@ export function parseVelaModels(stdout) {
118
118
  // `trimmed` is already non-empty and has no leading whitespace (checked
119
119
  // above), so its first whitespace-split token is always non-empty too.
120
120
  const rawId = trimmed.split(/\s+/)[0];
121
- const id = normalizeVelaModelId(rawId);
121
+ const id = normalizeVelaModelId({ rawId: rawId });
122
122
  if (!id || seen.has(id) || !isVelaChatModelId(id))
123
123
  continue;
124
124
  seen.add(id);
@@ -126,7 +126,7 @@ export function parseVelaModels(stdout) {
126
126
  }
127
127
  return orderAmrChatModels(models);
128
128
  }
129
- export function parseVelaModelJson(stdout, expectedSource) {
129
+ export function parseVelaModelJson({ stdout, expectedSource }) {
130
130
  let parsed;
131
131
  try {
132
132
  parsed = JSON.parse(stdout);
@@ -193,15 +193,15 @@ function isRetriableVelaModelsError(error) {
193
193
  '504',
194
194
  ].some((pattern) => message.includes(pattern));
195
195
  }
196
- export async function fetchVelaPresetModels(resolvedBin, env) {
197
- const { stdout } = await execAgentFile(resolvedBin, ['model', 'preset', '--format', 'json'], {
198
- env,
199
- timeout: AMR_MODELS_TIMEOUT_MS,
200
- maxBuffer: 1024 * 1024,
201
- });
202
- return parseVelaModelJson(String(stdout), 'preset');
196
+ export async function fetchVelaPresetModels({ resolvedBin, env }) {
197
+ const { stdout } = await execAgentFile({ command: resolvedBin, args: ['model', 'preset', '--format', 'json'] }, { options: {
198
+ env,
199
+ timeout: AMR_MODELS_TIMEOUT_MS,
200
+ maxBuffer: 1024 * 1024,
201
+ } });
202
+ return parseVelaModelJson({ stdout: String(stdout), expectedSource: 'preset' });
203
203
  }
204
- export async function fetchVelaRemoteModelsWithRetry(resolvedBin, env) {
204
+ export async function fetchVelaRemoteModelsWithRetry({ resolvedBin, env }) {
205
205
  // The internal exhaustion check below (`attempt === AMR_MODELS_RETRY_DELAYS_MS.length`)
206
206
  // always throws on the last allowed attempt — strictly before the `for`
207
207
  // condition itself would ever end the loop — so there's no way to fall
@@ -222,16 +222,16 @@ export async function fetchVelaRemoteModelsWithRetry(resolvedBin, env) {
222
222
  // below) can never execute — v8/istanbul still instruments it because
223
223
  // it can't prove an arbitrary `for(;;)` terminates, but the real control
224
224
  // flow genuinely never reaches it. Documented here (and in
225
- // `source-map.md`'s 2026-07-22 entry) instead of forced via a test,
225
+ // `archived provenance ledger`'s 2026-07-22 entry) instead of forced via a test,
226
226
  // per this repo's no-`/* v8 ignore */` standard.
227
227
  for (let attempt = 0;; attempt += 1) {
228
228
  try {
229
- const { stdout } = await execAgentFile(resolvedBin, ['model', 'list', '--format', 'json'], {
230
- env,
231
- timeout: AMR_MODELS_TIMEOUT_MS,
232
- maxBuffer: 1024 * 1024,
233
- });
234
- return parseVelaModelJson(String(stdout), 'remote');
229
+ const { stdout } = await execAgentFile({ command: resolvedBin, args: ['model', 'list', '--format', 'json'] }, { options: {
230
+ env,
231
+ timeout: AMR_MODELS_TIMEOUT_MS,
232
+ maxBuffer: 1024 * 1024,
233
+ } });
234
+ return parseVelaModelJson({ stdout: String(stdout), expectedSource: 'remote' });
235
235
  }
236
236
  catch (error) {
237
237
  if (attempt === AMR_MODELS_RETRY_DELAYS_MS.length ||
@@ -248,8 +248,8 @@ export async function fetchVelaRemoteModelsWithRetry(resolvedBin, env) {
248
248
  * contract rather than a separate HTTP call. Returns total available balance
249
249
  * and the real membership tier.
250
250
  */
251
- export async function fetchVelaBillingSummary(resolvedBin, env) {
252
- const { stdout } = await execAgentFile(resolvedBin, ['billing', 'summary', '--format', 'json'], { env, timeout: AMR_MODELS_TIMEOUT_MS, maxBuffer: 1024 * 1024 });
251
+ export async function fetchVelaBillingSummary({ resolvedBin, env }) {
252
+ const { stdout } = await execAgentFile({ command: resolvedBin, args: ['billing', 'summary', '--format', 'json'] }, { options: { env, timeout: AMR_MODELS_TIMEOUT_MS, maxBuffer: 1024 * 1024 } });
253
253
  const data = JSON.parse(String(stdout));
254
254
  // Use `balanceUsd` — the same field the console wallet page renders as the
255
255
  // headline "余额" — so the two surfaces always agree. Fall back to the total