@agentproto/runtime 5.4.1 → 5.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/config-schema.mjs +14 -1
- package/dist/config-schema.mjs.map +1 -1
- package/dist/config.mjs +14 -1
- package/dist/config.mjs.map +1 -1
- package/dist/index.d.ts +331 -1
- package/dist/index.mjs +1194 -243
- package/dist/index.mjs.map +1 -1
- package/dist/session-presence.mjs +14 -1
- package/dist/session-presence.mjs.map +1 -1
- package/package.json +7 -7
package/dist/index.mjs
CHANGED
|
@@ -19,9 +19,12 @@ import { resolvePairPageUrl, DEFAULT_PAIR_PAGE } from '@agentproto/secrets/pairi
|
|
|
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
21
|
import { syncPiModels, getConfiguredEndpoints, checkHarnessFit, connectorById } from '@agentproto/llm-endpoint';
|
|
22
|
+
import { createServer } from 'http';
|
|
23
|
+
import { createServer as createServer$1 } from 'net';
|
|
24
|
+
import { Readable } from 'stream';
|
|
25
|
+
import { pipeline } from 'stream/promises';
|
|
22
26
|
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
23
27
|
import { StreamableHTTPError, StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
|
|
24
|
-
import { createServer as createServer$1 } from 'net';
|
|
25
28
|
import { SandboxSpecSchema, resolveLifecyclePolicy, createSandboxAgentSessionHost, isSandboxBoxGoneError, SandboxHostBootFailedError } from '@agentproto/sandbox';
|
|
26
29
|
import { APP_UI_DISCOVERY_TOOLS, RUNNER_SELECT_SCRIPT } from '@agentproto/app-client/runner-select';
|
|
27
30
|
import { DISPLAY_MODE_SCRIPT } from '@agentproto/app-client/display-mode';
|
|
@@ -35,8 +38,6 @@ import { createBrainManager, parseKnowledgeConfig } from '@agentproto/workspace-
|
|
|
35
38
|
import { browserAdapters, getBrowserAdapter } from '@agentproto/adapter-browser';
|
|
36
39
|
import { defineHttpDriver } from '@agentproto/driver-http';
|
|
37
40
|
import matter2 from 'gray-matter';
|
|
38
|
-
import { createServer } from 'http';
|
|
39
|
-
import { Readable } from 'stream';
|
|
40
41
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
41
42
|
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
|
|
42
43
|
import { WebSocketServer } from 'ws';
|
|
@@ -67,7 +68,8 @@ import { createRequire } from 'module';
|
|
|
67
68
|
import { signArtifact, recordAuditEvent } from '@agentproto/governance-engine';
|
|
68
69
|
import { parseRoutineManifest } from '@agentproto/routine';
|
|
69
70
|
import { navigateOptionsSchema, evaluateOptionsSchema, clickOptionsSchema, fillOptionsSchema, screenshotOptionsSchema, assertToolSupported } from '@agentproto/driver-browser';
|
|
70
|
-
import {
|
|
71
|
+
import { lookup } from 'dns/promises';
|
|
72
|
+
import { request } from 'https';
|
|
71
73
|
import { identityFingerprint } from '@agentproto/secrets/identity';
|
|
72
74
|
|
|
73
75
|
/**
|
|
@@ -4874,16 +4876,16 @@ var init_workspaces_config = __esm({
|
|
|
4874
4876
|
function normalizeWorktreeField(field) {
|
|
4875
4877
|
if (field === void 0 || field === false) return void 0;
|
|
4876
4878
|
if (field === true) return {};
|
|
4877
|
-
const
|
|
4878
|
-
if (field.slug !== void 0)
|
|
4879
|
-
if (field.base !== void 0)
|
|
4880
|
-
if (field.async !== void 0)
|
|
4881
|
-
return
|
|
4879
|
+
const request2 = {};
|
|
4880
|
+
if (field.slug !== void 0) request2.slug = field.slug;
|
|
4881
|
+
if (field.base !== void 0) request2.base = field.base;
|
|
4882
|
+
if (field.async !== void 0) request2.async = field.async;
|
|
4883
|
+
return request2;
|
|
4882
4884
|
}
|
|
4883
4885
|
function decideWorktreeIsolation(input) {
|
|
4884
|
-
const
|
|
4886
|
+
const request2 = normalizeWorktreeField(input.field);
|
|
4885
4887
|
if (input.depth > 0) {
|
|
4886
|
-
if (
|
|
4888
|
+
if (request2 !== void 0) {
|
|
4887
4889
|
return {
|
|
4888
4890
|
action: "reject",
|
|
4889
4891
|
message: `agent_start: \`worktree\` isolation was requested on a nested spawn (depth ${input.depth}), but a spawn made through an orchestrator inherits its parent's working tree and cannot provision a worktree of its own. Remove the \`worktree\` field; for an isolated child use \`sandbox\` (the box already isolates) instead.`
|
|
@@ -4899,7 +4901,7 @@ function decideWorktreeIsolation(input) {
|
|
|
4899
4901
|
}
|
|
4900
4902
|
switch (input.mode) {
|
|
4901
4903
|
case "never":
|
|
4902
|
-
if (
|
|
4904
|
+
if (request2 !== void 0) {
|
|
4903
4905
|
return {
|
|
4904
4906
|
action: "reject",
|
|
4905
4907
|
message: `agent_start: \`worktree\` was requested but this daemon's \`worktrees.isolation\` policy is set to "never". Remove the \`worktree\` field, or change the policy (config \`worktrees.isolation\` / env ${WORKTREE_ISOLATION_ENV}).`
|
|
@@ -4907,9 +4909,9 @@ function decideWorktreeIsolation(input) {
|
|
|
4907
4909
|
}
|
|
4908
4910
|
return { action: "spawn-in-place" };
|
|
4909
4911
|
case "on-request":
|
|
4910
|
-
return
|
|
4912
|
+
return request2 !== void 0 ? { action: "provision", request: request2, implicit: false } : { action: "spawn-in-place" };
|
|
4911
4913
|
case "always":
|
|
4912
|
-
return { action: "provision", request:
|
|
4914
|
+
return { action: "provision", request: request2 ?? {}, implicit: request2 === void 0 };
|
|
4913
4915
|
}
|
|
4914
4916
|
}
|
|
4915
4917
|
function parseWorktreeIsolationMode(raw) {
|
|
@@ -4942,16 +4944,16 @@ function normalizeAttachField(field) {
|
|
|
4942
4944
|
if (field === void 0) return void 0;
|
|
4943
4945
|
if (field === false) return { detached: true };
|
|
4944
4946
|
if (field === true) return { optIn: true };
|
|
4945
|
-
const
|
|
4946
|
-
if (field.parent !== void 0)
|
|
4947
|
-
return
|
|
4947
|
+
const request2 = { optIn: true };
|
|
4948
|
+
if (field.parent !== void 0) request2.parent = field.parent;
|
|
4949
|
+
return request2;
|
|
4948
4950
|
}
|
|
4949
4951
|
function decideSpawnAttach(input) {
|
|
4950
|
-
const
|
|
4951
|
-
if (
|
|
4952
|
-
if (
|
|
4952
|
+
const request2 = normalizeAttachField(input.field);
|
|
4953
|
+
if (request2?.detached) return { parent: void 0, detached: true };
|
|
4954
|
+
if (request2?.parent) return { parent: request2.parent, detached: false };
|
|
4953
4955
|
const candidate = input.hint ?? input.autoParent;
|
|
4954
|
-
if (
|
|
4956
|
+
if (request2?.optIn) return { parent: candidate, detached: false };
|
|
4955
4957
|
switch (input.mode) {
|
|
4956
4958
|
case "always":
|
|
4957
4959
|
return { parent: candidate, detached: false };
|
|
@@ -5676,8 +5678,8 @@ function pathSegmentsMatch(entryPath, concretePath) {
|
|
|
5676
5678
|
return entrySegs.every((seg, i) => seg === "*" || seg === concreteSegs[i]);
|
|
5677
5679
|
}
|
|
5678
5680
|
function findConfigKey(path) {
|
|
5679
|
-
const
|
|
5680
|
-
return CONFIG_KEYS.find((entry) => pathSegmentsMatch(entry.path,
|
|
5681
|
+
const lookup2 = path.startsWith("models.") && path.length > "models.".length ? "models.*" : path;
|
|
5682
|
+
return CONFIG_KEYS.find((entry) => pathSegmentsMatch(entry.path, lookup2));
|
|
5681
5683
|
}
|
|
5682
5684
|
function validateConfig(raw) {
|
|
5683
5685
|
const result = agentprotoConfigSchema.safeParse(raw);
|
|
@@ -9243,6 +9245,139 @@ var init_sandbox_agent_session_proxy = __esm({
|
|
|
9243
9245
|
]);
|
|
9244
9246
|
}
|
|
9245
9247
|
});
|
|
9248
|
+
function collectHeaders(raw) {
|
|
9249
|
+
const headers = {};
|
|
9250
|
+
for (const [k, v] of Object.entries(raw)) {
|
|
9251
|
+
if (v === void 0 || HOP_BY_HOP.has(k.toLowerCase())) continue;
|
|
9252
|
+
headers[k] = Array.isArray(v) ? v.join(", ") : v;
|
|
9253
|
+
}
|
|
9254
|
+
return headers;
|
|
9255
|
+
}
|
|
9256
|
+
function isPrematureCloseError(err) {
|
|
9257
|
+
return typeof err === "object" && err !== null && "code" in err && err.code === "ERR_STREAM_PREMATURE_CLOSE";
|
|
9258
|
+
}
|
|
9259
|
+
async function readBody(req) {
|
|
9260
|
+
const chunks = [];
|
|
9261
|
+
for await (const chunk of req) chunks.push(chunk);
|
|
9262
|
+
if (chunks.length === 0) return void 0;
|
|
9263
|
+
return new Uint8Array(Buffer.concat(chunks));
|
|
9264
|
+
}
|
|
9265
|
+
async function startDeviceSandboxBridge(opts) {
|
|
9266
|
+
const { hostRegistry, target } = opts;
|
|
9267
|
+
const server = createServer((req, res) => {
|
|
9268
|
+
void handleRequest(req, res).catch((err) => {
|
|
9269
|
+
if (res.writableEnded) return;
|
|
9270
|
+
if (!res.headersSent) {
|
|
9271
|
+
res.writeHead(502, { "content-type": "application/json" });
|
|
9272
|
+
res.end(
|
|
9273
|
+
JSON.stringify({
|
|
9274
|
+
error: "device_sandbox_bridge_failed",
|
|
9275
|
+
message: err instanceof Error ? err.message : String(err)
|
|
9276
|
+
})
|
|
9277
|
+
);
|
|
9278
|
+
return;
|
|
9279
|
+
}
|
|
9280
|
+
res.end();
|
|
9281
|
+
});
|
|
9282
|
+
});
|
|
9283
|
+
async function handleRequest(req, res) {
|
|
9284
|
+
const body = await readBody(req);
|
|
9285
|
+
let upstream;
|
|
9286
|
+
try {
|
|
9287
|
+
upstream = await hostRegistry.forwardHttpStream(target, {
|
|
9288
|
+
method: req.method ?? "GET",
|
|
9289
|
+
path: `/device-spawn${req.url ?? "/"}`,
|
|
9290
|
+
headers: collectHeaders(req.headers),
|
|
9291
|
+
...body ? { body } : {}
|
|
9292
|
+
});
|
|
9293
|
+
} catch (err) {
|
|
9294
|
+
res.writeHead(502, { "content-type": "application/json" });
|
|
9295
|
+
res.end(
|
|
9296
|
+
JSON.stringify({
|
|
9297
|
+
error: "device_unreachable",
|
|
9298
|
+
message: err instanceof Error ? err.message : String(err)
|
|
9299
|
+
})
|
|
9300
|
+
);
|
|
9301
|
+
return;
|
|
9302
|
+
}
|
|
9303
|
+
const resHeaders = {};
|
|
9304
|
+
for (const [k, v] of Object.entries(upstream.headers)) {
|
|
9305
|
+
if (HOP_BY_HOP.has(k.toLowerCase()) || k.toLowerCase() === "content-length") continue;
|
|
9306
|
+
resHeaders[k] = v;
|
|
9307
|
+
}
|
|
9308
|
+
res.writeHead(upstream.status, resHeaders);
|
|
9309
|
+
try {
|
|
9310
|
+
await pipeline(
|
|
9311
|
+
Readable.fromWeb(upstream.body),
|
|
9312
|
+
res
|
|
9313
|
+
);
|
|
9314
|
+
} catch (err) {
|
|
9315
|
+
if (!isPrematureCloseError(err)) throw err;
|
|
9316
|
+
}
|
|
9317
|
+
}
|
|
9318
|
+
const port = await getFreePort();
|
|
9319
|
+
await new Promise((resolve41, reject) => {
|
|
9320
|
+
server.once("error", reject);
|
|
9321
|
+
server.listen(port, "127.0.0.1", () => resolve41());
|
|
9322
|
+
});
|
|
9323
|
+
return {
|
|
9324
|
+
mcpUrl: `http://127.0.0.1:${port}/mcp`,
|
|
9325
|
+
async close() {
|
|
9326
|
+
await new Promise((resolve41) => server.close(() => resolve41()));
|
|
9327
|
+
}
|
|
9328
|
+
};
|
|
9329
|
+
}
|
|
9330
|
+
async function getFreePort() {
|
|
9331
|
+
return new Promise((resolve41, reject) => {
|
|
9332
|
+
const srv = createServer$1();
|
|
9333
|
+
srv.once("error", reject);
|
|
9334
|
+
srv.listen(0, "127.0.0.1", () => {
|
|
9335
|
+
const address = srv.address();
|
|
9336
|
+
if (address === null || typeof address === "string") {
|
|
9337
|
+
srv.close(() => reject(new Error("failed to allocate a free port")));
|
|
9338
|
+
return;
|
|
9339
|
+
}
|
|
9340
|
+
const { port } = address;
|
|
9341
|
+
srv.close(() => resolve41(port));
|
|
9342
|
+
});
|
|
9343
|
+
});
|
|
9344
|
+
}
|
|
9345
|
+
var HOP_BY_HOP;
|
|
9346
|
+
var init_device_sandbox_bridge = __esm({
|
|
9347
|
+
"src/device-sandbox-bridge.ts"() {
|
|
9348
|
+
HOP_BY_HOP = /* @__PURE__ */ new Set(["connection", "keep-alive", "transfer-encoding", "upgrade", "host"]);
|
|
9349
|
+
}
|
|
9350
|
+
});
|
|
9351
|
+
function isDeviceSandboxTarget(sandbox) {
|
|
9352
|
+
const slug = typeof sandbox === "string" ? sandbox : sandbox?.provider ?? "";
|
|
9353
|
+
return slug.startsWith("device:");
|
|
9354
|
+
}
|
|
9355
|
+
function createDeviceSandboxProvider(target, hostRegistry) {
|
|
9356
|
+
return {
|
|
9357
|
+
async boot(_spec, _opts) {
|
|
9358
|
+
const hosts = await hostRegistry.list();
|
|
9359
|
+
const known = hosts.some((h) => h.fingerprint === target || h.name === target);
|
|
9360
|
+
if (!known) {
|
|
9361
|
+
throw new Error(
|
|
9362
|
+
`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.`
|
|
9363
|
+
);
|
|
9364
|
+
}
|
|
9365
|
+
const bridge = await startDeviceSandboxBridge({ hostRegistry, target });
|
|
9366
|
+
return {
|
|
9367
|
+
mcpUrl: bridge.mcpUrl,
|
|
9368
|
+
sandboxId: `device-${target}-${randomUUID()}`,
|
|
9369
|
+
async stop() {
|
|
9370
|
+
await bridge.close();
|
|
9371
|
+
}
|
|
9372
|
+
};
|
|
9373
|
+
}
|
|
9374
|
+
};
|
|
9375
|
+
}
|
|
9376
|
+
var init_device = __esm({
|
|
9377
|
+
"src/sandbox-providers/device.ts"() {
|
|
9378
|
+
init_device_sandbox_bridge();
|
|
9379
|
+
}
|
|
9380
|
+
});
|
|
9246
9381
|
async function connectBoxToolClient(mcpUrl) {
|
|
9247
9382
|
const client = new Client(
|
|
9248
9383
|
{ name: "agentproto-sandbox-app-serve", version: "0.1.0" },
|
|
@@ -9471,7 +9606,7 @@ var init_sandbox_app_serve = __esm({
|
|
|
9471
9606
|
REQUIRED_ALLOWLIST_BASENAMES = ["sh", "agentproto"];
|
|
9472
9607
|
}
|
|
9473
9608
|
});
|
|
9474
|
-
async function
|
|
9609
|
+
async function getFreePort2() {
|
|
9475
9610
|
return new Promise((resolve41, reject) => {
|
|
9476
9611
|
const srv = createServer$1();
|
|
9477
9612
|
srv.once("error", reject);
|
|
@@ -9505,7 +9640,7 @@ var init_local = __esm({
|
|
|
9505
9640
|
POLL_INTERVAL_MS = 250;
|
|
9506
9641
|
localSandboxProvider = {
|
|
9507
9642
|
async boot(_spec, opts) {
|
|
9508
|
-
const port = await
|
|
9643
|
+
const port = await getFreePort2();
|
|
9509
9644
|
const workspace = await mkdtemp(join(tmpdir(), "agentproto-sandbox-local-"));
|
|
9510
9645
|
const child = spawn(
|
|
9511
9646
|
"agentproto",
|
|
@@ -10159,6 +10294,9 @@ ${callerPrompt}`;
|
|
|
10159
10294
|
const pre = composed.slice(0, composed.length - tail.length);
|
|
10160
10295
|
return pre || void 0;
|
|
10161
10296
|
}
|
|
10297
|
+
function deviceContractLine(device) {
|
|
10298
|
+
return `This session was spawned remotely on paired device "${device}" by the controlling daemon \u2014 its own workspace (and any AGENTS.md along that path) is on ANOTHER machine and has not been injected here. If your resolved cwd has an AGENTS.md, treat it as binding on your next tool call (read it first); if you \`cd\` somewhere else (a nested repo included), re-resolve and re-read the AGENTS.md of wherever you end up.`;
|
|
10299
|
+
}
|
|
10162
10300
|
async function spawnAgentSession(deps2, input) {
|
|
10163
10301
|
if (input.inference) {
|
|
10164
10302
|
if (input.model || input.route || input.access) {
|
|
@@ -10972,22 +11110,30 @@ async function spawnAgentSession(deps2, input) {
|
|
|
10972
11110
|
let effectivePrompt = input.prompt;
|
|
10973
11111
|
let agentsMdResolution;
|
|
10974
11112
|
const resolveAgentsMdForSpawn = resolveAgentsMd2 ?? (async (cwdArg, _kb, o) => resolveAgentsMd(cwdArg, await loadAgentsMdInlineMaxKb(), void 0, o?.stopAt));
|
|
10975
|
-
|
|
10976
|
-
|
|
10977
|
-
|
|
10978
|
-
agentsMdResolution = { mode: "absent", contractLine:
|
|
11113
|
+
const isDeviceSandboxSpawn = isDeviceSandboxTarget(input.sandbox);
|
|
11114
|
+
const deviceTargetName = typeof input.sandbox === "string" ? input.sandbox.slice("device:".length) : (input.sandbox?.provider ?? "").slice("device:".length);
|
|
11115
|
+
if (isDeviceSandboxSpawn) {
|
|
11116
|
+
agentsMdResolution = { mode: "absent", contractLine: deviceContractLine(deviceTargetName) };
|
|
11117
|
+
} else {
|
|
11118
|
+
try {
|
|
11119
|
+
agentsMdResolution = appBoundary ? await resolveAgentsMdForSpawn(cwd, void 0, { stopAt: appBoundary.root }) : await resolveAgentsMdForSpawn(cwd);
|
|
11120
|
+
} catch {
|
|
11121
|
+
agentsMdResolution = { mode: "absent", contractLine: cdContractLine };
|
|
11122
|
+
}
|
|
10979
11123
|
}
|
|
10980
11124
|
const agentsMdParts = [agentsMdResolution.block, agentsMdResolution.contractLine].filter(
|
|
10981
11125
|
(p) => !!p
|
|
10982
11126
|
);
|
|
10983
|
-
const agentsMdReadPaths = additionalReadPathsForAgentsMd(agentsMdResolution, cwd);
|
|
11127
|
+
const agentsMdReadPaths = isDeviceSandboxSpawn ? void 0 : additionalReadPathsForAgentsMd(agentsMdResolution, cwd);
|
|
10984
11128
|
const additionalReadPaths = [...agentsMdReadPaths ?? [], ...browserReadPaths];
|
|
10985
11129
|
let workspaceRulesResolution = {};
|
|
10986
11130
|
const resolveWorkspaceRulesForSpawn = resolveWorkspaceRules2 ?? resolveWorkspaceRules;
|
|
10987
|
-
|
|
10988
|
-
|
|
10989
|
-
|
|
10990
|
-
|
|
11131
|
+
if (!isDeviceSandboxSpawn) {
|
|
11132
|
+
try {
|
|
11133
|
+
workspaceRulesResolution = await resolveWorkspaceRulesForSpawn(resolvedSlug);
|
|
11134
|
+
} catch {
|
|
11135
|
+
workspaceRulesResolution = {};
|
|
11136
|
+
}
|
|
10991
11137
|
}
|
|
10992
11138
|
const rulesMdParts = workspaceRulesResolution.block ? [workspaceRulesResolution.block] : [];
|
|
10993
11139
|
if (input.prompt && typeof input.prompt === "string") {
|
|
@@ -11941,6 +12087,7 @@ var init_session_spawn = __esm({
|
|
|
11941
12087
|
init_inference_binding();
|
|
11942
12088
|
init_mcp_credential_deps();
|
|
11943
12089
|
init_sandbox_agent_session_proxy();
|
|
12090
|
+
init_device();
|
|
11944
12091
|
init_sandbox_ledger();
|
|
11945
12092
|
init_sandbox_app_serve();
|
|
11946
12093
|
init_sandbox_adapters();
|
|
@@ -12667,21 +12814,21 @@ function injectStandaloneAppBridge(html, baseUrl) {
|
|
|
12667
12814
|
return injectAfterStructuralTag(html, script);
|
|
12668
12815
|
}
|
|
12669
12816
|
function createUiHtmlCache() {
|
|
12670
|
-
const
|
|
12817
|
+
const cache3 = /* @__PURE__ */ new Map();
|
|
12671
12818
|
return {
|
|
12672
12819
|
async get(path, version) {
|
|
12673
12820
|
const st = await stat(path);
|
|
12674
12821
|
const stamp = `${version}:${st.mtimeMs}:${st.size}`;
|
|
12675
|
-
const cached =
|
|
12822
|
+
const cached = cache3.get(path);
|
|
12676
12823
|
if (cached && cached.stamp === stamp) return cached.html;
|
|
12677
12824
|
const raw = await readFile(path, "utf8");
|
|
12678
12825
|
const html = injectMcpAppBridge(raw);
|
|
12679
|
-
|
|
12826
|
+
cache3.set(path, { stamp, html });
|
|
12680
12827
|
return html;
|
|
12681
12828
|
}
|
|
12682
12829
|
};
|
|
12683
12830
|
}
|
|
12684
|
-
async function makeInstalledAppUiApps(appRegistry,
|
|
12831
|
+
async function makeInstalledAppUiApps(appRegistry, cache3, existingToolNames) {
|
|
12685
12832
|
const apps = [];
|
|
12686
12833
|
const seen = new Set(existingToolNames);
|
|
12687
12834
|
for (const app of appRegistry.listApps()) {
|
|
@@ -12714,7 +12861,7 @@ async function makeInstalledAppUiApps(appRegistry, cache2, existingToolNames) {
|
|
|
12714
12861
|
});
|
|
12715
12862
|
}
|
|
12716
12863
|
try {
|
|
12717
|
-
return await
|
|
12864
|
+
return await cache3.get(ui.path, app.updatedAt);
|
|
12718
12865
|
} catch (err) {
|
|
12719
12866
|
return renderAppUiErrorHtml({
|
|
12720
12867
|
appName,
|
|
@@ -24217,7 +24364,7 @@ function createDefaultHostProbes() {
|
|
|
24217
24364
|
);
|
|
24218
24365
|
if (child.pid !== void 0) own.add(child.pid);
|
|
24219
24366
|
});
|
|
24220
|
-
const
|
|
24367
|
+
const sleep3 = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
24221
24368
|
const probes = {
|
|
24222
24369
|
platform,
|
|
24223
24370
|
table: (t) => createProcessTableSource(t)(),
|
|
@@ -24245,7 +24392,7 @@ function createDefaultHostProbes() {
|
|
|
24245
24392
|
const read = () => Promise.all([readFile("/proc/stat", "utf8"), readFile("/proc/diskstats", "utf8").catch(() => "")]);
|
|
24246
24393
|
const t0 = Date.now();
|
|
24247
24394
|
const [statBefore, diskBefore] = await read();
|
|
24248
|
-
await
|
|
24395
|
+
await sleep3(400);
|
|
24249
24396
|
const [statAfter, diskAfter] = await read();
|
|
24250
24397
|
return { statBefore, statAfter, diskBefore, diskAfter, intervalSec: (Date.now() - t0) / 1e3 };
|
|
24251
24398
|
};
|
|
@@ -24790,7 +24937,7 @@ async function callJevSystemOne(state, questions, opts) {
|
|
|
24790
24937
|
const fetchImpl = opts.fetchImpl ?? fetch;
|
|
24791
24938
|
const url = (opts.baseUrl ?? JEV_DEFAULT_URL).replace(/\/+$/, "");
|
|
24792
24939
|
const maxRetries = opts.maxRetries ?? 3;
|
|
24793
|
-
const
|
|
24940
|
+
const sleep3 = opts.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
24794
24941
|
const model = opts.model ?? JEV_DEFAULT_MODEL;
|
|
24795
24942
|
const timeoutMs = opts.timeoutMs ?? 3e4;
|
|
24796
24943
|
let attempt = 0;
|
|
@@ -24831,7 +24978,7 @@ async function callJevSystemOne(state, questions, opts) {
|
|
|
24831
24978
|
}
|
|
24832
24979
|
if (RETRYABLE_STATUSES.has(res.status) && attempt < maxRetries) {
|
|
24833
24980
|
attempt++;
|
|
24834
|
-
await
|
|
24981
|
+
await sleep3(backoffDelayMs(attempt, res.headers.get("retry-after")));
|
|
24835
24982
|
continue;
|
|
24836
24983
|
}
|
|
24837
24984
|
const body = await res.text().catch(() => "");
|
|
@@ -34907,6 +35054,9 @@ var DEFAULT_PROBE_BACKOFF_MAX_MS = 3e5;
|
|
|
34907
35054
|
var DEFAULT_PROBE_CONCURRENCY = 4;
|
|
34908
35055
|
var RESUME_STAGGER_MS = 500;
|
|
34909
35056
|
var MAX_LAST_ERROR_CHARS = 300;
|
|
35057
|
+
var HOST_HANDSHAKE_FAILURE_HINT_THRESHOLD = 5;
|
|
35058
|
+
var HOST_HANDSHAKE_REMEDIATION_HINT = "host handshake failing \u2014 the controller should re-run `agentproto devices add` with a fresh `pair offer --host` (a reboot/reinstall on the host invalidates the old registration)";
|
|
35059
|
+
var handshakeFailures = /* @__PURE__ */ new Map();
|
|
34910
35060
|
var DEFAULT_LAST_SEEN_PERSIST_MS = 3e4;
|
|
34911
35061
|
var DEFAULT_SNAPSHOT_INTERVAL_MS = 15e3;
|
|
34912
35062
|
var DEFAULT_SNAPSHOT_ACTIVE_INTERVAL_MS = 5e3;
|
|
@@ -35186,6 +35336,7 @@ function createHostRegistry(deps2) {
|
|
|
35186
35336
|
async function markReached(record2) {
|
|
35187
35337
|
const t = now();
|
|
35188
35338
|
lastReachedAt.set(record2.fingerprint, t);
|
|
35339
|
+
handshakeFailures.delete(record2.fingerprint);
|
|
35189
35340
|
record2.lastSeen = new Date(t).toISOString();
|
|
35190
35341
|
record2.lastProbeAt = record2.lastSeen;
|
|
35191
35342
|
const hadError = record2.lastError !== void 0;
|
|
@@ -35198,6 +35349,13 @@ function createHostRegistry(deps2) {
|
|
|
35198
35349
|
async function markUnreachable(record2, message) {
|
|
35199
35350
|
record2.lastProbeAt = new Date(now()).toISOString();
|
|
35200
35351
|
const error = message.slice(0, MAX_LAST_ERROR_CHARS);
|
|
35352
|
+
const failures = (handshakeFailures.get(record2.fingerprint) ?? 0) + 1;
|
|
35353
|
+
handshakeFailures.set(record2.fingerprint, failures);
|
|
35354
|
+
if (failures === HOST_HANDSHAKE_FAILURE_HINT_THRESHOLD) {
|
|
35355
|
+
log(
|
|
35356
|
+
`[hosts] ${record2.fingerprint} (${record2.name}): failed to dial/handshake ${failures} times in a row \u2014 ` + HOST_HANDSHAKE_REMEDIATION_HINT
|
|
35357
|
+
);
|
|
35358
|
+
}
|
|
35201
35359
|
if (record2.lastError === error) return;
|
|
35202
35360
|
record2.lastError = error;
|
|
35203
35361
|
await persist().catch((err) => log(`[hosts] lastError persist failed: ${errMsg3(err)}`));
|
|
@@ -35207,7 +35365,8 @@ function createHostRegistry(deps2) {
|
|
|
35207
35365
|
const attempts = [epoch, epoch - 1];
|
|
35208
35366
|
let lastErr;
|
|
35209
35367
|
let hungUpOnHello = 0;
|
|
35210
|
-
for (
|
|
35368
|
+
for (let attempt = 1; attempt <= attempts.length; attempt++) {
|
|
35369
|
+
const e = attempts[attempt - 1];
|
|
35211
35370
|
const { route, auth } = await deriveEpochTokens(record2.pairRoot, e);
|
|
35212
35371
|
let raw;
|
|
35213
35372
|
try {
|
|
@@ -35217,32 +35376,45 @@ function createHostRegistry(deps2) {
|
|
|
35217
35376
|
continue;
|
|
35218
35377
|
}
|
|
35219
35378
|
try {
|
|
35220
|
-
|
|
35221
|
-
|
|
35222
|
-
|
|
35223
|
-
authToken: auth,
|
|
35224
|
-
clientName: record2.name
|
|
35379
|
+
let rawCloseReason;
|
|
35380
|
+
const offRawClose = raw.onClose((reason) => {
|
|
35381
|
+
rawCloseReason = reason ?? "unknown";
|
|
35225
35382
|
});
|
|
35226
|
-
|
|
35227
|
-
|
|
35228
|
-
|
|
35229
|
-
|
|
35230
|
-
|
|
35231
|
-
|
|
35232
|
-
|
|
35233
|
-
|
|
35383
|
+
let client;
|
|
35384
|
+
try {
|
|
35385
|
+
const started = await startClientHandshake({
|
|
35386
|
+
daemonX25519Pub: record2.daemonX25519Pub,
|
|
35387
|
+
daemonEd25519Pub: record2.daemonEd25519Pub,
|
|
35388
|
+
authToken: auth,
|
|
35389
|
+
clientName: record2.name
|
|
35390
|
+
});
|
|
35391
|
+
const wrapped = await clientHandshakeOverSink(
|
|
35392
|
+
raw,
|
|
35393
|
+
encodePairingMessage(started.hello),
|
|
35394
|
+
(replyBytes) => started.complete(decodePairingReply(replyBytes)),
|
|
35395
|
+
{ timeoutMs: handshakeTimeoutMs }
|
|
35396
|
+
);
|
|
35397
|
+
client = createTunnelClient({ sink: wrapped });
|
|
35398
|
+
await awaitTunnelHello(client, wrapped, record2, () => rawCloseReason);
|
|
35399
|
+
} finally {
|
|
35400
|
+
offRawClose();
|
|
35401
|
+
}
|
|
35234
35402
|
await markReached(record2);
|
|
35235
35403
|
return client;
|
|
35236
35404
|
} catch (err) {
|
|
35237
35405
|
lastErr = err;
|
|
35238
35406
|
if (err instanceof Error && /transport closed during handshake/.test(err.message)) hungUpOnHello++;
|
|
35407
|
+
log(
|
|
35408
|
+
`[hosts] ${record2.name} (${record2.fingerprint}) attempt ${attempt}/${attempts.length} failed: ${errMsg3(err)}`
|
|
35409
|
+
);
|
|
35239
35410
|
try {
|
|
35240
35411
|
raw.close("handshake failed");
|
|
35241
35412
|
} catch {
|
|
35242
35413
|
}
|
|
35243
35414
|
}
|
|
35244
35415
|
}
|
|
35245
|
-
const
|
|
35416
|
+
const helloTimedOut = lastErr instanceof Error && /did not send hello/.test(lastErr.message);
|
|
35417
|
+
const hint = hungUpOnHello === attempts.length || helloTimedOut ? ` \u2014 the host hung up on (or never sent) the post-handshake tunnel 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})` : "";
|
|
35246
35418
|
const failure = `could not reach host ${record2.fingerprint} via ${record2.rendezvousUrl}: ${lastErr instanceof Error ? lastErr.message : String(lastErr)}${hint}`;
|
|
35247
35419
|
await markUnreachable(record2, errMsg3(lastErr));
|
|
35248
35420
|
throw new Error(failure);
|
|
@@ -35479,6 +35651,35 @@ function createHostRegistry(deps2) {
|
|
|
35479
35651
|
start: sweep
|
|
35480
35652
|
};
|
|
35481
35653
|
}
|
|
35654
|
+
async function awaitTunnelHello(client, sink, record2, closeReason) {
|
|
35655
|
+
return new Promise((resolve41, reject) => {
|
|
35656
|
+
if (!sink.isOpen) {
|
|
35657
|
+
reject(
|
|
35658
|
+
new Error(
|
|
35659
|
+
`host ${record2.fingerprint} (${record2.name}) closed the channel after handshake: ${closeReason() ?? "unknown"}`
|
|
35660
|
+
)
|
|
35661
|
+
);
|
|
35662
|
+
return;
|
|
35663
|
+
}
|
|
35664
|
+
const offClose = sink.onClose((reason) => {
|
|
35665
|
+
reject(
|
|
35666
|
+
new Error(
|
|
35667
|
+
`host ${record2.fingerprint} (${record2.name}) closed the channel after handshake: ${reason ?? "unknown"}`
|
|
35668
|
+
)
|
|
35669
|
+
);
|
|
35670
|
+
});
|
|
35671
|
+
client.ready().then(
|
|
35672
|
+
() => {
|
|
35673
|
+
offClose();
|
|
35674
|
+
resolve41();
|
|
35675
|
+
},
|
|
35676
|
+
(err) => {
|
|
35677
|
+
offClose();
|
|
35678
|
+
reject(err);
|
|
35679
|
+
}
|
|
35680
|
+
);
|
|
35681
|
+
});
|
|
35682
|
+
}
|
|
35482
35683
|
function wrapStreamWithCleanup(source, cleanup) {
|
|
35483
35684
|
const reader = source.getReader();
|
|
35484
35685
|
let cleanedUp = false;
|
|
@@ -35627,6 +35828,16 @@ function isHostsFile(v) {
|
|
|
35627
35828
|
if (!Array.isArray(rec["hosts"])) return false;
|
|
35628
35829
|
return rec["hosts"].every(isHostRecord);
|
|
35629
35830
|
}
|
|
35831
|
+
async function readHostsSnapshot(path) {
|
|
35832
|
+
try {
|
|
35833
|
+
const raw = await readFile(path ?? defaultHostsPath(), "utf8");
|
|
35834
|
+
const parsed = JSON.parse(raw);
|
|
35835
|
+
if (!isHostsFile(parsed)) return [];
|
|
35836
|
+
return parsed.hosts.map((rec) => ({ ...rec }));
|
|
35837
|
+
} catch {
|
|
35838
|
+
return [];
|
|
35839
|
+
}
|
|
35840
|
+
}
|
|
35630
35841
|
function isHostRecord(v) {
|
|
35631
35842
|
if (typeof v !== "object" || v === null) return false;
|
|
35632
35843
|
const r = v;
|
|
@@ -35731,6 +35942,114 @@ function createDeviceRegistry(pairing, hosts) {
|
|
|
35731
35942
|
}
|
|
35732
35943
|
};
|
|
35733
35944
|
}
|
|
35945
|
+
var DEVICE_PROMPT_PATH_PREFIX = "/device-prompt";
|
|
35946
|
+
function isValidPrompt(prompt) {
|
|
35947
|
+
if (typeof prompt === "string") return prompt.length > 0;
|
|
35948
|
+
if (Array.isArray(prompt)) {
|
|
35949
|
+
return prompt.length > 0 && prompt.every((b) => b !== null && typeof b === "object");
|
|
35950
|
+
}
|
|
35951
|
+
return prompt !== null && typeof prompt === "object";
|
|
35952
|
+
}
|
|
35953
|
+
function sleep(ms) {
|
|
35954
|
+
return new Promise((resolve41) => setTimeout(resolve41, ms));
|
|
35955
|
+
}
|
|
35956
|
+
function httpErrorMessage(body) {
|
|
35957
|
+
const raw = Buffer.from(body).toString("utf8");
|
|
35958
|
+
try {
|
|
35959
|
+
const parsed = JSON.parse(raw);
|
|
35960
|
+
if (parsed !== null && typeof parsed === "object" && typeof parsed["message"] === "string") {
|
|
35961
|
+
return parsed["message"];
|
|
35962
|
+
}
|
|
35963
|
+
} catch {
|
|
35964
|
+
}
|
|
35965
|
+
return raw;
|
|
35966
|
+
}
|
|
35967
|
+
async function promptHostSession(hosts, target, sessionId, input) {
|
|
35968
|
+
if (!hosts) {
|
|
35969
|
+
throw new Error(
|
|
35970
|
+
"this daemon has no host registry wired \u2014 device prompt is unavailable (internal configuration issue, not a user error)"
|
|
35971
|
+
);
|
|
35972
|
+
}
|
|
35973
|
+
if (!isValidPrompt(input.prompt)) {
|
|
35974
|
+
return {
|
|
35975
|
+
ok: false,
|
|
35976
|
+
message: "`prompt` must be a non-empty string, a content block object, or an array of content blocks."
|
|
35977
|
+
};
|
|
35978
|
+
}
|
|
35979
|
+
const pollMs = Math.min(Math.max(input.pollMs ?? 1e3, 10), 1e4);
|
|
35980
|
+
const delay2 = input.sleep ?? sleep;
|
|
35981
|
+
const enqueueBody = { prompt: input.prompt, queue: true };
|
|
35982
|
+
if (input.interrupt) enqueueBody.interrupt = true;
|
|
35983
|
+
if (input.force) enqueueBody.force = true;
|
|
35984
|
+
const startedAt = Date.now();
|
|
35985
|
+
let enqueueRes;
|
|
35986
|
+
try {
|
|
35987
|
+
enqueueRes = await hosts.forwardHttp(target, {
|
|
35988
|
+
method: "POST",
|
|
35989
|
+
path: `${DEVICE_PROMPT_PATH_PREFIX}/${encodeURIComponent(sessionId)}?wait=false`,
|
|
35990
|
+
headers: { "content-type": "application/json" },
|
|
35991
|
+
body: new Uint8Array(Buffer.from(JSON.stringify(enqueueBody), "utf8"))
|
|
35992
|
+
});
|
|
35993
|
+
} catch (err) {
|
|
35994
|
+
return { ok: false, message: err instanceof Error ? err.message : String(err) };
|
|
35995
|
+
}
|
|
35996
|
+
if (enqueueRes.status < 200 || enqueueRes.status >= 300) {
|
|
35997
|
+
return { ok: false, status: enqueueRes.status, message: httpErrorMessage(enqueueRes.body) };
|
|
35998
|
+
}
|
|
35999
|
+
let enqueueParsed = {};
|
|
36000
|
+
try {
|
|
36001
|
+
const parsed = JSON.parse(Buffer.from(enqueueRes.body).toString("utf8"));
|
|
36002
|
+
if (parsed !== null && typeof parsed === "object") enqueueParsed = parsed;
|
|
36003
|
+
} catch {
|
|
36004
|
+
}
|
|
36005
|
+
if (!input.wait) {
|
|
36006
|
+
const pending = enqueueParsed["pending"] === true;
|
|
36007
|
+
return {
|
|
36008
|
+
ok: true,
|
|
36009
|
+
...pending ? {
|
|
36010
|
+
pending: true,
|
|
36011
|
+
...typeof enqueueParsed["queueId"] === "string" ? { queueId: enqueueParsed["queueId"] } : {},
|
|
36012
|
+
...typeof enqueueParsed["queuePosition"] === "number" ? { queuePosition: enqueueParsed["queuePosition"] } : {}
|
|
36013
|
+
} : {}
|
|
36014
|
+
};
|
|
36015
|
+
}
|
|
36016
|
+
for (; ; ) {
|
|
36017
|
+
await delay2(pollMs);
|
|
36018
|
+
const waitedMs = Date.now() - startedAt;
|
|
36019
|
+
let descRes;
|
|
36020
|
+
try {
|
|
36021
|
+
descRes = await hosts.forwardHttp(target, {
|
|
36022
|
+
method: "GET",
|
|
36023
|
+
path: `/sessions/${encodeURIComponent(sessionId)}`
|
|
36024
|
+
});
|
|
36025
|
+
} catch (err) {
|
|
36026
|
+
return { ok: false, waitedMs, message: err instanceof Error ? err.message : String(err) };
|
|
36027
|
+
}
|
|
36028
|
+
if (descRes.status === 404) {
|
|
36029
|
+
return { ok: false, waitedMs, status: 404, message: `session ${sessionId} not found on host ${target}` };
|
|
36030
|
+
}
|
|
36031
|
+
if (descRes.status < 200 || descRes.status >= 300) {
|
|
36032
|
+
return { ok: false, waitedMs, status: descRes.status, message: httpErrorMessage(descRes.body) };
|
|
36033
|
+
}
|
|
36034
|
+
let descriptor = {};
|
|
36035
|
+
try {
|
|
36036
|
+
const parsed = JSON.parse(Buffer.from(descRes.body).toString("utf8"));
|
|
36037
|
+
if (parsed !== null && typeof parsed === "object") descriptor = parsed;
|
|
36038
|
+
} catch {
|
|
36039
|
+
return { ok: false, waitedMs, status: descRes.status, message: "host returned a non-JSON session descriptor" };
|
|
36040
|
+
}
|
|
36041
|
+
if (descriptor["alive"] === false) {
|
|
36042
|
+
return { ok: false, waitedMs, message: `session ${sessionId} ended while waiting for its turn to drain` };
|
|
36043
|
+
}
|
|
36044
|
+
const queue = Array.isArray(descriptor["promptQueue"]) ? descriptor["promptQueue"] : [];
|
|
36045
|
+
if (descriptor["busy"] !== true && queue.length === 0) {
|
|
36046
|
+
return { ok: true, waitedMs };
|
|
36047
|
+
}
|
|
36048
|
+
if (input.maxWaitMs !== void 0 && input.maxWaitMs > 0 && waitedMs >= input.maxWaitMs) {
|
|
36049
|
+
return { ok: false, waitedMs, message: `timed out after ${waitedMs}ms waiting for the turn to drain` };
|
|
36050
|
+
}
|
|
36051
|
+
}
|
|
36052
|
+
}
|
|
35734
36053
|
|
|
35735
36054
|
// src/app-tools.ts
|
|
35736
36055
|
init_app_ui_build();
|
|
@@ -37318,7 +37637,7 @@ function createLedgerAppender(app, appRunId, runId, item) {
|
|
|
37318
37637
|
};
|
|
37319
37638
|
return { append, flush: () => chain };
|
|
37320
37639
|
}
|
|
37321
|
-
async function executeRunWorkflow(state, runtimeWf, agents, signal, sessionEvents,
|
|
37640
|
+
async function executeRunWorkflow(state, runtimeWf, agents, signal, sessionEvents, cache3, cacheKey, input, persist, appRegistry, eventLog, lease, runsRoot) {
|
|
37322
37641
|
if (lease) {
|
|
37323
37642
|
const renew = () => {
|
|
37324
37643
|
state.run.lease = { ownerId: lease.ownerId, heartbeatAt: lease.now().toISOString() };
|
|
@@ -37350,7 +37669,7 @@ async function executeRunWorkflow(state, runtimeWf, agents, signal, sessionEvent
|
|
|
37350
37669
|
workspaceSlug: state.workspaceSlug,
|
|
37351
37670
|
...workspacePaths ? { workspace: workspacePaths.scratch, artifactsDir: workspacePaths.artifactsDir, runId: state.run.runId, onArtifact } : {},
|
|
37352
37671
|
input,
|
|
37353
|
-
...
|
|
37672
|
+
...cache3 ? { cache: cache3, cacheKey } : {},
|
|
37354
37673
|
// WP-S — human-resolved approval steps: a `kind: "approval"` step parks
|
|
37355
37674
|
// the run as "awaiting-approval" with a pending inbox entry, instead of
|
|
37356
37675
|
// the engine's silent auto-approve. A human answers through
|
|
@@ -38036,9 +38355,9 @@ function createWorkflowRunner(opts) {
|
|
|
38036
38355
|
}
|
|
38037
38356
|
);
|
|
38038
38357
|
state.agents = agents;
|
|
38039
|
-
const
|
|
38358
|
+
const cache3 = input.cacheKey ? journal ? teeIntoInternalJournal(createFileStepCache(input.cacheKey), journal) : createFileStepCache(input.cacheKey) : journal?.cache;
|
|
38040
38359
|
const effectiveCacheKey = input.cacheKey ?? journal?.cacheKey;
|
|
38041
|
-
void executeRunWorkflow(state, workflow, agents, abort.signal, sessionEvents,
|
|
38360
|
+
void executeRunWorkflow(state, workflow, agents, abort.signal, sessionEvents, cache3, effectiveCacheKey, void 0, persist, opts.appRegistry, eventLog, leaseOpts, workspace).then(() => {
|
|
38042
38361
|
for (const [sid, binding] of sessionToRun) {
|
|
38043
38362
|
if (binding.runId === runId) sessionToRun.delete(sid);
|
|
38044
38363
|
}
|
|
@@ -38169,7 +38488,7 @@ function createWorkflowRunner(opts) {
|
|
|
38169
38488
|
);
|
|
38170
38489
|
state.agents = agents;
|
|
38171
38490
|
const journal = internalJournal(workspaceRoot, runId);
|
|
38172
|
-
const
|
|
38491
|
+
const cache3 = args.cacheKey ? journal ? teeIntoInternalJournal(createFileStepCache(args.cacheKey), journal) : createFileStepCache(args.cacheKey) : journal?.cache;
|
|
38173
38492
|
const effectiveCacheKey = args.cacheKey ?? journal?.cacheKey;
|
|
38174
38493
|
const execWorkflow = journal && args.cacheKey === void 0 ? forceCacheableWorkflow(workflow) : workflow;
|
|
38175
38494
|
void executeRunWorkflow(
|
|
@@ -38178,7 +38497,7 @@ function createWorkflowRunner(opts) {
|
|
|
38178
38497
|
agents,
|
|
38179
38498
|
abort.signal,
|
|
38180
38499
|
sessionEvents,
|
|
38181
|
-
|
|
38500
|
+
cache3,
|
|
38182
38501
|
effectiveCacheKey,
|
|
38183
38502
|
args.input,
|
|
38184
38503
|
persist,
|
|
@@ -38965,6 +39284,36 @@ async function assertCronAdapterResolvable(action, deps2) {
|
|
|
38965
39284
|
}
|
|
38966
39285
|
var DEFAULT_PERSIST_PATH3 = () => join(homedir(), ".agentproto", "cron-jobs.json");
|
|
38967
39286
|
var TICK_INTERVAL_MS = 2e4;
|
|
39287
|
+
var MAX_RUNS_PER_JOB = 50;
|
|
39288
|
+
function ledgerPath(persistPath) {
|
|
39289
|
+
return `${persistPath}.runs.json`;
|
|
39290
|
+
}
|
|
39291
|
+
function loadRuns2(path) {
|
|
39292
|
+
try {
|
|
39293
|
+
const parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
39294
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return /* @__PURE__ */ new Map();
|
|
39295
|
+
const runs = /* @__PURE__ */ new Map();
|
|
39296
|
+
for (const [jobId, value] of Object.entries(parsed)) {
|
|
39297
|
+
if (Array.isArray(value)) {
|
|
39298
|
+
runs.set(jobId, value.filter(
|
|
39299
|
+
(run) => run && typeof run.runId === "string" && run.jobId === jobId && typeof run.startedAt === "string" && typeof run.endedAt === "string" && typeof run.ok === "boolean" && typeof run.result === "string"
|
|
39300
|
+
).slice(-MAX_RUNS_PER_JOB));
|
|
39301
|
+
}
|
|
39302
|
+
}
|
|
39303
|
+
return runs;
|
|
39304
|
+
} catch {
|
|
39305
|
+
return /* @__PURE__ */ new Map();
|
|
39306
|
+
}
|
|
39307
|
+
}
|
|
39308
|
+
function saveRuns2(runs, path) {
|
|
39309
|
+
try {
|
|
39310
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
39311
|
+
const tmp = `${path}.tmp.${process.pid}`;
|
|
39312
|
+
writeFileSync(tmp, JSON.stringify(Object.fromEntries(runs), null, 2) + "\n", "utf8");
|
|
39313
|
+
renameSync(tmp, path);
|
|
39314
|
+
} catch {
|
|
39315
|
+
}
|
|
39316
|
+
}
|
|
38968
39317
|
function loadJobs(persistPath) {
|
|
38969
39318
|
const result = /* @__PURE__ */ new Map();
|
|
38970
39319
|
if (!existsSync(persistPath)) return result;
|
|
@@ -38987,6 +39336,9 @@ function loadJobs(persistPath) {
|
|
|
38987
39336
|
const job = item;
|
|
38988
39337
|
job.recurring = job.recurring ?? true;
|
|
38989
39338
|
job.active = job.active ?? true;
|
|
39339
|
+
if (job.finished === void 0 && !job.recurring && !job.active && !!job.lastRunAt) {
|
|
39340
|
+
job.finished = true;
|
|
39341
|
+
}
|
|
38990
39342
|
result.set(job.id, { job });
|
|
38991
39343
|
}
|
|
38992
39344
|
return result;
|
|
@@ -39024,6 +39376,14 @@ function createCronScheduler(opts) {
|
|
|
39024
39376
|
const persistPath = opts.persistPath ?? DEFAULT_PERSIST_PATH3();
|
|
39025
39377
|
const shouldPersist = opts.persist ?? opts.persistPath !== void 0;
|
|
39026
39378
|
const jobs = shouldPersist ? loadJobs(persistPath) : /* @__PURE__ */ new Map();
|
|
39379
|
+
const runHistory = shouldPersist ? loadRuns2(ledgerPath(persistPath)) : /* @__PURE__ */ new Map();
|
|
39380
|
+
const appendRun = (run) => {
|
|
39381
|
+
const history = runHistory.get(run.jobId) ?? [];
|
|
39382
|
+
history.push(run);
|
|
39383
|
+
if (history.length > MAX_RUNS_PER_JOB) history.splice(0, history.length - MAX_RUNS_PER_JOB);
|
|
39384
|
+
runHistory.set(run.jobId, history);
|
|
39385
|
+
if (shouldPersist) saveRuns2(runHistory, ledgerPath(persistPath));
|
|
39386
|
+
};
|
|
39027
39387
|
for (const state of jobs.values()) {
|
|
39028
39388
|
if (!state.job.active) continue;
|
|
39029
39389
|
try {
|
|
@@ -39146,7 +39506,15 @@ function createCronScheduler(opts) {
|
|
|
39146
39506
|
if (!ok4) {
|
|
39147
39507
|
throw new Error(`cron job '${job.id}': tool '${action.tool}' failed: ${summary2}`);
|
|
39148
39508
|
}
|
|
39149
|
-
|
|
39509
|
+
let sessionId2;
|
|
39510
|
+
if (action.tool === "agent_start") {
|
|
39511
|
+
try {
|
|
39512
|
+
const body = JSON.parse(summary2);
|
|
39513
|
+
if (typeof body.id === "string") sessionId2 = body.id;
|
|
39514
|
+
} catch {
|
|
39515
|
+
}
|
|
39516
|
+
}
|
|
39517
|
+
return { ok: true, summary: `tool '${action.tool}': ${summary2}`, sessionId: sessionId2 };
|
|
39150
39518
|
}
|
|
39151
39519
|
if (!dispatchTool) {
|
|
39152
39520
|
throw new Error(
|
|
@@ -39169,6 +39537,7 @@ function createCronScheduler(opts) {
|
|
|
39169
39537
|
}
|
|
39170
39538
|
return {
|
|
39171
39539
|
ok: true,
|
|
39540
|
+
sessionId,
|
|
39172
39541
|
summary: sessionId ? `spawned session ${sessionId} (adapter=${action.adapter ?? action.harness ?? "preset"})` : `agent_start: ${summary}`
|
|
39173
39542
|
};
|
|
39174
39543
|
};
|
|
@@ -39177,45 +39546,37 @@ function createCronScheduler(opts) {
|
|
|
39177
39546
|
state.running = true;
|
|
39178
39547
|
try {
|
|
39179
39548
|
const { job } = state;
|
|
39180
|
-
const
|
|
39181
|
-
job.lastRunAt =
|
|
39182
|
-
sessionEvents.emit({ type: "cron:fired", jobId: job.id, label: job.label, ts:
|
|
39549
|
+
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
39550
|
+
job.lastRunAt = startedAt;
|
|
39551
|
+
sessionEvents.emit({ type: "cron:fired", jobId: job.id, label: job.label, ts: startedAt });
|
|
39183
39552
|
let result;
|
|
39553
|
+
let error;
|
|
39184
39554
|
try {
|
|
39185
39555
|
result = await executeAction(job);
|
|
39186
39556
|
} catch (err) {
|
|
39187
|
-
|
|
39557
|
+
error = err instanceof Error ? err.message : String(err);
|
|
39188
39558
|
result = { ok: false, summary: error };
|
|
39189
|
-
sessionEvents.emit({
|
|
39190
|
-
type: "cron:failed",
|
|
39191
|
-
jobId: job.id,
|
|
39192
|
-
label: job.label,
|
|
39193
|
-
error,
|
|
39194
|
-
ts: (/* @__PURE__ */ new Date()).toISOString()
|
|
39195
|
-
});
|
|
39196
|
-
job.lastResult = result;
|
|
39197
|
-
if (!job.recurring) {
|
|
39198
|
-
job.active = false;
|
|
39199
|
-
job.nextRunAt = void 0;
|
|
39200
|
-
state.cronInstance?.stop();
|
|
39201
|
-
state.cronInstance = void 0;
|
|
39202
|
-
} else if (state.cronInstance) {
|
|
39203
|
-
const next = nextFireDate(state.cronInstance);
|
|
39204
|
-
job.nextRunAt = next?.toISOString();
|
|
39205
|
-
}
|
|
39206
|
-
persistNow();
|
|
39207
|
-
return;
|
|
39208
39559
|
}
|
|
39209
|
-
|
|
39210
|
-
|
|
39560
|
+
const endedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
39561
|
+
appendRun({
|
|
39562
|
+
runId: `run_${randomUUID()}`,
|
|
39211
39563
|
jobId: job.id,
|
|
39212
|
-
|
|
39213
|
-
|
|
39214
|
-
|
|
39564
|
+
startedAt,
|
|
39565
|
+
endedAt,
|
|
39566
|
+
ok: result.ok,
|
|
39567
|
+
...result.sessionId ? { sessionId: result.sessionId } : {},
|
|
39568
|
+
result: result.summary,
|
|
39569
|
+
...error ? { error } : {}
|
|
39215
39570
|
});
|
|
39216
|
-
|
|
39571
|
+
if (error) {
|
|
39572
|
+
sessionEvents.emit({ type: "cron:failed", jobId: job.id, label: job.label, error, ts: endedAt });
|
|
39573
|
+
} else {
|
|
39574
|
+
sessionEvents.emit({ type: "cron:succeeded", jobId: job.id, label: job.label, summary: result.summary, ts: endedAt });
|
|
39575
|
+
}
|
|
39576
|
+
job.lastResult = { ok: result.ok, summary: result.summary };
|
|
39217
39577
|
if (!job.recurring) {
|
|
39218
39578
|
job.active = false;
|
|
39579
|
+
job.finished = true;
|
|
39219
39580
|
job.nextRunAt = void 0;
|
|
39220
39581
|
state.cronInstance?.stop();
|
|
39221
39582
|
state.cronInstance = void 0;
|
|
@@ -39262,6 +39623,7 @@ function createCronScheduler(opts) {
|
|
|
39262
39623
|
action,
|
|
39263
39624
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
39264
39625
|
active: true,
|
|
39626
|
+
finished: false,
|
|
39265
39627
|
nextRunAt: next?.toISOString()
|
|
39266
39628
|
};
|
|
39267
39629
|
jobs.set(id, { job, cronInstance });
|
|
@@ -39274,11 +39636,55 @@ function createCronScheduler(opts) {
|
|
|
39274
39636
|
get(id) {
|
|
39275
39637
|
return jobs.get(id)?.job;
|
|
39276
39638
|
},
|
|
39639
|
+
async update(id, patch) {
|
|
39640
|
+
const state = jobs.get(id);
|
|
39641
|
+
if (!state) throw new Error(`cron job not found: ${id}`);
|
|
39642
|
+
if (state.running) throw new Error(`cron job is running: ${id}`);
|
|
39643
|
+
const job = state.job;
|
|
39644
|
+
const schedule = patch.schedule ?? job.schedule;
|
|
39645
|
+
const timezone = patch.timezone === null ? void 0 : patch.timezone ?? job.timezone;
|
|
39646
|
+
const action = patch.action ?? job.action;
|
|
39647
|
+
if (!schedule) throw new Error("cron schedule must not be empty");
|
|
39648
|
+
assertCronActionAllowed(action);
|
|
39649
|
+
const cronInstance = parseCron(schedule, timezone);
|
|
39650
|
+
await assertCronAdapterResolvable(action, {
|
|
39651
|
+
resolveAgentAdapter,
|
|
39652
|
+
getAuthProfile: opts.getAuthProfile,
|
|
39653
|
+
listAgentAdapters: opts.listAgentAdapters
|
|
39654
|
+
});
|
|
39655
|
+
const active = patch.active ?? job.active;
|
|
39656
|
+
const next = active ? nextFireDate(cronInstance) : null;
|
|
39657
|
+
state.cronInstance?.stop();
|
|
39658
|
+
state.cronInstance = active ? cronInstance : void 0;
|
|
39659
|
+
job.schedule = schedule;
|
|
39660
|
+
job.timezone = timezone;
|
|
39661
|
+
job.action = action;
|
|
39662
|
+
job.recurring = patch.recurring ?? job.recurring;
|
|
39663
|
+
if (patch.label !== void 0) job.label = patch.label === null ? void 0 : patch.label;
|
|
39664
|
+
job.active = active;
|
|
39665
|
+
if (active) job.finished = false;
|
|
39666
|
+
job.nextRunAt = next?.toISOString();
|
|
39667
|
+
persistNow();
|
|
39668
|
+
return job;
|
|
39669
|
+
},
|
|
39670
|
+
runs({ jobId, limit = 20, cursor } = {}) {
|
|
39671
|
+
if (jobId && !jobs.has(jobId)) throw new Error(`cron job not found: ${jobId}`);
|
|
39672
|
+
if (!Number.isInteger(limit) || limit < 1 || limit > 200) {
|
|
39673
|
+
throw new Error("limit must be an integer from 1 to 200");
|
|
39674
|
+
}
|
|
39675
|
+
const all = (jobId ? runHistory.get(jobId) ?? [] : Array.from(runHistory.values()).flat()).slice().sort((a, b) => b.startedAt.localeCompare(a.startedAt) || b.runId.localeCompare(a.runId));
|
|
39676
|
+
const start = cursor ? all.findIndex((run) => run.runId === cursor) + 1 : 0;
|
|
39677
|
+
if (cursor && start === 0) throw new Error("invalid cron runs cursor");
|
|
39678
|
+
const runs = all.slice(start, start + limit);
|
|
39679
|
+
return { runs, ...start + limit < all.length ? { nextCursor: runs[runs.length - 1]?.runId } : {} };
|
|
39680
|
+
},
|
|
39277
39681
|
delete(id) {
|
|
39278
39682
|
const state = jobs.get(id);
|
|
39279
39683
|
if (!state) throw new Error(`cron job not found: ${id}`);
|
|
39280
39684
|
state.cronInstance?.stop();
|
|
39281
39685
|
jobs.delete(id);
|
|
39686
|
+
runHistory.delete(id);
|
|
39687
|
+
if (shouldPersist) saveRuns2(runHistory, ledgerPath(persistPath));
|
|
39282
39688
|
persistNow();
|
|
39283
39689
|
},
|
|
39284
39690
|
async run(id) {
|
|
@@ -39555,7 +39961,7 @@ function createTaskLedger(opts) {
|
|
|
39555
39961
|
conflict: false,
|
|
39556
39962
|
error: `task "${taskId}" not found`
|
|
39557
39963
|
});
|
|
39558
|
-
const
|
|
39964
|
+
const lookup2 = (taskId, caller) => {
|
|
39559
39965
|
const task = tasks.get(taskId);
|
|
39560
39966
|
if (!task) return void 0;
|
|
39561
39967
|
if (!canAccessBoard(caller, task.boardId)) return void 0;
|
|
@@ -39700,7 +40106,7 @@ function createTaskLedger(opts) {
|
|
|
39700
40106
|
return Array.from(tasks.values()).filter((t) => t.boardId === boardId).filter((t) => filter?.status !== void 0 ? t.status === filter.status : true).filter((t) => includeClosed || !isClosedTaskStatus(t.status)).sort((a, b) => a.createdAt.localeCompare(b.createdAt));
|
|
39701
40107
|
};
|
|
39702
40108
|
const claim = (input, caller) => {
|
|
39703
|
-
const task =
|
|
40109
|
+
const task = lookup2(input.taskId, caller);
|
|
39704
40110
|
if (!task) return notFound(input.taskId);
|
|
39705
40111
|
if (input.rev !== task.rev || task.owner !== void 0) {
|
|
39706
40112
|
return { ok: false, conflict: true, current: task };
|
|
@@ -39723,7 +40129,7 @@ function createTaskLedger(opts) {
|
|
|
39723
40129
|
return { ok: true, task };
|
|
39724
40130
|
};
|
|
39725
40131
|
const update = (input, caller) => {
|
|
39726
|
-
const task =
|
|
40132
|
+
const task = lookup2(input.taskId, caller);
|
|
39727
40133
|
if (!task) return notFound(input.taskId);
|
|
39728
40134
|
if (input.rev !== task.rev) {
|
|
39729
40135
|
return { ok: false, conflict: true, current: task };
|
|
@@ -39906,7 +40312,7 @@ function createTaskLedger(opts) {
|
|
|
39906
40312
|
return { ok: true, task };
|
|
39907
40313
|
};
|
|
39908
40314
|
const linkApproval = (taskId, approvalId, caller) => {
|
|
39909
|
-
const task =
|
|
40315
|
+
const task = lookup2(taskId, caller);
|
|
39910
40316
|
if (!task) return notFound(taskId);
|
|
39911
40317
|
const manager = isManager(caller, task);
|
|
39912
40318
|
const owner = isOwner(caller, task);
|
|
@@ -39951,7 +40357,7 @@ function createTaskLedger(opts) {
|
|
|
39951
40357
|
return {
|
|
39952
40358
|
create,
|
|
39953
40359
|
list,
|
|
39954
|
-
get: (taskId, caller) =>
|
|
40360
|
+
get: (taskId, caller) => lookup2(taskId, caller),
|
|
39955
40361
|
claim,
|
|
39956
40362
|
update,
|
|
39957
40363
|
linkApproval,
|
|
@@ -42093,6 +42499,7 @@ function registerOrchestrationTools(rawServer, opts) {
|
|
|
42093
42499
|
"When false, fires once then deactivates. Default true."
|
|
42094
42500
|
),
|
|
42095
42501
|
label: z.string().optional().describe("Human-readable label for the job."),
|
|
42502
|
+
timezone: z.string().optional().describe("IANA timezone for interpreting the schedule. Defaults to host local time."),
|
|
42096
42503
|
action: cronActionSchema.describe(
|
|
42097
42504
|
"What to do when the job fires. 'command' runs an allowlisted shell command; 'agent' spawns a brand-new agent session and takes every agent_start field (access.profileRef, role, worktree, \u2026; not `wait`); 'prompt-session' re-prompts an existing, already-running session in place (for durable check-in jobs); 'tool' calls a registered daemon MCP tool in-process (cron_* refused)."
|
|
42098
42505
|
)
|
|
@@ -42107,6 +42514,7 @@ function registerOrchestrationTools(rawServer, opts) {
|
|
|
42107
42514
|
const job = await cronScheduler.create({
|
|
42108
42515
|
label: input.label,
|
|
42109
42516
|
schedule: input.schedule,
|
|
42517
|
+
timezone: input.timezone,
|
|
42110
42518
|
recurring: input.recurring ?? true,
|
|
42111
42519
|
action: input.action
|
|
42112
42520
|
});
|
|
@@ -42144,6 +42552,7 @@ function registerOrchestrationTools(rawServer, opts) {
|
|
|
42144
42552
|
schedule: j.schedule,
|
|
42145
42553
|
recurring: j.recurring,
|
|
42146
42554
|
active: j.active,
|
|
42555
|
+
finished: j.finished ?? false,
|
|
42147
42556
|
nextRunAt: j.nextRunAt,
|
|
42148
42557
|
lastRunAt: j.lastRunAt,
|
|
42149
42558
|
// A failed last run must be visible without `full: true`.
|
|
@@ -42151,7 +42560,7 @@ function registerOrchestrationTools(rawServer, opts) {
|
|
|
42151
42560
|
});
|
|
42152
42561
|
registerBuiltinTool(server, {
|
|
42153
42562
|
id: "cron_list",
|
|
42154
|
-
description: "List all cron jobs (active and inactive) with their schedule, last result, and next fire time. COMPACT BY DEFAULT: each entry is a slim projection (id/label/schedule/recurring/active/nextRunAt/lastRunAt/lastOk); pass `full: true` (or `compact: false`) for the complete job record including action/createdAt/lastResult.",
|
|
42563
|
+
description: "List all cron jobs (active and inactive) with their schedule, last result, and next fire time. COMPACT BY DEFAULT: each entry is a slim projection (id/label/schedule/recurring/active/finished/nextRunAt/lastRunAt/lastOk); pass `full: true` (or `compact: false`) for the complete job record including action/createdAt/lastResult.",
|
|
42155
42564
|
inputSchema: cronListSchema,
|
|
42156
42565
|
handler: async () => cronScheduler.list(),
|
|
42157
42566
|
transformers: [
|
|
@@ -42164,6 +42573,47 @@ function registerOrchestrationTools(rawServer, opts) {
|
|
|
42164
42573
|
})
|
|
42165
42574
|
]
|
|
42166
42575
|
});
|
|
42576
|
+
server.tool(
|
|
42577
|
+
"cron_update",
|
|
42578
|
+
"Edit a cron job's schedule, label, timezone, recurrence, action, or active state. Set active false to pause and true to resume.",
|
|
42579
|
+
{
|
|
42580
|
+
jobId: z.string().min(1),
|
|
42581
|
+
schedule: z.string().min(1).optional(),
|
|
42582
|
+
label: z.string().nullable().optional(),
|
|
42583
|
+
timezone: z.string().nullable().optional(),
|
|
42584
|
+
recurring: z.boolean().optional(),
|
|
42585
|
+
active: z.boolean().optional(),
|
|
42586
|
+
action: cronActionSchema.optional()
|
|
42587
|
+
},
|
|
42588
|
+
async (input) => {
|
|
42589
|
+
try {
|
|
42590
|
+
if (input.action?.kind === "tool" && opts.toolSubset && !opts.toolSubset.has(input.action.tool)) {
|
|
42591
|
+
throw new Error(`cron action kind "tool" cannot dispatch '${input.action.tool}': not in this server's tool subset`);
|
|
42592
|
+
}
|
|
42593
|
+
const { jobId, ...patch } = input;
|
|
42594
|
+
const job = await cronScheduler.update(jobId, patch);
|
|
42595
|
+
return { content: [{ type: "text", text: JSON.stringify(job) }] };
|
|
42596
|
+
} catch (err) {
|
|
42597
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: err instanceof Error ? err.message : String(err) }) }], isError: true };
|
|
42598
|
+
}
|
|
42599
|
+
}
|
|
42600
|
+
);
|
|
42601
|
+
server.tool(
|
|
42602
|
+
"cron_runs",
|
|
42603
|
+
"Read cron fire history, newest first. Use nextCursor for the next page.",
|
|
42604
|
+
{
|
|
42605
|
+
jobId: z.string().min(1).optional(),
|
|
42606
|
+
limit: z.number().int().min(1).max(200).optional(),
|
|
42607
|
+
cursor: z.string().min(1).optional()
|
|
42608
|
+
},
|
|
42609
|
+
async (input) => {
|
|
42610
|
+
try {
|
|
42611
|
+
return { content: [{ type: "text", text: JSON.stringify(cronScheduler.runs(input)) }] };
|
|
42612
|
+
} catch (err) {
|
|
42613
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: err instanceof Error ? err.message : String(err) }) }], isError: true };
|
|
42614
|
+
}
|
|
42615
|
+
}
|
|
42616
|
+
);
|
|
42167
42617
|
server.tool(
|
|
42168
42618
|
"cron_delete",
|
|
42169
42619
|
"Permanently remove a cron job. Throws if the job is not found.",
|
|
@@ -42354,7 +42804,7 @@ function createRemoteCatalogClient(options = {}) {
|
|
|
42354
42804
|
const timeoutMs = options.timeoutMs ?? DEFAULT_REMOTE_CATALOG_TIMEOUT_MS;
|
|
42355
42805
|
const fetchImpl = options.fetchImpl ?? fetch;
|
|
42356
42806
|
const now = options.now ?? Date.now;
|
|
42357
|
-
const
|
|
42807
|
+
const cache3 = /* @__PURE__ */ new Map();
|
|
42358
42808
|
async function fetchOne(url) {
|
|
42359
42809
|
let body;
|
|
42360
42810
|
try {
|
|
@@ -42386,13 +42836,13 @@ function createRemoteCatalogClient(options = {}) {
|
|
|
42386
42836
|
async fetchSources(sources, opts = {}) {
|
|
42387
42837
|
const results = await Promise.all(
|
|
42388
42838
|
sources.map(async ({ url }) => {
|
|
42389
|
-
const hit =
|
|
42839
|
+
const hit = cache3.get(url);
|
|
42390
42840
|
if (!opts.refresh && hit && now() - hit.at < ttlMs) return hit.result;
|
|
42391
42841
|
const result = await fetchOne(url);
|
|
42392
42842
|
if (result.entries.length > 0 || result.warnings.length === 0) {
|
|
42393
|
-
|
|
42843
|
+
cache3.set(url, { at: now(), result });
|
|
42394
42844
|
} else {
|
|
42395
|
-
|
|
42845
|
+
cache3.delete(url);
|
|
42396
42846
|
}
|
|
42397
42847
|
return result;
|
|
42398
42848
|
})
|
|
@@ -43728,7 +44178,7 @@ function send(res, status, body) {
|
|
|
43728
44178
|
res.writeHead(status, { "content-type": "application/json" });
|
|
43729
44179
|
res.end(JSON.stringify(body));
|
|
43730
44180
|
}
|
|
43731
|
-
async function
|
|
44181
|
+
async function readBody2(req) {
|
|
43732
44182
|
const chunks = [];
|
|
43733
44183
|
let size = 0;
|
|
43734
44184
|
for await (const chunk of req) {
|
|
@@ -43765,7 +44215,7 @@ async function handleA2aTaskRequest(req, res, path, deps2) {
|
|
|
43765
44215
|
return true;
|
|
43766
44216
|
}
|
|
43767
44217
|
if (appId.includes("/.well-known/")) return false;
|
|
43768
|
-
const body = await
|
|
44218
|
+
const body = await readBody2(req);
|
|
43769
44219
|
if ("tooLarge" in body) {
|
|
43770
44220
|
send(res, 413, { jsonrpc: "2.0", id: null, error: { code: JSON_RPC_ERROR.invalidRequest, message: "request body too large" } });
|
|
43771
44221
|
return true;
|
|
@@ -45151,8 +45601,8 @@ async function startHttpServer(opts) {
|
|
|
45151
45601
|
}))
|
|
45152
45602
|
};
|
|
45153
45603
|
});
|
|
45154
|
-
server2.server.setRequestHandler(CallToolRequestSchema, async (
|
|
45155
|
-
const out = await proxy.callTool(importId,
|
|
45604
|
+
server2.server.setRequestHandler(CallToolRequestSchema, async (request2) => {
|
|
45605
|
+
const out = await proxy.callTool(importId, request2.params.name, request2.params.arguments ?? {});
|
|
45156
45606
|
if (!out.ok) {
|
|
45157
45607
|
return { content: [{ type: "text", text: out.error }], isError: true };
|
|
45158
45608
|
}
|
|
@@ -46841,6 +47291,16 @@ async function startHttpServer(opts) {
|
|
|
46841
47291
|
);
|
|
46842
47292
|
if (handled) return;
|
|
46843
47293
|
}
|
|
47294
|
+
if (path.startsWith("/device-prompt/")) {
|
|
47295
|
+
const handled = await handleDevicePrompt(
|
|
47296
|
+
req,
|
|
47297
|
+
res,
|
|
47298
|
+
path,
|
|
47299
|
+
opts.port,
|
|
47300
|
+
opts.deviceSpawnAllow === true
|
|
47301
|
+
);
|
|
47302
|
+
if (handled) return;
|
|
47303
|
+
}
|
|
46844
47304
|
if (opts.remote && path.startsWith("/remote")) {
|
|
46845
47305
|
const handled = await handleRemoteControl(req, res, path, opts.remote);
|
|
46846
47306
|
if (handled) return;
|
|
@@ -49471,6 +49931,79 @@ async function handleDeviceSpawn(req, res, path, port, spawnAllowed) {
|
|
|
49471
49931
|
Readable.fromWeb(upstreamRes.body).pipe(res);
|
|
49472
49932
|
return true;
|
|
49473
49933
|
}
|
|
49934
|
+
async function handleDevicePrompt(req, res, path, port, spawnAllowed) {
|
|
49935
|
+
const json3 = (status, body2) => {
|
|
49936
|
+
res.writeHead(status, { "content-type": "application/json" });
|
|
49937
|
+
res.end(JSON.stringify(body2));
|
|
49938
|
+
};
|
|
49939
|
+
const promptMatch = path.match(/^\/device-prompt\/([^/?]+)(\?.*)?$/);
|
|
49940
|
+
if (!promptMatch) return false;
|
|
49941
|
+
const sessionId = decodeURIComponent(promptMatch[1] ?? "");
|
|
49942
|
+
if (!sessionId) return false;
|
|
49943
|
+
if (req.headers[HOST_SCOPE_HEADER] !== "1") {
|
|
49944
|
+
json3(403, {
|
|
49945
|
+
error: "host_scope_required",
|
|
49946
|
+
message: "device-prompt is reachable only over a host-scoped pairing (the far end must have registered this daemon via `agentproto pair offer --host` + `agentproto devices add`)"
|
|
49947
|
+
});
|
|
49948
|
+
return true;
|
|
49949
|
+
}
|
|
49950
|
+
if (!spawnAllowed) {
|
|
49951
|
+
json3(403, {
|
|
49952
|
+
error: "spawn_disabled",
|
|
49953
|
+
message: "this host has not opted in \u2014 run `agentproto devices allow-spawn on` on it"
|
|
49954
|
+
});
|
|
49955
|
+
return true;
|
|
49956
|
+
}
|
|
49957
|
+
if (req.method !== "POST") {
|
|
49958
|
+
json3(405, { error: "method_not_allowed", message: "POST /device-prompt/:sessionId" });
|
|
49959
|
+
return true;
|
|
49960
|
+
}
|
|
49961
|
+
const chunks = [];
|
|
49962
|
+
for await (const chunk of req) chunks.push(chunk);
|
|
49963
|
+
const body = Buffer.concat(chunks);
|
|
49964
|
+
const headers = {};
|
|
49965
|
+
for (const [k, v] of Object.entries(req.headers)) {
|
|
49966
|
+
if (v === void 0) continue;
|
|
49967
|
+
const lower = k.toLowerCase();
|
|
49968
|
+
if (lower === "connection" || lower === "keep-alive" || lower === "transfer-encoding") continue;
|
|
49969
|
+
if (lower === "upgrade" || lower === "host" || lower === "content-length") continue;
|
|
49970
|
+
if (lower === HOST_SCOPE_HEADER) continue;
|
|
49971
|
+
headers[k] = Array.isArray(v) ? v.join(", ") : v;
|
|
49972
|
+
}
|
|
49973
|
+
const rawUrl = req.url ?? "";
|
|
49974
|
+
const query = rawUrl.includes("?") ? rawUrl.slice(rawUrl.indexOf("?")) : "";
|
|
49975
|
+
let upstreamRes;
|
|
49976
|
+
try {
|
|
49977
|
+
upstreamRes = await fetch(
|
|
49978
|
+
`http://127.0.0.1:${port}/sessions/${encodeURIComponent(sessionId)}/prompt${query}`,
|
|
49979
|
+
{
|
|
49980
|
+
method: "POST",
|
|
49981
|
+
headers,
|
|
49982
|
+
...body.length > 0 ? { body } : {}
|
|
49983
|
+
// Cast rationale — see `handleDeviceSpawn`'s identical cast above.
|
|
49984
|
+
}
|
|
49985
|
+
);
|
|
49986
|
+
} catch (err) {
|
|
49987
|
+
json3(502, {
|
|
49988
|
+
error: "self_proxy_unreachable",
|
|
49989
|
+
message: err instanceof Error ? err.message : String(err)
|
|
49990
|
+
});
|
|
49991
|
+
return true;
|
|
49992
|
+
}
|
|
49993
|
+
const resHeaders = {};
|
|
49994
|
+
upstreamRes.headers.forEach((v, k) => {
|
|
49995
|
+
if (k === "content-length" || k === "connection") return;
|
|
49996
|
+
resHeaders[k] = v;
|
|
49997
|
+
});
|
|
49998
|
+
res.writeHead(upstreamRes.status, resHeaders);
|
|
49999
|
+
if (!upstreamRes.body) {
|
|
50000
|
+
res.end();
|
|
50001
|
+
return true;
|
|
50002
|
+
}
|
|
50003
|
+
const resBuf = Buffer.from(await upstreamRes.arrayBuffer());
|
|
50004
|
+
res.end(resBuf);
|
|
50005
|
+
return true;
|
|
50006
|
+
}
|
|
49474
50007
|
async function handleRemoteControl(req, res, path, controller) {
|
|
49475
50008
|
const json3 = (status, body) => {
|
|
49476
50009
|
res.writeHead(status, { "content-type": "application/json" });
|
|
@@ -50234,6 +50767,33 @@ async function handleDevices(req, res, path, registry, hostRegistry, joinTokens)
|
|
|
50234
50767
|
}
|
|
50235
50768
|
return true;
|
|
50236
50769
|
}
|
|
50770
|
+
const deviceSessionPromptMatch = path.match(/^\/devices\/([^/]+)\/sessions\/([^/]+)\/prompt$/);
|
|
50771
|
+
if (deviceSessionPromptMatch && req.method === "POST") {
|
|
50772
|
+
if (!hostRegistry) {
|
|
50773
|
+
json3(404, { error: "no_host_registry", message: "this daemon has no host registry wired" });
|
|
50774
|
+
return true;
|
|
50775
|
+
}
|
|
50776
|
+
const target = decodeURIComponent(deviceSessionPromptMatch[1] ?? "");
|
|
50777
|
+
const sessionId = decodeURIComponent(deviceSessionPromptMatch[2] ?? "");
|
|
50778
|
+
const body = await readJsonBody2(req);
|
|
50779
|
+
const b = body && typeof body === "object" ? body : {};
|
|
50780
|
+
const reqUrl = req.url ?? "";
|
|
50781
|
+
const qs = new URLSearchParams(reqUrl.includes("?") ? reqUrl.slice(reqUrl.indexOf("?") + 1) : "");
|
|
50782
|
+
try {
|
|
50783
|
+
const result = await promptHostSession(hostRegistry, target, sessionId, {
|
|
50784
|
+
prompt: b["prompt"],
|
|
50785
|
+
interrupt: b["interrupt"] === true,
|
|
50786
|
+
force: b["force"] === true,
|
|
50787
|
+
wait: ["1", "true"].includes(qs.get("wait") ?? ""),
|
|
50788
|
+
...qs.get("waitPollMs") ? { pollMs: Number(qs.get("waitPollMs")) } : {},
|
|
50789
|
+
...qs.get("maxWaitMs") ? { maxWaitMs: Number(qs.get("maxWaitMs")) } : {}
|
|
50790
|
+
});
|
|
50791
|
+
json3(result.ok ? 200 : result.status ?? 400, result);
|
|
50792
|
+
} catch (err) {
|
|
50793
|
+
json3(502, { error: "prompt_forward_failed", message: err instanceof Error ? err.message : String(err) });
|
|
50794
|
+
}
|
|
50795
|
+
return true;
|
|
50796
|
+
}
|
|
50237
50797
|
const idMatch = path.match(/^\/devices\/([^/]+)$/);
|
|
50238
50798
|
if (idMatch && req.method === "PATCH") {
|
|
50239
50799
|
const target = decodeURIComponent(idMatch[1] ?? "");
|
|
@@ -50280,6 +50840,22 @@ async function handleCron(req, res, path, scheduler) {
|
|
|
50280
50840
|
json3(200, { jobs: scheduler.list() });
|
|
50281
50841
|
return true;
|
|
50282
50842
|
}
|
|
50843
|
+
if (path === "/cron/runs" && req.method === "GET") {
|
|
50844
|
+
const params = new URL(req.url ?? "/", "http://localhost").searchParams;
|
|
50845
|
+
const rawLimit = params.get("limit");
|
|
50846
|
+
const limit = rawLimit === null ? void 0 : Number(rawLimit);
|
|
50847
|
+
try {
|
|
50848
|
+
json3(200, scheduler.runs({
|
|
50849
|
+
jobId: params.get("jobId") ?? void 0,
|
|
50850
|
+
limit,
|
|
50851
|
+
cursor: params.get("cursor") ?? void 0
|
|
50852
|
+
}));
|
|
50853
|
+
} catch (err) {
|
|
50854
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
50855
|
+
json3(message.includes("not found") ? 404 : 400, { error: "runs_failed", message });
|
|
50856
|
+
}
|
|
50857
|
+
return true;
|
|
50858
|
+
}
|
|
50283
50859
|
if (path === "/cron" && req.method === "POST") {
|
|
50284
50860
|
const body = await readJsonBody2(req);
|
|
50285
50861
|
if (!body || typeof body !== "object") {
|
|
@@ -50301,6 +50877,7 @@ async function handleCron(req, res, path, scheduler) {
|
|
|
50301
50877
|
const job = await scheduler.create({
|
|
50302
50878
|
label: typeof b.label === "string" ? b.label : void 0,
|
|
50303
50879
|
schedule,
|
|
50880
|
+
timezone: typeof b.timezone === "string" ? b.timezone : void 0,
|
|
50304
50881
|
recurring: typeof b.recurring === "boolean" ? b.recurring : true,
|
|
50305
50882
|
action
|
|
50306
50883
|
});
|
|
@@ -50348,6 +50925,27 @@ async function handleCron(req, res, path, scheduler) {
|
|
|
50348
50925
|
}
|
|
50349
50926
|
return true;
|
|
50350
50927
|
}
|
|
50928
|
+
if (req.method === "PATCH") {
|
|
50929
|
+
const body = await readJsonBody2(req);
|
|
50930
|
+
if (!body || typeof body !== "object" || Array.isArray(body)) {
|
|
50931
|
+
json3(400, { error: "invalid_body" });
|
|
50932
|
+
return true;
|
|
50933
|
+
}
|
|
50934
|
+
const b = body;
|
|
50935
|
+
const allowed = /* @__PURE__ */ new Set(["schedule", "label", "timezone", "recurring", "active", "action"]);
|
|
50936
|
+
if (Object.keys(b).some((key) => !allowed.has(key)) || b.schedule !== void 0 && (typeof b.schedule !== "string" || !b.schedule) || b.label !== void 0 && b.label !== null && typeof b.label !== "string" || b.timezone !== void 0 && b.timezone !== null && typeof b.timezone !== "string" || b.recurring !== void 0 && typeof b.recurring !== "boolean" || b.active !== void 0 && typeof b.active !== "boolean" || b.action !== void 0 && (!b.action || typeof b.action !== "object" || Array.isArray(b.action))) {
|
|
50937
|
+
json3(400, { error: "invalid_body" });
|
|
50938
|
+
return true;
|
|
50939
|
+
}
|
|
50940
|
+
try {
|
|
50941
|
+
const job = await scheduler.update(jobId, b);
|
|
50942
|
+
json3(200, job);
|
|
50943
|
+
} catch (err) {
|
|
50944
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
50945
|
+
json3(message.includes("not found") ? 404 : 400, { error: "update_failed", message });
|
|
50946
|
+
}
|
|
50947
|
+
return true;
|
|
50948
|
+
}
|
|
50351
50949
|
return false;
|
|
50352
50950
|
}
|
|
50353
50951
|
async function handleRoutineDefs(req, res, path, registrar) {
|
|
@@ -55603,7 +56201,7 @@ function waitingOnKey(w) {
|
|
|
55603
56201
|
var PR_SETTLE_SWEEP_INTERVAL_MS = 5 * 6e4;
|
|
55604
56202
|
var PR_SETTLE_SWEEP_MAX_URLS = 10;
|
|
55605
56203
|
function createActivityProjector(opts) {
|
|
55606
|
-
const
|
|
56204
|
+
const cache3 = /* @__PURE__ */ new Map();
|
|
55607
56205
|
const prStates = /* @__PURE__ */ new Map();
|
|
55608
56206
|
const projectOwnerRaw = (owner) => {
|
|
55609
56207
|
switch (owner) {
|
|
@@ -55664,9 +56262,9 @@ function createActivityProjector(opts) {
|
|
|
55664
56262
|
const seen = /* @__PURE__ */ new Set();
|
|
55665
56263
|
for (const rec of next) {
|
|
55666
56264
|
seen.add(rec.id);
|
|
55667
|
-
const prev =
|
|
56265
|
+
const prev = cache3.get(rec.id);
|
|
55668
56266
|
const changed = !prev || prev.state !== rec.state || prev.taskId !== rec.taskId || waitingOnKey(prev.waitingOn) !== waitingOnKey(rec.waitingOn);
|
|
55669
|
-
|
|
56267
|
+
cache3.set(rec.id, rec);
|
|
55670
56268
|
if (changed && announce) {
|
|
55671
56269
|
opts.sessionEvents.emit({
|
|
55672
56270
|
type: "activity:changed",
|
|
@@ -55675,8 +56273,8 @@ function createActivityProjector(opts) {
|
|
|
55675
56273
|
});
|
|
55676
56274
|
}
|
|
55677
56275
|
}
|
|
55678
|
-
for (const [id, rec] of
|
|
55679
|
-
if (OWNER_OF_SOURCE[rec.source] === owner && !seen.has(id))
|
|
56276
|
+
for (const [id, rec] of cache3) {
|
|
56277
|
+
if (OWNER_OF_SOURCE[rec.source] === owner && !seen.has(id)) cache3.delete(id);
|
|
55680
56278
|
}
|
|
55681
56279
|
};
|
|
55682
56280
|
const ownersTouchedBy = (type) => {
|
|
@@ -55788,7 +56386,7 @@ function createActivityProjector(opts) {
|
|
|
55788
56386
|
dispose() {
|
|
55789
56387
|
unsubscribe();
|
|
55790
56388
|
if (sweepTimer) clearInterval(sweepTimer);
|
|
55791
|
-
|
|
56389
|
+
cache3.clear();
|
|
55792
56390
|
prStates.clear();
|
|
55793
56391
|
}
|
|
55794
56392
|
};
|
|
@@ -55973,7 +56571,7 @@ function createApprovalsEngine(opts) {
|
|
|
55973
56571
|
const record2 = approvals.get(id);
|
|
55974
56572
|
return record2 ? checkExpiry(record2) : void 0;
|
|
55975
56573
|
};
|
|
55976
|
-
const
|
|
56574
|
+
const request2 = (input, requester) => {
|
|
55977
56575
|
const id = `apr_${randomUUID().replace(/-/g, "").slice(0, 20)}`;
|
|
55978
56576
|
const requestedChannels = input.channels && input.channels.length > 0 ? input.channels.filter((c) => enabledChannels.includes(c)) : enabledChannels;
|
|
55979
56577
|
const canonicalJson2 = canonicalPayloadFor(input.payload);
|
|
@@ -56139,7 +56737,7 @@ function createApprovalsEngine(opts) {
|
|
|
56139
56737
|
};
|
|
56140
56738
|
return {
|
|
56141
56739
|
enabledChannels,
|
|
56142
|
-
request,
|
|
56740
|
+
request: request2,
|
|
56143
56741
|
get,
|
|
56144
56742
|
list,
|
|
56145
56743
|
wait,
|
|
@@ -58045,127 +58643,9 @@ function registerPairingTools(server, opts) {
|
|
|
58045
58643
|
}
|
|
58046
58644
|
);
|
|
58047
58645
|
}
|
|
58048
|
-
var HOP_BY_HOP = /* @__PURE__ */ new Set(["connection", "keep-alive", "transfer-encoding", "upgrade", "host"]);
|
|
58049
|
-
function collectHeaders(raw) {
|
|
58050
|
-
const headers = {};
|
|
58051
|
-
for (const [k, v] of Object.entries(raw)) {
|
|
58052
|
-
if (v === void 0 || HOP_BY_HOP.has(k.toLowerCase())) continue;
|
|
58053
|
-
headers[k] = Array.isArray(v) ? v.join(", ") : v;
|
|
58054
|
-
}
|
|
58055
|
-
return headers;
|
|
58056
|
-
}
|
|
58057
|
-
function isPrematureCloseError(err) {
|
|
58058
|
-
return typeof err === "object" && err !== null && "code" in err && err.code === "ERR_STREAM_PREMATURE_CLOSE";
|
|
58059
|
-
}
|
|
58060
|
-
async function readBody2(req) {
|
|
58061
|
-
const chunks = [];
|
|
58062
|
-
for await (const chunk of req) chunks.push(chunk);
|
|
58063
|
-
if (chunks.length === 0) return void 0;
|
|
58064
|
-
return new Uint8Array(Buffer.concat(chunks));
|
|
58065
|
-
}
|
|
58066
|
-
async function startDeviceSandboxBridge(opts) {
|
|
58067
|
-
const { hostRegistry, target } = opts;
|
|
58068
|
-
const server = createServer((req, res) => {
|
|
58069
|
-
void handleRequest(req, res).catch((err) => {
|
|
58070
|
-
if (res.writableEnded) return;
|
|
58071
|
-
if (!res.headersSent) {
|
|
58072
|
-
res.writeHead(502, { "content-type": "application/json" });
|
|
58073
|
-
res.end(
|
|
58074
|
-
JSON.stringify({
|
|
58075
|
-
error: "device_sandbox_bridge_failed",
|
|
58076
|
-
message: err instanceof Error ? err.message : String(err)
|
|
58077
|
-
})
|
|
58078
|
-
);
|
|
58079
|
-
return;
|
|
58080
|
-
}
|
|
58081
|
-
res.end();
|
|
58082
|
-
});
|
|
58083
|
-
});
|
|
58084
|
-
async function handleRequest(req, res) {
|
|
58085
|
-
const body = await readBody2(req);
|
|
58086
|
-
let upstream;
|
|
58087
|
-
try {
|
|
58088
|
-
upstream = await hostRegistry.forwardHttpStream(target, {
|
|
58089
|
-
method: req.method ?? "GET",
|
|
58090
|
-
path: `/device-spawn${req.url ?? "/"}`,
|
|
58091
|
-
headers: collectHeaders(req.headers),
|
|
58092
|
-
...body ? { body } : {}
|
|
58093
|
-
});
|
|
58094
|
-
} catch (err) {
|
|
58095
|
-
res.writeHead(502, { "content-type": "application/json" });
|
|
58096
|
-
res.end(
|
|
58097
|
-
JSON.stringify({
|
|
58098
|
-
error: "device_unreachable",
|
|
58099
|
-
message: err instanceof Error ? err.message : String(err)
|
|
58100
|
-
})
|
|
58101
|
-
);
|
|
58102
|
-
return;
|
|
58103
|
-
}
|
|
58104
|
-
const resHeaders = {};
|
|
58105
|
-
for (const [k, v] of Object.entries(upstream.headers)) {
|
|
58106
|
-
if (HOP_BY_HOP.has(k.toLowerCase()) || k.toLowerCase() === "content-length") continue;
|
|
58107
|
-
resHeaders[k] = v;
|
|
58108
|
-
}
|
|
58109
|
-
res.writeHead(upstream.status, resHeaders);
|
|
58110
|
-
try {
|
|
58111
|
-
await pipeline(
|
|
58112
|
-
Readable.fromWeb(upstream.body),
|
|
58113
|
-
res
|
|
58114
|
-
);
|
|
58115
|
-
} catch (err) {
|
|
58116
|
-
if (!isPrematureCloseError(err)) throw err;
|
|
58117
|
-
}
|
|
58118
|
-
}
|
|
58119
|
-
const port = await getFreePort2();
|
|
58120
|
-
await new Promise((resolve41, reject) => {
|
|
58121
|
-
server.once("error", reject);
|
|
58122
|
-
server.listen(port, "127.0.0.1", () => resolve41());
|
|
58123
|
-
});
|
|
58124
|
-
return {
|
|
58125
|
-
mcpUrl: `http://127.0.0.1:${port}/mcp`,
|
|
58126
|
-
async close() {
|
|
58127
|
-
await new Promise((resolve41) => server.close(() => resolve41()));
|
|
58128
|
-
}
|
|
58129
|
-
};
|
|
58130
|
-
}
|
|
58131
|
-
async function getFreePort2() {
|
|
58132
|
-
return new Promise((resolve41, reject) => {
|
|
58133
|
-
const srv = createServer$1();
|
|
58134
|
-
srv.once("error", reject);
|
|
58135
|
-
srv.listen(0, "127.0.0.1", () => {
|
|
58136
|
-
const address = srv.address();
|
|
58137
|
-
if (address === null || typeof address === "string") {
|
|
58138
|
-
srv.close(() => reject(new Error("failed to allocate a free port")));
|
|
58139
|
-
return;
|
|
58140
|
-
}
|
|
58141
|
-
const { port } = address;
|
|
58142
|
-
srv.close(() => resolve41(port));
|
|
58143
|
-
});
|
|
58144
|
-
});
|
|
58145
|
-
}
|
|
58146
58646
|
|
|
58147
|
-
// src/
|
|
58148
|
-
|
|
58149
|
-
return {
|
|
58150
|
-
async boot(_spec, _opts) {
|
|
58151
|
-
const hosts = await hostRegistry.list();
|
|
58152
|
-
const known = hosts.some((h) => h.fingerprint === target || h.name === target);
|
|
58153
|
-
if (!known) {
|
|
58154
|
-
throw new Error(
|
|
58155
|
-
`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.`
|
|
58156
|
-
);
|
|
58157
|
-
}
|
|
58158
|
-
const bridge = await startDeviceSandboxBridge({ hostRegistry, target });
|
|
58159
|
-
return {
|
|
58160
|
-
mcpUrl: bridge.mcpUrl,
|
|
58161
|
-
sandboxId: `device-${target}-${randomUUID()}`,
|
|
58162
|
-
async stop() {
|
|
58163
|
-
await bridge.close();
|
|
58164
|
-
}
|
|
58165
|
-
};
|
|
58166
|
-
}
|
|
58167
|
-
};
|
|
58168
|
-
}
|
|
58647
|
+
// src/index.ts
|
|
58648
|
+
init_device();
|
|
58169
58649
|
function text9(value) {
|
|
58170
58650
|
return {
|
|
58171
58651
|
content: [
|
|
@@ -58177,7 +58657,7 @@ function text9(value) {
|
|
|
58177
58657
|
};
|
|
58178
58658
|
}
|
|
58179
58659
|
function registerDeviceTools(server, opts) {
|
|
58180
|
-
const { registry, joinTokens } = opts;
|
|
58660
|
+
const { registry, hosts: registryHosts, joinTokens } = opts;
|
|
58181
58661
|
server.tool(
|
|
58182
58662
|
"device_list",
|
|
58183
58663
|
"List devices known to this daemon: fingerprint, name, role (client|host), kind (browser|cli|daemon, best-effort from the client's self-reported name; always daemon for a host), rendezvous, createdAt, lastSeen, scope (host, when granted via `pair offer --host`), online (a channel/forward is active right now or a recent contact succeeded), and for hosts lastProbeAt/lastError (last contact attempt and why it failed). Joined CI hosts that are gone (said goodbye, or unreachable past the TTL) are hidden unless includeEnded is set; a manually added host unreachable past the TTL shows stale: true. Read-only.",
|
|
@@ -58259,6 +58739,37 @@ function registerDeviceTools(server, opts) {
|
|
|
58259
58739
|
}
|
|
58260
58740
|
}
|
|
58261
58741
|
);
|
|
58742
|
+
if (registryHosts) {
|
|
58743
|
+
server.tool(
|
|
58744
|
+
"device_prompt",
|
|
58745
|
+
"Send a prompt (a follow-up turn) to a session on a registered HOST device \u2014 the write counterpart of device_sessions, forwarded live over the host's E2E channel. Queueing rules are identical to a local agent_prompt: fire-and-forget by default, queued behind the session's in-flight turn, `interrupt`/`force` to redirect or jump the queue. wait: true blocks until the prompted turn drains. Requires the HOST daemon to have opted in (agentproto devices allow-spawn on) over a host-scoped pairing \u2014 the same gate as device spawn; refused for a plain remote-control pairing or an unknown target.",
|
|
58746
|
+
{
|
|
58747
|
+
target: z.string().describe("The host's fingerprint or name (see device_list)."),
|
|
58748
|
+
sessionId: z.string().min(1).describe("The session id ON the host to prompt."),
|
|
58749
|
+
prompt: z.union([z.string(), z.record(z.string(), z.unknown()), z.array(z.record(z.string(), z.unknown()))]).describe("Non-empty string, a content block, or an array of content blocks."),
|
|
58750
|
+
wait: z.boolean().optional().describe("Block until the prompted turn drains. Default false."),
|
|
58751
|
+
interrupt: z.boolean().optional().describe("Mid-turn: redirect instead of queueing (agent_prompt's interrupt). Default false."),
|
|
58752
|
+
force: z.boolean().optional().describe("Mid-turn: jump the FRONT of the FIFO (agent_prompt's force). Default false."),
|
|
58753
|
+
pollMs: z.number().int().min(10).max(1e4).optional().describe("Poll cadence while waiting, ms. Default 1000. Each poll is a fresh E2E dial."),
|
|
58754
|
+
maxWaitMs: z.number().int().positive().optional().describe("Give up waiting after this long (ms). Default: wait forever.")
|
|
58755
|
+
},
|
|
58756
|
+
async ({ target, sessionId, prompt, wait, interrupt, force, pollMs, maxWaitMs }) => {
|
|
58757
|
+
try {
|
|
58758
|
+
const result = await promptHostSession(registryHosts, target, sessionId, {
|
|
58759
|
+
prompt,
|
|
58760
|
+
...wait ? { wait: true } : {},
|
|
58761
|
+
...interrupt ? { interrupt: true } : {},
|
|
58762
|
+
...force ? { force: true } : {},
|
|
58763
|
+
...pollMs !== void 0 ? { pollMs } : {},
|
|
58764
|
+
...maxWaitMs !== void 0 ? { maxWaitMs } : {}
|
|
58765
|
+
});
|
|
58766
|
+
return text9(result);
|
|
58767
|
+
} catch (err) {
|
|
58768
|
+
return text9({ ok: false, message: err instanceof Error ? err.message : String(err) });
|
|
58769
|
+
}
|
|
58770
|
+
}
|
|
58771
|
+
);
|
|
58772
|
+
}
|
|
58262
58773
|
if (joinTokens) {
|
|
58263
58774
|
server.tool(
|
|
58264
58775
|
"join_token_create",
|
|
@@ -59926,6 +60437,440 @@ function createWorkspaceFs(opts) {
|
|
|
59926
60437
|
|
|
59927
60438
|
// src/index.ts
|
|
59928
60439
|
init_role();
|
|
60440
|
+
var WHSEC_PREFIX = "whsec_";
|
|
60441
|
+
function encodeWhsecSecret(raw) {
|
|
60442
|
+
return WHSEC_PREFIX + Buffer.from(raw).toString("base64");
|
|
60443
|
+
}
|
|
60444
|
+
function decodeWhsecSecret(secret) {
|
|
60445
|
+
if (typeof secret !== "string" || !secret.startsWith(WHSEC_PREFIX)) return null;
|
|
60446
|
+
const encoded = secret.slice(WHSEC_PREFIX.length);
|
|
60447
|
+
if (encoded.length === 0 || !/^[A-Za-z0-9+/]*={0,2}$/.test(encoded) || encoded.length % 4 !== 0) {
|
|
60448
|
+
return null;
|
|
60449
|
+
}
|
|
60450
|
+
let key;
|
|
60451
|
+
try {
|
|
60452
|
+
key = Buffer.from(encoded, "base64");
|
|
60453
|
+
} catch {
|
|
60454
|
+
return null;
|
|
60455
|
+
}
|
|
60456
|
+
if (key.length < 24 || key.length > 64) return null;
|
|
60457
|
+
return key;
|
|
60458
|
+
}
|
|
60459
|
+
function signWebhook(input) {
|
|
60460
|
+
if (input.secrets.length === 0) throw new Error("signWebhook: at least one secret is required");
|
|
60461
|
+
const prefix = Buffer.from(`${input.msgId}.${input.timestamp}.`, "utf8");
|
|
60462
|
+
const payload = Buffer.from(input.payload.buffer, input.payload.byteOffset, input.payload.byteLength);
|
|
60463
|
+
const segments = input.secrets.map((secret) => {
|
|
60464
|
+
const key = decodeWhsecSecret(secret);
|
|
60465
|
+
if (!key) throw new Error(`signWebhook: invalid webhooks secret format: ${redactPrefix(secret)}`);
|
|
60466
|
+
const mac = createHmac("sha256", key).update(prefix).update(payload).digest();
|
|
60467
|
+
return `v1,${mac.toString("base64")}`;
|
|
60468
|
+
});
|
|
60469
|
+
return {
|
|
60470
|
+
"webhook-id": input.msgId,
|
|
60471
|
+
"webhook-timestamp": String(input.timestamp),
|
|
60472
|
+
"webhook-signature": segments.join(" ")
|
|
60473
|
+
};
|
|
60474
|
+
}
|
|
60475
|
+
function redactPrefix(secret) {
|
|
60476
|
+
return `${WHSEC_PREFIX}<${secret.length - WHSEC_PREFIX.length} b64 chars, invalid>`;
|
|
60477
|
+
}
|
|
60478
|
+
var SsrfFetchError = class extends Error {
|
|
60479
|
+
constructor(reason, message) {
|
|
60480
|
+
super(message);
|
|
60481
|
+
this.reason = reason;
|
|
60482
|
+
this.name = "SsrfFetchError";
|
|
60483
|
+
}
|
|
60484
|
+
reason;
|
|
60485
|
+
};
|
|
60486
|
+
var MAX_REQUEST_BODY_BYTES = 262144;
|
|
60487
|
+
var MAX_RESPONSE_DIAGNOSTIC_BYTES = 4096;
|
|
60488
|
+
function isPubliclyRoutable(ipRaw) {
|
|
60489
|
+
const ip = ipRaw.includes("%") ? ipRaw.slice(0, ipRaw.indexOf("%")) : ipRaw;
|
|
60490
|
+
const v4 = parseIpv4(ip);
|
|
60491
|
+
if (v4) return ipv4Public(v4[0], v4[1], v4[2], v4[3]);
|
|
60492
|
+
const v6 = parseIpv6Bytes(ip);
|
|
60493
|
+
if (v6) return ipv6Public(v6);
|
|
60494
|
+
return false;
|
|
60495
|
+
}
|
|
60496
|
+
function ipv4Public(a, b, c, d) {
|
|
60497
|
+
if (a === 0) return false;
|
|
60498
|
+
if (a === 10) return false;
|
|
60499
|
+
if (a === 127) return false;
|
|
60500
|
+
if (a === 100 && b >= 64 && b <= 127) return false;
|
|
60501
|
+
if (a === 169 && b === 254) return false;
|
|
60502
|
+
if (a === 172 && b >= 16 && b <= 31) return false;
|
|
60503
|
+
if (a === 192 && b === 168) return false;
|
|
60504
|
+
if (a === 192 && b === 0 && c === 0) return false;
|
|
60505
|
+
if (a === 192 && b === 0 && c === 2) return false;
|
|
60506
|
+
if (a === 198 && b === 51 && c === 100) return false;
|
|
60507
|
+
if (a === 198 && (b === 18 || b === 19)) return false;
|
|
60508
|
+
if (a === 203 && b === 0 && c === 113) return false;
|
|
60509
|
+
if (a >= 224) return false;
|
|
60510
|
+
return true;
|
|
60511
|
+
}
|
|
60512
|
+
function ipv6Public(v6) {
|
|
60513
|
+
if (v6.mapped) return ipv4Public(v6.mapped[0], v6.mapped[1], v6.mapped[2], v6.mapped[3]);
|
|
60514
|
+
const b = v6.bytes;
|
|
60515
|
+
const v = new DataView(b.buffer, b.byteOffset, b.byteLength);
|
|
60516
|
+
const b0 = v.getUint8(0);
|
|
60517
|
+
const b1 = v.getUint8(1);
|
|
60518
|
+
const b2 = v.getUint8(2);
|
|
60519
|
+
const b3 = v.getUint8(3);
|
|
60520
|
+
const b15 = v.getUint8(15);
|
|
60521
|
+
const tailZero = b.slice(1, 15).every((x) => x === 0);
|
|
60522
|
+
if (b.every((x) => x === 0)) return false;
|
|
60523
|
+
if (tailZero && b0 === 0 && b15 === 1) return false;
|
|
60524
|
+
if ((b0 & 254) === 252) return false;
|
|
60525
|
+
if (b0 === 254 && (b1 & 192) === 128) return false;
|
|
60526
|
+
if (b0 === 255) return false;
|
|
60527
|
+
if (b0 === 32 && b1 === 1 && b2 === 13 && b3 === 184) return false;
|
|
60528
|
+
return true;
|
|
60529
|
+
}
|
|
60530
|
+
function parseIpv4(ip) {
|
|
60531
|
+
const parts = ip.split(".");
|
|
60532
|
+
if (parts.length !== 4) return null;
|
|
60533
|
+
const o = [];
|
|
60534
|
+
for (const part of parts) {
|
|
60535
|
+
if (!/^(?:0|[1-9]\d{0,2})$/.test(part)) return null;
|
|
60536
|
+
const n = Number(part);
|
|
60537
|
+
if (n > 255) return null;
|
|
60538
|
+
o.push(n);
|
|
60539
|
+
}
|
|
60540
|
+
const first = o[0];
|
|
60541
|
+
const second = o[1];
|
|
60542
|
+
const third = o[2];
|
|
60543
|
+
const fourth = o[3];
|
|
60544
|
+
if (first === void 0 || second === void 0 || third === void 0 || fourth === void 0) return null;
|
|
60545
|
+
return [first, second, third, fourth];
|
|
60546
|
+
}
|
|
60547
|
+
function parseIpv6Bytes(rawIp) {
|
|
60548
|
+
const ip = rawIp.replace(/^\[|\]$/g, "");
|
|
60549
|
+
if (!ip.includes(":")) return null;
|
|
60550
|
+
if (ip.lastIndexOf("::") !== ip.indexOf("::")) return null;
|
|
60551
|
+
const v4Match = ip.match(/^(?:.*:)(\d{1,3}(?:\.\d{1,3}){3})$/);
|
|
60552
|
+
const compressed = ip.includes("::");
|
|
60553
|
+
if (compressed) {
|
|
60554
|
+
const halves = ip.split("::");
|
|
60555
|
+
const before = halves[0] ?? "";
|
|
60556
|
+
const after = halves[1] ?? "";
|
|
60557
|
+
const head = before === "" ? [] : before.split(":");
|
|
60558
|
+
if (head.some((g) => !isHexGroup(g))) return null;
|
|
60559
|
+
const tail = after === "" ? [] : after.replace(/(\d{1,3}(?:\.\d{1,3}){3})$/, "").split(":").filter((g) => g !== "");
|
|
60560
|
+
if (tail.some((g) => !isHexGroup(g))) return null;
|
|
60561
|
+
return fromGroups(head, tail, v4Match?.[1]);
|
|
60562
|
+
}
|
|
60563
|
+
const all = ip.split(":");
|
|
60564
|
+
const v4 = v4Match ? [all[all.length - 1]] : [];
|
|
60565
|
+
const hexGroups = all.slice(0, all.length - (v4Match ? 1 : 0));
|
|
60566
|
+
if (all.length !== (v4Match ? 6 : 8) + (v4Match ? 1 : 0)) return null;
|
|
60567
|
+
if (hexGroups.some((g) => !isHexGroup(g))) return null;
|
|
60568
|
+
return fromGroups(hexGroups, [], v4 ? v4[0] : void 0);
|
|
60569
|
+
}
|
|
60570
|
+
function isHexGroup(g) {
|
|
60571
|
+
return /^[\da-fA-F]{1,4}$/.test(g);
|
|
60572
|
+
}
|
|
60573
|
+
function fromGroups(head, tail, v4) {
|
|
60574
|
+
if (head.length + tail.length > (v4 ? 6 : 8)) return null;
|
|
60575
|
+
const bytes = new Uint8Array(16);
|
|
60576
|
+
const writeGroup = (g, offset) => {
|
|
60577
|
+
if (g === void 0) return false;
|
|
60578
|
+
const v = parseInt(g, 16);
|
|
60579
|
+
bytes[offset] = v >> 8;
|
|
60580
|
+
bytes[offset + 1] = v & 255;
|
|
60581
|
+
return true;
|
|
60582
|
+
};
|
|
60583
|
+
head.forEach((g, i) => writeGroup(g, i * 2));
|
|
60584
|
+
const tailStart = 16 - tail.length * 2 - (v4 ? 4 : 0);
|
|
60585
|
+
tail.forEach((g, i) => writeGroup(g, tailStart + i * 2));
|
|
60586
|
+
if (v4) {
|
|
60587
|
+
const o = parseIpv4(v4);
|
|
60588
|
+
if (!o) return null;
|
|
60589
|
+
bytes[12] = o[0];
|
|
60590
|
+
bytes[13] = o[1];
|
|
60591
|
+
bytes[14] = o[2];
|
|
60592
|
+
bytes[15] = o[3];
|
|
60593
|
+
const mapped = bytes.slice(0, 10).every((x) => x === 0) && bytes[10] === 255 && bytes[11] === 255;
|
|
60594
|
+
return { bytes, mapped: mapped ? o : void 0 };
|
|
60595
|
+
}
|
|
60596
|
+
return { bytes };
|
|
60597
|
+
}
|
|
60598
|
+
async function resolveHost(hostname6, resolvers) {
|
|
60599
|
+
if (parseIpv4(hostname6)) return [hostname6];
|
|
60600
|
+
const bracketless = hostname6.replace(/^\[|\]$/g, "");
|
|
60601
|
+
if (bracketless.includes(":")) return [bracketless];
|
|
60602
|
+
if (resolvers.lookup) return (await resolvers.lookup(bracketless)).map((r) => r.address);
|
|
60603
|
+
const found2 = await lookup(bracketless, { all: true, verbatim: true });
|
|
60604
|
+
return found2.map((f) => f.address);
|
|
60605
|
+
}
|
|
60606
|
+
var testDispatcher = null;
|
|
60607
|
+
function setEgressDispatcherForTests(dispatcher) {
|
|
60608
|
+
testDispatcher = dispatcher;
|
|
60609
|
+
}
|
|
60610
|
+
async function ssrfFetch(url, init, io = {}) {
|
|
60611
|
+
let parsed;
|
|
60612
|
+
try {
|
|
60613
|
+
parsed = new URL(url);
|
|
60614
|
+
} catch {
|
|
60615
|
+
throw new SsrfFetchError("non_https", "unparseable callback URL");
|
|
60616
|
+
}
|
|
60617
|
+
if (parsed.protocol !== "https:") {
|
|
60618
|
+
throw new SsrfFetchError("non_https", `scheme ${parsed.protocol} \u2014 https only`);
|
|
60619
|
+
}
|
|
60620
|
+
if ((init.body?.byteLength ?? 0) > MAX_REQUEST_BODY_BYTES) {
|
|
60621
|
+
throw new SsrfFetchError("connect", `request body exceeds the ${MAX_REQUEST_BODY_BYTES}-byte clamp`);
|
|
60622
|
+
}
|
|
60623
|
+
if (testDispatcher) {
|
|
60624
|
+
const literal = parseIpv4(parsed.hostname);
|
|
60625
|
+
if (literal && !ipv4Public(literal[0], literal[1], literal[2], literal[3])) {
|
|
60626
|
+
throw new SsrfFetchError("private_target", `literal non-public address ${parsed.hostname}`);
|
|
60627
|
+
}
|
|
60628
|
+
return await testDispatcher({ url, headers: init.headers ?? {}, body: init.body, timeoutMs: init.timeoutMs });
|
|
60629
|
+
}
|
|
60630
|
+
const candidates = await resolveHost(parsed.hostname, io.resolvers ?? {});
|
|
60631
|
+
if (candidates.length === 0) {
|
|
60632
|
+
throw new SsrfFetchError("connect", `no addresses resolved for ${parsed.hostname}`);
|
|
60633
|
+
}
|
|
60634
|
+
const rejected = candidates.find((ip) => !isPubliclyRoutable(ip));
|
|
60635
|
+
if (rejected !== void 0) {
|
|
60636
|
+
throw new SsrfFetchError("private_target", `${parsed.hostname} resolves to non-public address ${rejected}`);
|
|
60637
|
+
}
|
|
60638
|
+
const connectIp = candidates[0] ?? candidates[candidates.length - 1];
|
|
60639
|
+
if (connectIp === void 0) throw new SsrfFetchError("connect", "no address available for connect");
|
|
60640
|
+
const port = parsed.port ? Number(parsed.port) : 443;
|
|
60641
|
+
return await rawPost({
|
|
60642
|
+
connectIp,
|
|
60643
|
+
servername: parsed.hostname,
|
|
60644
|
+
port,
|
|
60645
|
+
path: `${parsed.pathname}${parsed.search}`,
|
|
60646
|
+
headers: init.headers ?? {},
|
|
60647
|
+
body: init.body,
|
|
60648
|
+
timeoutMs: init.timeoutMs
|
|
60649
|
+
});
|
|
60650
|
+
}
|
|
60651
|
+
function rawPost(args) {
|
|
60652
|
+
return new Promise((resolve41, reject) => {
|
|
60653
|
+
const req = request({
|
|
60654
|
+
// Connect to the PRE-VALIDATED address; keep the original hostname in
|
|
60655
|
+
// `servername` so SNI and the certificate check never loosen.
|
|
60656
|
+
host: args.connectIp,
|
|
60657
|
+
port: args.port,
|
|
60658
|
+
path: args.path,
|
|
60659
|
+
method: "POST",
|
|
60660
|
+
servername: args.servername,
|
|
60661
|
+
headers: args.headers,
|
|
60662
|
+
rejectUnauthorized: true
|
|
60663
|
+
});
|
|
60664
|
+
let settled = false;
|
|
60665
|
+
const fail3 = (reason, message) => {
|
|
60666
|
+
if (settled) return;
|
|
60667
|
+
settled = true;
|
|
60668
|
+
req.destroy();
|
|
60669
|
+
reject(new SsrfFetchError(reason, message));
|
|
60670
|
+
};
|
|
60671
|
+
const ok3 = (status, body) => {
|
|
60672
|
+
if (settled) return;
|
|
60673
|
+
settled = true;
|
|
60674
|
+
resolve41({ status, body });
|
|
60675
|
+
};
|
|
60676
|
+
req.setTimeout(args.timeoutMs, () => fail3("timeout", `no response within ${args.timeoutMs}ms`));
|
|
60677
|
+
req.on("error", (err) => {
|
|
60678
|
+
fail3("connect", err.message);
|
|
60679
|
+
});
|
|
60680
|
+
req.on("response", (res) => {
|
|
60681
|
+
const status = res.statusCode ?? 0;
|
|
60682
|
+
let diags = Buffer.alloc(0);
|
|
60683
|
+
res.on("data", (chunk) => {
|
|
60684
|
+
if (diags.length < MAX_RESPONSE_DIAGNOSTIC_BYTES) {
|
|
60685
|
+
diags = Buffer.concat([diags, chunk.subarray(0, MAX_RESPONSE_DIAGNOSTIC_BYTES - diags.length)]);
|
|
60686
|
+
}
|
|
60687
|
+
if (diags.length >= MAX_RESPONSE_DIAGNOSTIC_BYTES) res.destroy();
|
|
60688
|
+
});
|
|
60689
|
+
res.on("end", () => {
|
|
60690
|
+
if (status >= 300 && status < 400) {
|
|
60691
|
+
const location = res.headers["location"];
|
|
60692
|
+
if (typeof location === "string" && !new URL(location, `https://${args.servername}`).protocol.startsWith("https")) {
|
|
60693
|
+
fail3("redirect", `redirect target ${location} is not https \u2014 never chased`);
|
|
60694
|
+
return;
|
|
60695
|
+
}
|
|
60696
|
+
}
|
|
60697
|
+
ok3(status, diags.toString("utf8"));
|
|
60698
|
+
});
|
|
60699
|
+
res.on("error", (err) => {
|
|
60700
|
+
fail3("connect", err.message);
|
|
60701
|
+
});
|
|
60702
|
+
});
|
|
60703
|
+
if (args.body) req.write(Buffer.from(args.body));
|
|
60704
|
+
req.end();
|
|
60705
|
+
});
|
|
60706
|
+
}
|
|
60707
|
+
function challengeReasonForSsrf(reason) {
|
|
60708
|
+
if (reason === "non_https") return "non_https";
|
|
60709
|
+
if (reason === "private_target" || reason === "redirect") return "ssrf_blocked";
|
|
60710
|
+
return "timeout";
|
|
60711
|
+
}
|
|
60712
|
+
var CHALLENGE_TTL_MS = 10 * 60 * 1e3;
|
|
60713
|
+
var encoder = new TextEncoder();
|
|
60714
|
+
var cache2 = /* @__PURE__ */ new Map();
|
|
60715
|
+
function challengeCacheSize() {
|
|
60716
|
+
return cache2.size;
|
|
60717
|
+
}
|
|
60718
|
+
function resetChallengeCacheForTests() {
|
|
60719
|
+
cache2.clear();
|
|
60720
|
+
}
|
|
60721
|
+
function normalizeCallbackUrl(url) {
|
|
60722
|
+
try {
|
|
60723
|
+
const u = new URL(url);
|
|
60724
|
+
const port = u.port === "443" ? "" : `:${u.port}`;
|
|
60725
|
+
return `${u.protocol.toLowerCase()}//${u.hostname.toLowerCase()}${port}${u.pathname}${u.search}`;
|
|
60726
|
+
} catch {
|
|
60727
|
+
return null;
|
|
60728
|
+
}
|
|
60729
|
+
}
|
|
60730
|
+
function hashSecret(secret) {
|
|
60731
|
+
return createHash("sha256").update(secret, "utf8").digest("hex");
|
|
60732
|
+
}
|
|
60733
|
+
function constantTimeEq(a, b) {
|
|
60734
|
+
const da = createHash("sha256").update(a, "utf8").digest();
|
|
60735
|
+
const db = createHash("sha256").update(b, "utf8").digest();
|
|
60736
|
+
return timingSafeEqual(da, db);
|
|
60737
|
+
}
|
|
60738
|
+
async function verifyCallback(input, deps2 = {}) {
|
|
60739
|
+
const now = deps2.now ?? (() => Date.now());
|
|
60740
|
+
const normalized = normalizeCallbackUrl(input.url);
|
|
60741
|
+
if (normalized === null || !normalized.startsWith("https://")) {
|
|
60742
|
+
return { ok: false, reason: "non_https", detail: "callback URL must be an https URL" };
|
|
60743
|
+
}
|
|
60744
|
+
if (decodeWhsecSecret(input.secret) === null) {
|
|
60745
|
+
return {
|
|
60746
|
+
ok: false,
|
|
60747
|
+
reason: "challenge_failed",
|
|
60748
|
+
detail: "invalid secret format \u2014 must be whsec_ + base64 decoding to 24..64 bytes (the caller validates before this; a bad secret is never cached)"
|
|
60749
|
+
};
|
|
60750
|
+
}
|
|
60751
|
+
const cacheKey = `${input.principal}|${normalized}|${hashSecret(input.secret)}`;
|
|
60752
|
+
const cached = cache2.get(cacheKey);
|
|
60753
|
+
if (cached && cached.expiresAt > now()) {
|
|
60754
|
+
return { ok: true, verificationBytes: cached.requestBytes };
|
|
60755
|
+
}
|
|
60756
|
+
if (cached) cache2.delete(cacheKey);
|
|
60757
|
+
const challenge = randomBytes(32).toString("hex");
|
|
60758
|
+
const requestBytes = encoder.encode(JSON.stringify({ type: "verification", challenge }));
|
|
60759
|
+
const msgId = `msg_verification_${randomBytes(12).toString("hex")}`;
|
|
60760
|
+
const timestamp = Math.floor(now() / 1e3);
|
|
60761
|
+
let headers;
|
|
60762
|
+
try {
|
|
60763
|
+
const signed = signWebhook({ msgId, timestamp, payload: requestBytes, secrets: [input.secret] });
|
|
60764
|
+
headers = {
|
|
60765
|
+
...signed,
|
|
60766
|
+
"content-type": "application/json",
|
|
60767
|
+
"X-MCP-Subscription-Id": input.subscriptionId
|
|
60768
|
+
};
|
|
60769
|
+
} catch (err) {
|
|
60770
|
+
return { ok: false, reason: "challenge_failed", detail: `signing failed: ${err.message}` };
|
|
60771
|
+
}
|
|
60772
|
+
let response;
|
|
60773
|
+
try {
|
|
60774
|
+
response = await (deps2.fetch ?? ssrfFetch)(input.url, {
|
|
60775
|
+
method: "POST",
|
|
60776
|
+
headers,
|
|
60777
|
+
body: requestBytes,
|
|
60778
|
+
timeoutMs: 1e4
|
|
60779
|
+
// default challenge timeout (delivery uses 15_000)
|
|
60780
|
+
});
|
|
60781
|
+
} catch (err) {
|
|
60782
|
+
if (err instanceof SsrfFetchError) {
|
|
60783
|
+
return { ok: false, reason: challengeReasonForSsrf(err.reason), detail: `${err.reason}: ${err.message}` };
|
|
60784
|
+
}
|
|
60785
|
+
return { ok: false, reason: "timeout", detail: err.message };
|
|
60786
|
+
}
|
|
60787
|
+
if (response.status < 200 || response.status > 299) {
|
|
60788
|
+
return { ok: false, reason: "non_2xx", detail: `callback responded with status ${response.status}` };
|
|
60789
|
+
}
|
|
60790
|
+
let parsedBody;
|
|
60791
|
+
try {
|
|
60792
|
+
parsedBody = JSON.parse(response.body);
|
|
60793
|
+
} catch {
|
|
60794
|
+
return { ok: false, reason: "challenge_failed", detail: "response body is not JSON" };
|
|
60795
|
+
}
|
|
60796
|
+
const echoed = parsedBody?.challenge;
|
|
60797
|
+
if (typeof echoed !== "string" || !constantTimeEq(echoed, challenge)) {
|
|
60798
|
+
return { ok: false, reason: "challenge_failed", detail: "response did not echo the fresh challenge" };
|
|
60799
|
+
}
|
|
60800
|
+
cache2.set(cacheKey, { requestBytes, expiresAt: now() + CHALLENGE_TTL_MS });
|
|
60801
|
+
return { ok: true, verificationBytes: requestBytes };
|
|
60802
|
+
}
|
|
60803
|
+
|
|
60804
|
+
// src/webhook-egress/delivery.ts
|
|
60805
|
+
var CLAMP_LIMIT_BYTES = 262144;
|
|
60806
|
+
var MAX_ATTEMPTS = 5;
|
|
60807
|
+
var BACKOFF_BASE_MS = 250;
|
|
60808
|
+
var BACKOFF_CAP_MS = 4e3;
|
|
60809
|
+
function deliveryBackoffDelay(attempt) {
|
|
60810
|
+
return Math.min(BACKOFF_BASE_MS * 2 ** attempt, BACKOFF_CAP_MS);
|
|
60811
|
+
}
|
|
60812
|
+
var TERMINAL_NO_RETRY_STATUSES = /* @__PURE__ */ new Set([410, 413]);
|
|
60813
|
+
function serializeEnvelope(envelope) {
|
|
60814
|
+
return new TextEncoder().encode(JSON.stringify(envelope));
|
|
60815
|
+
}
|
|
60816
|
+
function envelopeClampedBytes(event) {
|
|
60817
|
+
let bytes = serializeEnvelope(event);
|
|
60818
|
+
if (bytes.byteLength <= CLAMP_LIMIT_BYTES) return { bytes, clamped: false };
|
|
60819
|
+
const data = event.data;
|
|
60820
|
+
const clampedEvent = {
|
|
60821
|
+
...event,
|
|
60822
|
+
data: {
|
|
60823
|
+
summary: `Payload exceeded the ${CLAMP_LIMIT_BYTES}-byte wire clamp and was truncated`,
|
|
60824
|
+
subject: data && typeof data === "object" && "subject" in data ? data.subject : null
|
|
60825
|
+
},
|
|
60826
|
+
truncated: true
|
|
60827
|
+
};
|
|
60828
|
+
bytes = serializeEnvelope(clampedEvent);
|
|
60829
|
+
return { bytes, clamped: true };
|
|
60830
|
+
}
|
|
60831
|
+
async function deliverEventEnvelope(replay, event, deps2 = {}) {
|
|
60832
|
+
const now = deps2.now ?? (() => Date.now());
|
|
60833
|
+
const sleep3 = deps2.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
60834
|
+
const fetcher = deps2.fetch ?? ((url, init) => ssrfFetch(url, init));
|
|
60835
|
+
const { bytes: payloadBytes } = envelopeClampedBytes(event);
|
|
60836
|
+
const delivery = { attempts: 0 };
|
|
60837
|
+
for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt++) {
|
|
60838
|
+
if (attempt > 0) await sleep3(deliveryBackoffDelay(attempt));
|
|
60839
|
+
delivery.attempts += 1;
|
|
60840
|
+
delivery.lastAt = new Date(now()).toISOString();
|
|
60841
|
+
const timestamp = Math.floor(now() / 1e3);
|
|
60842
|
+
let signed;
|
|
60843
|
+
try {
|
|
60844
|
+
signed = signWebhook({ msgId: event.eventId, timestamp, payload: payloadBytes, secrets: replay.secrets });
|
|
60845
|
+
} catch (err) {
|
|
60846
|
+
delivery.lastError = `signing failed: ${err.message}`;
|
|
60847
|
+
return { ok: false, reason: "invalid_secret", delivery };
|
|
60848
|
+
}
|
|
60849
|
+
let response;
|
|
60850
|
+
try {
|
|
60851
|
+
response = await fetcher(replay.callbackUrl, {
|
|
60852
|
+
method: "POST",
|
|
60853
|
+
headers: { ...signed, "content-type": "application/json" },
|
|
60854
|
+
body: payloadBytes,
|
|
60855
|
+
timeoutMs: 15e3
|
|
60856
|
+
// delivery default (challenge uses 10_000)
|
|
60857
|
+
});
|
|
60858
|
+
} catch (err) {
|
|
60859
|
+
delivery.lastError = err instanceof Error ? err.message : String(err);
|
|
60860
|
+
if (attempt < MAX_ATTEMPTS - 1) continue;
|
|
60861
|
+
return { ok: false, reason: "network", delivery };
|
|
60862
|
+
}
|
|
60863
|
+
if (response.status >= 200 && response.status <= 299) {
|
|
60864
|
+
return { ok: true, delivery };
|
|
60865
|
+
}
|
|
60866
|
+
if (TERMINAL_NO_RETRY_STATUSES.has(response.status)) {
|
|
60867
|
+
delivery.lastError = `terminal status ${response.status}`;
|
|
60868
|
+
return { ok: false, reason: `http_${response.status}`, delivery };
|
|
60869
|
+
}
|
|
60870
|
+
delivery.lastError = `retryable status ${response.status}`;
|
|
60871
|
+
}
|
|
60872
|
+
return { ok: false, reason: "retries_exhausted", delivery };
|
|
60873
|
+
}
|
|
59929
60874
|
async function hasNamedLlmEndpoints() {
|
|
59930
60875
|
const override = process.env.LLM_ENDPOINT_ENDPOINTS_FILE?.trim();
|
|
59931
60876
|
const path = override && override.length > 0 ? override : join(homedir(), ".agentproto", "llm-endpoints.json");
|
|
@@ -60186,13 +61131,13 @@ function createJoinTokenRegistry(deps2) {
|
|
|
60186
61131
|
} catch (err) {
|
|
60187
61132
|
if (signal.aborted) break;
|
|
60188
61133
|
log(`[join-tokens] "${record2.name}": dial failed: ${errMsg4(err)}`);
|
|
60189
|
-
await
|
|
61134
|
+
await sleep2(backoff, signal);
|
|
60190
61135
|
backoff = Math.min(backoff * 2, reconnectMaxMs);
|
|
60191
61136
|
continue;
|
|
60192
61137
|
}
|
|
60193
61138
|
if (!sink.isOpen) {
|
|
60194
61139
|
if (signal.aborted) break;
|
|
60195
|
-
await
|
|
61140
|
+
await sleep2(backoff, signal);
|
|
60196
61141
|
backoff = Math.min(backoff * 2, reconnectMaxMs);
|
|
60197
61142
|
continue;
|
|
60198
61143
|
}
|
|
@@ -60221,12 +61166,17 @@ function createJoinTokenRegistry(deps2) {
|
|
|
60221
61166
|
capturedSession = result.session;
|
|
60222
61167
|
return { reply: encodePairingMessage(result.reply), keys: result.session };
|
|
60223
61168
|
},
|
|
60224
|
-
{
|
|
61169
|
+
{
|
|
61170
|
+
timeoutMs: handshakeTimeoutMs,
|
|
61171
|
+
// Post-handshake close reason (BOOTSTRAP P4 item 2) — same
|
|
61172
|
+
// diagnostic hook the pairing-host accept-loop wires.
|
|
61173
|
+
log: (line) => log(`[join-tokens] "${record2.name}": ${line}`)
|
|
61174
|
+
}
|
|
60225
61175
|
);
|
|
60226
61176
|
} catch (err) {
|
|
60227
61177
|
if (signal.aborted) break;
|
|
60228
61178
|
log(`[join-tokens] "${record2.name}": handshake failed: ${errMsg4(err)}`);
|
|
60229
|
-
await
|
|
61179
|
+
await sleep2(backoff, signal);
|
|
60230
61180
|
backoff = Math.min(backoff * 2, reconnectMaxMs);
|
|
60231
61181
|
continue;
|
|
60232
61182
|
}
|
|
@@ -60252,7 +61202,7 @@ function createJoinTokenRegistry(deps2) {
|
|
|
60252
61202
|
}
|
|
60253
61203
|
return { create, list, revoke, startAutoconnect, shutdown };
|
|
60254
61204
|
}
|
|
60255
|
-
function
|
|
61205
|
+
function sleep2(ms, signal) {
|
|
60256
61206
|
return new Promise((resolve41) => {
|
|
60257
61207
|
if (signal.aborted) return resolve41();
|
|
60258
61208
|
const timer = setTimeout(resolve41, ms);
|
|
@@ -60988,6 +61938,7 @@ async function createGateway(opts) {
|
|
|
60988
61938
|
registerPairingTools(server, { registry: opts.pairingRegistry });
|
|
60989
61939
|
registerDeviceTools(server, {
|
|
60990
61940
|
registry: createDeviceRegistry(opts.pairingRegistry, opts.hostRegistry),
|
|
61941
|
+
...opts.hostRegistry ? { hosts: opts.hostRegistry } : {},
|
|
60991
61942
|
...opts.joinTokens ? { joinTokens: opts.joinTokens } : {}
|
|
60992
61943
|
});
|
|
60993
61944
|
}
|
|
@@ -61614,6 +62565,6 @@ var export_readPairingsSnapshot = pairing_registry_exports.readPairingsSnapshot;
|
|
|
61614
62565
|
var export_removeProviderKey = providers_store_exports.removeProviderKey;
|
|
61615
62566
|
var export_setProviderKey = providers_store_exports.setProviderKey;
|
|
61616
62567
|
|
|
61617
|
-
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_HOST_LOAD_BUDGET_MS, DEFAULT_HOST_LOAD_THRESHOLDS, DEFAULT_MODEL_ROLES, DEFAULT_ORCHESTRATOR_TOOLS, DEFAULT_WORKTREE_ISOLATION, DEFAULT_WORKTREE_PROVISION_CONCURRENCY, 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, MODEL_ROLE_REF_PREFIX, export_PAIRINGS_VERSION as 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, WORKTREE_PROVISION_CONCURRENCY_ENV, activityCounts, addHarnessPreset, allowedSignersLine, appendConversationRecord, attachSandbox, bucketDir, bucketSessionsFile, bucketTranscriptDir, buildCatalogModels, buildCatalogProviderModels, buildHostLoadReport, buildMcpConfigSnippet, buildRouteAwareLaunchConfig, canonicalForModeId, claudeProjectSlug, collectGcCandidates, collectHostSample, compactOutcome, composeMode, composeSessionObservers, composedFromRangeSha, computeHostWarnings, continueInterruptedSessions, continueSkipReason, conversationIndexPath, createActivityProjector, createDaemonReviewerHost, createDefaultHostProbes, createDeviceRegistry, createFileStepCache, createGateway, createHostLoadService, createHostRegistry, createInboundEndpointStore, createJoinTokenRegistry, createOrchestratorInjector, createOrchestratorMcpServerFactory, export_createPairingRegistry as createPairingRegistry, createPrProvenanceReconciler, createProcessStatsService, createReviewLaneExecutor, createReviewLedger, createReviewPackLoader, createReviewRunner, createScopeTokenRegistry, createSessionMessage, createSupervisorTaskGateRunner, createTaskLedger, createWorkspaceBrainSubscriber, createWorkspaceBrains, createWorkspaceFs, credentialFingerprint, daemonRegistryDir, decideWorktreeIsolation, declaredPresetToProviderPreset, decomposeMode, defaultBrowserAdapterIds, defaultBrowserAdapterResolution, defaultReviewKeysDir, defaultReviewLedgerRoot, defaultReviewPackCacheDir, defineBraveSearchHttpDriver, defineSerperHttpDriver, deleteUserPreset, deriveSessionOutcome, deriveSessionUsage, describeOwner, enrichRollupWithProviderQuota, enrichWithRemainingQuota, ensureReviewSigningKey, escapeHumanPrompt, escapeMessageBody, evaluateCostBudget, execGh, extractHelpSection, fetchPrStatus, fileConversationStore, fileExists3 as fileExists, filterActivities, findComposeCandidate, findConversationRecord, findNativeMode, findPrForCommit, formatToolCall, formatToolResult, getDefaultHarnessPreset, getHarnessPreset, getHostLoadService, getMcpCredentialDeps, getProcessStatsService, export_getProviderKey as getProviderKey, getToolHelp, getUserPreset, githubRepoOf, harnessPresetsPath, hostPlaceholders, injectMcpAppBridge, export_injectProviderKeysIntoEnv as injectProviderKeysIntoEnv, isAgentCliAuthConfigured, isClosedTaskStatus, isKnownSessionEndReason, isMessageAllowed, isProviderLimitError, isSafeBucketSlug, isTerminalActivityState, listBuckets, listHarnessPresets, listModelRoles, listPresets, listToolHelpTopics, listUserPresets, loadHarnessPresets, export_loadProviders as loadProviders, loadQuotaStore, loadUserPresets, loadWorkspaceModelRoles, loadWorktreeIsolation, locateConversationByNativePath, locateConversationBySessionId, makeBrowserAdapterLister, makeSandboxCredsStore, makeSandboxResolver, matchesMessageFilter, measureTool, measureToolList, messageFrom, migrateLegacySessionsFile, migrationMarkerPath, mintMessageId, monitorPolicyWait, monitorSessionWait, narrowOrchestratorTools, normalizeCommand, normalizeInbound, normalizeModeId, normalizeRemote, normalizeSkillsOption, normalizeWorktreeField, parseAnthropicRateLimitHeaders, parseDuration, parseModelRoleRef, parsePrUrl, parseTaskStatus, parseWindow, parseWorktreeIsolationMode, pickUsageDetail, policyToActivities, policyWatchesSession, prHeadSha, prToActivities, projectBrowserTools, projectSessionUsage, export_providerEnvVar as providerEnvVar, export_providersPath as providersPath, readConversationIndex, readDaemonRegistry, export_readPairingsSnapshot as 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, resolveModelRole, 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, trackWorktreeProvision, turnToActivities, unlinkDaemonRegistryEntry, unlinkRuntimeMeta, updateHarnessPreset, upsertSandboxLedger, userPresetsPath, verifyInboundSignature, verifySignedAttestation, webSearchTool, withPr, withPrStatus, workflowToActivities, writeDaemonRegistryEntry };
|
|
62568
|
+
export { AnthropicRemainingQuotaReader, AuthResolutionError, BUCKETS_ROOT, CLAMP_LIMIT_BYTES, CLAUDE_CODE_OAUTH_SOURCE, CONTINUE_PROMPT_SOURCE, DEAD_SESSION_STATUSES, DEFAULT_ALWAYS_ON_TOOLS, DEFAULT_BUCKET, DEFAULT_CONTINUE_PROMPT, DEFAULT_HOST_LOAD_BUDGET_MS, DEFAULT_HOST_LOAD_THRESHOLDS, DEFAULT_MODEL_ROLES, DEFAULT_ORCHESTRATOR_TOOLS, DEFAULT_WORKTREE_ISOLATION, DEFAULT_WORKTREE_PROVISION_CONCURRENCY, DEVICE_PROMPT_PATH_PREFIX, GC_REAPABLE_STATES, HOSTS_VERSION, HOST_HANDSHAKE_REMEDIATION_HINT, HOST_SCOPE_HEADER, HarnessPresetValidationError, INBOUND_PROVIDERS, JOIN_TOKENS_VERSION, LEGACY_SESSIONS_FILE, MAX_ATTEMPTS, MAX_AUTO_CONTINUE_ATTEMPTS, MAX_MESSAGE_DATA_BYTES, MCP_APP_BRIDGE_SCRIPT, MESSAGE_KINDS, MESSAGE_PREAMBLE, MESSAGE_TAG, MESSAGE_URGENCIES, MODEL_ROLE_REF_PREFIX, export_PAIRINGS_VERSION as 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, SsrfFetchError, SubscriptionSourceError, TASK_STATUSES, WORKSPACE_SLUG_ENV, WORKTREE_ISOLATION_ENV, WORKTREE_PROVISION_CONCURRENCY_ENV, activityCounts, addHarnessPreset, allowedSignersLine, appendConversationRecord, attachSandbox, bucketDir, bucketSessionsFile, bucketTranscriptDir, buildCatalogModels, buildCatalogProviderModels, buildHostLoadReport, buildMcpConfigSnippet, buildRouteAwareLaunchConfig, canonicalForModeId, challengeCacheSize, challengeReasonForSsrf, claudeProjectSlug, collectGcCandidates, collectHostSample, compactOutcome, composeMode, composeSessionObservers, composedFromRangeSha, computeHostWarnings, continueInterruptedSessions, continueSkipReason, conversationIndexPath, createActivityProjector, createDaemonReviewerHost, createDefaultHostProbes, createDeviceRegistry, createFileStepCache, createGateway, createHostLoadService, createHostRegistry, createInboundEndpointStore, createJoinTokenRegistry, createOrchestratorInjector, createOrchestratorMcpServerFactory, export_createPairingRegistry as createPairingRegistry, createPrProvenanceReconciler, createProcessStatsService, createReviewLaneExecutor, createReviewLedger, createReviewPackLoader, createReviewRunner, createScopeTokenRegistry, createSessionMessage, createSupervisorTaskGateRunner, createTaskLedger, createWorkspaceBrainSubscriber, createWorkspaceBrains, createWorkspaceFs, credentialFingerprint, daemonRegistryDir, decideWorktreeIsolation, declaredPresetToProviderPreset, decodeWhsecSecret, decomposeMode, defaultBrowserAdapterIds, defaultBrowserAdapterResolution, defaultReviewKeysDir, defaultReviewLedgerRoot, defaultReviewPackCacheDir, defineBraveSearchHttpDriver, defineSerperHttpDriver, deleteUserPreset, deliverEventEnvelope, deliveryBackoffDelay, deriveSessionOutcome, deriveSessionUsage, describeOwner, encodeWhsecSecret, enrichRollupWithProviderQuota, enrichWithRemainingQuota, ensureReviewSigningKey, envelopeClampedBytes, escapeHumanPrompt, escapeMessageBody, evaluateCostBudget, execGh, extractHelpSection, fetchPrStatus, fileConversationStore, fileExists3 as fileExists, filterActivities, findComposeCandidate, findConversationRecord, findNativeMode, findPrForCommit, formatToolCall, formatToolResult, getDefaultHarnessPreset, getHarnessPreset, getHostLoadService, getMcpCredentialDeps, getProcessStatsService, export_getProviderKey as getProviderKey, getToolHelp, getUserPreset, githubRepoOf, harnessPresetsPath, hostPlaceholders, injectMcpAppBridge, export_injectProviderKeysIntoEnv as injectProviderKeysIntoEnv, isAgentCliAuthConfigured, isClosedTaskStatus, isKnownSessionEndReason, isMessageAllowed, isProviderLimitError, isPubliclyRoutable, isSafeBucketSlug, isTerminalActivityState, listBuckets, listHarnessPresets, listModelRoles, listPresets, listToolHelpTopics, listUserPresets, loadHarnessPresets, export_loadProviders as loadProviders, loadQuotaStore, loadUserPresets, loadWorkspaceModelRoles, loadWorktreeIsolation, locateConversationByNativePath, locateConversationBySessionId, makeBrowserAdapterLister, makeSandboxCredsStore, makeSandboxResolver, matchesMessageFilter, measureTool, measureToolList, messageFrom, migrateLegacySessionsFile, migrationMarkerPath, mintMessageId, monitorPolicyWait, monitorSessionWait, narrowOrchestratorTools, normalizeCallbackUrl, normalizeCommand, normalizeInbound, normalizeModeId, normalizeRemote, normalizeSkillsOption, normalizeWorktreeField, parseAnthropicRateLimitHeaders, parseDuration, parseModelRoleRef, parsePrUrl, parseTaskStatus, parseWindow, parseWorktreeIsolationMode, pickUsageDetail, policyToActivities, policyWatchesSession, prHeadSha, prToActivities, projectBrowserTools, projectSessionUsage, promptHostSession, export_providerEnvVar as providerEnvVar, export_providersPath as providersPath, readConversationIndex, readDaemonRegistry, readHostsSnapshot, export_readPairingsSnapshot as 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, resetChallengeCacheForTests, resolveAuthSpec, resolveBucketSlug, resolveDeferredToolsGatewayOption, resolveEffectiveLlmEndpointFlag, resolveModelRole, resolveNativeLink, resolvePosture, resolvePrincipal, resolveRelation, resolveReuseFromLedger, resolveReviewerPreset, resolveSpawnDefaults, resolveSubscriptionCredential, rollupSessionSubtree, rollupUsage, runContinueOnBootPass, runCrashDetectPass, runEagerResumePass, runIdleReapPass, runShellLane, runStallWatchdogPass, sandboxLedgerPath, sanitizeLabel, saveUserPreset, serializeEnvelope, setDefaultPreset, setEgressDispatcherForTests, setMcpCredentialDeps, export_setProviderKey as setProviderKey, signAttestation, signWebhook, ssrfFetch, sweepStaleDaemonRegistry, sweepStaleRuntimeMetas, toWorktreeStatusView, trackWorktreeProvision, turnToActivities, unlinkDaemonRegistryEntry, unlinkRuntimeMeta, updateHarnessPreset, upsertSandboxLedger, userPresetsPath, verifyCallback, verifyInboundSignature, verifySignedAttestation, webSearchTool, withPr, withPrStatus, workflowToActivities, writeDaemonRegistryEntry };
|
|
61618
62569
|
//# sourceMappingURL=index.mjs.map
|
|
61619
62570
|
//# sourceMappingURL=index.mjs.map
|