@agentproto/runtime 0.5.0 → 0.6.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/LICENSE +202 -21
- package/README.md +2 -1
- package/dist/config.d.ts +123 -2
- package/dist/config.mjs +24 -1
- package/dist/config.mjs.map +1 -1
- package/dist/index.d.ts +417 -13
- package/dist/index.mjs +1705 -283
- package/dist/index.mjs.map +1 -1
- package/dist/providers-store.d.ts +1 -57
- package/dist/providers-store.mjs +1 -90
- package/dist/providers-store.mjs.map +1 -1
- package/dist/session-story.d.ts +113 -0
- package/dist/session-story.mjs +314 -0
- package/dist/session-story.mjs.map +1 -0
- package/dist/spawn-defaults-DAbADRd4.d.ts +256 -0
- package/dist/workspaces-config.d.ts +5 -1
- package/dist/workspaces-config.mjs +6 -1
- package/dist/workspaces-config.mjs.map +1 -1
- package/package.json +27 -19
- package/dist/spawn-defaults-d5gAhNkV.d.ts +0 -61
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { randomBytes, randomUUID, createHash } from 'crypto';
|
|
1
|
+
import { randomBytes, randomUUID, createHash, createHmac, timingSafeEqual } from 'crypto';
|
|
2
2
|
import { existsSync, mkdirSync, writeFileSync, renameSync, readFileSync, promises, createReadStream, createWriteStream, openSync, closeSync } from 'fs';
|
|
3
|
-
import { mkdir, writeFile, unlink, readdir, readFile, stat,
|
|
3
|
+
import { mkdir, writeFile, unlink, readdir, readFile, stat, chmod, rename, appendFile, rm, mkdtemp, access } from 'fs/promises';
|
|
4
4
|
import { resolve, join, dirname, isAbsolute, normalize, relative, basename } from 'path';
|
|
5
5
|
import { createMcpServer } from '@agentproto/mcp-server';
|
|
6
6
|
import { homedir, tmpdir } from 'os';
|
|
@@ -8,9 +8,10 @@ import { execFile, spawn } from 'child_process';
|
|
|
8
8
|
import { z } from 'zod';
|
|
9
9
|
import { EventEmitter } from 'events';
|
|
10
10
|
import { createInterface } from 'readline';
|
|
11
|
+
import { getModelProvider, resolvePricing } from '@agentproto/model-catalog/llm';
|
|
12
|
+
import * as providers_store_star from '@agentproto/providers-store';
|
|
11
13
|
import { makeAdapterLister, makeAdapterResolver, makeCredsStore, discoverAdapterPackages, makeSetupLedger, makeListTool, makeSetupTool } from '@agentproto/provider-kit';
|
|
12
14
|
import { SandboxSpecSchema, resolveLifecyclePolicy, createSandboxAgentSessionHost } from '@agentproto/sandbox';
|
|
13
|
-
import { resolvePricing } from '@agentproto/model-catalog/llm';
|
|
14
15
|
import matter from 'gray-matter';
|
|
15
16
|
import { createServer } from 'http';
|
|
16
17
|
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
|
|
@@ -29,12 +30,27 @@ import { ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
|
29
30
|
import { Cron } from 'croner';
|
|
30
31
|
import { promisify } from 'util';
|
|
31
32
|
import { createServer as createServer$1 } from 'net';
|
|
33
|
+
import { daemonHandshakeOverSink } from '@agentproto/acp/tunnel';
|
|
34
|
+
import { PairingError, encodeOfferUrl, HOSTED_RENDEZVOUS_URL, decodePairingHello, respondToHandshake, encodePairingMessage, derivePairRoot, deriveEpochRoutingToken, currentEpoch } from '@agentproto/secrets/pairing';
|
|
35
|
+
import { identityFingerprint } from '@agentproto/secrets/identity';
|
|
32
36
|
|
|
33
37
|
/**
|
|
34
38
|
* @agentproto/runtime v0.1.0-alpha
|
|
35
39
|
* Long-running gateway: MCP server + HTTP transport + HEARTBEAT autonomy + conversation persistence over a workspace dir.
|
|
36
40
|
*/
|
|
37
|
-
|
|
41
|
+
var __defProp = Object.defineProperty;
|
|
42
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
43
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
44
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
45
|
+
var __copyProps = (to, from, except, desc) => {
|
|
46
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
47
|
+
for (let key of __getOwnPropNames(from))
|
|
48
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
49
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
50
|
+
}
|
|
51
|
+
return to;
|
|
52
|
+
};
|
|
53
|
+
var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget);
|
|
38
54
|
async function writeRuntimeMeta(workspace, meta) {
|
|
39
55
|
const dir = join(workspace, ".agentproto");
|
|
40
56
|
try {
|
|
@@ -317,12 +333,12 @@ async function runCommand(input) {
|
|
|
317
333
|
return buf;
|
|
318
334
|
}
|
|
319
335
|
const room = STREAM_BUFFER_CAP - buf.length;
|
|
320
|
-
const
|
|
321
|
-
if (
|
|
336
|
+
const text6 = chunk.toString("utf8");
|
|
337
|
+
if (text6.length > room) {
|
|
322
338
|
truncated = true;
|
|
323
|
-
return buf +
|
|
339
|
+
return buf + text6.slice(0, room);
|
|
324
340
|
}
|
|
325
|
-
return buf +
|
|
341
|
+
return buf + text6;
|
|
326
342
|
}
|
|
327
343
|
child.stdout?.on("data", (d) => {
|
|
328
344
|
stdout = appendCapped(stdout, d);
|
|
@@ -727,14 +743,14 @@ function extractText(value) {
|
|
|
727
743
|
return null;
|
|
728
744
|
}
|
|
729
745
|
function formatToolResult(toolName, result, isError) {
|
|
730
|
-
const
|
|
746
|
+
const text6 = extractText(result);
|
|
731
747
|
if (isError) {
|
|
732
|
-
const message =
|
|
748
|
+
const message = text6 ?? (result != null ? JSON.stringify(result) : "failed");
|
|
733
749
|
const firstLine = message.split(/\r?\n/)[0] ?? message;
|
|
734
750
|
return truncate(firstLine, MAX_RESULT_LENGTH);
|
|
735
751
|
}
|
|
736
|
-
if (
|
|
737
|
-
const trimmed =
|
|
752
|
+
if (text6 == null) return null;
|
|
753
|
+
const trimmed = text6.trim();
|
|
738
754
|
if (!trimmed) return null;
|
|
739
755
|
const lines = trimmed.split(/\r?\n/);
|
|
740
756
|
if (lines.length > 1) {
|
|
@@ -750,20 +766,42 @@ function sessionTranscriptDir(sessionId, baseDir) {
|
|
|
750
766
|
function sessionEventsPath(sessionId, baseDir) {
|
|
751
767
|
return join(sessionTranscriptDir(sessionId, baseDir), "events.jsonl");
|
|
752
768
|
}
|
|
769
|
+
function highestSeqOnDisk(path) {
|
|
770
|
+
let raw;
|
|
771
|
+
try {
|
|
772
|
+
raw = readFileSync(path, "utf8");
|
|
773
|
+
} catch {
|
|
774
|
+
return 0;
|
|
775
|
+
}
|
|
776
|
+
let max = 0;
|
|
777
|
+
for (const line of raw.split("\n")) {
|
|
778
|
+
const trimmed = line.trim();
|
|
779
|
+
if (!trimmed) continue;
|
|
780
|
+
try {
|
|
781
|
+
const rec = JSON.parse(trimmed);
|
|
782
|
+
if (typeof rec.seq === "number" && rec.seq > max) max = rec.seq;
|
|
783
|
+
} catch {
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
return max;
|
|
787
|
+
}
|
|
753
788
|
function createTranscriptWriter(opts) {
|
|
754
789
|
const baseDir = opts?.baseDir;
|
|
755
790
|
const states = /* @__PURE__ */ new Map();
|
|
791
|
+
const listeners = /* @__PURE__ */ new Map();
|
|
756
792
|
const getState = (sessionId) => {
|
|
757
793
|
const existing = states.get(sessionId);
|
|
758
794
|
if (existing) return existing;
|
|
759
795
|
mkdirSync(sessionTranscriptDir(sessionId, baseDir), { recursive: true });
|
|
760
|
-
const
|
|
796
|
+
const eventsPath = sessionEventsPath(sessionId, baseDir);
|
|
797
|
+
const resumeSeq = highestSeqOnDisk(eventsPath);
|
|
798
|
+
const stream = createWriteStream(eventsPath, { flags: "a" });
|
|
761
799
|
stream.on("error", (err) => {
|
|
762
800
|
console.warn(`[transcript-writer] session ${sessionId}: ${err.message}`);
|
|
763
801
|
});
|
|
764
802
|
const state = {
|
|
765
803
|
stream,
|
|
766
|
-
seq:
|
|
804
|
+
seq: resumeSeq,
|
|
767
805
|
textBuf: "",
|
|
768
806
|
thoughtBuf: "",
|
|
769
807
|
textDebounce: null,
|
|
@@ -774,9 +812,12 @@ function createTranscriptWriter(opts) {
|
|
|
774
812
|
};
|
|
775
813
|
const writeRecord = (sessionId, state, record) => {
|
|
776
814
|
state.seq += 1;
|
|
777
|
-
state.
|
|
778
|
-
|
|
779
|
-
);
|
|
815
|
+
const full = { seq: state.seq, ts: (/* @__PURE__ */ new Date()).toISOString(), ...record };
|
|
816
|
+
state.stream.write(JSON.stringify(full) + "\n");
|
|
817
|
+
const subs = listeners.get(sessionId);
|
|
818
|
+
if (subs) {
|
|
819
|
+
for (const onRecord of subs) onRecord(full);
|
|
820
|
+
}
|
|
780
821
|
};
|
|
781
822
|
const flushTextBuf = (sessionId, state) => {
|
|
782
823
|
if (state.textDebounce) {
|
|
@@ -784,9 +825,9 @@ function createTranscriptWriter(opts) {
|
|
|
784
825
|
state.textDebounce = null;
|
|
785
826
|
}
|
|
786
827
|
if (!state.textBuf) return;
|
|
787
|
-
const
|
|
828
|
+
const text6 = state.textBuf;
|
|
788
829
|
state.textBuf = "";
|
|
789
|
-
writeRecord(sessionId, state, { kind: "text-delta", sessionId, text:
|
|
830
|
+
writeRecord(sessionId, state, { kind: "text-delta", sessionId, text: text6 });
|
|
790
831
|
};
|
|
791
832
|
const flushThoughtBuf = (sessionId, state) => {
|
|
792
833
|
if (state.thoughtDebounce) {
|
|
@@ -794,9 +835,9 @@ function createTranscriptWriter(opts) {
|
|
|
794
835
|
state.thoughtDebounce = null;
|
|
795
836
|
}
|
|
796
837
|
if (!state.thoughtBuf) return;
|
|
797
|
-
const
|
|
838
|
+
const text6 = state.thoughtBuf;
|
|
798
839
|
state.thoughtBuf = "";
|
|
799
|
-
writeRecord(sessionId, state, { kind: "thought", sessionId, text:
|
|
840
|
+
writeRecord(sessionId, state, { kind: "thought", sessionId, text: text6 });
|
|
800
841
|
};
|
|
801
842
|
const flushBuffers = (sessionId, state) => {
|
|
802
843
|
flushThoughtBuf(sessionId, state);
|
|
@@ -807,9 +848,9 @@ function createTranscriptWriter(opts) {
|
|
|
807
848
|
state.textDebounce = setTimeout(() => {
|
|
808
849
|
state.textDebounce = null;
|
|
809
850
|
if (!state.textBuf) return;
|
|
810
|
-
const
|
|
851
|
+
const text6 = state.textBuf;
|
|
811
852
|
state.textBuf = "";
|
|
812
|
-
writeRecord(sessionId, state, { kind: "text-delta", sessionId, text:
|
|
853
|
+
writeRecord(sessionId, state, { kind: "text-delta", sessionId, text: text6, partial: true });
|
|
813
854
|
}, DEBOUNCE_MS);
|
|
814
855
|
};
|
|
815
856
|
const scheduleThoughtDebounce = (sessionId, state) => {
|
|
@@ -817,17 +858,17 @@ function createTranscriptWriter(opts) {
|
|
|
817
858
|
state.thoughtDebounce = setTimeout(() => {
|
|
818
859
|
state.thoughtDebounce = null;
|
|
819
860
|
if (!state.thoughtBuf) return;
|
|
820
|
-
const
|
|
861
|
+
const text6 = state.thoughtBuf;
|
|
821
862
|
state.thoughtBuf = "";
|
|
822
|
-
writeRecord(sessionId, state, { kind: "thought", sessionId, text:
|
|
863
|
+
writeRecord(sessionId, state, { kind: "thought", sessionId, text: text6, partial: true });
|
|
823
864
|
}, DEBOUNCE_MS);
|
|
824
865
|
};
|
|
825
866
|
return {
|
|
826
867
|
recordPrompt(sessionId, message) {
|
|
827
868
|
const state = getState(sessionId);
|
|
828
869
|
flushBuffers(sessionId, state);
|
|
829
|
-
const
|
|
830
|
-
writeRecord(sessionId, state, { kind: "user-prompt", sessionId, text:
|
|
870
|
+
const text6 = typeof message === "string" ? message : JSON.stringify(message);
|
|
871
|
+
writeRecord(sessionId, state, { kind: "user-prompt", sessionId, text: text6 });
|
|
831
872
|
},
|
|
832
873
|
recordEvent(sessionId, evt) {
|
|
833
874
|
const state = getState(sessionId);
|
|
@@ -863,7 +904,12 @@ function createTranscriptWriter(opts) {
|
|
|
863
904
|
sessionId,
|
|
864
905
|
toolCallId: evt.toolCallId,
|
|
865
906
|
toolName: evt.toolName,
|
|
866
|
-
arguments: evt.arguments
|
|
907
|
+
arguments: evt.arguments,
|
|
908
|
+
// Carried through so a replay can tell an ENRICHMENT of an
|
|
909
|
+
// announced call from a genuine second call. Readers merge by
|
|
910
|
+
// toolCallId either way, but a record that silently drops the
|
|
911
|
+
// distinction can't be reasoned about after the fact.
|
|
912
|
+
...evt.isUpdate ? { isUpdate: true } : {}
|
|
867
913
|
});
|
|
868
914
|
break;
|
|
869
915
|
case "tool-result":
|
|
@@ -936,8 +982,8 @@ function createTranscriptWriter(opts) {
|
|
|
936
982
|
if (!state) return Promise.resolve();
|
|
937
983
|
flushBuffers(sessionId, state);
|
|
938
984
|
states.delete(sessionId);
|
|
939
|
-
return new Promise((
|
|
940
|
-
state.stream.end(() =>
|
|
985
|
+
return new Promise((resolve12) => {
|
|
986
|
+
state.stream.end(() => resolve12());
|
|
941
987
|
});
|
|
942
988
|
},
|
|
943
989
|
closeAll() {
|
|
@@ -946,10 +992,24 @@ function createTranscriptWriter(opts) {
|
|
|
946
992
|
const state = states.get(sessionId);
|
|
947
993
|
if (!state) continue;
|
|
948
994
|
flushBuffers(sessionId, state);
|
|
949
|
-
closings.push(new Promise((
|
|
995
|
+
closings.push(new Promise((resolve12) => state.stream.end(() => resolve12())));
|
|
950
996
|
}
|
|
951
997
|
states.clear();
|
|
952
998
|
return Promise.all(closings).then(() => void 0);
|
|
999
|
+
},
|
|
1000
|
+
subscribe(sessionId, onRecord) {
|
|
1001
|
+
let subs = listeners.get(sessionId);
|
|
1002
|
+
if (!subs) {
|
|
1003
|
+
subs = /* @__PURE__ */ new Set();
|
|
1004
|
+
listeners.set(sessionId, subs);
|
|
1005
|
+
}
|
|
1006
|
+
subs.add(onRecord);
|
|
1007
|
+
return () => {
|
|
1008
|
+
const current = listeners.get(sessionId);
|
|
1009
|
+
if (!current) return;
|
|
1010
|
+
current.delete(onRecord);
|
|
1011
|
+
if (current.size === 0) listeners.delete(sessionId);
|
|
1012
|
+
};
|
|
953
1013
|
}
|
|
954
1014
|
};
|
|
955
1015
|
}
|
|
@@ -961,10 +1021,10 @@ var ROLE_ICON = {
|
|
|
961
1021
|
tool: "\u{1F527} Tool",
|
|
962
1022
|
system: "\u2699\uFE0F System"
|
|
963
1023
|
};
|
|
964
|
-
function trunc(
|
|
965
|
-
if (
|
|
966
|
-
return
|
|
967
|
-
\u2026 [${
|
|
1024
|
+
function trunc(text6, n) {
|
|
1025
|
+
if (text6.length <= n) return text6;
|
|
1026
|
+
return text6.slice(0, n) + `
|
|
1027
|
+
\u2026 [${text6.length - n} chars truncated]`;
|
|
968
1028
|
}
|
|
969
1029
|
function renderMarkdown(session, opts = {}) {
|
|
970
1030
|
const { meta, messages } = session;
|
|
@@ -1065,9 +1125,9 @@ async function exportClaudeCodeSession(adapterSessionId, cwd) {
|
|
|
1065
1125
|
let stream;
|
|
1066
1126
|
try {
|
|
1067
1127
|
stream = createReadStream(filePath, { encoding: "utf8" });
|
|
1068
|
-
await new Promise((
|
|
1128
|
+
await new Promise((resolve12, reject) => {
|
|
1069
1129
|
stream.once("error", reject);
|
|
1070
|
-
stream.once("open",
|
|
1130
|
+
stream.once("open", resolve12);
|
|
1071
1131
|
});
|
|
1072
1132
|
} catch (err) {
|
|
1073
1133
|
const code = err.code;
|
|
@@ -1270,7 +1330,7 @@ async function exportHermesSession(adapterSessionId) {
|
|
|
1270
1330
|
}
|
|
1271
1331
|
async function defaultHermesRunner(adapterSessionId) {
|
|
1272
1332
|
const { spawn: spawn6 } = await import('child_process');
|
|
1273
|
-
return new Promise((
|
|
1333
|
+
return new Promise((resolve12, reject) => {
|
|
1274
1334
|
const chunks = [];
|
|
1275
1335
|
const errChunks = [];
|
|
1276
1336
|
const proc = spawn6(
|
|
@@ -1289,7 +1349,7 @@ async function defaultHermesRunner(adapterSessionId) {
|
|
|
1289
1349
|
)
|
|
1290
1350
|
);
|
|
1291
1351
|
} else {
|
|
1292
|
-
|
|
1352
|
+
resolve12(Buffer.concat(chunks).toString("utf8"));
|
|
1293
1353
|
}
|
|
1294
1354
|
});
|
|
1295
1355
|
});
|
|
@@ -1326,9 +1386,9 @@ async function exportDaemonEventsSession(sessionId, desc) {
|
|
|
1326
1386
|
let stream;
|
|
1327
1387
|
try {
|
|
1328
1388
|
stream = createReadStream(filePath, { encoding: "utf8" });
|
|
1329
|
-
await new Promise((
|
|
1389
|
+
await new Promise((resolve12, reject) => {
|
|
1330
1390
|
stream.once("error", reject);
|
|
1331
|
-
stream.once("open",
|
|
1391
|
+
stream.once("open", resolve12);
|
|
1332
1392
|
});
|
|
1333
1393
|
} catch (err) {
|
|
1334
1394
|
const code = err.code;
|
|
@@ -1398,10 +1458,10 @@ Either the session never drove an agent-cli turn, or it predates this feature.`
|
|
|
1398
1458
|
case "tool-result": {
|
|
1399
1459
|
flushAssistant();
|
|
1400
1460
|
const name = rec.toolCallId ? toolNameById.get(rec.toolCallId) : void 0;
|
|
1401
|
-
const
|
|
1461
|
+
const text6 = typeof rec.result === "string" ? rec.result : JSON.stringify(rec.result ?? "");
|
|
1402
1462
|
messages.push({
|
|
1403
1463
|
role: "tool",
|
|
1404
|
-
text: rec.isError ? `[error] ${
|
|
1464
|
+
text: rec.isError ? `[error] ${text6}` : text6,
|
|
1405
1465
|
...name ? { toolName: name } : {},
|
|
1406
1466
|
...tsOrUndefined !== void 0 ? { ts: tsOrUndefined } : {}
|
|
1407
1467
|
});
|
|
@@ -1571,6 +1631,11 @@ async function loadWorkspacesConfig(path = DEFAULT_CONFIG_PATH()) {
|
|
|
1571
1631
|
function findWorkspace(config, slug) {
|
|
1572
1632
|
return config.workspaces.find((w) => w.slug === sanitizeSlug(slug));
|
|
1573
1633
|
}
|
|
1634
|
+
function findWorkspaceByPath(config, dir) {
|
|
1635
|
+
const resolved = resolve(dir);
|
|
1636
|
+
const candidates = config.workspaces.filter((w) => resolved.startsWith(resolve(w.path) + "/") || resolved === resolve(w.path)).sort((a, b) => b.path.length - a.path.length);
|
|
1637
|
+
return candidates[0];
|
|
1638
|
+
}
|
|
1574
1639
|
function getActiveWorkspace(config) {
|
|
1575
1640
|
if (!config.active) return config.workspaces[0];
|
|
1576
1641
|
return findWorkspace(config, config.active) ?? config.workspaces[0];
|
|
@@ -1609,13 +1674,36 @@ function normalizeConfig(parsed) {
|
|
|
1609
1674
|
return out;
|
|
1610
1675
|
}
|
|
1611
1676
|
var CONFIG_FILE_PATH = () => join(homedir(), ".agentproto", "config.json");
|
|
1677
|
+
function sanitizeAcpAgents(raw, target) {
|
|
1678
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
1679
|
+
console.warn(
|
|
1680
|
+
`[runtime/config] ${target}: 'acpAgents' is not an object \u2014 ignoring`
|
|
1681
|
+
);
|
|
1682
|
+
return void 0;
|
|
1683
|
+
}
|
|
1684
|
+
const out = {};
|
|
1685
|
+
for (const [slug, value] of Object.entries(raw)) {
|
|
1686
|
+
if (value && typeof value === "object" && !Array.isArray(value) && typeof value.bin === "string" && value.bin.length > 0) {
|
|
1687
|
+
out[slug] = value;
|
|
1688
|
+
} else {
|
|
1689
|
+
console.warn(
|
|
1690
|
+
`[runtime/config] ${target}: acpAgents.${slug} is missing a string 'bin' \u2014 ignoring`
|
|
1691
|
+
);
|
|
1692
|
+
}
|
|
1693
|
+
}
|
|
1694
|
+
return Object.keys(out).length > 0 ? out : void 0;
|
|
1695
|
+
}
|
|
1612
1696
|
async function loadConfig(path) {
|
|
1613
1697
|
const target = CONFIG_FILE_PATH();
|
|
1614
1698
|
try {
|
|
1615
1699
|
const raw = await promises.readFile(target, "utf8");
|
|
1616
1700
|
const parsed = JSON.parse(raw);
|
|
1617
1701
|
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
1618
|
-
|
|
1702
|
+
const cfg = parsed;
|
|
1703
|
+
if (cfg.acpAgents !== void 0) {
|
|
1704
|
+
cfg.acpAgents = sanitizeAcpAgents(cfg.acpAgents, target);
|
|
1705
|
+
}
|
|
1706
|
+
return cfg;
|
|
1619
1707
|
}
|
|
1620
1708
|
console.warn(
|
|
1621
1709
|
`[runtime/config] ${target}: top-level value is not an object \u2014 ignoring`
|
|
@@ -1632,6 +1720,10 @@ async function loadConfig(path) {
|
|
|
1632
1720
|
}
|
|
1633
1721
|
}
|
|
1634
1722
|
|
|
1723
|
+
// src/providers-store.ts
|
|
1724
|
+
var providers_store_exports = {};
|
|
1725
|
+
__reExport(providers_store_exports, providers_store_star);
|
|
1726
|
+
|
|
1635
1727
|
// src/spawn-defaults.ts
|
|
1636
1728
|
function resolveSpawnDefaults(defaults, adapterSlug, input) {
|
|
1637
1729
|
const adapterDefaults = defaults?.adapters?.[adapterSlug];
|
|
@@ -1643,7 +1735,111 @@ function resolveSpawnDefaults(defaults, adapterSlug, input) {
|
|
|
1643
1735
|
const skills = input.skills !== void 0 ? input.skills : Array.from(
|
|
1644
1736
|
/* @__PURE__ */ new Set([...defaults?.skills ?? [], ...adapterDefaults?.skills ?? []])
|
|
1645
1737
|
);
|
|
1646
|
-
|
|
1738
|
+
const requestedMode = input.auth?.mode ?? adapterDefaults?.auth?.mode;
|
|
1739
|
+
const explicit = input.auth !== void 0 || adapterDefaults?.auth !== void 0;
|
|
1740
|
+
const subscriptionCredential = input.auth?.token ?? adapterDefaults?.auth?.token;
|
|
1741
|
+
const apiKeyCredential = input.auth?.apiKey ?? adapterDefaults?.auth?.apiKey;
|
|
1742
|
+
const authProvider = input.auth?.provider ?? adapterDefaults?.auth?.provider;
|
|
1743
|
+
return {
|
|
1744
|
+
skills,
|
|
1745
|
+
options,
|
|
1746
|
+
auth: {
|
|
1747
|
+
explicit,
|
|
1748
|
+
...requestedMode ? { requestedMode } : {},
|
|
1749
|
+
...subscriptionCredential !== void 0 ? { subscriptionCredential } : {},
|
|
1750
|
+
...apiKeyCredential !== void 0 ? { apiKeyCredential } : {},
|
|
1751
|
+
...authProvider ? { provider: authProvider } : {}
|
|
1752
|
+
}
|
|
1753
|
+
};
|
|
1754
|
+
}
|
|
1755
|
+
var CREDENTIAL_FINGERPRINT_PREFIXES = [
|
|
1756
|
+
"sk-ant-oat",
|
|
1757
|
+
"sk-ant-api",
|
|
1758
|
+
"sk-ant-",
|
|
1759
|
+
"sk-or-v1-",
|
|
1760
|
+
"sk-or-",
|
|
1761
|
+
"sk-proj-",
|
|
1762
|
+
"sk-",
|
|
1763
|
+
"gsk_",
|
|
1764
|
+
"AIza"
|
|
1765
|
+
];
|
|
1766
|
+
function credentialFingerprint(mode, credential) {
|
|
1767
|
+
const prefix = CREDENTIAL_FINGERPRINT_PREFIXES.find((p) => credential.startsWith(p));
|
|
1768
|
+
const last4 = credential.slice(-4);
|
|
1769
|
+
return prefix ? `${mode} \xB7 ${prefix}\u2026${last4}` : `${mode} \xB7 \u2026${last4}`;
|
|
1770
|
+
}
|
|
1771
|
+
var AuthResolutionError = class extends Error {
|
|
1772
|
+
code = "unsupported_auth_mode";
|
|
1773
|
+
constructor(message) {
|
|
1774
|
+
super(message);
|
|
1775
|
+
this.name = "AuthResolutionError";
|
|
1776
|
+
}
|
|
1777
|
+
};
|
|
1778
|
+
function resolveAuthSpec(input) {
|
|
1779
|
+
const provider = input.requestedProvider ?? input.descriptor.provider ?? (input.model ? getModelProvider(input.model) : void 0);
|
|
1780
|
+
if (!provider) return void 0;
|
|
1781
|
+
const sub = input.descriptor.authSubscription;
|
|
1782
|
+
const supportsSub = sub !== void 0;
|
|
1783
|
+
const enforce = input.descriptor.authEnforce ?? "when-configured";
|
|
1784
|
+
const apiKeyEnv = (0, providers_store_exports.providerEnvVar)(provider);
|
|
1785
|
+
const subCredAvailable = input.subscriptionCredential !== void 0;
|
|
1786
|
+
const apiCredAvailable = input.apiKeyConfigCredential !== void 0 || input.apiKeyStoreCredential !== void 0;
|
|
1787
|
+
let mode;
|
|
1788
|
+
if (input.requestedMode) {
|
|
1789
|
+
if (input.requestedMode === "subscription" && !supportsSub) {
|
|
1790
|
+
throw new AuthResolutionError(
|
|
1791
|
+
`auth mode "subscription" is not supported for provider "${provider}" (this adapter declares no authSubscription) \u2014 use api-key instead.`
|
|
1792
|
+
);
|
|
1793
|
+
}
|
|
1794
|
+
mode = input.requestedMode;
|
|
1795
|
+
} else {
|
|
1796
|
+
const preference = supportsSub ? ["subscription", "api-key"] : ["api-key"];
|
|
1797
|
+
mode = preference.find((m) => m === "subscription" ? subCredAvailable : apiCredAvailable) ?? preference[0];
|
|
1798
|
+
}
|
|
1799
|
+
let setEnv;
|
|
1800
|
+
let credential;
|
|
1801
|
+
let credentialSource;
|
|
1802
|
+
if (mode === "subscription") {
|
|
1803
|
+
setEnv = sub.setEnv;
|
|
1804
|
+
credential = input.subscriptionCredential;
|
|
1805
|
+
credentialSource = credential !== void 0 ? "explicit-config" : "none";
|
|
1806
|
+
} else {
|
|
1807
|
+
setEnv = apiKeyEnv;
|
|
1808
|
+
if (input.apiKeyConfigCredential !== void 0) {
|
|
1809
|
+
credential = input.apiKeyConfigCredential;
|
|
1810
|
+
credentialSource = "explicit-config";
|
|
1811
|
+
} else if (input.apiKeyStoreCredential !== void 0) {
|
|
1812
|
+
credential = input.apiKeyStoreCredential;
|
|
1813
|
+
credentialSource = "providers-store";
|
|
1814
|
+
} else {
|
|
1815
|
+
credential = void 0;
|
|
1816
|
+
credentialSource = "none";
|
|
1817
|
+
}
|
|
1818
|
+
}
|
|
1819
|
+
const credVars = /* @__PURE__ */ new Set([apiKeyEnv]);
|
|
1820
|
+
if (sub) {
|
|
1821
|
+
credVars.add(sub.setEnv);
|
|
1822
|
+
for (const c of sub.conflictEnv ?? []) credVars.add(c);
|
|
1823
|
+
}
|
|
1824
|
+
credVars.delete(setEnv);
|
|
1825
|
+
const unsetEnv = [...credVars];
|
|
1826
|
+
if (mode === "subscription" && sub?.unsetEnvAdd) unsetEnv.push(...sub.unsetEnvAdd);
|
|
1827
|
+
const spec = {
|
|
1828
|
+
mode,
|
|
1829
|
+
...credential !== void 0 ? { credential } : {},
|
|
1830
|
+
setEnv,
|
|
1831
|
+
unsetEnv,
|
|
1832
|
+
explicit: input.explicit,
|
|
1833
|
+
enforce
|
|
1834
|
+
};
|
|
1835
|
+
const echo = {
|
|
1836
|
+
provider,
|
|
1837
|
+
authMode: mode,
|
|
1838
|
+
credentialSource,
|
|
1839
|
+
setEnv,
|
|
1840
|
+
...credential !== void 0 ? { fingerprint: credentialFingerprint(mode, credential) } : {}
|
|
1841
|
+
};
|
|
1842
|
+
return { spec, echo };
|
|
1647
1843
|
}
|
|
1648
1844
|
function normalizeSkillsOption(skills, options, declaredOptions) {
|
|
1649
1845
|
if (skills.length === 0 || "skills" in options) return options;
|
|
@@ -1868,8 +2064,8 @@ var MAX_OUTPUT_LINES = 500;
|
|
|
1868
2064
|
function extractPromptText(message) {
|
|
1869
2065
|
if (typeof message === "string") return message;
|
|
1870
2066
|
if (message && typeof message === "object" && "text" in message) {
|
|
1871
|
-
const
|
|
1872
|
-
if (typeof
|
|
2067
|
+
const text6 = message.text;
|
|
2068
|
+
if (typeof text6 === "string") return text6;
|
|
1873
2069
|
}
|
|
1874
2070
|
return JSON.stringify(message);
|
|
1875
2071
|
}
|
|
@@ -1928,6 +2124,16 @@ function createSandboxAgentSessionProxy(opts) {
|
|
|
1928
2124
|
}
|
|
1929
2125
|
|
|
1930
2126
|
// src/session-spawn.ts
|
|
2127
|
+
var SPAWN_CLAIM_WINDOW_MS = 3e4;
|
|
2128
|
+
var spawnClaimsByRegistry = /* @__PURE__ */ new WeakMap();
|
|
2129
|
+
function claimsFor(registry) {
|
|
2130
|
+
let claims = spawnClaimsByRegistry.get(registry);
|
|
2131
|
+
if (!claims) {
|
|
2132
|
+
claims = /* @__PURE__ */ new Map();
|
|
2133
|
+
spawnClaimsByRegistry.set(registry, claims);
|
|
2134
|
+
}
|
|
2135
|
+
return claims;
|
|
2136
|
+
}
|
|
1931
2137
|
function cleanAgentLines(lines) {
|
|
1932
2138
|
return lines.map((l) => l.replace(/\x1b\[[0-9;]*m/g, "")).filter((l) => {
|
|
1933
2139
|
const t = l.trim();
|
|
@@ -1947,18 +2153,26 @@ async function spawnAgentSession(deps2, input) {
|
|
|
1947
2153
|
resolveSandboxProvider: resolveSandboxProvider2
|
|
1948
2154
|
} = deps2;
|
|
1949
2155
|
let cwd = input.cwd;
|
|
1950
|
-
let resolvedSlug = input.workspaceSlug
|
|
1951
|
-
if (!cwd) {
|
|
2156
|
+
let resolvedSlug = input.workspaceSlug;
|
|
2157
|
+
if (!cwd || !resolvedSlug) {
|
|
1952
2158
|
try {
|
|
1953
2159
|
const config = await loadWorkspacesConfig();
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
2160
|
+
if (!cwd) {
|
|
2161
|
+
const ws = input.workspaceSlug ? findWorkspace(config, input.workspaceSlug) : getActiveWorkspace(config);
|
|
2162
|
+
if (ws) {
|
|
2163
|
+
cwd = ws.path;
|
|
2164
|
+
resolvedSlug = ws.slug;
|
|
2165
|
+
}
|
|
2166
|
+
} else if (!resolvedSlug) {
|
|
2167
|
+
const ws = findWorkspaceByPath(config, cwd);
|
|
2168
|
+
if (ws) {
|
|
2169
|
+
resolvedSlug = ws.slug;
|
|
2170
|
+
}
|
|
1958
2171
|
}
|
|
1959
2172
|
} catch {
|
|
1960
2173
|
}
|
|
1961
2174
|
}
|
|
2175
|
+
resolvedSlug = resolvedSlug ?? "default";
|
|
1962
2176
|
if (!cwd) {
|
|
1963
2177
|
return {
|
|
1964
2178
|
ok: false,
|
|
@@ -2078,8 +2292,43 @@ async function spawnAgentSession(deps2, input) {
|
|
|
2078
2292
|
}
|
|
2079
2293
|
const spawnDefaults = resolveSpawnDefaults(configDefaults, input.adapter, {
|
|
2080
2294
|
skills: input.skills,
|
|
2081
|
-
options: input.options
|
|
2295
|
+
options: input.options,
|
|
2296
|
+
auth: input.auth
|
|
2082
2297
|
});
|
|
2298
|
+
let authSpec;
|
|
2299
|
+
let authEcho;
|
|
2300
|
+
if (resolved && input.sandbox === void 0 && resolved.authDescriptor) {
|
|
2301
|
+
const authModel = input.model ?? resolved.defaultModel;
|
|
2302
|
+
const pinnedProvider = spawnDefaults.auth.provider;
|
|
2303
|
+
const resolvedProvider = pinnedProvider ?? resolved.authDescriptor.provider ?? (authModel ? getModelProvider(authModel) : void 0);
|
|
2304
|
+
const apiKeyStoreCredential = resolvedProvider && spawnDefaults.auth.explicit && spawnDefaults.auth.apiKeyCredential === void 0 ? await (0, providers_store_exports.getProviderKey)(resolvedProvider) : void 0;
|
|
2305
|
+
try {
|
|
2306
|
+
const result = resolveAuthSpec({
|
|
2307
|
+
descriptor: resolved.authDescriptor,
|
|
2308
|
+
...authModel ? { model: authModel } : {},
|
|
2309
|
+
...pinnedProvider ? { requestedProvider: pinnedProvider } : {},
|
|
2310
|
+
...spawnDefaults.auth.requestedMode ? { requestedMode: spawnDefaults.auth.requestedMode } : {},
|
|
2311
|
+
explicit: spawnDefaults.auth.explicit,
|
|
2312
|
+
...spawnDefaults.auth.subscriptionCredential !== void 0 ? { subscriptionCredential: spawnDefaults.auth.subscriptionCredential } : {},
|
|
2313
|
+
...spawnDefaults.auth.apiKeyCredential !== void 0 ? { apiKeyConfigCredential: spawnDefaults.auth.apiKeyCredential } : {},
|
|
2314
|
+
...apiKeyStoreCredential !== void 0 ? { apiKeyStoreCredential } : {}
|
|
2315
|
+
});
|
|
2316
|
+
if (result) {
|
|
2317
|
+
authSpec = result.spec;
|
|
2318
|
+
authEcho = result.echo;
|
|
2319
|
+
}
|
|
2320
|
+
} catch (err) {
|
|
2321
|
+
if (err instanceof AuthResolutionError) {
|
|
2322
|
+
return {
|
|
2323
|
+
ok: false,
|
|
2324
|
+
code: "unsupported_auth_mode",
|
|
2325
|
+
message: `agent_start: ${err.message}`,
|
|
2326
|
+
details: { adapter: input.adapter, provider: resolvedProvider }
|
|
2327
|
+
};
|
|
2328
|
+
}
|
|
2329
|
+
throw err;
|
|
2330
|
+
}
|
|
2331
|
+
}
|
|
2083
2332
|
const effectiveOptions = normalizeSkillsOption(
|
|
2084
2333
|
spawnDefaults.skills,
|
|
2085
2334
|
spawnDefaults.options,
|
|
@@ -2088,6 +2337,39 @@ async function spawnAgentSession(deps2, input) {
|
|
|
2088
2337
|
const effectivePrompt = input.prompt ? `${composeRoleContext(role, input.promptAppend, roleRegistry)}
|
|
2089
2338
|
|
|
2090
2339
|
${input.prompt}` : input.prompt;
|
|
2340
|
+
let settleClaim;
|
|
2341
|
+
if (input.idempotencyKey) {
|
|
2342
|
+
const claims = claimsFor(registry);
|
|
2343
|
+
const key = `${input.adapter}${cwd}${input.idempotencyKey}`;
|
|
2344
|
+
const now = Date.now();
|
|
2345
|
+
for (const [k, claim] of claims) {
|
|
2346
|
+
if (claim.resolvedAt !== void 0 && now - claim.resolvedAt > SPAWN_CLAIM_WINDOW_MS) {
|
|
2347
|
+
claims.delete(k);
|
|
2348
|
+
}
|
|
2349
|
+
}
|
|
2350
|
+
const existing = claims.get(key);
|
|
2351
|
+
if (existing) {
|
|
2352
|
+
const result = await existing.result;
|
|
2353
|
+
return result.ok ? { ...result, deduped: true } : result;
|
|
2354
|
+
}
|
|
2355
|
+
let resolveClaim;
|
|
2356
|
+
claims.set(key, { result: new Promise((resolve12) => {
|
|
2357
|
+
resolveClaim = resolve12;
|
|
2358
|
+
}) });
|
|
2359
|
+
settleClaim = (result) => {
|
|
2360
|
+
if (result.ok) {
|
|
2361
|
+
const claim = claims.get(key);
|
|
2362
|
+
if (claim) claim.resolvedAt = Date.now();
|
|
2363
|
+
} else {
|
|
2364
|
+
claims.delete(key);
|
|
2365
|
+
}
|
|
2366
|
+
resolveClaim(result);
|
|
2367
|
+
};
|
|
2368
|
+
}
|
|
2369
|
+
const finish = (result) => {
|
|
2370
|
+
settleClaim?.(result);
|
|
2371
|
+
return result;
|
|
2372
|
+
};
|
|
2091
2373
|
try {
|
|
2092
2374
|
const resolvedMcpServers = await resolveMcpCredentialHeaders(mcpServers);
|
|
2093
2375
|
let liveSessionId;
|
|
@@ -2128,7 +2410,9 @@ ${input.prompt}` : input.prompt;
|
|
|
2128
2410
|
...Object.keys(effectiveOptions).length > 0 ? { options: effectiveOptions } : {},
|
|
2129
2411
|
...input.model ? { model: input.model } : {},
|
|
2130
2412
|
...input.effort ? { effort: input.effort } : {},
|
|
2413
|
+
...authSpec ? { auth: authSpec } : {},
|
|
2131
2414
|
...resolvedMcpServers ? { mcpServers: resolvedMcpServers } : {},
|
|
2415
|
+
...input.permissionHold ? { permissionHold: true } : {},
|
|
2132
2416
|
onActivity: () => {
|
|
2133
2417
|
if (liveSessionId) registry.pulseActivity(liveSessionId);
|
|
2134
2418
|
}
|
|
@@ -2157,8 +2441,28 @@ ${input.prompt}` : input.prompt;
|
|
|
2157
2441
|
...input.maxCostUsd !== void 0 ? { maxCostUsd: input.maxCostUsd } : {},
|
|
2158
2442
|
...readUsage ? { readUsage } : {},
|
|
2159
2443
|
...input.trace !== void 0 ? { trace: input.trace } : {},
|
|
2444
|
+
// Verifiability: record the OBSERVABLE echo — resolved provider, mode,
|
|
2445
|
+
// credential source, the env var actually set, and a non-secret
|
|
2446
|
+
// fingerprint (never the credential). DECISION 9③/10②. Recorded only
|
|
2447
|
+
// when a credential actually resolved (fingerprint present); a
|
|
2448
|
+
// configured-but-missing-credential spawn fails fast in the driver
|
|
2449
|
+
// before a descriptor exists. Absent for sandbox spawns (the box
|
|
2450
|
+
// resolves its own credential, so a host-side echo would misrepresent
|
|
2451
|
+
// it — `authEcho` is already left undefined for those above).
|
|
2452
|
+
...authEcho?.fingerprint ? {
|
|
2453
|
+
auth: {
|
|
2454
|
+
mode: authEcho.authMode,
|
|
2455
|
+
fingerprint: authEcho.fingerprint,
|
|
2456
|
+
provider: authEcho.provider,
|
|
2457
|
+
credentialSource: authEcho.credentialSource,
|
|
2458
|
+
setEnv: authEcho.setEnv
|
|
2459
|
+
}
|
|
2460
|
+
} : {},
|
|
2160
2461
|
...sandboxId ? { remote: true, sandboxId } : {},
|
|
2161
|
-
...sandboxTeardown ? { sandboxTeardown } : {}
|
|
2462
|
+
...sandboxTeardown ? { sandboxTeardown } : {},
|
|
2463
|
+
// Hold mode is a local-driver capability; a sandbox spawn proxies to the
|
|
2464
|
+
// box's own daemon, which handles permissions there.
|
|
2465
|
+
...input.permissionHold && input.sandbox === void 0 ? { permissionHold: true } : {}
|
|
2162
2466
|
});
|
|
2163
2467
|
liveSessionId = desc.id;
|
|
2164
2468
|
bindOrchestratorLifecycle?.(desc.id);
|
|
@@ -2174,28 +2478,28 @@ ${input.prompt}` : input.prompt;
|
|
|
2174
2478
|
if (waitUnsub) waitUnsub();
|
|
2175
2479
|
const waitTail = waitLines.slice(-80);
|
|
2176
2480
|
const output = cleanAgentLines(waitTail);
|
|
2177
|
-
return { ok: true, descriptor: desc, output };
|
|
2481
|
+
return finish({ ok: true, descriptor: desc, output });
|
|
2178
2482
|
}
|
|
2179
|
-
return { ok: true, descriptor: desc };
|
|
2483
|
+
return finish({ ok: true, descriptor: desc });
|
|
2180
2484
|
} catch (err) {
|
|
2181
|
-
return {
|
|
2485
|
+
return finish({
|
|
2182
2486
|
ok: false,
|
|
2183
2487
|
code: "agent_spawn_failed",
|
|
2184
2488
|
message: `agent_start: spawn failed \u2014 ${err instanceof Error ? err.message : String(err)}`
|
|
2185
|
-
};
|
|
2489
|
+
});
|
|
2186
2490
|
}
|
|
2187
2491
|
}
|
|
2188
2492
|
async function resolveMcpCredentialHeaders(mcpServers) {
|
|
2189
2493
|
if (!mcpServers || mcpServers.length === 0) return mcpServers;
|
|
2190
|
-
const { resolveMcpCredentialHeaders:
|
|
2191
|
-
if (!
|
|
2494
|
+
const { resolveMcpCredentialHeaders: resolve12 } = getMcpCredentialDeps();
|
|
2495
|
+
if (!resolve12) return mcpServers;
|
|
2192
2496
|
return Promise.all(
|
|
2193
2497
|
mcpServers.map(async (entry) => {
|
|
2194
2498
|
const ref = entry.credentialRef;
|
|
2195
2499
|
if (!ref) return entry;
|
|
2196
2500
|
let brokered;
|
|
2197
2501
|
try {
|
|
2198
|
-
brokered = await
|
|
2502
|
+
brokered = await resolve12({ credentialRef: ref });
|
|
2199
2503
|
} catch (err) {
|
|
2200
2504
|
console.warn(
|
|
2201
2505
|
`[agent_start] credentialRef resolution failed for "${entry.name}" (${ref}): ${err instanceof Error ? err.message : String(err)}`
|
|
@@ -2294,10 +2598,10 @@ async function bootSandboxAgentSession(opts) {
|
|
|
2294
2598
|
};
|
|
2295
2599
|
}
|
|
2296
2600
|
async function resolveSandboxSecret(slug) {
|
|
2297
|
-
const { resolveSandboxSecret:
|
|
2298
|
-
if (!
|
|
2601
|
+
const { resolveSandboxSecret: resolve12 } = getMcpCredentialDeps();
|
|
2602
|
+
if (!resolve12) return null;
|
|
2299
2603
|
try {
|
|
2300
|
-
return await
|
|
2604
|
+
return await resolve12(slug);
|
|
2301
2605
|
} catch (err) {
|
|
2302
2606
|
console.warn(
|
|
2303
2607
|
`[agent_start] sandbox secret resolution failed for "${slug}": ${err instanceof Error ? err.message : String(err)}`
|
|
@@ -2322,6 +2626,24 @@ var mcpPositiveNumber = z.preprocess(
|
|
|
2322
2626
|
(v) => typeof v === "string" && v.trim() !== "" ? Number(v) : v,
|
|
2323
2627
|
z.number().positive()
|
|
2324
2628
|
);
|
|
2629
|
+
var sessionIdField = z.string().optional().describe(
|
|
2630
|
+
"Session id (as returned by agent_start). Accepts either `sessionId` or its alias `id` \u2014 pass whichever you have."
|
|
2631
|
+
);
|
|
2632
|
+
var sessionIdAliasField = z.string().optional().describe("Alias for `sessionId` \u2014 the `id` field returned by agent_start.");
|
|
2633
|
+
function resolveSessionIdArg(input) {
|
|
2634
|
+
return input.sessionId ?? input.id;
|
|
2635
|
+
}
|
|
2636
|
+
function missingSessionIdError(tool) {
|
|
2637
|
+
return {
|
|
2638
|
+
content: [
|
|
2639
|
+
{
|
|
2640
|
+
type: "text",
|
|
2641
|
+
text: `${tool}: missing session id \u2014 pass \`sessionId\` (or its alias \`id\`).`
|
|
2642
|
+
}
|
|
2643
|
+
],
|
|
2644
|
+
isError: true
|
|
2645
|
+
};
|
|
2646
|
+
}
|
|
2325
2647
|
function registerAgentTools(server, opts) {
|
|
2326
2648
|
const {
|
|
2327
2649
|
registry,
|
|
@@ -2356,6 +2678,12 @@ function registerAgentTools(server, opts) {
|
|
|
2356
2678
|
mode: z.string().optional().describe(
|
|
2357
2679
|
"Manifest-declared mode id (AIP-45 `modes`) applied at spawn time, BEFORE the child process starts \u2014 e.g. claude-code's 'plan' (read-only: reasons and proposes but does not edit or run commands), 'accept-edits', 'bypass-permissions'; codex's 'read-only' / 'full-access'; mastracode/opencode's 'plan' / 'build'. Adapters that don't declare `modes` (e.g. hermes) reject ANY value here \u2014 only pass this for adapters known to support it. Omit for the adapter's normal interactive mode."
|
|
2358
2680
|
),
|
|
2681
|
+
idempotencyKey: z.string().min(1).optional().describe(
|
|
2682
|
+
"Caller-declared 'this is the same logical spawn' token. A retried agent_start call (e.g. after a slow/lost response) that repeats the same `idempotencyKey` for the same `adapter`+`cwd` within ~30s of a successful spawn gets that SAME session's descriptor back instead of forking a second process \u2014 set `deduped: true` on the response so you can tell. Omit to spawn unconditionally (today's behaviour, and still required for deliberate concurrent spawns into the same cwd \u2014 this field can't distinguish a retry from an intentional duplicate spawn, only your own declared key can). Recommended for any caller that might retry a spawn it can't otherwise confirm succeeded."
|
|
2683
|
+
),
|
|
2684
|
+
permissionHold: mcpBool.optional().describe(
|
|
2685
|
+
"Start the session in permission-hold mode: every ACP permission request the agent raises (Write, Bash, \u2026) is SURFACED and HELD in the cross-session inbox (`permissions_list` / `permissions_respond`) instead of auto-answered, and the agent blocks until a human/orchestrator approves or denies it. Default false = today's auto-answer behaviour. ACP adapters only; others ignore it."
|
|
2686
|
+
),
|
|
2359
2687
|
options: jsonTolerant(
|
|
2360
2688
|
z.record(z.string(), z.union([z.boolean(), z.number(), z.string()]))
|
|
2361
2689
|
).optional().describe(
|
|
@@ -2370,6 +2698,15 @@ function registerAgentTools(server, opts) {
|
|
|
2370
2698
|
effort: z.string().optional().describe(
|
|
2371
2699
|
"Reasoning effort level (e.g. 'low', 'medium', 'high', 'xhigh', 'max', 'ultracode'). IMPORTANT: effort is calibrated per model \u2014 the same label maps to different compute budgets across models, and defaults differ by model (Sonnet 4.6 / Opus 4.8 default 'high'; Opus 4.7 default 'xhigh'). 'max' and 'ultracode' are session-only. Omit to keep the model's own default."
|
|
2372
2700
|
),
|
|
2701
|
+
auth: jsonTolerant(
|
|
2702
|
+
z.object({
|
|
2703
|
+
mode: z.enum(["subscription", "api-key"]).optional(),
|
|
2704
|
+
token: z.string().optional(),
|
|
2705
|
+
apiKey: z.string().optional()
|
|
2706
|
+
})
|
|
2707
|
+
).optional().describe(
|
|
2708
|
+
"Deterministic billing-auth mode + EXPLICIT credential for adapters that declare it (today: claude-code). EXPLICIT credential selection, not scrub-by-absence: `mode` picks 'subscription' (default) or 'api-key'; `token`/`apiKey` (matching the resolved mode) is the secret VALUE, merged against `~/.agentproto/config.json`'s `defaults.adapters.claude-code.auth` (this field's `mode` wins; the credential for the resolved mode wins over the matching config field). For claude-code, 'subscription' SETS CLAUDE_CODE_OAUTH_TOKEN to `token` (a bearer token minted via `claude setup-token` \u2014 bills the Max/Pro subscription, not API credits) and DELETES ANTHROPIC_API_KEY + the cloud-provider redirect toggles + ANTHROPIC_BASE_URL. 'api-key' SETS ANTHROPIC_API_KEY to `apiKey` and DELETES ANTHROPIC_AUTH_TOKEN \u2014 the deliberate 'bill the API' choice. FAILS FAST (refuses the spawn, no fallback) when the resolved mode has no credential configured anywhere. The secret is never logged or echoed back \u2014 only a fingerprint appears on the session descriptor / `agent_sessions_list`. Adapters that don't declare this vocabulary ignore this field entirely."
|
|
2709
|
+
),
|
|
2373
2710
|
mcpServers: jsonTolerant(
|
|
2374
2711
|
z.array(
|
|
2375
2712
|
z.object({
|
|
@@ -2460,18 +2797,22 @@ function registerAgentTools(server, opts) {
|
|
|
2460
2797
|
input
|
|
2461
2798
|
);
|
|
2462
2799
|
if (result.ok) {
|
|
2463
|
-
const body =
|
|
2800
|
+
const body = {
|
|
2801
|
+
...result.descriptor,
|
|
2802
|
+
...result.output ? { output: result.output } : {},
|
|
2803
|
+
...result.deduped ? { deduped: true } : {}
|
|
2804
|
+
};
|
|
2464
2805
|
return {
|
|
2465
2806
|
content: [{ type: "text", text: JSON.stringify(body, null, 2) }]
|
|
2466
2807
|
};
|
|
2467
2808
|
}
|
|
2468
|
-
const
|
|
2809
|
+
const text6 = result.code === "orchestrator_max_depth_exceeded" || result.code === "orchestrator_child_quota_exceeded" || result.code === "role_spawn_denied" ? JSON.stringify(
|
|
2469
2810
|
{ error: result.code, message: result.message, ...result.details },
|
|
2470
2811
|
null,
|
|
2471
2812
|
2
|
|
2472
2813
|
) : result.message;
|
|
2473
2814
|
return {
|
|
2474
|
-
content: [{ type: "text", text:
|
|
2815
|
+
content: [{ type: "text", text: text6 }],
|
|
2475
2816
|
isError: true
|
|
2476
2817
|
};
|
|
2477
2818
|
}
|
|
@@ -2480,15 +2821,18 @@ function registerAgentTools(server, opts) {
|
|
|
2480
2821
|
"agent_prompt",
|
|
2481
2822
|
"Send a follow-up prompt to a live agent session \u2014 multi-turn continuity without re-spawning. The session id comes from `agent_start` (or `agent_sessions_list`). Returns immediately; tail output via `agent_output` or the SSE /sessions/:id/stream endpoint. By default, a session mid-turn rejects the new prompt \u2014 pass `interrupt: true` to cancel the in-flight turn and redirect the SAME session onto this prompt instead, without losing its context (unlike `agent_kill`, which ends the session entirely). `interrupt` is a no-op on an already-idle session.",
|
|
2482
2823
|
{
|
|
2483
|
-
sessionId:
|
|
2824
|
+
sessionId: sessionIdField,
|
|
2825
|
+
id: sessionIdAliasField,
|
|
2484
2826
|
prompt: z.string().min(1).describe("The next user turn (plain text)."),
|
|
2485
2827
|
interrupt: z.boolean().optional().describe(
|
|
2486
2828
|
"When true and the session is mid-turn, cancel the in-flight turn and deliver this prompt on the same session instead of rejecting. No-op when the session is already idle. Default false (mid-turn rejects, as today)."
|
|
2487
2829
|
)
|
|
2488
2830
|
},
|
|
2489
2831
|
async (input) => {
|
|
2832
|
+
const sessionId = resolveSessionIdArg(input);
|
|
2833
|
+
if (!sessionId) return missingSessionIdError("agent_prompt");
|
|
2490
2834
|
try {
|
|
2491
|
-
await registry.enqueuePrompt(
|
|
2835
|
+
await registry.enqueuePrompt(sessionId, input.prompt, {
|
|
2492
2836
|
interrupt: input.interrupt
|
|
2493
2837
|
});
|
|
2494
2838
|
return {
|
|
@@ -2496,7 +2840,7 @@ function registerAgentTools(server, opts) {
|
|
|
2496
2840
|
{
|
|
2497
2841
|
type: "text",
|
|
2498
2842
|
text: JSON.stringify(
|
|
2499
|
-
{ ok: true, sessionId
|
|
2843
|
+
{ ok: true, sessionId, queued: true },
|
|
2500
2844
|
null,
|
|
2501
2845
|
2
|
|
2502
2846
|
)
|
|
@@ -2520,25 +2864,28 @@ function registerAgentTools(server, opts) {
|
|
|
2520
2864
|
"agent_output",
|
|
2521
2865
|
"Tail the recent output of a session. Returns the last N lines of the ring buffer (stdout + stderr inter-leaved, newest last). Use this to read an agent's reply after `agent_prompt`.",
|
|
2522
2866
|
{
|
|
2523
|
-
sessionId:
|
|
2867
|
+
sessionId: sessionIdField,
|
|
2868
|
+
id: sessionIdAliasField,
|
|
2524
2869
|
lastN: z.number().int().min(1).max(500).optional().describe("Max lines to return. Default 80, max 500."),
|
|
2525
2870
|
clean: mcpBool.optional().describe(
|
|
2526
2871
|
"Strip ANSI codes and drop framing/decoration lines, returning human-readable text."
|
|
2527
2872
|
)
|
|
2528
2873
|
},
|
|
2529
2874
|
async (input) => {
|
|
2530
|
-
const
|
|
2875
|
+
const sessionId = resolveSessionIdArg(input);
|
|
2876
|
+
if (!sessionId) return missingSessionIdError("agent_output");
|
|
2877
|
+
const desc = registry.get(sessionId);
|
|
2531
2878
|
if (!desc) {
|
|
2532
2879
|
return {
|
|
2533
2880
|
content: [
|
|
2534
|
-
{ type: "text", text: `agent_output: no session "${
|
|
2881
|
+
{ type: "text", text: `agent_output: no session "${sessionId}"` }
|
|
2535
2882
|
],
|
|
2536
2883
|
isError: true
|
|
2537
2884
|
};
|
|
2538
2885
|
}
|
|
2539
2886
|
const limit = input.lastN ?? 80;
|
|
2540
2887
|
const lines = [];
|
|
2541
|
-
const unsub = registry.attach(
|
|
2888
|
+
const unsub = registry.attach(sessionId, (line, _stream) => {
|
|
2542
2889
|
lines.push(line);
|
|
2543
2890
|
});
|
|
2544
2891
|
if (unsub) unsub();
|
|
@@ -2555,7 +2902,7 @@ function registerAgentTools(server, opts) {
|
|
|
2555
2902
|
type: "text",
|
|
2556
2903
|
text: JSON.stringify(
|
|
2557
2904
|
{
|
|
2558
|
-
sessionId
|
|
2905
|
+
sessionId,
|
|
2559
2906
|
status: desc.status,
|
|
2560
2907
|
lastOutputAt: desc.lastOutputAt,
|
|
2561
2908
|
// Distinct liveness heartbeat: advances on ANY adapter-process
|
|
@@ -2585,15 +2932,18 @@ function registerAgentTools(server, opts) {
|
|
|
2585
2932
|
"agent_kill",
|
|
2586
2933
|
"Stop a session \u2014 SIGTERM the underlying child + close the agent protocol session. Use to free resources after the operator is done, or when a session is wedged.",
|
|
2587
2934
|
{
|
|
2588
|
-
sessionId:
|
|
2935
|
+
sessionId: sessionIdField,
|
|
2936
|
+
id: sessionIdAliasField
|
|
2589
2937
|
},
|
|
2590
2938
|
async (input) => {
|
|
2939
|
+
const sessionId = resolveSessionIdArg(input);
|
|
2940
|
+
if (!sessionId) return missingSessionIdError("agent_kill");
|
|
2591
2941
|
if (callerScope) {
|
|
2592
2942
|
const subtree = collectSubtree(
|
|
2593
2943
|
callerScope.ownerSessionId,
|
|
2594
2944
|
registry.list()
|
|
2595
2945
|
);
|
|
2596
|
-
if (!subtree.has(
|
|
2946
|
+
if (!subtree.has(sessionId)) {
|
|
2597
2947
|
return {
|
|
2598
2948
|
content: [
|
|
2599
2949
|
{
|
|
@@ -2601,9 +2951,9 @@ function registerAgentTools(server, opts) {
|
|
|
2601
2951
|
text: JSON.stringify(
|
|
2602
2952
|
{
|
|
2603
2953
|
error: "orchestrator_session_out_of_scope",
|
|
2604
|
-
message: `agent_kill: session "${
|
|
2954
|
+
message: `agent_kill: session "${sessionId}" is not in your subtree \u2014 a scoped orchestrator can only kill sessions it (transitively) spawned. No action taken.`,
|
|
2605
2955
|
ok: false,
|
|
2606
|
-
sessionId
|
|
2956
|
+
sessionId
|
|
2607
2957
|
},
|
|
2608
2958
|
null,
|
|
2609
2959
|
2
|
|
@@ -2614,12 +2964,12 @@ function registerAgentTools(server, opts) {
|
|
|
2614
2964
|
};
|
|
2615
2965
|
}
|
|
2616
2966
|
}
|
|
2617
|
-
const ok = registry.kill(
|
|
2967
|
+
const ok = registry.kill(sessionId);
|
|
2618
2968
|
return {
|
|
2619
2969
|
content: [
|
|
2620
2970
|
{
|
|
2621
2971
|
type: "text",
|
|
2622
|
-
text: JSON.stringify({ ok, sessionId
|
|
2972
|
+
text: JSON.stringify({ ok, sessionId }, null, 2)
|
|
2623
2973
|
}
|
|
2624
2974
|
]
|
|
2625
2975
|
};
|
|
@@ -2729,9 +3079,10 @@ function registerExportSessionTool(server, ops) {
|
|
|
2729
3079
|
"agent_export",
|
|
2730
3080
|
"Export a clean, human-readable transcript of an agent session. Prefers the source the adapter already persists (claude-code: JSONL in ~/.claude/projects/; hermes: state.db in ~/.hermes/), falling back to agentproto's own daemon-captured events.jsonl for every other adapter (or once the native store is unreadable). Returns markdown (default) or JSON. Works on stopped and running sessions alike. Use after a long agent run to review the full conversation without the ANSI noise of the ring buffer.",
|
|
2731
3081
|
{
|
|
2732
|
-
sessionId: z.string().describe(
|
|
2733
|
-
"agentproto session id (sess_xxx), adapter-native id, or session name."
|
|
3082
|
+
sessionId: z.string().optional().describe(
|
|
3083
|
+
"agentproto session id (sess_xxx), adapter-native id, or session name. Accepts either `sessionId` or its alias `id`."
|
|
2734
3084
|
),
|
|
3085
|
+
id: sessionIdAliasField,
|
|
2735
3086
|
adapter: z.string().optional().describe(
|
|
2736
3087
|
"Override adapter slug (e.g. 'claude-code', 'hermes') when the session is not in the registry. Required when passing a raw adapter-native id."
|
|
2737
3088
|
),
|
|
@@ -2746,8 +3097,10 @@ function registerExportSessionTool(server, ops) {
|
|
|
2746
3097
|
)
|
|
2747
3098
|
},
|
|
2748
3099
|
async (input) => {
|
|
3100
|
+
const sessionId = resolveSessionIdArg(input);
|
|
3101
|
+
if (!sessionId) return missingSessionIdError("agent_export");
|
|
2749
3102
|
const result = await doExport({
|
|
2750
|
-
sessionId
|
|
3103
|
+
sessionId,
|
|
2751
3104
|
registry: ops.registry,
|
|
2752
3105
|
...input.adapter ? { adapter: input.adapter } : {},
|
|
2753
3106
|
...input.cwd ? { cwd: input.cwd } : {},
|
|
@@ -3138,8 +3491,6 @@ function tokenizeCommand(s) {
|
|
|
3138
3491
|
if (buf) out.push(buf);
|
|
3139
3492
|
return out;
|
|
3140
3493
|
}
|
|
3141
|
-
|
|
3142
|
-
// src/session-restart-core.ts
|
|
3143
3494
|
async function restartAgentSession(registry, resolveAgentAdapter, prev, opts = {}) {
|
|
3144
3495
|
const augmented = opts.forceAgentResume ? prev : await augmentWithFsResume(prev);
|
|
3145
3496
|
const strategy = opts.forceAgentResume ? { kind: "agent", resumeSessionId: prev.adapterSessionId } : decideRestartStrategy(augmented);
|
|
@@ -3161,6 +3512,33 @@ async function restartAgentSession(registry, resolveAgentAdapter, prev, opts = {
|
|
|
3161
3512
|
let cwd = prev.cwd;
|
|
3162
3513
|
if (!cwd) console.warn(`[restartAgentSession] no cwd on prior descriptor ${prev.id} \u2014 falling back to daemon's cwd ${process.cwd()}`);
|
|
3163
3514
|
cwd ??= process.cwd();
|
|
3515
|
+
let authSpec;
|
|
3516
|
+
let authEcho;
|
|
3517
|
+
if (resolved.authDescriptor) {
|
|
3518
|
+
const configDefaults = opts.loadDefaultsConfig ? await opts.loadDefaultsConfig() : (await loadConfig()).defaults;
|
|
3519
|
+
const explicitAuthInput = prev.auth ? { mode: prev.auth.mode } : void 0;
|
|
3520
|
+
const spawnDefaults = resolveSpawnDefaults(configDefaults, adapterSlug, {
|
|
3521
|
+
auth: explicitAuthInput
|
|
3522
|
+
});
|
|
3523
|
+
const authModel = prev.model ?? resolved.defaultModel;
|
|
3524
|
+
const pinnedProvider = spawnDefaults.auth.provider;
|
|
3525
|
+
const resolvedProvider = pinnedProvider ?? resolved.authDescriptor.provider ?? (authModel ? getModelProvider(authModel) : void 0);
|
|
3526
|
+
const apiKeyStoreCredential = resolvedProvider && spawnDefaults.auth.explicit && spawnDefaults.auth.apiKeyCredential === void 0 ? await (0, providers_store_exports.getProviderKey)(resolvedProvider) : void 0;
|
|
3527
|
+
const result = resolveAuthSpec({
|
|
3528
|
+
descriptor: resolved.authDescriptor,
|
|
3529
|
+
...authModel ? { model: authModel } : {},
|
|
3530
|
+
...pinnedProvider ? { requestedProvider: pinnedProvider } : {},
|
|
3531
|
+
...spawnDefaults.auth.requestedMode ? { requestedMode: spawnDefaults.auth.requestedMode } : {},
|
|
3532
|
+
explicit: spawnDefaults.auth.explicit,
|
|
3533
|
+
...spawnDefaults.auth.subscriptionCredential !== void 0 ? { subscriptionCredential: spawnDefaults.auth.subscriptionCredential } : {},
|
|
3534
|
+
...spawnDefaults.auth.apiKeyCredential !== void 0 ? { apiKeyConfigCredential: spawnDefaults.auth.apiKeyCredential } : {},
|
|
3535
|
+
...apiKeyStoreCredential !== void 0 ? { apiKeyStoreCredential } : {}
|
|
3536
|
+
});
|
|
3537
|
+
if (result) {
|
|
3538
|
+
authSpec = result.spec;
|
|
3539
|
+
authEcho = result.echo;
|
|
3540
|
+
}
|
|
3541
|
+
}
|
|
3164
3542
|
const spawnWithResume = async (resumeSessionId) => {
|
|
3165
3543
|
let liveSessionId;
|
|
3166
3544
|
const agentSession = await resolved.startSession({
|
|
@@ -3168,6 +3546,7 @@ async function restartAgentSession(registry, resolveAgentAdapter, prev, opts = {
|
|
|
3168
3546
|
...resumeSessionId ? { resumeSessionId } : {},
|
|
3169
3547
|
...prev.model ? { model: prev.model } : {},
|
|
3170
3548
|
...prev.mcpServers ? { mcpServers: prev.mcpServers } : {},
|
|
3549
|
+
...authSpec ? { auth: authSpec } : {},
|
|
3171
3550
|
onActivity: () => {
|
|
3172
3551
|
if (liveSessionId) registry.pulseActivity(liveSessionId);
|
|
3173
3552
|
}
|
|
@@ -3180,7 +3559,19 @@ async function restartAgentSession(registry, resolveAgentAdapter, prev, opts = {
|
|
|
3180
3559
|
...prev.label ? { label: prev.label } : {},
|
|
3181
3560
|
...prev.mcpServers ? { mcpServers: prev.mcpServers } : {},
|
|
3182
3561
|
...prev.model ? { model: prev.model } : {},
|
|
3183
|
-
...resolved.commandPreview ? { commandPreview: resolved.commandPreview } : {}
|
|
3562
|
+
...resolved.commandPreview ? { commandPreview: resolved.commandPreview } : {},
|
|
3563
|
+
// Verifiability echo (never the credential) — see the auth
|
|
3564
|
+
// resolution block above. Absent when no credential resolved,
|
|
3565
|
+
// same as session-spawn.ts.
|
|
3566
|
+
...authEcho?.fingerprint ? {
|
|
3567
|
+
auth: {
|
|
3568
|
+
mode: authEcho.authMode,
|
|
3569
|
+
fingerprint: authEcho.fingerprint,
|
|
3570
|
+
provider: authEcho.provider,
|
|
3571
|
+
credentialSource: authEcho.credentialSource,
|
|
3572
|
+
setEnv: authEcho.setEnv
|
|
3573
|
+
}
|
|
3574
|
+
} : {}
|
|
3184
3575
|
});
|
|
3185
3576
|
liveSessionId = desc2.id;
|
|
3186
3577
|
return desc2;
|
|
@@ -3274,24 +3665,30 @@ function normalize3(parsed) {
|
|
|
3274
3665
|
}
|
|
3275
3666
|
return { version: IMPORTED_MCPS_VERSION, imports };
|
|
3276
3667
|
}
|
|
3668
|
+
function plausibleContextUsed(contextSize, contextUsed) {
|
|
3669
|
+
if (contextUsed === void 0) return void 0;
|
|
3670
|
+
if (contextSize !== void 0 && contextUsed > contextSize) return void 0;
|
|
3671
|
+
return contextUsed;
|
|
3672
|
+
}
|
|
3277
3673
|
var defaultResolver = (model) => resolvePricing(model);
|
|
3278
3674
|
function tokenCost(tokens, pricePer1M) {
|
|
3279
3675
|
return tokens === void 0 ? 0 : tokens * pricePer1M / 1e6;
|
|
3280
3676
|
}
|
|
3281
|
-
function deriveSessionUsage(input,
|
|
3677
|
+
function deriveSessionUsage(input, resolve12 = defaultResolver) {
|
|
3678
|
+
const contextUsed = plausibleContextUsed(input.contextSize, input.contextUsed);
|
|
3282
3679
|
const base = {
|
|
3283
3680
|
...input.model !== void 0 ? { model: input.model } : {},
|
|
3284
3681
|
...input.tokensIn !== void 0 ? { tokensIn: input.tokensIn } : {},
|
|
3285
3682
|
...input.tokensOut !== void 0 ? { tokensOut: input.tokensOut } : {},
|
|
3286
3683
|
...input.contextSize !== void 0 ? { contextSize: input.contextSize } : {},
|
|
3287
|
-
...
|
|
3684
|
+
...contextUsed !== void 0 ? { contextUsed } : {}
|
|
3288
3685
|
};
|
|
3289
3686
|
if (input.adapterCostUsd !== void 0) {
|
|
3290
3687
|
return { ...base, costUsd: input.adapterCostUsd, source: "adapter" };
|
|
3291
3688
|
}
|
|
3292
3689
|
const hasTokens = input.tokensIn !== void 0 || input.tokensOut !== void 0;
|
|
3293
3690
|
if (hasTokens) {
|
|
3294
|
-
const pricing = input.model !== void 0 ?
|
|
3691
|
+
const pricing = input.model !== void 0 ? resolve12(input.model) : void 0;
|
|
3295
3692
|
if (!pricing) {
|
|
3296
3693
|
return { ...base, source: "no-pricing" };
|
|
3297
3694
|
}
|
|
@@ -3301,13 +3698,14 @@ function deriveSessionUsage(input, resolve13 = defaultResolver) {
|
|
|
3301
3698
|
return { ...base, source: "none" };
|
|
3302
3699
|
}
|
|
3303
3700
|
function projectSessionUsage(desc) {
|
|
3701
|
+
const contextUsed = plausibleContextUsed(desc.contextSize, desc.contextUsed);
|
|
3304
3702
|
return {
|
|
3305
3703
|
...desc.model !== void 0 ? { model: desc.model } : {},
|
|
3306
3704
|
...desc.costUsd !== void 0 ? { costUsd: desc.costUsd } : {},
|
|
3307
3705
|
...desc.tokensIn !== void 0 ? { tokensIn: desc.tokensIn } : {},
|
|
3308
3706
|
...desc.tokensOut !== void 0 ? { tokensOut: desc.tokensOut } : {},
|
|
3309
3707
|
...desc.contextSize !== void 0 ? { contextSize: desc.contextSize } : {},
|
|
3310
|
-
...
|
|
3708
|
+
...contextUsed !== void 0 ? { contextUsed } : {},
|
|
3311
3709
|
source: desc.usageSource ?? "none"
|
|
3312
3710
|
};
|
|
3313
3711
|
}
|
|
@@ -3995,6 +4393,13 @@ function registerSessionTools(rawServer, opts) {
|
|
|
3995
4393
|
}
|
|
3996
4394
|
} catch {
|
|
3997
4395
|
}
|
|
4396
|
+
} else if (!input.workspaceSlug) {
|
|
4397
|
+
try {
|
|
4398
|
+
const config = await loadWorkspacesConfig();
|
|
4399
|
+
const ws = findWorkspaceByPath(config, cwd);
|
|
4400
|
+
if (ws) resolvedSlug = ws.slug;
|
|
4401
|
+
} catch {
|
|
4402
|
+
}
|
|
3998
4403
|
}
|
|
3999
4404
|
if (!cwd) {
|
|
4000
4405
|
return {
|
|
@@ -4184,8 +4589,8 @@ var okResult = (r) => ({
|
|
|
4184
4589
|
content: [{ type: "text", text: JSON.stringify(r, null, 2) }],
|
|
4185
4590
|
structuredContent: r
|
|
4186
4591
|
});
|
|
4187
|
-
var errResult = (
|
|
4188
|
-
content: [{ type: "text", text:
|
|
4592
|
+
var errResult = (text6) => ({
|
|
4593
|
+
content: [{ type: "text", text: text6 }],
|
|
4189
4594
|
isError: true
|
|
4190
4595
|
});
|
|
4191
4596
|
function registerBrowserTools(server, opts) {
|
|
@@ -6850,8 +7255,8 @@ function expandTemplate(template, when) {
|
|
|
6850
7255
|
const date = when.toISOString().slice(0, 10);
|
|
6851
7256
|
return template.replace(/\{date\}/g, date);
|
|
6852
7257
|
}
|
|
6853
|
-
function preview(
|
|
6854
|
-
const flat =
|
|
7258
|
+
function preview(text6, max = 120) {
|
|
7259
|
+
const flat = text6.replace(/\s+/g, " ").trim();
|
|
6855
7260
|
return flat.length > max ? `${flat.slice(0, max - 1)}\u2026` : flat;
|
|
6856
7261
|
}
|
|
6857
7262
|
function writeCommandLogEntry(sessionId, entry, baseDir) {
|
|
@@ -6974,8 +7379,8 @@ function createTerminalTranscriptWriter(opts) {
|
|
|
6974
7379
|
const stream = streams.get(sessionId);
|
|
6975
7380
|
if (!stream) return Promise.resolve();
|
|
6976
7381
|
streams.delete(sessionId);
|
|
6977
|
-
return new Promise((
|
|
6978
|
-
stream.end(() =>
|
|
7382
|
+
return new Promise((resolve12) => {
|
|
7383
|
+
stream.end(() => resolve12());
|
|
6979
7384
|
});
|
|
6980
7385
|
},
|
|
6981
7386
|
closeAll() {
|
|
@@ -6984,8 +7389,8 @@ function createTerminalTranscriptWriter(opts) {
|
|
|
6984
7389
|
const stream = streams.get(sessionId);
|
|
6985
7390
|
if (!stream) continue;
|
|
6986
7391
|
closings.push(
|
|
6987
|
-
new Promise((
|
|
6988
|
-
stream.end(() =>
|
|
7392
|
+
new Promise((resolve12) => {
|
|
7393
|
+
stream.end(() => resolve12());
|
|
6989
7394
|
})
|
|
6990
7395
|
);
|
|
6991
7396
|
}
|
|
@@ -7007,6 +7412,35 @@ function normalizeAgentPromptOptions(raw) {
|
|
|
7007
7412
|
}).filter((s) => typeof s === "string");
|
|
7008
7413
|
return labels.length > 0 ? labels : void 0;
|
|
7009
7414
|
}
|
|
7415
|
+
function normalizePermissionOptions(raw) {
|
|
7416
|
+
if (!Array.isArray(raw)) return [];
|
|
7417
|
+
const out = [];
|
|
7418
|
+
for (const o of raw) {
|
|
7419
|
+
if (!o || typeof o !== "object") continue;
|
|
7420
|
+
const r = o;
|
|
7421
|
+
if (typeof r.optionId !== "string") continue;
|
|
7422
|
+
out.push({
|
|
7423
|
+
optionId: r.optionId,
|
|
7424
|
+
...typeof r.name === "string" ? { name: r.name } : {},
|
|
7425
|
+
...typeof r.kind === "string" ? { kind: r.kind } : {}
|
|
7426
|
+
});
|
|
7427
|
+
}
|
|
7428
|
+
return out;
|
|
7429
|
+
}
|
|
7430
|
+
function selectPermissionOptionId(options, input) {
|
|
7431
|
+
if (input.optionId) {
|
|
7432
|
+
return options.some((o) => o.optionId === input.optionId) ? { optionId: input.optionId } : null;
|
|
7433
|
+
}
|
|
7434
|
+
const kindStarts = (prefix) => options.find((o) => typeof o.kind === "string" && o.kind.startsWith(prefix));
|
|
7435
|
+
if (input.decision === "approve") {
|
|
7436
|
+
const chosen = (input.scope === "always" ? options.find((o) => o.kind === "allow_always") : options.find((o) => o.kind === "allow_once")) ?? kindStarts("allow") ?? // Some agents label the grant "proceed"/"yes" without an `allow_` kind —
|
|
7437
|
+
// fall back to the first non-reject option before giving up.
|
|
7438
|
+
options.find((o) => !(typeof o.kind === "string" && o.kind.startsWith("reject")));
|
|
7439
|
+
return chosen ? { optionId: chosen.optionId } : null;
|
|
7440
|
+
}
|
|
7441
|
+
const reject = options.find((o) => o.kind === "reject_once") ?? kindStarts("reject") ?? kindStarts("deny");
|
|
7442
|
+
return reject ? { optionId: reject.optionId } : { cancelled: true };
|
|
7443
|
+
}
|
|
7010
7444
|
function classifyBlockedOn(toolName) {
|
|
7011
7445
|
if (!toolName) return void 0;
|
|
7012
7446
|
const n = toolName.toLowerCase();
|
|
@@ -7014,6 +7448,10 @@ function classifyBlockedOn(toolName) {
|
|
|
7014
7448
|
if (/^(command_execute|terminal_start)$|bash|terminal|command/.test(n)) return "command";
|
|
7015
7449
|
return void 0;
|
|
7016
7450
|
}
|
|
7451
|
+
function releaseBlockedOn(desc) {
|
|
7452
|
+
desc.blockedOn = void 0;
|
|
7453
|
+
desc.pendingToolCallId = void 0;
|
|
7454
|
+
}
|
|
7017
7455
|
function isAbortError(err) {
|
|
7018
7456
|
if (typeof err !== "object" || err === null) return false;
|
|
7019
7457
|
const name = "name" in err ? err.name : void 0;
|
|
@@ -7095,11 +7533,12 @@ function createSessionsRegistry(opts) {
|
|
|
7095
7533
|
const resumeAgent = opts?.resumeAgent;
|
|
7096
7534
|
const sessionEvents = opts?.sessionEvents;
|
|
7097
7535
|
const sessions = /* @__PURE__ */ new Map();
|
|
7536
|
+
const pendingPermissions = /* @__PURE__ */ new Map();
|
|
7098
7537
|
let persistTimer = null;
|
|
7099
7538
|
let nextSubId = 1;
|
|
7100
7539
|
let shutdownDone = false;
|
|
7101
7540
|
if (persist) {
|
|
7102
|
-
loadHistorySnapshot(persistPath, sessions);
|
|
7541
|
+
loadHistorySnapshot(persistPath, sessions, sessionEvents);
|
|
7103
7542
|
}
|
|
7104
7543
|
const onProcessExit = () => {
|
|
7105
7544
|
shutdownImpl();
|
|
@@ -7108,7 +7547,9 @@ function createSessionsRegistry(opts) {
|
|
|
7108
7547
|
process.on("exit", onProcessExit);
|
|
7109
7548
|
}
|
|
7110
7549
|
const emitExited = (rt) => {
|
|
7111
|
-
if (
|
|
7550
|
+
if (rt.exitedEmitted) return;
|
|
7551
|
+
cancelPendingPermissionsForSession(rt);
|
|
7552
|
+
if (!sessionEvents) return;
|
|
7112
7553
|
rt.exitedEmitted = true;
|
|
7113
7554
|
sessionEvents.emit({
|
|
7114
7555
|
type: "session:exited",
|
|
@@ -7116,9 +7557,72 @@ function createSessionsRegistry(opts) {
|
|
|
7116
7557
|
exitCode: rt.desc.exitCode,
|
|
7117
7558
|
status: rt.desc.status,
|
|
7118
7559
|
label: rt.desc.label,
|
|
7119
|
-
ts: (/* @__PURE__ */ new Date()).toISOString()
|
|
7560
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7561
|
+
...rt.desc.endedReason ? { reason: rt.desc.endedReason } : {}
|
|
7120
7562
|
});
|
|
7121
7563
|
};
|
|
7564
|
+
const refreshAwaitingPermission = (rt) => {
|
|
7565
|
+
let has = false;
|
|
7566
|
+
for (const p of pendingPermissions.values()) {
|
|
7567
|
+
if (p.sessionId === rt.desc.id) {
|
|
7568
|
+
has = true;
|
|
7569
|
+
break;
|
|
7570
|
+
}
|
|
7571
|
+
}
|
|
7572
|
+
if (has) rt.desc.awaitingPermission = true;
|
|
7573
|
+
else delete rt.desc.awaitingPermission;
|
|
7574
|
+
};
|
|
7575
|
+
const registerPendingPermission = (rt, evt) => {
|
|
7576
|
+
const id = evt.toolCallId;
|
|
7577
|
+
if (!id) return;
|
|
7578
|
+
const options = normalizePermissionOptions(evt.options);
|
|
7579
|
+
const toolName = evt.toolName;
|
|
7580
|
+
const text6 = evt.text ?? (toolName ? `Allow "${toolName}"?` : "The agent is requesting permission.");
|
|
7581
|
+
const pending = {
|
|
7582
|
+
id,
|
|
7583
|
+
sessionId: rt.desc.id,
|
|
7584
|
+
toolCallId: id,
|
|
7585
|
+
...toolName ? { toolName } : {},
|
|
7586
|
+
text: text6,
|
|
7587
|
+
options,
|
|
7588
|
+
requestedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
7589
|
+
};
|
|
7590
|
+
pendingPermissions.set(id, pending);
|
|
7591
|
+
rt.desc.awaitingPermission = true;
|
|
7592
|
+
schedulePersist();
|
|
7593
|
+
if (sessionEvents) {
|
|
7594
|
+
sessionEvents.emit({
|
|
7595
|
+
type: "session:permission-request",
|
|
7596
|
+
sessionId: rt.desc.id,
|
|
7597
|
+
permissionId: id,
|
|
7598
|
+
...toolName ? { toolName } : {},
|
|
7599
|
+
text: text6,
|
|
7600
|
+
...rt.desc.label ? { label: rt.desc.label } : {},
|
|
7601
|
+
ts: pending.requestedAt
|
|
7602
|
+
});
|
|
7603
|
+
}
|
|
7604
|
+
};
|
|
7605
|
+
const cancelPendingPermissionsForSession = (rt) => {
|
|
7606
|
+
for (const [id, pending] of pendingPermissions) {
|
|
7607
|
+
if (pending.sessionId !== rt.desc.id) continue;
|
|
7608
|
+
pendingPermissions.delete(id);
|
|
7609
|
+
try {
|
|
7610
|
+
void rt.agentSession?.respondPermission?.(id, { cancelled: true });
|
|
7611
|
+
} catch {
|
|
7612
|
+
}
|
|
7613
|
+
if (sessionEvents) {
|
|
7614
|
+
sessionEvents.emit({
|
|
7615
|
+
type: "session:permission-resolved",
|
|
7616
|
+
sessionId: rt.desc.id,
|
|
7617
|
+
permissionId: id,
|
|
7618
|
+
decision: "cancelled",
|
|
7619
|
+
...rt.desc.label ? { label: rt.desc.label } : {},
|
|
7620
|
+
ts: (/* @__PURE__ */ new Date()).toISOString()
|
|
7621
|
+
});
|
|
7622
|
+
}
|
|
7623
|
+
}
|
|
7624
|
+
delete rt.desc.awaitingPermission;
|
|
7625
|
+
};
|
|
7122
7626
|
const schedulePersist = () => {
|
|
7123
7627
|
if (!persist) return;
|
|
7124
7628
|
if (persistTimer) clearTimeout(persistTimer);
|
|
@@ -7198,6 +7702,7 @@ function createSessionsRegistry(opts) {
|
|
|
7198
7702
|
const projectEvent = (rt, evt) => {
|
|
7199
7703
|
switch (evt.kind) {
|
|
7200
7704
|
case "text-delta":
|
|
7705
|
+
releaseBlockedOn(rt.desc);
|
|
7201
7706
|
if (evt.text) {
|
|
7202
7707
|
const combined = rt.textBuf + evt.text;
|
|
7203
7708
|
const lines = combined.split(/\r?\n/);
|
|
@@ -7223,17 +7728,18 @@ function createSessionsRegistry(opts) {
|
|
|
7223
7728
|
rt.desc.blockedOn = blocked;
|
|
7224
7729
|
rt.desc.pendingToolCallId = evt.toolCallId;
|
|
7225
7730
|
}
|
|
7226
|
-
|
|
7227
|
-
|
|
7228
|
-
|
|
7229
|
-
|
|
7230
|
-
|
|
7731
|
+
if (!evt.isUpdate) {
|
|
7732
|
+
appendLine(
|
|
7733
|
+
rt,
|
|
7734
|
+
`\x1B[36m[tool] ${formatToolCall(evt.toolName ?? "?", evt.arguments)}\x1B[0m`,
|
|
7735
|
+
"stdout"
|
|
7736
|
+
);
|
|
7737
|
+
}
|
|
7231
7738
|
break;
|
|
7232
7739
|
}
|
|
7233
7740
|
case "tool-result": {
|
|
7234
7741
|
if (rt.desc.blockedOn && evt.toolCallId === rt.desc.pendingToolCallId) {
|
|
7235
|
-
rt.desc
|
|
7236
|
-
rt.desc.pendingToolCallId = void 0;
|
|
7742
|
+
releaseBlockedOn(rt.desc);
|
|
7237
7743
|
}
|
|
7238
7744
|
const summary = formatToolResult(evt.toolName, evt.result, evt.isError ?? false);
|
|
7239
7745
|
if (summary) {
|
|
@@ -7257,7 +7763,12 @@ function createSessionsRegistry(opts) {
|
|
|
7257
7763
|
source: "structured"
|
|
7258
7764
|
};
|
|
7259
7765
|
}
|
|
7260
|
-
|
|
7766
|
+
if (rt.permissionHold) {
|
|
7767
|
+
registerPendingPermission(rt, evt);
|
|
7768
|
+
appendLine(rt, `\x1B[33m[permission] ${evt.text ?? evt.toolName ?? "requesting permission"}\x1B[0m`, "stdout");
|
|
7769
|
+
} else {
|
|
7770
|
+
appendLine(rt, `\x1B[33m[awaiting input]\x1B[0m`, "stdout");
|
|
7771
|
+
}
|
|
7261
7772
|
break;
|
|
7262
7773
|
}
|
|
7263
7774
|
case "turn-end": {
|
|
@@ -7282,6 +7793,7 @@ function createSessionsRegistry(opts) {
|
|
|
7282
7793
|
break;
|
|
7283
7794
|
}
|
|
7284
7795
|
case "error": {
|
|
7796
|
+
releaseBlockedOn(rt.desc);
|
|
7285
7797
|
const code = typeof evt.error?.code === "number" ? ` (code ${evt.error.code})` : "";
|
|
7286
7798
|
appendLine(
|
|
7287
7799
|
rt,
|
|
@@ -7314,7 +7826,10 @@ function createSessionsRegistry(opts) {
|
|
|
7314
7826
|
// reported cost/tokens are available live to session_list / session_usage.
|
|
7315
7827
|
case "usage_update": {
|
|
7316
7828
|
if (typeof evt.size === "number" && evt.size > 0) rt.desc.contextSize = evt.size;
|
|
7317
|
-
if (typeof evt.used === "number" && evt.used > 0)
|
|
7829
|
+
if (typeof evt.used === "number" && evt.used > 0) {
|
|
7830
|
+
const used = plausibleContextUsed(rt.desc.contextSize, evt.used);
|
|
7831
|
+
if (used !== void 0) rt.desc.contextUsed = used;
|
|
7832
|
+
}
|
|
7318
7833
|
if (evt.cost) {
|
|
7319
7834
|
rt.desc.costUsd = evt.cost.amount;
|
|
7320
7835
|
rt.adapterReportedCost = true;
|
|
@@ -7344,13 +7859,13 @@ function createSessionsRegistry(opts) {
|
|
|
7344
7859
|
}
|
|
7345
7860
|
return rt;
|
|
7346
7861
|
};
|
|
7347
|
-
const waitForTurnSettled = (rt, id) => {
|
|
7862
|
+
const waitForTurnSettled = (rt, id, caller) => {
|
|
7348
7863
|
if (!rt.busy) return Promise.resolve();
|
|
7349
|
-
return new Promise((
|
|
7864
|
+
return new Promise((resolve12, reject) => {
|
|
7350
7865
|
const onBusy = (busy) => {
|
|
7351
7866
|
if (busy) return;
|
|
7352
7867
|
cleanup();
|
|
7353
|
-
|
|
7868
|
+
resolve12();
|
|
7354
7869
|
};
|
|
7355
7870
|
const cleanup = () => {
|
|
7356
7871
|
clearTimeout(timer);
|
|
@@ -7360,28 +7875,28 @@ function createSessionsRegistry(opts) {
|
|
|
7360
7875
|
cleanup();
|
|
7361
7876
|
reject(
|
|
7362
7877
|
new Error(
|
|
7363
|
-
|
|
7878
|
+
`${caller}: session "${id}" did not settle after interrupt within ${INTERRUPT_SETTLE_TIMEOUT_MS}ms`
|
|
7364
7879
|
)
|
|
7365
7880
|
);
|
|
7366
7881
|
}, INTERRUPT_SETTLE_TIMEOUT_MS);
|
|
7367
7882
|
rt.emitter.on("busy", onBusy);
|
|
7368
7883
|
});
|
|
7369
7884
|
};
|
|
7370
|
-
const interruptInFlightTurn = async (rt, id) => {
|
|
7885
|
+
const interruptInFlightTurn = async (rt, id, caller) => {
|
|
7371
7886
|
const session = rt.agentSession;
|
|
7372
7887
|
if (!session) {
|
|
7373
7888
|
throw new Error(
|
|
7374
|
-
|
|
7889
|
+
`${caller}: session "${id}" is mid-turn but has no live agent session to cancel`
|
|
7375
7890
|
);
|
|
7376
7891
|
}
|
|
7377
7892
|
try {
|
|
7378
7893
|
await session.cancel();
|
|
7379
7894
|
} catch (err) {
|
|
7380
7895
|
throw new Error(
|
|
7381
|
-
|
|
7896
|
+
`${caller}: session "${id}" does not support interrupt \u2014 cancelling the in-flight turn failed: ${err instanceof Error ? err.message : String(err)}`
|
|
7382
7897
|
);
|
|
7383
7898
|
}
|
|
7384
|
-
await waitForTurnSettled(rt, id);
|
|
7899
|
+
await waitForTurnSettled(rt, id, caller);
|
|
7385
7900
|
};
|
|
7386
7901
|
const maybeResumeAgent = async (rt) => {
|
|
7387
7902
|
if (rt.agentSession) return;
|
|
@@ -7469,12 +7984,13 @@ function createSessionsRegistry(opts) {
|
|
|
7469
7984
|
rt.emitter.emit("busy", true);
|
|
7470
7985
|
rt.desc.awaitingInput = false;
|
|
7471
7986
|
rt.desc.awaitingQuestion = void 0;
|
|
7472
|
-
rt.desc
|
|
7473
|
-
rt.desc.pendingToolCallId = void 0;
|
|
7987
|
+
releaseBlockedOn(rt.desc);
|
|
7474
7988
|
let turnCompleted = false;
|
|
7475
7989
|
let sawTurnEnd = false;
|
|
7476
7990
|
let abnormalReason;
|
|
7477
7991
|
let turnEndReason;
|
|
7992
|
+
let sawAssistantText = false;
|
|
7993
|
+
let sawToolCall = false;
|
|
7478
7994
|
try {
|
|
7479
7995
|
appendLine(
|
|
7480
7996
|
rt,
|
|
@@ -7486,6 +8002,8 @@ function createSessionsRegistry(opts) {
|
|
|
7486
8002
|
for await (const evt of rt.agentSession.send(wrapped)) {
|
|
7487
8003
|
transcriptWriter.recordEvent(rt.desc.id, evt);
|
|
7488
8004
|
projectEvent(rt, evt);
|
|
8005
|
+
if (evt.kind === "text-delta" && evt.text?.trim()) sawAssistantText = true;
|
|
8006
|
+
else if (evt.kind === "tool-call") sawToolCall = true;
|
|
7489
8007
|
if (evt.kind === "turn-end") {
|
|
7490
8008
|
sawTurnEnd = true;
|
|
7491
8009
|
turnEndReason = evt.reason;
|
|
@@ -7512,8 +8030,7 @@ function createSessionsRegistry(opts) {
|
|
|
7512
8030
|
rt.busy = false;
|
|
7513
8031
|
rt.desc.busy = false;
|
|
7514
8032
|
rt.emitter.emit("busy", false);
|
|
7515
|
-
rt.desc
|
|
7516
|
-
rt.desc.pendingToolCallId = void 0;
|
|
8033
|
+
releaseBlockedOn(rt.desc);
|
|
7517
8034
|
if (!sawTurnEnd) {
|
|
7518
8035
|
const reason = abnormalReason ?? (rt.desc.status === "killed" ? "aborted" : "exited");
|
|
7519
8036
|
const synthetic = { kind: "turn-end", reason };
|
|
@@ -7566,6 +8083,14 @@ function createSessionsRegistry(opts) {
|
|
|
7566
8083
|
void transcriptWriter.close(rt.desc.id);
|
|
7567
8084
|
tracedSessions.delete(rt.desc.id);
|
|
7568
8085
|
}
|
|
8086
|
+
const emptyTurn = !sawAssistantText && !sawToolCall && !(rt.desc.awaitingInput ?? false);
|
|
8087
|
+
if (emptyTurn) {
|
|
8088
|
+
appendLine(
|
|
8089
|
+
rt,
|
|
8090
|
+
`\x1B[33m[warning] empty turn \u2014 no assistant output, no tool call, cost ${rt.desc.costUsd !== void 0 ? `$${rt.desc.costUsd}` : "unknown"}. Likely an invalid model id or a provider that returned nothing; verify the model slug (agentproto models <adapter>).\x1B[0m`,
|
|
8091
|
+
"stderr"
|
|
8092
|
+
);
|
|
8093
|
+
}
|
|
7569
8094
|
if (sessionEvents) {
|
|
7570
8095
|
if (rt.desc.awaitingInput && !rt.desc.awaitingQuestion) {
|
|
7571
8096
|
rt.desc.awaitingQuestion = deriveHeuristicQuestion(rt.recentLines);
|
|
@@ -7578,7 +8103,8 @@ function createSessionsRegistry(opts) {
|
|
|
7578
8103
|
label: rt.desc.label,
|
|
7579
8104
|
ts,
|
|
7580
8105
|
...rt.desc.awaitingQuestion ? { question: rt.desc.awaitingQuestion } : {},
|
|
7581
|
-
...turnEndReason ? { reason: turnEndReason } : {}
|
|
8106
|
+
...turnEndReason ? { reason: turnEndReason } : {},
|
|
8107
|
+
...emptyTurn ? { empty: true } : {}
|
|
7582
8108
|
});
|
|
7583
8109
|
if (rt.desc.awaitingInput) {
|
|
7584
8110
|
sessionEvents.emit({
|
|
@@ -7608,8 +8134,8 @@ function createSessionsRegistry(opts) {
|
|
|
7608
8134
|
};
|
|
7609
8135
|
const wireOutputStreams = (rt) => {
|
|
7610
8136
|
const onChunk = (stream) => (chunk) => {
|
|
7611
|
-
const
|
|
7612
|
-
for (const line of
|
|
8137
|
+
const text6 = typeof chunk === "string" ? chunk : chunk.toString("utf8");
|
|
8138
|
+
for (const line of text6.split(/\r?\n/)) {
|
|
7613
8139
|
if (line.length > 0) appendLine(rt, line, stream);
|
|
7614
8140
|
}
|
|
7615
8141
|
};
|
|
@@ -7764,6 +8290,7 @@ function createSessionsRegistry(opts) {
|
|
|
7764
8290
|
...input.parentSessionId ? { parentSessionId: input.parentSessionId } : {},
|
|
7765
8291
|
depth: input.depth ?? 0,
|
|
7766
8292
|
...input.model ? { model: input.model } : {},
|
|
8293
|
+
...input.auth ? { auth: input.auth } : {},
|
|
7767
8294
|
...priorCommandSessionId ? { priorCommandSessionId } : {},
|
|
7768
8295
|
...input.remote ? { remote: true } : {},
|
|
7769
8296
|
...input.sandboxId ? { sandboxId: input.sandboxId } : {},
|
|
@@ -7784,7 +8311,8 @@ function createSessionsRegistry(opts) {
|
|
|
7784
8311
|
textBuf: "",
|
|
7785
8312
|
thoughtBuf: "",
|
|
7786
8313
|
maxCostUsd: input.maxCostUsd,
|
|
7787
|
-
readUsage: input.readUsage
|
|
8314
|
+
readUsage: input.readUsage,
|
|
8315
|
+
...input.permissionHold ? { permissionHold: true } : {}
|
|
7788
8316
|
};
|
|
7789
8317
|
rt.emitter.setMaxListeners(50);
|
|
7790
8318
|
sessions.set(id, rt);
|
|
@@ -7983,8 +8511,11 @@ function createSessionsRegistry(opts) {
|
|
|
7983
8511
|
schedulePersist();
|
|
7984
8512
|
return desc;
|
|
7985
8513
|
},
|
|
7986
|
-
async sendPrompt(id, message) {
|
|
8514
|
+
async sendPrompt(id, message, opts2) {
|
|
7987
8515
|
const rtPre = sessions.get(id);
|
|
8516
|
+
if (opts2?.interrupt && rtPre?.busy) {
|
|
8517
|
+
await interruptInFlightTurn(rtPre, id, "sendPrompt");
|
|
8518
|
+
}
|
|
7988
8519
|
if (rtPre) await maybeResumeAgent(rtPre);
|
|
7989
8520
|
const rt = validateAgentTurn(id, "sendPrompt");
|
|
7990
8521
|
await runAgentTurn(rt, message);
|
|
@@ -7995,7 +8526,7 @@ function createSessionsRegistry(opts) {
|
|
|
7995
8526
|
throw new Error(`enqueuePrompt: no session "${id}"`);
|
|
7996
8527
|
}
|
|
7997
8528
|
if (opts2?.interrupt && rtPre.busy) {
|
|
7998
|
-
await interruptInFlightTurn(rtPre, id);
|
|
8529
|
+
await interruptInFlightTurn(rtPre, id, "enqueuePrompt");
|
|
7999
8530
|
}
|
|
8000
8531
|
await maybeResumeAgent(rtPre);
|
|
8001
8532
|
const rt = validateAgentTurn(id, "enqueuePrompt");
|
|
@@ -8032,6 +8563,9 @@ function createSessionsRegistry(opts) {
|
|
|
8032
8563
|
rt.emitter.on("line", handler);
|
|
8033
8564
|
return () => rt.emitter.off("line", handler);
|
|
8034
8565
|
},
|
|
8566
|
+
subscribeToRecords(id, onRecord) {
|
|
8567
|
+
return baseTranscriptWriter.subscribe(id, onRecord);
|
|
8568
|
+
},
|
|
8035
8569
|
attachPty(id, initial, onData, onExit) {
|
|
8036
8570
|
const rt = sessions.get(id);
|
|
8037
8571
|
if (!rt || !rt.pty || !rt.ptySubscribers) return null;
|
|
@@ -8126,6 +8660,74 @@ function createSessionsRegistry(opts) {
|
|
|
8126
8660
|
emitExited(rt);
|
|
8127
8661
|
return true;
|
|
8128
8662
|
},
|
|
8663
|
+
listPendingPermissions(filter) {
|
|
8664
|
+
const all = Array.from(pendingPermissions.values());
|
|
8665
|
+
const scoped = filter?.sessionId ? all.filter((p) => p.sessionId === filter.sessionId) : all;
|
|
8666
|
+
return scoped.map((p) => ({ ...p, options: p.options.map((o) => ({ ...o })) }));
|
|
8667
|
+
},
|
|
8668
|
+
async respondPermission(id, input) {
|
|
8669
|
+
const pending = pendingPermissions.get(id);
|
|
8670
|
+
if (!pending) {
|
|
8671
|
+
return {
|
|
8672
|
+
ok: false,
|
|
8673
|
+
error: "not_found",
|
|
8674
|
+
message: `no pending permission "${id}" (unknown or already resolved)`
|
|
8675
|
+
};
|
|
8676
|
+
}
|
|
8677
|
+
const rt = sessions.get(pending.sessionId);
|
|
8678
|
+
if (!rt || !rt.agentSession) {
|
|
8679
|
+
pendingPermissions.delete(id);
|
|
8680
|
+
if (rt) refreshAwaitingPermission(rt);
|
|
8681
|
+
return {
|
|
8682
|
+
ok: false,
|
|
8683
|
+
error: "session_gone",
|
|
8684
|
+
message: `session "${pending.sessionId}" for permission "${id}" is no longer alive`
|
|
8685
|
+
};
|
|
8686
|
+
}
|
|
8687
|
+
const respond = rt.agentSession.respondPermission;
|
|
8688
|
+
if (!respond) {
|
|
8689
|
+
return {
|
|
8690
|
+
ok: false,
|
|
8691
|
+
error: "unsupported",
|
|
8692
|
+
message: `session "${pending.sessionId}" driver does not support held permissions`
|
|
8693
|
+
};
|
|
8694
|
+
}
|
|
8695
|
+
const selected = selectPermissionOptionId(pending.options, input);
|
|
8696
|
+
if (selected === null) {
|
|
8697
|
+
return {
|
|
8698
|
+
ok: false,
|
|
8699
|
+
error: "no_matching_option",
|
|
8700
|
+
message: `permission "${id}" offers no ${input.decision === "approve" ? "allow" : "reject"}-flavored option; pass an explicit optionId (one of: ${pending.options.map((o) => o.optionId).join(", ")})`
|
|
8701
|
+
};
|
|
8702
|
+
}
|
|
8703
|
+
pendingPermissions.delete(id);
|
|
8704
|
+
const chosenOptionId = "optionId" in selected ? selected.optionId : void 0;
|
|
8705
|
+
delete rt.desc.awaitingInput;
|
|
8706
|
+
rt.desc.awaitingQuestion = void 0;
|
|
8707
|
+
refreshAwaitingPermission(rt);
|
|
8708
|
+
const okResolved = await respond(id, selected);
|
|
8709
|
+
if (sessionEvents) {
|
|
8710
|
+
sessionEvents.emit({
|
|
8711
|
+
type: "session:permission-resolved",
|
|
8712
|
+
sessionId: pending.sessionId,
|
|
8713
|
+
permissionId: id,
|
|
8714
|
+
decision: input.decision,
|
|
8715
|
+
...chosenOptionId ? { optionId: chosenOptionId } : {},
|
|
8716
|
+
...rt.desc.label ? { label: rt.desc.label } : {},
|
|
8717
|
+
ts: (/* @__PURE__ */ new Date()).toISOString()
|
|
8718
|
+
});
|
|
8719
|
+
}
|
|
8720
|
+
schedulePersist();
|
|
8721
|
+
if (!okResolved) {
|
|
8722
|
+
return { ok: true, permission: pending, decision: input.decision, ...chosenOptionId ? { optionId: chosenOptionId } : {} };
|
|
8723
|
+
}
|
|
8724
|
+
return {
|
|
8725
|
+
ok: true,
|
|
8726
|
+
permission: pending,
|
|
8727
|
+
decision: input.decision,
|
|
8728
|
+
...chosenOptionId ? { optionId: chosenOptionId } : {}
|
|
8729
|
+
};
|
|
8730
|
+
},
|
|
8129
8731
|
forget(id) {
|
|
8130
8732
|
const rt = sessions.get(id);
|
|
8131
8733
|
if (!rt) return false;
|
|
@@ -8157,6 +8759,8 @@ function createSessionsRegistry(opts) {
|
|
|
8157
8759
|
if (rt.desc.status === "running" || rt.desc.status === "starting") {
|
|
8158
8760
|
rt.desc.status = "killed";
|
|
8159
8761
|
rt.desc.endedAt = nowIso;
|
|
8762
|
+
rt.desc.endedReason = "daemon-restart";
|
|
8763
|
+
clearInFlightFlags(rt.desc);
|
|
8160
8764
|
if (rt.agentSession) {
|
|
8161
8765
|
recordExitUsageSnapshot(rt);
|
|
8162
8766
|
void rt.agentSession.close().catch(() => void 0);
|
|
@@ -8168,6 +8772,7 @@ function createSessionsRegistry(opts) {
|
|
|
8168
8772
|
}
|
|
8169
8773
|
}
|
|
8170
8774
|
rt.child?.kill("SIGTERM");
|
|
8775
|
+
emitExited(rt);
|
|
8171
8776
|
}
|
|
8172
8777
|
}
|
|
8173
8778
|
void transcriptWriter.closeAll();
|
|
@@ -8191,7 +8796,15 @@ function createSessionsRegistry(opts) {
|
|
|
8191
8796
|
sessions.clear();
|
|
8192
8797
|
}
|
|
8193
8798
|
}
|
|
8194
|
-
function
|
|
8799
|
+
function clearInFlightFlags(desc) {
|
|
8800
|
+
desc.busy = false;
|
|
8801
|
+
desc.awaitingInput = false;
|
|
8802
|
+
desc.awaitingQuestion = void 0;
|
|
8803
|
+
delete desc.awaitingPermission;
|
|
8804
|
+
desc.blockedOn = void 0;
|
|
8805
|
+
desc.pendingToolCallId = void 0;
|
|
8806
|
+
}
|
|
8807
|
+
function loadHistorySnapshot(persistPath, sessions, sessionEvents) {
|
|
8195
8808
|
let raw;
|
|
8196
8809
|
try {
|
|
8197
8810
|
raw = readFileSync(persistPath, "utf8");
|
|
@@ -8216,8 +8829,14 @@ function loadHistorySnapshot(persistPath, sessions) {
|
|
|
8216
8829
|
const reclassified = wasAlive ? {
|
|
8217
8830
|
...desc,
|
|
8218
8831
|
status: "killed",
|
|
8219
|
-
endedAt: desc.endedAt ?? now
|
|
8832
|
+
endedAt: desc.endedAt ?? now,
|
|
8833
|
+
endedReason: "daemon-restart"
|
|
8220
8834
|
} : desc;
|
|
8835
|
+
clearInFlightFlags(reclassified);
|
|
8836
|
+
reclassified.contextUsed = plausibleContextUsed(
|
|
8837
|
+
reclassified.contextSize,
|
|
8838
|
+
reclassified.contextUsed
|
|
8839
|
+
);
|
|
8221
8840
|
const rt = {
|
|
8222
8841
|
desc: reclassified,
|
|
8223
8842
|
recentLines: [],
|
|
@@ -8226,10 +8845,22 @@ function loadHistorySnapshot(persistPath, sessions) {
|
|
|
8226
8845
|
emitter: new EventEmitter(),
|
|
8227
8846
|
busy: false,
|
|
8228
8847
|
textBuf: "",
|
|
8229
|
-
thoughtBuf: ""
|
|
8848
|
+
thoughtBuf: "",
|
|
8849
|
+
exitedEmitted: wasAlive
|
|
8230
8850
|
};
|
|
8231
8851
|
rt.emitter.setMaxListeners(50);
|
|
8232
8852
|
sessions.set(desc.id, rt);
|
|
8853
|
+
if (wasAlive) {
|
|
8854
|
+
sessionEvents?.emit({
|
|
8855
|
+
type: "session:exited",
|
|
8856
|
+
sessionId: reclassified.id,
|
|
8857
|
+
exitCode: reclassified.exitCode,
|
|
8858
|
+
status: "killed",
|
|
8859
|
+
...reclassified.label ? { label: reclassified.label } : {},
|
|
8860
|
+
reason: "daemon-restart",
|
|
8861
|
+
ts: now
|
|
8862
|
+
});
|
|
8863
|
+
}
|
|
8233
8864
|
}
|
|
8234
8865
|
}
|
|
8235
8866
|
function quoteArg(arg) {
|
|
@@ -8318,7 +8949,7 @@ async function monitorSessionWait(opts) {
|
|
|
8318
8949
|
};
|
|
8319
8950
|
}
|
|
8320
8951
|
}
|
|
8321
|
-
return new Promise((
|
|
8952
|
+
return new Promise((resolve12) => {
|
|
8322
8953
|
const unsubs = [];
|
|
8323
8954
|
let settled = false;
|
|
8324
8955
|
const finish = (result) => {
|
|
@@ -8326,7 +8957,7 @@ async function monitorSessionWait(opts) {
|
|
|
8326
8957
|
settled = true;
|
|
8327
8958
|
clearTimeout(timer);
|
|
8328
8959
|
for (const u of unsubs) u();
|
|
8329
|
-
|
|
8960
|
+
resolve12(result);
|
|
8330
8961
|
};
|
|
8331
8962
|
const relevantTypes = targetEvent === "any" ? ["session:turn-end", "session:awaiting-input", "session:exited"] : targetEvent === "turn-end" ? ["session:turn-end", "session:awaiting-input"] : targetEvent === "awaiting-input" ? ["session:awaiting-input"] : ["session:exited"];
|
|
8332
8963
|
const idSet = new Set(resolvedIds);
|
|
@@ -8362,13 +8993,13 @@ async function monitorPolicyWait(opts) {
|
|
|
8362
8993
|
if (isSettledStatus(initial.status)) {
|
|
8363
8994
|
return { timedOut: false, state: initial };
|
|
8364
8995
|
}
|
|
8365
|
-
return new Promise((
|
|
8996
|
+
return new Promise((resolve12) => {
|
|
8366
8997
|
let settled = false;
|
|
8367
8998
|
const timer = setTimeout(() => {
|
|
8368
8999
|
if (settled) return;
|
|
8369
9000
|
settled = true;
|
|
8370
9001
|
unsub();
|
|
8371
|
-
|
|
9002
|
+
resolve12({ timedOut: true });
|
|
8372
9003
|
}, timeoutMs);
|
|
8373
9004
|
const unsub = supervisor.onSettle((id) => {
|
|
8374
9005
|
if (id !== policyId) return;
|
|
@@ -8378,7 +9009,7 @@ async function monitorPolicyWait(opts) {
|
|
|
8378
9009
|
settled = true;
|
|
8379
9010
|
clearTimeout(timer);
|
|
8380
9011
|
unsub();
|
|
8381
|
-
|
|
9012
|
+
resolve12({ timedOut: false, state });
|
|
8382
9013
|
});
|
|
8383
9014
|
});
|
|
8384
9015
|
}
|
|
@@ -8398,7 +9029,7 @@ function registerOrchestrationTools(rawServer, opts) {
|
|
|
8398
9029
|
"Opaque cursor from a prior call's `nextCursor`. Omit (or pass 0) to start from the current tail \u2014 returns events emitted after this call."
|
|
8399
9030
|
),
|
|
8400
9031
|
sessionIds: z.array(z.string()).optional().describe("Filter to these session ids. Omit \u2192 all sessions."),
|
|
8401
|
-
types: z.array(z.enum(["turn-end", "awaiting-input", "exited", "command-done", "policy:passed", "policy:failed", "policy:commit-ready", "policy:committed", "cron:fired", "cron:succeeded", "cron:failed"])).optional().describe("Filter to these event types. Omit \u2192 all types."),
|
|
9032
|
+
types: z.array(z.enum(["turn-end", "awaiting-input", "permission-request", "permission-resolved", "exited", "command-done", "policy:passed", "policy:failed", "policy:commit-ready", "policy:committed", "cron:fired", "cron:succeeded", "cron:failed"])).optional().describe("Filter to these event types. Omit \u2192 all types."),
|
|
8402
9033
|
limit: z.number().int().min(1).max(200).optional().describe("Max events to return. Default 50.")
|
|
8403
9034
|
},
|
|
8404
9035
|
async (input) => {
|
|
@@ -8415,6 +9046,88 @@ function registerOrchestrationTools(rawServer, opts) {
|
|
|
8415
9046
|
};
|
|
8416
9047
|
}
|
|
8417
9048
|
);
|
|
9049
|
+
const isSessionInScope = (sessionId) => {
|
|
9050
|
+
if (!callerScope) return true;
|
|
9051
|
+
if (!callerScope.ownerSessionId) return false;
|
|
9052
|
+
return collectSubtree(callerScope.ownerSessionId, registry.list()).has(sessionId);
|
|
9053
|
+
};
|
|
9054
|
+
const enrichPermission2 = (p) => {
|
|
9055
|
+
const desc = registry.get(p.sessionId);
|
|
9056
|
+
const ageMs = Date.now() - new Date(p.requestedAt).getTime();
|
|
9057
|
+
return {
|
|
9058
|
+
...p,
|
|
9059
|
+
...desc?.adapterSlug ? { adapter: desc.adapterSlug } : {},
|
|
9060
|
+
...desc?.label ? { sessionLabel: desc.label } : {},
|
|
9061
|
+
...desc?.command ? { sessionTitle: desc.command } : {},
|
|
9062
|
+
ageMs: ageMs >= 0 ? ageMs : 0
|
|
9063
|
+
};
|
|
9064
|
+
};
|
|
9065
|
+
server.tool(
|
|
9066
|
+
"permissions_list",
|
|
9067
|
+
"List permission requests currently HELD across all permission-hold sessions (spawned with `permissionHold`). Each entry: id, sessionId, session adapter/title, the tool being requested, the offered options, and age. Resolve one with `permissions_respond`. Optionally filter by `sessionId`. Empty list = nothing is waiting on a decision.",
|
|
9068
|
+
{
|
|
9069
|
+
sessionId: z.string().optional().describe("Filter to one session's pending permissions. Omit \u2192 all sessions.")
|
|
9070
|
+
},
|
|
9071
|
+
async (input) => {
|
|
9072
|
+
const pending = registry.listPendingPermissions(input.sessionId ? { sessionId: input.sessionId } : void 0).filter((p) => isSessionInScope(p.sessionId)).map(enrichPermission2);
|
|
9073
|
+
return {
|
|
9074
|
+
content: [{ type: "text", text: JSON.stringify({ permissions: pending }, null, 2) }]
|
|
9075
|
+
};
|
|
9076
|
+
}
|
|
9077
|
+
);
|
|
9078
|
+
server.tool(
|
|
9079
|
+
"permissions_respond",
|
|
9080
|
+
'Resolve a permission request parked by a permission-hold session (see `permissions_list`). `decision:"approve"` selects an allow option (allow-always when `scope:"always"` is offered, else allow-once); `decision:"deny"` selects a reject option (or cancels the request when none is offered). An explicit `optionId` overrides the decision\u2192option mapping. Errors clearly on an unknown/already-resolved id. The agent\'s turn unblocks with the chosen outcome.',
|
|
9081
|
+
{
|
|
9082
|
+
id: z.string().describe("Pending permission id from `permissions_list`."),
|
|
9083
|
+
decision: z.enum(["approve", "deny"]).describe("approve \u2192 an allow option; deny \u2192 a reject option / cancel."),
|
|
9084
|
+
optionId: z.string().optional().describe("Explicit offered optionId \u2014 wins over `decision` mapping."),
|
|
9085
|
+
scope: z.enum(["once", "always"]).optional().describe("For approve: prefer allow-always when the request offers it.")
|
|
9086
|
+
},
|
|
9087
|
+
async (input) => {
|
|
9088
|
+
const pending = registry.listPendingPermissions().find((p) => p.id === input.id);
|
|
9089
|
+
if (!pending || !isSessionInScope(pending.sessionId)) {
|
|
9090
|
+
return {
|
|
9091
|
+
content: [
|
|
9092
|
+
{
|
|
9093
|
+
type: "text",
|
|
9094
|
+
text: JSON.stringify({ error: "not_found", message: `no pending permission "${input.id}"` })
|
|
9095
|
+
}
|
|
9096
|
+
],
|
|
9097
|
+
isError: true
|
|
9098
|
+
};
|
|
9099
|
+
}
|
|
9100
|
+
const result = await registry.respondPermission(input.id, {
|
|
9101
|
+
decision: input.decision,
|
|
9102
|
+
...input.optionId ? { optionId: input.optionId } : {},
|
|
9103
|
+
...input.scope ? { scope: input.scope } : {}
|
|
9104
|
+
});
|
|
9105
|
+
if (!result.ok) {
|
|
9106
|
+
return {
|
|
9107
|
+
content: [{ type: "text", text: JSON.stringify({ error: result.error, message: result.message }) }],
|
|
9108
|
+
isError: true
|
|
9109
|
+
};
|
|
9110
|
+
}
|
|
9111
|
+
return {
|
|
9112
|
+
content: [
|
|
9113
|
+
{
|
|
9114
|
+
type: "text",
|
|
9115
|
+
text: JSON.stringify(
|
|
9116
|
+
{
|
|
9117
|
+
ok: true,
|
|
9118
|
+
id: input.id,
|
|
9119
|
+
sessionId: result.permission.sessionId,
|
|
9120
|
+
decision: result.decision,
|
|
9121
|
+
...result.optionId ? { optionId: result.optionId } : {}
|
|
9122
|
+
},
|
|
9123
|
+
null,
|
|
9124
|
+
2
|
|
9125
|
+
)
|
|
9126
|
+
}
|
|
9127
|
+
]
|
|
9128
|
+
};
|
|
9129
|
+
}
|
|
9130
|
+
);
|
|
8418
9131
|
const { routineRunner } = opts;
|
|
8419
9132
|
if (routineRunner) {
|
|
8420
9133
|
server.tool(
|
|
@@ -8899,7 +9612,16 @@ function registerOrchestrationTools(rawServer, opts) {
|
|
|
8899
9612
|
"session_monitor",
|
|
8900
9613
|
"Multiplexed long-poll: block until ANY of the listed sessions fires a lifecycle event. Returns immediately when a session is already in the target state. Eliminates polling in multi-session fan-in orchestration \u2014 one call replaces N concurrent waitForTurnEnd calls.",
|
|
8901
9614
|
{
|
|
8902
|
-
|
|
9615
|
+
// Accept the natural shapes an agent already has on hand: a single id
|
|
9616
|
+
// (the `id` agent_start returns, or `sessionId` from the drive tools),
|
|
9617
|
+
// or the fan-in array. `sessionIds` additionally tolerates a bare
|
|
9618
|
+
// string (not just an array) so a one-session monitor doesn't force
|
|
9619
|
+
// array-wrapping. All three coalesce into the watched-id list below.
|
|
9620
|
+
sessionIds: jsonTolerant(z.union([z.string(), z.array(z.string())])).optional().describe(
|
|
9621
|
+
"Session ids (or names) to watch \u2014 an array for fan-in, or a single id. Returns on the first hit. Provide this OR `sessionId`/`id`."
|
|
9622
|
+
),
|
|
9623
|
+
sessionId: z.string().optional().describe("Single session id/name \u2014 singular alias for `sessionIds`."),
|
|
9624
|
+
id: z.string().optional().describe("Alias for `sessionId` \u2014 the `id` returned by agent_start."),
|
|
8903
9625
|
timeoutMs: z.number().int().min(1e3).max(49e3).optional().describe("Max wait in ms. Default 25 000. Stays under MCP request timeout."),
|
|
8904
9626
|
event: z.enum(["turn-end", "awaiting-input", "exited", "any"]).optional().describe(
|
|
8905
9627
|
"Event type to wait for. Default 'any'. 'turn-end' also matches 'awaiting-input' (both signal end-of-turn)."
|
|
@@ -8911,14 +9633,43 @@ function registerOrchestrationTools(rawServer, opts) {
|
|
|
8911
9633
|
async (input) => {
|
|
8912
9634
|
const timeout = input.timeoutMs ?? 25e3;
|
|
8913
9635
|
const targetEvent = input.event ?? "any";
|
|
8914
|
-
const
|
|
8915
|
-
|
|
8916
|
-
|
|
8917
|
-
|
|
8918
|
-
|
|
8919
|
-
|
|
8920
|
-
|
|
8921
|
-
|
|
9636
|
+
const fromSessionIds = input.sessionIds === void 0 ? [] : Array.isArray(input.sessionIds) ? input.sessionIds : [input.sessionIds];
|
|
9637
|
+
const singular = input.sessionId ?? input.id;
|
|
9638
|
+
const sessionIds = [...fromSessionIds, ...singular ? [singular] : []];
|
|
9639
|
+
if (sessionIds.length === 0) {
|
|
9640
|
+
return {
|
|
9641
|
+
content: [
|
|
9642
|
+
{
|
|
9643
|
+
type: "text",
|
|
9644
|
+
text: JSON.stringify({
|
|
9645
|
+
error: "session_monitor: no sessions to watch \u2014 pass `sessionIds` (array or single id) or `sessionId`/`id`."
|
|
9646
|
+
})
|
|
9647
|
+
}
|
|
9648
|
+
],
|
|
9649
|
+
isError: true
|
|
9650
|
+
};
|
|
9651
|
+
}
|
|
9652
|
+
if (sessionIds.length > 20) {
|
|
9653
|
+
return {
|
|
9654
|
+
content: [
|
|
9655
|
+
{
|
|
9656
|
+
type: "text",
|
|
9657
|
+
text: JSON.stringify({
|
|
9658
|
+
error: `session_monitor: too many sessions (${sessionIds.length}); max 20 per call.`
|
|
9659
|
+
})
|
|
9660
|
+
}
|
|
9661
|
+
],
|
|
9662
|
+
isError: true
|
|
9663
|
+
};
|
|
9664
|
+
}
|
|
9665
|
+
const result = await monitorSessionWait({
|
|
9666
|
+
registry,
|
|
9667
|
+
sessionEvents,
|
|
9668
|
+
eventRing,
|
|
9669
|
+
sessionIds,
|
|
9670
|
+
event: targetEvent,
|
|
9671
|
+
timeoutMs: timeout,
|
|
9672
|
+
...input.since !== void 0 ? { since: input.since } : {}
|
|
8922
9673
|
});
|
|
8923
9674
|
const payload = { ...result };
|
|
8924
9675
|
if (result.source === "ring" && input.since !== void 0) {
|
|
@@ -9777,6 +10528,28 @@ async function startHttpServer(opts) {
|
|
|
9777
10528
|
);
|
|
9778
10529
|
if (handled) return;
|
|
9779
10530
|
}
|
|
10531
|
+
if (opts.sessions && path.startsWith("/permissions")) {
|
|
10532
|
+
if ((req.method ?? "GET") !== "GET") {
|
|
10533
|
+
const gate = checkSessionsToken(req);
|
|
10534
|
+
if (gate !== "ok") {
|
|
10535
|
+
rejectUnauthorizedSession(req, res, gate);
|
|
10536
|
+
return;
|
|
10537
|
+
}
|
|
10538
|
+
}
|
|
10539
|
+
const handled = await handlePermissions(req, res, path, opts.sessions);
|
|
10540
|
+
if (handled) return;
|
|
10541
|
+
}
|
|
10542
|
+
if (opts.pairings && path.startsWith("/pairings")) {
|
|
10543
|
+
if ((req.method ?? "GET") !== "GET") {
|
|
10544
|
+
const gate = checkSessionsToken(req);
|
|
10545
|
+
if (gate !== "ok") {
|
|
10546
|
+
rejectUnauthorizedSession(req, res, gate);
|
|
10547
|
+
return;
|
|
10548
|
+
}
|
|
10549
|
+
}
|
|
10550
|
+
const handled = await handlePairings(req, res, path, opts.pairings);
|
|
10551
|
+
if (handled) return;
|
|
10552
|
+
}
|
|
9780
10553
|
if (opts.cronScheduler && path.startsWith("/cron")) {
|
|
9781
10554
|
const handled = await handleCron(req, res, path, opts.cronScheduler);
|
|
9782
10555
|
if (handled) return;
|
|
@@ -9842,16 +10615,16 @@ async function startHttpServer(opts) {
|
|
|
9842
10615
|
});
|
|
9843
10616
|
});
|
|
9844
10617
|
const bind = opts.bind ?? "127.0.0.1";
|
|
9845
|
-
await new Promise((
|
|
10618
|
+
await new Promise((resolve12, reject) => {
|
|
9846
10619
|
server.once("error", reject);
|
|
9847
|
-
server.listen(opts.port, bind, () =>
|
|
10620
|
+
server.listen(opts.port, bind, () => resolve12());
|
|
9848
10621
|
});
|
|
9849
10622
|
return {
|
|
9850
10623
|
url: `http://${bind}:${opts.port}`,
|
|
9851
10624
|
async stop() {
|
|
9852
10625
|
wss.close();
|
|
9853
10626
|
server.closeAllConnections();
|
|
9854
|
-
await new Promise((
|
|
10627
|
+
await new Promise((resolve12) => server.close(() => resolve12()));
|
|
9855
10628
|
}
|
|
9856
10629
|
};
|
|
9857
10630
|
}
|
|
@@ -9969,6 +10742,33 @@ function clampInt(raw, fallback, min, max) {
|
|
|
9969
10742
|
if (n > max) return max;
|
|
9970
10743
|
return n;
|
|
9971
10744
|
}
|
|
10745
|
+
function deliverRecordsExactlyOnce(opts) {
|
|
10746
|
+
let lastSeqSent = opts.since;
|
|
10747
|
+
let replaying = true;
|
|
10748
|
+
const buffered = [];
|
|
10749
|
+
const gate = (record) => {
|
|
10750
|
+
const seq = record.seq;
|
|
10751
|
+
if (typeof seq !== "number" || seq <= lastSeqSent) return;
|
|
10752
|
+
lastSeqSent = seq;
|
|
10753
|
+
opts.send(record);
|
|
10754
|
+
};
|
|
10755
|
+
const unsubscribe = opts.subscribe((record) => {
|
|
10756
|
+
if (replaying) {
|
|
10757
|
+
buffered.push(record);
|
|
10758
|
+
} else {
|
|
10759
|
+
gate(record);
|
|
10760
|
+
}
|
|
10761
|
+
});
|
|
10762
|
+
const done = (async () => {
|
|
10763
|
+
for await (const rec of opts.diskRecords) {
|
|
10764
|
+
gate(rec);
|
|
10765
|
+
}
|
|
10766
|
+
replaying = false;
|
|
10767
|
+
for (const rec of buffered) gate(rec);
|
|
10768
|
+
buffered.length = 0;
|
|
10769
|
+
})();
|
|
10770
|
+
return { unsubscribe, done };
|
|
10771
|
+
}
|
|
9972
10772
|
function parseOrchestratorField(raw) {
|
|
9973
10773
|
const value = typeof raw === "string" ? tryParseJson(raw) : raw;
|
|
9974
10774
|
if (typeof value === "boolean") return value;
|
|
@@ -10002,6 +10802,39 @@ function parseMcpServersField(raw) {
|
|
|
10002
10802
|
}
|
|
10003
10803
|
return servers;
|
|
10004
10804
|
}
|
|
10805
|
+
function parseOptionsField(raw) {
|
|
10806
|
+
const value = typeof raw === "string" ? tryParseJson(raw) : raw;
|
|
10807
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
10808
|
+
const out = {};
|
|
10809
|
+
for (const [k, v] of Object.entries(value)) {
|
|
10810
|
+
if (typeof v === "boolean" || typeof v === "number" || typeof v === "string") {
|
|
10811
|
+
out[k] = v;
|
|
10812
|
+
}
|
|
10813
|
+
}
|
|
10814
|
+
return out;
|
|
10815
|
+
}
|
|
10816
|
+
function parseAuthField(raw) {
|
|
10817
|
+
const value = typeof raw === "string" ? tryParseJson(raw) : raw;
|
|
10818
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
10819
|
+
const obj = value;
|
|
10820
|
+
const mode = obj.mode === "subscription" || obj.mode === "api-key" ? obj.mode : void 0;
|
|
10821
|
+
const token = typeof obj.token === "string" ? obj.token : void 0;
|
|
10822
|
+
const apiKey = typeof obj.apiKey === "string" ? obj.apiKey : void 0;
|
|
10823
|
+
return {
|
|
10824
|
+
...mode ? { mode } : {},
|
|
10825
|
+
...token !== void 0 ? { token } : {},
|
|
10826
|
+
...apiKey !== void 0 ? { apiKey } : {}
|
|
10827
|
+
};
|
|
10828
|
+
}
|
|
10829
|
+
async function resolveSlugFromCwd(cwd) {
|
|
10830
|
+
if (!cwd) return void 0;
|
|
10831
|
+
try {
|
|
10832
|
+
const config = await loadWorkspacesConfig();
|
|
10833
|
+
return findWorkspaceByPath(config, cwd)?.slug;
|
|
10834
|
+
} catch {
|
|
10835
|
+
return void 0;
|
|
10836
|
+
}
|
|
10837
|
+
}
|
|
10005
10838
|
async function handleSessions(req, res, path, registry, resolveAgentAdapter, ptyEnabled = false, resolveBrowserAdapter, listBrowserAdapters, sessionEvents, eventRing, buildOrchestratorMcp, daemonMcpUrl) {
|
|
10006
10839
|
const json = (status, body) => {
|
|
10007
10840
|
res.writeHead(status, { "content-type": "application/json" });
|
|
@@ -10040,8 +10873,17 @@ async function handleSessions(req, res, path, registry, resolveAgentAdapter, pty
|
|
|
10040
10873
|
...typeof b.mode === "string" && b.mode.length > 0 ? { mode: b.mode } : {},
|
|
10041
10874
|
...typeof b.model === "string" && b.model.length > 0 ? { model: b.model } : {},
|
|
10042
10875
|
...typeof b.effort === "string" && b.effort.length > 0 ? { effort: b.effort } : {},
|
|
10876
|
+
...b.options !== void 0 ? (() => {
|
|
10877
|
+
const parsed = parseOptionsField(b.options);
|
|
10878
|
+
return parsed !== void 0 ? { options: parsed } : {};
|
|
10879
|
+
})() : {},
|
|
10880
|
+
...b.auth !== void 0 ? (() => {
|
|
10881
|
+
const parsed = parseAuthField(b.auth);
|
|
10882
|
+
return parsed !== void 0 ? { auth: parsed } : {};
|
|
10883
|
+
})() : {},
|
|
10043
10884
|
...typeof b.prompt === "string" ? { prompt: b.prompt } : {},
|
|
10044
10885
|
...typeof b.label === "string" ? { label: b.label } : {},
|
|
10886
|
+
...typeof b.idempotencyKey === "string" && b.idempotencyKey.length > 0 ? { idempotencyKey: b.idempotencyKey } : {},
|
|
10045
10887
|
...typeof b.role === "string" && b.role.length > 0 ? { role: b.role } : {},
|
|
10046
10888
|
...typeof b.promptAppend === "string" ? { promptAppend: b.promptAppend } : {},
|
|
10047
10889
|
...b.orchestrator !== void 0 ? (() => {
|
|
@@ -10059,6 +10901,13 @@ async function handleSessions(req, res, path, registry, resolveAgentAdapter, pty
|
|
|
10059
10901
|
...b.trace !== void 0 ? (() => {
|
|
10060
10902
|
const t = typeof b.trace === "boolean" ? b.trace : b.trace === "true" ? true : b.trace === "false" ? false : void 0;
|
|
10061
10903
|
return t !== void 0 ? { trace: t } : {};
|
|
10904
|
+
})() : {},
|
|
10905
|
+
// Permission-hold mode — the HTTP twin of the MCP `agent_start` tool's
|
|
10906
|
+
// `permissionHold` field (and `agentproto sessions start
|
|
10907
|
+
// --hold-permissions`). Tolerate a stringified boolean like `trace`.
|
|
10908
|
+
...b.permissionHold !== void 0 ? (() => {
|
|
10909
|
+
const h = typeof b.permissionHold === "boolean" ? b.permissionHold : b.permissionHold === "true" ? true : b.permissionHold === "false" ? false : void 0;
|
|
10910
|
+
return h ? { permissionHold: true } : {};
|
|
10062
10911
|
})() : {}
|
|
10063
10912
|
}
|
|
10064
10913
|
);
|
|
@@ -10071,7 +10920,10 @@ async function handleSessions(req, res, path, registry, resolveAgentAdapter, pty
|
|
|
10071
10920
|
});
|
|
10072
10921
|
return true;
|
|
10073
10922
|
}
|
|
10074
|
-
json(201,
|
|
10923
|
+
json(201, {
|
|
10924
|
+
...result.descriptor,
|
|
10925
|
+
...result.deduped ? { deduped: true } : {}
|
|
10926
|
+
});
|
|
10075
10927
|
return true;
|
|
10076
10928
|
}
|
|
10077
10929
|
if (path === "/sessions/browser" && req.method === "POST") {
|
|
@@ -10205,7 +11057,9 @@ async function handleSessions(req, res, path, registry, resolveAgentAdapter, pty
|
|
|
10205
11057
|
`[/sessions/terminal] no cwd resolvable for argv=${argv.join(" ")} \u2014 falling back to daemon's cwd ${cwd}`
|
|
10206
11058
|
);
|
|
10207
11059
|
}
|
|
10208
|
-
if (!workspaceSlug)
|
|
11060
|
+
if (!workspaceSlug) {
|
|
11061
|
+
workspaceSlug = await resolveSlugFromCwd(cwd) ?? "default";
|
|
11062
|
+
}
|
|
10209
11063
|
try {
|
|
10210
11064
|
const desc = registry.spawnPty({
|
|
10211
11065
|
argv,
|
|
@@ -10249,7 +11103,7 @@ async function handleSessions(req, res, path, registry, resolveAgentAdapter, pty
|
|
|
10249
11103
|
await registry.enqueuePrompt(id2, prompt, { interrupt });
|
|
10250
11104
|
json(202, { ok: true, id: id2, queued: true });
|
|
10251
11105
|
} else {
|
|
10252
|
-
await registry.sendPrompt(id2, prompt);
|
|
11106
|
+
await registry.sendPrompt(id2, prompt, { interrupt });
|
|
10253
11107
|
json(200, { ok: true, id: id2 });
|
|
10254
11108
|
}
|
|
10255
11109
|
} catch (err) {
|
|
@@ -10276,10 +11130,14 @@ async function handleSessions(req, res, path, registry, resolveAgentAdapter, pty
|
|
|
10276
11130
|
return true;
|
|
10277
11131
|
}
|
|
10278
11132
|
try {
|
|
11133
|
+
const rawCwd = typeof b.cwd === "string" ? b.cwd : process.cwd();
|
|
10279
11134
|
const desc = registry.spawn({
|
|
10280
11135
|
kind: b.kind === "terminal" || b.kind === "agent-cli" || b.kind === "command" ? b.kind : "command",
|
|
10281
|
-
|
|
10282
|
-
|
|
11136
|
+
// Same reverse-map as /sessions/terminal and spawnAgentSession: an
|
|
11137
|
+
// explicit slug wins, otherwise derive it from cwd rather than
|
|
11138
|
+
// dumping the session into "default".
|
|
11139
|
+
workspaceSlug: typeof b.workspaceSlug === "string" ? b.workspaceSlug : await resolveSlugFromCwd(rawCwd) ?? "default",
|
|
11140
|
+
cwd: rawCwd,
|
|
10283
11141
|
argv,
|
|
10284
11142
|
env: b.env && typeof b.env === "object" ? b.env : void 0,
|
|
10285
11143
|
label: typeof b.label === "string" ? b.label : void 0
|
|
@@ -10294,7 +11152,7 @@ async function handleSessions(req, res, path, registry, resolveAgentAdapter, pty
|
|
|
10294
11152
|
return true;
|
|
10295
11153
|
}
|
|
10296
11154
|
const idMatch = path.match(
|
|
10297
|
-
/^\/sessions\/([^/]+)(\/stream|\/kill|\/preview|\/export|\/events|\/wait)?$/
|
|
11155
|
+
/^\/sessions\/([^/]+)(\/events\/stream|\/stream|\/kill|\/preview|\/export|\/events|\/wait)?$/
|
|
10298
11156
|
);
|
|
10299
11157
|
if (!idMatch) return false;
|
|
10300
11158
|
const [, rawIdOrName, suffix] = idMatch;
|
|
@@ -10351,9 +11209,9 @@ async function handleSessions(req, res, path, registry, resolveAgentAdapter, pty
|
|
|
10351
11209
|
let fileStream;
|
|
10352
11210
|
try {
|
|
10353
11211
|
fileStream = createReadStream(filePath, { encoding: "utf8" });
|
|
10354
|
-
await new Promise((
|
|
11212
|
+
await new Promise((resolve12, reject) => {
|
|
10355
11213
|
fileStream.once("error", reject);
|
|
10356
|
-
fileStream.once("open",
|
|
11214
|
+
fileStream.once("open", resolve12);
|
|
10357
11215
|
});
|
|
10358
11216
|
} catch (err) {
|
|
10359
11217
|
const code = err.code;
|
|
@@ -10391,6 +11249,85 @@ async function handleSessions(req, res, path, registry, resolveAgentAdapter, pty
|
|
|
10391
11249
|
});
|
|
10392
11250
|
return true;
|
|
10393
11251
|
}
|
|
11252
|
+
if (suffix === "/events/stream" && req.method === "GET") {
|
|
11253
|
+
const reqUrl = req.url ?? "";
|
|
11254
|
+
const qs = new URLSearchParams(
|
|
11255
|
+
reqUrl.includes("?") ? reqUrl.slice(reqUrl.indexOf("?") + 1) : ""
|
|
11256
|
+
);
|
|
11257
|
+
const sinceRaw = qs.get("since");
|
|
11258
|
+
if (sinceRaw !== null && !/^\d+$/.test(sinceRaw)) {
|
|
11259
|
+
json(400, {
|
|
11260
|
+
error: "invalid_since",
|
|
11261
|
+
message: "since must be a non-negative integer"
|
|
11262
|
+
});
|
|
11263
|
+
return true;
|
|
11264
|
+
}
|
|
11265
|
+
const since = sinceRaw !== null ? Number.parseInt(sinceRaw, 10) : 0;
|
|
11266
|
+
const filePath = sessionEventsPath(id);
|
|
11267
|
+
let fileStream;
|
|
11268
|
+
try {
|
|
11269
|
+
fileStream = createReadStream(filePath, { encoding: "utf8" });
|
|
11270
|
+
await new Promise((resolve12, reject) => {
|
|
11271
|
+
fileStream.once("error", reject);
|
|
11272
|
+
fileStream.once("open", resolve12);
|
|
11273
|
+
});
|
|
11274
|
+
} catch (err) {
|
|
11275
|
+
const code = err.code;
|
|
11276
|
+
if (code === "ENOENT") {
|
|
11277
|
+
json(404, { error: "no_transcript" });
|
|
11278
|
+
return true;
|
|
11279
|
+
}
|
|
11280
|
+
throw err;
|
|
11281
|
+
}
|
|
11282
|
+
res.writeHead(200, {
|
|
11283
|
+
"content-type": "text/event-stream",
|
|
11284
|
+
"cache-control": "no-cache",
|
|
11285
|
+
connection: "keep-alive"
|
|
11286
|
+
});
|
|
11287
|
+
res.write(`: connected
|
|
11288
|
+
|
|
11289
|
+
`);
|
|
11290
|
+
const ping = setInterval(() => {
|
|
11291
|
+
try {
|
|
11292
|
+
res.write(`: keep-alive
|
|
11293
|
+
|
|
11294
|
+
`);
|
|
11295
|
+
} catch {
|
|
11296
|
+
clearInterval(ping);
|
|
11297
|
+
}
|
|
11298
|
+
}, 25e3);
|
|
11299
|
+
async function* diskRecords() {
|
|
11300
|
+
const rl = createInterface({ input: fileStream, crlfDelay: Infinity });
|
|
11301
|
+
for await (const line of rl) {
|
|
11302
|
+
const trimmed = line.trim();
|
|
11303
|
+
if (!trimmed) continue;
|
|
11304
|
+
try {
|
|
11305
|
+
yield JSON.parse(trimmed);
|
|
11306
|
+
} catch {
|
|
11307
|
+
continue;
|
|
11308
|
+
}
|
|
11309
|
+
}
|
|
11310
|
+
}
|
|
11311
|
+
const { unsubscribe, done } = deliverRecordsExactlyOnce({
|
|
11312
|
+
since,
|
|
11313
|
+
diskRecords: diskRecords(),
|
|
11314
|
+
subscribe: (onRecord) => registry.subscribeToRecords(id, onRecord),
|
|
11315
|
+
send: (record) => {
|
|
11316
|
+
try {
|
|
11317
|
+
res.write(`data: ${JSON.stringify(record)}
|
|
11318
|
+
|
|
11319
|
+
`);
|
|
11320
|
+
} catch {
|
|
11321
|
+
}
|
|
11322
|
+
}
|
|
11323
|
+
});
|
|
11324
|
+
req.once("close", () => {
|
|
11325
|
+
unsubscribe();
|
|
11326
|
+
clearInterval(ping);
|
|
11327
|
+
});
|
|
11328
|
+
await done;
|
|
11329
|
+
return true;
|
|
11330
|
+
}
|
|
10394
11331
|
if (suffix === "/preview" && req.method === "GET") {
|
|
10395
11332
|
if (!resolvedDesc) {
|
|
10396
11333
|
json(404, { error: "session_not_found", id: rawIdOrName });
|
|
@@ -10915,6 +11852,133 @@ async function handlePolicies(req, res, path, supervisor, registry) {
|
|
|
10915
11852
|
json(200, state);
|
|
10916
11853
|
return true;
|
|
10917
11854
|
}
|
|
11855
|
+
async function handlePermissions(req, res, path, registry) {
|
|
11856
|
+
const json = (status, body) => {
|
|
11857
|
+
res.writeHead(status, { "content-type": "application/json" });
|
|
11858
|
+
res.end(JSON.stringify(body));
|
|
11859
|
+
};
|
|
11860
|
+
if (path === "/permissions" && req.method === "GET") {
|
|
11861
|
+
const reqUrl = req.url ?? "";
|
|
11862
|
+
const qs = new URLSearchParams(
|
|
11863
|
+
reqUrl.includes("?") ? reqUrl.slice(reqUrl.indexOf("?") + 1) : ""
|
|
11864
|
+
);
|
|
11865
|
+
const sessionId = qs.get("sessionId") ?? void 0;
|
|
11866
|
+
const permissions = registry.listPendingPermissions(sessionId ? { sessionId } : void 0).map((p) => enrichPermission(p, registry));
|
|
11867
|
+
json(200, { permissions });
|
|
11868
|
+
return true;
|
|
11869
|
+
}
|
|
11870
|
+
const idMatch = path.match(/^\/permissions\/([^/]+)$/);
|
|
11871
|
+
if (idMatch && req.method === "POST") {
|
|
11872
|
+
const id = decodeURIComponent(idMatch[1] ?? "");
|
|
11873
|
+
const body = await readJsonBody(req);
|
|
11874
|
+
const b = body && typeof body === "object" ? body : {};
|
|
11875
|
+
const decision = b.decision;
|
|
11876
|
+
if (decision !== "approve" && decision !== "deny") {
|
|
11877
|
+
json(400, {
|
|
11878
|
+
error: "invalid_decision",
|
|
11879
|
+
message: 'body.decision must be "approve" or "deny"'
|
|
11880
|
+
});
|
|
11881
|
+
return true;
|
|
11882
|
+
}
|
|
11883
|
+
const optionId = typeof b.optionId === "string" ? b.optionId : void 0;
|
|
11884
|
+
const scope = b.scope === "always" || b.scope === "once" ? b.scope : void 0;
|
|
11885
|
+
const result = await registry.respondPermission(id, {
|
|
11886
|
+
decision,
|
|
11887
|
+
...optionId ? { optionId } : {},
|
|
11888
|
+
...scope ? { scope } : {}
|
|
11889
|
+
});
|
|
11890
|
+
if (!result.ok) {
|
|
11891
|
+
const status = result.error === "not_found" || result.error === "session_gone" ? 404 : 409;
|
|
11892
|
+
json(status, { error: result.error, message: result.message });
|
|
11893
|
+
return true;
|
|
11894
|
+
}
|
|
11895
|
+
json(200, {
|
|
11896
|
+
ok: true,
|
|
11897
|
+
id,
|
|
11898
|
+
sessionId: result.permission.sessionId,
|
|
11899
|
+
decision: result.decision,
|
|
11900
|
+
...result.optionId ? { optionId: result.optionId } : {}
|
|
11901
|
+
});
|
|
11902
|
+
return true;
|
|
11903
|
+
}
|
|
11904
|
+
if (path === "/permissions" || idMatch) {
|
|
11905
|
+
json(405, { error: "method_not_allowed", message: "GET /permissions or POST /permissions/:id" });
|
|
11906
|
+
return true;
|
|
11907
|
+
}
|
|
11908
|
+
return false;
|
|
11909
|
+
}
|
|
11910
|
+
function enrichPermission(p, registry) {
|
|
11911
|
+
const desc = registry.get(p.sessionId);
|
|
11912
|
+
const ageMs = Date.now() - new Date(p.requestedAt).getTime();
|
|
11913
|
+
return {
|
|
11914
|
+
...p,
|
|
11915
|
+
...desc?.adapterSlug ? { adapter: desc.adapterSlug } : {},
|
|
11916
|
+
...desc?.label ? { sessionLabel: desc.label } : {},
|
|
11917
|
+
...desc?.command ? { sessionTitle: desc.command } : {},
|
|
11918
|
+
ageMs: ageMs >= 0 ? ageMs : 0
|
|
11919
|
+
};
|
|
11920
|
+
}
|
|
11921
|
+
async function handlePairings(req, res, path, registry) {
|
|
11922
|
+
const json = (status, body) => {
|
|
11923
|
+
res.writeHead(status, { "content-type": "application/json" });
|
|
11924
|
+
res.end(JSON.stringify(body));
|
|
11925
|
+
};
|
|
11926
|
+
if (path === "/pairings/offer" && req.method === "POST") {
|
|
11927
|
+
const body = await readJsonBody(req);
|
|
11928
|
+
const b = body && typeof body === "object" ? body : {};
|
|
11929
|
+
const ttlMinutes = typeof b.ttlMinutes === "number" ? b.ttlMinutes : void 0;
|
|
11930
|
+
const rendezvous = typeof b.rendezvous === "string" ? b.rendezvous : void 0;
|
|
11931
|
+
try {
|
|
11932
|
+
const offer = await registry.createOffer({
|
|
11933
|
+
...ttlMinutes ? { ttlMs: ttlMinutes * 6e4 } : {},
|
|
11934
|
+
...rendezvous ? { rendezvousUrl: rendezvous } : {}
|
|
11935
|
+
});
|
|
11936
|
+
json(200, {
|
|
11937
|
+
url: offer.url,
|
|
11938
|
+
fingerprint: offer.fingerprint,
|
|
11939
|
+
rendezvous: offer.rendezvousUrl,
|
|
11940
|
+
rendezvousIsHostedDefault: offer.rendezvousIsHostedDefault,
|
|
11941
|
+
expiresAt: new Date(offer.exp * 1e3).toISOString()
|
|
11942
|
+
});
|
|
11943
|
+
} catch (err) {
|
|
11944
|
+
json(400, {
|
|
11945
|
+
error: "offer_failed",
|
|
11946
|
+
message: err instanceof Error ? err.message : String(err)
|
|
11947
|
+
});
|
|
11948
|
+
}
|
|
11949
|
+
return true;
|
|
11950
|
+
}
|
|
11951
|
+
if (path === "/pairings" && req.method === "GET") {
|
|
11952
|
+
const pairings = (await registry.list()).map((p) => ({
|
|
11953
|
+
name: p.name,
|
|
11954
|
+
fingerprint: p.fingerprint,
|
|
11955
|
+
createdAt: p.createdAt,
|
|
11956
|
+
lastSeen: p.lastSeen,
|
|
11957
|
+
rendezvous: p.rendezvousUrl
|
|
11958
|
+
}));
|
|
11959
|
+
json(200, { pairings });
|
|
11960
|
+
return true;
|
|
11961
|
+
}
|
|
11962
|
+
const fpMatch = path.match(/^\/pairings\/([^/]+)$/);
|
|
11963
|
+
if (fpMatch && req.method === "DELETE") {
|
|
11964
|
+
const target = decodeURIComponent(fpMatch[1] ?? "");
|
|
11965
|
+
const revoked = await registry.revoke(target);
|
|
11966
|
+
if (!revoked) {
|
|
11967
|
+
json(404, { error: "not_found", message: `no pairing matched "${target}"` });
|
|
11968
|
+
return true;
|
|
11969
|
+
}
|
|
11970
|
+
json(200, { ok: true, revoked: target });
|
|
11971
|
+
return true;
|
|
11972
|
+
}
|
|
11973
|
+
if (path === "/pairings" || path === "/pairings/offer" || fpMatch) {
|
|
11974
|
+
json(405, {
|
|
11975
|
+
error: "method_not_allowed",
|
|
11976
|
+
message: "POST /pairings/offer \xB7 GET /pairings \xB7 DELETE /pairings/:fingerprint"
|
|
11977
|
+
});
|
|
11978
|
+
return true;
|
|
11979
|
+
}
|
|
11980
|
+
return false;
|
|
11981
|
+
}
|
|
10918
11982
|
async function handleCron(req, res, path, scheduler) {
|
|
10919
11983
|
const json = (status, body) => {
|
|
10920
11984
|
res.writeHead(status, { "content-type": "application/json" });
|
|
@@ -11634,11 +12698,11 @@ function createRoutineRunner(opts) {
|
|
|
11634
12698
|
const persist = () => {
|
|
11635
12699
|
saveRuns(runs, persistPath);
|
|
11636
12700
|
};
|
|
11637
|
-
const waitTurnEnd = (sessionId) => new Promise((
|
|
12701
|
+
const waitTurnEnd = (sessionId) => new Promise((resolve12) => {
|
|
11638
12702
|
const unsubs = [];
|
|
11639
12703
|
const done = () => {
|
|
11640
12704
|
for (const u of unsubs) u();
|
|
11641
|
-
|
|
12705
|
+
resolve12();
|
|
11642
12706
|
};
|
|
11643
12707
|
unsubs.push(
|
|
11644
12708
|
sessionEvents.on("session:turn-end", (ev) => {
|
|
@@ -11954,9 +13018,9 @@ var SessionsRegistryAgentHost = class {
|
|
|
11954
13018
|
if (!("role" in m)) continue;
|
|
11955
13019
|
if (m.role !== "assistant") continue;
|
|
11956
13020
|
if (!("text" in m)) continue;
|
|
11957
|
-
const
|
|
11958
|
-
if (typeof
|
|
11959
|
-
return
|
|
13021
|
+
const text6 = m.text;
|
|
13022
|
+
if (typeof text6 === "string" && text6.trim()) {
|
|
13023
|
+
return text6;
|
|
11960
13024
|
}
|
|
11961
13025
|
}
|
|
11962
13026
|
return "";
|
|
@@ -11967,11 +13031,11 @@ var SessionsRegistryAgentHost = class {
|
|
|
11967
13031
|
}
|
|
11968
13032
|
// ── Internal helpers ──────────────────────────────────────────────────
|
|
11969
13033
|
waitTurnEnd(sessionId) {
|
|
11970
|
-
return new Promise((
|
|
13034
|
+
return new Promise((resolve12, reject) => {
|
|
11971
13035
|
const unsubs = [];
|
|
11972
13036
|
const done = () => {
|
|
11973
13037
|
for (const u of unsubs) u();
|
|
11974
|
-
|
|
13038
|
+
resolve12();
|
|
11975
13039
|
};
|
|
11976
13040
|
const fail = (reason) => {
|
|
11977
13041
|
for (const u of unsubs) u();
|
|
@@ -12185,9 +13249,9 @@ async function executeRunWorkflow(state, runtimeWf, agents, signal, cache, cache
|
|
|
12185
13249
|
state.run.status = "cancelled";
|
|
12186
13250
|
state.run.endedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
12187
13251
|
} else {
|
|
12188
|
-
const
|
|
13252
|
+
const errMsg2 = err instanceof Error ? err.message : String(err);
|
|
12189
13253
|
state.run.status = "failed";
|
|
12190
|
-
state.run.error =
|
|
13254
|
+
state.run.error = errMsg2;
|
|
12191
13255
|
state.run.endedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
12192
13256
|
for (let i = 0; i < state.run.stages.length; i++) {
|
|
12193
13257
|
const stage = state.run.stages[i];
|
|
@@ -12196,7 +13260,7 @@ async function executeRunWorkflow(state, runtimeWf, agents, signal, cache, cache
|
|
|
12196
13260
|
for (const step of stage.steps) {
|
|
12197
13261
|
step.status = "failed";
|
|
12198
13262
|
step.endedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
12199
|
-
step.error =
|
|
13263
|
+
step.error = errMsg2;
|
|
12200
13264
|
}
|
|
12201
13265
|
}
|
|
12202
13266
|
}
|
|
@@ -12445,11 +13509,11 @@ var ANSI_RE2 = /\x1b\[[0-9;?]*[A-Za-z]/g;
|
|
|
12445
13509
|
function stripAnsi2(s) {
|
|
12446
13510
|
return s.replace(ANSI_RE2, "");
|
|
12447
13511
|
}
|
|
12448
|
-
function parseVerdict(
|
|
13512
|
+
function parseVerdict(text6) {
|
|
12449
13513
|
const re = /VERDICT:\s*(PASS|FAIL)/gi;
|
|
12450
13514
|
let last = null;
|
|
12451
13515
|
let m;
|
|
12452
|
-
while ((m = re.exec(
|
|
13516
|
+
while ((m = re.exec(text6)) !== null) {
|
|
12453
13517
|
last = m[1].toUpperCase();
|
|
12454
13518
|
}
|
|
12455
13519
|
return last;
|
|
@@ -13226,11 +14290,11 @@ function createInboundWatcher(opts) {
|
|
|
13226
14290
|
return;
|
|
13227
14291
|
}
|
|
13228
14292
|
const raw = out.result;
|
|
13229
|
-
const
|
|
13230
|
-
if (!
|
|
14293
|
+
const text6 = raw?.content?.find((c) => c.type === "text")?.text;
|
|
14294
|
+
if (!text6) return;
|
|
13231
14295
|
let data;
|
|
13232
14296
|
try {
|
|
13233
|
-
data = JSON.parse(
|
|
14297
|
+
data = JSON.parse(text6);
|
|
13234
14298
|
} catch {
|
|
13235
14299
|
return;
|
|
13236
14300
|
}
|
|
@@ -13646,7 +14710,9 @@ var DEFAULT_ORCHESTRATOR_TOOLS = [
|
|
|
13646
14710
|
"policy_attach",
|
|
13647
14711
|
"policy_status",
|
|
13648
14712
|
"policy_list",
|
|
13649
|
-
"policy_cancel"
|
|
14713
|
+
"policy_cancel",
|
|
14714
|
+
"permissions_list",
|
|
14715
|
+
"permissions_respond"
|
|
13650
14716
|
];
|
|
13651
14717
|
var DEFAULT_MAX_DEPTH = 3;
|
|
13652
14718
|
var HARD_MAX_DEPTH = 8;
|
|
@@ -13756,7 +14822,7 @@ function createOrchestratorInjector(deps2) {
|
|
|
13756
14822
|
return { entry, scope, bindLifecycle };
|
|
13757
14823
|
};
|
|
13758
14824
|
}
|
|
13759
|
-
function makeBrowserHandle(entry,
|
|
14825
|
+
function makeBrowserHandle(entry, resolve12) {
|
|
13760
14826
|
return {
|
|
13761
14827
|
slug: entry.id,
|
|
13762
14828
|
name: entry.name,
|
|
@@ -13767,7 +14833,7 @@ function makeBrowserHandle(entry, resolve13) {
|
|
|
13767
14833
|
// check() is available for on-demand health probes but is never called
|
|
13768
14834
|
// by the lister (kit invariant OQ-5). Returns true when the adapter
|
|
13769
14835
|
// is present in the injected resolver map, false otherwise.
|
|
13770
|
-
check: async () =>
|
|
14836
|
+
check: async () => resolve12 ? resolve12(entry.id) != null : true
|
|
13771
14837
|
};
|
|
13772
14838
|
}
|
|
13773
14839
|
var noopLedger = {
|
|
@@ -13825,13 +14891,13 @@ async function spawnCloudflaredUntil(argv, opts) {
|
|
|
13825
14891
|
};
|
|
13826
14892
|
let settled = false;
|
|
13827
14893
|
let forwardedLen = 0;
|
|
13828
|
-
return await new Promise((
|
|
14894
|
+
return await new Promise((resolve12, reject) => {
|
|
13829
14895
|
const poll = setInterval(() => {
|
|
13830
|
-
const
|
|
14896
|
+
const text6 = readAll();
|
|
13831
14897
|
if (opts.onLog) {
|
|
13832
|
-
const lastNl =
|
|
14898
|
+
const lastNl = text6.lastIndexOf("\n");
|
|
13833
14899
|
if (lastNl + 1 > forwardedLen) {
|
|
13834
|
-
const chunk =
|
|
14900
|
+
const chunk = text6.slice(forwardedLen, lastNl + 1);
|
|
13835
14901
|
for (const line of chunk.split(/\r?\n/)) {
|
|
13836
14902
|
if (line.length > 0) opts.onLog(`[cloudflared] ${line}`);
|
|
13837
14903
|
}
|
|
@@ -13839,11 +14905,11 @@ async function spawnCloudflaredUntil(argv, opts) {
|
|
|
13839
14905
|
}
|
|
13840
14906
|
}
|
|
13841
14907
|
if (settled) return;
|
|
13842
|
-
const m =
|
|
14908
|
+
const m = text6.match(opts.readyRegex);
|
|
13843
14909
|
if (m) {
|
|
13844
14910
|
settled = true;
|
|
13845
14911
|
clearTimeout(timer);
|
|
13846
|
-
|
|
14912
|
+
resolve12({ proc, match: m[0], stopTail: () => clearInterval(poll) });
|
|
13847
14913
|
}
|
|
13848
14914
|
}, POLL_INTERVAL_MS);
|
|
13849
14915
|
if (poll.unref) poll.unref();
|
|
@@ -13977,15 +15043,15 @@ function quickTunnelProvider() {
|
|
|
13977
15043
|
}
|
|
13978
15044
|
}, 3e3);
|
|
13979
15045
|
timer.unref();
|
|
13980
|
-
await new Promise((
|
|
15046
|
+
await new Promise((resolve12) => {
|
|
13981
15047
|
if (proc.exitCode !== null) {
|
|
13982
15048
|
clearTimeout(timer);
|
|
13983
|
-
|
|
15049
|
+
resolve12();
|
|
13984
15050
|
return;
|
|
13985
15051
|
}
|
|
13986
15052
|
proc.once("exit", () => {
|
|
13987
15053
|
clearTimeout(timer);
|
|
13988
|
-
|
|
15054
|
+
resolve12();
|
|
13989
15055
|
});
|
|
13990
15056
|
});
|
|
13991
15057
|
}
|
|
@@ -14224,9 +15290,75 @@ function registerRemoteTools(server, opts) {
|
|
|
14224
15290
|
}
|
|
14225
15291
|
);
|
|
14226
15292
|
}
|
|
15293
|
+
function text3(value) {
|
|
15294
|
+
return {
|
|
15295
|
+
content: [
|
|
15296
|
+
{
|
|
15297
|
+
type: "text",
|
|
15298
|
+
text: typeof value === "string" ? value : JSON.stringify(value, null, 2)
|
|
15299
|
+
}
|
|
15300
|
+
]
|
|
15301
|
+
};
|
|
15302
|
+
}
|
|
15303
|
+
function registerPairingTools(server, opts) {
|
|
15304
|
+
const { registry } = opts;
|
|
15305
|
+
server.tool(
|
|
15306
|
+
"pair_offer",
|
|
15307
|
+
"Mint a one-time pairing offer and start listening for a client on the rendezvous broker. Returns an `agentproto://pair?\u2026` offer URL (share it / render it as a QR), the daemon's identity fingerprint, and the offer expiry. The URL is the bootstrap secret \u2014 it carries the daemon's public keys (MITM-proof against the broker) and a short-lived single-use token. The client runs `agentproto pair accept \"<url>\"`.",
|
|
15308
|
+
{
|
|
15309
|
+
ttlMinutes: z.number().int().min(1).max(1440).optional().describe("Offer time-to-live in minutes (default 10)."),
|
|
15310
|
+
rendezvous: z.string().optional().describe(
|
|
15311
|
+
"Rendezvous WS URL to route through (ws:// or wss://). Defaults to `pairing.rendezvous` from config.json, or the hosted broker (wss://rdv.agentproto.sh/v1) when neither is set. The response's `rendezvousIsHostedDefault` flags when that fallback applied."
|
|
15312
|
+
)
|
|
15313
|
+
},
|
|
15314
|
+
async ({ ttlMinutes, rendezvous }) => {
|
|
15315
|
+
const offer = await registry.createOffer({
|
|
15316
|
+
...ttlMinutes ? { ttlMs: ttlMinutes * 6e4 } : {},
|
|
15317
|
+
...rendezvous ? { rendezvousUrl: rendezvous } : {}
|
|
15318
|
+
});
|
|
15319
|
+
return text3({
|
|
15320
|
+
url: offer.url,
|
|
15321
|
+
fingerprint: offer.fingerprint,
|
|
15322
|
+
rendezvous: offer.rendezvousUrl,
|
|
15323
|
+
rendezvousIsHostedDefault: offer.rendezvousIsHostedDefault,
|
|
15324
|
+
expiresAt: new Date(offer.exp * 1e3).toISOString()
|
|
15325
|
+
});
|
|
15326
|
+
}
|
|
15327
|
+
);
|
|
15328
|
+
server.tool(
|
|
15329
|
+
"pair_list",
|
|
15330
|
+
"List clients paired with this daemon: name, fingerprint, when first paired, and last seen. Read-only.",
|
|
15331
|
+
{},
|
|
15332
|
+
async () => {
|
|
15333
|
+
const pairings = await registry.list();
|
|
15334
|
+
return text3({
|
|
15335
|
+
pairings: pairings.map((p) => ({
|
|
15336
|
+
name: p.name,
|
|
15337
|
+
fingerprint: p.fingerprint,
|
|
15338
|
+
createdAt: p.createdAt,
|
|
15339
|
+
lastSeen: p.lastSeen,
|
|
15340
|
+
rendezvous: p.rendezvousUrl
|
|
15341
|
+
}))
|
|
15342
|
+
});
|
|
15343
|
+
}
|
|
15344
|
+
);
|
|
15345
|
+
server.tool(
|
|
15346
|
+
"pair_revoke",
|
|
15347
|
+
"Revoke a pairing by fingerprint or name. Drops its rendezvous connections so the client can no longer reconnect, and removes it from pairings.json.",
|
|
15348
|
+
{
|
|
15349
|
+
target: z.string().describe("The pairing's fingerprint or name (see pair_list).")
|
|
15350
|
+
},
|
|
15351
|
+
async ({ target }) => {
|
|
15352
|
+
const revoked = await registry.revoke(target);
|
|
15353
|
+
return text3(
|
|
15354
|
+
revoked ? { ok: true, revoked: target } : { ok: false, message: `no pairing matched "${target}"` }
|
|
15355
|
+
);
|
|
15356
|
+
}
|
|
15357
|
+
);
|
|
15358
|
+
}
|
|
14227
15359
|
|
|
14228
15360
|
// src/daemon-health-tools.ts
|
|
14229
|
-
function
|
|
15361
|
+
function text4(value) {
|
|
14230
15362
|
return {
|
|
14231
15363
|
content: [
|
|
14232
15364
|
{
|
|
@@ -14242,7 +15374,7 @@ function registerDaemonHealthTools(server, opts) {
|
|
|
14242
15374
|
"Cheap, side-effect-free liveness probe for this daemon. Returns the same fields as GET /health but in-process. `alive` is always true when the tool returns successfully; if the daemon is unreachable the failure is observed as a thrown/failed tool call at the transport level, not as a false field.",
|
|
14243
15375
|
{},
|
|
14244
15376
|
async () => {
|
|
14245
|
-
return
|
|
15377
|
+
return text4({
|
|
14246
15378
|
alive: true,
|
|
14247
15379
|
status: "ok",
|
|
14248
15380
|
workspace: opts.workspace,
|
|
@@ -14382,15 +15514,15 @@ function namedTunnelProvider(cfg) {
|
|
|
14382
15514
|
}
|
|
14383
15515
|
}, 3e3);
|
|
14384
15516
|
timer.unref();
|
|
14385
|
-
await new Promise((
|
|
15517
|
+
await new Promise((resolve12) => {
|
|
14386
15518
|
if (proc.exitCode !== null) {
|
|
14387
15519
|
clearTimeout(timer);
|
|
14388
|
-
|
|
15520
|
+
resolve12();
|
|
14389
15521
|
return;
|
|
14390
15522
|
}
|
|
14391
15523
|
proc.once("exit", () => {
|
|
14392
15524
|
clearTimeout(timer);
|
|
14393
|
-
|
|
15525
|
+
resolve12();
|
|
14394
15526
|
});
|
|
14395
15527
|
});
|
|
14396
15528
|
}
|
|
@@ -14507,14 +15639,14 @@ function ngrokTunnelProvider(opts) {
|
|
|
14507
15639
|
});
|
|
14508
15640
|
child = proc;
|
|
14509
15641
|
const forwardLogs = (chunk) => {
|
|
14510
|
-
const
|
|
14511
|
-
for (const line of
|
|
15642
|
+
const text6 = chunk.toString("utf8");
|
|
15643
|
+
for (const line of text6.split(/\r?\n/)) {
|
|
14512
15644
|
if (line.length > 0) opts2.onLog?.(`[ngrok] ${line}`);
|
|
14513
15645
|
}
|
|
14514
15646
|
};
|
|
14515
15647
|
proc.stderr?.on("data", forwardLogs);
|
|
14516
15648
|
proc.stdout?.on("data", forwardLogs);
|
|
14517
|
-
const url = await new Promise((
|
|
15649
|
+
const url = await new Promise((resolve12, reject) => {
|
|
14518
15650
|
let settled = false;
|
|
14519
15651
|
const timer = setTimeout(() => {
|
|
14520
15652
|
if (settled) return;
|
|
@@ -14542,7 +15674,7 @@ function ngrokTunnelProvider(opts) {
|
|
|
14542
15674
|
settled = true;
|
|
14543
15675
|
clearTimeout(timer);
|
|
14544
15676
|
if (apiPollHandle) clearInterval(apiPollHandle);
|
|
14545
|
-
|
|
15677
|
+
resolve12(pubUrl);
|
|
14546
15678
|
}
|
|
14547
15679
|
} catch {
|
|
14548
15680
|
}
|
|
@@ -14551,16 +15683,16 @@ function ngrokTunnelProvider(opts) {
|
|
|
14551
15683
|
};
|
|
14552
15684
|
const onStdout = (chunk) => {
|
|
14553
15685
|
if (settled) return;
|
|
14554
|
-
const
|
|
14555
|
-
const reMatch =
|
|
15686
|
+
const text6 = chunk.toString("utf8");
|
|
15687
|
+
const reMatch = text6.match(URL_JSON_REGEX);
|
|
14556
15688
|
if (reMatch) {
|
|
14557
15689
|
settled = true;
|
|
14558
15690
|
clearTimeout(timer);
|
|
14559
15691
|
if (apiPollHandle) clearInterval(apiPollHandle);
|
|
14560
|
-
|
|
15692
|
+
resolve12(reMatch[1]);
|
|
14561
15693
|
return;
|
|
14562
15694
|
}
|
|
14563
|
-
for (const line of
|
|
15695
|
+
for (const line of text6.split(/\r?\n/)) {
|
|
14564
15696
|
if (line.length === 0) continue;
|
|
14565
15697
|
try {
|
|
14566
15698
|
const parsed = JSON.parse(line);
|
|
@@ -14568,7 +15700,7 @@ function ngrokTunnelProvider(opts) {
|
|
|
14568
15700
|
settled = true;
|
|
14569
15701
|
clearTimeout(timer);
|
|
14570
15702
|
if (apiPollHandle) clearInterval(apiPollHandle);
|
|
14571
|
-
|
|
15703
|
+
resolve12(parsed.url);
|
|
14572
15704
|
return;
|
|
14573
15705
|
}
|
|
14574
15706
|
} catch {
|
|
@@ -14615,15 +15747,15 @@ function ngrokTunnelProvider(opts) {
|
|
|
14615
15747
|
}
|
|
14616
15748
|
}, 3e3);
|
|
14617
15749
|
timer.unref();
|
|
14618
|
-
await new Promise((
|
|
15750
|
+
await new Promise((resolve12) => {
|
|
14619
15751
|
if (proc.exitCode !== null) {
|
|
14620
15752
|
clearTimeout(timer);
|
|
14621
|
-
|
|
15753
|
+
resolve12();
|
|
14622
15754
|
return;
|
|
14623
15755
|
}
|
|
14624
15756
|
proc.once("exit", () => {
|
|
14625
15757
|
clearTimeout(timer);
|
|
14626
|
-
|
|
15758
|
+
resolve12();
|
|
14627
15759
|
});
|
|
14628
15760
|
});
|
|
14629
15761
|
}
|
|
@@ -14993,7 +16125,7 @@ function makeStubProvider() {
|
|
|
14993
16125
|
}
|
|
14994
16126
|
};
|
|
14995
16127
|
}
|
|
14996
|
-
function
|
|
16128
|
+
function text5(value) {
|
|
14997
16129
|
return {
|
|
14998
16130
|
content: [
|
|
14999
16131
|
{
|
|
@@ -15057,7 +16189,7 @@ function registerTunnelTools(server, opts) {
|
|
|
15057
16189
|
...input.tunnelId ? { tunnelId: input.tunnelId } : {},
|
|
15058
16190
|
...input.credentialsFile ? { credentialsFile: input.credentialsFile } : {}
|
|
15059
16191
|
});
|
|
15060
|
-
return
|
|
16192
|
+
return text5(desc);
|
|
15061
16193
|
} catch (err) {
|
|
15062
16194
|
return errText("tunnel_create", err);
|
|
15063
16195
|
}
|
|
@@ -15078,7 +16210,7 @@ function registerTunnelTools(server, opts) {
|
|
|
15078
16210
|
(t) => t.status === "starting" || t.status === "active"
|
|
15079
16211
|
);
|
|
15080
16212
|
}
|
|
15081
|
-
return
|
|
16213
|
+
return text5({ tunnels });
|
|
15082
16214
|
}
|
|
15083
16215
|
);
|
|
15084
16216
|
server.tool(
|
|
@@ -15103,7 +16235,7 @@ function registerTunnelTools(server, opts) {
|
|
|
15103
16235
|
isError: true
|
|
15104
16236
|
};
|
|
15105
16237
|
}
|
|
15106
|
-
return
|
|
16238
|
+
return text5({ ok, tunnelId: input.tunnelId });
|
|
15107
16239
|
} catch (err) {
|
|
15108
16240
|
return errText("tunnel_stop", err);
|
|
15109
16241
|
}
|
|
@@ -15130,7 +16262,7 @@ function registerTunnelTools(server, opts) {
|
|
|
15130
16262
|
isError: true
|
|
15131
16263
|
};
|
|
15132
16264
|
}
|
|
15133
|
-
return
|
|
16265
|
+
return text5(desc);
|
|
15134
16266
|
}
|
|
15135
16267
|
);
|
|
15136
16268
|
}
|
|
@@ -15294,7 +16426,7 @@ var localSandboxProvider = {
|
|
|
15294
16426
|
}
|
|
15295
16427
|
};
|
|
15296
16428
|
async function getFreePort() {
|
|
15297
|
-
return new Promise((
|
|
16429
|
+
return new Promise((resolve12, reject) => {
|
|
15298
16430
|
const srv = createServer$1();
|
|
15299
16431
|
srv.once("error", reject);
|
|
15300
16432
|
srv.listen(0, "127.0.0.1", () => {
|
|
@@ -15304,7 +16436,7 @@ async function getFreePort() {
|
|
|
15304
16436
|
return;
|
|
15305
16437
|
}
|
|
15306
16438
|
const { port } = address;
|
|
15307
|
-
srv.close(() =>
|
|
16439
|
+
srv.close(() => resolve12(port));
|
|
15308
16440
|
});
|
|
15309
16441
|
});
|
|
15310
16442
|
}
|
|
@@ -15317,7 +16449,7 @@ async function probeHealth(url, timeoutMs) {
|
|
|
15317
16449
|
} catch {
|
|
15318
16450
|
}
|
|
15319
16451
|
if (Date.now() >= deadline) return false;
|
|
15320
|
-
await new Promise((
|
|
16452
|
+
await new Promise((resolve12) => setTimeout(resolve12, POLL_INTERVAL_MS2));
|
|
15321
16453
|
}
|
|
15322
16454
|
}
|
|
15323
16455
|
|
|
@@ -15625,84 +16757,355 @@ function createWorkspaceFs(opts) {
|
|
|
15625
16757
|
}
|
|
15626
16758
|
};
|
|
15627
16759
|
}
|
|
15628
|
-
var
|
|
15629
|
-
|
|
15630
|
-
|
|
15631
|
-
|
|
15632
|
-
|
|
15633
|
-
|
|
15634
|
-
|
|
15635
|
-
|
|
15636
|
-
|
|
15637
|
-
|
|
15638
|
-
|
|
15639
|
-
|
|
15640
|
-
|
|
15641
|
-
|
|
15642
|
-
|
|
15643
|
-
|
|
15644
|
-
}
|
|
15645
|
-
function
|
|
15646
|
-
|
|
15647
|
-
|
|
15648
|
-
|
|
15649
|
-
|
|
16760
|
+
var PAIRINGS_VERSION = 1;
|
|
16761
|
+
var DEFAULT_TTL_MS = 10 * 6e4;
|
|
16762
|
+
var DEFAULT_RECONNECT_MIN_MS = 1e3;
|
|
16763
|
+
var DEFAULT_RECONNECT_MAX_MS = 3e4;
|
|
16764
|
+
var DEFAULT_HANDSHAKE_TIMEOUT_MS = 11e4;
|
|
16765
|
+
function defaultPairingsPath() {
|
|
16766
|
+
return join(homedir(), ".agentproto", "pairings.json");
|
|
16767
|
+
}
|
|
16768
|
+
function b64url(bytes) {
|
|
16769
|
+
return bytes.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
16770
|
+
}
|
|
16771
|
+
function constantTimeEqual(a, b) {
|
|
16772
|
+
const key = randomBytes(32);
|
|
16773
|
+
const da = createHmac("sha256", key).update(a, "utf8").digest();
|
|
16774
|
+
const db = createHmac("sha256", key).update(b, "utf8").digest();
|
|
16775
|
+
return timingSafeEqual(da, db);
|
|
16776
|
+
}
|
|
16777
|
+
function createPairingRegistry(deps2) {
|
|
16778
|
+
const pairingsPath = deps2.pairingsPath ?? defaultPairingsPath();
|
|
16779
|
+
const now = deps2.now ?? Date.now;
|
|
16780
|
+
const log = deps2.log ?? (() => {
|
|
16781
|
+
});
|
|
16782
|
+
const reconnectMinMs = deps2.reconnectMinMs ?? DEFAULT_RECONNECT_MIN_MS;
|
|
16783
|
+
const reconnectMaxMs = deps2.reconnectMaxMs ?? DEFAULT_RECONNECT_MAX_MS;
|
|
16784
|
+
const handshakeTimeoutMs = deps2.handshakeTimeoutMs ?? DEFAULT_HANDSHAKE_TIMEOUT_MS;
|
|
16785
|
+
const pairings = /* @__PURE__ */ new Map();
|
|
16786
|
+
const offers = /* @__PURE__ */ new Map();
|
|
16787
|
+
const loops = /* @__PURE__ */ new Map();
|
|
16788
|
+
const channels = /* @__PURE__ */ new Set();
|
|
16789
|
+
let shuttingDown = false;
|
|
16790
|
+
let loaded = false;
|
|
16791
|
+
async function ensureLoaded() {
|
|
16792
|
+
if (loaded) return;
|
|
16793
|
+
loaded = true;
|
|
16794
|
+
try {
|
|
16795
|
+
const raw = await readFile(pairingsPath, "utf8");
|
|
16796
|
+
const parsed = JSON.parse(raw);
|
|
16797
|
+
if (isPairingsFile(parsed)) {
|
|
16798
|
+
for (const rec of parsed.pairings) pairings.set(rec.fingerprint, rec);
|
|
16799
|
+
}
|
|
16800
|
+
} catch (err) {
|
|
16801
|
+
if (!isEnoent(err)) {
|
|
16802
|
+
log(`[pairing] could not read ${pairingsPath}: ${errMsg(err)}`);
|
|
16803
|
+
}
|
|
16804
|
+
}
|
|
16805
|
+
}
|
|
16806
|
+
async function persist() {
|
|
16807
|
+
const file = {
|
|
16808
|
+
v: PAIRINGS_VERSION,
|
|
16809
|
+
pairings: Array.from(pairings.values())
|
|
16810
|
+
};
|
|
16811
|
+
const dir = dirname(pairingsPath);
|
|
16812
|
+
await mkdir(dir, { recursive: true });
|
|
16813
|
+
const tmp = join(dir, `.${basename(pairingsPath)}.tmp-${process.pid}`);
|
|
16814
|
+
await writeFile(tmp, JSON.stringify(file, null, 2) + "\n", { encoding: "utf8", mode: 384 });
|
|
16815
|
+
await chmod(tmp, 384).catch(() => {
|
|
16816
|
+
});
|
|
16817
|
+
await rename(tmp, pairingsPath);
|
|
16818
|
+
}
|
|
16819
|
+
function startLoop(spec) {
|
|
16820
|
+
if (loops.has(spec.key)) return;
|
|
16821
|
+
const abort = new AbortController();
|
|
16822
|
+
const done = runLoop(spec, abort.signal).catch((err) => {
|
|
16823
|
+
log(`[pairing] loop ${spec.key} ended: ${errMsg(err)}`);
|
|
16824
|
+
});
|
|
16825
|
+
loops.set(spec.key, { abort, done });
|
|
16826
|
+
}
|
|
16827
|
+
async function stopLoop(key) {
|
|
16828
|
+
const loop = loops.get(key);
|
|
16829
|
+
if (!loop) return;
|
|
16830
|
+
loops.delete(key);
|
|
16831
|
+
loop.abort.abort();
|
|
16832
|
+
await loop.done.catch(() => {
|
|
16833
|
+
});
|
|
16834
|
+
}
|
|
16835
|
+
async function runLoop(spec, signal) {
|
|
16836
|
+
let backoff = reconnectMinMs;
|
|
16837
|
+
while (!signal.aborted && spec.shouldContinue()) {
|
|
16838
|
+
const expected = spec.token();
|
|
16839
|
+
let sink;
|
|
16840
|
+
try {
|
|
16841
|
+
sink = await deps2.dial(dialUrl(spec.rendezvousUrl, expected), signal);
|
|
16842
|
+
} catch (err) {
|
|
16843
|
+
if (signal.aborted) break;
|
|
16844
|
+
log(`[pairing] dial ${spec.key} failed: ${errMsg(err)}`);
|
|
16845
|
+
await sleep(backoff, signal);
|
|
16846
|
+
backoff = Math.min(backoff * 2, reconnectMaxMs);
|
|
16847
|
+
continue;
|
|
16848
|
+
}
|
|
16849
|
+
const onAbort = () => {
|
|
16850
|
+
try {
|
|
16851
|
+
sink.close("registry shutdown");
|
|
16852
|
+
} catch {
|
|
16853
|
+
}
|
|
16854
|
+
};
|
|
16855
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
16856
|
+
try {
|
|
16857
|
+
let capturedSession = null;
|
|
16858
|
+
let capturedHello = null;
|
|
16859
|
+
let wrapped;
|
|
16860
|
+
try {
|
|
16861
|
+
const identity = await deps2.loadIdentity();
|
|
16862
|
+
wrapped = await daemonHandshakeOverSink(
|
|
16863
|
+
sink,
|
|
16864
|
+
(helloBytes) => {
|
|
16865
|
+
const hello = decodePairingHello(helloBytes);
|
|
16866
|
+
const result = respondToHandshake(hello, {
|
|
16867
|
+
identity,
|
|
16868
|
+
verifyOfferToken: (presented) => spec.verify(presented, expected)
|
|
16869
|
+
});
|
|
16870
|
+
capturedSession = result.session;
|
|
16871
|
+
capturedHello = hello;
|
|
16872
|
+
return { reply: encodePairingMessage(result.reply), keys: result.session };
|
|
16873
|
+
},
|
|
16874
|
+
{ timeoutMs: handshakeTimeoutMs }
|
|
16875
|
+
);
|
|
16876
|
+
} catch (err) {
|
|
16877
|
+
if (signal.aborted) break;
|
|
16878
|
+
void err;
|
|
16879
|
+
await sleep(backoff, signal);
|
|
16880
|
+
backoff = Math.min(backoff * 2, reconnectMaxMs);
|
|
16881
|
+
continue;
|
|
16882
|
+
}
|
|
16883
|
+
backoff = reconnectMinMs;
|
|
16884
|
+
if (!capturedSession || !capturedHello) {
|
|
16885
|
+
wrapped.close("internal: missing session");
|
|
16886
|
+
continue;
|
|
16887
|
+
}
|
|
16888
|
+
let ctx;
|
|
16889
|
+
try {
|
|
16890
|
+
ctx = await spec.onPaired(capturedSession, capturedHello);
|
|
16891
|
+
} catch (err) {
|
|
16892
|
+
log(`[pairing] persist for ${spec.key} failed: ${errMsg(err)}`);
|
|
16893
|
+
wrapped.close("persist failed");
|
|
16894
|
+
continue;
|
|
16895
|
+
}
|
|
16896
|
+
const handle = deps2.serve(wrapped, ctx);
|
|
16897
|
+
channels.add(handle);
|
|
16898
|
+
log(`[pairing] channel up (${ctx.mode}) for ${ctx.fingerprint} via ${spec.key}`);
|
|
16899
|
+
await waitClosed(wrapped, signal);
|
|
16900
|
+
channels.delete(handle);
|
|
16901
|
+
await handle.close().catch(() => {
|
|
16902
|
+
});
|
|
16903
|
+
log(`[pairing] channel closed (${ctx.mode}) for ${ctx.fingerprint}`);
|
|
16904
|
+
if (spec.singleUse) break;
|
|
16905
|
+
} finally {
|
|
16906
|
+
signal.removeEventListener("abort", onAbort);
|
|
16907
|
+
}
|
|
16908
|
+
}
|
|
16909
|
+
}
|
|
16910
|
+
async function createOffer(input = {}) {
|
|
16911
|
+
await ensureLoaded();
|
|
16912
|
+
let rendezvousUrl;
|
|
16913
|
+
let rendezvousIsHostedDefault = false;
|
|
16914
|
+
if (input.rendezvousUrl) {
|
|
16915
|
+
rendezvousUrl = input.rendezvousUrl;
|
|
16916
|
+
} else if (deps2.defaultRendezvousUrl !== void 0) {
|
|
16917
|
+
if (deps2.defaultRendezvousUrl === "") {
|
|
16918
|
+
throw new PairingError(
|
|
16919
|
+
"malformed_offer",
|
|
16920
|
+
'rendezvous disabled \u2014 pairing.rendezvous is set to "" (explicit opt-out); pass --rendezvous to route this offer through a specific broker'
|
|
16921
|
+
);
|
|
16922
|
+
}
|
|
16923
|
+
rendezvousUrl = deps2.defaultRendezvousUrl;
|
|
16924
|
+
} else {
|
|
16925
|
+
rendezvousUrl = HOSTED_RENDEZVOUS_URL;
|
|
16926
|
+
rendezvousIsHostedDefault = true;
|
|
16927
|
+
}
|
|
16928
|
+
const identity = await deps2.loadIdentity();
|
|
16929
|
+
const token = b64url(randomBytes(16));
|
|
16930
|
+
const ttlMs = input.ttlMs ?? DEFAULT_TTL_MS;
|
|
16931
|
+
const exp = Math.floor((now() + ttlMs) / 1e3);
|
|
16932
|
+
offers.set(token, { token, exp, spent: false, rendezvousUrl });
|
|
16933
|
+
const fingerprint = identityFingerprint(identity.x25519.pub);
|
|
16934
|
+
const url = encodeOfferUrl({
|
|
16935
|
+
v: 1,
|
|
16936
|
+
rendezvousUrl,
|
|
16937
|
+
fingerprint,
|
|
16938
|
+
daemonX25519Pub: identity.x25519.pub,
|
|
16939
|
+
daemonEd25519Pub: identity.ed25519.pub,
|
|
16940
|
+
token,
|
|
16941
|
+
exp
|
|
16942
|
+
});
|
|
16943
|
+
startOfferLoop(token, rendezvousUrl);
|
|
16944
|
+
log(
|
|
16945
|
+
`[pairing] offer minted (exp ${new Date(exp * 1e3).toISOString()}) via ${rendezvousUrl}` + (rendezvousIsHostedDefault ? " (hosted default)" : "")
|
|
16946
|
+
);
|
|
16947
|
+
return { token, exp, url, fingerprint, rendezvousUrl, rendezvousIsHostedDefault };
|
|
16948
|
+
}
|
|
16949
|
+
function offerValid(token) {
|
|
16950
|
+
const entry = offers.get(token);
|
|
16951
|
+
return !!entry && !entry.spent && entry.exp * 1e3 > now();
|
|
16952
|
+
}
|
|
16953
|
+
function startOfferLoop(token, rendezvousUrl) {
|
|
16954
|
+
startLoop({
|
|
16955
|
+
key: `offer:${token}`,
|
|
16956
|
+
rendezvousUrl,
|
|
16957
|
+
token: () => token,
|
|
16958
|
+
shouldContinue: () => offerValid(token),
|
|
16959
|
+
singleUse: true,
|
|
16960
|
+
verify: (presented, expected) => {
|
|
16961
|
+
const entry = offers.get(expected);
|
|
16962
|
+
if (!entry || entry.spent || entry.exp * 1e3 <= now()) return false;
|
|
16963
|
+
if (!constantTimeEqual(presented, expected)) return false;
|
|
16964
|
+
entry.spent = true;
|
|
16965
|
+
return true;
|
|
16966
|
+
},
|
|
16967
|
+
onPaired: async (session, hello) => {
|
|
16968
|
+
const pairRoot = derivePairRoot(session);
|
|
16969
|
+
const fingerprint = session.peerFingerprint;
|
|
16970
|
+
const nowIso = new Date(now()).toISOString();
|
|
16971
|
+
const record = {
|
|
16972
|
+
clientPub: hello.ePub,
|
|
16973
|
+
name: session.clientName ?? fingerprint,
|
|
16974
|
+
fingerprint,
|
|
16975
|
+
createdAt: pairings.get(fingerprint)?.createdAt ?? nowIso,
|
|
16976
|
+
lastSeen: nowIso,
|
|
16977
|
+
pairRoot,
|
|
16978
|
+
rendezvousUrl
|
|
16979
|
+
};
|
|
16980
|
+
pairings.set(fingerprint, record);
|
|
16981
|
+
await persist();
|
|
16982
|
+
startReconnectLoops(record);
|
|
16983
|
+
return { mode: "offer", fingerprint, name: record.name };
|
|
16984
|
+
}
|
|
16985
|
+
});
|
|
16986
|
+
}
|
|
16987
|
+
function startReconnectLoops(record) {
|
|
16988
|
+
for (const slot of ["cur", "prev"]) {
|
|
16989
|
+
const epochOf = () => slot === "cur" ? currentEpoch(now()) : currentEpoch(now()) - 1;
|
|
16990
|
+
startLoop({
|
|
16991
|
+
key: `pair:${record.fingerprint}:${slot}`,
|
|
16992
|
+
rendezvousUrl: record.rendezvousUrl,
|
|
16993
|
+
token: () => deriveEpochRoutingToken(record.pairRoot, epochOf()),
|
|
16994
|
+
shouldContinue: () => pairings.has(record.fingerprint),
|
|
16995
|
+
singleUse: false,
|
|
16996
|
+
verify: (presented, expected) => constantTimeEqual(presented, expected),
|
|
16997
|
+
onPaired: async (session, _hello) => {
|
|
16998
|
+
const existing = pairings.get(record.fingerprint);
|
|
16999
|
+
if (existing) {
|
|
17000
|
+
existing.lastSeen = new Date(now()).toISOString();
|
|
17001
|
+
await persist().catch((err) => log(`[pairing] lastSeen persist failed: ${errMsg(err)}`));
|
|
17002
|
+
}
|
|
17003
|
+
return {
|
|
17004
|
+
mode: "reconnect",
|
|
17005
|
+
fingerprint: record.fingerprint,
|
|
17006
|
+
name: existing?.name ?? record.name
|
|
17007
|
+
};
|
|
17008
|
+
}
|
|
17009
|
+
});
|
|
17010
|
+
}
|
|
17011
|
+
}
|
|
17012
|
+
async function startAutoconnect() {
|
|
17013
|
+
await ensureLoaded();
|
|
17014
|
+
for (const record of pairings.values()) {
|
|
17015
|
+
startReconnectLoops(record);
|
|
17016
|
+
}
|
|
17017
|
+
if (pairings.size > 0) {
|
|
17018
|
+
log(`[pairing] autoconnect: standing connections for ${pairings.size} pairing(s)`);
|
|
17019
|
+
}
|
|
17020
|
+
}
|
|
17021
|
+
async function revoke(idOrName) {
|
|
17022
|
+
await ensureLoaded();
|
|
17023
|
+
let target = pairings.get(idOrName);
|
|
17024
|
+
if (!target) {
|
|
17025
|
+
for (const rec of pairings.values()) {
|
|
17026
|
+
if (rec.name === idOrName) {
|
|
17027
|
+
target = rec;
|
|
17028
|
+
break;
|
|
17029
|
+
}
|
|
17030
|
+
}
|
|
17031
|
+
}
|
|
17032
|
+
if (!target) return false;
|
|
17033
|
+
pairings.delete(target.fingerprint);
|
|
17034
|
+
await persist();
|
|
17035
|
+
await stopLoop(`pair:${target.fingerprint}:cur`);
|
|
17036
|
+
await stopLoop(`pair:${target.fingerprint}:prev`);
|
|
17037
|
+
log(`[pairing] revoked ${target.fingerprint} (${target.name})`);
|
|
17038
|
+
return true;
|
|
17039
|
+
}
|
|
17040
|
+
async function list() {
|
|
17041
|
+
await ensureLoaded();
|
|
17042
|
+
return Array.from(pairings.values()).map((r) => ({ ...r }));
|
|
17043
|
+
}
|
|
17044
|
+
async function shutdown() {
|
|
17045
|
+
if (shuttingDown) return;
|
|
17046
|
+
shuttingDown = true;
|
|
17047
|
+
for (const key of Array.from(loops.keys())) {
|
|
17048
|
+
await stopLoop(key);
|
|
17049
|
+
}
|
|
17050
|
+
for (const handle of Array.from(channels)) {
|
|
17051
|
+
await handle.close().catch(() => {
|
|
17052
|
+
});
|
|
17053
|
+
}
|
|
17054
|
+
channels.clear();
|
|
17055
|
+
}
|
|
17056
|
+
return { createOffer, list, revoke, startAutoconnect, shutdown };
|
|
15650
17057
|
}
|
|
15651
|
-
function
|
|
15652
|
-
|
|
17058
|
+
function dialUrl(rendezvousUrl, token) {
|
|
17059
|
+
const sep = rendezvousUrl.includes("?") ? "&" : "?";
|
|
17060
|
+
return `${rendezvousUrl}${sep}side=daemon&t=${encodeURIComponent(token)}`;
|
|
15653
17061
|
}
|
|
15654
|
-
|
|
15655
|
-
|
|
15656
|
-
|
|
15657
|
-
|
|
15658
|
-
|
|
15659
|
-
return emptyFile();
|
|
17062
|
+
function waitClosed(sink, signal) {
|
|
17063
|
+
return new Promise((resolve12) => {
|
|
17064
|
+
if (!sink.isOpen) {
|
|
17065
|
+
resolve12();
|
|
17066
|
+
return;
|
|
15660
17067
|
}
|
|
15661
|
-
|
|
15662
|
-
|
|
15663
|
-
|
|
17068
|
+
let settled = false;
|
|
17069
|
+
const finish = () => {
|
|
17070
|
+
if (settled) return;
|
|
17071
|
+
settled = true;
|
|
17072
|
+
resolve12();
|
|
15664
17073
|
};
|
|
15665
|
-
|
|
15666
|
-
|
|
15667
|
-
|
|
17074
|
+
sink.onClose(() => finish());
|
|
17075
|
+
signal.addEventListener("abort", () => {
|
|
17076
|
+
sink.close("registry shutdown");
|
|
17077
|
+
finish();
|
|
17078
|
+
});
|
|
17079
|
+
});
|
|
15668
17080
|
}
|
|
15669
|
-
|
|
15670
|
-
|
|
15671
|
-
|
|
15672
|
-
|
|
15673
|
-
|
|
15674
|
-
|
|
17081
|
+
function sleep(ms, signal) {
|
|
17082
|
+
return new Promise((resolve12) => {
|
|
17083
|
+
if (signal.aborted) return resolve12();
|
|
17084
|
+
const timer = setTimeout(resolve12, ms);
|
|
17085
|
+
if (typeof timer.unref === "function") timer.unref();
|
|
17086
|
+
signal.addEventListener("abort", () => {
|
|
17087
|
+
clearTimeout(timer);
|
|
17088
|
+
resolve12();
|
|
17089
|
+
});
|
|
15675
17090
|
});
|
|
15676
17091
|
}
|
|
15677
|
-
|
|
15678
|
-
|
|
15679
|
-
|
|
15680
|
-
|
|
15681
|
-
|
|
15682
|
-
|
|
15683
|
-
};
|
|
15684
|
-
await writeProviders(file);
|
|
15685
|
-
return providerEnvVar(provider);
|
|
15686
|
-
}
|
|
15687
|
-
async function removeProviderKey(provider) {
|
|
15688
|
-
const file = await loadProviders();
|
|
15689
|
-
if (!(provider in file.providers)) return false;
|
|
15690
|
-
delete file.providers[provider];
|
|
15691
|
-
await writeProviders(file);
|
|
15692
|
-
return true;
|
|
17092
|
+
function isPairingsFile(v) {
|
|
17093
|
+
if (typeof v !== "object" || v === null) return false;
|
|
17094
|
+
const rec = v;
|
|
17095
|
+
if (rec["v"] !== PAIRINGS_VERSION) return false;
|
|
17096
|
+
if (!Array.isArray(rec["pairings"])) return false;
|
|
17097
|
+
return rec["pairings"].every(isPairingRecord);
|
|
15693
17098
|
}
|
|
15694
|
-
|
|
15695
|
-
|
|
15696
|
-
const
|
|
15697
|
-
|
|
15698
|
-
|
|
15699
|
-
|
|
15700
|
-
|
|
15701
|
-
|
|
15702
|
-
|
|
15703
|
-
|
|
15704
|
-
}
|
|
15705
|
-
return injected;
|
|
17099
|
+
function isPairingRecord(v) {
|
|
17100
|
+
if (typeof v !== "object" || v === null) return false;
|
|
17101
|
+
const r = v;
|
|
17102
|
+
return typeof r["clientPub"] === "string" && typeof r["name"] === "string" && typeof r["fingerprint"] === "string" && typeof r["createdAt"] === "string" && typeof r["lastSeen"] === "string" && typeof r["pairRoot"] === "string" && typeof r["rendezvousUrl"] === "string";
|
|
17103
|
+
}
|
|
17104
|
+
function isEnoent(err) {
|
|
17105
|
+
return typeof err === "object" && err !== null && "code" in err && err.code === "ENOENT";
|
|
17106
|
+
}
|
|
17107
|
+
function errMsg(err) {
|
|
17108
|
+
return err instanceof Error ? err.message : String(err);
|
|
15706
17109
|
}
|
|
15707
17110
|
|
|
15708
17111
|
// src/index.ts
|
|
@@ -15714,7 +17117,9 @@ var DEFAULT_ALWAYS_ON_TOOLS = [
|
|
|
15714
17117
|
"agent_kill",
|
|
15715
17118
|
"session_list",
|
|
15716
17119
|
"session_monitor",
|
|
15717
|
-
"session_events_poll"
|
|
17120
|
+
"session_events_poll",
|
|
17121
|
+
"permissions_list",
|
|
17122
|
+
"permissions_respond"
|
|
15718
17123
|
];
|
|
15719
17124
|
async function createGateway(opts) {
|
|
15720
17125
|
const startedAt = Date.now();
|
|
@@ -15891,6 +17296,9 @@ async function createGateway(opts) {
|
|
|
15891
17296
|
registerDaemonHealthTools(server, { workspace, registered, startedAt });
|
|
15892
17297
|
registerCommandTools(server, { workspace, registry: sessions });
|
|
15893
17298
|
registerRemoteTools(server, { controller: remote });
|
|
17299
|
+
if (opts.pairingRegistry) {
|
|
17300
|
+
registerPairingTools(server, { registry: opts.pairingRegistry });
|
|
17301
|
+
}
|
|
15894
17302
|
registerSessionTools(server, {
|
|
15895
17303
|
registry: sessions,
|
|
15896
17304
|
mcpProxy,
|
|
@@ -16038,6 +17446,7 @@ async function createGateway(opts) {
|
|
|
16038
17446
|
token,
|
|
16039
17447
|
ptyEnabled: opts.spawnPty != null,
|
|
16040
17448
|
tunnels,
|
|
17449
|
+
...opts.pairingRegistry ? { pairings: opts.pairingRegistry } : {},
|
|
16041
17450
|
sessionEvents,
|
|
16042
17451
|
eventRing,
|
|
16043
17452
|
supervisor,
|
|
@@ -16082,6 +17491,7 @@ async function createGateway(opts) {
|
|
|
16082
17491
|
registered,
|
|
16083
17492
|
sessions,
|
|
16084
17493
|
tunnels,
|
|
17494
|
+
...opts.pairingRegistry ? { pairing: opts.pairingRegistry } : {},
|
|
16085
17495
|
token,
|
|
16086
17496
|
mintOrchestratorScope: scopeTokens.mint,
|
|
16087
17497
|
async stop() {
|
|
@@ -16090,6 +17500,10 @@ async function createGateway(opts) {
|
|
|
16090
17500
|
cronScheduler.shutdown();
|
|
16091
17501
|
supervisor.shutdown();
|
|
16092
17502
|
sessions.shutdown();
|
|
17503
|
+
if (opts.pairingRegistry) {
|
|
17504
|
+
await opts.pairingRegistry.shutdown().catch(() => {
|
|
17505
|
+
});
|
|
17506
|
+
}
|
|
16093
17507
|
await mcpProxy.closeAll();
|
|
16094
17508
|
await tunnels.shutdown();
|
|
16095
17509
|
await remote.shutdown();
|
|
@@ -16125,7 +17539,15 @@ async function runBoot(workspace, opts, conversations, events) {
|
|
|
16125
17539
|
durationMs: 0
|
|
16126
17540
|
});
|
|
16127
17541
|
}
|
|
17542
|
+
var export_PROVIDER_ENV_VARS = providers_store_exports.PROVIDER_ENV_VARS;
|
|
17543
|
+
var export_getProviderKey = providers_store_exports.getProviderKey;
|
|
17544
|
+
var export_injectProviderKeysIntoEnv = providers_store_exports.injectProviderKeysIntoEnv;
|
|
17545
|
+
var export_loadProviders = providers_store_exports.loadProviders;
|
|
17546
|
+
var export_providerEnvVar = providers_store_exports.providerEnvVar;
|
|
17547
|
+
var export_providersPath = providers_store_exports.providersPath;
|
|
17548
|
+
var export_removeProviderKey = providers_store_exports.removeProviderKey;
|
|
17549
|
+
var export_setProviderKey = providers_store_exports.setProviderKey;
|
|
16128
17550
|
|
|
16129
|
-
export { DEFAULT_ORCHESTRATOR_TOOLS, PROVIDER_ENV_VARS, composeSessionObservers, createFileStepCache, createGateway, createOrchestratorInjector, createOrchestratorMcpServerFactory, createScopeTokenRegistry, createWorkspaceFs, daemonRegistryDir, declaredPresetToProviderPreset, deriveSessionUsage, fileConversationStore, formatToolCall, formatToolResult, getMcpCredentialDeps, injectProviderKeysIntoEnv, listPresets, loadProviders, makeBrowserAdapterLister, monitorPolicyWait, monitorSessionWait, narrowOrchestratorTools, parseDuration, projectSessionUsage, providerEnvVar, providersPath, readDaemonRegistry, readRuntimeMeta, removeProviderKey, setMcpCredentialDeps, setProviderKey, sweepStaleDaemonRegistry, sweepStaleRuntimeMetas, unlinkDaemonRegistryEntry, unlinkRuntimeMeta, writeDaemonRegistryEntry };
|
|
17551
|
+
export { AuthResolutionError, DEFAULT_ORCHESTRATOR_TOOLS, PAIRINGS_VERSION, export_PROVIDER_ENV_VARS as PROVIDER_ENV_VARS, composeSessionObservers, createFileStepCache, createGateway, createOrchestratorInjector, createOrchestratorMcpServerFactory, createPairingRegistry, createScopeTokenRegistry, createWorkspaceFs, credentialFingerprint, daemonRegistryDir, declaredPresetToProviderPreset, deriveSessionUsage, fileConversationStore, formatToolCall, formatToolResult, getMcpCredentialDeps, export_getProviderKey as getProviderKey, export_injectProviderKeysIntoEnv as injectProviderKeysIntoEnv, listPresets, export_loadProviders as loadProviders, makeBrowserAdapterLister, monitorPolicyWait, monitorSessionWait, narrowOrchestratorTools, normalizeSkillsOption, parseDuration, projectSessionUsage, export_providerEnvVar as providerEnvVar, export_providersPath as providersPath, readDaemonRegistry, readRuntimeMeta, registerPairingTools, export_removeProviderKey as removeProviderKey, resolveAuthSpec, resolveSpawnDefaults, setMcpCredentialDeps, export_setProviderKey as setProviderKey, sweepStaleDaemonRegistry, sweepStaleRuntimeMetas, unlinkDaemonRegistryEntry, unlinkRuntimeMeta, writeDaemonRegistryEntry };
|
|
16130
17552
|
//# sourceMappingURL=index.mjs.map
|
|
16131
17553
|
//# sourceMappingURL=index.mjs.map
|