@corenel/harness 0.10.0 → 0.12.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 (251) 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 +67 -6
  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 +41 -6
  106. package/dist/guardrail/permission-service.d.ts.map +1 -1
  107. package/dist/guardrail/permission-service.js +118 -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 +112 -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 +41 -0
  136. package/dist/orchestrator/planDoc.d.ts.map +1 -0
  137. package/dist/orchestrator/planDoc.js +181 -0
  138. package/dist/orchestrator/planDoc.js.map +1 -0
  139. package/dist/orchestrator/record.d.ts +110 -0
  140. package/dist/orchestrator/record.d.ts.map +1 -0
  141. package/dist/orchestrator/record.js +221 -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/init.d.ts +12 -0
  155. package/dist/prompts/init.d.ts.map +1 -0
  156. package/dist/prompts/init.js +48 -0
  157. package/dist/prompts/init.js.map +1 -0
  158. package/dist/prompts/library/defaults.generated.d.ts.map +1 -1
  159. package/dist/prompts/library/defaults.generated.js +29 -12
  160. package/dist/prompts/library/defaults.generated.js.map +1 -1
  161. package/dist/redact.d.ts.map +1 -1
  162. package/dist/redact.js +1 -0
  163. package/dist/redact.js.map +1 -1
  164. package/dist/rooms/findings.d.ts +14 -0
  165. package/dist/rooms/findings.d.ts.map +1 -1
  166. package/dist/rooms/findings.js +37 -0
  167. package/dist/rooms/findings.js.map +1 -1
  168. package/dist/sessions/blobGc.d.ts.map +1 -1
  169. package/dist/sessions/blobGc.js +2 -5
  170. package/dist/sessions/blobGc.js.map +1 -1
  171. package/dist/sessions/childSessions.d.ts +12 -0
  172. package/dist/sessions/childSessions.d.ts.map +1 -0
  173. package/dist/sessions/childSessions.js +43 -0
  174. package/dist/sessions/childSessions.js.map +1 -0
  175. package/dist/sessions/fileRecallStore.d.ts.map +1 -1
  176. package/dist/sessions/fileRecallStore.js +1 -3
  177. package/dist/sessions/fileRecallStore.js.map +1 -1
  178. package/dist/sessions/fileSessionStore.d.ts +11 -7
  179. package/dist/sessions/fileSessionStore.d.ts.map +1 -1
  180. package/dist/sessions/fileSessionStore.js +27 -16
  181. package/dist/sessions/fileSessionStore.js.map +1 -1
  182. package/dist/sessions/inflight.js +0 -0
  183. package/dist/sessions/inflight.js.map +1 -1
  184. package/dist/sessions/jsonl.d.ts +4 -0
  185. package/dist/sessions/jsonl.d.ts.map +1 -1
  186. package/dist/sessions/jsonl.js +1 -0
  187. package/dist/sessions/jsonl.js.map +1 -1
  188. package/dist/sessions/testFs.d.ts.map +1 -1
  189. package/dist/sessions/testFs.js +1 -1
  190. package/dist/sessions/testFs.js.map +1 -1
  191. package/dist/sidecar/host.d.ts +194 -3
  192. package/dist/sidecar/host.d.ts.map +1 -1
  193. package/dist/sidecar/host.js +401 -6
  194. package/dist/sidecar/host.js.map +1 -1
  195. package/dist/sidecar/identity.d.ts +44 -0
  196. package/dist/sidecar/identity.d.ts.map +1 -0
  197. package/dist/sidecar/identity.js +45 -0
  198. package/dist/sidecar/identity.js.map +1 -0
  199. package/dist/sidecar/server.d.ts +250 -4
  200. package/dist/sidecar/server.d.ts.map +1 -1
  201. package/dist/sidecar/server.js +866 -46
  202. package/dist/sidecar/server.js.map +1 -1
  203. package/dist/skillSource.js +1 -1
  204. package/dist/skillSource.js.map +1 -1
  205. package/dist/skills.d.ts +23 -7
  206. package/dist/skills.d.ts.map +1 -1
  207. package/dist/skills.js +62 -37
  208. package/dist/skills.js.map +1 -1
  209. package/dist/tools/callAgent.d.ts +29 -12
  210. package/dist/tools/callAgent.d.ts.map +1 -1
  211. package/dist/tools/callAgent.js +120 -14
  212. package/dist/tools/callAgent.js.map +1 -1
  213. package/dist/tools/crewList.d.ts +33 -0
  214. package/dist/tools/crewList.d.ts.map +1 -0
  215. package/dist/tools/crewList.js +66 -0
  216. package/dist/tools/crewList.js.map +1 -0
  217. package/dist/tools/files.d.ts.map +1 -1
  218. package/dist/tools/files.js +11 -5
  219. package/dist/tools/files.js.map +1 -1
  220. package/dist/tools/findAgent.d.ts +23 -0
  221. package/dist/tools/findAgent.d.ts.map +1 -0
  222. package/dist/tools/findAgent.js +58 -0
  223. package/dist/tools/findAgent.js.map +1 -0
  224. package/dist/tools/namespaces.d.ts.map +1 -1
  225. package/dist/tools/namespaces.js +7 -0
  226. package/dist/tools/namespaces.js.map +1 -1
  227. package/dist/tools/orchestrate.d.ts +4 -0
  228. package/dist/tools/orchestrate.d.ts.map +1 -0
  229. package/dist/tools/orchestrate.js +38 -0
  230. package/dist/tools/orchestrate.js.map +1 -0
  231. package/dist/tools/orchestration.d.ts +24 -0
  232. package/dist/tools/orchestration.d.ts.map +1 -0
  233. package/dist/tools/orchestration.js +173 -0
  234. package/dist/tools/orchestration.js.map +1 -0
  235. package/dist/tools/orchestrationSet.d.ts +6 -0
  236. package/dist/tools/orchestrationSet.d.ts.map +1 -0
  237. package/dist/tools/orchestrationSet.js +12 -0
  238. package/dist/tools/orchestrationSet.js.map +1 -0
  239. package/dist/tools/roomTools.d.ts +6 -0
  240. package/dist/tools/roomTools.d.ts.map +1 -1
  241. package/dist/tools/roomTools.js +26 -2
  242. package/dist/tools/roomTools.js.map +1 -1
  243. package/dist/tools/skillTool.d.ts +15 -0
  244. package/dist/tools/skillTool.d.ts.map +1 -0
  245. package/dist/tools/skillTool.js +39 -0
  246. package/dist/tools/skillTool.js.map +1 -0
  247. package/dist/tools/urlGuard.d.ts +7 -0
  248. package/dist/tools/urlGuard.d.ts.map +1 -1
  249. package/dist/tools/urlGuard.js +54 -18
  250. package/dist/tools/urlGuard.js.map +1 -1
  251. package/package.json +6 -2
