@corenel/harness 0.22.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 (75) hide show
  1. package/README.md +48 -0
  2. package/dist/chat/engine.d.ts +3 -0
  3. package/dist/chat/engine.d.ts.map +1 -1
  4. package/dist/chat/engine.js +5 -3
  5. package/dist/chat/engine.js.map +1 -1
  6. package/dist/core/loop.d.ts.map +1 -1
  7. package/dist/core/loop.js +134 -78
  8. package/dist/core/loop.js.map +1 -1
  9. package/dist/dag/executeNode.js +3 -3
  10. package/dist/guardrail/guardrail.d.ts +9 -0
  11. package/dist/guardrail/guardrail.d.ts.map +1 -1
  12. package/dist/guardrail/guardrail.js +13 -0
  13. package/dist/guardrail/guardrail.js.map +1 -1
  14. package/dist/guardrail/index.d.ts +3 -3
  15. package/dist/guardrail/index.d.ts.map +1 -1
  16. package/dist/guardrail/index.js +2 -2
  17. package/dist/guardrail/index.js.map +1 -1
  18. package/dist/guardrail/permission-service.d.ts +17 -3
  19. package/dist/guardrail/permission-service.d.ts.map +1 -1
  20. package/dist/guardrail/permission-service.js +11 -6
  21. package/dist/guardrail/permission-service.js.map +1 -1
  22. package/dist/guardrail/store.d.ts +4 -0
  23. package/dist/guardrail/store.d.ts.map +1 -1
  24. package/dist/guardrail/store.js +6 -0
  25. package/dist/guardrail/store.js.map +1 -1
  26. package/dist/prompts/config.d.ts +6 -2
  27. package/dist/prompts/config.d.ts.map +1 -1
  28. package/dist/prompts/config.js +6 -3
  29. package/dist/prompts/config.js.map +1 -1
  30. package/dist/prompts/host.d.ts +3 -2
  31. package/dist/prompts/host.d.ts.map +1 -1
  32. package/dist/prompts/host.js.map +1 -1
  33. package/dist/prompts/index.d.ts +8 -1
  34. package/dist/prompts/index.d.ts.map +1 -1
  35. package/dist/prompts/index.js +9 -7
  36. package/dist/prompts/index.js.map +1 -1
  37. package/dist/prompts/systemPrompts.d.ts +3 -0
  38. package/dist/prompts/systemPrompts.d.ts.map +1 -1
  39. package/dist/prompts/systemPrompts.js +1 -1
  40. package/dist/prompts/systemPrompts.js.map +1 -1
  41. package/dist/sidecar/host.d.ts +49 -10
  42. package/dist/sidecar/host.d.ts.map +1 -1
  43. package/dist/sidecar/host.js +194 -6
  44. package/dist/sidecar/host.js.map +1 -1
  45. package/dist/sidecar/memberGate.d.ts.map +1 -1
  46. package/dist/sidecar/memberGate.js +9 -0
  47. package/dist/sidecar/memberGate.js.map +1 -1
  48. package/dist/sidecar/server.d.ts +83 -6
  49. package/dist/sidecar/server.d.ts.map +1 -1
  50. package/dist/sidecar/server.js +348 -19
  51. package/dist/sidecar/server.js.map +1 -1
  52. package/dist/skills.d.ts +9 -1
  53. package/dist/skills.d.ts.map +1 -1
  54. package/dist/skills.js +22 -8
  55. package/dist/skills.js.map +1 -1
  56. package/dist/tools/builtins.d.ts.map +1 -1
  57. package/dist/tools/builtins.js +7 -0
  58. package/dist/tools/builtins.js.map +1 -1
  59. package/dist/tools/callAgent.d.ts +6 -2
  60. package/dist/tools/callAgent.d.ts.map +1 -1
  61. package/dist/tools/callAgent.js +7 -3
  62. package/dist/tools/callAgent.js.map +1 -1
  63. package/dist/tools/files.d.ts.map +1 -1
  64. package/dist/tools/files.js +4 -0
  65. package/dist/tools/files.js.map +1 -1
  66. package/dist/tools/sessions.d.ts.map +1 -1
  67. package/dist/tools/sessions.js +1 -0
  68. package/dist/tools/sessions.js.map +1 -1
  69. package/dist/tools/web.d.ts.map +1 -1
  70. package/dist/tools/web.js +19 -5
  71. package/dist/tools/web.js.map +1 -1
  72. package/dist/tools/workflowTool.d.ts +2 -18
  73. package/dist/tools/workflowTool.d.ts.map +1 -1
  74. package/dist/tools/workflowTool.js.map +1 -1
  75. package/package.json +3 -3
