@staix/agent-hub 0.12.37 → 0.12.39

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 (72) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/README.md +12 -6
  3. package/docs/agent-notes/adapters.md +19 -8
  4. package/docs/agent-notes/benchmarks.md +1 -1
  5. package/docs/agent-notes/bus.md +3 -2
  6. package/docs/agent-notes/daemon.md +6 -3
  7. package/docs/agent-notes/tasks.md +4 -3
  8. package/docs/agent-notes/tests.md +3 -3
  9. package/docs/operations.md +45 -17
  10. package/docs/quickstart.md +19 -5
  11. package/docs/security.md +7 -3
  12. package/docs/smoke.md +22 -2
  13. package/docs/specs/2026-09-19-agent-hub-design.md +40 -9
  14. package/docs/verification/2026-10-19-agy-p1-t0.md +55 -0
  15. package/docs/verification/2026-10-19-bus-defer-reset.md +9 -0
  16. package/docs/verification/2026-10-19-kimi-agy-reviewer-isolation.md +220 -0
  17. package/docs/verification/2026-10-20-0.12.38.md +56 -0
  18. package/docs/verification/2026-10-20-0.12.39.md +55 -0
  19. package/docs/verification/2026-10-20-jcode-tui-0.98.1.md +39 -0
  20. package/docs/verified.json +22 -14
  21. package/package.json +1 -1
  22. package/plugins/agent-hub/.claude-plugin/plugin.json +1 -1
  23. package/plugins/agent-hub/server.js +1759 -437
  24. package/src/adapters/acp.ts +20 -2
  25. package/src/adapters/agy/definition.ts +61 -0
  26. package/src/adapters/agy/entry.ts +76 -0
  27. package/src/adapters/agy/hooks.ts +81 -0
  28. package/src/adapters/claude-channel.ts +1 -0
  29. package/src/adapters/codex-review-isolation.ts +30 -16
  30. package/src/adapters/definitions/builtin-metadata.ts +8 -8
  31. package/src/adapters/definitions/native-cli-entry-claude.ts +3 -0
  32. package/src/adapters/definitions/native-cli-entry-codex.ts +5 -1
  33. package/src/adapters/definitions/native-cli-entry-jcode.ts +82 -11
  34. package/src/adapters/definitions/native-cli-entry-kimi.ts +12 -4
  35. package/src/adapters/definitions/native-cli-entry-local.ts +3 -0
  36. package/src/adapters/definitions/native-cli-entry-pi.ts +7 -1
  37. package/src/adapters/definitions/native-cli-entry-qwen.ts +3 -0
  38. package/src/adapters/definitions/native-cli-entry-types.ts +1 -0
  39. package/src/adapters/definitions/native-cli-entry.ts +2 -0
  40. package/src/adapters/definitions/native-cli-preview-common.ts +1 -0
  41. package/src/adapters/definitions/native-cli-preview-jcode.ts +7 -7
  42. package/src/adapters/definitions/native-cli-retained.ts +15 -0
  43. package/src/adapters/definitions/native-daemon-policy.ts +1 -1
  44. package/src/adapters/definitions/qwen.ts +1 -1
  45. package/src/adapters/definitions/reviewer-variants.ts +4 -3
  46. package/src/adapters/jcode.ts +58 -1
  47. package/src/adapters/kimi-review.ts +161 -0
  48. package/src/adapters/registry-types.ts +29 -1
  49. package/src/adapters/registry.ts +16 -7
  50. package/src/adapters/reviewer-mcp.ts +85 -0
  51. package/src/adapters/reviewer-print.ts +83 -20
  52. package/src/cli/agy-mcp.ts +370 -0
  53. package/src/cli/console-state.ts +6 -0
  54. package/src/cli/console.ts +2 -0
  55. package/src/cli/facts-hook.ts +15 -2
  56. package/src/cli/init.ts +7 -0
  57. package/src/cli/launch.ts +3 -1
  58. package/src/cli/main.ts +5 -4
  59. package/src/cli/output.ts +5 -0
  60. package/src/cli/preview.ts +2 -2
  61. package/src/hub/bus.ts +17 -6
  62. package/src/hub/control-client.ts +7 -1
  63. package/src/hub/daemon.ts +182 -24
  64. package/src/hub/native-hook-launch.ts +37 -0
  65. package/src/hub/peer-catalog.ts +2 -3
  66. package/src/hub/peers.ts +2 -2
  67. package/src/hub/reviewer-pool.ts +12 -6
  68. package/src/hub/routing.ts +3 -0
  69. package/src/hub/start-mode.ts +1 -1
  70. package/src/hub/tasks.ts +7 -0
  71. package/src/ui/index.html +6 -1
  72. package/templates/AGENTS.block.md +1 -1
@@ -456,12 +456,12 @@ class BasePeer {
456
456
  this._state = next;
457
457
  this.onState?.(next);
458
458
  }
459
- touch() {
459
+ touch(ms = this.watchdogMs) {
460
460
  this.clearWatchdog();
461
461
  this.timer = setTimeout(() => {
462
462
  if (this._state === "busy")
463
463
  this.onWatchdog();
464
- }, this.watchdogMs);
464
+ }, ms);
465
465
  this.timer.unref?.();
466
466
  }
