@kontextmind/kxm 0.7.167 → 0.7.169

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 (35) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/CHANGELOG.md +30 -0
  3. package/docs/contributing/ci-and-release.md +6 -1
  4. package/docs/operations/runtime-sync.md +2 -1
  5. package/docs/operations/troubleshooting.md +4 -0
  6. package/docs/operations/upgrade.md +18 -3
  7. package/docs/operations.md +70 -7
  8. package/docs/reference/cli-reference.md +21 -6
  9. package/docs/reference/config-reference.md +28 -13
  10. package/docs/start/install.md +1 -1
  11. package/package.json +1 -1
  12. package/plugins/kxm/.claude-plugin/plugin.json +1 -1
  13. package/plugins/kxm/dist/claude-hook.js +170 -36
  14. package/plugins/kxm/dist/cli.js +451 -132
  15. package/plugins/kxm/dist/extension.js +184 -53
  16. package/plugins/kxm/dist/mcp-server.js +97 -40
  17. package/plugins/kxm/dist/runtime-supervisor.js +240 -47
  18. package/plugins/kxm/dist/runtime.js +240 -47
  19. package/plugins/kxm/dist/server.js +9 -0
  20. package/plugins/kxm/package.json +1 -1
  21. package/plugins/kxm/skills/kxm-definitions/SKILL.md +3 -2
  22. package/plugins/kxm/skills/kxm-harness-auth/SKILL.md +4 -2
  23. package/plugins/kxm/skills/kxm-hub-ops/SKILL.md +9 -4
  24. package/plugins/kxm/src/cli/hub.ts +42 -7
  25. package/plugins/kxm/src/cli/system.ts +3 -1
  26. package/plugins/kxm/src/cli/types.ts +5 -3
  27. package/plugins/kxm/src/cli.ts +2 -1
  28. package/plugins/kxm/src/engine.ts +105 -8
  29. package/plugins/kxm/src/hub-binding.ts +301 -55
  30. package/plugins/kxm/src/hub-identity.ts +17 -9
  31. package/plugins/kxm/src/kxm-update.ts +29 -6
  32. package/plugins/kxm/src/mcp-server.ts +1 -1
  33. package/plugins/kxm/src/project-config.ts +63 -1
  34. package/plugins/kxm/src/runtime-supervisor.ts +22 -4
  35. package/scripts/kxm-publish-npm.mjs +47 -1