@@ -1,4 +1,4 @@
1
- import { SIDECAR_PROTOCOL, HOST_METHODS, errMessage, } from '@corenel/protocol';
1
+ import { SIDECAR_PROTOCOL, HOST_METHODS, errMessage, RUN_TASK_FEATURE, RUN_PLAN_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,60 @@ 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
+ });
219
+ Object.defineProperty(this, "runPlanSeq", {
220
+ enumerable: true,
221
+ configurable: true,
222
+ writable: true,
223
+ value: 0
224
+ });
225
+ Object.defineProperty(this, "runPlanPending", {
226
+ enumerable: true,
227
+ configurable: true,
228
+ writable: true,
229
+ value: new Map()
230
+ });
165
231
  Object.defineProperty(this, "hostsSeq", {
166
232
  enumerable: true,
167
233
  configurable: true,
@@ -281,6 +347,24 @@ export class SidecarHost {
281
347
  writable: true,
282
348
  value: []
283
349
  });
350
+ Object.defineProperty(this, "vapidKey", {
351
+ enumerable: true,
352
+ configurable: true,
353
+ writable: true,
354
+ value: null
355
+ });
356
+ Object.defineProperty(this, "pushSeq", {
357
+ enumerable: true,
358
+ configurable: true,
359
+ writable: true,
360
+ value: 0
361
+ });
362
+ Object.defineProperty(this, "pushPending", {
363
+ enumerable: true,
364
+ configurable: true,
365
+ writable: true,
366
+ value: new Map()
367
+ });
284
368
  /** The synced directory name the daemon echoed for THIS connection
285
369
  * (`welcome.dirName`), or null against an older daemon that sends none. */
286
370
  Object.defineProperty(this, "dirName", {
@@ -289,6 +373,16 @@ export class SidecarHost {
289
373
  writable: true,
290
374
  value: null
291
375
  });
376
+ /** The daemon's verdict on the identity challenge/assert exchange, from
377
+ * `welcome.identity`, runtime-validated the same way `dirName`/`info` are:
378
+ * a malformed value is dropped to undefined (=> `identityVerified()`
379
+ * reports null, "not told"), never coerced. */
380
+ Object.defineProperty(this, "identity", {
381
+ enumerable: true,
382
+ configurable: true,
383
+ writable: true,
384
+ value: void 0
385
+ });
292
386
  Object.defineProperty(this, "info", {
293
387
  enumerable: true,
294
388
  configurable: true,
@@ -342,9 +436,29 @@ export class SidecarHost {
342
436
  ...(this.opts.clientId ? { clientId: this.opts.clientId } : {}),
343
437
  ...(this.opts.clientName ? { clientName: this.opts.clientName } : {}),
344
438
  ...(this.opts.accounts?.length ? { accounts: this.opts.accounts } : {}),
439
+ ...(this.opts.wantsIdentity ? { wantsIdentity: true } : {}),
440
+ // What THIS client understands, so a daemon can tell an older app
441
+ // (whose agents sync but never fire until deployed) from this one.
442
+ capabilities: [CREW_DEPLOY_FEATURE],
345
443
  });
346
444
  });
347
445
  }