467
467
  onWatchdog() {
@@ -505,6 +505,10 @@ var init_reviewer_print = __esm(() => {
505
505
  hubNative = true;
506
506
  generation = 0;
507
507
  child;
508
+ childStop;
509
+ childCleanup;
510
+ childCurrent;
511
+ reviewMcpClose;
508
512
  activeDeliveryId;
509
513
  batch;
510
514
  preparation;
@@ -514,8 +518,23 @@ var init_reviewer_print = __esm(() => {
514
518
  }
515
519
  reviewing;
516
520
  reviewSeq = 0;
521
+ runningBinding;
522
+ reviewBinding(task) {
523
+ return this.reviewing === task && this.runningBinding?.task === task ? this.runningBinding.binding : undefined;
524
+ }
517
525
  reviewToken(task) {
518
- return this.reviewing === task ? this.reviewSeq : undefined;
526
+ if (this.reviewing !== task)
527
+ return;
528
+ try {
529
+ if (this.childCurrent?.() === false) {
530
+ this.abortReview("review policy changed");
531
+ return;
532
+ }
533
+ } catch {
534
+ this.abortReview("review policy changed");
535
+ return;
536
+ }
537
+ return this.reviewSeq;
519
538
  }
520
539
  constructor(id, opts) {
521
540
  super(id, opts.watchdogMs);
@@ -550,6 +569,7 @@ var init_reviewer_print = __esm(() => {
550
569
  }
551
570
  async stop(reason) {
552
571
  this.generation++;
572
+ this.reviewMcpClose?.();
553
573
  this.receipt = undefined;
554
574
  this.batch = undefined;
555
575
  this.preparation?.abort.abort();
@@ -566,6 +586,7 @@ var init_reviewer_print = __esm(() => {
566
586
  abortReview(reason) {
567
587
  const generation = ++this.generation, batch = this.batch, preparation = this.preparation;
568
588
  this.reviewing = undefined;
589
+ this.reviewMcpClose?.();
569
590
  this.receipt = undefined;
570
591
  this.clearWatchdog();
571
592
  preparation?.abort.abort();
@@ -694,23 +715,28 @@ var init_reviewer_print = __esm(() => {
694
715
  }
695
716
  }
696
717
  }
697
- prepare(sessionId) {
718
+ prepare(sessionId, input, task, mcpConfig) {
698
719
  const abort = new AbortController;
699
720
  const done = (async () => {
721
+ if (this.opts.backend.taskMcp && !this.opts.taskMcp)
722
+ throw new Error("task-bound reviewer MCP is unavailable");
700
723
  const launch = await this.opts.backend.prepare({
701
724
  peer: this.id,
702
725
  cwd: this.opts.cwd,
703
726
  sessionId,
704
- mcpConfig: this.opts.mcpConfig,
727
+ mcpConfig: this.opts.backend.taskMcp ? mcpConfig ?? '{"mcpServers":{}}' : this.opts.mcpConfig,
705
728
  systemPrompt: this.opts.systemPrompt,
706
729
  privatePaths: this.opts.privatePaths ?? [],
707
730
  command: this.opts.command,
708
- signal: abort.signal
731
+ signal: abort.signal,
732
+ ...input === undefined ? {} : { input },
733
+ ...task === undefined ? {} : { task }
709
734
  });
710
735
  if ("unavailable" in launch)
711
736
  throw new Error(launch.unavailable);
712
737
  const proof = launch.isolation;
713
- if (abort.signal.aborted || !proof || proof.backend !== this.opts.backend.id || proof.readOnly !== true || proof.strictMcp !== true || proof.ephemeral !== true || !proof.binary || !proof.version || !proof.profile || !proof.evidence.length || !launch.command.length) {
738
+ const policy = this.opts.backend.nativePolicy ? proof?.enforcement === "native-policy" && proof.freshSession === true && proof.hubToolsOnly === true : proof?.strictMcp === true && proof.ephemeral === true;
739
+ if (abort.signal.aborted || !proof || proof.backend !== this.opts.backend.id || proof.readOnly !== true || !policy || !proof.binary || !proof.version || !proof.profile || !proof.evidence.length || !launch.command.length) {
714
740
  await launch.cleanup?.();
715
741
  throw new Error("reviewer isolation is unavailable or preparation was cancelled");
716
742
  }
@@ -725,19 +751,58 @@ var init_reviewer_print = __esm(() => {
725
751
  return done;
726
752
  }
727
753
  async review(env, task, generation) {
728
- const launch = await this.prepare(randomUUID());
754
+ if (this.opts.admit?.(env) === false)
755
+ throw new Error("review admission changed");
756
+ let facts;
729
757
  try {
730
- if (generation !== this.generation || launch.current?.() === false)
731
- throw new Error("reviewer isolation changed before child spawn");
758
+ facts = this.opts.facts?.(env);
759
+ } catch (error) {
760
+ this.opts.log?.(`[${this.id}] review facts for task #${task} failed: ${error.message}`);
761
+ }
762
+ const input = `${frame(env)}${facts ? `
763
+
764
+ ${facts}` : ""}`;
765
+ let launch;
766
+ const revision = Number(env.refs?.reviewRevision);
767
+ const binding = Object.freeze({ head: env.refs?.commit, flowRevision: Number.isSafeInteger(revision) && revision > 0 ? revision : undefined });
768
+ const mcp = this.opts.backend.taskMcp ? this.opts.taskMcp?.(task, () => generation === this.generation && this.reviewToken(task) !== undefined && this.opts.bindingCurrent?.(task, binding) !== false) : undefined;
769
+ let mcpClosed = false;
770
+ const closeMcp = () => {
771
+ if (mcpClosed)
772
+ return;
773
+ mcpClosed = true;
774
+ mcp?.close();
775
+ if (this.reviewMcpClose === closeMcp)
776
+ this.reviewMcpClose = undefined;
777
+ };
778
+ this.reviewMcpClose = closeMcp;
779
+ let ownedChild, releasing;
780
+ let policyTimer;
781
+ const release = () => releasing ??= Promise.resolve().then(() => launch?.cleanup?.()).then(() => {});
782
+ try {
783
+ launch = await this.prepare(randomUUID(), input, task, mcp?.config);
784
+ if (generation !== this.generation || this.opts.admit?.(env) === false || launch.current?.() === false)
785
+ throw new Error("reviewer isolation or admission changed before child spawn");
732
786
  this.receipt = launch.isolation;
733
787
  const [cmd, ...prefix] = launch.command;
734
788
  let quota = false;
735
789
  return await new Promise((resolve, reject) => {
736
- const child = spawn(cmd, [...prefix, ...launch.args], { cwd: this.opts.cwd, detached: true, env: peerChildEnv(this.id, { ...process.env, ...launch.env }), stdio: ["pipe", "pipe", "ignore"] });
790
+ const child = spawn(cmd, [...prefix, ...launch.args], {
791
+ cwd: launch.cwd ?? this.opts.cwd,
792
+ detached: true,
793
+ env: peerChildEnv(this.id, launch.envMode === "replace" ? launch.env : { ...process.env, ...launch.env }),
794
+ stdio: ["pipe", "pipe", "ignore"]
795
+ });
737
796
  this.child = child;
797
+ ownedChild = child;
798
+ this.childStop = launch.stop;
799
+ this.childCleanup = release;
800
+ this.childCurrent = launch.watchCurrent ? launch.current : undefined;
738
801
  child.once("error", (error) => {
739
- if (this.child === child)
802
+ if (this.child === child) {
740
803
  this.child = undefined;
804
+ this.childStop = undefined;
805
+ }
741
806
  reject(error);
742
807
  });
743
808
  child.stdin.on("error", () => {});
@@ -747,17 +812,14 @@ var init_reviewer_print = __esm(() => {
747
812
  child.stdin.destroy();
748
813
  return;
749
814
  }
815
+ this.runningBinding = { task, binding };
750
816
  this.reviewing = task;
751
817
  this.reviewSeq++;
752
- let facts;
753
- try {
754
- facts = this.opts.facts?.(env);
755
- } catch (error) {
756
- this.opts.log?.(`[${this.id}] review facts for task #${task} failed: ${error.message}`);
757
- }
758
- child.stdin.end(`${frame(env)}${facts ? `
759
-
760
- ${facts}` : ""}`);
818
+ if (this.childCurrent)
819
+ policyTimer = setInterval(() => {
820
+ this.reviewToken(task);
821
+ }, 100);
822
+ child.stdin.end(input);
761
823
  });
762
824
  let buffer = "", outputBytes = 0;
763
825
  child.stdout.on("data", (chunk) => {
@@ -793,18 +855,29 @@ ${facts}` : ""}`);
793
855
  this.touch();
794
856
  });
795
857
  child.once("exit", (code) => {
796
- if (this.child === child)
858
+ if (this.child === child) {
797
859
  this.reviewing = undefined;
798
- stopOwnedProcess(child, { group: true }).then(() => {
799
- if (this.child === child)
860
+ this.runningBinding = undefined;
861
+ }
862
+ (launch.stop ? launch.stop(child) : stopOwnedProcess(child, { group: true })).then(() => {
863
+ if (this.child === child) {
800
864
  this.child = undefined;
865
+ this.childStop = undefined;
866
+ }
801
867
  resolve({ code, quota });
802
868
  }, (error) => reject(new GroupCleanupError(error.message)));
803
869
  });
804
870
  });
805
871
  } finally {
872
+ closeMcp();
873
+ if (policyTimer)
874
+ clearInterval(policyTimer);
806
875
  try {
807
- await launch.cleanup?.();
876
+ if (!ownedChild || this.child !== ownedChild) {
877
+ await release();
878
+ if (this.childCleanup === release)
879
+ this.childCleanup = undefined;
880
+ }
808
881
  } catch {
809
882
  this.opts.log?.(`[${this.id}] reviewer isolation cleanup incomplete`);
810
883
  }
@@ -818,12 +891,18 @@ ${facts}` : ""}`);
818
891
  }
819
892
  async kill() {
820
893
  this.reviewing = undefined;
894
+ this.runningBinding = undefined;
821
895
  const child = this.child;
822
896
  if (!child)
823
897
  return;
824
- await stopOwnedProcess(child, { group: true });
825
- if (this.child === child)
898
+ await (this.childStop ? this.childStop(child) : stopOwnedProcess(child, { group: true }));
899
+ if (this.child === child) {
900
+ await this.childCleanup?.();
826
901
  this.child = undefined;
902
+ this.childStop = undefined;
903
+ this.childCleanup = undefined;
904
+ this.childCurrent = undefined;
905
+ }
827
906
  }
828
907
  };
829
908
  });
@@ -1047,19 +1126,27 @@ function configFiles(cwd, env) {
1047
1126
  }
1048
1127
  function hostCredentialRoots(env, cwd) {
1049
1128
  const home = resolve3(cwd, env.HOME || homedir());
1050
- const homes = [...new Set([home, credentialCanonicalPath(home)])];
1051
- const roots = homes.flatMap((base) => HOST_CREDENTIAL_PATHS.map((entry) => join3(base, entry)));
1052
- const configured = HOST_CREDENTIAL_ENV.flatMap((key) => env[key] ? [env[key]] : []);
1129
+ const homes = [...new Set([home, canonicalLabelled(home, "HOME")])];
1130
+ const roots = homes.flatMap((base) => HOST_CREDENTIAL_PATHS.map((entry) => ({ path: join3(base, entry), label: `~/${entry}` })));
1131
+ const configured = HOST_CREDENTIAL_ENV.flatMap((key) => env[key] ? [{ path: env[key], label: `$${key}` }] : []);
1053
1132
  if (env.KUBECONFIG)
1054
- configured.push(...env.KUBECONFIG.split(delimiter).filter(Boolean));
1133
+ configured.push(...env.KUBECONFIG.split(delimiter).filter(Boolean).map((path) => ({ path, label: "$KUBECONFIG" })));
1055
1134
  if (configured.length > 64)
1056
1135
  throw new Error("credential root bound exceeded");
1057
- for (const entry of configured) {
1136
+ for (const { path: entry, label } of configured) {
1058
1137
  if (Buffer.byteLength(entry) > 4096 || /\p{Cc}/u.test(entry))
1059
- throw new Error("invalid credential root");
1060
- roots.push(entry.startsWith("~/") ? resolve3(home, entry.slice(2)) : resolve3(cwd, entry));
1138
+ throw new CredentialRootError(label, "unsupported");
1139
+ roots.push({ path: entry.startsWith("~/") ? resolve3(home, entry.slice(2)) : resolve3(cwd, entry), label });
1140
+ }
1141
+ const seen = new Set;
1142
+ return roots.filter((root) => !seen.has(root.path) && seen.add(root.path));
1143
+ }
1144
+ function canonicalLabelled(lexical, label) {
1145
+ try {
1146
+ return credentialCanonicalPath(lexical);
1147
+ } catch (error) {
1148
+ throw new CredentialRootError(label, error.code === "ENOENT" ? "dangling" : "unreadable");
1061
1149
  }
1062
- return [...new Set(roots)];
1063
1150
  }
1064
1151
  function credentialCanonicalPath(lexical) {
1065
1152
  let ancestor = lexical;
@@ -1076,15 +1163,13 @@ function credentialCanonicalPath(lexical) {
1076
1163
  return join3(realPath(ancestor), relative(ancestor, lexical));
1077
1164
  }
1078
1165
  function denyCredentialRoots(filesystem, roots, cwd) {
1079
- for (const lexical of roots) {
1166
+ for (const { path: lexical, label } of roots) {
1080
1167
  if (/[?*\[\]{}]/.test(lexical))
1081
- throw new Error("unverified credential root syntax");
1082
- const targets = new Set([lexical, credentialCanonicalPath(lexical)]);
1168
+ throw new CredentialRootError(label, "unsupported");
1169
+ const targets = new Set([lexical, canonicalLabelled(lexical, label)]);
1083
1170
  for (const target of targets) {
1084
- if (/[?*\[\]{}]/.test(target))
1085
- throw new Error("unverified credential root syntax");
1086
- if (inside(cwd, target))
1087
- throw new Error("credential root overlaps checkout");
1171
+ if (/[?*\[\]{}]/.test(target) || inside(cwd, target))
1172
+ throw new CredentialRootError(label, "unsupported");
1088
1173
  filesystem[target] = "deny";
1089
1174
  filesystem[`${target.replace(/\/$/, "")}/**`] = "deny";
1090
1175
  }
@@ -1337,12 +1422,16 @@ AHUB_CODEX_HOST_ALIAS_DENIED` || existsSync2(writeFile))
1337
1422
  evidence: ["model-free-mcp-inventory-disabled-ambient", "hub-tools-exact-allowlist", "plugins-and-hooks-disabled", "apps-disabled-effective-feature-readback", "web-search-disabled", "project-untrusted", "native-public-read-private-deny-write-deny", "native-glob-and-symlink-private-deny", "portable-core-name-and-state-deny", "standard-host-credential-roots-denied", "binary-config-stable", "binary-hash-once-metadata-fence", "session-ephemeral"]
1338
1423
  }
1339
1424
  };
1340
- } catch {
1425
+ } catch (error) {
1341
1426
  if (root) {
1342
1427
  try {
1343
1428
  rmSync(root, { recursive: true, force: true });
1344
1429
  } catch {}
1345
1430
  }
1431
+ if (error instanceof CredentialRootError) {
1432
+ const why = { dangling: "a dangling link", unreadable: "an unreadable path", unsupported: "unsupported syntax or overlapping the checkout" }[error.kind];
1433
+ return { unavailable: `Codex reviewer isolation receipt unavailable (credential root ${error.label} is ${why}; remove or repair it)` };
1434
+ }
1346
1435
  return { unavailable: "Codex reviewer isolation receipt unavailable (version, MCP inventory, sandbox, configuration or process proof)" };
1347
1436
  }
1348
1437
  }
@@ -1353,7 +1442,7 @@ var VERSION = "0.146.0", TOOLS, CAP, MAX_SERVERS = 64, digest = (value) => creat
1353
1442
  if (!value || typeof value !== "object" || Array.isArray(value))
1354
1443
  throw new Error("invalid metadata");
1355
1444
  return value;
1356
- }, HOST_CREDENTIAL_PATHS, HOST_CREDENTIAL_ENV, codexReviewerBackend;
1445
+ }, HOST_CREDENTIAL_PATHS, HOST_CREDENTIAL_ENV, CredentialRootError, codexReviewerBackend;
1357
1446
  var init_codex_review_isolation = __esm(() => {
1358
1447
  init_platform();
1359
1448
  init_child_process();
@@ -1362,6 +1451,15 @@ var init_codex_review_isolation = __esm(() => {
1362
1451
  CAP = 128 * 1024;
1363
1452
  HOST_CREDENTIAL_PATHS = [".config", ".ssh", ".aws", ".azure", ".kube", ".docker", ".gnupg", ".codex", ".claude", ".kimi", ".kimi-code", ".gemini", ".git-credentials", ".vault-token", "Library/Keychains", ".local/share/keyrings", ".password-store"];
1364
1453
  HOST_CREDENTIAL_ENV = ["XDG_CONFIG_HOME", "CODEX_HOME", "CLAUDE_CONFIG_DIR", "KIMI_CODE_HOME", "KIMI_HOME", "GEMINI_CLI_HOME", "GNUPGHOME", "AWS_SHARED_CREDENTIALS_FILE", "AWS_CONFIG_FILE", "AZURE_CONFIG_DIR", "DOCKER_CONFIG", "GOOGLE_APPLICATION_CREDENTIALS"];
1454
+ CredentialRootError = class CredentialRootError extends Error {
1455
+ label;
1456
+ kind;
1457
+ constructor(label, kind) {
1458
+ super("credential root refused");
1459
+ this.label = label;
1460
+ this.kind = kind;
1461
+ }
1462
+ };
1365
1463
  codexReviewerBackend = {
1366
1464
  id: "codex",
1367
1465
  prepare,
@@ -1372,10 +1470,271 @@ var init_codex_review_isolation = __esm(() => {
1372
1470
  };
1373
1471
  });
1374
1472
 
1473
+ // src/adapters/kimi-review.ts
1474
+ import { spawn as spawn3 } from "child_process";
1475
+ import { createHash as createHash3 } from "crypto";
1476
+ import { existsSync as existsSync3, lstatSync as lstatSync3, mkdirSync as mkdirSync2, mkdtempSync as mkdtempSync2, readFileSync as readFileSync3, rmSync as rmSync2, symlinkSync as symlinkSync2, writeFileSync as writeFileSync2 } from "fs";
1477
+ import { homedir as homedir2, tmpdir as tmpdir2 } from "os";
1478
+ import { basename as basename3, dirname as dirname4, join as join4, resolve as resolve4 } from "path";
1479
+ function assertCleanCwdAncestors(...candidates) {
1480
+ const seen = new Set;
1481
+ for (const candidate of candidates) {
1482
+ let ancestor = resolve4(candidate);
1483
+ for (;; ) {
1484
+ if (!seen.has(ancestor)) {
1485
+ seen.add(ancestor);
1486
+ for (const marker of [".git", ".kimi-code", "AGENTS.md"]) {
1487
+ if (lstatSync3(join4(ancestor, marker), { throwIfNoEntry: false }) !== undefined) {
1488
+ throw new Error("private cwd ancestor contains ambient configuration");
1489
+ }
1490
+ }
1491
+ }
1492
+ const parent = dirname4(ancestor);
1493
+ if (parent === ancestor)
1494
+ break;
1495
+ ancestor = parent;
1496
+ }
1497
+ }
1498
+ }
1499
+ function managedConfig(raw) {
1500
+ const config = Bun.TOML.parse(raw);
1501
+ if (!object2(config.providers) || !object2(config.models) || typeof config.default_model !== "string")
1502
+ throw new Error("managed Kimi configuration required");
1503
+ const provider = config.providers[MANAGED], model = config.models[config.default_model];
1504
+ if (!object2(provider) || provider.type !== "kimi" || provider.api_key && provider.api_key !== "" || !object2(provider.oauth) || provider.oauth.storage !== "file" || !object2(model) || model.provider !== MANAGED)
1505
+ throw new Error("managed file-backed Kimi OAuth required");
1506
+ const oauth = provider.oauth;
1507
+ const key = oauth.key;
1508
+ const lock = !key || key === "kimi-code" || key === "oauth/kimi-code" ? "kimi-code" : typeof key === "string" && key.startsWith("oauth/") ? key.slice(6) : key;
1509
+ if (typeof lock !== "string" || !lock || basename3(lock) !== lock || lock.startsWith("."))
1510
+ throw new Error("unsupported OAuth slot");
1511
+ if (typeof provider.base_url !== "string")
1512
+ throw new Error("managed Kimi endpoint required");
1513
+ const endpoint = new URL(provider.base_url);
1514
+ if (endpoint.protocol !== "https:" || !["api.kimi.ai", "api.kimi.com"].includes(endpoint.hostname) || endpoint.username || endpoint.password)
1515
+ throw new Error("unsupported managed Kimi endpoint");
1516
+ if (oauth.oauth_host !== undefined) {
1517
+ const auth = new URL(String(oauth.oauth_host));
1518
+ if (auth.protocol !== "https:" || !["auth.kimi.ai", "auth.kimi.com"].includes(auth.hostname) || auth.username || auth.password)
1519
+ throw new Error("unsupported OAuth endpoint");
1520
+ }
1521
+ const lines = [
1522
+ "telemetry = false",
1523
+ "hooks = []",
1524
+ `default_model = ${scalar(config.default_model)}`,
1525
+ ...config.thinking === undefined ? [] : [`thinking = ${scalar(config.thinking)}`],
1526
+ `[providers.${scalar(MANAGED)}]`,
1527
+ 'type = "kimi"',
1528
+ `base_url = ${scalar(provider.base_url)}`,
1529
+ `[providers.${scalar(MANAGED)}.oauth]`
1530
+ ];
1531
+ for (const name of ["storage", "key", "oauth_host"])
1532
+ if (oauth[name] !== undefined)
1533
+ lines.push(`${name} = ${scalar(oauth[name])}`);
1534
+ lines.push(`[models.${scalar(config.default_model)}]`);
1535
+ const modelFields = ["provider", "model", "protocol", "max_context_size", "max_input_size", "capabilities", "display_name", "support_efforts", "default_effort", "beta_api", "adaptive_thinking", "overrides"];
1536
+ if (Object.keys(model).some((name) => !modelFields.includes(name)))
1537
+ throw new Error("unsupported managed model setting");
1538
+ if (model.overrides !== undefined && (!object2(model.overrides) || Object.keys(model.overrides).some((name) => !modelFields.includes(name) || ["provider", "overrides"].includes(name))))
1539
+ throw new Error("unsupported managed model override");
1540
+ for (const name of modelFields)
1541
+ if (model[name] !== undefined)
1542
+ lines.push(`${name} = ${scalar(model[name])}`);
1543
+ return { config: lines.join(`
1544
+ `) + `
1545
+ `, lock };
1546
+ }
1547
+ function mcpConfig(context) {
1548
+ const config = JSON.parse(context.mcpConfig);
1549
+ if (!object2(config) || !object2(config.mcpServers))
1550
+ throw new Error("review MCP unavailable");
1551
+ if (context.task === undefined && Object.keys(config.mcpServers).length === 0)
1552
+ return context.mcpConfig;
1553
+ if (Object.keys(config.mcpServers).join() !== "agent-hub")
1554
+ throw new Error("ambient MCP refused");
1555
+ const server = config.mcpServers["agent-hub"];
1556
+ if (!object2(server) || Object.keys(server).some((k) => !["transport", "url", "headers"].includes(k)) || server.transport !== "http" || typeof server.url !== "string" || !object2(server.headers) || Object.keys(server.headers).join() !== "Authorization" || typeof server.headers.Authorization !== "string" || !server.headers.Authorization.startsWith("Bearer "))
1557
+ throw new Error("task-bound HTTP MCP required");
1558
+ const url = new URL(server.url);
1559
+ if (url.protocol !== "http:" || url.hostname !== "127.0.0.1" || !url.port || url.pathname !== "/mcp" || url.username || url.password || url.search || url.hash)
1560
+ throw new Error("local review MCP required");
1561
+ return context.mcpConfig;
1562
+ }
1563
+ async function version(command, cwd, env, signal) {
1564
+ return await new Promise((accept, reject) => {
1565
+ const child = spawn3(command[0], [...command.slice(1), "--version"], { cwd, env, detached: true, stdio: ["ignore", "pipe", "ignore"] });
1566
+ let text = "", done = false;
1567
+ const finish = async (code) => {
1568
+ if (done)
1569
+ return;
1570
+ done = true;
1571
+ clearTimeout(timer);
1572
+ signal.removeEventListener("abort", abort);
1573
+ try {
1574
+ await stopOwnedProcess(child, { group: true });
1575
+ } catch {
1576
+ reject(new Error("version probe cleanup failed"));
1577
+ return;
1578
+ }
1579
+ if (code !== 0 || signal.aborted)
1580
+ reject(new Error("version probe failed"));
1581
+ else
1582
+ accept(text.trim());
1583
+ };
1584
+ const abort = () => {
1585
+ finish(null);
1586
+ };
1587
+ const timer = setTimeout(abort, 5000);
1588
+ signal.addEventListener("abort", abort, { once: true });
1589
+ child.once("spawn", () => trackGroup(child));
1590
+ child.stdout.on("data", (bytes) => {
1591
+ text += bytes.toString();
1592
+ if (text.length > 4096)
1593
+ abort();
1594
+ });
1595
+ child.once("error", () => {
1596
+ finish(null);
1597
+ });
1598
+ child.once("close", (code) => {
1599
+ finish(code);
1600
+ });
1601
+ if (signal.aborted)
1602
+ abort();
1603
+ });
1604
+ }
1605
+ async function stopKimiReview(child, locks, graceMs = 1e4) {
1606
+ const locked = () => locks.some((file) => existsSync3(file));
1607
+ if (child.exitCode === null && child.signalCode === null && locked()) {
1608
+ child.kill("SIGTERM");
1609
+ const end = Date.now() + graceMs;
1610
+ while (child.exitCode === null && child.signalCode === null && locked() && Date.now() < end)
1611
+ await Bun.sleep(50);
1612
+ if (child.exitCode === null && child.signalCode === null && locked())
1613
+ throw new Error("Kimi OAuth refresh still locked; graceful stop incomplete");
1614
+ }
1615
+ await stopOwnedProcess(child, { group: true });
1616
+ }
1617
+ var VERSION2 = "2.2.0", MANAGED = "managed:kimi-code", object2 = (v) => !!v && typeof v === "object" && !Array.isArray(v), hash = (text) => createHash3("sha256").update(text).digest("hex"), read = (file, limit = 262144) => {
1618
+ const stat = lstatSync3(file);
1619
+ if (!stat.isFile() || stat.size > limit)
1620
+ throw new Error("unsupported reviewer file");
1621
+ return readFileSync3(file, "utf8");
1622
+ }, scalar = (value, depth = 0) => {
1623
+ if (depth > 4)
1624
+ throw new Error("managed model setting too deeply nested");
1625
+ if (typeof value === "string" || typeof value === "boolean" || typeof value === "number" && Number.isFinite(value))
1626
+ return JSON.stringify(value);
1627
+ if (Array.isArray(value) && value.every((v) => typeof v === "string"))
1628
+ return JSON.stringify(value);
1629
+ if (object2(value) && Object.keys(value).length <= 32)
1630
+ return `{ ${Object.entries(value).map(([key, item]) => `${JSON.stringify(key)} = ${scalar(item, depth + 1)}`).join(", ")} }`;
1631
+ throw new Error("unsupported managed model setting");
1632
+ }, kimiReviewerBackend;
1633
+ var init_kimi_review = __esm(() => {
1634
+ init_child_process();
1635
+ init_project();
1636
+ kimiReviewerBackend = {
1637
+ id: "kimi",
1638
+ taskMcp: true,
1639
+ nativePolicy: true,
1640
+ async prepare(context) {
1641
+ let own;
1642
+ try {
1643
+ if (context.signal.aborted)
1644
+ throw new Error("cancelled");
1645
+ const sourcePath = process.env.KIMI_CODE_HOME ?? join4(process.env.HOME ?? homedir2(), ".kimi-code"), source = realPath(sourcePath);
1646
+ const sourceConfig = join4(source, "config.toml"), original = read(sourceConfig), selected = managedConfig(original);
1647
+ const sources = ["credentials", "oauth", "device_id"].map((name) => realPath(join4(source, name)));
1648
+ if (!sources.every((file, index) => index < 2 ? lstatSync3(file).isDirectory() : lstatSync3(file).isFile()))
1649
+ throw new Error("native OAuth directories and device identity required");
1650
+ const mcp = mcpConfig(context);
1651
+ if (Buffer.byteLength(context.input ?? "") > 1048576)
1652
+ throw new Error("review facts exceed limit");
1653
+ const lexicalRoot = mkdtempSync2(join4(process.env.TMPDIR ?? tmpdir2(), "ahub-kimi-review-"));
1654
+ own = realPath(lexicalRoot);
1655
+ assertCleanCwdAncestors(join4(lexicalRoot, "workspace"), join4(own, "workspace"));
1656
+ const root = own, home = join4(root, "home"), tool = join4(root, "tool"), cwd = join4(root, "workspace"), skills = join4(root, "empty-skills"), temporary = join4(root, "tmp");
1657
+ for (const dir of [home, tool, cwd, skills, temporary])
1658
+ mkdirSync2(dir, { mode: 448 });
1659
+ ["credentials", "oauth", "device_id"].forEach((name, index) => symlinkSync2(sources[index], join4(tool, name), index < 2 ? "dir" : "file"));
1660
+ const agentFile = join4(cwd, "reviewer.md"), configFile = join4(tool, "config.toml"), mcpFile = join4(tool, "mcp.json");
1661
+ writeFileSync2(configFile, selected.config, { mode: 384 });
1662
+ writeFileSync2(mcpFile, mcp, { mode: 384 });
1663
+ writeFileSync2(agentFile, `---
1664
+ name: hub-reviewer
1665
+ description: Fresh agent-hub reviewer
1666
+ tools:
1667
+ - mcp__agent-hub__hub_review
1668
+ - mcp__agent-hub__hub_task_show
1669
+ subagents: []
1670
+ ---
1671
+
1672
+ ${context.systemPrompt}
1673
+
1674
+ Use only the supplied public facts and hub_task_show. Never claim to have read evidence that was not supplied.
1675
+
1676
+ ${context.input ?? "No review is active; startup self-check only."}
1677
+ `, { mode: 384 });
1678
+ const env = { PATH: process.env.PATH ?? "/usr/bin:/bin", HOME: home, KIMI_CODE_HOME: tool, TMPDIR: `${temporary}/`, XDG_CONFIG_HOME: join4(home, ".config"), LANG: process.env.LANG ?? "en_US.UTF-8", TERM: "dumb", ...process.env.USER ? { USER: process.env.USER } : {} };
1679
+ const native = context.command ?? [Bun.which("kimi", { PATH: process.env.PATH }) ?? "kimi"];
1680
+ const executable = realPath(Bun.which(native[0], { PATH: process.env.PATH }) ?? resolve4(native[0]));
1681
+ const command = [executable, ...native.slice(1)];
1682
+ const executableStat = lstatSync3(executable);
1683
+ if (await version(command, cwd, env, context.signal) !== VERSION2)
1684
+ throw new Error("unsupported Kimi version; receipt covers 2.2.0");
1685
+ const fingerprints = [configFile, mcpFile, agentFile].map((file) => hash(read(file, 2097152)));
1686
+ let cleaned = false;
1687
+ const cleanup = () => {
1688
+ if (cleaned)
1689
+ return;
1690
+ rmSync2(root, { recursive: true, force: true });
1691
+ cleaned = true;
1692
+ };
1693
+ return {
1694
+ command,
1695
+ args: ["-p", "Review the supplied task facts and record the evidence-based verdict with hub_review.", "--output-format", "stream-json", "--agent-file", agentFile, "--skills-dir", skills],
1696
+ cwd,
1697
+ env,
1698
+ envMode: "replace",
1699
+ cleanup,
1700
+ watchCurrent: true,
1701
+ stop: (child) => stopKimiReview(child, [join4(sources[1], "kimi-code.lock"), join4(sources[1], `${selected.lock}.lock`)]),
1702
+ current: () => {
1703
+ try {
1704
+ assertCleanCwdAncestors(join4(lexicalRoot, "workspace"), cwd);
1705
+ const now = lstatSync3(executable);
1706
+ return !cleaned && !context.signal.aborted && realPath(sourcePath) === source && now.dev === executableStat.dev && now.ino === executableStat.ino && now.size === executableStat.size && now.mtimeMs === executableStat.mtimeMs && now.ctimeMs === executableStat.ctimeMs && hash(read(sourceConfig)) === hash(original) && [configFile, mcpFile, agentFile].every((file, index) => hash(read(file, 2097152)) === fingerprints[index]) && ["credentials", "oauth", "device_id"].every((name, index) => realPath(join4(source, name)) === sources[index] && realPath(join4(tool, name)) === sources[index]);
1707
+ } catch {
1708
+ return false;
1709
+ }
1710
+ },
1711
+ isolation: {
1712
+ backend: "kimi",
1713
+ version: VERSION2,
1714
+ binary: executable,
1715
+ profile: "private-config-exact-two-tools-shared-native-oauth",
1716
+ readOnly: true,
1717
+ strictMcp: true,
1718
+ ephemeral: true,
1719
+ enforcement: "native-policy",
1720
+ freshSession: true,
1721
+ hubToolsOnly: true,
1722
+ evidence: ["#533 person ruling2026-10-19: native execution-time exact tool allowlist, no kernel sandbox", "private config/cwd/empty skills; shared OAuth directory symlinks and native lock/atomic rename", "task-bound in-process hub MCP; no full control token", "residual: interruption mid-refresh may require native re-login"]
1723
+ }
1724
+ };
1725
+ } catch (error) {
1726
+ if (own)
1727
+ rmSync2(own, { recursive: true, force: true });
1728
+ return { unavailable: `Kimi reviewer unavailable: ${error.message}` };
1729
+ }
1730
+ }
1731
+ };
1732
+ });
1733
+
1375
1734
  // src/adapters/definitions/native-context.ts
1376
1735
  function claudeContext(value, sessionId, measuredAt) {
1377
- const c = object2(value);
1378
- const current = object2(c.current_usage);
1736
+ const c = object3(value);
1737
+ const current = object3(c.current_usage);
1379
1738
  const parts = [current.input_tokens, current.cache_creation_input_tokens, current.cache_read_input_tokens];
1380
1739
  const window = positive(c.context_window_size) ? c.context_window_size : null;
1381
1740
  const sum = c.current_usage != null && parts.every(nonnegative) ? parts.reduce((sum, n) => sum + n, 0) : null;
@@ -1384,7 +1743,7 @@ function claudeContext(value, sessionId, measuredAt) {
1384
1743
  return { source: "claude_statusline", sessionId, measuredAt, tokens, window, used };
1385
1744
  }
1386
1745
  function codexContext(value, sessionId, measuredAt) {
1387
- const c = object2(value), last = object2(c.last);
1746
+ const c = object3(value), last = object3(c.last);
1388
1747
  const window = positive(c.modelContextWindow) ? c.modelContextWindow : null;
1389
1748
  const tokens = nonnegative(last.totalTokens) ? last.totalTokens : null;
1390
1749
  return { source: "codex_token_usage", sessionId, measuredAt, tokens, window, used: tokens !== null && window !== null ? Math.min(1, tokens / window) : null };
@@ -1395,16 +1754,16 @@ function acpContext(usage, sessionId, measuredAt) {
1395
1754
  return { source: "acp_usage_update", sessionId, measuredAt, tokens, window, used: tokens !== null && window !== null ? Math.min(1, tokens / window) : null };
1396
1755
  }
1397
1756
  function acpContextValue(value, sessionId, measuredAt) {
1398
- const raw = object2(value);
1757
+ const raw = object3(value);
1399
1758
  return acpContext({ used: raw.used, size: raw.size }, sessionId, measuredAt);
1400
1759
  }
1401
- var nonnegative = (n) => typeof n === "number" && Number.isFinite(n) && n >= 0, positive = (n) => nonnegative(n) && n > 0, record = (v) => v !== null && typeof v === "object" && !Array.isArray(v), object2 = (v) => record(v) ? v : {};
1760
+ var nonnegative = (n) => typeof n === "number" && Number.isFinite(n) && n >= 0, positive = (n) => nonnegative(n) && n > 0, record = (v) => v !== null && typeof v === "object" && !Array.isArray(v), object3 = (v) => record(v) ? v : {};
1402
1761
 
1403
1762
  // src/hub/context-window.ts
1404
1763
  var init_context_window = () => {};
1405
1764
 
1406
1765
  // src/adapters/codex-appserver.ts
1407
- import { spawn as spawn3 } from "child_process";
1766
+ import { spawn as spawn4 } from "child_process";
1408
1767
  function parse(raw) {
1409
1768
  try {
1410
1769
  return JSON.parse(raw);
@@ -1752,7 +2111,7 @@ ${renderDigest(envs, false)}`;
1752
2111
  }
1753
2112
  let gone = "";
1754
2113
  const env = peerChildEnv(this.id, { ...process.env, ...this.opts.env ?? {} });
1755
- this.proc = spawn3(this.opts.bin ?? "codex", ["app-server", "--listen", `ws://127.0.0.1:${port}`, ...this.opts.extraArgs ?? []], {
2114
+ this.proc = spawn4(this.opts.bin ?? "codex", ["app-server", "--listen", `ws://127.0.0.1:${port}`, ...this.opts.extraArgs ?? []], {
1756
2115
  cwd: this.opts.cwd,
1757
2116
  env,
1758
2117
  stdio: ["ignore", "ignore", "pipe"],
@@ -2152,7 +2511,7 @@ var init_qwen_native = __esm(() => {
2152
2511
  });
2153
2512
 
2154
2513
  // src/adapters/acp.ts
2155
- import { spawn as spawn4 } from "child_process";
2514
+ import { spawn as spawn5 } from "child_process";
2156
2515
  import { createInterface } from "readline";
2157
2516
  function normalizeACPUsage(value, source = "usage_update") {
2158
2517
  const unknown = { source, availability: "unsupported", shape: "none" };
@@ -2192,13 +2551,14 @@ function jsonObject(text) {
2192
2551
  return;
2193
2552
  }
2194
2553
  }
2195
- var HANDSHAKE_MS = 30000, TOOL_INPUT_CAP = 64, RpcTimeoutError, AcpPeer;
2554
+ var HANDSHAKE_MS = 30000, TOOL_INPUT_CAP = 64, TOOL_CEILING_MS, RpcTimeoutError, AcpPeer;
2196
2555
  var init_acp = __esm(() => {
2197
2556
  init_envelope();
2198
2557
  init_kimi_native();
2199
2558
  init_qwen_native();
2200
2559
  init_qwen_native();
2201
2560
  init_child_process();
2561
+ TOOL_CEILING_MS = 30 * 60000;
2202
2562
  RpcTimeoutError = class RpcTimeoutError extends Error {
2203
2563
  };
2204
2564
  AcpPeer = class AcpPeer extends BasePeer {
@@ -2216,6 +2576,8 @@ var init_acp = __esm(() => {
2216
2576
  toolInputs = new Map;
2217
2577
  toolText = new Map;
2218
2578
  toolTitles = new Map;
2579
+ openTools = new Map;
2580
+ lastSignAt = 0;
2219
2581
  primed = false;
2220
2582
  turn = 0;
2221
2583
  stopping = false;
@@ -2314,7 +2676,7 @@ var init_acp = __esm(() => {
2314
2676
  async start() {
2315
2677
  this.stopping = false;
2316
2678
  const [bin, ...args] = this.opts.cmd;
2317
- const proc = spawn4(bin, args, { cwd: this.opts.cwd, env: peerChildEnv(this.id, { ...process.env, ...this.opts.env ?? {} }), stdio: ["pipe", "pipe", "pipe"], detached: true });
2679
+ const proc = spawn5(bin, args, { cwd: this.opts.cwd, env: peerChildEnv(this.id, { ...process.env, ...this.opts.env ?? {} }), stdio: ["pipe", "pipe", "pipe"], detached: true });
2318
2680
  this.proc = proc;
2319
2681
  trackGroup(proc);
2320
2682
  proc.on("error", (e) => this.down(`spawn failed: ${e.message}`));
@@ -2434,6 +2796,7 @@ var init_acp = __esm(() => {
2434
2796
  this.deliveryAccepted = false;
2435
2797
  this.promptActivity = false;
2436
2798
  this.chunks = [];
2799
+ this.openTools.clear();
2437
2800
  this.activityOpen = true;
2438
2801
  this.activityPrivate = envs.some((env) => env.private === true);
2439
2802
  try {
@@ -2535,6 +2898,21 @@ ${renderDigest(envs, false)}` }] });
2535
2898
  this.clearWatchdog();
2536
2899
  return;
2537
2900
  }
2901
+ if (this.openTools.size) {
2902
+ const ceiling = Math.max(this.watchdogMs, this.opts.toolCeilingMs ?? TOOL_CEILING_MS);
2903
+ const remaining = ceiling - (Date.now() - this.lastSignAt);
2904
+ if (remaining > 0) {
2905
+ this.touch(Math.min(this.watchdogMs, remaining));
2906
+ return;
2907
+ }
2908
+ try {
2909
+ this.activityPrivate ||= this.opts.isPrivateTurn?.() === true;
2910
+ } catch {
2911
+ this.activityPrivate = true;
2912
+ }
2913
+ const tools = this.activityPrivate ? "a tool call" : [...new Set(this.openTools.values())].map((title) => JSON.stringify(activityText(title).replace(/[\p{Cc}\p{Cf}\p{Zl}\p{Zp}]/gu, " ").slice(0, 80))).join(", ");
2914
+ this.opts.log?.(`[${this.id}] open tool call (${tools}) silent past the ${Math.round(ceiling / 60000)} min tool ceiling; cancelling the turn`);
2915
+ }
2538
2916
  this.endActivity("watchdog cancelled", true);
2539
2917
  const cancelledTurn = this.turn;
2540
2918
  this.cancelPermissions();
@@ -2608,8 +2986,10 @@ ${renderDigest(envs, false)}` }] });
2608
2986
  } catch {
2609
2987
  return;
2610
2988
  }
2611
- if (this.state === "busy" && !this.cancelWait)
2989
+ if (this.state === "busy" && !this.cancelWait) {
2990
+ this.lastSignAt = Date.now();
2612
2991
  this.touch();
2992
+ }
2613
2993
  if (msg.method === "session/update") {
2614
2994
  if (msg.params?.sessionId && msg.params.sessionId !== this.sessionId)
2615
2995
  return;
@@ -2640,7 +3020,11 @@ ${renderDigest(envs, false)}` }] });
2640
3020
  if (text)
2641
3021
  this.toolText.set(u.toolCallId, text);
2642
3022
  if (u.status === "completed" || u.status === "failed")
2643
- this.toolInputs.delete(u.toolCallId), this.toolText.delete(u.toolCallId), this.toolTitles.delete(u.toolCallId);
3023
+ this.toolInputs.delete(u.toolCallId), this.toolText.delete(u.toolCallId), this.toolTitles.delete(u.toolCallId), this.openTools.delete(u.toolCallId);
3024
+ else if (u.sessionUpdate === "tool_call" || this.openTools.has(u.toolCallId))
3025
+ this.openTools.set(u.toolCallId, this.toolTitles.get(u.toolCallId) ?? "tool");
3026
+ while (this.openTools.size > TOOL_INPUT_CAP)
3027
+ this.openTools.delete(this.openTools.keys().next().value);
2644
3028
  for (const map of [this.toolInputs, this.toolText, this.toolTitles])
2645
3029
  while (map.size > TOOL_INPUT_CAP)
2646
3030
  map.delete(map.keys().next().value);
@@ -2744,10 +3128,10 @@ ${renderDigest(envs, false)}` }] });
2744
3128
  });
2745
3129
 
2746
3130
  // src/adapters/jcode.ts
2747
- import { spawn as spawn5, spawnSync as spawnSync2 } from "child_process";
2748
- import { existsSync as existsSync3, lstatSync as lstatSync3, mkdtempSync as mkdtempSync2, rmSync as rmSync2 } from "fs";
2749
- import { tmpdir as tmpdir2 } from "os";
2750
- import { isAbsolute as isAbsolute3, join as join4 } from "path";
3131
+ import { spawn as spawn6, spawnSync as spawnSync2 } from "child_process";
3132
+ import { closeSync, constants, existsSync as existsSync4, fstatSync, lstatSync as lstatSync4, mkdtempSync as mkdtempSync3, openSync, readSync, rmSync as rmSync3 } from "fs";
3133
+ import { tmpdir as tmpdir3 } from "os";
3134
+ import { isAbsolute as isAbsolute3, join as join5 } from "path";
2751
3135
  function jcodeCommand(command, kind, socket, cwd, model, profile) {
2752
3136
  if (!command.length || command.some((word) => typeof word !== "string" || !word))
2753
3137
  throw new Error("jcode_cmd must name an executable argv");
@@ -2765,6 +3149,72 @@ function jcodeCommand(command, kind, socket, cwd, model, profile) {
2765
3149
  ...kind === "serve" ? ["--temporary-server", "--owner-pid", String(process.pid), "--server-name", "agent-hub-jcode"] : ["--mcp-tools", "eager"]
2766
3150
  ];
2767
3151
  }
3152
+ function jcodeTuiCommand(command, socket, sessionId, model, profile) {
3153
+ if (!command.length || command.some((word) => typeof word !== "string" || !word))
3154
+ throw new Error("jcode_cmd must name an executable argv");
3155
+ return [
3156
+ ...command,
3157
+ "--socket",
3158
+ socket,
3159
+ "--resume",
3160
+ sessionId,
3161
+ "--no-update",
3162
+ "--no-selfdev",
3163
+ ...model ? ["--model", model] : [],
3164
+ ...profile ? ["--provider-profile", profile] : []
3165
+ ];
3166
+ }
3167
+ function savedSession(home, id) {
3168
+ if (!/^[A-Za-z0-9_-]{1,128}$/.test(id))
3169
+ throw new Error("invalid native session identity");
3170
+ const directory = join5(home, "sessions"), info = lstatSync4(directory, { throwIfNoEntry: false });
3171
+ if (!info)
3172
+ return false;
3173
+ if (!info.isDirectory() || info.isSymbolicLink())
3174
+ throw new Error("unsafe native session directory");
3175
+ const file = join5(directory, `${id}.json`), entry = lstatSync4(file, { throwIfNoEntry: false });
3176
+ if (!entry)
3177
+ return false;
3178
+ if (!entry.isFile() || entry.isSymbolicLink())
3179
+ throw new Error("unsafe native session proof");
3180
+ let fd;
3181
+ try {
3182
+ fd = openSync(file, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
3183
+ } catch (error) {
3184
+ if (error.code === "ENOENT")
3185
+ return false;
3186
+ throw error;
3187
+ }
3188
+ try {
3189
+ const before = fstatSync(fd);
3190
+ if (!before.isFile() || before.nlink !== 1 || before.size > 32 * 1024 * 1024)
3191
+ throw new Error("unsafe native session proof");
3192
+ if (!before.size)
3193
+ return false;
3194
+ const bytes = Buffer.alloc(before.size);
3195
+ let offset = 0;
3196
+ while (offset < bytes.length) {
3197
+ const n = readSync(fd, bytes, offset, bytes.length - offset, offset);
3198
+ if (!n)
3199
+ return false;
3200
+ offset += n;
3201
+ }
3202
+ const after = fstatSync(fd);
3203
+ if (before.size !== after.size || before.mtimeMs !== after.mtimeMs || before.ctimeMs !== after.ctimeMs)
3204
+ return false;
3205
+ let data;
3206
+ try {
3207
+ data = JSON.parse(bytes.toString("utf8"));
3208
+ } catch {
3209
+ return false;
3210
+ }
3211
+ if (!data || typeof data !== "object" || data.id !== id || !Array.isArray(data.messages))
3212
+ throw new Error("native saved session identity is unproven");
3213
+ return true;
3214
+ } finally {
3215
+ closeSync(fd);
3216
+ }
3217
+ }
2768
3218
  function jcodeVersionSupported(version) {
2769
3219
  const v = typeof version === "string" ? release(version) : undefined;
2770
3220
  return !!v && compare(v, release(JCODE_MIN_VERSION)) >= 0 && compare(v, release(JCODE_BREAKING_VERSION)) < 0;
@@ -2776,6 +3226,8 @@ var init_jcode = __esm(() => {
2776
3226
  JCODE_OWNED_ENV = { JCODE_DISABLE_CLAUDE_MCP: "1", JCODE_NO_TELEMETRY: "1" };
2777
3227
  JcodePeer = class JcodePeer extends AcpPeer {
2778
3228
  jcode;
3229
+ launchId = crypto.randomUUID();
3230
+ socketIdentity;
2779
3231
  root;
2780
3232
  socket;
2781
3233
  server;
@@ -2786,7 +3238,7 @@ var init_jcode = __esm(() => {
2786
3238
  if (!isAbsolute3(jcode.home))
2787
3239
  throw new Error("jcode_home must be an absolute native data path");
2788
3240
  jcodeCommand(jcode.command, "acp", "preview", jcode.cwd);
2789
- const root = mkdtempSync2(join4(tmpdir2(), "ahub-jcode-")), socket = join4(root, "j.sock");
3241
+ const root = mkdtempSync3(join5(tmpdir3(), "ahub-jcode-")), socket = join5(root, "j.sock");
2790
3242
  const cmd = jcodeCommand(jcode.command, "acp", socket, jcode.cwd, jcode.launchModel, jcode.providerProfile);
2791
3243
  super("jcode", { ...jcode, cmd, env: { ...jcode.env, JCODE_HOME: jcode.home, JCODE_SOCKET: socket, ...JCODE_OWNED_ENV }, mcpServers: [], requireLoadSessionEcho: true, expectedAgent: { name: "jcode", version: jcodeVersionSupported } });
2792
3244
  this.jcode = jcode;
@@ -2794,15 +3246,15 @@ var init_jcode = __esm(() => {
2794
3246
  this.socket = socket;
2795
3247
  }
2796
3248
  async start() {
2797
- if (!existsSync3(join4(this.jcode.home, "mcp.json")))
3249
+ if (!existsSync4(join5(this.jcode.home, "mcp.json")))
2798
3250
  throw new Error("initialize Jcode's global MCP store with its native client before enabling this peer; the hub never initializes or copies it");
2799
- if (existsSync3(this.socket)) {
3251
+ if (existsSync4(this.socket)) {
2800
3252
  this.preserveRoot = true;
2801
3253
  throw new Error("Jcode private socket is occupied; no daemon was adopted or removed");
2802
3254
  }
2803
3255
  this.ending = false;
2804
3256
  const [bin, ...args] = jcodeCommand(this.jcode.command, "serve", this.socket, this.jcode.cwd, this.jcode.launchModel, this.jcode.providerProfile);
2805
- const server = spawn5(bin, args, { cwd: this.jcode.cwd, detached: true, env: { ...peerChildEnv("jcode"), JCODE_HOME: this.jcode.home, JCODE_SOCKET: this.socket, JCODE_READY_FD: "3", ...JCODE_OWNED_ENV }, stdio: ["ignore", "ignore", "ignore", "pipe"] });
3257
+ const server = spawn6(bin, args, { cwd: this.jcode.cwd, detached: true, env: { ...peerChildEnv("jcode"), JCODE_HOME: this.jcode.home, JCODE_SOCKET: this.socket, JCODE_READY_FD: "3", ...JCODE_OWNED_ENV }, stdio: ["ignore", "ignore", "ignore", "pipe"] });
2806
3258
  this.server = server;
2807
3259
  trackGroup(server);
2808
3260
  try {
@@ -2818,7 +3270,7 @@ var init_jcode = __esm(() => {
2818
3270
  ready.once("data", () => {
2819
3271
  clearTimeout(timer);
2820
3272
  server.off("exit", fail);
2821
- if (lstatSync3(this.socket, { throwIfNoEntry: false })?.isSocket())
3273
+ if (lstatSync4(this.socket, { throwIfNoEntry: false })?.isSocket())
2822
3274
  resolve();
2823
3275
  else
2824
3276
  reject(new Error("Jcode readiness has no private socket"));
@@ -2828,6 +3280,8 @@ var init_jcode = __esm(() => {
2828
3280
  if (!this.ending && this.server === server)
2829
3281
  this.stop("Jcode owned daemon disconnected").catch(() => {});
2830
3282
  });
3283
+ const identity = lstatSync4(this.socket);
3284
+ this.socketIdentity = { dev: identity.dev, ino: identity.ino };
2831
3285
  await super.start();
2832
3286
  } catch (error) {
2833
3287
  await this.stop().catch(() => {});
@@ -2887,12 +3341,27 @@ var init_jcode = __esm(() => {
2887
3341
  }
2888
3342
  if (!failure && !this.server) {
2889
3343
  if (!this.preserveRoot)
2890
- rmSync2(this.root, { recursive: true, force: true });
3344
+ rmSync3(this.root, { recursive: true, force: true });
2891
3345
  super.setState("offline");
2892
3346
  }
2893
3347
  if (failure)
2894
3348
  throw failure;
2895
3349
  }
3350
+ tuiLaunch(expected, options = {}) {
3351
+ const sessionId = super.recoveryMetadata().sessionId;
3352
+ const socket = lstatSync4(this.socket, { throwIfNoEntry: false });
3353
+ if (this.ending || !this.server || this.server.exitCode !== null || this.server.signalCode !== null || this.state === "offline" || !socket?.isSocket() || socket.dev !== this.socketIdentity?.dev || socket.ino !== this.socketIdentity.ino || typeof sessionId !== "string")
3354
+ throw new Error("owned Jcode launch is unavailable");
3355
+ const binding = { launchId: this.launchId, sessionId };
3356
+ if (expected && (expected.launchId !== binding.launchId || expected.sessionId !== binding.sessionId))
3357
+ throw new Error("Jcode launch changed");
3358
+ if (options.sessionId !== undefined && options.sessionId !== sessionId || options.model !== undefined && options.model !== this.jcode.launchModel || options.providerProfile !== undefined && options.providerProfile !== this.jcode.providerProfile)
3359
+ throw new Error("Jcode launch options differ from its running session");
3360
+ if (!savedSession(this.jcode.home, sessionId))
3361
+ return { state: "waiting", binding };
3362
+ const [cmd, ...args] = jcodeTuiCommand(this.jcode.command, this.socket, sessionId, this.jcode.launchModel, this.jcode.providerProfile);
3363
+ return { state: "ready", binding, launch: { cmd, args, env: { JCODE_HOME: this.jcode.home, JCODE_SOCKET: this.socket, ...JCODE_OWNED_ENV } } };
3364
+ }
2896
3365
  recoveryMetadata() {
2897
3366
  return { ...super.recoveryMetadata(), launch: { kind: "jcode", peer: "jcode", mode: "headless", ...this.jcode.launchModel ? { model: this.jcode.launchModel } : {}, ...this.jcode.providerProfile ? { providerProfile: this.jcode.providerProfile } : {} } };
2898
3367
  }
@@ -2900,11 +3369,11 @@ var init_jcode = __esm(() => {
2900
3369
  });
2901
3370
 
2902
3371
  // src/pi/launch.ts
2903
- import { join as join5 } from "path";
3372
+ import { join as join6 } from "path";
2904
3373
  function buildPiLaunch(opts, inherited, extension, bridgePort, bridgeToken) {
2905
3374
  const adapter = ADAPTERS.find((adapter) => adapter.start.daemonPort === "pi-rpc");
2906
- const env = { ...peerChildEnv(opts.instance ?? adapter.kind, inherited), ...opts.hubStateDir !== undefined ? { AGENTHUB_STATE_DIR: opts.hubStateDir } : {}, PI_CODING_AGENT_DIR: join5(opts.stateDir, "pi"), AGENTHUB_PI_BRIDGE_URL: `http://127.0.0.1:${bridgePort}`, AGENTHUB_PI_BRIDGE_TOKEN: bridgeToken, AGENTHUB_PI_OWNER_TOKEN: bridgeToken, AGENTHUB_PI_RELAY_URL: opts.relay.url, AGENTHUB_PI_RELAY_TOKEN: opts.relay.token, AGENTHUB_PI_MODELS: JSON.stringify(opts.relay.models), AGENTHUB_PI_TOOLS: JSON.stringify(opts.tools), AGENTHUB_PI_MAX_STEPS: String(opts.maxSteps ?? 30) };
2907
- const args = [...opts.mode === "headless" ? ["--mode", "rpc"] : [], "--provider", "agent-hub-local", "--model", opts.model ?? modelFor(opts.backend, opts.relay.models), "--models", opts.relay.models.map((model) => `agent-hub-local/${model.id}`).join(","), "--no-builtin-tools", "--no-skills", "--no-prompt-templates", "--no-extensions", "--extension", extension, ...opts.preamble ? ["--append-system-prompt", opts.preamble] : [], "--session-dir", join5(opts.stateDir, "pi-sessions")];
3375
+ const env = { ...peerChildEnv(opts.instance ?? adapter.kind, inherited), ...opts.hubStateDir !== undefined ? { AGENTHUB_STATE_DIR: opts.hubStateDir } : {}, PI_CODING_AGENT_DIR: join6(opts.stateDir, "pi"), AGENTHUB_PI_BRIDGE_URL: `http://127.0.0.1:${bridgePort}`, AGENTHUB_PI_BRIDGE_TOKEN: bridgeToken, AGENTHUB_PI_OWNER_TOKEN: bridgeToken, AGENTHUB_PI_RELAY_URL: opts.relay.url, AGENTHUB_PI_RELAY_TOKEN: opts.relay.token, AGENTHUB_PI_MODELS: JSON.stringify(opts.relay.models), AGENTHUB_PI_TOOLS: JSON.stringify(opts.tools), AGENTHUB_PI_MAX_STEPS: String(opts.maxSteps ?? 30) };
3376
+ const args = [...opts.mode === "headless" ? ["--mode", "rpc"] : [], "--provider", "agent-hub-local", "--model", opts.model ?? modelFor(opts.backend, opts.relay.models), "--models", opts.relay.models.map((model) => `agent-hub-local/${model.id}`).join(","), "--no-builtin-tools", "--no-skills", "--no-prompt-templates", "--no-extensions", "--extension", extension, ...opts.preamble ? ["--append-system-prompt", opts.preamble] : [], "--session-dir", join6(opts.stateDir, "pi-sessions")];
2908
3377
  if (opts.sessionFile)
2909
3378
  args.push("--session", opts.sessionFile);
2910
3379
  else if (opts.sessionId)
@@ -2940,8 +3409,8 @@ var init_recovery_store = __esm(() => {
2940
3409
  });
2941
3410
 
2942
3411
  // src/pi/session.ts
2943
- import { readFileSync as readFileSync3, openSync, closeSync, fsyncSync, mkdirSync as mkdirSync2 } from "fs";
2944
- import { join as join6, relative as relative2, resolve as resolve4, dirname as dirname4 } from "path";
3412
+ import { readFileSync as readFileSync4, openSync as openSync2, closeSync as closeSync2, fsyncSync, mkdirSync as mkdirSync3 } from "fs";
3413
+ import { join as join7, relative as relative2, resolve as resolve5, dirname as dirname5 } from "path";
2945
3414
  function sessionReadFailure(error) {
2946
3415
  if (error.code === "ENOENT")
2947
3416
  throw new PiSessionUnresumable("missing", error.message, { cause: error });
@@ -2951,16 +3420,16 @@ function validatePiSession(stateDir, cwd, sessionFile, sessionId) {
2951
3420
  let file, sessions;
2952
3421
  try {
2953
3422
  file = realPath(sessionFile);
2954
- sessions = realPath(join6(stateDir, "pi-sessions"));
3423
+ sessions = realPath(join7(stateDir, "pi-sessions"));
2955
3424
  } catch (error) {
2956
3425
  return sessionReadFailure(error);
2957
3426
  }
2958
3427
  const rel = relative2(sessions, file);
2959
- if (!rel || rel.startsWith("..") || resolve4(sessions, rel) !== file)
3428
+ if (!rel || rel.startsWith("..") || resolve5(sessions, rel) !== file)
2960
3429
  throw new PiSessionUnresumable("outside_managed", "Pi session file is outside the managed project session directory");
2961
3430
  let first;
2962
3431
  try {
2963
- first = readFileSync3(file, "utf8").split(`
3432
+ first = readFileSync4(file, "utf8").split(`
2964
3433
  `, 1)[0];
2965
3434
  } catch (error) {
2966
3435
  return sessionReadFailure(error);
@@ -3009,11 +3478,11 @@ var init_permission_mode = __esm(() => {
3009
3478
  });
3010
3479
 
3011
3480
  // src/hub/path-policy.ts
3012
- import { accessSync, constants, lstatSync as lstatSync4, opendirSync, readlinkSync, statSync as statSync2 } from "fs";
3013
- import { dirname as dirname5, isAbsolute as isAbsolute4, join as join7, relative as relative3, resolve as resolve5, sep as sep2 } from "path";
3481
+ import { accessSync, constants as constants2, lstatSync as lstatSync5, opendirSync, readlinkSync, statSync as statSync2 } from "fs";
3482
+ import { dirname as dirname6, isAbsolute as isAbsolute4, join as join8, relative as relative3, resolve as resolve6, sep as sep2 } from "path";
3014
3483
  function existing(path) {
3015
3484
  try {
3016
- lstatSync4(path);
3485
+ lstatSync5(path);
3017
3486
  return true;
3018
3487
  } catch (error) {
3019
3488
  if (error.code === "ENOENT")
@@ -3024,17 +3493,17 @@ function existing(path) {
3024
3493
  function policyRealPath(path) {
3025
3494
  let ancestor = path;
3026
3495
  while (!existing(ancestor)) {
3027
- const parent = dirname5(ancestor);
3496
+ const parent = dirname6(ancestor);
3028
3497
  if (parent === ancestor)
3029
3498
  throw new Error("path cannot be resolved");
3030
3499
  ancestor = parent;
3031
3500
  }
3032
- return join7(realPath(ancestor), relative3(ancestor, path));
3501
+ return join8(realPath(ancestor), relative3(ancestor, path));
3033
3502
  }
3034
3503
  function policyTargets(cwd, paths) {
3035
3504
  const root = realPath(cwd);
3036
3505
  return [...new Set(paths.flatMap((path) => {
3037
- const lexical = resolve5(root, path);
3506
+ const lexical = resolve6(root, path);
3038
3507
  return [lexical, policyRealPath(lexical)];
3039
3508
  }))];
3040
3509
  }
@@ -3052,19 +3521,19 @@ function directAliasTarget(path) {
3052
3521
  }
3053
3522
  let ancestor = target;
3054
3523
  while (!existing(ancestor)) {
3055
- const parent = dirname5(ancestor);
3524
+ const parent = dirname6(ancestor);
3056
3525
  if (parent === ancestor)
3057
3526
  return;
3058
3527
  ancestor = parent;
3059
3528
  }
3060
- if (!lstatSync4(ancestor).isSymbolicLink())
3529
+ if (!lstatSync5(ancestor).isSymbolicLink())
3061
3530
  return;
3062
- target = resolve5(dirname5(ancestor), readlinkSync(ancestor), relative3(ancestor, target));
3531
+ target = resolve6(dirname6(ancestor), readlinkSync(ancestor), relative3(ancestor, target));
3063
3532
  }
3064
3533
  return;
3065
3534
  }
3066
3535
  function aliasScope(dir, lexical, actual, includeParents, budget) {
3067
- accessSync(dir, constants.R_OK);
3536
+ accessSync(dir, constants2.R_OK);
3068
3537
  const handle = opendirSync(dir);
3069
3538
  let matched = false;
3070
3539
  try {
@@ -3076,7 +3545,7 @@ function aliasScope(dir, lexical, actual, includeParents, budget) {
3076
3545
  budget.entries++;
3077
3546
  if (!entry.isSymbolicLink())
3078
3547
  continue;
3079
- const target = directAliasTarget(join7(dir, entry.name));
3548
+ const target = directAliasTarget(join8(dir, entry.name));
3080
3549
  if (target !== undefined && (beneath(lexical, target) || beneath(actual, target) || includeParents && (beneath(target, lexical) || beneath(target, actual))))
3081
3550
  matched = true;
3082
3551
  }
@@ -3172,7 +3641,7 @@ function globScope(root, lexical, actual, path, includeParents, budget, roots, a
3172
3641
  if (++budget.entries > 4096)
3173
3642
  throw new Error("policy glob discovery unavailable");
3174
3643
  const next = advance(pattern, states, entry.name);
3175
- if (next.size && visit(join7(dir, entry.name), next, depth + 1, chain))
3644
+ if (next.size && visit(join8(dir, entry.name), next, depth + 1, chain))
3176
3645
  return true;
3177
3646
  }
3178
3647
  } finally {
@@ -3188,7 +3657,7 @@ function matchesPolicy(cwd, path, paths, includeParents = false) {
3188
3657
  try {
3189
3658
  if (path.length > 4096)
3190
3659
  return true;
3191
- const root = realPath(cwd), lexical = resolve5(root, path), actual = policyRealPath(lexical);
3660
+ const root = realPath(cwd), lexical = resolve6(root, path), actual = policyRealPath(lexical);
3192
3661
  includeParents &&= !existing(lexical) || statSync2(lexical).isDirectory();
3193
3662
  const budget = { entries: 0 }, roots = new Set, exact = [];
3194
3663
  if (paths.some((entry) => {
@@ -3223,7 +3692,7 @@ var init_path_policy = __esm(() => {
3223
3692
  });
3224
3693
 
3225
3694
  // src/local/deny.ts
3226
- import { basename as basename3, dirname as dirname6 } from "path";
3695
+ import { basename as basename4, dirname as dirname7 } from "path";
3227
3696
  function hubWriteRegexes(root, gitDirs = [], moduleGit) {
3228
3697
  const git = hfsFoldedName(".git");
3229
3698
  const re = (s) => `(regex ${sbplString(s)})`;
@@ -3235,7 +3704,7 @@ function hubWriteRegexes(root, gitDirs = [], moduleGit) {
3235
3704
  const modules = moduleGit ? [
3236
3705
  `(require-all ${re(`^${sbplEscape(moduleGit.root)}/(.*/)?${hfsFoldedName("config")}$`)} (require-not ${re(`^${sbplEscape(moduleGit.root)}/(.*/)?(refs|logs)/`)}))`,
3237
3706
  `(require-all ${re(`^${sbplEscape(moduleGit.root)}/(.*/)?${hfsFoldedName("hooks")}(/|$)`)} (require-not ${re(`^${sbplEscape(moduleGit.root)}/(.*/)?(refs|logs)/`)}))`,
3238
- ...moduleGit.entries.map((d) => re(`^${sbplEscape(dirname6(d))}/${hfsFoldedName(basename3(d))}$`))
3707
+ ...moduleGit.entries.map((d) => re(`^${sbplEscape(dirname7(d))}/${hfsFoldedName(basename4(d))}$`))
3239
3708
  ] : [];
3240
3709
  return [
3241
3710
  `(require-all (subpath ${sbplString(root)}) ${re(`^.*/${hfsFoldedName(".agenthub")}(/|$)`)})`,
@@ -3267,10 +3736,10 @@ var init_deny = __esm(() => {
3267
3736
  });
3268
3737
 
3269
3738
  // src/local/sandbox.ts
3270
- import { spawn as spawn6, spawnSync as spawnSync3 } from "child_process";
3271
- import { existsSync as existsSync4, mkdtempSync as mkdtempSync3, readdirSync, rmSync as rmSync3 } from "fs";
3272
- import { homedir as homedir2, tmpdir as tmpdir3 } from "os";
3273
- import { dirname as dirname7, join as join8, resolve as resolve6 } from "path";
3739
+ import { spawn as spawn7, spawnSync as spawnSync3 } from "child_process";
3740
+ import { existsSync as existsSync5, mkdtempSync as mkdtempSync4, readdirSync, rmSync as rmSync4 } from "fs";
3741
+ import { homedir as homedir3, tmpdir as tmpdir4 } from "os";
3742
+ import { dirname as dirname8, join as join9, resolve as resolve7 } from "path";
3274
3743
  function developerDir() {
3275
3744
  const out = spawnSync3("xcode-select", ["-p"], { encoding: "utf8" });
3276
3745
  let dir = out.status === 0 ? out.stdout.trim() : "";
@@ -3284,14 +3753,14 @@ function externalGitDirs(root) {
3284
3753
  if (out.status !== 0)
3285
3754
  return [];
3286
3755
  const dirs = out.stdout.trim().split(`
3287
- `).map((d) => resolve6(root, d));
3756
+ `).map((d) => resolve7(root, d));
3288
3757
  return [...new Set(dirs)].filter((d) => !d.startsWith(`${root}/`));
3289
3758
  }
3290
3759
  function moduleGitDirs(root) {
3291
3760
  const out = memoryGitSync(["-C", root, "rev-parse", "--git-common-dir"], { encoding: "utf8" });
3292
3761
  if (out.status !== 0)
3293
3762
  return;
3294
- const modulesRoot = join8(resolve6(root, out.stdout.trim()), "modules");
3763
+ const modulesRoot = join9(resolve7(root, out.stdout.trim()), "modules");
3295
3764
  const dirs = [];
3296
3765
  const walk = (dir, depth) => {
3297
3766
  if (depth > 8)
@@ -3305,31 +3774,31 @@ function moduleGitDirs(root) {
3305
3774
  const names = new Set(entries.map((e) => e.name));
3306
3775
  if (names.has("HEAD") && names.has("config")) {
3307
3776
  dirs.push(dir);
3308
- const nested = join8(dir, "modules");
3777
+ const nested = join9(dir, "modules");
3309
3778
  if (names.has("modules"))
3310
3779
  walk(nested, depth + 1);
3311
3780
  return;
3312
3781
  }
3313
3782
  for (const e of entries)
3314
3783
  if (e.isDirectory())
3315
- walk(join8(dir, e.name), depth + 1);
3784
+ walk(join9(dir, e.name), depth + 1);
3316
3785
  };
3317
3786
  walk(modulesRoot, 0);
3318
3787
  const entries = new Set([modulesRoot]);
3319
3788
  for (const d of dirs)
3320
- for (let p = d;p.length > modulesRoot.length; p = dirname7(p))
3789
+ for (let p = d;p.length > modulesRoot.length; p = dirname8(p))
3321
3790
  entries.add(p);
3322
3791
  return { root: modulesRoot, entries: [...entries] };
3323
3792
  }
3324
3793
  function profile(cwd, network, readAllow = [], deny = [], readOnly = []) {
3325
- const home = homedir2();
3326
- const inHome = (p) => p.startsWith("~/") ? join8(home, p.slice(2)) : p;
3794
+ const home = homedir3();
3795
+ const inHome = (p) => p.startsWith("~/") ? join9(home, p.slice(2)) : p;
3327
3796
  const root = realPath(cwd);
3328
3797
  const gitDirs = externalGitDirs(root);
3329
3798
  const moduleGit = moduleGitDirs(root);
3330
3799
  const creds = [".ssh", ".aws", ".gnupg", ".config/gh", ".config/gcloud", ".kube", ".docker", ".netrc", ".npmrc", ".omniroute", ".claude", ".codex", ".kimi-code", "Library/Keychains"];
3331
3800
  const dev = developerDir();
3332
- const readable = [root, ...HOME_READABLE.map((p) => join8(home, p)), ...readAllow.map(inHome), ...gitDirs, ...dev ? [dev] : []];
3801
+ const readable = [root, ...HOME_READABLE.map((p) => join9(home, p)), ...readAllow.map(inHome), ...gitDirs, ...dev ? [dev] : []];
3333
3802
  const subpaths = (paths) => paths.map((p) => `(subpath ${q(p)})`).join(" ");
3334
3803
  const globals = (names) => names.map((n) => `(global-name ${q(n)})`).join(" ");
3335
3804
  const proxy = typeof network === "object" ? `(allow network-outbound (remote ip ${q(`localhost:${network.proxyPort}`)}))` : undefined;
@@ -3347,14 +3816,14 @@ function profile(cwd, network, readAllow = [], deny = [], readOnly = []) {
3347
3816
  '(allow file-ioctl (regex #"^/dev/"))',
3348
3817
  ...proxy ? [proxy] : network ? ["(allow network*)"] : []
3349
3818
  ];
3350
- const places = `${creds.map((c) => `(subpath ${q(join8(home, c))})`).join(" ")} ${denyRegexes(root, deny, false).join(" ")}`;
3819
+ const places = `${creds.map((c) => `(subpath ${q(join9(home, c))})`).join(" ")} ${denyRegexes(root, deny, false).join(" ")}`;
3351
3820
  return [
3352
3821
  "(version 1)",
3353
3822
  ...start,
3354
3823
  "(deny file-write*)",
3355
3824
  ...readOnly.length ? ['(allow file-write* (literal "/dev/null"))'] : [`(allow file-write* (subpath ${q(root)}) (regex #"^/dev/") ${gitDirs.map((d) => `(subpath ${q(d)})`).join(" ")})`],
3356
3825
  `(deny file-write* ${hubWriteRegexes(root, gitDirs, moduleGit).join(" ")})`,
3357
- `(deny file-read* file-write* ${creds.map((c) => `(subpath ${q(join8(home, c))})`).join(" ")})`,
3826
+ `(deny file-read* file-write* ${creds.map((c) => `(subpath ${q(join9(home, c))})`).join(" ")})`,
3358
3827
  `(deny file-read* file-write* ${denyRegexes(root, deny).join(" ")})`,
3359
3828
  ...network ? [`(allow file-read* ${CA_BUNDLES.map((p) => `(literal ${q(p)})`).join(" ")} (regex ${q("/certifi/cacert\\.pem$")}))`, `(deny file-read* file-write* ${places})`] : []
3360
3829
  ].join(`
@@ -3368,11 +3837,11 @@ function sandboxedExec(argv, opts) {
3368
3837
  if (opts.signal?.aborted)
3369
3838
  return Promise.resolve({ code: null, output: "error: command cancelled before execution" });
3370
3839
  return new Promise((resolve) => {
3371
- const own = realPath(mkdtempSync3(join8(tmpdir3(), "ahub-cmd-")));
3840
+ const own = realPath(mkdtempSync4(join9(tmpdir4(), "ahub-cmd-")));
3372
3841
  const sandbox = `${opts.profile}
3373
3842
  (allow file-read* ${opts.readOnlyData ? "" : "file-write* "}process-exec (subpath ${q(own)}))`;
3374
- const env = { PATH: process.env.PATH ?? "/usr/bin:/bin", HOME: homedir2(), LANG: process.env.LANG ?? "en_US.UTF-8", TERM: "dumb", ...opts.env, TMPDIR: `${own}/` };
3375
- const child = spawn6(SANDBOX_EXEC, ["-p", sandbox, ...argv], { cwd: opts.cwd, env, stdio: ["ignore", "pipe", "pipe"], detached: true });
3843
+ const env = { PATH: process.env.PATH ?? "/usr/bin:/bin", HOME: homedir3(), LANG: process.env.LANG ?? "en_US.UTF-8", TERM: "dumb", ...opts.env, TMPDIR: `${own}/` };
3844
+ const child = spawn7(SANDBOX_EXEC, ["-p", sandbox, ...argv], { cwd: opts.cwd, env, stdio: ["ignore", "pipe", "pipe"], detached: true });
3376
3845
  let output = "";
3377
3846
  const take = (d) => {
3378
3847
  if (output.length < OUTPUT_CAP)
@@ -3391,11 +3860,11 @@ function sandboxedExec(argv, opts) {
3391
3860
  opts.signal?.addEventListener("abort", abort, { once: true });
3392
3861
  const dropOwn = () => {
3393
3862
  try {
3394
- rmSync3(own, { recursive: true, force: true });
3863
+ rmSync4(own, { recursive: true, force: true });
3395
3864
  } catch {
3396
3865
  try {
3397
3866
  spawnSync3("chflags", ["-R", "nouchg", own]);
3398
- rmSync3(own, { recursive: true, force: true });
3867
+ rmSync4(own, { recursive: true, force: true });
3399
3868
  } catch {}
3400
3869
  }
3401
3870
  };
@@ -3424,7 +3893,7 @@ function proxyEnv(network) {
3424
3893
  const url = `http://127.0.0.1:${network.proxyPort}`;
3425
3894
  return { HTTPS_PROXY: url, https_proxy: url, HTTP_PROXY: url, http_proxy: url, ALL_PROXY: url, all_proxy: url, NO_PROXY: "", no_proxy: "" };
3426
3895
  }
3427
- var SANDBOX_EXEC = "/usr/bin/sandbox-exec", OUTPUT_CAP = 20000, sandboxAvailable = () => process.platform === "darwin" && existsSync4(SANDBOX_EXEC), q, HOME_READABLE, CA_BUNDLES, SYSTEM_READABLE, MACH_SERVICES, NETWORK_MACH_SERVICES, PROXY_MACH_SERVICES;
3896
+ var SANDBOX_EXEC = "/usr/bin/sandbox-exec", OUTPUT_CAP = 20000, sandboxAvailable = () => process.platform === "darwin" && existsSync5(SANDBOX_EXEC), q, HOME_READABLE, CA_BUNDLES, SYSTEM_READABLE, MACH_SERVICES, NETWORK_MACH_SERVICES, PROXY_MACH_SERVICES;
3428
3897
  var init_sandbox = __esm(() => {
3429
3898
  init_project();
3430
3899
  init_git();
@@ -3439,29 +3908,29 @@ var init_sandbox = __esm(() => {
3439
3908
  });
3440
3909
 
3441
3910
  // src/local/tools.ts
3442
- import { lstatSync as lstatSync5, mkdirSync as mkdirSync3, readFileSync as readFileSync4, statSync as statSync3, writeFileSync as writeFileSync2 } from "fs";
3443
- import { dirname as dirname8, join as join9, relative as relative4, resolve as resolve7 } from "path";
3911
+ import { lstatSync as lstatSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync5, statSync as statSync3, writeFileSync as writeFileSync3 } from "fs";
3912
+ import { dirname as dirname9, join as join10, relative as relative4, resolve as resolve8 } from "path";
3444
3913
  function guardPath(ctx, path, mode) {
3445
3914
  const root = realPath(ctx.cwd);
3446
- const abs = resolve7(root, path);
3915
+ const abs = resolve8(root, path);
3447
3916
  let existing = abs;
3448
3917
  while (!lexists(existing))
3449
- existing = dirname8(existing);
3918
+ existing = dirname9(existing);
3450
3919
  let realExisting;
3451
3920
  try {
3452
3921
  realExisting = realPath(existing);
3453
3922
  } catch {
3454
3923
  throw new Error(`${path} goes through a dangling symlink`);
3455
3924
  }
3456
- const real = join9(realExisting, relative4(existing, abs));
3925
+ const real = join10(realExisting, relative4(existing, abs));
3457
3926
  const rel = relative4(root, real);
3458
- if (rel.startsWith("..") || resolve7(root, rel) !== real)
3927
+ if (rel.startsWith("..") || resolve8(root, rel) !== real)
3459
3928
  throw new Error(`${path} is outside the project directory`);
3460
3929
  if (isDenied(rel, ctx.deny))
3461
3930
  throw new Error(`${path} is on the secrets denylist`);
3462
3931
  if (mode === "write" && isHubWritePath(rel))
3463
3932
  throw new Error(`${path} is not writable by the local worker`);
3464
- if (mode === "write" && ctx.readOnly?.length && (lstatSync5(real, { throwIfNoEntry: false })?.nlink ?? 1) > 1)
3933
+ if (mode === "write" && ctx.readOnly?.length && (lstatSync6(real, { throwIfNoEntry: false })?.nlink ?? 1) > 1)
3465
3934
  throw new Error("hardlinked write target cannot be verified with data.readonly");
3466
3935
  if (mode === "write" && matchesPolicy(root, path, ctx.readOnly ?? []))
3467
3936
  throw new Error("target is protected by data.readonly");
@@ -3511,7 +3980,7 @@ async function runTool(name, rawArgs, ctx) {
3511
3980
  const file = guardPath(ctx, String(a.path), "read");
3512
3981
  if (statSync3(file).size > 5000000)
3513
3982
  return "error: file is larger than 5 MB; use bash (head, grep, sed -n) to look at parts of it";
3514
- const lines = readFileSync4(file, "utf8").split(`
3983
+ const lines = readFileSync5(file, "utf8").split(`
3515
3984
  `);
3516
3985
  const from = Math.max(1, Number(a.offset) || 1);
3517
3986
  const out = lines.slice(from - 1, from - 1 + (Number(a.limit) || 2000)).map((l, i) => `${from + i} ${l}`).join(`
@@ -3532,14 +4001,14 @@ ${preview(content)}`, "write", ctx.signal, file, undefined, String(a.path)), "er
3532
4001
  file = guardPath(ctx, String(a.path), "write");
3533
4002
  if (file !== approvedTarget)
3534
4003
  return "error: path target changed during approval; inspect before retrying";
3535
- mkdirSync3(dirname8(file), { recursive: true });
3536
- writeFileSync2(file, content);
4004
+ mkdirSync4(dirname9(file), { recursive: true });
4005
+ writeFileSync3(file, content);
3537
4006
  return `wrote ${a.path}`;
3538
4007
  }
3539
4008
  case "edit": {
3540
4009
  let file = guardPath(ctx, String(a.path), "write");
3541
4010
  const approvedTarget = file;
3542
- let text = readFileSync4(file, "utf8");
4011
+ let text = readFileSync5(file, "utf8");
3543
4012
  let count = text.split(String(a.old)).length - 1;
3544
4013
  if (!a.old || count !== 1)
3545
4014
  return `error: \`old\` must match exactly once, it matched ${count} times`;
@@ -3553,11 +4022,11 @@ ${preview(content)}`, "write", ctx.signal, file, undefined, String(a.path)), "er
3553
4022
  file = guardPath(ctx, String(a.path), "write");
3554
4023
  if (file !== approvedTarget)
3555
4024
  return "error: path target changed during approval; inspect before retrying";
3556
- text = readFileSync4(file, "utf8");
4025
+ text = readFileSync5(file, "utf8");
3557
4026
  count = text.split(String(a.old)).length - 1;
3558
4027
  if (count !== 1)
3559
4028
  return `error: file changed during approval; \`old\` must match exactly once, it matched ${count} times`;
3560
- writeFileSync2(file, text.replace(String(a.old), () => String(a.new ?? "")));
4029
+ writeFileSync3(file, text.replace(String(a.old), () => String(a.new ?? "")));
3561
4030
  return `edited ${a.path}`;
3562
4031
  }
3563
4032
  case "bash": {
@@ -3607,7 +4076,7 @@ ${preview(content)}`, "write", ctx.signal, file, undefined, String(a.path)), "er
3607
4076
  }
3608
4077
  var APPROVAL_EXPIRED_TEXT = "error: approval expired: no person answered; do not retry this call; hand the task off or stop", ApprovalWaitStop, approvalError = (picked, denied) => picked === true ? undefined : picked === "expired" ? APPROVAL_EXPIRED_TEXT : picked === "aborted" ? "error: approval withdrawn because the turn ended; no operation was executed" : denied, lexists = (p) => {
3609
4078
  try {
3610
- lstatSync5(p);
4079
+ lstatSync6(p);
3611
4080
  return true;
3612
4081
  } catch {
3613
4082
  return false;
@@ -3661,9 +4130,9 @@ var PI_CEILING_KIND = "tool-step-ceiling", PI_CEILING_UNIT = "tool-step";
3661
4130
 
3662
4131
  // src/adapters/pi.ts
3663
4132
  import { randomUUID as randomUUID2 } from "crypto";
3664
- import { existsSync as existsSync5, mkdirSync as mkdirSync4 } from "fs";
3665
- import { join as join10, resolve as resolve8 } from "path";
3666
- import { spawn as spawn7 } from "child_process";
4133
+ import { existsSync as existsSync6, mkdirSync as mkdirSync5 } from "fs";
4134
+ import { join as join11, resolve as resolve9 } from "path";
4135
+ import { spawn as spawn8 } from "child_process";
3667
4136
  function ownerStillAlive(pid, signature) {
3668
4137
  const current = processSignature(pid);
3669
4138
  if (current !== undefined)
@@ -3779,7 +4248,7 @@ var init_pi = __esm(() => {
3779
4248
  get recoveryReady() {
3780
4249
  if (this.state === "busy" || this.starting || this.activeTools)
3781
4250
  return false;
3782
- if (this.sessionFile && !existsSync5(this.sessionFile) && (this.activityObserved || this.persistedOnce))
4251
+ if (this.sessionFile && !existsSync6(this.sessionFile) && (this.activityObserved || this.persistedOnce))
3783
4252
  return false;
3784
4253
  if (this.opts.mode === "headless")
3785
4254
  return this.state === "idle" || !this.proc || this.proc.exitCode !== null || this.proc.signalCode !== null;
@@ -3798,7 +4267,7 @@ var init_pi = __esm(() => {
3798
4267
  return this.ownerClaimed && this.ownerPid && this.ownerSignature ? `${this.ownerPid}:${this.ownerSignature}:${this.sessionId ?? ""}` : undefined;
3799
4268
  }
3800
4269
  recoveryMetadata() {
3801
- const exists = !!this.sessionFile && existsSync5(this.sessionFile);
4270
+ const exists = !!this.sessionFile && existsSync6(this.sessionFile);
3802
4271
  if (exists) {
3803
4272
  this.persistedOnce = true;
3804
4273
  this.verifiedEmptyResume = undefined;
@@ -3811,14 +4280,14 @@ var init_pi = __esm(() => {
3811
4280
  if (this.state === "offline") {
3812
4281
  if (this.canReuseVerifiedEmptyResume())
3813
4282
  return this.recoveryMetadata();
3814
- if (this.recoveryReady && this.sessionFile && existsSync5(this.sessionFile))
4283
+ if (this.recoveryReady && this.sessionFile && existsSync6(this.sessionFile))
3815
4284
  return this.recoveryMetadata();
3816
4285
  throw new Error("Pi source session is offline and has no verified persisted resume state; inspect preserved history, then a person may run ahub pi --replace-session --session-file <managed-file>");
3817
4286
  }
3818
4287
  this.emptyResumeVerified = false;
3819
4288
  if (this.state !== "idle" || this.stopping || this.activeTools)
3820
4289
  throw new Error("Pi must settle before session capture");
3821
- if (this.sessionFile && existsSync5(this.sessionFile))
4290
+ if (this.sessionFile && existsSync6(this.sessionFile))
3822
4291
  return this.recoveryMetadata();
3823
4292
  const snapshot = this.opts.mode === "headless" ? (await this.waitRpc("get_state", 15000)).data : await this.sendTui({ type: "get_session_state" });
3824
4293
  if (this.state !== "idle" || this.activeTools || snapshot?.sessionId !== this.sessionId || snapshot?.sessionFile !== this.sessionFile)
@@ -3833,7 +4302,7 @@ var init_pi = __esm(() => {
3833
4302
  return captured;
3834
4303
  }
3835
4304
  canReuseVerifiedEmptyResume() {
3836
- return !!this.verifiedEmptyResume && this.recoveryReady && !this.activityObserved && !this.persistedOnce && this.sessionId === this.verifiedEmptyResume.sessionId && (!this.sessionFile || !existsSync5(this.sessionFile));
4305
+ return !!this.verifiedEmptyResume && this.recoveryReady && !this.activityObserved && !this.persistedOnce && this.sessionId === this.verifiedEmptyResume.sessionId && (!this.sessionFile || !existsSync6(this.sessionFile));
3837
4306
  }
3838
4307
  noteActivity() {
3839
4308
  this.activityObserved = true;
@@ -4025,9 +4494,9 @@ var init_pi = __esm(() => {
4025
4494
  this.approvalAnswerEpoch = undefined;
4026
4495
  this.approvalStopReason = "";
4027
4496
  this.approvalAbortSent = false;
4028
- mkdirSync4(this.opts.stateDir, { recursive: true });
4029
- const sessions = join10(this.opts.stateDir, "pi-sessions");
4030
- mkdirSync4(sessions, { recursive: true, mode: 448 });
4497
+ mkdirSync5(this.opts.stateDir, { recursive: true });
4498
+ const sessions = join11(this.opts.stateDir, "pi-sessions");
4499
+ mkdirSync5(sessions, { recursive: true, mode: 448 });
4031
4500
  if (this.opts.sessionFile) {
4032
4501
  try {
4033
4502
  validatePiSession(this.opts.stateDir, this.opts.cwd, this.opts.sessionFile, this.opts.sessionId);
@@ -4084,7 +4553,7 @@ var init_pi = __esm(() => {
4084
4553
  return new Response("not found", { status: 404 });
4085
4554
  } });
4086
4555
  this.server = bridge;
4087
- const extension = resolve8(join10(import.meta.dir, "../pi/extension.ts"));
4556
+ const extension = resolve9(join11(import.meta.dir, "../pi/extension.ts"));
4088
4557
  const inherited = {};
4089
4558
  for (const key of ["PATH", "HOME", "USER", "SHELL", "TMPDIR", "TERM", "TERM_PROGRAM", "LANG", "LC_ALL", "LC_CTYPE", "NO_COLOR", "CODEX_HOME"])
4090
4559
  if (process.env[key])
@@ -4094,7 +4563,7 @@ var init_pi = __esm(() => {
4094
4563
  if (this.opts.mode === "tui") {
4095
4564
  return;
4096
4565
  }
4097
- this.proc = spawn7(bin, args, { cwd: this.opts.cwd, env, stdio: ["pipe", "pipe", "pipe"], detached: true });
4566
+ this.proc = spawn8(bin, args, { cwd: this.opts.cwd, env, stdio: ["pipe", "pipe", "pipe"], detached: true });
4098
4567
  trackGroup(this.proc);
4099
4568
  this.proc.stdout.on("data", (chunk) => this.onOutput(String(chunk)));
4100
4569
  this.proc.stderr.on("data", (chunk) => this.opts.log?.(`[${this.id}] ${String(chunk).trimEnd()}`));
@@ -4126,7 +4595,7 @@ var init_pi = __esm(() => {
4126
4595
  this.ownerToken = String(this._tuiLaunch?.env.AGENTHUB_PI_OWNER_TOKEN ?? "");
4127
4596
  this.ownerClaimed = true;
4128
4597
  }
4129
- this.persistedOnce = existsSync5(this.sessionFile);
4598
+ this.persistedOnce = existsSync6(this.sessionFile);
4130
4599
  if (this.opts.sessionId && this.sessionId !== this.opts.sessionId || this.opts.sessionFile && this.sessionFile !== this.opts.sessionFile)
4131
4600
  throw new Error("Pi startup session identity mismatch");
4132
4601
  this.started = true;
@@ -5865,7 +6334,7 @@ var init_signals = __esm(() => {
5865
6334
  });
5866
6335
 
5867
6336
  // src/models/route/labels.ts
5868
- import { createHash as createHash3, randomUUID as randomUUID3 } from "crypto";
6337
+ import { createHash as createHash4, randomUUID as randomUUID3 } from "crypto";
5869
6338
 
5870
6339
  class RouteLabelTracker {
5871
6340
  sink;
@@ -6015,7 +6484,7 @@ function safeJson(value) {
6015
6484
  return "";
6016
6485
  }
6017
6486
  }
6018
- var discreteSeverity = (value) => value >= 1 ? 1 : value >= 0.7 ? 0.7 : value >= 0.3 ? 0.3 : 0, sha256 = (value) => createHash3("sha256").update(value).digest("hex");
6487
+ var discreteSeverity = (value) => value >= 1 ? 1 : value >= 0.7 ? 0.7 : value >= 0.3 ? 0.3 : 0, sha256 = (value) => createHash4("sha256").update(value).digest("hex");
6019
6488
  var init_labels = __esm(() => {
6020
6489
  init_signals();
6021
6490
  });
@@ -6883,9 +7352,9 @@ var init_shipped_roles = __esm(() => {
6883
7352
  });
6884
7353
 
6885
7354
  // src/hub/roles.ts
6886
- import { createHash as createHash4 } from "crypto";
6887
- import { constants as constants2, closeSync as closeSync2, fstatSync, lstatSync as lstatSync6, openSync as openSync2, readSync, readdirSync as readdirSync2 } from "fs";
6888
- import { join as join11 } from "path";
7355
+ import { createHash as createHash5 } from "crypto";
7356
+ import { constants as constants3, closeSync as closeSync3, fstatSync as fstatSync2, lstatSync as lstatSync7, openSync as openSync3, readSync as readSync2, readdirSync as readdirSync2 } from "fs";
7357
+ import { join as join12 } from "path";
6889
7358
  function parseRole(text, stem, layer) {
6890
7359
  if (Buffer.byteLength(text) > 8192 || text.includes("\r") || !ID.test(stem))
6891
7360
  throw new Error("invalid role size, encoding or file id");
@@ -6929,27 +7398,27 @@ function parseRole(text, stem, layer) {
6929
7398
  }
6930
7399
  const reviewStart = sections[4].index + sections[4][0].length;
6931
7400
  const review = body.slice(reviewStart, sections[5].index).trim();
6932
- return { id: stem, name: header.name, group: header.group, summary: header.summary, source: header.source, base: list("base", BASES2), classes: list("classes", CLASSES2), body, review, layer, hash: createHash4("sha256").update(text).digest("hex") };
7401
+ return { id: stem, name: header.name, group: header.group, summary: header.summary, source: header.source, base: list("base", BASES2), classes: list("classes", CLASSES2), body, review, layer, hash: createHash5("sha256").update(text).digest("hex") };
6933
7402
  }
6934
7403
  function projectRole(file) {
6935
- const fd = openSync2(file, constants2.O_RDONLY | constants2.O_NOFOLLOW | constants2.O_NONBLOCK);
7404
+ const fd = openSync3(file, constants3.O_RDONLY | constants3.O_NOFOLLOW | constants3.O_NONBLOCK);
6936
7405
  try {
6937
- const before = fstatSync(fd);
7406
+ const before = fstatSync2(fd);
6938
7407
  if (!before.isFile() || before.nlink !== 1 || before.size > 8192)
6939
7408
  throw new Error("role must be a bounded regular file");
6940
7409
  const bytes = Buffer.alloc(8193);
6941
7410
  let length = 0, n = 0;
6942
- while ((n = readSync(fd, bytes, length, bytes.length - length, null)) > 0) {
7411
+ while ((n = readSync2(fd, bytes, length, bytes.length - length, null)) > 0) {
6943
7412
  length += n;
6944
7413
  if (length > 8192)
6945
7414
  throw new Error("role exceeds file cap");
6946
7415
  }
6947
- const after = fstatSync(fd);
7416
+ const after = fstatSync2(fd);
6948
7417
  if (before.size !== after.size || before.mtimeMs !== after.mtimeMs || length !== after.size)
6949
7418
  throw new Error("role changed during read");
6950
7419
  return bytes.subarray(0, length).toString("utf8");
6951
7420
  } finally {
6952
- closeSync2(fd);
7421
+ closeSync3(fd);
6953
7422
  }
6954
7423
  }
6955
7424
  function loadRoleCatalog(cwd) {
@@ -6964,9 +7433,9 @@ function loadRoleCatalog(cwd) {
6964
7433
  if (!cwd)
6965
7434
  return { roles, errors };
6966
7435
  try {
6967
- const config = join11(cwd, ".agenthub"), dir = join11(config, "roles");
7436
+ const config = join12(cwd, ".agenthub"), dir = join12(config, "roles");
6968
7437
  for (const path of [config, dir]) {
6969
- const stat = lstatSync6(path);
7438
+ const stat = lstatSync7(path);
6970
7439
  if (!stat.isDirectory() || stat.isSymbolicLink())
6971
7440
  throw new Error("unsafe role directory");
6972
7441
  }
@@ -6976,7 +7445,7 @@ function loadRoleCatalog(cwd) {
6976
7445
  for (const file of files) {
6977
7446
  const id = file.slice(0, -3);
6978
7447
  try {
6979
- roles.set(id, parseRole(projectRole(join11(dir, file)), id, "project"));
7448
+ roles.set(id, parseRole(projectRole(join12(dir, file)), id, "project"));
6980
7449
  } catch {
6981
7450
  errors.push({ id: ID.test(id) ? id : "invalid-file-id", reason: "invalid or unsafe project role" });
6982
7451
  }
@@ -7878,21 +8347,748 @@ function reviewerVariant(kind, displayName, reviewer, display) {
7878
8347
  display
7879
8348
  };
7880
8349
  }
7881
- var codexVariant, CODEX_REVIEWER_VARIANT, KIMI_REVIEWER_VARIANT, AGY_DEFINITION;
8350
+ var codexVariant, CODEX_REVIEWER_VARIANT, KIMI_REVIEWER_VARIANT, AGY_REVIEWER_VARIANT;
7882
8351
  var init_reviewer_variants = __esm(() => {
7883
8352
  init_claude_print();
7884
8353
  init_codex_review_isolation();
8354
+ init_kimi_review();
7885
8355
  init_native_factories();
7886
8356
  init_native_display();
7887
8357
  codexVariant = reviewerVariant("codex", "Codex reviewer", { backend: codexReviewerBackend, defaultPool: true, declaration: "explicit" }, CODEX_DISPLAY);
7888
8358
  CODEX_REVIEWER_VARIANT = { ...codexVariant, start: { ...codexVariant.start, create: createCodexReviewer } };
7889
- KIMI_REVIEWER_VARIANT = reviewerVariant("kimi", "Kimi reviewer", { defaultPool: false, declaration: "explicit", unavailable: "Kimi reviewer unavailable: runtime and isolation verification deferred to #533" }, KIMI_DISPLAY);
7890
- AGY_DEFINITION = {
7891
- ...reviewerVariant("agy", "agy reviewer", { defaultPool: false, declaration: "automatic", unavailable: "agy reviewer unavailable: native adapter and isolation verification deferred to #533" }, { palette: { fg: "#6558a6", bg: "#f1effa" } }),
8359
+ KIMI_REVIEWER_VARIANT = reviewerVariant("kimi", "Kimi reviewer", { backend: kimiReviewerBackend, defaultPool: true, declaration: "explicit" }, KIMI_DISPLAY);
8360
+ AGY_REVIEWER_VARIANT = {
8361
+ ...reviewerVariant("agy", "agy reviewer", { defaultPool: false, declaration: "automatic", unavailable: "agy is an implementer peer (#485)" }, { palette: { fg: "#6558a6", bg: "#f1effa" } }),
7892
8362
  optional: true
7893
8363
  };
7894
8364
  });
7895
8365
 
8366
+ // src/adapters/agy/hooks.ts
8367
+ import { closeSync as closeSync4, constants as constants4, fstatSync as fstatSync3, lstatSync as lstatSync8, openSync as openSync4, readSync as readSync3 } from "fs";
8368
+ import { homedir as homedir4 } from "os";
8369
+ import { isAbsolute as isAbsolute5, join as join13, relative as relative5 } from "path";
8370
+ import { createHash as createHash6 } from "crypto";
8371
+ function decodeAgyHook(event, value) {
8372
+ if (!EVENTS.includes(event) || !record3(value) || typeof value.conversationId !== "string" || !UUID.test(value.conversationId) || typeof value.transcriptPath !== "string" || !isAbsolute5(value.transcriptPath) || value.transcriptPath.length > 4096 || !Array.isArray(value.workspacePaths) || !value.workspacePaths.length || value.workspacePaths.length > 32 || !value.workspacePaths.every((path) => typeof path === "string" && path.length <= 4096 && isAbsolute5(path)))
8373
+ return;
8374
+ if (event === "Stop" && typeof value.fullyIdle !== "boolean")
8375
+ return;
8376
+ const call = record3(value.toolCall) ? value.toolCall : undefined;
8377
+ if ((event === "PreToolUse" || event === "PostToolUse") && (!call || typeof call.name !== "string" || call.name.length > 128 || !record3(call.args)))
8378
+ return;
8379
+ const native = call?.args;
8380
+ const observedRead = call?.name === "view_file" && typeof native?.AbsolutePath === "string";
8381
+ const phase = { SessionStart: "session", PreInvocation: "start", PostInvocation: "post", PreToolUse: "pre", PostToolUse: "post", Stop: "stop" }[event];
8382
+ return {
8383
+ event,
8384
+ phase,
8385
+ sessionId: value.conversationId,
8386
+ transcriptPath: value.transcriptPath,
8387
+ workspacePaths: value.workspacePaths,
8388
+ tool: observedRead ? "Read" : typeof call?.name === "string" ? call.name : "",
8389
+ input: observedRead ? { file_path: native.AbsolutePath } : native ?? {},
8390
+ ...Number.isSafeInteger(value.stepIdx) && value.stepIdx >= 0 ? { toolUseId: `${value.conversationId}:${value.stepIdx}` } : {},
8391
+ ...Number.isSafeInteger(value.invocationNum) && value.invocationNum >= 0 ? { invocation: value.invocationNum } : {},
8392
+ ...event === "Stop" ? { fullyIdle: value.fullyIdle, terminationReason: typeof value.terminationReason === "string" ? value.terminationReason : "" } : {}
8393
+ };
8394
+ }
8395
+ function encodeAgyHook(event, text) {
8396
+ if (text && (event === "SessionStart" || event === "PreInvocation"))
8397
+ return { injectSteps: [{ ephemeralMessage: text }] };
8398
+ return;
8399
+ }
8400
+ function readAgyTranscript(session, transcript) {
8401
+ const empty = { factIds: [] };
8402
+ if (!UUID.test(session))
8403
+ return empty;
8404
+ const home = process.env.HOME ?? homedir4();
8405
+ const candidates = ["antigravity-cli", "antigravity", "antigravity-ide"].map((surface) => join13(home, ".gemini", surface, "brain", session, ".system_generated/logs/transcript_full.jsonl"));
8406
+ if (!candidates.includes(transcript))
8407
+ return empty;
8408
+ let fd;
8409
+ try {
8410
+ let cursor = home;
8411
+ for (const name of relative5(home, transcript).split("/")) {
8412
+ cursor = join13(cursor, name);
8413
+ if (lstatSync8(cursor).isSymbolicLink())
8414
+ return empty;
8415
+ }
8416
+ fd = openSync4(transcript, constants4.O_RDONLY | constants4.O_NOFOLLOW);
8417
+ const stat = fstatSync3(fd);
8418
+ if (!stat.isFile() || stat.nlink !== 1)
8419
+ return empty;
8420
+ const cap = 4 * 1024 * 1024;
8421
+ for (let window = 64 * 1024;; window = Math.min(cap, window * 2)) {
8422
+ const start = Math.max(0, stat.size - window), from = start > 0 ? start - 1 : 0;
8423
+ const buffer = Buffer.alloc(stat.size - from);
8424
+ let bytes = 0;
8425
+ while (bytes < buffer.length) {
8426
+ const count = readSync3(fd, buffer, bytes, buffer.length - bytes, from + bytes);
8427
+ if (!count)
8428
+ break;
8429
+ bytes += count;
8430
+ }
8431
+ const lines = buffer.subarray(0, bytes).toString("utf8").split(`
8432
+ `);
8433
+ if (start)
8434
+ lines.shift();
8435
+ const out = { factIds: [] };
8436
+ for (const line of lines) {
8437
+ let row;
8438
+ try {
8439
+ row = JSON.parse(line);
8440
+ } catch {
8441
+ continue;
8442
+ }
8443
+ if (!record3(row) || row.status !== "DONE")
8444
+ continue;
8445
+ if (row.source === "SYSTEM_SDK" && row.type === "EPHEMERAL_MESSAGE" && typeof row.content === "string") {
8446
+ for (const match of row.content.matchAll(/agent-hub facts \[([a-zA-Z0-9-]{1,128})\]/g))
8447
+ out.factIds.push(match[1]);
8448
+ }
8449
+ if (row.source === "MODEL" && row.type === "PLANNER_RESPONSE" && Number.isSafeInteger(row.step_index) && typeof row.content === "string" && row.content.trim().length > 0 && (!Array.isArray(row.tool_calls) || row.tool_calls.length === 0))
8450
+ out.completion = { id: createHash6("sha256").update(`${session}:${row.step_index}:${line}`).digest("hex"), index: row.step_index };
8451
+ }
8452
+ if (out.completion || start === 0 || window === cap)
8453
+ return out;
8454
+ }
8455
+ } catch {
8456
+ return empty;
8457
+ } finally {
8458
+ if (fd !== undefined)
8459
+ closeSync4(fd);
8460
+ }
8461
+ }
8462
+ var EVENTS, UUID, record3 = (value) => !!value && typeof value === "object" && !Array.isArray(value), AGY_FACTS_HOOK;
8463
+ var init_hooks = __esm(() => {
8464
+ EVENTS = ["SessionStart", "PreInvocation", "PostInvocation", "PreToolUse", "PostToolUse", "Stop"];
8465
+ UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
8466
+ AGY_FACTS_HOOK = { decode: decodeAgyHook, encode: encodeAgyHook, read: readAgyTranscript };
8467
+ });
8468
+
8469
+ // src/adapters/agy/definition.ts
8470
+ function agyLaunch(ctx) {
8471
+ const argv = ["agy", ...ctx.userArgs], command = argv.map(shellQuote).join(" ");
8472
+ const metadata = { packageEntrypoint: ctx.entrypoint, command, argv, env: {}, kind: "agy", peer: ctx.instance };
8473
+ return { cmd: "agy", args: ctx.userArgs, metadata };
8474
+ }
8475
+ var DISPLAY, shellQuote = (value) => `'${value.replace(/'/g, `'\\''`)}'`, AGY_DEFINITION;
8476
+ var init_definition = __esm(() => {
8477
+ init_reviewer_variants();
8478
+ init_hooks();
8479
+ DISPLAY = { palette: { fg: "#6558a6", bg: "#f1effa" } };
8480
+ AGY_DEFINITION = {
8481
+ api: 1,
8482
+ kind: "agy",
8483
+ displayName: "Antigravity",
8484
+ family: "agy",
8485
+ transport: "ws-pull",
8486
+ roles: ["implementer", "verifier"],
8487
+ optional: true,
8488
+ defaultOnRoster: false,
8489
+ trust: { onPrem: false, hubSandboxed: false },
8490
+ start: {
8491
+ forms: ["tui"],
8492
+ defaultForm: "tui",
8493
+ ownsTerminal: (form) => form === "tui",
8494
+ launch: agyLaunch,
8495
+ conductorStartable: false,
8496
+ hubSwitches: [],
8497
+ ownedFlags: ["--new", "--continue", "--conversation", "--resume"],
8498
+ conductorStartArgs: (args) => args.length ? new Error("conductor starts accept no launch overrides; use ahub agy") : {}
8499
+ },
8500
+ identity: { mcp: "channel-plugin" },
8501
+ attach: {
8502
+ pullOnlyForms: ["tui"],
8503
+ recoveryLaunch: (instance) => ({ kind: "agy", peer: instance }),
8504
+ pullOnlyNotice: "pull-only: messages wait for hub_inbox; permission modes and session recovery are not managed"
8505
+ },
8506
+ permission: {
8507
+ modes: {},
8508
+ unmanaged: "Antigravity selects its own permission mode in its TUI; no hub permission mode",
8509
+ unmanagedForms: ["tui"],
8510
+ unmanagedNote: "Antigravity approves tools in its own TUI; no hub permission mode",
8511
+ unmanagedStatusNote: "unmanaged native permission policy"
8512
+ },
8513
+ session: {
8514
+ idField: "sessionId",
8515
+ currentId: () => {
8516
+ return;
8517
+ },
8518
+ freshOnRestore: true,
8519
+ recoveryRefusal: () => "Antigravity session continuity and resume are unavailable until native behavior is verified"
8520
+ },
8521
+ turnEnd: "native-hook",
8522
+ cli: { command: "agy", authority: "person", startWords: () => ["agy"] },
8523
+ display: DISPLAY,
8524
+ routing: { allowedRoles: ["implementer", "verifier"] },
8525
+ reviewer: { defaultPool: false, declaration: "automatic", unavailable: "agy is an implementer peer (#485)" },
8526
+ reviewerVariant: AGY_REVIEWER_VARIANT,
8527
+ hooks: { factsHook: AGY_FACTS_HOOK }
8528
+ };
8529
+ });
8530
+
8531
+ // src/adapters/definitions/native-cli-preview-common.ts
8532
+ function publicArg(arg) {
8533
+ const flags = new Set(["--settings", "--model", "--mcp-config", "--session-id", "--session-file", "--resume", "--continue", "--print", "--safe", "--new"]);
8534
+ const key = arg.split("=", 1)[0];
8535
+ return flags.has(key) ? arg.includes("=") ? key + "=" + REDACTED : key : REDACTED;
8536
+ }
8537
+ function nativeCommand(command, trusted) {
8538
+ return command.map((value, i) => value === trusted[i] ? value : REDACTED);
8539
+ }
8540
+ function previewState(args, baseEnvNames) {
8541
+ const unresolved = [];
8542
+ const envNames = [...baseEnvNames];
8543
+ const value = (flag) => {
8544
+ const at = args.indexOf(flag);
8545
+ if (at < 0)
8546
+ return;
8547
+ const next = args[at + 1];
8548
+ if (!next || next.startsWith("--"))
8549
+ throw new Error("missing option value");
8550
+ return next;
8551
+ };
8552
+ return { unresolved, envNames, value };
8553
+ }
8554
+ function finishPreview(ctx, launch, envNames, unresolved, metadata, settings) {
8555
+ return { tool: ctx.tool, ...metadata ?? {}, cmd: launch.cmd, args: launch.args, settings, envNames: [...new Set(envNames)].sort(), ...launch.warning ? { warning: launch.warning } : {}, unresolved, redaction: "Arbitrary user arguments, settings, commands and configured executable overrides are redacted. Environment values are never displayed.", needs: ["Native executable, accounts and runtime readiness are not checked."] };
8556
+ }
8557
+ var REDACTED = "[redacted]", UNRESOLVED = "[unresolved]";
8558
+
8559
+ // src/hub/status-roster.ts
8560
+ function rosterInStatus(status, peer) {
8561
+ const rows = status?.roster;
8562
+ if (!Array.isArray(rows))
8563
+ return;
8564
+ const row = rows.find((r) => !!r && typeof r === "object" && r.peer === peer);
8565
+ return typeof row?.on === "boolean" ? row.on : undefined;
8566
+ }
8567
+ function bundleInStatus(status) {
8568
+ const value = status?.pluginBundle;
8569
+ return typeof value === "string" && value.startsWith("/") && /[\\/]plugins[\\/]agent-hub[\\/]server\.js$/.test(value) ? value : undefined;
8570
+ }
8571
+
8572
+ // src/cli/agy-mcp.ts
8573
+ import { randomUUID as randomUUID5 } from "crypto";
8574
+ import { closeSync as closeSync5, fstatSync as fstatSync4, lstatSync as lstatSync9, mkdirSync as mkdirSync6, openSync as openSync5, readFileSync as readFileSync6, renameSync, rmdirSync, unlinkSync, writeFileSync as writeFileSync4 } from "fs";
8575
+ import { isAbsolute as isAbsolute6, join as join14, resolve as resolve10 } from "path";
8576
+ import { isDeepStrictEqual } from "util";
8577
+ function object4(value) {
8578
+ return !!value && typeof value === "object" && !Array.isArray(value);
8579
+ }
8580
+ function jsonFile(value, name) {
8581
+ if (!object4(value))
8582
+ throw new Error(`${name} must be a JSON object`);
8583
+ return value;
8584
+ }
8585
+ function parseJson(bytes, name) {
8586
+ try {
8587
+ return jsonFile(JSON.parse(bytes.toString("utf8")), name);
8588
+ } catch (error) {
8589
+ if (error instanceof SyntaxError)
8590
+ throw new Error(`${name} is invalid JSON`);
8591
+ throw error;
8592
+ }
8593
+ }
8594
+ function shellQuote2(value) {
8595
+ return `'${value.replace(/'/g, `'\\''`)}'`;
8596
+ }
8597
+ function shellUnquote(value) {
8598
+ if (!value.startsWith("'") || !value.endsWith("'"))
8599
+ return;
8600
+ let out = "";
8601
+ for (let index = 1;index < value.length - 1; ) {
8602
+ if (value.startsWith("'\\''", index)) {
8603
+ out += "'";
8604
+ index += 4;
8605
+ } else if (value[index] === "'")
8606
+ return;
8607
+ else
8608
+ out += value[index++];
8609
+ }
8610
+ return out;
8611
+ }
8612
+ function validateOptions(options) {
8613
+ const peer = options.peer ?? "agy";
8614
+ if (!/^[a-z][a-z0-9-]{0,31}$/.test(peer))
8615
+ throw new Error("agy peer id is invalid");
8616
+ for (const [name, value] of Object.entries({ projectRoot: options.projectRoot, stateDir: options.stateDir, serverJs: options.serverJs, hookScript: options.hookScript })) {
8617
+ if (typeof value !== "string" || !isAbsolute6(value) || value.length > 4096 || /[\u0000-\u001f\u007f]/.test(value))
8618
+ throw new Error(`${name} must be an absolute control-free path`);
8619
+ }
8620
+ for (const [name, value] of Object.entries({ instanceId: options.instanceId, launchId: options.launchId })) {
8621
+ if (typeof value !== "string" || !/^[A-Za-z0-9._:-]{1,128}$/.test(value))
8622
+ throw new Error(`${name} is invalid`);
8623
+ }
8624
+ const root = resolve10(options.projectRoot);
8625
+ return { root, peer };
8626
+ }
8627
+ function safeDirectory(path) {
8628
+ const stat = lstatSync9(path, { throwIfNoEntry: false });
8629
+ if (stat && (!stat.isDirectory() || stat.isSymbolicLink()))
8630
+ throw new Error(`${path} must be a project directory, not a link`);
8631
+ return stat;
8632
+ }
8633
+ function safeFile(path) {
8634
+ const stat = lstatSync9(path, { throwIfNoEntry: false });
8635
+ if (!stat)
8636
+ return;
8637
+ if (!stat.isFile() || stat.isSymbolicLink() || stat.nlink !== 1 || stat.size > MAX_CONFIG_BYTES)
8638
+ throw new Error(`${path} must be a bounded regular file, not a link`);
8639
+ const bytes = readFileSync6(path);
8640
+ if (bytes.length > MAX_CONFIG_BYTES)
8641
+ throw new Error(`${path} exceeds the configuration size limit`);
8642
+ return { stat, bytes };
8643
+ }
8644
+ function pluginDirectories(root) {
8645
+ const directories = [];
8646
+ for (const relative of [".", ".agents", ".agents/plugins", PLUGIN_DIR]) {
8647
+ const path = relative === "." ? root : join14(root, relative);
8648
+ const stat = safeDirectory(path);
8649
+ if (!stat)
8650
+ throw new Error(`${path} must be an existing project directory`);
8651
+ directories.push({ path, stat });
8652
+ }
8653
+ return directories;
8654
+ }
8655
+ function verifyPluginDirectory(plan, target) {
8656
+ if (!plan.canonicalPluginDir || !plan.directories)
8657
+ throw new Error("managed agy plugin directory identity is unavailable");
8658
+ if (realPath(plan.root) !== plan.canonicalRoot || realPath(plan.pluginDir) !== plan.canonicalPluginDir || plan.canonicalPluginDir !== join14(plan.canonicalRoot, PLUGIN_DIR)) {
8659
+ throw new Error("managed agy plugin path changed; inspect it before writing");
8660
+ }
8661
+ for (const expected of plan.directories) {
8662
+ const current = safeDirectory(expected.path);
8663
+ if (!current || current.dev !== expected.stat.dev || current.ino !== expected.stat.ino)
8664
+ throw new Error("managed agy plugin directory changed; inspect it before writing");
8665
+ }
8666
+ if (!FILES.some((file) => join14(plan.root, file) === target)) {
8667
+ throw new Error("atomic write target is outside the managed agy plugin files");
8668
+ }
8669
+ }
8670
+ function hookCommand(phase, options, peer) {
8671
+ return `AGENTHUB_HOOK_EVENT='${phase}' AGENTHUB_INSTANCE_ID='${options.instanceId}' AGENTHUB_LAUNCH_ID='${options.launchId}' AGENTHUB_HOOK_PURPOSE='facts' AGENTHUB_PEER_ID=${shellQuote2(peer)} AGENTHUB_STATE_DIR=${shellQuote2(options.stateDir)} AGENTHUB_PROJECT_DIR=${shellQuote2(resolve10(options.projectRoot))} bun ${shellQuote2(options.hookScript)}`;
8672
+ }
8673
+ function desired(options, root, peer) {
8674
+ const events = {};
8675
+ for (const phase of EVENTS2) {
8676
+ if (phase === "PostInvocation") {
8677
+ events[phase] = [];
8678
+ continue;
8679
+ }
8680
+ const command = { type: "command", command: hookCommand(phase, options, peer), timeout: 5 };
8681
+ events[phase] = phase === "PreToolUse" || phase === "PostToolUse" ? [{ matcher: ".*", hooks: [command] }] : [command];
8682
+ }
8683
+ return {
8684
+ [MANIFEST_FILE]: { name: PLUGIN_KEY, description: MANAGED_DESCRIPTION },
8685
+ [MCP_FILE]: { mcpServers: { [PLUGIN_KEY]: { command: "bun", args: ["run", options.serverJs], env: { AGENTHUB_PEER_ID: peer, AGENTHUB_STATE_DIR: options.stateDir, AGENTHUB_PROJECT_DIR: root } } } },
8686
+ [HOOKS_FILE]: { [PLUGIN_KEY]: events }
8687
+ };
8688
+ }
8689
+ function parseManagedHookCommand(command, phase, options, peer) {
8690
+ if (typeof command !== "string")
8691
+ return;
8692
+ const prefix = `AGENTHUB_HOOK_EVENT='${phase}' AGENTHUB_INSTANCE_ID='`;
8693
+ const middle = `' AGENTHUB_LAUNCH_ID='`;
8694
+ const suffix = `' AGENTHUB_HOOK_PURPOSE='facts' AGENTHUB_PEER_ID=${shellQuote2(peer)} AGENTHUB_STATE_DIR=${shellQuote2(options.stateDir)} AGENTHUB_PROJECT_DIR=${shellQuote2(resolve10(options.projectRoot))} bun `;
8695
+ if (!command.startsWith(prefix))
8696
+ return;
8697
+ const rest = command.slice(prefix.length);
8698
+ const launchSplit = rest.indexOf(middle);
8699
+ if (launchSplit < 1)
8700
+ return;
8701
+ const instanceId = rest.slice(0, launchSplit);
8702
+ const afterInstance = rest.slice(launchSplit + middle.length);
8703
+ const pathSplit = afterInstance.indexOf(suffix);
8704
+ if (pathSplit < 1)
8705
+ return;
8706
+ const launchId = afterInstance.slice(0, pathSplit);
8707
+ const hookScript = shellUnquote(afterInstance.slice(pathSplit + suffix.length));
8708
+ if (!/^[A-Za-z0-9._:-]{1,128}$/.test(instanceId) || !/^[A-Za-z0-9._:-]{1,128}$/.test(launchId) || !hookScript || !isAbsolute6(hookScript) || !hookScript.endsWith("/src/cli/facts-hook.ts"))
8709
+ return;
8710
+ return { instanceId, launchId };
8711
+ }
8712
+ function managedHookLaunch(value, options, peer) {
8713
+ if (Object.keys(value).length !== 1 || !Object.hasOwn(value, PLUGIN_KEY))
8714
+ return;
8715
+ const events = value[PLUGIN_KEY];
8716
+ if (!object4(events) || Object.keys(events).length !== EVENTS2.length || EVENTS2.some((phase) => !Object.hasOwn(events, phase)))
8717
+ return;
8718
+ let identity;
8719
+ for (const phase of EVENTS2) {
8720
+ const rows = events[phase];
8721
+ if (phase === "PostInvocation") {
8722
+ if (!Array.isArray(rows) || rows.length !== 0)
8723
+ return;
8724
+ continue;
8725
+ }
8726
+ if (!Array.isArray(rows) || rows.length !== 1 || !object4(rows[0]))
8727
+ return;
8728
+ let command;
8729
+ if (phase === "PreToolUse" || phase === "PostToolUse") {
8730
+ const row = rows[0];
8731
+ if (Object.keys(row).sort().join(",") !== "hooks,matcher" || row.matcher !== ".*" || !Array.isArray(row.hooks) || row.hooks.length !== 1 || !object4(row.hooks[0]))
8732
+ return;
8733
+ command = row.hooks[0];
8734
+ } else
8735
+ command = rows[0];
8736
+ if (!object4(command) || Object.keys(command).sort().join(",") !== "command,timeout,type" || command.type !== "command" || command.timeout !== 5)
8737
+ return;
8738
+ const found = parseManagedHookCommand(command.command, phase, options, peer);
8739
+ if (!found || identity && (identity.instanceId !== found.instanceId || identity.launchId !== found.launchId))
8740
+ return;
8741
+ identity = found;
8742
+ }
8743
+ return identity;
8744
+ }
8745
+ function nextDocuments(current, wanted) {
8746
+ const nextMcp = current[MCP_FILE] ? { ...current[MCP_FILE] } : {};
8747
+ const currentServers = nextMcp.mcpServers;
8748
+ const servers = currentServers === undefined ? {} : currentServers;
8749
+ if (!object4(servers))
8750
+ throw new Error(`${MCP_FILE} mcpServers must be an object`);
8751
+ nextMcp.mcpServers = { ...servers, [PLUGIN_KEY]: wanted[MCP_FILE].mcpServers[PLUGIN_KEY] };
8752
+ return { [MANIFEST_FILE]: wanted[MANIFEST_FILE], [MCP_FILE]: nextMcp, [HOOKS_FILE]: wanted[HOOKS_FILE] };
8753
+ }
8754
+ function internalPlan(options) {
8755
+ const { root, peer } = validateOptions(options);
8756
+ const rootStat = safeDirectory(root);
8757
+ if (!rootStat)
8758
+ throw new Error("project root must be an existing directory");
8759
+ const canonicalRoot = realPath(root);
8760
+ const pluginDir = join14(root, PLUGIN_DIR);
8761
+ let parentsPresent = true;
8762
+ for (const relative of [".agents", ".agents/plugins", PLUGIN_DIR]) {
8763
+ if (!parentsPresent)
8764
+ break;
8765
+ parentsPresent = !!safeDirectory(join14(root, relative));
8766
+ }
8767
+ let directories;
8768
+ let canonicalPluginDir;
8769
+ if (parentsPresent) {
8770
+ directories = pluginDirectories(root);
8771
+ canonicalPluginDir = realPath(pluginDir);
8772
+ if (canonicalPluginDir !== join14(canonicalRoot, PLUGIN_DIR))
8773
+ throw new Error("managed agy plugin path traverses a symlink");
8774
+ }
8775
+ const snapshots = Object.fromEntries(FILES.map((relative) => {
8776
+ const path = join14(root, relative);
8777
+ const found = parentsPresent ? safeFile(path) : undefined;
8778
+ return [relative, found];
8779
+ }));
8780
+ const current = {};
8781
+ for (const relative of FILES) {
8782
+ const found = snapshots[relative];
8783
+ current[relative] = found ? parseJson(found.bytes, relative) : undefined;
8784
+ }
8785
+ const manifest = current[MANIFEST_FILE];
8786
+ if (manifest && (Object.keys(manifest).sort().join(",") !== "description,name" || manifest.name !== PLUGIN_KEY || manifest.description !== MANAGED_DESCRIPTION)) {
8787
+ throw new Error(`${MANIFEST_FILE} is a foreign agent-hub plugin manifest; it was not changed`);
8788
+ }
8789
+ const mcp = current[MCP_FILE];
8790
+ if (mcp) {
8791
+ if (Object.hasOwn(mcp, "mcpServers") && !object4(mcp.mcpServers))
8792
+ throw new Error(`${MCP_FILE} mcpServers must be an object`);
8793
+ const entry = mcp.mcpServers?.[PLUGIN_KEY];
8794
+ if (entry !== undefined && (!object4(entry) || Object.keys(entry).sort().join(",") !== "args,command,env" || entry.command !== "bun" || !Array.isArray(entry.args) || entry.args.length !== 2 || entry.args[0] !== "run" || typeof entry.args[1] !== "string" || !isAbsolute6(entry.args[1]) || !object4(entry.env) || Object.keys(entry.env).sort().join(",") !== "AGENTHUB_PEER_ID,AGENTHUB_PROJECT_DIR,AGENTHUB_STATE_DIR" || entry.env.AGENTHUB_PEER_ID !== peer || entry.env.AGENTHUB_PROJECT_DIR !== root || entry.env.AGENTHUB_STATE_DIR !== options.stateDir)) {
8795
+ throw new Error(`${MCP_FILE} agent-hub is a foreign MCP entry; it was not changed`);
8796
+ }
8797
+ }
8798
+ const hooks = current[HOOKS_FILE];
8799
+ const oldLaunch = hooks ? managedHookLaunch(hooks, options, peer) : undefined;
8800
+ if (hooks && !oldLaunch)
8801
+ throw new Error(`${HOOKS_FILE} agent-hub is a foreign hooks entry; it was not changed`);
8802
+ const wanted = desired(options, root, peer);
8803
+ const docs = nextDocuments(current, wanted);
8804
+ const targets = FILES.map((relative) => {
8805
+ const old = snapshots[relative];
8806
+ const next = Buffer.from(`${JSON.stringify(docs[relative], null, 2)}
8807
+ `);
8808
+ return { path: join14(root, relative), relative, ...old ? { current: old.bytes, stat: old.stat } : {}, next, changed: !old || !isDeepStrictEqual(current[relative], docs[relative]) };
8809
+ });
8810
+ const git = memoryGitSync(["ls-files", "--", ...FILES], { cwd: root, encoding: "utf8", timeout: 3000 });
8811
+ const tracked = git.status === 0 && !!git.stdout.trim();
8812
+ const changed = targets.some((target) => target.changed);
8813
+ return { file: pluginDir, tracked, changed, action: changed ? "create or update the managed agent-hub plugin" : "keep the managed agent-hub plugin", root, pluginDir, canonicalRoot, ...canonicalPluginDir ? { canonicalPluginDir } : {}, ...directories ? { directories } : {}, targets };
8814
+ }
8815
+ function identityMatches(path, before) {
8816
+ const now = lstatSync9(path, { throwIfNoEntry: false });
8817
+ return before ? !!now && now.dev === before.dev && now.ino === before.ino : !now;
8818
+ }
8819
+ function atomicWrite(path, bytes, expected, plan) {
8820
+ const temporary = `${path}.${randomUUID5()}.tmp`;
8821
+ let fd;
8822
+ let tempIdentity;
8823
+ try {
8824
+ verifyPluginDirectory(plan, path);
8825
+ fd = openSync5(temporary, "wx", 384);
8826
+ const tempStat = fstatSync4(fd);
8827
+ tempIdentity = { dev: tempStat.dev, ino: tempStat.ino };
8828
+ writeFileSync4(fd, bytes);
8829
+ closeSync5(fd);
8830
+ fd = undefined;
8831
+ verifyPluginDirectory(plan, path);
8832
+ if (!identityMatches(path, expected))
8833
+ throw new Error(`${path} changed while the managed plugin was being written`);
8834
+ renameSync(temporary, path);
8835
+ const stat = lstatSync9(path);
8836
+ if (!stat.isFile() || stat.isSymbolicLink() || stat.nlink !== 1)
8837
+ throw new Error(`${path} changed while the managed plugin was written`);
8838
+ return { dev: stat.dev, ino: stat.ino };
8839
+ } finally {
8840
+ if (fd !== undefined)
8841
+ closeSync5(fd);
8842
+ try {
8843
+ const current = lstatSync9(temporary, { throwIfNoEntry: false });
8844
+ if (current && tempIdentity && current.dev === tempIdentity.dev && current.ino === tempIdentity.ino)
8845
+ unlinkSync(temporary);
8846
+ } catch {}
8847
+ }
8848
+ }
8849
+ function rollback(plan, written, createdDirs) {
8850
+ for (const { target, own } of written.reverse()) {
8851
+ try {
8852
+ const now = lstatSync9(target.path, { throwIfNoEntry: false });
8853
+ if (!now || now.dev !== own.dev || now.ino !== own.ino)
8854
+ continue;
8855
+ if (target.current)
8856
+ atomicWrite(target.path, target.current, now, plan);
8857
+ else
8858
+ unlinkSync(target.path);
8859
+ } catch {}
8860
+ }
8861
+ for (const { path, dev, ino } of createdDirs.reverse()) {
8862
+ try {
8863
+ const now = lstatSync9(path);
8864
+ if (now.isDirectory() && !now.isSymbolicLink() && now.dev === dev && now.ino === ino)
8865
+ rmdirSync(path);
8866
+ } catch {}
8867
+ }
8868
+ }
8869
+ function ensureAgyMcp(options) {
8870
+ const plan = internalPlan(options);
8871
+ if (!plan.changed)
8872
+ return { changed: false, tracked: plan.tracked };
8873
+ const createdDirs = [];
8874
+ const written = [];
8875
+ try {
8876
+ for (const relative of [".agents", ".agents/plugins", PLUGIN_DIR]) {
8877
+ const path = join14(plan.root, relative);
8878
+ const prior = safeDirectory(path);
8879
+ if (prior)
8880
+ continue;
8881
+ mkdirSync6(path, { mode: 448 });
8882
+ const stat = safeDirectory(path);
8883
+ createdDirs.push({ path, dev: stat.dev, ino: stat.ino });
8884
+ }
8885
+ plan.directories = pluginDirectories(plan.root);
8886
+ plan.canonicalPluginDir = realPath(plan.pluginDir);
8887
+ if (plan.canonicalRoot !== realPath(plan.root) || plan.canonicalPluginDir !== join14(plan.canonicalRoot, PLUGIN_DIR))
8888
+ throw new Error("managed agy plugin path changed; inspect it before writing");
8889
+ for (const target of plan.targets) {
8890
+ if (!target.changed)
8891
+ continue;
8892
+ if (!identityMatches(target.path, target.stat))
8893
+ throw new Error(`${target.path} changed while the managed plugin was being prepared`);
8894
+ const own = atomicWrite(target.path, target.next, target.stat, plan);
8895
+ written.push({ target, own });
8896
+ }
8897
+ } catch (error) {
8898
+ rollback(plan, written, createdDirs);
8899
+ throw error;
8900
+ }
8901
+ return { changed: true, tracked: plan.tracked };
8902
+ }
8903
+ function cleanupAgyMcp(options) {
8904
+ const plan = internalPlan(options);
8905
+ const present = plan.targets.filter((target) => target.current !== undefined);
8906
+ if (!present.length)
8907
+ return;
8908
+ if (present.length !== FILES.length)
8909
+ throw new Error("managed agy plugin is incomplete; inspect it before cleanup");
8910
+ const currentHooks = parseJson(readFileSync6(join14(plan.root, HOOKS_FILE)), HOOKS_FILE);
8911
+ const launch = managedHookLaunch(currentHooks, options, options.peer ?? "agy");
8912
+ if (!launch || launch.instanceId !== options.instanceId || launch.launchId !== options.launchId)
8913
+ return;
8914
+ if (plan.changed)
8915
+ return;
8916
+ const deleted = [];
8917
+ try {
8918
+ for (const target of plan.targets) {
8919
+ const stat = lstatSync9(target.path);
8920
+ if (!target.stat || stat.dev !== target.stat.dev || stat.ino !== target.stat.ino)
8921
+ throw new Error(`${target.path} changed before agy plugin cleanup`);
8922
+ }
8923
+ for (const target of plan.targets) {
8924
+ if (!identityMatches(target.path, target.stat))
8925
+ throw new Error(`${target.path} changed before agy plugin cleanup`);
8926
+ if (target.relative === MCP_FILE) {
8927
+ const currentMcp = parseJson(target.current, MCP_FILE);
8928
+ const servers = { ...currentMcp.mcpServers };
8929
+ delete servers[PLUGIN_KEY];
8930
+ const nextMcp = { ...currentMcp, mcpServers: servers };
8931
+ const retain = Object.keys(servers).length > 0 || Object.keys(currentMcp).some((key) => key !== "mcpServers");
8932
+ if (retain) {
8933
+ const own = atomicWrite(target.path, Buffer.from(`${JSON.stringify(nextMcp, null, 2)}
8934
+ `), target.stat, plan);
8935
+ deleted.push({ path: target.path, bytes: target.current, own });
8936
+ } else {
8937
+ unlinkSync(target.path);
8938
+ deleted.push({ path: target.path, bytes: target.current });
8939
+ }
8940
+ } else {
8941
+ unlinkSync(target.path);
8942
+ deleted.push({ path: target.path, bytes: target.current });
8943
+ }
8944
+ }
8945
+ } catch (error) {
8946
+ for (const item of deleted.reverse()) {
8947
+ try {
8948
+ const current = lstatSync9(item.path, { throwIfNoEntry: false });
8949
+ if (item.own) {
8950
+ if (current?.dev === item.own.dev && current.ino === item.own.ino)
8951
+ atomicWrite(item.path, item.bytes, current, plan);
8952
+ } else if (!current)
8953
+ atomicWrite(item.path, item.bytes, undefined, plan);
8954
+ } catch {}
8955
+ }
8956
+ throw error;
8957
+ }
8958
+ }
8959
+ var PLUGIN_DIR = ".agents/plugins/agent-hub", PLUGIN_KEY = "agent-hub", MANIFEST_FILE, MCP_FILE, HOOKS_FILE, FILES, EVENTS2, MANAGED_DESCRIPTION = "Managed by ahub agy launcher.", MAX_CONFIG_BYTES;
8960
+ var init_agy_mcp = __esm(() => {
8961
+ init_git();
8962
+ init_project();
8963
+ MANIFEST_FILE = `${PLUGIN_DIR}/plugin.json`;
8964
+ MCP_FILE = `${PLUGIN_DIR}/mcp_config.json`;
8965
+ HOOKS_FILE = `${PLUGIN_DIR}/hooks.json`;
8966
+ FILES = [MANIFEST_FILE, MCP_FILE, HOOKS_FILE];
8967
+ EVENTS2 = ["SessionStart", "PreInvocation", "PostInvocation", "PreToolUse", "PostToolUse", "Stop"];
8968
+ MAX_CONFIG_BYTES = 256 * 1024;
8969
+ });
8970
+
8971
+ // src/adapters/agy/entry.ts
8972
+ import { spawn as spawn9, spawnSync as spawnSync4 } from "child_process";
8973
+ import { resolve as resolve11 } from "path";
8974
+ function agyTuiSpawnOptions(cwd, env) {
8975
+ return { cwd, env, stdio: "inherit" };
8976
+ }
8977
+ function runAgyTui(argv, cwd, env) {
8978
+ const child = spawn9("agy", argv, agyTuiSpawnOptions(cwd, env));
8979
+ return new Promise((resolveExit, reject) => {
8980
+ const forward = (signal) => {
8981
+ if (child.exitCode !== null || child.signalCode !== null)
8982
+ return;
8983
+ try {
8984
+ child.kill(signal);
8985
+ } catch {}
8986
+ };
8987
+ const onInterrupt = () => forward("SIGINT");
8988
+ const onTerminate = () => forward("SIGTERM");
8989
+ const onHangup = () => forward("SIGHUP");
8990
+ const cleanup = () => {
8991
+ process.off("SIGINT", onInterrupt);
8992
+ process.off("SIGTERM", onTerminate);
8993
+ process.off("SIGHUP", onHangup);
8994
+ };
8995
+ process.on("SIGINT", onInterrupt);
8996
+ process.on("SIGTERM", onTerminate);
8997
+ process.on("SIGHUP", onHangup);
8998
+ child.once("error", (error) => {
8999
+ cleanup();
9000
+ reject(error);
9001
+ });
9002
+ child.once("close", (code) => {
9003
+ cleanup();
9004
+ resolveExit(code ?? 1);
9005
+ });
9006
+ });
9007
+ }
9008
+ async function runAgyEntry(ctx) {
9009
+ const { args, cwd, stateDir, cliDir, connect, fail } = ctx;
9010
+ if (ctx.instance)
9011
+ fail("extra Antigravity TUI instances are not verified in P1");
9012
+ const preview = args.includes("--print-command") || args.includes("--dry-run");
9013
+ const unattended = ctx.unattendedEnv || args.includes("--unattended");
9014
+ const nativeArgs = args.filter((arg) => !["--print-command", "--dry-run", "--unattended"].includes(arg));
9015
+ if (nativeArgs.some((arg) => ["--print", "-p", "--prompt", "--input-format", "--output-format", "--conversation", "--continue", "-c", "--new", "--mode", "--dangerously-skip-permissions"].some((flag) => arg === flag || arg.startsWith(`${flag}=`))))
9016
+ fail("agy P1 supports a fresh TUI only; headless, resume and permission flags need a verified mapping");
9017
+ if (unattended)
9018
+ fail("agy P1 native permissions are unmanaged; unattended mode has no verified prompt mapping");
9019
+ const argv = nativeArgs;
9020
+ if (preview) {
9021
+ console.log(JSON.stringify({ command: "agy", args: argv.map(publicArg), mode: "tui", permission: "unmanaged", mcp: ".agents/plugins/agent-hub/", writes: false }));
9022
+ return;
9023
+ }
9024
+ const version = spawnSync4("agy", ["--version"], { encoding: "utf8", timeout: 5000, maxBuffer: 4096, env: peerChildEnv("agy") });
9025
+ if (version.status !== 0 || version.stdout.trim() !== "1.4.2")
9026
+ fail("agy P1 requires the verified Antigravity CLI 1.4.2");
9027
+ const hub = await connect();
9028
+ let options;
9029
+ let pluginWritten = false;
9030
+ try {
9031
+ if (!hub.features.includes("native_hook_launch"))
9032
+ fail("running hub lacks the verified native hook launcher; upgrade or restart it before launching agy");
9033
+ const status = (await hub.request({ t: "status" })).status;
9034
+ if (rosterInStatus(status, "agy") !== true)
9035
+ fail("agy is off roster; add it with ahub roster add agy");
9036
+ const admitted = await hub.request({ t: "native_hook_launch", op: "begin", peer: "agy" });
9037
+ if (!admitted.ok || typeof admitted.launchId !== "string" || typeof admitted.instanceId !== "string")
9038
+ fail(admitted.error ?? "native hook launch was not admitted");
9039
+ options = {
9040
+ projectRoot: cwd,
9041
+ stateDir,
9042
+ serverJs: bundleInStatus(status) ?? resolve11(cliDir, "../../plugins/agent-hub/server.js"),
9043
+ hookScript: resolve11(cliDir, "facts-hook.ts"),
9044
+ peer: "agy",
9045
+ launchId: admitted.launchId,
9046
+ instanceId: admitted.instanceId
9047
+ };
9048
+ const written = ensureAgyMcp(options);
9049
+ pluginWritten = written.changed;
9050
+ if (written.tracked)
9051
+ console.error("The managed agy plugin is tracked; review its local machine paths before committing");
9052
+ console.error("Antigravity: pull-only TUI; native permissions unmanaged, no hub sandbox");
9053
+ const env = { ...ctx.helpers.nativeLaunchEnv("agy", ctx.helpers.childEnv()), AGENTHUB_STATE_DIR: stateDir, AGENTHUB_PROJECT_DIR: cwd };
9054
+ delete env.AGENTHUB_CHANNEL;
9055
+ const code = await runAgyTui(argv, cwd, env);
9056
+ if (code !== 0)
9057
+ process.exitCode = code;
9058
+ } finally {
9059
+ if (options) {
9060
+ await hub.request({ t: "native_hook_launch", op: "end", peer: "agy", launchId: options.launchId }).catch(() => {});
9061
+ if (pluginWritten)
9062
+ try {
9063
+ cleanupAgyMcp(options);
9064
+ } catch {
9065
+ console.error("Managed agy plugin cleanup refused: files changed or ownership could not be verified");
9066
+ }
9067
+ }
9068
+ hub.close();
9069
+ }
9070
+ }
9071
+ var init_entry = __esm(() => {
9072
+ init_agy_mcp();
9073
+ init_child_process();
9074
+ });
9075
+
9076
+ // src/adapters/definitions/native-cli-retained.ts
9077
+ function retainedLaunchWarning(status, peer, definition) {
9078
+ const keys = definition.cli.launchConfigKeys;
9079
+ const retained = status?.retainedConfig;
9080
+ if (!keys?.length || !Array.isArray(retained))
9081
+ return;
9082
+ const pending = retained.flatMap((row) => {
9083
+ if (!row || typeof row !== "object")
9084
+ return [];
9085
+ const key = row.key;
9086
+ return typeof key === "string" && keys.some((candidate) => key === candidate || key.startsWith(`${candidate}.`)) ? [key] : [];
9087
+ });
9088
+ const unique = [...new Set(pending)];
9089
+ return unique.length ? `ahub: config retained: ${unique.join(", ")}; starting ${peer} with the running value; run ahub settings reload to apply the file's value` : undefined;
9090
+ }
9091
+
7896
9092
  // src/hub/claude-state.ts
7897
9093
  function claudeStateFile(base, peer) {
7898
9094
  return peer.includes("-") && PEER2.test(peer) ? `${base}.${peer}.json` : `${base}.json`;
@@ -7903,9 +9099,9 @@ var init_claude_state = __esm(() => {
7903
9099
  });
7904
9100
 
7905
9101
  // src/adapters/definitions/native-cli-entry-claude.ts
7906
- import { readFileSync as readFileSync5 } from "fs";
7907
- import { join as join12 } from "path";
7908
- import { randomUUID as randomUUID5 } from "crypto";
9102
+ import { readFileSync as readFileSync7 } from "fs";
9103
+ import { join as join15 } from "path";
9104
+ import { randomUUID as randomUUID6 } from "crypto";
7909
9105
  async function runClaudeEntry(ctx) {
7910
9106
  const { args, cwd, stateDir, cliDir, unattendedEnv, projectConfig, fail } = ctx;
7911
9107
  const peer = ctx.instance ?? "claude";
@@ -7914,6 +9110,10 @@ async function runClaudeEntry(ctx) {
7914
9110
  return console.log(JSON.stringify({ peer, start: "hub", note: "a headless reviewer the hub runs per review" }, null, 2));
7915
9111
  const hub = await ctx.connect();
7916
9112
  try {
9113
+ const snapshot = (await hub.request({ t: "status" })).status;
9114
+ const warning = retainedLaunchWarning(snapshot, peer, ctx.definition);
9115
+ if (warning)
9116
+ console.error(warning);
7917
9117
  const res = await hub.request({ t: "start", peer, operationId: process.env.AGENTHUB_RECOVERY_OPERATION });
7918
9118
  if (res.ok !== true)
7919
9119
  fail(String(res.error ?? `${peer} was not started`));
@@ -7935,20 +9135,20 @@ async function runClaudeEntry(ctx) {
7935
9135
  process.env.AGENTHUB_INSTANCE_ID = control.instanceId;
7936
9136
  const terminal = await recordTerminalLaunch(peer, cwd, stateDir, control.instanceId, ctx.instance ? { executionCwd: cwd } : undefined);
7937
9137
  if (!terminal)
7938
- process.env.AGENTHUB_LAUNCH_ID = randomUUID5();
9138
+ process.env.AGENTHUB_LAUNCH_ID = randomUUID6();
7939
9139
  }
7940
9140
  let original;
7941
- for (const file of [join12(cwd, ".claude", "settings.local.json"), join12(cwd, ".claude", "settings.json"), join12(process.env.HOME ?? "", ".claude", "settings.json")]) {
9141
+ for (const file of [join15(cwd, ".claude", "settings.local.json"), join15(cwd, ".claude", "settings.json"), join15(process.env.HOME ?? "", ".claude", "settings.json")]) {
7942
9142
  try {
7943
- original ??= JSON.parse(readFileSync5(file, "utf8")).statusLine;
9143
+ original ??= JSON.parse(readFileSync7(file, "utf8")).statusLine;
7944
9144
  } catch {}
7945
9145
  }
7946
- const facts = claudeObservationHooks(projectConfig(), { script: join12(cliDir, "facts-hook.ts"), stateDir });
9146
+ const facts = claudeObservationHooks(projectConfig(), { script: join15(cliDir, "facts-hook.ts"), stateDir });
7947
9147
  let previousLaunch;
7948
9148
  try {
7949
- previousLaunch = JSON.parse(readFileSync5(join12(stateDir, claudeStateFile("claude-launch", peer)), "utf8"));
9149
+ previousLaunch = JSON.parse(readFileSync7(join15(stateDir, claudeStateFile("claude-launch", peer)), "utf8"));
7950
9150
  } catch {}
7951
- const launch = buildLaunch("claude", args, { unattended: unattendedEnv, statusLine: { script: join12(cliDir, "statusline-tee.ts"), stateDir, ...original ? { original } : {} }, ...facts ? { facts } : {} });
9151
+ const launch = buildLaunch("claude", args, { unattended: unattendedEnv, statusLine: { script: join15(cliDir, "statusline-tee.ts"), stateDir, ...original ? { original } : {} }, ...facts ? { facts } : {} });
7952
9152
  let result;
7953
9153
  try {
7954
9154
  let launchRecord;
@@ -7992,6 +9192,10 @@ async function runCodexEntry(ctx) {
7992
9192
  if (recovering && !ctx.instance && before?.instanceId)
7993
9193
  await recordTerminalLaunch(peer, cwd, stateDir, before.instanceId);
7994
9194
  const hub = await connect();
9195
+ const snapshot = (await hub.request({ t: "status" })).status;
9196
+ const warning = retainedLaunchWarning(snapshot, peer, ctx.definition);
9197
+ if (warning)
9198
+ console.error(warning);
7995
9199
  const res = await hub.request({ t: "start", peer, args: { unattended: launch0.unattended === true }, operationId: process.env.AGENTHUB_RECOVERY_OPERATION });
7996
9200
  hub.close();
7997
9201
  if (!res.ok)
@@ -8004,43 +9208,99 @@ async function runCodexEntry(ctx) {
8004
9208
  const control = readControl(stateDir);
8005
9209
  if ((!recovering || ctx.instance) && control?.instanceId)
8006
9210
  await recordTerminalLaunch(peer, cwd, stateDir, control.instanceId, ctx.instance ? { executionCwd: ctx.executionCwd } : undefined);
8007
- const launch = buildLaunch("codex", args, { unattended: unattendedEnv, instance: peer, proxyUrl: res.proxyUrl, codexBin: projectConfig().codex_bin });
9211
+ const runningCodexBin = res.retainedLaunchConfig?.codex_bin;
9212
+ const launch = buildLaunch("codex", args, { unattended: unattendedEnv, instance: peer, proxyUrl: res.proxyUrl, codexBin: typeof runningCodexBin === "string" ? runningCodexBin : projectConfig().codex_bin });
8008
9213
  if (launch0.warning)
8009
9214
  console.error(launch0.warning);
8010
9215
  exec(launch.cmd, launch.args, "codex");
8011
9216
  }
8012
-
8013
- // src/hub/status-roster.ts
8014
- function rosterInStatus(status, peer) {
8015
- const rows = status?.roster;
8016
- if (!Array.isArray(rows))
8017
- return;
8018
- const row = rows.find((r) => !!r && typeof r === "object" && r.peer === peer);
8019
- return typeof row?.on === "boolean" ? row.on : undefined;
8020
- }
8021
- function bundleInStatus(status) {
8022
- const value = status?.pluginBundle;
8023
- return typeof value === "string" && value.startsWith("/") && /[\\/]plugins[\\/]agent-hub[\\/]server\.js$/.test(value) ? value : undefined;
8024
- }
9217
+ var init_native_cli_entry_codex = () => {};
8025
9218
 
8026
9219
  // src/adapters/definitions/native-cli-entry-jcode.ts
8027
- import { resolve as resolve9 } from "path";
9220
+ import { resolve as resolve12 } from "path";
9221
+ async function waitForJcodeTui(hub, fail, signal, options, first) {
9222
+ let binding = first?.binding, announced = false, current = first;
9223
+ while (!signal.aborted) {
9224
+ if (!current) {
9225
+ const reply = await hub.request({ t: "native_tui_launch", peer: "jcode", binding, args: options });
9226
+ if (signal.aborted)
9227
+ return;
9228
+ if (!reply.ok || !reply.binding || !["waiting", "ready"].includes(reply.state))
9229
+ fail("Jcode launch changed or its native saved session is unavailable; retry from a terminal");
9230
+ current = reply;
9231
+ }
9232
+ if (binding && (binding.launchId !== current.binding.launchId || binding.sessionId !== current.binding.sessionId))
9233
+ fail("Jcode launch changed while waiting");
9234
+ binding = current.binding;
9235
+ if (current.state === "ready")
9236
+ return current;
9237
+ if (!announced) {
9238
+ console.log(WAITING);
9239
+ announced = true;
9240
+ }
9241
+ await new Promise((resolve) => {
9242
+ const finish = () => {
9243
+ clearTimeout(timer);
9244
+ signal.removeEventListener("abort", finish);
9245
+ resolve();
9246
+ };
9247
+ const timer = setTimeout(finish, 200);
9248
+ signal.addEventListener("abort", finish, { once: true });
9249
+ if (signal.aborted)
9250
+ finish();
9251
+ });
9252
+ current = undefined;
9253
+ }
9254
+ }
8028
9255
  async function runJcodeEntry(ctx) {
8029
9256
  const { args, cwd, stateDir, cliDir, unattendedEnv, connect, fail, takeFlags } = ctx;
8030
- const { launcherPreview, qwenSessionId, configureJcodeMcp } = ctx.helpers;
8031
- if (args.includes("--print-command") || args.includes("--dry-run")) {
9257
+ const { launcherPreview, qwenSessionId, configureJcodeMcp, readControl, startModeOf } = ctx.helpers;
9258
+ const print = args.includes("--print-command");
9259
+ if (args.includes("--dry-run") || print) {
9260
+ if (!print || args.includes("--dry-run") || !ctx.person) {
9261
+ try {
9262
+ return console.log(JSON.stringify(launcherPreview("jcode", args, cwd, stateDir, unattendedEnv), null, 2));
9263
+ } catch {
9264
+ fail("cannot preview Jcode launch: invalid arguments or configuration (details withheld)");
9265
+ }
9266
+ }
9267
+ }
9268
+ const { one, rest } = takeFlags(args.filter((arg) => arg !== "--print-command"), ["--mode", "--model", "--provider-profile", "--session-id"], []);
9269
+ if (rest.length || one["--mode"] !== undefined && !["headless", "tui"].includes(one["--mode"]))
9270
+ fail("usage: ahub jcode [--mode tui|headless] [--model <alias>] [--provider-profile <name>] [--session-id <id>]");
9271
+ if (one["--session-id"] !== undefined && !qwenSessionId(one["--session-id"]))
9272
+ fail("invalid Jcode session identity");
9273
+ const control = readControl(stateDir);
9274
+ const mode = one["--mode"] ?? (ctx.instance ? "headless" : control?.startModes?.jcode ?? startModeOf(ctx.projectConfig().peers, "jcode"));
9275
+ if (print && (mode === "headless" || !control?.features?.includes("native_tui_launch"))) {
8032
9276
  try {
8033
9277
  return console.log(JSON.stringify(launcherPreview("jcode", args, cwd, stateDir, unattendedEnv), null, 2));
8034
9278
  } catch {
8035
- fail("cannot preview Jcode launch: invalid arguments or configuration (details withheld)");
9279
+ return fail("cannot preview Jcode launch: invalid arguments or configuration (details withheld)");
8036
9280
  }
8037
9281
  }
8038
- const { one, rest } = takeFlags(args, ["--mode", "--model", "--provider-profile", "--session-id"], []);
8039
- if (rest.length || one["--mode"] !== undefined && one["--mode"] !== "headless")
8040
- fail("usage: ahub jcode [--mode headless] [--model <alias>] [--provider-profile <name>] [--session-id <id>]");
8041
- if (one["--session-id"] !== undefined && !qwenSessionId(one["--session-id"]))
8042
- fail("invalid Jcode session identity");
8043
- const hub = await connect();
9282
+ if (mode === "tui" && !ctx.person)
9283
+ fail("Jcode's private TUI is person-only; run ahub jcode in your terminal");
9284
+ const hub = await connect(), abort = new AbortController;
9285
+ let interrupted = false;
9286
+ const interruptedWait = () => {
9287
+ if (abort.signal.aborted && !interrupted)
9288
+ fail("hub connection closed while waiting for Jcode; retry from a terminal");
9289
+ return abort.signal.aborted;
9290
+ };
9291
+ const interrupt = () => {
9292
+ interrupted = true;
9293
+ abort.abort();
9294
+ hub.close();
9295
+ process.exitCode = 130;
9296
+ };
9297
+ process.once("SIGINT", interrupt);
9298
+ const oldClose = hub.onClose;
9299
+ hub.onClose = (code, reason) => {
9300
+ if (!abort.signal.aborted)
9301
+ abort.abort();
9302
+ oldClose?.(code, reason);
9303
+ };
8044
9304
  try {
8045
9305
  const snapshot = (await hub.request({ t: "status" })).status;
8046
9306
  const rostered = rosterInStatus(snapshot, "jcode");
@@ -8048,17 +9308,63 @@ async function runJcodeEntry(ctx) {
8048
9308
  fail("cannot read jcode's roster membership from the running hub; upgrade or restart it, then retry (nothing was started)");
8049
9309
  if (!rostered)
8050
9310
  fail("jcode is off roster; add it with ahub roster add jcode");
8051
- const written = configureJcodeMcp(cwd, stateDir, bundleInStatus(snapshot) ?? resolve9(cliDir, "../../plugins/agent-hub/server.js"));
9311
+ const warning = retainedLaunchWarning(snapshot, "jcode", ctx.definition);
9312
+ if (warning)
9313
+ console.error(warning);
9314
+ const options = { model: one["--model"], providerProfile: one["--provider-profile"], sessionId: one["--session-id"] };
9315
+ if (print) {
9316
+ if (!hub.features.includes("native_tui_launch") || mode !== "tui")
9317
+ return console.log(JSON.stringify(launcherPreview("jcode", args, cwd, stateDir, unattendedEnv), null, 2));
9318
+ const reply = await hub.request({ t: "native_tui_launch", peer: "jcode", args: options });
9319
+ if (!reply.ok || reply.state !== "ready")
9320
+ fail("Jcode's selected native session is not saved and ready; run ahub jcode to wait for its first hub turn");
9321
+ return console.log(JSON.stringify(reply.launch, null, 2));
9322
+ }
9323
+ if (mode === "tui") {
9324
+ if (!hub.features.includes("native_tui_launch"))
9325
+ fail("the running hub does not support Jcode TUI launch; upgrade it before retrying");
9326
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
9327
+ if (one["--mode"] || Object.values(options).some((value) => value !== undefined))
9328
+ fail("run ahub jcode with these options in a terminal");
9329
+ const reply = await hub.request({ t: "peer_start", peer: "jcode" });
9330
+ if (!reply.ok)
9331
+ fail(reply.error);
9332
+ console.log(reply.command ?? "Jcode terminal requested");
9333
+ return;
9334
+ }
9335
+ }
9336
+ const written = configureJcodeMcp(cwd, stateDir, bundleInStatus(snapshot) ?? resolve12(cliDir, "../../plugins/agent-hub/server.js"));
8052
9337
  if (written.tracked)
8053
9338
  console.error(".jcode/mcp.json is tracked; review this local machine-path change before committing it");
8054
- const reply = await hub.request({ t: "start", peer: "jcode", args: { mode: "headless", model: one["--model"], providerProfile: one["--provider-profile"], sessionId: one["--session-id"] }, operationId: process.env.AGENTHUB_RECOVERY_OPERATION }, 65000);
9339
+ const reply = await hub.request({ t: "start", peer: "jcode", args: { mode, ...options }, operationId: process.env.AGENTHUB_RECOVERY_OPERATION }, 65000);
9340
+ if (interruptedWait())
9341
+ return;
8055
9342
  if (!reply.ok)
8056
9343
  fail(reply.error);
8057
- console.log(reply.already ? "jcode is already attached" : "jcode attached (headless); native policy unmanaged, NO hub sandbox");
9344
+ if (mode === "headless") {
9345
+ console.log(reply.already ? "jcode is already attached" : "jcode attached (headless); native policy unmanaged, NO hub sandbox");
9346
+ return;
9347
+ }
9348
+ const ready = await waitForJcodeTui(hub, fail, abort.signal, options);
9349
+ if (interruptedWait() || !ready)
9350
+ return;
9351
+ const instanceId = typeof snapshot?.instanceId === "string" ? snapshot.instanceId : undefined;
9352
+ if (!instanceId)
9353
+ return fail("cannot bind Jcode's terminal to the running hub");
9354
+ await ctx.helpers.recordTerminalLaunch("jcode", cwd, stateDir, instanceId);
9355
+ const confirmed = await hub.request({ t: "native_tui_launch", peer: "jcode", binding: ready.binding, args: options });
9356
+ if (interruptedWait())
9357
+ return;
9358
+ if (!confirmed.ok || confirmed.state !== "ready")
9359
+ fail("Jcode launch changed before its terminal opened");
9360
+ ctx.execWithEnv(confirmed.launch.cmd, confirmed.launch.args, { ...ctx.helpers.childEnv(), ...confirmed.launch.env });
8058
9361
  } finally {
9362
+ process.removeListener("SIGINT", interrupt);
9363
+ hub.onClose = oldClose;
8059
9364
  hub.close();
8060
9365
  }
8061
9366
  }
9367
+ var WAITING = "jcode's TUI opens after its first hub turn; send it work, or Ctrl-C";
8062
9368
  var init_native_cli_entry_jcode = () => {};
8063
9369
 
8064
9370
  // src/adapters/definitions/native-cli-entry-qwen.ts
@@ -8081,6 +9387,10 @@ async function runQwenEntry(ctx) {
8081
9387
  fail("Qwen TUI is not offered before verification; usage: ahub qwen [--mode headless] [--model <alias>] [--session-id <id>]");
8082
9388
  const hub = await connect();
8083
9389
  try {
9390
+ const snapshot = (await hub.request({ t: "status" })).status;
9391
+ const warning = retainedLaunchWarning(snapshot, peer, ctx.definition);
9392
+ if (warning)
9393
+ console.error(warning);
8084
9394
  const reply = await hub.request({ t: "start", peer, args: { mode: "headless", ...one["--model"] ? { model: one["--model"] } : {}, ...one["--session-id"] ? { sessionId: one["--session-id"] } : {} }, operationId: process.env.AGENTHUB_RECOVERY_OPERATION }, 35000);
8085
9395
  if (!reply.ok)
8086
9396
  fail(reply.error);
@@ -8089,14 +9399,15 @@ async function runQwenEntry(ctx) {
8089
9399
  hub.close();
8090
9400
  }
8091
9401
  }
9402
+ var init_native_cli_entry_qwen = () => {};
8092
9403
 
8093
9404
  // src/adapters/definitions/native-cli-entry-kimi.ts
8094
- import { resolve as resolve10 } from "path";
8095
- import { spawnSync as spawnSync4 } from "child_process";
9405
+ import { resolve as resolve13 } from "path";
9406
+ import { spawnSync as spawnSync5 } from "child_process";
8096
9407
  async function runKimiEntry(ctx) {
8097
9408
  const peer = ctx.instance ?? "kimi";
8098
9409
  const { args, cwd, stateDir, cliDir, unattendedEnv, projectConfig, connect, fail, takeFlags } = ctx;
8099
- const { launcherPreview, startModeOf, buildKimiTuiLaunch, configureKimiMcp, nativeLaunchEnv, childEnv } = ctx.helpers;
9410
+ const { launcherPreview, readControl, startModeOf, buildKimiTuiLaunch, configureKimiMcp, nativeLaunchEnv, childEnv } = ctx.helpers;
8100
9411
  if (args.includes("--print-command") || args.includes("--dry-run")) {
8101
9412
  try {
8102
9413
  return console.log(JSON.stringify(launcherPreview("kimi", args, cwd, stateDir, unattendedEnv), null, 2));
@@ -8105,7 +9416,8 @@ async function runKimiEntry(ctx) {
8105
9416
  }
8106
9417
  }
8107
9418
  const { one, rest } = takeFlags(args, ["--mode", "--model"], []);
8108
- const mode = one["--mode"] ?? (ctx.instance ? "headless" : startModeOf(projectConfig().peers, "kimi")), model = one["--model"];
9419
+ const acceptedMode = readControl(stateDir)?.startModes?.[peer];
9420
+ const mode = one["--mode"] ?? (ctx.instance ? "headless" : acceptedMode ?? startModeOf(projectConfig().peers, "kimi")), model = one["--model"];
8109
9421
  if (ctx.instance && mode !== "headless")
8110
9422
  fail("extra Kimi instances require headless mode");
8111
9423
  if (rest.length || !["tui", "headless"].includes(mode))
@@ -8115,6 +9427,10 @@ async function runKimiEntry(ctx) {
8115
9427
  fail("Kimi's TUI needs a terminal for these options; run in a terminal or add --mode headless");
8116
9428
  const hub = await connect();
8117
9429
  try {
9430
+ const snapshot = (await hub.request({ t: "status" })).status;
9431
+ const warning = retainedLaunchWarning(snapshot, peer, ctx.definition);
9432
+ if (warning)
9433
+ console.error(warning);
8118
9434
  const reply = await hub.request({ t: "peer_start", peer }, 35000);
8119
9435
  if (!reply.ok)
8120
9436
  fail(reply.error);
@@ -8126,25 +9442,29 @@ async function runKimiEntry(ctx) {
8126
9442
  const hub = await connect();
8127
9443
  try {
8128
9444
  if (mode === "tui") {
8129
- const launch = buildKimiTuiLaunch(projectConfig().kimi_cmd, model);
8130
9445
  const snapshot = (await hub.request({ t: "status" })).status;
8131
9446
  const rostered = rosterInStatus(snapshot, peer);
8132
9447
  if (rostered === undefined)
8133
9448
  fail("cannot read kimi's roster membership from the running hub; upgrade or restart it, then retry (nothing was started)");
8134
9449
  if (!rostered)
8135
9450
  fail("kimi is off roster; add it with ahub roster add kimi");
9451
+ const warning = retainedLaunchWarning(snapshot, peer, ctx.definition);
9452
+ if (warning)
9453
+ console.error(warning);
8136
9454
  const owner = snapshot?.peers?.[peer];
8137
9455
  if (owner && !["offline"].includes(owner.state) && !owner.pullOnly)
8138
9456
  fail("Kimi is running headless; run ahub stop kimi before starting its TUI");
8139
9457
  const admission = await hub.request({ t: "start", peer, args: { mode: "tui" }, operationId: process.env.AGENTHUB_RECOVERY_OPERATION });
8140
9458
  if (!admission.ok)
8141
9459
  fail(admission.error);
9460
+ const runningCommand = admission.retainedLaunchConfig?.kimi_cmd;
9461
+ const launch = buildKimiTuiLaunch(Array.isArray(runningCommand) && runningCommand.every((arg) => typeof arg === "string") ? runningCommand : projectConfig().kimi_cmd, model);
8142
9462
  let result;
8143
9463
  try {
8144
- const written = configureKimiMcp(cwd, stateDir, bundleInStatus(snapshot) ?? resolve10(cliDir, "../../plugins/agent-hub/server.js"), "tui");
9464
+ const written = configureKimiMcp(cwd, stateDir, bundleInStatus(snapshot) ?? resolve13(cliDir, "../../plugins/agent-hub/server.js"), "tui");
8145
9465
  if (written.tracked)
8146
9466
  console.error(".kimi-code/mcp.json is tracked; the agent-hub entry contains machine paths. Review this local change before committing it.");
8147
- result = spawnSync4(launch.cmd, launch.args, { cwd, stdio: "inherit", env: { ...nativeLaunchEnv("kimi", childEnv()), AGENTHUB_STATE_DIR: stateDir, AGENTHUB_PROJECT_DIR: cwd } });
9467
+ result = spawnSync5(launch.cmd, launch.args, { cwd, stdio: "inherit", env: { ...nativeLaunchEnv("kimi", childEnv()), AGENTHUB_STATE_DIR: stateDir, AGENTHUB_PROJECT_DIR: cwd } });
8148
9468
  } finally {
8149
9469
  if (admission.admissionId)
8150
9470
  await hub.request({ t: "kimi_launch_end", admissionId: admission.admissionId }).catch(() => {});
@@ -8153,11 +9473,15 @@ async function runKimiEntry(ctx) {
8153
9473
  fail(`cannot run ${launch.cmd}: ${result.error.message}`);
8154
9474
  process.exit(result.status ?? 1);
8155
9475
  }
8156
- const owner = (await hub.request({ t: "status" })).status?.peers?.[peer];
9476
+ const snapshot = (await hub.request({ t: "status" })).status;
9477
+ const warning = retainedLaunchWarning(snapshot, peer, ctx.definition);
9478
+ if (warning)
9479
+ console.error(warning);
9480
+ const owner = snapshot?.peers?.[peer];
8157
9481
  if (owner?.pullOnly && owner.state !== "offline")
8158
9482
  fail("Kimi runs in a terminal; end it there before starting a headless one");
8159
9483
  if (!ctx.instance)
8160
- configureKimiMcp(cwd, stateDir, resolve10(cliDir, "../../plugins/agent-hub/server.js"), "headless");
9484
+ configureKimiMcp(cwd, stateDir, resolve13(cliDir, "../../plugins/agent-hub/server.js"), "headless");
8161
9485
  const res = await hub.request({ t: "start", peer, args: { mode: "headless", model }, operationId: process.env.AGENTHUB_RECOVERY_OPERATION });
8162
9486
  if (!res.ok)
8163
9487
  fail(res.error);
@@ -8224,7 +9548,8 @@ async function runPiEntry(ctx) {
8224
9548
  }
8225
9549
  }
8226
9550
  const flags = parsePiFlags(args, fail);
8227
- const options = { ...flags, mode: flags.mode ?? (ctx.instance ? "headless" : startModeOf(projectConfig().peers, "pi")) };
9551
+ const acceptedMode = ctx.helpers.readControl(stateDir)?.startModes?.[peer];
9552
+ const options = { ...flags, mode: flags.mode ?? (ctx.instance ? "headless" : acceptedMode ?? startModeOf(projectConfig().peers, "pi")) };
8228
9553
  if (ctx.instance && options.mode !== "headless")
8229
9554
  fail("extra Pi instances require headless mode");
8230
9555
  if (flags.mode === undefined && options.mode === "tui" && !(process.stdin.isTTY && process.stdout.isTTY)) {
@@ -8232,6 +9557,10 @@ async function runPiEntry(ctx) {
8232
9557
  fail("Pi's TUI needs a terminal, and these options are not passed to one the hub opens: run this in a terminal, or add --mode headless");
8233
9558
  const hub = await connect();
8234
9559
  try {
9560
+ const snapshot = (await hub.request({ t: "status" })).status;
9561
+ const warning = retainedLaunchWarning(snapshot, peer, ctx.definition);
9562
+ if (warning)
9563
+ console.error(warning);
8235
9564
  const reply = await hub.request({ t: "peer_start", peer }, 35000);
8236
9565
  if (reply.ok !== true) {
8237
9566
  const error = String(reply.error ?? "pi start refused");
@@ -8247,6 +9576,10 @@ async function runPiEntry(ctx) {
8247
9576
  if (recovering && before?.instanceId)
8248
9577
  await recordTerminalLaunch(peer, cwd, stateDir, before.instanceId);
8249
9578
  const hub = await connect();
9579
+ const snapshot = (await hub.request({ t: "status" })).status;
9580
+ const warning = retainedLaunchWarning(snapshot, peer, ctx.definition);
9581
+ if (warning)
9582
+ console.error(warning);
8250
9583
  const res = await hub.request({ t: "start", peer, args: options, operationId: process.env.AGENTHUB_RECOVERY_OPERATION });
8251
9584
  hub.close();
8252
9585
  if (!res.ok)
@@ -8280,21 +9613,31 @@ async function runLocalEntry(ctx) {
8280
9613
  if (args.some((a, i) => i % 2 === 0 && !["--route", "--model"].includes(a)))
8281
9614
  fail("usage: ahub local [--route <id> | --model <id>]");
8282
9615
  const hub = await connect();
9616
+ const snapshot = (await hub.request({ t: "status" })).status;
9617
+ const warning = retainedLaunchWarning(snapshot, peer, ctx.definition);
9618
+ if (warning)
9619
+ console.error(warning);
8283
9620
  const res = await hub.request({ t: "start", peer, args: { route: opt("--route"), model: opt("--model") }, operationId: process.env.AGENTHUB_RECOVERY_OPERATION });
8284
9621
  hub.close();
8285
9622
  if (!res.ok)
8286
9623
  fail(res.error);
8287
9624
  console.log(res.already ? `${peer} is already attached` : `${peer} attached on ${res.model}. Give it work with: ahub say @${peer} "..."`);
8288
9625
  }
9626
+ var init_native_cli_entry_local = () => {};
8289
9627
 
8290
9628
  // src/adapters/definitions/native-cli-entry.ts
8291
9629
  var builtinCliEntries;
8292
9630
  var init_native_cli_entry = __esm(() => {
9631
+ init_entry();
8293
9632
  init_native_cli_entry_claude();
9633
+ init_native_cli_entry_codex();
8294
9634
  init_native_cli_entry_jcode();
9635
+ init_native_cli_entry_qwen();
8295
9636
  init_native_cli_entry_kimi();
8296
9637
  init_native_cli_entry_pi();
9638
+ init_native_cli_entry_local();
8297
9639
  builtinCliEntries = Object.freeze([
9640
+ { command: "agy", lifecycleAdmission: true, execute: runAgyEntry },
8298
9641
  { command: "claude", lifecycleAdmission: true, execute: runClaudeEntry },
8299
9642
  { command: "codex", lifecycleAdmission: true, execute: runCodexEntry },
8300
9643
  { command: "jcode", lifecycleAdmission: true, execute: runJcodeEntry },
@@ -8305,37 +9648,9 @@ var init_native_cli_entry = __esm(() => {
8305
9648
  ]);
8306
9649
  });
8307
9650
 
8308
- // src/adapters/definitions/native-cli-preview-common.ts
8309
- function publicArg(arg) {
8310
- const flags = new Set(["--settings", "--model", "--mcp-config", "--session-id", "--session-file", "--resume", "--continue", "--print", "--safe", "--new"]);
8311
- const key = arg.split("=", 1)[0];
8312
- return flags.has(key) ? arg.includes("=") ? key + "=" + REDACTED : key : REDACTED;
8313
- }
8314
- function nativeCommand(command, trusted) {
8315
- return command.map((value, i) => value === trusted[i] ? value : REDACTED);
8316
- }
8317
- function previewState(args, baseEnvNames) {
8318
- const unresolved = [];
8319
- const envNames = [...baseEnvNames];
8320
- const value = (flag) => {
8321
- const at = args.indexOf(flag);
8322
- if (at < 0)
8323
- return;
8324
- const next = args[at + 1];
8325
- if (!next || next.startsWith("--"))
8326
- throw new Error("missing option value");
8327
- return next;
8328
- };
8329
- return { unresolved, envNames, value };
8330
- }
8331
- function finishPreview(ctx, launch, envNames, unresolved, metadata, settings) {
8332
- return { tool: ctx.tool, ...metadata ?? {}, cmd: launch.cmd, args: launch.args, settings, envNames: [...new Set(envNames)].sort(), ...launch.warning ? { warning: launch.warning } : {}, unresolved, redaction: "Arbitrary user arguments, settings, commands and configured executable overrides are redacted. Environment values are never displayed.", needs: ["Native executable, accounts and runtime readiness are not checked."] };
8333
- }
8334
- var REDACTED = "[redacted]", UNRESOLVED = "[unresolved]";
8335
-
8336
9651
  // src/adapters/definitions/native-cli-preview-claude.ts
8337
- import { readFileSync as readFileSync6 } from "fs";
8338
- import { join as join13 } from "path";
9652
+ import { readFileSync as readFileSync8 } from "fs";
9653
+ import { join as join16 } from "path";
8339
9654
  function previewClaude(ctx) {
8340
9655
  const { args, cwd, stateDir, cliDir, unattended, config } = ctx;
8341
9656
  const { buildLaunch, claudeObservationHooks } = ctx.helpers;
@@ -8343,13 +9658,13 @@ function previewClaude(ctx) {
8343
9658
  let envNames = state.envNames;
8344
9659
  let launch;
8345
9660
  let original;
8346
- for (const file of [join13(cwd, ".claude/settings.local.json"), join13(cwd, ".claude/settings.json"), join13(process.env.HOME ?? "", ".claude/settings.json")]) {
9661
+ for (const file of [join16(cwd, ".claude/settings.local.json"), join16(cwd, ".claude/settings.json"), join16(process.env.HOME ?? "", ".claude/settings.json")]) {
8347
9662
  try {
8348
- original ??= JSON.parse(readFileSync6(file, "utf8")).statusLine;
9663
+ original ??= JSON.parse(readFileSync8(file, "utf8")).statusLine;
8349
9664
  } catch {}
8350
9665
  }
8351
- const facts = claudeObservationHooks(config, { script: join13(cliDir, "facts-hook.ts"), stateDir });
8352
- launch = buildLaunch("claude", args, { preview: true, unattended, statusLine: { script: join13(cliDir, "statusline-tee.ts"), stateDir, ...original ? { original: { command: original.command ? REDACTED : "", ...typeof original.refreshInterval === "number" && Number.isFinite(original.refreshInterval) ? { refreshInterval: original.refreshInterval } : {}, ...typeof original.padding === "number" && Number.isFinite(original.padding) ? { padding: original.padding } : {} } } : {} }, ...facts ? { facts } : {} });
9666
+ const facts = claudeObservationHooks(config, { script: join16(cliDir, "facts-hook.ts"), stateDir });
9667
+ launch = buildLaunch("claude", args, { preview: true, unattended, statusLine: { script: join16(cliDir, "statusline-tee.ts"), stateDir, ...original ? { original: { command: original.command ? REDACTED : "", ...typeof original.refreshInterval === "number" && Number.isFinite(original.refreshInterval) ? { refreshInterval: original.refreshInterval } : {}, ...typeof original.padding === "number" && Number.isFinite(original.padding) ? { padding: original.padding } : {} } } : {} }, ...facts ? { facts } : {} });
8353
9668
  const safeArgs = args.map((arg, i) => {
8354
9669
  if (args[i - 1] === "--settings")
8355
9670
  return "{}";
@@ -8360,7 +9675,7 @@ function previewClaude(ctx) {
8360
9675
  return publicArg(arg);
8361
9676
  });
8362
9677
  const publicLaunch = buildLaunch("claude", safeArgs, { preview: true, unattended, statusLine: {
8363
- script: join13(cliDir, "statusline-tee.ts"),
9678
+ script: join16(cliDir, "statusline-tee.ts"),
8364
9679
  stateDir,
8365
9680
  ...original ? { original: { command: original.command ? REDACTED : "", ...typeof original.refreshInterval === "number" && Number.isFinite(original.refreshInterval) ? { refreshInterval: original.refreshInterval } : {}, ...typeof original.padding === "number" && Number.isFinite(original.padding) ? { padding: original.padding } : {} } } : {}
8366
9681
  }, facts });
@@ -8393,7 +9708,7 @@ function previewCodex(ctx) {
8393
9708
  var init_native_cli_preview_codex = () => {};
8394
9709
 
8395
9710
  // src/adapters/definitions/native-cli-preview-kimi.ts
8396
- import { join as join14 } from "path";
9711
+ import { join as join17 } from "path";
8397
9712
  function previewKimi(ctx) {
8398
9713
  const { args, cwd, stateDir, cliDir, config } = ctx;
8399
9714
  const { childEnv, startModeOf, buildKimiLaunch, buildKimiTuiLaunch, nativeLaunchEnv, kimiMcpPlan } = ctx.helpers;
@@ -8416,14 +9731,14 @@ function previewKimi(ctx) {
8416
9731
  launch = { ...native, cmd: native.cmd === "kimi" ? "kimi" : REDACTED, args: native.args.map((arg) => arg === "--model" || arg === REDACTED ? arg : REDACTED) };
8417
9732
  envNames = Object.keys(nativeLaunchEnv("kimi", childEnv())).concat(["AGENTHUB_STATE_DIR", "AGENTHUB_PROJECT_DIR"]);
8418
9733
  }
8419
- const { file, action, changed, tracked } = kimiMcpPlan(cwd, stateDir, join14(cliDir, "../../plugins/agent-hub/server.js"), mode);
9734
+ const { file, action, changed, tracked } = kimiMcpPlan(cwd, stateDir, join17(cliDir, "../../plugins/agent-hub/server.js"), mode);
8420
9735
  kimiMetadata = { mode, mcp: { file, action, changed, tracked } };
8421
9736
  return finishPreview(ctx, launch, envNames, unresolved, kimiMetadata);
8422
9737
  }
8423
9738
  var init_native_cli_preview_kimi = () => {};
8424
9739
 
8425
9740
  // src/adapters/definitions/native-cli-preview-pi.ts
8426
- import { join as join15, resolve as resolve11 } from "path";
9741
+ import { join as join18, resolve as resolve14 } from "path";
8427
9742
  function previewPi(ctx) {
8428
9743
  const { args, stateDir, cliDir, config } = ctx;
8429
9744
  const { relayModelIds, buildPiLaunch, startModeOf } = ctx.helpers;
@@ -8458,7 +9773,7 @@ function previewPi(ctx) {
8458
9773
  tools: [],
8459
9774
  preamble: REDACTED,
8460
9775
  maxSteps: config.pi.max_steps
8461
- }, {}, resolve11(join15(cliDir, "../pi/extension.ts")), UNRESOLVED, UNRESOLVED);
9776
+ }, {}, resolve14(join18(cliDir, "../pi/extension.ts")), UNRESOLVED, UNRESOLVED);
8462
9777
  launch = pi;
8463
9778
  envNames = Object.keys(pi.env).concat(["PATH", "HOME", "USER", "SHELL", "TMPDIR", "TERM", "TERM_PROGRAM", "LANG", "LC_ALL", "LC_CTYPE", "NO_COLOR", "CODEX_HOME"].filter((key) => process.env[key] !== undefined));
8464
9779
  if (mode === "tui")
@@ -8471,7 +9786,7 @@ function previewPi(ctx) {
8471
9786
  var init_native_cli_preview_pi = () => {};
8472
9787
 
8473
9788
  // src/adapters/definitions/native-cli-preview-qwen.ts
8474
- import { join as join16 } from "path";
9789
+ import { join as join19 } from "path";
8475
9790
  function previewQwen(ctx) {
8476
9791
  const { tool, args, cwd, stateDir, cliDir, config } = ctx;
8477
9792
  const state = previewState(args, ctx.baseEnvNames), { unresolved, value } = state;
@@ -8494,7 +9809,7 @@ function previewQwen(ctx) {
8494
9809
  form: "headless",
8495
9810
  projectRoot: cwd,
8496
9811
  stateDir,
8497
- entrypoint: join16(cliDir, "main.js"),
9812
+ entrypoint: join19(cliDir, "main.js"),
8498
9813
  userArgs: [],
8499
9814
  unattended: false,
8500
9815
  env: {},
@@ -8510,23 +9825,23 @@ var init_native_cli_preview_qwen = () => {};
8510
9825
  // src/adapters/definitions/native-cli-preview-jcode.ts
8511
9826
  function previewJcode(ctx) {
8512
9827
  const { args, config } = ctx;
8513
- const { jcodeCommand, qwenSessionId } = ctx.helpers;
9828
+ const { jcodeCommand, jcodeTuiCommand, qwenSessionId, startModeOf } = ctx.helpers;
8514
9829
  const state = previewState(args, ctx.baseEnvNames), { unresolved, value } = state;
8515
9830
  let envNames = state.envNames;
8516
9831
  let launch;
8517
- const mode = value("--mode") ?? "headless", model = value("--model"), profile = value("--provider-profile"), session = value("--session-id");
8518
- if (mode !== "headless")
8519
- throw new Error("Jcode TUI driving is unverified");
9832
+ const mode = value("--mode") ?? startModeOf(config.peers, "jcode"), model = value("--model"), profile = value("--provider-profile"), session = value("--session-id");
9833
+ if (mode !== "headless" && mode !== "tui")
9834
+ throw new Error("invalid Jcode start mode");
8520
9835
  for (let i = 0;i < args.length; i += 2)
8521
9836
  if (!["--model", "--mode", "--provider-profile", "--session-id"].includes(args[i]) || !args[i + 1])
8522
9837
  throw new Error("unknown Jcode option");
8523
9838
  if (session !== undefined && !qwenSessionId(session))
8524
9839
  throw new Error("invalid Jcode session identity");
8525
- const cmd = jcodeCommand(nativeCommand(config.jcode_cmd, ["jcode"]), "acp", UNRESOLVED, UNRESOLVED, model ? REDACTED : undefined, profile || config.jcode_provider_profile ? REDACTED : undefined);
9840
+ const cmd = mode === "tui" ? jcodeTuiCommand(nativeCommand(config.jcode_cmd, ["jcode"]), UNRESOLVED, UNRESOLVED, model ? REDACTED : undefined, profile || config.jcode_provider_profile ? REDACTED : undefined) : jcodeCommand(nativeCommand(config.jcode_cmd, ["jcode"]), "acp", UNRESOLVED, UNRESOLVED, model ? REDACTED : undefined, profile || config.jcode_provider_profile ? REDACTED : undefined);
8526
9841
  launch = { cmd: cmd[0], args: cmd.slice(1) };
8527
9842
  unresolved.push({ field: "ownedDaemon", reason: "private daemon/socket and source-pinned native identity verified at start; preview starts nothing" });
8528
9843
  unresolved.push({ field: "hubTools", reason: "project MCP bridge required; native tool-call receipt remains unverified" });
8529
- return finishPreview(ctx, launch, envNames, unresolved, undefined);
9844
+ return { ...finishPreview(ctx, launch, envNames, unresolved), mode };
8530
9845
  }
8531
9846
  var init_native_cli_preview_jcode = () => {};
8532
9847
 
@@ -8556,7 +9871,7 @@ function kimiAcpCommand(command, model) {
8556
9871
  }
8557
9872
 
8558
9873
  // src/adapters/definitions/native-daemon-policy.ts
8559
- var claudeRecoveryLaunch = (instance) => ({ kind: "claude-channel", peer: instance }), kimiRecoveryLaunch = (instance) => ({ kind: "kimi-tui", peer: instance, mode: "tui" }), claudeRetainedTerminalNotice = (instance) => `Stop ${instance}'s existing work in its retained terminal; removal detaches the hub connection and does not stop that terminal's work.`, piRetainedTerminalNotice = (instance, form) => form === "tui" ? claudeRetainedTerminalNotice(instance) : undefined, codexRetainedTerminalNotice = (_instance, _form, resumeCommand) => `Check codex's existing terminal and stop any remaining work. Its app-server and proxy stay available, but Codex 0.146.0 exits its TUI on disconnect and does not reconnect automatically; after ahub roster add codex, resume the previous session with ${resumeCommand}.`, codexRosterReturnNotice = (resumeCommand) => `Codex 0.146.0 needs a manual return: resume the previous native session with ${resumeCommand}.`, jcodeStartPreflight = (request) => request.form !== undefined && request.form !== "headless" ? "Jcode native TUI driving is unverified; use headless" : undefined, claudeObservedVersion = (context) => context.observedVersion, codexObservedVersion = (context) => context.peer?.version ?? context.observedVersion;
9874
+ var claudeRecoveryLaunch = (instance) => ({ kind: "claude-channel", peer: instance }), kimiRecoveryLaunch = (instance) => ({ kind: "kimi-tui", peer: instance, mode: "tui" }), claudeRetainedTerminalNotice = (instance) => `Stop ${instance}'s existing work in its retained terminal; removal detaches the hub connection and does not stop that terminal's work.`, piRetainedTerminalNotice = (instance, form) => form === "tui" ? claudeRetainedTerminalNotice(instance) : undefined, codexRetainedTerminalNotice = (_instance, _form, resumeCommand) => `Check codex's existing terminal and stop any remaining work. Its app-server and proxy stay available, but Codex 0.146.0 exits its TUI on disconnect and does not reconnect automatically; after ahub roster add codex, resume the previous session with ${resumeCommand}.`, codexRosterReturnNotice = (resumeCommand) => `Codex 0.146.0 needs a manual return: resume the previous native session with ${resumeCommand}.`, jcodeStartPreflight = (request) => request.form !== undefined && request.form !== "headless" && request.form !== "tui" ? "invalid Jcode start mode" : undefined, claudeObservedVersion = (context) => context.observedVersion, codexObservedVersion = (context) => context.peer?.version ?? context.observedVersion;
8560
9875
 
8561
9876
  // src/adapters/definitions/native-config-policy.ts
8562
9877
  function piConfigSchema(v) {
@@ -8602,8 +9917,8 @@ var init_native_config_policy = __esm(() => {
8602
9917
  });
8603
9918
 
8604
9919
  // src/adapters/definitions/native-policy-aux.ts
8605
- import { closeSync as closeSync3, constants as constants3, fstatSync as fstatSync2, openSync as openSync3, readdirSync as readdirSync3, readFileSync as readFileSync7 } from "fs";
8606
- import { isAbsolute as isAbsolute5, join as join17, normalize } from "path";
9920
+ import { closeSync as closeSync6, constants as constants5, fstatSync as fstatSync5, openSync as openSync6, readdirSync as readdirSync3, readFileSync as readFileSync9 } from "fs";
9921
+ import { isAbsolute as isAbsolute7, join as join20, normalize } from "path";
8607
9922
  function claudeCrashPlan(_meta) {
8608
9923
  return { how: "claude: the Claude Code plugin reconnects by itself while that session is still open" };
8609
9924
  }
@@ -8648,13 +9963,13 @@ function localCrashPlan(meta) {
8648
9963
  }
8649
9964
  function claudePlugins(root, cwd) {
8650
9965
  const enabled = new Map;
8651
- for (const file of [join17(root, "settings.json"), ...cwd ? [join17(cwd, ".claude", "settings.json"), join17(cwd, ".claude", "settings.local.json")] : []]) {
9966
+ for (const file of [join20(root, "settings.json"), ...cwd ? [join20(cwd, ".claude", "settings.json"), join20(cwd, ".claude", "settings.local.json")] : []]) {
8652
9967
  const map = json(file)?.enabledPlugins;
8653
9968
  if (map && typeof map === "object")
8654
9969
  for (const [key, on] of Object.entries(map))
8655
9970
  enabled.set(key, on === true);
8656
9971
  }
8657
- const installed = json(join17(root, "plugins", "installed_plugins.json"))?.plugins ?? {};
9972
+ const installed = json(join20(root, "plugins", "installed_plugins.json"))?.plugins ?? {};
8658
9973
  const out = [];
8659
9974
  for (const [key, on] of enabled) {
8660
9975
  if (!on)
@@ -8663,12 +9978,12 @@ function claudePlugins(root, cwd) {
8663
9978
  const entries = Array.isArray(installed[key]) ? installed[key] : [];
8664
9979
  const entry = entries.find((e) => e?.scope === "user") ?? entries.find((e) => e?.projectPath === cwd);
8665
9980
  if (name && entry)
8666
- out.push({ name, version: version(entry.version) });
9981
+ out.push({ name, version: version2(entry.version) });
8667
9982
  }
8668
9983
  return out;
8669
9984
  }
8670
9985
  function codexPlugins(root) {
8671
- const config = text(join17(root, "config.toml"));
9986
+ const config = text(join20(root, "config.toml"));
8672
9987
  if (config === undefined)
8673
9988
  return [];
8674
9989
  const found = new Map, disabled = new Set;
@@ -8687,9 +10002,9 @@ function codexPlugins(root) {
8687
10002
  continue;
8688
10003
  let versions = [];
8689
10004
  try {
8690
- versions = readdirSync3(join17(root, "plugins", "cache", market, plugin));
10005
+ versions = readdirSync3(join20(root, "plugins", "cache", market, plugin));
8691
10006
  } catch {}
8692
- out.push({ name, version: versions.length === 1 ? version(versions[0]) : null });
10007
+ out.push({ name, version: versions.length === 1 ? version2(versions[0]) : null });
8693
10008
  }
8694
10009
  return out;
8695
10010
  }
@@ -8702,21 +10017,21 @@ function parsePiContinuity(value) {
8702
10017
  return { sessionId: value.sessionId, ...value.sessionFile === undefined ? {} : { sessionFile: value.sessionFile }, ...value.backend === undefined ? {} : { backend: value.backend }, ...value.model === undefined ? {} : { model: value.model } };
8703
10018
  }
8704
10019
  function absoluteContinuityPath(value) {
8705
- return typeof value === "string" && value.length > 0 && value.length <= 4096 && !/[\x00-\x1f\x7f]/.test(value) && isAbsolute5(value) && normalize(value) === value;
10020
+ return typeof value === "string" && value.length > 0 && value.length <= 4096 && !/[\x00-\x1f\x7f]/.test(value) && isAbsolute7(value) && normalize(value) === value;
8706
10021
  }
8707
10022
  var str3 = (value) => typeof value === "string" && value ? value : undefined, PLUGINS, CLAUDE_ONLY, text = (file) => {
8708
10023
  let fd;
8709
10024
  try {
8710
- fd = openSync3(file, constants3.O_RDONLY | constants3.O_NONBLOCK);
8711
- const st = fstatSync2(fd);
10025
+ fd = openSync6(file, constants5.O_RDONLY | constants5.O_NONBLOCK);
10026
+ const st = fstatSync5(fd);
8712
10027
  if (!st.isFile() || st.size > 1024 * 1024)
8713
10028
  return;
8714
- return readFileSync7(fd, "utf8");
10029
+ return readFileSync9(fd, "utf8");
8715
10030
  } catch {
8716
10031
  return;
8717
10032
  } finally {
8718
10033
  if (fd !== undefined)
8719
- closeSync3(fd);
10034
+ closeSync6(fd);
8720
10035
  }
8721
10036
  }, json = (file) => {
8722
10037
  try {
@@ -8725,7 +10040,7 @@ var str3 = (value) => typeof value === "string" && value ? value : undefined, PL
8725
10040
  } catch {
8726
10041
  return;
8727
10042
  }
8728
- }, version = (v) => typeof v === "string" && /^[0-9A-Za-z.+_-]{1,40}$/.test(v) ? v : null, claudePluginInventory = (ctx) => claudePlugins(ctx.env.CLAUDE_CONFIG_DIR?.trim() || join17(ctx.home, ".claude"), ctx.cwd), codexPluginInventory = (ctx) => codexPlugins(ctx.env.CODEX_HOME?.trim() || join17(ctx.home, ".codex")), codexToolSemantic = (name, _command) => name.toLowerCase() === "filechange" ? "edit" : undefined;
10043
+ }, version2 = (v) => typeof v === "string" && /^[0-9A-Za-z.+_-]{1,40}$/.test(v) ? v : null, claudePluginInventory = (ctx) => claudePlugins(ctx.env.CLAUDE_CONFIG_DIR?.trim() || join20(ctx.home, ".claude"), ctx.cwd), codexPluginInventory = (ctx) => codexPlugins(ctx.env.CODEX_HOME?.trim() || join20(ctx.home, ".codex")), codexToolSemantic = (name, _command) => name.toLowerCase() === "filechange" ? "edit" : undefined;
8729
10044
  var init_native_policy_aux = __esm(() => {
8730
10045
  PLUGINS = new Map([["claude-mem", "claude-mem"], ["ponytail", "ponytail"], ["open-code-review", "open-code-review"]]);
8731
10046
  CLAUDE_ONLY = new Map([["codex@openai-codex", "codex-plugin"]]);
@@ -8771,7 +10086,7 @@ var init_launch2 = __esm(() => {
8771
10086
  });
8772
10087
 
8773
10088
  // src/cli/shell-quote.ts
8774
- function shellQuote(value) {
10089
+ function shellQuote3(value) {
8775
10090
  return `'${value.replace(/'/g, `'\\''`)}'`;
8776
10091
  }
8777
10092
 
@@ -8786,26 +10101,26 @@ function nativeLaunchArguments(tool, ctx, policy) {
8786
10101
  }
8787
10102
  function nativeLaunchPlan(launch, ctx) {
8788
10103
  const argv = [launch.cmd, ...launch.args];
8789
- return { ...launch, metadata: { packageEntrypoint: ctx.entrypoint, argv, env: {}, command: argv.map(shellQuote).join(" ") } };
10104
+ return { ...launch, metadata: { packageEntrypoint: ctx.entrypoint, argv, env: {}, command: argv.map(shellQuote3).join(" ") } };
8790
10105
  }
8791
10106
  var init_native_cli_launch_common = __esm(() => {
8792
10107
  init_launch2();
8793
10108
  });
8794
10109
 
8795
10110
  // src/adapters/definitions/native-cli-claude-launch.ts
8796
- import { mkdirSync as mkdirSync5, writeFileSync as writeFileSync3, renameSync, unlinkSync, readFileSync as readFileSync8 } from "fs";
8797
- import { randomUUID as randomUUID6 } from "crypto";
8798
- import { resolve as resolve12, join as join18 } from "path";
10111
+ import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync5, renameSync as renameSync2, unlinkSync as unlinkSync2, readFileSync as readFileSync10 } from "fs";
10112
+ import { randomUUID as randomUUID7 } from "crypto";
10113
+ import { resolve as resolve15, join as join21 } from "path";
8799
10114
  function claudeChannel(userArgs) {
8800
- const bundle = resolve12(import.meta.dir, "../../../plugins/agent-hub/server.js");
10115
+ const bundle = resolve15(import.meta.dir, "../../../plugins/agent-hub/server.js");
8801
10116
  for (let i = 0;i < userArgs.length; i++) {
8802
10117
  const arg = userArgs[i];
8803
10118
  const path = arg === "--mcp-config" ? userArgs[i + 1] : arg.startsWith("--mcp-config=") ? arg.slice("--mcp-config=".length) : undefined;
8804
10119
  if (!path)
8805
10120
  continue;
8806
10121
  try {
8807
- const server = JSON.parse(readFileSync8(resolve12(path), "utf8")).mcpServers?.["agent-hub"];
8808
- if (server?.command === "bun" && Array.isArray(server.args) && server.args.some((value) => typeof value === "string" && resolve12(value) === bundle))
10122
+ const server = JSON.parse(readFileSync10(resolve15(path), "utf8")).mcpServers?.["agent-hub"];
10123
+ if (server?.command === "bun" && Array.isArray(server.args) && server.args.some((value) => typeof value === "string" && resolve15(value) === bundle))
8809
10124
  return "server:agent-hub";
8810
10125
  } catch {}
8811
10126
  }
@@ -8828,7 +10143,7 @@ function claudeNativeLaunch(ctx) {
8828
10143
  throw new Error("--settings requires a JSON object or readable file so the hub permission hook can be installed");
8829
10144
  let caller;
8830
10145
  try {
8831
- caller = JSON.parse(value.trim().startsWith("{") ? value : readFileSync8(resolve12(value), "utf8"));
10146
+ caller = JSON.parse(value.trim().startsWith("{") ? value : readFileSync10(resolve15(value), "utf8"));
8832
10147
  if (!caller || typeof caller !== "object" || Array.isArray(caller))
8833
10148
  throw new Error("not an object");
8834
10149
  } catch {
@@ -8857,14 +10172,14 @@ function claudeNativeLaunch(ctx) {
8857
10172
  const stateDir = ctx.facts?.stateDir ?? ctx.statusLine?.stateDir;
8858
10173
  if (!stateDir)
8859
10174
  throw new Error("Claude session settings require the hub state directory");
8860
- const file = join18(stateDir, `claude-settings-${randomUUID6()}.json`);
10175
+ const file = join21(stateDir, `claude-settings-${randomUUID7()}.json`);
8861
10176
  try {
8862
- mkdirSync5(stateDir, { recursive: true, mode: 448 });
8863
- writeFileSync3(`${file}.tmp`, settingsArgs[1], { mode: 384, flag: "wx" });
8864
- renameSync(`${file}.tmp`, file);
10177
+ mkdirSync7(stateDir, { recursive: true, mode: 448 });
10178
+ writeFileSync5(`${file}.tmp`, settingsArgs[1], { mode: 384, flag: "wx" });
10179
+ renameSync2(`${file}.tmp`, file);
8865
10180
  } catch {
8866
10181
  try {
8867
- unlinkSync(`${file}.tmp`);
10182
+ unlinkSync2(`${file}.tmp`);
8868
10183
  } catch {}
8869
10184
  throw new Error("cannot write private Claude session settings in the hub state directory");
8870
10185
  }
@@ -8914,11 +10229,11 @@ var init_native_cli_codex_launch = __esm(() => {
8914
10229
  });
8915
10230
 
8916
10231
  // src/hub/events.ts
8917
- import { appendFileSync, closeSync as closeSync4, existsSync as existsSync6, fstatSync as fstatSync3, openSync as openSync4, readFileSync as readFileSync9, readSync as readSync2 } from "fs";
10232
+ import { appendFileSync, closeSync as closeSync7, existsSync as existsSync7, fstatSync as fstatSync6, openSync as openSync7, readFileSync as readFileSync11, readSync as readSync4 } from "fs";
8918
10233
  function readEvents(file, since = 0) {
8919
- if (!existsSync6(file))
10234
+ if (!existsSync7(file))
8920
10235
  return [];
8921
- return readFileSync9(file, "utf8").split(`
10236
+ return readFileSync11(file, "utf8").split(`
8922
10237
  `).flatMap((line) => {
8923
10238
  if (!line.trim())
8924
10239
  return [];
@@ -8935,14 +10250,14 @@ var init_events = () => {};
8935
10250
 
8936
10251
  // src/cli/recovery-session-file.ts
8937
10252
  import { statSync as statSync4 } from "fs";
8938
- import { homedir as homedir3 } from "os";
8939
- import { join as join19 } from "path";
10253
+ import { homedir as homedir5 } from "os";
10254
+ import { join as join22 } from "path";
8940
10255
  function checkedSessionPath(binding, kind) {
8941
10256
  if (kind === "rollout")
8942
- return join19(binding.launch.env.CODEX_HOME ?? join19(homedir3(), ".codex"), "sessions");
8943
- const config = binding.launch.env.CLAUDE_CONFIG_DIR ?? join19(homedir3(), ".claude");
10257
+ return join22(binding.launch.env.CODEX_HOME ?? join22(homedir5(), ".codex"), "sessions");
10258
+ const config = binding.launch.env.CLAUDE_CONFIG_DIR ?? join22(homedir5(), ".claude");
8944
10259
  const slug = binding.projectRoot.replace(/[^a-zA-Z0-9]/g, "-");
8945
- return join19(config, "projects", slug, `${binding.sessionId}.jsonl`);
10260
+ return join22(config, "projects", slug, `${binding.sessionId}.jsonl`);
8946
10261
  }
8947
10262
  function recordedFileState(file) {
8948
10263
  try {
@@ -8955,17 +10270,17 @@ var init_recovery_session_file = () => {};
8955
10270
 
8956
10271
  // src/adapters/definitions/native-recovery.ts
8957
10272
  import { readdirSync as readdirSync4 } from "fs";
8958
- import { basename as basename4, join as join20 } from "path";
10273
+ import { basename as basename5, join as join23 } from "path";
8959
10274
  var rolloutState = (binding) => {
8960
10275
  try {
8961
- return readdirSync4(checkedSessionPath(binding, "rollout"), { recursive: true }).some((name) => String(name).endsWith(".jsonl") && basename4(String(name)).includes(binding.sessionId));
10276
+ return readdirSync4(checkedSessionPath(binding, "rollout"), { recursive: true }).some((name) => String(name).endsWith(".jsonl") && basename5(String(name)).includes(binding.sessionId));
8962
10277
  } catch (error) {
8963
10278
  return error.code === "ENOENT" ? false : "unknown";
8964
10279
  }
8965
10280
  }, transcriptState = (binding) => {
8966
10281
  const state = recordedFileState(checkedSessionPath(binding, "transcript"));
8967
10282
  return state === "unknown" ? "unknown" : state === "found";
8968
- }, startupArgs = (peer) => Object.fromEntries(["mode", "backend", "model", "route"].flatMap((key) => typeof peer.args?.[key] === "string" ? [[key, peer.args[key]]] : [])), text2 = (value) => typeof value === "string" && value.length > 0, object3 = (value) => value !== null && typeof value === "object" && !Array.isArray(value) ? value : {}, CODEX_RECOVERY_SESSION, CLAUDE_RECOVERY_SESSION, PI_RECOVERY_SESSION, KIMI_RECOVERY_SESSION, LOCAL_RECOVERY_SESSION, JCODE_RECOVERY_SESSION, QWEN_RECOVERY_SESSION;
10283
+ }, startupArgs = (peer) => Object.fromEntries(["mode", "backend", "model", "route"].flatMap((key) => typeof peer.args?.[key] === "string" ? [[key, peer.args[key]]] : [])), text2 = (value) => typeof value === "string" && value.length > 0, object5 = (value) => value !== null && typeof value === "object" && !Array.isArray(value) ? value : {}, CODEX_RECOVERY_SESSION, CLAUDE_RECOVERY_SESSION, PI_RECOVERY_SESSION, KIMI_RECOVERY_SESSION, LOCAL_RECOVERY_SESSION, JCODE_RECOVERY_SESSION, QWEN_RECOVERY_SESSION;
8969
10284
  var init_native_recovery = __esm(() => {
8970
10285
  init_events();
8971
10286
  init_recovery_session_file();
@@ -8979,7 +10294,7 @@ var init_native_recovery = __esm(() => {
8979
10294
  if (phase !== "plan")
8980
10295
  return false;
8981
10296
  let current, started = false, turned = false;
8982
- for (const e of readEvents(join20(stateDir, "events.jsonl"))) {
10297
+ for (const e of readEvents(join23(stateDir, "events.jsonl"))) {
8983
10298
  if (e.type === "native_thread" && e.peer === binding.peer) {
8984
10299
  current = e.thread;
8985
10300
  if (e.thread === binding.sessionId && e.fresh)
@@ -9020,7 +10335,7 @@ var init_native_recovery = __esm(() => {
9020
10335
  return args;
9021
10336
  },
9022
10337
  crashIdentity: (row) => {
9023
- const value = object3(row), args = object3(value.args), session = object3(value.session);
10338
+ const value = object5(row), args = object5(value.args), session = object5(value.session);
9024
10339
  const sessionId = [value.sessionId, value.agentSessionId, value.resumeSessionId, session.id, session.sessionId].find(text2);
9025
10340
  const sessionFile = [value.sessionFile, value.sessionPath, args.sessionFile, session.file, session.path].find(text2);
9026
10341
  return { ...sessionId ? { sessionId } : {}, ...sessionFile ? { sessionFile } : {} };
@@ -9076,8 +10391,8 @@ function claudeWindows(rateLimits) {
9076
10391
  }
9077
10392
 
9078
10393
  // src/adapters/definitions/builtin-metadata.ts
9079
- function compatibilityLaunch(kind, ctx) {
9080
- const mode = kind === "kimi" || kind === "pi" ? ["--mode", ctx.form] : [];
10394
+ function compatibilityLaunch(kind, ctx, explicitMode = false) {
10395
+ const mode = explicitMode || kind === "kimi" || kind === "pi" ? ["--mode", ctx.form] : [];
9081
10396
  const unattended = ctx.unattended && (kind === "claude" || kind === "codex") ? ["--unattended"] : [];
9082
10397
  const args = [
9083
10398
  ctx.entrypoint,
@@ -9094,12 +10409,12 @@ function compatibilityLaunch(kind, ctx) {
9094
10409
  ...ctx.env.CODEX_HOME !== undefined ? { CODEX_HOME: ctx.env.CODEX_HOME } : {},
9095
10410
  ...ctx.env.CLAUDE_CONFIG_DIR !== undefined ? { CLAUDE_CONFIG_DIR: ctx.env.CLAUDE_CONFIG_DIR } : {}
9096
10411
  };
9097
- const prefix = Object.entries(env).map(([name, value]) => `${name}=${shellQuote(value)}`);
10412
+ const prefix = Object.entries(env).map(([name, value]) => `${name}=${shellQuote3(value)}`);
9098
10413
  return {
9099
10414
  cmd: process.execPath,
9100
10415
  args,
9101
10416
  ...unattended.length ? { unattended: true } : {},
9102
- metadata: { packageEntrypoint: ctx.entrypoint, argv, env, command: [...prefix, ...argv.map(shellQuote)].join(" ") }
10417
+ metadata: { packageEntrypoint: ctx.entrypoint, argv, env, command: [...prefix, ...argv.map(shellQuote3)].join(" ") }
9103
10418
  };
9104
10419
  }
9105
10420
  function sessionId(field) {
@@ -9223,7 +10538,7 @@ var init_builtin_metadata = __esm(() => {
9223
10538
  session: { nativeRevert: true, idField: "threadId", currentId: sessionId("threadId"), configDirEnv: "CODEX_HOME", zeroTurnFresh: true, launchKind: "codex", launchRecord: true, ...CODEX_RECOVERY_SESSION, crashPlan: codexCrashPlan, unrecordedTerminal: "always" },
9224
10539
  turnEnd: "adapter",
9225
10540
  budget: { account: "codex", checkpoint: true, reader: { source: "codex rateLimits", read: codexWindows }, context: { source: "codex_token_usage", read: codexContext } },
9226
- cli: { command: "codex", retainedTerminalNotice: codexRetainedTerminalNotice, rosterReturnNotice: codexRosterReturnNotice, stopNotice: () => "this peer belongs to a native terminal; end it in its terminal", startWords: () => ["codex"] },
10541
+ cli: { command: "codex", launchConfigKeys: ["codex_bin"], retainedTerminalNotice: codexRetainedTerminalNotice, rosterReturnNotice: codexRosterReturnNotice, stopNotice: () => "this peer belongs to a native terminal; end it in its terminal", startWords: () => ["codex"] },
9227
10542
  display: CODEX_DISPLAY,
9228
10543
  hooks: { recallPlatform: "codex", recallSource: "codex", recallInjected: true, pluginInventory: codexPluginInventory, classifyTool: codexToolSemantic }
9229
10544
  },
@@ -9258,7 +10573,7 @@ var init_builtin_metadata = __esm(() => {
9258
10573
  capabilities: { watch: true },
9259
10574
  init: { mcpFile: ".kimi-code/mcp.json" },
9260
10575
  hooks: { recallPlatform: "kimi", recallSource: "kimi" },
9261
- cli: { command: "kimi", stopNotice: (form) => form === "tui" ? "Kimi belongs to its own TUI terminal; end it in that terminal" : "", startWords: (form) => ["kimi", "--mode", form] },
10576
+ cli: { command: "kimi", launchConfigKeys: ["kimi_cmd", "peers.kimi.start_mode"], stopNotice: (form) => form === "tui" ? "Kimi belongs to its own TUI terminal; end it in that terminal" : "", startWords: (form) => ["kimi", "--mode", form] },
9262
10577
  display: KIMI_DISPLAY
9263
10578
  },
9264
10579
  {
@@ -9280,7 +10595,7 @@ var init_builtin_metadata = __esm(() => {
9280
10595
  reporting: { tokensNotReported: true },
9281
10596
  configuration: { schema: piConfigSchema, routingSchema: piRoutingSchema, settings: PI_SETTINGS, restartReasons: PI_RESTART_REASONS, validateEffective: piEffectiveConfig },
9282
10597
  routing: { localAllowedRefusal: "local_allowed = false also excludes pi for this class", longContext: "backend-limit", preferredTier: true, handoffPriority: 0, trace: "backend", backendLabel: "pi" },
9283
- cli: { command: "pi", previewArgs: ["--mode", "tui"], retainedTerminalNotice: piRetainedTerminalNotice, personOnlyFlags: ["--replace-session"], startWords: (form) => ["pi", "--mode", form] },
10598
+ cli: { command: "pi", launchConfigKeys: ["pi.cmd", "pi.backend", "pi.dgx_coding", "pi.dgx_fast", "pi.max_steps", "routing.pi", "peers.pi.start_mode"], previewArgs: ["--mode", "tui"], retainedTerminalNotice: piRetainedTerminalNotice, personOnlyFlags: ["--replace-session"], startWords: (form) => ["pi", "--mode", form] },
9284
10599
  display: PI_DISPLAY
9285
10600
  },
9286
10601
  {
@@ -9300,7 +10615,7 @@ var init_builtin_metadata = __esm(() => {
9300
10615
  budget: { account: "local", quotaExempt: true, instrumented: true },
9301
10616
  configuration: { schema: localConfigSchema, routingSchema: localRoutingSchema, restartReasons: LOCAL_RESTART_REASONS },
9302
10617
  routing: { localAllowedRefusal: "local_allowed = false for this class", longContext: "skip-owner", preferredTier: true, handoffPriority: 1, trace: "route" },
9303
- cli: { command: "local", startWords: () => ["local"] },
10618
+ cli: { command: "local", launchConfigKeys: ["local.deny", "local.bash_network", "local.network_allow", "local.max_steps", "local.read_allow", "routing.local"], startWords: () => ["local"] },
9304
10619
  display: LOCAL_DISPLAY
9305
10620
  },
9306
10621
  {
@@ -9313,7 +10628,7 @@ var init_builtin_metadata = __esm(() => {
9313
10628
  optional: true,
9314
10629
  defaultOnRoster: false,
9315
10630
  trust: { onPrem: false, hubSandboxed: false },
9316
- start: { create: createJcode, preflight: jcodeStartPreflight, daemonPort: "owned-acp", forms: headlessForms, defaultForm: "headless", ownsTerminal: headless, launch: (ctx) => compatibilityLaunch("jcode", ctx), conductorStartable: true, hubSwitches: [], ownedFlags: [] },
10631
+ start: { create: createJcode, preflight: jcodeStartPreflight, daemonPort: "owned-acp", forms: dualForms, defaultForm: "tui", ownsTerminal: terminal, explicitHeadlessMode: true, launch: (ctx) => compatibilityLaunch("jcode", ctx, true), conductorStartable: true, hubSwitches: [], ownedFlags: [] },
9317
10632
  identity: { mcp: "project-file" },
9318
10633
  permission: { modes: {}, unmanaged: "Jcode native policy is unmanaged; no hub mode mapping", grants: {
9319
10634
  ask: "no verified hub mode mapping; native policy only",
@@ -9323,7 +10638,7 @@ var init_builtin_metadata = __esm(() => {
9323
10638
  session: { idField: "sessionId", currentId: sessionId("sessionId"), launchKind: "jcode", ...JCODE_RECOVERY_SESSION, crashPlan: jcodeCrashPlan },
9324
10639
  turnEnd: "adapter",
9325
10640
  budget: { account: "jcode" },
9326
- cli: { command: "jcode", authority: "person", startWords: () => ["jcode"] },
10641
+ cli: { command: "jcode", authority: "person", launchConfigKeys: ["jcode_cmd", "jcode_home", "jcode_provider_profile"], startWords: (mode) => ["jcode", "--mode", mode] },
9327
10642
  display: JCODE_DISPLAY,
9328
10643
  hooks: { recallPlatform: "jcode" }
9329
10644
  }
@@ -9514,7 +10829,7 @@ var init_qwen = __esm(() => {
9514
10829
  throw new Error("Qwen TUI is not offered before its native verification; use ahub qwen --mode headless");
9515
10830
  const launch = buildQwenLaunch(context.nativeCommand ?? ["qwen", "--acp"], context.model);
9516
10831
  const argv = [launch.cmd, ...launch.args];
9517
- return { ...launch, metadata: { packageEntrypoint: context.entrypoint, argv, command: argv.map(shellQuote).join(" "), env: {} } };
10832
+ return { ...launch, metadata: { packageEntrypoint: context.entrypoint, argv, command: argv.map(shellQuote3).join(" "), env: {} } };
9518
10833
  },
9519
10834
  create: (context) => {
9520
10835
  if (!("acp" in context.daemon))
@@ -9555,7 +10870,7 @@ var init_qwen = __esm(() => {
9555
10870
  turnEnd: "adapter",
9556
10871
  budget: { account: "qwen" },
9557
10872
  capabilities: { watch: true },
9558
- cli: { command: "qwen", startWords: () => ["qwen"] },
10873
+ cli: { command: "qwen", launchConfigKeys: ["qwen_cmd"], startWords: () => ["qwen"] },
9559
10874
  display: QWEN_DISPLAY,
9560
10875
  hooks: { agentIdentity: (init) => {
9561
10876
  qwenNativeIdentity("qwen", true, init);
@@ -9644,7 +10959,8 @@ function assertNativeCapabilityScope(definition) {
9644
10959
  ["start.daemonPort pi-rpc", definition.start.daemonPort === "pi-rpc"],
9645
10960
  ["routing", definition.routing !== undefined],
9646
10961
  ["reviewerVariant", definition.reviewerVariant !== undefined],
9647
- ["reviewer", definition.reviewer !== undefined]
10962
+ ["reviewer", definition.reviewer !== undefined],
10963
+ ["hooks.factsHook", declared(definition.hooks, "factsHook")]
9648
10964
  ];
9649
10965
  const field = restricted.find(([, present]) => present)?.[0];
9650
10966
  if (field)
@@ -9652,6 +10968,7 @@ function assertNativeCapabilityScope(definition) {
9652
10968
  }
9653
10969
  var ADAPTER_API = 1, KNOWN_PEERS, OPTIONAL_PEERS, SUPPORTED_PEERS, fixedBuiltinDefinitions, ADAPTERS, declarationOnlyDefinitions, definitions, REVIEW_POOL_KINDS, DEFAULT_REVIEW_POOL, QUOTA_HANDOFF_PLATFORMS, ROLE_ORDER, CONDUCTOR_START_PRIORITY, CONDUCTOR_START_PEERS, ADAPTER_DISPLAY, BUILTIN_CLI_ENTRIES;
9654
10970
  var init_registry = __esm(() => {
10971
+ init_definition();
9655
10972
  init_reviewer_variants();
9656
10973
  init_native_cli_entry();
9657
10974
  init_native_cli_preview();
@@ -9662,18 +10979,19 @@ var init_registry = __esm(() => {
9662
10979
  init_native_cli_doctor();
9663
10980
  init_native_display();
9664
10981
  KNOWN_PEERS = ["claude", "codex", "kimi", "pi", "local"];
9665
- OPTIONAL_PEERS = ["qwen", "jcode"];
10982
+ OPTIONAL_PEERS = ["qwen", "jcode", "agy"];
9666
10983
  SUPPORTED_PEERS = [...KNOWN_PEERS, ...OPTIONAL_PEERS];
9667
- fixedBuiltinDefinitions = new Set([...NATIVE_BUILTIN_DEFINITIONS_WITHOUT_QWEN, QWEN_DEFINITION, AGY_DEFINITION]);
10984
+ fixedBuiltinDefinitions = new Set([...NATIVE_BUILTIN_DEFINITIONS_WITHOUT_QWEN, QWEN_DEFINITION, AGY_DEFINITION, AGY_REVIEWER_VARIANT]);
9668
10985
  for (const definition of [...fixedBuiltinDefinitions])
9669
10986
  if (definition.reviewerVariant)
9670
10987
  fixedBuiltinDefinitions.add(definition.reviewerVariant);
9671
10988
  ADAPTERS = immutable([
9672
10989
  ...NATIVE_BUILTIN_DEFINITIONS_WITHOUT_QWEN.filter((definition) => definition.kind !== "jcode"),
9673
10990
  QWEN_DEFINITION,
9674
- ...NATIVE_BUILTIN_DEFINITIONS_WITHOUT_QWEN.filter((definition) => definition.kind === "jcode")
10991
+ ...NATIVE_BUILTIN_DEFINITIONS_WITHOUT_QWEN.filter((definition) => definition.kind === "jcode"),
10992
+ AGY_DEFINITION
9675
10993
  ]);
9676
- declarationOnlyDefinitions = immutable([AGY_DEFINITION]);
10994
+ declarationOnlyDefinitions = immutable([]);
9677
10995
  definitions = new Map;
9678
10996
  for (const definition of [...ADAPTERS, ...declarationOnlyDefinitions]) {
9679
10997
  assertDaemonEngine(definition);
@@ -9689,7 +11007,7 @@ var init_registry = __esm(() => {
9689
11007
  REVIEW_POOL_KINDS = Object.freeze([...definitions.values()].filter((definition) => !!(definition.reviewerVariant ?? definition).reviewer).map((definition) => definition.kind));
9690
11008
  DEFAULT_REVIEW_POOL = Object.freeze(ADAPTERS.filter((definition) => (definition.reviewerVariant ?? definition).reviewer?.defaultPool === true).map((definition) => definition.kind));
9691
11009
  QUOTA_HANDOFF_PLATFORMS = Object.freeze(Object.fromEntries(ADAPTERS.flatMap((adapter) => adapter.hooks?.recallPlatform ? [[adapter.kind, adapter.hooks.recallPlatform]] : [])));
9692
- ROLE_ORDER = ["claude", "codex", "kimi", "local", "pi", "qwen", "jcode"];
11010
+ ROLE_ORDER = ["claude", "codex", "kimi", "local", "pi", "qwen", "jcode", "agy"];
9693
11011
  CONDUCTOR_START_PRIORITY = ["local", "kimi", "pi"];
9694
11012
  CONDUCTOR_START_PEERS = [
9695
11013
  ...CONDUCTOR_START_PRIORITY,
@@ -16873,8 +18191,8 @@ var require_dist = __commonJS(function(exports, module) {
16873
18191
 
16874
18192
  // src/hub/instance-config.ts
16875
18193
  init_registry();
16876
- import { readFileSync as readFileSync10 } from "fs";
16877
- import { join as join23 } from "path";
18194
+ import { readFileSync as readFileSync12 } from "fs";
18195
+ import { join as join26 } from "path";
16878
18196
 
16879
18197
  // src/hub/conductor.ts
16880
18198
  init_registry();
@@ -16895,7 +18213,7 @@ var STDOUT_LIMIT = 512 * 1024;
16895
18213
  // package.json
16896
18214
  var package_default = {
16897
18215
  name: "@staix/agent-hub",
16898
- version: "0.12.37",
18216
+ version: "0.12.39",
16899
18217
  description: "Native multi-agent hub: Claude Code, Codex, Kimi Code, Pi and local inference as peers in one project",
16900
18218
  license: "MIT",
16901
18219
  type: "module",
@@ -16945,7 +18263,7 @@ var package_default = {
16945
18263
  };
16946
18264
 
16947
18265
  // src/version.ts
16948
- var VERSION2 = package_default.version;
18266
+ var VERSION3 = package_default.version;
16949
18267
 
16950
18268
  // src/hub/github-import.ts
16951
18269
  init_sdlc();
@@ -17036,7 +18354,7 @@ var USER2 = Symbol("shared-user-shard");
17036
18354
  // node_modules/@staix/agent-mem/src/mcp.ts
17037
18355
  var string = (maxLength, minLength = 1) => ({ type: "string", minLength, maxLength });
17038
18356
  var limit = { type: "integer", minimum: 1, maximum: 100 };
17039
- var object4 = (properties, required = []) => ({ type: "object", properties, required, additionalProperties: false });
18357
+ var object6 = (properties, required = []) => ({ type: "object", properties, required, additionalProperties: false });
17040
18358
  var tool2 = (name, description, inputSchema, readOnlyHint = true) => ({
17041
18359
  name,
17042
18360
  description,
@@ -17044,20 +18362,20 @@ var tool2 = (name, description, inputSchema, readOnlyHint = true) => ({
17044
18362
  annotations: { readOnlyHint, destructiveHint: false, openWorldHint: false }
17045
18363
  });
17046
18364
  var MCP_TOOLS = [
17047
- tool2("amem_search", "Search screened memory in this project. Capture results are recall, not curated evidence.", object4({ query: string(1000), limit, since: string(40), includeCapture: { type: "boolean" } }, ["query"])),
17048
- tool2("amem_show", "Show a screened memory entry in this project by its exact id or unique prefix.", object4({ id: string(80) }, ["id"])),
17049
- tool2("amem_timeline", "Read screened memory near an entry or time in this project.", object4({ id: string(80), since: string(40), limit })),
17050
- tool2("amem_graph", "Read the screened curated-memory neighbourhood. Capture is never graph evidence.", object4({ start: string(500), hops: { type: "integer", minimum: 1, maximum: 2 }, limit }, ["start"])),
17051
- tool2("amem_work_state_read", "Read project work lists; closed lists/items can be included explicitly.", object4({ list: string(200), includeClosed: { type: "boolean" } })),
17052
- tool2("amem_work_state_write", "Append scalar project work-list fields; null clears, task selects an item, list status done closes.", object4({ list: string(200), fields: {
18365
+ tool2("amem_search", "Search screened memory in this project. Capture results are recall, not curated evidence.", object6({ query: string(1000), limit, since: string(40), includeCapture: { type: "boolean" } }, ["query"])),
18366
+ tool2("amem_show", "Show a screened memory entry in this project by its exact id or unique prefix.", object6({ id: string(80) }, ["id"])),
18367
+ tool2("amem_timeline", "Read screened memory near an entry or time in this project.", object6({ id: string(80), since: string(40), limit })),
18368
+ tool2("amem_graph", "Read the screened curated-memory neighbourhood. Capture is never graph evidence.", object6({ start: string(500), hops: { type: "integer", minimum: 1, maximum: 2 }, limit }, ["start"])),
18369
+ tool2("amem_work_state_read", "Read project work lists; closed lists/items can be included explicitly.", object6({ list: string(200), includeClosed: { type: "boolean" } })),
18370
+ tool2("amem_work_state_write", "Append scalar project work-list fields; null clears, task selects an item, list status done closes.", object6({ list: string(200), fields: {
17053
18371
  type: "object",
17054
18372
  minProperties: 1,
17055
18373
  maxProperties: 64,
17056
18374
  additionalProperties: { type: ["string", "number", "boolean", "null"], maxLength: 4000 }
17057
18375
  } }, ["list", "fields"]), false),
17058
- tool2("amem_remember", "Propose a general memory candidate for person acceptance. Never writes directly to curated notes.", object4({ text: string(3000), title: string(200), kind: { type: "string", enum: ["decision", "finding", "contract", "fail"] } }, ["text"]), false),
17059
- tool2("amem_vault_candidates", "List screened durable curated notes eligible for a person to move to the vault. Read only.", object4({ limit })),
17060
- tool2("amem_capacity", "Read this machine's bounded capture capacity proposal; applying it is person-only.", object4({}))
18376
+ tool2("amem_remember", "Propose a general memory candidate for person acceptance. Never writes directly to curated notes.", object6({ text: string(3000), title: string(200), kind: { type: "string", enum: ["decision", "finding", "contract", "fail"] } }, ["text"]), false),
18377
+ tool2("amem_vault_candidates", "List screened durable curated notes eligible for a person to move to the vault. Read only.", object6({ limit })),
18378
+ tool2("amem_capacity", "Read this machine's bounded capture capacity proposal; applying it is person-only.", object6({}))
17061
18379
  ];
17062
18380
  // node_modules/@staix/agent-mem/src/mcp-memory.ts
17063
18381
  init_capture();
@@ -17084,14 +18402,14 @@ init_generation();
17084
18402
  // src/hub/routing.ts
17085
18403
  init_registry();
17086
18404
  init_board();
17087
- import { join as join21, resolve as resolve13 } from "path";
18405
+ import { join as join24, resolve as resolve16 } from "path";
17088
18406
 
17089
18407
  // src/models/route/config.ts
17090
18408
  var TYPES = new Set(["stage", "plan_execute", "advisor", "escalation"]);
17091
18409
 
17092
18410
  // src/hub/routing.ts
17093
18411
  init_stage();
17094
- var TEMPLATE = join21(import.meta.dir, "..", "..", "templates", "routing.toml");
18412
+ var TEMPLATE = join24(import.meta.dir, "..", "..", "templates", "routing.toml");
17095
18413
  var cache = new Map;
17096
18414
 
17097
18415
  // src/hub/memory-policy.ts
@@ -17118,8 +18436,8 @@ var OWNERSHIP_EVENTS = new Set(["assigned", "escalated", "reassigned", "unassign
17118
18436
  // src/hub/config-trust.ts
17119
18437
  init_child_process();
17120
18438
  init_git();
17121
- import { lstatSync as lstatSync7, statSync as statSync5 } from "fs";
17122
- import { dirname as dirname9, join as join22 } from "path";
18439
+ import { lstatSync as lstatSync10, statSync as statSync5 } from "fs";
18440
+ import { dirname as dirname10, join as join25 } from "path";
17123
18441
  var MACHINE_LOCAL = [
17124
18442
  "kimi_cmd",
17125
18443
  "permission_modes",
@@ -17151,7 +18469,7 @@ function configTracked(cwd, name) {
17151
18469
  return;
17152
18470
  let opened;
17153
18471
  try {
17154
- opened = statSync5(join22(cwd, ".agenthub", name));
18472
+ opened = statSync5(join25(cwd, ".agenthub", name));
17155
18473
  } catch {}
17156
18474
  return r.stdout.split("\x00").filter(Boolean).some((row) => {
17157
18475
  const path = row.slice(row.indexOf("\t") + 1);
@@ -17160,7 +18478,7 @@ function configTracked(cwd, name) {
17160
18478
  if (!opened)
17161
18479
  return path === `.agenthub/${name}`;
17162
18480
  try {
17163
- const s = statSync5(join22(cwd, path));
18481
+ const s = statSync5(join25(cwd, path));
17164
18482
  return !!opened && s.dev === opened.dev && s.ino === opened.ino;
17165
18483
  } catch {
17166
18484
  return false;
@@ -17293,7 +18611,7 @@ var INSTANCE_CONFIG_FILES = ["config.json", "config.local.json"];
17293
18611
  function readInstanceConfigLayers(cwd) {
17294
18612
  return INSTANCE_CONFIG_FILES.flatMap((name) => {
17295
18613
  try {
17296
- return [{ name, raw: JSON.parse(readFileSync10(join23(cwd, ".agenthub", name), "utf8")) }];
18614
+ return [{ name, raw: JSON.parse(readFileSync12(join26(cwd, ".agenthub", name), "utf8")) }];
17297
18615
  } catch (error) {
17298
18616
  if (error.code === "ENOENT")
17299
18617
  return [];
@@ -17323,8 +18641,8 @@ function readInstanceConfig(cwd, layers = readInstanceConfigLayers(cwd)) {
17323
18641
 
17324
18642
  // src/hub/control-client.ts
17325
18643
  init_project();
17326
- import { readFileSync as readFileSync11 } from "fs";
17327
- import { join as join24 } from "path";
18644
+ import { readFileSync as readFileSync13 } from "fs";
18645
+ import { join as join27 } from "path";
17328
18646
  function stateDirFor(cwd) {
17329
18647
  return projectContext(cwd).stateDir;
17330
18648
  }
@@ -17332,10 +18650,11 @@ var PROTOCOL = 16;
17332
18650
  var RECOVERY_SOURCE_PROTOCOLS = [9, 10, 11, 12, 13, 14, 15, PROTOCOL];
17333
18651
  function readControl(stateDir) {
17334
18652
  try {
17335
- const status = JSON.parse(readFileSync11(join24(stateDir, "status.json"), "utf8"));
17336
- const token = readFileSync11(join24(stateDir, "control-token"), "utf8").trim();
18653
+ const status = JSON.parse(readFileSync13(join27(stateDir, "status.json"), "utf8"));
18654
+ const token = readFileSync13(join27(stateDir, "control-token"), "utf8").trim();
17337
18655
  if (!Number.isInteger(status.controlPort) || status.controlPort < 1 || status.controlPort > 65535 || !token)
17338
18656
  return;
18657
+ const startModes = status.startModes && typeof status.startModes === "object" && !Array.isArray(status.startModes) ? Object.fromEntries(Object.entries(status.startModes).filter((entry) => entry[1] === "tui" || entry[1] === "headless")) : undefined;
17339
18658
  return {
17340
18659
  url: `ws://127.0.0.1:${status.controlPort}`,
17341
18660
  token,
@@ -17345,6 +18664,7 @@ function readControl(stateDir) {
17345
18664
  protocol: status.protocol,
17346
18665
  pid: status.pid,
17347
18666
  features: Array.isArray(status.features) ? status.features.filter((feature) => typeof feature === "string") : [],
18667
+ ...startModes ? { startModes } : {},
17348
18668
  ...typeof status.pidSignature === "string" && status.pidSignature ? { pidSignature: status.pidSignature } : {}
17349
18669
  };
17350
18670
  } catch {
@@ -18811,7 +20131,7 @@ ${content.join(`
18811
20131
  }
18812
20132
 
18813
20133
  // node_modules/zod/v4/core/versions.js
18814
- var version2 = {
20134
+ var version3 = {
18815
20135
  major: 4,
18816
20136
  minor: 6,
18817
20137
  patch: 5
@@ -18823,7 +20143,7 @@ var $ZodType = /* @__PURE__ */ $constructor("$ZodType", (inst, def) => {
18823
20143
  inst ?? (inst = {});
18824
20144
  inst._zod.def = def;
18825
20145
  inst._zod.bag = inst._zod.bag || {};
18826
- inst._zod.version = version2;
20146
+ inst._zod.version = version3;
18827
20147
  const defChecks = inst._zod.def.checks;
18828
20148
  const checks = inst._zod.traits.has("$ZodCheck") ? [inst, ...defChecks ?? []] : defChecks?.length ? [...defChecks] : [];
18829
20149
  for (const ch of checks) {
@@ -23131,7 +24451,7 @@ var ZodObject = /* @__PURE__ */ $constructor("ZodObject", (inst, def) => {
23131
24451
  return required(ZodNonOptional, this, args[0]);
23132
24452
  }
23133
24453
  });
23134
- function object6(shape, params) {
24454
+ function object8(shape, params) {
23135
24455
  const def = {
23136
24456
  type: "object",
23137
24457
  shape: shape ?? {},
@@ -23192,7 +24512,7 @@ var ZodRecord = /* @__PURE__ */ $constructor("ZodRecord", (inst, def) => {
23192
24512
  inst.keyType = def.keyType;
23193
24513
  inst.valueType = def.valueType;
23194
24514
  });
23195
- function record3(keyType, valueType, params) {
24515
+ function record4(keyType, valueType, params) {
23196
24516
  if (!valueType || !valueType._zod) {
23197
24517
  return new ZodRecord({
23198
24518
  type: "record",
@@ -23476,31 +24796,31 @@ var TaskCreationParamsSchema = looseObject({
23476
24796
  ttl: number2().optional(),
23477
24797
  pollInterval: number2().optional()
23478
24798
  });
23479
- var TaskMetadataSchema = object6({
24799
+ var TaskMetadataSchema = object8({
23480
24800
  ttl: number2().optional()
23481
24801
  });
23482
- var RelatedTaskMetadataSchema = object6({
24802
+ var RelatedTaskMetadataSchema = object8({
23483
24803
  taskId: string3()
23484
24804
  });
23485
24805
  var RequestMetaSchema = looseObject({
23486
24806
  progressToken: ProgressTokenSchema.optional(),
23487
24807
  [RELATED_TASK_META_KEY]: RelatedTaskMetadataSchema.optional()
23488
24808
  });
23489
- var BaseRequestParamsSchema = object6({
24809
+ var BaseRequestParamsSchema = object8({
23490
24810
  _meta: RequestMetaSchema.optional()
23491
24811
  });
23492
24812
  var TaskAugmentedRequestParamsSchema = BaseRequestParamsSchema.extend({
23493
24813
  task: TaskMetadataSchema.optional()
23494
24814
  });
23495
24815
  var isTaskAugmentedRequestParams = (value) => TaskAugmentedRequestParamsSchema.safeParse(value).success;
23496
- var RequestSchema = object6({
24816
+ var RequestSchema = object8({
23497
24817
  method: string3(),
23498
24818
  params: BaseRequestParamsSchema.loose().optional()
23499
24819
  });
23500
- var NotificationsParamsSchema = object6({
24820
+ var NotificationsParamsSchema = object8({
23501
24821
  _meta: RequestMetaSchema.optional()
23502
24822
  });
23503
- var NotificationSchema = object6({
24823
+ var NotificationSchema = object8({
23504
24824
  method: string3(),
23505
24825
  params: NotificationsParamsSchema.loose().optional()
23506
24826
  });
@@ -23508,18 +24828,18 @@ var ResultSchema = looseObject({
23508
24828
  _meta: RequestMetaSchema.optional()
23509
24829
  });
23510
24830
  var RequestIdSchema = union([string3(), number2().int()]);
23511
- var JSONRPCRequestSchema = object6({
24831
+ var JSONRPCRequestSchema = object8({
23512
24832
  jsonrpc: literal(JSONRPC_VERSION),
23513
24833
  id: RequestIdSchema,
23514
24834
  ...RequestSchema.shape
23515
24835
  }).strict();
23516
24836
  var isJSONRPCRequest = (value) => JSONRPCRequestSchema.safeParse(value).success;
23517
- var JSONRPCNotificationSchema = object6({
24837
+ var JSONRPCNotificationSchema = object8({
23518
24838
  jsonrpc: literal(JSONRPC_VERSION),
23519
24839
  ...NotificationSchema.shape
23520
24840
  }).strict();
23521
24841
  var isJSONRPCNotification = (value) => JSONRPCNotificationSchema.safeParse(value).success;
23522
- var JSONRPCResultResponseSchema = object6({
24842
+ var JSONRPCResultResponseSchema = object8({
23523
24843
  jsonrpc: literal(JSONRPC_VERSION),
23524
24844
  id: RequestIdSchema,
23525
24845
  result: ResultSchema
@@ -23536,10 +24856,10 @@ var ErrorCode;
23536
24856
  ErrorCode[ErrorCode["InternalError"] = -32603] = "InternalError";
23537
24857
  ErrorCode[ErrorCode["UrlElicitationRequired"] = -32042] = "UrlElicitationRequired";
23538
24858
  })(ErrorCode || (ErrorCode = {}));
23539
- var JSONRPCErrorResponseSchema = object6({
24859
+ var JSONRPCErrorResponseSchema = object8({
23540
24860
  jsonrpc: literal(JSONRPC_VERSION),
23541
24861
  id: RequestIdSchema.optional(),
23542
- error: object6({
24862
+ error: object8({
23543
24863
  code: number2().int(),
23544
24864
  message: string3(),
23545
24865
  data: unknown().optional()
@@ -23562,16 +24882,16 @@ var CancelledNotificationSchema = NotificationSchema.extend({
23562
24882
  method: literal("notifications/cancelled"),
23563
24883
  params: CancelledNotificationParamsSchema
23564
24884
  });
23565
- var IconSchema = object6({
24885
+ var IconSchema = object8({
23566
24886
  src: string3(),
23567
24887
  mimeType: string3().optional(),
23568
24888
  sizes: array(string3()).optional(),
23569
24889
  theme: _enum(["light", "dark"]).optional()
23570
24890
  });
23571
- var IconsSchema = object6({
24891
+ var IconsSchema = object8({
23572
24892
  icons: array(IconSchema).optional()
23573
24893
  });
23574
- var BaseMetadataSchema = object6({
24894
+ var BaseMetadataSchema = object8({
23575
24895
  name: string3(),
23576
24896
  title: string3().optional()
23577
24897
  });
@@ -23582,9 +24902,9 @@ var ImplementationSchema = BaseMetadataSchema.extend({
23582
24902
  websiteUrl: string3().optional(),
23583
24903
  description: string3().optional()
23584
24904
  });
23585
- var FormElicitationCapabilitySchema = intersection(object6({
24905
+ var FormElicitationCapabilitySchema = intersection(object8({
23586
24906
  applyDefaults: boolean2().optional()
23587
- }), record3(string3(), unknown()));
24907
+ }), record4(string3(), unknown()));
23588
24908
  var ElicitationCapabilitySchema = preprocess((value) => {
23589
24909
  if (value && typeof value === "object" && !Array.isArray(value)) {
23590
24910
  if (Object.keys(value).length === 0) {
@@ -23592,10 +24912,10 @@ var ElicitationCapabilitySchema = preprocess((value) => {
23592
24912
  }
23593
24913
  }
23594
24914
  return value;
23595
- }, intersection(object6({
24915
+ }, intersection(object8({
23596
24916
  form: FormElicitationCapabilitySchema.optional(),
23597
24917
  url: AssertObjectSchema.optional()
23598
- }), record3(string3(), unknown()).optional()));
24918
+ }), record4(string3(), unknown()).optional()));
23599
24919
  var ClientTasksCapabilitySchema = looseObject({
23600
24920
  list: AssertObjectSchema.optional(),
23601
24921
  cancel: AssertObjectSchema.optional(),
@@ -23617,18 +24937,18 @@ var ServerTasksCapabilitySchema = looseObject({
23617
24937
  }).optional()
23618
24938
  }).optional()
23619
24939
  });
23620
- var ClientCapabilitiesSchema = object6({
23621
- experimental: record3(string3(), AssertObjectSchema).optional(),
23622
- sampling: object6({
24940
+ var ClientCapabilitiesSchema = object8({
24941
+ experimental: record4(string3(), AssertObjectSchema).optional(),
24942
+ sampling: object8({
23623
24943
  context: AssertObjectSchema.optional(),
23624
24944
  tools: AssertObjectSchema.optional()
23625
24945
  }).optional(),
23626
24946
  elicitation: ElicitationCapabilitySchema.optional(),
23627
- roots: object6({
24947
+ roots: object8({
23628
24948
  listChanged: boolean2().optional()
23629
24949
  }).optional(),
23630
24950
  tasks: ClientTasksCapabilitySchema.optional(),
23631
- extensions: record3(string3(), AssertObjectSchema).optional()
24951
+ extensions: record4(string3(), AssertObjectSchema).optional()
23632
24952
  });
23633
24953
  var InitializeRequestParamsSchema = BaseRequestParamsSchema.extend({
23634
24954
  protocolVersion: string3(),
@@ -23639,22 +24959,22 @@ var InitializeRequestSchema = RequestSchema.extend({
23639
24959
  method: literal("initialize"),
23640
24960
  params: InitializeRequestParamsSchema
23641
24961
  });
23642
- var ServerCapabilitiesSchema = object6({
23643
- experimental: record3(string3(), AssertObjectSchema).optional(),
24962
+ var ServerCapabilitiesSchema = object8({
24963
+ experimental: record4(string3(), AssertObjectSchema).optional(),
23644
24964
  logging: AssertObjectSchema.optional(),
23645
24965
  completions: AssertObjectSchema.optional(),
23646
- prompts: object6({
24966
+ prompts: object8({
23647
24967
  listChanged: boolean2().optional()
23648
24968
  }).optional(),
23649
- resources: object6({
24969
+ resources: object8({
23650
24970
  subscribe: boolean2().optional(),
23651
24971
  listChanged: boolean2().optional()
23652
24972
  }).optional(),
23653
- tools: object6({
24973
+ tools: object8({
23654
24974
  listChanged: boolean2().optional()
23655
24975
  }).optional(),
23656
24976
  tasks: ServerTasksCapabilitySchema.optional(),
23657
- extensions: record3(string3(), AssertObjectSchema).optional()
24977
+ extensions: record4(string3(), AssertObjectSchema).optional()
23658
24978
  });
23659
24979
  var InitializeResultSchema = ResultSchema.extend({
23660
24980
  protocolVersion: string3(),
@@ -23670,12 +24990,12 @@ var PingRequestSchema = RequestSchema.extend({
23670
24990
  method: literal("ping"),
23671
24991
  params: BaseRequestParamsSchema.optional()
23672
24992
  });
23673
- var ProgressSchema = object6({
24993
+ var ProgressSchema = object8({
23674
24994
  progress: number2(),
23675
24995
  total: optional(number2()),
23676
24996
  message: optional(string3())
23677
24997
  });
23678
- var ProgressNotificationParamsSchema = object6({
24998
+ var ProgressNotificationParamsSchema = object8({
23679
24999
  ...NotificationsParamsSchema.shape,
23680
25000
  ...ProgressSchema.shape,
23681
25001
  progressToken: ProgressTokenSchema
@@ -23694,7 +25014,7 @@ var PaginatedResultSchema = ResultSchema.extend({
23694
25014
  nextCursor: CursorSchema.optional()
23695
25015
  });
23696
25016
  var TaskStatusSchema = _enum(["working", "input_required", "completed", "failed", "cancelled"]);
23697
- var TaskSchema = object6({
25017
+ var TaskSchema = object8({
23698
25018
  taskId: string3(),
23699
25019
  status: TaskStatusSchema,
23700
25020
  ttl: union([number2(), _null3()]),
@@ -23738,10 +25058,10 @@ var CancelTaskRequestSchema = RequestSchema.extend({
23738
25058
  })
23739
25059
  });
23740
25060
  var CancelTaskResultSchema = ResultSchema.merge(TaskSchema);
23741
- var ResourceContentsSchema = object6({
25061
+ var ResourceContentsSchema = object8({
23742
25062
  uri: string3(),
23743
25063
  mimeType: optional(string3()),
23744
- _meta: record3(string3(), unknown()).optional()
25064
+ _meta: record4(string3(), unknown()).optional()
23745
25065
  });
23746
25066
  var TextResourceContentsSchema = ResourceContentsSchema.extend({
23747
25067
  text: string3()
@@ -23758,12 +25078,12 @@ var BlobResourceContentsSchema = ResourceContentsSchema.extend({
23758
25078
  blob: Base64Schema
23759
25079
  });
23760
25080
  var RoleSchema = _enum(["user", "assistant"]);
23761
- var AnnotationsSchema = object6({
25081
+ var AnnotationsSchema = object8({
23762
25082
  audience: array(RoleSchema).optional(),
23763
25083
  priority: number2().min(0).max(1).optional(),
23764
25084
  lastModified: datetime2({ offset: true }).optional()
23765
25085
  });
23766
- var ResourceSchema = object6({
25086
+ var ResourceSchema = object8({
23767
25087
  ...BaseMetadataSchema.shape,
23768
25088
  ...IconsSchema.shape,
23769
25089
  uri: string3(),
@@ -23773,7 +25093,7 @@ var ResourceSchema = object6({
23773
25093
  annotations: AnnotationsSchema.optional(),
23774
25094
  _meta: optional(looseObject({}))
23775
25095
  });
23776
- var ResourceTemplateSchema = object6({
25096
+ var ResourceTemplateSchema = object8({
23777
25097
  ...BaseMetadataSchema.shape,
23778
25098
  ...IconsSchema.shape,
23779
25099
  uriTemplate: string3(),
@@ -23826,12 +25146,12 @@ var ResourceUpdatedNotificationSchema = NotificationSchema.extend({
23826
25146
  method: literal("notifications/resources/updated"),
23827
25147
  params: ResourceUpdatedNotificationParamsSchema
23828
25148
  });
23829
- var PromptArgumentSchema = object6({
25149
+ var PromptArgumentSchema = object8({
23830
25150
  name: string3(),
23831
25151
  description: optional(string3()),
23832
25152
  required: optional(boolean2())
23833
25153
  });
23834
- var PromptSchema = object6({
25154
+ var PromptSchema = object8({
23835
25155
  ...BaseMetadataSchema.shape,
23836
25156
  ...IconsSchema.shape,
23837
25157
  description: optional(string3()),
@@ -23846,44 +25166,44 @@ var ListPromptsResultSchema = PaginatedResultSchema.extend({
23846
25166
  });
23847
25167
  var GetPromptRequestParamsSchema = BaseRequestParamsSchema.extend({
23848
25168
  name: string3(),
23849
- arguments: record3(string3(), string3()).optional()
25169
+ arguments: record4(string3(), string3()).optional()
23850
25170
  });
23851
25171
  var GetPromptRequestSchema = RequestSchema.extend({
23852
25172
  method: literal("prompts/get"),
23853
25173
  params: GetPromptRequestParamsSchema
23854
25174
  });
23855
- var TextContentSchema = object6({
25175
+ var TextContentSchema = object8({
23856
25176
  type: literal("text"),
23857
25177
  text: string3(),
23858
25178
  annotations: AnnotationsSchema.optional(),
23859
- _meta: record3(string3(), unknown()).optional()
25179
+ _meta: record4(string3(), unknown()).optional()
23860
25180
  });
23861
- var ImageContentSchema = object6({
25181
+ var ImageContentSchema = object8({
23862
25182
  type: literal("image"),
23863
25183
  data: Base64Schema,
23864
25184
  mimeType: string3(),
23865
25185
  annotations: AnnotationsSchema.optional(),
23866
- _meta: record3(string3(), unknown()).optional()
25186
+ _meta: record4(string3(), unknown()).optional()
23867
25187
  });
23868
- var AudioContentSchema = object6({
25188
+ var AudioContentSchema = object8({
23869
25189
  type: literal("audio"),
23870
25190
  data: Base64Schema,
23871
25191
  mimeType: string3(),
23872
25192
  annotations: AnnotationsSchema.optional(),
23873
- _meta: record3(string3(), unknown()).optional()
25193
+ _meta: record4(string3(), unknown()).optional()
23874
25194
  });
23875
- var ToolUseContentSchema = object6({
25195
+ var ToolUseContentSchema = object8({
23876
25196
  type: literal("tool_use"),
23877
25197
  name: string3(),
23878
25198
  id: string3(),
23879
- input: record3(string3(), unknown()),
23880
- _meta: record3(string3(), unknown()).optional()
25199
+ input: record4(string3(), unknown()),
25200
+ _meta: record4(string3(), unknown()).optional()
23881
25201
  });
23882
- var EmbeddedResourceSchema = object6({
25202
+ var EmbeddedResourceSchema = object8({
23883
25203
  type: literal("resource"),
23884
25204
  resource: union([TextResourceContentsSchema, BlobResourceContentsSchema]),
23885
25205
  annotations: AnnotationsSchema.optional(),
23886
- _meta: record3(string3(), unknown()).optional()
25206
+ _meta: record4(string3(), unknown()).optional()
23887
25207
  });
23888
25208
  var ResourceLinkSchema = ResourceSchema.extend({
23889
25209
  type: literal("resource_link")
@@ -23895,7 +25215,7 @@ var ContentBlockSchema = union([
23895
25215
  ResourceLinkSchema,
23896
25216
  EmbeddedResourceSchema
23897
25217
  ]);
23898
- var PromptMessageSchema = object6({
25218
+ var PromptMessageSchema = object8({
23899
25219
  role: RoleSchema,
23900
25220
  content: ContentBlockSchema
23901
25221
  });
@@ -23907,33 +25227,33 @@ var PromptListChangedNotificationSchema = NotificationSchema.extend({
23907
25227
  method: literal("notifications/prompts/list_changed"),
23908
25228
  params: NotificationsParamsSchema.optional()
23909
25229
  });
23910
- var ToolAnnotationsSchema = object6({
25230
+ var ToolAnnotationsSchema = object8({
23911
25231
  title: string3().optional(),
23912
25232
  readOnlyHint: boolean2().optional(),
23913
25233
  destructiveHint: boolean2().optional(),
23914
25234
  idempotentHint: boolean2().optional(),
23915
25235
  openWorldHint: boolean2().optional()
23916
25236
  });
23917
- var ToolExecutionSchema = object6({
25237
+ var ToolExecutionSchema = object8({
23918
25238
  taskSupport: _enum(["required", "optional", "forbidden"]).optional()
23919
25239
  });
23920
- var ToolSchema = object6({
25240
+ var ToolSchema = object8({
23921
25241
  ...BaseMetadataSchema.shape,
23922
25242
  ...IconsSchema.shape,
23923
25243
  description: string3().optional(),
23924
- inputSchema: object6({
25244
+ inputSchema: object8({
23925
25245
  type: literal("object"),
23926
- properties: record3(string3(), AssertObjectSchema).optional(),
25246
+ properties: record4(string3(), AssertObjectSchema).optional(),
23927
25247
  required: array(string3()).optional()
23928
25248
  }).catchall(unknown()),
23929
- outputSchema: object6({
25249
+ outputSchema: object8({
23930
25250
  type: literal("object"),
23931
- properties: record3(string3(), AssertObjectSchema).optional(),
25251
+ properties: record4(string3(), AssertObjectSchema).optional(),
23932
25252
  required: array(string3()).optional()
23933
25253
  }).catchall(unknown()).optional(),
23934
25254
  annotations: ToolAnnotationsSchema.optional(),
23935
25255
  execution: ToolExecutionSchema.optional(),
23936
- _meta: record3(string3(), unknown()).optional()
25256
+ _meta: record4(string3(), unknown()).optional()
23937
25257
  });
23938
25258
  var ListToolsRequestSchema = PaginatedRequestSchema.extend({
23939
25259
  method: literal("tools/list")
@@ -23943,7 +25263,7 @@ var ListToolsResultSchema = PaginatedResultSchema.extend({
23943
25263
  });
23944
25264
  var CallToolResultSchema = ResultSchema.extend({
23945
25265
  content: array(ContentBlockSchema).default([]),
23946
- structuredContent: record3(string3(), unknown()).optional(),
25266
+ structuredContent: record4(string3(), unknown()).optional(),
23947
25267
  isError: boolean2().optional()
23948
25268
  });
23949
25269
  var CompatibilityCallToolResultSchema = CallToolResultSchema.or(ResultSchema.extend({
@@ -23951,7 +25271,7 @@ var CompatibilityCallToolResultSchema = CallToolResultSchema.or(ResultSchema.ext
23951
25271
  }));
23952
25272
  var CallToolRequestParamsSchema = TaskAugmentedRequestParamsSchema.extend({
23953
25273
  name: string3(),
23954
- arguments: record3(string3(), unknown()).optional()
25274
+ arguments: record4(string3(), unknown()).optional()
23955
25275
  });
23956
25276
  var CallToolRequestSchema = RequestSchema.extend({
23957
25277
  method: literal("tools/call"),
@@ -23961,7 +25281,7 @@ var ToolListChangedNotificationSchema = NotificationSchema.extend({
23961
25281
  method: literal("notifications/tools/list_changed"),
23962
25282
  params: NotificationsParamsSchema.optional()
23963
25283
  });
23964
- var ListChangedOptionsBaseSchema = object6({
25284
+ var ListChangedOptionsBaseSchema = object8({
23965
25285
  autoRefresh: boolean2().default(true),
23966
25286
  debounceMs: number2().int().nonnegative().default(300)
23967
25287
  });
@@ -23982,25 +25302,25 @@ var LoggingMessageNotificationSchema = NotificationSchema.extend({
23982
25302
  method: literal("notifications/message"),
23983
25303
  params: LoggingMessageNotificationParamsSchema
23984
25304
  });
23985
- var ModelHintSchema = object6({
25305
+ var ModelHintSchema = object8({
23986
25306
  name: string3().optional()
23987
25307
  });
23988
- var ModelPreferencesSchema = object6({
25308
+ var ModelPreferencesSchema = object8({
23989
25309
  hints: array(ModelHintSchema).optional(),
23990
25310
  costPriority: number2().min(0).max(1).optional(),
23991
25311
  speedPriority: number2().min(0).max(1).optional(),
23992
25312
  intelligencePriority: number2().min(0).max(1).optional()
23993
25313
  });
23994
- var ToolChoiceSchema = object6({
25314
+ var ToolChoiceSchema = object8({
23995
25315
  mode: _enum(["auto", "required", "none"]).optional()
23996
25316
  });
23997
- var ToolResultContentSchema = object6({
25317
+ var ToolResultContentSchema = object8({
23998
25318
  type: literal("tool_result"),
23999
25319
  toolUseId: string3().describe("The unique identifier for the corresponding tool call."),
24000
25320
  content: array(ContentBlockSchema).default([]),
24001
- structuredContent: object6({}).loose().optional(),
25321
+ structuredContent: object8({}).loose().optional(),
24002
25322
  isError: boolean2().optional(),
24003
- _meta: record3(string3(), unknown()).optional()
25323
+ _meta: record4(string3(), unknown()).optional()
24004
25324
  });
24005
25325
  var SamplingContentSchema = discriminatedUnion("type", [TextContentSchema, ImageContentSchema, AudioContentSchema]);
24006
25326
  var SamplingMessageContentBlockSchema = discriminatedUnion("type", [
@@ -24010,10 +25330,10 @@ var SamplingMessageContentBlockSchema = discriminatedUnion("type", [
24010
25330
  ToolUseContentSchema,
24011
25331
  ToolResultContentSchema
24012
25332
  ]);
24013
- var SamplingMessageSchema = object6({
25333
+ var SamplingMessageSchema = object8({
24014
25334
  role: RoleSchema,
24015
25335
  content: union([SamplingMessageContentBlockSchema, array(SamplingMessageContentBlockSchema)]),
24016
- _meta: record3(string3(), unknown()).optional()
25336
+ _meta: record4(string3(), unknown()).optional()
24017
25337
  });
24018
25338
  var CreateMessageRequestParamsSchema = TaskAugmentedRequestParamsSchema.extend({
24019
25339
  messages: array(SamplingMessageSchema),
@@ -24043,13 +25363,13 @@ var CreateMessageResultWithToolsSchema = ResultSchema.extend({
24043
25363
  role: RoleSchema,
24044
25364
  content: union([SamplingMessageContentBlockSchema, array(SamplingMessageContentBlockSchema)])
24045
25365
  });
24046
- var BooleanSchemaSchema = object6({
25366
+ var BooleanSchemaSchema = object8({
24047
25367
  type: literal("boolean"),
24048
25368
  title: string3().optional(),
24049
25369
  description: string3().optional(),
24050
25370
  default: boolean2().optional()
24051
25371
  });
24052
- var StringSchemaSchema = object6({
25372
+ var StringSchemaSchema = object8({
24053
25373
  type: literal("string"),
24054
25374
  title: string3().optional(),
24055
25375
  description: string3().optional(),
@@ -24058,7 +25378,7 @@ var StringSchemaSchema = object6({
24058
25378
  format: _enum(["email", "uri", "date", "date-time"]).optional(),
24059
25379
  default: string3().optional()
24060
25380
  });
24061
- var NumberSchemaSchema = object6({
25381
+ var NumberSchemaSchema = object8({
24062
25382
  type: _enum(["number", "integer"]),
24063
25383
  title: string3().optional(),
24064
25384
  description: string3().optional(),
@@ -24066,24 +25386,24 @@ var NumberSchemaSchema = object6({
24066
25386
  maximum: number2().optional(),
24067
25387
  default: number2().optional()
24068
25388
  });
24069
- var UntitledSingleSelectEnumSchemaSchema = object6({
25389
+ var UntitledSingleSelectEnumSchemaSchema = object8({
24070
25390
  type: literal("string"),
24071
25391
  title: string3().optional(),
24072
25392
  description: string3().optional(),
24073
25393
  enum: array(string3()),
24074
25394
  default: string3().optional()
24075
25395
  });
24076
- var TitledSingleSelectEnumSchemaSchema = object6({
25396
+ var TitledSingleSelectEnumSchemaSchema = object8({
24077
25397
  type: literal("string"),
24078
25398
  title: string3().optional(),
24079
25399
  description: string3().optional(),
24080
- oneOf: array(object6({
25400
+ oneOf: array(object8({
24081
25401
  const: string3(),
24082
25402
  title: string3()
24083
25403
  })),
24084
25404
  default: string3().optional()
24085
25405
  });
24086
- var LegacyTitledEnumSchemaSchema = object6({
25406
+ var LegacyTitledEnumSchemaSchema = object8({
24087
25407
  type: literal("string"),
24088
25408
  title: string3().optional(),
24089
25409
  description: string3().optional(),
@@ -24092,26 +25412,26 @@ var LegacyTitledEnumSchemaSchema = object6({
24092
25412
  default: string3().optional()
24093
25413
  });
24094
25414
  var SingleSelectEnumSchemaSchema = union([UntitledSingleSelectEnumSchemaSchema, TitledSingleSelectEnumSchemaSchema]);
24095
- var UntitledMultiSelectEnumSchemaSchema = object6({
25415
+ var UntitledMultiSelectEnumSchemaSchema = object8({
24096
25416
  type: literal("array"),
24097
25417
  title: string3().optional(),
24098
25418
  description: string3().optional(),
24099
25419
  minItems: number2().optional(),
24100
25420
  maxItems: number2().optional(),
24101
- items: object6({
25421
+ items: object8({
24102
25422
  type: literal("string"),
24103
25423
  enum: array(string3())
24104
25424
  }),
24105
25425
  default: array(string3()).optional()
24106
25426
  });
24107
- var TitledMultiSelectEnumSchemaSchema = object6({
25427
+ var TitledMultiSelectEnumSchemaSchema = object8({
24108
25428
  type: literal("array"),
24109
25429
  title: string3().optional(),
24110
25430
  description: string3().optional(),
24111
25431
  minItems: number2().optional(),
24112
25432
  maxItems: number2().optional(),
24113
- items: object6({
24114
- anyOf: array(object6({
25433
+ items: object8({
25434
+ anyOf: array(object8({
24115
25435
  const: string3(),
24116
25436
  title: string3()
24117
25437
  }))
@@ -24124,9 +25444,9 @@ var PrimitiveSchemaDefinitionSchema = union([EnumSchemaSchema, BooleanSchemaSche
24124
25444
  var ElicitRequestFormParamsSchema = TaskAugmentedRequestParamsSchema.extend({
24125
25445
  mode: literal("form").optional(),
24126
25446
  message: string3(),
24127
- requestedSchema: object6({
25447
+ requestedSchema: object8({
24128
25448
  type: literal("object"),
24129
- properties: record3(string3(), PrimitiveSchemaDefinitionSchema),
25449
+ properties: record4(string3(), PrimitiveSchemaDefinitionSchema),
24130
25450
  required: array(string3()).optional()
24131
25451
  })
24132
25452
  });
@@ -24150,24 +25470,24 @@ var ElicitationCompleteNotificationSchema = NotificationSchema.extend({
24150
25470
  });
24151
25471
  var ElicitResultSchema = ResultSchema.extend({
24152
25472
  action: _enum(["accept", "decline", "cancel"]),
24153
- content: preprocess((val) => val === null ? undefined : val, record3(string3(), union([string3(), number2(), boolean2(), array(string3())])).optional())
25473
+ content: preprocess((val) => val === null ? undefined : val, record4(string3(), union([string3(), number2(), boolean2(), array(string3())])).optional())
24154
25474
  });
24155
- var ResourceTemplateReferenceSchema = object6({
25475
+ var ResourceTemplateReferenceSchema = object8({
24156
25476
  type: literal("ref/resource"),
24157
25477
  uri: string3()
24158
25478
  });
24159
- var PromptReferenceSchema = object6({
25479
+ var PromptReferenceSchema = object8({
24160
25480
  type: literal("ref/prompt"),
24161
25481
  name: string3()
24162
25482
  });
24163
25483
  var CompleteRequestParamsSchema = BaseRequestParamsSchema.extend({
24164
25484
  ref: union([PromptReferenceSchema, ResourceTemplateReferenceSchema]),
24165
- argument: object6({
25485
+ argument: object8({
24166
25486
  name: string3(),
24167
25487
  value: string3()
24168
25488
  }),
24169
- context: object6({
24170
- arguments: record3(string3(), string3()).optional()
25489
+ context: object8({
25490
+ arguments: record4(string3(), string3()).optional()
24171
25491
  }).optional()
24172
25492
  });
24173
25493
  var CompleteRequestSchema = RequestSchema.extend({
@@ -24181,10 +25501,10 @@ var CompleteResultSchema = ResultSchema.extend({
24181
25501
  hasMore: optional(boolean2())
24182
25502
  })
24183
25503
  });
24184
- var RootSchema = object6({
25504
+ var RootSchema = object8({
24185
25505
  uri: string3().startsWith("file://"),
24186
25506
  name: string3().optional(),
24187
- _meta: record3(string3(), unknown()).optional()
25507
+ _meta: record4(string3(), unknown()).optional()
24188
25508
  });
24189
25509
  var ListRootsRequestSchema = RequestSchema.extend({
24190
25510
  method: literal("roots/list"),
@@ -25754,8 +27074,8 @@ class StdioServerTransport {
25754
27074
  }
25755
27075
 
25756
27076
  // src/adapters/claude-channel.ts
25757
- import { readFileSync as readFileSync12 } from "fs";
25758
- import { join as join25 } from "path";
27077
+ import { readFileSync as readFileSync14 } from "fs";
27078
+ import { join as join28 } from "path";
25759
27079
  init_hub_tools();
25760
27080
  init_envelope();
25761
27081
 
@@ -25794,7 +27114,7 @@ var channel = !toolsMode && process.env.AGENTHUB_CHANNEL === "1";
25794
27114
  function roles2() {
25795
27115
  const read = (name) => {
25796
27116
  try {
25797
- return JSON.parse(readFileSync12(join25(projectRoot2, ".agenthub", name), "utf8")).roles ?? {};
27117
+ return JSON.parse(readFileSync14(join28(projectRoot2, ".agenthub", name), "utf8")).roles ?? {};
25798
27118
  } catch {
25799
27119
  return {};
25800
27120
  }
@@ -25813,7 +27133,7 @@ function sessionProfile() {
25813
27133
  for (const name of ["config.json", "config.local.json"]) {
25814
27134
  let data;
25815
27135
  try {
25816
- data = JSON.parse(readFileSync12(join25(projectRoot2, ".agenthub", name), "utf8"));
27136
+ data = JSON.parse(readFileSync14(join28(projectRoot2, ".agenthub", name), "utf8"));
25817
27137
  } catch (error) {
25818
27138
  if (error.code === "ENOENT")
25819
27139
  continue;
@@ -25840,7 +27160,7 @@ function sessionRoles() {
25840
27160
  const read = (name) => {
25841
27161
  let raw;
25842
27162
  try {
25843
- raw = JSON.parse(readFileSync12(join25(projectRoot2, ".agenthub", name), "utf8")).catalog_roles;
27163
+ raw = JSON.parse(readFileSync14(join28(projectRoot2, ".agenthub", name), "utf8")).catalog_roles;
25844
27164
  } catch {
25845
27165
  return {};
25846
27166
  }
@@ -25869,7 +27189,7 @@ var HELD = `another session is attached to the hub as "${peerId}"; this one is s
25869
27189
  var INBOX_CAP = 200;
25870
27190
  function peerHeld() {
25871
27191
  try {
25872
- const status = JSON.parse(readFileSync12(join25(stateDir, "status.json"), "utf8"));
27192
+ const status = JSON.parse(readFileSync14(join28(stateDir, "status.json"), "utf8"));
25873
27193
  const peer = status.peers?.[peerId];
25874
27194
  if (channel && peer?.toolsOnly && !peer.claiming)
25875
27195
  return false;
@@ -25904,7 +27224,7 @@ var TOOLS_INSTRUCTIONS = ["agent-hub task tools for this project. Messages from
25904
27224
  `);
25905
27225
  var log = (line) => console.error(`[agent-hub] ${line}`);
25906
27226
  var text4 = (s) => ({ content: [{ type: "text", text: s }] });
25907
- var server = new Server({ name: "agent-hub", version: VERSION2 }, toolsMode ? { capabilities: { tools: {} }, instructions: TOOLS_INSTRUCTIONS } : { capabilities: channel ? { experimental: { "claude/channel": {} }, tools: {} } : { tools: {} }, instructions: INSTRUCTIONS });
27227
+ var server = new Server({ name: "agent-hub", version: VERSION3 }, toolsMode ? { capabilities: { tools: {} }, instructions: TOOLS_INSTRUCTIONS } : { capabilities: channel ? { experimental: { "claude/channel": {} }, tools: {} } : { tools: {} }, instructions: INSTRUCTIONS });
25908
27228
  var inbox = [];
25909
27229
  var hub;
25910
27230
  var detached;
@@ -26144,6 +27464,8 @@ async function callTool(req) {
26144
27464
  return text4(`not sent: ${res.error}`);
26145
27465
  if (res.recorded)
26146
27466
  return text4("recorded only ([FYI]): it is on the hub console and log, and no peer spent a turn on it");
27467
+ if (res.dropped === "hop")
27468
+ return text4(res.notice);
26147
27469
  const sent = `sent to: ${res.targets.join(", ") || "(no other peers attached)"}`;
26148
27470
  return text4(typeof res.notice === "string" ? `${sent}; ${res.notice}` : sent);
26149
27471
  }