@integrity-labs/agt-cli 0.28.860 → 0.28.861

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.
Files changed (25) hide show
  1. package/dist/bin/agt.js +5 -5
  2. package/dist/{chunk-MRDINJFN.js → chunk-G23CONQJ.js} +2 -2
  3. package/dist/{chunk-HKJLINLX.js → chunk-IQROQ3XJ.js} +382 -130
  4. package/dist/chunk-IQROQ3XJ.js.map +1 -0
  5. package/dist/{chunk-7F7DI322.js → chunk-USHD2L3F.js} +47 -10
  6. package/dist/chunk-USHD2L3F.js.map +1 -0
  7. package/dist/{claude-pair-runtime-YU6U3NCG.js → claude-pair-runtime-6FJOWTEX.js} +2 -2
  8. package/dist/lib/manager-worker.js +35 -16
  9. package/dist/lib/manager-worker.js.map +1 -1
  10. package/dist/mcp/direct-chat-channel.js +46 -9
  11. package/dist/mcp/index.js +46 -9
  12. package/dist/mcp/origami.js +46 -9
  13. package/dist/mcp/slack-channel.js +46 -9
  14. package/dist/mcp/telegram-channel.js +46 -9
  15. package/dist/{persistent-session-2HFQVRWH.js → persistent-session-6DET4JLF.js} +3 -3
  16. package/dist/{responsiveness-probe-2RCDUK7B.js → responsiveness-probe-ZNKOWUIE.js} +3 -3
  17. package/dist/{session-auth-dead-UNB2JGAH.js → session-auth-dead-XU7VC6HM.js} +2 -2
  18. package/package.json +1 -1
  19. package/dist/chunk-7F7DI322.js.map +0 -1
  20. package/dist/chunk-HKJLINLX.js.map +0 -1
  21. /package/dist/{chunk-MRDINJFN.js.map → chunk-G23CONQJ.js.map} +0 -0
  22. /package/dist/{claude-pair-runtime-YU6U3NCG.js.map → claude-pair-runtime-6FJOWTEX.js.map} +0 -0
  23. /package/dist/{persistent-session-2HFQVRWH.js.map → persistent-session-6DET4JLF.js.map} +0 -0
  24. /package/dist/{responsiveness-probe-2RCDUK7B.js.map → responsiveness-probe-ZNKOWUIE.js.map} +0 -0
  25. /package/dist/{session-auth-dead-UNB2JGAH.js.map → session-auth-dead-XU7VC6HM.js.map} +0 -0
package/dist/bin/agt.js CHANGED
@@ -40,12 +40,12 @@ import {
40
40
  success,
41
41
  table,
42
42
  warn
43
- } from "../chunk-HKJLINLX.js";
43
+ } from "../chunk-IQROQ3XJ.js";
44
44
  import {
45
45
  getProjectDir,
46
46
  isSessionResumeDisabled,
47
47
  readDirectChatSessionState
48
- } from "../chunk-MRDINJFN.js";
48
+ } from "../chunk-G23CONQJ.js";
49
49
  import {
50
50
  AnchorSessionClient,
51
51
  CHANNEL_REGISTRY,
@@ -79,7 +79,7 @@ import {
79
79
  serializeManifestForSlackCli,
80
80
  sessionFileExists,
81
81
  sessionTranscriptDir
82
- } from "../chunk-7F7DI322.js";
82
+ } from "../chunk-USHD2L3F.js";
83
83
  import "../chunk-XWVM4KPK.js";
84
84
 
85
85
  // src/bin/agt.ts
@@ -5467,7 +5467,7 @@ import { execFileSync, execSync } from "child_process";
5467
5467
  import { existsSync as existsSync11, realpathSync as realpathSync2 } from "fs";
5468
5468
  import chalk18 from "chalk";
5469
5469
  import ora16 from "ora";
5470
- var cliVersion = true ? "0.28.860" : "dev";
5470
+ var cliVersion = true ? "0.28.861" : "dev";
5471
5471
  async function fetchLatestVersion() {
5472
5472
  const host2 = getHost();
5473
5473
  if (!host2) return null;
@@ -6658,7 +6658,7 @@ function handleError(err) {
6658
6658
  }
6659
6659
 
6660
6660
  // src/bin/agt.ts
6661
- var cliVersion2 = true ? "0.28.860" : "dev";
6661
+ var cliVersion2 = true ? "0.28.861" : "dev";
6662
6662
  var program = new Command();
6663
6663
  program.name("agt").description("Augmented CLI \u2014 agent provisioning and management").version(cliVersion2).option("--json", "Emit machine-readable JSON output (suppress spinners and colors)").option("--skip-update-check", "Skip the automatic update check on startup");
6664
6664
  program.hook("preAction", async (thisCommand, actionCommand) => {
@@ -18,7 +18,7 @@ import {
18
18
  rotateDailySession,
19
19
  sessionFileExists,
20
20
  todayLocalIso
21
- } from "./chunk-7F7DI322.js";
21
+ } from "./chunk-USHD2L3F.js";
22
22
  import {
23
23
  reapOrphanChannelMcps
24
24
  } from "./chunk-XWVM4KPK.js";
@@ -5431,4 +5431,4 @@ export {
5431
5431
  stopAllSessionsAndWait,
5432
5432
  getProjectDir
5433
5433
  };
5434
- //# sourceMappingURL=chunk-MRDINJFN.js.map
5434
+ //# sourceMappingURL=chunk-G23CONQJ.js.map
@@ -16,7 +16,7 @@ import {
16
16
  parseEnvIntegrations,
17
17
  shellQuote,
18
18
  summarizeUnanswerablePane
19
- } from "./chunk-MRDINJFN.js";
19
+ } from "./chunk-G23CONQJ.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-7F7DI322.js";
79
+ } from "./chunk-USHD2L3F.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 join(home, ".xurl");
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 join2 } from "path";
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 join2(dirname2(projectsPath), MANAGED_SIDECAR_NAME);
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 join2(xdg, "framer", "projects.json");
916
+ return join3(xdg, "framer", "projects.json");
798
917
  const home = process.env["HOME"] ?? process.env["USERPROFILE"] ?? homedir2();
799
- return join2(home, ".config", "framer", "projects.json");
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.860" : "dev";
6731
+ var agtCliVersion = true ? "0.28.861" : "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
- mcpJson,
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-HKJLINLX.js.map
11341
+ //# sourceMappingURL=chunk-IQROQ3XJ.js.map