@corenel/harness 0.22.0 → 0.24.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/approvals/service.d.ts +3 -0
- package/dist/approvals/service.d.ts.map +1 -1
- package/dist/approvals/service.js +13 -0
- package/dist/approvals/service.js.map +1 -1
- package/dist/approvals/suggest.js +1 -1
- package/dist/approvals/suggest.js.map +1 -1
- package/dist/chat/engine.d.ts +3 -0
- package/dist/chat/engine.d.ts.map +1 -1
- package/dist/chat/engine.js +10 -6
- package/dist/chat/engine.js.map +1 -1
- package/dist/chat/types.d.ts +1 -0
- package/dist/chat/types.d.ts.map +1 -1
- package/dist/core/loop.d.ts.map +1 -1
- package/dist/core/loop.js +138 -81
- package/dist/core/loop.js.map +1 -1
- package/dist/core/spawn.d.ts +3 -1
- package/dist/core/spawn.d.ts.map +1 -1
- package/dist/core/spawn.js +16 -1
- package/dist/core/spawn.js.map +1 -1
- package/dist/dag/executeNode.js +4 -4
- package/dist/dag/executeNode.js.map +1 -1
- package/dist/guardrail/builtins.d.ts.map +1 -1
- package/dist/guardrail/builtins.js +2 -0
- package/dist/guardrail/builtins.js.map +1 -1
- 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 +44 -3
- package/dist/guardrail/permission-service.d.ts.map +1 -1
- package/dist/guardrail/permission-service.js +58 -11
- 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.d.ts.map +1 -1
- package/dist/prompts/library/defaults.generated.js +15 -14
- 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 +18 -4
- package/dist/sessions/fileSessionStore.js.map +1 -1
- package/dist/sessions/jsonl.d.ts +1 -0
- package/dist/sessions/jsonl.d.ts.map +1 -1
- package/dist/sessions/jsonl.js +10 -0
- package/dist/sessions/jsonl.js.map +1 -1
- package/dist/sidecar/accessTestRig.d.ts +9 -2
- package/dist/sidecar/accessTestRig.d.ts.map +1 -1
- package/dist/sidecar/accessTestRig.js +13 -6
- package/dist/sidecar/accessTestRig.js.map +1 -1
- package/dist/sidecar/host.d.ts +96 -10
- package/dist/sidecar/host.d.ts.map +1 -1
- package/dist/sidecar/host.js +328 -6
- package/dist/sidecar/host.js.map +1 -1
- package/dist/sidecar/memberGate.d.ts.map +1 -1
- package/dist/sidecar/memberGate.js +17 -0
- package/dist/sidecar/memberGate.js.map +1 -1
- package/dist/sidecar/server.d.ts +171 -6
- package/dist/sidecar/server.d.ts.map +1 -1
- package/dist/sidecar/server.js +793 -65
- 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/templateGuard.d.ts +21 -0
- package/dist/templateGuard.d.ts.map +1 -0
- package/dist/templateGuard.js +150 -0
- package/dist/templateGuard.js.map +1 -0
- package/dist/tools/builtins.d.ts +1 -0
- package/dist/tools/builtins.d.ts.map +1 -1
- package/dist/tools/builtins.js +33 -3
- 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/namespaces.d.ts +1 -1
- package/dist/tools/namespaces.d.ts.map +1 -1
- package/dist/tools/namespaces.js +6 -2
- package/dist/tools/namespaces.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/skillTool.d.ts +29 -0
- package/dist/tools/skillTool.d.ts.map +1 -1
- package/dist/tools/skillTool.js +73 -10
- package/dist/tools/skillTool.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,10 @@
|
|
|
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
|
+
import { KNOWLEDGE_FEATURE, KNOWLEDGE_NOT_KEPT } from '@corenel/protocol';
|
|
2
3
|
import { asEcPublicJwk, jwkThumbprint, verifySignature, randomToken, serverProofPayload, clientProofPayload, parseEnrollmentCode, bytesToB64url, codeProof, codeHash, } from './deviceKeys.js';
|
|
3
4
|
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';
|
|
5
|
+
import { SIDECAR_PROTOCOL, HOST_METHODS, errMessage, ATTENDANCE_LEAVE_FEATURE, ATTENDANCE_JOIN_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, SIDECAR_KNOWLEDGE_TOOLS, encodePcm16, FLEET_LOGINS_DENIED, SIGN_IN_PAUSED_DENIED, isClerkOrgId, WORKFLOW_RUNNER_FEATURE, readWorkflowOkReply, readWorkflowResultsListReply, readWorkflowResultsTakeReply, } from '@corenel/protocol';
|
|
6
|
+
/** The sidecar knowledge tools a welcome may list, never registered as they are (see the welcome). */
|
|
7
|
+
const SIDECAR_KNOWLEDGE_NAMES = new Set(SIDECAR_KNOWLEDGE_TOOLS);
|
|
5
8
|
const NO_WORKFLOWS = 'this daemon does not run workflows';
|
|
6
9
|
const WRONG_REPLY = 'the daemon answered with the wrong reply';
|
|
7
10
|
/** A reply that failed its check (protocol workflowWire.ts readers). */
|
|
@@ -13,6 +16,11 @@ const MALFORMED_REPLY = 'the daemon sent a reply this app cannot read';
|
|
|
13
16
|
* slow link. */
|
|
14
17
|
export const WORKFLOW_REQUEST_TIMEOUT_MS = 60000;
|
|
15
18
|
export const WORKFLOW_PUT_TIMEOUT_MS = 120000;
|
|
19
|
+
/** After a fleet workflow call is stopped, how long the host waits for the sidecar's answer before it settles the
|
|
20
|
+
* call itself (a lost reply, a hung daemon, a cancel that did not land): Stop never hangs. Shorter than
|
|
21
|
+
* WORKFLOW_REQUEST_TIMEOUT_MS because a person is waiting on Stop. A late answer is dropped. */
|
|
22
|
+
export const FLEET_CALL_STOP_GRACE_MS = 15000;
|
|
23
|
+
export const FLEET_CALL_STOPPED = 'The fleet workflow call was stopped.';
|
|
16
24
|
const NO_ANSWER = 'the sidecar did not answer in time';
|
|
17
25
|
/** The longest root or path an `fs-changed` may name; a real one is far shorter. */
|
|
18
26
|
const MAX_FS_CHANGE_LEN = 4096;
|
|
@@ -306,6 +314,55 @@ export class SidecarHost {
|
|
|
306
314
|
writable: true,
|
|
307
315
|
value: new Map()
|
|
308
316
|
});
|
|
317
|
+
Object.defineProperty(this, "fleetItemsSeq", {
|
|
318
|
+
enumerable: true,
|
|
319
|
+
configurable: true,
|
|
320
|
+
writable: true,
|
|
321
|
+
value: 0
|
|
322
|
+
});
|
|
323
|
+
Object.defineProperty(this, "knowledgeSeq", {
|
|
324
|
+
enumerable: true,
|
|
325
|
+
configurable: true,
|
|
326
|
+
writable: true,
|
|
327
|
+
value: 0
|
|
328
|
+
});
|
|
329
|
+
Object.defineProperty(this, "knowledgePending", {
|
|
330
|
+
enumerable: true,
|
|
331
|
+
configurable: true,
|
|
332
|
+
writable: true,
|
|
333
|
+
value: new Map()
|
|
334
|
+
});
|
|
335
|
+
Object.defineProperty(this, "fleetItemsPending", {
|
|
336
|
+
enumerable: true,
|
|
337
|
+
configurable: true,
|
|
338
|
+
writable: true,
|
|
339
|
+
value: new Map()
|
|
340
|
+
});
|
|
341
|
+
/** One id sequence for the three fleet-use requests; each has its own reply map. */
|
|
342
|
+
Object.defineProperty(this, "fleetUseSeq", {
|
|
343
|
+
enumerable: true,
|
|
344
|
+
configurable: true,
|
|
345
|
+
writable: true,
|
|
346
|
+
value: 0
|
|
347
|
+
});
|
|
348
|
+
Object.defineProperty(this, "fleetSkillCatalogPending", {
|
|
349
|
+
enumerable: true,
|
|
350
|
+
configurable: true,
|
|
351
|
+
writable: true,
|
|
352
|
+
value: new Map()
|
|
353
|
+
});
|
|
354
|
+
Object.defineProperty(this, "fleetWorkflowCatalogPending", {
|
|
355
|
+
enumerable: true,
|
|
356
|
+
configurable: true,
|
|
357
|
+
writable: true,
|
|
358
|
+
value: new Map()
|
|
359
|
+
});
|
|
360
|
+
Object.defineProperty(this, "fleetWorkflowCallPending", {
|
|
361
|
+
enumerable: true,
|
|
362
|
+
configurable: true,
|
|
363
|
+
writable: true,
|
|
364
|
+
value: new Map()
|
|
365
|
+
});
|
|
309
366
|
Object.defineProperty(this, "crewDeploySeq", {
|
|
310
367
|
enumerable: true,
|
|
311
368
|
configurable: true,
|
|
@@ -722,6 +779,14 @@ export class SidecarHost {
|
|
|
722
779
|
writable: true,
|
|
723
780
|
value: []
|
|
724
781
|
});
|
|
782
|
+
/** The sidecar knowledge tools its welcome listed: never registered, called
|
|
783
|
+
* only through invokeKnowledgeTool. */
|
|
784
|
+
Object.defineProperty(this, "knowledgeTools", {
|
|
785
|
+
enumerable: true,
|
|
786
|
+
configurable: true,
|
|
787
|
+
writable: true,
|
|
788
|
+
value: new Set()
|
|
789
|
+
});
|
|
725
790
|
Object.defineProperty(this, "welcome", {
|
|
726
791
|
enumerable: true,
|
|
727
792
|
configurable: true,
|
|
@@ -917,6 +982,9 @@ export class SidecarHost {
|
|
|
917
982
|
for (const name of this.imported)
|
|
918
983
|
this.opts.unregisterTool?.(name);
|
|
919
984
|
this.imported.length = 0;
|
|
985
|
+
// A sidecar_knowledge_* tool listed by this connection's welcome is not
|
|
986
|
+
// listed by the next one until it says so again.
|
|
987
|
+
this.knowledgeTools.clear();
|
|
920
988
|
for (const p of this.invokePending.values())
|
|
921
989
|
p.reject(new Error(reason));
|
|
922
990
|
this.invokePending.clear();
|
|
@@ -985,6 +1053,21 @@ export class SidecarHost {
|
|
|
985
1053
|
for (const p of this.crewDeployPending.values())
|
|
986
1054
|
p.reject(new Error(reason));
|
|
987
1055
|
this.crewDeployPending.clear();
|
|
1056
|
+
for (const p of this.fleetItemsPending.values())
|
|
1057
|
+
p.reject(new Error(reason));
|
|
1058
|
+
this.fleetItemsPending.clear();
|
|
1059
|
+
for (const p of this.knowledgePending.values())
|
|
1060
|
+
p.reject(new Error(reason));
|
|
1061
|
+
this.knowledgePending.clear();
|
|
1062
|
+
for (const p of this.fleetSkillCatalogPending.values())
|
|
1063
|
+
p.reject(new Error(reason));
|
|
1064
|
+
this.fleetSkillCatalogPending.clear();
|
|
1065
|
+
for (const p of this.fleetWorkflowCatalogPending.values())
|
|
1066
|
+
p.reject(new Error(reason));
|
|
1067
|
+
this.fleetWorkflowCatalogPending.clear();
|
|
1068
|
+
for (const p of this.fleetWorkflowCallPending.values())
|
|
1069
|
+
p.reject(new Error(reason));
|
|
1070
|
+
this.fleetWorkflowCallPending.clear();
|
|
988
1071
|
for (const p of this.workflowsPending.values())
|
|
989
1072
|
p.reject(new Error(reason));
|
|
990
1073
|
this.workflowsPending.clear();
|
|
@@ -1115,7 +1198,20 @@ export class SidecarHost {
|
|
|
1115
1198
|
}
|
|
1116
1199
|
catch { /* the connection is up either way */ }
|
|
1117
1200
|
}
|
|
1201
|
+
// Only what THIS welcome lists may be invoked: a later welcome on the
|
|
1202
|
+
// same host (a reconnect) replaces the knowledge tools, never adds to them.
|
|
1203
|
+
this.knowledgeTools.clear();
|
|
1118
1204
|
for (const decl of m.tools) {
|
|
1205
|
+
/* The sidecar's knowledge tools are never offered to a model as
|
|
1206
|
+
* they are: that would bypass the knowledge gating (the run's host,
|
|
1207
|
+
* its account, its grant) and the knowledge injection thresholds.
|
|
1208
|
+
* The browser offers them as knowledge_* (src/knowledge/hostKnowledge.ts)
|
|
1209
|
+
* and runs them on this machine through invokeKnowledgeTool, so the
|
|
1210
|
+
* output is checked under the knowledge_* name the model called. */
|
|
1211
|
+
if (SIDECAR_KNOWLEDGE_NAMES.has(decl.name)) {
|
|
1212
|
+
this.knowledgeTools.add(decl.name);
|
|
1213
|
+
continue;
|
|
1214
|
+
}
|
|
1119
1215
|
const tool = { ...decl, run: (args) => this.invoke(decl.name, args) };
|
|
1120
1216
|
this.opts.registerTool(tool);
|
|
1121
1217
|
this.imported.push(decl.name);
|
|
@@ -1562,7 +1658,11 @@ export class SidecarHost {
|
|
|
1562
1658
|
if (!p)
|
|
1563
1659
|
return;
|
|
1564
1660
|
this.mcpAdminPending.delete(m.id);
|
|
1565
|
-
p.resolve({
|
|
1661
|
+
p.resolve({
|
|
1662
|
+
ok: true,
|
|
1663
|
+
...(typeof m.reloadError === 'string' ? { reloadError: m.reloadError } : {}),
|
|
1664
|
+
...(Array.isArray(m.servers) ? { servers: m.servers } : {}),
|
|
1665
|
+
});
|
|
1566
1666
|
return;
|
|
1567
1667
|
}
|
|
1568
1668
|
case 'mcp-admin-error': {
|
|
@@ -1575,7 +1675,11 @@ export class SidecarHost {
|
|
|
1575
1675
|
this.mcpAdminPending.delete(m.id);
|
|
1576
1676
|
// `reason` carried through so a caller can tell an ordinary refusal
|
|
1577
1677
|
// ("this server wants a pasted token") from a real failure.
|
|
1578
|
-
p.resolve(
|
|
1678
|
+
p.resolve({
|
|
1679
|
+
ok: false, error: m.error,
|
|
1680
|
+
...(m.reason ? { reason: m.reason } : {}),
|
|
1681
|
+
...(m.code ? { code: m.code } : {}),
|
|
1682
|
+
});
|
|
1579
1683
|
return;
|
|
1580
1684
|
}
|
|
1581
1685
|
/* A progress push, not a reply: the request stays pending. Delivered to
|
|
@@ -1702,6 +1806,51 @@ export class SidecarHost {
|
|
|
1702
1806
|
p.resolve(m.reply);
|
|
1703
1807
|
return;
|
|
1704
1808
|
}
|
|
1809
|
+
case 'knowledge-list-result':
|
|
1810
|
+
case 'knowledge-set-result':
|
|
1811
|
+
case 'knowledge-files-result':
|
|
1812
|
+
case 'knowledge-file-result':
|
|
1813
|
+
case 'knowledge-changes-result': {
|
|
1814
|
+
const p = this.knowledgePending.get(m.id);
|
|
1815
|
+
// A reply of another kind under this id answers nothing.
|
|
1816
|
+
if (!p || p.t !== m.t)
|
|
1817
|
+
return;
|
|
1818
|
+
this.knowledgePending.delete(m.id);
|
|
1819
|
+
p.resolve(m);
|
|
1820
|
+
return;
|
|
1821
|
+
}
|
|
1822
|
+
case 'fleet-items-result': {
|
|
1823
|
+
const p = this.fleetItemsPending.get(m.id);
|
|
1824
|
+
if (!p)
|
|
1825
|
+
return;
|
|
1826
|
+
this.fleetItemsPending.delete(m.id);
|
|
1827
|
+
p.resolve(m.reply);
|
|
1828
|
+
return;
|
|
1829
|
+
}
|
|
1830
|
+
case 'fleet-skill-catalog-result': {
|
|
1831
|
+
const p = this.fleetSkillCatalogPending.get(m.id);
|
|
1832
|
+
if (!p)
|
|
1833
|
+
return;
|
|
1834
|
+
this.fleetSkillCatalogPending.delete(m.id);
|
|
1835
|
+
p.resolve(m.reply);
|
|
1836
|
+
return;
|
|
1837
|
+
}
|
|
1838
|
+
case 'fleet-workflow-catalog-result': {
|
|
1839
|
+
const p = this.fleetWorkflowCatalogPending.get(m.id);
|
|
1840
|
+
if (!p)
|
|
1841
|
+
return;
|
|
1842
|
+
this.fleetWorkflowCatalogPending.delete(m.id);
|
|
1843
|
+
p.resolve(m.reply);
|
|
1844
|
+
return;
|
|
1845
|
+
}
|
|
1846
|
+
case 'fleet-workflow-call-result': {
|
|
1847
|
+
const p = this.fleetWorkflowCallPending.get(m.id);
|
|
1848
|
+
if (!p)
|
|
1849
|
+
return;
|
|
1850
|
+
this.fleetWorkflowCallPending.delete(m.id);
|
|
1851
|
+
p.resolve(m.reply);
|
|
1852
|
+
return;
|
|
1853
|
+
}
|
|
1705
1854
|
case 'workflow-package-result':
|
|
1706
1855
|
case 'workflow-results-list-result':
|
|
1707
1856
|
case 'workflow-results-take-result':
|
|
@@ -2421,6 +2570,39 @@ export class SidecarHost {
|
|
|
2421
2570
|
answerPermission(id, decision, note) {
|
|
2422
2571
|
this.ch.send({ t: 'permission-answer', id, decision, ...(note ? { note } : {}) });
|
|
2423
2572
|
}
|
|
2573
|
+
/** Stop attending on this connection while it stays open for this client's
|
|
2574
|
+
* own calls: the daemon sends it no more permission asks, and an ask nobody
|
|
2575
|
+
* else attends gets its unattended policy at once. Sent only to a daemon
|
|
2576
|
+
* that advertises it (an older one would drop it unread); false when not. */
|
|
2577
|
+
leaveAttendance() {
|
|
2578
|
+
if (!this.features.includes(ATTENDANCE_LEAVE_FEATURE))
|
|
2579
|
+
return false;
|
|
2580
|
+
this.ch.send({ t: 'attendance-leave' });
|
|
2581
|
+
return true;
|
|
2582
|
+
}
|
|
2583
|
+
/** Whether this daemon takes `attendance-join`, so a connection that left
|
|
2584
|
+
* attendance can attend again without a new dial. */
|
|
2585
|
+
canRejoinAttendance() {
|
|
2586
|
+
return this.features.includes(ATTENDANCE_LEAVE_FEATURE) && this.features.includes(ATTENDANCE_JOIN_FEATURE);
|
|
2587
|
+
}
|
|
2588
|
+
/** True while an ACP session (its start entry lives as long as the session)
|
|
2589
|
+
* or a dictation stream is open on this connection: its frames keep coming
|
|
2590
|
+
* over this socket and its stop goes through it, so a client must not set
|
|
2591
|
+
* the connection aside unseen while one is. */
|
|
2592
|
+
hasLiveStreams() {
|
|
2593
|
+
return this.acpPending.size > 0 || this.sttStreams.size > 0;
|
|
2594
|
+
}
|
|
2595
|
+
/** Attend again on this connection after `leaveAttendance`: the daemon
|
|
2596
|
+
* replays the asks this connection may see, the registrations, the live
|
|
2597
|
+
* runs and the recently settled task runs, through the usual handlers.
|
|
2598
|
+
* Sent only to a daemon that advertises it; false when not, and the caller
|
|
2599
|
+
* dials a new connection instead. */
|
|
2600
|
+
joinAttendance() {
|
|
2601
|
+
if (!this.canRejoinAttendance())
|
|
2602
|
+
return false;
|
|
2603
|
+
this.ch.send({ t: 'attendance-join' });
|
|
2604
|
+
return true;
|
|
2605
|
+
}
|
|
2424
2606
|
/** Push one definition file's content to the daemon, to be persisted under
|
|
2425
2607
|
* this client's own `<stateDir>/hosts/<dirName>/` (Slice 2 host sync).
|
|
2426
2608
|
* Resolves on `host-sync-result { ok: true }`; rejects with the daemon's
|
|
@@ -2584,8 +2766,8 @@ export class SidecarHost {
|
|
|
2584
2766
|
/** The MCP servers configured on this machine, with credential PRESENCE.
|
|
2585
2767
|
* There is no read path for a credential value anywhere in this feature. */
|
|
2586
2768
|
mcpServers() { return this.sendMcpAdmin({ t: 'mcp-servers-list' }); }
|
|
2587
|
-
/** Add a server to this machine's daemon.json.
|
|
2588
|
-
*
|
|
2769
|
+
/** Add a server to this machine's daemon.json. Resolves after the daemon
|
|
2770
|
+
* applied it (`reloadError` when it could not yet). */
|
|
2589
2771
|
mcpAddServer(name, url, hosted = false, authUrl) {
|
|
2590
2772
|
/* Each field sent only when it has a value, so the message an older daemon
|
|
2591
2773
|
* receives is byte-identical to what it received before these existed. */
|
|
@@ -2622,6 +2804,18 @@ export class SidecarHost {
|
|
|
2622
2804
|
mcpSetCredential(name, header, value) {
|
|
2623
2805
|
return this.sendMcpAdmin({ t: 'mcp-set-credential', name, header, value });
|
|
2624
2806
|
}
|
|
2807
|
+
/** Whether this daemon takes `mcpSetAllow`. */
|
|
2808
|
+
supportsMcpAllow() { return this.features.includes(MCP_ALLOW_FEATURE); }
|
|
2809
|
+
/** Switch which of server `name`'s tools this machine's `mcpAllow` grants:
|
|
2810
|
+
* exactly `tools`, replacing that server's entries. Empty turns them all
|
|
2811
|
+
* off. Resolves after the daemon applied it. Refused here, sending nothing,
|
|
2812
|
+
* against a daemon without the feature: an older one would never answer. */
|
|
2813
|
+
mcpSetAllow(name, tools) {
|
|
2814
|
+
if (!this.supportsMcpAllow()) {
|
|
2815
|
+
return Promise.resolve({ ok: false, error: 'this daemon cannot switch MCP tools -- update it and reconnect' });
|
|
2816
|
+
}
|
|
2817
|
+
return this.sendMcpAdmin({ t: 'mcp-allow-set', name, tools });
|
|
2818
|
+
}
|
|
2625
2819
|
sendMcpAdmin(body) {
|
|
2626
2820
|
const id = ++this.mcpSeq;
|
|
2627
2821
|
return new Promise((resolve, reject) => {
|
|
@@ -2687,6 +2881,134 @@ export class SidecarHost {
|
|
|
2687
2881
|
this.ch.send(send(id));
|
|
2688
2882
|
});
|
|
2689
2883
|
}
|
|
2884
|
+
/** True when the connected sidecar keeps its own knowledge folders and
|
|
2885
|
+
* answers the knowledge-* messages. */
|
|
2886
|
+
supportsKnowledge() { return this.features.includes(KNOWLEDGE_FEATURE); }
|
|
2887
|
+
/** Send one knowledge request and resolve with the reply of kind `t` under
|
|
2888
|
+
* its id. Against a sidecar without the feature nothing is sent. `ok: false`
|
|
2889
|
+
* is the sidecar's considered answer and resolves; only a dropped connection
|
|
2890
|
+
* rejects. */
|
|
2891
|
+
knowledgeCall(t, request, replyOf, unsupported) {
|
|
2892
|
+
if (!this.supportsKnowledge())
|
|
2893
|
+
return Promise.resolve(unsupported);
|
|
2894
|
+
const id = ++this.knowledgeSeq;
|
|
2895
|
+
return new Promise((resolve, reject) => {
|
|
2896
|
+
this.knowledgePending.set(id, {
|
|
2897
|
+
t,
|
|
2898
|
+
resolve: (m) => {
|
|
2899
|
+
const reply = replyOf(m);
|
|
2900
|
+
if (reply === undefined)
|
|
2901
|
+
reject(new Error(`unexpected ${m.t}`));
|
|
2902
|
+
else
|
|
2903
|
+
resolve(reply);
|
|
2904
|
+
},
|
|
2905
|
+
reject,
|
|
2906
|
+
});
|
|
2907
|
+
this.ch.send(request(id));
|
|
2908
|
+
});
|
|
2909
|
+
}
|
|
2910
|
+
/** Run one of the sidecar's knowledge tools (sidecar_knowledge_*), which the
|
|
2911
|
+
* welcome listed and nothing registered. Any other name, or one the welcome
|
|
2912
|
+
* did not list, is refused without sending. */
|
|
2913
|
+
invokeKnowledgeTool(name, args) {
|
|
2914
|
+
if (!SIDECAR_KNOWLEDGE_NAMES.has(name) || !this.knowledgeTools.has(name)) {
|
|
2915
|
+
return Promise.reject(new Error(`sidecar tool not connected: ${name}`));
|
|
2916
|
+
}
|
|
2917
|
+
return this.invoke(name, args);
|
|
2918
|
+
}
|
|
2919
|
+
/** The connected sidecar's knowledge folders, and whether this connection manages them. */
|
|
2920
|
+
knowledgeList() {
|
|
2921
|
+
return this.knowledgeCall('knowledge-list-result', (id) => ({ t: 'knowledge-list', id }), (m) => (m.t === 'knowledge-list-result' ? m.reply : undefined), { ok: false, error: KNOWLEDGE_NOT_KEPT });
|
|
2922
|
+
}
|
|
2923
|
+
/** Add one folder or remove one by id; answers the new list. Refused by the
|
|
2924
|
+
* sidecar unless this connection manages its folders. */
|
|
2925
|
+
knowledgeSet(change) {
|
|
2926
|
+
return this.knowledgeCall('knowledge-set-result', (id) => ({ t: 'knowledge-set', id, ...change }), (m) => (m.t === 'knowledge-set-result' ? m.reply : undefined), { ok: false, error: KNOWLEDGE_NOT_KEPT });
|
|
2927
|
+
}
|
|
2928
|
+
/** The files in one knowledge folder, by its id. */
|
|
2929
|
+
knowledgeFiles(folder) {
|
|
2930
|
+
return this.knowledgeCall('knowledge-files-result', (id) => ({ t: 'knowledge-files', id, folder }), (m) => (m.t === 'knowledge-files-result' ? m.reply : undefined), { ok: false, error: KNOWLEDGE_NOT_KEPT });
|
|
2931
|
+
}
|
|
2932
|
+
/** One file's text, or with `stat` its size and mtime, in one knowledge folder. */
|
|
2933
|
+
knowledgeFile(folder, path, stat = false) {
|
|
2934
|
+
return this.knowledgeCall('knowledge-file-result', (id) => ({ t: 'knowledge-file', id, folder, path, ...(stat ? { stat: true } : {}) }), (m) => (m.t === 'knowledge-file-result' ? m.reply : undefined), { ok: false, error: KNOWLEDGE_NOT_KEPT });
|
|
2935
|
+
}
|
|
2936
|
+
/** What was written into the sidecar's knowledge folders, newest first. */
|
|
2937
|
+
knowledgeChanges() {
|
|
2938
|
+
return this.knowledgeCall('knowledge-changes-result', (id) => ({ t: 'knowledge-changes', id }), (m) => (m.t === 'knowledge-changes-result' ? m.reply : undefined), { ok: false, error: KNOWLEDGE_NOT_KEPT });
|
|
2939
|
+
}
|
|
2940
|
+
/** True when the connected sidecar lists its fleet items. */
|
|
2941
|
+
supportsFleetItems() { return this.features.includes(FLEET_ITEMS_FEATURE); }
|
|
2942
|
+
/** The fleet agents, skills and workflows installed on the connected
|
|
2943
|
+
* sidecar. `ok: false` is its considered answer and resolves; only a dropped
|
|
2944
|
+
* connection rejects. Against a sidecar without the feature nothing is sent. */
|
|
2945
|
+
fleetItems() {
|
|
2946
|
+
if (!this.supportsFleetItems())
|
|
2947
|
+
return Promise.resolve({ ok: false, error: 'this sidecar does not list fleet items' });
|
|
2948
|
+
const id = ++this.fleetItemsSeq;
|
|
2949
|
+
return new Promise((resolve, reject) => {
|
|
2950
|
+
this.fleetItemsPending.set(id, { resolve, reject });
|
|
2951
|
+
this.ch.send({ t: 'fleet-items', id });
|
|
2952
|
+
});
|
|
2953
|
+
}
|
|
2954
|
+
/** True when the connected sidecar offers its fleet skills and workflows for use. */
|
|
2955
|
+
supportsFleetUse() { return this.features.includes(FLEET_USE_FEATURE); }
|
|
2956
|
+
/** The fleet skills the connected sidecar offers now. `ok: false` is its considered answer and resolves; only
|
|
2957
|
+
* a dropped connection rejects. Against a sidecar without the feature nothing is sent. */
|
|
2958
|
+
fleetSkillCatalog() {
|
|
2959
|
+
if (!this.supportsFleetUse())
|
|
2960
|
+
return Promise.resolve({ ok: false, error: 'this sidecar does not offer fleet items' });
|
|
2961
|
+
const id = ++this.fleetUseSeq;
|
|
2962
|
+
return new Promise((resolve, reject) => {
|
|
2963
|
+
this.fleetSkillCatalogPending.set(id, { resolve, reject });
|
|
2964
|
+
this.ch.send({ t: 'fleet-skill-catalog', id });
|
|
2965
|
+
});
|
|
2966
|
+
}
|
|
2967
|
+
/** The fleet workflows the connected sidecar can run now. Same contract as fleetSkillCatalog. */
|
|
2968
|
+
fleetWorkflowCatalog() {
|
|
2969
|
+
if (!this.supportsFleetUse())
|
|
2970
|
+
return Promise.resolve({ ok: false, error: 'this sidecar does not offer fleet items' });
|
|
2971
|
+
const id = ++this.fleetUseSeq;
|
|
2972
|
+
return new Promise((resolve, reject) => {
|
|
2973
|
+
this.fleetWorkflowCatalogPending.set(id, { resolve, reject });
|
|
2974
|
+
this.ch.send({ t: 'fleet-workflow-catalog', id });
|
|
2975
|
+
});
|
|
2976
|
+
}
|
|
2977
|
+
/** Run one fleet workflow on the connected sidecar and wait for its final text. Aborting `signal` sends
|
|
2978
|
+
* `fleet-workflow-cancel` and waits up to FLEET_CALL_STOP_GRACE_MS for the sidecar's answer (the run settles as
|
|
2979
|
+
* stopped); with none by then the call is forgotten (a late answer is dropped) and resolves FLEET_CALL_STOPPED.
|
|
2980
|
+
* An already-aborted signal sends nothing. `ok: false` resolves; only a dropped connection rejects. */
|
|
2981
|
+
callFleetWorkflow(name, inputs, signal) {
|
|
2982
|
+
if (!this.supportsFleetUse())
|
|
2983
|
+
return Promise.resolve({ ok: false, error: 'this sidecar does not offer fleet items' });
|
|
2984
|
+
if (signal.aborted)
|
|
2985
|
+
return Promise.resolve({ ok: false, error: FLEET_CALL_STOPPED });
|
|
2986
|
+
const id = ++this.fleetUseSeq;
|
|
2987
|
+
return new Promise((resolve, reject) => {
|
|
2988
|
+
let timer;
|
|
2989
|
+
const onAbort = () => {
|
|
2990
|
+
if (!this.fleetWorkflowCallPending.has(id))
|
|
2991
|
+
return;
|
|
2992
|
+
this.ch.send({ t: 'fleet-workflow-cancel', id });
|
|
2993
|
+
timer = setTimeout(() => {
|
|
2994
|
+
if (!this.fleetWorkflowCallPending.delete(id))
|
|
2995
|
+
return;
|
|
2996
|
+
resolve({ ok: false, error: FLEET_CALL_STOPPED });
|
|
2997
|
+
}, FLEET_CALL_STOP_GRACE_MS);
|
|
2998
|
+
};
|
|
2999
|
+
const settle = () => {
|
|
3000
|
+
signal.removeEventListener('abort', onAbort);
|
|
3001
|
+
if (timer !== undefined)
|
|
3002
|
+
clearTimeout(timer);
|
|
3003
|
+
};
|
|
3004
|
+
this.fleetWorkflowCallPending.set(id, {
|
|
3005
|
+
resolve: (r) => { settle(); resolve(r); },
|
|
3006
|
+
reject: (e) => { settle(); reject(e); },
|
|
3007
|
+
});
|
|
3008
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
3009
|
+
this.ch.send({ t: 'fleet-workflow-call', id, name, inputs });
|
|
3010
|
+
});
|
|
3011
|
+
}
|
|
2690
3012
|
/** The machine's deploy list, statuses and audit. */
|
|
2691
3013
|
crewDeployments() { return this.crewDeployRequest((id) => ({ t: 'crew-deployments', id })); }
|
|
2692
3014
|
/** Deploy an agent or a workflow. `workspaceId`: where a deployed
|