@corenel/harness 0.19.0 → 0.21.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 (163) hide show
  1. package/dist/chat/engine.d.ts +47 -0
  2. package/dist/chat/engine.d.ts.map +1 -0
  3. package/dist/chat/engine.js +803 -0
  4. package/dist/chat/engine.js.map +1 -0
  5. package/dist/chat/events.d.ts +5 -0
  6. package/dist/chat/events.d.ts.map +1 -0
  7. package/dist/chat/events.js +100 -0
  8. package/dist/chat/events.js.map +1 -0
  9. package/dist/chat/index.d.ts +9 -0
  10. package/dist/chat/index.d.ts.map +1 -0
  11. package/dist/chat/index.js +9 -0
  12. package/dist/chat/index.js.map +1 -0
  13. package/dist/chat/liveFold.d.ts +10 -0
  14. package/dist/chat/liveFold.d.ts.map +1 -0
  15. package/dist/chat/liveFold.js +31 -0
  16. package/dist/chat/liveFold.js.map +1 -0
  17. package/dist/chat/messages.d.ts +20 -0
  18. package/dist/chat/messages.d.ts.map +1 -0
  19. package/dist/chat/messages.js +56 -0
  20. package/dist/chat/messages.js.map +1 -0
  21. package/dist/chat/persist.d.ts +39 -0
  22. package/dist/chat/persist.d.ts.map +1 -0
  23. package/dist/chat/persist.js +107 -0
  24. package/dist/chat/persist.js.map +1 -0
  25. package/dist/chat/runWatchdog.d.ts +21 -0
  26. package/dist/chat/runWatchdog.d.ts.map +1 -0
  27. package/dist/chat/runWatchdog.js +92 -0
  28. package/dist/chat/runWatchdog.js.map +1 -0
  29. package/dist/chat/stitch.d.ts +26 -0
  30. package/dist/chat/stitch.d.ts.map +1 -0
  31. package/dist/chat/stitch.js +40 -0
  32. package/dist/chat/stitch.js.map +1 -0
  33. package/dist/chat/types.d.ts +163 -0
  34. package/dist/chat/types.d.ts.map +1 -0
  35. package/dist/chat/types.js +2 -0
  36. package/dist/chat/types.js.map +1 -0
  37. package/dist/core/loop.d.ts.map +1 -1
  38. package/dist/core/loop.js +50 -15
  39. package/dist/core/loop.js.map +1 -1
  40. package/dist/core/spawn.d.ts +17 -0
  41. package/dist/core/spawn.d.ts.map +1 -1
  42. package/dist/core/spawn.js +48 -14
  43. package/dist/core/spawn.js.map +1 -1
  44. package/dist/fileSchema.js +1 -1
  45. package/dist/guardrail/builtins.d.ts.map +1 -1
  46. package/dist/guardrail/builtins.js +11 -6
  47. package/dist/guardrail/builtins.js.map +1 -1
  48. package/dist/guardrail/glob.d.ts +1 -2
  49. package/dist/guardrail/glob.d.ts.map +1 -1
  50. package/dist/guardrail/glob.js +4 -29
  51. package/dist/guardrail/glob.js.map +1 -1
  52. package/dist/guardrail/guardrail.d.ts +8 -0
  53. package/dist/guardrail/guardrail.d.ts.map +1 -1
  54. package/dist/guardrail/guardrail.js +12 -0
  55. package/dist/guardrail/guardrail.js.map +1 -1
  56. package/dist/guardrail/index.d.ts +1 -1
  57. package/dist/guardrail/index.d.ts.map +1 -1
  58. package/dist/guardrail/index.js +1 -1
  59. package/dist/guardrail/index.js.map +1 -1
  60. package/dist/guardrail/serialize.d.ts.map +1 -1
  61. package/dist/guardrail/serialize.js +3 -0
  62. package/dist/guardrail/serialize.js.map +1 -1
  63. package/dist/guardrail/validate.d.ts.map +1 -1
  64. package/dist/guardrail/validate.js +4 -0
  65. package/dist/guardrail/validate.js.map +1 -1
  66. package/dist/install/lockfile.d.ts +25 -5
  67. package/dist/install/lockfile.d.ts.map +1 -1
  68. package/dist/install/lockfile.js +60 -4
  69. package/dist/install/lockfile.js.map +1 -1
  70. package/dist/install/nameEntries.d.ts +28 -0
  71. package/dist/install/nameEntries.d.ts.map +1 -0
  72. package/dist/install/nameEntries.js +198 -0
  73. package/dist/install/nameEntries.js.map +1 -0
  74. package/dist/install/pinRefusal.d.ts +4 -0
  75. package/dist/install/pinRefusal.d.ts.map +1 -0
  76. package/dist/install/pinRefusal.js +19 -0
  77. package/dist/install/pinRefusal.js.map +1 -0
  78. package/dist/install/requirements.d.ts +7 -0
  79. package/dist/install/requirements.d.ts.map +1 -0
  80. package/dist/install/requirements.js +23 -0
  81. package/dist/install/requirements.js.map +1 -0
  82. package/dist/orchestrator/sidecarTools.d.ts.map +1 -1
  83. package/dist/orchestrator/sidecarTools.js +8 -4
  84. package/dist/orchestrator/sidecarTools.js.map +1 -1
  85. package/dist/prompts/fitProject.d.ts +6 -0
  86. package/dist/prompts/fitProject.d.ts.map +1 -0
  87. package/dist/prompts/fitProject.js +6 -0
  88. package/dist/prompts/fitProject.js.map +1 -0
  89. package/dist/prompts/library/defaults.generated.js +7 -7
  90. package/dist/prompts/library/defaults.generated.js.map +1 -1
  91. package/dist/prompts/library/index.d.ts +2 -1
  92. package/dist/prompts/library/index.d.ts.map +1 -1
  93. package/dist/prompts/library/index.js +11 -16
  94. package/dist/prompts/library/index.js.map +1 -1
  95. package/dist/providers/egressGuard.d.ts +17 -0
  96. package/dist/providers/egressGuard.d.ts.map +1 -0
  97. package/dist/providers/egressGuard.js +26 -0
  98. package/dist/providers/egressGuard.js.map +1 -0
  99. package/dist/providers/gateway.d.ts +34 -0
  100. package/dist/providers/gateway.d.ts.map +1 -1
  101. package/dist/providers/gateway.js +43 -2
  102. package/dist/providers/gateway.js.map +1 -1
  103. package/dist/sidecar/askUserBroker.d.ts +40 -0
  104. package/dist/sidecar/askUserBroker.d.ts.map +1 -0
  105. package/dist/sidecar/askUserBroker.js +103 -0
  106. package/dist/sidecar/askUserBroker.js.map +1 -0
  107. package/dist/sidecar/fleetJwe.d.ts +25 -0
  108. package/dist/sidecar/fleetJwe.d.ts.map +1 -0
  109. package/dist/sidecar/fleetJwe.js +115 -0
  110. package/dist/sidecar/fleetJwe.js.map +1 -0
  111. package/dist/sidecar/fleetJws.d.ts +15 -0
  112. package/dist/sidecar/fleetJws.d.ts.map +1 -0
  113. package/dist/sidecar/fleetJws.js +47 -0
  114. package/dist/sidecar/fleetJws.js.map +1 -0
  115. package/dist/sidecar/host.d.ts +81 -1
  116. package/dist/sidecar/host.d.ts.map +1 -1
  117. package/dist/sidecar/host.js +227 -6
  118. package/dist/sidecar/host.js.map +1 -1
  119. package/dist/sidecar/identity.d.ts +9 -0
  120. package/dist/sidecar/identity.d.ts.map +1 -1
  121. package/dist/sidecar/memberGate.d.ts.map +1 -1
  122. package/dist/sidecar/memberGate.js +5 -0
  123. package/dist/sidecar/memberGate.js.map +1 -1
  124. package/dist/sidecar/server.d.ts +61 -10
  125. package/dist/sidecar/server.d.ts.map +1 -1
  126. package/dist/sidecar/server.js +592 -15
  127. package/dist/sidecar/server.js.map +1 -1
  128. package/dist/skills.d.ts +18 -0
  129. package/dist/skills.d.ts.map +1 -1
  130. package/dist/skills.js +93 -12
  131. package/dist/skills.js.map +1 -1
  132. package/dist/systemParts.d.ts +0 -1
  133. package/dist/systemParts.d.ts.map +1 -1
  134. package/dist/systemParts.js +6 -2
  135. package/dist/systemParts.js.map +1 -1
  136. package/dist/todos.d.ts +8 -0
  137. package/dist/todos.d.ts.map +1 -1
  138. package/dist/todos.js +21 -17
  139. package/dist/todos.js.map +1 -1
  140. package/dist/tools/builtins.d.ts +8 -0
  141. package/dist/tools/builtins.d.ts.map +1 -1
  142. package/dist/tools/builtins.js +21 -1
  143. package/dist/tools/builtins.js.map +1 -1
  144. package/dist/tools/namespaces.d.ts +8 -0
  145. package/dist/tools/namespaces.d.ts.map +1 -1
  146. package/dist/tools/namespaces.js +18 -0
  147. package/dist/tools/namespaces.js.map +1 -1
  148. package/dist/tools/skillTool.d.ts.map +1 -1
  149. package/dist/tools/skillTool.js +22 -1
  150. package/dist/tools/skillTool.js.map +1 -1
  151. package/dist/tools/toolFacts.d.ts +9 -0
  152. package/dist/tools/toolFacts.d.ts.map +1 -0
  153. package/dist/tools/toolFacts.js +19 -0
  154. package/dist/tools/toolFacts.js.map +1 -0
  155. package/dist/tools/toolSearch.d.ts +25 -6
  156. package/dist/tools/toolSearch.d.ts.map +1 -1
  157. package/dist/tools/toolSearch.js +62 -14
  158. package/dist/tools/toolSearch.js.map +1 -1
  159. package/dist/tools/workflowTool.d.ts +34 -0
  160. package/dist/tools/workflowTool.d.ts.map +1 -0
  161. package/dist/tools/workflowTool.js +122 -0
  162. package/dist/tools/workflowTool.js.map +1 -0
  163. package/package.json +8 -4