@@ -17261,7 +17261,28 @@ var RESOURCE_SCHEMA = Object.freeze({
17261
17261
  var IDENTIFIER2 = /^[a-z][a-z0-9]*(?:[-_][a-z0-9]+)*$/;
17262
17262
  var WINDOWS_RESERVED = /^(?:con|prn|aux|nul|com[1-9¹²³]|lpt[1-9¹²³]|conin\$|conout\$|clock\$)$/i;
17263
17263
  var BUILTIN_EXECUTORS = ["local", "ssh", "exe-dev"];
17264
- var BUILTIN_TOOL_PRESETS = ["coordinator", "read-only", "workspace-writer", "tests-writer"];
17264
+ var TOOL_PRESET_PROFILES = Object.freeze({
17265
+ coordinator: "read-only",
17266
+ "read-only": "read-only",
17267
+ "workspace-writer": "edit",
17268
+ "tests-writer": "edit"
17269
+ });
17270
+ var BUILTIN_TOOL_PRESETS = Object.keys(TOOL_PRESET_PROFILES);
17271
+ function toolPresetProfile(preset) {
17272
+ if (!Object.hasOwn(TOOL_PRESET_PROFILES, preset)) return void 0;
17273
+ return TOOL_PRESET_PROFILES[preset];
17274
+ }
17275
+ function toolPresetExceedsRoleMessage(preset, roleId, rolePermission, rolePreset) {
17276
+ const profile = toolPresetProfile(preset);
17277
+ if (profile !== "edit") return void 0;
17278
+ if (rolePermission === "read-only") {
17279
+ return `tools.preset ${preset} exceeds role ${roleId} permission read-only`;
17280
+ }
17281
+ if (rolePreset !== void 0 && toolPresetProfile(rolePreset) === "read-only") {
17282
+ return `tools.preset ${preset} exceeds role ${roleId} preset ${rolePreset}`;
17283
+ }
17284
+ return void 0;
17285
+ }
17265
17286
  var SECRET_VALUE_PATTERNS = [
17266
17287
  /-----BEGIN (?:[A-Z ]+ )?PRIVATE KEY-----/,
17267
17288
  /\b(?:ghp|github_pat)_[A-Za-z0-9_]{20,}\b/,
@@ -17661,6 +17682,15 @@ function objectValue(value) {
17661
17682
  function stringValue(value) {
17662
17683
  return typeof value === "string" ? value : void 0;
17663
17684
  }
17685
+ function roleById(roles, roleId) {
17686
+ const direct = roles.get(roleId);
17687
+ if (direct) return direct;
17688
+ const folded = roleId.toLocaleLowerCase("en-US");
17689
+ for (const role of roles.values()) {
17690
+ if ((role.id ?? "").toLocaleLowerCase("en-US") === folded) return role;
17691
+ }
17692
+ return void 0;
17693
+ }
17664
17694
  function numberValue(value, fallback) {
17665
17695
  return typeof value === "number" ? value : fallback;
17666
17696
  }
@@ -18175,10 +18205,29 @@ function validateBundle(project, repositories, agents, models, workflows, enviro
18175
18205
  if (executor && !executors.has(executor)) issues.push(issue3("reference", "executor_unknown", agent.logicalPath, `executor ${executor} is not registered`));
18176
18206
  const preset = stringValue(objectValue(agent.value.tools)?.preset);
18177
18207
  if (preset && !presets.has(preset)) issues.push(issue3("reference", "tool_preset_unknown", agent.logicalPath, `tool preset ${preset} is not registered`));
18208
+ const roleId = stringValue(agent.value.role);
18209
+ const role = roleId ? roleById(roles, roleId) : void 0;
18210
+ const rolePermission = stringValue(role?.value.permission);
18211
+ if (preset && role && (rolePermission === "edit" || rolePermission === "read-only")) {
18212
+ const message = toolPresetExceedsRoleMessage(
18213
+ preset,
18214
+ role.id ?? roleId ?? "role",
18215
+ rolePermission,
18216
+ stringValue(objectValue(role.value.tools)?.preset)
18217
+ );
18218
+ if (message) issues.push(issue3("semantic", "tool_preset_exceeds_role", agent.logicalPath, message));
18219
+ }
18178
18220
  for (const repositoryId of Object.keys(objectValue(agent.value.repositories) ?? {})) {
18179
18221
  if (!repositoryIds.has(repositoryId)) issues.push(issue3("reference", "repository_unknown", agent.logicalPath, `references unknown repository ${repositoryId}`));
18180
18222
  }
18181
18223
  }
18224
+ for (const role of roles.values()) {
18225
+ const permission = stringValue(role.value.permission);
18226
+ const rolePreset = stringValue(objectValue(role.value.tools)?.preset);
18227
+ if (permission === "read-only" && rolePreset && toolPresetProfile(rolePreset) === "edit") {
18228
+ issues.push(issue3("semantic", "tool_preset_exceeds_role", role.logicalPath, `tools.preset ${rolePreset} exceeds permission read-only`));
18229
+ }
18230
+ }
18182
18231
  validateModelReferences(models, issues);
18183
18232
  const defaultWorkflow = stringValue(project.value.defaultWorkflow) ?? "default";
18184
18233
  if (!lookupKxmResource(workflows.values(), defaultWorkflow, "workflow")) issues.push(issue3("reference", "default_workflow_unknown", project.logicalPath, `default workflow ${defaultWorkflow} does not exist`));
@@ -29377,6 +29426,68 @@ function unsupportedAgentStepTimeout(step, limitMs) {
29377
29426
  detail: "step timeoutMs is wider than project limits.agentStepTimeoutMs"
29378
29427
  };
29379
29428
  }
29429
+ function narrowerPermission(left, right) {
29430
+ return left === "read-only" || right === "read-only" ? "read-only" : "edit";
29431
+ }
29432
+ function stepRepositoryPermission(step) {
29433
+ return Object.values(step.repositories).some((access) => access === "write") ? "edit" : "read-only";
29434
+ }
29435
+ function stepAccessDetail(step) {
29436
+ const entries = Object.entries(step.repositories);
29437
+ if (entries.length === 0) return "none";
29438
+ return entries.map(([id, access]) => `${id} ${access}`).sort((left, right) => left < right ? -1 : left > right ? 1 : 0).join(", ");
29439
+ }
29440
+ function yamlToolsPreset(doc) {
29441
+ const tools = doc?.tools;
29442
+ if (!tools || typeof tools !== "object" || Array.isArray(tools)) return void 0;
29443
+ const preset = tools.preset;
29444
+ return typeof preset === "string" && preset.length > 0 ? preset : void 0;
29445
+ }
29446
+ function resolveToolDispatch(projectRoot, step, agentId) {
29447
+ const stepPermission = stepRepositoryPermission(step);
29448
+ if (step.kind !== "agent" && step.kind !== "moa") return { permission: stepPermission };
29449
+ const agentFile = findYamlBasename(join18(projectRoot, ".kxm", "agents"), agentId, "agent") ?? agentId;
29450
+ const agent = readYamlFile(join18(projectRoot, ".kxm", "agents", `${agentFile}.yaml`));
29451
+ const roleId = typeof agent?.role === "string" ? agent.role : "";
29452
+ if (!roleId) return { permission: stepPermission };
29453
+ const roleDoc = readYamlFile(join18(projectRoot, ".kxm", "roles", `${roleId}.yaml`));
29454
+ const rawPermission = roleDoc?.permission;
29455
+ const rolePermission = rawPermission === "edit" || rawPermission === "read-only" ? rawPermission : void 0;
29456
+ if (!rolePermission) return { permission: stepPermission };
29457
+ const rolePreset = yamlToolsPreset(roleDoc);
29458
+ const agentPreset = yamlToolsPreset(agent);
29459
+ const rolePresetProfile = rolePreset ? toolPresetProfile(rolePreset) : void 0;
29460
+ if (rolePreset && rolePresetProfile === "edit" && rolePermission === "read-only") {
29461
+ return {
29462
+ error: {
29463
+ reason: "step_unsupported",
29464
+ field: "tools.preset",
29465
+ detail: `tools.preset ${rolePreset} exceeds role ${roleId} permission read-only`
29466
+ }
29467
+ };
29468
+ }
29469
+ if (agentPreset) {
29470
+ const exceeds = toolPresetExceedsRoleMessage(agentPreset, roleId, rolePermission, rolePreset);
29471
+ if (exceeds) {
29472
+ return { error: { reason: "step_unsupported", field: "tools.preset", detail: exceeds } };
29473
+ }
29474
+ }
29475
+ let chain = rolePermission;
29476
+ if (rolePresetProfile) chain = narrowerPermission(chain, rolePresetProfile);
29477
+ const agentProfile = agentPreset ? toolPresetProfile(agentPreset) : void 0;
29478
+ if (agentProfile) chain = narrowerPermission(chain, agentProfile);
29479
+ const permission = narrowerPermission(chain, stepPermission);
29480
+ if (stepPermission === "edit" && permission === "read-only") {
29481
+ return {
29482
+ error: {
29483
+ reason: "step_unsupported",
29484
+ field: "permission",
29485
+ detail: `role permission ${rolePermission} resolves to ${chain}; step access ${stepAccessDetail(step)}`
29486
+ }
29487
+ };
29488
+ }
29489
+ return { permission };
29490
+ }
29380
29491
  function executingSingletonAttempt(plan, state) {
29381
29492
  const current = state.currentStep;
29382
29493
  if (!current || current.panel.order.length !== 1) return void 0;
@@ -29569,10 +29680,17 @@ function prepareDispatch(context, runId, producerId, dispatchSources) {
29569
29680
  if (unsupported) return { kind: "return", state, handoff: { ...unsupported, stepId } };
29570
29681
  const wideTimeout = unsupportedAgentStepTimeout(step, projectAgentStepTimeoutMs(context, run));
29571
29682
  if (wideTimeout) return { kind: "return", state, handoff: { ...wideTimeout, stepId } };
29683
+ const firstListed = step.assignments.allowedAgents[0];
29684
+ const firstAgentId = step.kind === "agent" || step.kind === "moa" ? firstListed ? firstListed : step.agent : void 0;
29685
+ if (firstAgentId) {
29686
+ const toolDispatch = resolveToolDispatch(context.projectRoot, step, firstAgentId);
29687
+ if ("error" in toolDispatch) {
29688
+ return { kind: "return", state, handoff: { ...toolDispatch.error, stepId } };
29689
+ }
29690
+ }
29572
29691
  let resolvedRoute;
29573
29692
  if (producerId !== "driver-simulated") {
29574
- const allowed = step.assignments.allowedAgents;
29575
- const agentId = allowed && allowed.length > 0 && allowed[0] ? allowed[0] : step.kind === "agent" || step.kind === "moa" ? step.agent : "coordinator";
29693
+ const agentId = firstAgentId ?? (step.kind === "agent" || step.kind === "moa" ? step.agent : "coordinator");
29576
29694
  const routeResult = resolveProducerRoute(context.projectRoot, step, agentId);
29577
29695
  if ("error" in routeResult) {
29578
29696
  return { kind: "return", state, handoff: { ...routeResult.error, stepId } };
@@ -29692,6 +29810,10 @@ function birthMember(context, input) {
29692
29810
  const born = folded.currentStep?.panel.order.length ?? 0;
29693
29811
  const allowed = input.step.assignments.allowedAgents;
29694
29812
  const agentId = allowed && allowed.length > born && allowed[born] ? allowed[born] : input.step.kind === "agent" || input.step.kind === "moa" ? input.step.agent : "coordinator";
29813
+ const toolDispatch = resolveToolDispatch(context.projectRoot, input.step, agentId);
29814
+ if ("error" in toolDispatch) {
29815
+ return { handoff: { ...toolDispatch.error, stepId: input.stepId } };
29816
+ }
29695
29817
  let resolvedRoute = input.resolvedRoute;
29696
29818
  if (!resolvedRoute && input.producerId && input.producerId !== "driver-simulated") {
29697
29819
  const routeResult = resolveProducerRoute(context.projectRoot, input.step, agentId);
@@ -29781,7 +29903,7 @@ function birthMember(context, input) {
29781
29903
  stepAttempt: input.stepAttempt,
29782
29904
  objective: promptText,
29783
29905
  allowedOutcomes: [...input.step.outcomes],
29784
- permissionCeiling: Object.values(input.step.repositories).some((access) => access === "write") ? "edit" : "read-only"
29906
+ permissionCeiling: toolDispatch.permission
29785
29907
  },
29786
29908
  acceptanceCriteria: input.step.requiredEvidence.map((ev) => ({
29787
29909
  id: ev.key,
@@ -29826,7 +29948,7 @@ function birthMember(context, input) {
29826
29948
  prompt: input.step.instructions ? `${input.step.instructions}
29827
29949
 
29828
29950
  ${generatedPrompt}` : generatedPrompt,
29829
- permission: Object.values(input.step.repositories).some((access) => access === "write") ? "edit" : "read-only",
29951
+ permission: toolDispatch.permission,
29830
29952
  ...(input.step.kind === "agent" || input.step.kind === "moa") && input.step.timeoutMs !== void 0 ? { timeoutMs: input.step.timeoutMs } : {},
29831
29953
  contextPacket,
29832
29954
  ...resolvedRoute?.effort !== void 0 ? { thinking: resolvedRoute.effort } : {},
@@ -31668,6 +31790,9 @@ function resolveUserStateRoot(env) {
31668
31790
  function hubBindingFile(env = process.env) {
31669
31791
  return join19(resolveUserStateRoot(env), "hub-binding.json");
31670
31792
  }
31793
+ function hubBindingCloudFile(env = process.env) {
31794
+ return join19(resolveUserStateRoot(env), "hub-binding.cloud.json");
31795
+ }
31671
31796
  function validateHubUrl(raw) {
31672
31797
  let parsed;
31673
31798
  try {
@@ -31777,33 +31902,37 @@ function isIsoTimestamp(value) {
31777
31902
  if (Number.isNaN(Date.parse(value))) return false;
31778
31903
  return value === new Date(value).toISOString();
31779
31904
  }
31780
- function readHubBinding(env = process.env) {
31781
- const file = hubBindingFile(env);
31782
- if (!existsSync16(file)) return void 0;
31905
+ var LEGACY_BINDING_KEYS = ["schema", "url", "boundAt"];
31906
+ var BINDING_KEYS = /* @__PURE__ */ new Set([...LEGACY_BINDING_KEYS, "cloud", "tokenEnv", "tokenCommand"]);
31907
+ function bindingFallbackWarning(file, reason) {
31908
+ return `hub binding at ${file} ${reason}; falling back to the local hub. Restart the Runtime supervisor, the hub, and workers after upgrading and before a cloud bind.`;
31909
+ }
31910
+ function writeBindingJson(file, value) {
31911
+ mkdirSync7(dirname9(file), { recursive: true, mode: 448 });
31912
+ const temporary = join19(dirname9(file), `.${file.endsWith("cloud.json") ? "hub-binding-cloud" : "hub-binding"}-${process.pid}.tmp`);
31913
+ try {
31914
+ writeFileSync8(temporary, `${JSON.stringify(value, null, 2)}
31915
+ `, { encoding: "utf8", mode: 384 });
31916
+ renameSync2(temporary, file);
31917
+ } finally {
31918
+ rmSync2(temporary, { force: true });
31919
+ }
31920
+ }
31921
+ function parseBindingObject(file) {
31922
+ if (!existsSync16(file)) return new HubBindingError(`missing hub binding at ${file}`);
31783
31923
  let parsed;
31784
31924
  try {
31785
31925
  parsed = JSON.parse(readFileSync14(file, "utf8"));
31786
31926
  } catch {
31787
- throw new HubBindingError(`malformed hub binding at ${file}`);
31927
+ return new HubBindingError(`malformed hub binding at ${file}`);
31788
31928
  }
31789
31929
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
31790
- throw new HubBindingError(`malformed hub binding at ${file}`);
31791
- }
31792
- const row = parsed;
31793
- const keys = Object.keys(row);
31794
- const allowed = /* @__PURE__ */ new Set(["schema", "url", "boundAt", "cloud", "tokenEnv", "tokenCommand"]);
31795
- const cloudish = keys.some((key) => key === "cloud" || key === "tokenEnv" || key === "tokenCommand");
31796
- if (keys.some((key) => !allowed.has(key)) || !cloudish && keys.length !== 3 || row.schema !== HUB_BINDING_SCHEMA || typeof row.url !== "string" || typeof row.boundAt !== "string" || !isIsoTimestamp(row.boundAt)) {
31797
- throw new HubBindingError(`malformed hub binding at ${file}`);
31930
+ return new HubBindingError(`malformed hub binding at ${file}`);
31798
31931
  }
31799
- let url;
31800
- try {
31801
- url = validateHubUrl(row.url);
31802
- } catch {
31803
- throw new HubBindingError(`malformed hub binding at ${file}`);
31804
- }
31805
- if (!cloudish) return { schema: HUB_BINDING_SCHEMA, url, boundAt: row.boundAt };
31806
- if (row.cloud !== true) throw new HubBindingError(`malformed hub binding at ${file}`);
31932
+ return parsed;
31933
+ }
31934
+ function cloudRecordFromRow(file, row, url, boundAt) {
31935
+ if (row.cloud !== true) return void 0;
31807
31936
  let tokenEnv;
31808
31937
  let tokenCommand;
31809
31938
  if (row.tokenEnv !== void 0) {
@@ -31825,12 +31954,65 @@ function readHubBinding(env = process.env) {
31825
31954
  return {
31826
31955
  schema: HUB_BINDING_SCHEMA,
31827
31956
  url,
31828
- boundAt: row.boundAt,
31957
+ boundAt,
31829
31958
  cloud: true,
31830
31959
  ...tokenEnv ? { tokenEnv } : {},
31831
31960
  ...tokenCommand ? { tokenCommand } : {}
31832
31961
  };
31833
31962
  }
31963
+ function interpretBindingFile(file) {
31964
+ if (!existsSync16(file)) return {};
31965
+ const parsed = parseBindingObject(file);
31966
+ if (parsed instanceof HubBindingError) throw parsed;
31967
+ const keys = Object.keys(parsed);
31968
+ const unknown = keys.filter((key) => !BINDING_KEYS.has(key));
31969
+ if (parsed.schema !== HUB_BINDING_SCHEMA || unknown.length > 0) {
31970
+ return { warning: bindingFallbackWarning(file, `is newer than this build understands (${String(parsed.schema)})`) };
31971
+ }
31972
+ if (typeof parsed.url !== "string" || typeof parsed.boundAt !== "string" || !isIsoTimestamp(parsed.boundAt)) {
31973
+ throw new HubBindingError(`malformed hub binding at ${file}`);
31974
+ }
31975
+ let url;
31976
+ try {
31977
+ url = validateHubUrl(parsed.url);
31978
+ } catch {
31979
+ throw new HubBindingError(`malformed hub binding at ${file}`);
31980
+ }
31981
+ const cloudish = keys.some((key) => key === "cloud" || key === "tokenEnv" || key === "tokenCommand");
31982
+ if (!cloudish) {
31983
+ if (keys.length !== 3) throw new HubBindingError(`malformed hub binding at ${file}`);
31984
+ return { record: { schema: HUB_BINDING_SCHEMA, url, boundAt: parsed.boundAt } };
31985
+ }
31986
+ const record2 = cloudRecordFromRow(file, parsed, url, parsed.boundAt);
31987
+ if (!record2) throw new HubBindingError(`malformed hub binding at ${file}`);
31988
+ return { record: record2, legacyCloud: true };
31989
+ }
31990
+ function loadHubBinding(env = process.env) {
31991
+ const cloudFile = hubBindingCloudFile(env);
31992
+ const cloud = interpretBindingFile(cloudFile);
31993
+ if (cloud.warning && !cloud.record) {
31994
+ const local = existsSync16(hubBindingFile(env)) ? interpretBindingFile(hubBindingFile(env)) : {};
31995
+ if (local.record && !local.record.cloud) return { record: local.record, warning: cloud.warning };
31996
+ return { warning: cloud.warning };
31997
+ }
31998
+ if (cloud.record?.cloud) return { record: cloud.record, ...cloud.warning ? { warning: cloud.warning } : {} };
31999
+ const main = interpretBindingFile(hubBindingFile(env));
32000
+ if (main.legacyCloud && main.record) {
32001
+ writeBindingJson(cloudFile, main.record);
32002
+ rmSync2(hubBindingFile(env), { force: true });
32003
+ return {
32004
+ record: main.record,
32005
+ warning: bindingFallbackWarning(
32006
+ hubBindingFile(env),
32007
+ "stored a cloud bind in the pre-0.7.160 file"
32008
+ )
32009
+ };
32010
+ }
32011
+ return { ...main.record ? { record: main.record } : {}, ...main.warning ? { warning: main.warning } : {} };
32012
+ }
32013
+ function readHubBinding(env = process.env) {
32014
+ return loadHubBinding(env).record;
32015
+ }
31834
32016
 
31835
32017
  // plugins/kxm/src/hub-env.ts
31836
32018
  import { existsSync as existsSync18, mkdirSync as mkdirSync8, readFileSync as readFileSync16, renameSync as renameSync3, rmSync as rmSync3, writeFileSync as writeFileSync9 } from "node:fs";
@@ -31906,15 +32088,6 @@ function resolveProjectIdentity(cwd, env = process.env, explicit) {
31906
32088
  if (fromPackage) return { project: fromPackage, source: "package.json", sourceLabel: "package.json name" };
31907
32089
  return { project: basename4(cwd), source: "directory", sourceLabel: "directory name" };
31908
32090
  }
31909
- function opReferenceFromTokenCommand(command) {
31910
- try {
31911
- const args = splitTokenCommand(command);
31912
- if (args[0] === "op" && args[1] === "read" && args[2] && isHubOpReference(args[2])) return args[2];
31913
- } catch {
31914
- return void 0;
31915
- }
31916
- return void 0;
31917
- }
31918
32091
  function defaultOpRead(reference, env) {
31919
32092
  if (!isHubOpReference(reference)) {
31920
32093
  throw new CloudTokenError("cloud_token_command_invalid", `refusing to resolve ${reference}; an op:// reference is required`);
@@ -31959,18 +32132,24 @@ function resolveKeyReference(ref, env, opRead = defaultOpRead) {
31959
32132
  }
31960
32133
  function keySourceFor(input) {
31961
32134
  const ref = input.endpoint?.key;
31962
- if (input.mode === "local" && input.env.KXM_AUTH_TOKEN?.trim()) return "env:KXM_AUTH_TOKEN";
31963
- if (ref?.env && input.env[ref.env]?.trim()) return `env:${ref.env}`;
31964
- if (ref?.op) return `op:${ref.op}`;
31965
- if (ref?.env) return `env:${ref.env}`;
32135
+ const envName = (name) => {
32136
+ if (!name) return void 0;
32137
+ return input.env[name]?.trim() ? `env:${name}` : void 0;
32138
+ };
31966
32139
  if (input.mode === "cloud") {
31967
- if (input.binding?.tokenEnv) return `env:${input.binding.tokenEnv}`;
31968
- if (input.binding?.tokenCommand) {
31969
- const op = opReferenceFromTokenCommand(input.binding.tokenCommand);
31970
- return op ? `op:${op}` : "command";
31971
- }
32140
+ const fromEnv2 = envName(ref?.env);
32141
+ if (fromEnv2) return fromEnv2;
32142
+ if (ref?.op) return `op:${ref.op}`;
32143
+ const fromBindingEnv = envName(input.binding?.tokenEnv);
32144
+ if (fromBindingEnv) return fromBindingEnv;
32145
+ if (input.binding?.tokenCommand) return "token-command";
31972
32146
  return "missing";
31973
32147
  }
32148
+ const fromAuth = envName("KXM_AUTH_TOKEN");
32149
+ if (fromAuth) return fromAuth;
32150
+ const fromEnv = envName(ref?.env);
32151
+ if (fromEnv) return fromEnv;
32152
+ if (ref?.op) return `op:${ref.op}`;
31974
32153
  if (input.hasSavedProjectToken?.(input.project)) return "hub-env";
31975
32154
  return "missing";
31976
32155
  }
@@ -32631,8 +32810,17 @@ async function syncKxmOutbox(eventStore, client, options = {}) {
32631
32810
  }
32632
32811
  }
32633
32812
  }
32634
- function runtimeHubClientFor(context, env) {
32635
- const serverUrl = env.KXM_SERVER_URL?.trim() || readHubBinding(env)?.url;
32813
+ function runtimeHubClientFor(context, env, onBindingWarning) {
32814
+ let recordUrl;
32815
+ try {
32816
+ const loaded = loadHubBinding(env);
32817
+ if (loaded.warning) onBindingWarning?.(loaded.warning);
32818
+ recordUrl = loaded.record?.url;
32819
+ } catch (error) {
32820
+ if (!(error instanceof HubBindingError)) throw error;
32821
+ onBindingWarning?.(`${error.message}; falling back to the local hub so sync can continue`);
32822
+ }
32823
+ const serverUrl = env.KXM_SERVER_URL?.trim() || recordUrl;
32636
32824
  if (!serverUrl) return void 0;
32637
32825
  const project = context.projectId;
32638
32826
  const authToken = resolveClientHubAuthToken(env, project);
@@ -33160,6 +33348,7 @@ async function startKxmRuntimeSupervisorInner(paths, requestedPortOption, now, e
33160
33348
  else logger.info(entry);
33161
33349
  };
33162
33350
  let syncing = false;
33351
+ let bindingWarning;
33163
33352
  const syncTimer = setInterval(() => {
33164
33353
  if (syncing || stopping) return;
33165
33354
  syncing = true;
@@ -33177,7 +33366,11 @@ async function startKxmRuntimeSupervisorInner(paths, requestedPortOption, now, e
33177
33366
  consecutiveFailures: 0
33178
33367
  };
33179
33368
  try {
33180
- const client = runtimeHubClientFor(context, env);
33369
+ const client = runtimeHubClientFor(context, env, (message) => {
33370
+ if (bindingWarning === message) return;
33371
+ bindingWarning = message;
33372
+ logger.warn({ event: "hub_binding_fallback", message });
33373
+ });
33181
33374
  if (!client) {
33182
33375
  recordSyncStatus(context, { ...base, state: "no_hub" });
33183
33376
  continue;