@staix/agent-hub 0.25.0 → 0.26.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +10 -5
  3. package/docs/agent-notes/README.md +12 -0
  4. package/docs/agent-notes/adapters.md +1 -1
  5. package/docs/agent-notes/daemon.md +6 -4
  6. package/docs/agent-notes/tasks.md +5 -4
  7. package/docs/agent-notes/tests.md +1 -0
  8. package/docs/dogfood.md +4 -0
  9. package/docs/operations.md +131 -14
  10. package/docs/quickstart.md +14 -4
  11. package/docs/security.md +51 -8
  12. package/docs/specs/2026-11-01-research-campaigns-design.md +9 -6
  13. package/docs/verification/2026-11-01-0.26.0.md +54 -0
  14. package/docs/verified.json +28 -16
  15. package/package.json +1 -1
  16. package/plugins/agent-hub/.claude-plugin/plugin.json +1 -1
  17. package/plugins/agent-hub/server.js +182 -95
  18. package/src/adapters/channel-peer.ts +4 -2
  19. package/src/adapters/definitions/builtin-metadata.ts +6 -6
  20. package/src/adapters/definitions/qwen.ts +2 -0
  21. package/src/adapters/registry-types.ts +2 -0
  22. package/src/adapters/registry.ts +1 -0
  23. package/src/cli/conductor-recovery.ts +3 -2
  24. package/src/cli/help.ts +7 -7
  25. package/src/cli/main.ts +62 -23
  26. package/src/cli/recovery-package.ts +94 -4
  27. package/src/cli/reset.ts +14 -4
  28. package/src/cli/runtime-version-warning.ts +28 -0
  29. package/src/cli/start-agents-run.ts +125 -0
  30. package/src/cli/task-output.ts +5 -0
  31. package/src/cli/upgrade-interactive.ts +4 -4
  32. package/src/cli/upgrade-runtime.ts +105 -57
  33. package/src/cli/upgrade.ts +90 -28
  34. package/src/hub/autonomy-defaults.ts +32 -7
  35. package/src/hub/board.ts +129 -13
  36. package/src/hub/budget.ts +3 -4
  37. package/src/hub/child-process.ts +4 -3
  38. package/src/hub/conductor.ts +3 -4
  39. package/src/hub/config-apply.ts +13 -2
  40. package/src/hub/config-edit-host.ts +1 -0
  41. package/src/hub/config-tier.ts +3 -0
  42. package/src/hub/config-trust.ts +10 -2
  43. package/src/hub/control-client.ts +30 -10
  44. package/src/hub/daemon.ts +82 -17
  45. package/src/hub/db-schema.ts +143 -0
  46. package/src/hub/delivery-journal.ts +3 -4
  47. package/src/hub/execution-budget.ts +3 -4
  48. package/src/hub/folder-trigger.ts +3 -4
  49. package/src/hub/invocations.ts +3 -4
  50. package/src/hub/lifecycle.ts +87 -29
  51. package/src/hub/manager.ts +49 -33
  52. package/src/hub/orca-env.ts +19 -0
  53. package/src/hub/project-runtime-state.ts +139 -0
  54. package/src/hub/project-runtime.ts +62 -0
  55. package/src/hub/protocol-window.ts +19 -0
  56. package/src/hub/recovery-store.ts +102 -12
  57. package/src/hub/registry.ts +5 -0
  58. package/src/hub/research-experiments.ts +33 -4
  59. package/src/hub/research-runner.ts +172 -0
  60. package/src/hub/restart.ts +9 -0
  61. package/src/hub/runtime-capture-prune.ts +415 -0
  62. package/src/hub/runtime-prune-health.ts +112 -0
  63. package/src/hub/schedules.ts +3 -4
  64. package/src/hub/sdlc-merge.ts +7 -0
  65. package/src/hub/sdlc-queue.ts +13 -1
  66. package/src/hub/settings.ts +4 -0
  67. package/src/hub/snapshots.ts +3 -4
  68. package/src/hub/start-agents.ts +44 -0
  69. package/src/hub/task-approval.ts +23 -0
  70. package/src/hub/tasks.ts +133 -34
  71. package/src/hub/usage-rollup.ts +3 -4
  72. package/src/pi/tool-receipts.ts +3 -4
@@ -806,6 +806,12 @@ var init_agy_mcp = __esm(() => {
806
806
  MAX_CONFIG_BYTES = 256 * 1024;
807
807
  });
808
808
 
809
+ // src/hub/orca-env.ts
810
+ var ORCA_LAUNCHER_ENV;
811
+ var init_orca_env = __esm(() => {
812
+ ORCA_LAUNCHER_ENV = Object.freeze(["ORCA_CLI_COMMAND", "ORCA_DEV_REPO_ROOT", "ORCA_TERMINAL_HANDLE", "ORCA_WORKTREE_ID"]);
813
+ });
814
+
809
815
  // src/hub/child-process.ts