@@ -1,6 +1,7 @@
1
+ import { CHAT_FEATURE, CHAT_ID_RE } from '@corenel/protocol';
1
2
  import { asEcPublicJwk, jwkThumbprint, verifySignature, randomToken, serverProofPayload, clientProofPayload, parseEnrollmentCode, bytesToB64url, codeProof, codeHash, } from './deviceKeys.js';
2
3
  import { capabilityMissingMessage } from './hostCapability.js';
3
- import { SIDECAR_PROTOCOL, HOST_METHODS, errMessage, RUN_TASK_FEATURE, RUN_PLAN_FEATURE, CREW_DEPLOY_FEATURE, DEVICES_FEATURE, AGENT_ACCESS_FEATURE, AGENT_RUNS_FEATURE, PEER_LINKS_FEATURE, CHILD_EVENTS_FEATURE, PEER_DAEMON_FEATURE, TEAM_MEMBERS_FEATURE, SPEECH_FEATURE, encodePcm16, isClerkOrgId, WORKFLOW_RUNNER_FEATURE, readWorkflowOkReply, readWorkflowResultsListReply, readWorkflowResultsTakeReply, } from '@corenel/protocol';
4
+ import { SIDECAR_PROTOCOL, HOST_METHODS, errMessage, RUN_TASK_FEATURE, RUN_PLAN_FEATURE, CREW_DEPLOY_FEATURE, DEVICES_FEATURE, AGENT_ACCESS_FEATURE, AGENT_RUNS_FEATURE, PEER_LINKS_FEATURE, CHILD_EVENTS_FEATURE, PEER_DAEMON_FEATURE, TEAM_MEMBERS_FEATURE, SPEECH_FEATURE, encodePcm16, FLEET_LOGINS_DENIED, SIGN_IN_PAUSED_DENIED, isClerkOrgId, WORKFLOW_RUNNER_FEATURE, readWorkflowOkReply, readWorkflowResultsListReply, readWorkflowResultsTakeReply, } from '@corenel/protocol';
4
5
  const NO_WORKFLOWS = 'this daemon does not run workflows';
