@corenel/harness 0.9.0 → 0.11.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 (247) hide show
  1. package/dist/approvals/decisionLog.d.ts +21 -0
  2. package/dist/approvals/decisionLog.d.ts.map +1 -0
  3. package/dist/approvals/decisionLog.js +129 -0
  4. package/dist/approvals/decisionLog.js.map +1 -0
  5. package/dist/approvals/hooks.d.ts +18 -0
  6. package/dist/approvals/hooks.d.ts.map +1 -0
  7. package/dist/approvals/hooks.js +34 -0
  8. package/dist/approvals/hooks.js.map +1 -0
  9. package/dist/approvals/index.d.ts +10 -0
  10. package/dist/approvals/index.d.ts.map +1 -0
  11. package/dist/approvals/index.js +10 -0
  12. package/dist/approvals/index.js.map +1 -0
  13. package/dist/approvals/service.d.ts +46 -0
  14. package/dist/approvals/service.d.ts.map +1 -0
  15. package/dist/approvals/service.js +181 -0
  16. package/dist/approvals/service.js.map +1 -0
  17. package/dist/approvals/settings.d.ts +20 -0
  18. package/dist/approvals/settings.d.ts.map +1 -0
  19. package/dist/approvals/settings.js +32 -0
  20. package/dist/approvals/settings.js.map +1 -0
  21. package/dist/approvals/shellTools.d.ts +10 -0
  22. package/dist/approvals/shellTools.d.ts.map +1 -0
  23. package/dist/approvals/shellTools.js +17 -0
  24. package/dist/approvals/shellTools.js.map +1 -0
  25. package/dist/approvals/standingStore.d.ts +15 -0
  26. package/dist/approvals/standingStore.d.ts.map +1 -0
  27. package/dist/approvals/standingStore.js +50 -0
  28. package/dist/approvals/standingStore.js.map +1 -0
  29. package/dist/approvals/suggest.d.ts +15 -0
  30. package/dist/approvals/suggest.d.ts.map +1 -0
  31. package/dist/approvals/suggest.js +23 -0
  32. package/dist/approvals/suggest.js.map +1 -0
  33. package/dist/approvals/summarize.d.ts +7 -0
  34. package/dist/approvals/summarize.d.ts.map +1 -0
  35. package/dist/approvals/summarize.js +70 -0
  36. package/dist/approvals/summarize.js.map +1 -0
  37. package/dist/approvals/testTextStore.d.ts +6 -0
  38. package/dist/approvals/testTextStore.d.ts.map +1 -0
  39. package/dist/approvals/testTextStore.js +11 -0
  40. package/dist/approvals/testTextStore.js.map +1 -0
  41. package/dist/approvals/textStore.d.ts +9 -0
  42. package/dist/approvals/textStore.d.ts.map +1 -0
  43. package/dist/approvals/textStore.js +2 -0
  44. package/dist/approvals/textStore.js.map +1 -0
  45. package/dist/audit/canonical.d.ts +5 -0
  46. package/dist/audit/canonical.d.ts.map +1 -0
  47. package/dist/audit/canonical.js +15 -0
  48. package/dist/audit/canonical.js.map +1 -0
  49. package/dist/audit/ids.d.ts +3 -0
  50. package/dist/audit/ids.d.ts.map +1 -0
  51. package/dist/audit/ids.js +11 -0
  52. package/dist/audit/ids.js.map +1 -0
  53. package/dist/audit/index.d.ts +7 -0
  54. package/dist/audit/index.d.ts.map +1 -0
  55. package/dist/audit/index.js +7 -0
  56. package/dist/audit/index.js.map +1 -0
  57. package/dist/audit/log.d.ts +64 -0
  58. package/dist/audit/log.d.ts.map +1 -0
  59. package/dist/audit/log.js +457 -0
  60. package/dist/audit/log.js.map +1 -0
  61. package/dist/audit/recorder.d.ts +31 -0
  62. package/dist/audit/recorder.d.ts.map +1 -0
  63. package/dist/audit/recorder.js +185 -0
  64. package/dist/audit/recorder.js.map +1 -0
  65. package/dist/audit/runAudit.d.ts +38 -0
  66. package/dist/audit/runAudit.d.ts.map +1 -0
  67. package/dist/audit/runAudit.js +99 -0
  68. package/dist/audit/runAudit.js.map +1 -0
  69. package/dist/audit/verify.d.ts +14 -0
  70. package/dist/audit/verify.d.ts.map +1 -0
  71. package/dist/audit/verify.js +47 -0
  72. package/dist/audit/verify.js.map +1 -0
  73. package/dist/conversation.d.ts +31 -0
  74. package/dist/conversation.d.ts.map +1 -1
  75. package/dist/conversation.js +33 -0
  76. package/dist/conversation.js.map +1 -1
  77. package/dist/core/hash.d.ts +2 -0
  78. package/dist/core/hash.d.ts.map +1 -0
  79. package/dist/core/hash.js +9 -0
  80. package/dist/core/hash.js.map +1 -0
  81. package/dist/core/loop.d.ts +18 -0
  82. package/dist/core/loop.d.ts.map +1 -1
  83. package/dist/core/loop.js +61 -5
  84. package/dist/core/loop.js.map +1 -1
  85. package/dist/core/spawn.d.ts +59 -4
  86. package/dist/core/spawn.d.ts.map +1 -1
  87. package/dist/core/spawn.js +83 -5
  88. package/dist/core/spawn.js.map +1 -1
  89. package/dist/core/turnVerify.d.ts +25 -0
  90. package/dist/core/turnVerify.d.ts.map +1 -0
  91. package/dist/core/turnVerify.js +177 -0
  92. package/dist/core/turnVerify.js.map +1 -0
  93. package/dist/dag/executeNode.d.ts +7 -0
  94. package/dist/dag/executeNode.d.ts.map +1 -1
  95. package/dist/dag/executeNode.js +3 -0
  96. package/dist/dag/executeNode.js.map +1 -1
  97. package/dist/guardrail/guardrail.d.ts +7 -0
  98. package/dist/guardrail/guardrail.d.ts.map +1 -1
  99. package/dist/guardrail/guardrail.js +8 -0
  100. package/dist/guardrail/guardrail.js.map +1 -1
  101. package/dist/guardrail/index.d.ts +2 -2
  102. package/dist/guardrail/index.d.ts.map +1 -1
  103. package/dist/guardrail/index.js +2 -2
  104. package/dist/guardrail/index.js.map +1 -1
  105. package/dist/guardrail/permission-service.d.ts +37 -6
  106. package/dist/guardrail/permission-service.d.ts.map +1 -1
  107. package/dist/guardrail/permission-service.js +111 -11
  108. package/dist/guardrail/permission-service.js.map +1 -1
  109. package/dist/guardrail/serialize.d.ts.map +1 -1
  110. package/dist/guardrail/serialize.js +2 -0
  111. package/dist/guardrail/serialize.js.map +1 -1
  112. package/dist/guardrail/validate.d.ts.map +1 -1
  113. package/dist/guardrail/validate.js +3 -1
  114. package/dist/guardrail/validate.js.map +1 -1
  115. package/dist/install/lockfile.d.ts +13 -5
  116. package/dist/install/lockfile.d.ts.map +1 -1
  117. package/dist/install/lockfile.js +31 -2
  118. package/dist/install/lockfile.js.map +1 -1
  119. package/dist/orchestrator/agentSearch.d.ts +15 -0
  120. package/dist/orchestrator/agentSearch.d.ts.map +1 -0
  121. package/dist/orchestrator/agentSearch.js +102 -0
  122. package/dist/orchestrator/agentSearch.js.map +1 -0
  123. package/dist/orchestrator/agentTags.d.ts +13 -0
  124. package/dist/orchestrator/agentTags.d.ts.map +1 -0
  125. package/dist/orchestrator/agentTags.js +48 -0
  126. package/dist/orchestrator/agentTags.js.map +1 -0
  127. package/dist/orchestrator/branch.d.ts +9 -0
  128. package/dist/orchestrator/branch.d.ts.map +1 -0
  129. package/dist/orchestrator/branch.js +26 -0
  130. package/dist/orchestrator/branch.js.map +1 -0
  131. package/dist/orchestrator/checks.d.ts +16 -0
  132. package/dist/orchestrator/checks.d.ts.map +1 -0
  133. package/dist/orchestrator/checks.js +66 -0
  134. package/dist/orchestrator/checks.js.map +1 -0
  135. package/dist/orchestrator/planDoc.d.ts +15 -0
  136. package/dist/orchestrator/planDoc.d.ts.map +1 -0
  137. package/dist/orchestrator/planDoc.js +59 -0
  138. package/dist/orchestrator/planDoc.js.map +1 -0
  139. package/dist/orchestrator/record.d.ts +103 -0
  140. package/dist/orchestrator/record.d.ts.map +1 -0
  141. package/dist/orchestrator/record.js +168 -0
  142. package/dist/orchestrator/record.js.map +1 -0
  143. package/dist/orchestrator/sidecarTools.d.ts +7 -0
  144. package/dist/orchestrator/sidecarTools.d.ts.map +1 -0
  145. package/dist/orchestrator/sidecarTools.js +141 -0
  146. package/dist/orchestrator/sidecarTools.js.map +1 -0
  147. package/dist/prompts/config.d.ts.map +1 -1
  148. package/dist/prompts/config.js +11 -0
  149. package/dist/prompts/config.js.map +1 -1
  150. package/dist/prompts/index.d.ts +0 -6
  151. package/dist/prompts/index.d.ts.map +1 -1
  152. package/dist/prompts/index.js +10 -17
  153. package/dist/prompts/index.js.map +1 -1
  154. package/dist/prompts/library/defaults.generated.d.ts.map +1 -1
  155. package/dist/prompts/library/defaults.generated.js +29 -12
  156. package/dist/prompts/library/defaults.generated.js.map +1 -1
  157. package/dist/redact.d.ts.map +1 -1
  158. package/dist/redact.js +1 -0
  159. package/dist/redact.js.map +1 -1
  160. package/dist/rooms/findings.d.ts +14 -0
  161. package/dist/rooms/findings.d.ts.map +1 -1
  162. package/dist/rooms/findings.js +37 -0
  163. package/dist/rooms/findings.js.map +1 -1
  164. package/dist/sessions/blobGc.d.ts.map +1 -1
  165. package/dist/sessions/blobGc.js +2 -5
  166. package/dist/sessions/blobGc.js.map +1 -1
  167. package/dist/sessions/childSessions.d.ts +12 -0
  168. package/dist/sessions/childSessions.d.ts.map +1 -0
  169. package/dist/sessions/childSessions.js +43 -0
  170. package/dist/sessions/childSessions.js.map +1 -0
  171. package/dist/sessions/fileRecallStore.d.ts.map +1 -1
  172. package/dist/sessions/fileRecallStore.js +1 -3
  173. package/dist/sessions/fileRecallStore.js.map +1 -1
  174. package/dist/sessions/fileSessionStore.d.ts +13 -8
  175. package/dist/sessions/fileSessionStore.d.ts.map +1 -1
  176. package/dist/sessions/fileSessionStore.js +53 -17
  177. package/dist/sessions/fileSessionStore.js.map +1 -1
  178. package/dist/sessions/inflight.js +0 -0
  179. package/dist/sessions/inflight.js.map +1 -1
  180. package/dist/sessions/jsonl.d.ts +9 -0
  181. package/dist/sessions/jsonl.d.ts.map +1 -1
  182. package/dist/sessions/jsonl.js +11 -0
  183. package/dist/sessions/jsonl.js.map +1 -1
  184. package/dist/sessions/testFs.d.ts.map +1 -1
  185. package/dist/sessions/testFs.js +1 -1
  186. package/dist/sessions/testFs.js.map +1 -1
  187. package/dist/sidecar/host.d.ts +178 -1
  188. package/dist/sidecar/host.d.ts.map +1 -1
  189. package/dist/sidecar/host.js +363 -3
  190. package/dist/sidecar/host.js.map +1 -1
  191. package/dist/sidecar/identity.d.ts +44 -0
  192. package/dist/sidecar/identity.d.ts.map +1 -0
  193. package/dist/sidecar/identity.js +45 -0
  194. package/dist/sidecar/identity.js.map +1 -0
  195. package/dist/sidecar/server.d.ts +273 -6
  196. package/dist/sidecar/server.d.ts.map +1 -1
  197. package/dist/sidecar/server.js +1009 -40
  198. package/dist/sidecar/server.js.map +1 -1
  199. package/dist/skillSource.js +1 -1
  200. package/dist/skillSource.js.map +1 -1
  201. package/dist/skills.d.ts +23 -7
  202. package/dist/skills.d.ts.map +1 -1
  203. package/dist/skills.js +62 -37
  204. package/dist/skills.js.map +1 -1
  205. package/dist/tools/callAgent.d.ts +29 -12
  206. package/dist/tools/callAgent.d.ts.map +1 -1
  207. package/dist/tools/callAgent.js +120 -14
  208. package/dist/tools/callAgent.js.map +1 -1
  209. package/dist/tools/crewList.d.ts +33 -0
  210. package/dist/tools/crewList.d.ts.map +1 -0
  211. package/dist/tools/crewList.js +66 -0
  212. package/dist/tools/crewList.js.map +1 -0
  213. package/dist/tools/files.d.ts.map +1 -1
  214. package/dist/tools/files.js +11 -5
  215. package/dist/tools/files.js.map +1 -1
  216. package/dist/tools/findAgent.d.ts +23 -0
  217. package/dist/tools/findAgent.d.ts.map +1 -0
  218. package/dist/tools/findAgent.js +58 -0
  219. package/dist/tools/findAgent.js.map +1 -0
  220. package/dist/tools/namespaces.d.ts.map +1 -1
  221. package/dist/tools/namespaces.js +7 -0
  222. package/dist/tools/namespaces.js.map +1 -1
  223. package/dist/tools/orchestrate.d.ts +4 -0
  224. package/dist/tools/orchestrate.d.ts.map +1 -0
  225. package/dist/tools/orchestrate.js +38 -0
  226. package/dist/tools/orchestrate.js.map +1 -0
  227. package/dist/tools/orchestration.d.ts +24 -0
  228. package/dist/tools/orchestration.d.ts.map +1 -0
  229. package/dist/tools/orchestration.js +173 -0
  230. package/dist/tools/orchestration.js.map +1 -0
  231. package/dist/tools/orchestrationSet.d.ts +6 -0
  232. package/dist/tools/orchestrationSet.d.ts.map +1 -0
  233. package/dist/tools/orchestrationSet.js +12 -0
  234. package/dist/tools/orchestrationSet.js.map +1 -0
  235. package/dist/tools/roomTools.d.ts +6 -0
  236. package/dist/tools/roomTools.d.ts.map +1 -1
  237. package/dist/tools/roomTools.js +26 -2
  238. package/dist/tools/roomTools.js.map +1 -1
  239. package/dist/tools/skillTool.d.ts +15 -0
  240. package/dist/tools/skillTool.d.ts.map +1 -0
  241. package/dist/tools/skillTool.js +39 -0
  242. package/dist/tools/skillTool.js.map +1 -0
  243. package/dist/tools/urlGuard.d.ts +7 -0
  244. package/dist/tools/urlGuard.d.ts.map +1 -1
  245. package/dist/tools/urlGuard.js +54 -18
  246. package/dist/tools/urlGuard.js.map +1 -1
  247. package/package.json +8 -3
