@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.
- package/README.md +48 -0
- package/dist/chat/engine.d.ts +9 -0
- package/dist/chat/engine.d.ts.map +1 -1
- package/dist/chat/engine.js +90 -6
- package/dist/chat/engine.js.map +1 -1
- package/dist/chat/persist.d.ts +8 -0
- package/dist/chat/persist.d.ts.map +1 -1
- package/dist/chat/persist.js +11 -0
- package/dist/chat/persist.js.map +1 -1
- package/dist/chat/runWatchdog.d.ts +1 -1
- package/dist/chat/runWatchdog.d.ts.map +1 -1
- package/dist/chat/runWatchdog.js +1 -1
- package/dist/chat/runWatchdog.js.map +1 -1
- package/dist/core/loop.d.ts.map +1 -1
- package/dist/core/loop.js +134 -78
- package/dist/core/loop.js.map +1 -1
- package/dist/dag/executeNode.js +3 -3
- package/dist/guardrail/guardrail.d.ts +9 -0
- package/dist/guardrail/guardrail.d.ts.map +1 -1
- package/dist/guardrail/guardrail.js +13 -0
- package/dist/guardrail/guardrail.js.map +1 -1
- package/dist/guardrail/index.d.ts +3 -3
- package/dist/guardrail/index.d.ts.map +1 -1
- package/dist/guardrail/index.js +2 -2
- package/dist/guardrail/index.js.map +1 -1
- package/dist/guardrail/permission-service.d.ts +17 -3
- package/dist/guardrail/permission-service.d.ts.map +1 -1
- package/dist/guardrail/permission-service.js +11 -6
- package/dist/guardrail/permission-service.js.map +1 -1
- package/dist/guardrail/store.d.ts +4 -0
- package/dist/guardrail/store.d.ts.map +1 -1
- package/dist/guardrail/store.js +6 -0
- package/dist/guardrail/store.js.map +1 -1
- package/dist/prompts/config.d.ts +6 -2
- package/dist/prompts/config.d.ts.map +1 -1
- package/dist/prompts/config.js +6 -3
- package/dist/prompts/config.js.map +1 -1
- package/dist/prompts/host.d.ts +3 -2
- package/dist/prompts/host.d.ts.map +1 -1
- package/dist/prompts/host.js.map +1 -1
- package/dist/prompts/index.d.ts +8 -1
- package/dist/prompts/index.d.ts.map +1 -1
- package/dist/prompts/index.js +9 -7
- package/dist/prompts/index.js.map +1 -1
- package/dist/prompts/library/defaults.generated.js +2 -2
- package/dist/prompts/library/defaults.generated.js.map +1 -1
- package/dist/prompts/systemPrompts.d.ts +3 -0
- package/dist/prompts/systemPrompts.d.ts.map +1 -1
- package/dist/prompts/systemPrompts.js +1 -1
- package/dist/prompts/systemPrompts.js.map +1 -1
- package/dist/sessions/fileSessionStore.d.ts.map +1 -1
- package/dist/sessions/fileSessionStore.js.map +1 -1
- package/dist/sidecar/fsChanges.d.ts +23 -0
- package/dist/sidecar/fsChanges.d.ts.map +1 -0
- package/dist/sidecar/fsChanges.js +77 -0
- package/dist/sidecar/fsChanges.js.map +1 -0
- package/dist/sidecar/host.d.ts +62 -9
- package/dist/sidecar/host.d.ts.map +1 -1
- package/dist/sidecar/host.js +243 -6
- package/dist/sidecar/host.js.map +1 -1
- package/dist/sidecar/memberGate.d.ts.map +1 -1
- package/dist/sidecar/memberGate.js +9 -0
- package/dist/sidecar/memberGate.js.map +1 -1
- package/dist/sidecar/server.d.ts +98 -6
- package/dist/sidecar/server.d.ts.map +1 -1
- package/dist/sidecar/server.js +408 -19
- package/dist/sidecar/server.js.map +1 -1
- package/dist/skills.d.ts +9 -1
- package/dist/skills.d.ts.map +1 -1
- package/dist/skills.js +22 -8
- package/dist/skills.js.map +1 -1
- package/dist/tools/builtins.d.ts +6 -1
- package/dist/tools/builtins.d.ts.map +1 -1
- package/dist/tools/builtins.js +13 -2
- package/dist/tools/builtins.js.map +1 -1
- package/dist/tools/callAgent.d.ts +6 -2
- package/dist/tools/callAgent.d.ts.map +1 -1
- package/dist/tools/callAgent.js +7 -3
- package/dist/tools/callAgent.js.map +1 -1
- package/dist/tools/files.d.ts.map +1 -1
- package/dist/tools/files.js +4 -0
- package/dist/tools/files.js.map +1 -1
- package/dist/tools/sessions.d.ts.map +1 -1
- package/dist/tools/sessions.js +1 -0
- package/dist/tools/sessions.js.map +1 -1
- package/dist/tools/web.d.ts.map +1 -1
- package/dist/tools/web.js +19 -5
- package/dist/tools/web.js.map +1 -1
- package/dist/tools/workflowTool.d.ts +2 -18
- package/dist/tools/workflowTool.d.ts.map +1 -1
- package/dist/tools/workflowTool.js.map +1 -1
- package/package.json +3 -3
package/dist/sidecar/host.js
CHANGED
|
@@ -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({
|
|
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(
|
|
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.
|
|
2539
|
-
*
|
|
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
|