5
6
  const WRONG_REPLY = 'the daemon answered with the wrong reply';
6
7
  /** A reply that failed its check (protocol workflowWire.ts readers). */
@@ -69,6 +70,19 @@ export function isSidecarAccountRefusal(e) {
69
70
  const msg = e instanceof Error ? e.message : String(e);
70
71
  return msg.startsWith(SIDECAR_NOT_ADMITTED);
71
72
  }
73
+ /** The sidecar refused this ACCOUNT because its fleet policy limits sign-in
74
+ * (slice 4). The device is fine and stays paired; retrying changes nothing
75
+ * until a fleet admin changes the policy. */
76
+ export function isFleetLoginsRefusal(e) {
77
+ const msg = e instanceof Error ? e.message : String(e);
78
+ return msg === `${SIDECAR_NOT_ADMITTED}: ${FLEET_LOGINS_DENIED}`;
79
+ }
80
+ /** The sidecar's fleet sign-in is paused for a reason that clears by itself (SIGN_IN_PAUSED_DENIED). The device is
81
+ * fine and stays paired, and unlike every other account refusal a retry is expected to succeed later. */
82
+ export function isSignInPausedRefusal(e) {
83
+ const msg = e instanceof Error ? e.message : String(e);
84
+ return msg === `${SIDECAR_NOT_ADMITTED}: ${SIGN_IN_PAUSED_DENIED}`;
85
+ }
72
86
  export class SidecarHost {
73
87
  constructor(opts) {
74
88
  Object.defineProperty(this, "ch", {
@@ -131,6 +145,51 @@ export class SidecarHost {
131
145
  writable: true,
132
146
  value: new Map()
133
147
  });
148
+ /** Daemon chat (Harness Chat Plan 2): run-inject, chat-list and chat-get replies, by request id. */
149
+ Object.defineProperty(this, "chatSeq", {
150
+ enumerable: true,
151
+ configurable: true,
152
+ writable: true,
153
+ value: 0
154
+ });
155
+ Object.defineProperty(this, "injectPending", {
156
+ enumerable: true,
157
+ configurable: true,
158
+ writable: true,
159
+ value: new Map()
160
+ });
161
+ Object.defineProperty(this, "chatListPending", {
162
+ enumerable: true,
163
+ configurable: true,
164
+ writable: true,
165
+ value: new Map()
166
+ });
167
+ Object.defineProperty(this, "chatGetPending", {
168
+ enumerable: true,
169
+ configurable: true,
170
+ writable: true,
171
+ value: new Map()
172
+ });
173
+ /** Chat ask_user prompts not yet answered or resolved, by the daemon's ask id,
174
+ * so a handler registered after one arrived still sees it. */
175
+ Object.defineProperty(this, "askUserOpen", {
176
+ enumerable: true,
177
+ configurable: true,
178
+ writable: true,
179
+ value: new Map()
180
+ });
181
+ Object.defineProperty(this, "askUserHandlers", {
182
+ enumerable: true,
183
+ configurable: true,
184
+ writable: true,
185
+ value: new Set()
186
+ });
187
+ Object.defineProperty(this, "askUserResolvedHandlers", {
188
+ enumerable: true,
189
+ configurable: true,
190
+ writable: true,
191
+ value: new Set()
192
+ });
134
193
  Object.defineProperty(this, "acpSeq", {
135
194
  enumerable: true,
136
195
  configurable: true,
@@ -854,6 +913,18 @@ export class SidecarHost {
854
913
  for (const p of this.cancelPending.values())
855
914
  p({ ok: false, reason: 'unsupported' });
856
915
  this.cancelPending.clear();
916
+ // Same reasoning for an inject: answered, not thrown, so a send during a drop settles.
917
+ for (const p of this.injectPending.values())
918
+ p({ ok: false, reason });
919
+ this.injectPending.clear();
920
+ for (const p of this.chatListPending.values())
921
+ p.reject(new Error(reason));
922
+ this.chatListPending.clear();
923
+ for (const p of this.chatGetPending.values())
924
+ p.reject(new Error(reason));
925
+ this.chatGetPending.clear();
926
+ // The daemon delivers any still-open ask again when the connection comes back.
927
+ this.askUserOpen.clear();
857
928
  for (const p of this.acpPending.values())
858
929
  p.reject(new Error(reason));
859
930
  this.acpPending.clear();
@@ -1152,10 +1223,14 @@ export class SidecarHost {
1152
1223
  }
1153
1224
  case 'run-accepted': {
1154
1225
  const p = this.runPending.get(m.id);
1155
- if (p) {
1156
- p.runId = m.runId;
1226
+ if (!p)
1227
+ return;
1228
+ p.runId = m.runId;
1229
+ // Runtime-validated: a conversation id is only ever a chat id.
1230
+ if (typeof m.conversationId === 'string' && CHAT_ID_RE.test(m.conversationId))
1231
+ p.onAccepted?.(m.runId, m.conversationId);
1232
+ else
1157
1233
  p.onAccepted?.(m.runId);
1158
- }
1159
1234
  return;
1160
1235
  }
1161
1236
  case 'event': {
@@ -1171,7 +1246,10 @@ export class SidecarHost {
1171
1246
  if (!p)
1172
1247
  return;
1173
1248
  this.runPending.delete(m.id);
1174
- p.resolve({ runId: m.runId, text: m.text, stopReason: m.stopReason });
1249
+ p.resolve({
1250
+ runId: m.runId, text: m.text, stopReason: m.stopReason,
1251
+ ...(m.outcome === 'done' || m.outcome === 'stopped' ? { outcome: m.outcome } : {}),
1252
+ });
1175
1253
  return;
1176
1254
  }
1177
1255
  case 'run-error': {
@@ -1179,7 +1257,59 @@ export class SidecarHost {
1179
1257
  if (!p)
1180
1258
  return;
1181
1259
  this.runPending.delete(m.id);
1182
- p.reject(new Error(m.error || 'run failed'));
1260
+ if (p.fail)
1261
+ p.fail(m.error || 'run failed', m.outcome === 'failed' || m.outcome === 'refused' ? m.outcome : undefined);
1262
+ else
1263
+ p.reject(new Error(m.error || 'run failed'));
1264
+ return;
1265
+ }
1266
+ case 'run-inject-result': {
1267
+ const p = this.injectPending.get(m.id);
1268
+ if (!p)
1269
+ return;
1270
+ this.injectPending.delete(m.id);
1271
+ p(m.ok === true ? { ok: true } : { ok: false, ...(typeof m.reason === 'string' ? { reason: m.reason } : {}) });
1272
+ return;
1273
+ }
1274
+ case 'chat-list-result': {
1275
+ const p = this.chatListPending.get(m.id);
1276
+ if (!p)
1277
+ return;
1278
+ this.chatListPending.delete(m.id);
1279
+ if (m.ok)
1280
+ p.resolve(Array.isArray(m.chats) ? m.chats : []);
1281
+ else
1282
+ p.reject(new Error(m.error || 'could not list chats'));
1283
+ return;
1284
+ }
1285
+ case 'chat-get-result': {
1286
+ const p = this.chatGetPending.get(m.id);
1287
+ if (!p)
1288
+ return;
1289
+ this.chatGetPending.delete(m.id);
1290
+ if (m.ok)
1291
+ p.resolve(m.conversation);
1292
+ else
1293
+ p.reject(new Error(m.error || 'could not read the chat'));
1294
+ return;
1295
+ }
1296
+ case 'ask-user': {
1297
+ // Runtime-validated like the rest of what crosses the wire.
1298
+ if (!Number.isSafeInteger(m.id) || typeof m.runId !== 'string' || typeof m.agent !== 'string'
1299
+ || !Array.isArray(m.questions) || typeof m.deadline !== 'number')
1300
+ return;
1301
+ const ask = { id: m.id, runId: m.runId, agent: m.agent, questions: m.questions, deadline: m.deadline };
1302
+ this.askUserOpen.set(ask.id, ask);
1303
+ for (const h of [...this.askUserHandlers])
1304
+ h(ask);
1305
+ return;
1306
+ }
1307
+ case 'ask-user-resolved': {
1308
+ if (!this.askUserOpen.delete(m.id))
1309
+ return;
1310
+ const by = m.by === 'client' ? 'client' : 'timeout';
1311
+ for (const h of [...this.askUserResolvedHandlers])
1312
+ h({ id: m.id, by });
1183
1313
  return;
1184
1314
  }
1185
1315
  case 'cancel-run-result': {
@@ -2096,6 +2226,97 @@ export class SidecarHost {
2096
2226
  this.ch.send({ t: 'cancel-run', id, runId });
2097
2227
  });
2098
2228
  }
2229
+ /** True when the connected daemon holds chats (Harness Chat Plan 2):
2230
+ * `runChat`, `injectRun`, `listChats`, `getChat` and ask_user prompts. An
2231
+ * older daemon would run a chat turn as a plain one-shot run, so check this
2232
+ * before calling any of them. */
2233
+ supportsChat() { return this.features.includes(CHAT_FEATURE); }
2234
+ /** One turn of a daemon chat. `chat.conversationId` absent starts a new
2235
+ * chat; the daemon mints its id and hands it to `onAccepted` with the run
2236
+ * id. Resolves with how the turn ended, a refused or failed one included
2237
+ * (with `error`); rejects only when the connection drops mid-turn. */
2238
+ runChat(agent, text, chat, onEvent, opts) {
2239
+ const id = ++this.runSeq;
2240
+ return new Promise((resolve, reject) => {
2241
+ let runId = null;
2242
+ let conversationId = chat.conversationId ?? null;
2243
+ this.runPending.set(id, {
2244
+ resolve: (r) => resolve({ runId: r.runId, conversationId, text: r.text, stopReason: r.stopReason, outcome: r.outcome ?? 'done' }),
2245
+ reject,
2246
+ fail: (error, outcome) => resolve({ runId, conversationId, text: '', stopReason: '', outcome: outcome ?? 'failed', error }),
2247
+ onEvent,
2248
+ onAccepted: (rid, cid) => {
2249
+ runId = rid;
2250
+ if (cid !== undefined)
2251
+ conversationId = cid;
2252
+ if (conversationId !== null)
2253
+ opts?.onAccepted?.(rid, conversationId);
2254
+ },
2255
+ });
2256
+ this.ch.send({
2257
+ t: 'run', id, agent, input: text,
2258
+ chat: {
2259
+ ...(chat.conversationId !== undefined ? { conversationId: chat.conversationId } : {}),
2260
+ ...(chat.maxUsd !== undefined ? { maxUsd: chat.maxUsd } : {}),
2261
+ ...(chat.ref !== undefined ? { ref: chat.ref } : {}),
2262
+ },
2263
+ });
2264
+ });
2265
+ }
2266
+ /** Messages typed while a chat turn runs; `hard` interrupts it. Never
2267
+ * rejects: a refusal or a dropped connection answers `ok: false`. */
2268
+ injectRun(runId, messages, hard) {
2269
+ const id = ++this.chatSeq;
2270
+ return new Promise((resolve) => {
2271
+ this.injectPending.set(id, resolve);
2272
+ this.ch.send({ t: 'run-inject', id, runId, messages, hard });
2273
+ });
2274
+ }
2275
+ /** This user's chats with `agent`; the admin may name a `member`. Rejects
2276
+ * with the daemon's refusal. */
2277
+ listChats(agent, opts) {
2278
+ const id = ++this.chatSeq;
2279
+ return new Promise((resolve, reject) => {
2280
+ this.chatListPending.set(id, { resolve, reject });
2281
+ this.ch.send({
2282
+ t: 'chat-list', id, agent,
2283
+ ...(opts?.ref !== undefined ? { ref: opts.ref } : {}),
2284
+ ...(opts?.member !== undefined ? { member: opts.member } : {}),
2285
+ });
2286
+ });
2287
+ }
2288
+ /** One conversation, to its member or the admin. Rejects with the daemon's
2289
+ * refusal (CHAT_NOT_YOURS, CHAT_NOT_FOUND). */
2290
+ getChat(agent, conversationId, opts) {
2291
+ const id = ++this.chatSeq;
2292
+ return new Promise((resolve, reject) => {
2293
+ this.chatGetPending.set(id, { resolve, reject });
2294
+ this.ch.send({
2295
+ t: 'chat-get', id, agent, conversationId,
2296
+ ...(opts?.ref !== undefined ? { ref: opts.ref } : {}),
2297
+ ...(opts?.member !== undefined ? { member: opts.member } : {}),
2298
+ });
2299
+ });
2300
+ }
2301
+ /** Chat ask_user prompts for this user. Prompts still open when the handler
2302
+ * is added are handed to it at once. Returns the unsubscribe function. */
2303
+ onAskUser(handler) {
2304
+ this.askUserHandlers.add(handler);
2305
+ for (const ask of [...this.askUserOpen.values()])
2306
+ handler(ask);
2307
+ return () => { this.askUserHandlers.delete(handler); };
2308
+ }
2309
+ /** A prompt was answered on another connection, or timed out: drop it. */
2310
+ onAskUserResolved(handler) {
2311
+ this.askUserResolvedHandlers.add(handler);
2312
+ return () => { this.askUserResolvedHandlers.delete(handler); };
2313
+ }
2314
+ /** Answer prompt `id`; null declines. The first answer from any of this
2315
+ * user's or the admin's connections wins; a late one is ignored. */
2316
+ answerAskUser(id, answers) {
2317
+ this.askUserOpen.delete(id);
2318
+ this.ch.send({ t: 'ask-user-answer', id, answers });
2319
+ }
2099
2320
  /** True when the connected sidecar's runner supports cancelling a run. */
2100
2321
  supportsRunCancel() { return this.features.includes('run-cancel'); }
2101
2322
  /** True when the connected daemon fans a delegated callee's own node events