@corenel/harness 0.20.0 → 0.22.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 (137) hide show
  1. package/dist/chat/engine.d.ts +53 -0
  2. package/dist/chat/engine.d.ts.map +1 -0
  3. package/dist/chat/engine.js +885 -0
  4. package/dist/chat/engine.js.map +1 -0
  5. package/dist/chat/events.d.ts +5 -0
  6. package/dist/chat/events.d.ts.map +1 -0
  7. package/dist/chat/events.js +100 -0
  8. package/dist/chat/events.js.map +1 -0
  9. package/dist/chat/index.d.ts +9 -0
  10. package/dist/chat/index.d.ts.map +1 -0
  11. package/dist/chat/index.js +9 -0
  12. package/dist/chat/index.js.map +1 -0
  13. package/dist/chat/liveFold.d.ts +10 -0
  14. package/dist/chat/liveFold.d.ts.map +1 -0
  15. package/dist/chat/liveFold.js +31 -0
  16. package/dist/chat/liveFold.js.map +1 -0
  17. package/dist/chat/messages.d.ts +20 -0
  18. package/dist/chat/messages.d.ts.map +1 -0
  19. package/dist/chat/messages.js +56 -0
  20. package/dist/chat/messages.js.map +1 -0
  21. package/dist/chat/persist.d.ts +47 -0
  22. package/dist/chat/persist.d.ts.map +1 -0
  23. package/dist/chat/persist.js +118 -0
  24. package/dist/chat/persist.js.map +1 -0
  25. package/dist/chat/runWatchdog.d.ts +21 -0
  26. package/dist/chat/runWatchdog.d.ts.map +1 -0
  27. package/dist/chat/runWatchdog.js +92 -0
  28. package/dist/chat/runWatchdog.js.map +1 -0
  29. package/dist/chat/stitch.d.ts +26 -0
  30. package/dist/chat/stitch.d.ts.map +1 -0
  31. package/dist/chat/stitch.js +40 -0
  32. package/dist/chat/stitch.js.map +1 -0
  33. package/dist/chat/types.d.ts +163 -0
  34. package/dist/chat/types.d.ts.map +1 -0
  35. package/dist/chat/types.js +2 -0
  36. package/dist/chat/types.js.map +1 -0
  37. package/dist/core/loop.d.ts.map +1 -1
  38. package/dist/core/loop.js +50 -15
  39. package/dist/core/loop.js.map +1 -1
  40. package/dist/core/spawn.d.ts +10 -0
  41. package/dist/core/spawn.d.ts.map +1 -1
  42. package/dist/core/spawn.js +28 -5
  43. package/dist/core/spawn.js.map +1 -1
  44. package/dist/fileSchema.js +1 -1
  45. package/dist/guardrail/builtins.d.ts.map +1 -1
  46. package/dist/guardrail/builtins.js +11 -6
  47. package/dist/guardrail/builtins.js.map +1 -1
  48. package/dist/guardrail/guardrail.d.ts +8 -0
  49. package/dist/guardrail/guardrail.d.ts.map +1 -1
  50. package/dist/guardrail/guardrail.js +12 -0
  51. package/dist/guardrail/guardrail.js.map +1 -1
  52. package/dist/guardrail/index.d.ts +1 -1
  53. package/dist/guardrail/index.d.ts.map +1 -1
  54. package/dist/guardrail/index.js +1 -1
  55. package/dist/guardrail/index.js.map +1 -1
  56. package/dist/guardrail/serialize.d.ts.map +1 -1
  57. package/dist/guardrail/serialize.js +3 -0
  58. package/dist/guardrail/serialize.js.map +1 -1
  59. package/dist/guardrail/validate.d.ts.map +1 -1
  60. package/dist/guardrail/validate.js +4 -0
  61. package/dist/guardrail/validate.js.map +1 -1
  62. package/dist/orchestrator/sidecarTools.d.ts.map +1 -1
  63. package/dist/orchestrator/sidecarTools.js +8 -4
  64. package/dist/orchestrator/sidecarTools.js.map +1 -1
  65. package/dist/prompts/fitProject.d.ts +6 -0
  66. package/dist/prompts/fitProject.d.ts.map +1 -0
  67. package/dist/prompts/fitProject.js +6 -0
  68. package/dist/prompts/fitProject.js.map +1 -0
  69. package/dist/prompts/library/defaults.generated.js +9 -9
  70. package/dist/prompts/library/defaults.generated.js.map +1 -1
  71. package/dist/prompts/library/index.d.ts +2 -1
  72. package/dist/prompts/library/index.d.ts.map +1 -1
  73. package/dist/prompts/library/index.js +11 -16
  74. package/dist/prompts/library/index.js.map +1 -1
  75. package/dist/providers/gateway.d.ts +1 -0
  76. package/dist/providers/gateway.d.ts.map +1 -1
  77. package/dist/providers/gateway.js +1 -0
  78. package/dist/providers/gateway.js.map +1 -1
  79. package/dist/sessions/fileSessionStore.d.ts.map +1 -1
  80. package/dist/sessions/fileSessionStore.js.map +1 -1
  81. package/dist/sidecar/askUserBroker.d.ts +40 -0
  82. package/dist/sidecar/askUserBroker.d.ts.map +1 -0
  83. package/dist/sidecar/askUserBroker.js +103 -0
  84. package/dist/sidecar/askUserBroker.js.map +1 -0
  85. package/dist/sidecar/fleetJwe.d.ts +25 -0
  86. package/dist/sidecar/fleetJwe.d.ts.map +1 -0
  87. package/dist/sidecar/fleetJwe.js +115 -0
  88. package/dist/sidecar/fleetJwe.js.map +1 -0
  89. package/dist/sidecar/fsChanges.d.ts +23 -0
  90. package/dist/sidecar/fsChanges.d.ts.map +1 -0
  91. package/dist/sidecar/fsChanges.js +77 -0
  92. package/dist/sidecar/fsChanges.js.map +1 -0
  93. package/dist/sidecar/host.d.ts +91 -1
  94. package/dist/sidecar/host.d.ts.map +1 -1
  95. package/dist/sidecar/host.js +269 -6
  96. package/dist/sidecar/host.js.map +1 -1
  97. package/dist/sidecar/identity.d.ts +4 -0
  98. package/dist/sidecar/identity.d.ts.map +1 -1
  99. package/dist/sidecar/memberGate.d.ts.map +1 -1
  100. package/dist/sidecar/memberGate.js +5 -0
  101. package/dist/sidecar/memberGate.js.map +1 -1
  102. package/dist/sidecar/server.d.ts +61 -4
  103. package/dist/sidecar/server.d.ts.map +1 -1
  104. package/dist/sidecar/server.js +583 -13
  105. package/dist/sidecar/server.js.map +1 -1
  106. package/dist/systemParts.d.ts +0 -1
  107. package/dist/systemParts.d.ts.map +1 -1
  108. package/dist/systemParts.js +6 -2
  109. package/dist/systemParts.js.map +1 -1
  110. package/dist/todos.d.ts +8 -0
  111. package/dist/todos.d.ts.map +1 -1
  112. package/dist/todos.js +21 -17
  113. package/dist/todos.js.map +1 -1
  114. package/dist/tools/builtins.d.ts +14 -1
  115. package/dist/tools/builtins.d.ts.map +1 -1
  116. package/dist/tools/builtins.js +27 -3
  117. package/dist/tools/builtins.js.map +1 -1
  118. package/dist/tools/namespaces.d.ts +8 -0
  119. package/dist/tools/namespaces.d.ts.map +1 -1
  120. package/dist/tools/namespaces.js +18 -0
  121. package/dist/tools/namespaces.js.map +1 -1
  122. package/dist/tools/skillTool.d.ts.map +1 -1
  123. package/dist/tools/skillTool.js +22 -1
  124. package/dist/tools/skillTool.js.map +1 -1
  125. package/dist/tools/toolFacts.d.ts +9 -0
  126. package/dist/tools/toolFacts.d.ts.map +1 -0
  127. package/dist/tools/toolFacts.js +19 -0
  128. package/dist/tools/toolFacts.js.map +1 -0
  129. package/dist/tools/toolSearch.d.ts +25 -6
  130. package/dist/tools/toolSearch.d.ts.map +1 -1
  131. package/dist/tools/toolSearch.js +62 -14
  132. package/dist/tools/toolSearch.js.map +1 -1
  133. package/dist/tools/workflowTool.d.ts +34 -0
  134. package/dist/tools/workflowTool.d.ts.map +1 -0
  135. package/dist/tools/workflowTool.js +122 -0
  136. package/dist/tools/workflowTool.js.map +1 -0
  137. package/package.json +6 -3
