@corenel/harness 0.21.0 → 0.23.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 (92) hide show
  1. package/README.md +48 -0
  2. package/dist/chat/engine.d.ts +9 -0
  3. package/dist/chat/engine.d.ts.map +1 -1
  4. package/dist/chat/engine.js +90 -6
  5. package/dist/chat/engine.js.map +1 -1
  6. package/dist/chat/persist.d.ts +8 -0
  7. package/dist/chat/persist.d.ts.map +1 -1
  8. package/dist/chat/persist.js +11 -0
  9. package/dist/chat/persist.js.map +1 -1
  10. package/dist/chat/runWatchdog.d.ts +1 -1
  11. package/dist/chat/runWatchdog.d.ts.map +1 -1
  12. package/dist/chat/runWatchdog.js +1 -1
  13. package/dist/chat/runWatchdog.js.map +1 -1
  14. package/dist/core/loop.d.ts.map +1 -1
  15. package/dist/core/loop.js +134 -78
  16. package/dist/core/loop.js.map +1 -1
  17. package/dist/dag/executeNode.js +3 -3
  18. package/dist/guardrail/guardrail.d.ts +9 -0
  19. package/dist/guardrail/guardrail.d.ts.map +1 -1
  20. package/dist/guardrail/guardrail.js +13 -0
  21. package/dist/guardrail/guardrail.js.map +1 -1
  22. package/dist/guardrail/index.d.ts +3 -3
  23. package/dist/guardrail/index.d.ts.map +1 -1
  24. package/dist/guardrail/index.js +2 -2
  25. package/dist/guardrail/index.js.map +1 -1
  26. package/dist/guardrail/permission-service.d.ts +17 -3
  27. package/dist/guardrail/permission-service.d.ts.map +1 -1
  28. package/dist/guardrail/permission-service.js +11 -6
  29. package/dist/guardrail/permission-service.js.map +1 -1
  30. package/dist/guardrail/store.d.ts +4 -0
  31. package/dist/guardrail/store.d.ts.map +1 -1
  32. package/dist/guardrail/store.js +6 -0
  33. package/dist/guardrail/store.js.map +1 -1
  34. package/dist/prompts/config.d.ts +6 -2
  35. package/dist/prompts/config.d.ts.map +1 -1
  36. package/dist/prompts/config.js +6 -3
  37. package/dist/prompts/config.js.map +1 -1
  38. package/dist/prompts/host.d.ts +3 -2
  39. package/dist/prompts/host.d.ts.map +1 -1
  40. package/dist/prompts/host.js.map +1 -1
  41. package/dist/prompts/index.d.ts +8 -1
  42. package/dist/prompts/index.d.ts.map +1 -1
  43. package/dist/prompts/index.js +9 -7
  44. package/dist/prompts/index.js.map +1 -1
  45. package/dist/prompts/library/defaults.generated.js +2 -2
  46. package/dist/prompts/library/defaults.generated.js.map +1 -1
  47. package/dist/prompts/systemPrompts.d.ts +3 -0
  48. package/dist/prompts/systemPrompts.d.ts.map +1 -1
  49. package/dist/prompts/systemPrompts.js +1 -1
  50. package/dist/prompts/systemPrompts.js.map +1 -1
  51. package/dist/sessions/fileSessionStore.d.ts.map +1 -1
  52. package/dist/sessions/fileSessionStore.js.map +1 -1
  53. package/dist/sidecar/fsChanges.d.ts +23 -0
  54. package/dist/sidecar/fsChanges.d.ts.map +1 -0
  55. package/dist/sidecar/fsChanges.js +77 -0
  56. package/dist/sidecar/fsChanges.js.map +1 -0
  57. package/dist/sidecar/host.d.ts +62 -9
  58. package/dist/sidecar/host.d.ts.map +1 -1
  59. package/dist/sidecar/host.js +243 -6
  60. package/dist/sidecar/host.js.map +1 -1
  61. package/dist/sidecar/memberGate.d.ts.map +1 -1
  62. package/dist/sidecar/memberGate.js +9 -0
  63. package/dist/sidecar/memberGate.js.map +1 -1
  64. package/dist/sidecar/server.d.ts +98 -6
  65. package/dist/sidecar/server.d.ts.map +1 -1
  66. package/dist/sidecar/server.js +408 -19
  67. package/dist/sidecar/server.js.map +1 -1
  68. package/dist/skills.d.ts +9 -1
  69. package/dist/skills.d.ts.map +1 -1
  70. package/dist/skills.js +22 -8
  71. package/dist/skills.js.map +1 -1
  72. package/dist/tools/builtins.d.ts +6 -1
  73. package/dist/tools/builtins.d.ts.map +1 -1
  74. package/dist/tools/builtins.js +13 -2
  75. package/dist/tools/builtins.js.map +1 -1
  76. package/dist/tools/callAgent.d.ts +6 -2
  77. package/dist/tools/callAgent.d.ts.map +1 -1
  78. package/dist/tools/callAgent.js +7 -3
  79. package/dist/tools/callAgent.js.map +1 -1
  80. package/dist/tools/files.d.ts.map +1 -1
  81. package/dist/tools/files.js +4 -0
  82. package/dist/tools/files.js.map +1 -1
  83. package/dist/tools/sessions.d.ts.map +1 -1
  84. package/dist/tools/sessions.js +1 -0
  85. package/dist/tools/sessions.js.map +1 -1
  86. package/dist/tools/web.d.ts.map +1 -1
  87. package/dist/tools/web.js +19 -5
  88. package/dist/tools/web.js.map +1 -1
  89. package/dist/tools/workflowTool.d.ts +2 -18
  90. package/dist/tools/workflowTool.d.ts.map +1 -1
  91. package/dist/tools/workflowTool.js.map +1 -1
  92. package/package.json +3 -3
