@staix/agent-hub 0.12.37 → 0.12.38

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 (60) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/README.md +8 -6
  3. package/docs/agent-notes/adapters.md +16 -7
  4. package/docs/agent-notes/bus.md +3 -2
  5. package/docs/agent-notes/daemon.md +5 -3
  6. package/docs/agent-notes/tasks.md +4 -3
  7. package/docs/agent-notes/tests.md +2 -2
  8. package/docs/operations.md +21 -6
  9. package/docs/quickstart.md +13 -5
  10. package/docs/security.md +5 -1
  11. package/docs/smoke.md +22 -2
  12. package/docs/specs/2026-09-19-agent-hub-design.md +29 -5
  13. package/docs/verification/2026-10-19-agy-p1-t0.md +55 -0
  14. package/docs/verification/2026-10-19-bus-defer-reset.md +9 -0
  15. package/docs/verification/2026-10-19-kimi-agy-reviewer-isolation.md +220 -0
  16. package/docs/verification/2026-10-20-0.12.38.md +56 -0
  17. package/docs/verified.json +22 -14
  18. package/package.json +1 -1
  19. package/plugins/agent-hub/.claude-plugin/plugin.json +1 -1
  20. package/plugins/agent-hub/server.js +1507 -405
  21. package/src/adapters/agy/definition.ts +61 -0
  22. package/src/adapters/agy/entry.ts +76 -0
  23. package/src/adapters/agy/hooks.ts +81 -0
  24. package/src/adapters/claude-channel.ts +1 -0
  25. package/src/adapters/codex-review-isolation.ts +30 -16
  26. package/src/adapters/definitions/builtin-metadata.ts +5 -5
  27. package/src/adapters/definitions/native-cli-entry-claude.ts +3 -0
  28. package/src/adapters/definitions/native-cli-entry-codex.ts +5 -1
  29. package/src/adapters/definitions/native-cli-entry-jcode.ts +2 -0
  30. package/src/adapters/definitions/native-cli-entry-kimi.ts +12 -4
  31. package/src/adapters/definitions/native-cli-entry-local.ts +3 -0
  32. package/src/adapters/definitions/native-cli-entry-pi.ts +7 -1
  33. package/src/adapters/definitions/native-cli-entry-qwen.ts +3 -0
  34. package/src/adapters/definitions/native-cli-entry.ts +2 -0
  35. package/src/adapters/definitions/native-cli-retained.ts +15 -0
  36. package/src/adapters/definitions/qwen.ts +1 -1
  37. package/src/adapters/definitions/reviewer-variants.ts +4 -3
  38. package/src/adapters/kimi-review.ts +161 -0
  39. package/src/adapters/registry-types.ts +29 -1
  40. package/src/adapters/registry.ts +15 -6
  41. package/src/adapters/reviewer-mcp.ts +85 -0
  42. package/src/adapters/reviewer-print.ts +83 -20
  43. package/src/cli/agy-mcp.ts +370 -0
  44. package/src/cli/console-state.ts +6 -0
  45. package/src/cli/console.ts +2 -0
  46. package/src/cli/facts-hook.ts +15 -2
  47. package/src/cli/init.ts +7 -0
  48. package/src/cli/launch.ts +3 -1
  49. package/src/cli/main.ts +1 -0
  50. package/src/cli/output.ts +5 -0
  51. package/src/hub/bus.ts +17 -6
  52. package/src/hub/control-client.ts +7 -1
  53. package/src/hub/daemon.ts +169 -23
  54. package/src/hub/native-hook-launch.ts +37 -0
  55. package/src/hub/peer-catalog.ts +2 -3
  56. package/src/hub/reviewer-pool.ts +12 -6
  57. package/src/hub/routing.ts +3 -0
  58. package/src/hub/tasks.ts +7 -0
  59. package/src/ui/index.html +6 -1
  60. package/templates/AGENTS.block.md +1 -1