@@ -0,0 +1,103 @@
1
+ /** One seat's delivery; a dead connection must not stop the others'. */
2
+ function deliver(seat, prompt) {
3
+ try {
4
+ seat.ask(prompt);
5
+ }
6
+ catch {
7
+ // The seat's own connection is gone; its leave follows.
8
+ }
9
+ }
10
+ export class AskUserBroker {
11
+ constructor() {
12
+ Object.defineProperty(this, "seq", {
13
+ enumerable: true,
14
+ configurable: true,
15
+ writable: true,
16
+ value: 0
17
+ });
18
+ Object.defineProperty(this, "seats", {
19
+ enumerable: true,
20
+ configurable: true,
21
+ writable: true,
22
+ value: new Set()
23
+ });
24
+ Object.defineProperty(this, "open", {
25
+ enumerable: true,
26
+ configurable: true,
27
+ writable: true,
28
+ value: new Map()
29
+ });
30
+ }
31
+ /** Seat a connection. Asks still open for its user are delivered to it at
32
+ * once, so a reconnecting device picks up a question already showing
33
+ * elsewhere. Returns the leave function. */
34
+ join(seat) {
35
+ this.seats.add(seat);
36
+ for (const a of this.open.values()) {
37
+ if (!a.users.has(seat.userId) || a.shown.has(seat))
38
+ continue;
39
+ a.shown.add(seat);
40
+ deliver(seat, a.prompt);
41
+ }
42
+ return () => {
43
+ this.seats.delete(seat);
44
+ for (const a of this.open.values())
45
+ a.shown.delete(seat);
46
+ };
47
+ }
48
+ /** Ask `users`. Resolves with the first answer from a seat it was shown to,
49
+ * or null once `signal` ends. Nobody seated yet is not an answer: the ask
50
+ * waits for one of them to join or for the signal. */
51
+ ask(q, signal) {
52
+ if (signal.aborted)
53
+ return Promise.resolve(null);
54
+ const users = new Set(q.users.filter((u) => typeof u === 'string' && u !== ''));
55
+ const id = ++this.seq;
56
+ const prompt = { id, runId: q.runId, agent: q.agent, questions: q.questions, deadline: q.deadline };
57
+ return new Promise((resolve) => {
58
+ const onAbort = () => { this.finish(id, null, 'timeout', null); };
59
+ const entry = {
60
+ prompt, users, shown: new Set(),
61
+ settle: (answers) => {
62
+ signal.removeEventListener('abort', onAbort);
63
+ resolve(answers);
64
+ },
65
+ };
66
+ this.open.set(id, entry);
67
+ signal.addEventListener('abort', onAbort, { once: true });
68
+ for (const seat of this.seats) {
69
+ if (!users.has(seat.userId))
70
+ continue;
71
+ entry.shown.add(seat);
72
+ deliver(seat, prompt);
73
+ }
74
+ });
75
+ }
76
+ /** `seat` answers ask `id`. Ignored, returning false, for an unknown or
77
+ * settled id or a seat the ask was never shown to. */
78
+ answer(id, seat, answers) {
79
+ const a = this.open.get(id);
80
+ if (!a || !a.shown.has(seat) || !this.seats.has(seat))
81
+ return false;
82
+ this.finish(id, answers, 'client', seat);
83
+ return true;
84
+ }
85
+ finish(id, answers, by, answeredBy) {
86
+ const a = this.open.get(id);
87
+ if (!a)
88
+ return;
89
+ this.open.delete(id);
90
+ a.settle(answers);
91
+ for (const seat of a.shown) {
92
+ if (seat === answeredBy)
93
+ continue;
94
+ try {
95
+ seat.resolved(id, by);
96
+ }
97
+ catch {
98
+ // One dead connection must not keep the others' prompts up.
99
+ }
100
+ }
101
+ }
102
+ }
103
+ //# sourceMappingURL=askUserBroker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"askUserBroker.js","sourceRoot":"","sources":["../../sidecar/askUserBroker.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ import type { EcPublicJwk } from '@corenel/protocol';
2
+ export interface EncryptionKeyPair {
3
+ publicJwk: EcPublicJwk;
4
+ privateKey: CryptoKey;
5
+ }
6
+ export declare const JWE_ALG = "ECDH-ES";
7
+ export declare const JWE_ENC = "A256GCM";
8
+ /** The Concat KDF of RFC 7518 4.6.2 (NIST SP 800-56A, SHA-256) for a key of at
9
+ * most 256 bits, so one hash round: SHA-256(counter 1 || Z || OtherInfo). For
10
+ * ECDH-ES direct agreement `algorithmId` is the `enc` value. */
11
+ export declare function concatKdf(z: Uint8Array, algorithmId: string, keyBits: number, apu?: Uint8Array, apv?: Uint8Array): Promise<Uint8Array<ArrayBuffer>>;
12
+ /** A fresh encryption key pair. The private JWK is for the caller to store
13
+ * (mode 0600, like the node signing key); load it back with importEncryptionPrivateKey. */
14
+ export declare function generateEncryptionKey(): Promise<{
15
+ publicJwk: EcPublicJwk;
16
+ privateJwk: JsonWebKey;
17
+ }>;
18
+ /** The stored private JWK as a non-extractable ECDH key that can only derive bits. */
19
+ export declare function importEncryptionPrivateKey(privateJwk: JsonWebKey): Promise<CryptoKey>;
20
+ /** `plaintext` sealed to `recipient`: compact JWE with header { alg: ECDH-ES, enc: A256GCM, typ, epk } and an empty encrypted key. */
21
+ export declare function sealJwe(recipient: EcPublicJwk, typ: string, plaintext: string): Promise<string>;
22
+ /** The plaintext if `jwe` is a compact JWE of purpose `typ` sealed to `privateKey`, else null. Never throws:
23
+ * a wrong key, a wrong typ, any other header member, a changed byte, or a malformed token is null. */
24
+ export declare function openJwe(privateKey: CryptoKey, typ: string, jwe: string): Promise<string | null>;
25
+ //# sourceMappingURL=fleetJwe.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fleetJwe.d.ts","sourceRoot":"","sources":["../../sidecar/fleetJwe.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAGrD,MAAM,WAAW,iBAAiB;IAAG,SAAS,EAAE,WAAW,CAAC;IAAC,UAAU,EAAE,SAAS,CAAA;CAAE;AAEpF,eAAO,MAAM,OAAO,YAAY,CAAC;AACjC,eAAO,MAAM,OAAO,YAAY,CAAC;AA0BjC;;gEAEgE;AAChE,wBAAsB,SAAS,CAC7B,CAAC,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,GAAE,UAA8B,EAAE,GAAG,GAAE,UAA8B,GAC5H,OAAO,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC,CAKlC;AAQD;2FAC2F;AAC3F,wBAAsB,qBAAqB,IAAI,OAAO,CAAC;IAAE,SAAS,EAAE,WAAW,CAAC;IAAC,UAAU,EAAE,UAAU,CAAA;CAAE,CAAC,CAMzG;AAED,sFAAsF;AACtF,wBAAsB,0BAA0B,CAAC,UAAU,EAAE,UAAU,GAAG,OAAO,CAAC,SAAS,CAAC,CAO3F;AAED,sIAAsI;AACtI,wBAAsB,OAAO,CAAC,SAAS,EAAE,WAAW,EAAE,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAiBrG;AAED;sGACsG;AACtG,wBAAsB,OAAO,CAAC,UAAU,EAAE,SAAS,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAwBrG"}
@@ -0,0 +1,115 @@
1
+ import { asEcPublicJwk, b64urlToBytes, bytesToB64url } from './deviceKeys.js';
2
+ export const JWE_ALG = 'ECDH-ES';
3
+ export const JWE_ENC = 'A256GCM';
4
+ const ECDH = { name: 'ECDH', namedCurve: 'P-256' };
5
+ const HEADER_KEYS = ['alg', 'enc', 'typ', 'epk'];
6
+ const IV_BYTES = 12;
7
+ const TAG_BYTES = 16;
8
+ const B64URL = /^[A-Za-z0-9_-]+$/;
9
+ const te = new TextEncoder();
10
+ const isObj = (v) => typeof v === 'object' && v !== null && !Array.isArray(v);
11
+ function concat(...parts) {
12
+ const out = new Uint8Array(parts.reduce((n, p) => n + p.length, 0));
13
+ let at = 0;
14
+ for (const p of parts) {
15
+ out.set(p, at);
16
+ at += p.length;
17
+ }
18
+ return out;
19
+ }
20
+ function u32(n) {
21
+ const out = new Uint8Array(4);
22
+ new DataView(out.buffer).setUint32(0, n);
23
+ return out;
24
+ }
25
+ const lengthPrefixed = (b) => concat(u32(b.length), b);
26
+ /** The Concat KDF of RFC 7518 4.6.2 (NIST SP 800-56A, SHA-256) for a key of at
27
+ * most 256 bits, so one hash round: SHA-256(counter 1 || Z || OtherInfo). For
28
+ * ECDH-ES direct agreement `algorithmId` is the `enc` value. */
29
+ export async function concatKdf(z, algorithmId, keyBits, apu = new Uint8Array(0), apv = new Uint8Array(0)) {
30
+ if (!Number.isInteger(keyBits) || keyBits < 8 || keyBits > 256 || keyBits % 8 !== 0)
31
+ throw new Error('concatKdf derives 8 to 256 bits, in whole bytes');
32
+ const otherInfo = concat(lengthPrefixed(te.encode(algorithmId)), lengthPrefixed(apu), lengthPrefixed(apv), u32(keyBits));
33
+ const digest = new Uint8Array(await crypto.subtle.digest('SHA-256', concat(u32(1), z, otherInfo)));
34
+ return digest.slice(0, keyBits / 8);
35
+ }
36
+ async function contentKey(privateKey, peer, usage) {
37
+ const pub = await crypto.subtle.importKey('jwk', { kty: peer.kty, crv: peer.crv, x: peer.x, y: peer.y }, ECDH, false, []);
38
+ const z = new Uint8Array(await crypto.subtle.deriveBits({ name: 'ECDH', public: pub }, privateKey, 256));
39
+ return crypto.subtle.importKey('raw', await concatKdf(z, JWE_ENC, 256), { name: 'AES-GCM' }, false, [usage]);
40
+ }
41
+ /** A fresh encryption key pair. The private JWK is for the caller to store
42
+ * (mode 0600, like the node signing key); load it back with importEncryptionPrivateKey. */
43
+ export async function generateEncryptionKey() {
44
+ const pair = await crypto.subtle.generateKey(ECDH, true, ['deriveBits']);
45
+ const privateJwk = { ...(await crypto.subtle.exportKey('jwk', pair.privateKey)), key_ops: ['deriveBits'] };
46
+ const publicJwk = asEcPublicJwk({ kty: privateJwk.kty, crv: privateJwk.crv, x: privateJwk.x, y: privateJwk.y });
47
+ if (!publicJwk || typeof privateJwk.d !== 'string')
48
+ throw new Error('not a P-256 key');
49
+ return { publicJwk, privateJwk };
50
+ }
51
+ /** The stored private JWK as a non-extractable ECDH key that can only derive bits. */
52
+ export async function importEncryptionPrivateKey(privateJwk) {
53
+ const pub = asEcPublicJwk({ kty: privateJwk.kty, crv: privateJwk.crv, x: privateJwk.x, y: privateJwk.y });
54
+ if (!pub || typeof privateJwk.d !== 'string')
55
+ throw new Error('not a P-256 private key');
56
+ /* Only a key stored as an encryption key imports. The node signing key is stored as bare { kty, crv, x, y, d } with
57
+ * no key_ops, so a missing key_ops is refused too: a signing key never doubles as a key-agreement key. */
58
+ if (!privateJwk.key_ops?.includes('deriveBits'))
59
+ throw new Error('not an encryption key');
60
+ return crypto.subtle.importKey('jwk', { kty: 'EC', crv: 'P-256', x: pub.x, y: pub.y, d: privateJwk.d }, ECDH, false, ['deriveBits']);
61
+ }
62
+ /** `plaintext` sealed to `recipient`: compact JWE with header { alg: ECDH-ES, enc: A256GCM, typ, epk } and an empty encrypted key. */
63
+ export async function sealJwe(recipient, typ, plaintext) {
64
+ const to = asEcPublicJwk(recipient);
65
+ if (!to)
66
+ throw new Error('the recipient is not a P-256 public key');
67
+ const eph = await crypto.subtle.generateKey(ECDH, true, ['deriveBits']);
68
+ const e = await crypto.subtle.exportKey('jwk', eph.publicKey);
69
+ const epk = asEcPublicJwk({ kty: e.kty, crv: e.crv, x: e.x, y: e.y });
70
+ if (!epk)
71
+ throw new Error('not a P-256 key');
72
+ const header = bytesToB64url(te.encode(JSON.stringify({ alg: JWE_ALG, enc: JWE_ENC, typ, epk })));
73
+ const iv = new Uint8Array(IV_BYTES);
74
+ crypto.getRandomValues(iv);
75
+ const key = await contentKey(eph.privateKey, to, 'encrypt');
76
+ const sealed = new Uint8Array(await crypto.subtle.encrypt({ name: 'AES-GCM', iv, additionalData: te.encode(header), tagLength: TAG_BYTES * 8 }, key, te.encode(plaintext)));
77
+ const ct = sealed.slice(0, sealed.length - TAG_BYTES);
78
+ const tag = sealed.slice(sealed.length - TAG_BYTES);
79
+ return `${header}..${bytesToB64url(iv)}.${bytesToB64url(ct)}.${bytesToB64url(tag)}`;
80
+ }
81
+ /** The plaintext if `jwe` is a compact JWE of purpose `typ` sealed to `privateKey`, else null. Never throws:
82
+ * a wrong key, a wrong typ, any other header member, a changed byte, or a malformed token is null. */
83
+ export async function openJwe(privateKey, typ, jwe) {
84
+ try {
85
+ if (typeof jwe !== 'string')
86
+ return null;
87
+ const parts = jwe.split('.');
88
+ if (parts.length !== 5)
89
+ return null;
90
+ const [h, encryptedKey, ivPart, ctPart, tagPart] = parts;
91
+ if (encryptedKey !== '' || !B64URL.test(h) || !B64URL.test(ivPart) || !B64URL.test(tagPart))
92
+ return null;
93
+ if (ctPart !== '' && !B64URL.test(ctPart))
94
+ return null;
95
+ const header = JSON.parse(new TextDecoder().decode(b64urlToBytes(h)));
96
+ if (!isObj(header) || Object.keys(header).some((k) => !HEADER_KEYS.includes(k)))
97
+ return null;
98
+ if (header.alg !== JWE_ALG || header.enc !== JWE_ENC || header.typ !== typ)
99
+ return null;
100
+ const epk = asEcPublicJwk(header.epk);
101
+ if (!epk)
102
+ return null;
103
+ const iv = new Uint8Array(b64urlToBytes(ivPart));
104
+ const tag = b64urlToBytes(tagPart);
105
+ if (iv.length !== IV_BYTES || tag.length !== TAG_BYTES)
106
+ return null;
107
+ const key = await contentKey(privateKey, epk, 'decrypt');
108
+ const plain = await crypto.subtle.decrypt({ name: 'AES-GCM', iv, additionalData: te.encode(h), tagLength: TAG_BYTES * 8 }, key, concat(b64urlToBytes(ctPart), tag));
109
+ return new TextDecoder('utf-8', { fatal: true }).decode(plain);
110
+ }
111
+ catch {
112
+ return null;
113
+ }
114
+ }
115
+ //# sourceMappingURL=fleetJwe.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fleetJwe.js","sourceRoot":"","sources":["../../sidecar/fleetJwe.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ import type { SidecarFsChange } from '@corenel/protocol';
2
+ /** One published change: what changed, and which connection changed it, so
3
+ * that connection is not told about its own write. */
4
+ export interface FsChangeEvent extends SidecarFsChange {
5
+ by: string;
6
+ }
7
+ export interface SidecarFsChanges {
8
+ publish(change: FsChangeEvent): void;
9
+ subscribe(cb: (change: FsChangeEvent) => void): () => void;
10
+ }
11
+ export declare class FsChangeBus implements SidecarFsChanges {
12
+ private readonly subs;
13
+ publish(change: FsChangeEvent): void;
14
+ subscribe(cb: (change: FsChangeEvent) => void): () => void;
15
+ }
16
+ /** What a file tool's `{ root?, path }` changed, said the way every client
17
+ * reads it: the path is resolved the way the tools confine it (against the
18
+ * named root, or the primary one, so `../other/x` and an absolute path both
19
+ * land where the tool wrote), then reported for each shared root that holds
20
+ * it, with the daemon's spelling of that root and the path under it in
21
+ * forward slashes. Nothing when no root holds it. */
22
+ export declare function fsChangesFor(roots: string[], primary: string | undefined, rootArg: unknown, path: string): SidecarFsChange[];
23
+ //# sourceMappingURL=fsChanges.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fsChanges.d.ts","sourceRoot":"","sources":["../../sidecar/fsChanges.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEzD;sDACsD;AACtD,MAAM,WAAW,aAAc,SAAQ,eAAe;IACpD,EAAE,EAAE,MAAM,CAAC;CACZ;AAED,MAAM,WAAW,gBAAgB;IAC/B,OAAO,CAAC,MAAM,EAAE,aAAa,GAAG,IAAI,CAAC;IACrC,SAAS,CAAC,EAAE,EAAE,CAAC,MAAM,EAAE,aAAa,KAAK,IAAI,GAAG,MAAM,IAAI,CAAC;CAC5D;AAED,qBAAa,WAAY,YAAW,gBAAgB;IAClD,OAAO,CAAC,QAAQ,CAAC,IAAI,CAA8C;IAEnE,OAAO,CAAC,MAAM,EAAE,aAAa,GAAG,IAAI;IAQpC,SAAS,CAAC,EAAE,EAAE,CAAC,MAAM,EAAE,aAAa,KAAK,IAAI,GAAG,MAAM,IAAI;CAI3D;AA6BD;;;;;qDAKqD;AACrD,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE,MAAM,GAAG,SAAS,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,eAAe,EAAE,CAgB5H"}
@@ -0,0 +1,77 @@
1
+ export class FsChangeBus {
2
+ constructor() {
3
+ Object.defineProperty(this, "subs", {
4
+ enumerable: true,
5
+ configurable: true,
6
+ writable: true,
7
+ value: new Set()
8
+ });
9
+ }
10
+ publish(change) {
11
+ // A copy, so a subscriber that unsubscribes (a connection closing) while
12
+ // this runs cannot skip the one after it.
13
+ for (const cb of [...this.subs]) {
14
+ try {
15
+ cb(change);
16
+ }
17
+ catch { /* one connection's failure must not stop the rest hearing it */ }
18
+ }
19
+ }
20
+ subscribe(cb) {
21
+ this.subs.add(cb);
22
+ return () => { this.subs.delete(cb); };
23
+ }
24
+ }
25
+ function lexical(p) {
26
+ const s = p.replace(/\\/g, '/');
27
+ const drive = /^([A-Za-z]):(\/|$)/.exec(s);
28
+ const head = drive ? `${drive[1].toUpperCase()}:` : s.startsWith('//') ? '//' : s.startsWith('/') ? '/' : null;
29
+ if (head === null)
30
+ return null;
31
+ const parts = [];
32
+ for (const seg of s.slice(drive ? 2 : head.length).split('/')) {
33
+ if (seg === '' || seg === '.')
34
+ continue;
35
+ if (seg === '..')
36
+ parts.pop();
37
+ else
38
+ parts.push(seg);
39
+ }
40
+ return { head, parts, drive: drive !== null };
41
+ }
42
+ /** `abs` lies inside `root`: the same head and root's segments as its prefix,
43
+ * without regard to letter case on a drive (a Windows root). */
44
+ function inside(root, abs) {
45
+ if (root.head !== abs.head || abs.parts.length <= root.parts.length)
46
+ return false;
47
+ const same = root.drive ? (a, b) => a.toLowerCase() === b.toLowerCase() : (a, b) => a === b;
48
+ return root.parts.every((seg, i) => same(seg, abs.parts[i]));
49
+ }
50
+ /** What a file tool's `{ root?, path }` changed, said the way every client
51
+ * reads it: the path is resolved the way the tools confine it (against the
52
+ * named root, or the primary one, so `../other/x` and an absolute path both
53
+ * land where the tool wrote), then reported for each shared root that holds
54
+ * it, with the daemon's spelling of that root and the path under it in
55
+ * forward slashes. Nothing when no root holds it. */
56
+ export function fsChangesFor(roots, primary, rootArg, path) {
57
+ if (!path)
58
+ return [];
59
+ let abs = lexical(path);
60
+ if (!abs) {
61
+ const named = typeof rootArg === 'string' && rootArg !== '' ? lexical(rootArg) : null;
62
+ const base = named ?? (primary ? lexical(primary) : null);
63
+ if (!base)
64
+ return [];
65
+ abs = lexical(`${base.head}${base.head.endsWith('/') ? '' : '/'}${[...base.parts, path.replace(/\\/g, '/')].join('/')}`);
66
+ if (!abs)
67
+ return [];
68
+ }
69
+ const out = [];
70
+ for (const root of roots) {
71
+ const r = lexical(root);
72
+ if (r && inside(r, abs))
73
+ out.push({ root, path: abs.parts.slice(r.parts.length).join('/') });
74
+ }
75
+ return out;
76
+ }
77
+ //# sourceMappingURL=fsChanges.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fsChanges.js","sourceRoot":"","sources":["../../sidecar/fsChanges.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,6 @@
1
- import type { AgentTool, IMemoryProvider, IKeyValueStore, ISearchProvider, ISessionStore, PermissionRequest, AskQuestion, AskAnswers, AgentEvent, PermissionDecision, DaemonRunActivity, DaemonRegistration, AgentPause, HostSummary, ProcInfo, RunRecordInfo, RunHistoryCrew, SessionInfo, SessionRepoSpec, SessionSnapshotInfo, CrewMemoryInfo, SidecarConfigEntry, WebPushSubscription, ChecksState, WorkspaceLockRef, TaskSpec, TaskAutonomy, RunPlanView, DaemonApprovalChoice, DaemonApprovalOffer, DaemonApprovalsState, CrewAgentRef, CrewDeployReply, DeviceSummary, AccessReply, NodeEvent, PeerMcpToolDecl, MemberReply, AccessOverviewReply, AgentCredReply, AgentCredStatusReply, AgentRunsReply, PeerReply, AgentCredHeader, McpBinding } from '@corenel/protocol';
1
+ import type { AgentTool, IMemoryProvider, IKeyValueStore, ISearchProvider, ISessionStore, PermissionRequest, AskQuestion, AskAnswers, AgentEvent, PermissionDecision, DaemonRunActivity, DaemonRegistration, AgentPause, HostSummary, ProcInfo, RunRecordInfo, RunHistoryCrew, SessionInfo, SessionRepoSpec, SessionSnapshotInfo, CrewMemoryInfo, SidecarConfigEntry, WebPushSubscription, ChecksState, WorkspaceLockRef, TaskSpec, TaskAutonomy, RunPlanView, DaemonApprovalChoice, DaemonApprovalOffer, DaemonApprovalsState, CrewAgentRef, CrewDeployReply, DeviceSummary, AccessReply, NodeEvent, PeerMcpToolDecl, MemberReply, AccessOverviewReply, AgentCredReply, AgentCredStatusReply, AgentRunsReply, PeerReply, AgentCredHeader, McpBinding, ChatSummary, ChatTurnSpec, StoredChat } from '@corenel/protocol';
2
+ import { type SidecarFsChange } from '@corenel/protocol';
3
+ import type { ChatTurnResult } from '../chat/types';
2
4
  import type { DontAsk, FileService } from '@corenel/protocol';