@@ -1,7 +1,7 @@
1
- import { CHAT_FEATURE, CHAT_ID_RE } from '@corenel/protocol';
1
+ import { CHAT_FEATURE, CHAT_ID_RE, FS_CHANGED_FEATURE, MCP_ALLOW_FEATURE } from '@corenel/protocol';
2
2
  import { asEcPublicJwk, jwkThumbprint, verifySignature, randomToken, serverProofPayload, clientProofPayload, parseEnrollmentCode, bytesToB64url, codeProof, codeHash, } from './deviceKeys.js';
3
3
  import { capabilityMissingMessage } from './hostCapability.js';
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
+ import { SIDECAR_PROTOCOL, HOST_METHODS, errMessage, ATTENDANCE_LEAVE_FEATURE, RUN_TASK_FEATURE, RUN_PLAN_FEATURE, CREW_DEPLOY_FEATURE, FLEET_ITEMS_FEATURE, FLEET_USE_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';
5
5
  const NO_WORKFLOWS = 'this daemon does not run workflows';
6
6
  const WRONG_REPLY = 'the daemon answered with the wrong reply';
7
7
  /** A reply that failed its check (protocol workflowWire.ts readers). */
@@ -13,7 +13,23 @@ const MALFORMED_REPLY = 'the daemon sent a reply this app cannot read';
13
13
  * slow link. */
14
14
  export const WORKFLOW_REQUEST_TIMEOUT_MS = 60000;
15
15
  export const WORKFLOW_PUT_TIMEOUT_MS = 120000;
16
+ /** After a fleet workflow call is stopped, how long the host waits for the sidecar's answer before it settles the
17
+ * call itself (a lost reply, a hung daemon, a cancel that did not land): Stop never hangs. Shorter than
18
+ * WORKFLOW_REQUEST_TIMEOUT_MS because a person is waiting on Stop. A late answer is dropped. */
19
+ export const FLEET_CALL_STOP_GRACE_MS = 15000;
20
+ export const FLEET_CALL_STOPPED = 'The fleet workflow call was stopped.';
16
21
  const NO_ANSWER = 'the sidecar did not answer in time';
22
+ /** The longest root or path an `fs-changed` may name; a real one is far shorter. */
23
+ const MAX_FS_CHANGE_LEN = 4096;
24
+ /** An `fs-changed` message's change, or null when it is not one: a root and a
25
+ * path, each a non-empty string within MAX_FS_CHANGE_LEN with no NUL. */
26
+ export function fsChangeOf(m) {
27
+ if (typeof m !== 'object' || m === null)
28
+ return null;
29
+ const { root, path } = m;
30
+ const ok = (v) => typeof v === 'string' && v.length > 0 && v.length <= MAX_FS_CHANGE_LEN && !v.includes('\u0000');
31
+ return ok(root) && ok(path) ? { root, path } : null;
32
+ }
17
33
  /** Decode a base64 chunk to bytes (atob is available in the browser and Node 18+). */
18
34
  function b64ToBytes(b64) {
19
35
  const bin = atob(b64);
@@ -295,6 +311,43 @@ export class SidecarHost {
295
311
  writable: true,
296
312
  value: new Map()
297
313
  });
314
+ Object.defineProperty(this, "fleetItemsSeq", {
315
+ enumerable: true,
316
+ configurable: true,
317
+ writable: true,
318
+ value: 0
319
+ });
320
+ Object.defineProperty(this, "fleetItemsPending", {
321
+ enumerable: true,
322
+ configurable: true,
323
+ writable: true,
324
+ value: new Map()
325
+ });
326
+ /** One id sequence for the three fleet-use requests; each has its own reply map. */
327
+ Object.defineProperty(this, "fleetUseSeq", {
328
+ enumerable: true,
329
+ configurable: true,
330
+ writable: true,
331
+ value: 0
332
+ });
333
+ Object.defineProperty(this, "fleetSkillCatalogPending", {
334
+ enumerable: true,
335
+ configurable: true,
336
+ writable: true,
337
+ value: new Map()
338
+ });
339
+ Object.defineProperty(this, "fleetWorkflowCatalogPending", {
340
+ enumerable: true,
341
+ configurable: true,
342
+ writable: true,
343
+ value: new Map()
344
+ });
345
+ Object.defineProperty(this, "fleetWorkflowCallPending", {
346
+ enumerable: true,
347
+ configurable: true,
348
+ writable: true,
349
+ value: new Map()
350
+ });
298
351
  Object.defineProperty(this, "crewDeploySeq", {
299
352
  enumerable: true,
300
353
  configurable: true,
@@ -542,6 +595,14 @@ export class SidecarHost {
542
595
  writable: true,
543
596
  value: []
544
597
  });
598
+ /** Who hears another client's file changes (`fs-changed`), for the life of
599
+ * this connection. */
600
+ Object.defineProperty(this, "fsChangeSubs", {
601
+ enumerable: true,
602
+ configurable: true,
603
+ writable: true,
604
+ value: new Set()
605
+ });
545
606
  Object.defineProperty(this, "sttSeq", {
546
607
  enumerable: true,
547
608
  configurable: true,
@@ -966,6 +1027,18 @@ export class SidecarHost {
966
1027
  for (const p of this.crewDeployPending.values())
967
1028
  p.reject(new Error(reason));
968
1029
  this.crewDeployPending.clear();
1030
+ for (const p of this.fleetItemsPending.values())
1031
+ p.reject(new Error(reason));
1032
+ this.fleetItemsPending.clear();
1033
+ for (const p of this.fleetSkillCatalogPending.values())
1034
+ p.reject(new Error(reason));
1035
+ this.fleetSkillCatalogPending.clear();
1036
+ for (const p of this.fleetWorkflowCatalogPending.values())
1037
+ p.reject(new Error(reason));
1038
+ this.fleetWorkflowCatalogPending.clear();
1039
+ for (const p of this.fleetWorkflowCallPending.values())
1040
+ p.reject(new Error(reason));
1041
+ this.fleetWorkflowCallPending.clear();
969
1042
  for (const p of this.workflowsPending.values())
970
1043
  p.reject(new Error(reason));
971
1044
  this.workflowsPending.clear();
@@ -1048,6 +1121,8 @@ export class SidecarHost {
1048
1121
  for (const s of this.sttStreams.values())
1049
1122
  s.onClosed(reason);
1050
1123
  this.sttStreams.clear();
1124
+ // Nothing more arrives on this connection: its change listeners are let go.
1125
+ this.fsChangeSubs.clear();
1051
1126
  this.ch.close();
1052
1127
  }
1053
1128
  onMessage(m) {
@@ -1136,6 +1211,24 @@ export class SidecarHost {
1136
1211
  // Unsolicited: the set changed on the daemon (possibly via ANOTHER client).
1137
1212
  this.opts.onRoots?.({ root: m.root, roots: m.roots });
1138
1213
  return;
1214
+ case 'fs-changed': {
1215
+ /* Unsolicited, and JSON-parsed off the wire, so the declared strings
1216
+ * guarantee nothing. Heard only from a daemon that said it sends it,
1217
+ * and only in the shape it mints: a root and a path, each a short
1218
+ * non-empty string with no NUL. Anything else is dropped. */
1219
+ if (!this.features.includes(FS_CHANGED_FEATURE))
1220
+ return;
1221
+ const change = fsChangeOf(m);
1222
+ if (!change)
1223
+ return;
1224
+ for (const cb of [...this.fsChangeSubs]) {
1225
+ try {
1226
+ cb(change);
1227
+ }
1228
+ catch { /* one listener's failure must not stop the rest */ }
1229
+ }
1230
+ return;
1231
+ }
1139
1232
  case 'roots-browse-result': {
1140
1233
  const p = this.rootsPending.get(m.id);
1141
1234
  if (!p)
@@ -1523,7 +1616,11 @@ export class SidecarHost {
1523
1616
  if (!p)
1524
1617
  return;
1525
1618
  this.mcpAdminPending.delete(m.id);
1526
- p.resolve({ ok: true });
1619
+ p.resolve({
1620
+ ok: true,
1621
+ ...(typeof m.reloadError === 'string' ? { reloadError: m.reloadError } : {}),
1622
+ ...(Array.isArray(m.servers) ? { servers: m.servers } : {}),
1623
+ });
1527
1624
  return;
1528
1625
  }
1529
1626
  case 'mcp-admin-error': {
@@ -1536,7 +1633,11 @@ export class SidecarHost {
1536
1633
  this.mcpAdminPending.delete(m.id);
1537
1634
  // `reason` carried through so a caller can tell an ordinary refusal
1538
1635
  // ("this server wants a pasted token") from a real failure.
1539
- p.resolve(m.reason ? { ok: false, error: m.error, reason: m.reason } : { ok: false, error: m.error });
1636
+ p.resolve({
1637
+ ok: false, error: m.error,
1638
+ ...(m.reason ? { reason: m.reason } : {}),
1639
+ ...(m.code ? { code: m.code } : {}),
1640
+ });
1540
1641
  return;
1541
1642
  }
1542
1643
  /* A progress push, not a reply: the request stays pending. Delivered to
@@ -1663,6 +1764,38 @@ export class SidecarHost {
1663
1764
  p.resolve(m.reply);
1664
1765
  return;
1665
1766
  }
1767
+ case 'fleet-items-result': {
1768
+ const p = this.fleetItemsPending.get(m.id);
1769
+ if (!p)
1770
+ return;
1771
+ this.fleetItemsPending.delete(m.id);
1772
+ p.resolve(m.reply);
1773
+ return;
1774
+ }
1775
+ case 'fleet-skill-catalog-result': {
1776
+ const p = this.fleetSkillCatalogPending.get(m.id);
1777
+ if (!p)
1778
+ return;
1779
+ this.fleetSkillCatalogPending.delete(m.id);
1780
+ p.resolve(m.reply);
1781
+ return;
1782
+ }
1783
+ case 'fleet-workflow-catalog-result': {
1784
+ const p = this.fleetWorkflowCatalogPending.get(m.id);
1785
+ if (!p)
1786
+ return;
1787
+ this.fleetWorkflowCatalogPending.delete(m.id);
1788
+ p.resolve(m.reply);
1789
+ return;
1790
+ }
1791
+ case 'fleet-workflow-call-result': {
1792
+ const p = this.fleetWorkflowCallPending.get(m.id);
1793
+ if (!p)
1794
+ return;
1795
+ this.fleetWorkflowCallPending.delete(m.id);
1796
+ p.resolve(m.reply);
1797
+ return;
1798
+ }
1666
1799
  case 'workflow-package-result':
1667
1800
  case 'workflow-results-list-result':
1668
1801
  case 'workflow-results-take-result':
@@ -2382,6 +2515,16 @@ export class SidecarHost {
2382
2515
  answerPermission(id, decision, note) {
2383
2516
  this.ch.send({ t: 'permission-answer', id, decision, ...(note ? { note } : {}) });
2384
2517
  }
2518
+ /** Stop attending on this connection while it stays open for this client's
2519
+ * own calls: the daemon sends it no more permission asks, and an ask nobody
2520
+ * else attends gets its unattended policy at once. Sent only to a daemon
2521
+ * that advertises it (an older one would drop it unread); false when not. */
2522
+ leaveAttendance() {
2523
+ if (!this.features.includes(ATTENDANCE_LEAVE_FEATURE))
2524
+ return false;
2525
+ this.ch.send({ t: 'attendance-leave' });
2526
+ return true;
2527
+ }
2385
2528
  /** Push one definition file's content to the daemon, to be persisted under
2386
2529
  * this client's own `<stateDir>/hosts/<dirName>/` (Slice 2 host sync).
2387
2530
  * Resolves on `host-sync-result { ok: true }`; rejects with the daemon's
@@ -2475,6 +2618,16 @@ export class SidecarHost {
2475
2618
  * and every room-* message is refused server-side; feature-detecting here means
2476
2619
  * the browser never sends one it already knows will be refused. */
2477
2620
  supportsRooms() { return this.features.includes('rooms'); }
2621
+ /** True when the sidecar announces other clients' file changes (`fs-changed`).
2622
+ * An older one never does, and a listener here simply never fires. */
2623
+ supportsFsChanged() { return this.features.includes(FS_CHANGED_FEATURE); }
2624
+ /** Hear every file change another client of this daemon makes through its
2625
+ * file tools: the root the tool used and the root-relative path. Never this
2626
+ * client's own changes (the daemon leaves the writer out). */
2627
+ onFsChanged(cb) {
2628
+ this.fsChangeSubs.add(cb);
2629
+ return () => { this.fsChangeSubs.delete(cb); };
2630
+ }
2478
2631
  /** True when the connected daemon HOLDS agent pauses. Feature-detected from
2479
2632
  * the welcome, so a client offers "Pause" only where pausing would actually
2480
2633
  * stop the agent rather than reporting a success the daemon ignores. */
@@ -2535,8 +2688,8 @@ export class SidecarHost {
2535
2688
  /** The MCP servers configured on this machine, with credential PRESENCE.
2536
2689
  * There is no read path for a credential value anywhere in this feature. */
2537
2690
  mcpServers() { return this.sendMcpAdmin({ t: 'mcp-servers-list' }); }
2538
- /** Add a server to this machine's daemon.json. Takes effect on daemon
2539
- * restart -- the config plane cannot reach into a running daemon. */
2691
+ /** Add a server to this machine's daemon.json. Resolves after the daemon
2692
+ * applied it (`reloadError` when it could not yet). */
2540
2693
  mcpAddServer(name, url, hosted = false, authUrl) {
2541
2694
  /* Each field sent only when it has a value, so the message an older daemon
2542
2695
  * receives is byte-identical to what it received before these existed. */
@@ -2573,6 +2726,18 @@ export class SidecarHost {
2573
2726
  mcpSetCredential(name, header, value) {
2574
2727
  return this.sendMcpAdmin({ t: 'mcp-set-credential', name, header, value });
2575
2728
  }
2729
+ /** Whether this daemon takes `mcpSetAllow`. */
2730
+ supportsMcpAllow() { return this.features.includes(MCP_ALLOW_FEATURE); }
2731
+ /** Switch which of server `name`'s tools this machine's `mcpAllow` grants:
2732
+ * exactly `tools`, replacing that server's entries. Empty turns them all
2733
+ * off. Resolves after the daemon applied it. Refused here, sending nothing,
2734
+ * against a daemon without the feature: an older one would never answer. */
2735
+ mcpSetAllow(name, tools) {
2736
+ if (!this.supportsMcpAllow()) {
2737
+ return Promise.resolve({ ok: false, error: 'this daemon cannot switch MCP tools -- update it and reconnect' });
2738
+ }
2739
+ return this.sendMcpAdmin({ t: 'mcp-allow-set', name, tools });
2740
+ }
2576
2741
  sendMcpAdmin(body) {
2577
2742
  const id = ++this.mcpSeq;
2578
2743
  return new Promise((resolve, reject) => {
@@ -2638,6 +2803,78 @@ export class SidecarHost {
2638
2803
  this.ch.send(send(id));
2639
2804
  });
2640
2805
  }
2806
+ /** True when the connected sidecar lists its fleet items. */
2807
+ supportsFleetItems() { return this.features.includes(FLEET_ITEMS_FEATURE); }
2808
+ /** The fleet agents, skills and workflows installed on the connected
2809
+ * sidecar. `ok: false` is its considered answer and resolves; only a dropped
2810
+ * connection rejects. Against a sidecar without the feature nothing is sent. */
2811
+ fleetItems() {
2812
+ if (!this.supportsFleetItems())
2813
+ return Promise.resolve({ ok: false, error: 'this sidecar does not list fleet items' });
2814
+ const id = ++this.fleetItemsSeq;
2815
+ return new Promise((resolve, reject) => {
2816
+ this.fleetItemsPending.set(id, { resolve, reject });
2817
+ this.ch.send({ t: 'fleet-items', id });
2818
+ });
2819
+ }
2820
+ /** True when the connected sidecar offers its fleet skills and workflows for use. */
2821
+ supportsFleetUse() { return this.features.includes(FLEET_USE_FEATURE); }
2822
+ /** The fleet skills the connected sidecar offers now. `ok: false` is its considered answer and resolves; only
2823
+ * a dropped connection rejects. Against a sidecar without the feature nothing is sent. */
2824
+ fleetSkillCatalog() {
2825
+ if (!this.supportsFleetUse())
2826
+ return Promise.resolve({ ok: false, error: 'this sidecar does not offer fleet items' });
2827
+ const id = ++this.fleetUseSeq;
2828
+ return new Promise((resolve, reject) => {
2829
+ this.fleetSkillCatalogPending.set(id, { resolve, reject });
2830
+ this.ch.send({ t: 'fleet-skill-catalog', id });
2831
+ });
2832
+ }
2833
+ /** The fleet workflows the connected sidecar can run now. Same contract as fleetSkillCatalog. */
2834
+ fleetWorkflowCatalog() {
2835
+ if (!this.supportsFleetUse())
2836
+ return Promise.resolve({ ok: false, error: 'this sidecar does not offer fleet items' });
2837
+ const id = ++this.fleetUseSeq;
2838
+ return new Promise((resolve, reject) => {
2839
+ this.fleetWorkflowCatalogPending.set(id, { resolve, reject });
2840
+ this.ch.send({ t: 'fleet-workflow-catalog', id });
2841
+ });
2842
+ }
2843
+ /** Run one fleet workflow on the connected sidecar and wait for its final text. Aborting `signal` sends
2844
+ * `fleet-workflow-cancel` and waits up to FLEET_CALL_STOP_GRACE_MS for the sidecar's answer (the run settles as
2845
+ * stopped); with none by then the call is forgotten (a late answer is dropped) and resolves FLEET_CALL_STOPPED.
2846
+ * An already-aborted signal sends nothing. `ok: false` resolves; only a dropped connection rejects. */
2847
+ callFleetWorkflow(name, inputs, signal) {
2848
+ if (!this.supportsFleetUse())
2849
+ return Promise.resolve({ ok: false, error: 'this sidecar does not offer fleet items' });
2850
+ if (signal.aborted)
2851
+ return Promise.resolve({ ok: false, error: FLEET_CALL_STOPPED });
2852
+ const id = ++this.fleetUseSeq;
2853
+ return new Promise((resolve, reject) => {
2854
+ let timer;
2855
+ const onAbort = () => {
2856
+ if (!this.fleetWorkflowCallPending.has(id))
2857
+ return;
2858
+ this.ch.send({ t: 'fleet-workflow-cancel', id });
2859
+ timer = setTimeout(() => {
2860
+ if (!this.fleetWorkflowCallPending.delete(id))
2861
+ return;
2862
+ resolve({ ok: false, error: FLEET_CALL_STOPPED });
2863
+ }, FLEET_CALL_STOP_GRACE_MS);
2864
+ };
2865
+ const settle = () => {
2866
+ signal.removeEventListener('abort', onAbort);
2867
+ if (timer !== undefined)
2868
+ clearTimeout(timer);
2869
+ };
2870
+ this.fleetWorkflowCallPending.set(id, {
2871
+ resolve: (r) => { settle(); resolve(r); },
2872
+ reject: (e) => { settle(); reject(e); },
2873
+ });
2874
+ signal.addEventListener('abort', onAbort, { once: true });
2875
+ this.ch.send({ t: 'fleet-workflow-call', id, name, inputs });
2876
+ });
2877
+ }
2641
2878
  /** The machine's deploy list, statuses and audit. */
2642
2879
  crewDeployments() { return this.crewDeployRequest((id) => ({ t: 'crew-deployments', id })); }
2643
2880
  /** Deploy an agent or a workflow. `workspaceId`: where a deployed