446
+ /** Answers the daemon's `identity-challenge`. `onIdentityChallenge` resolving
447
+ * a string sends it as the assertion; resolving `null`, having no handler
448
+ * at all, or THROWING all send `identity-assert` with no assertion -- every
449
+ * one of those is a considered "no identity to offer", never a reason to
450
+ * fail the handshake. The Clerk token itself never reaches this class: only
451
+ * whatever assertion the callback hands back does. */
452
+ async answerIdentityChallenge(m) {
453
+ let assertion = null;
454
+ try {
455
+ assertion = (await this.opts.onIdentityChallenge?.({ nonce: m.nonce, sidecarId: m.sidecarId })) ?? null;
456
+ }
457
+ catch {
458
+ assertion = null;
459
+ }
460
+ this.ch.send(assertion ? { t: 'identity-assert', assertion } : { t: 'identity-assert' });
461
+ }
348
462
  /** Drop the connection: unregister imported tools and fail anything in flight. */
349
463
  disconnect(reason = 'disconnected') {
350
464
  for (const name of this.imported)
@@ -359,6 +473,12 @@ export class SidecarHost {
359
473
  for (const p of this.runPending.values())
360
474
  p.reject(new Error(reason));
361
475
  this.runPending.clear();
476
+ // A cancel in flight when the connection drops did not get a considered
477
+ // answer -- resolve `unsupported` (not reject) so a Stop click during a
478
+ // drop does not hang the caller awaiting an ok/reason pair.
479
+ for (const p of this.cancelPending.values())
480
+ p({ ok: false, reason: 'unsupported' });
481
+ this.cancelPending.clear();
362
482
  for (const p of this.acpPending.values())
363
483
  p.reject(new Error(reason));
364
484
  this.acpPending.clear();
@@ -397,6 +517,21 @@ export class SidecarHost {
397
517
  for (const p of this.pausePending.values())
398
518
  p.reject(new Error(reason));
399
519
  this.pausePending.clear();
520
+ for (const p of this.crewDeployPending.values())
521
+ p.reject(new Error(reason));
522
+ this.crewDeployPending.clear();
523
+ for (const p of this.approvalsPending.values())
524
+ p.reject(new Error(reason));
525
+ this.approvalsPending.clear();
526
+ for (const p of this.checksPending.values())
527
+ p.reject(new Error(reason));
528
+ this.checksPending.clear();
529
+ for (const p of this.releasePending.values())
530
+ p.reject(new Error(reason));
531
+ this.releasePending.clear();
532
+ for (const p of this.runPlanPending.values())
533
+ p.reject(new Error(reason));
534
+ this.runPlanPending.clear();
400
535
  for (const p of this.hostsPending.values())
401
536
  p.reject(new Error(reason));
402
537
  this.hostsPending.clear();
@@ -434,6 +569,9 @@ export class SidecarHost {
434
569
  for (const p of this.mcpAdminPending.values())
435
570
  p.reject(new Error(reason));
436
571
  this.mcpAdminPending.clear();
572
+ for (const p of this.pushPending.values())
573
+ p.reject(new Error(reason));
574
+ this.pushPending.clear();
437
575
  /* The rejections above already drop each login's waiter via the wrapper
438
576
  * mcpLogin installs. Cleared here too so that stays true if sendMcpAdmin
439
577
  * is ever the one carrying a login -- a stale waiter would fire a consent
@@ -447,12 +585,19 @@ export class SidecarHost {
447
585
  switch (m.t) {
448
586
  case 'welcome': {
449
587
  this.features = m.features ?? [];
588
+ // Runtime-validated: an untrusted/older daemon's `vapidPublicKey` is
589
+ // just a JSON-parsed `string`, and a malformed one must never be
590
+ // handed to the Push API. Anything that isn't the expected
591
+ // URL-safe-base64, 87-char shape is treated as "no key offered".
592
+ this.vapidKey = typeof m.vapidPublicKey === 'string' && /^[A-Za-z0-9_-]{87}$/.test(m.vapidPublicKey) ? m.vapidPublicKey : null;
450
593
  // Runtime-validated like `info.id`/`info.name` next door: the welcome
451
594
  // crossed the wire as `JSON.parse(...) as SidecarMsg`, so the declared
452
595
  // `string` guarantees nothing, and this value decides which
453
596
  // registrations a management surface treats as this browser's own.
454
597
  // Anything malformed is dropped to "not told", never coerced.
455
598
  this.dirName = typeof m.dirName === 'string' && m.dirName ? m.dirName : null;
599
+ this.identity = m.identity && typeof m.identity === 'object' && typeof m.identity.verified === 'boolean'
600
+ ? m.identity : undefined;
456
601
  this.info = m.info ?? {};
457
602
  for (const decl of m.tools) {
458
603
  const tool = { ...decl, run: (args) => this.invoke(decl.name, args) };
@@ -468,6 +613,10 @@ export class SidecarHost {
468
613
  this.welcome = null;
469
614
  return;
470
615
  }
616
+ case 'identity-challenge': {
617
+ void this.answerIdentityChallenge(m);
618
+ return;
619
+ }
471
620
  case 'invoke-result': {
472
621
  const p = this.invokePending.get(m.id);
473
622
  if (!p)
@@ -570,8 +719,10 @@ export class SidecarHost {
570
719
  }
571
720
  case 'run-accepted': {
572
721
  const p = this.runPending.get(m.id);
573
- if (p)
722
+ if (p) {
574
723
  p.runId = m.runId;
724
+ p.onAccepted?.(m.runId);
725
+ }
575
726
  return;
576
727
  }
577
728
  case 'event': {
@@ -594,6 +745,12 @@ export class SidecarHost {
594
745
  p.reject(new Error(m.error || 'run failed'));
595
746
  return;
596
747
  }
748
+ case 'cancel-run-result': {
749
+ const p = this.cancelPending.get(m.id);
750
+ this.cancelPending.delete(m.id);
751
+ p?.(m.ok ? { ok: true } : { ok: false, ...(m.reason ? { reason: m.reason } : {}) });
752
+ return;
753
+ }
597
754
  case 'config-result': {
598
755
  const p = this.configPending.get(m.id);
599
756
  if (!p)
@@ -616,6 +773,17 @@ export class SidecarHost {
616
773
  p.reject(new Error(m.error || 'host sync failed'));
617
774
  return;
618
775
  }
776
+ case 'push-result': {
777
+ const p = this.pushPending.get(m.id);
778
+ if (!p)
779
+ return;
780
+ this.pushPending.delete(m.id);
781
+ if (m.ok)
782
+ p.resolve();
783
+ else
784
+ p.reject(new Error(m.error));
785
+ return;
786
+ }
619
787
  case 'permission-ask': {
620
788
  const { t: _t, ...ask } = m;
621
789
  this.opts.onPermissionAsk?.(ask);
@@ -849,6 +1017,25 @@ export class SidecarHost {
849
1017
  this.opts.onRegistrations?.(m.registrations);
850
1018
  return;
851
1019
  }
1020
+ case 'approvals-suggest': {
1021
+ const { t: _t, ...offer } = m;
1022
+ this.opts.onApprovalSuggestion?.(offer);
1023
+ return;
1024
+ }
1025
+ case 'approvals-accept-result':
1026
+ case 'approvals-list-result':
1027
+ case 'approvals-revoke-result': {
1028
+ const p = this.approvalsPending.get(m.id);
1029
+ if (!p)
1030
+ return;
1031
+ this.approvalsPending.delete(m.id);
1032
+ if (!m.ok) {
1033
+ p.resolve({ ok: false, error: m.error });
1034
+ return;
1035
+ }
1036
+ p.resolve(m.t === 'approvals-accept-result' ? { ok: true } : { ok: true, state: m.state });
1037
+ return;
1038
+ }
852
1039
  case 'room-open-result': {
853
1040
  const p = this.roomOpenPending.get(m.id);
854
1041
  if (!p)
@@ -881,6 +1068,41 @@ export class SidecarHost {
881
1068
  p.resolve(m.ok ? { ok: true, pauses: m.pauses } : { ok: false, error: m.error });
882
1069
  return;
883
1070
  }
1071
+ case 'crew-deploy-result': {
1072
+ // One pending map for all six crew-* requests: they share one reply shape.
1073
+ const p = this.crewDeployPending.get(m.id);
1074
+ if (!p)
1075
+ return;
1076
+ this.crewDeployPending.delete(m.id);
1077
+ p.resolve(m.reply);
1078
+ return;
1079
+ }
1080
+ case 'checks-get-result':
1081
+ case 'checks-pin-result': {
1082
+ // One map for both: each answers with the complete state.
1083
+ const p = this.checksPending.get(m.id);
1084
+ if (!p)
1085
+ return;
1086
+ this.checksPending.delete(m.id);
1087
+ p.resolve(m.ok ? { ok: true, state: m.state } : { ok: false, error: m.error });
1088
+ return;
1089
+ }
1090
+ case 'workspace-release-result': {
1091
+ const p = this.releasePending.get(m.id);
1092
+ if (!p)
1093
+ return;
1094
+ this.releasePending.delete(m.id);
1095
+ p.resolve(m.ok ? { ok: true, released: m.released } : { ok: false, error: m.error });
1096
+ return;
1097
+ }
1098
+ case 'run-plan-get-result': {
1099
+ const p = this.runPlanPending.get(m.id);
1100
+ if (!p)
1101
+ return;
1102
+ this.runPlanPending.delete(m.id);
1103
+ p.resolve(m.ok ? { ok: true, plan: m.plan } : { ok: false, error: m.error });
1104
+ return;
1105
+ }
884
1106
  case 'room-list-result': {
885
1107
  const p = this.roomListPending.get(m.id);
886
1108
  if (!p)
@@ -1211,6 +1433,12 @@ export class SidecarHost {
1211
1433
  /** The sidecar's advertised self-description (root/platform), or {} if it sent
1212
1434
  * none. Valid only after connect() resolves. */
1213
1435
  sidecarInfo() { return this.info; }
1436
+ /** The daemon's verdict on the identity challenge/assert exchange for this
1437
+ * connection. `null` means the daemon does not verify at all (no `identity`
1438
+ * on `welcome` -- no verifier configured, or an older daemon), which is
1439
+ * distinct from `false` (the exchange ran and was rejected, or the
1440
+ * assertion never arrived). Valid only after connect() resolves. */
1441
+ identityVerified() { return this.identity ? this.identity.verified : null; }
1214
1442
  /** Fetch a (loopback) URL THROUGH the sidecar — a server-side fetch on the user's
1215
1443
  * machine, so the browser dodges CORS. Returns a streaming Response: it resolves
1216
1444
  * as soon as headers arrive (http-res) and the body streams in via http-chunk. */
@@ -1251,7 +1479,7 @@ export class SidecarHost {
1251
1479
  run(agent, input, onEvent, opts) {
1252
1480
  const id = ++this.runSeq;
1253
1481
  return new Promise((resolve, reject) => {
1254
- this.runPending.set(id, { resolve, reject, onEvent });
1482
+ this.runPending.set(id, { resolve, reject, onEvent, onAccepted: opts?.onAccepted });
1255
1483
  // `sessionId` asks for the run to happen INSIDE that session. A daemon
1256
1484
  // that cannot honour it answers run-error rather than running it on the
1257
1485
  // host, so the caller learns rather than being quietly downgraded.
@@ -1261,9 +1489,52 @@ export class SidecarHost {
1261
1489
  t: 'run', id, agent, input,
1262
1490
  ...(opts?.sessionId ? { sessionId: opts.sessionId } : {}),
1263
1491
  ...(opts?.keepTranscript ? { keepTranscript: true } : {}),
1492
+ ...(opts?.task ? { task: opts.task } : {}),
1493
+ ...(opts?.autonomy ? { autonomy: opts.autonomy } : {}),
1264
1494
  });
1265
1495
  });
1266
1496
  }
1497
+ /** WATCH a run this client did not start (a trigger-fired crew run). Rides
1498
+ * the same pending map as `run`, so `event`/`run-done`/`run-error` route here
1499
+ * unchanged; the server replays what already happened first. */
1500
+ watchRun(runId, onEvent) {
1501
+ const id = ++this.runSeq;
1502
+ let rejectDone;
1503
+ const done = new Promise((resolve, reject) => {
1504
+ rejectDone = reject;
1505
+ this.runPending.set(id, {
1506
+ resolve: (r) => resolve({ text: r.text, stopReason: r.stopReason }),
1507
+ reject,
1508
+ onEvent,
1509
+ });
1510
+ this.ch.send({ t: 'subscribe', id, runId });
1511
+ });
1512
+ return {
1513
+ done,
1514
+ // Client-side only: the server keeps the subscription until the run ends
1515
+ // or the socket drops, and events for a closed id are ignored.
1516
+ close: () => { if (this.runPending.delete(id))
1517
+ rejectDone(new Error('closed')); },
1518
+ };
1519
+ }
1520
+ /** Ask the daemon to cancel a running crew run. `ok: false, reason:
1521
+ * 'no-such-run'` means the daemon has no such run (already finished, or
1522
+ * never existed); `'unsupported'` means the daemon's runner has no `cancel`,
1523
+ * or the connection dropped before the daemon answered. */
1524
+ cancelRun(runId) {
1525
+ const id = ++this.cancelSeq;
1526
+ return new Promise((resolve) => {
1527
+ this.cancelPending.set(id, resolve);
1528
+ this.ch.send({ t: 'cancel-run', id, runId });
1529
+ });
1530
+ }
1531
+ /** True when the connected sidecar's runner supports cancelling a run. */
1532
+ supportsRunCancel() { return this.features.includes('run-cancel'); }
1533
+ /** True when the connected sidecar understands `run`'s `task`/`autonomy`
1534
+ * fields. An older sidecar without this feature would silently ignore
1535
+ * them, so callers must check this before sending either — never send and
1536
+ * hope. */
1537
+ supportsTasks() { return this.features.includes(RUN_TASK_FEATURE); }
1267
1538
  /** Start a proxied external ACP agent on the sidecar. The returned promise
1268
1539
  * settles on the handshake; `onUpdate` keeps receiving the agent's stream for
1269
1540
  * the life of the session. Permission asks do NOT come through here — they use
@@ -1313,9 +1584,10 @@ export class SidecarHost {
1313
1584
  }
1314
1585
  /** Answer an outstanding permission ask. Fire-and-forget over the wire — the
1315
1586
  * actual drop of the prompt is driven by the server's `permission-resolved`
1316
- * broadcast (`onPermissionResolved`), not by this call returning. */
1317
- answerPermission(id, decision) {
1318
- this.ch.send({ t: 'permission-answer', id, decision });
1587
+ * broadcast (`onPermissionResolved`), not by this call returning. `note` is
1588
+ * the person's reason for a deny; the daemon trims and caps it. */
1589
+ answerPermission(id, decision, note) {
1590
+ this.ch.send({ t: 'permission-answer', id, decision, ...(note ? { note } : {}) });
1319
1591
  }
1320
1592
  /** Push one definition file's content to the daemon, to be persisted under
1321
1593
  * this client's own `<stateDir>/hosts/<dirName>/` (Slice 2 host sync).
@@ -1339,6 +1611,30 @@ export class SidecarHost {
1339
1611
  this.ch.send({ t: 'host-sync-delete', id, path });
1340
1612
  });
1341
1613
  }
1614
+ /** The daemon's VAPID public key from `welcome`, or null when it offered
1615
+ * none (older daemon, or an offered value that failed validation). */
1616
+ vapidPublicKey() { return this.vapidKey; }
1617
+ /** True when this daemon advertises web push AND handed over a usable key. */
1618
+ supportsWebPush() { return this.features.includes('web-push') && this.vapidKey !== null; }
1619
+ /** Send a browser push subscription to the daemon to persist. Resolves on
1620
+ * `push-result { ok: true }`; rejects with the daemon's error, or on
1621
+ * disconnect while in flight. */
1622
+ pushSubscribe(subscription) {
1623
+ const id = ++this.pushSeq;
1624
+ return new Promise((resolve, reject) => {
1625
+ this.pushPending.set(id, { resolve, reject });
1626
+ this.ch.send({ t: 'push-subscribe', id, subscription });
1627
+ });
1628
+ }
1629
+ /** Tell the daemon to drop a subscription by endpoint. Same resolve/reject
1630
+ * contract as `pushSubscribe`. */
1631
+ pushUnsubscribe(endpoint) {
1632
+ const id = ++this.pushSeq;
1633
+ return new Promise((resolve, reject) => {
1634
+ this.pushPending.set(id, { resolve, reject });
1635
+ this.ch.send({ t: 'push-unsubscribe', id, endpoint });
1636
+ });
1637
+ }
1342
1638
  /** True when this daemon serves its OWN Harness Config (`config-*`). Feature-
1343
1639
  * detected from the welcome, so a client standing on a daemon too old to serve
1344
1640
  * one says the config is unreachable instead of sending a message that will be
@@ -1504,6 +1800,105 @@ export class SidecarHost {
1504
1800
  this.ch.send({ t: 'agent-pause-set', id, owner, agent, paused });
1505
1801
  });
1506
1802
  }
1803
+ /** True when the connected daemon keeps a deploy list (crew deploy per
1804
+ * machine). Feature-detected from the welcome; without it a client shows
1805
+ * the old read-only view. */
1806
+ supportsCrewDeploy() { return this.features.includes(CREW_DEPLOY_FEATURE); }
1807
+ /** One crew-* request. `ok: false` is the daemon's considered answer (a
1808
+ * refusal names its rule) and resolves; only a dropped connection rejects.
1809
+ * Against a daemon without the feature nothing is sent. */
1810
+ crewDeployRequest(send) {
1811
+ if (!this.supportsCrewDeploy())
1812
+ return Promise.resolve({ ok: false, error: 'this daemon does not keep a deploy list' });
1813
+ const id = ++this.crewDeploySeq;
1814
+ return new Promise((resolve, reject) => {
1815
+ this.crewDeployPending.set(id, { resolve, reject });
1816
+ this.ch.send(send(id));
1817
+ });
1818
+ }
1819
+ /** The machine's deploy list, statuses and audit. */
1820
+ crewDeployments() { return this.crewDeployRequest((id) => ({ t: 'crew-deployments', id })); }
1821
+ crewDeploy(ref, reason) { return this.crewDeployRequest((id) => ({ t: 'crew-deploy', id, ref, ...(reason ? { reason } : {}) })); }
1822
+ crewUndeploy(ref, reason) { return this.crewDeployRequest((id) => ({ t: 'crew-undeploy', id, ref, ...(reason ? { reason } : {}) })); }
1823
+ crewPause(ref, reason) { return this.crewDeployRequest((id) => ({ t: 'crew-pause', id, ref, reason })); }
1824
+ crewResume(ref, reason) { return this.crewDeployRequest((id) => ({ t: 'crew-resume', id, ref, ...(reason ? { reason } : {}) })); }
1825
+ /** Stop a run AND record who stopped it and why (the plain `cancelRun` records nothing). */
1826
+ crewStop(ref, runId, reason) { return this.crewDeployRequest((id) => ({ t: 'crew-stop', id, ref, runId, reason })); }
1827
+ /** True when the connected daemon keeps "This machine" standing approvals
1828
+ * (plan 3). Feature-detected from the welcome. */
1829
+ supportsApprovals() { return this.features.includes('approvals'); }
1830
+ approvalsRequest(send) {
1831
+ if (!this.supportsApprovals())
1832
+ return Promise.resolve({ ok: false, error: 'this daemon does not keep standing approvals' });
1833
+ const id = ++this.approvalsSeq;
1834
+ return new Promise((resolve, reject) => {
1835
+ this.approvalsPending.set(id, { resolve, reject });
1836
+ this.ch.send(send(id));
1837
+ });
1838
+ }
1839
+ /** Accept an offer this client received. The daemon checks that THIS
1840
+ * connection is the one it offered to; `ok: false` is its refusal (expired,
1841
+ * already used, offered to another connection), a real answer that
1842
+ * resolves. Only a dropped connection rejects. */
1843
+ async acceptApprovalSuggestion(suggestionId, choice) {
1844
+ const r = await this.approvalsRequest((id) => ({ t: 'approvals-accept', id, suggestionId, choice }));
1845
+ return r.ok ? { ok: true } : r;
1846
+ }
1847
+ /** The daemon's standing rules and its (daemon.json) approval settings. */
1848
+ async listDaemonApprovals() {
1849
+ return this.withState(await this.approvalsRequest((id) => ({ t: 'approvals-list', id })));
1850
+ }
1851
+ /** Remove one daemon standing rule; resolves with the complete state after. */
1852
+ async revokeDaemonApproval(tool) {
1853
+ return this.withState(await this.approvalsRequest((id) => ({ t: 'approvals-revoke', id, tool })));
1854
+ }
1855
+ withState(r) {
1856
+ if (!r.ok)
1857
+ return r;
1858
+ return r.state ? { ok: true, state: r.state } : { ok: false, error: 'the daemon answered without its approvals state' };
1859
+ }
1860
+ /** True when the daemon runs confirmed checks and holds the orchestrator's
1861
+ * workspace lock (it was started with --allow-shell, and its state dir is
1862
+ * not inside an approved root). */
1863
+ supportsChecks() { return this.features.includes('checks'); }
1864
+ /** The folder's checks file, whether it is confirmed, and proposals. `ok:
1865
+ * false` is the daemon's answer (no checks here, a bad path); only a dropped
1866
+ * connection rejects. */
1867
+ checksGet(root, stateDir) {
1868
+ const id = ++this.checksSeq;
1869
+ return new Promise((resolve, reject) => {
1870
+ this.checksPending.set(id, { resolve, reject });
1871
+ this.ch.send({ t: 'checks-get', id, root, stateDir });
1872
+ });
1873
+ }
1874
+ /** Write the content the user confirmed and pin its hash. */
1875
+ checksPin(root, stateDir, content) {
1876
+ const id = ++this.checksSeq;
1877
+ return new Promise((resolve, reject) => {
1878
+ this.checksPending.set(id, { resolve, reject });
1879
+ this.ch.send({ t: 'checks-pin', id, root, stateDir, content });
1880
+ });
1881
+ }
1882
+ /** Hand back a run's workspace lock. `released: false` means this run did
1883
+ * not hold it (already released, or held by another run). */
1884
+ workspaceRelease(ref) {
1885
+ const id = ++this.checksSeq;
1886
+ return new Promise((resolve, reject) => {
1887
+ this.releasePending.set(id, { resolve, reject });
1888
+ this.ch.send({ t: 'workspace-release', id, root: ref.root, stateDir: ref.stateDir, runId: ref.runId });
1889
+ });
1890
+ }
1891
+ /** True when the daemon answers run-plan-get. */
1892
+ supportsRunPlan() { return this.features.includes(RUN_PLAN_FEATURE); }
1893
+ /** One run's plan from its record. `ok: false` is the daemon's answer; only
1894
+ * a dropped connection rejects. Ask only when supportsRunPlan() is true. */
1895
+ runPlanGet(runId) {
1896
+ const id = ++this.runPlanSeq;
1897
+ return new Promise((resolve, reject) => {
1898
+ this.runPlanPending.set(id, { resolve, reject });
1899
+ this.ch.send({ t: 'run-plan-get', id, runId });
1900
+ });
1901
+ }
1507
1902
  /** Ask the daemon to open a room. `ok: false` here is the daemon's CONSIDERED
1508
1903
  * refusal (validation problems, or an unreachable participant) — a real answer,
1509
1904
  * not a transport failure, so it resolves rather than rejects. Only a dropped