3
5
  import { type KeySigner } from './deviceKeys';
4
6
  import { type SpeechStreamMsg, type SidecarChannel, type SidecarMsg, type SidecarInfo, type AcpPromptPart, type AcpModeInfo, type AcpStartOptions, type RoomOpenRequest, type RoomSummary, type RoomFinding, type RoomFindingEdit, type DeployRef, type WorkflowRef, type WorkflowPackagePut, type WorkflowPackageReply, type WorkflowResultsListReply, type WorkflowResultsTakeReply, type WorkflowResultsAckReply, type WorkflowStopReply } from '@corenel/protocol';
@@ -176,6 +178,24 @@ export interface SidecarHostOptions {
176
178
  meta?: Record<string, string>;
177
179
  }) => void;
178
180
  }
181
+ /** One chat ask_user prompt from the daemon (Harness Chat Plan 2, R8). */
182
+ export interface SidecarAskUser {
183
+ id: number;
184
+ runId: string;
185
+ agent: string;
186
+ questions: AskQuestion[];
187
+ deadline: number;
188
+ }
189
+ /** How one daemon chat turn ended. A refused or failed turn resolves too;
190
+ * only a dropped connection rejects. */
191
+ export interface SidecarChatRunResult {
192
+ runId: string | null;
193
+ conversationId: string | null;
194
+ text: string;
195
+ stopReason: string;
196
+ outcome: ChatTurnResult['outcome'];
197
+ error?: string;
198
+ }
179
199
  /** `mcp-call` resolves `ok: false` for a policy refusal (`mcp-error`) rather
180
200
  * than rejecting -- see the field comment on `mcpCallPending` above for why. A
181
201
  * reject here is reserved for the transport dropping mid-call. */
