@agentproto/runtime 5.1.0 → 5.2.1
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/dist/catalog-models.d.ts +3 -3
- package/dist/config-schema.d.ts +2 -2
- package/dist/config-schema.mjs +64 -35
- package/dist/config-schema.mjs.map +1 -1
- package/dist/config.d.ts +25 -3
- package/dist/config.mjs +64 -35
- package/dist/config.mjs.map +1 -1
- package/dist/{context-continuity-Bn99UtVC.d.ts → context-continuity-BfNVbX5W.d.ts} +1 -1
- package/dist/index.d.ts +1620 -7265
- package/dist/index.mjs +1956 -844
- package/dist/index.mjs.map +1 -1
- package/dist/{session-config-ByfVeEQJ.d.ts → session-config-RyRL2AbD.d.ts} +1 -1
- package/dist/session-presence.mjs +64 -35
- package/dist/session-presence.mjs.map +1 -1
- package/dist/sessions-g4fw_zKx.d.ts +6231 -0
- package/dist/{spawn-defaults-wQV9AeiG.d.ts → spawn-defaults-CHvz85DD.d.ts} +1 -1
- package/dist/user-presets.d.ts +6 -99
- package/dist/user-presets.mjs +35 -4
- package/dist/user-presets.mjs.map +1 -1
- package/package.json +13 -11
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { execFile, spawn, execFileSync } from 'child_process';
|
|
2
|
-
import { readFile, mkdir, writeFile, unlink, readdir, stat,
|
|
2
|
+
import { readFile, mkdir, writeFile, unlink, readdir, stat, chmod, mkdtemp, rm, rename, copyFile, realpath, appendFile, open, access } from 'fs/promises';
|
|
3
3
|
import { homedir, hostname, tmpdir } from 'os';
|
|
4
4
|
import { join, resolve, dirname, basename, isAbsolute, normalize as normalize$1, relative, sep, extname, delimiter } from 'path';
|
|
5
5
|
import { loadAdapterSpawnSandboxConfig, loadSandboxConfig, resolveCommandSandbox, COMMAND_SANDBOX_MODE_ENV } from '@agentproto/command-sandbox';
|
|
@@ -18,6 +18,7 @@ import { CatalogProviderSchema, getModelsByProvider, getStaticModelProvider } fr
|
|
|
18
18
|
import { resolvePairPageUrl, DEFAULT_PAIR_PAGE } from '@agentproto/secrets/pairing/browser';
|
|
19
19
|
import { ListToolsRequestSchema, CallToolRequestSchema, SUPPORTED_PROTOCOL_VERSIONS, LATEST_PROTOCOL_VERSION } from '@modelcontextprotocol/sdk/types.js';
|
|
20
20
|
import { makeCredsStore, makeAdapterResolver, makeAdapterLister, discoverAdapterPackages, makeSetupLedger, makeListTool, makeSetupTool } from '@agentproto/provider-kit';
|
|
21
|
+
import { syncPiModels, getConfiguredEndpoints, checkHarnessFit, connectorById } from '@agentproto/llm-endpoint';
|
|
21
22
|
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
22
23
|
import { StreamableHTTPError, StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
|
|
23
24
|
import { createServer as createServer$1 } from 'net';
|
|
@@ -38,6 +39,8 @@ import { Readable } from 'stream';
|
|
|
38
39
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
39
40
|
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
|
|
40
41
|
import { WebSocketServer } from 'ws';
|
|
42
|
+
import { clientHandshakeOverSink, daemonHandshakeOverSink, createTunnelClient } from '@agentproto/acp/tunnel';
|
|
43
|
+
import { parseOfferUrl, deriveOfferTokens, startClientHandshake, encodePairingMessage, decodePairingReply, derivePairRoot, encodeOfferUrl, OFFER_VERSION, decodePairingHello, respondToHandshake, PairingError, currentEpoch, deriveEpochTokens, HOSTED_RENDEZVOUS_URL, PAIRING_PROTOCOL_OUTDATED_MESSAGE, respondToLegacyHandshake } from '@agentproto/secrets/pairing';
|
|
41
44
|
import { loadAppBundledTools, peekAppUi, loadAppHandle } from '@agentproto/app-kit';
|
|
42
45
|
import { loadAgent } from '@agentproto/agent';
|
|
43
46
|
import { normalizeToolId } from '@agentproto/driver';
|
|
@@ -53,12 +56,11 @@ import { SseError, SSEClientTransport } from '@modelcontextprotocol/sdk/client/s
|
|
|
53
56
|
import { isToolVisibilityAppOnly, getToolUiResourceUri, RESOURCE_MIME_TYPE } from '@modelcontextprotocol/ext-apps/app-bridge';
|
|
54
57
|
import { EXTENSION_ID } from '@modelcontextprotocol/ext-apps/server';
|
|
55
58
|
import { UnauthorizedError } from '@modelcontextprotocol/sdk/client/auth.js';
|
|
56
|
-
import { ledgerKeyOf, buildAgentLanePrompt, parseAgentLaneReport, parseReviewManifest, resolveBinding, manifestSha, sha256Hex,
|
|
59
|
+
import { ledgerKeyOf, rangeSha, canonicalAttestationBytes, attestationSha256, buildAgentLanePrompt, parseAgentLaneReport, parseReviewManifest, resolvePacks, resolveBinding, manifestSha, sha256Hex, compileReview, buildAttestation, parsePackManifest } from '@agentproto/review';
|
|
60
|
+
import { createRequire } from 'module';
|
|
57
61
|
import { parseRoutineManifest } from '@agentproto/routine';
|
|
58
62
|
import { promisify } from 'util';
|
|
59
|
-
import {
|
|
60
|
-
import { clientHandshakeOverSink, daemonHandshakeOverSink, createTunnelClient } from '@agentproto/acp/tunnel';
|
|
61
|
-
import { parseOfferUrl, deriveOfferTokens, startClientHandshake, encodePairingMessage, decodePairingReply, derivePairRoot, encodeOfferUrl, OFFER_VERSION, decodePairingHello, respondToHandshake, PairingError, currentEpoch, deriveEpochTokens, HOSTED_RENDEZVOUS_URL, PAIRING_PROTOCOL_OUTDATED_MESSAGE, respondToLegacyHandshake } from '@agentproto/secrets/pairing';
|
|
63
|
+
import { pipeline } from 'stream/promises';
|
|
62
64
|
import { identityFingerprint } from '@agentproto/secrets/identity';
|
|
63
65
|
|
|
64
66
|
/**
|
|
@@ -134,15 +136,15 @@ function hasMarker(command, marker) {
|
|
|
134
136
|
return false;
|
|
135
137
|
}
|
|
136
138
|
function listProcesses() {
|
|
137
|
-
return new Promise((
|
|
139
|
+
return new Promise((resolve38) => {
|
|
138
140
|
execFile("ps", ["-axo", "pid=,command="], { maxBuffer: 32 * 1024 * 1024 }, (err, stdout) => {
|
|
139
|
-
if (err) return
|
|
141
|
+
if (err) return resolve38([]);
|
|
140
142
|
const out = [];
|
|
141
143
|
for (const line of stdout.split("\n")) {
|
|
142
144
|
const m = line.match(/^\s*(\d+)\s+(.*)$/);
|
|
143
145
|
if (m) out.push({ pid: Number(m[1]), command: m[2] ?? "" });
|
|
144
146
|
}
|
|
145
|
-
|
|
147
|
+
resolve38(out);
|
|
146
148
|
});
|
|
147
149
|
});
|
|
148
150
|
}
|
|
@@ -236,14 +238,14 @@ function extractText(value) {
|
|
|
236
238
|
return null;
|
|
237
239
|
}
|
|
238
240
|
function formatToolResult(toolName, result, isError) {
|
|
239
|
-
const
|
|
241
|
+
const text13 = extractText(result);
|
|
240
242
|
if (isError) {
|
|
241
|
-
const message =
|
|
243
|
+
const message = text13 ?? (result != null ? JSON.stringify(result) : "failed");
|
|
242
244
|
const firstLine = message.split(/\r?\n/)[0] ?? message;
|
|
243
245
|
return truncate(firstLine, MAX_RESULT_LENGTH);
|
|
244
246
|
}
|
|
245
|
-
if (
|
|
246
|
-
const trimmed =
|
|
247
|
+
if (text13 == null) return null;
|
|
248
|
+
const trimmed = text13.trim();
|
|
247
249
|
if (!trimmed) return null;
|
|
248
250
|
const lines = trimmed.split(/\r?\n/);
|
|
249
251
|
if (lines.length > 1) {
|
|
@@ -253,9 +255,9 @@ function formatToolResult(toolName, result, isError) {
|
|
|
253
255
|
return truncate(lines[0], MAX_RESULT_LENGTH);
|
|
254
256
|
}
|
|
255
257
|
function artifactMarkerLines(result) {
|
|
256
|
-
const
|
|
257
|
-
if (
|
|
258
|
-
return
|
|
258
|
+
const text13 = extractText(result);
|
|
259
|
+
if (text13 == null || !text13.includes(ARTIFACT_MARKER)) return [];
|
|
260
|
+
return text13.split(/\r?\n/).filter((line) => line.includes(ARTIFACT_MARKER)).slice(0, MAX_ARTIFACT_MARKER_LINES).map((line) => line.trim());
|
|
259
261
|
}
|
|
260
262
|
var SALIENT_ARG_KEYS, MAX_CALL_LENGTH, MAX_RESULT_LENGTH, BESPOKE_TOOLS, ARTIFACT_MARKER, MAX_ARTIFACT_MARKER_LINES;
|
|
261
263
|
var init_tool_presenter = __esm({
|
|
@@ -393,8 +395,8 @@ function detectShellPrCreate(command, resultText) {
|
|
|
393
395
|
}
|
|
394
396
|
function cwdRelated(sessionCwd, cwd) {
|
|
395
397
|
if (sessionCwd === cwd) return true;
|
|
396
|
-
const
|
|
397
|
-
return cwd.startsWith(sessionCwd +
|
|
398
|
+
const sep6 = "/";
|
|
399
|
+
return cwd.startsWith(sessionCwd + sep6) || sessionCwd.startsWith(cwd + sep6);
|
|
398
400
|
}
|
|
399
401
|
function pickExecutorSession(sessions, cwd) {
|
|
400
402
|
const candidates = sessions.filter(
|
|
@@ -471,7 +473,7 @@ function plausibleContextUsed(contextSize, contextUsed) {
|
|
|
471
473
|
function tokenCost(tokens, pricePer1M) {
|
|
472
474
|
return tokens === void 0 ? 0 : tokens * pricePer1M / 1e6;
|
|
473
475
|
}
|
|
474
|
-
function deriveSessionUsage(input,
|
|
476
|
+
function deriveSessionUsage(input, resolve38 = defaultResolver) {
|
|
475
477
|
const contextUsed = plausibleContextUsed(input.contextSize, input.contextUsed);
|
|
476
478
|
const base = {
|
|
477
479
|
...input.model !== void 0 ? { model: input.model } : {},
|
|
@@ -486,7 +488,7 @@ function deriveSessionUsage(input, resolve37 = defaultResolver) {
|
|
|
486
488
|
}
|
|
487
489
|
const hasTokens = input.tokensIn !== void 0 || input.tokensOut !== void 0 || input.cacheReadTokens !== void 0 || input.cacheWriteTokens !== void 0;
|
|
488
490
|
if (hasTokens) {
|
|
489
|
-
const pricing = input.model !== void 0 ?
|
|
491
|
+
const pricing = input.model !== void 0 ? resolve38(input.model) : void 0;
|
|
490
492
|
if (!pricing) {
|
|
491
493
|
return { ...base, source: "no-pricing" };
|
|
492
494
|
}
|
|
@@ -666,9 +668,9 @@ function createTranscriptWriter(opts) {
|
|
|
666
668
|
state.textDebounce = null;
|
|
667
669
|
}
|
|
668
670
|
if (!state.textBuf) return;
|
|
669
|
-
const
|
|
671
|
+
const text13 = state.textBuf;
|
|
670
672
|
state.textBuf = "";
|
|
671
|
-
writeRecord(sessionId, state, { kind: "text-delta", sessionId, text:
|
|
673
|
+
writeRecord(sessionId, state, { kind: "text-delta", sessionId, text: text13 });
|
|
672
674
|
};
|
|
673
675
|
const flushThoughtBuf = (sessionId, state) => {
|
|
674
676
|
if (state.thoughtDebounce) {
|
|
@@ -676,9 +678,9 @@ function createTranscriptWriter(opts) {
|
|
|
676
678
|
state.thoughtDebounce = null;
|
|
677
679
|
}
|
|
678
680
|
if (!state.thoughtBuf) return;
|
|
679
|
-
const
|
|
681
|
+
const text13 = state.thoughtBuf;
|
|
680
682
|
state.thoughtBuf = "";
|
|
681
|
-
writeRecord(sessionId, state, { kind: "thought", sessionId, text:
|
|
683
|
+
writeRecord(sessionId, state, { kind: "thought", sessionId, text: text13 });
|
|
682
684
|
};
|
|
683
685
|
const flushToolUpdates = (sessionId, state) => {
|
|
684
686
|
if (state.toolUpdates.size === 0) return;
|
|
@@ -696,9 +698,9 @@ function createTranscriptWriter(opts) {
|
|
|
696
698
|
state.textDebounce = setTimeout(() => {
|
|
697
699
|
state.textDebounce = null;
|
|
698
700
|
if (!state.textBuf) return;
|
|
699
|
-
const
|
|
701
|
+
const text13 = state.textBuf;
|
|
700
702
|
state.textBuf = "";
|
|
701
|
-
writeRecord(sessionId, state, { kind: "text-delta", sessionId, text:
|
|
703
|
+
writeRecord(sessionId, state, { kind: "text-delta", sessionId, text: text13, partial: true });
|
|
702
704
|
}, DEBOUNCE_MS);
|
|
703
705
|
};
|
|
704
706
|
const scheduleThoughtDebounce = (sessionId, state) => {
|
|
@@ -706,9 +708,9 @@ function createTranscriptWriter(opts) {
|
|
|
706
708
|
state.thoughtDebounce = setTimeout(() => {
|
|
707
709
|
state.thoughtDebounce = null;
|
|
708
710
|
if (!state.thoughtBuf) return;
|
|
709
|
-
const
|
|
711
|
+
const text13 = state.thoughtBuf;
|
|
710
712
|
state.thoughtBuf = "";
|
|
711
|
-
writeRecord(sessionId, state, { kind: "thought", sessionId, text:
|
|
713
|
+
writeRecord(sessionId, state, { kind: "thought", sessionId, text: text13, partial: true });
|
|
712
714
|
}, DEBOUNCE_MS);
|
|
713
715
|
};
|
|
714
716
|
return {
|
|
@@ -716,14 +718,14 @@ function createTranscriptWriter(opts) {
|
|
|
716
718
|
const state = getState(sessionId);
|
|
717
719
|
flushBuffers(sessionId, state);
|
|
718
720
|
const blocks = typeof message === "string" ? void 0 : Array.isArray(message) ? message : [message];
|
|
719
|
-
const
|
|
721
|
+
const text13 = typeof message === "string" ? message : (blocks ?? []).filter((b) => isRecord2(b) && typeof b.text === "string").map((b) => b.text).join("\n");
|
|
720
722
|
if (opts2?.system) {
|
|
721
723
|
writeRecord(sessionId, state, { kind: "system-prompt", sessionId, text: opts2.system });
|
|
722
724
|
}
|
|
723
|
-
let userText =
|
|
725
|
+
let userText = text13;
|
|
724
726
|
if (opts2?.system) {
|
|
725
|
-
if (
|
|
726
|
-
userText =
|
|
727
|
+
if (text13.startsWith(opts2.system)) {
|
|
728
|
+
userText = text13.slice(opts2.system.length).replace(/^\n\n/, "");
|
|
727
729
|
} else {
|
|
728
730
|
console.warn(
|
|
729
731
|
`[transcript-writer] recordPrompt: system preamble is not a prefix of the composed prompt for session ${sessionId} \u2014 composition invariant violated (see session-spawn.ts's composedPreamble), recording the full text as user-prompt instead of risking a bad slice.`
|
|
@@ -987,8 +989,8 @@ function createTranscriptWriter(opts) {
|
|
|
987
989
|
if (!state) return Promise.resolve();
|
|
988
990
|
flushBuffers(sessionId, state);
|
|
989
991
|
states.delete(sessionId);
|
|
990
|
-
return new Promise((
|
|
991
|
-
state.stream.end(() =>
|
|
992
|
+
return new Promise((resolve38) => {
|
|
993
|
+
state.stream.end(() => resolve38());
|
|
992
994
|
});
|
|
993
995
|
},
|
|
994
996
|
closeAll() {
|
|
@@ -997,7 +999,7 @@ function createTranscriptWriter(opts) {
|
|
|
997
999
|
const state = states.get(sessionId);
|
|
998
1000
|
if (!state) continue;
|
|
999
1001
|
flushBuffers(sessionId, state);
|
|
1000
|
-
closings.push(new Promise((
|
|
1002
|
+
closings.push(new Promise((resolve38) => state.stream.end(() => resolve38())));
|
|
1001
1003
|
}
|
|
1002
1004
|
states.clear();
|
|
1003
1005
|
return Promise.all(closings).then(() => void 0);
|
|
@@ -1060,10 +1062,10 @@ __export(transcript_export_exports, {
|
|
|
1060
1062
|
renderJson: () => renderJson,
|
|
1061
1063
|
renderMarkdown: () => renderMarkdown
|
|
1062
1064
|
});
|
|
1063
|
-
function trunc(
|
|
1064
|
-
if (
|
|
1065
|
-
return
|
|
1066
|
-
\u2026 [${
|
|
1065
|
+
function trunc(text13, n) {
|
|
1066
|
+
if (text13.length <= n) return text13;
|
|
1067
|
+
return text13.slice(0, n) + `
|
|
1068
|
+
\u2026 [${text13.length - n} chars truncated]`;
|
|
1067
1069
|
}
|
|
1068
1070
|
function renderMarkdown(session, opts = {}) {
|
|
1069
1071
|
const { meta, messages } = session;
|
|
@@ -1158,9 +1160,9 @@ async function exportClaudeCodeSession(adapterSessionId, cwd, configDir) {
|
|
|
1158
1160
|
let stream;
|
|
1159
1161
|
try {
|
|
1160
1162
|
stream = createReadStream(filePath, { encoding: "utf8" });
|
|
1161
|
-
await new Promise((
|
|
1163
|
+
await new Promise((resolve38, reject) => {
|
|
1162
1164
|
stream.once("error", reject);
|
|
1163
|
-
stream.once("open",
|
|
1165
|
+
stream.once("open", resolve38);
|
|
1164
1166
|
});
|
|
1165
1167
|
} catch (err) {
|
|
1166
1168
|
const code = err.code;
|
|
@@ -1434,7 +1436,7 @@ async function discoverHermesSessions(cwd, since, expectedId) {
|
|
|
1434
1436
|
}
|
|
1435
1437
|
async function defaultHermesRunner(adapterSessionId) {
|
|
1436
1438
|
const { spawn: spawn8 } = await import('child_process');
|
|
1437
|
-
return new Promise((
|
|
1439
|
+
return new Promise((resolve38, reject) => {
|
|
1438
1440
|
const chunks = [];
|
|
1439
1441
|
const errChunks = [];
|
|
1440
1442
|
const proc = spawn8(
|
|
@@ -1453,7 +1455,7 @@ async function defaultHermesRunner(adapterSessionId) {
|
|
|
1453
1455
|
)
|
|
1454
1456
|
);
|
|
1455
1457
|
} else {
|
|
1456
|
-
|
|
1458
|
+
resolve38(Buffer.concat(chunks).toString("utf8"));
|
|
1457
1459
|
}
|
|
1458
1460
|
});
|
|
1459
1461
|
});
|
|
@@ -1490,9 +1492,9 @@ async function exportDaemonEventsSession(sessionId, desc) {
|
|
|
1490
1492
|
let stream;
|
|
1491
1493
|
try {
|
|
1492
1494
|
stream = createReadStream(filePath, { encoding: "utf8" });
|
|
1493
|
-
await new Promise((
|
|
1495
|
+
await new Promise((resolve38, reject) => {
|
|
1494
1496
|
stream.once("error", reject);
|
|
1495
|
-
stream.once("open",
|
|
1497
|
+
stream.once("open", resolve38);
|
|
1496
1498
|
});
|
|
1497
1499
|
} catch (err) {
|
|
1498
1500
|
const code = err.code;
|
|
@@ -1558,10 +1560,10 @@ ${rec.stdout}`);
|
|
|
1558
1560
|
${rec.stderr}`);
|
|
1559
1561
|
if (rec.truncated) sections.push("(output truncated)");
|
|
1560
1562
|
if (rec.timedOut) sections.push("(timed out)");
|
|
1561
|
-
const
|
|
1563
|
+
const text13 = sections.join("\n");
|
|
1562
1564
|
messages.push({
|
|
1563
1565
|
role: "tool",
|
|
1564
|
-
text: rec.exitCode !== 0 ? `[error] ${
|
|
1566
|
+
text: rec.exitCode !== 0 ? `[error] ${text13}` : text13,
|
|
1565
1567
|
toolName,
|
|
1566
1568
|
...tsOrUndefined !== void 0 ? { ts: tsOrUndefined } : {}
|
|
1567
1569
|
});
|
|
@@ -1616,10 +1618,10 @@ ${rec.stderr}`);
|
|
|
1616
1618
|
case "tool-result": {
|
|
1617
1619
|
flushAssistant();
|
|
1618
1620
|
const name = rec.toolCallId ? toolNameById.get(rec.toolCallId) : void 0;
|
|
1619
|
-
const
|
|
1621
|
+
const text13 = typeof rec.result === "string" ? rec.result : JSON.stringify(rec.result ?? "");
|
|
1620
1622
|
messages.push({
|
|
1621
1623
|
role: "tool",
|
|
1622
|
-
text: rec.isError ? `[error] ${
|
|
1624
|
+
text: rec.isError ? `[error] ${text13}` : text13,
|
|
1623
1625
|
...name ? { toolName: name } : {},
|
|
1624
1626
|
...tsOrUndefined !== void 0 ? { ts: tsOrUndefined } : {}
|
|
1625
1627
|
});
|
|
@@ -1783,10 +1785,10 @@ The session may predate persistence, or CODEX_HOME points elsewhere.`
|
|
|
1783
1785
|
if (entry.type !== "response_item") continue;
|
|
1784
1786
|
switch (p.type) {
|
|
1785
1787
|
case "message": {
|
|
1786
|
-
const
|
|
1787
|
-
if (!
|
|
1788
|
+
const text13 = codexBlocksText(p.content).trim();
|
|
1789
|
+
if (!text13) break;
|
|
1788
1790
|
const role = p.role === "assistant" ? "assistant" : p.role === "user" ? "user" : "system";
|
|
1789
|
-
messages.push({ role, text:
|
|
1791
|
+
messages.push({ role, text: text13 });
|
|
1790
1792
|
break;
|
|
1791
1793
|
}
|
|
1792
1794
|
case "reasoning": {
|
|
@@ -1916,7 +1918,7 @@ async function exportOpenCodeSession(conversationId) {
|
|
|
1916
1918
|
continue;
|
|
1917
1919
|
}
|
|
1918
1920
|
const role = mdata.role === "assistant" ? "assistant" : "user";
|
|
1919
|
-
let
|
|
1921
|
+
let text13 = "";
|
|
1920
1922
|
let reasoning = "";
|
|
1921
1923
|
const toolCalls = [];
|
|
1922
1924
|
const toolResults = [];
|
|
@@ -1929,7 +1931,7 @@ async function exportOpenCodeSession(conversationId) {
|
|
|
1929
1931
|
}
|
|
1930
1932
|
switch (part.type) {
|
|
1931
1933
|
case "text":
|
|
1932
|
-
if (part.text)
|
|
1934
|
+
if (part.text) text13 += part.text;
|
|
1933
1935
|
break;
|
|
1934
1936
|
case "reasoning":
|
|
1935
1937
|
if (part.text) reasoning += part.text;
|
|
@@ -1946,9 +1948,9 @@ async function exportOpenCodeSession(conversationId) {
|
|
|
1946
1948
|
}
|
|
1947
1949
|
}
|
|
1948
1950
|
}
|
|
1949
|
-
if (
|
|
1951
|
+
if (text13.trim() || reasoning.trim() || toolCalls.length) {
|
|
1950
1952
|
const em = { role };
|
|
1951
|
-
if (
|
|
1953
|
+
if (text13.trim()) em.text = text13.trim();
|
|
1952
1954
|
if (reasoning.trim()) em.reasoning = reasoning.trim();
|
|
1953
1955
|
if (toolCalls.length) em.toolCalls = toolCalls;
|
|
1954
1956
|
em.ts = m.time_created;
|
|
@@ -2034,13 +2036,13 @@ function mastraContentToMessage(role, content) {
|
|
|
2034
2036
|
} catch {
|
|
2035
2037
|
return { message: void 0, toolResults, toolCalls: 0 };
|
|
2036
2038
|
}
|
|
2037
|
-
let
|
|
2039
|
+
let text13 = "";
|
|
2038
2040
|
let reasoning = "";
|
|
2039
2041
|
const toolCalls = [];
|
|
2040
2042
|
for (const raw of parsed.parts ?? []) {
|
|
2041
2043
|
if (!raw || typeof raw !== "object") continue;
|
|
2042
2044
|
const part = raw;
|
|
2043
|
-
if (part.type === "text" && part.text)
|
|
2045
|
+
if (part.type === "text" && part.text) text13 += part.text;
|
|
2044
2046
|
else if (part.type === "reasoning" && part.reasoning) reasoning += part.reasoning;
|
|
2045
2047
|
else if (part.type === "tool-invocation" && part.toolInvocation) {
|
|
2046
2048
|
const inv = part.toolInvocation;
|
|
@@ -2054,9 +2056,9 @@ function mastraContentToMessage(role, content) {
|
|
|
2054
2056
|
}
|
|
2055
2057
|
}
|
|
2056
2058
|
let message;
|
|
2057
|
-
if (
|
|
2059
|
+
if (text13.trim() || reasoning.trim() || toolCalls.length) {
|
|
2058
2060
|
message = { role };
|
|
2059
|
-
if (
|
|
2061
|
+
if (text13.trim()) message.text = text13.trim();
|
|
2060
2062
|
if (reasoning.trim()) message.reasoning = reasoning.trim();
|
|
2061
2063
|
if (toolCalls.length) message.toolCalls = toolCalls;
|
|
2062
2064
|
}
|
|
@@ -2214,25 +2216,25 @@ async function exportPiSession(conversationId) {
|
|
|
2214
2216
|
const m = entry.message;
|
|
2215
2217
|
const content = m.content;
|
|
2216
2218
|
if (m.role === "toolResult") {
|
|
2217
|
-
const
|
|
2219
|
+
const text14 = piBlocksText(content);
|
|
2218
2220
|
messages.push({
|
|
2219
2221
|
role: "tool",
|
|
2220
|
-
text: m.isError ? `[error] ${
|
|
2222
|
+
text: m.isError ? `[error] ${text14}` : text14,
|
|
2221
2223
|
...m.toolName ? { toolName: m.toolName } : {}
|
|
2222
2224
|
});
|
|
2223
2225
|
continue;
|
|
2224
2226
|
}
|
|
2225
2227
|
const role = m.role === "assistant" ? "assistant" : "user";
|
|
2226
|
-
let
|
|
2228
|
+
let text13 = "";
|
|
2227
2229
|
let reasoning = "";
|
|
2228
2230
|
const toolCalls = [];
|
|
2229
2231
|
if (typeof content === "string") {
|
|
2230
|
-
|
|
2232
|
+
text13 = content;
|
|
2231
2233
|
} else if (Array.isArray(content)) {
|
|
2232
2234
|
for (const raw of content) {
|
|
2233
2235
|
if (!raw || typeof raw !== "object") continue;
|
|
2234
2236
|
const part = raw;
|
|
2235
|
-
if (part.type === "text" && part.text)
|
|
2237
|
+
if (part.type === "text" && part.text) text13 += part.text;
|
|
2236
2238
|
else if (part.type === "thinking" && part.thinking) reasoning += part.thinking;
|
|
2237
2239
|
else if (part.type === "toolCall") {
|
|
2238
2240
|
toolCalls.push({ name: part.name ?? "tool", args: JSON.stringify(part.arguments ?? {}) });
|
|
@@ -2240,9 +2242,9 @@ async function exportPiSession(conversationId) {
|
|
|
2240
2242
|
}
|
|
2241
2243
|
}
|
|
2242
2244
|
}
|
|
2243
|
-
if (
|
|
2245
|
+
if (text13.trim() || reasoning.trim() || toolCalls.length) {
|
|
2244
2246
|
const em = { role };
|
|
2245
|
-
if (
|
|
2247
|
+
if (text13.trim()) em.text = text13.trim();
|
|
2246
2248
|
if (reasoning.trim()) em.reasoning = reasoning.trim();
|
|
2247
2249
|
if (toolCalls.length) em.toolCalls = toolCalls;
|
|
2248
2250
|
messages.push(em);
|
|
@@ -2435,8 +2437,8 @@ function extractFirstText(content) {
|
|
|
2435
2437
|
}
|
|
2436
2438
|
return void 0;
|
|
2437
2439
|
}
|
|
2438
|
-
function isClaudeControlText(
|
|
2439
|
-
return /^<(?:local-command-caveat|local-command-stdout|command-name|task-notification)>/i.test(
|
|
2440
|
+
function isClaudeControlText(text13) {
|
|
2441
|
+
return /^<(?:local-command-caveat|local-command-stdout|command-name|task-notification)>/i.test(text13);
|
|
2440
2442
|
}
|
|
2441
2443
|
async function scanClaudeJsonl(filePath) {
|
|
2442
2444
|
const stream = createReadStream(filePath, { encoding: "utf8" });
|
|
@@ -2465,9 +2467,9 @@ async function scanClaudeJsonl(filePath) {
|
|
|
2465
2467
|
if (entry.type === "user" || entry.type === "assistant") {
|
|
2466
2468
|
messageCount += 1;
|
|
2467
2469
|
if (preview2 === void 0 && entry.type === "user") {
|
|
2468
|
-
const
|
|
2469
|
-
if (
|
|
2470
|
-
preview2 =
|
|
2470
|
+
const text13 = extractFirstText(entry.message?.content);
|
|
2471
|
+
if (text13 !== void 0 && !isClaudeControlText(text13)) {
|
|
2472
|
+
preview2 = text13.length > 120 ? text13.slice(0, 120) : text13;
|
|
2471
2473
|
}
|
|
2472
2474
|
}
|
|
2473
2475
|
}
|
|
@@ -3816,8 +3818,8 @@ async function enrichWithRemainingQuota(rollup, opts) {
|
|
|
3816
3818
|
})
|
|
3817
3819
|
);
|
|
3818
3820
|
const capMs = opts.timeoutMs ?? DEFAULT_ENRICH_CAP_MS;
|
|
3819
|
-
const cap = new Promise((
|
|
3820
|
-
setTimeout(() =>
|
|
3821
|
+
const cap = new Promise((resolve38) => {
|
|
3822
|
+
setTimeout(() => resolve38(null), capMs);
|
|
3821
3823
|
});
|
|
3822
3824
|
const settled = await Promise.race([work, cap]);
|
|
3823
3825
|
if (settled === null) return rollup;
|
|
@@ -3881,8 +3883,8 @@ async function enrichWithAccountCredits(rollup, opts) {
|
|
|
3881
3883
|
})
|
|
3882
3884
|
);
|
|
3883
3885
|
const capMs = opts.timeoutMs ?? DEFAULT_ENRICH_CAP_MS;
|
|
3884
|
-
const cap = new Promise((
|
|
3885
|
-
setTimeout(() =>
|
|
3886
|
+
const cap = new Promise((resolve38) => {
|
|
3887
|
+
setTimeout(() => resolve38(null), capMs);
|
|
3886
3888
|
});
|
|
3887
3889
|
const settled = await Promise.race([work, cap]);
|
|
3888
3890
|
if (settled === null) return rollup;
|
|
@@ -4427,11 +4429,11 @@ async function defaultLoadConfig() {
|
|
|
4427
4429
|
const { loadConfig: loadConfig2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
4428
4430
|
return loadConfig2();
|
|
4429
4431
|
}
|
|
4430
|
-
function assembleShimPath(shimDir, basePath,
|
|
4432
|
+
function assembleShimPath(shimDir, basePath, sep6 = delimiter) {
|
|
4431
4433
|
const base = basePath ?? "";
|
|
4432
|
-
const entries = base.length > 0 ? base.split(
|
|
4434
|
+
const entries = base.length > 0 ? base.split(sep6) : [];
|
|
4433
4435
|
if (entries[0] === shimDir) return base;
|
|
4434
|
-
return [shimDir, ...entries].join(
|
|
4436
|
+
return [shimDir, ...entries].join(sep6);
|
|
4435
4437
|
}
|
|
4436
4438
|
function buildGhShimEnv(input) {
|
|
4437
4439
|
const env = {
|
|
@@ -4706,9 +4708,9 @@ async function readFirstTurn(sessionId) {
|
|
|
4706
4708
|
let stream;
|
|
4707
4709
|
try {
|
|
4708
4710
|
stream = createReadStream(sessionEventsPath(sessionId), { encoding: "utf8" });
|
|
4709
|
-
await new Promise((
|
|
4711
|
+
await new Promise((resolve38, reject) => {
|
|
4710
4712
|
stream.once("error", reject);
|
|
4711
|
-
stream.once("open",
|
|
4713
|
+
stream.once("open", resolve38);
|
|
4712
4714
|
});
|
|
4713
4715
|
} catch {
|
|
4714
4716
|
return null;
|
|
@@ -5085,7 +5087,8 @@ var init_config_schema = __esm({
|
|
|
5085
5087
|
featuresConfigSchema = z.object({
|
|
5086
5088
|
pty: z.boolean().optional(),
|
|
5087
5089
|
llmEndpoint: z.boolean().optional(),
|
|
5088
|
-
deviceInferenceShare: z.boolean().optional()
|
|
5090
|
+
deviceInferenceShare: z.boolean().optional(),
|
|
5091
|
+
deviceSpawnAllow: z.boolean().optional()
|
|
5089
5092
|
}).passthrough();
|
|
5090
5093
|
worktreesConfigSchema = z.object({
|
|
5091
5094
|
root: z.string().optional(),
|
|
@@ -5932,6 +5935,16 @@ var init_config_schema = __esm({
|
|
|
5932
5935
|
label: "Device inference sharing",
|
|
5933
5936
|
help: "Expose this daemon's local inference endpoint(s) to a paired HOST-scoped controller. Toggle via `agentproto devices share-inference on|off`.",
|
|
5934
5937
|
default: false
|
|
5938
|
+
},
|
|
5939
|
+
{
|
|
5940
|
+
path: "features.deviceSpawnAllow",
|
|
5941
|
+
schema: bool,
|
|
5942
|
+
apply: "restart",
|
|
5943
|
+
writable: true,
|
|
5944
|
+
section: "daemon",
|
|
5945
|
+
label: "Device spawn allow",
|
|
5946
|
+
help: 'Allow a paired HOST-scoped controller to spawn/drive agent sessions on this daemon (`agent_start({ sandbox: "device:<name>" })`). Toggle via `agentproto devices allow-spawn on|off`.',
|
|
5947
|
+
default: false
|
|
5935
5948
|
}
|
|
5936
5949
|
];
|
|
5937
5950
|
}
|
|
@@ -6987,10 +7000,118 @@ var init_canonical_posture = __esm({
|
|
|
6987
7000
|
})();
|
|
6988
7001
|
}
|
|
6989
7002
|
});
|
|
7003
|
+
function emptyFile3() {
|
|
7004
|
+
return { version: 1, presets: [] };
|
|
7005
|
+
}
|
|
7006
|
+
function userPresetsPath() {
|
|
7007
|
+
return resolve(homedir(), ".agentproto", "presets.json");
|
|
7008
|
+
}
|
|
7009
|
+
async function loadUserPresets() {
|
|
7010
|
+
try {
|
|
7011
|
+
return userPresetsFileSchema.parse(JSON.parse(await readFile(userPresetsPath(), "utf8")));
|
|
7012
|
+
} catch {
|
|
7013
|
+
return emptyFile3();
|
|
7014
|
+
}
|
|
7015
|
+
}
|
|
7016
|
+
async function writeUserPresets(file) {
|
|
7017
|
+
const dir = join(homedir(), ".agentproto");
|
|
7018
|
+
await mkdir(dir, { recursive: true });
|
|
7019
|
+
await writeFile(userPresetsPath(), JSON.stringify(file, null, 2) + "\n", {
|
|
7020
|
+
encoding: "utf8",
|
|
7021
|
+
mode: 384
|
|
7022
|
+
});
|
|
7023
|
+
}
|
|
7024
|
+
async function listUserPresets() {
|
|
7025
|
+
return (await loadUserPresets()).presets;
|
|
7026
|
+
}
|
|
7027
|
+
async function getUserPreset(id) {
|
|
7028
|
+
return (await loadUserPresets()).presets.find((preset) => preset.id === id);
|
|
7029
|
+
}
|
|
7030
|
+
async function saveUserPreset(preset) {
|
|
7031
|
+
const validated = userPresetSchema.parse(preset);
|
|
7032
|
+
const file = await loadUserPresets();
|
|
7033
|
+
const index = file.presets.findIndex((existing) => existing.id === validated.id);
|
|
7034
|
+
const lastUsedAt = validated.lastUsedAt ?? (index === -1 ? void 0 : file.presets[index]?.lastUsedAt);
|
|
7035
|
+
const next = { ...validated, ...lastUsedAt ? { lastUsedAt } : {} };
|
|
7036
|
+
if (index === -1) file.presets.push(next);
|
|
7037
|
+
else file.presets[index] = next;
|
|
7038
|
+
await writeUserPresets(file);
|
|
7039
|
+
}
|
|
7040
|
+
async function deleteUserPreset(id) {
|
|
7041
|
+
const file = await loadUserPresets();
|
|
7042
|
+
const index = file.presets.findIndex((preset) => preset.id === id);
|
|
7043
|
+
if (index === -1) return false;
|
|
7044
|
+
file.presets.splice(index, 1);
|
|
7045
|
+
await writeUserPresets(file);
|
|
7046
|
+
return true;
|
|
7047
|
+
}
|
|
7048
|
+
async function touchUserPreset(id) {
|
|
7049
|
+
const file = await loadUserPresets();
|
|
7050
|
+
const index = file.presets.findIndex((preset) => preset.id === id);
|
|
7051
|
+
if (index === -1) return;
|
|
7052
|
+
file.presets[index] = { ...file.presets[index], lastUsedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
7053
|
+
await writeUserPresets(file);
|
|
7054
|
+
}
|
|
7055
|
+
function deriveRecentSpawnConfigs(descriptors, limit = 5) {
|
|
7056
|
+
const seen = /* @__PURE__ */ new Set();
|
|
7057
|
+
const out = [];
|
|
7058
|
+
for (const desc of descriptors) {
|
|
7059
|
+
if (out.length >= limit) break;
|
|
7060
|
+
if (desc.kind !== "agent-cli") continue;
|
|
7061
|
+
const adapter = desc.harness ?? desc.adapterSlug;
|
|
7062
|
+
if (!adapter) continue;
|
|
7063
|
+
const config = {
|
|
7064
|
+
adapter,
|
|
7065
|
+
...desc.model ? { model: desc.model } : {},
|
|
7066
|
+
...desc.accessProfile?.profileRef ? { profileRef: desc.accessProfile.profileRef } : {},
|
|
7067
|
+
...desc.cwd ? { cwd: desc.cwd } : {}
|
|
7068
|
+
};
|
|
7069
|
+
const key = JSON.stringify(config);
|
|
7070
|
+
if (seen.has(key)) continue;
|
|
7071
|
+
seen.add(key);
|
|
7072
|
+
out.push({ ...config, recent: true });
|
|
7073
|
+
}
|
|
7074
|
+
return out;
|
|
7075
|
+
}
|
|
7076
|
+
var effortSchema, postureSchema, routeSchema, userPresetSchema, userPresetsFileSchema;
|
|
7077
|
+
var init_user_presets = __esm({
|
|
7078
|
+
"src/user-presets.ts"() {
|
|
7079
|
+
effortSchema = z.enum(["low", "medium", "high", "xhigh", "max", "ultracode"]);
|
|
7080
|
+
postureSchema = z.union([
|
|
7081
|
+
z.enum(["default", "plan", "accept-edits", "bypass", "read-only"]),
|
|
7082
|
+
z.object({ harnessModeId: z.string().min(1) })
|
|
7083
|
+
]);
|
|
7084
|
+
routeSchema = z.object({
|
|
7085
|
+
gateway: z.string().min(1),
|
|
7086
|
+
baseUrl: z.string().url().optional()
|
|
7087
|
+
});
|
|
7088
|
+
userPresetSchema = z.object({
|
|
7089
|
+
id: z.string().regex(/^[a-z0-9][a-z0-9-]*$/),
|
|
7090
|
+
label: z.string().min(1),
|
|
7091
|
+
adapter: z.string().min(1).optional(),
|
|
7092
|
+
harness: z.string().min(1).optional(),
|
|
7093
|
+
model: z.string().min(1).optional(),
|
|
7094
|
+
route: routeSchema.optional(),
|
|
7095
|
+
access: z.object({ profileRef: z.string().min(1).optional() }).optional(),
|
|
7096
|
+
posture: postureSchema.optional(),
|
|
7097
|
+
effort: effortSchema.optional(),
|
|
7098
|
+
contextProfile: z.string().min(1).optional(),
|
|
7099
|
+
cwd: z.string().min(1).optional(),
|
|
7100
|
+
skills: z.array(z.string().min(1)).optional(),
|
|
7101
|
+
bundles: z.array(z.string().min(1)).optional(),
|
|
7102
|
+
browser: z.union([z.literal("headless"), z.literal(false)]).optional(),
|
|
7103
|
+
lastUsedAt: z.string().min(1).optional()
|
|
7104
|
+
});
|
|
7105
|
+
userPresetsFileSchema = z.object({
|
|
7106
|
+
version: z.literal(1),
|
|
7107
|
+
presets: z.array(userPresetSchema)
|
|
7108
|
+
});
|
|
7109
|
+
}
|
|
7110
|
+
});
|
|
6990
7111
|
function defaultValidationDeps() {
|
|
6991
7112
|
return { getProfile: getAuthProfile };
|
|
6992
7113
|
}
|
|
6993
|
-
function
|
|
7114
|
+
function emptyFile4() {
|
|
6994
7115
|
return { version: 1, presets: [] };
|
|
6995
7116
|
}
|
|
6996
7117
|
function harnessPresetsPath() {
|
|
@@ -7000,7 +7121,7 @@ async function loadHarnessPresets() {
|
|
|
7000
7121
|
try {
|
|
7001
7122
|
return harnessPresetsFileSchema.parse(JSON.parse(await readFile(harnessPresetsPath(), "utf8")));
|
|
7002
7123
|
} catch {
|
|
7003
|
-
return
|
|
7124
|
+
return emptyFile4();
|
|
7004
7125
|
}
|
|
7005
7126
|
}
|
|
7006
7127
|
async function writeHarnessPresets(file) {
|
|
@@ -7567,8 +7688,8 @@ function extractText2(message) {
|
|
|
7567
7688
|
}
|
|
7568
7689
|
return isTextBlock(message) ? message.text : void 0;
|
|
7569
7690
|
}
|
|
7570
|
-
function truncate2(
|
|
7571
|
-
const codePoints = Array.from(
|
|
7691
|
+
function truncate2(text13, maxLength) {
|
|
7692
|
+
const codePoints = Array.from(text13);
|
|
7572
7693
|
const sliced = codePoints.slice(0, maxLength).join("");
|
|
7573
7694
|
const lastSpace = sliced.lastIndexOf(" ");
|
|
7574
7695
|
const cut = lastSpace > 0 ? sliced.slice(0, lastSpace) : sliced;
|
|
@@ -7590,6 +7711,162 @@ var init_session_title = __esm({
|
|
|
7590
7711
|
MAX_LENGTH = 72;
|
|
7591
7712
|
}
|
|
7592
7713
|
});
|
|
7714
|
+
function splitOnLastAt(value) {
|
|
7715
|
+
const m = LAST_AT_RE.exec(value);
|
|
7716
|
+
return m ? { head: m[1], tail: m[2] } : null;
|
|
7717
|
+
}
|
|
7718
|
+
function pickSoleLoadedModel(models) {
|
|
7719
|
+
const loaded = models.filter((m) => m.state === "loaded");
|
|
7720
|
+
if (loaded.length === 1) return { ok: true, model: loaded[0] };
|
|
7721
|
+
if (loaded.length === 0) {
|
|
7722
|
+
return { ok: false, message: "no model is currently loaded on this endpoint \u2014 load one first, or pass `inference.model` explicitly." };
|
|
7723
|
+
}
|
|
7724
|
+
return {
|
|
7725
|
+
ok: false,
|
|
7726
|
+
message: `${loaded.length} models are loaded on this endpoint (${loaded.map((m) => m.id).join(", ")}) \u2014 pass \`inference.model\` to pick one.`
|
|
7727
|
+
};
|
|
7728
|
+
}
|
|
7729
|
+
async function resolveLocalEndpoint(endpoint, modelId, deps2) {
|
|
7730
|
+
const connectorId = endpoint.connector ?? "openai-compatible";
|
|
7731
|
+
const connector = connectorById(connectorId);
|
|
7732
|
+
if (!connector) {
|
|
7733
|
+
return { ok: false, message: `endpoint "${endpoint.id}" declares unknown connector "${connectorId}".` };
|
|
7734
|
+
}
|
|
7735
|
+
const listModels = deps2.listModels ?? ((ep) => connector.listModels(ep.baseUrl));
|
|
7736
|
+
const models = await listModels(endpoint);
|
|
7737
|
+
let resolvedModelId;
|
|
7738
|
+
if (modelId) {
|
|
7739
|
+
resolvedModelId = modelId;
|
|
7740
|
+
} else {
|
|
7741
|
+
const sole = pickSoleLoadedModel(models);
|
|
7742
|
+
if (!sole.ok) return sole;
|
|
7743
|
+
resolvedModelId = sole.model.id;
|
|
7744
|
+
}
|
|
7745
|
+
const match = models.find((m) => m.id === resolvedModelId);
|
|
7746
|
+
const target = {
|
|
7747
|
+
kind: "local",
|
|
7748
|
+
endpointId: endpoint.id,
|
|
7749
|
+
modelId: resolvedModelId,
|
|
7750
|
+
loadedCtx: match?.loadedCtx,
|
|
7751
|
+
connector: connectorId,
|
|
7752
|
+
baseUrl: endpoint.baseUrl
|
|
7753
|
+
};
|
|
7754
|
+
return {
|
|
7755
|
+
ok: true,
|
|
7756
|
+
target,
|
|
7757
|
+
gatewayModelId: `${endpoint.id}/${resolvedModelId}`,
|
|
7758
|
+
label: endpoint.id
|
|
7759
|
+
};
|
|
7760
|
+
}
|
|
7761
|
+
async function resolveDeviceEndpoint(device, modelId, deps2) {
|
|
7762
|
+
if (!deps2.probeDevice) {
|
|
7763
|
+
return {
|
|
7764
|
+
ok: false,
|
|
7765
|
+
message: `"@${device}" needs a device probe (this daemon has no paired-host registry wired) \u2014 cannot resolve.`
|
|
7766
|
+
};
|
|
7767
|
+
}
|
|
7768
|
+
const probe = await deps2.probeDevice(device, modelId);
|
|
7769
|
+
if (!probe.reachable) {
|
|
7770
|
+
return { ok: false, message: `device "${device}" is offline or unreachable \u2014 ${probe.message}` };
|
|
7771
|
+
}
|
|
7772
|
+
if (!probe.modelIds.includes(modelId)) {
|
|
7773
|
+
return {
|
|
7774
|
+
ok: false,
|
|
7775
|
+
message: `device "${device}" is online but has no loaded model "${modelId}" (it has: ${probe.modelIds.join(", ") || "(none loaded)"}).`
|
|
7776
|
+
};
|
|
7777
|
+
}
|
|
7778
|
+
const target = {
|
|
7779
|
+
kind: "device",
|
|
7780
|
+
remoteEndpointId: probe.endpointId,
|
|
7781
|
+
device,
|
|
7782
|
+
modelId,
|
|
7783
|
+
loadedCtx: void 0
|
|
7784
|
+
};
|
|
7785
|
+
return {
|
|
7786
|
+
ok: true,
|
|
7787
|
+
target,
|
|
7788
|
+
gatewayModelId: `${probe.endpointId}@${device}/${modelId}`,
|
|
7789
|
+
label: `${probe.endpointId}@${device}`
|
|
7790
|
+
};
|
|
7791
|
+
}
|
|
7792
|
+
async function resolveInferenceTarget(req, deps2 = {}) {
|
|
7793
|
+
const getConfiguredEndpoints$1 = deps2.getConfiguredEndpoints ?? getConfiguredEndpoints;
|
|
7794
|
+
if (req.endpoint) {
|
|
7795
|
+
const deviceRef = splitOnLastAt(req.endpoint);
|
|
7796
|
+
if (deviceRef) {
|
|
7797
|
+
if (!req.model) {
|
|
7798
|
+
return { ok: false, message: `inference.endpoint "${req.endpoint}" is device-qualified \u2014 inference.model is required to pick a model on it.` };
|
|
7799
|
+
}
|
|
7800
|
+
return resolveDeviceEndpoint(deviceRef.tail, req.model, deps2);
|
|
7801
|
+
}
|
|
7802
|
+
const endpoint = getConfiguredEndpoints$1().find((e) => e.id === req.endpoint);
|
|
7803
|
+
if (!endpoint) {
|
|
7804
|
+
const known = getConfiguredEndpoints$1().map((e) => e.id);
|
|
7805
|
+
return {
|
|
7806
|
+
ok: false,
|
|
7807
|
+
message: `inference.endpoint "${req.endpoint}" is not a configured endpoint (known: ${known.length > 0 ? known.join(", ") : "(none configured)"}). Run \`agentproto llm endpoints add\` or \`detect\` first.`
|
|
7808
|
+
};
|
|
7809
|
+
}
|
|
7810
|
+
return resolveLocalEndpoint(endpoint, req.model, deps2);
|
|
7811
|
+
}
|
|
7812
|
+
if (req.model) {
|
|
7813
|
+
const split = splitOnLastAt(req.model);
|
|
7814
|
+
if (!split) {
|
|
7815
|
+
return {
|
|
7816
|
+
ok: false,
|
|
7817
|
+
message: `inference.model "${req.model}" has no "@<device|endpoint>" suffix \u2014 pass inference.endpoint explicitly, or use the "<model>@<ref>" shorthand.`
|
|
7818
|
+
};
|
|
7819
|
+
}
|
|
7820
|
+
const { head: modelId, tail: ref } = split;
|
|
7821
|
+
const asLocalEndpoint = getConfiguredEndpoints$1().find((e) => e.id === ref);
|
|
7822
|
+
if (asLocalEndpoint) return resolveLocalEndpoint(asLocalEndpoint, modelId, deps2);
|
|
7823
|
+
return resolveDeviceEndpoint(ref, modelId, deps2);
|
|
7824
|
+
}
|
|
7825
|
+
return { ok: false, message: "inference requires at least one of `endpoint` or `model`." };
|
|
7826
|
+
}
|
|
7827
|
+
function fitCheckForTarget(harness, target, label, opts = {}) {
|
|
7828
|
+
return checkHarnessFit({
|
|
7829
|
+
harness,
|
|
7830
|
+
loadedCtx: target.loadedCtx,
|
|
7831
|
+
...opts.headroomRatio !== void 0 ? { headroomRatio: opts.headroomRatio } : {},
|
|
7832
|
+
endpointLabel: label
|
|
7833
|
+
});
|
|
7834
|
+
}
|
|
7835
|
+
function projectInferenceBinding(harness, resolved) {
|
|
7836
|
+
switch (harness) {
|
|
7837
|
+
case "pi": {
|
|
7838
|
+
if (resolved.target.kind === "device") {
|
|
7839
|
+
return {
|
|
7840
|
+
ok: false,
|
|
7841
|
+
message: `"pi" against a paired device's endpoint ("${resolved.label}") is not supported yet \u2014 bind "claude-code"/"claude-sdk" through the gateway instead, or run pi directly on that device.`
|
|
7842
|
+
};
|
|
7843
|
+
}
|
|
7844
|
+
return { ok: true, projection: { model: resolved.gatewayModelId, needsPiModelsSync: true } };
|
|
7845
|
+
}
|
|
7846
|
+
case "claude-code":
|
|
7847
|
+
case "claude-sdk":
|
|
7848
|
+
return {
|
|
7849
|
+
ok: true,
|
|
7850
|
+
projection: {
|
|
7851
|
+
model: resolved.gatewayModelId,
|
|
7852
|
+
route: { gateway: "llm-endpoint" },
|
|
7853
|
+
auth: { mode: "api-key", apiKey: "not-needed" },
|
|
7854
|
+
deferredTools: true
|
|
7855
|
+
}
|
|
7856
|
+
};
|
|
7857
|
+
default:
|
|
7858
|
+
return {
|
|
7859
|
+
ok: false,
|
|
7860
|
+
message: `"${harness}" has no local-endpoint wiring yet (supported: pi, claude-code, claude-sdk) \u2014 pass \`model\`/\`route\`/\`access\` directly instead of \`inference\`.`
|
|
7861
|
+
};
|
|
7862
|
+
}
|
|
7863
|
+
}
|
|
7864
|
+
var LAST_AT_RE;
|
|
7865
|
+
var init_inference_binding = __esm({
|
|
7866
|
+
"src/inference-binding.ts"() {
|
|
7867
|
+
LAST_AT_RE = /^(.+)@([^@]+)$/;
|
|
7868
|
+
}
|
|
7869
|
+
});
|
|
7593
7870
|
|
|
7594
7871
|
// src/mcp-credential-deps.ts
|
|
7595
7872
|
function setMcpCredentialDeps(d) {
|
|
@@ -7609,8 +7886,8 @@ var init_mcp_credential_deps = __esm({
|
|
|
7609
7886
|
function extractPromptText(message) {
|
|
7610
7887
|
if (typeof message === "string") return message;
|
|
7611
7888
|
if (message && typeof message === "object" && "text" in message) {
|
|
7612
|
-
const
|
|
7613
|
-
if (typeof
|
|
7889
|
+
const text13 = message.text;
|
|
7890
|
+
if (typeof text13 === "string") return text13;
|
|
7614
7891
|
}
|
|
7615
7892
|
return JSON.stringify(message);
|
|
7616
7893
|
}
|
|
@@ -7683,7 +7960,7 @@ async function* pollAgentOutputFallback(host, remoteSessionId, since) {
|
|
|
7683
7960
|
`sandbox proxy: ${consecutivePollFailures} consecutive poll failures against the box daemon (session "${remoteSessionId}") \u2014 giving up. Last error: ${pollErr instanceof Error ? pollErr.message : String(pollErr)}`
|
|
7684
7961
|
);
|
|
7685
7962
|
}
|
|
7686
|
-
await new Promise((
|
|
7963
|
+
await new Promise((resolve38) => setTimeout(resolve38, POLL_RETRY_DELAY_MS));
|
|
7687
7964
|
continue;
|
|
7688
7965
|
}
|
|
7689
7966
|
if (result.timedOut) continue;
|
|
@@ -7849,9 +8126,9 @@ async function connectBoxToolClient(mcpUrl) {
|
|
|
7849
8126
|
}
|
|
7850
8127
|
const content = textBlocks(reply.content);
|
|
7851
8128
|
if (reply.isError === true) {
|
|
7852
|
-
const
|
|
8129
|
+
const text13 = content.find((c) => c.type === "text")?.text;
|
|
7853
8130
|
throw new Error(
|
|
7854
|
-
`box tool "${name}" failed${
|
|
8131
|
+
`box tool "${name}" failed${text13 ? `: ${text13}` : ""}`
|
|
7855
8132
|
);
|
|
7856
8133
|
}
|
|
7857
8134
|
return { content };
|
|
@@ -7880,15 +8157,15 @@ function firstText(res) {
|
|
|
7880
8157
|
function isJsonRecord(value) {
|
|
7881
8158
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
7882
8159
|
}
|
|
7883
|
-
function parseJsonRecordText(
|
|
7884
|
-
if (
|
|
7885
|
-
const parsed = JSON.parse(
|
|
8160
|
+
function parseJsonRecordText(text13) {
|
|
8161
|
+
if (text13.length === 0) return void 0;
|
|
8162
|
+
const parsed = JSON.parse(text13);
|
|
7886
8163
|
return isJsonRecord(parsed) ? parsed : void 0;
|
|
7887
8164
|
}
|
|
7888
8165
|
function parseToolJson(res) {
|
|
7889
|
-
const
|
|
7890
|
-
if (
|
|
7891
|
-
return parseJsonRecordText(
|
|
8166
|
+
const text13 = firstText(res);
|
|
8167
|
+
if (text13 === void 0) return void 0;
|
|
8168
|
+
return parseJsonRecordText(text13);
|
|
7892
8169
|
}
|
|
7893
8170
|
function buildServeLaunchScript(dir, port) {
|
|
7894
8171
|
if (dir.includes("'")) {
|
|
@@ -7937,15 +8214,15 @@ async function pollServeReady(url, opts) {
|
|
|
7937
8214
|
for (; ; ) {
|
|
7938
8215
|
if (await probe(url)) return true;
|
|
7939
8216
|
if (Date.now() >= deadline) return false;
|
|
7940
|
-
await new Promise((
|
|
8217
|
+
await new Promise((resolve38) => setTimeout(resolve38, intervalMs));
|
|
7941
8218
|
}
|
|
7942
8219
|
}
|
|
7943
8220
|
async function seedAllowlist(client) {
|
|
7944
8221
|
let existing = null;
|
|
7945
8222
|
try {
|
|
7946
8223
|
const read = await client.callTool("file_read", { path: BOX_ALLOWLIST_REL });
|
|
7947
|
-
const
|
|
7948
|
-
if (
|
|
8224
|
+
const text13 = firstText(read);
|
|
8225
|
+
if (text13 !== void 0) existing = text13;
|
|
7949
8226
|
} catch {
|
|
7950
8227
|
}
|
|
7951
8228
|
const merged = mergeAllowlistForAppServe(existing);
|
|
@@ -7974,9 +8251,9 @@ function extractServeError(logText) {
|
|
|
7974
8251
|
async function readServeError(client, dir) {
|
|
7975
8252
|
try {
|
|
7976
8253
|
const res = await client.callTool("file_read", { path: serveLogPath(dir) });
|
|
7977
|
-
const
|
|
7978
|
-
if (
|
|
7979
|
-
return extractServeError(
|
|
8254
|
+
const text13 = firstText(res);
|
|
8255
|
+
if (text13 === void 0) return void 0;
|
|
8256
|
+
return extractServeError(text13);
|
|
7980
8257
|
} catch {
|
|
7981
8258
|
return void 0;
|
|
7982
8259
|
}
|
|
@@ -8062,7 +8339,7 @@ var init_sandbox_app_serve = __esm({
|
|
|
8062
8339
|
}
|
|
8063
8340
|
});
|
|
8064
8341
|
async function getFreePort() {
|
|
8065
|
-
return new Promise((
|
|
8342
|
+
return new Promise((resolve38, reject) => {
|
|
8066
8343
|
const srv = createServer$1();
|
|
8067
8344
|
srv.once("error", reject);
|
|
8068
8345
|
srv.listen(0, "127.0.0.1", () => {
|
|
@@ -8072,7 +8349,7 @@ async function getFreePort() {
|
|
|
8072
8349
|
return;
|
|
8073
8350
|
}
|
|
8074
8351
|
const { port } = address;
|
|
8075
|
-
srv.close(() =>
|
|
8352
|
+
srv.close(() => resolve38(port));
|
|
8076
8353
|
});
|
|
8077
8354
|
});
|
|
8078
8355
|
}
|
|
@@ -8085,7 +8362,7 @@ async function probeHealth(url, timeoutMs) {
|
|
|
8085
8362
|
} catch {
|
|
8086
8363
|
}
|
|
8087
8364
|
if (Date.now() >= deadline) return false;
|
|
8088
|
-
await new Promise((
|
|
8365
|
+
await new Promise((resolve38) => setTimeout(resolve38, POLL_INTERVAL_MS));
|
|
8089
8366
|
}
|
|
8090
8367
|
}
|
|
8091
8368
|
var HEALTH_TIMEOUT_MS, POLL_INTERVAL_MS, localSandboxProvider;
|
|
@@ -8723,6 +9000,48 @@ ${callerPrompt}`;
|
|
|
8723
9000
|
return pre || void 0;
|
|
8724
9001
|
}
|
|
8725
9002
|
async function spawnAgentSession(deps2, input) {
|
|
9003
|
+
if (input.inference) {
|
|
9004
|
+
if (input.model || input.route || input.access) {
|
|
9005
|
+
return {
|
|
9006
|
+
ok: false,
|
|
9007
|
+
code: "inference_endpoint_unresolved",
|
|
9008
|
+
message: "agent_start: `inference` is mutually exclusive with `model`/`route`/`access` \u2014 bind the endpoint via `inference`, or pin those fields directly, never both in the same spawn."
|
|
9009
|
+
};
|
|
9010
|
+
}
|
|
9011
|
+
const harness = input.harness ?? input.adapter;
|
|
9012
|
+
const resolved2 = await resolveInferenceTarget(input.inference, {
|
|
9013
|
+
...deps2.probeDeviceInference ? { probeDevice: deps2.probeDeviceInference } : {}
|
|
9014
|
+
});
|
|
9015
|
+
if (!resolved2.ok) {
|
|
9016
|
+
return { ok: false, code: "inference_endpoint_unresolved", message: `agent_start: ${resolved2.message}` };
|
|
9017
|
+
}
|
|
9018
|
+
const fit = fitCheckForTarget(harness, resolved2.target, resolved2.label, {
|
|
9019
|
+
...input.inference.headroomPct !== void 0 ? { headroomRatio: input.inference.headroomPct / 100 } : {}
|
|
9020
|
+
});
|
|
9021
|
+
if (fit.verdict === "no-fit" && !input.inference.force) {
|
|
9022
|
+
return {
|
|
9023
|
+
ok: false,
|
|
9024
|
+
code: "inference_fit_check_failed",
|
|
9025
|
+
message: `agent_start: ${fit.message ?? "harness fit check failed."} Pass \`inference.force: true\` to override.`
|
|
9026
|
+
};
|
|
9027
|
+
}
|
|
9028
|
+
const projected = projectInferenceBinding(harness, resolved2);
|
|
9029
|
+
if (!projected.ok) {
|
|
9030
|
+
return { ok: false, code: "inference_endpoint_unresolved", message: `agent_start: ${projected.message}` };
|
|
9031
|
+
}
|
|
9032
|
+
if (projected.projection.needsPiModelsSync) {
|
|
9033
|
+
await syncPiModels().catch(() => {
|
|
9034
|
+
});
|
|
9035
|
+
}
|
|
9036
|
+
const { inference: _inference, ...withoutInference } = input;
|
|
9037
|
+
input = {
|
|
9038
|
+
...withoutInference,
|
|
9039
|
+
model: projected.projection.model,
|
|
9040
|
+
...projected.projection.route ? { route: projected.projection.route } : {},
|
|
9041
|
+
...projected.projection.auth ? { auth: projected.projection.auth } : {},
|
|
9042
|
+
...projected.projection.deferredTools !== void 0 ? { deferredTools: input.deferredTools ?? projected.projection.deferredTools } : {}
|
|
9043
|
+
};
|
|
9044
|
+
}
|
|
8726
9045
|
const presetBrowser = input.preset?.browser;
|
|
8727
9046
|
if (input.preset) {
|
|
8728
9047
|
const { preset, ...explicit } = input;
|
|
@@ -8741,6 +9060,8 @@ async function spawnAgentSession(deps2, input) {
|
|
|
8741
9060
|
skills: explicit.skills ?? preset.skills,
|
|
8742
9061
|
bundles: explicit.bundles ?? preset.bundles
|
|
8743
9062
|
};
|
|
9063
|
+
await touchUserPreset(preset.id).catch(() => {
|
|
9064
|
+
});
|
|
8744
9065
|
}
|
|
8745
9066
|
if (!input.access?.profileRef) {
|
|
8746
9067
|
const preset = await getDefaultHarnessPreset(input.harness ?? input.adapter);
|
|
@@ -9118,10 +9439,10 @@ async function spawnAgentSession(deps2, input) {
|
|
|
9118
9439
|
if (entry.transport !== "http" || typeof entry.ref !== "string") return entry;
|
|
9119
9440
|
const targetsDaemon = entry.ref === daemonMcpUrl || entry.ref.startsWith(daemonQ);
|
|
9120
9441
|
if (!targetsDaemon || entry.ref.includes("callerSessionId=")) return entry;
|
|
9121
|
-
const
|
|
9442
|
+
const sep6 = entry.ref.includes("?") ? "&" : "?";
|
|
9122
9443
|
return {
|
|
9123
9444
|
...entry,
|
|
9124
|
-
ref: `${entry.ref}${
|
|
9445
|
+
ref: `${entry.ref}${sep6}callerSessionId=${encodeURIComponent(mintedSessionId)}`
|
|
9125
9446
|
};
|
|
9126
9447
|
});
|
|
9127
9448
|
}
|
|
@@ -9493,8 +9814,8 @@ async function spawnAgentSession(deps2, input) {
|
|
|
9493
9814
|
}
|
|
9494
9815
|
let resolveClaim;
|
|
9495
9816
|
claims.set(key, {
|
|
9496
|
-
result: new Promise((
|
|
9497
|
-
resolveClaim =
|
|
9817
|
+
result: new Promise((resolve38) => {
|
|
9818
|
+
resolveClaim = resolve38;
|
|
9498
9819
|
}),
|
|
9499
9820
|
// An implicit claim expires sooner than an explicit one — see
|
|
9500
9821
|
// `IMPLICIT_SPAWN_CLAIM_WINDOW_MS`'s docblock.
|
|
@@ -9994,13 +10315,13 @@ async function isSharedDirtyCwd(cwd) {
|
|
|
9994
10315
|
const identity = resolveWorktreeIdentity(cwd);
|
|
9995
10316
|
if (identity?.worktreeId !== void 0) return false;
|
|
9996
10317
|
const { spawn: spawn8 } = await import('child_process');
|
|
9997
|
-
return await new Promise((
|
|
10318
|
+
return await new Promise((resolve38) => {
|
|
9998
10319
|
let stdout = "";
|
|
9999
10320
|
let settled = false;
|
|
10000
10321
|
const done = (v) => {
|
|
10001
10322
|
if (!settled) {
|
|
10002
10323
|
settled = true;
|
|
10003
|
-
|
|
10324
|
+
resolve38(v);
|
|
10004
10325
|
}
|
|
10005
10326
|
};
|
|
10006
10327
|
const child = spawn8("git", ["-C", cwd, "status", "--porcelain"], {
|
|
@@ -10015,15 +10336,15 @@ async function isSharedDirtyCwd(cwd) {
|
|
|
10015
10336
|
}
|
|
10016
10337
|
async function resolveMcpCredentialHeaders(mcpServers) {
|
|
10017
10338
|
if (!mcpServers || mcpServers.length === 0) return mcpServers;
|
|
10018
|
-
const { resolveMcpCredentialHeaders:
|
|
10019
|
-
if (!
|
|
10339
|
+
const { resolveMcpCredentialHeaders: resolve38 } = getMcpCredentialDeps();
|
|
10340
|
+
if (!resolve38) return mcpServers;
|
|
10020
10341
|
return Promise.all(
|
|
10021
10342
|
mcpServers.map(async (entry) => {
|
|
10022
10343
|
const ref = entry.credentialRef;
|
|
10023
10344
|
if (!ref) return entry;
|
|
10024
10345
|
let brokered;
|
|
10025
10346
|
try {
|
|
10026
|
-
brokered = await
|
|
10347
|
+
brokered = await resolve38({ credentialRef: ref });
|
|
10027
10348
|
} catch (err) {
|
|
10028
10349
|
console.warn(
|
|
10029
10350
|
`[agent_start] credentialRef resolution failed for "${entry.name}" (${ref}): ${err instanceof Error ? err.message : String(err)}`
|
|
@@ -10213,11 +10534,13 @@ async function bootSandboxAgentSession(opts) {
|
|
|
10213
10534
|
...boxCwd ? { cwd: boxCwd } : {},
|
|
10214
10535
|
...lifecyclePolicy.pauseAfterIdleMs !== void 0 ? { expiresAt: new Date(Date.now() + lifecyclePolicy.pauseAfterIdleMs).toISOString() } : {}
|
|
10215
10536
|
});
|
|
10537
|
+
const omitCwd = handle.omitCwdWhenImplicit === true && !opts.explicitCwd;
|
|
10216
10538
|
let remoteSessionId;
|
|
10539
|
+
let resolvedCwd = boxCwd;
|
|
10217
10540
|
try {
|
|
10218
10541
|
const remoteDesc = await host.start({
|
|
10219
10542
|
adapter: opts.adapter,
|
|
10220
|
-
cwd: boxCwd,
|
|
10543
|
+
...omitCwd ? {} : { cwd: boxCwd },
|
|
10221
10544
|
...opts.mcpServers ? { mcpServers: toMcpServerMounts(opts.mcpServers) } : {},
|
|
10222
10545
|
...opts.model ? { model: opts.model } : {},
|
|
10223
10546
|
...opts.route ? { route: opts.route } : {},
|
|
@@ -10226,6 +10549,7 @@ async function bootSandboxAgentSession(opts) {
|
|
|
10226
10549
|
...opts.auth ? { auth: opts.auth } : {}
|
|
10227
10550
|
});
|
|
10228
10551
|
remoteSessionId = remoteDesc.id;
|
|
10552
|
+
if (omitCwd && remoteDesc.cwd) resolvedCwd = remoteDesc.cwd;
|
|
10229
10553
|
} catch (err) {
|
|
10230
10554
|
await host.stop().catch(() => void 0);
|
|
10231
10555
|
recordSandboxState(host.sandboxId, "stopped");
|
|
@@ -10259,7 +10583,7 @@ async function bootSandboxAgentSession(opts) {
|
|
|
10259
10583
|
}),
|
|
10260
10584
|
commandPreview: `sandbox:${providerSlug} \u2192 ${opts.adapter}`,
|
|
10261
10585
|
sandboxId: host.sandboxId,
|
|
10262
|
-
cwd:
|
|
10586
|
+
cwd: resolvedCwd,
|
|
10263
10587
|
provider: providerSlug,
|
|
10264
10588
|
sandboxTeardown: lifecyclePolicy.teardown,
|
|
10265
10589
|
...host.ports && Object.keys(host.ports).length > 0 ? { sandboxPorts: host.ports } : {},
|
|
@@ -10312,10 +10636,10 @@ function markFailedBootLedger(opts) {
|
|
|
10312
10636
|
}
|
|
10313
10637
|
}
|
|
10314
10638
|
async function resolveSandboxSecret(slug) {
|
|
10315
|
-
const { resolveSandboxSecret:
|
|
10316
|
-
if (!
|
|
10639
|
+
const { resolveSandboxSecret: resolve38 } = getMcpCredentialDeps();
|
|
10640
|
+
if (!resolve38) return null;
|
|
10317
10641
|
try {
|
|
10318
|
-
return await
|
|
10642
|
+
return await resolve38(slug);
|
|
10319
10643
|
} catch (err) {
|
|
10320
10644
|
console.warn(
|
|
10321
10645
|
`[agent_start] sandbox secret resolution failed for "${slug}": ${err instanceof Error ? err.message : String(err)}`
|
|
@@ -10348,6 +10672,7 @@ var init_session_spawn = __esm({
|
|
|
10348
10672
|
init_catalog_models();
|
|
10349
10673
|
init_context_continuity();
|
|
10350
10674
|
init_canonical_posture();
|
|
10675
|
+
init_user_presets();
|
|
10351
10676
|
init_harness_preset_store();
|
|
10352
10677
|
init_browser_mount();
|
|
10353
10678
|
init_role();
|
|
@@ -10356,6 +10681,7 @@ var init_session_spawn = __esm({
|
|
|
10356
10681
|
init_agents_md();
|
|
10357
10682
|
init_workspace_rules();
|
|
10358
10683
|
init_session_title();
|
|
10684
|
+
init_inference_binding();
|
|
10359
10685
|
init_mcp_credential_deps();
|
|
10360
10686
|
init_sandbox_agent_session_proxy();
|
|
10361
10687
|
init_sandbox_ledger();
|
|
@@ -10538,6 +10864,16 @@ var init_agent_start_schema = __esm({
|
|
|
10538
10864
|
route: jsonTolerant(
|
|
10539
10865
|
z.object({ gateway: z.string().min(1), baseUrl: z.string().url().optional() })
|
|
10540
10866
|
).optional().describe("Billing route/gateway. Independent of `model` and `access`."),
|
|
10867
|
+
inference: jsonTolerant(
|
|
10868
|
+
z.object({
|
|
10869
|
+
endpoint: z.string().min(1).optional().describe('A registered local/LAN inference endpoint id (`agentproto llm endpoints list`), or "<id>@<device>" for a paired device\'s own shared endpoint.'),
|
|
10870
|
+
model: z.string().min(1).optional().describe('A bare model id (paired with `endpoint`), or the shorthand "<model>@<device|endpoint>" when `endpoint` is omitted.'),
|
|
10871
|
+
force: mcpBool.optional().describe("Skip the fit-check refusal on a clear miss (the check still runs and still warns)."),
|
|
10872
|
+
headroomPct: z.number().min(0).max(95).optional().describe("Headroom percentage held back from the endpoint's loaded ctx for the fit check. Default 25.")
|
|
10873
|
+
})
|
|
10874
|
+
).optional().describe(
|
|
10875
|
+
`Bind this spawn to a local/LAN inference endpoint instead of a cloud model: \`{endpoint}\`, \`{endpoint, model}\`, or the shorthand \`{model: "<model>@<device|endpoint>"}\`. Runs a fit check (the harness's known first-request size vs the endpoint's loaded ctx) BEFORE spawning and refuses with an actionable error on a clear miss (\`force\` overrides). Defaults \`adapter\`/\`harness\` to "pi" when neither is set. Mutually exclusive with \`model\`/\`route\`/\`access\` \u2014 set those directly instead of \`inference\`, never both. ${help("inference")}`
|
|
10876
|
+
),
|
|
10541
10877
|
access: jsonTolerant(z.object({ profileRef: z.string().min(1).optional() })).optional().describe("Named auth profile to bill at initial spawn; resolved from the local keychain."),
|
|
10542
10878
|
posture: z.string().optional().describe("Canonical agent posture (plan, bypass, accept-edits, read-only) or native harness mode."),
|
|
10543
10879
|
contextProfile: z.string().optional().describe("Context intake profile (for example full or lean)."),
|
|
@@ -10676,83 +11012,6 @@ var init_agent_start_schema = __esm({
|
|
|
10676
11012
|
detachedAgentStartSchema = agentStartInputSchema.omit({ wait: true });
|
|
10677
11013
|
}
|
|
10678
11014
|
});
|
|
10679
|
-
function emptyFile4() {
|
|
10680
|
-
return { version: 1, presets: [] };
|
|
10681
|
-
}
|
|
10682
|
-
function userPresetsPath() {
|
|
10683
|
-
return resolve(homedir(), ".agentproto", "presets.json");
|
|
10684
|
-
}
|
|
10685
|
-
async function loadUserPresets() {
|
|
10686
|
-
try {
|
|
10687
|
-
return userPresetsFileSchema.parse(JSON.parse(await readFile(userPresetsPath(), "utf8")));
|
|
10688
|
-
} catch {
|
|
10689
|
-
return emptyFile4();
|
|
10690
|
-
}
|
|
10691
|
-
}
|
|
10692
|
-
async function writeUserPresets(file) {
|
|
10693
|
-
const dir = join(homedir(), ".agentproto");
|
|
10694
|
-
await mkdir(dir, { recursive: true });
|
|
10695
|
-
await writeFile(userPresetsPath(), JSON.stringify(file, null, 2) + "\n", {
|
|
10696
|
-
encoding: "utf8",
|
|
10697
|
-
mode: 384
|
|
10698
|
-
});
|
|
10699
|
-
}
|
|
10700
|
-
async function listUserPresets() {
|
|
10701
|
-
return (await loadUserPresets()).presets;
|
|
10702
|
-
}
|
|
10703
|
-
async function getUserPreset(id) {
|
|
10704
|
-
return (await loadUserPresets()).presets.find((preset) => preset.id === id);
|
|
10705
|
-
}
|
|
10706
|
-
async function saveUserPreset(preset) {
|
|
10707
|
-
const validated = userPresetSchema.parse(preset);
|
|
10708
|
-
const file = await loadUserPresets();
|
|
10709
|
-
const index = file.presets.findIndex((existing) => existing.id === validated.id);
|
|
10710
|
-
if (index === -1) file.presets.push(validated);
|
|
10711
|
-
else file.presets[index] = validated;
|
|
10712
|
-
await writeUserPresets(file);
|
|
10713
|
-
}
|
|
10714
|
-
async function deleteUserPreset(id) {
|
|
10715
|
-
const file = await loadUserPresets();
|
|
10716
|
-
const index = file.presets.findIndex((preset) => preset.id === id);
|
|
10717
|
-
if (index === -1) return false;
|
|
10718
|
-
file.presets.splice(index, 1);
|
|
10719
|
-
await writeUserPresets(file);
|
|
10720
|
-
return true;
|
|
10721
|
-
}
|
|
10722
|
-
var effortSchema, postureSchema, routeSchema, userPresetSchema, userPresetsFileSchema;
|
|
10723
|
-
var init_user_presets = __esm({
|
|
10724
|
-
"src/user-presets.ts"() {
|
|
10725
|
-
effortSchema = z.enum(["low", "medium", "high", "xhigh", "max", "ultracode"]);
|
|
10726
|
-
postureSchema = z.union([
|
|
10727
|
-
z.enum(["default", "plan", "accept-edits", "bypass", "read-only"]),
|
|
10728
|
-
z.object({ harnessModeId: z.string().min(1) })
|
|
10729
|
-
]);
|
|
10730
|
-
routeSchema = z.object({
|
|
10731
|
-
gateway: z.string().min(1),
|
|
10732
|
-
baseUrl: z.string().url().optional()
|
|
10733
|
-
});
|
|
10734
|
-
userPresetSchema = z.object({
|
|
10735
|
-
id: z.string().regex(/^[a-z0-9][a-z0-9-]*$/),
|
|
10736
|
-
label: z.string().min(1),
|
|
10737
|
-
adapter: z.string().min(1).optional(),
|
|
10738
|
-
harness: z.string().min(1).optional(),
|
|
10739
|
-
model: z.string().min(1).optional(),
|
|
10740
|
-
route: routeSchema.optional(),
|
|
10741
|
-
access: z.object({ profileRef: z.string().min(1).optional() }).optional(),
|
|
10742
|
-
posture: postureSchema.optional(),
|
|
10743
|
-
effort: effortSchema.optional(),
|
|
10744
|
-
contextProfile: z.string().min(1).optional(),
|
|
10745
|
-
cwd: z.string().min(1).optional(),
|
|
10746
|
-
skills: z.array(z.string().min(1)).optional(),
|
|
10747
|
-
bundles: z.array(z.string().min(1)).optional(),
|
|
10748
|
-
browser: z.union([z.literal("headless"), z.literal(false)]).optional()
|
|
10749
|
-
});
|
|
10750
|
-
userPresetsFileSchema = z.object({
|
|
10751
|
-
version: z.literal(1),
|
|
10752
|
-
presets: z.array(userPresetSchema)
|
|
10753
|
-
});
|
|
10754
|
-
}
|
|
10755
|
-
});
|
|
10756
11015
|
function labelOf(model) {
|
|
10757
11016
|
switch (model.kind) {
|
|
10758
11017
|
case "image":
|
|
@@ -10893,8 +11152,8 @@ async function newestSourceMtime(cwd, patterns) {
|
|
|
10893
11152
|
function appUiBuildLogPath(appDir) {
|
|
10894
11153
|
return join(appDir, ".agentproto", "ui-build.log");
|
|
10895
11154
|
}
|
|
10896
|
-
function tailLines(
|
|
10897
|
-
const lines =
|
|
11155
|
+
function tailLines(text13, n) {
|
|
11156
|
+
const lines = text13.split("\n");
|
|
10898
11157
|
return lines.slice(-n).join("\n");
|
|
10899
11158
|
}
|
|
10900
11159
|
async function warnIfNotSingleFile(uiPath) {
|
|
@@ -10992,8 +11251,8 @@ async function ensureAppUiBuilt(input) {
|
|
|
10992
11251
|
}
|
|
10993
11252
|
async function readLogTailSafe(dir, n) {
|
|
10994
11253
|
try {
|
|
10995
|
-
const
|
|
10996
|
-
const lines =
|
|
11254
|
+
const text13 = await readFile(appUiBuildLogPath(dir), "utf8");
|
|
11255
|
+
const lines = text13.split("\n").filter((line) => line.length > 0);
|
|
10997
11256
|
const tail = lines.slice(-n).join("\n");
|
|
10998
11257
|
return tail.length > 0 ? tail : void 0;
|
|
10999
11258
|
} catch {
|
|
@@ -11003,8 +11262,8 @@ async function readLogTailSafe(dir, n) {
|
|
|
11003
11262
|
async function resolveAppUiBuildState(input) {
|
|
11004
11263
|
const { dir, uiPath } = input;
|
|
11005
11264
|
const pending = peekInFlightBuild(uiPath) ?? ensureAppUiBuilt(input);
|
|
11006
|
-
const timeout = new Promise((
|
|
11007
|
-
const timer = setTimeout(() =>
|
|
11265
|
+
const timeout = new Promise((resolve38) => {
|
|
11266
|
+
const timer = setTimeout(() => resolve38(RESOLVE_TIMEOUT), RESOLVE_FAST_PATH_MS);
|
|
11008
11267
|
timer.unref();
|
|
11009
11268
|
});
|
|
11010
11269
|
const raced = await Promise.race([pending, timeout]);
|
|
@@ -11039,8 +11298,8 @@ var init_app_ui_build = __esm({
|
|
|
11039
11298
|
});
|
|
11040
11299
|
|
|
11041
11300
|
// src/app-ui-placeholder.ts
|
|
11042
|
-
function escapeHtml(
|
|
11043
|
-
return
|
|
11301
|
+
function escapeHtml(text13) {
|
|
11302
|
+
return text13.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
11044
11303
|
}
|
|
11045
11304
|
function pageShell(opts) {
|
|
11046
11305
|
return `<!DOCTYPE html>
|
|
@@ -11513,11 +11772,11 @@ function sanitizeLabel(label) {
|
|
|
11513
11772
|
function attr(value) {
|
|
11514
11773
|
return value.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">");
|
|
11515
11774
|
}
|
|
11516
|
-
function escapeMessageBody(
|
|
11517
|
-
return
|
|
11775
|
+
function escapeMessageBody(text13) {
|
|
11776
|
+
return text13.replace(BODY_SENTINEL_RE, (_m, slash, name) => `<${slash}${name}`);
|
|
11518
11777
|
}
|
|
11519
|
-
function escapeHumanPrompt(
|
|
11520
|
-
return
|
|
11778
|
+
function escapeHumanPrompt(text13) {
|
|
11779
|
+
return text13.replace(HUMAN_SENTINEL_RE, (_m, lead, rest) => `${lead}<${rest}`);
|
|
11521
11780
|
}
|
|
11522
11781
|
function renderSessionMessage(msg) {
|
|
11523
11782
|
const attrs = [`id="${attr(msg.id)}"`, `from="${msg.from.relation}"`];
|
|
@@ -11772,8 +12031,8 @@ var init_message_tools = __esm({
|
|
|
11772
12031
|
ok = (body) => ({
|
|
11773
12032
|
content: [{ type: "text", text: JSON.stringify({ ok: true, ...body }) }]
|
|
11774
12033
|
});
|
|
11775
|
-
fail = (code,
|
|
11776
|
-
content: [{ type: "text", text: JSON.stringify({ ok: false, error: code, message:
|
|
12034
|
+
fail = (code, text13) => ({
|
|
12035
|
+
content: [{ type: "text", text: JSON.stringify({ ok: false, error: code, message: text13 }) }],
|
|
11777
12036
|
isError: true
|
|
11778
12037
|
});
|
|
11779
12038
|
kindField = z.enum(MESSAGE_KINDS);
|
|
@@ -11879,7 +12138,7 @@ function registerAgentTools(server, opts) {
|
|
|
11879
12138
|
isError: true
|
|
11880
12139
|
};
|
|
11881
12140
|
}
|
|
11882
|
-
const adapter = input.adapter ?? input.harness ?? preset?.adapter ?? preset?.harness;
|
|
12141
|
+
const adapter = input.adapter ?? input.harness ?? preset?.adapter ?? preset?.harness ?? (input.inference ? "pi" : void 0);
|
|
11883
12142
|
if (!adapter) {
|
|
11884
12143
|
return {
|
|
11885
12144
|
content: [{ type: "text", text: "agent_start: adapter is required unless the selected preset provides one." }],
|
|
@@ -11950,13 +12209,13 @@ function registerAgentTools(server, opts) {
|
|
|
11950
12209
|
content: [{ type: "text", text: JSON.stringify(body) }]
|
|
11951
12210
|
};
|
|
11952
12211
|
}
|
|
11953
|
-
const
|
|
12212
|
+
const text13 = result.code === "orchestrator_max_depth_exceeded" || result.code === "orchestrator_child_quota_exceeded" || result.code === "role_spawn_denied" ? JSON.stringify(
|
|
11954
12213
|
{ error: result.code, message: result.message, ...result.details },
|
|
11955
12214
|
null,
|
|
11956
12215
|
2
|
|
11957
12216
|
) : result.message;
|
|
11958
12217
|
return {
|
|
11959
|
-
content: [{ type: "text", text:
|
|
12218
|
+
content: [{ type: "text", text: text13 }],
|
|
11960
12219
|
isError: true
|
|
11961
12220
|
};
|
|
11962
12221
|
}
|
|
@@ -12050,8 +12309,8 @@ function registerAgentTools(server, opts) {
|
|
|
12050
12309
|
)
|
|
12051
12310
|
},
|
|
12052
12311
|
async (input) => {
|
|
12053
|
-
const fail3 = (
|
|
12054
|
-
content: [{ type: "text", text:
|
|
12312
|
+
const fail3 = (text13) => ({
|
|
12313
|
+
content: [{ type: "text", text: text13 }],
|
|
12055
12314
|
isError: true
|
|
12056
12315
|
});
|
|
12057
12316
|
const selfId = callerScope?.ownerSessionId ?? callerSessionId;
|
|
@@ -12812,15 +13071,15 @@ function resolveGroup(input) {
|
|
|
12812
13071
|
}
|
|
12813
13072
|
return group;
|
|
12814
13073
|
}
|
|
12815
|
-
function truncateForContext(
|
|
12816
|
-
return
|
|
12817
|
-
${
|
|
13074
|
+
function truncateForContext(text13) {
|
|
13075
|
+
return text13.length > GATE_OUTPUT_CONTEXT_CHARS ? `\u2026(truncated)\u2026
|
|
13076
|
+
${text13.slice(-GATE_OUTPUT_CONTEXT_CHARS)}` : text13;
|
|
12818
13077
|
}
|
|
12819
|
-
function parseVerdict(
|
|
13078
|
+
function parseVerdict(text13) {
|
|
12820
13079
|
const re = /VERDICT:\s*(PASS|FAIL)/gi;
|
|
12821
13080
|
let last = null;
|
|
12822
13081
|
let m;
|
|
12823
|
-
while ((m = re.exec(
|
|
13082
|
+
while ((m = re.exec(text13)) !== null) {
|
|
12824
13083
|
last = m[1].toUpperCase();
|
|
12825
13084
|
}
|
|
12826
13085
|
return last;
|
|
@@ -12828,15 +13087,15 @@ function parseVerdict(text12) {
|
|
|
12828
13087
|
function coerceSeverity(v) {
|
|
12829
13088
|
return typeof v === "string" && KNOWN_SEVERITIES.has(v) ? v : "medium";
|
|
12830
13089
|
}
|
|
12831
|
-
function parseJsonVerdict(
|
|
13090
|
+
function parseJsonVerdict(text13) {
|
|
12832
13091
|
const candidates = [];
|
|
12833
13092
|
const fenceRe = /```json\s*([\s\S]*?)```/gi;
|
|
12834
13093
|
let fm;
|
|
12835
|
-
while ((fm = fenceRe.exec(
|
|
13094
|
+
while ((fm = fenceRe.exec(text13)) !== null) candidates.push(fm[1]);
|
|
12836
13095
|
if (candidates.length === 0) {
|
|
12837
13096
|
const bareRe = /\{[^{}]*"decision"[^{}]*\}/gi;
|
|
12838
13097
|
let bm;
|
|
12839
|
-
while ((bm = bareRe.exec(
|
|
13098
|
+
while ((bm = bareRe.exec(text13)) !== null) candidates.push(bm[0]);
|
|
12840
13099
|
}
|
|
12841
13100
|
for (let i = candidates.length - 1; i >= 0; i--) {
|
|
12842
13101
|
let parsed;
|
|
@@ -14237,8 +14496,8 @@ function createTerminalTranscriptWriter(opts) {
|
|
|
14237
14496
|
const stream = streams.get(sessionId);
|
|
14238
14497
|
if (!stream) return Promise.resolve();
|
|
14239
14498
|
streams.delete(sessionId);
|
|
14240
|
-
return new Promise((
|
|
14241
|
-
stream.end(() =>
|
|
14499
|
+
return new Promise((resolve38) => {
|
|
14500
|
+
stream.end(() => resolve38());
|
|
14242
14501
|
});
|
|
14243
14502
|
},
|
|
14244
14503
|
closeAll() {
|
|
@@ -14247,8 +14506,8 @@ function createTerminalTranscriptWriter(opts) {
|
|
|
14247
14506
|
const stream = streams.get(sessionId);
|
|
14248
14507
|
if (!stream) continue;
|
|
14249
14508
|
closings.push(
|
|
14250
|
-
new Promise((
|
|
14251
|
-
stream.end(() =>
|
|
14509
|
+
new Promise((resolve38) => {
|
|
14510
|
+
stream.end(() => resolve38());
|
|
14252
14511
|
})
|
|
14253
14512
|
);
|
|
14254
14513
|
}
|
|
@@ -14316,10 +14575,10 @@ var init_context_window = __esm({
|
|
|
14316
14575
|
"src/context-window.ts"() {
|
|
14317
14576
|
}
|
|
14318
14577
|
});
|
|
14319
|
-
function truncSection(
|
|
14320
|
-
if (
|
|
14321
|
-
return `${
|
|
14322
|
-
\u2026 [${
|
|
14578
|
+
function truncSection(text13) {
|
|
14579
|
+
if (text13.length <= SECTION_CHAR_CAP) return text13;
|
|
14580
|
+
return `${text13.slice(0, SECTION_CHAR_CAP)}
|
|
14581
|
+
\u2026 [${text13.length - SECTION_CHAR_CAP} chars truncated]`;
|
|
14323
14582
|
}
|
|
14324
14583
|
function approxMessageLen2(m) {
|
|
14325
14584
|
const toolLen = m.toolCalls?.reduce(
|
|
@@ -14356,14 +14615,14 @@ async function buildRecentDigest(sessionId) {
|
|
|
14356
14615
|
}
|
|
14357
14616
|
async function captureGitStatus(cwd) {
|
|
14358
14617
|
if (!cwd) return void 0;
|
|
14359
|
-
return new Promise((
|
|
14618
|
+
return new Promise((resolve38) => {
|
|
14360
14619
|
execFile("git", ["status", "--porcelain"], { cwd }, (err, stdout) => {
|
|
14361
14620
|
if (err) {
|
|
14362
|
-
|
|
14621
|
+
resolve38(void 0);
|
|
14363
14622
|
return;
|
|
14364
14623
|
}
|
|
14365
14624
|
const trimmed = stdout.trim();
|
|
14366
|
-
|
|
14625
|
+
resolve38(trimmed || "(working tree clean)");
|
|
14367
14626
|
});
|
|
14368
14627
|
});
|
|
14369
14628
|
}
|
|
@@ -14594,9 +14853,9 @@ var init_session_continue_fresh = __esm({
|
|
|
14594
14853
|
init_session_spawn();
|
|
14595
14854
|
}
|
|
14596
14855
|
});
|
|
14597
|
-
function trimOutcomeText(
|
|
14598
|
-
if (!
|
|
14599
|
-
const flat =
|
|
14856
|
+
function trimOutcomeText(text13, max, keep) {
|
|
14857
|
+
if (!text13) return void 0;
|
|
14858
|
+
const flat = text13.replace(/\s+/g, " ").trim();
|
|
14600
14859
|
if (!flat) return void 0;
|
|
14601
14860
|
if (flat.length <= max) return flat;
|
|
14602
14861
|
return keep === "tail" ? `\u2026${flat.slice(flat.length - (max - 1))}` : `${flat.slice(0, max - 1)}\u2026`;
|
|
@@ -14813,17 +15072,17 @@ function adapterConfigDirFor(sessionId) {
|
|
|
14813
15072
|
return resolve(homedir(), ".agentproto", "adapter-config", sessionId);
|
|
14814
15073
|
}
|
|
14815
15074
|
function previewPrompt(message, maxLength = 80) {
|
|
14816
|
-
let
|
|
15075
|
+
let text13;
|
|
14817
15076
|
if (typeof message === "string") {
|
|
14818
|
-
|
|
15077
|
+
text13 = message;
|
|
14819
15078
|
} else if (Array.isArray(message)) {
|
|
14820
|
-
|
|
15079
|
+
text13 = message.map((block) => textOfContentBlock(block)).filter(Boolean).join("\n");
|
|
14821
15080
|
} else if (message !== null && typeof message === "object") {
|
|
14822
|
-
|
|
15081
|
+
text13 = textOfContentBlock(message);
|
|
14823
15082
|
} else {
|
|
14824
|
-
|
|
15083
|
+
text13 = "";
|
|
14825
15084
|
}
|
|
14826
|
-
const single =
|
|
15085
|
+
const single = text13.replace(/\s+/g, " ").trim();
|
|
14827
15086
|
if (!single) return "[non-text content]";
|
|
14828
15087
|
return single.length > maxLength ? `${single.slice(0, maxLength - 1)}\u2026` : single;
|
|
14829
15088
|
}
|
|
@@ -14948,18 +15207,18 @@ function isStrictPrefixOf(candidate, full) {
|
|
|
14948
15207
|
return candidate.length > 0 && candidate.length < full.length && full.startsWith(candidate);
|
|
14949
15208
|
}
|
|
14950
15209
|
function scanBracketedPaste(state, chunk) {
|
|
14951
|
-
const
|
|
15210
|
+
const text13 = state.carry + chunk.toString("latin1");
|
|
14952
15211
|
let mode = state.mode;
|
|
14953
15212
|
let matchEnd = 0;
|
|
14954
15213
|
let i = 0;
|
|
14955
|
-
while (i <
|
|
14956
|
-
if (
|
|
15214
|
+
while (i < text13.length) {
|
|
15215
|
+
if (text13.startsWith(BRACKETED_PASTE_ON, i)) {
|
|
14957
15216
|
mode = "on";
|
|
14958
15217
|
i += BRACKETED_PASTE_ON.length;
|
|
14959
15218
|
matchEnd = i;
|
|
14960
15219
|
continue;
|
|
14961
15220
|
}
|
|
14962
|
-
if (
|
|
15221
|
+
if (text13.startsWith(BRACKETED_PASTE_OFF, i)) {
|
|
14963
15222
|
mode = "off";
|
|
14964
15223
|
i += BRACKETED_PASTE_OFF.length;
|
|
14965
15224
|
matchEnd = i;
|
|
@@ -14967,7 +15226,7 @@ function scanBracketedPaste(state, chunk) {
|
|
|
14967
15226
|
}
|
|
14968
15227
|
i++;
|
|
14969
15228
|
}
|
|
14970
|
-
const tail =
|
|
15229
|
+
const tail = text13.slice(matchEnd);
|
|
14971
15230
|
const escIdx = tail.lastIndexOf("\x1B");
|
|
14972
15231
|
let carry = "";
|
|
14973
15232
|
if (escIdx !== -1) {
|
|
@@ -14978,15 +15237,15 @@ function scanBracketedPaste(state, chunk) {
|
|
|
14978
15237
|
}
|
|
14979
15238
|
return { mode, carry };
|
|
14980
15239
|
}
|
|
14981
|
-
function shouldWrapBracketedPaste(mode,
|
|
14982
|
-
return mode === "on" &&
|
|
15240
|
+
function shouldWrapBracketedPaste(mode, text13) {
|
|
15241
|
+
return mode === "on" && text13.includes("\n");
|
|
14983
15242
|
}
|
|
14984
|
-
function wrapBracketedPaste(
|
|
14985
|
-
return `\x1B[200~${
|
|
15243
|
+
function wrapBracketedPaste(text13) {
|
|
15244
|
+
return `\x1B[200~${text13}\x1B[201~`;
|
|
14986
15245
|
}
|
|
14987
|
-
function applyBracketedPasteWrap(registry, sessionId,
|
|
15246
|
+
function applyBracketedPasteWrap(registry, sessionId, text13) {
|
|
14988
15247
|
const mode = registry.getBracketedPasteMode(sessionId);
|
|
14989
|
-
return shouldWrapBracketedPaste(mode,
|
|
15248
|
+
return shouldWrapBracketedPaste(mode, text13) ? wrapBracketedPaste(text13) : text13;
|
|
14990
15249
|
}
|
|
14991
15250
|
function markHeldId(map, slug, id) {
|
|
14992
15251
|
let set = map.get(slug);
|
|
@@ -15087,8 +15346,8 @@ function stripAnsiCodes(s) {
|
|
|
15087
15346
|
}
|
|
15088
15347
|
function terminalOutputTail(rt, maxChars = 500) {
|
|
15089
15348
|
if (rt.recentBytes.length === 0) return "";
|
|
15090
|
-
const
|
|
15091
|
-
const plain = stripAnsiCodes(
|
|
15349
|
+
const text13 = Buffer.concat(rt.recentBytes, rt.recentBytesSize).toString("utf8");
|
|
15350
|
+
const plain = stripAnsiCodes(text13).replace(/\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)/g, "").replace(/\x1b[()][A-Za-z0-9]/g, "").replace(/\x1b[0-9A-Za-z=>]/g, "").replace(/[\x00-\x09\x0b-\x1f]/g, "").trim();
|
|
15092
15351
|
if (!plain) return "";
|
|
15093
15352
|
return plain.length > maxChars ? plain.slice(-maxChars) : plain;
|
|
15094
15353
|
}
|
|
@@ -15342,13 +15601,13 @@ function createSessionsRegistry(opts) {
|
|
|
15342
15601
|
if (!id) return;
|
|
15343
15602
|
const options = normalizePermissionOptions(evt.options);
|
|
15344
15603
|
const toolName = evt.toolName;
|
|
15345
|
-
const
|
|
15604
|
+
const text13 = evt.text ?? (toolName ? `Allow "${toolName}"?` : "The agent is requesting permission.");
|
|
15346
15605
|
const pending = {
|
|
15347
15606
|
id,
|
|
15348
15607
|
sessionId: rt.desc.id,
|
|
15349
15608
|
toolCallId: id,
|
|
15350
15609
|
...toolName ? { toolName } : {},
|
|
15351
|
-
text:
|
|
15610
|
+
text: text13,
|
|
15352
15611
|
options,
|
|
15353
15612
|
requestedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
15354
15613
|
...evt.rawInput !== void 0 ? { rawInput: evt.rawInput } : {},
|
|
@@ -15363,7 +15622,7 @@ function createSessionsRegistry(opts) {
|
|
|
15363
15622
|
sessionId: rt.desc.id,
|
|
15364
15623
|
permissionId: id,
|
|
15365
15624
|
...toolName ? { toolName } : {},
|
|
15366
|
-
text:
|
|
15625
|
+
text: text13,
|
|
15367
15626
|
...rt.desc.label ? { label: rt.desc.label } : {},
|
|
15368
15627
|
ts: pending.requestedAt
|
|
15369
15628
|
});
|
|
@@ -15688,8 +15947,8 @@ function createSessionsRegistry(opts) {
|
|
|
15688
15947
|
}
|
|
15689
15948
|
};
|
|
15690
15949
|
const sniffConversationTerminalChunk = (rt, chunk) => {
|
|
15691
|
-
const
|
|
15692
|
-
const lines =
|
|
15950
|
+
const text13 = (rt.sniffCarry ?? "") + chunk.toString("utf8");
|
|
15951
|
+
const lines = text13.split(/\r?\n|\r/);
|
|
15693
15952
|
rt.sniffCarry = (lines.pop() ?? "").slice(-4096);
|
|
15694
15953
|
for (const line of lines) {
|
|
15695
15954
|
if (line) sniffResumeHints(rt, line);
|
|
@@ -15764,8 +16023,8 @@ function createSessionsRegistry(opts) {
|
|
|
15764
16023
|
rt.activeToolCalls?.clear();
|
|
15765
16024
|
if (evt.text) {
|
|
15766
16025
|
const prior = rt.lastAssistantTextSealed ? "" : rt.lastAssistantText ?? "";
|
|
15767
|
-
const
|
|
15768
|
-
rt.lastAssistantText =
|
|
16026
|
+
const text13 = prior + evt.text;
|
|
16027
|
+
rt.lastAssistantText = text13.length > LAST_ASSISTANT_TEXT_CAP ? text13.slice(text13.length - LAST_ASSISTANT_TEXT_CAP) : text13;
|
|
15769
16028
|
rt.lastAssistantTextSealed = false;
|
|
15770
16029
|
const combined = rt.textBuf + evt.text;
|
|
15771
16030
|
const lines = combined.split(/\r?\n/);
|
|
@@ -16006,11 +16265,11 @@ function createSessionsRegistry(opts) {
|
|
|
16006
16265
|
};
|
|
16007
16266
|
const waitForTurnSettled = (rt, id, caller) => {
|
|
16008
16267
|
if (!rt.busy) return Promise.resolve();
|
|
16009
|
-
return new Promise((
|
|
16268
|
+
return new Promise((resolve38, reject) => {
|
|
16010
16269
|
const onBusy = (busy) => {
|
|
16011
16270
|
if (busy) return;
|
|
16012
16271
|
cleanup();
|
|
16013
|
-
|
|
16272
|
+
resolve38();
|
|
16014
16273
|
};
|
|
16015
16274
|
const cleanup = () => {
|
|
16016
16275
|
clearTimeout(timer);
|
|
@@ -16472,8 +16731,15 @@ function createSessionsRegistry(opts) {
|
|
|
16472
16731
|
}
|
|
16473
16732
|
setInbox(rt, next);
|
|
16474
16733
|
};
|
|
16734
|
+
const rememberReceived = (rt, msgs) => {
|
|
16735
|
+
if (!msgs.length) return;
|
|
16736
|
+
const ids = new Set(msgs.map((m) => m.id));
|
|
16737
|
+
const next = [...(rt.recentlyReceived ?? []).filter((m) => !ids.has(m.id)), ...msgs];
|
|
16738
|
+
rt.recentlyReceived = next.length > INBOX_CAP ? next.slice(-INBOX_CAP) : next;
|
|
16739
|
+
};
|
|
16475
16740
|
const removeFromInbox = (rt, ids) => {
|
|
16476
16741
|
if (!rt.desc.inbox?.some((m) => ids.has(m.id))) return;
|
|
16742
|
+
rememberReceived(rt, rt.desc.inbox.filter((m) => ids.has(m.id)));
|
|
16477
16743
|
setInbox(rt, rt.desc.inbox.filter((m) => !ids.has(m.id)));
|
|
16478
16744
|
};
|
|
16479
16745
|
const dropQueuedEnvelopes = (rt, ids) => {
|
|
@@ -16529,6 +16795,7 @@ ${content}`;
|
|
|
16529
16795
|
}
|
|
16530
16796
|
recordSent(stamped);
|
|
16531
16797
|
transcriptWriter.recordSessionMessage?.(rt.desc.id, stamped);
|
|
16798
|
+
rememberReceived(rt, [stamped]);
|
|
16532
16799
|
emitSessionMessage(stamped);
|
|
16533
16800
|
appendLine(rt, `[message] ${stamped.id} from ${stamped.from.relation} steered into the running turn`, "stdout");
|
|
16534
16801
|
return true;
|
|
@@ -17174,8 +17441,8 @@ ${message}`;
|
|
|
17174
17441
|
};
|
|
17175
17442
|
const wireOutputStreams = (rt) => {
|
|
17176
17443
|
const onChunk = (stream) => (chunk) => {
|
|
17177
|
-
const
|
|
17178
|
-
for (const line of
|
|
17444
|
+
const text13 = typeof chunk === "string" ? chunk : chunk.toString("utf8");
|
|
17445
|
+
for (const line of text13.split(/\r?\n/)) {
|
|
17179
17446
|
if (line.length > 0) appendLine(rt, line, stream);
|
|
17180
17447
|
}
|
|
17181
17448
|
};
|
|
@@ -18080,6 +18347,8 @@ ${message}`;
|
|
|
18080
18347
|
if (!rt) return void 0;
|
|
18081
18348
|
const inInbox = rt.desc.inbox?.find((m) => m.id === messageId);
|
|
18082
18349
|
if (inInbox) return inInbox;
|
|
18350
|
+
const recent = rt.recentlyReceived?.find((m) => m.id === messageId);
|
|
18351
|
+
if (recent) return recent;
|
|
18083
18352
|
let raw;
|
|
18084
18353
|
try {
|
|
18085
18354
|
raw = readFileSync(sessionEventsPath(id, transcriptBaseDir), "utf8");
|
|
@@ -18769,11 +19038,11 @@ ${message}`;
|
|
|
18769
19038
|
transcriptWriter.recordEvent(rt.desc.id, { kind: "notice", text: message });
|
|
18770
19039
|
return true;
|
|
18771
19040
|
},
|
|
18772
|
-
recordNotice(id,
|
|
19041
|
+
recordNotice(id, text13) {
|
|
18773
19042
|
const rt = sessions.get(id);
|
|
18774
19043
|
if (!rt || rt.desc.kind !== "agent-cli") return false;
|
|
18775
|
-
appendLine(rt,
|
|
18776
|
-
transcriptWriter.recordEvent(rt.desc.id, { kind: "notice", text:
|
|
19044
|
+
appendLine(rt, text13, "stdout");
|
|
19045
|
+
transcriptWriter.recordEvent(rt.desc.id, { kind: "notice", text: text13 });
|
|
18777
19046
|
return true;
|
|
18778
19047
|
},
|
|
18779
19048
|
archiveSession(id) {
|
|
@@ -19293,14 +19562,14 @@ var init_pr_provenance_stamp = __esm({
|
|
|
19293
19562
|
init_pr_provenance();
|
|
19294
19563
|
defaultGhRunner = async (args, cwd) => {
|
|
19295
19564
|
const { spawn: spawn8 } = await import('child_process');
|
|
19296
|
-
return await new Promise((
|
|
19565
|
+
return await new Promise((resolve38) => {
|
|
19297
19566
|
let stdout = "";
|
|
19298
19567
|
const child = spawn8("gh", [...args], { cwd, shell: false });
|
|
19299
19568
|
child.stdout?.on("data", (d) => {
|
|
19300
19569
|
stdout += d.toString("utf8");
|
|
19301
19570
|
});
|
|
19302
|
-
child.on("error", () =>
|
|
19303
|
-
child.on("close", (code) =>
|
|
19571
|
+
child.on("error", () => resolve38({ exitCode: 1, stdout }));
|
|
19572
|
+
child.on("close", (code) => resolve38({ exitCode: code ?? 1, stdout }));
|
|
19304
19573
|
});
|
|
19305
19574
|
};
|
|
19306
19575
|
}
|
|
@@ -19610,7 +19879,7 @@ function registerCommandTools(server, opts) {
|
|
|
19610
19879
|
}
|
|
19611
19880
|
);
|
|
19612
19881
|
const RESULT_PREVIEW_CHARS = 500;
|
|
19613
|
-
const previewResultText = (
|
|
19882
|
+
const previewResultText = (text13) => text13.length > RESULT_PREVIEW_CHARS ? `${text13.slice(0, RESULT_PREVIEW_CHARS)}\u2026` : text13;
|
|
19614
19883
|
const shapeToolCallRecords = (records, fields, full) => records.map((record2) => {
|
|
19615
19884
|
let entries = Object.entries(record2);
|
|
19616
19885
|
if (fields !== void 0) {
|
|
@@ -19736,12 +20005,12 @@ async function runCommand(input) {
|
|
|
19736
20005
|
return buf;
|
|
19737
20006
|
}
|
|
19738
20007
|
const room = STREAM_BUFFER_CAP - buf.length;
|
|
19739
|
-
const
|
|
19740
|
-
if (
|
|
20008
|
+
const text13 = chunk.toString("utf8");
|
|
20009
|
+
if (text13.length > room) {
|
|
19741
20010
|
truncated = true;
|
|
19742
|
-
return buf +
|
|
20011
|
+
return buf + text13.slice(0, room);
|
|
19743
20012
|
}
|
|
19744
|
-
return buf +
|
|
20013
|
+
return buf + text13;
|
|
19745
20014
|
}
|
|
19746
20015
|
child.stdout?.on("data", (d) => {
|
|
19747
20016
|
stdout = appendCapped(stdout, d);
|
|
@@ -20314,8 +20583,8 @@ async function ownerRepoOfReviewWorktree(path) {
|
|
|
20314
20583
|
// src/session-tools.ts
|
|
20315
20584
|
init_sessions();
|
|
20316
20585
|
init_agent_tools();
|
|
20317
|
-
function parseCodexMcpServers(
|
|
20318
|
-
const root = parseToml(
|
|
20586
|
+
function parseCodexMcpServers(text13) {
|
|
20587
|
+
const root = parseToml(text13);
|
|
20319
20588
|
const servers = root.mcp_servers;
|
|
20320
20589
|
if (!isTable(servers)) return {};
|
|
20321
20590
|
const out = {};
|
|
@@ -20344,14 +20613,14 @@ function parseCodexMcpServers(text12) {
|
|
|
20344
20613
|
return out;
|
|
20345
20614
|
}
|
|
20346
20615
|
async function readCodexMcpServers(path) {
|
|
20347
|
-
let
|
|
20616
|
+
let text13;
|
|
20348
20617
|
try {
|
|
20349
|
-
|
|
20618
|
+
text13 = await promises.readFile(path, "utf8");
|
|
20350
20619
|
} catch (err) {
|
|
20351
20620
|
if (err.code === "ENOENT") return {};
|
|
20352
20621
|
throw err;
|
|
20353
20622
|
}
|
|
20354
|
-
return parseCodexMcpServers(
|
|
20623
|
+
return parseCodexMcpServers(text13);
|
|
20355
20624
|
}
|
|
20356
20625
|
function isTable(v) {
|
|
20357
20626
|
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
@@ -20364,8 +20633,8 @@ function stringTable(t) {
|
|
|
20364
20633
|
}
|
|
20365
20634
|
return out;
|
|
20366
20635
|
}
|
|
20367
|
-
function parseToml(
|
|
20368
|
-
return new TomlParser(
|
|
20636
|
+
function parseToml(text13) {
|
|
20637
|
+
return new TomlParser(text13).parse();
|
|
20369
20638
|
}
|
|
20370
20639
|
var TomlParser = class {
|
|
20371
20640
|
constructor(src) {
|
|
@@ -21435,10 +21704,10 @@ function sandboxAuthForBox(auth) {
|
|
|
21435
21704
|
};
|
|
21436
21705
|
}
|
|
21437
21706
|
async function resolveSandboxSecretFromBroker(slug) {
|
|
21438
|
-
const { resolveSandboxSecret:
|
|
21439
|
-
if (!
|
|
21707
|
+
const { resolveSandboxSecret: resolve38 } = getMcpCredentialDeps();
|
|
21708
|
+
if (!resolve38) return null;
|
|
21440
21709
|
try {
|
|
21441
|
-
return await
|
|
21710
|
+
return await resolve38(slug);
|
|
21442
21711
|
} catch (err) {
|
|
21443
21712
|
console.warn(
|
|
21444
21713
|
`[restartAgentSession] sandbox secret resolution failed for "${slug}": ${err instanceof Error ? err.message : String(err)}`
|
|
@@ -21931,7 +22200,7 @@ async function runContinueOnBootPass(opts) {
|
|
|
21931
22200
|
|
|
21932
22201
|
// src/session-tools.ts
|
|
21933
22202
|
init_session_outcome();
|
|
21934
|
-
var defaultPsExecutor = () => new Promise((
|
|
22203
|
+
var defaultPsExecutor = () => new Promise((resolve38, reject) => {
|
|
21935
22204
|
if (process.platform === "win32") {
|
|
21936
22205
|
reject(new Error("processTreeRss: ps is not available on win32"));
|
|
21937
22206
|
return;
|
|
@@ -21942,7 +22211,7 @@ var defaultPsExecutor = () => new Promise((resolve37, reject) => {
|
|
|
21942
22211
|
{ maxBuffer: 16 * 1024 * 1024 },
|
|
21943
22212
|
(err, stdout) => {
|
|
21944
22213
|
if (err) reject(err);
|
|
21945
|
-
else
|
|
22214
|
+
else resolve38(stdout);
|
|
21946
22215
|
}
|
|
21947
22216
|
);
|
|
21948
22217
|
});
|
|
@@ -26357,8 +26626,8 @@ var okResult = (r) => ({
|
|
|
26357
26626
|
content: [{ type: "text", text: JSON.stringify(r) }],
|
|
26358
26627
|
structuredContent: r
|
|
26359
26628
|
});
|
|
26360
|
-
var errResult = (
|
|
26361
|
-
content: [{ type: "text", text:
|
|
26629
|
+
var errResult = (text13) => ({
|
|
26630
|
+
content: [{ type: "text", text: text13 }],
|
|
26362
26631
|
isError: true
|
|
26363
26632
|
});
|
|
26364
26633
|
function registerBrowserTools(server, opts) {
|
|
@@ -27827,6 +28096,59 @@ function registerHarnessPresetTools(server, deps2 = {}) {
|
|
|
27827
28096
|
}
|
|
27828
28097
|
);
|
|
27829
28098
|
}
|
|
28099
|
+
|
|
28100
|
+
// src/user-preset-tools.ts
|
|
28101
|
+
init_user_presets();
|
|
28102
|
+
function text6(value) {
|
|
28103
|
+
return { content: [{ type: "text", text: JSON.stringify(value) }] };
|
|
28104
|
+
}
|
|
28105
|
+
function errorText5(message) {
|
|
28106
|
+
return { content: [{ type: "text", text: message }], isError: true };
|
|
28107
|
+
}
|
|
28108
|
+
function registerUserPresetTools(server, deps2 = {}) {
|
|
28109
|
+
const { registry } = deps2;
|
|
28110
|
+
server.tool(
|
|
28111
|
+
"user_preset_list",
|
|
28112
|
+
'List the user\'s saved spawn presets ("favorites") from `~/.agentproto/presets.json`. Each preset is a reusable, named subset of the spawn/session config axes (adapter, model, route, access, posture, effort, contextProfile, cwd, skills, bundles, browser) plus `lastUsedAt` (ISO 8601), stamped the last time a spawn resolved a `presetId` to it. Pass `includeRecent: true` to ALSO return up to 5 distinct recent spawn configurations (adapter, model, profileRef, cwd) derived from this host\'s own recent session history, each marked `recent: true` \u2014 not persisted, a candidate list for "save as favorite" rather than an actual preset.',
|
|
28113
|
+
{
|
|
28114
|
+
includeRecent: z.boolean().optional().describe("Also return up to 5 recent distinct spawn configs derived from session history.")
|
|
28115
|
+
},
|
|
28116
|
+
async ({ includeRecent }) => {
|
|
28117
|
+
const presets = await listUserPresets();
|
|
28118
|
+
const recent = includeRecent ? deriveRecentSpawnConfigs(registry?.list() ?? []) : void 0;
|
|
28119
|
+
return text6({ presets, ...recent ? { recent } : {} });
|
|
28120
|
+
}
|
|
28121
|
+
);
|
|
28122
|
+
server.tool(
|
|
28123
|
+
"user_preset_save",
|
|
28124
|
+
'Create or update (upsert by id) a user spawn preset ("favorite"). `id` must be lowercase kebab-case. Every other field is an optional spawn/session config axis a future `agent_start({ presetId })` (or `/sessions/agent`, `/sessions/chat`) resolves and applies at lower precedence than the caller\'s own explicit fields. Editing an existing preset without naming `lastUsedAt` preserves its current stamp rather than clearing it.',
|
|
28125
|
+
userPresetSchema.shape,
|
|
28126
|
+
async (input) => {
|
|
28127
|
+
try {
|
|
28128
|
+
await saveUserPreset(input);
|
|
28129
|
+
const saved = await getUserPreset(input.id);
|
|
28130
|
+
return text6({ preset: saved });
|
|
28131
|
+
} catch (err) {
|
|
28132
|
+
return errorText5(`user_preset_save failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
28133
|
+
}
|
|
28134
|
+
}
|
|
28135
|
+
);
|
|
28136
|
+
server.tool(
|
|
28137
|
+
"user_preset_delete",
|
|
28138
|
+
"Delete a user spawn preset by id. Idempotent: a missing id returns `{ deleted: false }`.",
|
|
28139
|
+
{
|
|
28140
|
+
id: z.string().describe("The preset id to delete.")
|
|
28141
|
+
},
|
|
28142
|
+
async ({ id }) => {
|
|
28143
|
+
try {
|
|
28144
|
+
const deleted = await deleteUserPreset(id);
|
|
28145
|
+
return text6({ deleted });
|
|
28146
|
+
} catch (err) {
|
|
28147
|
+
return errorText5(`user_preset_delete failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
28148
|
+
}
|
|
28149
|
+
}
|
|
28150
|
+
);
|
|
28151
|
+
}
|
|
27830
28152
|
var webSearchInputSchema = z.object({
|
|
27831
28153
|
query: z.string().min(1).describe("The web search query."),
|
|
27832
28154
|
count: z.number().int().min(1).max(20).optional().default(10).describe("Number of results to return (1\u201320, default 10)."),
|
|
@@ -28171,10 +28493,15 @@ var PANEL_APP_HANDLES = [
|
|
|
28171
28493
|
workBoardApp,
|
|
28172
28494
|
reviewPanelApp
|
|
28173
28495
|
];
|
|
28174
|
-
|
|
28496
|
+
var DEEP_LINK_SESSION_ID_RE = /^sess_[A-Za-z0-9_-]+$/;
|
|
28497
|
+
function isValidDeepLinkSessionId(id) {
|
|
28498
|
+
return DEEP_LINK_SESSION_ID_RE.test(id);
|
|
28499
|
+
}
|
|
28500
|
+
function resolveBuiltinPanelUi(appId, httpBaseUrl, sessionId) {
|
|
28175
28501
|
if (appId === liveSessionApp.id) {
|
|
28502
|
+
const pinnedSessionId = sessionId && isValidDeepLinkSessionId(sessionId) ? sessionId : void 0;
|
|
28176
28503
|
const app = makeLiveSessionApp({ httpBaseUrl });
|
|
28177
|
-
const html = typeof app.html === "function" ? app.html({ httpBaseUrl }) : app.html;
|
|
28504
|
+
const html = typeof app.html === "function" ? app.html({ httpBaseUrl, sessionId: pinnedSessionId }) : app.html;
|
|
28178
28505
|
return { html, tools: liveSessionApp.ui?.tools ?? [] };
|
|
28179
28506
|
}
|
|
28180
28507
|
const handle = [sessionsPanelApp, agentsOverviewApp, bureauSessionsApp, sessionStoryApp, workBoardApp, reviewPanelApp].find(
|
|
@@ -28885,8 +29212,8 @@ function expandTemplate(template, when) {
|
|
|
28885
29212
|
const date = when.toISOString().slice(0, 10);
|
|
28886
29213
|
return template.replace(/\{date\}/g, date);
|
|
28887
29214
|
}
|
|
28888
|
-
function preview(
|
|
28889
|
-
const flat =
|
|
29215
|
+
function preview(text13, max = 120) {
|
|
29216
|
+
const flat = text13.replace(/\s+/g, " ").trim();
|
|
28890
29217
|
return flat.length > max ? `${flat.slice(0, max - 1)}\u2026` : flat;
|
|
28891
29218
|
}
|
|
28892
29219
|
|
|
@@ -29386,6 +29713,392 @@ function registerSentinelTools(server, opts) {
|
|
|
29386
29713
|
}
|
|
29387
29714
|
);
|
|
29388
29715
|
}
|
|
29716
|
+
var HOSTS_VERSION = 1;
|
|
29717
|
+
var DIAL_TIMEOUT_MS = 15e3;
|
|
29718
|
+
var HANDSHAKE_TIMEOUT_MS = 15e3;
|
|
29719
|
+
var DEFAULT_JOINED_HOST_TTL_MS = 7 * 864e5;
|
|
29720
|
+
var MAX_CACHED_SESSION_PATHS_PER_HOST = 32;
|
|
29721
|
+
var MAX_CACHED_SESSION_BODY_BYTES = 256 * 1024;
|
|
29722
|
+
function defaultHostsPath() {
|
|
29723
|
+
return join(homedir(), ".agentproto", "hosts.json");
|
|
29724
|
+
}
|
|
29725
|
+
function isSessionsPath(path) {
|
|
29726
|
+
return path === "/sessions" || path.startsWith("/sessions/") || path.startsWith("/sessions?");
|
|
29727
|
+
}
|
|
29728
|
+
function rvUrl(base, route) {
|
|
29729
|
+
const sep6 = base.includes("?") ? "&" : "?";
|
|
29730
|
+
return `${base}${sep6}side=client&t=${encodeURIComponent(route)}`;
|
|
29731
|
+
}
|
|
29732
|
+
function defaultSelfName() {
|
|
29733
|
+
try {
|
|
29734
|
+
return hostname() || "agentproto-host-registry";
|
|
29735
|
+
} catch {
|
|
29736
|
+
return "agentproto-host-registry";
|
|
29737
|
+
}
|
|
29738
|
+
}
|
|
29739
|
+
function createHostRegistry(deps2) {
|
|
29740
|
+
const hostsPath = deps2.hostsPath ?? defaultHostsPath();
|
|
29741
|
+
const now = deps2.now ?? Date.now;
|
|
29742
|
+
const log = deps2.log ?? (() => {
|
|
29743
|
+
});
|
|
29744
|
+
const dialTimeoutMs = deps2.dialTimeoutMs ?? DIAL_TIMEOUT_MS;
|
|
29745
|
+
const handshakeTimeoutMs = deps2.handshakeTimeoutMs ?? HANDSHAKE_TIMEOUT_MS;
|
|
29746
|
+
const joinedHostTtlMs = deps2.joinedHostTtlMs ?? DEFAULT_JOINED_HOST_TTL_MS;
|
|
29747
|
+
const selfName = defaultSelfName();
|
|
29748
|
+
const hosts = /* @__PURE__ */ new Map();
|
|
29749
|
+
const onlineCounts = /* @__PURE__ */ new Map();
|
|
29750
|
+
const sessionsCache = /* @__PURE__ */ new Map();
|
|
29751
|
+
let loaded = false;
|
|
29752
|
+
async function ensureLoaded() {
|
|
29753
|
+
if (loaded) return;
|
|
29754
|
+
loaded = true;
|
|
29755
|
+
try {
|
|
29756
|
+
const raw = await readFile(hostsPath, "utf8");
|
|
29757
|
+
const parsed = JSON.parse(raw);
|
|
29758
|
+
if (isHostsFile(parsed)) {
|
|
29759
|
+
for (const rec of parsed.hosts) hosts.set(rec.fingerprint, rec);
|
|
29760
|
+
} else {
|
|
29761
|
+
log(`[hosts] ignoring ${hostsPath}: unrecognised format`);
|
|
29762
|
+
}
|
|
29763
|
+
} catch (err) {
|
|
29764
|
+
if (!isEnoent(err)) log(`[hosts] could not read ${hostsPath}: ${errMsg(err)}`);
|
|
29765
|
+
}
|
|
29766
|
+
}
|
|
29767
|
+
let persistChain = Promise.resolve();
|
|
29768
|
+
function persist() {
|
|
29769
|
+
const run = persistChain.then(doPersist);
|
|
29770
|
+
persistChain = run.catch(() => {
|
|
29771
|
+
});
|
|
29772
|
+
return run;
|
|
29773
|
+
}
|
|
29774
|
+
async function doPersist() {
|
|
29775
|
+
const file = { v: HOSTS_VERSION, hosts: Array.from(hosts.values()) };
|
|
29776
|
+
const dir = dirname(hostsPath);
|
|
29777
|
+
await mkdir(dir, { recursive: true });
|
|
29778
|
+
const tmp = join(dir, `.${basename(hostsPath)}.tmp-${process.pid}`);
|
|
29779
|
+
await writeFile(tmp, JSON.stringify(file, null, 2) + "\n", { encoding: "utf8", mode: 384 });
|
|
29780
|
+
await chmod(tmp, 384).catch(() => {
|
|
29781
|
+
});
|
|
29782
|
+
await rename(tmp, hostsPath);
|
|
29783
|
+
}
|
|
29784
|
+
function findHost(idOrName) {
|
|
29785
|
+
const byFp = hosts.get(idOrName);
|
|
29786
|
+
if (byFp) return byFp;
|
|
29787
|
+
for (const rec of hosts.values()) if (rec.name === idOrName) return rec;
|
|
29788
|
+
return void 0;
|
|
29789
|
+
}
|
|
29790
|
+
async function dialWithTimeout(wsUrl) {
|
|
29791
|
+
const ac = new AbortController();
|
|
29792
|
+
const timer = setTimeout(() => ac.abort(new Error(`dial timed out after ${dialTimeoutMs}ms`)), dialTimeoutMs);
|
|
29793
|
+
if (typeof timer.unref === "function") timer.unref();
|
|
29794
|
+
try {
|
|
29795
|
+
return await deps2.dial(wsUrl, ac.signal);
|
|
29796
|
+
} finally {
|
|
29797
|
+
clearTimeout(timer);
|
|
29798
|
+
}
|
|
29799
|
+
}
|
|
29800
|
+
async function add(offerUrl, name, meta) {
|
|
29801
|
+
await ensureLoaded();
|
|
29802
|
+
const offer = await parseOfferUrl(offerUrl, { now: now() });
|
|
29803
|
+
if (offer.scope !== "host") {
|
|
29804
|
+
throw new Error(
|
|
29805
|
+
"this offer is not host-scoped \u2014 it only grants remote-control access, not host registration; ask the other machine to run `agentproto pair offer --host` and use that offer instead"
|
|
29806
|
+
);
|
|
29807
|
+
}
|
|
29808
|
+
const { route, auth } = await deriveOfferTokens(offer.secret);
|
|
29809
|
+
const raw = await dialWithTimeout(rvUrl(offer.rendezvousUrl, route));
|
|
29810
|
+
const started = await startClientHandshake({
|
|
29811
|
+
daemonX25519Pub: offer.daemonX25519Pub,
|
|
29812
|
+
daemonEd25519Pub: offer.daemonEd25519Pub,
|
|
29813
|
+
authToken: auth,
|
|
29814
|
+
clientName: name ?? selfName
|
|
29815
|
+
});
|
|
29816
|
+
let session = null;
|
|
29817
|
+
const wrapped = await clientHandshakeOverSink(
|
|
29818
|
+
raw,
|
|
29819
|
+
encodePairingMessage(started.hello),
|
|
29820
|
+
async (replyBytes) => {
|
|
29821
|
+
session = await started.complete(decodePairingReply(replyBytes));
|
|
29822
|
+
return session;
|
|
29823
|
+
},
|
|
29824
|
+
{ timeoutMs: handshakeTimeoutMs }
|
|
29825
|
+
);
|
|
29826
|
+
if (!session) throw new Error("handshake did not derive a session");
|
|
29827
|
+
const derived = session;
|
|
29828
|
+
if (derived.peerFingerprint !== offer.fingerprint) {
|
|
29829
|
+
wrapped.close("fingerprint mismatch");
|
|
29830
|
+
throw new Error(
|
|
29831
|
+
`host fingerprint ${derived.peerFingerprint} does not match the offer's ${offer.fingerprint}`
|
|
29832
|
+
);
|
|
29833
|
+
}
|
|
29834
|
+
const pairRoot = await derivePairRoot(derived);
|
|
29835
|
+
const nowIso2 = new Date(now()).toISOString();
|
|
29836
|
+
const existing = hosts.get(offer.fingerprint);
|
|
29837
|
+
const addedVia = existing?.addedVia === "manual" ? "manual" : meta?.joined ? "join" : "manual";
|
|
29838
|
+
const record2 = {
|
|
29839
|
+
fingerprint: offer.fingerprint,
|
|
29840
|
+
name: name ?? offer.fingerprint,
|
|
29841
|
+
daemonX25519Pub: offer.daemonX25519Pub,
|
|
29842
|
+
daemonEd25519Pub: offer.daemonEd25519Pub,
|
|
29843
|
+
rendezvousUrl: offer.rendezvousUrl,
|
|
29844
|
+
pairRoot,
|
|
29845
|
+
createdAt: existing?.createdAt ?? nowIso2,
|
|
29846
|
+
lastSeen: nowIso2,
|
|
29847
|
+
...meta?.provider ? { provider: meta.provider } : {},
|
|
29848
|
+
...meta?.sandboxId ? { sandboxId: meta.sandboxId } : {},
|
|
29849
|
+
...meta?.labels ? { labels: meta.labels } : {},
|
|
29850
|
+
addedVia
|
|
29851
|
+
};
|
|
29852
|
+
hosts.set(record2.fingerprint, record2);
|
|
29853
|
+
await persist();
|
|
29854
|
+
log(`[hosts] added ${record2.fingerprint} (${record2.name})`);
|
|
29855
|
+
wrapped.close("add complete");
|
|
29856
|
+
return { fingerprint: record2.fingerprint, name: record2.name, rendezvousUrl: record2.rendezvousUrl };
|
|
29857
|
+
}
|
|
29858
|
+
async function pruneExpiredJoinedHosts() {
|
|
29859
|
+
if (joinedHostTtlMs <= 0) return;
|
|
29860
|
+
const cutoff = now() - joinedHostTtlMs;
|
|
29861
|
+
const expired = [];
|
|
29862
|
+
for (const rec of hosts.values()) {
|
|
29863
|
+
if (rec.addedVia === "join" && Date.parse(rec.lastSeen) <= cutoff) expired.push(rec.fingerprint);
|
|
29864
|
+
}
|
|
29865
|
+
if (expired.length === 0) return;
|
|
29866
|
+
for (const fp of expired) {
|
|
29867
|
+
hosts.delete(fp);
|
|
29868
|
+
sessionsCache.delete(fp);
|
|
29869
|
+
log(`[hosts] pruned ${fp}: joined host, unseen for over ${joinedHostTtlMs}ms`);
|
|
29870
|
+
}
|
|
29871
|
+
await persist();
|
|
29872
|
+
}
|
|
29873
|
+
async function list() {
|
|
29874
|
+
await ensureLoaded();
|
|
29875
|
+
await pruneExpiredJoinedHosts();
|
|
29876
|
+
return Array.from(hosts.values()).map((r) => ({ ...r }));
|
|
29877
|
+
}
|
|
29878
|
+
async function renameHost(idOrName, newName) {
|
|
29879
|
+
const trimmed = newName.trim();
|
|
29880
|
+
if (!trimmed) throw new Error("host name must not be empty");
|
|
29881
|
+
await ensureLoaded();
|
|
29882
|
+
const target = findHost(idOrName);
|
|
29883
|
+
if (!target) return false;
|
|
29884
|
+
target.name = trimmed;
|
|
29885
|
+
await persist();
|
|
29886
|
+
log(`[hosts] renamed ${target.fingerprint} to "${trimmed}"`);
|
|
29887
|
+
return true;
|
|
29888
|
+
}
|
|
29889
|
+
async function revoke(idOrName) {
|
|
29890
|
+
await ensureLoaded();
|
|
29891
|
+
const target = findHost(idOrName);
|
|
29892
|
+
if (!target) return false;
|
|
29893
|
+
hosts.delete(target.fingerprint);
|
|
29894
|
+
await persist();
|
|
29895
|
+
log(`[hosts] revoked ${target.fingerprint} (${target.name})`);
|
|
29896
|
+
return true;
|
|
29897
|
+
}
|
|
29898
|
+
function isOnline(fingerprint) {
|
|
29899
|
+
return (onlineCounts.get(fingerprint) ?? 0) > 0;
|
|
29900
|
+
}
|
|
29901
|
+
async function connectToHost(record2) {
|
|
29902
|
+
const epoch = currentEpoch(now());
|
|
29903
|
+
const attempts = [epoch, epoch - 1];
|
|
29904
|
+
let lastErr;
|
|
29905
|
+
let hungUpOnHello = 0;
|
|
29906
|
+
for (const e of attempts) {
|
|
29907
|
+
const { route, auth } = await deriveEpochTokens(record2.pairRoot, e);
|
|
29908
|
+
let raw;
|
|
29909
|
+
try {
|
|
29910
|
+
raw = await dialWithTimeout(rvUrl(record2.rendezvousUrl, route));
|
|
29911
|
+
} catch (err) {
|
|
29912
|
+
lastErr = err;
|
|
29913
|
+
continue;
|
|
29914
|
+
}
|
|
29915
|
+
try {
|
|
29916
|
+
const started = await startClientHandshake({
|
|
29917
|
+
daemonX25519Pub: record2.daemonX25519Pub,
|
|
29918
|
+
daemonEd25519Pub: record2.daemonEd25519Pub,
|
|
29919
|
+
authToken: auth,
|
|
29920
|
+
clientName: record2.name
|
|
29921
|
+
});
|
|
29922
|
+
const wrapped = await clientHandshakeOverSink(
|
|
29923
|
+
raw,
|
|
29924
|
+
encodePairingMessage(started.hello),
|
|
29925
|
+
(replyBytes) => started.complete(decodePairingReply(replyBytes)),
|
|
29926
|
+
{ timeoutMs: handshakeTimeoutMs }
|
|
29927
|
+
);
|
|
29928
|
+
const client = createTunnelClient({ sink: wrapped });
|
|
29929
|
+
await client.ready();
|
|
29930
|
+
record2.lastSeen = new Date(now()).toISOString();
|
|
29931
|
+
await persist().catch((err) => log(`[hosts] lastSeen persist failed: ${errMsg(err)}`));
|
|
29932
|
+
return client;
|
|
29933
|
+
} catch (err) {
|
|
29934
|
+
lastErr = err;
|
|
29935
|
+
if (err instanceof Error && /transport closed during handshake/.test(err.message)) hungUpOnHello++;
|
|
29936
|
+
try {
|
|
29937
|
+
raw.close("handshake failed");
|
|
29938
|
+
} catch {
|
|
29939
|
+
}
|
|
29940
|
+
}
|
|
29941
|
+
}
|
|
29942
|
+
const hint = hungUpOnHello === attempts.length ? ` \u2014 the host hung up on the pair/v2 hello; if it runs an older agentproto (pair/v1), upgrade it and re-pair: run \`agentproto pair offer --host\` on the host, then \`agentproto devices add\` here. (${PAIRING_PROTOCOL_OUTDATED_MESSAGE})` : "";
|
|
29943
|
+
throw new Error(
|
|
29944
|
+
`could not reach host ${record2.fingerprint} via ${record2.rendezvousUrl}: ${lastErr instanceof Error ? lastErr.message : String(lastErr)}${hint}`
|
|
29945
|
+
);
|
|
29946
|
+
}
|
|
29947
|
+
function toTunnelReq(req) {
|
|
29948
|
+
return {
|
|
29949
|
+
method: req.method,
|
|
29950
|
+
path: req.path,
|
|
29951
|
+
...req.headers ? { headers: req.headers } : {},
|
|
29952
|
+
...req.body ? { body: Buffer.from(req.body) } : {}
|
|
29953
|
+
};
|
|
29954
|
+
}
|
|
29955
|
+
function cacheSessionsResponse(fingerprint, req, res) {
|
|
29956
|
+
if (req.method !== "GET" || !isSessionsPath(req.path)) return;
|
|
29957
|
+
if (res.status < 200 || res.status >= 300) return;
|
|
29958
|
+
if (res.body.byteLength > MAX_CACHED_SESSION_BODY_BYTES) return;
|
|
29959
|
+
let byPath = sessionsCache.get(fingerprint);
|
|
29960
|
+
if (!byPath) {
|
|
29961
|
+
byPath = /* @__PURE__ */ new Map();
|
|
29962
|
+
sessionsCache.set(fingerprint, byPath);
|
|
29963
|
+
}
|
|
29964
|
+
byPath.delete(req.path);
|
|
29965
|
+
byPath.set(req.path, {
|
|
29966
|
+
status: res.status,
|
|
29967
|
+
headers: { ...res.headers },
|
|
29968
|
+
body: res.body,
|
|
29969
|
+
stale: true,
|
|
29970
|
+
capturedAt: new Date(now()).toISOString()
|
|
29971
|
+
});
|
|
29972
|
+
while (byPath.size > MAX_CACHED_SESSION_PATHS_PER_HOST) {
|
|
29973
|
+
const oldest = byPath.keys().next().value;
|
|
29974
|
+
if (oldest === void 0) break;
|
|
29975
|
+
byPath.delete(oldest);
|
|
29976
|
+
}
|
|
29977
|
+
}
|
|
29978
|
+
function getSessionsSnapshot(idOrName, path) {
|
|
29979
|
+
const record2 = findHost(idOrName);
|
|
29980
|
+
if (!record2) return void 0;
|
|
29981
|
+
const entry = sessionsCache.get(record2.fingerprint)?.get(path);
|
|
29982
|
+
if (!entry) return void 0;
|
|
29983
|
+
return { ...entry, headers: { ...entry.headers }, body: entry.body };
|
|
29984
|
+
}
|
|
29985
|
+
async function forwardHttp(idOrName, req) {
|
|
29986
|
+
await ensureLoaded();
|
|
29987
|
+
const record2 = findHost(idOrName);
|
|
29988
|
+
if (!record2) throw new Error(`no host matched "${idOrName}"`);
|
|
29989
|
+
onlineCounts.set(record2.fingerprint, (onlineCounts.get(record2.fingerprint) ?? 0) + 1);
|
|
29990
|
+
let client;
|
|
29991
|
+
try {
|
|
29992
|
+
client = await connectToHost(record2);
|
|
29993
|
+
const res = await client.forwardHttp(toTunnelReq(req));
|
|
29994
|
+
const result = { status: res.status, headers: { ...res.headers }, body: new Uint8Array(res.body) };
|
|
29995
|
+
cacheSessionsResponse(record2.fingerprint, req, result);
|
|
29996
|
+
return result;
|
|
29997
|
+
} finally {
|
|
29998
|
+
if (client) await client.close().catch(() => {
|
|
29999
|
+
});
|
|
30000
|
+
const remaining = (onlineCounts.get(record2.fingerprint) ?? 1) - 1;
|
|
30001
|
+
if (remaining > 0) onlineCounts.set(record2.fingerprint, remaining);
|
|
30002
|
+
else onlineCounts.delete(record2.fingerprint);
|
|
30003
|
+
}
|
|
30004
|
+
}
|
|
30005
|
+
async function forwardHttpStream(idOrName, req) {
|
|
30006
|
+
await ensureLoaded();
|
|
30007
|
+
const record2 = findHost(idOrName);
|
|
30008
|
+
if (!record2) throw new Error(`no host matched "${idOrName}"`);
|
|
30009
|
+
onlineCounts.set(record2.fingerprint, (onlineCounts.get(record2.fingerprint) ?? 0) + 1);
|
|
30010
|
+
let decremented = false;
|
|
30011
|
+
const decrement = () => {
|
|
30012
|
+
if (decremented) return;
|
|
30013
|
+
decremented = true;
|
|
30014
|
+
const remaining = (onlineCounts.get(record2.fingerprint) ?? 1) - 1;
|
|
30015
|
+
if (remaining > 0) onlineCounts.set(record2.fingerprint, remaining);
|
|
30016
|
+
else onlineCounts.delete(record2.fingerprint);
|
|
30017
|
+
};
|
|
30018
|
+
let client;
|
|
30019
|
+
try {
|
|
30020
|
+
client = await connectToHost(record2);
|
|
30021
|
+
} catch (err) {
|
|
30022
|
+
decrement();
|
|
30023
|
+
throw err;
|
|
30024
|
+
}
|
|
30025
|
+
let res;
|
|
30026
|
+
try {
|
|
30027
|
+
res = await client.forwardHttpStream(toTunnelReq(req));
|
|
30028
|
+
} catch (err) {
|
|
30029
|
+
await client.close().catch(() => {
|
|
30030
|
+
});
|
|
30031
|
+
decrement();
|
|
30032
|
+
throw err;
|
|
30033
|
+
}
|
|
30034
|
+
const cleanup = () => {
|
|
30035
|
+
void client.close().catch(() => {
|
|
30036
|
+
});
|
|
30037
|
+
decrement();
|
|
30038
|
+
};
|
|
30039
|
+
return { status: res.status, headers: { ...res.headers }, body: wrapStreamWithCleanup(res.body, cleanup) };
|
|
30040
|
+
}
|
|
30041
|
+
return {
|
|
30042
|
+
add,
|
|
30043
|
+
list,
|
|
30044
|
+
rename: renameHost,
|
|
30045
|
+
revoke,
|
|
30046
|
+
isOnline,
|
|
30047
|
+
forwardHttp,
|
|
30048
|
+
forwardHttpStream,
|
|
30049
|
+
getSessionsSnapshot
|
|
30050
|
+
};
|
|
30051
|
+
}
|
|
30052
|
+
function wrapStreamWithCleanup(source, cleanup) {
|
|
30053
|
+
const reader = source.getReader();
|
|
30054
|
+
let cleanedUp = false;
|
|
30055
|
+
const runCleanup = () => {
|
|
30056
|
+
if (cleanedUp) return;
|
|
30057
|
+
cleanedUp = true;
|
|
30058
|
+
cleanup();
|
|
30059
|
+
};
|
|
30060
|
+
return new ReadableStream({
|
|
30061
|
+
async pull(controller) {
|
|
30062
|
+
try {
|
|
30063
|
+
const { done, value } = await reader.read();
|
|
30064
|
+
if (done) {
|
|
30065
|
+
controller.close();
|
|
30066
|
+
runCleanup();
|
|
30067
|
+
return;
|
|
30068
|
+
}
|
|
30069
|
+
controller.enqueue(value);
|
|
30070
|
+
} catch (err) {
|
|
30071
|
+
controller.error(err);
|
|
30072
|
+
runCleanup();
|
|
30073
|
+
}
|
|
30074
|
+
},
|
|
30075
|
+
async cancel(reason) {
|
|
30076
|
+
try {
|
|
30077
|
+
await reader.cancel(reason);
|
|
30078
|
+
} finally {
|
|
30079
|
+
runCleanup();
|
|
30080
|
+
}
|
|
30081
|
+
}
|
|
30082
|
+
});
|
|
30083
|
+
}
|
|
30084
|
+
function isHostsFile(v) {
|
|
30085
|
+
if (typeof v !== "object" || v === null) return false;
|
|
30086
|
+
const rec = v;
|
|
30087
|
+
if (rec["v"] !== HOSTS_VERSION) return false;
|
|
30088
|
+
if (!Array.isArray(rec["hosts"])) return false;
|
|
30089
|
+
return rec["hosts"].every(isHostRecord);
|
|
30090
|
+
}
|
|
30091
|
+
function isHostRecord(v) {
|
|
30092
|
+
if (typeof v !== "object" || v === null) return false;
|
|
30093
|
+
const r = v;
|
|
30094
|
+
return typeof r["fingerprint"] === "string" && typeof r["name"] === "string" && typeof r["daemonX25519Pub"] === "string" && typeof r["daemonEd25519Pub"] === "string" && typeof r["rendezvousUrl"] === "string" && typeof r["pairRoot"] === "string" && typeof r["createdAt"] === "string" && typeof r["lastSeen"] === "string";
|
|
30095
|
+
}
|
|
30096
|
+
function isEnoent(err) {
|
|
30097
|
+
return typeof err === "object" && err !== null && "code" in err && err.code === "ENOENT";
|
|
30098
|
+
}
|
|
30099
|
+
function errMsg(err) {
|
|
30100
|
+
return err instanceof Error ? err.message : String(err);
|
|
30101
|
+
}
|
|
29389
30102
|
|
|
29390
30103
|
// src/device-registry.ts
|
|
29391
30104
|
function inferKind2(name) {
|
|
@@ -29457,7 +30170,14 @@ function createDeviceRegistry(pairing, hosts) {
|
|
|
29457
30170
|
"this daemon has no host registry wired \u2014 device sessions is unavailable (internal configuration issue, not a user error)"
|
|
29458
30171
|
);
|
|
29459
30172
|
}
|
|
29460
|
-
|
|
30173
|
+
try {
|
|
30174
|
+
return await hosts.forwardHttp(idOrName, req);
|
|
30175
|
+
} catch (err) {
|
|
30176
|
+
if (req.method !== "GET" || !isSessionsPath(req.path)) throw err;
|
|
30177
|
+
const snapshot = hosts.getSessionsSnapshot(idOrName, req.path);
|
|
30178
|
+
if (!snapshot) throw err;
|
|
30179
|
+
return snapshot;
|
|
30180
|
+
}
|
|
29461
30181
|
}
|
|
29462
30182
|
};
|
|
29463
30183
|
}
|
|
@@ -29525,13 +30245,13 @@ function passthroughToolHandle(id) {
|
|
|
29525
30245
|
function unwrapDispatchResult(result) {
|
|
29526
30246
|
if (!result || typeof result !== "object" || !("content" in result)) return result;
|
|
29527
30247
|
const r = result;
|
|
29528
|
-
const
|
|
29529
|
-
if (r.isError) throw new Error(
|
|
29530
|
-
if (!
|
|
30248
|
+
const text13 = (r.content ?? []).map((c) => c.text ?? "").join("\n").trim();
|
|
30249
|
+
if (r.isError) throw new Error(text13 || "tool call failed");
|
|
30250
|
+
if (!text13) return void 0;
|
|
29531
30251
|
try {
|
|
29532
|
-
return JSON.parse(
|
|
30252
|
+
return JSON.parse(text13);
|
|
29533
30253
|
} catch {
|
|
29534
|
-
return
|
|
30254
|
+
return text13;
|
|
29535
30255
|
}
|
|
29536
30256
|
}
|
|
29537
30257
|
function createDaemonToolRegistry(handle, dispatchTool) {
|
|
@@ -30084,8 +30804,8 @@ async function locateAppDataPath(roots, relPath) {
|
|
|
30084
30804
|
function textResult(body) {
|
|
30085
30805
|
return { content: [{ type: "text", text: JSON.stringify(body) }] };
|
|
30086
30806
|
}
|
|
30087
|
-
function errorResult(
|
|
30088
|
-
return { content: [{ type: "text", text: JSON.stringify({ error:
|
|
30807
|
+
function errorResult(text13) {
|
|
30808
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: text13 }) }], isError: true };
|
|
30089
30809
|
}
|
|
30090
30810
|
async function atomicWrite(filePath, data) {
|
|
30091
30811
|
await mkdir(dirname(filePath), { recursive: true });
|
|
@@ -30450,8 +31170,8 @@ function agentStepMcpServers(input) {
|
|
|
30450
31170
|
return void 0;
|
|
30451
31171
|
}
|
|
30452
31172
|
params.set("callerSessionId", sessionId);
|
|
30453
|
-
const
|
|
30454
|
-
return [{ name: "agentproto", transport: "http", ref: `${daemonMcpUrl}${
|
|
31173
|
+
const sep6 = daemonMcpUrl.includes("?") ? "&" : "?";
|
|
31174
|
+
return [{ name: "agentproto", transport: "http", ref: `${daemonMcpUrl}${sep6}${params.toString()}` }];
|
|
30455
31175
|
}
|
|
30456
31176
|
function withAllowlistToolSearchOff(options, agentTools, declaredOptions) {
|
|
30457
31177
|
if (!agentTools || agentTools.length === 0) return options;
|
|
@@ -30687,10 +31407,17 @@ var SessionsRegistryAgentHost = class {
|
|
|
30687
31407
|
async releaseAll() {
|
|
30688
31408
|
await Promise.all([...this.unreleased].map((id) => this.releaseSession(id)));
|
|
30689
31409
|
}
|
|
31410
|
+
/**
|
|
31411
|
+
* `waitTurnEnd` subscribes before `sendPrompt` is even called, so a cancel
|
|
31412
|
+
* that kills the session WHILE `sendPrompt` is still in flight (the whole
|
|
31413
|
+
* turn, for an on-host claude-code step) rejects `turnEnded` before either
|
|
31414
|
+
* promise had a `.then`/`.catch` attached to it — an unhandled rejection
|
|
31415
|
+
* that crashes the daemon process outright (not just this run). Passing
|
|
31416
|
+
* both to `Promise.all` in the same expression attaches a handler to each
|
|
31417
|
+
* synchronously, before the event bus can fire the rejection asynchronously.
|
|
31418
|
+
*/
|
|
30690
31419
|
async sendPromptAndWait(sessionId, prompt) {
|
|
30691
|
-
|
|
30692
|
-
await this.registry.sendPrompt(sessionId, prompt);
|
|
30693
|
-
await turnEnded;
|
|
31420
|
+
await Promise.all([this.waitTurnEnd(sessionId), this.registry.sendPrompt(sessionId, prompt)]);
|
|
30694
31421
|
}
|
|
30695
31422
|
resolveByLabel(stepId) {
|
|
30696
31423
|
return this.sessionsByLabel.get(stepId);
|
|
@@ -30719,9 +31446,7 @@ var SessionsRegistryAgentHost = class {
|
|
|
30719
31446
|
const desc = this.registry.get(sessionId);
|
|
30720
31447
|
if (!desc?.awaitingInput) return;
|
|
30721
31448
|
if (policy.awaiting === "auto-allow") {
|
|
30722
|
-
|
|
30723
|
-
await this.registry.sendPrompt(sessionId, policy.prompt);
|
|
30724
|
-
await turnEnded;
|
|
31449
|
+
await this.sendPromptAndWait(sessionId, policy.prompt);
|
|
30725
31450
|
} else if (policy.awaiting === "escalate") {
|
|
30726
31451
|
const escalateUrl = policy.webhookUrl ?? this.opts?.notifyUrl;
|
|
30727
31452
|
if (escalateUrl) {
|
|
@@ -30767,9 +31492,9 @@ var SessionsRegistryAgentHost = class {
|
|
|
30767
31492
|
if (!("role" in m)) continue;
|
|
30768
31493
|
if (m.role !== "assistant") continue;
|
|
30769
31494
|
if (!("text" in m)) continue;
|
|
30770
|
-
const
|
|
30771
|
-
if (typeof
|
|
30772
|
-
return
|
|
31495
|
+
const text13 = m.text;
|
|
31496
|
+
if (typeof text13 === "string" && text13.trim()) {
|
|
31497
|
+
return text13;
|
|
30773
31498
|
}
|
|
30774
31499
|
}
|
|
30775
31500
|
return "";
|
|
@@ -30780,11 +31505,11 @@ var SessionsRegistryAgentHost = class {
|
|
|
30780
31505
|
}
|
|
30781
31506
|
// ── Internal helpers ──────────────────────────────────────────────────
|
|
30782
31507
|
waitTurnEnd(sessionId) {
|
|
30783
|
-
return new Promise((
|
|
31508
|
+
return new Promise((resolve38, reject) => {
|
|
30784
31509
|
const unsubs = [];
|
|
30785
31510
|
const done = () => {
|
|
30786
31511
|
for (const u of unsubs) u();
|
|
30787
|
-
|
|
31512
|
+
resolve38();
|
|
30788
31513
|
};
|
|
30789
31514
|
const fail3 = (reason) => {
|
|
30790
31515
|
for (const u of unsubs) u();
|
|
@@ -30956,9 +31681,9 @@ function translateStages(stages, workflowId) {
|
|
|
30956
31681
|
if (b.steps && typeof b.steps === "object") {
|
|
30957
31682
|
for (const [id, val] of Object.entries(b.steps)) {
|
|
30958
31683
|
if (val && typeof val === "object" && "text" in val) {
|
|
30959
|
-
const
|
|
30960
|
-
if (
|
|
30961
|
-
${
|
|
31684
|
+
const text13 = val.text;
|
|
31685
|
+
if (text13) prevTexts.push(`[Output from step "${id}"]
|
|
31686
|
+
${text13}`);
|
|
30962
31687
|
}
|
|
30963
31688
|
}
|
|
30964
31689
|
}
|
|
@@ -31145,6 +31870,43 @@ function findStepDef(defs, stepId) {
|
|
|
31145
31870
|
var DEFAULT_PERSIST_PATH2 = () => join(homedir(), ".agentproto", "workflow-runs.json");
|
|
31146
31871
|
var DEFAULT_LEASE_TTL_MS = 6e4;
|
|
31147
31872
|
var DEFAULT_HEARTBEAT_INTERVAL_MS = 15e3;
|
|
31873
|
+
function finalizeStuckSteps(run) {
|
|
31874
|
+
if (run.status !== "cancelled" && run.status !== "failed" && run.status !== "done") return false;
|
|
31875
|
+
const cancelled = run.status === "cancelled";
|
|
31876
|
+
const endedAt = run.endedAt ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
31877
|
+
let anyTouched = false;
|
|
31878
|
+
for (const stage of run.stages) {
|
|
31879
|
+
let stageTouched = false;
|
|
31880
|
+
for (const step of stage.steps) {
|
|
31881
|
+
if (step.status === "running") {
|
|
31882
|
+
delete step.phase;
|
|
31883
|
+
step.endedAt = endedAt;
|
|
31884
|
+
if (cancelled) {
|
|
31885
|
+
step.status = "cancelled";
|
|
31886
|
+
} else {
|
|
31887
|
+
step.status = "failed";
|
|
31888
|
+
step.error = "interrupted by daemon restart";
|
|
31889
|
+
}
|
|
31890
|
+
stageTouched = true;
|
|
31891
|
+
} else if (step.status === "pending") {
|
|
31892
|
+
if (cancelled) {
|
|
31893
|
+
step.status = "skipped";
|
|
31894
|
+
step.skipReason = "run-cancelled";
|
|
31895
|
+
} else {
|
|
31896
|
+
step.status = "failed";
|
|
31897
|
+
step.endedAt = endedAt;
|
|
31898
|
+
step.error = "interrupted by daemon restart";
|
|
31899
|
+
}
|
|
31900
|
+
stageTouched = true;
|
|
31901
|
+
}
|
|
31902
|
+
}
|
|
31903
|
+
if (stageTouched) {
|
|
31904
|
+
anyTouched = true;
|
|
31905
|
+
if (stage.status !== "done" && stage.status !== "failed") stage.status = "failed";
|
|
31906
|
+
}
|
|
31907
|
+
}
|
|
31908
|
+
return anyTouched;
|
|
31909
|
+
}
|
|
31148
31910
|
function loadRuns(persistPath, runsRoot) {
|
|
31149
31911
|
const result = /* @__PURE__ */ new Map();
|
|
31150
31912
|
if (!existsSync(persistPath)) return result;
|
|
@@ -31178,6 +31940,13 @@ function loadRuns(persistPath, runsRoot) {
|
|
|
31178
31940
|
data: { code: "host-interrupted", message: run.error }
|
|
31179
31941
|
});
|
|
31180
31942
|
}
|
|
31943
|
+
if (finalizeStuckSteps(run)) {
|
|
31944
|
+
anyMarkedInterrupted = true;
|
|
31945
|
+
createRunEventLog(run.runId, runsRoot).append({
|
|
31946
|
+
type: "run.steps-finalized",
|
|
31947
|
+
data: { code: "host-interrupted", status: run.status }
|
|
31948
|
+
});
|
|
31949
|
+
}
|
|
31181
31950
|
result.set(run.runId, { run, cancelled: false, abort: new AbortController(), stages: [] });
|
|
31182
31951
|
}
|
|
31183
31952
|
if (anyMarkedInterrupted) saveRuns(result, persistPath);
|
|
@@ -31265,7 +32034,7 @@ function createOnEscalate(state, persist) {
|
|
|
31265
32034
|
state.run.status = "awaiting-input";
|
|
31266
32035
|
persist();
|
|
31267
32036
|
try {
|
|
31268
|
-
return await new Promise((
|
|
32037
|
+
return await new Promise((resolve38, reject) => {
|
|
31269
32038
|
const timeoutMs = policy.timeoutMs ?? 3e5;
|
|
31270
32039
|
const timer = setTimeout(() => {
|
|
31271
32040
|
state.pendingResolve = void 0;
|
|
@@ -31277,7 +32046,7 @@ function createOnEscalate(state, persist) {
|
|
|
31277
32046
|
resolver: (response) => {
|
|
31278
32047
|
clearTimeout(timer);
|
|
31279
32048
|
state.pendingResolve = void 0;
|
|
31280
|
-
|
|
32049
|
+
resolve38(response);
|
|
31281
32050
|
}
|
|
31282
32051
|
};
|
|
31283
32052
|
});
|
|
@@ -31413,7 +32182,7 @@ async function executeRunWorkflow(state, runtimeWf, agents, signal, sessionEvent
|
|
|
31413
32182
|
// `resolveApproval` (wired to `workflow_escalation_resolve`'s approval
|
|
31414
32183
|
// form); the decision lands on the app ledger (`kind: "approval"`,
|
|
31415
32184
|
// `by: "human"`) per the trame rule.
|
|
31416
|
-
approve: (req) => new Promise((
|
|
32185
|
+
approve: (req) => new Promise((resolve38) => {
|
|
31417
32186
|
const approvalId = `wfappr_${randomUUID()}`;
|
|
31418
32187
|
const requestedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
31419
32188
|
state.run.status = "awaiting-approval";
|
|
@@ -31470,7 +32239,7 @@ async function executeRunWorkflow(state, runtimeWf, agents, signal, sessionEvent
|
|
|
31470
32239
|
runId: state.run.runId
|
|
31471
32240
|
}
|
|
31472
32241
|
});
|
|
31473
|
-
|
|
32242
|
+
resolve38(decision);
|
|
31474
32243
|
};
|
|
31475
32244
|
if (req.timeoutMs !== void 0) {
|
|
31476
32245
|
timer = setTimeout(() => {
|
|
@@ -31487,7 +32256,7 @@ async function executeRunWorkflow(state, runtimeWf, agents, signal, sessionEvent
|
|
|
31487
32256
|
// resumes through `resumeSuspend` (workflow_escalation_resolve's
|
|
31488
32257
|
// suspend form); after a daemon restart the run stays suspended and
|
|
31489
32258
|
// the pending entry is re-registered below.
|
|
31490
|
-
resume: (req) => new Promise((
|
|
32259
|
+
resume: (req) => new Promise((resolve38) => {
|
|
31491
32260
|
const since = (/* @__PURE__ */ new Date()).toISOString();
|
|
31492
32261
|
state.run.status = "awaiting-input";
|
|
31493
32262
|
state.run.awaitingSuspend = {
|
|
@@ -31525,7 +32294,7 @@ async function executeRunWorkflow(state, runtimeWf, agents, signal, sessionEvent
|
|
|
31525
32294
|
});
|
|
31526
32295
|
eventLog?.append({ stepId: req.stepId, type: "step.resumed", data: {} });
|
|
31527
32296
|
eventLog?.append({ type: "run.resumed", data: { stepId: req.stepId } });
|
|
31528
|
-
|
|
32297
|
+
resolve38(payload);
|
|
31529
32298
|
};
|
|
31530
32299
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
31531
32300
|
state.pendingSuspend = { stepId: req.stepId, resolve: finish };
|
|
@@ -31541,7 +32310,7 @@ async function executeRunWorkflow(state, runtimeWf, agents, signal, sessionEvent
|
|
|
31541
32310
|
// read of the turn's text). On resume, `execAgentStep` sends the
|
|
31542
32311
|
// validated payload back to the SAME session as its next prompt and
|
|
31543
32312
|
// re-applies the outcome rule — this hook only parks and resolves.
|
|
31544
|
-
onInputRequired: (req) => new Promise((
|
|
32313
|
+
onInputRequired: (req) => new Promise((resolve38) => {
|
|
31545
32314
|
const since = (/* @__PURE__ */ new Date()).toISOString();
|
|
31546
32315
|
const suspend = {
|
|
31547
32316
|
reason: "input-required",
|
|
@@ -31594,7 +32363,7 @@ async function executeRunWorkflow(state, runtimeWf, agents, signal, sessionEvent
|
|
|
31594
32363
|
});
|
|
31595
32364
|
eventLog?.append({ stepId: req.stepId, type: "step.resumed", data: {} });
|
|
31596
32365
|
eventLog?.append({ type: "run.resumed", data: { stepId: req.stepId } });
|
|
31597
|
-
|
|
32366
|
+
resolve38(payload);
|
|
31598
32367
|
};
|
|
31599
32368
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
31600
32369
|
state.pendingSuspend = { stepId: req.stepId, resolve: finish };
|
|
@@ -32610,10 +33379,10 @@ function unwrapMcpToolResult(result) {
|
|
|
32610
33379
|
if (envelope.structuredContent && typeof envelope.structuredContent === "object") {
|
|
32611
33380
|
return envelope.structuredContent;
|
|
32612
33381
|
}
|
|
32613
|
-
const
|
|
32614
|
-
if (typeof
|
|
33382
|
+
const text13 = envelope.content?.find((c) => c.type === "text")?.text;
|
|
33383
|
+
if (typeof text13 === "string") {
|
|
32615
33384
|
try {
|
|
32616
|
-
const parsed = JSON.parse(
|
|
33385
|
+
const parsed = JSON.parse(text13);
|
|
32617
33386
|
if (parsed && typeof parsed === "object") return parsed;
|
|
32618
33387
|
} catch {
|
|
32619
33388
|
}
|
|
@@ -32912,9 +33681,9 @@ Content-Type: ${file.contentType}\r
|
|
|
32912
33681
|
function toFetchBody(buffer) {
|
|
32913
33682
|
return new Uint8Array(buffer);
|
|
32914
33683
|
}
|
|
32915
|
-
function convertMarkdownToTelegram(
|
|
32916
|
-
if (!
|
|
32917
|
-
let result =
|
|
33684
|
+
function convertMarkdownToTelegram(text13) {
|
|
33685
|
+
if (!text13) return text13;
|
|
33686
|
+
let result = text13;
|
|
32918
33687
|
const codeBlocks = [];
|
|
32919
33688
|
const inlineCodes = [];
|
|
32920
33689
|
result = result.replace(/```([\s\S]*?)```/g, (_match, code) => {
|
|
@@ -33023,8 +33792,8 @@ function parseCron(schedule, timezone) {
|
|
|
33023
33792
|
function summarizeToolResult(result) {
|
|
33024
33793
|
if (result && typeof result === "object" && "content" in result) {
|
|
33025
33794
|
const r = result;
|
|
33026
|
-
const
|
|
33027
|
-
return { ok: !r.isError, summary:
|
|
33795
|
+
const text13 = (r.content ?? []).map((c) => c.text ?? "").join("\n").trim();
|
|
33796
|
+
return { ok: !r.isError, summary: text13 || (r.isError ? "tool call failed" : "ok") };
|
|
33028
33797
|
}
|
|
33029
33798
|
return { ok: true, summary: JSON.stringify(result ?? null) };
|
|
33030
33799
|
}
|
|
@@ -33334,14 +34103,14 @@ function createSupervisorTaskGateRunner(opts) {
|
|
|
33334
34103
|
const { supervisor, registry, workspace } = opts;
|
|
33335
34104
|
const exec = opts.runCommand ?? runCommand;
|
|
33336
34105
|
const settleTimeoutMs = opts.settleTimeoutMs ?? DEFAULT_VERIFY_SETTLE_TIMEOUT_MS;
|
|
33337
|
-
const waitForSettle = (policyId) => new Promise((
|
|
34106
|
+
const waitForSettle = (policyId) => new Promise((resolve38) => {
|
|
33338
34107
|
let settled = false;
|
|
33339
34108
|
const finish = (outcome) => {
|
|
33340
34109
|
if (settled) return;
|
|
33341
34110
|
settled = true;
|
|
33342
34111
|
clearTimeout(timer);
|
|
33343
34112
|
unsub();
|
|
33344
|
-
|
|
34113
|
+
resolve38(outcome);
|
|
33345
34114
|
};
|
|
33346
34115
|
const read = () => {
|
|
33347
34116
|
const state = supervisor.getStatus(policyId);
|
|
@@ -34606,7 +35375,7 @@ async function monitorSessionWait(opts) {
|
|
|
34606
35375
|
};
|
|
34607
35376
|
}
|
|
34608
35377
|
}
|
|
34609
|
-
return new Promise((
|
|
35378
|
+
return new Promise((resolve38) => {
|
|
34610
35379
|
const unsubs = [];
|
|
34611
35380
|
let settled = false;
|
|
34612
35381
|
const watcherLabel = callerScope?.ownerSessionId ? registry.get(callerScope.ownerSessionId)?.label ?? registry.get(callerScope.ownerSessionId)?.title : void 0;
|
|
@@ -34646,7 +35415,7 @@ async function monitorSessionWait(opts) {
|
|
|
34646
35415
|
ts: (/* @__PURE__ */ new Date()).toISOString()
|
|
34647
35416
|
});
|
|
34648
35417
|
}
|
|
34649
|
-
|
|
35418
|
+
resolve38(result);
|
|
34650
35419
|
};
|
|
34651
35420
|
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"];
|
|
34652
35421
|
const idSet = new Set(resolvedIds);
|
|
@@ -34687,13 +35456,13 @@ async function monitorPolicyWait(opts) {
|
|
|
34687
35456
|
if (isSettledStatus(initial.status)) {
|
|
34688
35457
|
return { timedOut: false, state: initial };
|
|
34689
35458
|
}
|
|
34690
|
-
return new Promise((
|
|
35459
|
+
return new Promise((resolve38) => {
|
|
34691
35460
|
let settled = false;
|
|
34692
35461
|
const timer = setTimeout(() => {
|
|
34693
35462
|
if (settled) return;
|
|
34694
35463
|
settled = true;
|
|
34695
35464
|
unsub();
|
|
34696
|
-
|
|
35465
|
+
resolve38({ timedOut: true });
|
|
34697
35466
|
}, timeoutMs);
|
|
34698
35467
|
const unsub = supervisor.onSettle((id) => {
|
|
34699
35468
|
if (id !== policyId) return;
|
|
@@ -34703,7 +35472,7 @@ async function monitorPolicyWait(opts) {
|
|
|
34703
35472
|
settled = true;
|
|
34704
35473
|
clearTimeout(timer);
|
|
34705
35474
|
unsub();
|
|
34706
|
-
|
|
35475
|
+
resolve38({ timedOut: false, state });
|
|
34707
35476
|
});
|
|
34708
35477
|
});
|
|
34709
35478
|
}
|
|
@@ -35819,8 +36588,8 @@ function registerOrchestrationTools(rawServer, opts) {
|
|
|
35819
36588
|
isError: true
|
|
35820
36589
|
};
|
|
35821
36590
|
}
|
|
35822
|
-
const
|
|
35823
|
-
if (
|
|
36591
|
+
const text13 = input.text ?? "";
|
|
36592
|
+
if (text13.length === 0 && (!input.attachments || input.attachments.length === 0)) {
|
|
35824
36593
|
return {
|
|
35825
36594
|
content: [
|
|
35826
36595
|
{ type: "text", text: JSON.stringify({ sent: false, bound: false, error: "text or attachments required" }) }
|
|
@@ -35834,7 +36603,7 @@ function registerOrchestrationTools(rawServer, opts) {
|
|
|
35834
36603
|
alias: input.alias,
|
|
35835
36604
|
source: input.source,
|
|
35836
36605
|
contactRef: input.contact_ref,
|
|
35837
|
-
text:
|
|
36606
|
+
text: text13,
|
|
35838
36607
|
attachments: input.attachments
|
|
35839
36608
|
},
|
|
35840
36609
|
{ mcpProxy, telegramCreds }
|
|
@@ -36265,7 +37034,7 @@ async function waitForSessionTerminal(registry, sessionId) {
|
|
|
36265
37034
|
for (let attempt = 0; attempt < MAX_SEQUENTIAL_POLLS; attempt++) {
|
|
36266
37035
|
const status = registry.get(sessionId)?.status;
|
|
36267
37036
|
if (isSessionTerminal(status)) return;
|
|
36268
|
-
await new Promise((
|
|
37037
|
+
await new Promise((resolve38) => setTimeout(resolve38, SEQUENTIAL_POLL_INTERVAL_MS));
|
|
36269
37038
|
}
|
|
36270
37039
|
}
|
|
36271
37040
|
var MODEL_ROUTED_ADAPTER = "claude-code";
|
|
@@ -36340,8 +37109,8 @@ async function loadAgentPromptDefaults(agentPath) {
|
|
|
36340
37109
|
function textResult2(body) {
|
|
36341
37110
|
return { content: [{ type: "text", text: JSON.stringify(body) }] };
|
|
36342
37111
|
}
|
|
36343
|
-
function errorResult2(
|
|
36344
|
-
return { content: [{ type: "text", text: JSON.stringify({ error:
|
|
37112
|
+
function errorResult2(text13) {
|
|
37113
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: text13 }) }], isError: true };
|
|
36345
37114
|
}
|
|
36346
37115
|
function notEnabled(tool) {
|
|
36347
37116
|
return errorResult2(
|
|
@@ -37430,8 +38199,8 @@ async function assertExternalPathRealInside(root, target) {
|
|
|
37430
38199
|
function textResult3(body) {
|
|
37431
38200
|
return { content: [{ type: "text", text: JSON.stringify(body) }] };
|
|
37432
38201
|
}
|
|
37433
|
-
function errorResult3(
|
|
37434
|
-
return { content: [{ type: "text", text: JSON.stringify({ error:
|
|
38202
|
+
function errorResult3(text13) {
|
|
38203
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: text13 }) }], isError: true };
|
|
37435
38204
|
}
|
|
37436
38205
|
function registerAppExternalTools(server, opts) {
|
|
37437
38206
|
const { appRegistry } = opts;
|
|
@@ -37627,18 +38396,18 @@ function getStringField(obj, keys) {
|
|
|
37627
38396
|
function buildMsg(body, ctx, fields) {
|
|
37628
38397
|
const source = ctx.sourceOverride ?? (typeof fields.source === "function" ? fields.source() : fields.source);
|
|
37629
38398
|
const contactRef = typeof fields.contactRef === "function" ? fields.contactRef() : fields.contactRef;
|
|
37630
|
-
const
|
|
38399
|
+
const text13 = typeof fields.text === "function" ? fields.text() : fields.text;
|
|
37631
38400
|
const displayName = typeof fields.displayName === "function" ? fields.displayName() : fields.displayName;
|
|
37632
38401
|
const surface = typeof fields.surface === "function" ? fields.surface() : fields.surface;
|
|
37633
38402
|
const providerMessageId = typeof fields.providerMessageId === "function" ? fields.providerMessageId() : fields.providerMessageId;
|
|
37634
38403
|
if (!source) return { ok: false, error: "missing_source" };
|
|
37635
38404
|
if (!contactRef) return { ok: false, error: "missing_contact_ref" };
|
|
37636
|
-
if (!
|
|
38405
|
+
if (!text13) return { ok: false, error: "no_text" };
|
|
37637
38406
|
const msg = {
|
|
37638
38407
|
alias: ctx.alias,
|
|
37639
38408
|
source,
|
|
37640
38409
|
contactRef,
|
|
37641
|
-
text:
|
|
38410
|
+
text: text13,
|
|
37642
38411
|
// Opt-in attribution — only set when the dialect knows a sender name,
|
|
37643
38412
|
// so 1:1 bindings keep receiving the raw text unprefixed.
|
|
37644
38413
|
...displayName ? { displayName } : {},
|
|
@@ -37657,18 +38426,18 @@ function normalizeAgentpush(body, ctx) {
|
|
|
37657
38426
|
}
|
|
37658
38427
|
const source = ctx.sourceOverride ?? (typeof envelope.channel === "string" ? envelope.channel : void 0);
|
|
37659
38428
|
const contactRef = typeof envelope.from === "string" ? envelope.from : void 0;
|
|
37660
|
-
const
|
|
38429
|
+
const text13 = typeof envelope.text === "string" ? envelope.text : void 0;
|
|
37661
38430
|
const providerMessageId = typeof envelope.messageId === "string" ? envelope.messageId : void 0;
|
|
37662
38431
|
if (!source) return { ok: false, error: "missing_source" };
|
|
37663
38432
|
if (!contactRef) return { ok: false, error: "missing_contact_ref" };
|
|
37664
|
-
if (!
|
|
38433
|
+
if (!text13) return { ok: false, error: "no_text" };
|
|
37665
38434
|
return {
|
|
37666
38435
|
ok: true,
|
|
37667
38436
|
msg: {
|
|
37668
38437
|
alias: ctx.alias,
|
|
37669
38438
|
source,
|
|
37670
38439
|
contactRef,
|
|
37671
|
-
text:
|
|
38440
|
+
text: text13,
|
|
37672
38441
|
messages: [envelope]
|
|
37673
38442
|
},
|
|
37674
38443
|
providerMessageId
|
|
@@ -37694,13 +38463,13 @@ function normalizeTelegram(body, ctx) {
|
|
|
37694
38463
|
}
|
|
37695
38464
|
const source = ctx.sourceOverride ?? "telegram";
|
|
37696
38465
|
const contactRef = chat && typeof chat.id === "number" ? String(chat.id) : void 0;
|
|
37697
|
-
const
|
|
38466
|
+
const text13 = typeof message.text === "string" ? message.text : void 0;
|
|
37698
38467
|
const providerMessageId = typeof message.message_id === "number" ? String(message.message_id) : void 0;
|
|
37699
38468
|
const displayName = typeof from?.first_name === "string" && from.first_name ? from.first_name : typeof from?.username === "string" && from.username ? from.username : void 0;
|
|
37700
38469
|
return buildMsg(body, ctx, {
|
|
37701
38470
|
source,
|
|
37702
38471
|
contactRef,
|
|
37703
|
-
text:
|
|
38472
|
+
text: text13,
|
|
37704
38473
|
displayName,
|
|
37705
38474
|
surface: "telegram",
|
|
37706
38475
|
providerMessageId
|
|
@@ -37726,14 +38495,14 @@ function normalizeWhatsapp(body, ctx) {
|
|
|
37726
38495
|
const typedMsg = msg;
|
|
37727
38496
|
const from = typeof typedMsg.from === "string" ? typedMsg.from : void 0;
|
|
37728
38497
|
const textObj = typeof typedMsg.text === "object" && typedMsg.text !== null ? typedMsg.text : void 0;
|
|
37729
|
-
const
|
|
38498
|
+
const text13 = typeof textObj?.body === "string" ? textObj.body : void 0;
|
|
37730
38499
|
const providerMessageId = typeof typedMsg.id === "string" ? typedMsg.id : void 0;
|
|
37731
38500
|
if (!from) continue;
|
|
37732
|
-
if (!
|
|
38501
|
+
if (!text13) continue;
|
|
37733
38502
|
return buildMsg(body, ctx, {
|
|
37734
38503
|
source: ctx.sourceOverride ?? from,
|
|
37735
38504
|
contactRef: from,
|
|
37736
|
-
text:
|
|
38505
|
+
text: text13,
|
|
37737
38506
|
providerMessageId
|
|
37738
38507
|
});
|
|
37739
38508
|
}
|
|
@@ -37766,12 +38535,12 @@ function normalizeSlack(body, ctx) {
|
|
|
37766
38535
|
}
|
|
37767
38536
|
const source = ctx.sourceOverride ?? (typeof event.channel === "string" ? event.channel : void 0);
|
|
37768
38537
|
const contactRef = typeof event.user === "string" ? event.user : void 0;
|
|
37769
|
-
const
|
|
38538
|
+
const text13 = typeof event.text === "string" ? event.text : void 0;
|
|
37770
38539
|
const providerMessageId = typeof event.ts === "string" || typeof event.ts === "number" ? String(event.ts) : void 0;
|
|
37771
38540
|
return buildMsg(body, ctx, {
|
|
37772
38541
|
source,
|
|
37773
38542
|
contactRef,
|
|
37774
|
-
text:
|
|
38543
|
+
text: text13,
|
|
37775
38544
|
providerMessageId
|
|
37776
38545
|
});
|
|
37777
38546
|
}
|
|
@@ -37781,12 +38550,12 @@ function normalizeGeneric(body, ctx) {
|
|
|
37781
38550
|
}
|
|
37782
38551
|
const source = ctx.sourceOverride ?? getStringField(body, ["channel", "source"]);
|
|
37783
38552
|
const contactRef = getStringField(body, ["from", "sender", "contact_ref"]);
|
|
37784
|
-
const
|
|
38553
|
+
const text13 = getStringField(body, ["text", "body", "message"]);
|
|
37785
38554
|
const providerMessageId = getStringField(body, ["provider_message_id", "message_id", "id"]);
|
|
37786
38555
|
return buildMsg(body, ctx, {
|
|
37787
38556
|
source,
|
|
37788
38557
|
contactRef,
|
|
37789
|
-
text:
|
|
38558
|
+
text: text13,
|
|
37790
38559
|
providerMessageId
|
|
37791
38560
|
});
|
|
37792
38561
|
}
|
|
@@ -37797,11 +38566,11 @@ function normalizeNative(body, ctx) {
|
|
|
37797
38566
|
const typed = body;
|
|
37798
38567
|
const source = ctx.sourceOverride ?? (typeof typed.source === "string" ? typed.source : void 0);
|
|
37799
38568
|
const contactRef = typeof typed.contact_ref === "string" ? typed.contact_ref : void 0;
|
|
37800
|
-
const
|
|
38569
|
+
const text13 = typeof typed.text === "string" ? typed.text : void 0;
|
|
37801
38570
|
return buildMsg(body, ctx, {
|
|
37802
38571
|
source,
|
|
37803
38572
|
contactRef,
|
|
37804
|
-
text:
|
|
38573
|
+
text: text13
|
|
37805
38574
|
});
|
|
37806
38575
|
}
|
|
37807
38576
|
function headerValue(headers, key) {
|
|
@@ -39950,8 +40719,11 @@ async function startHttpServer(opts) {
|
|
|
39950
40719
|
return;
|
|
39951
40720
|
}
|
|
39952
40721
|
if (path === "/user-presets" && req.method === "GET") {
|
|
40722
|
+
const includeRecent = new URL(req.url ?? "/", "http://localhost").searchParams.get("includeRecent") === "1";
|
|
40723
|
+
const presets = await listUserPresets();
|
|
40724
|
+
const recent = includeRecent ? deriveRecentSpawnConfigs(opts.sessions?.list() ?? []) : void 0;
|
|
39953
40725
|
res.writeHead(200, { "content-type": "application/json" });
|
|
39954
|
-
res.end(JSON.stringify({ presets
|
|
40726
|
+
res.end(JSON.stringify({ presets, ...recent ? { recent } : {} }));
|
|
39955
40727
|
return;
|
|
39956
40728
|
}
|
|
39957
40729
|
if (path === "/user-presets" && req.method === "POST") {
|
|
@@ -40096,6 +40868,16 @@ async function startHttpServer(opts) {
|
|
|
40096
40868
|
);
|
|
40097
40869
|
if (handled) return;
|
|
40098
40870
|
}
|
|
40871
|
+
if (path === "/device-spawn" || path.startsWith("/device-spawn/")) {
|
|
40872
|
+
const handled = await handleDeviceSpawn(
|
|
40873
|
+
req,
|
|
40874
|
+
res,
|
|
40875
|
+
path,
|
|
40876
|
+
opts.port,
|
|
40877
|
+
opts.deviceSpawnAllow === true
|
|
40878
|
+
);
|
|
40879
|
+
if (handled) return;
|
|
40880
|
+
}
|
|
40099
40881
|
if (opts.remote && path.startsWith("/remote")) {
|
|
40100
40882
|
const handled = await handleRemoteControl(req, res, path, opts.remote);
|
|
40101
40883
|
if (handled) return;
|
|
@@ -40299,16 +41081,16 @@ async function startHttpServer(opts) {
|
|
|
40299
41081
|
});
|
|
40300
41082
|
});
|
|
40301
41083
|
const bind = opts.bind ?? "127.0.0.1";
|
|
40302
|
-
await new Promise((
|
|
41084
|
+
await new Promise((resolve38, reject) => {
|
|
40303
41085
|
server.once("error", reject);
|
|
40304
|
-
server.listen(opts.port, bind, () =>
|
|
41086
|
+
server.listen(opts.port, bind, () => resolve38());
|
|
40305
41087
|
});
|
|
40306
41088
|
return {
|
|
40307
41089
|
url: `http://${bind}:${opts.port}`,
|
|
40308
41090
|
async stop() {
|
|
40309
41091
|
wss.close();
|
|
40310
41092
|
server.closeAllConnections();
|
|
40311
|
-
await new Promise((
|
|
41093
|
+
await new Promise((resolve38) => server.close(() => resolve38()));
|
|
40312
41094
|
}
|
|
40313
41095
|
};
|
|
40314
41096
|
}
|
|
@@ -40517,9 +41299,9 @@ async function currentTranscriptSeq(id) {
|
|
|
40517
41299
|
let stream;
|
|
40518
41300
|
try {
|
|
40519
41301
|
stream = createReadStream(filePath, { encoding: "utf8" });
|
|
40520
|
-
await new Promise((
|
|
41302
|
+
await new Promise((resolve38, reject) => {
|
|
40521
41303
|
stream.once("error", reject);
|
|
40522
|
-
stream.once("open",
|
|
41304
|
+
stream.once("open", resolve38);
|
|
40523
41305
|
});
|
|
40524
41306
|
} catch (err) {
|
|
40525
41307
|
if (err.code === "ENOENT") return 0;
|
|
@@ -40544,9 +41326,9 @@ async function* transcriptDiskRecords(id) {
|
|
|
40544
41326
|
let stream;
|
|
40545
41327
|
try {
|
|
40546
41328
|
stream = createReadStream(filePath, { encoding: "utf8" });
|
|
40547
|
-
await new Promise((
|
|
41329
|
+
await new Promise((resolve38, reject) => {
|
|
40548
41330
|
stream.once("error", reject);
|
|
40549
|
-
stream.once("open",
|
|
41331
|
+
stream.once("open", resolve38);
|
|
40550
41332
|
});
|
|
40551
41333
|
} catch (err) {
|
|
40552
41334
|
if (err.code === "ENOENT") return;
|
|
@@ -40592,6 +41374,8 @@ function buildSpawnSessionHttpArgs(b, adapter, preset) {
|
|
|
40592
41374
|
const browserField = browser !== void 0 ? { browser } : {};
|
|
40593
41375
|
const sentinelOptOut = typeof b.sentinel === "boolean" ? b.sentinel : b.sentinel === "true" ? true : b.sentinel === "false" ? false : void 0;
|
|
40594
41376
|
const sentinelField = sentinelOptOut === false ? { sentinel: false } : {};
|
|
41377
|
+
const inferenceParsed = b.inference !== void 0 ? parseInferenceField(b.inference) : void 0;
|
|
41378
|
+
const inferenceField = inferenceParsed !== void 0 ? { inference: inferenceParsed } : {};
|
|
40595
41379
|
return {
|
|
40596
41380
|
adapter,
|
|
40597
41381
|
...typeof b.origin === "string" && b.origin.length > 0 ? { origin: b.origin } : {},
|
|
@@ -40610,6 +41394,7 @@ function buildSpawnSessionHttpArgs(b, adapter, preset) {
|
|
|
40610
41394
|
const parsed = parseAccessField(b.access);
|
|
40611
41395
|
return parsed !== void 0 ? { access: parsed } : {};
|
|
40612
41396
|
})() : {},
|
|
41397
|
+
...inferenceField,
|
|
40613
41398
|
...typeof b.posture === "string" && b.posture.length > 0 ? { posture: parsePostureInput(b.posture) } : {},
|
|
40614
41399
|
...typeof b.contextProfile === "string" && b.contextProfile.length > 0 ? { contextProfile: b.contextProfile } : {},
|
|
40615
41400
|
...preset ? { preset } : {},
|
|
@@ -40836,6 +41621,20 @@ function parseAccessField(raw) {
|
|
|
40836
41621
|
const profileRef = value.profileRef;
|
|
40837
41622
|
return typeof profileRef === "string" && profileRef.length > 0 ? { profileRef } : {};
|
|
40838
41623
|
}
|
|
41624
|
+
function parseInferenceField(raw) {
|
|
41625
|
+
const value = typeof raw === "string" ? tryParseJson(raw) : raw;
|
|
41626
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
41627
|
+
const obj = value;
|
|
41628
|
+
const force = typeof obj.force === "boolean" ? obj.force : obj.force === "true" ? true : obj.force === "false" ? false : void 0;
|
|
41629
|
+
const headroomPctRaw = typeof obj.headroomPct === "string" ? Number(obj.headroomPct) : obj.headroomPct;
|
|
41630
|
+
const headroomPct = typeof headroomPctRaw === "number" && Number.isFinite(headroomPctRaw) && headroomPctRaw >= 0 && headroomPctRaw <= 95 ? headroomPctRaw : void 0;
|
|
41631
|
+
return {
|
|
41632
|
+
...typeof obj.endpoint === "string" && obj.endpoint.length > 0 ? { endpoint: obj.endpoint } : {},
|
|
41633
|
+
...typeof obj.model === "string" && obj.model.length > 0 ? { model: obj.model } : {},
|
|
41634
|
+
...force !== void 0 ? { force } : {},
|
|
41635
|
+
...headroomPct !== void 0 ? { headroomPct } : {}
|
|
41636
|
+
};
|
|
41637
|
+
}
|
|
40839
41638
|
function parseRestartPolicyField(raw) {
|
|
40840
41639
|
const value = typeof raw === "string" ? tryParseJson(raw) : raw;
|
|
40841
41640
|
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
@@ -40870,8 +41669,8 @@ function parseSandboxField(raw) {
|
|
|
40870
41669
|
return parsed.data;
|
|
40871
41670
|
}
|
|
40872
41671
|
function parseAppServeField(raw) {
|
|
40873
|
-
const
|
|
40874
|
-
const record2 = parseJsonRecordText(
|
|
41672
|
+
const text13 = typeof raw === "string" ? raw : JSON.stringify(raw);
|
|
41673
|
+
const record2 = parseJsonRecordText(text13);
|
|
40875
41674
|
if (!record2) return void 0;
|
|
40876
41675
|
const dir = record2.dir;
|
|
40877
41676
|
const port = record2.port;
|
|
@@ -41095,7 +41894,7 @@ async function handleSessions(req, res, path, registry, resolveAgentAdapter, pty
|
|
|
41095
41894
|
json2(400, { error: "preset_not_found", message: `No user preset "${presetId}" found.` });
|
|
41096
41895
|
return true;
|
|
41097
41896
|
}
|
|
41098
|
-
const adapter = typeof b.adapter === "string" ? b.adapter : typeof b.harness === "string" ? b.harness : preset?.adapter ?? preset?.harness ?? "";
|
|
41897
|
+
const adapter = typeof b.adapter === "string" ? b.adapter : typeof b.harness === "string" ? b.harness : preset?.adapter ?? preset?.harness ?? (b.inference ? "pi" : "");
|
|
41099
41898
|
if (!adapter) {
|
|
41100
41899
|
json2(400, { error: "missing_adapter" });
|
|
41101
41900
|
return true;
|
|
@@ -41162,7 +41961,7 @@ async function handleSessions(req, res, path, registry, resolveAgentAdapter, pty
|
|
|
41162
41961
|
json2(400, { error: "preset_not_found", message: `No user preset "${presetId}" found.` });
|
|
41163
41962
|
return true;
|
|
41164
41963
|
}
|
|
41165
|
-
const adapter = typeof b.adapter === "string" ? b.adapter : typeof b.harness === "string" ? b.harness : preset?.adapter ?? preset?.harness ?? "";
|
|
41964
|
+
const adapter = typeof b.adapter === "string" ? b.adapter : typeof b.harness === "string" ? b.harness : preset?.adapter ?? preset?.harness ?? (b.inference ? "pi" : "");
|
|
41166
41965
|
if (!adapter) {
|
|
41167
41966
|
json2(400, { error: "missing_adapter" });
|
|
41168
41967
|
return true;
|
|
@@ -41676,8 +42475,8 @@ async function handleSessions(req, res, path, registry, resolveAgentAdapter, pty
|
|
|
41676
42475
|
return true;
|
|
41677
42476
|
}
|
|
41678
42477
|
const body = await readJsonBody(req);
|
|
41679
|
-
const
|
|
41680
|
-
if (typeof
|
|
42478
|
+
const text13 = body?.text;
|
|
42479
|
+
if (typeof text13 !== "string") {
|
|
41681
42480
|
json2(400, { error: "missing_text", message: "Body `text` must be a string." });
|
|
41682
42481
|
return true;
|
|
41683
42482
|
}
|
|
@@ -41690,8 +42489,8 @@ async function handleSessions(req, res, path, registry, resolveAgentAdapter, pty
|
|
|
41690
42489
|
}
|
|
41691
42490
|
const enter = body?.enter !== false;
|
|
41692
42491
|
let ok3 = true;
|
|
41693
|
-
if (
|
|
41694
|
-
const toWrite = applyBracketedPasteWrap(registry, id2,
|
|
42492
|
+
if (text13.length > 0) {
|
|
42493
|
+
const toWrite = applyBracketedPasteWrap(registry, id2, text13);
|
|
41695
42494
|
ok3 = registry.writeTerminalInput(id2, toWrite) && ok3;
|
|
41696
42495
|
}
|
|
41697
42496
|
if (enter) ok3 = registry.writeTerminalInput(id2, "\r") && ok3;
|
|
@@ -41987,8 +42786,8 @@ async function handleSessions(req, res, path, registry, resolveAgentAdapter, pty
|
|
|
41987
42786
|
...source ? { source } : {}
|
|
41988
42787
|
});
|
|
41989
42788
|
if (result.content.startsWith("Error:")) {
|
|
41990
|
-
const
|
|
41991
|
-
json2(
|
|
42789
|
+
const isNotFound2 = result.content.includes("not found in registry") || result.content.includes("not found") || result.content.includes("not found");
|
|
42790
|
+
json2(isNotFound2 ? 404 : 422, {
|
|
41992
42791
|
error: "export_failed",
|
|
41993
42792
|
message: result.content,
|
|
41994
42793
|
sessionId: rawIdOrName,
|
|
@@ -42018,9 +42817,9 @@ async function handleSessions(req, res, path, registry, resolveAgentAdapter, pty
|
|
|
42018
42817
|
let fileStream;
|
|
42019
42818
|
try {
|
|
42020
42819
|
fileStream = createReadStream(filePath, { encoding: "utf8" });
|
|
42021
|
-
await new Promise((
|
|
42820
|
+
await new Promise((resolve38, reject) => {
|
|
42022
42821
|
fileStream.once("error", reject);
|
|
42023
|
-
fileStream.once("open",
|
|
42822
|
+
fileStream.once("open", resolve38);
|
|
42024
42823
|
});
|
|
42025
42824
|
} catch (err) {
|
|
42026
42825
|
const code = err.code;
|
|
@@ -42076,9 +42875,9 @@ async function handleSessions(req, res, path, registry, resolveAgentAdapter, pty
|
|
|
42076
42875
|
let fileStream;
|
|
42077
42876
|
try {
|
|
42078
42877
|
fileStream = createReadStream(filePath, { encoding: "utf8" });
|
|
42079
|
-
await new Promise((
|
|
42878
|
+
await new Promise((resolve38, reject) => {
|
|
42080
42879
|
fileStream.once("error", reject);
|
|
42081
|
-
fileStream.once("open",
|
|
42880
|
+
fileStream.once("open", resolve38);
|
|
42082
42881
|
});
|
|
42083
42882
|
} catch (err) {
|
|
42084
42883
|
const code = err.code;
|
|
@@ -42541,7 +43340,7 @@ async function handleDeviceInference(req, res, path, registry, shareEnabled) {
|
|
|
42541
43340
|
});
|
|
42542
43341
|
return true;
|
|
42543
43342
|
}
|
|
42544
|
-
const
|
|
43343
|
+
const HOP_BY_HOP2 = /* @__PURE__ */ new Set([
|
|
42545
43344
|
"connection",
|
|
42546
43345
|
"keep-alive",
|
|
42547
43346
|
"transfer-encoding",
|
|
@@ -42552,7 +43351,7 @@ async function handleDeviceInference(req, res, path, registry, shareEnabled) {
|
|
|
42552
43351
|
]);
|
|
42553
43352
|
const headers = {};
|
|
42554
43353
|
for (const [k, v] of Object.entries(req.headers)) {
|
|
42555
|
-
if (v === void 0 ||
|
|
43354
|
+
if (v === void 0 || HOP_BY_HOP2.has(k.toLowerCase())) continue;
|
|
42556
43355
|
headers[k] = Array.isArray(v) ? v.join(", ") : v;
|
|
42557
43356
|
}
|
|
42558
43357
|
let body;
|
|
@@ -42596,6 +43395,70 @@ async function handleDeviceInference(req, res, path, registry, shareEnabled) {
|
|
|
42596
43395
|
Readable.fromWeb(upstreamRes.body).pipe(res);
|
|
42597
43396
|
return true;
|
|
42598
43397
|
}
|
|
43398
|
+
async function handleDeviceSpawn(req, res, path, port, spawnAllowed) {
|
|
43399
|
+
const json2 = (status, body2) => {
|
|
43400
|
+
res.writeHead(status, { "content-type": "application/json" });
|
|
43401
|
+
res.end(JSON.stringify(body2));
|
|
43402
|
+
};
|
|
43403
|
+
if (!path.startsWith("/device-spawn/") && path !== "/device-spawn") return false;
|
|
43404
|
+
if (req.headers[HOST_SCOPE_HEADER] !== "1") {
|
|
43405
|
+
json2(403, {
|
|
43406
|
+
error: "host_scope_required",
|
|
43407
|
+
message: "device-spawn is reachable only over a host-scoped pairing (the far end must have registered this daemon via `agentproto pair offer --host` + `agentproto devices add`)"
|
|
43408
|
+
});
|
|
43409
|
+
return true;
|
|
43410
|
+
}
|
|
43411
|
+
if (!spawnAllowed) {
|
|
43412
|
+
json2(403, {
|
|
43413
|
+
error: "spawn_disabled",
|
|
43414
|
+
message: "this host has not opted in \u2014 run `agentproto devices allow-spawn on` on it"
|
|
43415
|
+
});
|
|
43416
|
+
return true;
|
|
43417
|
+
}
|
|
43418
|
+
const subpath = path.slice("/device-spawn".length) || "/";
|
|
43419
|
+
const headers = {};
|
|
43420
|
+
for (const [k, v] of Object.entries(req.headers)) {
|
|
43421
|
+
if (v === void 0) continue;
|
|
43422
|
+
const lower = k.toLowerCase();
|
|
43423
|
+
if (lower === "connection" || lower === "keep-alive" || lower === "transfer-encoding") continue;
|
|
43424
|
+
if (lower === "upgrade" || lower === "host" || lower === "content-length") continue;
|
|
43425
|
+
if (lower === HOST_SCOPE_HEADER) continue;
|
|
43426
|
+
headers[k] = Array.isArray(v) ? v.join(", ") : v;
|
|
43427
|
+
}
|
|
43428
|
+
let body;
|
|
43429
|
+
if ((req.method ?? "GET") !== "GET" && (req.method ?? "GET") !== "HEAD") {
|
|
43430
|
+
const chunks = [];
|
|
43431
|
+
for await (const chunk of req) chunks.push(chunk);
|
|
43432
|
+
body = Buffer.concat(chunks);
|
|
43433
|
+
}
|
|
43434
|
+
let upstreamRes;
|
|
43435
|
+
try {
|
|
43436
|
+
upstreamRes = await fetch(`http://127.0.0.1:${port}${subpath}`, {
|
|
43437
|
+
method: req.method ?? "GET",
|
|
43438
|
+
headers,
|
|
43439
|
+
...body ? { body } : {}
|
|
43440
|
+
// Cast rationale — see `handleDeviceInference`'s identical cast above.
|
|
43441
|
+
});
|
|
43442
|
+
} catch (err) {
|
|
43443
|
+
json2(502, {
|
|
43444
|
+
error: "self_proxy_unreachable",
|
|
43445
|
+
message: err instanceof Error ? err.message : String(err)
|
|
43446
|
+
});
|
|
43447
|
+
return true;
|
|
43448
|
+
}
|
|
43449
|
+
const resHeaders = {};
|
|
43450
|
+
upstreamRes.headers.forEach((v, k) => {
|
|
43451
|
+
if (k === "content-length" || k === "connection") return;
|
|
43452
|
+
resHeaders[k] = v;
|
|
43453
|
+
});
|
|
43454
|
+
res.writeHead(upstreamRes.status, resHeaders);
|
|
43455
|
+
if (!upstreamRes.body) {
|
|
43456
|
+
res.end();
|
|
43457
|
+
return true;
|
|
43458
|
+
}
|
|
43459
|
+
Readable.fromWeb(upstreamRes.body).pipe(res);
|
|
43460
|
+
return true;
|
|
43461
|
+
}
|
|
42599
43462
|
async function handleRemoteControl(req, res, path, controller) {
|
|
42600
43463
|
const json2 = (status, body) => {
|
|
42601
43464
|
res.writeHead(status, { "content-type": "application/json" });
|
|
@@ -43208,7 +44071,7 @@ async function handleDevices(req, res, path, registry, hostRegistry, joinTokens)
|
|
|
43208
44071
|
});
|
|
43209
44072
|
return true;
|
|
43210
44073
|
}
|
|
43211
|
-
const
|
|
44074
|
+
const HOP_BY_HOP2 = /* @__PURE__ */ new Set([
|
|
43212
44075
|
"connection",
|
|
43213
44076
|
"keep-alive",
|
|
43214
44077
|
"transfer-encoding",
|
|
@@ -43220,7 +44083,7 @@ async function handleDevices(req, res, path, registry, hostRegistry, joinTokens)
|
|
|
43220
44083
|
]);
|
|
43221
44084
|
const headers = {};
|
|
43222
44085
|
for (const [k, v] of Object.entries(req.headers)) {
|
|
43223
|
-
if (v === void 0 ||
|
|
44086
|
+
if (v === void 0 || HOP_BY_HOP2.has(k.toLowerCase())) continue;
|
|
43224
44087
|
headers[k] = Array.isArray(v) ? v.join(", ") : v;
|
|
43225
44088
|
}
|
|
43226
44089
|
const chunks = [];
|
|
@@ -43296,13 +44159,28 @@ async function handleDevices(req, res, path, registry, hostRegistry, joinTokens)
|
|
|
43296
44159
|
json2(200, { ok: true, revoked: target });
|
|
43297
44160
|
return true;
|
|
43298
44161
|
}
|
|
44162
|
+
function writeDeviceSessionsResponse(res2) {
|
|
44163
|
+
if (!res2.stale) {
|
|
44164
|
+
res.writeHead(res2.status, { "content-type": "application/json" });
|
|
44165
|
+
res.end(Buffer.from(res2.body));
|
|
44166
|
+
return;
|
|
44167
|
+
}
|
|
44168
|
+
let merged;
|
|
44169
|
+
try {
|
|
44170
|
+
const parsed = JSON.parse(Buffer.from(res2.body).toString("utf8"));
|
|
44171
|
+
merged = parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) ? { ...parsed, stale: true, capturedAt: res2.capturedAt } : { stale: true, capturedAt: res2.capturedAt, value: parsed };
|
|
44172
|
+
} catch {
|
|
44173
|
+
merged = { stale: true, capturedAt: res2.capturedAt };
|
|
44174
|
+
}
|
|
44175
|
+
res.writeHead(res2.status, { "content-type": "application/json" });
|
|
44176
|
+
res.end(JSON.stringify(merged));
|
|
44177
|
+
}
|
|
43299
44178
|
const deviceSessionsMatch = path.match(/^\/devices\/([^/]+)\/sessions$/);
|
|
43300
44179
|
if (deviceSessionsMatch && req.method === "GET") {
|
|
43301
44180
|
const target = decodeURIComponent(deviceSessionsMatch[1] ?? "");
|
|
43302
44181
|
try {
|
|
43303
44182
|
const res2 = await devices.forwardHttp(target, { method: "GET", path: "/sessions" });
|
|
43304
|
-
|
|
43305
|
-
res.end(Buffer.from(res2.body));
|
|
44183
|
+
writeDeviceSessionsResponse(res2);
|
|
43306
44184
|
} catch (err) {
|
|
43307
44185
|
json2(502, { error: "forward_failed", message: err instanceof Error ? err.message : String(err) });
|
|
43308
44186
|
}
|
|
@@ -43316,8 +44194,7 @@ async function handleDevices(req, res, path, registry, hostRegistry, joinTokens)
|
|
|
43316
44194
|
const qs = reqUrl.includes("?") ? reqUrl.slice(reqUrl.indexOf("?")) : "";
|
|
43317
44195
|
try {
|
|
43318
44196
|
const res2 = await devices.forwardHttp(target, { method: "GET", path: `/sessions/${sessionId}/output${qs}` });
|
|
43319
|
-
|
|
43320
|
-
res.end(Buffer.from(res2.body));
|
|
44197
|
+
writeDeviceSessionsResponse(res2);
|
|
43321
44198
|
} catch (err) {
|
|
43322
44199
|
json2(502, { error: "forward_failed", message: err instanceof Error ? err.message : String(err) });
|
|
43323
44200
|
}
|
|
@@ -43684,7 +44561,8 @@ function requestHttpBaseUrl(req) {
|
|
|
43684
44561
|
}
|
|
43685
44562
|
async function handleAppUiPage(req, res, appId, appRegistry, frameAncestors) {
|
|
43686
44563
|
const app = appRegistry.getApp(appId);
|
|
43687
|
-
const
|
|
44564
|
+
const sessionIdParam = new URL(req.url ?? "/", "http://localhost").searchParams.get("sessionId") ?? void 0;
|
|
44565
|
+
const builtin = app?.ui ? void 0 : resolveBuiltinPanelUi(appId, requestHttpBaseUrl(req), sessionIdParam);
|
|
43688
44566
|
if (!app?.ui && !builtin) {
|
|
43689
44567
|
res.writeHead(404, { "content-type": "application/json" });
|
|
43690
44568
|
res.end(JSON.stringify({ error: `app "${appId}" is not installed or has no UI.` }));
|
|
@@ -43742,7 +44620,7 @@ async function handleAppUiPage(req, res, appId, appRegistry, frameAncestors) {
|
|
|
43742
44620
|
} else {
|
|
43743
44621
|
const html = builtin.html;
|
|
43744
44622
|
rep = await appUiRepresentations.get(
|
|
43745
|
-
`builtin\0${appId}\0${baseUrl}\0${variant ?? ""}`,
|
|
44623
|
+
`builtin\0${appId}\0${baseUrl}\0${variant ?? ""}\0${sessionIdParam ?? ""}`,
|
|
43746
44624
|
html,
|
|
43747
44625
|
() => appUiPageRepresentation(html, baseUrl, variant)
|
|
43748
44626
|
);
|
|
@@ -46100,8 +46978,8 @@ function isRecord8(v) {
|
|
|
46100
46978
|
function errorMessage(err) {
|
|
46101
46979
|
return err instanceof Error ? err.message : String(err);
|
|
46102
46980
|
}
|
|
46103
|
-
function errorResult4(
|
|
46104
|
-
return { isError: true, content: [{ type: "text", text:
|
|
46981
|
+
function errorResult4(text13) {
|
|
46982
|
+
return { isError: true, content: [{ type: "text", text: text13 }] };
|
|
46105
46983
|
}
|
|
46106
46984
|
init_mcp_credential_deps();
|
|
46107
46985
|
init_mcp_imports();
|
|
@@ -46441,6 +47319,10 @@ var sameRubrics = (a, b) => {
|
|
|
46441
47319
|
const norm = (r) => r.map((d) => `${d.check}\0${d.path}\0${d.sha256}`).sort().join("\n");
|
|
46442
47320
|
return norm(a) === norm(b);
|
|
46443
47321
|
};
|
|
47322
|
+
var samePacks = (a, b) => {
|
|
47323
|
+
const norm = (r) => r.map((d) => `${d.ref}\0${d.id}\0${d.version}\0${d.sha256}`).sort().join("\n");
|
|
47324
|
+
return norm(a) === norm(b);
|
|
47325
|
+
};
|
|
46444
47326
|
async function readEntry(path) {
|
|
46445
47327
|
try {
|
|
46446
47328
|
const parsed = JSON.parse(await readFile(path, "utf8"));
|
|
@@ -46471,6 +47353,21 @@ function createReviewLedger(opts = {}) {
|
|
|
46471
47353
|
const annotationLocks = /* @__PURE__ */ new Map();
|
|
46472
47354
|
const requesterIndex = /* @__PURE__ */ new Map();
|
|
46473
47355
|
let requesterIndexBuilt = false;
|
|
47356
|
+
const entryCache = /* @__PURE__ */ new Map();
|
|
47357
|
+
async function readEntryCached(path) {
|
|
47358
|
+
let s;
|
|
47359
|
+
try {
|
|
47360
|
+
s = await stat(path);
|
|
47361
|
+
} catch {
|
|
47362
|
+
entryCache.delete(path);
|
|
47363
|
+
return void 0;
|
|
47364
|
+
}
|
|
47365
|
+
const cached = entryCache.get(path);
|
|
47366
|
+
if (cached && cached.mtimeMs === s.mtimeMs && cached.size === s.size) return cached.entry;
|
|
47367
|
+
const entry = await readEntry(path);
|
|
47368
|
+
entryCache.set(path, { mtimeMs: s.mtimeMs, size: s.size, entry });
|
|
47369
|
+
return entry;
|
|
47370
|
+
}
|
|
46474
47371
|
function indexRequester(entry) {
|
|
46475
47372
|
const sessionId = entry.attestation.requester?.sessionId;
|
|
46476
47373
|
if (!sessionId) return;
|
|
@@ -46502,7 +47399,7 @@ function createReviewLedger(opts = {}) {
|
|
|
46502
47399
|
for (const manifestDir of await subdirs(repoDir)) {
|
|
46503
47400
|
for (const bindingDir of await subdirs(manifestDir)) {
|
|
46504
47401
|
for (const file of await jsonFiles(bindingDir)) {
|
|
46505
|
-
const entry = await
|
|
47402
|
+
const entry = await readEntryCached(file);
|
|
46506
47403
|
if (!entry) continue;
|
|
46507
47404
|
const a = entry.attestation;
|
|
46508
47405
|
if (filter.repoRemote !== void 0 && a.target.repoRemote !== filter.repoRemote) continue;
|
|
@@ -46520,23 +47417,25 @@ function createReviewLedger(opts = {}) {
|
|
|
46520
47417
|
root,
|
|
46521
47418
|
async put(entry) {
|
|
46522
47419
|
const path = keyPath(root, ledgerKeyOf(entry.attestation));
|
|
46523
|
-
const previous = await
|
|
47420
|
+
const previous = await readEntryCached(path);
|
|
46524
47421
|
const prevSessionId = previous?.attestation.requester?.sessionId;
|
|
46525
47422
|
const nextSessionId = entry.attestation.requester?.sessionId;
|
|
46526
47423
|
if (prevSessionId && prevSessionId !== nextSessionId) requesterIndex.get(prevSessionId)?.delete(path);
|
|
46527
47424
|
await writeJsonAtomic(path, entry);
|
|
47425
|
+
entryCache.delete(path);
|
|
46528
47426
|
indexRequester(entry);
|
|
46529
47427
|
return path;
|
|
46530
47428
|
},
|
|
46531
47429
|
async get(key) {
|
|
46532
|
-
return
|
|
47430
|
+
return readEntryCached(keyPath(root, key));
|
|
46533
47431
|
},
|
|
46534
|
-
async lookupCached(key, rubrics) {
|
|
46535
|
-
const entry = await
|
|
47432
|
+
async lookupCached(key, rubrics, packs = []) {
|
|
47433
|
+
const entry = await readEntryCached(keyPath(root, key));
|
|
46536
47434
|
if (!entry) return void 0;
|
|
46537
47435
|
const a = entry.attestation;
|
|
46538
47436
|
if (a.verdict === "incomplete" || a.dirty) return void 0;
|
|
46539
47437
|
if (!sameRubrics(a.rubrics ?? [], rubrics)) return void 0;
|
|
47438
|
+
if (!samePacks(a.packs ?? [], packs)) return void 0;
|
|
46540
47439
|
return entry;
|
|
46541
47440
|
},
|
|
46542
47441
|
async findByRunId(runId) {
|
|
@@ -46549,7 +47448,7 @@ function createReviewLedger(opts = {}) {
|
|
|
46549
47448
|
for (const sessionId of filter.requesterSessionIds) {
|
|
46550
47449
|
for (const path of requesterIndex.get(sessionId) ?? []) paths.add(path);
|
|
46551
47450
|
}
|
|
46552
|
-
const entries = (await Promise.all([...paths].map(
|
|
47451
|
+
const entries = (await Promise.all([...paths].map(readEntryCached))).filter((e) => e !== void 0);
|
|
46553
47452
|
const { requesterSessionIds: _requesterSessionIds, ...rest } = filter;
|
|
46554
47453
|
const filtered = entries.filter((entry) => {
|
|
46555
47454
|
const a = entry.attestation;
|
|
@@ -46579,6 +47478,311 @@ function createReviewLedger(opts = {}) {
|
|
|
46579
47478
|
}
|
|
46580
47479
|
};
|
|
46581
47480
|
}
|
|
47481
|
+
function isAncestor(root, ancestor, descendant) {
|
|
47482
|
+
if (ancestor === descendant) return Promise.resolve(false);
|
|
47483
|
+
return new Promise((resolvePromise) => {
|
|
47484
|
+
execFile("git", ["merge-base", "--is-ancestor", ancestor, descendant], { cwd: root }, (err) => {
|
|
47485
|
+
resolvePromise(!err);
|
|
47486
|
+
});
|
|
47487
|
+
});
|
|
47488
|
+
}
|
|
47489
|
+
async function findComposeCandidate(input) {
|
|
47490
|
+
const entries = await input.ledger.list({
|
|
47491
|
+
repoRemote: input.repoRemote,
|
|
47492
|
+
manifestSha: input.manifestSha,
|
|
47493
|
+
binding: input.binding
|
|
47494
|
+
});
|
|
47495
|
+
for (const entry of entries) {
|
|
47496
|
+
const a = entry.attestation;
|
|
47497
|
+
if (a.verdict !== "pass" || a.dirty) continue;
|
|
47498
|
+
if (a.target.baseSha !== input.baseSha) continue;
|
|
47499
|
+
const lane = a.lanes.find((l) => l.id === input.checkId);
|
|
47500
|
+
if (!lane || lane.status !== "pass") continue;
|
|
47501
|
+
const rubric = (a.rubrics ?? []).find((r) => r.check === input.checkId);
|
|
47502
|
+
if (!rubric || rubric.sha256 !== input.rubricSha256) continue;
|
|
47503
|
+
if (input.packDigest) {
|
|
47504
|
+
const priorPack = (a.packs ?? []).find((p) => p.id === input.packDigest.id);
|
|
47505
|
+
if (!priorPack || priorPack.sha256 !== input.packDigest.sha256) continue;
|
|
47506
|
+
}
|
|
47507
|
+
if (!await isAncestor(input.repoRoot, a.target.headSha, input.headSha)) continue;
|
|
47508
|
+
return { attestation: a };
|
|
47509
|
+
}
|
|
47510
|
+
return void 0;
|
|
47511
|
+
}
|
|
47512
|
+
function composedFromRangeSha(candidate) {
|
|
47513
|
+
return rangeSha(candidate.attestation.target);
|
|
47514
|
+
}
|
|
47515
|
+
var defaultReviewPackCacheDir = () => join(homedir(), ".agentproto", "review-packs");
|
|
47516
|
+
var FULL_SHA = /^[0-9a-f]{40}$/;
|
|
47517
|
+
function execFileP(bin, args, opts = {}) {
|
|
47518
|
+
return new Promise((resolvePromise, reject) => {
|
|
47519
|
+
execFile(bin, [...args], { cwd: opts.cwd, env: opts.env, maxBuffer: 16 * 1024 * 1024 }, (err, stdout, stderr) => {
|
|
47520
|
+
if (err) reject(new Error(`${bin} ${args.join(" ")} failed: ${String(stderr || err.message).trim()}`));
|
|
47521
|
+
else resolvePromise(String(stdout));
|
|
47522
|
+
});
|
|
47523
|
+
});
|
|
47524
|
+
}
|
|
47525
|
+
async function readSource(root) {
|
|
47526
|
+
try {
|
|
47527
|
+
return await readFile(join(root, "REVIEW.md"), "utf8");
|
|
47528
|
+
} catch (err) {
|
|
47529
|
+
throw new Error(`no REVIEW.md at ${join(root, "REVIEW.md")} (${err instanceof Error ? err.message : String(err)})`);
|
|
47530
|
+
}
|
|
47531
|
+
}
|
|
47532
|
+
function isRealPathInside(realChild, realParent) {
|
|
47533
|
+
return realChild === realParent || realChild.startsWith(realParent + sep);
|
|
47534
|
+
}
|
|
47535
|
+
async function readRubricConfined(root, relPath) {
|
|
47536
|
+
const resolved = resolve(root, relPath);
|
|
47537
|
+
let realResolved;
|
|
47538
|
+
let realRoot;
|
|
47539
|
+
try {
|
|
47540
|
+
;
|
|
47541
|
+
[realResolved, realRoot] = await Promise.all([realpath(resolved), realpath(root)]);
|
|
47542
|
+
} catch (err) {
|
|
47543
|
+
throw new Error(`could not read (${err instanceof Error ? err.message : String(err)})`);
|
|
47544
|
+
}
|
|
47545
|
+
if (!isRealPathInside(realResolved, realRoot)) {
|
|
47546
|
+
throw new Error(`resolves outside its pack root (${root}) \u2014 refusing to read it`);
|
|
47547
|
+
}
|
|
47548
|
+
return readFile(realResolved);
|
|
47549
|
+
}
|
|
47550
|
+
async function loadFromRoot(root, refKind, trusted) {
|
|
47551
|
+
const source = await readSource(root);
|
|
47552
|
+
return {
|
|
47553
|
+
manifest: parsePackManifest(source),
|
|
47554
|
+
source,
|
|
47555
|
+
refKind,
|
|
47556
|
+
trusted,
|
|
47557
|
+
root,
|
|
47558
|
+
readRubric: (relPath) => readRubricConfined(root, relPath)
|
|
47559
|
+
};
|
|
47560
|
+
}
|
|
47561
|
+
async function isTrustedRelativePack(root, repoRoot) {
|
|
47562
|
+
let realRoot;
|
|
47563
|
+
let realRepo;
|
|
47564
|
+
try {
|
|
47565
|
+
;
|
|
47566
|
+
[realRoot, realRepo] = await Promise.all([realpath(root), realpath(repoRoot)]);
|
|
47567
|
+
} catch {
|
|
47568
|
+
return false;
|
|
47569
|
+
}
|
|
47570
|
+
if (!isRealPathInside(realRoot, realRepo)) return false;
|
|
47571
|
+
const relReviewMd = join(relative(realRepo, realRoot), "REVIEW.md");
|
|
47572
|
+
try {
|
|
47573
|
+
await execFileP("git", ["-C", realRepo, "ls-files", "--error-unmatch", "--", relReviewMd]);
|
|
47574
|
+
return true;
|
|
47575
|
+
} catch {
|
|
47576
|
+
return false;
|
|
47577
|
+
}
|
|
47578
|
+
}
|
|
47579
|
+
async function loadRelative(ref, manifestDir, repoRoot) {
|
|
47580
|
+
const root = isAbsolute(ref) ? ref : resolve(manifestDir, ref);
|
|
47581
|
+
const trusted = await isTrustedRelativePack(root, repoRoot);
|
|
47582
|
+
return loadFromRoot(root, "relative", trusted);
|
|
47583
|
+
}
|
|
47584
|
+
async function loadNpm(ref, repoRoot) {
|
|
47585
|
+
const req = createRequire(join(repoRoot, "package.json"));
|
|
47586
|
+
let pkgJsonPath;
|
|
47587
|
+
try {
|
|
47588
|
+
pkgJsonPath = req.resolve(`${ref}/package.json`);
|
|
47589
|
+
} catch (err) {
|
|
47590
|
+
throw new Error(
|
|
47591
|
+
`review pack '${ref}' is not resolvable from ${repoRoot} (node_modules, no network, no install) \u2014 is it a dependency? (${err instanceof Error ? err.message : String(err)})`
|
|
47592
|
+
);
|
|
47593
|
+
}
|
|
47594
|
+
return loadFromRoot(dirname(pkgJsonPath), "npm", false);
|
|
47595
|
+
}
|
|
47596
|
+
async function loadGit(ref, cacheDir, env) {
|
|
47597
|
+
const rest = ref.slice("git+".length);
|
|
47598
|
+
if (!rest.startsWith("https://")) {
|
|
47599
|
+
throw new Error(
|
|
47600
|
+
`git pack ref '${ref}' must use git+https:// \u2014 other git transports (ssh://, file://, ext::, plain http://) are not accepted`
|
|
47601
|
+
);
|
|
47602
|
+
}
|
|
47603
|
+
const hashAt = rest.lastIndexOf("#");
|
|
47604
|
+
const sha = hashAt === -1 ? "" : rest.slice(hashAt + 1);
|
|
47605
|
+
const url = hashAt === -1 ? rest : rest.slice(0, hashAt);
|
|
47606
|
+
if (!FULL_SHA.test(sha)) {
|
|
47607
|
+
throw new Error(`git pack ref '${ref}' must be pinned to a full 40-hex commit sha (git+https://...#<sha>)`);
|
|
47608
|
+
}
|
|
47609
|
+
const dest = join(cacheDir, sha);
|
|
47610
|
+
if (existsSync(join(dest, "REVIEW.md"))) return loadFromRoot(dest, "git", false);
|
|
47611
|
+
const tmp = await mkdtemp(join(tmpdir(), "agentproto-review-pack-"));
|
|
47612
|
+
try {
|
|
47613
|
+
await execFileP("git", ["clone", "--quiet", "--", url, tmp], { env });
|
|
47614
|
+
await execFileP("git", ["checkout", "--quiet", sha], { cwd: tmp, env });
|
|
47615
|
+
try {
|
|
47616
|
+
await mkdir(cacheDir, { recursive: true });
|
|
47617
|
+
await rename(tmp, dest);
|
|
47618
|
+
} catch {
|
|
47619
|
+
if (!existsSync(join(dest, "REVIEW.md"))) throw new Error(`could not place cloned pack at ${dest}`);
|
|
47620
|
+
await rm(tmp, { recursive: true, force: true }).catch(() => void 0);
|
|
47621
|
+
}
|
|
47622
|
+
} catch (err) {
|
|
47623
|
+
await rm(tmp, { recursive: true, force: true }).catch(() => void 0);
|
|
47624
|
+
throw err;
|
|
47625
|
+
}
|
|
47626
|
+
return loadFromRoot(dest, "git", false);
|
|
47627
|
+
}
|
|
47628
|
+
function createReviewPackLoader(opts) {
|
|
47629
|
+
const cacheDir = opts.cacheDir ?? defaultReviewPackCacheDir();
|
|
47630
|
+
return {
|
|
47631
|
+
async load(ref) {
|
|
47632
|
+
if (ref.startsWith("git+")) return loadGit(ref, cacheDir, opts.env);
|
|
47633
|
+
if (ref.startsWith("./") || ref.startsWith("../") || isAbsolute(ref)) {
|
|
47634
|
+
return loadRelative(ref, opts.manifestDir, opts.repoRoot);
|
|
47635
|
+
}
|
|
47636
|
+
return loadNpm(ref, opts.repoRoot);
|
|
47637
|
+
}
|
|
47638
|
+
};
|
|
47639
|
+
}
|
|
47640
|
+
var SIGN_NAMESPACE = "agentproto-review";
|
|
47641
|
+
var defaultReviewKeysDir = () => join(homedir(), ".agentproto", "keys");
|
|
47642
|
+
var errMessage = (err) => err instanceof Error ? err.message : String(err);
|
|
47643
|
+
var isNotFound = (err) => err?.code === "ENOENT";
|
|
47644
|
+
function execFileP2(bin, args, opts = {}) {
|
|
47645
|
+
return new Promise((resolvePromise, reject) => {
|
|
47646
|
+
const child = execFile(
|
|
47647
|
+
bin,
|
|
47648
|
+
[...args],
|
|
47649
|
+
{ env: opts.env ?? process.env, maxBuffer: 16 * 1024 * 1024 },
|
|
47650
|
+
(err, stdout, stderr) => {
|
|
47651
|
+
if (err) {
|
|
47652
|
+
reject(Object.assign(err, { stdout: String(stdout), stderr: String(stderr) }));
|
|
47653
|
+
return;
|
|
47654
|
+
}
|
|
47655
|
+
resolvePromise({ stdout: String(stdout), stderr: String(stderr) });
|
|
47656
|
+
}
|
|
47657
|
+
);
|
|
47658
|
+
if (opts.input !== void 0) child.stdin?.end(opts.input);
|
|
47659
|
+
else child.stdin?.end();
|
|
47660
|
+
});
|
|
47661
|
+
}
|
|
47662
|
+
async function ensureReviewSigningKey(opts = {}) {
|
|
47663
|
+
const dir = opts.dir ?? defaultReviewKeysDir();
|
|
47664
|
+
await mkdir(dir, { recursive: true });
|
|
47665
|
+
const privateKeyPath = join(dir, "review_ed25519");
|
|
47666
|
+
const publicKeyPath = `${privateKeyPath}.pub`;
|
|
47667
|
+
const exists = await stat(privateKeyPath).then(() => true).catch(() => false);
|
|
47668
|
+
if (!exists) {
|
|
47669
|
+
await execFileP2(
|
|
47670
|
+
"ssh-keygen",
|
|
47671
|
+
["-t", "ed25519", "-N", "", "-C", `agentproto-review@${hostname()}`, "-f", privateKeyPath],
|
|
47672
|
+
{ env: opts.env }
|
|
47673
|
+
);
|
|
47674
|
+
}
|
|
47675
|
+
await chmod(privateKeyPath, 384);
|
|
47676
|
+
const publicKeyLine = (await readFile(publicKeyPath, "utf8")).trim();
|
|
47677
|
+
const fingerprint = await fingerprintOf(publicKeyPath, opts.env);
|
|
47678
|
+
return { privateKeyPath, publicKeyPath, fingerprint, publicKeyLine };
|
|
47679
|
+
}
|
|
47680
|
+
async function fingerprintOf(publicKeyPath, env) {
|
|
47681
|
+
const { stdout } = await execFileP2("ssh-keygen", ["-lf", publicKeyPath], { env });
|
|
47682
|
+
const fp = stdout.trim().split(/\s+/)[1];
|
|
47683
|
+
if (!fp) throw new Error(`could not parse a fingerprint from: ${stdout.trim()}`);
|
|
47684
|
+
return fp;
|
|
47685
|
+
}
|
|
47686
|
+
function allowedSignersLine(principal, publicKeyLine) {
|
|
47687
|
+
const [algo, key] = publicKeyLine.trim().split(/\s+/);
|
|
47688
|
+
if (!algo || !key) throw new Error(`malformed public key line: ${publicKeyLine}`);
|
|
47689
|
+
return `${principal} namespaces="${SIGN_NAMESPACE}" ${algo} ${key}`;
|
|
47690
|
+
}
|
|
47691
|
+
async function resolvePrincipal(opts) {
|
|
47692
|
+
if (opts.configuredPrincipal) return opts.configuredPrincipal;
|
|
47693
|
+
const email = await new Promise((resolvePromise) => {
|
|
47694
|
+
execFile("git", ["config", "user.email"], { cwd: opts.repoRoot }, (err, stdout) => {
|
|
47695
|
+
resolvePromise(err ? void 0 : stdout.trim() || void 0);
|
|
47696
|
+
});
|
|
47697
|
+
});
|
|
47698
|
+
return email ?? `agentproto-review@${hostname()}`;
|
|
47699
|
+
}
|
|
47700
|
+
async function signAttestation(att, opts) {
|
|
47701
|
+
let key;
|
|
47702
|
+
try {
|
|
47703
|
+
key = await ensureReviewSigningKey({ dir: opts.keysDir, env: opts.env });
|
|
47704
|
+
} catch (err) {
|
|
47705
|
+
return { error: `could not prepare the review signing key: ${errMessage(err)}` };
|
|
47706
|
+
}
|
|
47707
|
+
const tmpDir = await mkdtemp(join(tmpdir(), "agentproto-review-sign-"));
|
|
47708
|
+
try {
|
|
47709
|
+
const payloadPath = join(tmpDir, "attestation.json");
|
|
47710
|
+
await writeFile(payloadPath, canonicalAttestationBytes(att), "utf8");
|
|
47711
|
+
await execFileP2("ssh-keygen", ["-Y", "sign", "-f", key.privateKeyPath, "-n", SIGN_NAMESPACE, payloadPath], {
|
|
47712
|
+
env: opts.env
|
|
47713
|
+
});
|
|
47714
|
+
const sig = await readFile(`${payloadPath}.sig`, "utf8");
|
|
47715
|
+
return {
|
|
47716
|
+
signature: {
|
|
47717
|
+
alg: "ssh-ed25519",
|
|
47718
|
+
keyFingerprint: key.fingerprint,
|
|
47719
|
+
principal: opts.principal,
|
|
47720
|
+
signedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
47721
|
+
sig
|
|
47722
|
+
}
|
|
47723
|
+
};
|
|
47724
|
+
} catch (err) {
|
|
47725
|
+
return { error: `could not sign the attestation: ${isNotFound(err) ? "ssh-keygen not found on PATH" : errMessage(err)}` };
|
|
47726
|
+
} finally {
|
|
47727
|
+
await rm(tmpDir, { recursive: true, force: true }).catch(() => void 0);
|
|
47728
|
+
}
|
|
47729
|
+
}
|
|
47730
|
+
async function verifySignedAttestation(att, opts) {
|
|
47731
|
+
const sig = att.attestor.signature;
|
|
47732
|
+
if (!sig) return { ok: false, problems: ["attestation is not signed"] };
|
|
47733
|
+
let allowedSignersReadable = true;
|
|
47734
|
+
try {
|
|
47735
|
+
await stat(opts.allowedSignersPath);
|
|
47736
|
+
} catch {
|
|
47737
|
+
allowedSignersReadable = false;
|
|
47738
|
+
}
|
|
47739
|
+
if (!allowedSignersReadable) {
|
|
47740
|
+
return { ok: false, problems: [`allowed_signers file not found: ${opts.allowedSignersPath}`] };
|
|
47741
|
+
}
|
|
47742
|
+
const tmpDir = await mkdtemp(join(tmpdir(), "agentproto-review-verify-"));
|
|
47743
|
+
try {
|
|
47744
|
+
const payload = canonicalAttestationBytes(att);
|
|
47745
|
+
const payloadPath = join(tmpDir, "attestation.json");
|
|
47746
|
+
const sigPath = `${payloadPath}.sig`;
|
|
47747
|
+
await writeFile(payloadPath, payload, "utf8");
|
|
47748
|
+
await writeFile(sigPath, sig.sig.endsWith("\n") ? sig.sig : `${sig.sig}
|
|
47749
|
+
`, "utf8");
|
|
47750
|
+
let principals;
|
|
47751
|
+
try {
|
|
47752
|
+
const { stdout } = await execFileP2(
|
|
47753
|
+
"ssh-keygen",
|
|
47754
|
+
["-Y", "find-principals", "-f", opts.allowedSignersPath, "-s", sigPath],
|
|
47755
|
+
{ env: opts.env }
|
|
47756
|
+
);
|
|
47757
|
+
principals = stdout.split("\n").map((s) => s.trim()).filter(Boolean);
|
|
47758
|
+
} catch (err) {
|
|
47759
|
+
if (isNotFound(err)) return { ok: false, problems: ["ssh-keygen is not available \u2014 cannot verify signatures"] };
|
|
47760
|
+
principals = [];
|
|
47761
|
+
}
|
|
47762
|
+
if (!principals.includes(sig.principal)) {
|
|
47763
|
+
return {
|
|
47764
|
+
ok: false,
|
|
47765
|
+
problems: [`no principal '${sig.principal}' maps to the signing key (${sig.keyFingerprint}) in ${opts.allowedSignersPath}`]
|
|
47766
|
+
};
|
|
47767
|
+
}
|
|
47768
|
+
try {
|
|
47769
|
+
await execFileP2(
|
|
47770
|
+
"ssh-keygen",
|
|
47771
|
+
["-Y", "verify", "-f", opts.allowedSignersPath, "-I", sig.principal, "-n", SIGN_NAMESPACE, "-s", sigPath],
|
|
47772
|
+
{ input: payload, env: opts.env }
|
|
47773
|
+
);
|
|
47774
|
+
} catch (err) {
|
|
47775
|
+
if (isNotFound(err)) return { ok: false, problems: ["ssh-keygen is not available \u2014 cannot verify signatures"] };
|
|
47776
|
+
return { ok: false, problems: [`signature does not verify: ${errMessage(err)}`] };
|
|
47777
|
+
}
|
|
47778
|
+
return { ok: true, problems: [] };
|
|
47779
|
+
} finally {
|
|
47780
|
+
await rm(tmpDir, { recursive: true, force: true }).catch(() => void 0);
|
|
47781
|
+
}
|
|
47782
|
+
}
|
|
47783
|
+
async function fileExists2(path) {
|
|
47784
|
+
return stat(path).then(() => true).catch(() => false);
|
|
47785
|
+
}
|
|
46582
47786
|
|
|
46583
47787
|
// src/review-runner.ts
|
|
46584
47788
|
function git2(cwd, args) {
|
|
@@ -46684,19 +47888,54 @@ function createReviewLaneExecutor(ctx) {
|
|
|
46684
47888
|
error: "agent lanes are not available on this daemon (started without an agent adapter resolver)"
|
|
46685
47889
|
};
|
|
46686
47890
|
}
|
|
46687
|
-
const rubricPath = resolve(dirname(ctx.manifestPath), check.rubric);
|
|
47891
|
+
const rubricPath = resolve(check.rubricBase ?? dirname(ctx.manifestPath), check.rubric);
|
|
46688
47892
|
try {
|
|
46689
47893
|
await readFile(rubricPath);
|
|
46690
47894
|
} catch {
|
|
46691
47895
|
return { outcome: "skipped", error: `rubric not found: ${rubricPath}` };
|
|
46692
47896
|
}
|
|
46693
|
-
|
|
47897
|
+
let promptTarget = target;
|
|
47898
|
+
let composedFrom;
|
|
47899
|
+
if (ctx.compose?.enabled) {
|
|
47900
|
+
const rubric = ctx.compose.rubrics.find((r) => r.check === check.id);
|
|
47901
|
+
const namespace = check.id.includes("/") ? check.id.slice(0, check.id.indexOf("/")) : void 0;
|
|
47902
|
+
const packDigest = namespace ? ctx.compose.packByNamespace?.[namespace] : void 0;
|
|
47903
|
+
const candidate = rubric ? await findComposeCandidate({
|
|
47904
|
+
ledger: ctx.compose.ledger,
|
|
47905
|
+
repoRoot: ctx.repoRoot,
|
|
47906
|
+
repoRemote: target.repoRemote,
|
|
47907
|
+
manifestSha: ctx.compose.manifestSha,
|
|
47908
|
+
binding: ctx.compose.binding,
|
|
47909
|
+
checkId: check.id,
|
|
47910
|
+
rubricSha256: rubric.sha256,
|
|
47911
|
+
...packDigest ? { packDigest: { id: packDigest.id, sha256: packDigest.sha256 } } : {},
|
|
47912
|
+
baseSha: target.baseSha,
|
|
47913
|
+
headSha: target.headSha
|
|
47914
|
+
}) : void 0;
|
|
47915
|
+
if (candidate) {
|
|
47916
|
+
const priorHeadSha = candidate.attestation.target.headSha;
|
|
47917
|
+
promptTarget = { ...target, baseSha: priorHeadSha };
|
|
47918
|
+
composedFrom = {
|
|
47919
|
+
rangeSha: composedFromRangeSha(candidate),
|
|
47920
|
+
headSha: priorHeadSha,
|
|
47921
|
+
attestationSha256: attestationSha256(candidate.attestation)
|
|
47922
|
+
};
|
|
47923
|
+
}
|
|
47924
|
+
}
|
|
47925
|
+
const verdictPath = join(ctx.runDir, `${check.id.replace(/\//g, "__")}.verdict.json`);
|
|
46694
47926
|
await mkdir(ctx.runDir, { recursive: true });
|
|
46695
47927
|
await rm(verdictPath, { force: true });
|
|
46696
47928
|
const result = await ctx.reviewers.run({
|
|
46697
47929
|
preset: check.preset,
|
|
46698
47930
|
cwd: ctx.repoRoot,
|
|
46699
|
-
prompt: buildAgentLanePrompt({
|
|
47931
|
+
prompt: buildAgentLanePrompt({
|
|
47932
|
+
reviewId: ctx.reviewId,
|
|
47933
|
+
check,
|
|
47934
|
+
target: promptTarget,
|
|
47935
|
+
rubricPath,
|
|
47936
|
+
verdictPath,
|
|
47937
|
+
...composedFrom ? { composedFrom: { priorHeadSha: composedFrom.headSha } } : {}
|
|
47938
|
+
}),
|
|
46700
47939
|
label: `review:${ctx.reviewId}:${check.id}`,
|
|
46701
47940
|
timeoutMs: check.timeoutMs,
|
|
46702
47941
|
...ctx.parentSessionId ? { parentSessionId: ctx.parentSessionId } : {},
|
|
@@ -46739,7 +47978,8 @@ function createReviewLaneExecutor(ctx) {
|
|
|
46739
47978
|
report: parseAgentLaneReport(raw),
|
|
46740
47979
|
sessionId: result.sessionId,
|
|
46741
47980
|
preset: result.preset,
|
|
46742
|
-
...model
|
|
47981
|
+
...model,
|
|
47982
|
+
...composedFrom ? { composedFrom } : {}
|
|
46743
47983
|
};
|
|
46744
47984
|
} catch (err) {
|
|
46745
47985
|
return {
|
|
@@ -46783,7 +48023,9 @@ function createReviewRunner(opts) {
|
|
|
46783
48023
|
const source = await readFile(manifestPath, "utf8").catch(() => {
|
|
46784
48024
|
throw new Error(`no REVIEW.md at ${manifestPath}`);
|
|
46785
48025
|
});
|
|
46786
|
-
const
|
|
48026
|
+
const parsed = parseReviewManifest(source);
|
|
48027
|
+
const packLoader = createReviewPackLoader({ repoRoot: root, manifestDir: dirname(manifestPath) });
|
|
48028
|
+
const { manifest, packs: packDigests, packByNamespace } = await resolvePacks(parsed, packLoader);
|
|
46787
48029
|
const binding = resolveBinding(manifest, input.binding);
|
|
46788
48030
|
run.reviewId = manifest.id;
|
|
46789
48031
|
run.binding = binding.name;
|
|
@@ -46802,7 +48044,7 @@ function createReviewRunner(opts) {
|
|
|
46802
48044
|
for (const id of binding.checks) {
|
|
46803
48045
|
const check = manifest.checks.find((c) => c.id === id);
|
|
46804
48046
|
if (check?.kind !== "agent") continue;
|
|
46805
|
-
const path = resolve(dirname(manifestPath), check.rubric);
|
|
48047
|
+
const path = resolve(check.rubricBase ?? dirname(manifestPath), check.rubric);
|
|
46806
48048
|
const bytes = await readFile(path).catch(() => void 0);
|
|
46807
48049
|
if (bytes) rubrics.push({ check: check.id, path: check.rubric, sha256: sha256Hex(bytes) });
|
|
46808
48050
|
}
|
|
@@ -46810,7 +48052,8 @@ function createReviewRunner(opts) {
|
|
|
46810
48052
|
if (!await isDirty(root)) {
|
|
46811
48053
|
const hit = await ledger.lookupCached(
|
|
46812
48054
|
{ repoRemote, manifestSha: mSha, binding: binding.name, rangeSha: rangeSha({ baseSha, headSha: headNow }) },
|
|
46813
|
-
rubrics
|
|
48055
|
+
rubrics,
|
|
48056
|
+
packDigests
|
|
46814
48057
|
);
|
|
46815
48058
|
if (hit) {
|
|
46816
48059
|
run.attestation = hit.attestation;
|
|
@@ -46820,6 +48063,7 @@ function createReviewRunner(opts) {
|
|
|
46820
48063
|
}
|
|
46821
48064
|
}
|
|
46822
48065
|
}
|
|
48066
|
+
const composeEnabled = input.nocache ? false : input.compose ?? true;
|
|
46823
48067
|
let dirty = false;
|
|
46824
48068
|
const runDir = join(ledger.root, repoSlug(repoRemote), "runs", run.runId);
|
|
46825
48069
|
const compiled = compileReview(manifest, {
|
|
@@ -46840,7 +48084,8 @@ function createReviewRunner(opts) {
|
|
|
46840
48084
|
manifestPath,
|
|
46841
48085
|
runDir,
|
|
46842
48086
|
...opts.reviewers ? { reviewers: opts.reviewers } : {},
|
|
46843
|
-
...input.parentSessionId ? { parentSessionId: input.parentSessionId } : {}
|
|
48087
|
+
...input.parentSessionId ? { parentSessionId: input.parentSessionId } : {},
|
|
48088
|
+
compose: { enabled: composeEnabled, ledger, manifestSha: mSha, binding: binding.name, rubrics, packByNamespace }
|
|
46844
48089
|
}),
|
|
46845
48090
|
onLaneSettled: (lane) => {
|
|
46846
48091
|
run.lanes = [...run.lanes, lane];
|
|
@@ -46860,7 +48105,7 @@ function createReviewRunner(opts) {
|
|
|
46860
48105
|
...requesterSessionId ? { sessionId: requesterSessionId } : {},
|
|
46861
48106
|
...gitAuthor ? { gitAuthor } : {}
|
|
46862
48107
|
};
|
|
46863
|
-
const
|
|
48108
|
+
const unsigned = buildAttestation({
|
|
46864
48109
|
runId: run.runId,
|
|
46865
48110
|
reviewId: manifest.id,
|
|
46866
48111
|
manifestSha: mSha,
|
|
@@ -46870,10 +48115,19 @@ function createReviewRunner(opts) {
|
|
|
46870
48115
|
lanes: outcome.lanes,
|
|
46871
48116
|
attestor: { daemon: daemonId, presets: outcome.lanes.flatMap((l) => l.preset ? [l.preset] : []) },
|
|
46872
48117
|
rubrics,
|
|
48118
|
+
packs: packDigests,
|
|
46873
48119
|
dirty,
|
|
46874
48120
|
requester,
|
|
46875
48121
|
...input.pr ? { pr: input.pr } : {}
|
|
46876
48122
|
});
|
|
48123
|
+
const principal = await resolvePrincipal({ repoRoot: root, configuredPrincipal: opts.signingPrincipal });
|
|
48124
|
+
const signed = await signAttestation(unsigned, {
|
|
48125
|
+
principal,
|
|
48126
|
+
...opts.signingKeysDir ? { keysDir: opts.signingKeysDir } : {},
|
|
48127
|
+
...opts.signingEnv ? { env: opts.signingEnv } : {}
|
|
48128
|
+
});
|
|
48129
|
+
if (signed.error) run.signingError = signed.error;
|
|
48130
|
+
const attestation = signed.signature ? { ...unsigned, attestor: { ...unsigned.attestor, signature: signed.signature } } : unsigned;
|
|
46877
48131
|
const entry = {
|
|
46878
48132
|
attestation,
|
|
46879
48133
|
host: {
|
|
@@ -47108,7 +48362,7 @@ function jsonContent2(payload) {
|
|
|
47108
48362
|
function errorContent(message) {
|
|
47109
48363
|
return { content: [{ type: "text", text: JSON.stringify({ error: message }) }], isError: true };
|
|
47110
48364
|
}
|
|
47111
|
-
var
|
|
48365
|
+
var errMessage2 = (err) => err instanceof Error ? err.message : String(err);
|
|
47112
48366
|
function runView(run) {
|
|
47113
48367
|
return {
|
|
47114
48368
|
runId: run.runId,
|
|
@@ -47118,6 +48372,7 @@ function runView(run) {
|
|
|
47118
48372
|
...run.attestation ? { verdict: run.attestation.verdict } : {},
|
|
47119
48373
|
...run.cached ? { cached: true } : {},
|
|
47120
48374
|
...run.error ? { error: run.error } : {},
|
|
48375
|
+
...run.signingError ? { signingError: run.signingError } : {},
|
|
47121
48376
|
...run.supersededBy ? { supersededBy: run.supersededBy } : {},
|
|
47122
48377
|
...run.status === "running" ? { lanes: run.lanes.map((l) => ({ id: l.id, status: l.status })) } : {},
|
|
47123
48378
|
...run.attestation ? { attestation: run.attestation } : {},
|
|
@@ -47144,6 +48399,9 @@ function ledgerRow(entry, annotations = {}) {
|
|
|
47144
48399
|
...a.requester ? { requester: a.requester } : {},
|
|
47145
48400
|
...pr ? { pr } : {},
|
|
47146
48401
|
...lastPrStatus ? { prState: lastPrStatus.state } : {},
|
|
48402
|
+
// Compact signed/unsigned indicator — the full signature (and any
|
|
48403
|
+
// composedFrom lane detail) lives on the full attestation (review_export).
|
|
48404
|
+
signed: !!a.attestor.signature,
|
|
47147
48405
|
lanes: a.lanes.map((l) => ({ id: l.id, status: l.status, blocking: l.blocking }))
|
|
47148
48406
|
};
|
|
47149
48407
|
}
|
|
@@ -47201,14 +48459,14 @@ async function reviewLedgerView(runner, input, opts = {}) {
|
|
|
47201
48459
|
root = repo.root;
|
|
47202
48460
|
repoRemote ??= repo.repoRemote;
|
|
47203
48461
|
}
|
|
47204
|
-
let
|
|
48462
|
+
let rangeSha3;
|
|
47205
48463
|
if (input.range !== void 0) {
|
|
47206
|
-
|
|
48464
|
+
rangeSha3 = await resolveRangeSha(input.range, root);
|
|
47207
48465
|
}
|
|
47208
48466
|
const requesterSessionIds = input.requesterSessionId ? input.subtree && opts.resolveSubtree ? [...opts.resolveSubtree(input.requesterSessionId)] : [input.requesterSessionId] : void 0;
|
|
47209
48467
|
const entries = await runner.ledger.list({
|
|
47210
48468
|
...repoRemote !== void 0 ? { repoRemote } : {},
|
|
47211
|
-
...
|
|
48469
|
+
...rangeSha3 !== void 0 ? { rangeSha: rangeSha3 } : {},
|
|
47212
48470
|
...input.binding !== void 0 ? { binding: input.binding } : {},
|
|
47213
48471
|
...requesterSessionIds ? { requesterSessionIds } : {}
|
|
47214
48472
|
});
|
|
@@ -47236,7 +48494,10 @@ function registerReviewTools(server, opts) {
|
|
|
47236
48494
|
binding: z.string().optional().describe("Binding to run (e.g. local, ci). Default: the sole binding, or `default`."),
|
|
47237
48495
|
base: z.string().optional().describe("Range base ref/sha. Default: merge-base(<manifest target.base>, HEAD)."),
|
|
47238
48496
|
head: z.string().optional().describe("Range head ref/sha. Default: HEAD, resolved after the prepare phase."),
|
|
47239
|
-
nocache: z.boolean().optional().describe("Ignore a cached ledger verdict and re-run. Default false."),
|
|
48497
|
+
nocache: z.boolean().optional().describe("Ignore a cached ledger verdict and re-run. Default false. Implies compose:false."),
|
|
48498
|
+
compose: z.boolean().optional().describe(
|
|
48499
|
+
"Let an agent lane reuse a prior passing attestation and review only the delta on top of it (attestation composition \u2014 see LaneResult.composedFrom). Default true. Command lanes are never composed. Ignored (false) when `nocache` is set."
|
|
48500
|
+
),
|
|
47240
48501
|
wait: z.boolean().optional().describe("Block until the review finishes (default true). false \u21D2 return a runId immediately."),
|
|
47241
48502
|
requesterSessionId: z.string().optional().describe("Session recorded as attestation.requester.sessionId. Default: the calling session."),
|
|
47242
48503
|
pr: prRefSchema.optional().describe(
|
|
@@ -47255,6 +48516,7 @@ function registerReviewTools(server, opts) {
|
|
|
47255
48516
|
...input.base !== void 0 ? { base: input.base } : {},
|
|
47256
48517
|
...input.head !== void 0 ? { head: input.head } : {},
|
|
47257
48518
|
...input.nocache ? { nocache: true } : {},
|
|
48519
|
+
...input.compose !== void 0 ? { compose: input.compose } : {},
|
|
47258
48520
|
...callerSessionId ? { parentSessionId: callerSessionId } : {},
|
|
47259
48521
|
...input.requesterSessionId !== void 0 ? { requesterSessionId: input.requesterSessionId } : {},
|
|
47260
48522
|
...input.pr ? { pr: input.pr } : {},
|
|
@@ -47265,7 +48527,7 @@ function registerReviewTools(server, opts) {
|
|
|
47265
48527
|
const view2 = runView(finished);
|
|
47266
48528
|
return finished.status === "failed" ? { ...jsonContent2(view2), isError: true } : jsonContent2(view2);
|
|
47267
48529
|
} catch (err) {
|
|
47268
|
-
return errorContent(`review_run failed: ${
|
|
48530
|
+
return errorContent(`review_run failed: ${errMessage2(err)}`);
|
|
47269
48531
|
}
|
|
47270
48532
|
}
|
|
47271
48533
|
);
|
|
@@ -47312,7 +48574,7 @@ function registerReviewTools(server, opts) {
|
|
|
47312
48574
|
try {
|
|
47313
48575
|
return jsonContent2(await reviewLedgerView(runner, input, { resolveSubtree }));
|
|
47314
48576
|
} catch (err) {
|
|
47315
|
-
return errorContent(`review_ledger failed: ${
|
|
48577
|
+
return errorContent(`review_ledger failed: ${errMessage2(err)}`);
|
|
47316
48578
|
}
|
|
47317
48579
|
}
|
|
47318
48580
|
);
|
|
@@ -47373,7 +48635,7 @@ function registerReviewTools(server, opts) {
|
|
|
47373
48635
|
`, "utf8");
|
|
47374
48636
|
return jsonContent2({ path: outPath, runId: attestation.runId, verdict: attestation.verdict });
|
|
47375
48637
|
} catch (err) {
|
|
47376
|
-
return errorContent(`review_export failed: ${
|
|
48638
|
+
return errorContent(`review_export failed: ${errMessage2(err)}`);
|
|
47377
48639
|
}
|
|
47378
48640
|
}
|
|
47379
48641
|
);
|
|
@@ -47404,8 +48666,8 @@ function registerReviewTools(server, opts) {
|
|
|
47404
48666
|
if (run?.attestation) entry = await runner.ledger.get(ledgerKeyOf(run.attestation));
|
|
47405
48667
|
if (!entry) return errorContent(`review_pr: no recorded attestation for run '${input.runId}'`);
|
|
47406
48668
|
} else if (input.rangeSha !== void 0 || input.range !== void 0) {
|
|
47407
|
-
const
|
|
47408
|
-
entry = (await scoped({ rangeSha:
|
|
48669
|
+
const rangeSha3 = await resolveRangeSha(input.rangeSha ?? input.range, root);
|
|
48670
|
+
entry = (await scoped({ rangeSha: rangeSha3 }))[0];
|
|
47409
48671
|
if (!entry) return errorContent("review_pr: no recorded attestation for that range");
|
|
47410
48672
|
} else if (input.prUrl) {
|
|
47411
48673
|
const parsed = parsePrUrl(input.prUrl);
|
|
@@ -47489,7 +48751,7 @@ function registerReviewTools(server, opts) {
|
|
|
47489
48751
|
});
|
|
47490
48752
|
}
|
|
47491
48753
|
} catch (err) {
|
|
47492
|
-
return errorContent(`review_pr failed: ${
|
|
48754
|
+
return errorContent(`review_pr failed: ${errMessage2(err)}`);
|
|
47493
48755
|
}
|
|
47494
48756
|
}
|
|
47495
48757
|
);
|
|
@@ -47796,14 +49058,14 @@ function createActivityProjector(opts) {
|
|
|
47796
49058
|
if (current && isTerminalActivityState(current.state)) {
|
|
47797
49059
|
return Promise.resolve(current);
|
|
47798
49060
|
}
|
|
47799
|
-
return new Promise((
|
|
49061
|
+
return new Promise((resolve38) => {
|
|
47800
49062
|
let settled = false;
|
|
47801
49063
|
const finish = (value) => {
|
|
47802
49064
|
if (settled) return;
|
|
47803
49065
|
settled = true;
|
|
47804
49066
|
clearTimeout(timer);
|
|
47805
49067
|
unsubscribeWait();
|
|
47806
|
-
|
|
49068
|
+
resolve38(value);
|
|
47807
49069
|
};
|
|
47808
49070
|
const timer = setTimeout(() => finish(null), timeoutMs);
|
|
47809
49071
|
const unsubscribeWait = opts.sessionEvents.on("activity:changed", (ev) => {
|
|
@@ -47978,11 +49240,11 @@ function createInboundWatcher(opts) {
|
|
|
47978
49240
|
return;
|
|
47979
49241
|
}
|
|
47980
49242
|
const raw = out.result;
|
|
47981
|
-
const
|
|
47982
|
-
if (!
|
|
49243
|
+
const text13 = raw?.content?.find((c) => c.type === "text")?.text;
|
|
49244
|
+
if (!text13) return;
|
|
47983
49245
|
let data;
|
|
47984
49246
|
try {
|
|
47985
|
-
data = JSON.parse(
|
|
49247
|
+
data = JSON.parse(text13);
|
|
47986
49248
|
} catch {
|
|
47987
49249
|
return;
|
|
47988
49250
|
}
|
|
@@ -48267,9 +49529,9 @@ function createRoutineRegistrar(opts) {
|
|
|
48267
49529
|
const call = routineTargetToToolCall(found2.frontmatter.target, routineId);
|
|
48268
49530
|
const result = await dispatchTool(call.tool, call.inputs);
|
|
48269
49531
|
const r = result;
|
|
48270
|
-
const
|
|
49532
|
+
const text13 = (r?.content ?? []).map((c) => c.text ?? "").join("\n").trim();
|
|
48271
49533
|
const ok3 = !r?.isError;
|
|
48272
|
-
return { ok: ok3, summary:
|
|
49534
|
+
return { ok: ok3, summary: text13 || (ok3 ? `tool '${call.tool}' ok` : `tool '${call.tool}' failed`) };
|
|
48273
49535
|
},
|
|
48274
49536
|
list() {
|
|
48275
49537
|
return lastParsed.map((p) => p.frontmatter);
|
|
@@ -48557,7 +49819,7 @@ async function registerBuiltinRoutes(opts) {
|
|
|
48557
49819
|
|
|
48558
49820
|
// src/index.ts
|
|
48559
49821
|
init_catalog_provider_models();
|
|
48560
|
-
function makeBrowserHandle(entry,
|
|
49822
|
+
function makeBrowserHandle(entry, resolve38) {
|
|
48561
49823
|
return {
|
|
48562
49824
|
slug: entry.id,
|
|
48563
49825
|
name: entry.name,
|
|
@@ -48568,7 +49830,7 @@ function makeBrowserHandle(entry, resolve37) {
|
|
|
48568
49830
|
// check() is available for on-demand health probes but is never called
|
|
48569
49831
|
// by the lister (kit invariant OQ-5). Returns true when the adapter
|
|
48570
49832
|
// is present in the injected resolver map, false otherwise.
|
|
48571
|
-
check: async () =>
|
|
49833
|
+
check: async () => resolve38 ? resolve38(entry.id) != null : true
|
|
48572
49834
|
};
|
|
48573
49835
|
}
|
|
48574
49836
|
var noopLedger = {
|
|
@@ -48913,13 +50175,13 @@ async function spawnCloudflaredUntil(argv, opts) {
|
|
|
48913
50175
|
};
|
|
48914
50176
|
let settled = false;
|
|
48915
50177
|
let forwardedLen = 0;
|
|
48916
|
-
return await new Promise((
|
|
50178
|
+
return await new Promise((resolve38, reject) => {
|
|
48917
50179
|
const poll = setInterval(() => {
|
|
48918
|
-
const
|
|
50180
|
+
const text13 = readAll();
|
|
48919
50181
|
if (opts.onLog) {
|
|
48920
|
-
const lastNl =
|
|
50182
|
+
const lastNl = text13.lastIndexOf("\n");
|
|
48921
50183
|
if (lastNl + 1 > forwardedLen) {
|
|
48922
|
-
const chunk =
|
|
50184
|
+
const chunk = text13.slice(forwardedLen, lastNl + 1);
|
|
48923
50185
|
for (const line of chunk.split(/\r?\n/)) {
|
|
48924
50186
|
if (line.length > 0) opts.onLog(`[cloudflared] ${line}`);
|
|
48925
50187
|
}
|
|
@@ -48927,11 +50189,11 @@ async function spawnCloudflaredUntil(argv, opts) {
|
|
|
48927
50189
|
}
|
|
48928
50190
|
}
|
|
48929
50191
|
if (settled) return;
|
|
48930
|
-
const m =
|
|
50192
|
+
const m = text13.match(opts.readyRegex);
|
|
48931
50193
|
if (m) {
|
|
48932
50194
|
settled = true;
|
|
48933
50195
|
clearTimeout(timer);
|
|
48934
|
-
|
|
50196
|
+
resolve38({ proc, match: m[0], stopTail: () => clearInterval(poll) });
|
|
48935
50197
|
}
|
|
48936
50198
|
}, POLL_INTERVAL_MS2);
|
|
48937
50199
|
if (poll.unref) poll.unref();
|
|
@@ -49068,15 +50330,15 @@ function quickTunnelProvider() {
|
|
|
49068
50330
|
}
|
|
49069
50331
|
}, 3e3);
|
|
49070
50332
|
timer.unref();
|
|
49071
|
-
await new Promise((
|
|
50333
|
+
await new Promise((resolve38) => {
|
|
49072
50334
|
if (proc.exitCode !== null) {
|
|
49073
50335
|
clearTimeout(timer);
|
|
49074
|
-
|
|
50336
|
+
resolve38();
|
|
49075
50337
|
return;
|
|
49076
50338
|
}
|
|
49077
50339
|
proc.once("exit", () => {
|
|
49078
50340
|
clearTimeout(timer);
|
|
49079
|
-
|
|
50341
|
+
resolve38();
|
|
49080
50342
|
});
|
|
49081
50343
|
});
|
|
49082
50344
|
}
|
|
@@ -49271,7 +50533,7 @@ function buildMcpConfigSnippet2(endpoint, token) {
|
|
|
49271
50533
|
2
|
|
49272
50534
|
);
|
|
49273
50535
|
}
|
|
49274
|
-
function
|
|
50536
|
+
function text7(value) {
|
|
49275
50537
|
return {
|
|
49276
50538
|
content: [
|
|
49277
50539
|
{
|
|
@@ -49299,7 +50561,7 @@ function registerRemoteTools(server, opts) {
|
|
|
49299
50561
|
},
|
|
49300
50562
|
async ({ provider, targetPort, targetHost }) => {
|
|
49301
50563
|
const result = await controller.enable({ provider, targetPort, targetHost });
|
|
49302
|
-
return
|
|
50564
|
+
return text7(result);
|
|
49303
50565
|
}
|
|
49304
50566
|
);
|
|
49305
50567
|
server.tool(
|
|
@@ -49308,7 +50570,7 @@ function registerRemoteTools(server, opts) {
|
|
|
49308
50570
|
{},
|
|
49309
50571
|
async () => {
|
|
49310
50572
|
const result = await controller.disable();
|
|
49311
|
-
return
|
|
50573
|
+
return text7(result);
|
|
49312
50574
|
}
|
|
49313
50575
|
);
|
|
49314
50576
|
server.tool(
|
|
@@ -49316,11 +50578,11 @@ function registerRemoteTools(server, opts) {
|
|
|
49316
50578
|
"Report whether a remote tunnel is active. When active: provider, public URL, supervised PID, createdAt. The bearer token itself is never returned by status \u2014 it was shown once at enable.",
|
|
49317
50579
|
{},
|
|
49318
50580
|
async () => {
|
|
49319
|
-
return
|
|
50581
|
+
return text7(controller.status());
|
|
49320
50582
|
}
|
|
49321
50583
|
);
|
|
49322
50584
|
}
|
|
49323
|
-
function
|
|
50585
|
+
function text8(value) {
|
|
49324
50586
|
return {
|
|
49325
50587
|
content: [
|
|
49326
50588
|
{
|
|
@@ -49350,7 +50612,7 @@ function registerPairingTools(server, opts) {
|
|
|
49350
50612
|
...rendezvous ? { rendezvousUrl: rendezvous } : {},
|
|
49351
50613
|
...host ? { scope: "host" } : {}
|
|
49352
50614
|
});
|
|
49353
|
-
return
|
|
50615
|
+
return text8({
|
|
49354
50616
|
url: offer.url,
|
|
49355
50617
|
fingerprint: offer.fingerprint,
|
|
49356
50618
|
rendezvous: offer.rendezvousUrl,
|
|
@@ -49366,7 +50628,7 @@ function registerPairingTools(server, opts) {
|
|
|
49366
50628
|
{},
|
|
49367
50629
|
async () => {
|
|
49368
50630
|
const pairings = await registry.list();
|
|
49369
|
-
return
|
|
50631
|
+
return text8({
|
|
49370
50632
|
pairings: pairings.map((p) => ({
|
|
49371
50633
|
name: p.name,
|
|
49372
50634
|
fingerprint: p.fingerprint,
|
|
@@ -49387,13 +50649,134 @@ function registerPairingTools(server, opts) {
|
|
|
49387
50649
|
},
|
|
49388
50650
|
async ({ target }) => {
|
|
49389
50651
|
const revoked = await registry.revoke(target);
|
|
49390
|
-
return
|
|
50652
|
+
return text8(
|
|
49391
50653
|
revoked ? { ok: true, revoked: target } : { ok: false, message: `no pairing matched "${target}"` }
|
|
49392
50654
|
);
|
|
49393
50655
|
}
|
|
49394
50656
|
);
|
|
49395
50657
|
}
|
|
49396
|
-
|
|
50658
|
+
var HOP_BY_HOP = /* @__PURE__ */ new Set(["connection", "keep-alive", "transfer-encoding", "upgrade", "host"]);
|
|
50659
|
+
function collectHeaders(raw) {
|
|
50660
|
+
const headers = {};
|
|
50661
|
+
for (const [k, v] of Object.entries(raw)) {
|
|
50662
|
+
if (v === void 0 || HOP_BY_HOP.has(k.toLowerCase())) continue;
|
|
50663
|
+
headers[k] = Array.isArray(v) ? v.join(", ") : v;
|
|
50664
|
+
}
|
|
50665
|
+
return headers;
|
|
50666
|
+
}
|
|
50667
|
+
function isPrematureCloseError(err) {
|
|
50668
|
+
return typeof err === "object" && err !== null && "code" in err && err.code === "ERR_STREAM_PREMATURE_CLOSE";
|
|
50669
|
+
}
|
|
50670
|
+
async function readBody(req) {
|
|
50671
|
+
const chunks = [];
|
|
50672
|
+
for await (const chunk of req) chunks.push(chunk);
|
|
50673
|
+
if (chunks.length === 0) return void 0;
|
|
50674
|
+
return new Uint8Array(Buffer.concat(chunks));
|
|
50675
|
+
}
|
|
50676
|
+
async function startDeviceSandboxBridge(opts) {
|
|
50677
|
+
const { hostRegistry, target } = opts;
|
|
50678
|
+
const server = createServer((req, res) => {
|
|
50679
|
+
void handleRequest(req, res).catch((err) => {
|
|
50680
|
+
if (res.writableEnded) return;
|
|
50681
|
+
if (!res.headersSent) {
|
|
50682
|
+
res.writeHead(502, { "content-type": "application/json" });
|
|
50683
|
+
res.end(
|
|
50684
|
+
JSON.stringify({
|
|
50685
|
+
error: "device_sandbox_bridge_failed",
|
|
50686
|
+
message: err instanceof Error ? err.message : String(err)
|
|
50687
|
+
})
|
|
50688
|
+
);
|
|
50689
|
+
return;
|
|
50690
|
+
}
|
|
50691
|
+
res.end();
|
|
50692
|
+
});
|
|
50693
|
+
});
|
|
50694
|
+
async function handleRequest(req, res) {
|
|
50695
|
+
const body = await readBody(req);
|
|
50696
|
+
let upstream;
|
|
50697
|
+
try {
|
|
50698
|
+
upstream = await hostRegistry.forwardHttpStream(target, {
|
|
50699
|
+
method: req.method ?? "GET",
|
|
50700
|
+
path: `/device-spawn${req.url ?? "/"}`,
|
|
50701
|
+
headers: collectHeaders(req.headers),
|
|
50702
|
+
...body ? { body } : {}
|
|
50703
|
+
});
|
|
50704
|
+
} catch (err) {
|
|
50705
|
+
res.writeHead(502, { "content-type": "application/json" });
|
|
50706
|
+
res.end(
|
|
50707
|
+
JSON.stringify({
|
|
50708
|
+
error: "device_unreachable",
|
|
50709
|
+
message: err instanceof Error ? err.message : String(err)
|
|
50710
|
+
})
|
|
50711
|
+
);
|
|
50712
|
+
return;
|
|
50713
|
+
}
|
|
50714
|
+
const resHeaders = {};
|
|
50715
|
+
for (const [k, v] of Object.entries(upstream.headers)) {
|
|
50716
|
+
if (HOP_BY_HOP.has(k.toLowerCase()) || k.toLowerCase() === "content-length") continue;
|
|
50717
|
+
resHeaders[k] = v;
|
|
50718
|
+
}
|
|
50719
|
+
res.writeHead(upstream.status, resHeaders);
|
|
50720
|
+
try {
|
|
50721
|
+
await pipeline(
|
|
50722
|
+
Readable.fromWeb(upstream.body),
|
|
50723
|
+
res
|
|
50724
|
+
);
|
|
50725
|
+
} catch (err) {
|
|
50726
|
+
if (!isPrematureCloseError(err)) throw err;
|
|
50727
|
+
}
|
|
50728
|
+
}
|
|
50729
|
+
const port = await getFreePort2();
|
|
50730
|
+
await new Promise((resolve38, reject) => {
|
|
50731
|
+
server.once("error", reject);
|
|
50732
|
+
server.listen(port, "127.0.0.1", () => resolve38());
|
|
50733
|
+
});
|
|
50734
|
+
return {
|
|
50735
|
+
mcpUrl: `http://127.0.0.1:${port}/mcp`,
|
|
50736
|
+
async close() {
|
|
50737
|
+
await new Promise((resolve38) => server.close(() => resolve38()));
|
|
50738
|
+
}
|
|
50739
|
+
};
|
|
50740
|
+
}
|
|
50741
|
+
async function getFreePort2() {
|
|
50742
|
+
return new Promise((resolve38, reject) => {
|
|
50743
|
+
const srv = createServer$1();
|
|
50744
|
+
srv.once("error", reject);
|
|
50745
|
+
srv.listen(0, "127.0.0.1", () => {
|
|
50746
|
+
const address = srv.address();
|
|
50747
|
+
if (address === null || typeof address === "string") {
|
|
50748
|
+
srv.close(() => reject(new Error("failed to allocate a free port")));
|
|
50749
|
+
return;
|
|
50750
|
+
}
|
|
50751
|
+
const { port } = address;
|
|
50752
|
+
srv.close(() => resolve38(port));
|
|
50753
|
+
});
|
|
50754
|
+
});
|
|
50755
|
+
}
|
|
50756
|
+
|
|
50757
|
+
// src/sandbox-providers/device.ts
|
|
50758
|
+
function createDeviceSandboxProvider(target, hostRegistry) {
|
|
50759
|
+
return {
|
|
50760
|
+
async boot(_spec, _opts) {
|
|
50761
|
+
const hosts = await hostRegistry.list();
|
|
50762
|
+
const known = hosts.some((h) => h.fingerprint === target || h.name === target);
|
|
50763
|
+
if (!known) {
|
|
50764
|
+
throw new Error(
|
|
50765
|
+
`device sandbox: no paired host device matches "${target}" \u2014 run \`agentproto devices add <offer-url>\` here first (the offer must come from \`agentproto pair offer --host\` run ON that device). Check \`agentproto devices list\` for known devices.`
|
|
50766
|
+
);
|
|
50767
|
+
}
|
|
50768
|
+
const bridge = await startDeviceSandboxBridge({ hostRegistry, target });
|
|
50769
|
+
return {
|
|
50770
|
+
mcpUrl: bridge.mcpUrl,
|
|
50771
|
+
sandboxId: `device-${target}-${randomUUID()}`,
|
|
50772
|
+
async stop() {
|
|
50773
|
+
await bridge.close();
|
|
50774
|
+
}
|
|
50775
|
+
};
|
|
50776
|
+
}
|
|
50777
|
+
};
|
|
50778
|
+
}
|
|
50779
|
+
function text9(value) {
|
|
49397
50780
|
return {
|
|
49398
50781
|
content: [
|
|
49399
50782
|
{
|
|
@@ -49411,7 +50794,7 @@ function registerDeviceTools(server, opts) {
|
|
|
49411
50794
|
{},
|
|
49412
50795
|
async () => {
|
|
49413
50796
|
const devices = await registry.list();
|
|
49414
|
-
return
|
|
50797
|
+
return text9({ devices });
|
|
49415
50798
|
}
|
|
49416
50799
|
);
|
|
49417
50800
|
server.tool(
|
|
@@ -49424,9 +50807,9 @@ function registerDeviceTools(server, opts) {
|
|
|
49424
50807
|
async ({ offerUrl, name }) => {
|
|
49425
50808
|
try {
|
|
49426
50809
|
const result = await registry.add(offerUrl, name);
|
|
49427
|
-
return
|
|
50810
|
+
return text9({ ok: true, ...result });
|
|
49428
50811
|
} catch (err) {
|
|
49429
|
-
return
|
|
50812
|
+
return text9({ ok: false, message: err instanceof Error ? err.message : String(err) });
|
|
49430
50813
|
}
|
|
49431
50814
|
}
|
|
49432
50815
|
);
|
|
@@ -49439,7 +50822,7 @@ function registerDeviceTools(server, opts) {
|
|
|
49439
50822
|
},
|
|
49440
50823
|
async ({ target, name }) => {
|
|
49441
50824
|
const renamed = await registry.rename(target, name);
|
|
49442
|
-
return
|
|
50825
|
+
return text9(
|
|
49443
50826
|
renamed ? { ok: true, target, name } : { ok: false, message: `no device matched "${target}"` }
|
|
49444
50827
|
);
|
|
49445
50828
|
}
|
|
@@ -49452,14 +50835,14 @@ function registerDeviceTools(server, opts) {
|
|
|
49452
50835
|
},
|
|
49453
50836
|
async ({ target }) => {
|
|
49454
50837
|
const revoked = await registry.revoke(target);
|
|
49455
|
-
return
|
|
50838
|
+
return text9(
|
|
49456
50839
|
revoked ? { ok: true, revoked: target } : { ok: false, message: `no device matched "${target}"` }
|
|
49457
50840
|
);
|
|
49458
50841
|
}
|
|
49459
50842
|
);
|
|
49460
50843
|
server.tool(
|
|
49461
50844
|
"device_sessions",
|
|
49462
|
-
"Read-only: a registered HOST device's own session list (compact), or (with sessionId) a tail of one session's output \u2014 forwarded live over the host's E2E channel (HostRegistry.forwardHttp), the same path device_add / `agentproto devices exec` uses.
|
|
50845
|
+
"Read-only: a registered HOST device's own session list (compact), or (with sessionId) a tail of one session's output \u2014 forwarded live over the host's E2E channel (HostRegistry.forwardHttp), the same path device_add / `agentproto devices exec` uses. If the host is offline, falls back to the last successful response for this exact query (stale: true, capturedAt: when it was captured) instead of failing outright \u2014 still rejects if there's nothing cached, or the target isn't a registered host at all.",
|
|
49463
50846
|
{
|
|
49464
50847
|
target: z.string().describe("The host's fingerprint or name (see device_list)."),
|
|
49465
50848
|
sessionId: z.string().optional().describe("Tail this session's output instead of listing all sessions."),
|
|
@@ -49474,12 +50857,13 @@ function registerDeviceTools(server, opts) {
|
|
|
49474
50857
|
try {
|
|
49475
50858
|
const res = await registry.forwardHttp(target, { method: "GET", path });
|
|
49476
50859
|
const body = Buffer.from(res.body).toString("utf8");
|
|
50860
|
+
const staleFields = res.stale ? { stale: true, capturedAt: res.capturedAt } : {};
|
|
49477
50861
|
if (res.status !== 200) {
|
|
49478
|
-
return
|
|
50862
|
+
return text9({ ok: false, status: res.status, message: body, ...staleFields });
|
|
49479
50863
|
}
|
|
49480
|
-
return
|
|
50864
|
+
return text9({ ok: true, ...staleFields, ...JSON.parse(body) });
|
|
49481
50865
|
} catch (err) {
|
|
49482
|
-
return
|
|
50866
|
+
return text9({ ok: false, message: err instanceof Error ? err.message : String(err) });
|
|
49483
50867
|
}
|
|
49484
50868
|
}
|
|
49485
50869
|
);
|
|
@@ -49499,9 +50883,9 @@ function registerDeviceTools(server, opts) {
|
|
|
49499
50883
|
...ttlMs !== void 0 ? { ttlMs } : {},
|
|
49500
50884
|
...maxUses !== void 0 ? { maxUses } : {}
|
|
49501
50885
|
});
|
|
49502
|
-
return
|
|
50886
|
+
return text9({ ok: true, ...created });
|
|
49503
50887
|
} catch (err) {
|
|
49504
|
-
return
|
|
50888
|
+
return text9({ ok: false, message: err instanceof Error ? err.message : String(err) });
|
|
49505
50889
|
}
|
|
49506
50890
|
}
|
|
49507
50891
|
);
|
|
@@ -49509,7 +50893,7 @@ function registerDeviceTools(server, opts) {
|
|
|
49509
50893
|
"join_token_list",
|
|
49510
50894
|
"List join tokens: id, name, createdAt, expiresAt, maxUses, useCount, lastUsedAt, revokedAt. Never returns the token's secret. Read-only.",
|
|
49511
50895
|
{},
|
|
49512
|
-
async () =>
|
|
50896
|
+
async () => text9({ tokens: await joinTokens.list() })
|
|
49513
50897
|
);
|
|
49514
50898
|
server.tool(
|
|
49515
50899
|
"join_token_revoke",
|
|
@@ -49517,7 +50901,7 @@ function registerDeviceTools(server, opts) {
|
|
|
49517
50901
|
{ target: z.string().describe("The token's id or name (see join_token_list).") },
|
|
49518
50902
|
async ({ target }) => {
|
|
49519
50903
|
const revoked = await joinTokens.revoke(target);
|
|
49520
|
-
return
|
|
50904
|
+
return text9(
|
|
49521
50905
|
revoked ? { ok: true, revoked: target } : { ok: false, message: `no join token matched "${target}"` }
|
|
49522
50906
|
);
|
|
49523
50907
|
}
|
|
@@ -49526,7 +50910,7 @@ function registerDeviceTools(server, opts) {
|
|
|
49526
50910
|
}
|
|
49527
50911
|
|
|
49528
50912
|
// src/daemon-health-tools.ts
|
|
49529
|
-
function
|
|
50913
|
+
function text10(value) {
|
|
49530
50914
|
return {
|
|
49531
50915
|
content: [
|
|
49532
50916
|
{
|
|
@@ -49542,7 +50926,7 @@ function registerDaemonHealthTools(server, opts) {
|
|
|
49542
50926
|
"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.",
|
|
49543
50927
|
{},
|
|
49544
50928
|
async () => {
|
|
49545
|
-
return
|
|
50929
|
+
return text10({
|
|
49546
50930
|
alive: true,
|
|
49547
50931
|
status: "ok",
|
|
49548
50932
|
workspace: opts.workspace,
|
|
@@ -49651,7 +51035,7 @@ function namedTunnelProvider(cfg) {
|
|
|
49651
51035
|
async check() {
|
|
49652
51036
|
const [bin, creds] = await Promise.all([
|
|
49653
51037
|
cloudflaredOnPath2(),
|
|
49654
|
-
|
|
51038
|
+
fileExists3(credentialsFile)
|
|
49655
51039
|
]);
|
|
49656
51040
|
return bin && creds;
|
|
49657
51041
|
},
|
|
@@ -49730,15 +51114,15 @@ function namedTunnelProvider(cfg) {
|
|
|
49730
51114
|
}
|
|
49731
51115
|
}, 3e3);
|
|
49732
51116
|
timer.unref();
|
|
49733
|
-
await new Promise((
|
|
51117
|
+
await new Promise((resolve38) => {
|
|
49734
51118
|
if (proc.exitCode !== null) {
|
|
49735
51119
|
clearTimeout(timer);
|
|
49736
|
-
|
|
51120
|
+
resolve38();
|
|
49737
51121
|
return;
|
|
49738
51122
|
}
|
|
49739
51123
|
proc.once("exit", () => {
|
|
49740
51124
|
clearTimeout(timer);
|
|
49741
|
-
|
|
51125
|
+
resolve38();
|
|
49742
51126
|
});
|
|
49743
51127
|
});
|
|
49744
51128
|
}
|
|
@@ -49752,7 +51136,7 @@ async function cloudflaredOnPath2() {
|
|
|
49752
51136
|
return false;
|
|
49753
51137
|
}
|
|
49754
51138
|
}
|
|
49755
|
-
async function
|
|
51139
|
+
async function fileExists3(path) {
|
|
49756
51140
|
try {
|
|
49757
51141
|
await access(path);
|
|
49758
51142
|
return true;
|
|
@@ -49855,14 +51239,14 @@ function ngrokTunnelProvider(opts) {
|
|
|
49855
51239
|
});
|
|
49856
51240
|
child = proc;
|
|
49857
51241
|
const forwardLogs = (chunk) => {
|
|
49858
|
-
const
|
|
49859
|
-
for (const line of
|
|
51242
|
+
const text13 = chunk.toString("utf8");
|
|
51243
|
+
for (const line of text13.split(/\r?\n/)) {
|
|
49860
51244
|
if (line.length > 0) opts2.onLog?.(`[ngrok] ${line}`);
|
|
49861
51245
|
}
|
|
49862
51246
|
};
|
|
49863
51247
|
proc.stderr?.on("data", forwardLogs);
|
|
49864
51248
|
proc.stdout?.on("data", forwardLogs);
|
|
49865
|
-
const url = await new Promise((
|
|
51249
|
+
const url = await new Promise((resolve38, reject) => {
|
|
49866
51250
|
let settled = false;
|
|
49867
51251
|
const timer = setTimeout(() => {
|
|
49868
51252
|
if (settled) return;
|
|
@@ -49890,7 +51274,7 @@ function ngrokTunnelProvider(opts) {
|
|
|
49890
51274
|
settled = true;
|
|
49891
51275
|
clearTimeout(timer);
|
|
49892
51276
|
if (apiPollHandle) clearInterval(apiPollHandle);
|
|
49893
|
-
|
|
51277
|
+
resolve38(pubUrl);
|
|
49894
51278
|
}
|
|
49895
51279
|
} catch {
|
|
49896
51280
|
}
|
|
@@ -49899,16 +51283,16 @@ function ngrokTunnelProvider(opts) {
|
|
|
49899
51283
|
};
|
|
49900
51284
|
const onStdout = (chunk) => {
|
|
49901
51285
|
if (settled) return;
|
|
49902
|
-
const
|
|
49903
|
-
const reMatch =
|
|
51286
|
+
const text13 = chunk.toString("utf8");
|
|
51287
|
+
const reMatch = text13.match(URL_JSON_REGEX);
|
|
49904
51288
|
if (reMatch) {
|
|
49905
51289
|
settled = true;
|
|
49906
51290
|
clearTimeout(timer);
|
|
49907
51291
|
if (apiPollHandle) clearInterval(apiPollHandle);
|
|
49908
|
-
|
|
51292
|
+
resolve38(reMatch[1]);
|
|
49909
51293
|
return;
|
|
49910
51294
|
}
|
|
49911
|
-
for (const line of
|
|
51295
|
+
for (const line of text13.split(/\r?\n/)) {
|
|
49912
51296
|
if (line.length === 0) continue;
|
|
49913
51297
|
try {
|
|
49914
51298
|
const parsed = JSON.parse(line);
|
|
@@ -49916,7 +51300,7 @@ function ngrokTunnelProvider(opts) {
|
|
|
49916
51300
|
settled = true;
|
|
49917
51301
|
clearTimeout(timer);
|
|
49918
51302
|
if (apiPollHandle) clearInterval(apiPollHandle);
|
|
49919
|
-
|
|
51303
|
+
resolve38(parsed.url);
|
|
49920
51304
|
return;
|
|
49921
51305
|
}
|
|
49922
51306
|
} catch {
|
|
@@ -49963,15 +51347,15 @@ function ngrokTunnelProvider(opts) {
|
|
|
49963
51347
|
}
|
|
49964
51348
|
}, 3e3);
|
|
49965
51349
|
timer.unref();
|
|
49966
|
-
await new Promise((
|
|
51350
|
+
await new Promise((resolve38) => {
|
|
49967
51351
|
if (proc.exitCode !== null) {
|
|
49968
51352
|
clearTimeout(timer);
|
|
49969
|
-
|
|
51353
|
+
resolve38();
|
|
49970
51354
|
return;
|
|
49971
51355
|
}
|
|
49972
51356
|
proc.once("exit", () => {
|
|
49973
51357
|
clearTimeout(timer);
|
|
49974
|
-
|
|
51358
|
+
resolve38();
|
|
49975
51359
|
});
|
|
49976
51360
|
});
|
|
49977
51361
|
}
|
|
@@ -50341,7 +51725,7 @@ function makeStubProvider() {
|
|
|
50341
51725
|
}
|
|
50342
51726
|
};
|
|
50343
51727
|
}
|
|
50344
|
-
function
|
|
51728
|
+
function text11(value) {
|
|
50345
51729
|
return {
|
|
50346
51730
|
content: [
|
|
50347
51731
|
{
|
|
@@ -50416,7 +51800,7 @@ function registerTunnelTools(server, opts) {
|
|
|
50416
51800
|
...input.tunnelId ? { tunnelId: input.tunnelId } : {},
|
|
50417
51801
|
...input.credentialsFile ? { credentialsFile: input.credentialsFile } : {}
|
|
50418
51802
|
});
|
|
50419
|
-
return
|
|
51803
|
+
return text11(desc);
|
|
50420
51804
|
} catch (err) {
|
|
50421
51805
|
return errText2("tunnel_create", err);
|
|
50422
51806
|
}
|
|
@@ -50472,7 +51856,7 @@ function registerTunnelTools(server, opts) {
|
|
|
50472
51856
|
isError: true
|
|
50473
51857
|
};
|
|
50474
51858
|
}
|
|
50475
|
-
return
|
|
51859
|
+
return text11({ ok: ok3, tunnelId: input.tunnelId });
|
|
50476
51860
|
} catch (err) {
|
|
50477
51861
|
return errText2("tunnel_stop", err);
|
|
50478
51862
|
}
|
|
@@ -50499,7 +51883,7 @@ function registerTunnelTools(server, opts) {
|
|
|
50499
51883
|
isError: true
|
|
50500
51884
|
};
|
|
50501
51885
|
}
|
|
50502
|
-
return
|
|
51886
|
+
return text11(desc);
|
|
50503
51887
|
}
|
|
50504
51888
|
);
|
|
50505
51889
|
}
|
|
@@ -51083,7 +52467,7 @@ function tailFile(path, n) {
|
|
|
51083
52467
|
return "(no llm-endpoint output captured)";
|
|
51084
52468
|
}
|
|
51085
52469
|
}
|
|
51086
|
-
function
|
|
52470
|
+
function text12(value) {
|
|
51087
52471
|
return {
|
|
51088
52472
|
content: [
|
|
51089
52473
|
{
|
|
@@ -51119,11 +52503,11 @@ function withLinksMap() {
|
|
|
51119
52503
|
name: "withLinksMap",
|
|
51120
52504
|
wrapHandler: (handler) => async (input) => {
|
|
51121
52505
|
const result = await handler(input);
|
|
51122
|
-
const
|
|
51123
|
-
if (result.isError ||
|
|
52506
|
+
const text13 = result.content[0]?.text;
|
|
52507
|
+
if (result.isError || text13 === void 0) return result;
|
|
51124
52508
|
let parsed;
|
|
51125
52509
|
try {
|
|
51126
|
-
parsed = JSON.parse(
|
|
52510
|
+
parsed = JSON.parse(text13);
|
|
51127
52511
|
} catch {
|
|
51128
52512
|
return result;
|
|
51129
52513
|
}
|
|
@@ -51171,7 +52555,7 @@ function registerLlmEndpointTools(server, opts) {
|
|
|
51171
52555
|
...input.env ? { env: input.env } : {},
|
|
51172
52556
|
...input.binPath ? { binPath: input.binPath } : {}
|
|
51173
52557
|
});
|
|
51174
|
-
return
|
|
52558
|
+
return text12(desc);
|
|
51175
52559
|
} catch (err) {
|
|
51176
52560
|
return errText3("llm_endpoint_start", err);
|
|
51177
52561
|
}
|
|
@@ -51184,7 +52568,7 @@ function registerLlmEndpointTools(server, opts) {
|
|
|
51184
52568
|
async () => {
|
|
51185
52569
|
try {
|
|
51186
52570
|
const ok3 = await registry.stop();
|
|
51187
|
-
return
|
|
52571
|
+
return text12({ ok: ok3 });
|
|
51188
52572
|
} catch (err) {
|
|
51189
52573
|
return errText3("llm_endpoint_stop", err);
|
|
51190
52574
|
}
|
|
@@ -51197,7 +52581,7 @@ function registerLlmEndpointTools(server, opts) {
|
|
|
51197
52581
|
async () => {
|
|
51198
52582
|
try {
|
|
51199
52583
|
const s = await registry.status();
|
|
51200
|
-
return
|
|
52584
|
+
return text12(s);
|
|
51201
52585
|
} catch (err) {
|
|
51202
52586
|
return errText3("llm_endpoint_status", err);
|
|
51203
52587
|
}
|
|
@@ -51230,7 +52614,7 @@ function registerLlmEndpointTools(server, opts) {
|
|
|
51230
52614
|
}
|
|
51231
52615
|
const s = await registry.status();
|
|
51232
52616
|
const restartRequired = s.running;
|
|
51233
|
-
return
|
|
52617
|
+
return text12({
|
|
51234
52618
|
ok: true,
|
|
51235
52619
|
provider,
|
|
51236
52620
|
profileId,
|
|
@@ -51674,8 +53058,8 @@ function createPairingRegistry(deps2) {
|
|
|
51674
53058
|
log(`[pairing] ignoring ${pairingsPath}: unrecognised format`);
|
|
51675
53059
|
}
|
|
51676
53060
|
} catch (err) {
|
|
51677
|
-
if (!
|
|
51678
|
-
log(`[pairing] could not read ${pairingsPath}: ${
|
|
53061
|
+
if (!isEnoent2(err)) {
|
|
53062
|
+
log(`[pairing] could not read ${pairingsPath}: ${errMsg2(err)}`);
|
|
51679
53063
|
}
|
|
51680
53064
|
}
|
|
51681
53065
|
}
|
|
@@ -51705,7 +53089,7 @@ function createPairingRegistry(deps2) {
|
|
|
51705
53089
|
if (loops.has(spec.key)) return;
|
|
51706
53090
|
const abort = new AbortController();
|
|
51707
53091
|
const done = runLoop(spec, abort.signal).catch((err) => {
|
|
51708
|
-
log(`[pairing] loop ${spec.key} ended: ${
|
|
53092
|
+
log(`[pairing] loop ${spec.key} ended: ${errMsg2(err)}`);
|
|
51709
53093
|
});
|
|
51710
53094
|
loops.set(spec.key, { abort, done });
|
|
51711
53095
|
}
|
|
@@ -51728,7 +53112,7 @@ function createPairingRegistry(deps2) {
|
|
|
51728
53112
|
if (signal.aborted) break;
|
|
51729
53113
|
const line = dialFailureGate.onFailure(
|
|
51730
53114
|
spec.key,
|
|
51731
|
-
`[pairing] dial ${spec.key} failed: ${
|
|
53115
|
+
`[pairing] dial ${spec.key} failed: ${errMsg2(err)}`
|
|
51732
53116
|
);
|
|
51733
53117
|
if (line) log(line);
|
|
51734
53118
|
await sleep(backoff, signal);
|
|
@@ -51808,7 +53192,7 @@ function createPairingRegistry(deps2) {
|
|
|
51808
53192
|
try {
|
|
51809
53193
|
ctx = await spec.onPaired(capturedSession, capturedHello);
|
|
51810
53194
|
} catch (err) {
|
|
51811
|
-
log(`[pairing] persist for ${spec.key} failed: ${
|
|
53195
|
+
log(`[pairing] persist for ${spec.key} failed: ${errMsg2(err)}`);
|
|
51812
53196
|
wrapped.close("persist failed");
|
|
51813
53197
|
continue;
|
|
51814
53198
|
}
|
|
@@ -51931,7 +53315,7 @@ function createPairingRegistry(deps2) {
|
|
|
51931
53315
|
const existing = pairings.get(record2.fingerprint);
|
|
51932
53316
|
if (existing) {
|
|
51933
53317
|
existing.lastSeen = new Date(now()).toISOString();
|
|
51934
|
-
await persist().catch((err) => log(`[pairing] lastSeen persist failed: ${
|
|
53318
|
+
await persist().catch((err) => log(`[pairing] lastSeen persist failed: ${errMsg2(err)}`));
|
|
51935
53319
|
}
|
|
51936
53320
|
return {
|
|
51937
53321
|
mode: "reconnect",
|
|
@@ -52057,8 +53441,8 @@ function createPairingRegistry(deps2) {
|
|
|
52057
53441
|
return { createOffer, list, rename: renamePairing, revoke, isOnline, startAutoconnect, shutdown };
|
|
52058
53442
|
}
|
|
52059
53443
|
function dialUrl(rendezvousUrl, token) {
|
|
52060
|
-
const
|
|
52061
|
-
return `${rendezvousUrl}${
|
|
53444
|
+
const sep6 = rendezvousUrl.includes("?") ? "&" : "?";
|
|
53445
|
+
return `${rendezvousUrl}${sep6}side=daemon&t=${encodeURIComponent(token)}`;
|
|
52062
53446
|
}
|
|
52063
53447
|
function sendOutdatedNotice(sink) {
|
|
52064
53448
|
sink.send({ t: "error", code: "pairing_protocol_outdated", message: PAIRING_PROTOCOL_OUTDATED_MESSAGE });
|
|
@@ -52075,16 +53459,16 @@ function sendOutdatedNotice(sink) {
|
|
|
52075
53459
|
}
|
|
52076
53460
|
var REVOKED = /* @__PURE__ */ Symbol("pairing revoked");
|
|
52077
53461
|
function waitClosed(sink, signal) {
|
|
52078
|
-
return new Promise((
|
|
53462
|
+
return new Promise((resolve38) => {
|
|
52079
53463
|
if (!sink.isOpen) {
|
|
52080
|
-
|
|
53464
|
+
resolve38();
|
|
52081
53465
|
return;
|
|
52082
53466
|
}
|
|
52083
53467
|
let settled = false;
|
|
52084
53468
|
const finish = () => {
|
|
52085
53469
|
if (settled) return;
|
|
52086
53470
|
settled = true;
|
|
52087
|
-
|
|
53471
|
+
resolve38();
|
|
52088
53472
|
};
|
|
52089
53473
|
sink.onClose(() => finish());
|
|
52090
53474
|
signal.addEventListener("abort", () => {
|
|
@@ -52099,13 +53483,13 @@ function waitClosed(sink, signal) {
|
|
|
52099
53483
|
});
|
|
52100
53484
|
}
|
|
52101
53485
|
function sleep(ms, signal) {
|
|
52102
|
-
return new Promise((
|
|
52103
|
-
if (signal.aborted) return
|
|
52104
|
-
const timer = setTimeout(
|
|
53486
|
+
return new Promise((resolve38) => {
|
|
53487
|
+
if (signal.aborted) return resolve38();
|
|
53488
|
+
const timer = setTimeout(resolve38, ms);
|
|
52105
53489
|
if (typeof timer.unref === "function") timer.unref();
|
|
52106
53490
|
signal.addEventListener("abort", () => {
|
|
52107
53491
|
clearTimeout(timer);
|
|
52108
|
-
|
|
53492
|
+
resolve38();
|
|
52109
53493
|
});
|
|
52110
53494
|
});
|
|
52111
53495
|
}
|
|
@@ -52130,324 +53514,6 @@ function isRevokedRecord(v) {
|
|
|
52130
53514
|
return typeof t["epoch"] === "number" && typeof t["route"] === "string" && typeof t["auth"] === "string";
|
|
52131
53515
|
});
|
|
52132
53516
|
}
|
|
52133
|
-
function isEnoent(err) {
|
|
52134
|
-
return typeof err === "object" && err !== null && "code" in err && err.code === "ENOENT";
|
|
52135
|
-
}
|
|
52136
|
-
function errMsg(err) {
|
|
52137
|
-
return err instanceof Error ? err.message : String(err);
|
|
52138
|
-
}
|
|
52139
|
-
var HOSTS_VERSION = 1;
|
|
52140
|
-
var DIAL_TIMEOUT_MS = 15e3;
|
|
52141
|
-
var HANDSHAKE_TIMEOUT_MS = 15e3;
|
|
52142
|
-
function defaultHostsPath() {
|
|
52143
|
-
return join(homedir(), ".agentproto", "hosts.json");
|
|
52144
|
-
}
|
|
52145
|
-
function rvUrl(base, route) {
|
|
52146
|
-
const sep5 = base.includes("?") ? "&" : "?";
|
|
52147
|
-
return `${base}${sep5}side=client&t=${encodeURIComponent(route)}`;
|
|
52148
|
-
}
|
|
52149
|
-
function defaultSelfName() {
|
|
52150
|
-
try {
|
|
52151
|
-
return hostname() || "agentproto-host-registry";
|
|
52152
|
-
} catch {
|
|
52153
|
-
return "agentproto-host-registry";
|
|
52154
|
-
}
|
|
52155
|
-
}
|
|
52156
|
-
function createHostRegistry(deps2) {
|
|
52157
|
-
const hostsPath = deps2.hostsPath ?? defaultHostsPath();
|
|
52158
|
-
const now = deps2.now ?? Date.now;
|
|
52159
|
-
const log = deps2.log ?? (() => {
|
|
52160
|
-
});
|
|
52161
|
-
const dialTimeoutMs = deps2.dialTimeoutMs ?? DIAL_TIMEOUT_MS;
|
|
52162
|
-
const handshakeTimeoutMs = deps2.handshakeTimeoutMs ?? HANDSHAKE_TIMEOUT_MS;
|
|
52163
|
-
const selfName = defaultSelfName();
|
|
52164
|
-
const hosts = /* @__PURE__ */ new Map();
|
|
52165
|
-
const onlineCounts = /* @__PURE__ */ new Map();
|
|
52166
|
-
let loaded = false;
|
|
52167
|
-
async function ensureLoaded() {
|
|
52168
|
-
if (loaded) return;
|
|
52169
|
-
loaded = true;
|
|
52170
|
-
try {
|
|
52171
|
-
const raw = await readFile(hostsPath, "utf8");
|
|
52172
|
-
const parsed = JSON.parse(raw);
|
|
52173
|
-
if (isHostsFile(parsed)) {
|
|
52174
|
-
for (const rec of parsed.hosts) hosts.set(rec.fingerprint, rec);
|
|
52175
|
-
} else {
|
|
52176
|
-
log(`[hosts] ignoring ${hostsPath}: unrecognised format`);
|
|
52177
|
-
}
|
|
52178
|
-
} catch (err) {
|
|
52179
|
-
if (!isEnoent2(err)) log(`[hosts] could not read ${hostsPath}: ${errMsg2(err)}`);
|
|
52180
|
-
}
|
|
52181
|
-
}
|
|
52182
|
-
let persistChain = Promise.resolve();
|
|
52183
|
-
function persist() {
|
|
52184
|
-
const run = persistChain.then(doPersist);
|
|
52185
|
-
persistChain = run.catch(() => {
|
|
52186
|
-
});
|
|
52187
|
-
return run;
|
|
52188
|
-
}
|
|
52189
|
-
async function doPersist() {
|
|
52190
|
-
const file = { v: HOSTS_VERSION, hosts: Array.from(hosts.values()) };
|
|
52191
|
-
const dir = dirname(hostsPath);
|
|
52192
|
-
await mkdir(dir, { recursive: true });
|
|
52193
|
-
const tmp = join(dir, `.${basename(hostsPath)}.tmp-${process.pid}`);
|
|
52194
|
-
await writeFile(tmp, JSON.stringify(file, null, 2) + "\n", { encoding: "utf8", mode: 384 });
|
|
52195
|
-
await chmod(tmp, 384).catch(() => {
|
|
52196
|
-
});
|
|
52197
|
-
await rename(tmp, hostsPath);
|
|
52198
|
-
}
|
|
52199
|
-
function findHost(idOrName) {
|
|
52200
|
-
const byFp = hosts.get(idOrName);
|
|
52201
|
-
if (byFp) return byFp;
|
|
52202
|
-
for (const rec of hosts.values()) if (rec.name === idOrName) return rec;
|
|
52203
|
-
return void 0;
|
|
52204
|
-
}
|
|
52205
|
-
async function dialWithTimeout(wsUrl) {
|
|
52206
|
-
const ac = new AbortController();
|
|
52207
|
-
const timer = setTimeout(() => ac.abort(new Error(`dial timed out after ${dialTimeoutMs}ms`)), dialTimeoutMs);
|
|
52208
|
-
if (typeof timer.unref === "function") timer.unref();
|
|
52209
|
-
try {
|
|
52210
|
-
return await deps2.dial(wsUrl, ac.signal);
|
|
52211
|
-
} finally {
|
|
52212
|
-
clearTimeout(timer);
|
|
52213
|
-
}
|
|
52214
|
-
}
|
|
52215
|
-
async function add(offerUrl, name, meta) {
|
|
52216
|
-
await ensureLoaded();
|
|
52217
|
-
const offer = await parseOfferUrl(offerUrl, { now: now() });
|
|
52218
|
-
if (offer.scope !== "host") {
|
|
52219
|
-
throw new Error(
|
|
52220
|
-
"this offer is not host-scoped \u2014 it only grants remote-control access, not host registration; ask the other machine to run `agentproto pair offer --host` and use that offer instead"
|
|
52221
|
-
);
|
|
52222
|
-
}
|
|
52223
|
-
const { route, auth } = await deriveOfferTokens(offer.secret);
|
|
52224
|
-
const raw = await dialWithTimeout(rvUrl(offer.rendezvousUrl, route));
|
|
52225
|
-
const started = await startClientHandshake({
|
|
52226
|
-
daemonX25519Pub: offer.daemonX25519Pub,
|
|
52227
|
-
daemonEd25519Pub: offer.daemonEd25519Pub,
|
|
52228
|
-
authToken: auth,
|
|
52229
|
-
clientName: name ?? selfName
|
|
52230
|
-
});
|
|
52231
|
-
let session = null;
|
|
52232
|
-
const wrapped = await clientHandshakeOverSink(
|
|
52233
|
-
raw,
|
|
52234
|
-
encodePairingMessage(started.hello),
|
|
52235
|
-
async (replyBytes) => {
|
|
52236
|
-
session = await started.complete(decodePairingReply(replyBytes));
|
|
52237
|
-
return session;
|
|
52238
|
-
},
|
|
52239
|
-
{ timeoutMs: handshakeTimeoutMs }
|
|
52240
|
-
);
|
|
52241
|
-
if (!session) throw new Error("handshake did not derive a session");
|
|
52242
|
-
const derived = session;
|
|
52243
|
-
if (derived.peerFingerprint !== offer.fingerprint) {
|
|
52244
|
-
wrapped.close("fingerprint mismatch");
|
|
52245
|
-
throw new Error(
|
|
52246
|
-
`host fingerprint ${derived.peerFingerprint} does not match the offer's ${offer.fingerprint}`
|
|
52247
|
-
);
|
|
52248
|
-
}
|
|
52249
|
-
const pairRoot = await derivePairRoot(derived);
|
|
52250
|
-
const nowIso2 = new Date(now()).toISOString();
|
|
52251
|
-
const record2 = {
|
|
52252
|
-
fingerprint: offer.fingerprint,
|
|
52253
|
-
name: name ?? offer.fingerprint,
|
|
52254
|
-
daemonX25519Pub: offer.daemonX25519Pub,
|
|
52255
|
-
daemonEd25519Pub: offer.daemonEd25519Pub,
|
|
52256
|
-
rendezvousUrl: offer.rendezvousUrl,
|
|
52257
|
-
pairRoot,
|
|
52258
|
-
createdAt: hosts.get(offer.fingerprint)?.createdAt ?? nowIso2,
|
|
52259
|
-
lastSeen: nowIso2,
|
|
52260
|
-
...meta?.provider ? { provider: meta.provider } : {},
|
|
52261
|
-
...meta?.sandboxId ? { sandboxId: meta.sandboxId } : {},
|
|
52262
|
-
...meta?.labels ? { labels: meta.labels } : {}
|
|
52263
|
-
};
|
|
52264
|
-
hosts.set(record2.fingerprint, record2);
|
|
52265
|
-
await persist();
|
|
52266
|
-
log(`[hosts] added ${record2.fingerprint} (${record2.name})`);
|
|
52267
|
-
wrapped.close("add complete");
|
|
52268
|
-
return { fingerprint: record2.fingerprint, name: record2.name, rendezvousUrl: record2.rendezvousUrl };
|
|
52269
|
-
}
|
|
52270
|
-
async function list() {
|
|
52271
|
-
await ensureLoaded();
|
|
52272
|
-
return Array.from(hosts.values()).map((r) => ({ ...r }));
|
|
52273
|
-
}
|
|
52274
|
-
async function renameHost(idOrName, newName) {
|
|
52275
|
-
const trimmed = newName.trim();
|
|
52276
|
-
if (!trimmed) throw new Error("host name must not be empty");
|
|
52277
|
-
await ensureLoaded();
|
|
52278
|
-
const target = findHost(idOrName);
|
|
52279
|
-
if (!target) return false;
|
|
52280
|
-
target.name = trimmed;
|
|
52281
|
-
await persist();
|
|
52282
|
-
log(`[hosts] renamed ${target.fingerprint} to "${trimmed}"`);
|
|
52283
|
-
return true;
|
|
52284
|
-
}
|
|
52285
|
-
async function revoke(idOrName) {
|
|
52286
|
-
await ensureLoaded();
|
|
52287
|
-
const target = findHost(idOrName);
|
|
52288
|
-
if (!target) return false;
|
|
52289
|
-
hosts.delete(target.fingerprint);
|
|
52290
|
-
await persist();
|
|
52291
|
-
log(`[hosts] revoked ${target.fingerprint} (${target.name})`);
|
|
52292
|
-
return true;
|
|
52293
|
-
}
|
|
52294
|
-
function isOnline(fingerprint) {
|
|
52295
|
-
return (onlineCounts.get(fingerprint) ?? 0) > 0;
|
|
52296
|
-
}
|
|
52297
|
-
async function connectToHost(record2) {
|
|
52298
|
-
const epoch = currentEpoch(now());
|
|
52299
|
-
const attempts = [epoch, epoch - 1];
|
|
52300
|
-
let lastErr;
|
|
52301
|
-
let hungUpOnHello = 0;
|
|
52302
|
-
for (const e of attempts) {
|
|
52303
|
-
const { route, auth } = await deriveEpochTokens(record2.pairRoot, e);
|
|
52304
|
-
let raw;
|
|
52305
|
-
try {
|
|
52306
|
-
raw = await dialWithTimeout(rvUrl(record2.rendezvousUrl, route));
|
|
52307
|
-
} catch (err) {
|
|
52308
|
-
lastErr = err;
|
|
52309
|
-
continue;
|
|
52310
|
-
}
|
|
52311
|
-
try {
|
|
52312
|
-
const started = await startClientHandshake({
|
|
52313
|
-
daemonX25519Pub: record2.daemonX25519Pub,
|
|
52314
|
-
daemonEd25519Pub: record2.daemonEd25519Pub,
|
|
52315
|
-
authToken: auth,
|
|
52316
|
-
clientName: record2.name
|
|
52317
|
-
});
|
|
52318
|
-
const wrapped = await clientHandshakeOverSink(
|
|
52319
|
-
raw,
|
|
52320
|
-
encodePairingMessage(started.hello),
|
|
52321
|
-
(replyBytes) => started.complete(decodePairingReply(replyBytes)),
|
|
52322
|
-
{ timeoutMs: handshakeTimeoutMs }
|
|
52323
|
-
);
|
|
52324
|
-
const client = createTunnelClient({ sink: wrapped });
|
|
52325
|
-
await client.ready();
|
|
52326
|
-
record2.lastSeen = new Date(now()).toISOString();
|
|
52327
|
-
await persist().catch((err) => log(`[hosts] lastSeen persist failed: ${errMsg2(err)}`));
|
|
52328
|
-
return client;
|
|
52329
|
-
} catch (err) {
|
|
52330
|
-
lastErr = err;
|
|
52331
|
-
if (err instanceof Error && /transport closed during handshake/.test(err.message)) hungUpOnHello++;
|
|
52332
|
-
try {
|
|
52333
|
-
raw.close("handshake failed");
|
|
52334
|
-
} catch {
|
|
52335
|
-
}
|
|
52336
|
-
}
|
|
52337
|
-
}
|
|
52338
|
-
const hint = hungUpOnHello === attempts.length ? ` \u2014 the host hung up on the pair/v2 hello; if it runs an older agentproto (pair/v1), upgrade it and re-pair: run \`agentproto pair offer --host\` on the host, then \`agentproto devices add\` here. (${PAIRING_PROTOCOL_OUTDATED_MESSAGE})` : "";
|
|
52339
|
-
throw new Error(
|
|
52340
|
-
`could not reach host ${record2.fingerprint} via ${record2.rendezvousUrl}: ${lastErr instanceof Error ? lastErr.message : String(lastErr)}${hint}`
|
|
52341
|
-
);
|
|
52342
|
-
}
|
|
52343
|
-
function toTunnelReq(req) {
|
|
52344
|
-
return {
|
|
52345
|
-
method: req.method,
|
|
52346
|
-
path: req.path,
|
|
52347
|
-
...req.headers ? { headers: req.headers } : {},
|
|
52348
|
-
...req.body ? { body: Buffer.from(req.body) } : {}
|
|
52349
|
-
};
|
|
52350
|
-
}
|
|
52351
|
-
async function forwardHttp(idOrName, req) {
|
|
52352
|
-
await ensureLoaded();
|
|
52353
|
-
const record2 = findHost(idOrName);
|
|
52354
|
-
if (!record2) throw new Error(`no host matched "${idOrName}"`);
|
|
52355
|
-
onlineCounts.set(record2.fingerprint, (onlineCounts.get(record2.fingerprint) ?? 0) + 1);
|
|
52356
|
-
let client;
|
|
52357
|
-
try {
|
|
52358
|
-
client = await connectToHost(record2);
|
|
52359
|
-
const res = await client.forwardHttp(toTunnelReq(req));
|
|
52360
|
-
return { status: res.status, headers: { ...res.headers }, body: new Uint8Array(res.body) };
|
|
52361
|
-
} finally {
|
|
52362
|
-
if (client) await client.close().catch(() => {
|
|
52363
|
-
});
|
|
52364
|
-
const remaining = (onlineCounts.get(record2.fingerprint) ?? 1) - 1;
|
|
52365
|
-
if (remaining > 0) onlineCounts.set(record2.fingerprint, remaining);
|
|
52366
|
-
else onlineCounts.delete(record2.fingerprint);
|
|
52367
|
-
}
|
|
52368
|
-
}
|
|
52369
|
-
async function forwardHttpStream(idOrName, req) {
|
|
52370
|
-
await ensureLoaded();
|
|
52371
|
-
const record2 = findHost(idOrName);
|
|
52372
|
-
if (!record2) throw new Error(`no host matched "${idOrName}"`);
|
|
52373
|
-
onlineCounts.set(record2.fingerprint, (onlineCounts.get(record2.fingerprint) ?? 0) + 1);
|
|
52374
|
-
let decremented = false;
|
|
52375
|
-
const decrement = () => {
|
|
52376
|
-
if (decremented) return;
|
|
52377
|
-
decremented = true;
|
|
52378
|
-
const remaining = (onlineCounts.get(record2.fingerprint) ?? 1) - 1;
|
|
52379
|
-
if (remaining > 0) onlineCounts.set(record2.fingerprint, remaining);
|
|
52380
|
-
else onlineCounts.delete(record2.fingerprint);
|
|
52381
|
-
};
|
|
52382
|
-
let client;
|
|
52383
|
-
try {
|
|
52384
|
-
client = await connectToHost(record2);
|
|
52385
|
-
} catch (err) {
|
|
52386
|
-
decrement();
|
|
52387
|
-
throw err;
|
|
52388
|
-
}
|
|
52389
|
-
let res;
|
|
52390
|
-
try {
|
|
52391
|
-
res = await client.forwardHttpStream(toTunnelReq(req));
|
|
52392
|
-
} catch (err) {
|
|
52393
|
-
await client.close().catch(() => {
|
|
52394
|
-
});
|
|
52395
|
-
decrement();
|
|
52396
|
-
throw err;
|
|
52397
|
-
}
|
|
52398
|
-
const cleanup = () => {
|
|
52399
|
-
void client.close().catch(() => {
|
|
52400
|
-
});
|
|
52401
|
-
decrement();
|
|
52402
|
-
};
|
|
52403
|
-
return { status: res.status, headers: { ...res.headers }, body: wrapStreamWithCleanup(res.body, cleanup) };
|
|
52404
|
-
}
|
|
52405
|
-
return { add, list, rename: renameHost, revoke, isOnline, forwardHttp, forwardHttpStream };
|
|
52406
|
-
}
|
|
52407
|
-
function wrapStreamWithCleanup(source, cleanup) {
|
|
52408
|
-
const reader = source.getReader();
|
|
52409
|
-
let cleanedUp = false;
|
|
52410
|
-
const runCleanup = () => {
|
|
52411
|
-
if (cleanedUp) return;
|
|
52412
|
-
cleanedUp = true;
|
|
52413
|
-
cleanup();
|
|
52414
|
-
};
|
|
52415
|
-
return new ReadableStream({
|
|
52416
|
-
async pull(controller) {
|
|
52417
|
-
try {
|
|
52418
|
-
const { done, value } = await reader.read();
|
|
52419
|
-
if (done) {
|
|
52420
|
-
controller.close();
|
|
52421
|
-
runCleanup();
|
|
52422
|
-
return;
|
|
52423
|
-
}
|
|
52424
|
-
controller.enqueue(value);
|
|
52425
|
-
} catch (err) {
|
|
52426
|
-
controller.error(err);
|
|
52427
|
-
runCleanup();
|
|
52428
|
-
}
|
|
52429
|
-
},
|
|
52430
|
-
async cancel(reason) {
|
|
52431
|
-
try {
|
|
52432
|
-
await reader.cancel(reason);
|
|
52433
|
-
} finally {
|
|
52434
|
-
runCleanup();
|
|
52435
|
-
}
|
|
52436
|
-
}
|
|
52437
|
-
});
|
|
52438
|
-
}
|
|
52439
|
-
function isHostsFile(v) {
|
|
52440
|
-
if (typeof v !== "object" || v === null) return false;
|
|
52441
|
-
const rec = v;
|
|
52442
|
-
if (rec["v"] !== HOSTS_VERSION) return false;
|
|
52443
|
-
if (!Array.isArray(rec["hosts"])) return false;
|
|
52444
|
-
return rec["hosts"].every(isHostRecord);
|
|
52445
|
-
}
|
|
52446
|
-
function isHostRecord(v) {
|
|
52447
|
-
if (typeof v !== "object" || v === null) return false;
|
|
52448
|
-
const r = v;
|
|
52449
|
-
return typeof r["fingerprint"] === "string" && typeof r["name"] === "string" && typeof r["daemonX25519Pub"] === "string" && typeof r["daemonEd25519Pub"] === "string" && typeof r["rendezvousUrl"] === "string" && typeof r["pairRoot"] === "string" && typeof r["createdAt"] === "string" && typeof r["lastSeen"] === "string";
|
|
52450
|
-
}
|
|
52451
53517
|
function isEnoent2(err) {
|
|
52452
53518
|
return typeof err === "object" && err !== null && "code" in err && err.code === "ENOENT";
|
|
52453
53519
|
}
|
|
@@ -52472,8 +53538,8 @@ function constantTimeEqual2(a, b) {
|
|
|
52472
53538
|
return timingSafeEqual(da, db);
|
|
52473
53539
|
}
|
|
52474
53540
|
function dialUrl2(rendezvousUrl, route) {
|
|
52475
|
-
const
|
|
52476
|
-
return `${rendezvousUrl}${
|
|
53541
|
+
const sep6 = rendezvousUrl.includes("?") ? "&" : "?";
|
|
53542
|
+
return `${rendezvousUrl}${sep6}side=daemon&t=${encodeURIComponent(route)}`;
|
|
52477
53543
|
}
|
|
52478
53544
|
function parseJoinHello(clientName) {
|
|
52479
53545
|
if (!clientName) return null;
|
|
@@ -52630,17 +53696,29 @@ function createJoinTokenRegistry(deps2) {
|
|
|
52630
53696
|
log(`[join-tokens] "${record2.name}": joined box sent no self-offer \u2014 nothing added`);
|
|
52631
53697
|
return;
|
|
52632
53698
|
}
|
|
52633
|
-
const name = typeof hello.name === "string" && hello.name ? hello.name : void 0;
|
|
52634
53699
|
const meta = {
|
|
53700
|
+
joined: true,
|
|
52635
53701
|
...typeof hello.provider === "string" && hello.provider ? { provider: hello.provider } : {},
|
|
52636
53702
|
...typeof hello.sandboxId === "string" && hello.sandboxId ? { sandboxId: hello.sandboxId } : {},
|
|
52637
53703
|
...isPlainStringRecord(hello.labels) ? { labels: hello.labels } : {}
|
|
52638
53704
|
};
|
|
53705
|
+
const name = typeof hello.name === "string" && hello.name ? hello.name : meta.labels?.["pr"] ? `${record2.name} #${meta.labels["pr"]}` : record2.name;
|
|
52639
53706
|
try {
|
|
52640
53707
|
await deps2.addHost(hello.offerUrl, name, meta);
|
|
52641
53708
|
log(`[join-tokens] "${record2.name}": added host via join (self-reported name "${name ?? "(none)"}")`);
|
|
53709
|
+
if (record2.lastJoinError !== void 0 || record2.lastJoinErrorAt !== void 0) {
|
|
53710
|
+
delete record2.lastJoinError;
|
|
53711
|
+
delete record2.lastJoinErrorAt;
|
|
53712
|
+
await persist();
|
|
53713
|
+
}
|
|
52642
53714
|
} catch (err) {
|
|
52643
|
-
|
|
53715
|
+
const message = errMsg3(err);
|
|
53716
|
+
log(
|
|
53717
|
+
`[join-tokens] "${record2.name}": ADDING THE JOINED BOX AS A HOST FAILED (useCount bumped, no device recorded \u2014 visible via join_token_list until the next successful join): ${message}`
|
|
53718
|
+
);
|
|
53719
|
+
record2.lastJoinError = message;
|
|
53720
|
+
record2.lastJoinErrorAt = new Date(now()).toISOString();
|
|
53721
|
+
await persist();
|
|
52644
53722
|
}
|
|
52645
53723
|
}
|
|
52646
53724
|
async function runAcceptLoop(record2, signal) {
|
|
@@ -52718,13 +53796,13 @@ function createJoinTokenRegistry(deps2) {
|
|
|
52718
53796
|
return { create, list, revoke, startAutoconnect, shutdown };
|
|
52719
53797
|
}
|
|
52720
53798
|
function sleep2(ms, signal) {
|
|
52721
|
-
return new Promise((
|
|
52722
|
-
if (signal.aborted) return
|
|
52723
|
-
const timer = setTimeout(
|
|
53799
|
+
return new Promise((resolve38) => {
|
|
53800
|
+
if (signal.aborted) return resolve38();
|
|
53801
|
+
const timer = setTimeout(resolve38, ms);
|
|
52724
53802
|
if (typeof timer.unref === "function") timer.unref();
|
|
52725
53803
|
signal.addEventListener("abort", () => {
|
|
52726
53804
|
clearTimeout(timer);
|
|
52727
|
-
|
|
53805
|
+
resolve38();
|
|
52728
53806
|
});
|
|
52729
53807
|
});
|
|
52730
53808
|
}
|
|
@@ -52864,7 +53942,35 @@ async function createGateway(opts) {
|
|
|
52864
53942
|
const port = opts.port ?? 18790;
|
|
52865
53943
|
const token = opts.token ?? randomUUID();
|
|
52866
53944
|
const daemonMcpUrl = `http://127.0.0.1:${port}/mcp`;
|
|
52867
|
-
const
|
|
53945
|
+
const baseSandboxProviderResolver = opts.resolveSandboxProvider ?? makeSandboxResolver(makeSandboxCredsStore());
|
|
53946
|
+
const resolveSandboxProviderResolved = async (slug) => {
|
|
53947
|
+
if (slug.startsWith("device:")) {
|
|
53948
|
+
const deviceName = slug.slice("device:".length);
|
|
53949
|
+
if (!opts.hostRegistry || deviceName.length === 0) return null;
|
|
53950
|
+
return {
|
|
53951
|
+
provider: createDeviceSandboxProvider(deviceName, opts.hostRegistry),
|
|
53952
|
+
slug,
|
|
53953
|
+
name: deviceName,
|
|
53954
|
+
version: "builtin",
|
|
53955
|
+
description: `Spawns and proxies an agent session on the paired host device "${deviceName}" over its pair/v2 channel.`,
|
|
53956
|
+
requiresSetup: false,
|
|
53957
|
+
capabilities: {
|
|
53958
|
+
networkEgress: true,
|
|
53959
|
+
mounts: false,
|
|
53960
|
+
lifecyclePause: false,
|
|
53961
|
+
readOnly: false
|
|
53962
|
+
},
|
|
53963
|
+
// See that field's doc — a device's filesystem is disjoint from the
|
|
53964
|
+
// driving daemon's; forwarding a HOST-shaped implicit cwd would
|
|
53965
|
+
// almost always ENOENT there.
|
|
53966
|
+
omitCwdWhenImplicit: true,
|
|
53967
|
+
async check() {
|
|
53968
|
+
return true;
|
|
53969
|
+
}
|
|
53970
|
+
};
|
|
53971
|
+
}
|
|
53972
|
+
return baseSandboxProviderResolver(slug);
|
|
53973
|
+
};
|
|
52868
53974
|
const { httpOrigin: publicHttpOrigin, wsOrigin: ptyWsBaseUrl } = resolvePublicAppOrigins(port);
|
|
52869
53975
|
const events = createRuntimeEvents();
|
|
52870
53976
|
const conversations = fileConversationStore({ workspace });
|
|
@@ -53172,12 +54278,16 @@ async function createGateway(opts) {
|
|
|
53172
54278
|
persist ? {} : { root: join(tmpdir(), `agentproto-reviews-${process.pid}-${randomUUID()}`) }
|
|
53173
54279
|
),
|
|
53174
54280
|
daemonId: `agentproto-runtime@${hostname()}:${port}`,
|
|
54281
|
+
// A test gateway (`persist: false`) signs with a throwaway keypair, same
|
|
54282
|
+
// as its ledger — never the real `~/.agentproto/keys`.
|
|
54283
|
+
...persist ? {} : { signingKeysDir: join(tmpdir(), `agentproto-review-keys-${process.pid}-${randomUUID()}`) },
|
|
54284
|
+
...daemonConfig.review?.principal ? { signingPrincipal: daemonConfig.review.principal } : {},
|
|
53175
54285
|
// Display-only settle notice (Goal A item 4, review-session-panel step):
|
|
53176
54286
|
// a daemon-authored `notice` in the requester's own transcript, never a
|
|
53177
54287
|
// prompt/inbox delivery — see `SessionsRegistry.recordNotice`'s doc for
|
|
53178
54288
|
// why this can't wake an idle session or interrupt a busy one.
|
|
53179
|
-
notifyRequester: (sessionId,
|
|
53180
|
-
sessions.recordNotice(sessionId,
|
|
54289
|
+
notifyRequester: (sessionId, text13) => {
|
|
54290
|
+
sessions.recordNotice(sessionId, text13);
|
|
53181
54291
|
},
|
|
53182
54292
|
...opts.resolveAgentAdapter ? {
|
|
53183
54293
|
reviewers: createDaemonReviewerHost({
|
|
@@ -53420,6 +54530,7 @@ async function createGateway(opts) {
|
|
|
53420
54530
|
registerAuthProfileTools(server);
|
|
53421
54531
|
registerConfigTools(server, configToolsDeps);
|
|
53422
54532
|
registerHarnessPresetTools(server);
|
|
54533
|
+
registerUserPresetTools(server, { registry: sessions });
|
|
53423
54534
|
registerCredentialDiscoveryTools(server);
|
|
53424
54535
|
registerWebSearchTools(server);
|
|
53425
54536
|
registerBrowserTools(server, {
|
|
@@ -53686,6 +54797,7 @@ async function createGateway(opts) {
|
|
|
53686
54797
|
},
|
|
53687
54798
|
...llmEndpoint ? { llmEndpoint } : {},
|
|
53688
54799
|
...opts.deviceInferenceShare ? { deviceInferenceShare: true } : {},
|
|
54800
|
+
...opts.deviceSpawnAllow ? { deviceSpawnAllow: true } : {},
|
|
53689
54801
|
remote,
|
|
53690
54802
|
...opts.pairingRegistry ? { pairings: opts.pairingRegistry } : {},
|
|
53691
54803
|
...opts.hostRegistry ? { hostRegistry: opts.hostRegistry } : {},
|
|
@@ -53980,6 +55092,6 @@ var export_providersPath = providers_store_exports.providersPath;
|
|
|
53980
55092
|
var export_removeProviderKey = providers_store_exports.removeProviderKey;
|
|
53981
55093
|
var export_setProviderKey = providers_store_exports.setProviderKey;
|
|
53982
55094
|
|
|
53983
|
-
export { AnthropicRemainingQuotaReader, AuthResolutionError, BUCKETS_ROOT, CLAUDE_CODE_OAUTH_SOURCE, CONTINUE_PROMPT_SOURCE, DEAD_SESSION_STATUSES, DEFAULT_ALWAYS_ON_TOOLS, DEFAULT_BUCKET, DEFAULT_CONTINUE_PROMPT, DEFAULT_ORCHESTRATOR_TOOLS, DEFAULT_WORKTREE_ISOLATION, GC_REAPABLE_STATES, HOSTS_VERSION, HOST_SCOPE_HEADER, HarnessPresetValidationError, INBOUND_PROVIDERS, JOIN_TOKENS_VERSION, LEGACY_SESSIONS_FILE, MAX_AUTO_CONTINUE_ATTEMPTS, MAX_MESSAGE_DATA_BYTES, MCP_APP_BRIDGE_SCRIPT, MESSAGE_KINDS, MESSAGE_PREAMBLE, MESSAGE_TAG, MESSAGE_URGENCIES, PAIRINGS_VERSION, POSTURE_NATIVE_ALIASES, POSTURE_PREAMBLES, export_PROVIDER_ENV_VARS as PROVIDER_ENV_VARS, PrLookupError, ResumeDisabledError, SESSION_END_REASONS, SESSION_ID_ENV, SubscriptionSourceError, TASK_STATUSES, WORKSPACE_SLUG_ENV, WORKTREE_ISOLATION_ENV, activityCounts, addHarnessPreset, appendConversationRecord, attachSandbox, bucketDir, bucketSessionsFile, bucketTranscriptDir, buildCatalogModels, buildCatalogProviderModels, buildMcpConfigSnippet, buildRouteAwareLaunchConfig, canonicalForModeId, claudeProjectSlug, collectGcCandidates, compactOutcome, composeMode, composeSessionObservers, continueInterruptedSessions, continueSkipReason, conversationIndexPath, createActivityProjector, createDaemonReviewerHost, createDeviceRegistry, createFileStepCache, createGateway, createHostRegistry, createInboundEndpointStore, createJoinTokenRegistry, createOrchestratorInjector, createOrchestratorMcpServerFactory, createPairingRegistry, createPrProvenanceReconciler, createReconnectLogGate, createReviewLaneExecutor, createReviewLedger, createReviewRunner, createScopeTokenRegistry, createSessionMessage, createSupervisorTaskGateRunner, createTaskLedger, createWorkspaceBrainSubscriber, createWorkspaceBrains, createWorkspaceFs, credentialFingerprint, daemonRegistryDir, decideWorktreeIsolation, declaredPresetToProviderPreset, decomposeMode, defaultReviewLedgerRoot, defineBraveSearchHttpDriver, defineSerperHttpDriver, deleteUserPreset, deriveSessionOutcome, deriveSessionUsage, enrichRollupWithProviderQuota, enrichWithRemainingQuota, escapeHumanPrompt, escapeMessageBody, evaluateCostBudget, execGh, extractHelpSection, fetchPrStatus, fileConversationStore, filterActivities, findConversationRecord, findNativeMode, findPrForCommit, formatToolCall, formatToolResult, getDefaultHarnessPreset, getHarnessPreset, getMcpCredentialDeps, export_getProviderKey as getProviderKey, getToolHelp, getUserPreset, githubRepoOf, harnessPresetsPath, hostPlaceholders, injectMcpAppBridge, export_injectProviderKeysIntoEnv as injectProviderKeysIntoEnv, isAgentCliAuthConfigured, isClosedTaskStatus, isKnownSessionEndReason, isMessageAllowed, isProviderLimitError, isSafeBucketSlug, isTerminalActivityState, listBuckets, listHarnessPresets, listPresets, listToolHelpTopics, listUserPresets, loadHarnessPresets, export_loadProviders as loadProviders, loadQuotaStore, loadUserPresets, loadWorktreeIsolation, locateConversationByNativePath, locateConversationBySessionId, makeBrowserAdapterLister, makeSandboxCredsStore, makeSandboxResolver, matchesMessageFilter, measureTool, measureToolList, messageFrom, migrateLegacySessionsFile, migrationMarkerPath, mintMessageId, monitorPolicyWait, monitorSessionWait, narrowOrchestratorTools, normalizeInbound, normalizeModeId, normalizeRemote, normalizeSkillsOption, normalizeWorktreeField, parseAnthropicRateLimitHeaders, parseDuration, parsePrUrl, parseTaskStatus, parseWindow, parseWorktreeIsolationMode, pickUsageDetail, policyToActivities, policyWatchesSession, prHeadSha, prToActivities, projectSessionUsage, export_providerEnvVar as providerEnvVar, export_providersPath as providersPath, readConversationIndex, readDaemonRegistry, readPairingsSnapshot, readProfileQuota, readRegisteredSlugs, readRuntimeMeta, readSandboxLedger, readSessionForBrain, readUsageSnapshots, reapGcEntry, reapOrphanedDescendants, reconcileSandboxLedger, recordProfileQuota, recordSandboxBoot, recordSandboxLiveness, recordSandboxOrigin, recordSandboxState, registerBrainTools, registerBuiltinRoutes, registerDeviceTools, registerPairingTools, registerReviewTools, registerSandboxAttachTool, registerWebSearchTools, removeHarnessPreset, export_removeProviderKey as removeProviderKey, removeSandboxLedgerEntry, renderInboxDigest, renderSessionMessage, renderSessionMessages, repoSlug, resolveAuthSpec, resolveBucketSlug, resolveDeferredToolsGatewayOption, resolveEffectiveLlmEndpointFlag, resolveNativeLink, resolvePosture, resolveRelation, resolveReuseFromLedger, resolveReviewerPreset, resolveSpawnDefaults, resolveSubscriptionCredential, rollupSessionSubtree, rollupUsage, runContinueOnBootPass, runCrashDetectPass, runEagerResumePass, runIdleReapPass, runShellLane, runStallWatchdogPass, sandboxLedgerPath, sanitizeLabel, saveUserPreset, setDefaultPreset, setMcpCredentialDeps, export_setProviderKey as setProviderKey, sweepStaleDaemonRegistry, sweepStaleRuntimeMetas, toWorktreeStatusView, turnToActivities, unlinkDaemonRegistryEntry, unlinkRuntimeMeta, updateHarnessPreset, upsertSandboxLedger, userPresetsPath, verifyInboundSignature, webSearchTool, withPr, withPrStatus, workflowToActivities, writeDaemonRegistryEntry };
|
|
55095
|
+
export { AnthropicRemainingQuotaReader, AuthResolutionError, BUCKETS_ROOT, CLAUDE_CODE_OAUTH_SOURCE, CONTINUE_PROMPT_SOURCE, DEAD_SESSION_STATUSES, DEFAULT_ALWAYS_ON_TOOLS, DEFAULT_BUCKET, DEFAULT_CONTINUE_PROMPT, DEFAULT_ORCHESTRATOR_TOOLS, DEFAULT_WORKTREE_ISOLATION, GC_REAPABLE_STATES, HOSTS_VERSION, HOST_SCOPE_HEADER, HarnessPresetValidationError, INBOUND_PROVIDERS, JOIN_TOKENS_VERSION, LEGACY_SESSIONS_FILE, MAX_AUTO_CONTINUE_ATTEMPTS, MAX_MESSAGE_DATA_BYTES, MCP_APP_BRIDGE_SCRIPT, MESSAGE_KINDS, MESSAGE_PREAMBLE, MESSAGE_TAG, MESSAGE_URGENCIES, PAIRINGS_VERSION, POSTURE_NATIVE_ALIASES, POSTURE_PREAMBLES, export_PROVIDER_ENV_VARS as PROVIDER_ENV_VARS, PrLookupError, ResumeDisabledError, SESSION_END_REASONS, SESSION_ID_ENV, SIGN_NAMESPACE, SubscriptionSourceError, TASK_STATUSES, WORKSPACE_SLUG_ENV, WORKTREE_ISOLATION_ENV, activityCounts, addHarnessPreset, allowedSignersLine, appendConversationRecord, attachSandbox, bucketDir, bucketSessionsFile, bucketTranscriptDir, buildCatalogModels, buildCatalogProviderModels, buildMcpConfigSnippet, buildRouteAwareLaunchConfig, canonicalForModeId, claudeProjectSlug, collectGcCandidates, compactOutcome, composeMode, composeSessionObservers, composedFromRangeSha, continueInterruptedSessions, continueSkipReason, conversationIndexPath, createActivityProjector, createDaemonReviewerHost, createDeviceRegistry, createFileStepCache, createGateway, createHostRegistry, createInboundEndpointStore, createJoinTokenRegistry, createOrchestratorInjector, createOrchestratorMcpServerFactory, createPairingRegistry, createPrProvenanceReconciler, createReconnectLogGate, createReviewLaneExecutor, createReviewLedger, createReviewPackLoader, createReviewRunner, createScopeTokenRegistry, createSessionMessage, createSupervisorTaskGateRunner, createTaskLedger, createWorkspaceBrainSubscriber, createWorkspaceBrains, createWorkspaceFs, credentialFingerprint, daemonRegistryDir, decideWorktreeIsolation, declaredPresetToProviderPreset, decomposeMode, defaultReviewKeysDir, defaultReviewLedgerRoot, defaultReviewPackCacheDir, defineBraveSearchHttpDriver, defineSerperHttpDriver, deleteUserPreset, deriveSessionOutcome, deriveSessionUsage, enrichRollupWithProviderQuota, enrichWithRemainingQuota, ensureReviewSigningKey, escapeHumanPrompt, escapeMessageBody, evaluateCostBudget, execGh, extractHelpSection, fetchPrStatus, fileConversationStore, fileExists2 as fileExists, filterActivities, findComposeCandidate, findConversationRecord, findNativeMode, findPrForCommit, formatToolCall, formatToolResult, getDefaultHarnessPreset, getHarnessPreset, getMcpCredentialDeps, export_getProviderKey as getProviderKey, getToolHelp, getUserPreset, githubRepoOf, harnessPresetsPath, hostPlaceholders, injectMcpAppBridge, export_injectProviderKeysIntoEnv as injectProviderKeysIntoEnv, isAgentCliAuthConfigured, isClosedTaskStatus, isKnownSessionEndReason, isMessageAllowed, isProviderLimitError, isSafeBucketSlug, isTerminalActivityState, listBuckets, listHarnessPresets, listPresets, listToolHelpTopics, listUserPresets, loadHarnessPresets, export_loadProviders as loadProviders, loadQuotaStore, loadUserPresets, loadWorktreeIsolation, locateConversationByNativePath, locateConversationBySessionId, makeBrowserAdapterLister, makeSandboxCredsStore, makeSandboxResolver, matchesMessageFilter, measureTool, measureToolList, messageFrom, migrateLegacySessionsFile, migrationMarkerPath, mintMessageId, monitorPolicyWait, monitorSessionWait, narrowOrchestratorTools, normalizeInbound, normalizeModeId, normalizeRemote, normalizeSkillsOption, normalizeWorktreeField, parseAnthropicRateLimitHeaders, parseDuration, parsePrUrl, parseTaskStatus, parseWindow, parseWorktreeIsolationMode, pickUsageDetail, policyToActivities, policyWatchesSession, prHeadSha, prToActivities, projectSessionUsage, export_providerEnvVar as providerEnvVar, export_providersPath as providersPath, readConversationIndex, readDaemonRegistry, readPairingsSnapshot, readProfileQuota, readRegisteredSlugs, readRuntimeMeta, readSandboxLedger, readSessionForBrain, readUsageSnapshots, reapGcEntry, reapOrphanedDescendants, reconcileSandboxLedger, recordProfileQuota, recordSandboxBoot, recordSandboxLiveness, recordSandboxOrigin, recordSandboxState, registerBrainTools, registerBuiltinRoutes, registerDeviceTools, registerPairingTools, registerReviewTools, registerSandboxAttachTool, registerWebSearchTools, removeHarnessPreset, export_removeProviderKey as removeProviderKey, removeSandboxLedgerEntry, renderInboxDigest, renderSessionMessage, renderSessionMessages, repoSlug, resolveAuthSpec, resolveBucketSlug, resolveDeferredToolsGatewayOption, resolveEffectiveLlmEndpointFlag, resolveNativeLink, resolvePosture, resolvePrincipal, resolveRelation, resolveReuseFromLedger, resolveReviewerPreset, resolveSpawnDefaults, resolveSubscriptionCredential, rollupSessionSubtree, rollupUsage, runContinueOnBootPass, runCrashDetectPass, runEagerResumePass, runIdleReapPass, runShellLane, runStallWatchdogPass, sandboxLedgerPath, sanitizeLabel, saveUserPreset, setDefaultPreset, setMcpCredentialDeps, export_setProviderKey as setProviderKey, signAttestation, sweepStaleDaemonRegistry, sweepStaleRuntimeMetas, toWorktreeStatusView, turnToActivities, unlinkDaemonRegistryEntry, unlinkRuntimeMeta, updateHarnessPreset, upsertSandboxLedger, userPresetsPath, verifyInboundSignature, verifySignedAttestation, webSearchTool, withPr, withPrStatus, workflowToActivities, writeDaemonRegistryEntry };
|
|
53984
55096
|
//# sourceMappingURL=index.mjs.map
|
|
53985
55097
|
//# sourceMappingURL=index.mjs.map
|