@@ -1,7 +1,7 @@
1
- import { CHAT_FEATURE, CHAT_ID_RE, FS_CHANGED_FEATURE } 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,6 +13,11 @@ 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';
17
22
  /** The longest root or path an `fs-changed` may name; a real one is far shorter. */
18
23
  const MAX_FS_CHANGE_LEN = 4096;
@@ -306,6 +311,43 @@ export class SidecarHost {
306
311
  writable: true,
307
312
  value: new Map()
308
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
+ });
309
351
  Object.defineProperty(this, "crewDeploySeq", {
310
352
  enumerable: true,
311
353
  configurable: true,
@@ -985,6 +1027,18 @@ export class SidecarHost {
985
1027
  for (const p of this.crewDeployPending.values())
986
1028
  p.reject(new Error(reason));
987
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();
988
1042
  for (const p of this.workflowsPending.values())
989
1043
  p.reject(new Error(reason));
990
1044
  this.workflowsPending.clear();
@@ -1562,7 +1616,11 @@ export class SidecarHost {
1562
1616
  if (!p)
1563
1617
  return;
1564
1618
  this.mcpAdminPending.delete(m.id);
1565
- 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
+ });
1566
1624
  return;
1567
1625
  }
1568
1626
  case 'mcp-admin-error': {
@@ -1575,7 +1633,11 @@ export class SidecarHost {
1575
1633
  this.mcpAdminPending.delete(m.id);
1576
1634
  // `reason` carried through so a caller can tell an ordinary refusal
1577
1635
  // ("this server wants a pasted token") from a real failure.
1578
- 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
+ });
1579
1641
  return;
1580
1642
  }
1581
1643
  /* A progress push, not a reply: the request stays pending. Delivered to
@@ -1702,6 +1764,38 @@ export class SidecarHost {
1702
1764
  p.resolve(m.reply);
1703
1765
  return;
1704
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
+ }
1705
1799
  case 'workflow-package-result':
1706
1800
  case 'workflow-results-list-result':
1707
1801
  case 'workflow-results-take-result':
@@ -2421,6 +2515,16 @@ export class SidecarHost {
2421
2515
  answerPermission(id, decision, note) {
2422
2516
  this.ch.send({ t: 'permission-answer', id, decision, ...(note ? { note } : {}) });
2423
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
+ }
2424
2528
  /** Push one definition file's content to the daemon, to be persisted under
2425
2529
  * this client's own `<stateDir>/hosts/<dirName>/` (Slice 2 host sync).
2426
2530
  * Resolves on `host-sync-result { ok: true }`; rejects with the daemon's
@@ -2584,8 +2688,8 @@ export class SidecarHost {
2584
2688
  /** The MCP servers configured on this machine, with credential PRESENCE.
2585
2689
  * There is no read path for a credential value anywhere in this feature. */
2586
2690
  mcpServers() { return this.sendMcpAdmin({ t: 'mcp-servers-list' }); }
2587
- /** Add a server to this machine's daemon.json. Takes effect on daemon
2588
- * 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). */
2589
2693
  mcpAddServer(name, url, hosted = false, authUrl) {
2590
2694
  /* Each field sent only when it has a value, so the message an older daemon
2591
2695
  * receives is byte-identical to what it received before these existed. */
@@ -2622,6 +2726,18 @@ export class SidecarHost {
2622
2726
  mcpSetCredential(name, header, value) {
2623
2727
  return this.sendMcpAdmin({ t: 'mcp-set-credential', name, header, value });
2624
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
+ }
2625
2741
  sendMcpAdmin(body) {
2626
2742
  const id = ++this.mcpSeq;
2627
2743
  return new Promise((resolve, reject) => {
@@ -2687,6 +2803,78 @@ export class SidecarHost {
2687
2803
  this.ch.send(send(id));
2688
2804
  });
2689
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
+ }
2690
2878
  /** The machine's deploy list, statuses and audit. */
2691
2879
  crewDeployments() { return this.crewDeployRequest((id) => ({ t: 'crew-deployments', id })); }
2692
2880
  /** Deploy an agent or a workflow. `workspaceId`: where a deployed