@@ -220,6 +240,9 @@ export interface ProcWatcher {
220
240
  signal?: string;
221
241
  }) => void;
222
242
  }
243
+ /** An `fs-changed` message's change, or null when it is not one: a root and a
244
+ * path, each a non-empty string within MAX_FS_CHANGE_LEN with no NUL. */
245
+ export declare function fsChangeOf(m: unknown): SidecarFsChange | null;
223
246
  /** Prefix of the error a connection rejects with when the daemon's key is not
224
247
  * the one this client pinned (or it cannot prove it holds it). A caller shows
225
248
  * this state as its own, never as a generic connection failure, and never
@@ -251,6 +274,9 @@ export declare function isSidecarAccountRefusal(e: unknown): boolean;
251
274
  * (slice 4). The device is fine and stays paired; retrying changes nothing
252
275
  * until a fleet admin changes the policy. */
253
276
  export declare function isFleetLoginsRefusal(e: unknown): boolean;
277
+ /** The sidecar's fleet sign-in is paused for a reason that clears by itself (SIGN_IN_PAUSED_DENIED). The device is
278
+ * fine and stays paired, and unlike every other account refusal a retry is expected to succeed later. */
279
+ export declare function isSignInPausedRefusal(e: unknown): boolean;
254
280
  export declare class SidecarHost {
255
281
  private readonly ch;
256
282
  private readonly opts;
@@ -262,6 +288,16 @@ export declare class SidecarHost {
262
288
  private readonly runPending;
263
289
  private cancelSeq;
264
290
  private readonly cancelPending;
291
+ /** Daemon chat (Harness Chat Plan 2): run-inject, chat-list and chat-get replies, by request id. */
292
+ private chatSeq;
293
+ private readonly injectPending;
294
+ private readonly chatListPending;
295
+ private readonly chatGetPending;
296
+ /** Chat ask_user prompts not yet answered or resolved, by the daemon's ask id,
297
+ * so a handler registered after one arrived still sees it. */
298
+ private readonly askUserOpen;
299
+ private readonly askUserHandlers;
300
+ private readonly askUserResolvedHandlers;
265
301
  private acpSeq;
266
302
  private readonly acpPending;
267
303
  private syncSeq;
@@ -330,6 +366,9 @@ export declare class SidecarHost {
330
366
  /** Per-request consent-URL listeners, cleared when the request settles. */
331
367
  private readonly mcpLoginUrlWaiters;
332
368
  private features;
369
+ /** Who hears another client's file changes (`fs-changed`), for the life of
370
+ * this connection. */
371
+ private readonly fsChangeSubs;
333
372
  private sttSeq;
334
373
  private readonly sttStreams;
335
374
  private vapidKey;
@@ -655,6 +694,50 @@ export declare class SidecarHost {
655
694
  ok: boolean;
656
695
  reason?: 'no-such-run' | 'unsupported' | 'not-allowed';
657
696
  }>;
697
+ /** True when the connected daemon holds chats (Harness Chat Plan 2):
698
+ * `runChat`, `injectRun`, `listChats`, `getChat` and ask_user prompts. An
699
+ * older daemon would run a chat turn as a plain one-shot run, so check this
700
+ * before calling any of them. */
701
+ supportsChat(): boolean;
702
+ /** One turn of a daemon chat. `chat.conversationId` absent starts a new
703
+ * chat; the daemon mints its id and hands it to `onAccepted` with the run
704
+ * id. Resolves with how the turn ended, a refused or failed one included
705
+ * (with `error`); rejects only when the connection drops mid-turn. */
706
+ runChat(agent: string, text: string, chat: ChatTurnSpec, onEvent: (e: AgentEvent) => void, opts?: {
707
+ onAccepted?: (runId: string, conversationId: string) => void;
708
+ }): Promise<SidecarChatRunResult>;
709
+ /** Messages typed while a chat turn runs; `hard` interrupts it. Never
710
+ * rejects: a refusal or a dropped connection answers `ok: false`. */
711
+ injectRun(runId: string, messages: {
712
+ id: string;
713
+ text: string;
714
+ }[], hard: boolean): Promise<{
715
+ ok: boolean;
716
+ reason?: string;
717
+ }>;
718
+ /** This user's chats with `agent`; the admin may name a `member`. Rejects
719
+ * with the daemon's refusal. */
720
+ listChats(agent: string, opts?: {
721
+ ref?: CrewAgentRef;
722
+ member?: string;
723
+ }): Promise<ChatSummary[]>;
724
+ /** One conversation, to its member or the admin. Rejects with the daemon's
725
+ * refusal (CHAT_NOT_YOURS, CHAT_NOT_FOUND). */
726
+ getChat(agent: string, conversationId: string, opts?: {
727
+ ref?: CrewAgentRef;
728
+ member?: string;
729
+ }): Promise<StoredChat>;
730
+ /** Chat ask_user prompts for this user. Prompts still open when the handler
731
+ * is added are handed to it at once. Returns the unsubscribe function. */
732
+ onAskUser(handler: (ask: SidecarAskUser) => void): () => void;
733
+ /** A prompt was answered on another connection, or timed out: drop it. */
734
+ onAskUserResolved(handler: (r: {
735
+ id: number;
736
+ by: 'client' | 'timeout';
737
+ }) => void): () => void;
738
+ /** Answer prompt `id`; null declines. The first answer from any of this
739
+ * user's or the admin's connections wins; a late one is ignored. */
740
+ answerAskUser(id: number, answers: AskAnswers | null): void;
658
741
  /** True when the connected sidecar's runner supports cancelling a run. */
659
742
  supportsRunCancel(): boolean;
660
743
  /** True when the connected daemon fans a delegated callee's own node events
@@ -746,6 +829,13 @@ export declare class SidecarHost {
746
829
  * and every room-* message is refused server-side; feature-detecting here means
747
830
  * the browser never sends one it already knows will be refused. */
748
831
  supportsRooms(): boolean;
832
+ /** True when the sidecar announces other clients' file changes (`fs-changed`).
833
+ * An older one never does, and a listener here simply never fires. */
834
+ supportsFsChanged(): boolean;
835
+ /** Hear every file change another client of this daemon makes through its
836
+ * file tools: the root the tool used and the root-relative path. Never this
837
+ * client's own changes (the daemon leaves the writer out). */
838
+ onFsChanged(cb: (change: SidecarFsChange) => void): () => void;
749
839
  /** True when the connected daemon HOLDS agent pauses. Feature-detected from
750
840
  * the welcome, so a client offers "Pause" only where pausing would actually
751
841
  * stop the agent rather than reporting a success the daemon ignores. */