@@ -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;
757
+ try {
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(() => {});
729
782
  try {
730
- if (generation !== this.generation || launch.current?.() === false)
731
- throw new Error("reviewer isolation changed before child spawn");
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" };
@@ -2314,7 +2673,7 @@ var init_acp = __esm(() => {
2314
2673
  async start() {
2315
2674
  this.stopping = false;
2316
2675
  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 });
2676
+ 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
2677
  this.proc = proc;
2319
2678
  trackGroup(proc);
2320
2679
  proc.on("error", (e) => this.down(`spawn failed: ${e.message}`));
@@ -2744,10 +3103,10 @@ ${renderDigest(envs, false)}` }] });
2744
3103
  });
2745
3104
 
2746
3105
  // 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";
3106
+ import { spawn as spawn6, spawnSync as spawnSync2 } from "child_process";
3107
+ import { existsSync as existsSync4, lstatSync as lstatSync4, mkdtempSync as mkdtempSync3, rmSync as rmSync3 } from "fs";
3108
+ import { tmpdir as tmpdir3 } from "os";
3109
+ import { isAbsolute as isAbsolute3, join as join5 } from "path";
2751
3110
  function jcodeCommand(command, kind, socket, cwd, model, profile) {
2752
3111
  if (!command.length || command.some((word) => typeof word !== "string" || !word))
2753
3112
  throw new Error("jcode_cmd must name an executable argv");
@@ -2786,7 +3145,7 @@ var init_jcode = __esm(() => {
2786
3145
  if (!isAbsolute3(jcode.home))
2787
3146
  throw new Error("jcode_home must be an absolute native data path");
2788
3147
  jcodeCommand(jcode.command, "acp", "preview", jcode.cwd);
2789
- const root = mkdtempSync2(join4(tmpdir2(), "ahub-jcode-")), socket = join4(root, "j.sock");
3148
+ const root = mkdtempSync3(join5(tmpdir3(), "ahub-jcode-")), socket = join5(root, "j.sock");
2790
3149
  const cmd = jcodeCommand(jcode.command, "acp", socket, jcode.cwd, jcode.launchModel, jcode.providerProfile);
2791
3150
  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
3151
  this.jcode = jcode;
@@ -2794,15 +3153,15 @@ var init_jcode = __esm(() => {
2794
3153
  this.socket = socket;
2795
3154
  }
2796
3155
  async start() {
2797
- if (!existsSync3(join4(this.jcode.home, "mcp.json")))
3156
+ if (!existsSync4(join5(this.jcode.home, "mcp.json")))
2798
3157
  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)) {
3158
+ if (existsSync4(this.socket)) {
2800
3159
  this.preserveRoot = true;
2801
3160
  throw new Error("Jcode private socket is occupied; no daemon was adopted or removed");
2802
3161
  }
2803
3162
  this.ending = false;
2804
3163
  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"] });
3164
+ 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
3165
  this.server = server;
2807
3166
  trackGroup(server);
2808
3167
  try {
@@ -2818,7 +3177,7 @@ var init_jcode = __esm(() => {
2818
3177
  ready.once("data", () => {
2819
3178
  clearTimeout(timer);
2820
3179
  server.off("exit", fail);
2821
- if (lstatSync3(this.socket, { throwIfNoEntry: false })?.isSocket())
3180
+ if (lstatSync4(this.socket, { throwIfNoEntry: false })?.isSocket())
2822
3181
  resolve();
2823
3182
  else
2824
3183
  reject(new Error("Jcode readiness has no private socket"));
@@ -2887,7 +3246,7 @@ var init_jcode = __esm(() => {
2887
3246
  }
2888
3247
  if (!failure && !this.server) {
2889
3248
  if (!this.preserveRoot)
2890
- rmSync2(this.root, { recursive: true, force: true });
3249
+ rmSync3(this.root, { recursive: true, force: true });
2891
3250
  super.setState("offline");
2892
3251
  }
2893
3252
  if (failure)
@@ -2900,11 +3259,11 @@ var init_jcode = __esm(() => {
2900
3259
  });
2901
3260
 
2902
3261
  // src/pi/launch.ts
2903
- import { join as join5 } from "path";
3262
+ import { join as join6 } from "path";
2904
3263
  function buildPiLaunch(opts, inherited, extension, bridgePort, bridgeToken) {
2905
3264
  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")];
3265
+ 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) };
3266
+ 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
3267
  if (opts.sessionFile)
2909
3268
  args.push("--session", opts.sessionFile);
2910
3269
  else if (opts.sessionId)
@@ -2940,8 +3299,8 @@ var init_recovery_store = __esm(() => {
2940
3299
  });
2941
3300
 
2942
3301
  // 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";
3302
+ import { readFileSync as readFileSync4, openSync, closeSync, fsyncSync, mkdirSync as mkdirSync3 } from "fs";
3303
+ import { join as join7, relative as relative2, resolve as resolve5, dirname as dirname5 } from "path";
2945
3304
  function sessionReadFailure(error) {
2946
3305
  if (error.code === "ENOENT")
2947
3306
  throw new PiSessionUnresumable("missing", error.message, { cause: error });
@@ -2951,16 +3310,16 @@ function validatePiSession(stateDir, cwd, sessionFile, sessionId) {
2951
3310
  let file, sessions;
2952
3311
  try {
2953
3312
  file = realPath(sessionFile);
2954
- sessions = realPath(join6(stateDir, "pi-sessions"));
3313
+ sessions = realPath(join7(stateDir, "pi-sessions"));
2955
3314
  } catch (error) {
2956
3315
  return sessionReadFailure(error);
2957
3316
  }
2958
3317
  const rel = relative2(sessions, file);
2959
- if (!rel || rel.startsWith("..") || resolve4(sessions, rel) !== file)
3318
+ if (!rel || rel.startsWith("..") || resolve5(sessions, rel) !== file)
2960
3319
  throw new PiSessionUnresumable("outside_managed", "Pi session file is outside the managed project session directory");
2961
3320
  let first;
2962
3321
  try {
2963
- first = readFileSync3(file, "utf8").split(`
3322
+ first = readFileSync4(file, "utf8").split(`
2964
3323
  `, 1)[0];
2965
3324
  } catch (error) {
2966
3325
  return sessionReadFailure(error);
@@ -3009,11 +3368,11 @@ var init_permission_mode = __esm(() => {
3009
3368
  });
3010
3369
 
3011
3370
  // 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";
3371
+ import { accessSync, constants, lstatSync as lstatSync5, opendirSync, readlinkSync, statSync as statSync2 } from "fs";
3372
+ import { dirname as dirname6, isAbsolute as isAbsolute4, join as join8, relative as relative3, resolve as resolve6, sep as sep2 } from "path";
3014
3373
  function existing(path) {
3015
3374
  try {
3016
- lstatSync4(path);
3375
+ lstatSync5(path);
3017
3376
  return true;
3018
3377
  } catch (error) {
3019
3378
  if (error.code === "ENOENT")
@@ -3024,17 +3383,17 @@ function existing(path) {
3024
3383
  function policyRealPath(path) {
3025
3384
  let ancestor = path;
3026
3385
  while (!existing(ancestor)) {
3027
- const parent = dirname5(ancestor);
3386
+ const parent = dirname6(ancestor);
3028
3387
  if (parent === ancestor)
3029
3388
  throw new Error("path cannot be resolved");
3030
3389
  ancestor = parent;
3031
3390
  }
3032
- return join7(realPath(ancestor), relative3(ancestor, path));
3391
+ return join8(realPath(ancestor), relative3(ancestor, path));
3033
3392
  }
3034
3393
  function policyTargets(cwd, paths) {
3035
3394
  const root = realPath(cwd);
3036
3395
  return [...new Set(paths.flatMap((path) => {
3037
- const lexical = resolve5(root, path);
3396
+ const lexical = resolve6(root, path);
3038
3397
  return [lexical, policyRealPath(lexical)];
3039
3398
  }))];
3040
3399
  }
@@ -3052,14 +3411,14 @@ function directAliasTarget(path) {
3052
3411
  }
3053
3412
  let ancestor = target;
3054
3413
  while (!existing(ancestor)) {
3055
- const parent = dirname5(ancestor);
3414
+ const parent = dirname6(ancestor);
3056
3415
  if (parent === ancestor)
3057
3416
  return;
3058
3417
  ancestor = parent;
3059
3418
  }
3060
- if (!lstatSync4(ancestor).isSymbolicLink())
3419
+ if (!lstatSync5(ancestor).isSymbolicLink())
3061
3420
  return;
3062
- target = resolve5(dirname5(ancestor), readlinkSync(ancestor), relative3(ancestor, target));
3421
+ target = resolve6(dirname6(ancestor), readlinkSync(ancestor), relative3(ancestor, target));
3063
3422
  }
3064
3423
  return;
3065
3424
  }
@@ -3076,7 +3435,7 @@ function aliasScope(dir, lexical, actual, includeParents, budget) {
3076
3435
  budget.entries++;
3077
3436
  if (!entry.isSymbolicLink())
3078
3437
  continue;
3079
- const target = directAliasTarget(join7(dir, entry.name));
3438
+ const target = directAliasTarget(join8(dir, entry.name));
3080
3439
  if (target !== undefined && (beneath(lexical, target) || beneath(actual, target) || includeParents && (beneath(target, lexical) || beneath(target, actual))))
3081
3440
  matched = true;
3082
3441
  }
@@ -3172,7 +3531,7 @@ function globScope(root, lexical, actual, path, includeParents, budget, roots, a
3172
3531
  if (++budget.entries > 4096)
3173
3532
  throw new Error("policy glob discovery unavailable");
3174
3533
  const next = advance(pattern, states, entry.name);
3175
- if (next.size && visit(join7(dir, entry.name), next, depth + 1, chain))
3534
+ if (next.size && visit(join8(dir, entry.name), next, depth + 1, chain))
3176
3535
  return true;
3177
3536
  }
3178
3537
  } finally {
@@ -3188,7 +3547,7 @@ function matchesPolicy(cwd, path, paths, includeParents = false) {
3188
3547
  try {
3189
3548
  if (path.length > 4096)
3190
3549
  return true;
3191
- const root = realPath(cwd), lexical = resolve5(root, path), actual = policyRealPath(lexical);
3550
+ const root = realPath(cwd), lexical = resolve6(root, path), actual = policyRealPath(lexical);
3192
3551
  includeParents &&= !existing(lexical) || statSync2(lexical).isDirectory();
3193
3552
  const budget = { entries: 0 }, roots = new Set, exact = [];
3194
3553
  if (paths.some((entry) => {
@@ -3223,7 +3582,7 @@ var init_path_policy = __esm(() => {
3223
3582
  });
3224
3583
 
3225
3584
  // src/local/deny.ts
3226
- import { basename as basename3, dirname as dirname6 } from "path";
3585
+ import { basename as basename4, dirname as dirname7 } from "path";
3227
3586
  function hubWriteRegexes(root, gitDirs = [], moduleGit) {
3228
3587
  const git = hfsFoldedName(".git");
3229
3588
  const re = (s) => `(regex ${sbplString(s)})`;
@@ -3235,7 +3594,7 @@ function hubWriteRegexes(root, gitDirs = [], moduleGit) {
3235
3594
  const modules = moduleGit ? [
3236
3595
  `(require-all ${re(`^${sbplEscape(moduleGit.root)}/(.*/)?${hfsFoldedName("config")}$`)} (require-not ${re(`^${sbplEscape(moduleGit.root)}/(.*/)?(refs|logs)/`)}))`,
3237
3596
  `(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))}$`))
3597
+ ...moduleGit.entries.map((d) => re(`^${sbplEscape(dirname7(d))}/${hfsFoldedName(basename4(d))}$`))
3239
3598
  ] : [];
3240
3599
  return [
3241
3600
  `(require-all (subpath ${sbplString(root)}) ${re(`^.*/${hfsFoldedName(".agenthub")}(/|$)`)})`,
@@ -3267,10 +3626,10 @@ var init_deny = __esm(() => {
3267
3626
  });
3268
3627
 
3269
3628
  // 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";
3629
+ import { spawn as spawn7, spawnSync as spawnSync3 } from "child_process";
3630
+ import { existsSync as existsSync5, mkdtempSync as mkdtempSync4, readdirSync, rmSync as rmSync4 } from "fs";
3631
+ import { homedir as homedir3, tmpdir as tmpdir4 } from "os";
3632
+ import { dirname as dirname8, join as join9, resolve as resolve7 } from "path";
3274
3633
  function developerDir() {
3275
3634
  const out = spawnSync3("xcode-select", ["-p"], { encoding: "utf8" });
3276
3635
  let dir = out.status === 0 ? out.stdout.trim() : "";
@@ -3284,14 +3643,14 @@ function externalGitDirs(root) {
3284
3643
  if (out.status !== 0)
3285
3644
  return [];
3286
3645
  const dirs = out.stdout.trim().split(`
3287
- `).map((d) => resolve6(root, d));
3646
+ `).map((d) => resolve7(root, d));
3288
3647
  return [...new Set(dirs)].filter((d) => !d.startsWith(`${root}/`));
3289
3648
  }
3290
3649
  function moduleGitDirs(root) {
3291
3650
  const out = memoryGitSync(["-C", root, "rev-parse", "--git-common-dir"], { encoding: "utf8" });
3292
3651
  if (out.status !== 0)
3293
3652
  return;
3294
- const modulesRoot = join8(resolve6(root, out.stdout.trim()), "modules");
3653
+ const modulesRoot = join9(resolve7(root, out.stdout.trim()), "modules");
3295
3654
  const dirs = [];
3296
3655
  const walk = (dir, depth) => {
3297
3656
  if (depth > 8)
@@ -3305,31 +3664,31 @@ function moduleGitDirs(root) {
3305
3664
  const names = new Set(entries.map((e) => e.name));
3306
3665
  if (names.has("HEAD") && names.has("config")) {
3307
3666
  dirs.push(dir);
3308
- const nested = join8(dir, "modules");
3667
+ const nested = join9(dir, "modules");
3309
3668
  if (names.has("modules"))
3310
3669
  walk(nested, depth + 1);
3311
3670
  return;
3312
3671
  }
3313
3672
  for (const e of entries)
3314
3673
  if (e.isDirectory())
3315
- walk(join8(dir, e.name), depth + 1);
3674
+ walk(join9(dir, e.name), depth + 1);
3316
3675
  };
3317
3676
  walk(modulesRoot, 0);
3318
3677
  const entries = new Set([modulesRoot]);
3319
3678
  for (const d of dirs)
3320
- for (let p = d;p.length > modulesRoot.length; p = dirname7(p))
3679
+ for (let p = d;p.length > modulesRoot.length; p = dirname8(p))
3321
3680
  entries.add(p);
3322
3681
  return { root: modulesRoot, entries: [...entries] };
3323
3682
  }
3324
3683
  function profile(cwd, network, readAllow = [], deny = [], readOnly = []) {
3325
- const home = homedir2();
3326
- const inHome = (p) => p.startsWith("~/") ? join8(home, p.slice(2)) : p;
3684
+ const home = homedir3();
3685
+ const inHome = (p) => p.startsWith("~/") ? join9(home, p.slice(2)) : p;
3327
3686
  const root = realPath(cwd);
3328
3687
  const gitDirs = externalGitDirs(root);
3329
3688
  const moduleGit = moduleGitDirs(root);
3330
3689
  const creds = [".ssh", ".aws", ".gnupg", ".config/gh", ".config/gcloud", ".kube", ".docker", ".netrc", ".npmrc", ".omniroute", ".claude", ".codex", ".kimi-code", "Library/Keychains"];
3331
3690
  const dev = developerDir();
3332
- const readable = [root, ...HOME_READABLE.map((p) => join8(home, p)), ...readAllow.map(inHome), ...gitDirs, ...dev ? [dev] : []];
3691
+ const readable = [root, ...HOME_READABLE.map((p) => join9(home, p)), ...readAllow.map(inHome), ...gitDirs, ...dev ? [dev] : []];
3333
3692
  const subpaths = (paths) => paths.map((p) => `(subpath ${q(p)})`).join(" ");
3334
3693
  const globals = (names) => names.map((n) => `(global-name ${q(n)})`).join(" ");
3335
3694
  const proxy = typeof network === "object" ? `(allow network-outbound (remote ip ${q(`localhost:${network.proxyPort}`)}))` : undefined;
@@ -3347,14 +3706,14 @@ function profile(cwd, network, readAllow = [], deny = [], readOnly = []) {
3347
3706
  '(allow file-ioctl (regex #"^/dev/"))',
3348
3707
  ...proxy ? [proxy] : network ? ["(allow network*)"] : []
3349
3708
  ];
3350
- const places = `${creds.map((c) => `(subpath ${q(join8(home, c))})`).join(" ")} ${denyRegexes(root, deny, false).join(" ")}`;
3709
+ const places = `${creds.map((c) => `(subpath ${q(join9(home, c))})`).join(" ")} ${denyRegexes(root, deny, false).join(" ")}`;
3351
3710
  return [
3352
3711
  "(version 1)",
3353
3712
  ...start,
3354
3713
  "(deny file-write*)",
3355
3714
  ...readOnly.length ? ['(allow file-write* (literal "/dev/null"))'] : [`(allow file-write* (subpath ${q(root)}) (regex #"^/dev/") ${gitDirs.map((d) => `(subpath ${q(d)})`).join(" ")})`],
3356
3715
  `(deny file-write* ${hubWriteRegexes(root, gitDirs, moduleGit).join(" ")})`,
3357
- `(deny file-read* file-write* ${creds.map((c) => `(subpath ${q(join8(home, c))})`).join(" ")})`,
3716
+ `(deny file-read* file-write* ${creds.map((c) => `(subpath ${q(join9(home, c))})`).join(" ")})`,
3358
3717
  `(deny file-read* file-write* ${denyRegexes(root, deny).join(" ")})`,
3359
3718
  ...network ? [`(allow file-read* ${CA_BUNDLES.map((p) => `(literal ${q(p)})`).join(" ")} (regex ${q("/certifi/cacert\\.pem$")}))`, `(deny file-read* file-write* ${places})`] : []
3360
3719
  ].join(`
@@ -3368,11 +3727,11 @@ function sandboxedExec(argv, opts) {
3368
3727
  if (opts.signal?.aborted)
3369
3728
  return Promise.resolve({ code: null, output: "error: command cancelled before execution" });
3370
3729
  return new Promise((resolve) => {
3371
- const own = realPath(mkdtempSync3(join8(tmpdir3(), "ahub-cmd-")));
3730
+ const own = realPath(mkdtempSync4(join9(tmpdir4(), "ahub-cmd-")));
3372
3731
  const sandbox = `${opts.profile}
3373
3732
  (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 });
3733
+ 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}/` };
3734
+ const child = spawn7(SANDBOX_EXEC, ["-p", sandbox, ...argv], { cwd: opts.cwd, env, stdio: ["ignore", "pipe", "pipe"], detached: true });
3376
3735
  let output = "";
3377
3736
  const take = (d) => {
3378
3737
  if (output.length < OUTPUT_CAP)
@@ -3391,11 +3750,11 @@ function sandboxedExec(argv, opts) {
3391
3750
  opts.signal?.addEventListener("abort", abort, { once: true });
3392
3751
  const dropOwn = () => {
3393
3752
  try {
3394
- rmSync3(own, { recursive: true, force: true });
3753
+ rmSync4(own, { recursive: true, force: true });
3395
3754
  } catch {
3396
3755
  try {
3397
3756
  spawnSync3("chflags", ["-R", "nouchg", own]);
3398
- rmSync3(own, { recursive: true, force: true });
3757
+ rmSync4(own, { recursive: true, force: true });
3399
3758
  } catch {}
3400
3759
  }
3401
3760
  };
@@ -3424,7 +3783,7 @@ function proxyEnv(network) {
3424
3783
  const url = `http://127.0.0.1:${network.proxyPort}`;
3425
3784
  return { HTTPS_PROXY: url, https_proxy: url, HTTP_PROXY: url, http_proxy: url, ALL_PROXY: url, all_proxy: url, NO_PROXY: "", no_proxy: "" };
3426
3785
  }
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;
3786
+ 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
3787
  var init_sandbox = __esm(() => {
3429
3788
  init_project();
3430
3789
  init_git();
@@ -3439,29 +3798,29 @@ var init_sandbox = __esm(() => {
3439
3798
  });
3440
3799
 
3441
3800
  // 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";
3801
+ import { lstatSync as lstatSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync5, statSync as statSync3, writeFileSync as writeFileSync3 } from "fs";
3802
+ import { dirname as dirname9, join as join10, relative as relative4, resolve as resolve8 } from "path";
3444
3803
  function guardPath(ctx, path, mode) {
3445
3804
  const root = realPath(ctx.cwd);
3446
- const abs = resolve7(root, path);
3805
+ const abs = resolve8(root, path);
3447
3806
  let existing = abs;
3448
3807
  while (!lexists(existing))
3449
- existing = dirname8(existing);
3808
+ existing = dirname9(existing);
3450
3809
  let realExisting;
3451
3810
  try {
3452
3811
  realExisting = realPath(existing);
3453
3812
  } catch {
3454
3813
  throw new Error(`${path} goes through a dangling symlink`);
3455
3814
  }
3456
- const real = join9(realExisting, relative4(existing, abs));
3815
+ const real = join10(realExisting, relative4(existing, abs));
3457
3816
  const rel = relative4(root, real);
3458
- if (rel.startsWith("..") || resolve7(root, rel) !== real)
3817
+ if (rel.startsWith("..") || resolve8(root, rel) !== real)
3459
3818
  throw new Error(`${path} is outside the project directory`);
3460
3819
  if (isDenied(rel, ctx.deny))
3461
3820
  throw new Error(`${path} is on the secrets denylist`);
3462
3821
  if (mode === "write" && isHubWritePath(rel))
3463
3822
  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)
3823
+ if (mode === "write" && ctx.readOnly?.length && (lstatSync6(real, { throwIfNoEntry: false })?.nlink ?? 1) > 1)
3465
3824
  throw new Error("hardlinked write target cannot be verified with data.readonly");
3466
3825
  if (mode === "write" && matchesPolicy(root, path, ctx.readOnly ?? []))
3467
3826
  throw new Error("target is protected by data.readonly");
@@ -3511,7 +3870,7 @@ async function runTool(name, rawArgs, ctx) {
3511
3870
  const file = guardPath(ctx, String(a.path), "read");
3512
3871
  if (statSync3(file).size > 5000000)
3513
3872
  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(`
3873
+ const lines = readFileSync5(file, "utf8").split(`
3515
3874
  `);
3516
3875
  const from = Math.max(1, Number(a.offset) || 1);
3517
3876
  const out = lines.slice(from - 1, from - 1 + (Number(a.limit) || 2000)).map((l, i) => `${from + i} ${l}`).join(`
@@ -3532,14 +3891,14 @@ ${preview(content)}`, "write", ctx.signal, file, undefined, String(a.path)), "er
3532
3891
  file = guardPath(ctx, String(a.path), "write");
3533
3892
  if (file !== approvedTarget)
3534
3893
  return "error: path target changed during approval; inspect before retrying";
3535
- mkdirSync3(dirname8(file), { recursive: true });
3536
- writeFileSync2(file, content);
3894
+ mkdirSync4(dirname9(file), { recursive: true });
3895
+ writeFileSync3(file, content);
3537
3896
  return `wrote ${a.path}`;
3538
3897
  }
3539
3898
  case "edit": {
3540
3899
  let file = guardPath(ctx, String(a.path), "write");
3541
3900
  const approvedTarget = file;
3542
- let text = readFileSync4(file, "utf8");
3901
+ let text = readFileSync5(file, "utf8");
3543
3902
  let count = text.split(String(a.old)).length - 1;
3544
3903
  if (!a.old || count !== 1)
3545
3904
  return `error: \`old\` must match exactly once, it matched ${count} times`;
@@ -3553,11 +3912,11 @@ ${preview(content)}`, "write", ctx.signal, file, undefined, String(a.path)), "er
3553
3912
  file = guardPath(ctx, String(a.path), "write");
3554
3913
  if (file !== approvedTarget)
3555
3914
  return "error: path target changed during approval; inspect before retrying";
3556
- text = readFileSync4(file, "utf8");
3915
+ text = readFileSync5(file, "utf8");
3557
3916
  count = text.split(String(a.old)).length - 1;
3558
3917
  if (count !== 1)
3559
3918
  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 ?? "")));
3919
+ writeFileSync3(file, text.replace(String(a.old), () => String(a.new ?? "")));
3561
3920
  return `edited ${a.path}`;
3562
3921
  }
3563
3922
  case "bash": {
@@ -3607,7 +3966,7 @@ ${preview(content)}`, "write", ctx.signal, file, undefined, String(a.path)), "er
3607
3966
  }
3608
3967
  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
3968
  try {
3610
- lstatSync5(p);
3969
+ lstatSync6(p);
3611
3970
  return true;
3612
3971
  } catch {
3613
3972
  return false;
@@ -3661,9 +4020,9 @@ var PI_CEILING_KIND = "tool-step-ceiling", PI_CEILING_UNIT = "tool-step";
3661
4020
 
3662
4021
  // src/adapters/pi.ts
3663
4022
  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";
4023
+ import { existsSync as existsSync6, mkdirSync as mkdirSync5 } from "fs";
4024
+ import { join as join11, resolve as resolve9 } from "path";
4025
+ import { spawn as spawn8 } from "child_process";
3667
4026
  function ownerStillAlive(pid, signature) {
3668
4027
  const current = processSignature(pid);
3669
4028
  if (current !== undefined)
@@ -3779,7 +4138,7 @@ var init_pi = __esm(() => {
3779
4138
  get recoveryReady() {
3780
4139
  if (this.state === "busy" || this.starting || this.activeTools)
3781
4140
  return false;
3782
- if (this.sessionFile && !existsSync5(this.sessionFile) && (this.activityObserved || this.persistedOnce))
4141
+ if (this.sessionFile && !existsSync6(this.sessionFile) && (this.activityObserved || this.persistedOnce))
3783
4142
  return false;
3784
4143
  if (this.opts.mode === "headless")
3785
4144
  return this.state === "idle" || !this.proc || this.proc.exitCode !== null || this.proc.signalCode !== null;
@@ -3798,7 +4157,7 @@ var init_pi = __esm(() => {
3798
4157
  return this.ownerClaimed && this.ownerPid && this.ownerSignature ? `${this.ownerPid}:${this.ownerSignature}:${this.sessionId ?? ""}` : undefined;
3799
4158
  }
3800
4159
  recoveryMetadata() {
3801
- const exists = !!this.sessionFile && existsSync5(this.sessionFile);
4160
+ const exists = !!this.sessionFile && existsSync6(this.sessionFile);
3802
4161
  if (exists) {
3803
4162
  this.persistedOnce = true;
3804
4163
  this.verifiedEmptyResume = undefined;
@@ -3811,14 +4170,14 @@ var init_pi = __esm(() => {
3811
4170
  if (this.state === "offline") {
3812
4171
  if (this.canReuseVerifiedEmptyResume())
3813
4172
  return this.recoveryMetadata();
3814
- if (this.recoveryReady && this.sessionFile && existsSync5(this.sessionFile))
4173
+ if (this.recoveryReady && this.sessionFile && existsSync6(this.sessionFile))
3815
4174
  return this.recoveryMetadata();
3816
4175
  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
4176
  }
3818
4177
  this.emptyResumeVerified = false;
3819
4178
  if (this.state !== "idle" || this.stopping || this.activeTools)
3820
4179
  throw new Error("Pi must settle before session capture");
3821
- if (this.sessionFile && existsSync5(this.sessionFile))
4180
+ if (this.sessionFile && existsSync6(this.sessionFile))
3822
4181
  return this.recoveryMetadata();
3823
4182
  const snapshot = this.opts.mode === "headless" ? (await this.waitRpc("get_state", 15000)).data : await this.sendTui({ type: "get_session_state" });
3824
4183
  if (this.state !== "idle" || this.activeTools || snapshot?.sessionId !== this.sessionId || snapshot?.sessionFile !== this.sessionFile)
@@ -3833,7 +4192,7 @@ var init_pi = __esm(() => {
3833
4192
  return captured;
3834
4193
  }
3835
4194
  canReuseVerifiedEmptyResume() {
3836
- return !!this.verifiedEmptyResume && this.recoveryReady && !this.activityObserved && !this.persistedOnce && this.sessionId === this.verifiedEmptyResume.sessionId && (!this.sessionFile || !existsSync5(this.sessionFile));
4195
+ return !!this.verifiedEmptyResume && this.recoveryReady && !this.activityObserved && !this.persistedOnce && this.sessionId === this.verifiedEmptyResume.sessionId && (!this.sessionFile || !existsSync6(this.sessionFile));
3837
4196
  }
3838
4197
  noteActivity() {
3839
4198
  this.activityObserved = true;
@@ -4025,9 +4384,9 @@ var init_pi = __esm(() => {
4025
4384
  this.approvalAnswerEpoch = undefined;
4026
4385
  this.approvalStopReason = "";
4027
4386
  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 });
4387
+ mkdirSync5(this.opts.stateDir, { recursive: true });
4388
+ const sessions = join11(this.opts.stateDir, "pi-sessions");
4389
+ mkdirSync5(sessions, { recursive: true, mode: 448 });
4031
4390
  if (this.opts.sessionFile) {
4032
4391
  try {
4033
4392
  validatePiSession(this.opts.stateDir, this.opts.cwd, this.opts.sessionFile, this.opts.sessionId);
@@ -4084,7 +4443,7 @@ var init_pi = __esm(() => {
4084
4443
  return new Response("not found", { status: 404 });
4085
4444
  } });
4086
4445
  this.server = bridge;
4087
- const extension = resolve8(join10(import.meta.dir, "../pi/extension.ts"));
4446
+ const extension = resolve9(join11(import.meta.dir, "../pi/extension.ts"));
4088
4447
  const inherited = {};
4089
4448
  for (const key of ["PATH", "HOME", "USER", "SHELL", "TMPDIR", "TERM", "TERM_PROGRAM", "LANG", "LC_ALL", "LC_CTYPE", "NO_COLOR", "CODEX_HOME"])
4090
4449
  if (process.env[key])
@@ -4094,7 +4453,7 @@ var init_pi = __esm(() => {
4094
4453
  if (this.opts.mode === "tui") {
4095
4454
  return;
4096
4455
  }
4097
- this.proc = spawn7(bin, args, { cwd: this.opts.cwd, env, stdio: ["pipe", "pipe", "pipe"], detached: true });
4456
+ this.proc = spawn8(bin, args, { cwd: this.opts.cwd, env, stdio: ["pipe", "pipe", "pipe"], detached: true });
4098
4457
  trackGroup(this.proc);
4099
4458
  this.proc.stdout.on("data", (chunk) => this.onOutput(String(chunk)));
4100
4459
  this.proc.stderr.on("data", (chunk) => this.opts.log?.(`[${this.id}] ${String(chunk).trimEnd()}`));
@@ -4126,7 +4485,7 @@ var init_pi = __esm(() => {
4126
4485
  this.ownerToken = String(this._tuiLaunch?.env.AGENTHUB_PI_OWNER_TOKEN ?? "");
4127
4486
  this.ownerClaimed = true;
4128
4487
  }
4129
- this.persistedOnce = existsSync5(this.sessionFile);
4488
+ this.persistedOnce = existsSync6(this.sessionFile);
4130
4489
  if (this.opts.sessionId && this.sessionId !== this.opts.sessionId || this.opts.sessionFile && this.sessionFile !== this.opts.sessionFile)
4131
4490
  throw new Error("Pi startup session identity mismatch");
4132
4491
  this.started = true;
@@ -5865,7 +6224,7 @@ var init_signals = __esm(() => {
5865
6224
  });
5866
6225
 
5867
6226
  // src/models/route/labels.ts
5868
- import { createHash as createHash3, randomUUID as randomUUID3 } from "crypto";
6227
+ import { createHash as createHash4, randomUUID as randomUUID3 } from "crypto";
5869
6228
 
5870
6229
  class RouteLabelTracker {
5871
6230
  sink;
@@ -6015,7 +6374,7 @@ function safeJson(value) {
6015
6374
  return "";
6016
6375
  }
6017
6376
  }
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");
6377
+ 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
6378
  var init_labels = __esm(() => {
6020
6379
  init_signals();
6021
6380
  });
@@ -6883,9 +7242,9 @@ var init_shipped_roles = __esm(() => {
6883
7242
  });
6884
7243
 
6885
7244
  // 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";
7245
+ import { createHash as createHash5 } from "crypto";
7246
+ import { constants as constants2, closeSync as closeSync2, fstatSync, lstatSync as lstatSync7, openSync as openSync2, readSync, readdirSync as readdirSync2 } from "fs";
7247
+ import { join as join12 } from "path";
6889
7248
  function parseRole(text, stem, layer) {
6890
7249
  if (Buffer.byteLength(text) > 8192 || text.includes("\r") || !ID.test(stem))
6891
7250
  throw new Error("invalid role size, encoding or file id");
@@ -6929,7 +7288,7 @@ function parseRole(text, stem, layer) {
6929
7288
  }
6930
7289
  const reviewStart = sections[4].index + sections[4][0].length;
6931
7290
  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") };
7291
+ 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
7292
  }
6934
7293
  function projectRole(file) {
6935
7294
  const fd = openSync2(file, constants2.O_RDONLY | constants2.O_NOFOLLOW | constants2.O_NONBLOCK);
@@ -6964,9 +7323,9 @@ function loadRoleCatalog(cwd) {
6964
7323
  if (!cwd)
6965
7324
  return { roles, errors };
6966
7325
  try {
6967
- const config = join11(cwd, ".agenthub"), dir = join11(config, "roles");
7326
+ const config = join12(cwd, ".agenthub"), dir = join12(config, "roles");
6968
7327
  for (const path of [config, dir]) {
6969
- const stat = lstatSync6(path);
7328
+ const stat = lstatSync7(path);
6970
7329
  if (!stat.isDirectory() || stat.isSymbolicLink())
6971
7330
  throw new Error("unsafe role directory");
6972
7331
  }
@@ -6976,7 +7335,7 @@ function loadRoleCatalog(cwd) {
6976
7335
  for (const file of files) {
6977
7336
  const id = file.slice(0, -3);
6978
7337
  try {
6979
- roles.set(id, parseRole(projectRole(join11(dir, file)), id, "project"));
7338
+ roles.set(id, parseRole(projectRole(join12(dir, file)), id, "project"));
6980
7339
  } catch {
6981
7340
  errors.push({ id: ID.test(id) ? id : "invalid-file-id", reason: "invalid or unsafe project role" });
6982
7341
  }
@@ -7878,21 +8237,748 @@ function reviewerVariant(kind, displayName, reviewer, display) {
7878
8237
  display
7879
8238
  };
7880
8239
  }
7881
- var codexVariant, CODEX_REVIEWER_VARIANT, KIMI_REVIEWER_VARIANT, AGY_DEFINITION;
8240
+ var codexVariant, CODEX_REVIEWER_VARIANT, KIMI_REVIEWER_VARIANT, AGY_REVIEWER_VARIANT;
7882
8241
  var init_reviewer_variants = __esm(() => {
7883
8242
  init_claude_print();
7884
8243
  init_codex_review_isolation();
8244
+ init_kimi_review();
7885
8245
  init_native_factories();
7886
8246
  init_native_display();
7887
8247
  codexVariant = reviewerVariant("codex", "Codex reviewer", { backend: codexReviewerBackend, defaultPool: true, declaration: "explicit" }, CODEX_DISPLAY);
7888
8248
  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" } }),
8249
+ KIMI_REVIEWER_VARIANT = reviewerVariant("kimi", "Kimi reviewer", { backend: kimiReviewerBackend, defaultPool: true, declaration: "explicit" }, KIMI_DISPLAY);
8250
+ AGY_REVIEWER_VARIANT = {
8251
+ ...reviewerVariant("agy", "agy reviewer", { defaultPool: false, declaration: "automatic", unavailable: "agy is an implementer peer (#485)" }, { palette: { fg: "#6558a6", bg: "#f1effa" } }),
7892
8252
  optional: true
7893
8253
  };
7894
8254
  });
7895
8255
 
8256
+ // src/adapters/agy/hooks.ts
8257
+ import { closeSync as closeSync3, constants as constants3, fstatSync as fstatSync2, lstatSync as lstatSync8, openSync as openSync3, readSync as readSync2 } from "fs";
8258
+ import { homedir as homedir4 } from "os";
8259
+ import { isAbsolute as isAbsolute5, join as join13, relative as relative5 } from "path";
8260
+ import { createHash as createHash6 } from "crypto";
8261
+ function decodeAgyHook(event, value) {
8262
+ 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)))
8263
+ return;
8264
+ if (event === "Stop" && typeof value.fullyIdle !== "boolean")
8265
+ return;
8266
+ const call = record3(value.toolCall) ? value.toolCall : undefined;
8267
+ if ((event === "PreToolUse" || event === "PostToolUse") && (!call || typeof call.name !== "string" || call.name.length > 128 || !record3(call.args)))
8268
+ return;
8269
+ const native = call?.args;
8270
+ const observedRead = call?.name === "view_file" && typeof native?.AbsolutePath === "string";
8271
+ const phase = { SessionStart: "session", PreInvocation: "start", PostInvocation: "post", PreToolUse: "pre", PostToolUse: "post", Stop: "stop" }[event];
8272
+ return {
8273
+ event,
8274
+ phase,
8275
+ sessionId: value.conversationId,
8276
+ transcriptPath: value.transcriptPath,
8277
+ workspacePaths: value.workspacePaths,
8278
+ tool: observedRead ? "Read" : typeof call?.name === "string" ? call.name : "",
8279
+ input: observedRead ? { file_path: native.AbsolutePath } : native ?? {},
8280
+ ...Number.isSafeInteger(value.stepIdx) && value.stepIdx >= 0 ? { toolUseId: `${value.conversationId}:${value.stepIdx}` } : {},
8281
+ ...Number.isSafeInteger(value.invocationNum) && value.invocationNum >= 0 ? { invocation: value.invocationNum } : {},
8282
+ ...event === "Stop" ? { fullyIdle: value.fullyIdle, terminationReason: typeof value.terminationReason === "string" ? value.terminationReason : "" } : {}
8283
+ };
8284
+ }
8285
+ function encodeAgyHook(event, text) {
8286
+ if (text && (event === "SessionStart" || event === "PreInvocation"))
8287
+ return { injectSteps: [{ ephemeralMessage: text }] };
8288
+ return;
8289
+ }
8290
+ function readAgyTranscript(session, transcript) {
8291
+ const empty = { factIds: [] };
8292
+ if (!UUID.test(session))
8293
+ return empty;
8294
+ const home = process.env.HOME ?? homedir4();
8295
+ const candidates = ["antigravity-cli", "antigravity", "antigravity-ide"].map((surface) => join13(home, ".gemini", surface, "brain", session, ".system_generated/logs/transcript_full.jsonl"));
8296
+ if (!candidates.includes(transcript))
8297
+ return empty;
8298
+ let fd;
8299
+ try {
8300
+ let cursor = home;
8301
+ for (const name of relative5(home, transcript).split("/")) {
8302
+ cursor = join13(cursor, name);
8303
+ if (lstatSync8(cursor).isSymbolicLink())
8304
+ return empty;
8305
+ }
8306
+ fd = openSync3(transcript, constants3.O_RDONLY | constants3.O_NOFOLLOW);
8307
+ const stat = fstatSync2(fd);
8308
+ if (!stat.isFile() || stat.nlink !== 1)
8309
+ return empty;
8310
+ const cap = 4 * 1024 * 1024;
8311
+ for (let window = 64 * 1024;; window = Math.min(cap, window * 2)) {
8312
+ const start = Math.max(0, stat.size - window), from = start > 0 ? start - 1 : 0;
8313
+ const buffer = Buffer.alloc(stat.size - from);
8314
+ let bytes = 0;
8315
+ while (bytes < buffer.length) {
8316
+ const count = readSync2(fd, buffer, bytes, buffer.length - bytes, from + bytes);
8317
+ if (!count)
8318
+ break;
8319
+ bytes += count;
8320
+ }
8321
+ const lines = buffer.subarray(0, bytes).toString("utf8").split(`
8322
+ `);
8323
+ if (start)
8324
+ lines.shift();
8325
+ const out = { factIds: [] };
8326
+ for (const line of lines) {
8327
+ let row;
8328
+ try {
8329
+ row = JSON.parse(line);
8330
+ } catch {
8331
+ continue;
8332
+ }
8333
+ if (!record3(row) || row.status !== "DONE")
8334
+ continue;
8335
+ if (row.source === "SYSTEM_SDK" && row.type === "EPHEMERAL_MESSAGE" && typeof row.content === "string") {
8336
+ for (const match of row.content.matchAll(/agent-hub facts \[([a-zA-Z0-9-]{1,128})\]/g))
8337
+ out.factIds.push(match[1]);
8338
+ }
8339
+ 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))
8340
+ out.completion = { id: createHash6("sha256").update(`${session}:${row.step_index}:${line}`).digest("hex"), index: row.step_index };
8341
+ }
8342
+ if (out.completion || start === 0 || window === cap)
8343
+ return out;
8344
+ }
8345
+ } catch {
8346
+ return empty;
8347
+ } finally {
8348
+ if (fd !== undefined)
8349
+ closeSync3(fd);
8350
+ }
8351
+ }
8352
+ var EVENTS, UUID, record3 = (value) => !!value && typeof value === "object" && !Array.isArray(value), AGY_FACTS_HOOK;
8353
+ var init_hooks = __esm(() => {
8354
+ EVENTS = ["SessionStart", "PreInvocation", "PostInvocation", "PreToolUse", "PostToolUse", "Stop"];
8355
+ UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
8356
+ AGY_FACTS_HOOK = { decode: decodeAgyHook, encode: encodeAgyHook, read: readAgyTranscript };
8357
+ });
8358
+
8359
+ // src/adapters/agy/definition.ts
8360
+ function agyLaunch(ctx) {
8361
+ const argv = ["agy", ...ctx.userArgs], command = argv.map(shellQuote).join(" ");
8362
+ const metadata = { packageEntrypoint: ctx.entrypoint, command, argv, env: {}, kind: "agy", peer: ctx.instance };
8363
+ return { cmd: "agy", args: ctx.userArgs, metadata };
8364
+ }
8365
+ var DISPLAY, shellQuote = (value) => `'${value.replace(/'/g, `'\\''`)}'`, AGY_DEFINITION;
8366
+ var init_definition = __esm(() => {
8367
+ init_reviewer_variants();
8368
+ init_hooks();
8369
+ DISPLAY = { palette: { fg: "#6558a6", bg: "#f1effa" } };
8370
+ AGY_DEFINITION = {
8371
+ api: 1,
8372
+ kind: "agy",
8373
+ displayName: "Antigravity",
8374
+ family: "agy",
8375
+ transport: "ws-pull",
8376
+ roles: ["implementer", "verifier"],
8377
+ optional: true,
8378
+ defaultOnRoster: false,
8379
+ trust: { onPrem: false, hubSandboxed: false },
8380
+ start: {
8381
+ forms: ["tui"],
8382
+ defaultForm: "tui",
8383
+ ownsTerminal: (form) => form === "tui",
8384
+ launch: agyLaunch,
8385
+ conductorStartable: false,
8386
+ hubSwitches: [],
8387
+ ownedFlags: ["--new", "--continue", "--conversation", "--resume"],
8388
+ conductorStartArgs: (args) => args.length ? new Error("conductor starts accept no launch overrides; use ahub agy") : {}
8389
+ },
8390
+ identity: { mcp: "channel-plugin" },
8391
+ attach: {
8392
+ pullOnlyForms: ["tui"],
8393
+ recoveryLaunch: (instance) => ({ kind: "agy", peer: instance }),
8394
+ pullOnlyNotice: "pull-only: messages wait for hub_inbox; permission modes and session recovery are not managed"
8395
+ },
8396
+ permission: {
8397
+ modes: {},
8398
+ unmanaged: "Antigravity selects its own permission mode in its TUI; no hub permission mode",
8399
+ unmanagedForms: ["tui"],
8400
+ unmanagedNote: "Antigravity approves tools in its own TUI; no hub permission mode",
8401
+ unmanagedStatusNote: "unmanaged native permission policy"
8402
+ },
8403
+ session: {
8404
+ idField: "sessionId",
8405
+ currentId: () => {
8406
+ return;
8407
+ },
8408
+ freshOnRestore: true,
8409
+ recoveryRefusal: () => "Antigravity session continuity and resume are unavailable until native behavior is verified"
8410
+ },
8411
+ turnEnd: "native-hook",
8412
+ cli: { command: "agy", authority: "person", startWords: () => ["agy"] },
8413
+ display: DISPLAY,
8414
+ routing: { allowedRoles: ["implementer", "verifier"] },
8415
+ reviewer: { defaultPool: false, declaration: "automatic", unavailable: "agy is an implementer peer (#485)" },
8416
+ reviewerVariant: AGY_REVIEWER_VARIANT,
8417
+ hooks: { factsHook: AGY_FACTS_HOOK }
8418
+ };
8419
+ });
8420
+
8421
+ // src/adapters/definitions/native-cli-preview-common.ts
8422
+ function publicArg(arg) {
8423
+ const flags = new Set(["--settings", "--model", "--mcp-config", "--session-id", "--session-file", "--resume", "--continue", "--print", "--safe", "--new"]);
8424
+ const key = arg.split("=", 1)[0];
8425
+ return flags.has(key) ? arg.includes("=") ? key + "=" + REDACTED : key : REDACTED;
8426
+ }
8427
+ function nativeCommand(command, trusted) {
8428
+ return command.map((value, i) => value === trusted[i] ? value : REDACTED);
8429
+ }
8430
+ function previewState(args, baseEnvNames) {
8431
+ const unresolved = [];
8432
+ const envNames = [...baseEnvNames];
8433
+ const value = (flag) => {
8434
+ const at = args.indexOf(flag);
8435
+ if (at < 0)
8436
+ return;
8437
+ const next = args[at + 1];
8438
+ if (!next || next.startsWith("--"))
8439
+ throw new Error("missing option value");
8440
+ return next;
8441
+ };
8442
+ return { unresolved, envNames, value };
8443
+ }
8444
+ function finishPreview(ctx, launch, envNames, unresolved, metadata, settings) {
8445
+ 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."] };
8446
+ }
8447
+ var REDACTED = "[redacted]", UNRESOLVED = "[unresolved]";
8448
+
8449
+ // src/hub/status-roster.ts
8450
+ function rosterInStatus(status, peer) {
8451
+ const rows = status?.roster;
8452
+ if (!Array.isArray(rows))
8453
+ return;
8454
+ const row = rows.find((r) => !!r && typeof r === "object" && r.peer === peer);
8455
+ return typeof row?.on === "boolean" ? row.on : undefined;
8456
+ }
8457
+ function bundleInStatus(status) {
8458
+ const value = status?.pluginBundle;
8459
+ return typeof value === "string" && value.startsWith("/") && /[\\/]plugins[\\/]agent-hub[\\/]server\.js$/.test(value) ? value : undefined;
8460
+ }
8461
+
8462
+ // src/cli/agy-mcp.ts
8463
+ import { randomUUID as randomUUID5 } from "crypto";
8464
+ import { closeSync as closeSync4, fstatSync as fstatSync3, lstatSync as lstatSync9, mkdirSync as mkdirSync6, openSync as openSync4, readFileSync as readFileSync6, renameSync, rmdirSync, unlinkSync, writeFileSync as writeFileSync4 } from "fs";
8465
+ import { isAbsolute as isAbsolute6, join as join14, resolve as resolve10 } from "path";
8466
+ import { isDeepStrictEqual } from "util";
8467
+ function object4(value) {
8468
+ return !!value && typeof value === "object" && !Array.isArray(value);
8469
+ }
8470
+ function jsonFile(value, name) {
8471
+ if (!object4(value))
8472
+ throw new Error(`${name} must be a JSON object`);
8473
+ return value;
8474
+ }
8475
+ function parseJson(bytes, name) {
8476
+ try {
8477
+ return jsonFile(JSON.parse(bytes.toString("utf8")), name);
8478
+ } catch (error) {
8479
+ if (error instanceof SyntaxError)
8480
+ throw new Error(`${name} is invalid JSON`);
8481
+ throw error;
8482
+ }
8483
+ }
8484
+ function shellQuote2(value) {
8485
+ return `'${value.replace(/'/g, `'\\''`)}'`;
8486
+ }
8487
+ function shellUnquote(value) {
8488
+ if (!value.startsWith("'") || !value.endsWith("'"))
8489
+ return;
8490
+ let out = "";
8491
+ for (let index = 1;index < value.length - 1; ) {
8492
+ if (value.startsWith("'\\''", index)) {
8493
+ out += "'";
8494
+ index += 4;
8495
+ } else if (value[index] === "'")
8496
+ return;
8497
+ else
8498
+ out += value[index++];
8499
+ }
8500
+ return out;
8501
+ }
8502
+ function validateOptions(options) {
8503
+ const peer = options.peer ?? "agy";
8504
+ if (!/^[a-z][a-z0-9-]{0,31}$/.test(peer))
8505
+ throw new Error("agy peer id is invalid");
8506
+ for (const [name, value] of Object.entries({ projectRoot: options.projectRoot, stateDir: options.stateDir, serverJs: options.serverJs, hookScript: options.hookScript })) {
8507
+ if (typeof value !== "string" || !isAbsolute6(value) || value.length > 4096 || /[\u0000-\u001f\u007f]/.test(value))
8508
+ throw new Error(`${name} must be an absolute control-free path`);
8509
+ }
8510
+ for (const [name, value] of Object.entries({ instanceId: options.instanceId, launchId: options.launchId })) {
8511
+ if (typeof value !== "string" || !/^[A-Za-z0-9._:-]{1,128}$/.test(value))
8512
+ throw new Error(`${name} is invalid`);
8513
+ }
8514
+ const root = resolve10(options.projectRoot);
8515
+ return { root, peer };
8516
+ }
8517
+ function safeDirectory(path) {
8518
+ const stat = lstatSync9(path, { throwIfNoEntry: false });
8519
+ if (stat && (!stat.isDirectory() || stat.isSymbolicLink()))
8520
+ throw new Error(`${path} must be a project directory, not a link`);
8521
+ return stat;
8522
+ }
8523
+ function safeFile(path) {
8524
+ const stat = lstatSync9(path, { throwIfNoEntry: false });
8525
+ if (!stat)
8526
+ return;
8527
+ if (!stat.isFile() || stat.isSymbolicLink() || stat.nlink !== 1 || stat.size > MAX_CONFIG_BYTES)
8528
+ throw new Error(`${path} must be a bounded regular file, not a link`);
8529
+ const bytes = readFileSync6(path);
8530
+ if (bytes.length > MAX_CONFIG_BYTES)
8531
+ throw new Error(`${path} exceeds the configuration size limit`);
8532
+ return { stat, bytes };
8533
+ }
8534
+ function pluginDirectories(root) {
8535
+ const directories = [];
8536
+ for (const relative of [".", ".agents", ".agents/plugins", PLUGIN_DIR]) {
8537
+ const path = relative === "." ? root : join14(root, relative);
8538
+ const stat = safeDirectory(path);
8539
+ if (!stat)
8540
+ throw new Error(`${path} must be an existing project directory`);
8541
+ directories.push({ path, stat });
8542
+ }
8543
+ return directories;
8544
+ }
8545
+ function verifyPluginDirectory(plan, target) {
8546
+ if (!plan.canonicalPluginDir || !plan.directories)
8547
+ throw new Error("managed agy plugin directory identity is unavailable");
8548
+ if (realPath(plan.root) !== plan.canonicalRoot || realPath(plan.pluginDir) !== plan.canonicalPluginDir || plan.canonicalPluginDir !== join14(plan.canonicalRoot, PLUGIN_DIR)) {
8549
+ throw new Error("managed agy plugin path changed; inspect it before writing");
8550
+ }
8551
+ for (const expected of plan.directories) {
8552
+ const current = safeDirectory(expected.path);
8553
+ if (!current || current.dev !== expected.stat.dev || current.ino !== expected.stat.ino)
8554
+ throw new Error("managed agy plugin directory changed; inspect it before writing");
8555
+ }
8556
+ if (!FILES.some((file) => join14(plan.root, file) === target)) {
8557
+ throw new Error("atomic write target is outside the managed agy plugin files");
8558
+ }
8559
+ }
8560
+ function hookCommand(phase, options, peer) {
8561
+ 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)}`;
8562
+ }
8563
+ function desired(options, root, peer) {
8564
+ const events = {};
8565
+ for (const phase of EVENTS2) {
8566
+ if (phase === "PostInvocation") {
8567
+ events[phase] = [];
8568
+ continue;
8569
+ }
8570
+ const command = { type: "command", command: hookCommand(phase, options, peer), timeout: 5 };
8571
+ events[phase] = phase === "PreToolUse" || phase === "PostToolUse" ? [{ matcher: ".*", hooks: [command] }] : [command];
8572
+ }
8573
+ return {
8574
+ [MANIFEST_FILE]: { name: PLUGIN_KEY, description: MANAGED_DESCRIPTION },
8575
+ [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 } } } },
8576
+ [HOOKS_FILE]: { [PLUGIN_KEY]: events }
8577
+ };
8578
+ }
8579
+ function parseManagedHookCommand(command, phase, options, peer) {
8580
+ if (typeof command !== "string")
8581
+ return;
8582
+ const prefix = `AGENTHUB_HOOK_EVENT='${phase}' AGENTHUB_INSTANCE_ID='`;
8583
+ const middle = `' AGENTHUB_LAUNCH_ID='`;
8584
+ 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 `;
8585
+ if (!command.startsWith(prefix))
8586
+ return;
8587
+ const rest = command.slice(prefix.length);
8588
+ const launchSplit = rest.indexOf(middle);
8589
+ if (launchSplit < 1)
8590
+ return;
8591
+ const instanceId = rest.slice(0, launchSplit);
8592
+ const afterInstance = rest.slice(launchSplit + middle.length);
8593
+ const pathSplit = afterInstance.indexOf(suffix);
8594
+ if (pathSplit < 1)
8595
+ return;
8596
+ const launchId = afterInstance.slice(0, pathSplit);
8597
+ const hookScript = shellUnquote(afterInstance.slice(pathSplit + suffix.length));
8598
+ 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"))
8599
+ return;
8600
+ return { instanceId, launchId };
8601
+ }
8602
+ function managedHookLaunch(value, options, peer) {
8603
+ if (Object.keys(value).length !== 1 || !Object.hasOwn(value, PLUGIN_KEY))
8604
+ return;
8605
+ const events = value[PLUGIN_KEY];
8606
+ if (!object4(events) || Object.keys(events).length !== EVENTS2.length || EVENTS2.some((phase) => !Object.hasOwn(events, phase)))
8607
+ return;
8608
+ let identity;
8609
+ for (const phase of EVENTS2) {
8610
+ const rows = events[phase];
8611
+ if (phase === "PostInvocation") {
8612
+ if (!Array.isArray(rows) || rows.length !== 0)
8613
+ return;
8614
+ continue;
8615
+ }
8616
+ if (!Array.isArray(rows) || rows.length !== 1 || !object4(rows[0]))
8617
+ return;
8618
+ let command;
8619
+ if (phase === "PreToolUse" || phase === "PostToolUse") {
8620
+ const row = rows[0];
8621
+ if (Object.keys(row).sort().join(",") !== "hooks,matcher" || row.matcher !== ".*" || !Array.isArray(row.hooks) || row.hooks.length !== 1 || !object4(row.hooks[0]))
8622
+ return;
8623
+ command = row.hooks[0];
8624
+ } else
8625
+ command = rows[0];
8626
+ if (!object4(command) || Object.keys(command).sort().join(",") !== "command,timeout,type" || command.type !== "command" || command.timeout !== 5)
8627
+ return;
8628
+ const found = parseManagedHookCommand(command.command, phase, options, peer);
8629
+ if (!found || identity && (identity.instanceId !== found.instanceId || identity.launchId !== found.launchId))
8630
+ return;
8631
+ identity = found;
8632
+ }
8633
+ return identity;
8634
+ }
8635
+ function nextDocuments(current, wanted) {
8636
+ const nextMcp = current[MCP_FILE] ? { ...current[MCP_FILE] } : {};
8637
+ const currentServers = nextMcp.mcpServers;
8638
+ const servers = currentServers === undefined ? {} : currentServers;
8639
+ if (!object4(servers))
8640
+ throw new Error(`${MCP_FILE} mcpServers must be an object`);
8641
+ nextMcp.mcpServers = { ...servers, [PLUGIN_KEY]: wanted[MCP_FILE].mcpServers[PLUGIN_KEY] };
8642
+ return { [MANIFEST_FILE]: wanted[MANIFEST_FILE], [MCP_FILE]: nextMcp, [HOOKS_FILE]: wanted[HOOKS_FILE] };
8643
+ }
8644
+ function internalPlan(options) {
8645
+ const { root, peer } = validateOptions(options);
8646
+ const rootStat = safeDirectory(root);
8647
+ if (!rootStat)
8648
+ throw new Error("project root must be an existing directory");
8649
+ const canonicalRoot = realPath(root);
8650
+ const pluginDir = join14(root, PLUGIN_DIR);
8651
+ let parentsPresent = true;
8652
+ for (const relative of [".agents", ".agents/plugins", PLUGIN_DIR]) {
8653
+ if (!parentsPresent)
8654
+ break;
8655
+ parentsPresent = !!safeDirectory(join14(root, relative));
8656
+ }
8657
+ let directories;
8658
+ let canonicalPluginDir;
8659
+ if (parentsPresent) {
8660
+ directories = pluginDirectories(root);
8661
+ canonicalPluginDir = realPath(pluginDir);
8662
+ if (canonicalPluginDir !== join14(canonicalRoot, PLUGIN_DIR))
8663
+ throw new Error("managed agy plugin path traverses a symlink");
8664
+ }
8665
+ const snapshots = Object.fromEntries(FILES.map((relative) => {
8666
+ const path = join14(root, relative);
8667
+ const found = parentsPresent ? safeFile(path) : undefined;
8668
+ return [relative, found];
8669
+ }));
8670
+ const current = {};
8671
+ for (const relative of FILES) {
8672
+ const found = snapshots[relative];
8673
+ current[relative] = found ? parseJson(found.bytes, relative) : undefined;
8674
+ }
8675
+ const manifest = current[MANIFEST_FILE];
8676
+ if (manifest && (Object.keys(manifest).sort().join(",") !== "description,name" || manifest.name !== PLUGIN_KEY || manifest.description !== MANAGED_DESCRIPTION)) {
8677
+ throw new Error(`${MANIFEST_FILE} is a foreign agent-hub plugin manifest; it was not changed`);
8678
+ }
8679
+ const mcp = current[MCP_FILE];
8680
+ if (mcp) {
8681
+ if (Object.hasOwn(mcp, "mcpServers") && !object4(mcp.mcpServers))
8682
+ throw new Error(`${MCP_FILE} mcpServers must be an object`);
8683
+ const entry = mcp.mcpServers?.[PLUGIN_KEY];
8684
+ 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)) {
8685
+ throw new Error(`${MCP_FILE} agent-hub is a foreign MCP entry; it was not changed`);
8686
+ }
8687
+ }
8688
+ const hooks = current[HOOKS_FILE];
8689
+ const oldLaunch = hooks ? managedHookLaunch(hooks, options, peer) : undefined;
8690
+ if (hooks && !oldLaunch)
8691
+ throw new Error(`${HOOKS_FILE} agent-hub is a foreign hooks entry; it was not changed`);
8692
+ const wanted = desired(options, root, peer);
8693
+ const docs = nextDocuments(current, wanted);
8694
+ const targets = FILES.map((relative) => {
8695
+ const old = snapshots[relative];
8696
+ const next = Buffer.from(`${JSON.stringify(docs[relative], null, 2)}
8697
+ `);
8698
+ return { path: join14(root, relative), relative, ...old ? { current: old.bytes, stat: old.stat } : {}, next, changed: !old || !isDeepStrictEqual(current[relative], docs[relative]) };
8699
+ });
8700
+ const git = memoryGitSync(["ls-files", "--", ...FILES], { cwd: root, encoding: "utf8", timeout: 3000 });
8701
+ const tracked = git.status === 0 && !!git.stdout.trim();
8702
+ const changed = targets.some((target) => target.changed);
8703
+ 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 };
8704
+ }
8705
+ function identityMatches(path, before) {
8706
+ const now = lstatSync9(path, { throwIfNoEntry: false });
8707
+ return before ? !!now && now.dev === before.dev && now.ino === before.ino : !now;
8708
+ }
8709
+ function atomicWrite(path, bytes, expected, plan) {
8710
+ const temporary = `${path}.${randomUUID5()}.tmp`;
8711
+ let fd;
8712
+ let tempIdentity;
8713
+ try {
8714
+ verifyPluginDirectory(plan, path);
8715
+ fd = openSync4(temporary, "wx", 384);
8716
+ const tempStat = fstatSync3(fd);
8717
+ tempIdentity = { dev: tempStat.dev, ino: tempStat.ino };
8718
+ writeFileSync4(fd, bytes);
8719
+ closeSync4(fd);
8720
+ fd = undefined;
8721
+ verifyPluginDirectory(plan, path);
8722
+ if (!identityMatches(path, expected))
8723
+ throw new Error(`${path} changed while the managed plugin was being written`);
8724
+ renameSync(temporary, path);
8725
+ const stat = lstatSync9(path);
8726
+ if (!stat.isFile() || stat.isSymbolicLink() || stat.nlink !== 1)
8727
+ throw new Error(`${path} changed while the managed plugin was written`);
8728
+ return { dev: stat.dev, ino: stat.ino };
8729
+ } finally {
8730
+ if (fd !== undefined)
8731
+ closeSync4(fd);
8732
+ try {
8733
+ const current = lstatSync9(temporary, { throwIfNoEntry: false });
8734
+ if (current && tempIdentity && current.dev === tempIdentity.dev && current.ino === tempIdentity.ino)
8735
+ unlinkSync(temporary);
8736
+ } catch {}
8737
+ }
8738
+ }
8739
+ function rollback(plan, written, createdDirs) {
8740
+ for (const { target, own } of written.reverse()) {
8741
+ try {
8742
+ const now = lstatSync9(target.path, { throwIfNoEntry: false });
8743
+ if (!now || now.dev !== own.dev || now.ino !== own.ino)
8744
+ continue;
8745
+ if (target.current)
8746
+ atomicWrite(target.path, target.current, now, plan);
8747
+ else
8748
+ unlinkSync(target.path);
8749
+ } catch {}
8750
+ }
8751
+ for (const { path, dev, ino } of createdDirs.reverse()) {
8752
+ try {
8753
+ const now = lstatSync9(path);
8754
+ if (now.isDirectory() && !now.isSymbolicLink() && now.dev === dev && now.ino === ino)
8755
+ rmdirSync(path);
8756
+ } catch {}
8757
+ }
8758
+ }
8759
+ function ensureAgyMcp(options) {
8760
+ const plan = internalPlan(options);
8761
+ if (!plan.changed)
8762
+ return { changed: false, tracked: plan.tracked };
8763
+ const createdDirs = [];
8764
+ const written = [];
8765
+ try {
8766
+ for (const relative of [".agents", ".agents/plugins", PLUGIN_DIR]) {
8767
+ const path = join14(plan.root, relative);
8768
+ const prior = safeDirectory(path);
8769
+ if (prior)
8770
+ continue;
8771
+ mkdirSync6(path, { mode: 448 });
8772
+ const stat = safeDirectory(path);
8773
+ createdDirs.push({ path, dev: stat.dev, ino: stat.ino });
8774
+ }
8775
+ plan.directories = pluginDirectories(plan.root);
8776
+ plan.canonicalPluginDir = realPath(plan.pluginDir);
8777
+ if (plan.canonicalRoot !== realPath(plan.root) || plan.canonicalPluginDir !== join14(plan.canonicalRoot, PLUGIN_DIR))
8778
+ throw new Error("managed agy plugin path changed; inspect it before writing");
8779
+ for (const target of plan.targets) {
8780
+ if (!target.changed)
8781
+ continue;
8782
+ if (!identityMatches(target.path, target.stat))
8783
+ throw new Error(`${target.path} changed while the managed plugin was being prepared`);
8784
+ const own = atomicWrite(target.path, target.next, target.stat, plan);
8785
+ written.push({ target, own });
8786
+ }
8787
+ } catch (error) {
8788
+ rollback(plan, written, createdDirs);
8789
+ throw error;
8790
+ }
8791
+ return { changed: true, tracked: plan.tracked };
8792
+ }
8793
+ function cleanupAgyMcp(options) {
8794
+ const plan = internalPlan(options);
8795
+ const present = plan.targets.filter((target) => target.current !== undefined);
8796
+ if (!present.length)
8797
+ return;
8798
+ if (present.length !== FILES.length)
8799
+ throw new Error("managed agy plugin is incomplete; inspect it before cleanup");
8800
+ const currentHooks = parseJson(readFileSync6(join14(plan.root, HOOKS_FILE)), HOOKS_FILE);
8801
+ const launch = managedHookLaunch(currentHooks, options, options.peer ?? "agy");
8802
+ if (!launch || launch.instanceId !== options.instanceId || launch.launchId !== options.launchId)
8803
+ return;
8804
+ if (plan.changed)
8805
+ return;
8806
+ const deleted = [];
8807
+ try {
8808
+ for (const target of plan.targets) {
8809
+ const stat = lstatSync9(target.path);
8810
+ if (!target.stat || stat.dev !== target.stat.dev || stat.ino !== target.stat.ino)
8811
+ throw new Error(`${target.path} changed before agy plugin cleanup`);
8812
+ }
8813
+ for (const target of plan.targets) {
8814
+ if (!identityMatches(target.path, target.stat))
8815
+ throw new Error(`${target.path} changed before agy plugin cleanup`);
8816
+ if (target.relative === MCP_FILE) {
8817
+ const currentMcp = parseJson(target.current, MCP_FILE);
8818
+ const servers = { ...currentMcp.mcpServers };
8819
+ delete servers[PLUGIN_KEY];
8820
+ const nextMcp = { ...currentMcp, mcpServers: servers };
8821
+ const retain = Object.keys(servers).length > 0 || Object.keys(currentMcp).some((key) => key !== "mcpServers");
8822
+ if (retain) {
8823
+ const own = atomicWrite(target.path, Buffer.from(`${JSON.stringify(nextMcp, null, 2)}
8824
+ `), target.stat, plan);
8825
+ deleted.push({ path: target.path, bytes: target.current, own });
8826
+ } else {
8827
+ unlinkSync(target.path);
8828
+ deleted.push({ path: target.path, bytes: target.current });
8829
+ }
8830
+ } else {
8831
+ unlinkSync(target.path);
8832
+ deleted.push({ path: target.path, bytes: target.current });
8833
+ }
8834
+ }
8835
+ } catch (error) {
8836
+ for (const item of deleted.reverse()) {
8837
+ try {
8838
+ const current = lstatSync9(item.path, { throwIfNoEntry: false });
8839
+ if (item.own) {
8840
+ if (current?.dev === item.own.dev && current.ino === item.own.ino)
8841
+ atomicWrite(item.path, item.bytes, current, plan);
8842
+ } else if (!current)
8843
+ atomicWrite(item.path, item.bytes, undefined, plan);
8844
+ } catch {}
8845
+ }
8846
+ throw error;
8847
+ }
8848
+ }
8849
+ 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;
8850
+ var init_agy_mcp = __esm(() => {
8851
+ init_git();
8852
+ init_project();
8853
+ MANIFEST_FILE = `${PLUGIN_DIR}/plugin.json`;
8854
+ MCP_FILE = `${PLUGIN_DIR}/mcp_config.json`;
8855
+ HOOKS_FILE = `${PLUGIN_DIR}/hooks.json`;
8856
+ FILES = [MANIFEST_FILE, MCP_FILE, HOOKS_FILE];
8857
+ EVENTS2 = ["SessionStart", "PreInvocation", "PostInvocation", "PreToolUse", "PostToolUse", "Stop"];
8858
+ MAX_CONFIG_BYTES = 256 * 1024;
8859
+ });
8860
+
8861
+ // src/adapters/agy/entry.ts
8862
+ import { spawn as spawn9, spawnSync as spawnSync4 } from "child_process";
8863
+ import { resolve as resolve11 } from "path";
8864
+ function agyTuiSpawnOptions(cwd, env) {
8865
+ return { cwd, env, stdio: "inherit" };
8866
+ }
8867
+ function runAgyTui(argv, cwd, env) {
8868
+ const child = spawn9("agy", argv, agyTuiSpawnOptions(cwd, env));
8869
+ return new Promise((resolveExit, reject) => {
8870
+ const forward = (signal) => {
8871
+ if (child.exitCode !== null || child.signalCode !== null)
8872
+ return;
8873
+ try {
8874
+ child.kill(signal);
8875
+ } catch {}
8876
+ };
8877
+ const onInterrupt = () => forward("SIGINT");
8878
+ const onTerminate = () => forward("SIGTERM");
8879
+ const onHangup = () => forward("SIGHUP");
8880
+ const cleanup = () => {
8881
+ process.off("SIGINT", onInterrupt);
8882
+ process.off("SIGTERM", onTerminate);
8883
+ process.off("SIGHUP", onHangup);
8884
+ };
8885
+ process.on("SIGINT", onInterrupt);
8886
+ process.on("SIGTERM", onTerminate);
8887
+ process.on("SIGHUP", onHangup);
8888
+ child.once("error", (error) => {
8889
+ cleanup();
8890
+ reject(error);
8891
+ });
8892
+ child.once("close", (code) => {
8893
+ cleanup();
8894
+ resolveExit(code ?? 1);
8895
+ });
8896
+ });
8897
+ }
8898
+ async function runAgyEntry(ctx) {
8899
+ const { args, cwd, stateDir, cliDir, connect, fail } = ctx;
8900
+ if (ctx.instance)
8901
+ fail("extra Antigravity TUI instances are not verified in P1");
8902
+ const preview = args.includes("--print-command") || args.includes("--dry-run");
8903
+ const unattended = ctx.unattendedEnv || args.includes("--unattended");
8904
+ const nativeArgs = args.filter((arg) => !["--print-command", "--dry-run", "--unattended"].includes(arg));
8905
+ 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}=`))))
8906
+ fail("agy P1 supports a fresh TUI only; headless, resume and permission flags need a verified mapping");
8907
+ if (unattended)
8908
+ fail("agy P1 native permissions are unmanaged; unattended mode has no verified prompt mapping");
8909
+ const argv = nativeArgs;
8910
+ if (preview) {
8911
+ console.log(JSON.stringify({ command: "agy", args: argv.map(publicArg), mode: "tui", permission: "unmanaged", mcp: ".agents/plugins/agent-hub/", writes: false }));
8912
+ return;
8913
+ }
8914
+ const version = spawnSync4("agy", ["--version"], { encoding: "utf8", timeout: 5000, maxBuffer: 4096, env: peerChildEnv("agy") });
8915
+ if (version.status !== 0 || version.stdout.trim() !== "1.4.2")
8916
+ fail("agy P1 requires the verified Antigravity CLI 1.4.2");
8917
+ const hub = await connect();
8918
+ let options;
8919
+ let pluginWritten = false;
8920
+ try {
8921
+ if (!hub.features.includes("native_hook_launch"))
8922
+ fail("running hub lacks the verified native hook launcher; upgrade or restart it before launching agy");
8923
+ const status = (await hub.request({ t: "status" })).status;
8924
+ if (rosterInStatus(status, "agy") !== true)
8925
+ fail("agy is off roster; add it with ahub roster add agy");
8926
+ const admitted = await hub.request({ t: "native_hook_launch", op: "begin", peer: "agy" });
8927
+ if (!admitted.ok || typeof admitted.launchId !== "string" || typeof admitted.instanceId !== "string")
8928
+ fail(admitted.error ?? "native hook launch was not admitted");
8929
+ options = {
8930
+ projectRoot: cwd,
8931
+ stateDir,
8932
+ serverJs: bundleInStatus(status) ?? resolve11(cliDir, "../../plugins/agent-hub/server.js"),
8933
+ hookScript: resolve11(cliDir, "facts-hook.ts"),
8934
+ peer: "agy",
8935
+ launchId: admitted.launchId,
8936
+ instanceId: admitted.instanceId
8937
+ };
8938
+ const written = ensureAgyMcp(options);
8939
+ pluginWritten = written.changed;
8940
+ if (written.tracked)
8941
+ console.error("The managed agy plugin is tracked; review its local machine paths before committing");
8942
+ console.error("Antigravity: pull-only TUI; native permissions unmanaged, no hub sandbox");
8943
+ const env = { ...ctx.helpers.nativeLaunchEnv("agy", ctx.helpers.childEnv()), AGENTHUB_STATE_DIR: stateDir, AGENTHUB_PROJECT_DIR: cwd };
8944
+ delete env.AGENTHUB_CHANNEL;
8945
+ const code = await runAgyTui(argv, cwd, env);
8946
+ if (code !== 0)
8947
+ process.exitCode = code;
8948
+ } finally {
8949
+ if (options) {
8950
+ await hub.request({ t: "native_hook_launch", op: "end", peer: "agy", launchId: options.launchId }).catch(() => {});
8951
+ if (pluginWritten)
8952
+ try {
8953
+ cleanupAgyMcp(options);
8954
+ } catch {
8955
+ console.error("Managed agy plugin cleanup refused: files changed or ownership could not be verified");
8956
+ }
8957
+ }
8958
+ hub.close();
8959
+ }
8960
+ }
8961
+ var init_entry = __esm(() => {
8962
+ init_agy_mcp();
8963
+ init_child_process();
8964
+ });
8965
+
8966
+ // src/adapters/definitions/native-cli-retained.ts
8967
+ function retainedLaunchWarning(status, peer, definition) {
8968
+ const keys = definition.cli.launchConfigKeys;
8969
+ const retained = status?.retainedConfig;
8970
+ if (!keys?.length || !Array.isArray(retained))
8971
+ return;
8972
+ const pending = retained.flatMap((row) => {
8973
+ if (!row || typeof row !== "object")
8974
+ return [];
8975
+ const key = row.key;
8976
+ return typeof key === "string" && keys.some((candidate) => key === candidate || key.startsWith(`${candidate}.`)) ? [key] : [];
8977
+ });
8978
+ const unique = [...new Set(pending)];
8979
+ 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;
8980
+ }
8981
+
7896
8982
  // src/hub/claude-state.ts
7897
8983
  function claudeStateFile(base, peer) {
7898
8984
  return peer.includes("-") && PEER2.test(peer) ? `${base}.${peer}.json` : `${base}.json`;
@@ -7903,9 +8989,9 @@ var init_claude_state = __esm(() => {
7903
8989
  });
7904
8990
 
7905
8991
  // 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";
8992
+ import { readFileSync as readFileSync7 } from "fs";
8993
+ import { join as join15 } from "path";
8994
+ import { randomUUID as randomUUID6 } from "crypto";
7909
8995
  async function runClaudeEntry(ctx) {
7910
8996
  const { args, cwd, stateDir, cliDir, unattendedEnv, projectConfig, fail } = ctx;
7911
8997
  const peer = ctx.instance ?? "claude";
@@ -7914,6 +9000,10 @@ async function runClaudeEntry(ctx) {
7914
9000
  return console.log(JSON.stringify({ peer, start: "hub", note: "a headless reviewer the hub runs per review" }, null, 2));
7915
9001
  const hub = await ctx.connect();
7916
9002
  try {
9003
+ const snapshot = (await hub.request({ t: "status" })).status;
9004
+ const warning = retainedLaunchWarning(snapshot, peer, ctx.definition);
9005
+ if (warning)
9006
+ console.error(warning);
7917
9007
  const res = await hub.request({ t: "start", peer, operationId: process.env.AGENTHUB_RECOVERY_OPERATION });
7918
9008
  if (res.ok !== true)
7919
9009
  fail(String(res.error ?? `${peer} was not started`));
@@ -7935,20 +9025,20 @@ async function runClaudeEntry(ctx) {
7935
9025
  process.env.AGENTHUB_INSTANCE_ID = control.instanceId;
7936
9026
  const terminal = await recordTerminalLaunch(peer, cwd, stateDir, control.instanceId, ctx.instance ? { executionCwd: cwd } : undefined);
7937
9027
  if (!terminal)
7938
- process.env.AGENTHUB_LAUNCH_ID = randomUUID5();
9028
+ process.env.AGENTHUB_LAUNCH_ID = randomUUID6();
7939
9029
  }
7940
9030
  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")]) {
9031
+ for (const file of [join15(cwd, ".claude", "settings.local.json"), join15(cwd, ".claude", "settings.json"), join15(process.env.HOME ?? "", ".claude", "settings.json")]) {
7942
9032
  try {
7943
- original ??= JSON.parse(readFileSync5(file, "utf8")).statusLine;
9033
+ original ??= JSON.parse(readFileSync7(file, "utf8")).statusLine;
7944
9034
  } catch {}
7945
9035
  }
7946
- const facts = claudeObservationHooks(projectConfig(), { script: join12(cliDir, "facts-hook.ts"), stateDir });
9036
+ const facts = claudeObservationHooks(projectConfig(), { script: join15(cliDir, "facts-hook.ts"), stateDir });
7947
9037
  let previousLaunch;
7948
9038
  try {
7949
- previousLaunch = JSON.parse(readFileSync5(join12(stateDir, claudeStateFile("claude-launch", peer)), "utf8"));
9039
+ previousLaunch = JSON.parse(readFileSync7(join15(stateDir, claudeStateFile("claude-launch", peer)), "utf8"));
7950
9040
  } catch {}
7951
- const launch = buildLaunch("claude", args, { unattended: unattendedEnv, statusLine: { script: join12(cliDir, "statusline-tee.ts"), stateDir, ...original ? { original } : {} }, ...facts ? { facts } : {} });
9041
+ const launch = buildLaunch("claude", args, { unattended: unattendedEnv, statusLine: { script: join15(cliDir, "statusline-tee.ts"), stateDir, ...original ? { original } : {} }, ...facts ? { facts } : {} });
7952
9042
  let result;
7953
9043
  try {
7954
9044
  let launchRecord;
@@ -7992,6 +9082,10 @@ async function runCodexEntry(ctx) {
7992
9082
  if (recovering && !ctx.instance && before?.instanceId)
7993
9083
  await recordTerminalLaunch(peer, cwd, stateDir, before.instanceId);
7994
9084
  const hub = await connect();
9085
+ const snapshot = (await hub.request({ t: "status" })).status;
9086
+ const warning = retainedLaunchWarning(snapshot, peer, ctx.definition);
9087
+ if (warning)
9088
+ console.error(warning);
7995
9089
  const res = await hub.request({ t: "start", peer, args: { unattended: launch0.unattended === true }, operationId: process.env.AGENTHUB_RECOVERY_OPERATION });
7996
9090
  hub.close();
7997
9091
  if (!res.ok)
@@ -8004,27 +9098,16 @@ async function runCodexEntry(ctx) {
8004
9098
  const control = readControl(stateDir);
8005
9099
  if ((!recovering || ctx.instance) && control?.instanceId)
8006
9100
  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 });
9101
+ const runningCodexBin = res.retainedLaunchConfig?.codex_bin;
9102
+ const launch = buildLaunch("codex", args, { unattended: unattendedEnv, instance: peer, proxyUrl: res.proxyUrl, codexBin: typeof runningCodexBin === "string" ? runningCodexBin : projectConfig().codex_bin });
8008
9103
  if (launch0.warning)
8009
9104
  console.error(launch0.warning);
8010
9105
  exec(launch.cmd, launch.args, "codex");
8011
9106
  }
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
- }
9107
+ var init_native_cli_entry_codex = () => {};
8025
9108
 
8026
9109
  // src/adapters/definitions/native-cli-entry-jcode.ts
8027
- import { resolve as resolve9 } from "path";
9110
+ import { resolve as resolve12 } from "path";
8028
9111
  async function runJcodeEntry(ctx) {
8029
9112
  const { args, cwd, stateDir, cliDir, unattendedEnv, connect, fail, takeFlags } = ctx;
8030
9113
  const { launcherPreview, qwenSessionId, configureJcodeMcp } = ctx.helpers;
@@ -8048,7 +9131,10 @@ async function runJcodeEntry(ctx) {
8048
9131
  fail("cannot read jcode's roster membership from the running hub; upgrade or restart it, then retry (nothing was started)");
8049
9132
  if (!rostered)
8050
9133
  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"));
9134
+ const warning = retainedLaunchWarning(snapshot, "jcode", ctx.definition);
9135
+ if (warning)
9136
+ console.error(warning);
9137
+ const written = configureJcodeMcp(cwd, stateDir, bundleInStatus(snapshot) ?? resolve12(cliDir, "../../plugins/agent-hub/server.js"));
8052
9138
  if (written.tracked)
8053
9139
  console.error(".jcode/mcp.json is tracked; review this local machine-path change before committing it");
8054
9140
  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);
@@ -8081,6 +9167,10 @@ async function runQwenEntry(ctx) {
8081
9167
  fail("Qwen TUI is not offered before verification; usage: ahub qwen [--mode headless] [--model <alias>] [--session-id <id>]");
8082
9168
  const hub = await connect();
8083
9169
  try {
9170
+ const snapshot = (await hub.request({ t: "status" })).status;
9171
+ const warning = retainedLaunchWarning(snapshot, peer, ctx.definition);
9172
+ if (warning)
9173
+ console.error(warning);
8084
9174
  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
9175
  if (!reply.ok)
8086
9176
  fail(reply.error);
@@ -8089,14 +9179,15 @@ async function runQwenEntry(ctx) {
8089
9179
  hub.close();
8090
9180
  }
8091
9181
  }
9182
+ var init_native_cli_entry_qwen = () => {};
8092
9183
 
8093
9184
  // src/adapters/definitions/native-cli-entry-kimi.ts
8094
- import { resolve as resolve10 } from "path";
8095
- import { spawnSync as spawnSync4 } from "child_process";
9185
+ import { resolve as resolve13 } from "path";
9186
+ import { spawnSync as spawnSync5 } from "child_process";
8096
9187
  async function runKimiEntry(ctx) {
8097
9188
  const peer = ctx.instance ?? "kimi";
8098
9189
  const { args, cwd, stateDir, cliDir, unattendedEnv, projectConfig, connect, fail, takeFlags } = ctx;
8099
- const { launcherPreview, startModeOf, buildKimiTuiLaunch, configureKimiMcp, nativeLaunchEnv, childEnv } = ctx.helpers;
9190
+ const { launcherPreview, readControl, startModeOf, buildKimiTuiLaunch, configureKimiMcp, nativeLaunchEnv, childEnv } = ctx.helpers;
8100
9191
  if (args.includes("--print-command") || args.includes("--dry-run")) {
8101
9192
  try {
8102
9193
  return console.log(JSON.stringify(launcherPreview("kimi", args, cwd, stateDir, unattendedEnv), null, 2));
@@ -8105,7 +9196,8 @@ async function runKimiEntry(ctx) {
8105
9196
  }
8106
9197
  }
8107
9198
  const { one, rest } = takeFlags(args, ["--mode", "--model"], []);
8108
- const mode = one["--mode"] ?? (ctx.instance ? "headless" : startModeOf(projectConfig().peers, "kimi")), model = one["--model"];
9199
+ const acceptedMode = readControl(stateDir)?.startModes?.[peer];
9200
+ const mode = one["--mode"] ?? (ctx.instance ? "headless" : acceptedMode ?? startModeOf(projectConfig().peers, "kimi")), model = one["--model"];
8109
9201
  if (ctx.instance && mode !== "headless")
8110
9202
  fail("extra Kimi instances require headless mode");
8111
9203
  if (rest.length || !["tui", "headless"].includes(mode))
@@ -8115,6 +9207,10 @@ async function runKimiEntry(ctx) {
8115
9207
  fail("Kimi's TUI needs a terminal for these options; run in a terminal or add --mode headless");
8116
9208
  const hub = await connect();
8117
9209
  try {
9210
+ const snapshot = (await hub.request({ t: "status" })).status;
9211
+ const warning = retainedLaunchWarning(snapshot, peer, ctx.definition);
9212
+ if (warning)
9213
+ console.error(warning);
8118
9214
  const reply = await hub.request({ t: "peer_start", peer }, 35000);
8119
9215
  if (!reply.ok)
8120
9216
  fail(reply.error);
@@ -8126,25 +9222,29 @@ async function runKimiEntry(ctx) {
8126
9222
  const hub = await connect();
8127
9223
  try {
8128
9224
  if (mode === "tui") {
8129
- const launch = buildKimiTuiLaunch(projectConfig().kimi_cmd, model);
8130
9225
  const snapshot = (await hub.request({ t: "status" })).status;
8131
9226
  const rostered = rosterInStatus(snapshot, peer);
8132
9227
  if (rostered === undefined)
8133
9228
  fail("cannot read kimi's roster membership from the running hub; upgrade or restart it, then retry (nothing was started)");
8134
9229
  if (!rostered)
8135
9230
  fail("kimi is off roster; add it with ahub roster add kimi");
9231
+ const warning = retainedLaunchWarning(snapshot, peer, ctx.definition);
9232
+ if (warning)
9233
+ console.error(warning);
8136
9234
  const owner = snapshot?.peers?.[peer];
8137
9235
  if (owner && !["offline"].includes(owner.state) && !owner.pullOnly)
8138
9236
  fail("Kimi is running headless; run ahub stop kimi before starting its TUI");
8139
9237
  const admission = await hub.request({ t: "start", peer, args: { mode: "tui" }, operationId: process.env.AGENTHUB_RECOVERY_OPERATION });
8140
9238
  if (!admission.ok)
8141
9239
  fail(admission.error);
9240
+ const runningCommand = admission.retainedLaunchConfig?.kimi_cmd;
9241
+ const launch = buildKimiTuiLaunch(Array.isArray(runningCommand) && runningCommand.every((arg) => typeof arg === "string") ? runningCommand : projectConfig().kimi_cmd, model);
8142
9242
  let result;
8143
9243
  try {
8144
- const written = configureKimiMcp(cwd, stateDir, bundleInStatus(snapshot) ?? resolve10(cliDir, "../../plugins/agent-hub/server.js"), "tui");
9244
+ const written = configureKimiMcp(cwd, stateDir, bundleInStatus(snapshot) ?? resolve13(cliDir, "../../plugins/agent-hub/server.js"), "tui");
8145
9245
  if (written.tracked)
8146
9246
  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 } });
9247
+ result = spawnSync5(launch.cmd, launch.args, { cwd, stdio: "inherit", env: { ...nativeLaunchEnv("kimi", childEnv()), AGENTHUB_STATE_DIR: stateDir, AGENTHUB_PROJECT_DIR: cwd } });
8148
9248
  } finally {
8149
9249
  if (admission.admissionId)
8150
9250
  await hub.request({ t: "kimi_launch_end", admissionId: admission.admissionId }).catch(() => {});
@@ -8153,11 +9253,15 @@ async function runKimiEntry(ctx) {
8153
9253
  fail(`cannot run ${launch.cmd}: ${result.error.message}`);
8154
9254
  process.exit(result.status ?? 1);
8155
9255
  }
8156
- const owner = (await hub.request({ t: "status" })).status?.peers?.[peer];
9256
+ const snapshot = (await hub.request({ t: "status" })).status;
9257
+ const warning = retainedLaunchWarning(snapshot, peer, ctx.definition);
9258
+ if (warning)
9259
+ console.error(warning);
9260
+ const owner = snapshot?.peers?.[peer];
8157
9261
  if (owner?.pullOnly && owner.state !== "offline")
8158
9262
  fail("Kimi runs in a terminal; end it there before starting a headless one");
8159
9263
  if (!ctx.instance)
8160
- configureKimiMcp(cwd, stateDir, resolve10(cliDir, "../../plugins/agent-hub/server.js"), "headless");
9264
+ configureKimiMcp(cwd, stateDir, resolve13(cliDir, "../../plugins/agent-hub/server.js"), "headless");
8161
9265
  const res = await hub.request({ t: "start", peer, args: { mode: "headless", model }, operationId: process.env.AGENTHUB_RECOVERY_OPERATION });
8162
9266
  if (!res.ok)
8163
9267
  fail(res.error);
@@ -8224,7 +9328,8 @@ async function runPiEntry(ctx) {
8224
9328
  }
8225
9329
  }
8226
9330
  const flags = parsePiFlags(args, fail);
8227
- const options = { ...flags, mode: flags.mode ?? (ctx.instance ? "headless" : startModeOf(projectConfig().peers, "pi")) };
9331
+ const acceptedMode = ctx.helpers.readControl(stateDir)?.startModes?.[peer];
9332
+ const options = { ...flags, mode: flags.mode ?? (ctx.instance ? "headless" : acceptedMode ?? startModeOf(projectConfig().peers, "pi")) };
8228
9333
  if (ctx.instance && options.mode !== "headless")
8229
9334
  fail("extra Pi instances require headless mode");
8230
9335
  if (flags.mode === undefined && options.mode === "tui" && !(process.stdin.isTTY && process.stdout.isTTY)) {
@@ -8232,6 +9337,10 @@ async function runPiEntry(ctx) {
8232
9337
  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
9338
  const hub = await connect();
8234
9339
  try {
9340
+ const snapshot = (await hub.request({ t: "status" })).status;
9341
+ const warning = retainedLaunchWarning(snapshot, peer, ctx.definition);
9342
+ if (warning)
9343
+ console.error(warning);
8235
9344
  const reply = await hub.request({ t: "peer_start", peer }, 35000);
8236
9345
  if (reply.ok !== true) {
8237
9346
  const error = String(reply.error ?? "pi start refused");
@@ -8247,6 +9356,10 @@ async function runPiEntry(ctx) {
8247
9356
  if (recovering && before?.instanceId)
8248
9357
  await recordTerminalLaunch(peer, cwd, stateDir, before.instanceId);
8249
9358
  const hub = await connect();
9359
+ const snapshot = (await hub.request({ t: "status" })).status;
9360
+ const warning = retainedLaunchWarning(snapshot, peer, ctx.definition);
9361
+ if (warning)
9362
+ console.error(warning);
8250
9363
  const res = await hub.request({ t: "start", peer, args: options, operationId: process.env.AGENTHUB_RECOVERY_OPERATION });
8251
9364
  hub.close();
8252
9365
  if (!res.ok)
@@ -8280,21 +9393,31 @@ async function runLocalEntry(ctx) {
8280
9393
  if (args.some((a, i) => i % 2 === 0 && !["--route", "--model"].includes(a)))
8281
9394
  fail("usage: ahub local [--route <id> | --model <id>]");
8282
9395
  const hub = await connect();
9396
+ const snapshot = (await hub.request({ t: "status" })).status;
9397
+ const warning = retainedLaunchWarning(snapshot, peer, ctx.definition);
9398
+ if (warning)
9399
+ console.error(warning);
8283
9400
  const res = await hub.request({ t: "start", peer, args: { route: opt("--route"), model: opt("--model") }, operationId: process.env.AGENTHUB_RECOVERY_OPERATION });
8284
9401
  hub.close();
8285
9402
  if (!res.ok)
8286
9403
  fail(res.error);
8287
9404
  console.log(res.already ? `${peer} is already attached` : `${peer} attached on ${res.model}. Give it work with: ahub say @${peer} "..."`);
8288
9405
  }
9406
+ var init_native_cli_entry_local = () => {};
8289
9407
 
8290
9408
  // src/adapters/definitions/native-cli-entry.ts
8291
9409
  var builtinCliEntries;
8292
9410
  var init_native_cli_entry = __esm(() => {
9411
+ init_entry();
8293
9412
  init_native_cli_entry_claude();
9413
+ init_native_cli_entry_codex();
8294
9414
  init_native_cli_entry_jcode();
9415
+ init_native_cli_entry_qwen();
8295
9416
  init_native_cli_entry_kimi();
8296
9417
  init_native_cli_entry_pi();
9418
+ init_native_cli_entry_local();
8297
9419
  builtinCliEntries = Object.freeze([
9420
+ { command: "agy", lifecycleAdmission: true, execute: runAgyEntry },
8298
9421
  { command: "claude", lifecycleAdmission: true, execute: runClaudeEntry },
8299
9422
  { command: "codex", lifecycleAdmission: true, execute: runCodexEntry },
8300
9423
  { command: "jcode", lifecycleAdmission: true, execute: runJcodeEntry },
@@ -8305,37 +9428,9 @@ var init_native_cli_entry = __esm(() => {
8305
9428
  ]);
8306
9429
  });
8307
9430
 
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
9431
  // src/adapters/definitions/native-cli-preview-claude.ts
8337
- import { readFileSync as readFileSync6 } from "fs";
8338
- import { join as join13 } from "path";
9432
+ import { readFileSync as readFileSync8 } from "fs";
9433
+ import { join as join16 } from "path";
8339
9434
  function previewClaude(ctx) {
8340
9435
  const { args, cwd, stateDir, cliDir, unattended, config } = ctx;
8341
9436
  const { buildLaunch, claudeObservationHooks } = ctx.helpers;
@@ -8343,13 +9438,13 @@ function previewClaude(ctx) {
8343
9438
  let envNames = state.envNames;
8344
9439
  let launch;
8345
9440
  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")]) {
9441
+ for (const file of [join16(cwd, ".claude/settings.local.json"), join16(cwd, ".claude/settings.json"), join16(process.env.HOME ?? "", ".claude/settings.json")]) {
8347
9442
  try {
8348
- original ??= JSON.parse(readFileSync6(file, "utf8")).statusLine;
9443
+ original ??= JSON.parse(readFileSync8(file, "utf8")).statusLine;
8349
9444
  } catch {}
8350
9445
  }
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 } : {} });
9446
+ const facts = claudeObservationHooks(config, { script: join16(cliDir, "facts-hook.ts"), stateDir });
9447
+ 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
9448
  const safeArgs = args.map((arg, i) => {
8354
9449
  if (args[i - 1] === "--settings")
8355
9450
  return "{}";
@@ -8360,7 +9455,7 @@ function previewClaude(ctx) {
8360
9455
  return publicArg(arg);
8361
9456
  });
8362
9457
  const publicLaunch = buildLaunch("claude", safeArgs, { preview: true, unattended, statusLine: {
8363
- script: join13(cliDir, "statusline-tee.ts"),
9458
+ script: join16(cliDir, "statusline-tee.ts"),
8364
9459
  stateDir,
8365
9460
  ...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
9461
  }, facts });
@@ -8393,7 +9488,7 @@ function previewCodex(ctx) {
8393
9488
  var init_native_cli_preview_codex = () => {};
8394
9489
 
8395
9490
  // src/adapters/definitions/native-cli-preview-kimi.ts
8396
- import { join as join14 } from "path";
9491
+ import { join as join17 } from "path";
8397
9492
  function previewKimi(ctx) {
8398
9493
  const { args, cwd, stateDir, cliDir, config } = ctx;
8399
9494
  const { childEnv, startModeOf, buildKimiLaunch, buildKimiTuiLaunch, nativeLaunchEnv, kimiMcpPlan } = ctx.helpers;
@@ -8416,14 +9511,14 @@ function previewKimi(ctx) {
8416
9511
  launch = { ...native, cmd: native.cmd === "kimi" ? "kimi" : REDACTED, args: native.args.map((arg) => arg === "--model" || arg === REDACTED ? arg : REDACTED) };
8417
9512
  envNames = Object.keys(nativeLaunchEnv("kimi", childEnv())).concat(["AGENTHUB_STATE_DIR", "AGENTHUB_PROJECT_DIR"]);
8418
9513
  }
8419
- const { file, action, changed, tracked } = kimiMcpPlan(cwd, stateDir, join14(cliDir, "../../plugins/agent-hub/server.js"), mode);
9514
+ const { file, action, changed, tracked } = kimiMcpPlan(cwd, stateDir, join17(cliDir, "../../plugins/agent-hub/server.js"), mode);
8420
9515
  kimiMetadata = { mode, mcp: { file, action, changed, tracked } };
8421
9516
  return finishPreview(ctx, launch, envNames, unresolved, kimiMetadata);
8422
9517
  }
8423
9518
  var init_native_cli_preview_kimi = () => {};
8424
9519
 
8425
9520
  // src/adapters/definitions/native-cli-preview-pi.ts
8426
- import { join as join15, resolve as resolve11 } from "path";
9521
+ import { join as join18, resolve as resolve14 } from "path";
8427
9522
  function previewPi(ctx) {
8428
9523
  const { args, stateDir, cliDir, config } = ctx;
8429
9524
  const { relayModelIds, buildPiLaunch, startModeOf } = ctx.helpers;
@@ -8458,7 +9553,7 @@ function previewPi(ctx) {
8458
9553
  tools: [],
8459
9554
  preamble: REDACTED,
8460
9555
  maxSteps: config.pi.max_steps
8461
- }, {}, resolve11(join15(cliDir, "../pi/extension.ts")), UNRESOLVED, UNRESOLVED);
9556
+ }, {}, resolve14(join18(cliDir, "../pi/extension.ts")), UNRESOLVED, UNRESOLVED);
8462
9557
  launch = pi;
8463
9558
  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
9559
  if (mode === "tui")
@@ -8471,7 +9566,7 @@ function previewPi(ctx) {
8471
9566
  var init_native_cli_preview_pi = () => {};
8472
9567
 
8473
9568
  // src/adapters/definitions/native-cli-preview-qwen.ts
8474
- import { join as join16 } from "path";
9569
+ import { join as join19 } from "path";
8475
9570
  function previewQwen(ctx) {
8476
9571
  const { tool, args, cwd, stateDir, cliDir, config } = ctx;
8477
9572
  const state = previewState(args, ctx.baseEnvNames), { unresolved, value } = state;
@@ -8494,7 +9589,7 @@ function previewQwen(ctx) {
8494
9589
  form: "headless",
8495
9590
  projectRoot: cwd,
8496
9591
  stateDir,
8497
- entrypoint: join16(cliDir, "main.js"),
9592
+ entrypoint: join19(cliDir, "main.js"),
8498
9593
  userArgs: [],
8499
9594
  unattended: false,
8500
9595
  env: {},
@@ -8602,8 +9697,8 @@ var init_native_config_policy = __esm(() => {
8602
9697
  });
8603
9698
 
8604
9699
  // 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";
9700
+ import { closeSync as closeSync5, constants as constants4, fstatSync as fstatSync4, openSync as openSync5, readdirSync as readdirSync3, readFileSync as readFileSync9 } from "fs";
9701
+ import { isAbsolute as isAbsolute7, join as join20, normalize } from "path";
8607
9702
  function claudeCrashPlan(_meta) {
8608
9703
  return { how: "claude: the Claude Code plugin reconnects by itself while that session is still open" };
8609
9704
  }
@@ -8648,13 +9743,13 @@ function localCrashPlan(meta) {
8648
9743
  }
8649
9744
  function claudePlugins(root, cwd) {
8650
9745
  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")] : []]) {
9746
+ for (const file of [join20(root, "settings.json"), ...cwd ? [join20(cwd, ".claude", "settings.json"), join20(cwd, ".claude", "settings.local.json")] : []]) {
8652
9747
  const map = json(file)?.enabledPlugins;
8653
9748
  if (map && typeof map === "object")
8654
9749
  for (const [key, on] of Object.entries(map))
8655
9750
  enabled.set(key, on === true);
8656
9751
  }
8657
- const installed = json(join17(root, "plugins", "installed_plugins.json"))?.plugins ?? {};
9752
+ const installed = json(join20(root, "plugins", "installed_plugins.json"))?.plugins ?? {};
8658
9753
  const out = [];
8659
9754
  for (const [key, on] of enabled) {
8660
9755
  if (!on)
@@ -8663,12 +9758,12 @@ function claudePlugins(root, cwd) {
8663
9758
  const entries = Array.isArray(installed[key]) ? installed[key] : [];
8664
9759
  const entry = entries.find((e) => e?.scope === "user") ?? entries.find((e) => e?.projectPath === cwd);
8665
9760
  if (name && entry)
8666
- out.push({ name, version: version(entry.version) });
9761
+ out.push({ name, version: version2(entry.version) });
8667
9762
  }
8668
9763
  return out;
8669
9764
  }
8670
9765
  function codexPlugins(root) {
8671
- const config = text(join17(root, "config.toml"));
9766
+ const config = text(join20(root, "config.toml"));
8672
9767
  if (config === undefined)
8673
9768
  return [];
8674
9769
  const found = new Map, disabled = new Set;
@@ -8687,9 +9782,9 @@ function codexPlugins(root) {
8687
9782
  continue;
8688
9783
  let versions = [];
8689
9784
  try {
8690
- versions = readdirSync3(join17(root, "plugins", "cache", market, plugin));
9785
+ versions = readdirSync3(join20(root, "plugins", "cache", market, plugin));
8691
9786
  } catch {}
8692
- out.push({ name, version: versions.length === 1 ? version(versions[0]) : null });
9787
+ out.push({ name, version: versions.length === 1 ? version2(versions[0]) : null });
8693
9788
  }
8694
9789
  return out;
8695
9790
  }
@@ -8702,21 +9797,21 @@ function parsePiContinuity(value) {
8702
9797
  return { sessionId: value.sessionId, ...value.sessionFile === undefined ? {} : { sessionFile: value.sessionFile }, ...value.backend === undefined ? {} : { backend: value.backend }, ...value.model === undefined ? {} : { model: value.model } };
8703
9798
  }
8704
9799
  function absoluteContinuityPath(value) {
8705
- return typeof value === "string" && value.length > 0 && value.length <= 4096 && !/[\x00-\x1f\x7f]/.test(value) && isAbsolute5(value) && normalize(value) === value;
9800
+ return typeof value === "string" && value.length > 0 && value.length <= 4096 && !/[\x00-\x1f\x7f]/.test(value) && isAbsolute7(value) && normalize(value) === value;
8706
9801
  }
8707
9802
  var str3 = (value) => typeof value === "string" && value ? value : undefined, PLUGINS, CLAUDE_ONLY, text = (file) => {
8708
9803
  let fd;
8709
9804
  try {
8710
- fd = openSync3(file, constants3.O_RDONLY | constants3.O_NONBLOCK);
8711
- const st = fstatSync2(fd);
9805
+ fd = openSync5(file, constants4.O_RDONLY | constants4.O_NONBLOCK);
9806
+ const st = fstatSync4(fd);
8712
9807
  if (!st.isFile() || st.size > 1024 * 1024)
8713
9808
  return;
8714
- return readFileSync7(fd, "utf8");
9809
+ return readFileSync9(fd, "utf8");
8715
9810
  } catch {
8716
9811
  return;
8717
9812
  } finally {
8718
9813
  if (fd !== undefined)
8719
- closeSync3(fd);
9814
+ closeSync5(fd);
8720
9815
  }
8721
9816
  }, json = (file) => {
8722
9817
  try {
@@ -8725,7 +9820,7 @@ var str3 = (value) => typeof value === "string" && value ? value : undefined, PL
8725
9820
  } catch {
8726
9821
  return;
8727
9822
  }
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;
9823
+ }, 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
9824
  var init_native_policy_aux = __esm(() => {
8730
9825
  PLUGINS = new Map([["claude-mem", "claude-mem"], ["ponytail", "ponytail"], ["open-code-review", "open-code-review"]]);
8731
9826
  CLAUDE_ONLY = new Map([["codex@openai-codex", "codex-plugin"]]);
@@ -8771,7 +9866,7 @@ var init_launch2 = __esm(() => {
8771
9866
  });
8772
9867
 
8773
9868
  // src/cli/shell-quote.ts
8774
- function shellQuote(value) {
9869
+ function shellQuote3(value) {
8775
9870
  return `'${value.replace(/'/g, `'\\''`)}'`;
8776
9871
  }
8777
9872
 
@@ -8786,26 +9881,26 @@ function nativeLaunchArguments(tool, ctx, policy) {
8786
9881
  }
8787
9882
  function nativeLaunchPlan(launch, ctx) {
8788
9883
  const argv = [launch.cmd, ...launch.args];
8789
- return { ...launch, metadata: { packageEntrypoint: ctx.entrypoint, argv, env: {}, command: argv.map(shellQuote).join(" ") } };
9884
+ return { ...launch, metadata: { packageEntrypoint: ctx.entrypoint, argv, env: {}, command: argv.map(shellQuote3).join(" ") } };
8790
9885
  }
8791
9886
  var init_native_cli_launch_common = __esm(() => {
8792
9887
  init_launch2();
8793
9888
  });
8794
9889
 
8795
9890
  // 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";
9891
+ import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync5, renameSync as renameSync2, unlinkSync as unlinkSync2, readFileSync as readFileSync10 } from "fs";
9892
+ import { randomUUID as randomUUID7 } from "crypto";
9893
+ import { resolve as resolve15, join as join21 } from "path";
8799
9894
  function claudeChannel(userArgs) {
8800
- const bundle = resolve12(import.meta.dir, "../../../plugins/agent-hub/server.js");
9895
+ const bundle = resolve15(import.meta.dir, "../../../plugins/agent-hub/server.js");
8801
9896
  for (let i = 0;i < userArgs.length; i++) {
8802
9897
  const arg = userArgs[i];
8803
9898
  const path = arg === "--mcp-config" ? userArgs[i + 1] : arg.startsWith("--mcp-config=") ? arg.slice("--mcp-config=".length) : undefined;
8804
9899
  if (!path)
8805
9900
  continue;
8806
9901
  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))
9902
+ const server = JSON.parse(readFileSync10(resolve15(path), "utf8")).mcpServers?.["agent-hub"];
9903
+ if (server?.command === "bun" && Array.isArray(server.args) && server.args.some((value) => typeof value === "string" && resolve15(value) === bundle))
8809
9904
  return "server:agent-hub";
8810
9905
  } catch {}
8811
9906
  }
@@ -8828,7 +9923,7 @@ function claudeNativeLaunch(ctx) {
8828
9923
  throw new Error("--settings requires a JSON object or readable file so the hub permission hook can be installed");
8829
9924
  let caller;
8830
9925
  try {
8831
- caller = JSON.parse(value.trim().startsWith("{") ? value : readFileSync8(resolve12(value), "utf8"));
9926
+ caller = JSON.parse(value.trim().startsWith("{") ? value : readFileSync10(resolve15(value), "utf8"));
8832
9927
  if (!caller || typeof caller !== "object" || Array.isArray(caller))
8833
9928
  throw new Error("not an object");
8834
9929
  } catch {
@@ -8857,14 +9952,14 @@ function claudeNativeLaunch(ctx) {
8857
9952
  const stateDir = ctx.facts?.stateDir ?? ctx.statusLine?.stateDir;
8858
9953
  if (!stateDir)
8859
9954
  throw new Error("Claude session settings require the hub state directory");
8860
- const file = join18(stateDir, `claude-settings-${randomUUID6()}.json`);
9955
+ const file = join21(stateDir, `claude-settings-${randomUUID7()}.json`);
8861
9956
  try {
8862
- mkdirSync5(stateDir, { recursive: true, mode: 448 });
8863
- writeFileSync3(`${file}.tmp`, settingsArgs[1], { mode: 384, flag: "wx" });
8864
- renameSync(`${file}.tmp`, file);
9957
+ mkdirSync7(stateDir, { recursive: true, mode: 448 });
9958
+ writeFileSync5(`${file}.tmp`, settingsArgs[1], { mode: 384, flag: "wx" });
9959
+ renameSync2(`${file}.tmp`, file);
8865
9960
  } catch {
8866
9961
  try {
8867
- unlinkSync(`${file}.tmp`);
9962
+ unlinkSync2(`${file}.tmp`);
8868
9963
  } catch {}
8869
9964
  throw new Error("cannot write private Claude session settings in the hub state directory");
8870
9965
  }
@@ -8914,11 +10009,11 @@ var init_native_cli_codex_launch = __esm(() => {
8914
10009
  });
8915
10010
 
8916
10011
  // 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";
10012
+ import { appendFileSync, closeSync as closeSync6, existsSync as existsSync7, fstatSync as fstatSync5, openSync as openSync6, readFileSync as readFileSync11, readSync as readSync3 } from "fs";
8918
10013
  function readEvents(file, since = 0) {
8919
- if (!existsSync6(file))
10014
+ if (!existsSync7(file))
8920
10015
  return [];
8921
- return readFileSync9(file, "utf8").split(`
10016
+ return readFileSync11(file, "utf8").split(`
8922
10017
  `).flatMap((line) => {
8923
10018
  if (!line.trim())
8924
10019
  return [];
@@ -8935,14 +10030,14 @@ var init_events = () => {};
8935
10030
 
8936
10031
  // src/cli/recovery-session-file.ts
8937
10032
  import { statSync as statSync4 } from "fs";
8938
- import { homedir as homedir3 } from "os";
8939
- import { join as join19 } from "path";
10033
+ import { homedir as homedir5 } from "os";
10034
+ import { join as join22 } from "path";
8940
10035
  function checkedSessionPath(binding, kind) {
8941
10036
  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");
10037
+ return join22(binding.launch.env.CODEX_HOME ?? join22(homedir5(), ".codex"), "sessions");
10038
+ const config = binding.launch.env.CLAUDE_CONFIG_DIR ?? join22(homedir5(), ".claude");
8944
10039
  const slug = binding.projectRoot.replace(/[^a-zA-Z0-9]/g, "-");
8945
- return join19(config, "projects", slug, `${binding.sessionId}.jsonl`);
10040
+ return join22(config, "projects", slug, `${binding.sessionId}.jsonl`);
8946
10041
  }
8947
10042
  function recordedFileState(file) {
8948
10043
  try {
@@ -8955,17 +10050,17 @@ var init_recovery_session_file = () => {};
8955
10050
 
8956
10051
  // src/adapters/definitions/native-recovery.ts
8957
10052
  import { readdirSync as readdirSync4 } from "fs";
8958
- import { basename as basename4, join as join20 } from "path";
10053
+ import { basename as basename5, join as join23 } from "path";
8959
10054
  var rolloutState = (binding) => {
8960
10055
  try {
8961
- return readdirSync4(checkedSessionPath(binding, "rollout"), { recursive: true }).some((name) => String(name).endsWith(".jsonl") && basename4(String(name)).includes(binding.sessionId));
10056
+ return readdirSync4(checkedSessionPath(binding, "rollout"), { recursive: true }).some((name) => String(name).endsWith(".jsonl") && basename5(String(name)).includes(binding.sessionId));
8962
10057
  } catch (error) {
8963
10058
  return error.code === "ENOENT" ? false : "unknown";
8964
10059
  }
8965
10060
  }, transcriptState = (binding) => {
8966
10061
  const state = recordedFileState(checkedSessionPath(binding, "transcript"));
8967
10062
  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;
10063
+ }, 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
10064
  var init_native_recovery = __esm(() => {
8970
10065
  init_events();
8971
10066
  init_recovery_session_file();
@@ -8979,7 +10074,7 @@ var init_native_recovery = __esm(() => {
8979
10074
  if (phase !== "plan")
8980
10075
  return false;
8981
10076
  let current, started = false, turned = false;
8982
- for (const e of readEvents(join20(stateDir, "events.jsonl"))) {
10077
+ for (const e of readEvents(join23(stateDir, "events.jsonl"))) {
8983
10078
  if (e.type === "native_thread" && e.peer === binding.peer) {
8984
10079
  current = e.thread;
8985
10080
  if (e.thread === binding.sessionId && e.fresh)
@@ -9020,7 +10115,7 @@ var init_native_recovery = __esm(() => {
9020
10115
  return args;
9021
10116
  },
9022
10117
  crashIdentity: (row) => {
9023
- const value = object3(row), args = object3(value.args), session = object3(value.session);
10118
+ const value = object5(row), args = object5(value.args), session = object5(value.session);
9024
10119
  const sessionId = [value.sessionId, value.agentSessionId, value.resumeSessionId, session.id, session.sessionId].find(text2);
9025
10120
  const sessionFile = [value.sessionFile, value.sessionPath, args.sessionFile, session.file, session.path].find(text2);
9026
10121
  return { ...sessionId ? { sessionId } : {}, ...sessionFile ? { sessionFile } : {} };
@@ -9094,12 +10189,12 @@ function compatibilityLaunch(kind, ctx) {
9094
10189
  ...ctx.env.CODEX_HOME !== undefined ? { CODEX_HOME: ctx.env.CODEX_HOME } : {},
9095
10190
  ...ctx.env.CLAUDE_CONFIG_DIR !== undefined ? { CLAUDE_CONFIG_DIR: ctx.env.CLAUDE_CONFIG_DIR } : {}
9096
10191
  };
9097
- const prefix = Object.entries(env).map(([name, value]) => `${name}=${shellQuote(value)}`);
10192
+ const prefix = Object.entries(env).map(([name, value]) => `${name}=${shellQuote3(value)}`);
9098
10193
  return {
9099
10194
  cmd: process.execPath,
9100
10195
  args,
9101
10196
  ...unattended.length ? { unattended: true } : {},
9102
- metadata: { packageEntrypoint: ctx.entrypoint, argv, env, command: [...prefix, ...argv.map(shellQuote)].join(" ") }
10197
+ metadata: { packageEntrypoint: ctx.entrypoint, argv, env, command: [...prefix, ...argv.map(shellQuote3)].join(" ") }
9103
10198
  };
9104
10199
  }
9105
10200
  function sessionId(field) {
@@ -9223,7 +10318,7 @@ var init_builtin_metadata = __esm(() => {
9223
10318
  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
10319
  turnEnd: "adapter",
9225
10320
  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"] },
10321
+ 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
10322
  display: CODEX_DISPLAY,
9228
10323
  hooks: { recallPlatform: "codex", recallSource: "codex", recallInjected: true, pluginInventory: codexPluginInventory, classifyTool: codexToolSemantic }
9229
10324
  },
@@ -9258,7 +10353,7 @@ var init_builtin_metadata = __esm(() => {
9258
10353
  capabilities: { watch: true },
9259
10354
  init: { mcpFile: ".kimi-code/mcp.json" },
9260
10355
  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] },
10356
+ 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
10357
  display: KIMI_DISPLAY
9263
10358
  },
9264
10359
  {
@@ -9280,7 +10375,7 @@ var init_builtin_metadata = __esm(() => {
9280
10375
  reporting: { tokensNotReported: true },
9281
10376
  configuration: { schema: piConfigSchema, routingSchema: piRoutingSchema, settings: PI_SETTINGS, restartReasons: PI_RESTART_REASONS, validateEffective: piEffectiveConfig },
9282
10377
  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] },
10378
+ 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
10379
  display: PI_DISPLAY
9285
10380
  },
9286
10381
  {
@@ -9300,7 +10395,7 @@ var init_builtin_metadata = __esm(() => {
9300
10395
  budget: { account: "local", quotaExempt: true, instrumented: true },
9301
10396
  configuration: { schema: localConfigSchema, routingSchema: localRoutingSchema, restartReasons: LOCAL_RESTART_REASONS },
9302
10397
  routing: { localAllowedRefusal: "local_allowed = false for this class", longContext: "skip-owner", preferredTier: true, handoffPriority: 1, trace: "route" },
9303
- cli: { command: "local", startWords: () => ["local"] },
10398
+ cli: { command: "local", launchConfigKeys: ["local.deny", "local.bash_network", "local.network_allow", "local.max_steps", "local.read_allow", "routing.local"], startWords: () => ["local"] },
9304
10399
  display: LOCAL_DISPLAY
9305
10400
  },
9306
10401
  {
@@ -9323,7 +10418,7 @@ var init_builtin_metadata = __esm(() => {
9323
10418
  session: { idField: "sessionId", currentId: sessionId("sessionId"), launchKind: "jcode", ...JCODE_RECOVERY_SESSION, crashPlan: jcodeCrashPlan },
9324
10419
  turnEnd: "adapter",
9325
10420
  budget: { account: "jcode" },
9326
- cli: { command: "jcode", authority: "person", startWords: () => ["jcode"] },
10421
+ cli: { command: "jcode", authority: "person", launchConfigKeys: ["jcode_cmd", "jcode_home", "jcode_provider_profile"], startWords: () => ["jcode"] },
9327
10422
  display: JCODE_DISPLAY,
9328
10423
  hooks: { recallPlatform: "jcode" }
9329
10424
  }
@@ -9514,7 +10609,7 @@ var init_qwen = __esm(() => {
9514
10609
  throw new Error("Qwen TUI is not offered before its native verification; use ahub qwen --mode headless");
9515
10610
  const launch = buildQwenLaunch(context.nativeCommand ?? ["qwen", "--acp"], context.model);
9516
10611
  const argv = [launch.cmd, ...launch.args];
9517
- return { ...launch, metadata: { packageEntrypoint: context.entrypoint, argv, command: argv.map(shellQuote).join(" "), env: {} } };
10612
+ return { ...launch, metadata: { packageEntrypoint: context.entrypoint, argv, command: argv.map(shellQuote3).join(" "), env: {} } };
9518
10613
  },
9519
10614
  create: (context) => {
9520
10615
  if (!("acp" in context.daemon))
@@ -9555,7 +10650,7 @@ var init_qwen = __esm(() => {
9555
10650
  turnEnd: "adapter",
9556
10651
  budget: { account: "qwen" },
9557
10652
  capabilities: { watch: true },
9558
- cli: { command: "qwen", startWords: () => ["qwen"] },
10653
+ cli: { command: "qwen", launchConfigKeys: ["qwen_cmd"], startWords: () => ["qwen"] },
9559
10654
  display: QWEN_DISPLAY,
9560
10655
  hooks: { agentIdentity: (init) => {
9561
10656
  qwenNativeIdentity("qwen", true, init);
@@ -9644,7 +10739,8 @@ function assertNativeCapabilityScope(definition) {
9644
10739
  ["start.daemonPort pi-rpc", definition.start.daemonPort === "pi-rpc"],
9645
10740
  ["routing", definition.routing !== undefined],
9646
10741
  ["reviewerVariant", definition.reviewerVariant !== undefined],
9647
- ["reviewer", definition.reviewer !== undefined]
10742
+ ["reviewer", definition.reviewer !== undefined],
10743
+ ["hooks.factsHook", declared(definition.hooks, "factsHook")]
9648
10744
  ];
9649
10745
  const field = restricted.find(([, present]) => present)?.[0];
9650
10746
  if (field)
@@ -9652,6 +10748,7 @@ function assertNativeCapabilityScope(definition) {
9652
10748
  }
9653
10749
  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
10750
  var init_registry = __esm(() => {
10751
+ init_definition();
9655
10752
  init_reviewer_variants();
9656
10753
  init_native_cli_entry();
9657
10754
  init_native_cli_preview();
@@ -9662,18 +10759,19 @@ var init_registry = __esm(() => {
9662
10759
  init_native_cli_doctor();
9663
10760
  init_native_display();
9664
10761
  KNOWN_PEERS = ["claude", "codex", "kimi", "pi", "local"];
9665
- OPTIONAL_PEERS = ["qwen", "jcode"];
10762
+ OPTIONAL_PEERS = ["qwen", "jcode", "agy"];
9666
10763
  SUPPORTED_PEERS = [...KNOWN_PEERS, ...OPTIONAL_PEERS];
9667
- fixedBuiltinDefinitions = new Set([...NATIVE_BUILTIN_DEFINITIONS_WITHOUT_QWEN, QWEN_DEFINITION, AGY_DEFINITION]);
10764
+ fixedBuiltinDefinitions = new Set([...NATIVE_BUILTIN_DEFINITIONS_WITHOUT_QWEN, QWEN_DEFINITION, AGY_DEFINITION, AGY_REVIEWER_VARIANT]);
9668
10765
  for (const definition of [...fixedBuiltinDefinitions])
9669
10766
  if (definition.reviewerVariant)
9670
10767
  fixedBuiltinDefinitions.add(definition.reviewerVariant);
9671
10768
  ADAPTERS = immutable([
9672
10769
  ...NATIVE_BUILTIN_DEFINITIONS_WITHOUT_QWEN.filter((definition) => definition.kind !== "jcode"),
9673
10770
  QWEN_DEFINITION,
9674
- ...NATIVE_BUILTIN_DEFINITIONS_WITHOUT_QWEN.filter((definition) => definition.kind === "jcode")
10771
+ ...NATIVE_BUILTIN_DEFINITIONS_WITHOUT_QWEN.filter((definition) => definition.kind === "jcode"),
10772
+ AGY_DEFINITION
9675
10773
  ]);
9676
- declarationOnlyDefinitions = immutable([AGY_DEFINITION]);
10774
+ declarationOnlyDefinitions = immutable([]);
9677
10775
  definitions = new Map;
9678
10776
  for (const definition of [...ADAPTERS, ...declarationOnlyDefinitions]) {
9679
10777
  assertDaemonEngine(definition);
@@ -9689,7 +10787,7 @@ var init_registry = __esm(() => {
9689
10787
  REVIEW_POOL_KINDS = Object.freeze([...definitions.values()].filter((definition) => !!(definition.reviewerVariant ?? definition).reviewer).map((definition) => definition.kind));
9690
10788
  DEFAULT_REVIEW_POOL = Object.freeze(ADAPTERS.filter((definition) => (definition.reviewerVariant ?? definition).reviewer?.defaultPool === true).map((definition) => definition.kind));
9691
10789
  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"];
10790
+ ROLE_ORDER = ["claude", "codex", "kimi", "local", "pi", "qwen", "jcode", "agy"];
9693
10791
  CONDUCTOR_START_PRIORITY = ["local", "kimi", "pi"];
9694
10792
  CONDUCTOR_START_PEERS = [
9695
10793
  ...CONDUCTOR_START_PRIORITY,
@@ -16873,8 +17971,8 @@ var require_dist = __commonJS(function(exports, module) {
16873
17971
 
16874
17972
  // src/hub/instance-config.ts
16875
17973
  init_registry();
16876
- import { readFileSync as readFileSync10 } from "fs";
16877
- import { join as join23 } from "path";
17974
+ import { readFileSync as readFileSync12 } from "fs";
17975
+ import { join as join26 } from "path";
16878
17976
 
16879
17977
  // src/hub/conductor.ts
16880
17978
  init_registry();
@@ -16895,7 +17993,7 @@ var STDOUT_LIMIT = 512 * 1024;
16895
17993
  // package.json
16896
17994
  var package_default = {
16897
17995
  name: "@staix/agent-hub",
16898
- version: "0.12.37",
17996
+ version: "0.12.38",
16899
17997
  description: "Native multi-agent hub: Claude Code, Codex, Kimi Code, Pi and local inference as peers in one project",
16900
17998
  license: "MIT",
16901
17999
  type: "module",
@@ -16945,7 +18043,7 @@ var package_default = {
16945
18043
  };
16946
18044
 
16947
18045
  // src/version.ts
16948
- var VERSION2 = package_default.version;
18046
+ var VERSION3 = package_default.version;
16949
18047
 
16950
18048
  // src/hub/github-import.ts
16951
18049
  init_sdlc();
@@ -17036,7 +18134,7 @@ var USER2 = Symbol("shared-user-shard");
17036
18134
  // node_modules/@staix/agent-mem/src/mcp.ts
17037
18135
  var string = (maxLength, minLength = 1) => ({ type: "string", minLength, maxLength });
17038
18136
  var limit = { type: "integer", minimum: 1, maximum: 100 };
17039
- var object4 = (properties, required = []) => ({ type: "object", properties, required, additionalProperties: false });
18137
+ var object6 = (properties, required = []) => ({ type: "object", properties, required, additionalProperties: false });
17040
18138
  var tool2 = (name, description, inputSchema, readOnlyHint = true) => ({
17041
18139
  name,
17042
18140
  description,
@@ -17044,20 +18142,20 @@ var tool2 = (name, description, inputSchema, readOnlyHint = true) => ({
17044
18142
  annotations: { readOnlyHint, destructiveHint: false, openWorldHint: false }
17045
18143
  });
17046
18144
  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: {
18145
+ 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"])),
18146
+ tool2("amem_show", "Show a screened memory entry in this project by its exact id or unique prefix.", object6({ id: string(80) }, ["id"])),
18147
+ tool2("amem_timeline", "Read screened memory near an entry or time in this project.", object6({ id: string(80), since: string(40), limit })),
18148
+ 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"])),
18149
+ tool2("amem_work_state_read", "Read project work lists; closed lists/items can be included explicitly.", object6({ list: string(200), includeClosed: { type: "boolean" } })),
18150
+ 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
18151
  type: "object",
17054
18152
  minProperties: 1,
17055
18153
  maxProperties: 64,
17056
18154
  additionalProperties: { type: ["string", "number", "boolean", "null"], maxLength: 4000 }
17057
18155
  } }, ["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({}))
18156
+ 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),
18157
+ tool2("amem_vault_candidates", "List screened durable curated notes eligible for a person to move to the vault. Read only.", object6({ limit })),
18158
+ tool2("amem_capacity", "Read this machine's bounded capture capacity proposal; applying it is person-only.", object6({}))
17061
18159
  ];
17062
18160
  // node_modules/@staix/agent-mem/src/mcp-memory.ts
17063
18161
  init_capture();
@@ -17084,14 +18182,14 @@ init_generation();
17084
18182
  // src/hub/routing.ts
17085
18183
  init_registry();
17086
18184
  init_board();
17087
- import { join as join21, resolve as resolve13 } from "path";
18185
+ import { join as join24, resolve as resolve16 } from "path";
17088
18186
 
17089
18187
  // src/models/route/config.ts
17090
18188
  var TYPES = new Set(["stage", "plan_execute", "advisor", "escalation"]);
17091
18189
 
17092
18190
  // src/hub/routing.ts
17093
18191
  init_stage();
17094
- var TEMPLATE = join21(import.meta.dir, "..", "..", "templates", "routing.toml");
18192
+ var TEMPLATE = join24(import.meta.dir, "..", "..", "templates", "routing.toml");
17095
18193
  var cache = new Map;
17096
18194
 
17097
18195
  // src/hub/memory-policy.ts
@@ -17118,8 +18216,8 @@ var OWNERSHIP_EVENTS = new Set(["assigned", "escalated", "reassigned", "unassign
17118
18216
  // src/hub/config-trust.ts
17119
18217
  init_child_process();
17120
18218
  init_git();
17121
- import { lstatSync as lstatSync7, statSync as statSync5 } from "fs";
17122
- import { dirname as dirname9, join as join22 } from "path";
18219
+ import { lstatSync as lstatSync10, statSync as statSync5 } from "fs";
18220
+ import { dirname as dirname10, join as join25 } from "path";
17123
18221
  var MACHINE_LOCAL = [
17124
18222
  "kimi_cmd",
17125
18223
  "permission_modes",
@@ -17151,7 +18249,7 @@ function configTracked(cwd, name) {
17151
18249
  return;
17152
18250
  let opened;
17153
18251
  try {
17154
- opened = statSync5(join22(cwd, ".agenthub", name));
18252
+ opened = statSync5(join25(cwd, ".agenthub", name));
17155
18253
  } catch {}
17156
18254
  return r.stdout.split("\x00").filter(Boolean).some((row) => {
17157
18255
  const path = row.slice(row.indexOf("\t") + 1);
@@ -17160,7 +18258,7 @@ function configTracked(cwd, name) {
17160
18258
  if (!opened)
17161
18259
  return path === `.agenthub/${name}`;
17162
18260
  try {
17163
- const s = statSync5(join22(cwd, path));
18261
+ const s = statSync5(join25(cwd, path));
17164
18262
  return !!opened && s.dev === opened.dev && s.ino === opened.ino;
17165
18263
  } catch {
17166
18264
  return false;
@@ -17293,7 +18391,7 @@ var INSTANCE_CONFIG_FILES = ["config.json", "config.local.json"];
17293
18391
  function readInstanceConfigLayers(cwd) {
17294
18392
  return INSTANCE_CONFIG_FILES.flatMap((name) => {
17295
18393
  try {
17296
- return [{ name, raw: JSON.parse(readFileSync10(join23(cwd, ".agenthub", name), "utf8")) }];
18394
+ return [{ name, raw: JSON.parse(readFileSync12(join26(cwd, ".agenthub", name), "utf8")) }];
17297
18395
  } catch (error) {
17298
18396
  if (error.code === "ENOENT")
17299
18397
  return [];
@@ -17323,8 +18421,8 @@ function readInstanceConfig(cwd, layers = readInstanceConfigLayers(cwd)) {
17323
18421
 
17324
18422
  // src/hub/control-client.ts
17325
18423
  init_project();
17326
- import { readFileSync as readFileSync11 } from "fs";
17327
- import { join as join24 } from "path";
18424
+ import { readFileSync as readFileSync13 } from "fs";
18425
+ import { join as join27 } from "path";
17328
18426
  function stateDirFor(cwd) {
17329
18427
  return projectContext(cwd).stateDir;
17330
18428
  }
@@ -17332,10 +18430,11 @@ var PROTOCOL = 16;
17332
18430
  var RECOVERY_SOURCE_PROTOCOLS = [9, 10, 11, 12, 13, 14, 15, PROTOCOL];
17333
18431
  function readControl(stateDir) {
17334
18432
  try {
17335
- const status = JSON.parse(readFileSync11(join24(stateDir, "status.json"), "utf8"));
17336
- const token = readFileSync11(join24(stateDir, "control-token"), "utf8").trim();
18433
+ const status = JSON.parse(readFileSync13(join27(stateDir, "status.json"), "utf8"));
18434
+ const token = readFileSync13(join27(stateDir, "control-token"), "utf8").trim();
17337
18435
  if (!Number.isInteger(status.controlPort) || status.controlPort < 1 || status.controlPort > 65535 || !token)
17338
18436
  return;
18437
+ 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
18438
  return {
17340
18439
  url: `ws://127.0.0.1:${status.controlPort}`,
17341
18440
  token,
@@ -17345,6 +18444,7 @@ function readControl(stateDir) {
17345
18444
  protocol: status.protocol,
17346
18445
  pid: status.pid,
17347
18446
  features: Array.isArray(status.features) ? status.features.filter((feature) => typeof feature === "string") : [],
18447
+ ...startModes ? { startModes } : {},
17348
18448
  ...typeof status.pidSignature === "string" && status.pidSignature ? { pidSignature: status.pidSignature } : {}
17349
18449
  };
17350
18450
  } catch {
@@ -18811,7 +19911,7 @@ ${content.join(`
18811
19911
  }
18812
19912
 
18813
19913
  // node_modules/zod/v4/core/versions.js
18814
- var version2 = {
19914
+ var version3 = {
18815
19915
  major: 4,
18816
19916
  minor: 6,
18817
19917
  patch: 5
@@ -18823,7 +19923,7 @@ var $ZodType = /* @__PURE__ */ $constructor("$ZodType", (inst, def) => {
18823
19923
  inst ?? (inst = {});
18824
19924
  inst._zod.def = def;
18825
19925
  inst._zod.bag = inst._zod.bag || {};
18826
- inst._zod.version = version2;
19926
+ inst._zod.version = version3;
18827
19927
  const defChecks = inst._zod.def.checks;
18828
19928
  const checks = inst._zod.traits.has("$ZodCheck") ? [inst, ...defChecks ?? []] : defChecks?.length ? [...defChecks] : [];
18829
19929
  for (const ch of checks) {
@@ -23131,7 +24231,7 @@ var ZodObject = /* @__PURE__ */ $constructor("ZodObject", (inst, def) => {
23131
24231
  return required(ZodNonOptional, this, args[0]);
23132
24232
  }
23133
24233
  });
23134
- function object6(shape, params) {
24234
+ function object8(shape, params) {
23135
24235
  const def = {
23136
24236
  type: "object",
23137
24237
  shape: shape ?? {},
@@ -23192,7 +24292,7 @@ var ZodRecord = /* @__PURE__ */ $constructor("ZodRecord", (inst, def) => {
23192
24292
  inst.keyType = def.keyType;
23193
24293
  inst.valueType = def.valueType;
23194
24294
  });
23195
- function record3(keyType, valueType, params) {
24295
+ function record4(keyType, valueType, params) {
23196
24296
  if (!valueType || !valueType._zod) {
23197
24297
  return new ZodRecord({
23198
24298
  type: "record",
@@ -23476,31 +24576,31 @@ var TaskCreationParamsSchema = looseObject({
23476
24576
  ttl: number2().optional(),
23477
24577
  pollInterval: number2().optional()
23478
24578
  });
23479
- var TaskMetadataSchema = object6({
24579
+ var TaskMetadataSchema = object8({
23480
24580
  ttl: number2().optional()
23481
24581
  });
23482
- var RelatedTaskMetadataSchema = object6({
24582
+ var RelatedTaskMetadataSchema = object8({
23483
24583
  taskId: string3()
23484
24584
  });
23485
24585
  var RequestMetaSchema = looseObject({
23486
24586
  progressToken: ProgressTokenSchema.optional(),
23487
24587
  [RELATED_TASK_META_KEY]: RelatedTaskMetadataSchema.optional()
23488
24588
  });
23489
- var BaseRequestParamsSchema = object6({
24589
+ var BaseRequestParamsSchema = object8({
23490
24590
  _meta: RequestMetaSchema.optional()
23491
24591
  });
23492
24592
  var TaskAugmentedRequestParamsSchema = BaseRequestParamsSchema.extend({
23493
24593
  task: TaskMetadataSchema.optional()
23494
24594
  });
23495
24595
  var isTaskAugmentedRequestParams = (value) => TaskAugmentedRequestParamsSchema.safeParse(value).success;
23496
- var RequestSchema = object6({
24596
+ var RequestSchema = object8({
23497
24597
  method: string3(),
23498
24598
  params: BaseRequestParamsSchema.loose().optional()
23499
24599
  });
23500
- var NotificationsParamsSchema = object6({
24600
+ var NotificationsParamsSchema = object8({
23501
24601
  _meta: RequestMetaSchema.optional()
23502
24602
  });
23503
- var NotificationSchema = object6({
24603
+ var NotificationSchema = object8({
23504
24604
  method: string3(),
23505
24605
  params: NotificationsParamsSchema.loose().optional()
23506
24606
  });
@@ -23508,18 +24608,18 @@ var ResultSchema = looseObject({
23508
24608
  _meta: RequestMetaSchema.optional()
23509
24609
  });
23510
24610
  var RequestIdSchema = union([string3(), number2().int()]);
23511
- var JSONRPCRequestSchema = object6({
24611
+ var JSONRPCRequestSchema = object8({
23512
24612
  jsonrpc: literal(JSONRPC_VERSION),
23513
24613
  id: RequestIdSchema,
23514
24614
  ...RequestSchema.shape
23515
24615
  }).strict();
23516
24616
  var isJSONRPCRequest = (value) => JSONRPCRequestSchema.safeParse(value).success;
23517
- var JSONRPCNotificationSchema = object6({
24617
+ var JSONRPCNotificationSchema = object8({
23518
24618
  jsonrpc: literal(JSONRPC_VERSION),
23519
24619
  ...NotificationSchema.shape
23520
24620
  }).strict();
23521
24621
  var isJSONRPCNotification = (value) => JSONRPCNotificationSchema.safeParse(value).success;
23522
- var JSONRPCResultResponseSchema = object6({
24622
+ var JSONRPCResultResponseSchema = object8({
23523
24623
  jsonrpc: literal(JSONRPC_VERSION),
23524
24624
  id: RequestIdSchema,
23525
24625
  result: ResultSchema
@@ -23536,10 +24636,10 @@ var ErrorCode;
23536
24636
  ErrorCode[ErrorCode["InternalError"] = -32603] = "InternalError";
23537
24637
  ErrorCode[ErrorCode["UrlElicitationRequired"] = -32042] = "UrlElicitationRequired";
23538
24638
  })(ErrorCode || (ErrorCode = {}));
23539
- var JSONRPCErrorResponseSchema = object6({
24639
+ var JSONRPCErrorResponseSchema = object8({
23540
24640
  jsonrpc: literal(JSONRPC_VERSION),
23541
24641
  id: RequestIdSchema.optional(),
23542
- error: object6({
24642
+ error: object8({
23543
24643
  code: number2().int(),
23544
24644
  message: string3(),
23545
24645
  data: unknown().optional()
@@ -23562,16 +24662,16 @@ var CancelledNotificationSchema = NotificationSchema.extend({
23562
24662
  method: literal("notifications/cancelled"),
23563
24663
  params: CancelledNotificationParamsSchema
23564
24664
  });
23565
- var IconSchema = object6({
24665
+ var IconSchema = object8({
23566
24666
  src: string3(),
23567
24667
  mimeType: string3().optional(),
23568
24668
  sizes: array(string3()).optional(),
23569
24669
  theme: _enum(["light", "dark"]).optional()
23570
24670
  });
23571
- var IconsSchema = object6({
24671
+ var IconsSchema = object8({
23572
24672
  icons: array(IconSchema).optional()
23573
24673
  });
23574
- var BaseMetadataSchema = object6({
24674
+ var BaseMetadataSchema = object8({
23575
24675
  name: string3(),
23576
24676
  title: string3().optional()
23577
24677
  });
@@ -23582,9 +24682,9 @@ var ImplementationSchema = BaseMetadataSchema.extend({
23582
24682
  websiteUrl: string3().optional(),
23583
24683
  description: string3().optional()
23584
24684
  });
23585
- var FormElicitationCapabilitySchema = intersection(object6({
24685
+ var FormElicitationCapabilitySchema = intersection(object8({
23586
24686
  applyDefaults: boolean2().optional()
23587
- }), record3(string3(), unknown()));
24687
+ }), record4(string3(), unknown()));
23588
24688
  var ElicitationCapabilitySchema = preprocess((value) => {
23589
24689
  if (value && typeof value === "object" && !Array.isArray(value)) {
23590
24690
  if (Object.keys(value).length === 0) {
@@ -23592,10 +24692,10 @@ var ElicitationCapabilitySchema = preprocess((value) => {
23592
24692
  }
23593
24693
  }
23594
24694
  return value;
23595
- }, intersection(object6({
24695
+ }, intersection(object8({
23596
24696
  form: FormElicitationCapabilitySchema.optional(),
23597
24697
  url: AssertObjectSchema.optional()
23598
- }), record3(string3(), unknown()).optional()));
24698
+ }), record4(string3(), unknown()).optional()));
23599
24699
  var ClientTasksCapabilitySchema = looseObject({
23600
24700
  list: AssertObjectSchema.optional(),
23601
24701
  cancel: AssertObjectSchema.optional(),
@@ -23617,18 +24717,18 @@ var ServerTasksCapabilitySchema = looseObject({
23617
24717
  }).optional()
23618
24718
  }).optional()
23619
24719
  });
23620
- var ClientCapabilitiesSchema = object6({
23621
- experimental: record3(string3(), AssertObjectSchema).optional(),
23622
- sampling: object6({
24720
+ var ClientCapabilitiesSchema = object8({
24721
+ experimental: record4(string3(), AssertObjectSchema).optional(),
24722
+ sampling: object8({
23623
24723
  context: AssertObjectSchema.optional(),
23624
24724
  tools: AssertObjectSchema.optional()
23625
24725
  }).optional(),
23626
24726
  elicitation: ElicitationCapabilitySchema.optional(),
23627
- roots: object6({
24727
+ roots: object8({
23628
24728
  listChanged: boolean2().optional()
23629
24729
  }).optional(),
23630
24730
  tasks: ClientTasksCapabilitySchema.optional(),
23631
- extensions: record3(string3(), AssertObjectSchema).optional()
24731
+ extensions: record4(string3(), AssertObjectSchema).optional()
23632
24732
  });
23633
24733
  var InitializeRequestParamsSchema = BaseRequestParamsSchema.extend({
23634
24734
  protocolVersion: string3(),
@@ -23639,22 +24739,22 @@ var InitializeRequestSchema = RequestSchema.extend({
23639
24739
  method: literal("initialize"),
23640
24740
  params: InitializeRequestParamsSchema
23641
24741
  });
23642
- var ServerCapabilitiesSchema = object6({
23643
- experimental: record3(string3(), AssertObjectSchema).optional(),
24742
+ var ServerCapabilitiesSchema = object8({
24743
+ experimental: record4(string3(), AssertObjectSchema).optional(),
23644
24744
  logging: AssertObjectSchema.optional(),
23645
24745
  completions: AssertObjectSchema.optional(),
23646
- prompts: object6({
24746
+ prompts: object8({
23647
24747
  listChanged: boolean2().optional()
23648
24748
  }).optional(),
23649
- resources: object6({
24749
+ resources: object8({
23650
24750
  subscribe: boolean2().optional(),
23651
24751
  listChanged: boolean2().optional()
23652
24752
  }).optional(),
23653
- tools: object6({
24753
+ tools: object8({
23654
24754
  listChanged: boolean2().optional()
23655
24755
  }).optional(),
23656
24756
  tasks: ServerTasksCapabilitySchema.optional(),
23657
- extensions: record3(string3(), AssertObjectSchema).optional()
24757
+ extensions: record4(string3(), AssertObjectSchema).optional()
23658
24758
  });
23659
24759
  var InitializeResultSchema = ResultSchema.extend({
23660
24760
  protocolVersion: string3(),
@@ -23670,12 +24770,12 @@ var PingRequestSchema = RequestSchema.extend({
23670
24770
  method: literal("ping"),
23671
24771
  params: BaseRequestParamsSchema.optional()
23672
24772
  });
23673
- var ProgressSchema = object6({
24773
+ var ProgressSchema = object8({
23674
24774
  progress: number2(),
23675
24775
  total: optional(number2()),
23676
24776
  message: optional(string3())
23677
24777
  });
23678
- var ProgressNotificationParamsSchema = object6({
24778
+ var ProgressNotificationParamsSchema = object8({
23679
24779
  ...NotificationsParamsSchema.shape,
23680
24780
  ...ProgressSchema.shape,
23681
24781
  progressToken: ProgressTokenSchema
@@ -23694,7 +24794,7 @@ var PaginatedResultSchema = ResultSchema.extend({
23694
24794
  nextCursor: CursorSchema.optional()
23695
24795
  });
23696
24796
  var TaskStatusSchema = _enum(["working", "input_required", "completed", "failed", "cancelled"]);
23697
- var TaskSchema = object6({
24797
+ var TaskSchema = object8({
23698
24798
  taskId: string3(),
23699
24799
  status: TaskStatusSchema,
23700
24800
  ttl: union([number2(), _null3()]),
@@ -23738,10 +24838,10 @@ var CancelTaskRequestSchema = RequestSchema.extend({
23738
24838
  })
23739
24839
  });
23740
24840
  var CancelTaskResultSchema = ResultSchema.merge(TaskSchema);
23741
- var ResourceContentsSchema = object6({
24841
+ var ResourceContentsSchema = object8({
23742
24842
  uri: string3(),
23743
24843
  mimeType: optional(string3()),
23744
- _meta: record3(string3(), unknown()).optional()
24844
+ _meta: record4(string3(), unknown()).optional()
23745
24845
  });
23746
24846
  var TextResourceContentsSchema = ResourceContentsSchema.extend({
23747
24847
  text: string3()
@@ -23758,12 +24858,12 @@ var BlobResourceContentsSchema = ResourceContentsSchema.extend({
23758
24858
  blob: Base64Schema
23759
24859
  });
23760
24860
  var RoleSchema = _enum(["user", "assistant"]);
23761
- var AnnotationsSchema = object6({
24861
+ var AnnotationsSchema = object8({
23762
24862
  audience: array(RoleSchema).optional(),
23763
24863
  priority: number2().min(0).max(1).optional(),
23764
24864
  lastModified: datetime2({ offset: true }).optional()
23765
24865
  });
23766
- var ResourceSchema = object6({
24866
+ var ResourceSchema = object8({
23767
24867
  ...BaseMetadataSchema.shape,
23768
24868
  ...IconsSchema.shape,
23769
24869
  uri: string3(),
@@ -23773,7 +24873,7 @@ var ResourceSchema = object6({
23773
24873
  annotations: AnnotationsSchema.optional(),
23774
24874
  _meta: optional(looseObject({}))
23775
24875
  });
23776
- var ResourceTemplateSchema = object6({
24876
+ var ResourceTemplateSchema = object8({
23777
24877
  ...BaseMetadataSchema.shape,
23778
24878
  ...IconsSchema.shape,
23779
24879
  uriTemplate: string3(),
@@ -23826,12 +24926,12 @@ var ResourceUpdatedNotificationSchema = NotificationSchema.extend({
23826
24926
  method: literal("notifications/resources/updated"),
23827
24927
  params: ResourceUpdatedNotificationParamsSchema
23828
24928
  });
23829
- var PromptArgumentSchema = object6({
24929
+ var PromptArgumentSchema = object8({
23830
24930
  name: string3(),
23831
24931
  description: optional(string3()),
23832
24932
  required: optional(boolean2())
23833
24933
  });
23834
- var PromptSchema = object6({
24934
+ var PromptSchema = object8({
23835
24935
  ...BaseMetadataSchema.shape,
23836
24936
  ...IconsSchema.shape,
23837
24937
  description: optional(string3()),
@@ -23846,44 +24946,44 @@ var ListPromptsResultSchema = PaginatedResultSchema.extend({
23846
24946
  });
23847
24947
  var GetPromptRequestParamsSchema = BaseRequestParamsSchema.extend({
23848
24948
  name: string3(),
23849
- arguments: record3(string3(), string3()).optional()
24949
+ arguments: record4(string3(), string3()).optional()
23850
24950
  });
23851
24951
  var GetPromptRequestSchema = RequestSchema.extend({
23852
24952
  method: literal("prompts/get"),
23853
24953
  params: GetPromptRequestParamsSchema
23854
24954
  });
23855
- var TextContentSchema = object6({
24955
+ var TextContentSchema = object8({
23856
24956
  type: literal("text"),
23857
24957
  text: string3(),
23858
24958
  annotations: AnnotationsSchema.optional(),
23859
- _meta: record3(string3(), unknown()).optional()
24959
+ _meta: record4(string3(), unknown()).optional()
23860
24960
  });
23861
- var ImageContentSchema = object6({
24961
+ var ImageContentSchema = object8({
23862
24962
  type: literal("image"),
23863
24963
  data: Base64Schema,
23864
24964
  mimeType: string3(),
23865
24965
  annotations: AnnotationsSchema.optional(),
23866
- _meta: record3(string3(), unknown()).optional()
24966
+ _meta: record4(string3(), unknown()).optional()
23867
24967
  });
23868
- var AudioContentSchema = object6({
24968
+ var AudioContentSchema = object8({
23869
24969
  type: literal("audio"),
23870
24970
  data: Base64Schema,
23871
24971
  mimeType: string3(),
23872
24972
  annotations: AnnotationsSchema.optional(),
23873
- _meta: record3(string3(), unknown()).optional()
24973
+ _meta: record4(string3(), unknown()).optional()
23874
24974
  });
23875
- var ToolUseContentSchema = object6({
24975
+ var ToolUseContentSchema = object8({
23876
24976
  type: literal("tool_use"),
23877
24977
  name: string3(),
23878
24978
  id: string3(),
23879
- input: record3(string3(), unknown()),
23880
- _meta: record3(string3(), unknown()).optional()
24979
+ input: record4(string3(), unknown()),
24980
+ _meta: record4(string3(), unknown()).optional()
23881
24981
  });
23882
- var EmbeddedResourceSchema = object6({
24982
+ var EmbeddedResourceSchema = object8({
23883
24983
  type: literal("resource"),
23884
24984
  resource: union([TextResourceContentsSchema, BlobResourceContentsSchema]),
23885
24985
  annotations: AnnotationsSchema.optional(),
23886
- _meta: record3(string3(), unknown()).optional()
24986
+ _meta: record4(string3(), unknown()).optional()
23887
24987
  });
23888
24988
  var ResourceLinkSchema = ResourceSchema.extend({
23889
24989
  type: literal("resource_link")
@@ -23895,7 +24995,7 @@ var ContentBlockSchema = union([
23895
24995
  ResourceLinkSchema,
23896
24996
  EmbeddedResourceSchema
23897
24997
  ]);
23898
- var PromptMessageSchema = object6({
24998
+ var PromptMessageSchema = object8({
23899
24999
  role: RoleSchema,
23900
25000
  content: ContentBlockSchema
23901
25001
  });
@@ -23907,33 +25007,33 @@ var PromptListChangedNotificationSchema = NotificationSchema.extend({
23907
25007
  method: literal("notifications/prompts/list_changed"),
23908
25008
  params: NotificationsParamsSchema.optional()
23909
25009
  });
23910
- var ToolAnnotationsSchema = object6({
25010
+ var ToolAnnotationsSchema = object8({
23911
25011
  title: string3().optional(),
23912
25012
  readOnlyHint: boolean2().optional(),
23913
25013
  destructiveHint: boolean2().optional(),
23914
25014
  idempotentHint: boolean2().optional(),
23915
25015
  openWorldHint: boolean2().optional()
23916
25016
  });
23917
- var ToolExecutionSchema = object6({
25017
+ var ToolExecutionSchema = object8({
23918
25018
  taskSupport: _enum(["required", "optional", "forbidden"]).optional()
23919
25019
  });
23920
- var ToolSchema = object6({
25020
+ var ToolSchema = object8({
23921
25021
  ...BaseMetadataSchema.shape,
23922
25022
  ...IconsSchema.shape,
23923
25023
  description: string3().optional(),
23924
- inputSchema: object6({
25024
+ inputSchema: object8({
23925
25025
  type: literal("object"),
23926
- properties: record3(string3(), AssertObjectSchema).optional(),
25026
+ properties: record4(string3(), AssertObjectSchema).optional(),
23927
25027
  required: array(string3()).optional()
23928
25028
  }).catchall(unknown()),
23929
- outputSchema: object6({
25029
+ outputSchema: object8({
23930
25030
  type: literal("object"),
23931
- properties: record3(string3(), AssertObjectSchema).optional(),
25031
+ properties: record4(string3(), AssertObjectSchema).optional(),
23932
25032
  required: array(string3()).optional()
23933
25033
  }).catchall(unknown()).optional(),
23934
25034
  annotations: ToolAnnotationsSchema.optional(),
23935
25035
  execution: ToolExecutionSchema.optional(),
23936
- _meta: record3(string3(), unknown()).optional()
25036
+ _meta: record4(string3(), unknown()).optional()
23937
25037
  });
23938
25038
  var ListToolsRequestSchema = PaginatedRequestSchema.extend({
23939
25039
  method: literal("tools/list")
@@ -23943,7 +25043,7 @@ var ListToolsResultSchema = PaginatedResultSchema.extend({
23943
25043
  });
23944
25044
  var CallToolResultSchema = ResultSchema.extend({
23945
25045
  content: array(ContentBlockSchema).default([]),
23946
- structuredContent: record3(string3(), unknown()).optional(),
25046
+ structuredContent: record4(string3(), unknown()).optional(),
23947
25047
  isError: boolean2().optional()
23948
25048
  });
23949
25049
  var CompatibilityCallToolResultSchema = CallToolResultSchema.or(ResultSchema.extend({
@@ -23951,7 +25051,7 @@ var CompatibilityCallToolResultSchema = CallToolResultSchema.or(ResultSchema.ext
23951
25051
  }));
23952
25052
  var CallToolRequestParamsSchema = TaskAugmentedRequestParamsSchema.extend({
23953
25053
  name: string3(),
23954
- arguments: record3(string3(), unknown()).optional()
25054
+ arguments: record4(string3(), unknown()).optional()
23955
25055
  });
23956
25056
  var CallToolRequestSchema = RequestSchema.extend({
23957
25057
  method: literal("tools/call"),
@@ -23961,7 +25061,7 @@ var ToolListChangedNotificationSchema = NotificationSchema.extend({
23961
25061
  method: literal("notifications/tools/list_changed"),
23962
25062
  params: NotificationsParamsSchema.optional()
23963
25063
  });
23964
- var ListChangedOptionsBaseSchema = object6({
25064
+ var ListChangedOptionsBaseSchema = object8({
23965
25065
  autoRefresh: boolean2().default(true),
23966
25066
  debounceMs: number2().int().nonnegative().default(300)
23967
25067
  });
@@ -23982,25 +25082,25 @@ var LoggingMessageNotificationSchema = NotificationSchema.extend({
23982
25082
  method: literal("notifications/message"),
23983
25083
  params: LoggingMessageNotificationParamsSchema
23984
25084
  });
23985
- var ModelHintSchema = object6({
25085
+ var ModelHintSchema = object8({
23986
25086
  name: string3().optional()
23987
25087
  });
23988
- var ModelPreferencesSchema = object6({
25088
+ var ModelPreferencesSchema = object8({
23989
25089
  hints: array(ModelHintSchema).optional(),
23990
25090
  costPriority: number2().min(0).max(1).optional(),
23991
25091
  speedPriority: number2().min(0).max(1).optional(),
23992
25092
  intelligencePriority: number2().min(0).max(1).optional()
23993
25093
  });
23994
- var ToolChoiceSchema = object6({
25094
+ var ToolChoiceSchema = object8({
23995
25095
  mode: _enum(["auto", "required", "none"]).optional()
23996
25096
  });
23997
- var ToolResultContentSchema = object6({
25097
+ var ToolResultContentSchema = object8({
23998
25098
  type: literal("tool_result"),
23999
25099
  toolUseId: string3().describe("The unique identifier for the corresponding tool call."),
24000
25100
  content: array(ContentBlockSchema).default([]),
24001
- structuredContent: object6({}).loose().optional(),
25101
+ structuredContent: object8({}).loose().optional(),
24002
25102
  isError: boolean2().optional(),
24003
- _meta: record3(string3(), unknown()).optional()
25103
+ _meta: record4(string3(), unknown()).optional()
24004
25104
  });
24005
25105
  var SamplingContentSchema = discriminatedUnion("type", [TextContentSchema, ImageContentSchema, AudioContentSchema]);
24006
25106
  var SamplingMessageContentBlockSchema = discriminatedUnion("type", [
@@ -24010,10 +25110,10 @@ var SamplingMessageContentBlockSchema = discriminatedUnion("type", [
24010
25110
  ToolUseContentSchema,
24011
25111
  ToolResultContentSchema
24012
25112
  ]);
24013
- var SamplingMessageSchema = object6({
25113
+ var SamplingMessageSchema = object8({
24014
25114
  role: RoleSchema,
24015
25115
  content: union([SamplingMessageContentBlockSchema, array(SamplingMessageContentBlockSchema)]),
24016
- _meta: record3(string3(), unknown()).optional()
25116
+ _meta: record4(string3(), unknown()).optional()
24017
25117
  });
24018
25118
  var CreateMessageRequestParamsSchema = TaskAugmentedRequestParamsSchema.extend({
24019
25119
  messages: array(SamplingMessageSchema),
@@ -24043,13 +25143,13 @@ var CreateMessageResultWithToolsSchema = ResultSchema.extend({
24043
25143
  role: RoleSchema,
24044
25144
  content: union([SamplingMessageContentBlockSchema, array(SamplingMessageContentBlockSchema)])
24045
25145
  });
24046
- var BooleanSchemaSchema = object6({
25146
+ var BooleanSchemaSchema = object8({
24047
25147
  type: literal("boolean"),
24048
25148
  title: string3().optional(),
24049
25149
  description: string3().optional(),
24050
25150
  default: boolean2().optional()
24051
25151
  });
24052
- var StringSchemaSchema = object6({
25152
+ var StringSchemaSchema = object8({
24053
25153
  type: literal("string"),
24054
25154
  title: string3().optional(),
24055
25155
  description: string3().optional(),
@@ -24058,7 +25158,7 @@ var StringSchemaSchema = object6({
24058
25158
  format: _enum(["email", "uri", "date", "date-time"]).optional(),
24059
25159
  default: string3().optional()
24060
25160
  });
24061
- var NumberSchemaSchema = object6({
25161
+ var NumberSchemaSchema = object8({
24062
25162
  type: _enum(["number", "integer"]),
24063
25163
  title: string3().optional(),
24064
25164
  description: string3().optional(),
@@ -24066,24 +25166,24 @@ var NumberSchemaSchema = object6({
24066
25166
  maximum: number2().optional(),
24067
25167
  default: number2().optional()
24068
25168
  });
24069
- var UntitledSingleSelectEnumSchemaSchema = object6({
25169
+ var UntitledSingleSelectEnumSchemaSchema = object8({
24070
25170
  type: literal("string"),
24071
25171
  title: string3().optional(),
24072
25172
  description: string3().optional(),
24073
25173
  enum: array(string3()),
24074
25174
  default: string3().optional()
24075
25175
  });
24076
- var TitledSingleSelectEnumSchemaSchema = object6({
25176
+ var TitledSingleSelectEnumSchemaSchema = object8({
24077
25177
  type: literal("string"),
24078
25178
  title: string3().optional(),
24079
25179
  description: string3().optional(),
24080
- oneOf: array(object6({
25180
+ oneOf: array(object8({
24081
25181
  const: string3(),
24082
25182
  title: string3()
24083
25183
  })),
24084
25184
  default: string3().optional()
24085
25185
  });
24086
- var LegacyTitledEnumSchemaSchema = object6({
25186
+ var LegacyTitledEnumSchemaSchema = object8({
24087
25187
  type: literal("string"),
24088
25188
  title: string3().optional(),
24089
25189
  description: string3().optional(),
@@ -24092,26 +25192,26 @@ var LegacyTitledEnumSchemaSchema = object6({
24092
25192
  default: string3().optional()
24093
25193
  });
24094
25194
  var SingleSelectEnumSchemaSchema = union([UntitledSingleSelectEnumSchemaSchema, TitledSingleSelectEnumSchemaSchema]);
24095
- var UntitledMultiSelectEnumSchemaSchema = object6({
25195
+ var UntitledMultiSelectEnumSchemaSchema = object8({
24096
25196
  type: literal("array"),
24097
25197
  title: string3().optional(),
24098
25198
  description: string3().optional(),
24099
25199
  minItems: number2().optional(),
24100
25200
  maxItems: number2().optional(),
24101
- items: object6({
25201
+ items: object8({
24102
25202
  type: literal("string"),
24103
25203
  enum: array(string3())
24104
25204
  }),
24105
25205
  default: array(string3()).optional()
24106
25206
  });
24107
- var TitledMultiSelectEnumSchemaSchema = object6({
25207
+ var TitledMultiSelectEnumSchemaSchema = object8({
24108
25208
  type: literal("array"),
24109
25209
  title: string3().optional(),
24110
25210
  description: string3().optional(),
24111
25211
  minItems: number2().optional(),
24112
25212
  maxItems: number2().optional(),
24113
- items: object6({
24114
- anyOf: array(object6({
25213
+ items: object8({
25214
+ anyOf: array(object8({
24115
25215
  const: string3(),
24116
25216
  title: string3()
24117
25217
  }))
@@ -24124,9 +25224,9 @@ var PrimitiveSchemaDefinitionSchema = union([EnumSchemaSchema, BooleanSchemaSche
24124
25224
  var ElicitRequestFormParamsSchema = TaskAugmentedRequestParamsSchema.extend({
24125
25225
  mode: literal("form").optional(),
24126
25226
  message: string3(),
24127
- requestedSchema: object6({
25227
+ requestedSchema: object8({
24128
25228
  type: literal("object"),
24129
- properties: record3(string3(), PrimitiveSchemaDefinitionSchema),
25229
+ properties: record4(string3(), PrimitiveSchemaDefinitionSchema),
24130
25230
  required: array(string3()).optional()
24131
25231
  })
24132
25232
  });
@@ -24150,24 +25250,24 @@ var ElicitationCompleteNotificationSchema = NotificationSchema.extend({
24150
25250
  });
24151
25251
  var ElicitResultSchema = ResultSchema.extend({
24152
25252
  action: _enum(["accept", "decline", "cancel"]),
24153
- content: preprocess((val) => val === null ? undefined : val, record3(string3(), union([string3(), number2(), boolean2(), array(string3())])).optional())
25253
+ content: preprocess((val) => val === null ? undefined : val, record4(string3(), union([string3(), number2(), boolean2(), array(string3())])).optional())
24154
25254
  });
24155
- var ResourceTemplateReferenceSchema = object6({
25255
+ var ResourceTemplateReferenceSchema = object8({
24156
25256
  type: literal("ref/resource"),
24157
25257
  uri: string3()
24158
25258
  });
24159
- var PromptReferenceSchema = object6({
25259
+ var PromptReferenceSchema = object8({
24160
25260
  type: literal("ref/prompt"),
24161
25261
  name: string3()
24162
25262
  });
24163
25263
  var CompleteRequestParamsSchema = BaseRequestParamsSchema.extend({
24164
25264
  ref: union([PromptReferenceSchema, ResourceTemplateReferenceSchema]),
24165
- argument: object6({
25265
+ argument: object8({
24166
25266
  name: string3(),
24167
25267
  value: string3()
24168
25268
  }),
24169
- context: object6({
24170
- arguments: record3(string3(), string3()).optional()
25269
+ context: object8({
25270
+ arguments: record4(string3(), string3()).optional()
24171
25271
  }).optional()
24172
25272
  });
24173
25273
  var CompleteRequestSchema = RequestSchema.extend({
@@ -24181,10 +25281,10 @@ var CompleteResultSchema = ResultSchema.extend({
24181
25281
  hasMore: optional(boolean2())
24182
25282
  })
24183
25283
  });
24184
- var RootSchema = object6({
25284
+ var RootSchema = object8({
24185
25285
  uri: string3().startsWith("file://"),
24186
25286
  name: string3().optional(),
24187
- _meta: record3(string3(), unknown()).optional()
25287
+ _meta: record4(string3(), unknown()).optional()
24188
25288
  });
24189
25289
  var ListRootsRequestSchema = RequestSchema.extend({
24190
25290
  method: literal("roots/list"),
@@ -25754,8 +26854,8 @@ class StdioServerTransport {
25754
26854
  }
25755
26855
 
25756
26856
  // src/adapters/claude-channel.ts
25757
- import { readFileSync as readFileSync12 } from "fs";
25758
- import { join as join25 } from "path";
26857
+ import { readFileSync as readFileSync14 } from "fs";
26858
+ import { join as join28 } from "path";
25759
26859
  init_hub_tools();
25760
26860
  init_envelope();
25761
26861
 
@@ -25794,7 +26894,7 @@ var channel = !toolsMode && process.env.AGENTHUB_CHANNEL === "1";
25794
26894
  function roles2() {
25795
26895
  const read = (name) => {
25796
26896
  try {
25797
- return JSON.parse(readFileSync12(join25(projectRoot2, ".agenthub", name), "utf8")).roles ?? {};
26897
+ return JSON.parse(readFileSync14(join28(projectRoot2, ".agenthub", name), "utf8")).roles ?? {};
25798
26898
  } catch {
25799
26899
  return {};
25800
26900
  }
@@ -25813,7 +26913,7 @@ function sessionProfile() {
25813
26913
  for (const name of ["config.json", "config.local.json"]) {
25814
26914
  let data;
25815
26915
  try {
25816
- data = JSON.parse(readFileSync12(join25(projectRoot2, ".agenthub", name), "utf8"));
26916
+ data = JSON.parse(readFileSync14(join28(projectRoot2, ".agenthub", name), "utf8"));
25817
26917
  } catch (error) {
25818
26918
  if (error.code === "ENOENT")
25819
26919
  continue;
@@ -25840,7 +26940,7 @@ function sessionRoles() {
25840
26940
  const read = (name) => {
25841
26941
  let raw;
25842
26942
  try {
25843
- raw = JSON.parse(readFileSync12(join25(projectRoot2, ".agenthub", name), "utf8")).catalog_roles;
26943
+ raw = JSON.parse(readFileSync14(join28(projectRoot2, ".agenthub", name), "utf8")).catalog_roles;
25844
26944
  } catch {
25845
26945
  return {};
25846
26946
  }
@@ -25869,7 +26969,7 @@ var HELD = `another session is attached to the hub as "${peerId}"; this one is s
25869
26969
  var INBOX_CAP = 200;
25870
26970
  function peerHeld() {
25871
26971
  try {
25872
- const status = JSON.parse(readFileSync12(join25(stateDir, "status.json"), "utf8"));
26972
+ const status = JSON.parse(readFileSync14(join28(stateDir, "status.json"), "utf8"));
25873
26973
  const peer = status.peers?.[peerId];
25874
26974
  if (channel && peer?.toolsOnly && !peer.claiming)
25875
26975
  return false;
@@ -25904,7 +27004,7 @@ var TOOLS_INSTRUCTIONS = ["agent-hub task tools for this project. Messages from
25904
27004
  `);
25905
27005
  var log = (line) => console.error(`[agent-hub] ${line}`);
25906
27006
  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 });
27007
+ 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
27008
  var inbox = [];
25909
27009
  var hub;
25910
27010
  var detached;
@@ -26144,6 +27244,8 @@ async function callTool(req) {
26144
27244
  return text4(`not sent: ${res.error}`);
26145
27245
  if (res.recorded)
26146
27246
  return text4("recorded only ([FYI]): it is on the hub console and log, and no peer spent a turn on it");
27247
+ if (res.dropped === "hop")
27248
+ return text4(res.notice);
26147
27249
  const sent = `sent to: ${res.targets.join(", ") || "(no other peers attached)"}`;
26148
27250
  return text4(typeof res.notice === "string" ? `${sent}; ${res.notice}` : sent);
26149
27251
  }