@@ -1,4 +1,4 @@
1
- import { SIDECAR_PROTOCOL, HOST_METHODS, errMessage, } from '@corenel/protocol';
1
+ import { SIDECAR_PROTOCOL, HOST_METHODS, errMessage, RUN_TASK_FEATURE, CREW_DEPLOY_FEATURE, } from '@corenel/protocol';
2
2
  /** Decode a base64 chunk to bytes (atob is available in the browser and Node 18+). */
3
3
  function b64ToBytes(b64) {
4
4
  const bin = atob(b64);
@@ -57,6 +57,18 @@ export class SidecarHost {
57
57
  writable: true,
58
58
  value: new Map()
59
59
  });
60
+ Object.defineProperty(this, "cancelSeq", {
61
+ enumerable: true,
62
+ configurable: true,
63
+ writable: true,
64
+ value: 0
65
+ });
66
+ Object.defineProperty(this, "cancelPending", {
67
+ enumerable: true,
68
+ configurable: true,
69
+ writable: true,
70
+ value: new Map()
71
+ });
60
72
  Object.defineProperty(this, "acpSeq", {
61
73
  enumerable: true,
62
74
  configurable: true,
@@ -162,6 +174,48 @@ export class SidecarHost {
162
174
  writable: true,
163
175
  value: new Map()
164
176
  });
177
+ Object.defineProperty(this, "crewDeploySeq", {
178
+ enumerable: true,
179
+ configurable: true,
180
+ writable: true,
181
+ value: 0
182
+ });
183
+ Object.defineProperty(this, "crewDeployPending", {
184
+ enumerable: true,
185
+ configurable: true,
186
+ writable: true,
187
+ value: new Map()
188
+ });
189
+ Object.defineProperty(this, "approvalsSeq", {
190
+ enumerable: true,
191
+ configurable: true,
192
+ writable: true,
193
+ value: 0
194
+ });
195
+ Object.defineProperty(this, "approvalsPending", {
196
+ enumerable: true,
197
+ configurable: true,
198
+ writable: true,
199
+ value: new Map()
200
+ });
201
+ Object.defineProperty(this, "checksSeq", {
202
+ enumerable: true,
203
+ configurable: true,
204
+ writable: true,
205
+ value: 0
206
+ });
207
+ Object.defineProperty(this, "checksPending", {
208
+ enumerable: true,
209
+ configurable: true,
210
+ writable: true,
211
+ value: new Map()
212
+ });
213
+ Object.defineProperty(this, "releasePending", {
214
+ enumerable: true,
215
+ configurable: true,
216
+ writable: true,
217
+ value: new Map()
218
+ });
165
219
  Object.defineProperty(this, "hostsSeq", {
166
220
  enumerable: true,
167
221
  configurable: true,
@@ -281,6 +335,24 @@ export class SidecarHost {
281
335
  writable: true,
282
336
  value: []
283
337
  });
338
+ Object.defineProperty(this, "vapidKey", {
339
+ enumerable: true,
340
+ configurable: true,
341
+ writable: true,
342
+ value: null
343
+ });
344
+ Object.defineProperty(this, "pushSeq", {
345
+ enumerable: true,
346
+ configurable: true,
347
+ writable: true,
348
+ value: 0
349
+ });
350
+ Object.defineProperty(this, "pushPending", {
351
+ enumerable: true,
352
+ configurable: true,
353
+ writable: true,
354
+ value: new Map()
355
+ });
284
356
  /** The synced directory name the daemon echoed for THIS connection
285
357
  * (`welcome.dirName`), or null against an older daemon that sends none. */
286
358
  Object.defineProperty(this, "dirName", {
@@ -289,6 +361,16 @@ export class SidecarHost {
289
361
  writable: true,
290
362
  value: null
291
363
  });
364
+ /** The daemon's verdict on the identity challenge/assert exchange, from
365
+ * `welcome.identity`, runtime-validated the same way `dirName`/`info` are:
366
+ * a malformed value is dropped to undefined (=> `identityVerified()`
367
+ * reports null, "not told"), never coerced. */
368
+ Object.defineProperty(this, "identity", {
369
+ enumerable: true,
370
+ configurable: true,
371
+ writable: true,
372
+ value: void 0
373
+ });
292
374
  Object.defineProperty(this, "info", {
293
375
  enumerable: true,
294
376
  configurable: true,
@@ -342,9 +424,29 @@ export class SidecarHost {
342
424
  ...(this.opts.clientId ? { clientId: this.opts.clientId } : {}),
343
425
  ...(this.opts.clientName ? { clientName: this.opts.clientName } : {}),
344
426
  ...(this.opts.accounts?.length ? { accounts: this.opts.accounts } : {}),
427
+ ...(this.opts.wantsIdentity ? { wantsIdentity: true } : {}),
428
+ // What THIS client understands, so a daemon can tell an older app
429
+ // (whose agents sync but never fire until deployed) from this one.
430
+ capabilities: [CREW_DEPLOY_FEATURE],
345
431
  });
346
432
  });
347
433
  }
434
+ /** Answers the daemon's `identity-challenge`. `onIdentityChallenge` resolving
435
+ * a string sends it as the assertion; resolving `null`, having no handler
436
+ * at all, or THROWING all send `identity-assert` with no assertion -- every
437
+ * one of those is a considered "no identity to offer", never a reason to
438
+ * fail the handshake. The Clerk token itself never reaches this class: only
439
+ * whatever assertion the callback hands back does. */
440
+ async answerIdentityChallenge(m) {
441
+ let assertion = null;
442
+ try {
443
+ assertion = (await this.opts.onIdentityChallenge?.({ nonce: m.nonce, sidecarId: m.sidecarId })) ?? null;
444
+ }
445
+ catch {
446
+ assertion = null;
447
+ }
448
+ this.ch.send(assertion ? { t: 'identity-assert', assertion } : { t: 'identity-assert' });
449
+ }
348
450
  /** Drop the connection: unregister imported tools and fail anything in flight. */
349
451
  disconnect(reason = 'disconnected') {
350
452
  for (const name of this.imported)
@@ -359,6 +461,12 @@ export class SidecarHost {
359
461
  for (const p of this.runPending.values())
360
462
  p.reject(new Error(reason));
361
463
  this.runPending.clear();
464
+ // A cancel in flight when the connection drops did not get a considered
465
+ // answer -- resolve `unsupported` (not reject) so a Stop click during a
466
+ // drop does not hang the caller awaiting an ok/reason pair.
467
+ for (const p of this.cancelPending.values())
468
+ p({ ok: false, reason: 'unsupported' });
469
+ this.cancelPending.clear();
362
470
  for (const p of this.acpPending.values())
363
471
  p.reject(new Error(reason));
364
472
  this.acpPending.clear();
@@ -397,6 +505,18 @@ export class SidecarHost {
397
505
  for (const p of this.pausePending.values())
398
506
  p.reject(new Error(reason));
399
507
  this.pausePending.clear();
508
+ for (const p of this.crewDeployPending.values())
509
+ p.reject(new Error(reason));
510
+ this.crewDeployPending.clear();
511
+ for (const p of this.approvalsPending.values())
512
+ p.reject(new Error(reason));
513
+ this.approvalsPending.clear();
514
+ for (const p of this.checksPending.values())
515
+ p.reject(new Error(reason));
516
+ this.checksPending.clear();
517
+ for (const p of this.releasePending.values())
518
+ p.reject(new Error(reason));
519
+ this.releasePending.clear();
400
520
  for (const p of this.hostsPending.values())
401
521
  p.reject(new Error(reason));
402
522
  this.hostsPending.clear();
@@ -434,6 +554,9 @@ export class SidecarHost {
434
554
  for (const p of this.mcpAdminPending.values())
435
555
  p.reject(new Error(reason));
436
556
  this.mcpAdminPending.clear();
557
+ for (const p of this.pushPending.values())
558
+ p.reject(new Error(reason));
559
+ this.pushPending.clear();
437
560
  /* The rejections above already drop each login's waiter via the wrapper
438
561
  * mcpLogin installs. Cleared here too so that stays true if sendMcpAdmin
439
562
  * is ever the one carrying a login -- a stale waiter would fire a consent
@@ -447,12 +570,19 @@ export class SidecarHost {
447
570
  switch (m.t) {
448
571
  case 'welcome': {
449
572
  this.features = m.features ?? [];
573
+ // Runtime-validated: an untrusted/older daemon's `vapidPublicKey` is
574
+ // just a JSON-parsed `string`, and a malformed one must never be
575
+ // handed to the Push API. Anything that isn't the expected
576
+ // URL-safe-base64, 87-char shape is treated as "no key offered".
577
+ this.vapidKey = typeof m.vapidPublicKey === 'string' && /^[A-Za-z0-9_-]{87}$/.test(m.vapidPublicKey) ? m.vapidPublicKey : null;
450
578
  // Runtime-validated like `info.id`/`info.name` next door: the welcome
451
579
  // crossed the wire as `JSON.parse(...) as SidecarMsg`, so the declared
452
580
  // `string` guarantees nothing, and this value decides which
453
581
  // registrations a management surface treats as this browser's own.
454
582
  // Anything malformed is dropped to "not told", never coerced.
455
583
  this.dirName = typeof m.dirName === 'string' && m.dirName ? m.dirName : null;
584
+ this.identity = m.identity && typeof m.identity === 'object' && typeof m.identity.verified === 'boolean'
585
+ ? m.identity : undefined;
456
586
  this.info = m.info ?? {};
457
587
  for (const decl of m.tools) {
458
588
  const tool = { ...decl, run: (args) => this.invoke(decl.name, args) };
@@ -468,6 +598,10 @@ export class SidecarHost {
468
598
  this.welcome = null;
469
599
  return;
470
600
  }
601
+ case 'identity-challenge': {
602
+ void this.answerIdentityChallenge(m);
603
+ return;
604
+ }
471
605
  case 'invoke-result': {
472
606
  const p = this.invokePending.get(m.id);
473
607
  if (!p)
@@ -570,8 +704,10 @@ export class SidecarHost {
570
704
  }
571
705
  case 'run-accepted': {
572
706
  const p = this.runPending.get(m.id);
573
- if (p)
707
+ if (p) {
574
708
  p.runId = m.runId;
709
+ p.onAccepted?.(m.runId);
710
+ }
575
711
  return;
576
712
  }
577
713
  case 'event': {
@@ -594,6 +730,12 @@ export class SidecarHost {
594
730
  p.reject(new Error(m.error || 'run failed'));
595
731
  return;
596
732
  }
733
+ case 'cancel-run-result': {
734
+ const p = this.cancelPending.get(m.id);
735
+ this.cancelPending.delete(m.id);
736
+ p?.(m.ok ? { ok: true } : { ok: false, ...(m.reason ? { reason: m.reason } : {}) });
737
+ return;
738
+ }
597
739
  case 'config-result': {
598
740
  const p = this.configPending.get(m.id);
599
741
  if (!p)
@@ -616,6 +758,17 @@ export class SidecarHost {
616
758
  p.reject(new Error(m.error || 'host sync failed'));
617
759
  return;
618
760
  }
761
+ case 'push-result': {
762
+ const p = this.pushPending.get(m.id);
763
+ if (!p)
764
+ return;
765
+ this.pushPending.delete(m.id);
766
+ if (m.ok)
767
+ p.resolve();
768
+ else
769
+ p.reject(new Error(m.error));
770
+ return;
771
+ }
619
772
  case 'permission-ask': {
620
773
  const { t: _t, ...ask } = m;
621
774
  this.opts.onPermissionAsk?.(ask);
@@ -849,6 +1002,25 @@ export class SidecarHost {
849
1002
  this.opts.onRegistrations?.(m.registrations);
850
1003
  return;
851
1004
  }
1005
+ case 'approvals-suggest': {
1006
+ const { t: _t, ...offer } = m;
1007
+ this.opts.onApprovalSuggestion?.(offer);
1008
+ return;
1009
+ }
1010
+ case 'approvals-accept-result':
1011
+ case 'approvals-list-result':
1012
+ case 'approvals-revoke-result': {
1013
+ const p = this.approvalsPending.get(m.id);
1014
+ if (!p)
1015
+ return;
1016
+ this.approvalsPending.delete(m.id);
1017
+ if (!m.ok) {
1018
+ p.resolve({ ok: false, error: m.error });
1019
+ return;
1020
+ }
1021
+ p.resolve(m.t === 'approvals-accept-result' ? { ok: true } : { ok: true, state: m.state });
1022
+ return;
1023
+ }
852
1024
  case 'room-open-result': {
853
1025
  const p = this.roomOpenPending.get(m.id);
854
1026
  if (!p)
@@ -881,6 +1053,33 @@ export class SidecarHost {
881
1053
  p.resolve(m.ok ? { ok: true, pauses: m.pauses } : { ok: false, error: m.error });
882
1054
  return;
883
1055
  }
1056
+ case 'crew-deploy-result': {
1057
+ // One pending map for all six crew-* requests: they share one reply shape.
1058
+ const p = this.crewDeployPending.get(m.id);
1059
+ if (!p)
1060
+ return;
1061
+ this.crewDeployPending.delete(m.id);
1062
+ p.resolve(m.reply);
1063
+ return;
1064
+ }
1065
+ case 'checks-get-result':
1066
+ case 'checks-pin-result': {
1067
+ // One map for both: each answers with the complete state.
1068
+ const p = this.checksPending.get(m.id);
1069
+ if (!p)
1070
+ return;
1071
+ this.checksPending.delete(m.id);
1072
+ p.resolve(m.ok ? { ok: true, state: m.state } : { ok: false, error: m.error });
1073
+ return;
1074
+ }
1075
+ case 'workspace-release-result': {
1076
+ const p = this.releasePending.get(m.id);
1077
+ if (!p)
1078
+ return;
1079
+ this.releasePending.delete(m.id);
1080
+ p.resolve(m.ok ? { ok: true, released: m.released } : { ok: false, error: m.error });
1081
+ return;
1082
+ }
884
1083
  case 'room-list-result': {
885
1084
  const p = this.roomListPending.get(m.id);
886
1085
  if (!p)
@@ -1211,6 +1410,12 @@ export class SidecarHost {
1211
1410
  /** The sidecar's advertised self-description (root/platform), or {} if it sent
1212
1411
  * none. Valid only after connect() resolves. */
1213
1412
  sidecarInfo() { return this.info; }
1413
+ /** The daemon's verdict on the identity challenge/assert exchange for this
1414
+ * connection. `null` means the daemon does not verify at all (no `identity`
1415
+ * on `welcome` -- no verifier configured, or an older daemon), which is
1416
+ * distinct from `false` (the exchange ran and was rejected, or the
1417
+ * assertion never arrived). Valid only after connect() resolves. */
1418
+ identityVerified() { return this.identity ? this.identity.verified : null; }
1214
1419
  /** Fetch a (loopback) URL THROUGH the sidecar — a server-side fetch on the user's
1215
1420
  * machine, so the browser dodges CORS. Returns a streaming Response: it resolves
1216
1421
  * as soon as headers arrive (http-res) and the body streams in via http-chunk. */
@@ -1251,7 +1456,7 @@ export class SidecarHost {
1251
1456
  run(agent, input, onEvent, opts) {
1252
1457
  const id = ++this.runSeq;
1253
1458
  return new Promise((resolve, reject) => {
1254
- this.runPending.set(id, { resolve, reject, onEvent });
1459
+ this.runPending.set(id, { resolve, reject, onEvent, onAccepted: opts?.onAccepted });
1255
1460
  // `sessionId` asks for the run to happen INSIDE that session. A daemon
1256
1461
  // that cannot honour it answers run-error rather than running it on the
1257
1462
  // host, so the caller learns rather than being quietly downgraded.
@@ -1261,9 +1466,52 @@ export class SidecarHost {
1261
1466
  t: 'run', id, agent, input,
1262
1467
  ...(opts?.sessionId ? { sessionId: opts.sessionId } : {}),
1263
1468
  ...(opts?.keepTranscript ? { keepTranscript: true } : {}),
1469
+ ...(opts?.task ? { task: opts.task } : {}),
1470
+ ...(opts?.autonomy ? { autonomy: opts.autonomy } : {}),
1264
1471
  });
1265
1472
  });
1266
1473
  }
1474
+ /** WATCH a run this client did not start (a trigger-fired crew run). Rides
1475
+ * the same pending map as `run`, so `event`/`run-done`/`run-error` route here
1476
+ * unchanged; the server replays what already happened first. */
1477
+ watchRun(runId, onEvent) {
1478
+ const id = ++this.runSeq;
1479
+ let rejectDone;
1480
+ const done = new Promise((resolve, reject) => {
1481
+ rejectDone = reject;
1482
+ this.runPending.set(id, {
1483
+ resolve: (r) => resolve({ text: r.text, stopReason: r.stopReason }),
1484
+ reject,
1485
+ onEvent,
1486
+ });
1487
+ this.ch.send({ t: 'subscribe', id, runId });
1488
+ });
1489
+ return {
1490
+ done,
1491
+ // Client-side only: the server keeps the subscription until the run ends
1492
+ // or the socket drops, and events for a closed id are ignored.
1493
+ close: () => { if (this.runPending.delete(id))
1494
+ rejectDone(new Error('closed')); },
1495
+ };
1496
+ }
1497
+ /** Ask the daemon to cancel a running crew run. `ok: false, reason:
1498
+ * 'no-such-run'` means the daemon has no such run (already finished, or
1499
+ * never existed); `'unsupported'` means the daemon's runner has no `cancel`,
1500
+ * or the connection dropped before the daemon answered. */
1501
+ cancelRun(runId) {
1502
+ const id = ++this.cancelSeq;
1503
+ return new Promise((resolve) => {
1504
+ this.cancelPending.set(id, resolve);
1505
+ this.ch.send({ t: 'cancel-run', id, runId });
1506
+ });
1507
+ }
1508
+ /** True when the connected sidecar's runner supports cancelling a run. */
1509
+ supportsRunCancel() { return this.features.includes('run-cancel'); }
1510
+ /** True when the connected sidecar understands `run`'s `task`/`autonomy`
1511
+ * fields. An older sidecar without this feature would silently ignore
1512
+ * them, so callers must check this before sending either — never send and
1513
+ * hope. */
1514
+ supportsTasks() { return this.features.includes(RUN_TASK_FEATURE); }
1267
1515
  /** Start a proxied external ACP agent on the sidecar. The returned promise
1268
1516
  * settles on the handshake; `onUpdate` keeps receiving the agent's stream for
1269
1517
  * the life of the session. Permission asks do NOT come through here — they use
@@ -1339,6 +1587,30 @@ export class SidecarHost {
1339
1587
  this.ch.send({ t: 'host-sync-delete', id, path });
1340
1588
  });
1341
1589
  }
1590
+ /** The daemon's VAPID public key from `welcome`, or null when it offered
1591
+ * none (older daemon, or an offered value that failed validation). */
1592
+ vapidPublicKey() { return this.vapidKey; }
1593
+ /** True when this daemon advertises web push AND handed over a usable key. */
1594
+ supportsWebPush() { return this.features.includes('web-push') && this.vapidKey !== null; }
1595
+ /** Send a browser push subscription to the daemon to persist. Resolves on
1596
+ * `push-result { ok: true }`; rejects with the daemon's error, or on
1597
+ * disconnect while in flight. */
1598
+ pushSubscribe(subscription) {
1599
+ const id = ++this.pushSeq;
1600
+ return new Promise((resolve, reject) => {
1601
+ this.pushPending.set(id, { resolve, reject });
1602
+ this.ch.send({ t: 'push-subscribe', id, subscription });
1603
+ });
1604
+ }
1605
+ /** Tell the daemon to drop a subscription by endpoint. Same resolve/reject
1606
+ * contract as `pushSubscribe`. */
1607
+ pushUnsubscribe(endpoint) {
1608
+ const id = ++this.pushSeq;
1609
+ return new Promise((resolve, reject) => {
1610
+ this.pushPending.set(id, { resolve, reject });
1611
+ this.ch.send({ t: 'push-unsubscribe', id, endpoint });
1612
+ });
1613
+ }
1342
1614
  /** True when this daemon serves its OWN Harness Config (`config-*`). Feature-
1343
1615
  * detected from the welcome, so a client standing on a daemon too old to serve
1344
1616
  * one says the config is unreachable instead of sending a message that will be
@@ -1504,6 +1776,94 @@ export class SidecarHost {
1504
1776
  this.ch.send({ t: 'agent-pause-set', id, owner, agent, paused });
1505
1777
  });
1506
1778
  }
1779
+ /** True when the connected daemon keeps a deploy list (crew deploy per
1780
+ * machine). Feature-detected from the welcome; without it a client shows
1781
+ * the old read-only view. */
1782
+ supportsCrewDeploy() { return this.features.includes(CREW_DEPLOY_FEATURE); }
1783
+ /** One crew-* request. `ok: false` is the daemon's considered answer (a
1784
+ * refusal names its rule) and resolves; only a dropped connection rejects.
1785
+ * Against a daemon without the feature nothing is sent. */
1786
+ crewDeployRequest(send) {
1787
+ if (!this.supportsCrewDeploy())
1788
+ return Promise.resolve({ ok: false, error: 'this daemon does not keep a deploy list' });
1789
+ const id = ++this.crewDeploySeq;
1790
+ return new Promise((resolve, reject) => {
1791
+ this.crewDeployPending.set(id, { resolve, reject });
1792
+ this.ch.send(send(id));
1793
+ });
1794
+ }
1795
+ /** The machine's deploy list, statuses and audit. */
1796
+ crewDeployments() { return this.crewDeployRequest((id) => ({ t: 'crew-deployments', id })); }
1797
+ crewDeploy(ref, reason) { return this.crewDeployRequest((id) => ({ t: 'crew-deploy', id, ref, ...(reason ? { reason } : {}) })); }
1798
+ crewUndeploy(ref, reason) { return this.crewDeployRequest((id) => ({ t: 'crew-undeploy', id, ref, ...(reason ? { reason } : {}) })); }
1799
+ crewPause(ref, reason) { return this.crewDeployRequest((id) => ({ t: 'crew-pause', id, ref, reason })); }
1800
+ crewResume(ref, reason) { return this.crewDeployRequest((id) => ({ t: 'crew-resume', id, ref, ...(reason ? { reason } : {}) })); }
1801
+ /** Stop a run AND record who stopped it and why (the plain `cancelRun` records nothing). */
1802
+ crewStop(ref, runId, reason) { return this.crewDeployRequest((id) => ({ t: 'crew-stop', id, ref, runId, reason })); }
1803
+ /** True when the connected daemon keeps "This machine" standing approvals
1804
+ * (plan 3). Feature-detected from the welcome. */
1805
+ supportsApprovals() { return this.features.includes('approvals'); }
1806
+ approvalsRequest(send) {
1807
+ if (!this.supportsApprovals())
1808
+ return Promise.resolve({ ok: false, error: 'this daemon does not keep standing approvals' });
1809
+ const id = ++this.approvalsSeq;
1810
+ return new Promise((resolve, reject) => {
1811
+ this.approvalsPending.set(id, { resolve, reject });
1812
+ this.ch.send(send(id));
1813
+ });
1814
+ }
1815
+ /** Accept an offer this client received. The daemon checks that THIS
1816
+ * connection is the one it offered to; `ok: false` is its refusal (expired,
1817
+ * already used, offered to another connection), a real answer that
1818
+ * resolves. Only a dropped connection rejects. */
1819
+ async acceptApprovalSuggestion(suggestionId, choice) {
1820
+ const r = await this.approvalsRequest((id) => ({ t: 'approvals-accept', id, suggestionId, choice }));
1821
+ return r.ok ? { ok: true } : r;
1822
+ }
1823
+ /** The daemon's standing rules and its (daemon.json) approval settings. */
1824
+ async listDaemonApprovals() {
1825
+ return this.withState(await this.approvalsRequest((id) => ({ t: 'approvals-list', id })));
1826
+ }
1827
+ /** Remove one daemon standing rule; resolves with the complete state after. */
1828
+ async revokeDaemonApproval(tool) {
1829
+ return this.withState(await this.approvalsRequest((id) => ({ t: 'approvals-revoke', id, tool })));
1830
+ }
1831
+ withState(r) {
1832
+ if (!r.ok)
1833
+ return r;
1834
+ return r.state ? { ok: true, state: r.state } : { ok: false, error: 'the daemon answered without its approvals state' };
1835
+ }
1836
+ /** True when the daemon runs confirmed checks and holds the orchestrator's
1837
+ * workspace lock (it was started with --allow-shell, and its state dir is
1838
+ * not inside an approved root). */
1839
+ supportsChecks() { return this.features.includes('checks'); }
1840
+ /** The folder's checks file, whether it is confirmed, and proposals. `ok:
1841
+ * false` is the daemon's answer (no checks here, a bad path); only a dropped
1842
+ * connection rejects. */
1843
+ checksGet(root, stateDir) {
1844
+ const id = ++this.checksSeq;
1845
+ return new Promise((resolve, reject) => {
1846
+ this.checksPending.set(id, { resolve, reject });
1847
+ this.ch.send({ t: 'checks-get', id, root, stateDir });
1848
+ });
1849
+ }
1850
+ /** Write the content the user confirmed and pin its hash. */
1851
+ checksPin(root, stateDir, content) {
1852
+ const id = ++this.checksSeq;
1853
+ return new Promise((resolve, reject) => {
1854
+ this.checksPending.set(id, { resolve, reject });
1855
+ this.ch.send({ t: 'checks-pin', id, root, stateDir, content });
1856
+ });
1857
+ }
1858
+ /** Hand back a run's workspace lock. `released: false` means this run did
1859
+ * not hold it (already released, or held by another run). */
1860
+ workspaceRelease(ref) {
1861
+ const id = ++this.checksSeq;
1862
+ return new Promise((resolve, reject) => {
1863
+ this.releasePending.set(id, { resolve, reject });
1864
+ this.ch.send({ t: 'workspace-release', id, root: ref.root, stateDir: ref.stateDir, runId: ref.runId });
1865
+ });
1866
+ }
1507
1867
  /** Ask the daemon to open a room. `ok: false` here is the daemon's CONSIDERED
1508
1868
  * refusal (validation problems, or an unreachable participant) — a real answer,
1509
1869
  * not a transport failure, so it resolves rather than rejects. Only a dropped