@integrity-labs/agt-cli 0.28.860 → 0.28.862
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/bin/agt.js +5 -5
- package/dist/{chunk-7F7DI322.js → chunk-3DDF2R5V.js} +47 -10
- package/dist/chunk-3DDF2R5V.js.map +1 -0
- package/dist/{chunk-HKJLINLX.js → chunk-K6ODYXGF.js} +382 -130
- package/dist/chunk-K6ODYXGF.js.map +1 -0
- package/dist/{chunk-MRDINJFN.js → chunk-KEF52TAB.js} +2 -2
- package/dist/{claude-pair-runtime-YU6U3NCG.js → claude-pair-runtime-K4AALPY6.js} +2 -2
- package/dist/lib/manager-worker.js +35 -16
- package/dist/lib/manager-worker.js.map +1 -1
- package/dist/mcp/direct-chat-channel.js +46 -9
- package/dist/mcp/index.js +46 -9
- package/dist/mcp/origami.js +46 -9
- package/dist/mcp/slack-channel.js +46 -9
- package/dist/mcp/telegram-channel.js +46 -9
- package/dist/{persistent-session-2HFQVRWH.js → persistent-session-6YD4WBFZ.js} +3 -3
- package/dist/{responsiveness-probe-2RCDUK7B.js → responsiveness-probe-YST6IPBA.js} +3 -3
- package/dist/{session-auth-dead-UNB2JGAH.js → session-auth-dead-DW6PJHGO.js} +2 -2
- package/package.json +1 -1
- package/dist/chunk-7F7DI322.js.map +0 -1
- package/dist/chunk-HKJLINLX.js.map +0 -1
- /package/dist/{chunk-MRDINJFN.js.map → chunk-KEF52TAB.js.map} +0 -0
- /package/dist/{claude-pair-runtime-YU6U3NCG.js.map → claude-pair-runtime-K4AALPY6.js.map} +0 -0
- /package/dist/{persistent-session-2HFQVRWH.js.map → persistent-session-6YD4WBFZ.js.map} +0 -0
- /package/dist/{responsiveness-probe-2RCDUK7B.js.map → responsiveness-probe-YST6IPBA.js.map} +0 -0
- /package/dist/{session-auth-dead-UNB2JGAH.js.map → session-auth-dead-DW6PJHGO.js.map} +0 -0
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
parseEnvIntegrations,
|
|
17
17
|
shellQuote,
|
|
18
18
|
summarizeUnanswerablePane
|
|
19
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-KEF52TAB.js";
|
|
20
20
|
import {
|
|
21
21
|
BIND_FAILURE_QUARANTINE_THRESHOLD,
|
|
22
22
|
INTEGRATIONS_SECTION_END,
|
|
@@ -76,7 +76,7 @@ import {
|
|
|
76
76
|
sessionTranscriptDir,
|
|
77
77
|
worseConnectivityOutcome,
|
|
78
78
|
wrapScheduledTaskPrompt
|
|
79
|
-
} from "./chunk-
|
|
79
|
+
} from "./chunk-3DDF2R5V.js";
|
|
80
80
|
import {
|
|
81
81
|
parsePsRows
|
|
82
82
|
} from "./chunk-XWVM4KPK.js";
|
|
@@ -320,6 +320,125 @@ function formatMirrorMismatch(m) {
|
|
|
320
320
|
return `[mcp-mirror] [parity-violation] server=${m.server} field=${m.field} location=${m.location} reason=${m.reason}`;
|
|
321
321
|
}
|
|
322
322
|
|
|
323
|
+
// ../../packages/core/dist/provisioning/computer-use-mcp.js
|
|
324
|
+
import { join } from "path";
|
|
325
|
+
var COMPUTER_USE_DEFINITION_ID = "open-computer-use";
|
|
326
|
+
var COMPUTER_USE_MCP_KEY = "open-computer-use";
|
|
327
|
+
var COMPUTER_USE_SSH_KEY_ENV = `${remoteMcpEnvPrefix(COMPUTER_USE_DEFINITION_ID, null)}_API_KEY`;
|
|
328
|
+
var DEFAULT_SSH_HOST = "127.0.0.1";
|
|
329
|
+
var DEFAULT_SSH_PORT = 2222;
|
|
330
|
+
var DEFAULT_REMOTE_COMMAND = "open-computer-use mcp";
|
|
331
|
+
var SAFE_SSH_USER = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
|
332
|
+
var ALLOWED_SSH_HOSTS = Object.freeze([
|
|
333
|
+
"127.0.0.1",
|
|
334
|
+
"::1",
|
|
335
|
+
"localhost"
|
|
336
|
+
]);
|
|
337
|
+
var SHELL_METACHARACTERS = /[;|&`$()<>\n\r*?\[\]{}!#~'"\\]/;
|
|
338
|
+
function readString(config, key) {
|
|
339
|
+
const v = config?.[key];
|
|
340
|
+
return typeof v === "string" && v.trim().length > 0 ? v.trim() : null;
|
|
341
|
+
}
|
|
342
|
+
function readPort(config) {
|
|
343
|
+
const v = config?.["ssh_port"];
|
|
344
|
+
if (v === void 0 || v === null || v === "")
|
|
345
|
+
return DEFAULT_SSH_PORT;
|
|
346
|
+
const n = typeof v === "number" ? v : Number(v);
|
|
347
|
+
if (!Number.isInteger(n) || n < 1 || n > 65535)
|
|
348
|
+
return null;
|
|
349
|
+
return n;
|
|
350
|
+
}
|
|
351
|
+
function buildComputerUseMcpEntry(input) {
|
|
352
|
+
const user = readString(input.config, "ssh_user");
|
|
353
|
+
if (user === null) {
|
|
354
|
+
return {
|
|
355
|
+
entry: null,
|
|
356
|
+
problem: `${COMPUTER_USE_DEFINITION_ID}: config.ssh_user is required (the macOS account the tunnel authenticates as) \u2014 no MCP entry was written`
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
const port = readPort(input.config);
|
|
360
|
+
if (port === null) {
|
|
361
|
+
return {
|
|
362
|
+
entry: null,
|
|
363
|
+
problem: `${COMPUTER_USE_DEFINITION_ID}: config.ssh_port must be an integer 1-65535 \u2014 no MCP entry was written`
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
if (!SAFE_SSH_USER.test(user)) {
|
|
367
|
+
return {
|
|
368
|
+
entry: null,
|
|
369
|
+
problem: `${COMPUTER_USE_DEFINITION_ID}: config.ssh_user must start with a letter or digit and contain only letters, digits, '.', '_' and '-' \u2014 no MCP entry was written`
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
const host = readString(input.config, "ssh_host") ?? DEFAULT_SSH_HOST;
|
|
373
|
+
if (!ALLOWED_SSH_HOSTS.includes(host)) {
|
|
374
|
+
return {
|
|
375
|
+
entry: null,
|
|
376
|
+
problem: `${COMPUTER_USE_DEFINITION_ID}: config.ssh_host must be one of ${ALLOWED_SSH_HOSTS.join(", ")} \u2014 the Mac opens a REVERSE tunnel, so the agent host connects to its own loopback. A different host would send this agent's key somewhere else. No MCP entry was written`
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
const remoteCommand = readString(input.config, "remote_command") ?? DEFAULT_REMOTE_COMMAND;
|
|
380
|
+
if (SHELL_METACHARACTERS.test(remoteCommand)) {
|
|
381
|
+
return {
|
|
382
|
+
entry: null,
|
|
383
|
+
problem: `${COMPUTER_USE_DEFINITION_ID}: config.remote_command must name a binary and its arguments and may not contain shell metacharacters (; | & \` $ ( ) < > newline) \u2014 no MCP entry was written`
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
const knownHosts = join(input.agentDir, ".computer-use-known-hosts");
|
|
387
|
+
return {
|
|
388
|
+
problem: null,
|
|
389
|
+
entry: {
|
|
390
|
+
command: "node",
|
|
391
|
+
args: [
|
|
392
|
+
input.proxyPath,
|
|
393
|
+
"--",
|
|
394
|
+
"ssh",
|
|
395
|
+
// -T: no pty. The upstream speaks JSON-RPC over stdio; a pty would
|
|
396
|
+
// inject terminal control sequences into the stream.
|
|
397
|
+
"-T",
|
|
398
|
+
// Never prompt. Without this a missing or rejected key makes ssh sit
|
|
399
|
+
// waiting for a passphrase on a stdin that is carrying JSON-RPC, and
|
|
400
|
+
// the server appears to hang rather than to fail.
|
|
401
|
+
"-o",
|
|
402
|
+
"BatchMode=yes",
|
|
403
|
+
"-o",
|
|
404
|
+
"StrictHostKeyChecking=accept-new",
|
|
405
|
+
"-o",
|
|
406
|
+
`UserKnownHostsFile=${knownHosts}`,
|
|
407
|
+
"-o",
|
|
408
|
+
"ConnectTimeout=10",
|
|
409
|
+
// Keepalives so a dropped tunnel surfaces as a closed stdio pipe
|
|
410
|
+
// rather than a silently wedged child. ENG-10134 owns detecting that
|
|
411
|
+
// and respawning; this just makes the event happen.
|
|
412
|
+
"-o",
|
|
413
|
+
"ServerAliveInterval=15",
|
|
414
|
+
"-o",
|
|
415
|
+
"ServerAliveCountMax=3",
|
|
416
|
+
"-p",
|
|
417
|
+
String(port),
|
|
418
|
+
`${user}@${host}`,
|
|
419
|
+
remoteCommand
|
|
420
|
+
],
|
|
421
|
+
env: {
|
|
422
|
+
// The three the approval gate reads (ENG-10132). Deliberately the vars
|
|
423
|
+
// the agent's environment already carries rather than new ones: the
|
|
424
|
+
// gate refuses every call when any is missing, so adding a bespoke var
|
|
425
|
+
// here would mean a new way for the proxy to be silently disarmed.
|
|
426
|
+
AGT_HOST: "${AGT_HOST}",
|
|
427
|
+
AGT_API_KEY: "${AGT_API_KEY}",
|
|
428
|
+
AGT_AGENT_ID: input.agentId,
|
|
429
|
+
// The credential. `${...}` is Claude Code's spawn-time substitution
|
|
430
|
+
// from the agent's environment, which the manager populates from
|
|
431
|
+
// `.env.integrations` — the same path every other integration
|
|
432
|
+
// credential travels.
|
|
433
|
+
[COMPUTER_USE_SSH_KEY_ENV]: `\${${COMPUTER_USE_SSH_KEY_ENV}}`,
|
|
434
|
+
// ssh must be resolvable, and it is not a node builtin.
|
|
435
|
+
PATH: process.env["PATH"] ?? "",
|
|
436
|
+
HOME: process.env["HOME"] ?? ""
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
|
|
323
442
|
// ../../packages/core/dist/provisioning/hook-env.js
|
|
324
443
|
function augmentedHookPath(currentPath) {
|
|
325
444
|
const extras = [
|
|
@@ -457,7 +576,7 @@ import { execFile } from "child_process";
|
|
|
457
576
|
// ../../packages/core/dist/integrations/xurl-config.js
|
|
458
577
|
import { chmodSync as chmodSync2, existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync as unlinkSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
459
578
|
import { homedir } from "os";
|
|
460
|
-
import { dirname, join } from "path";
|
|
579
|
+
import { dirname, join as join2 } from "path";
|
|
461
580
|
import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
|
|
462
581
|
var XURL_FILE_MODE = 384;
|
|
463
582
|
var XURL_AGT_APP_PREFIX = "agt-";
|
|
@@ -576,7 +695,7 @@ function buildAgtXurlApps(integrations) {
|
|
|
576
695
|
}
|
|
577
696
|
function getXurlStorePath() {
|
|
578
697
|
const home = process.env["HOME"] ?? process.env["USERPROFILE"] ?? homedir();
|
|
579
|
-
return
|
|
698
|
+
return join2(home, ".xurl");
|
|
580
699
|
}
|
|
581
700
|
function writeXurlStoreForIntegrations(integrations, filePath = getXurlStorePath()) {
|
|
582
701
|
const agtApps = buildAgtXurlApps(integrations);
|
|
@@ -619,7 +738,7 @@ function writeXurlStoreForIntegrations(integrations, filePath = getXurlStorePath
|
|
|
619
738
|
// ../../packages/core/dist/integrations/framer-credentials.js
|
|
620
739
|
import { chmodSync as chmodSync3, existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync3, renameSync as renameSync3, rmSync, statSync, unlinkSync as unlinkSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
621
740
|
import { homedir as homedir2 } from "os";
|
|
622
|
-
import { dirname as dirname2, join as
|
|
741
|
+
import { dirname as dirname2, join as join3 } from "path";
|
|
623
742
|
var FRAMER_PROJECTS_CONFIG_VERSION = 2;
|
|
624
743
|
var BARE_PROJECT_ID = /^[A-Za-z0-9]{20}$/u;
|
|
625
744
|
var SLUGGED_PROJECT_ID = /^.+--([A-Za-z0-9]+)/u;
|
|
@@ -718,7 +837,7 @@ function mergeFramerProjectsConfig(existing, inputs, options = {}) {
|
|
|
718
837
|
var FRAMER_FILE_MODE = 384;
|
|
719
838
|
var MANAGED_SIDECAR_NAME = ".augmented-managed-projects.json";
|
|
720
839
|
function sidecarPathFor(projectsPath) {
|
|
721
|
-
return
|
|
840
|
+
return join3(dirname2(projectsPath), MANAGED_SIDECAR_NAME);
|
|
722
841
|
}
|
|
723
842
|
function readManagedIds(projectsPath) {
|
|
724
843
|
try {
|
|
@@ -794,9 +913,9 @@ function asString2(value) {
|
|
|
794
913
|
function getFramerProjectsPath() {
|
|
795
914
|
const xdg = process.env["XDG_CONFIG_HOME"];
|
|
796
915
|
if (xdg)
|
|
797
|
-
return
|
|
916
|
+
return join3(xdg, "framer", "projects.json");
|
|
798
917
|
const home = process.env["HOME"] ?? process.env["USERPROFILE"] ?? homedir2();
|
|
799
|
-
return
|
|
918
|
+
return join3(home, ".config", "framer", "projects.json");
|
|
800
919
|
}
|
|
801
920
|
function buildFramerCredentialInputs(integrations) {
|
|
802
921
|
const inputs = [];
|
|
@@ -1245,125 +1364,6 @@ function decryptIntegrationCredentials(credentials, row) {
|
|
|
1245
1364
|
return out;
|
|
1246
1365
|
}
|
|
1247
1366
|
|
|
1248
|
-
// ../../packages/core/dist/provisioning/computer-use-mcp.js
|
|
1249
|
-
import { join as join3 } from "path";
|
|
1250
|
-
var COMPUTER_USE_DEFINITION_ID = "open-computer-use";
|
|
1251
|
-
var COMPUTER_USE_MCP_KEY = "open-computer-use";
|
|
1252
|
-
var COMPUTER_USE_SSH_KEY_ENV = `${remoteMcpEnvPrefix(COMPUTER_USE_DEFINITION_ID, null)}_API_KEY`;
|
|
1253
|
-
var DEFAULT_SSH_HOST = "127.0.0.1";
|
|
1254
|
-
var DEFAULT_SSH_PORT = 2222;
|
|
1255
|
-
var DEFAULT_REMOTE_COMMAND = "open-computer-use mcp";
|
|
1256
|
-
var SAFE_SSH_USER = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
|
1257
|
-
var ALLOWED_SSH_HOSTS = Object.freeze([
|
|
1258
|
-
"127.0.0.1",
|
|
1259
|
-
"::1",
|
|
1260
|
-
"localhost"
|
|
1261
|
-
]);
|
|
1262
|
-
var SHELL_METACHARACTERS = /[;|&`$()<>\n\r*?\[\]{}!#~'"\\]/;
|
|
1263
|
-
function readString(config, key) {
|
|
1264
|
-
const v = config?.[key];
|
|
1265
|
-
return typeof v === "string" && v.trim().length > 0 ? v.trim() : null;
|
|
1266
|
-
}
|
|
1267
|
-
function readPort(config) {
|
|
1268
|
-
const v = config?.["ssh_port"];
|
|
1269
|
-
if (v === void 0 || v === null || v === "")
|
|
1270
|
-
return DEFAULT_SSH_PORT;
|
|
1271
|
-
const n = typeof v === "number" ? v : Number(v);
|
|
1272
|
-
if (!Number.isInteger(n) || n < 1 || n > 65535)
|
|
1273
|
-
return null;
|
|
1274
|
-
return n;
|
|
1275
|
-
}
|
|
1276
|
-
function buildComputerUseMcpEntry(input) {
|
|
1277
|
-
const user = readString(input.config, "ssh_user");
|
|
1278
|
-
if (user === null) {
|
|
1279
|
-
return {
|
|
1280
|
-
entry: null,
|
|
1281
|
-
problem: `${COMPUTER_USE_DEFINITION_ID}: config.ssh_user is required (the macOS account the tunnel authenticates as) \u2014 no MCP entry was written`
|
|
1282
|
-
};
|
|
1283
|
-
}
|
|
1284
|
-
const port = readPort(input.config);
|
|
1285
|
-
if (port === null) {
|
|
1286
|
-
return {
|
|
1287
|
-
entry: null,
|
|
1288
|
-
problem: `${COMPUTER_USE_DEFINITION_ID}: config.ssh_port must be an integer 1-65535 \u2014 no MCP entry was written`
|
|
1289
|
-
};
|
|
1290
|
-
}
|
|
1291
|
-
if (!SAFE_SSH_USER.test(user)) {
|
|
1292
|
-
return {
|
|
1293
|
-
entry: null,
|
|
1294
|
-
problem: `${COMPUTER_USE_DEFINITION_ID}: config.ssh_user must start with a letter or digit and contain only letters, digits, '.', '_' and '-' \u2014 no MCP entry was written`
|
|
1295
|
-
};
|
|
1296
|
-
}
|
|
1297
|
-
const host = readString(input.config, "ssh_host") ?? DEFAULT_SSH_HOST;
|
|
1298
|
-
if (!ALLOWED_SSH_HOSTS.includes(host)) {
|
|
1299
|
-
return {
|
|
1300
|
-
entry: null,
|
|
1301
|
-
problem: `${COMPUTER_USE_DEFINITION_ID}: config.ssh_host must be one of ${ALLOWED_SSH_HOSTS.join(", ")} \u2014 the Mac opens a REVERSE tunnel, so the agent host connects to its own loopback. A different host would send this agent's key somewhere else. No MCP entry was written`
|
|
1302
|
-
};
|
|
1303
|
-
}
|
|
1304
|
-
const remoteCommand = readString(input.config, "remote_command") ?? DEFAULT_REMOTE_COMMAND;
|
|
1305
|
-
if (SHELL_METACHARACTERS.test(remoteCommand)) {
|
|
1306
|
-
return {
|
|
1307
|
-
entry: null,
|
|
1308
|
-
problem: `${COMPUTER_USE_DEFINITION_ID}: config.remote_command must name a binary and its arguments and may not contain shell metacharacters (; | & \` $ ( ) < > newline) \u2014 no MCP entry was written`
|
|
1309
|
-
};
|
|
1310
|
-
}
|
|
1311
|
-
const knownHosts = join3(input.agentDir, ".computer-use-known-hosts");
|
|
1312
|
-
return {
|
|
1313
|
-
problem: null,
|
|
1314
|
-
entry: {
|
|
1315
|
-
command: "node",
|
|
1316
|
-
args: [
|
|
1317
|
-
input.proxyPath,
|
|
1318
|
-
"--",
|
|
1319
|
-
"ssh",
|
|
1320
|
-
// -T: no pty. The upstream speaks JSON-RPC over stdio; a pty would
|
|
1321
|
-
// inject terminal control sequences into the stream.
|
|
1322
|
-
"-T",
|
|
1323
|
-
// Never prompt. Without this a missing or rejected key makes ssh sit
|
|
1324
|
-
// waiting for a passphrase on a stdin that is carrying JSON-RPC, and
|
|
1325
|
-
// the server appears to hang rather than to fail.
|
|
1326
|
-
"-o",
|
|
1327
|
-
"BatchMode=yes",
|
|
1328
|
-
"-o",
|
|
1329
|
-
"StrictHostKeyChecking=accept-new",
|
|
1330
|
-
"-o",
|
|
1331
|
-
`UserKnownHostsFile=${knownHosts}`,
|
|
1332
|
-
"-o",
|
|
1333
|
-
"ConnectTimeout=10",
|
|
1334
|
-
// Keepalives so a dropped tunnel surfaces as a closed stdio pipe
|
|
1335
|
-
// rather than a silently wedged child. ENG-10134 owns detecting that
|
|
1336
|
-
// and respawning; this just makes the event happen.
|
|
1337
|
-
"-o",
|
|
1338
|
-
"ServerAliveInterval=15",
|
|
1339
|
-
"-o",
|
|
1340
|
-
"ServerAliveCountMax=3",
|
|
1341
|
-
"-p",
|
|
1342
|
-
String(port),
|
|
1343
|
-
`${user}@${host}`,
|
|
1344
|
-
remoteCommand
|
|
1345
|
-
],
|
|
1346
|
-
env: {
|
|
1347
|
-
// The three the approval gate reads (ENG-10132). Deliberately the vars
|
|
1348
|
-
// the agent's environment already carries rather than new ones: the
|
|
1349
|
-
// gate refuses every call when any is missing, so adding a bespoke var
|
|
1350
|
-
// here would mean a new way for the proxy to be silently disarmed.
|
|
1351
|
-
AGT_HOST: "${AGT_HOST}",
|
|
1352
|
-
AGT_API_KEY: "${AGT_API_KEY}",
|
|
1353
|
-
AGT_AGENT_ID: input.agentId,
|
|
1354
|
-
// The credential. `${...}` is Claude Code's spawn-time substitution
|
|
1355
|
-
// from the agent's environment, which the manager populates from
|
|
1356
|
-
// `.env.integrations` — the same path every other integration
|
|
1357
|
-
// credential travels.
|
|
1358
|
-
[COMPUTER_USE_SSH_KEY_ENV]: `\${${COMPUTER_USE_SSH_KEY_ENV}}`,
|
|
1359
|
-
// ssh must be resolvable, and it is not a node builtin.
|
|
1360
|
-
PATH: process.env["PATH"] ?? "",
|
|
1361
|
-
HOME: process.env["HOME"] ?? ""
|
|
1362
|
-
}
|
|
1363
|
-
}
|
|
1364
|
-
};
|
|
1365
|
-
}
|
|
1366
|
-
|
|
1367
1367
|
// ../../packages/core/dist/provisioning/github-broker-credentials.js
|
|
1368
1368
|
var GITHUB_BROKER_BIN_DIR = ".claude/agt-bin";
|
|
1369
1369
|
var GIT_CREDENTIAL_HELPER_BASENAME = "git-credential-agt-github";
|
|
@@ -6728,7 +6728,7 @@ function exchangeFailureKind(err) {
|
|
|
6728
6728
|
}
|
|
6729
6729
|
|
|
6730
6730
|
// src/lib/api-client.ts
|
|
6731
|
-
var agtCliVersion = true ? "0.28.
|
|
6731
|
+
var agtCliVersion = true ? "0.28.862" : "dev";
|
|
6732
6732
|
var lastConfigHash = null;
|
|
6733
6733
|
function setConfigHash(hash) {
|
|
6734
6734
|
lastConfigHash = hash && hash.length > 0 ? hash : null;
|
|
@@ -9706,6 +9706,247 @@ function buildConnectivityProbeDeps(projectDir, probeEnv) {
|
|
|
9706
9706
|
};
|
|
9707
9707
|
}
|
|
9708
9708
|
|
|
9709
|
+
// src/lib/computer-use-tunnel.ts
|
|
9710
|
+
import { connect } from "net";
|
|
9711
|
+
function isTunnelUsable(v) {
|
|
9712
|
+
return v === null || v.state === "reachable";
|
|
9713
|
+
}
|
|
9714
|
+
function classifyTunnelProbe(o, at) {
|
|
9715
|
+
switch (o.kind) {
|
|
9716
|
+
case "banner":
|
|
9717
|
+
return o.line.startsWith("SSH-") ? { state: "reachable", detail: `sshd answered through the tunnel (${o.line.trim()})`, at } : {
|
|
9718
|
+
state: "stale-forward",
|
|
9719
|
+
detail: "the port accepted a connection and replied with something that is not an SSH banner \u2014 the forward is being held by something other than the Mac\u2019s sshd",
|
|
9720
|
+
at
|
|
9721
|
+
};
|
|
9722
|
+
case "connected-no-banner":
|
|
9723
|
+
return {
|
|
9724
|
+
state: "stale-forward",
|
|
9725
|
+
detail: "the port accepted a connection but sent no SSH banner \u2014 a dead forward is still holding it (the case ExitOnForwardFailure=yes prevents on the Mac side)",
|
|
9726
|
+
at
|
|
9727
|
+
};
|
|
9728
|
+
case "refused":
|
|
9729
|
+
return {
|
|
9730
|
+
state: "no-tunnel",
|
|
9731
|
+
detail: `nothing is listening: ${o.message} \u2014 the Mac is off, asleep, or autossh is not running`,
|
|
9732
|
+
at
|
|
9733
|
+
};
|
|
9734
|
+
case "timeout":
|
|
9735
|
+
return {
|
|
9736
|
+
state: "no-tunnel",
|
|
9737
|
+
detail: "the connection did not complete within its bound \u2014 the Mac is off, asleep, or unreachable",
|
|
9738
|
+
at
|
|
9739
|
+
};
|
|
9740
|
+
case "error":
|
|
9741
|
+
return { state: "no-tunnel", detail: `probe could not connect: ${o.message}`, at };
|
|
9742
|
+
}
|
|
9743
|
+
}
|
|
9744
|
+
function parseTunnelEndpoint(args) {
|
|
9745
|
+
if (!args || args.length === 0) return null;
|
|
9746
|
+
let port = null;
|
|
9747
|
+
for (let i = 0; i < args.length - 1; i++) {
|
|
9748
|
+
if (args[i] === "-p") {
|
|
9749
|
+
const n = Number(args[i + 1]);
|
|
9750
|
+
if (Number.isInteger(n) && n > 0 && n <= 65535) port = n;
|
|
9751
|
+
break;
|
|
9752
|
+
}
|
|
9753
|
+
}
|
|
9754
|
+
let host = null;
|
|
9755
|
+
for (const a of args) {
|
|
9756
|
+
const m = /^[A-Za-z0-9][A-Za-z0-9._-]*@(.+)$/.exec(a ?? "");
|
|
9757
|
+
if (m?.[1]) {
|
|
9758
|
+
host = m[1];
|
|
9759
|
+
break;
|
|
9760
|
+
}
|
|
9761
|
+
}
|
|
9762
|
+
if (port === null || host === null) return null;
|
|
9763
|
+
return { host, port };
|
|
9764
|
+
}
|
|
9765
|
+
var MAX_BANNER_BYTES = 255;
|
|
9766
|
+
var DEFAULT_PROBE_TIMEOUT_MS = 3e3;
|
|
9767
|
+
function probeTunnel(endpoint, opts = {}) {
|
|
9768
|
+
const timeoutMs = opts.timeoutMs ?? DEFAULT_PROBE_TIMEOUT_MS;
|
|
9769
|
+
return new Promise((resolve) => {
|
|
9770
|
+
let settled = false;
|
|
9771
|
+
let connected = false;
|
|
9772
|
+
let socket = null;
|
|
9773
|
+
let banner = null;
|
|
9774
|
+
const done = (o) => {
|
|
9775
|
+
if (settled) return;
|
|
9776
|
+
settled = true;
|
|
9777
|
+
clearTimeout(timer);
|
|
9778
|
+
try {
|
|
9779
|
+
socket?.destroy();
|
|
9780
|
+
} catch {
|
|
9781
|
+
}
|
|
9782
|
+
resolve(o);
|
|
9783
|
+
};
|
|
9784
|
+
const timer = setTimeout(() => {
|
|
9785
|
+
done(connected ? { kind: "connected-no-banner" } : { kind: "timeout" });
|
|
9786
|
+
}, timeoutMs);
|
|
9787
|
+
try {
|
|
9788
|
+
socket = connect({ host: endpoint.host, port: endpoint.port });
|
|
9789
|
+
} catch (err) {
|
|
9790
|
+
done({ kind: "error", message: err.message });
|
|
9791
|
+
return;
|
|
9792
|
+
}
|
|
9793
|
+
socket.on("connect", () => {
|
|
9794
|
+
connected = true;
|
|
9795
|
+
});
|
|
9796
|
+
socket.on("data", (chunk) => {
|
|
9797
|
+
banner = banner ? Buffer.concat([banner, chunk]) : chunk;
|
|
9798
|
+
const text = banner.toString("utf8");
|
|
9799
|
+
const nl = text.indexOf("\n");
|
|
9800
|
+
if (nl !== -1) {
|
|
9801
|
+
done({ kind: "banner", line: text.slice(0, nl) });
|
|
9802
|
+
return;
|
|
9803
|
+
}
|
|
9804
|
+
if (banner.length > MAX_BANNER_BYTES) {
|
|
9805
|
+
done({ kind: "banner", line: text.slice(0, MAX_BANNER_BYTES) });
|
|
9806
|
+
}
|
|
9807
|
+
});
|
|
9808
|
+
socket.on("error", (err) => {
|
|
9809
|
+
done(
|
|
9810
|
+
err.code === "ECONNREFUSED" ? { kind: "refused", message: `connection refused on ${endpoint.host}:${endpoint.port}` } : { kind: "error", message: err.message }
|
|
9811
|
+
);
|
|
9812
|
+
});
|
|
9813
|
+
socket.on("close", () => {
|
|
9814
|
+
done(
|
|
9815
|
+
connected ? { kind: "connected-no-banner" } : { kind: "refused", message: `closed without connecting on ${endpoint.host}:${endpoint.port}` }
|
|
9816
|
+
);
|
|
9817
|
+
});
|
|
9818
|
+
});
|
|
9819
|
+
}
|
|
9820
|
+
function computerUseSignatureView(mcpJson) {
|
|
9821
|
+
if (!mcpJson) return mcpJson;
|
|
9822
|
+
const servers = mcpJson.mcpServers;
|
|
9823
|
+
if (!servers || !Object.prototype.hasOwnProperty.call(servers, COMPUTER_USE_MCP_KEY)) {
|
|
9824
|
+
return mcpJson;
|
|
9825
|
+
}
|
|
9826
|
+
const entry = servers[COMPUTER_USE_MCP_KEY];
|
|
9827
|
+
const args = entry?.args;
|
|
9828
|
+
if (!Array.isArray(args) || args.length === 0) return mcpJson;
|
|
9829
|
+
const sep2 = args.indexOf("--");
|
|
9830
|
+
const head = sep2 > 0 ? args.slice(0, sep2) : [args[0]];
|
|
9831
|
+
return {
|
|
9832
|
+
...mcpJson,
|
|
9833
|
+
mcpServers: { ...servers, [COMPUTER_USE_MCP_KEY]: { ...entry, args: head } }
|
|
9834
|
+
};
|
|
9835
|
+
}
|
|
9836
|
+
function computerUseReaperView(mcpJson, verdict) {
|
|
9837
|
+
if (!mcpJson) return mcpJson;
|
|
9838
|
+
const servers = mcpJson.mcpServers;
|
|
9839
|
+
if (!servers || !Object.prototype.hasOwnProperty.call(servers, COMPUTER_USE_MCP_KEY)) {
|
|
9840
|
+
return mcpJson;
|
|
9841
|
+
}
|
|
9842
|
+
if (!isTunnelUsable(verdict)) {
|
|
9843
|
+
const { [COMPUTER_USE_MCP_KEY]: _omitted, ...rest } = servers;
|
|
9844
|
+
return { ...mcpJson, mcpServers: rest };
|
|
9845
|
+
}
|
|
9846
|
+
return computerUseSignatureView(mcpJson);
|
|
9847
|
+
}
|
|
9848
|
+
var PROBE_MIN_INTERVAL_MS = 6e4;
|
|
9849
|
+
var TunnelWatcher = class {
|
|
9850
|
+
entries = /* @__PURE__ */ new Map();
|
|
9851
|
+
now;
|
|
9852
|
+
probeFn;
|
|
9853
|
+
minIntervalMs;
|
|
9854
|
+
constructor(deps = {}) {
|
|
9855
|
+
this.now = deps.now ?? Date.now;
|
|
9856
|
+
this.probeFn = deps.probe ?? ((e) => probeTunnel(e));
|
|
9857
|
+
this.minIntervalMs = deps.minIntervalMs ?? PROBE_MIN_INTERVAL_MS;
|
|
9858
|
+
}
|
|
9859
|
+
verdictFor(codeName) {
|
|
9860
|
+
return this.entries.get(codeName)?.verdict ?? null;
|
|
9861
|
+
}
|
|
9862
|
+
/** Drop an agent's state (deprovision / rename). */
|
|
9863
|
+
forget(codeName) {
|
|
9864
|
+
this.entries.delete(codeName);
|
|
9865
|
+
}
|
|
9866
|
+
/**
|
|
9867
|
+
* Read the current verdict and, if it is stale, start a refresh.
|
|
9868
|
+
*
|
|
9869
|
+
* `isolated` short-circuits the probe entirely. A Docker-isolated agent's
|
|
9870
|
+
* `127.0.0.1` is the CONTAINER's loopback — the manager provisions either a
|
|
9871
|
+
* per-agent `--internal` network (egress mode) or the default bridge, and
|
|
9872
|
+
* neither shares the host's loopback where the Mac's reverse tunnel lands. So
|
|
9873
|
+
* the in-container ssh can never reach the tunnel, and a probe run from the
|
|
9874
|
+
* host would answer for an endpoint the agent cannot use: it would say
|
|
9875
|
+
* "reachable", expose the key, and the reaper would respawn into guaranteed
|
|
9876
|
+
* failure every poll — precisely the thrash AC#5 forbids. The verdict is
|
|
9877
|
+
* derived from that topology rather than measured, because it is a property of
|
|
9878
|
+
* how the container is created, not of the Mac.
|
|
9879
|
+
*/
|
|
9880
|
+
observe(codeName, args, isolated, hooks = {}) {
|
|
9881
|
+
const at = this.now();
|
|
9882
|
+
if (isolated) {
|
|
9883
|
+
return this.record(
|
|
9884
|
+
codeName,
|
|
9885
|
+
{
|
|
9886
|
+
state: "container-vantage",
|
|
9887
|
+
detail: "agent is Docker-isolated; the container\u2019s loopback is not the host\u2019s, so the Mac\u2019s reverse tunnel is unreachable from inside it \u2014 computer-use cannot work for this agent",
|
|
9888
|
+
at
|
|
9889
|
+
},
|
|
9890
|
+
hooks
|
|
9891
|
+
);
|
|
9892
|
+
}
|
|
9893
|
+
const endpoint = parseTunnelEndpoint(args);
|
|
9894
|
+
if (!endpoint) {
|
|
9895
|
+
return this.verdictFor(codeName);
|
|
9896
|
+
}
|
|
9897
|
+
const cur = this.entries.get(codeName);
|
|
9898
|
+
const fresh = cur?.verdict != null && at - cur.verdict.at < this.minIntervalMs;
|
|
9899
|
+
if (!cur?.probing && !fresh) {
|
|
9900
|
+
this.startProbe(codeName, endpoint, hooks);
|
|
9901
|
+
}
|
|
9902
|
+
return cur?.verdict ?? null;
|
|
9903
|
+
}
|
|
9904
|
+
startProbe(codeName, endpoint, hooks) {
|
|
9905
|
+
const prev = this.entries.get(codeName);
|
|
9906
|
+
this.entries.set(codeName, {
|
|
9907
|
+
verdict: prev?.verdict ?? null,
|
|
9908
|
+
probing: true,
|
|
9909
|
+
loggedState: prev?.loggedState ?? null
|
|
9910
|
+
});
|
|
9911
|
+
void this.probeFn(endpoint).then((outcome) => {
|
|
9912
|
+
this.record(codeName, classifyTunnelProbe(outcome, this.now()), hooks);
|
|
9913
|
+
}).catch((err) => {
|
|
9914
|
+
this.record(
|
|
9915
|
+
codeName,
|
|
9916
|
+
{ state: "no-tunnel", detail: `probe threw: ${err.message}`, at: this.now() },
|
|
9917
|
+
hooks
|
|
9918
|
+
);
|
|
9919
|
+
}).finally(() => {
|
|
9920
|
+
const e = this.entries.get(codeName);
|
|
9921
|
+
if (e) e.probing = false;
|
|
9922
|
+
});
|
|
9923
|
+
}
|
|
9924
|
+
record(codeName, verdict, hooks) {
|
|
9925
|
+
const prev = this.entries.get(codeName);
|
|
9926
|
+
const hadVerdict = prev?.verdict != null;
|
|
9927
|
+
const wasUsable = hadVerdict && prev.verdict.state === "reachable";
|
|
9928
|
+
this.entries.set(codeName, {
|
|
9929
|
+
verdict,
|
|
9930
|
+
probing: prev?.probing ?? false,
|
|
9931
|
+
loggedState: prev?.loggedState ?? null
|
|
9932
|
+
});
|
|
9933
|
+
if (prev?.loggedState !== verdict.state) {
|
|
9934
|
+
this.entries.get(codeName).loggedState = verdict.state;
|
|
9935
|
+
hooks.log?.(`[computer-use-tunnel] '${codeName}': ${verdict.state} \u2014 ${verdict.detail}`);
|
|
9936
|
+
}
|
|
9937
|
+
if (hadVerdict && !wasUsable && verdict.state === "reachable") {
|
|
9938
|
+
try {
|
|
9939
|
+
hooks.onRecovered?.(codeName);
|
|
9940
|
+
} catch (err) {
|
|
9941
|
+
hooks.log?.(
|
|
9942
|
+
`[computer-use-tunnel] '${codeName}': onRecovered threw (suppressed; verdict stands): ${err.message}`
|
|
9943
|
+
);
|
|
9944
|
+
}
|
|
9945
|
+
}
|
|
9946
|
+
return verdict;
|
|
9947
|
+
}
|
|
9948
|
+
};
|
|
9949
|
+
|
|
9709
9950
|
// src/lib/session-tool-bind-probe-host.ts
|
|
9710
9951
|
import { execFileSync as syncExecFile } from "child_process";
|
|
9711
9952
|
import { readFileSync as readFileSync8 } from "fs";
|
|
@@ -9756,7 +9997,15 @@ async function gatherSessionToolBindProbe(agent, integrations, projectDir, opts)
|
|
|
9756
9997
|
findMissingMcpServers({
|
|
9757
9998
|
rows: parsePsRows(psOutput),
|
|
9758
9999
|
codeName: agent.code_name,
|
|
9759
|
-
|
|
10000
|
+
// ENG-10134: correct the computer-use entry's argv signature before
|
|
10001
|
+
// the same matcher the presence reaper uses looks at it. Left raw, its
|
|
10002
|
+
// signature resolves to the Mac's SSH USERNAME (ENG-10258), so a dead
|
|
10003
|
+
// child matches any sibling with a /home/<user>/ path and this probe
|
|
10004
|
+
// reports `bound` to the control plane for a server that is not
|
|
10005
|
+
// running. Deliberately the signature view and NOT the reaper's gated
|
|
10006
|
+
// one: while the tunnel is down the server genuinely IS unbound, and
|
|
10007
|
+
// reporting that is the accurate answer here.
|
|
10008
|
+
mcpJson: computerUseSignatureView(mcpJson),
|
|
9760
10009
|
inContainer: isolated
|
|
9761
10010
|
})
|
|
9762
10011
|
);
|
|
@@ -10997,6 +11246,7 @@ function withEvidence(outcome, evidence) {
|
|
|
10997
11246
|
|
|
10998
11247
|
export {
|
|
10999
11248
|
safeWriteJsonAtomic,
|
|
11249
|
+
COMPUTER_USE_MCP_KEY,
|
|
11000
11250
|
extractCommandNotFound,
|
|
11001
11251
|
describeChannelWriteFailure,
|
|
11002
11252
|
migrateAgentDirToIdKeyed,
|
|
@@ -11062,6 +11312,8 @@ export {
|
|
|
11062
11312
|
deriveMcpServerKey,
|
|
11063
11313
|
buildProbeEnv,
|
|
11064
11314
|
buildConnectivityProbeDeps,
|
|
11315
|
+
computerUseReaperView,
|
|
11316
|
+
TunnelWatcher,
|
|
11065
11317
|
gatherSessionToolBindProbe,
|
|
11066
11318
|
provision,
|
|
11067
11319
|
describeStartupCompletion,
|
|
@@ -11086,4 +11338,4 @@ export {
|
|
|
11086
11338
|
managerInstallSystemUnitCommand,
|
|
11087
11339
|
managerUninstallSystemUnitCommand
|
|
11088
11340
|
};
|
|
11089
|
-
//# sourceMappingURL=chunk-
|
|
11341
|
+
//# sourceMappingURL=chunk-K6ODYXGF.js.map
|