810
816
  function childEnv(source = process.env) {
811
817
  const env = { ...source };
@@ -1066,6 +1072,7 @@ var PS, PS_OPTIONS, own = (text) => {
1066
1072
  proc.stderr?.destroy();
1067
1073
  }, emptied;
1068
1074
  var init_child_process = __esm(() => {
1075
+ init_orca_env();
1069
1076
  PS = ["ps", "-axo", "pid=,ppid=,pgid=,stat=,lstart=,command="];
1070
1077
  PS_OPTIONS = { env: { ...process.env, LC_ALL: "C", TZ: "UTC" }, detached: true };
1071
1078
  emptied = new WeakSet;
@@ -5896,8 +5903,13 @@ var init_launch = __esm(() => {
5896
5903
  init_child_process();
5897
5904
  });
5898
5905
 
5906
+ // src/hub/recovery-error.ts
5907
+ var init_recovery_error = __esm(() => {
5908
+ init_envelope();
5909
+ });
5910
+
5899
5911
  // src/hub/recovery-store.ts
5900
- import { chmodSync, existsSync as existsSync5, mkdirSync as mkdirSync6, readFileSync as readFileSync5, renameSync as renameSync4, rmSync as rmSync4, writeFileSync as writeFileSync6 } from "fs";
5912
+ import { chmodSync, existsSync as existsSync5, linkSync, lstatSync as lstatSync8, mkdirSync as mkdirSync6, readFileSync as readFileSync5, renameSync as renameSync4, rmSync as rmSync4, writeFileSync as writeFileSync6 } from "fs";
5901
5913
  import { randomUUID as randomUUID4 } from "crypto";
5902
5914
  function atomicPrivateJSON(file, value) {
5903
5915
  const temporary = `${file}.${randomUUID4()}.tmp`;
@@ -5912,6 +5924,7 @@ function atomicPrivateJSON(file, value) {
5912
5924
  }
5913
5925
  var init_recovery_store = __esm(() => {
5914
5926
  init_project();
5927
+ init_recovery_error();
5915
5928
  init_process_signature();
5916
5929
  });
5917
5930
 
@@ -5985,11 +5998,11 @@ var init_permission_mode = __esm(() => {
5985
5998
  });
5986
5999
 
5987
6000
  // src/hub/path-policy.ts
5988
- import { accessSync, constants as constants4, lstatSync as lstatSync8, opendirSync, readlinkSync, statSync as statSync2 } from "fs";
6001
+ import { accessSync, constants as constants4, lstatSync as lstatSync9, opendirSync, readlinkSync, statSync as statSync2 } from "fs";
5989
6002
  import { dirname as dirname7, isAbsolute as isAbsolute5, join as join11, relative as relative3, resolve as resolve8, sep as sep2 } from "path";
5990
6003
  function existing(path) {
5991
6004
  try {
5992
- lstatSync8(path);
6005
+ lstatSync9(path);
5993
6006
  return true;
5994
6007
  } catch (error) {
5995
6008
  if (error.code === "ENOENT")
@@ -6038,7 +6051,7 @@ function directAliasTarget(path) {
6038
6051
  return;
6039
6052
  ancestor = parent;
6040
6053
  }
6041
- if (!lstatSync8(ancestor).isSymbolicLink())
6054
+ if (!lstatSync9(ancestor).isSymbolicLink())
6042
6055
  return;
6043
6056
  target = resolve8(dirname7(ancestor), readlinkSync(ancestor), relative3(ancestor, target));
6044
6057
  }
@@ -6515,7 +6528,7 @@ var init_text_projection = __esm(() => {
6515
6528
  });
6516
6529
 
6517
6530
  // src/local/doc-read.ts
6518
- import { lstatSync as lstatSync9 } from "fs";
6531
+ import { lstatSync as lstatSync10 } from "fs";
6519
6532
  import { extname } from "path";
6520
6533
  function docRead(file, shown, args, port, cwd) {
6521
6534
  const projections = port.projections();
@@ -6525,7 +6538,7 @@ function docRead(file, shown, args, port, cwd) {
6525
6538
  }
6526
6539
  let st;
6527
6540
  try {
6528
- st = lstatSync9(file);
6541
+ st = lstatSync10(file);
6529
6542
  } catch {
6530
6543
  return `error: ${shown} cannot be read`;
6531
6544
  }
@@ -6848,7 +6861,7 @@ var init_web_fetch = __esm(() => {
6848
6861
  });
6849
6862
 
6850
6863
  // src/local/tools.ts
6851
- import { lstatSync as lstatSync10, mkdirSync as mkdirSync8, readFileSync as readFileSync7, statSync as statSync3, writeFileSync as writeFileSync7 } from "fs";
6864
+ import { lstatSync as lstatSync11, mkdirSync as mkdirSync8, readFileSync as readFileSync7, statSync as statSync3, writeFileSync as writeFileSync7 } from "fs";
6852
6865
  import { dirname as dirname10, join as join13, relative as relative4, resolve as resolve10 } from "path";
6853
6866
  function guardPath(ctx, path, mode) {
6854
6867
  const root = realPath(ctx.cwd);
@@ -6871,7 +6884,7 @@ function guardPath(ctx, path, mode) {
6871
6884
  throw new Error(`${path} is on the secrets denylist`);
6872
6885
  if (mode === "write" && (isHubWritePath(rel) || inside && isHubWritePath(typed)))
6873
6886
  throw new Error(`${path} is not writable by the local worker`);
6874
- if (mode === "write" && ctx.readOnly?.length && (lstatSync10(real, { throwIfNoEntry: false })?.nlink ?? 1) > 1)
6887
+ if (mode === "write" && ctx.readOnly?.length && (lstatSync11(real, { throwIfNoEntry: false })?.nlink ?? 1) > 1)
6875
6888
  throw new Error("hardlinked write target cannot be verified with data.readonly");
6876
6889
  if (mode === "write" && matchesPolicy(root, path, ctx.readOnly ?? []))
6877
6890
  throw new Error("target is protected by data.readonly");
@@ -7045,7 +7058,7 @@ ${preview(content)}`, "write", ctx.signal, file, undefined, String(a.path), a),
7045
7058
  }
7046
7059
  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) => {
7047
7060
  try {
7048
- lstatSync10(p);
7061
+ lstatSync11(p);
7049
7062
  return true;
7050
7063
  } catch {
7051
7064
  return false;
@@ -10695,7 +10708,7 @@ var init_shipped_roles = __esm(() => {
10695
10708
 
10696
10709
  // src/hub/roles.ts
10697
10710
  import { createHash as createHash6 } from "crypto";
10698
- import { constants as constants5, closeSync as closeSync6, fstatSync as fstatSync5, lstatSync as lstatSync11, openSync as openSync6, readSync as readSync4, readdirSync as readdirSync2 } from "fs";
10711
+ import { constants as constants5, closeSync as closeSync6, fstatSync as fstatSync5, lstatSync as lstatSync12, openSync as openSync6, readSync as readSync4, readdirSync as readdirSync2 } from "fs";
10699
10712
  import { join as join15 } from "path";
10700
10713
  function parseRole(text, stem, layer) {
10701
10714
  if (Buffer.byteLength(text) > 8192 || text.includes("\r") || !ID.test(stem))
@@ -10777,7 +10790,7 @@ function loadRoleCatalog(cwd) {
10777
10790
  try {
10778
10791
  const config = join15(cwd, ".agenthub"), dir = join15(config, "roles");
10779
10792
  for (const path of [config, dir]) {
10780
- const stat = lstatSync11(path);
10793
+ const stat = lstatSync12(path);
10781
10794
  if (!stat.isDirectory() || stat.isSymbolicLink())
10782
10795
  throw new Error("unsafe role directory");
10783
10796
  }
@@ -10871,6 +10884,14 @@ var init_packs = __esm(() => {
10871
10884
  init_workflow();
10872
10885
  });
10873
10886
 
10887
+ // src/hub/research-runner.ts
10888
+ var BODY, RUNNER_RECEIPT_DOMAIN;
10889
+ var init_research_runner = __esm(() => {
10890
+ init_packs();
10891
+ BODY = 64 * 1024;
10892
+ RUNNER_RECEIPT_DOMAIN = "agent-hub.runner-receipt/v1" + String.fromCharCode(0);
10893
+ });
10894
+
10874
10895
  // node_modules/@staix/agent-mem/src/memory/mutation.ts
10875
10896
  var mutations;
10876
10897
  var init_mutation = __esm(() => {
@@ -11890,12 +11911,13 @@ var init_deliverables = __esm(() => {
11890
11911
  });
11891
11912
 
11892
11913
  // src/hub/research-experiments.ts
11893
- var BODY;
11914
+ var BODY2;
11894
11915
  var init_research_experiments = __esm(() => {
11895
11916
  init_src();
11896
11917
  init_packs();
11918
+ init_research_runner();
11897
11919
  init_deliverables();
11898
- BODY = 64 * 1024;
11920
+ BODY2 = 64 * 1024;
11899
11921
  });
11900
11922
 
11901
11923
  // src/hub/task-code-symbols.ts
@@ -11904,24 +11926,98 @@ var init_task_code_symbols = __esm(() => {
11904
11926
  HOST_CODE_SYMBOLS = Symbol("host task code symbols");
11905
11927
  });
11906
11928
 
11929
+ // package.json
11930
+ var package_default;
11931
+ var init_package = __esm(() => {
11932
+ package_default = {
11933
+ name: "@staix/agent-hub",
11934
+ version: "0.26.0",
11935
+ description: "Native multi-agent hub: Claude Code, Codex, Kimi Code, Pi and local inference as peers in one project",
11936
+ license: "MIT",
11937
+ type: "module",
11938
+ bin: {
11939
+ ahub: "src/cli/main.js",
11940
+ "agent-hub": "src/cli/main.js"
11941
+ },
11942
+ scripts: {
11943
+ check: "scripts/check.sh",
11944
+ build: "bun scripts/build.mjs",
11945
+ test: "bun test"
11946
+ },
11947
+ engines: {
11948
+ bun: ">=1.4.2"
11949
+ },
11950
+ dependencies: {
11951
+ "@staix/agent-mem": "0.18.0"
11952
+ },
11953
+ devDependencies: {
11954
+ "@modelcontextprotocol/sdk": "^1.27.1",
11955
+ "@types/bun": "^1.3.0",
11956
+ typescript: "^5.8.0"
11957
+ },
11958
+ files: [
11959
+ ".claude-plugin",
11960
+ "src",
11961
+ "src/pet/window.jxa.js",
11962
+ "src/pet/window.html",
11963
+ "plugins",
11964
+ "templates",
11965
+ "scripts/check-docs.mjs",
11966
+ "docs",
11967
+ "README.md",
11968
+ "LICENSE",
11969
+ "CHANGELOG.md",
11970
+ "THIRD_PARTY_NOTICES.md",
11971
+ "LICENSES"
11972
+ ],
11973
+ repository: {
11974
+ type: "git",
11975
+ url: "git+https://github.com/STAIxBWLB/agent-hub.git"
11976
+ },
11977
+ homepage: "https://github.com/STAIxBWLB/agent-hub",
11978
+ publishConfig: {
11979
+ access: "public"
11980
+ },
11981
+ exports: {
11982
+ "./adapter": "./src/adapters/public.ts",
11983
+ "./conformance": "./src/adapters/conformance/index.ts",
11984
+ "./ui-plugin": "./src/ui/plugin.ts"
11985
+ }
11986
+ };
11987
+ });
11988
+
11989
+ // src/version.ts
11990
+ var VERSION;
11991
+ var init_version = __esm(() => {
11992
+ init_package();
11993
+ VERSION = package_default.version;
11994
+ });
11995
+ // src/hub/db-schema.ts
11996
+ var init_db_schema = __esm(() => {
11997
+ init_version();
11998
+ });
11999
+
11907
12000
  // src/hub/planning.ts
11908
12001
  var init_planning = __esm(() => {
11909
12002
  init_sdlc();
11910
12003
  });
12004
+
11911
12005
  // src/hub/research-campaign.ts
11912
- var BODY2;
12006
+ var BODY3;
11913
12007
  var init_research_campaign = __esm(() => {
11914
12008
  init_packs();
11915
- BODY2 = 64 * 1024;
12009
+ BODY3 = 64 * 1024;
11916
12010
  });
11917
12011
 
11918
12012
  // src/hub/board.ts
11919
12013
  var CLASSES2, PLAN_KEYS, ALL_PLAN_KEYS, OUTCOMES_KEPT_MS;
11920
12014
  var init_board = __esm(() => {
12015
+ init_research_runner();
11921
12016
  init_packs();
11922
12017
  init_research_experiments();
11923
12018
  init_task_code_symbols();
11924
12019
  init_plan_conformance();
12020
+ init_db_schema();
11925
12021
  init_planning();
11926
12022
  init_envelope();
11927
12023
  init_review_findings();
@@ -13012,7 +13108,7 @@ var WITHHELD = "Claude implementer turn failed (details withheld)";
13012
13108
 
13013
13109
  // src/adapters/claude-implementer.ts
13014
13110
  import { randomUUID as randomUUID9 } from "crypto";
13015
- import { lstatSync as lstatSync12, mkdtempSync as mkdtempSync5, rmSync as rmSync6, writeFileSync as writeFileSync8 } from "fs";
13111
+ import { lstatSync as lstatSync13, mkdtempSync as mkdtempSync5, rmSync as rmSync6, writeFileSync as writeFileSync8 } from "fs";
13016
13112
  import { tmpdir as tmpdir5 } from "os";
13017
13113
  import { join as join17 } from "path";
13018
13114
  function provenVersion(binding) {
@@ -13070,7 +13166,7 @@ function claudeImplementerArgs(turn, mcp, settings, prompt, model) {
13070
13166
  async function proveClaudeImplementer(peer, command) {
13071
13167
  const binary = Bun.which(command[0], { PATH: process.env.PATH }) ?? command[0];
13072
13168
  const fingerprint = () => {
13073
- const file = realPath(binary), stat = lstatSync12(file);
13169
+ const file = realPath(binary), stat = lstatSync13(file);
13074
13170
  return [file, stat.dev, stat.ino, stat.size, stat.mtimeMs, stat.ctimeMs].join(":");
13075
13171
  };
13076
13172
  const binding = fingerprint(), home = mkdtempSync5(join17(tmpdir5(), "ahub-claude-proof-"));
@@ -13625,7 +13721,7 @@ var init_reviewer_variants = __esm(() => {
13625
13721
  });
13626
13722
 
13627
13723
  // src/adapters/agy/hooks.ts
13628
- import { closeSync as closeSync7, constants as constants6, fstatSync as fstatSync6, lstatSync as lstatSync13, openSync as openSync7, readSync as readSync5 } from "fs";
13724
+ import { closeSync as closeSync7, constants as constants6, fstatSync as fstatSync6, lstatSync as lstatSync14, openSync as openSync7, readSync as readSync5 } from "fs";
13629
13725
  import { homedir as homedir5 } from "os";
13630
13726
  import { isAbsolute as isAbsolute6, join as join18, relative as relative5 } from "path";
13631
13727
  import { createHash as createHash7 } from "crypto";
@@ -13673,7 +13769,7 @@ function readAgyTranscript(session, transcript) {
13673
13769
  let cursor = home;
13674
13770
  for (const name of relative5(home, transcript).split("/")) {
13675
13771
  cursor = join18(cursor, name);
13676
- if (lstatSync13(cursor).isSymbolicLink())
13772
+ if (lstatSync14(cursor).isSymbolicLink())
13677
13773
  return empty;
13678
13774
  }
13679
13775
  fd = openSync7(transcript, constants6.O_RDONLY | constants6.O_NOFOLLOW);
@@ -15107,6 +15203,7 @@ var init_builtin_metadata = __esm(() => {
15107
15203
  version: claudeObservedVersion,
15108
15204
  ...CLAUDE_LAUNCH_POLICY,
15109
15205
  conductorStartable: true,
15206
+ upStart: { allowed: true, default: true },
15110
15207
  unattended: true,
15111
15208
  hubSwitches: switches,
15112
15209
  ownedFlags: Object.freeze(["--dangerously-load-development-channels", "--dangerously-skip-permissions"])
@@ -15148,6 +15245,7 @@ var init_builtin_metadata = __esm(() => {
15148
15245
  version: codexObservedVersion,
15149
15246
  ...CODEX_LAUNCH_POLICY,
15150
15247
  conductorStartable: true,
15248
+ upStart: { allowed: true, default: true },
15151
15249
  unattended: true,
15152
15250
  hubSwitches: switches,
15153
15251
  ownedFlags: Object.freeze(["--remote", "--remote-auth-token-env", "--yolo", "--dangerously-bypass-approvals-and-sandbox"])
@@ -15173,7 +15271,7 @@ var init_builtin_metadata = __esm(() => {
15173
15271
  roles: implementerRoles,
15174
15272
  defaultOnRoster: true,
15175
15273
  trust: { onPrem: false, hubSandboxed: false },
15176
- start: { create: createKimi, acpCommand: kimiAcpCommand, explicitHeadlessMode: true, planCommand: () => "ahub kimi", daemonPort: "acp", forms: dualForms, defaultForm: "tui", conductorStartable: true, hubSwitches: [], ownedFlags: [] },
15274
+ start: { create: createKimi, acpCommand: kimiAcpCommand, explicitHeadlessMode: true, planCommand: () => "ahub kimi", daemonPort: "acp", forms: dualForms, defaultForm: "tui", conductorStartable: true, upStart: { allowed: true }, hubSwitches: [], ownedFlags: [] },
15177
15275
  identity: { mcp: "acp-session" },
15178
15276
  instances: { mode: "headless" },
15179
15277
  attach: {
@@ -15207,7 +15305,7 @@ var init_builtin_metadata = __esm(() => {
15207
15305
  roles: implementerRoles,
15208
15306
  defaultOnRoster: false,
15209
15307
  trust: { onPrem: false, hubSandboxed: true },
15210
- start: { create: createPi, exclusiveStartRefusal: "Pi start is in progress; inspect status before retrying", refreshTools: true, explicitHeadlessMode: true, daemonPort: "pi-rpc", forms: dualForms, defaultForm: "tui", conductorStartable: true, autoStart: "pi.auto_start", conductorStartArgs: piConductorStartArgs, hubSwitches: [], ownedFlags: [] },
15308
+ start: { create: createPi, exclusiveStartRefusal: "Pi start is in progress; inspect status before retrying", refreshTools: true, explicitHeadlessMode: true, daemonPort: "pi-rpc", forms: dualForms, defaultForm: "tui", conductorStartable: true, upStart: { allowed: true }, autoStart: "pi.auto_start", conductorStartArgs: piConductorStartArgs, hubSwitches: [], ownedFlags: [] },
15211
15309
  identity: { mcp: "env" },
15212
15310
  instances: { mode: "headless" },
15213
15311
  permission: { grants: { ...hubGrants } },
@@ -15230,7 +15328,7 @@ var init_builtin_metadata = __esm(() => {
15230
15328
  roles: implementerRoles,
15231
15329
  defaultOnRoster: true,
15232
15330
  trust: { onPrem: true, hubSandboxed: true },
15233
- start: { create: createLocal, reconfigureWhileAttached: true, refreshTools: true, daemonPort: "local-worker", forms: headlessForms, defaultForm: "headless", conductorStartable: true, hubSwitches: [], ownedFlags: [] },
15331
+ start: { create: createLocal, reconfigureWhileAttached: true, refreshTools: true, daemonPort: "local-worker", forms: headlessForms, defaultForm: "headless", conductorStartable: true, upStart: { allowed: true }, hubSwitches: [], ownedFlags: [] },
15234
15332
  identity: { mcp: "env" },
15235
15333
  instances: { mode: "headless" },
15236
15334
  permission: { grants: { ...hubGrants } },
@@ -15253,7 +15351,7 @@ var init_builtin_metadata = __esm(() => {
15253
15351
  optional: true,
15254
15352
  defaultOnRoster: false,
15255
15353
  trust: { onPrem: false, hubSandboxed: false },
15256
- start: { create: createJcode, preflight: jcodeStartPreflight, daemonPort: "owned-acp", forms: dualForms, defaultForm: "tui", explicitHeadlessMode: true, conductorStartable: true, hubSwitches: [], ownedFlags: [] },
15354
+ start: { create: createJcode, preflight: jcodeStartPreflight, daemonPort: "owned-acp", forms: dualForms, defaultForm: "tui", explicitHeadlessMode: true, conductorStartable: true, upStart: { allowed: true }, hubSwitches: [], ownedFlags: [] },
15257
15355
  identity: { mcp: "project-file" },
15258
15356
  instances: { mode: "headless", nativeHome: true },
15259
15357
  permission: { unmanaged: "Jcode native policy is unmanaged; no hub mode mapping", grants: {
@@ -15411,6 +15509,7 @@ var init_qwen = __esm(() => {
15411
15509
  forms: ["headless"],
15412
15510
  defaultForm: "headless",
15413
15511
  conductorStartable: true,
15512
+ upStart: { allowed: false, refusal: "the hub never opens its TUI, and a Qwen is started only by ahub qwen" },
15414
15513
  hubSwitches: [],
15415
15514
  ownedFlags: [],
15416
15515
  preflight: (request) => request.form !== undefined && request.form !== "headless" && request.form !== "tui" ? "unknown Qwen mode; usage: ahub qwen [--mode headless|tui]" : undefined,
@@ -15838,6 +15937,7 @@ function assertNativeCapabilityScope(definition) {
15838
15937
  ["start.daemonPort pi-rpc", definition.start.daemonPort === "pi-rpc"],
15839
15938
  ["start.daemonPort qwen-dual", definition.start.daemonPort === "qwen-dual"],
15840
15939
  ["start.createTui", declared(definition.start, "createTui")],
15940
+ ["start.upStart", declared(definition.start, "upStart")],
15841
15941
  ["routing", definition.routing !== undefined],
15842
15942
  ["projection", definition.projection !== undefined],
15843
15943
  ["instances.nativeHome", declared(definition.instances, "nativeHome")],
@@ -22924,7 +23024,7 @@ init_registry();
22924
23024
  // src/hub/config-trust.ts
22925
23025
  init_child_process();
22926
23026
  init_git();
22927
- import { lstatSync as lstatSync14, statSync as statSync5 } from "fs";
23027
+ import { lstatSync as lstatSync15, statSync as statSync5 } from "fs";
22928
23028
  import { dirname as dirname12, join as join28 } from "path";
22929
23029
  var MACHINE_LOCAL = [
22930
23030
  "kimi_cmd",
@@ -22953,7 +23053,7 @@ var MACHINE_LOCAL = [
22953
23053
  "corpus.paths",
22954
23054
  "invocation"
22955
23055
  ];
22956
- var ALL_MACHINE_LOCAL = [...MACHINE_LOCAL, "rooms", "tasks.queue_backpressure", "context.max_assignment_used", "action_policy", "protect_instruction_files", "qwen_cmd", "jcode_cmd", "jcode_home", "jcode_provider_profile", "adapters", "adapter_modules", "text_projection", "instances"];
23056
+ var ALL_MACHINE_LOCAL = [...MACHINE_LOCAL, "research.runners", "rooms", "tasks.queue_backpressure", "context.max_assignment_used", "action_policy", "protect_instruction_files", "qwen_cmd", "jcode_cmd", "jcode_home", "jcode_provider_profile", "start.agents", "adapters", "adapter_modules", "text_projection", "instances"];
22957
23057
  function configRefusal(cwd, name) {
22958
23058
  const tracked = configTracked(cwd, name);
22959
23059
  return tracked === undefined ? `git could not confirm that .agenthub/${name} is untracked` : tracked ? `.agenthub/${name} is committed to git` : undefined;
@@ -23032,11 +23132,40 @@ function readAdapterModuleDeclarations(cwd, layers) {
23032
23132
  init_project();
23033
23133
  import { readFileSync as readFileSync14 } from "fs";
23034
23134
  import { join as join29, resolve as resolve17 } from "path";
23135
+
23136
+ // src/hub/protocol-window.ts
23137
+ function protocolWindow(v, vMin) {
23138
+ if (typeof v !== "number" || !Number.isSafeInteger(v) || v <= 0)
23139
+ return;
23140
+ const minimum = vMin === undefined ? v : vMin;
23141
+ if (typeof minimum !== "number" || !Number.isSafeInteger(minimum) || minimum <= 0 || minimum > v)
23142
+ return;
23143
+ return { v, vMin: minimum };
23144
+ }
23145
+ function negotiateProtocol(local, remote) {
23146
+ if (!local || typeof local !== "object" || Array.isArray(local) || !remote || typeof remote !== "object" || Array.isArray(remote))
23147
+ return;
23148
+ const a = protocolWindow(local.v, local.vMin), b = protocolWindow(remote.v, remote.vMin);
23149
+ if (!a || !b)
23150
+ return;
23151
+ const highest = Math.min(a.v, b.v);
23152
+ return highest >= Math.max(a.vMin, b.vMin) ? highest : undefined;
23153
+ }
23154
+
23155
+ // src/hub/control-client.ts
23035
23156
  function stateDirFor(cwd) {
23036
23157
  return projectContext(cwd).stateDir;
23037
23158
  }
23038
- var PROTOCOL = 16;
23039
- var RECOVERY_SOURCE_PROTOCOLS = [9, 10, 11, 12, 13, 14, 15, PROTOCOL];
23159
+ var PROTOCOL = 17;
23160
+ var PROTOCOL_MIN = 16;
23161
+ var RECOVERY_SOURCE_PROTOCOLS = [9, 10, 11, 12, 13, 14, 15, 16, PROTOCOL];
23162
+ function controlProtocolWindow(descriptor) {
23163
+ const legacy = protocolWindow(descriptor.protocol);
23164
+ const window = protocolWindow(descriptor.protocolMax ?? descriptor.protocol, descriptor.protocolMin ?? descriptor.protocol);
23165
+ if (!legacy || !window || legacy.v < window.vMin || legacy.v > window.v)
23166
+ return;
23167
+ return window;
23168
+ }
23040
23169
  function readControl(stateDir) {
23041
23170
  try {
23042
23171
  const status = JSON.parse(readFileSync14(join29(stateDir, "status.json"), "utf8"));
@@ -23051,6 +23180,8 @@ function readControl(stateDir) {
23051
23180
  instanceId: status.instanceId,
23052
23181
  cwd: status.cwd,
23053
23182
  protocol: status.protocol,
23183
+ protocolMin: status.protocolMin,
23184
+ protocolMax: status.protocolMax,
23054
23185
  pid: status.pid,
23055
23186
  features: Array.isArray(status.features) ? status.features.filter((feature) => typeof feature === "string") : [],
23056
23187
  ...startModes ? { startModes } : {},
@@ -23071,6 +23202,7 @@ class ControlClient {
23071
23202
  ws;
23072
23203
  features = [];
23073
23204
  sessionId;
23205
+ protocol;
23074
23206
  welcomeInstanceId;
23075
23207
  get instanceId() {
23076
23208
  return this.welcomeInstanceId;
@@ -23097,8 +23229,8 @@ class ControlClient {
23097
23229
  instancePins.delete(key);
23098
23230
  };
23099
23231
  }
23100
- static connect(stateDir, hello, timeoutMs = 3000, protocol = PROTOCOL) {
23101
- if (!RECOVERY_SOURCE_PROTOCOLS.includes(protocol))
23232
+ static connect(stateDir, hello, timeoutMs = 3000, protocol) {
23233
+ if (protocol !== undefined && !RECOVERY_SOURCE_PROTOCOLS.includes(protocol))
23102
23234
  return Promise.reject(new Error(`unsupported recovery source protocol ${protocol}`));
23103
23235
  const control = readControl(stateDir);
23104
23236
  if (!control)
@@ -23107,9 +23239,13 @@ class ControlClient {
23107
23239
  if (pin && (control.instanceId !== pin.instanceId || hello.instanceId !== undefined && hello.instanceId !== pin.instanceId)) {
23108
23240
  return Promise.reject(new Error("delegated audit consumer changed; refusing a connection to its replacement"));
23109
23241
  }
23110
- if (control.protocol !== undefined && control.protocol !== protocol) {
23111
- return Promise.reject(Object.assign(new Error(`wire version mismatch: hub speaks ${control.protocol}, CLI speaks ${protocol}; stop it with its matching CLI, then upgrade and restart`), { code: 4426 }));
23112
- }
23242
+ const local = protocolWindow(protocol ?? PROTOCOL, protocol ?? PROTOCOL_MIN);
23243
+ const versioned = control.protocol !== undefined || control.protocolMin !== undefined || control.protocolMax !== undefined;
23244
+ const remote = versioned ? controlProtocolWindow(control) : undefined;
23245
+ const selected = versioned ? remote && negotiateProtocol(local, remote) : protocol ?? PROTOCOL_MIN;
23246
+ const mismatch = () => Object.assign(new Error(`wire version mismatch: hub speaks ${control.protocolMax ?? control.protocol}, CLI speaks ${protocol ?? PROTOCOL}; stop it with its matching CLI, then upgrade and restart`), { code: 4426 });
23247
+ if (selected === undefined)
23248
+ return Promise.reject(mismatch());
23113
23249
  if (hello.projectId && hello.projectId !== control.projectId || hello.instanceId && hello.instanceId !== control.instanceId || hello.projectRoot && hello.projectRoot !== control.cwd) {
23114
23250
  return Promise.reject(Object.assign(new Error("hub project or instance does not match the selected project"), { code: 4404 }));
23115
23251
  }
@@ -23151,12 +23287,15 @@ class ControlClient {
23151
23287
  client.pending.delete(msg.rid);
23152
23288
  done(msg);
23153
23289
  };
23154
- ws.onopen = () => void client.request({ t: "hello", v: protocol, token: control.token, ...hello, ...expected }, timeoutMs).then((reply) => {
23290
+ ws.onopen = () => void client.request({ t: "hello", token: control.token, ...hello, ...expected, v: selected, vMin: remote?.vMin === undefined ? selected : Math.max(local.vMin, remote.vMin) }, timeoutMs).then((reply) => {
23155
23291
  if (reply.t !== "welcome" || reply.ok === false)
23156
23292
  return refuse(new Error(reply.error ?? "hub refused handshake"));
23157
23293
  if (expected.projectId && reply.projectId !== expected.projectId || expected.instanceId && reply.instanceId !== expected.instanceId || expected.projectRoot && reply.cwd !== expected.projectRoot) {
23158
23294
  return refuse(Object.assign(new Error("connected hub has a different project or instance"), { code: 4404 }));
23159
23295
  }
23296
+ if (reply.protocol !== undefined && reply.protocol !== selected)
23297
+ return refuse(mismatch());
23298
+ client.protocol = selected;
23160
23299
  client.features = Object.freeze(Array.isArray(reply.features) ? reply.features.filter((feature) => typeof feature === "string") : []);
23161
23300
  client.welcomeInstanceId = typeof reply.instanceId === "string" && reply.instanceId ? reply.instanceId : undefined;
23162
23301
  client.sessionId = typeof reply.session === "string" && reply.session.length <= 64 ? reply.session : undefined;
@@ -23239,10 +23378,12 @@ class ControlClient {
23239
23378
 
23240
23379
  // src/hub/conductor.ts
23241
23380
  init_registry();
23381
+ init_db_schema();
23242
23382
  init_hub_tools();
23243
23383
  init_envelope();
23244
23384
 
23245
23385
  // src/hub/tasks.ts
23386
+ init_research_runner();
23246
23387
  init_queue_backpressure();
23247
23388
 
23248
23389
  // src/hub/memory-symbols.ts
@@ -23275,7 +23416,9 @@ init_git();
23275
23416
 
23276
23417
  // src/hub/tasks.ts
23277
23418
  init_spec_read();
23419
+
23278
23420
  // src/hub/schedules.ts
23421
+ init_db_schema();
23279
23422
  var MIN_EVERY_MS = 15 * 60000;
23280
23423
  var MAX_EVERY_MS = 365 * 86400000;
23281
23424
  var KEYS = new Set(["id", "every", "cron", "tz", "class", "title", "detail", "sources", "owner", "queue"]);
@@ -23289,65 +23432,7 @@ var INTAKE_MAX_ANSWER = 64 * 1024;
23289
23432
  // src/hub/tasks.ts
23290
23433
  init_sdlc_metrics();
23291
23434
  init_security_baseline();
23292
- // package.json
23293
- var package_default = {
23294
- name: "@staix/agent-hub",
23295
- version: "0.25.0",
23296
- description: "Native multi-agent hub: Claude Code, Codex, Kimi Code, Pi and local inference as peers in one project",
23297
- license: "MIT",
23298
- type: "module",
23299
- bin: {
23300
- ahub: "src/cli/main.js",
23301
- "agent-hub": "src/cli/main.js"
23302
- },
23303
- scripts: {
23304
- check: "scripts/check.sh",
23305
- build: "bun scripts/build.mjs",
23306
- test: "bun test"
23307
- },
23308
- engines: {
23309
- bun: ">=1.4.2"
23310
- },
23311
- dependencies: {
23312
- "@staix/agent-mem": "0.18.0"
23313
- },
23314
- devDependencies: {
23315
- "@modelcontextprotocol/sdk": "^1.27.1",
23316
- "@types/bun": "^1.3.0",
23317
- typescript: "^5.8.0"
23318
- },
23319
- files: [
23320
- ".claude-plugin",
23321
- "src",
23322
- "src/pet/window.jxa.js",
23323
- "src/pet/window.html",
23324
- "plugins",
23325
- "templates",
23326
- "scripts/check-docs.mjs",
23327
- "docs",
23328
- "README.md",
23329
- "LICENSE",
23330
- "CHANGELOG.md",
23331
- "THIRD_PARTY_NOTICES.md",
23332
- "LICENSES"
23333
- ],
23334
- repository: {
23335
- type: "git",
23336
- url: "git+https://github.com/STAIxBWLB/agent-hub.git"
23337
- },
23338
- homepage: "https://github.com/STAIxBWLB/agent-hub",
23339
- publishConfig: {
23340
- access: "public"
23341
- },
23342
- exports: {
23343
- "./adapter": "./src/adapters/public.ts",
23344
- "./conformance": "./src/adapters/conformance/index.ts",
23345
- "./ui-plugin": "./src/ui/plugin.ts"
23346
- }
23347
- };
23348
-
23349
- // src/version.ts
23350
- var VERSION = package_default.version;
23435
+ init_version();
23351
23436
 
23352
23437
  // src/hub/no-writes.ts
23353
23438
  var OWNER_TOOL_EFFECTS = new Set(["hub_task_propose", "hub_task_decline", "hub_task_done", "hub_task_cancel", "hub_task_assign", "hub_task_escalate", "hub_review", "hub_checkpoint", "hub_remember", "hub_memory_correct", "hub_check_start", "hub_check_cancel", "hub_sdlc_record", "hub_plan_review", "hub_backlog_plan", "hub_backlog_pull", "hub_backlog_undo", "hub_backlog_split", "hub_backlog_return_untouched", "hub_release_new", "hub_release_configure", "hub_release_assign", "hub_release_cut", "hub_study_handoff"]);
@@ -23405,7 +23490,7 @@ var LEARNING_DEFAULTS = { windowMs: 30 * 24 * 3600000, threshold: 3, maxGroups:
23405
23490
  init_deliverables();
23406
23491
 
23407
23492
  // src/hub/deliverable-files.ts
23408
- import { constants as constants8, lstatSync as lstatSync15 } from "fs";
23493
+ import { constants as constants8, lstatSync as lstatSync16 } from "fs";
23409
23494
  init_tools();
23410
23495
  init_deny();
23411
23496
  init_permission_mode();
@@ -23466,6 +23551,7 @@ init_tool_refusal();
23466
23551
  // src/hub/peer-doing.ts
23467
23552
  var TITLE_READ_MAX = 1024 * 1024;
23468
23553
  var TITLE_LINE_MAX = 64 * 1024;
23554
+
23469
23555
  // src/hub/instances.ts
23470
23556
  import { types as types4 } from "util";
23471
23557
  init_project();
@@ -32106,7 +32192,7 @@ init_registry();
32106
32192
  // src/hub/instance-execution.ts
32107
32193
  init_git();
32108
32194
  init_project();
32109
- import { lstatSync as lstatSync16 } from "fs";
32195
+ import { lstatSync as lstatSync17 } from "fs";
32110
32196
  import { join as join31, resolve as resolve19 } from "path";
32111
32197
  var refuse = (reason) => {
32112
32198
  throw new Error(`instance execution workspace refused: ${reason}`);
@@ -32149,7 +32235,7 @@ function resolveInstanceExecutionCwd(projectRoot, id, declarations, roles, adapt
32149
32235
  try {
32150
32236
  root = realPath(projectRoot);
32151
32237
  cwd = realPath(resolve19(root, declaration.worktree));
32152
- if (!lstatSync16(root).isDirectory() || !lstatSync16(cwd).isDirectory())
32238
+ if (!lstatSync17(root).isDirectory() || !lstatSync17(cwd).isDirectory())
32153
32239
  return refuse("workspace is not a directory");
32154
32240
  } catch {
32155
32241
  return refuse("workspace paths are unavailable");
@@ -32220,6 +32306,7 @@ function channelProject(cwd, env, policyRoles) {
32220
32306
  }
32221
32307
 
32222
32308
  // src/adapters/claude-channel.ts
32309
+ init_version();
32223
32310
  init_hub_tools();
32224
32311
  init_envelope();
32225
32312
 
@@ -6,7 +6,7 @@ import type { StartForm } from "./registry-types.ts";
6
6
  /** Only the control socket operations the peer owns; authentication remains in the daemon. */
7
7
  export interface ChannelSocket {
8
8
  readyState: number;
9
- data: { launchId?: string };
9
+ data: { launchId?: string; protocol?: number };
10
10
  send(data: string): unknown;
11
11
  close(code?: number, reason?: string): void;
12
12
  }
@@ -142,7 +142,9 @@ export class WsPeer extends BasePeer {
142
142
  }
143
143
 
144
144
  recoveryMetadata(): Record<string, unknown> {
145
- return { launch: definitionOf(this.id)?.attach?.recoveryLaunch?.(this.id) ?? { kind: "claude-channel", peer: this.id } };
145
+ const protocol = this.attached ? this.sock?.data.protocol : undefined;
146
+ return { launch: definitionOf(this.id)?.attach?.recoveryLaunch?.(this.id) ?? { kind: "claude-channel", peer: this.id },
147
+ ...(typeof protocol === "number" && Number.isSafeInteger(protocol) && protocol > 0 ? { controlProtocol: protocol } : {}) };
146
148
  }
147
149
  }
148
150