@kontextmind/kxm 0.7.96 → 0.7.97

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 (41) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/CHANGELOG.md +22 -0
  3. package/docs/concepts/trust-model.md +3 -2
  4. package/docs/glossary.md +1 -1
  5. package/docs/guides/peer-messaging.md +2 -2
  6. package/docs/guides/pi-workers.md +1 -1
  7. package/docs/guides/webhook-workflows.md +53 -18
  8. package/docs/operations/deploy.md +1 -1
  9. package/docs/operations/troubleshooting.md +3 -2
  10. package/docs/reference/cli-reference.md +6 -6
  11. package/docs/reference/configuration.md +2 -2
  12. package/docs/reference/http-api.md +6 -6
  13. package/docs/reference/tools.md +1 -1
  14. package/docs/reference/workflow-definitions.md +2 -2
  15. package/docs/start/quickstart-pi.md +1 -1
  16. package/examples/workflow-signal.ts +4 -5
  17. package/package.json +1 -1
  18. package/plugins/kxm/.claude-plugin/plugin.json +1 -1
  19. package/plugins/kxm/dist/claude-hook.js +11 -1
  20. package/plugins/kxm/dist/cli.js +159 -74
  21. package/plugins/kxm/dist/client.js +3 -1
  22. package/plugins/kxm/dist/core.js +11 -1
  23. package/plugins/kxm/dist/extension.js +45 -13
  24. package/plugins/kxm/dist/mcp-server.js +20 -4
  25. package/plugins/kxm/dist/runtime-supervisor.js +1 -3
  26. package/plugins/kxm/dist/runtime.js +17 -3
  27. package/plugins/kxm/dist/server.js +115 -20
  28. package/plugins/kxm/package.json +1 -1
  29. package/plugins/kxm/src/cli/workflows.ts +12 -7
  30. package/plugins/kxm/src/cli.ts +19 -5
  31. package/plugins/kxm/src/client.ts +4 -0
  32. package/plugins/kxm/src/commands.ts +23 -1
  33. package/plugins/kxm/src/extension.ts +20 -14
  34. package/plugins/kxm/src/github-watch.ts +8 -5
  35. package/plugins/kxm/src/hub-env.ts +19 -1
  36. package/plugins/kxm/src/hub.ts +105 -21
  37. package/plugins/kxm/src/improve-sources.ts +2 -7
  38. package/plugins/kxm/src/mcp-server.ts +9 -2
  39. package/plugins/kxm/src/runtime-store.ts +23 -0
  40. package/plugins/kxm/src/workflow.ts +70 -1
  41. package/scripts/smoke-multi-pi.mjs +5 -1
@@ -18475,7 +18475,7 @@ function validateTerminalReceipt(value) {
18475
18475
  }
18476
18476
 
18477
18477
  // plugins/kxm/src/workflow.ts
18478
- import { createHash as createHash2 } from "node:crypto";
18478
+ import { createHash as createHash2, createHmac } from "node:crypto";
18479
18479
 
18480
18480
  // plugins/kxm/src/redact.ts
18481
18481
  var SECRET_PATTERNS = [
@@ -19123,6 +19123,37 @@ function parseOutcomeMap(stageId, raw) {
19123
19123
  }
19124
19124
  return map;
19125
19125
  }
19126
+ var WORKFLOW_WEBHOOK_SIGNATURE_VERSION = "kxm-webhook-v1";
19127
+ function workflowWebhookSignedMaterial(scope, timestamp, deliveryId, body) {
19128
+ const fields = [
19129
+ WORKFLOW_WEBHOOK_SIGNATURE_VERSION,
19130
+ scope.runId === void 0 ? "start" : "signal",
19131
+ timestamp,
19132
+ deliveryId,
19133
+ scope.definitionId,
19134
+ scope.runId ?? "",
19135
+ scope.signalKey ?? ""
19136
+ ];
19137
+ if (fields.some((field) => /[\r\n]/.test(field))) {
19138
+ throw new ProtocolError(400, "webhook signature fields must not contain line breaks", "webhook_signature_field_invalid");
19139
+ }
19140
+ return Buffer.concat([
19141
+ Buffer.from(`${fields.join("\n")}
19142
+ `, "utf8"),
19143
+ typeof body === "string" ? Buffer.from(body, "utf8") : Buffer.from(body)
19144
+ ]);
19145
+ }
19146
+ function workflowWebhookSignature(secret, scope, timestamp, deliveryId, body) {
19147
+ return `sha256=${createHmac("sha256", secret).update(workflowWebhookSignedMaterial(scope, timestamp, deliveryId, body)).digest("hex")}`;
19148
+ }
19149
+ function workflowWebhookHeaders(input) {
19150
+ const timestamp = String(Math.floor((input.nowMs ?? Date.now()) / 1e3));
19151
+ return {
19152
+ "x-kxm-delivery-id": input.deliveryId,
19153
+ "x-kxm-timestamp": timestamp,
19154
+ "x-kxm-signature": workflowWebhookSignature(input.secret, input.scope, timestamp, input.deliveryId, input.body)
19155
+ };
19156
+ }
19126
19157
  function resolveOutcomeRule(stage, outcomeKey) {
19127
19158
  const raw = stage.on?.[outcomeKey];
19128
19159
  return raw === void 0 ? void 0 : normalizeOutcomeValue(raw, `stage ${stage.id} on.${outcomeKey}`);
@@ -19552,7 +19583,9 @@ var HubClient = class {
19552
19583
  options.workflowContext
19553
19584
  )
19554
19585
  } : {},
19555
- ...options.ttlMs ? { ttlMs: options.ttlMs } : {}
19586
+ ...options.ttlMs ? { ttlMs: options.ttlMs } : {},
19587
+ ...options.hops !== void 0 ? { hops: options.hops } : {},
19588
+ ...options.maxHops !== void 0 ? { maxHops: options.maxHops } : {}
19556
19589
  });
19557
19590
  const completed2 = await this.awaitResponse(
19558
19591
  message.id,
@@ -19946,6 +19979,24 @@ function resolveClientHubAuthToken(env, project) {
19946
19979
  const record = readHubEnvRecord(env);
19947
19980
  return record?.projectTokens?.[project]?.trim() || record?.authToken?.trim() || void 0;
19948
19981
  }
19982
+ function resolveAgentHubAuthToken(env, project) {
19983
+ const envToken = env.KXM_AUTH_TOKEN?.trim();
19984
+ if (envToken) return envToken;
19985
+ const tokens = readHubEnvRecord(env)?.projectTokens;
19986
+ if (!tokens || !Object.hasOwn(tokens, project)) return void 0;
19987
+ return tokens[project]?.trim() || void 0;
19988
+ }
19989
+ var AgentProjectTokenMissingError = class extends Error {
19990
+ code = "project_token_missing";
19991
+ project;
19992
+ constructor(project) {
19993
+ super(
19994
+ `kxm has no project token for project ${project} on this machine. Set KXM_AUTH_TOKEN to that project's token, or add ${project} to the hub KXM_PROJECT_TOKENS (list every existing project too, because that variable replaces the saved map). An agent never uses the hub admin token.`
19995
+ );
19996
+ this.name = "AgentProjectTokenMissingError";
19997
+ this.project = project;
19998
+ }
19999
+ };
19949
20000
  function resolveClientAdminAuthToken(env = process.env) {
19950
20001
  const envToken = env.KXM_AUTH_TOKEN?.trim();
19951
20002
  if (envToken) return envToken;
@@ -19971,6 +20022,13 @@ import { createHash as createHash4, randomUUID as randomUUID2, timingSafeEqual }
19971
20022
  import { chmodSync, existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync4, unlinkSync, writeFileSync as writeFileSync3 } from "node:fs";
19972
20023
  import { homedir as homedir2 } from "node:os";
19973
20024
  import { dirname as dirname3, join as join5, resolve as resolve2 } from "node:path";
20025
+ function forwardedHops(handling) {
20026
+ if (!handling?.length) return void 0;
20027
+ return {
20028
+ hops: Math.max(...handling.map((message) => message.hops)) + 1,
20029
+ maxHops: Math.min(...handling.map((message) => message.maxHops))
20030
+ };
20031
+ }
19974
20032
  function requiredString(value, name) {
19975
20033
  if (typeof value !== "string" || value.trim() === "") throw new Error(`${name} is required`);
19976
20034
  return value.trim();
@@ -20100,12 +20158,13 @@ var AGENT_COMMANDS = [
20100
20158
  required: ["target", "content"],
20101
20159
  additionalProperties: false
20102
20160
  },
20103
- async execute(client, args) {
20161
+ async execute(client, args, context) {
20104
20162
  const delivery = optionalString2(args.delivery);
20105
20163
  const correlationId = optionalString2(args.correlationId);
20106
20164
  const idempotencyKey = optionalString2(args.idempotencyKey);
20107
20165
  const workflowContext = optionalWorkflowContext(args.workflowContext);
20108
20166
  const message = await client.send({
20167
+ ...forwardedHops(context?.handling),
20109
20168
  target: requiredString(args.target, "target"),
20110
20169
  content: requiredString(args.content, "content"),
20111
20170
  ...delivery ? { delivery } : {},
@@ -20183,6 +20242,7 @@ var AGENT_COMMANDS = [
20183
20242
  const targets = Array.isArray(args.targets) ? args.targets.map((t) => requiredString(t, "target")) : [];
20184
20243
  return {
20185
20244
  responses: await client.fanout({
20245
+ ...forwardedHops(context?.handling),
20186
20246
  targets,
20187
20247
  content: requiredString(args.content, "content"),
20188
20248
  ...optionalString2(args.correlationId) ? { correlationId: optionalString2(args.correlationId) } : {},
@@ -22985,8 +23045,8 @@ function validateWorkflow(workflow, agents, models, repositories, gates, issues)
22985
23045
  const writable = Object.values(objectValue(step.repositories) ?? {}).filter((access) => access === "write").length;
22986
23046
  if (maxWriteRepositories > writable) issues.push(issue2("semantic", "write_repository_bound_invalid", file, `${stepId} maxWriteRepositories exceeds writable repository scope`));
22987
23047
  }
22988
- const join50 = objectValue(step.join);
22989
- const minimumPassed = join50 && typeof join50.minimumPassed === "number" ? join50.minimumPassed : void 0;
23048
+ const join49 = objectValue(step.join);
23049
+ const minimumPassed = join49 && typeof join49.minimumPassed === "number" ? join49.minimumPassed : void 0;
22990
23050
  if (minimumPassed !== void 0 && minimumPassed > maximum) issues.push(issue2("semantic", "join_impossible", file, `${stepId} minimumPassed exceeds assignment maximum`));
22991
23051
  const distinctBy = names(assignment?.distinctBy);
22992
23052
  if (distinctBy.length > 0) {
@@ -23530,7 +23590,7 @@ function planKxmInitialization(start = process.cwd(), options = {}) {
23530
23590
 
23531
23591
  // plugins/kxm/src/runtime-supervisor.ts
23532
23592
  import { spawn as spawn2 } from "node:child_process";
23533
- import { createHash as createHash13, createHmac, randomBytes as randomBytes2, timingSafeEqual as timingSafeEqual2 } from "node:crypto";
23593
+ import { createHash as createHash13, createHmac as createHmac2, randomBytes as randomBytes2, timingSafeEqual as timingSafeEqual2 } from "node:crypto";
23534
23594
  import { chmodSync as chmodSync4, existsSync as existsSync16, lstatSync as lstatSync6, mkdirSync as mkdirSync11, readFileSync as readFileSync14, renameSync as renameSync5, rmSync as rmSync5, writeFileSync as writeFileSync11 } from "node:fs";
23535
23595
  import { dirname as dirname11, isAbsolute as isAbsolute6, join as join15 } from "node:path";
23536
23596
 
@@ -23539,25 +23599,6 @@ import { createHash as createHash10, randomUUID as randomUUID4 } from "node:cryp
23539
23599
  import { existsSync as existsSync10, lstatSync as lstatSync4, mkdirSync as mkdirSync6, readFileSync as readFileSync8, realpathSync as realpathSync3, writeFileSync as writeFileSync6 } from "node:fs";
23540
23600
  import { dirname as dirname7, join as join9, resolve as resolve7 } from "node:path";
23541
23601
 
23542
- // plugins/kxm/src/bindings.ts
23543
- import { spawnSync as spawnSync3 } from "node:child_process";
23544
- import { createHash as createHash7, randomUUID as randomUUID3 } from "node:crypto";
23545
- import {
23546
- chmodSync as chmodSync2,
23547
- closeSync,
23548
- existsSync as existsSync8,
23549
- fsyncSync,
23550
- lstatSync as lstatSync2,
23551
- mkdirSync as mkdirSync4,
23552
- openSync,
23553
- readFileSync as readFileSync6,
23554
- realpathSync as realpathSync2,
23555
- renameSync as renameSync2,
23556
- rmSync as rmSync2,
23557
- writeFileSync as writeFileSync4
23558
- } from "node:fs";
23559
- import { homedir as homedir3 } from "node:os";
23560
-
23561
23602
  // plugins/kxm/src/sqlite.ts
23562
23603
  import { existsSync as existsSync7 } from "node:fs";
23563
23604
  import { createRequire } from "node:module";
@@ -23607,6 +23648,23 @@ function openReadOnlyDatabase(path4) {
23607
23648
  }
23608
23649
 
23609
23650
  // plugins/kxm/src/bindings.ts
23651
+ import { spawnSync as spawnSync3 } from "node:child_process";
23652
+ import { createHash as createHash7, randomUUID as randomUUID3 } from "node:crypto";
23653
+ import {
23654
+ chmodSync as chmodSync2,
23655
+ closeSync,
23656
+ existsSync as existsSync8,
23657
+ fsyncSync,
23658
+ lstatSync as lstatSync2,
23659
+ mkdirSync as mkdirSync4,
23660
+ openSync,
23661
+ readFileSync as readFileSync6,
23662
+ realpathSync as realpathSync2,
23663
+ renameSync as renameSync2,
23664
+ rmSync as rmSync2,
23665
+ writeFileSync as writeFileSync4
23666
+ } from "node:fs";
23667
+ import { homedir as homedir3 } from "node:os";
23610
23668
  import { dirname as dirname5, isAbsolute as isAbsolute3, join as join7, parse, relative as relative2, resolve as resolve5, sep as sep2 } from "node:path";
23611
23669
  var MAX_BINDING_RECORD_BYTES = 256 * 1024;
23612
23670
  var BINDING_LABEL = "Runtime-local repository bindings";
@@ -24409,6 +24467,20 @@ function projectRuntimeKey(projectRoot) {
24409
24467
  const folded = process.platform === "win32" ? canonical2.toLocaleLowerCase("en-US") : canonical2;
24410
24468
  return createHash10("sha256").update(folded, "utf8").digest("hex").slice(0, 24);
24411
24469
  }
24470
+ function kxmProjectRunEventsPath(projectRoot, env) {
24471
+ return join9(kxmRuntimePaths({ env }).projectsDir, projectRuntimeKey(projectRoot), "run-events.db");
24472
+ }
24473
+ function projectRuntimeOwnsRun(projectRoot, runId, env) {
24474
+ const path4 = kxmProjectRunEventsPath(projectRoot, env);
24475
+ if (!existsSync10(path4)) return false;
24476
+ const database = new DatabaseSync(path4, { readOnly: true });
24477
+ try {
24478
+ database.exec("PRAGMA busy_timeout = 5000");
24479
+ return database.prepare("SELECT 1 FROM runs WHERE run_id = ?").get(runId) !== void 0;
24480
+ } finally {
24481
+ database.close();
24482
+ }
24483
+ }
24412
24484
  var KXM_REGISTRY_SCHEMA_VERSION = 1;
24413
24485
  var REGISTRY_TABLES = {
24414
24486
  supervisor: ["singleton_id", "runtime_id", "pid", "port", "token_hash", "started_at", "heartbeat_at", "state"],
@@ -24875,7 +24947,7 @@ function compileStep(step, index, stepIndex, requirePlanHash, sink) {
24875
24947
  const maxAttempts = compileCountField(step.maxAttempts, 1, `${id}.maxAttempts`, id, sink);
24876
24948
  const timeoutMs = compileOptionalDuration(step.timeoutMs, `${id}.timeoutMs`, id, sink);
24877
24949
  const assignments = compileAssignments(step, id, agent, sink);
24878
- const join50 = compileJoin(step, id, sink);
24950
+ const join49 = compileJoin(step, id, sink);
24879
24951
  const requiredEvidence = compileEvidence(step, id, sink);
24880
24952
  const transitions = compileTransitions(step, id, index, stepIndex, sink);
24881
24953
  const outcomes = Object.keys(transitions).sort(compareCodeUnits4);
@@ -24903,7 +24975,7 @@ function compileStep(step, index, stepIndex, requirePlanHash, sink) {
24903
24975
  transitions: orderedTransitions,
24904
24976
  requiresPlanHash: requirePlanHash.includes(id),
24905
24977
  assignments,
24906
- join: join50
24978
+ join: join49
24907
24979
  };
24908
24980
  if (kind === "agent" || kind === "moa") {
24909
24981
  if (!agent) return void 0;
@@ -24953,15 +25025,15 @@ function compileAssignments(step, stepId, primaryAgentId, sink) {
24953
25025
  };
24954
25026
  }
24955
25027
  function compileJoin(step, stepId, sink) {
24956
- const join50 = objectValue2(step.join);
24957
- if (!join50) return { strategy: "all" };
24958
- const declared = stringValue2(join50.strategy);
25028
+ const join49 = objectValue2(step.join);
25029
+ if (!join49) return { strategy: "all" };
25030
+ const declared = stringValue2(join49.strategy);
24959
25031
  const strategy = declared && JOIN_STRATEGIES.has(declared) ? declared : "all";
24960
- const minimumPassed = compileOptionalCount(join50.minimumPassed, `${stepId}.join.minimumPassed`, stepId, sink);
25032
+ const minimumPassed = compileOptionalCount(join49.minimumPassed, `${stepId}.join.minimumPassed`, stepId, sink);
24961
25033
  const compiled = {
24962
25034
  strategy,
24963
25035
  ...minimumPassed !== void 0 ? { minimumPassed } : {},
24964
- ...typeof join50.cancelRemaining === "boolean" ? { cancelRemaining: join50.cancelRemaining } : {}
25036
+ ...typeof join49.cancelRemaining === "boolean" ? { cancelRemaining: join49.cancelRemaining } : {}
24965
25037
  };
24966
25038
  return compiled;
24967
25039
  }
@@ -27026,7 +27098,7 @@ async function probeSupervisor(port, expectedRuntimeId, token, timeoutMs = 750)
27026
27098
  }
27027
27099
  }
27028
27100
  function hashKxmTokenProof(token, nonce) {
27029
- return createHmac("sha256", token).update(`kxm-runtime-token-proof\0${nonce}`, "utf8").digest("hex");
27101
+ return createHmac2("sha256", token).update(`kxm-runtime-token-proof\0${nonce}`, "utf8").digest("hex");
27030
27102
  }
27031
27103
  async function attachKxmSupervisor(options = {}) {
27032
27104
  const paths = kxmRuntimePaths(options.stateRoot !== void 0 ? { stateRoot: options.stateRoot } : { ...options.env ? { env: options.env } : {} });
@@ -28639,7 +28711,7 @@ async function cmdRoleResume(runtime, runId, ruling) {
28639
28711
  }
28640
28712
 
28641
28713
  // plugins/kxm/src/cli/workflows.ts
28642
- import { createHmac as createHmac3, randomUUID as randomUUID8 } from "node:crypto";
28714
+ import { randomUUID as randomUUID8 } from "node:crypto";
28643
28715
  import { existsSync as existsSync21, readFileSync as readFileSync20 } from "node:fs";
28644
28716
  import { join as join22, resolve as resolve17 } from "node:path";
28645
28717
 
@@ -28880,7 +28952,7 @@ function writeRetrospective(outDir, doc) {
28880
28952
  }
28881
28953
 
28882
28954
  // plugins/kxm/src/github-watch.ts
28883
- import { createHmac as createHmac2, randomUUID as randomUUID7 } from "node:crypto";
28955
+ import { randomUUID as randomUUID7 } from "node:crypto";
28884
28956
  async function fetchWithTimeout(fetchImpl, input, init, timeoutMs) {
28885
28957
  const controller = new AbortController();
28886
28958
  const timer = setTimeout(() => controller.abort(), Math.max(1, timeoutMs));
@@ -28928,7 +29000,6 @@ function mapCheckConclusion(runs, required = []) {
28928
29000
  }
28929
29001
  async function postWorkflowSignal(input) {
28930
29002
  const body = JSON.stringify({ status: input.status, summary: input.summary, evidence: input.evidence });
28931
- const signature = `sha256=${createHmac2("sha256", input.signalSecret).update(body).digest("hex")}`;
28932
29003
  const endpoint = [
28933
29004
  input.serverUrl.replace(/\/$/, ""),
28934
29005
  "v1/webhooks",
@@ -28942,8 +29013,12 @@ async function postWorkflowSignal(input) {
28942
29013
  method: "POST",
28943
29014
  headers: {
28944
29015
  "content-type": "application/json",
28945
- "x-hub-signature-256": signature,
28946
- "x-kxm-delivery-id": input.deliveryId
29016
+ ...workflowWebhookHeaders({
29017
+ secret: input.signalSecret,
29018
+ scope: { definitionId: input.definitionId, runId: input.runId, signalKey: input.signalKey },
29019
+ deliveryId: input.deliveryId,
29020
+ body
29021
+ })
28947
29022
  },
28948
29023
  body
28949
29024
  }, input.timeoutMs ?? 15e3);
@@ -29373,13 +29448,16 @@ function localWorkflowSnapshot(dataPath, runId) {
29373
29448
  async function postWorkflowStart(input) {
29374
29449
  const payload = input.event && input.payload["event"] === void 0 ? { ...input.payload, event: input.event } : input.payload;
29375
29450
  const body = JSON.stringify(payload);
29376
- const signature = `sha256=${createHmac3("sha256", input.secret).update(body).digest("hex")}`;
29377
29451
  const response = await input.fetchImpl(`${input.serverUrl.replace(/\/$/, "")}/v1/webhooks/${encodeURIComponent(input.definitionId)}`, {
29378
29452
  method: "POST",
29379
29453
  headers: {
29380
29454
  "content-type": "application/json",
29381
- "x-hub-signature-256": signature,
29382
- "x-kxm-delivery-id": input.deliveryId,
29455
+ ...workflowWebhookHeaders({
29456
+ secret: input.secret,
29457
+ scope: { definitionId: input.definitionId },
29458
+ deliveryId: input.deliveryId,
29459
+ body
29460
+ }),
29383
29461
  ...input.event ? { "x-github-event": input.event } : {}
29384
29462
  },
29385
29463
  body
@@ -29393,7 +29471,7 @@ async function postWorkflowStart(input) {
29393
29471
  if (!response.ok) throw new Error(`workflow_start_http_${response.status}`);
29394
29472
  return {
29395
29473
  status: response.status,
29396
- ...parsed.run?.id ? { runId: parsed.run.id } : {},
29474
+ ...parsed.runId ? { runId: parsed.runId } : {},
29397
29475
  duplicate: parsed.duplicate === true
29398
29476
  };
29399
29477
  }
@@ -29798,7 +29876,7 @@ async function cmdSignal(runtime, runId, signalKey, status, summary, evidenceArg
29798
29876
  }
29799
29877
  const worker = gateOf(runtime, "signal");
29800
29878
  const projectRoot = discoverKxmProjectRoot(runtime.cwd);
29801
- if (projectRoot && /^run_[a-f0-9]{32}$/i.test(runId)) {
29879
+ if (projectRoot && projectRuntimeOwnsRun(projectRoot, runId, runtime.env)) {
29802
29880
  if (runtime.dryRun) {
29803
29881
  printWorker(runtime, worker, { ok: true, command: "signal", dryRun: true, runId, signalKey, status, summary, evidence }, "would post signal to KXM run");
29804
29882
  return 0;
@@ -45756,7 +45834,7 @@ import { spawnSync as spawnSync8 } from "node:child_process";
45756
45834
  import { createHash as createHash16 } from "node:crypto";
45757
45835
  import { existsSync as existsSync40, mkdtempSync as mkdtempSync3, readFileSync as readFileSync37, rmSync as rmSync13 } from "node:fs";
45758
45836
  import { tmpdir as tmpdir2 } from "node:os";
45759
- import { join as join48, resolve as resolve31 } from "node:path";
45837
+ import { join as join47, resolve as resolve31 } from "node:path";
45760
45838
  import { createInterface as createInterface3 } from "node:readline";
45761
45839
 
45762
45840
  // plugins/kxm/src/improve.ts
@@ -46167,12 +46245,9 @@ function evaluatePromotionPolicy(candidate, policy = "manual_pr", options = {})
46167
46245
 
46168
46246
  // plugins/kxm/src/improve-sources.ts
46169
46247
  import { existsSync as existsSync35 } from "node:fs";
46170
- import { join as join43, resolve as resolve28 } from "node:path";
46248
+ import { resolve as resolve28 } from "node:path";
46171
46249
  var ENGINE_EVENTS_SQL = "SELECT run_id, sequence, event_type, payload FROM events WHERE event_type IN ('routing.attempt.recorded','step.entered','run.status_changed') ORDER BY run_id, sequence";
46172
46250
  var SIMULATED_HARNESS = "driver-simulated";
46173
- function kxmProjectRunEventsPath(projectRoot, env) {
46174
- return join43(kxmRuntimePaths({ env }).projectsDir, projectRuntimeKey(projectRoot), "run-events.db");
46175
- }
46176
46251
  function parsePayload(text) {
46177
46252
  try {
46178
46253
  const value = JSON.parse(text);
@@ -46313,7 +46388,7 @@ function loadRoutingSources(options) {
46313
46388
  // plugins/kxm/src/modes.ts
46314
46389
  var import_yaml16 = __toESM(require_dist(), 1);
46315
46390
  import { existsSync as existsSync36, readFileSync as readFileSync34 } from "node:fs";
46316
- import { join as join44 } from "node:path";
46391
+ import { join as join43 } from "node:path";
46317
46392
  var DEFAULT_MODES_CONFIG = Object.freeze({
46318
46393
  schema: "kxm.modes.v1",
46319
46394
  majorModes: {
@@ -46376,7 +46451,7 @@ function loadModesConfig(projectRoot) {
46376
46451
  if (!projectRoot) {
46377
46452
  return DEFAULT_MODES_CONFIG;
46378
46453
  }
46379
- const modesPath = join44(projectRoot, ".kxm", "modes.yaml");
46454
+ const modesPath = join43(projectRoot, ".kxm", "modes.yaml");
46380
46455
  if (!existsSync36(modesPath)) {
46381
46456
  return DEFAULT_MODES_CONFIG;
46382
46457
  }
@@ -46467,7 +46542,7 @@ function calculatePromptFootprint(resolved, projectRoot = process.cwd(), catalog
46467
46542
  estimatedTokens: estimateTokens(baseSystemPromptChars)
46468
46543
  });
46469
46544
  for (const relPath of resolved.contextFiles) {
46470
- const fullPath = join44(projectRoot, relPath);
46545
+ const fullPath = join43(projectRoot, relPath);
46471
46546
  let chars = 0;
46472
46547
  if (existsSync36(fullPath)) {
46473
46548
  try {
@@ -46605,7 +46680,7 @@ ${divider}
46605
46680
  import { spawnSync as spawnSync7 } from "node:child_process";
46606
46681
  import { existsSync as existsSync37, mkdirSync as mkdirSync28, readFileSync as readFileSync35, readdirSync as readdirSync13, rmSync as rmSync12, statSync as statSync5 } from "node:fs";
46607
46682
  import { homedir as homedir8 } from "node:os";
46608
- import { join as join45, resolve as resolve29 } from "node:path";
46683
+ import { join as join44, resolve as resolve29 } from "node:path";
46609
46684
  var MAX_SSH_OUTPUT_BYTES = 50 * 1024;
46610
46685
  var MAX_SSH_OUTPUT_LINES = 2e3;
46611
46686
  var DEFAULT_SOCKET_DIR = ".kxm/run/ssh-sockets";
@@ -46631,7 +46706,7 @@ function truncateSshOutput(raw) {
46631
46706
  return { text, truncated };
46632
46707
  }
46633
46708
  function parseSshConfig(configPath) {
46634
- const targetPath = configPath ?? join45(homedir8(), ".ssh", "config");
46709
+ const targetPath = configPath ?? join44(homedir8(), ".ssh", "config");
46635
46710
  if (!existsSync37(targetPath)) {
46636
46711
  return [];
46637
46712
  }
@@ -46721,7 +46796,7 @@ function ensureSocketDir(socketDir = DEFAULT_SOCKET_DIR) {
46721
46796
  }
46722
46797
  function buildSshArgs(options) {
46723
46798
  const socketDir = ensureSocketDir(options.socketDir ?? DEFAULT_SOCKET_DIR);
46724
- const controlPath = join45(socketDir, "%C");
46799
+ const controlPath = join44(socketDir, "%C");
46725
46800
  const persist = options.controlPersist ?? DEFAULT_CONTROL_PERSIST;
46726
46801
  const args = [
46727
46802
  "-o",
@@ -46746,7 +46821,7 @@ function buildSshArgs(options) {
46746
46821
  }
46747
46822
  function checkControlSocket(host, socketDir = DEFAULT_SOCKET_DIR, execFn = spawnSync7) {
46748
46823
  const resolvedDir = ensureSocketDir(socketDir);
46749
- const controlPath = join45(resolvedDir, "%C");
46824
+ const controlPath = join44(resolvedDir, "%C");
46750
46825
  try {
46751
46826
  const result = execFn("ssh", ["-O", "check", "-o", `ControlPath=${controlPath}`, host], {
46752
46827
  encoding: "utf-8"
@@ -46758,7 +46833,7 @@ function checkControlSocket(host, socketDir = DEFAULT_SOCKET_DIR, execFn = spawn
46758
46833
  }
46759
46834
  function closeControlSocket(host, socketDir = DEFAULT_SOCKET_DIR, execFn = spawnSync7) {
46760
46835
  const resolvedDir = ensureSocketDir(socketDir);
46761
- const controlPath = join45(resolvedDir, "%C");
46836
+ const controlPath = join44(resolvedDir, "%C");
46762
46837
  try {
46763
46838
  const result = execFn("ssh", ["-O", "stop", "-o", `ControlPath=${controlPath}`, host], {
46764
46839
  encoding: "utf-8"
@@ -47205,7 +47280,7 @@ complete -c kxm -n "__fish_seen_subcommand_from goal" -a "create list get"
47205
47280
  // plugins/kxm/src/completion-install.ts
47206
47281
  import { existsSync as existsSync38, mkdirSync as mkdirSync29, readFileSync as readFileSync36, writeFileSync as writeFileSync25 } from "node:fs";
47207
47282
  import { homedir as homedir9 } from "node:os";
47208
- import { basename as basename9, delimiter, dirname as dirname23, isAbsolute as isAbsolute9, join as join46, resolve as resolve30 } from "node:path";
47283
+ import { basename as basename9, delimiter, dirname as dirname23, isAbsolute as isAbsolute9, join as join45, resolve as resolve30 } from "node:path";
47209
47284
  var COMPLETION_MARKER = "# kxm completion";
47210
47285
  var PATH_MARKER = "# kxm path";
47211
47286
  function detectShell(env = process.env, platform = process.platform) {
@@ -47227,25 +47302,25 @@ function completionScriptPath(shell, options = {}) {
47227
47302
  const env = options.env ?? process.env;
47228
47303
  const explicit = options.configDir ?? env.KXM_USER_CONFIG_DIR?.trim();
47229
47304
  const base = explicit && explicit.length > 0 ? resolve30(explicit) : resolve30(effectiveHome(options), ".config", "kxm");
47230
- return join46(base, "completions", `kxm.${shell}`);
47305
+ return join45(base, "completions", `kxm.${shell}`);
47231
47306
  }
47232
47307
  function bashRcCandidate(options) {
47233
47308
  const home = effectiveHome(options);
47234
- const candidates = [join46(home, ".bashrc"), join46(home, ".bash_profile")];
47309
+ const candidates = [join45(home, ".bashrc"), join45(home, ".bash_profile")];
47235
47310
  const existing = candidates.find((candidate) => existsSync38(candidate));
47236
47311
  return existing ?? candidates[0];
47237
47312
  }
47238
47313
  function zshRcCandidate(options) {
47239
47314
  const env = options.env ?? process.env;
47240
47315
  const home = effectiveHome(options);
47241
- if (env.ZDOTDIR?.trim()) return join46(resolve30(env.ZDOTDIR.trim()), ".zshrc");
47242
- return join46(home, ".zshrc");
47316
+ if (env.ZDOTDIR?.trim()) return join45(resolve30(env.ZDOTDIR.trim()), ".zshrc");
47317
+ return join45(home, ".zshrc");
47243
47318
  }
47244
47319
  function fishCompletionTarget(shell, options) {
47245
47320
  const env = options.env ?? process.env;
47246
47321
  const home = effectiveHome(options);
47247
- if (env.XDG_CONFIG_HOME?.trim()) return join46(resolve30(env.XDG_CONFIG_HOME.trim()), "fish", "completions", "kxm.fish");
47248
- return join46(home, ".config", "fish", "completions", "kxm.fish");
47322
+ if (env.XDG_CONFIG_HOME?.trim()) return join45(resolve30(env.XDG_CONFIG_HOME.trim()), "fish", "completions", "kxm.fish");
47323
+ return join45(home, ".config", "fish", "completions", "kxm.fish");
47249
47324
  }
47250
47325
  function completionRcTarget(shell, options = {}) {
47251
47326
  if (shell === "fish") {
@@ -47333,7 +47408,7 @@ function kxmBinDir(env = process.env) {
47333
47408
  const argv1 = env.KXM_ENTRY ?? process.argv[1];
47334
47409
  if (argv1 && isAbsolute9(argv1)) {
47335
47410
  const dir = dirname23(argv1);
47336
- if (existsSync38(join46(dir, process.platform === "win32" ? "kxm.cmd" : "kxm"))) return dir;
47411
+ if (existsSync38(join45(dir, process.platform === "win32" ? "kxm.cmd" : "kxm"))) return dir;
47337
47412
  }
47338
47413
  const pathEnv = env.PATH ?? "";
47339
47414
  for (const part of pathEnv.split(delimiter)) {
@@ -47372,7 +47447,7 @@ ${line}
47372
47447
  // plugins/kxm/src/init-guide-setup.ts
47373
47448
  var import_yaml17 = __toESM(require_dist(), 1);
47374
47449
  import { existsSync as existsSync39, mkdirSync as mkdirSync30, writeFileSync as writeFileSync26 } from "node:fs";
47375
- import { dirname as dirname24, join as join47 } from "node:path";
47450
+ import { dirname as dirname24, join as join46 } from "node:path";
47376
47451
  var NATIVE_VENDOR_HARNESS = Object.freeze({
47377
47452
  anthropic: "claude",
47378
47453
  openai: "codex",
@@ -47771,13 +47846,13 @@ function renderGuideSetupFiles(projectRoot, plan) {
47771
47846
  const stage = roleStages.get(role);
47772
47847
  if (!stage) continue;
47773
47848
  files.push({
47774
- path: join47(projectRoot, ".kxm", "agents", `${role}.yaml`),
47849
+ path: join46(projectRoot, ".kxm", "agents", `${role}.yaml`),
47775
47850
  content: (0, import_yaml17.stringify)(agentDocument(role, stage, binding))
47776
47851
  });
47777
47852
  }
47778
47853
  for (const workflow of plan.workflows) {
47779
47854
  files.push({
47780
- path: join47(projectRoot, ".kxm", "workflows", `${workflow.slug}.yaml`),
47855
+ path: join46(projectRoot, ".kxm", "workflows", `${workflow.slug}.yaml`),
47781
47856
  content: (0, import_yaml17.stringify)(workflowDocument(workflow))
47782
47857
  });
47783
47858
  }
@@ -47855,7 +47930,7 @@ function applyKxmPackageUpdate(runtime, notice) {
47855
47930
  detail: `release v${notice.latest} has no sha256 digest for ${name}; refusing to install`
47856
47931
  };
47857
47932
  }
47858
- const releaseDir = mkdtempSync3(join48(tmpdir2(), "kxm-pkg-update-"));
47933
+ const releaseDir = mkdtempSync3(join47(tmpdir2(), "kxm-pkg-update-"));
47859
47934
  try {
47860
47935
  const planned = planKxmPackageUpdate(notice.source, notice.latest, releaseDir, notice.asset);
47861
47936
  if (runtime.dryRun) {
@@ -48228,7 +48303,7 @@ async function cmdImprove(runtime, options = {}) {
48228
48303
  print(runtime.io, runtime.json, { ok: false, command: "improve", error: "config_invalid", detail: message }, `config_invalid: ${message}`);
48229
48304
  return 1;
48230
48305
  }
48231
- const candidatesDir = options.outDir ? resolve31(runtime.cwd, options.outDir) : join48(root, ".kxm", "candidates");
48306
+ const candidatesDir = options.outDir ? resolve31(runtime.cwd, options.outDir) : join47(root, ".kxm", "candidates");
48232
48307
  const report2 = buildImprovementReport(loaded.records, {
48233
48308
  candidatesDir,
48234
48309
  projectRoot: root,
@@ -48237,7 +48312,7 @@ async function cmdImprove(runtime, options = {}) {
48237
48312
  autoThreshold: config.improvement.autoThreshold,
48238
48313
  halfLifeDays: config.improvement.telemetryHalfLifeDays
48239
48314
  });
48240
- const reportDir = join48(runtime.dirs.assets, "improvements");
48315
+ const reportDir = join47(runtime.dirs.assets, "improvements");
48241
48316
  const reportPath = writeImprovementReport(reportDir, report2, runtime.dryRun);
48242
48317
  const text = [
48243
48318
  "Sources:",
@@ -48526,7 +48601,7 @@ async function cmdRoutingReport(runtime, options) {
48526
48601
  let catalog;
48527
48602
  if (includeEquivalentListCost) {
48528
48603
  try {
48529
- const pricesPath = options.prices ? resolve31(runtime.cwd, options.prices) : join48(runtime.dirs.workspace, "prices.yaml");
48604
+ const pricesPath = options.prices ? resolve31(runtime.cwd, options.prices) : join47(runtime.dirs.workspace, "prices.yaml");
48530
48605
  catalog = loadPriceCatalog(pricesPath);
48531
48606
  } catch {
48532
48607
  }
@@ -48754,14 +48829,15 @@ async function ensureCliClient(runtime) {
48754
48829
  const project = defaultProjectName(runtime.cwd, runtime.env);
48755
48830
  const name = runtime.env.KXM_AGENT_NAME?.trim() || `cli-${process.pid}`;
48756
48831
  const purpose = runtime.env.KXM_AGENT_PURPOSE?.trim() || "CLI agent client";
48757
- const authToken = resolveClientHubAuthToken(runtime.env, project);
48832
+ const authToken = resolveAgentHubAuthToken(runtime.env, project);
48833
+ if (!authToken) throw new AgentProjectTokenMissingError(project);
48758
48834
  const client = new HubClient({
48759
48835
  serverUrl,
48760
48836
  name,
48761
48837
  project,
48762
48838
  purpose,
48763
48839
  fetchImpl: runtime.fetchImpl,
48764
- ...authToken ? { authToken } : {}
48840
+ authToken
48765
48841
  });
48766
48842
  await client.start(() => {
48767
48843
  });
@@ -48822,7 +48898,7 @@ async function dispatchAgentCliCommand(runtime, toolName, rawArgs) {
48822
48898
  if (toolName === "kxm_workflow_wait") {
48823
48899
  const runId = typeof args.runId === "string" ? args.runId : void 0;
48824
48900
  const projectRoot = discoverKxmProjectRoot(runtime.cwd);
48825
- if (runId && projectRoot && /^run_[a-f0-9]{32}$/i.test(runId)) {
48901
+ if (runId && projectRoot && projectRuntimeOwnsRun(projectRoot, runId, runtime.env)) {
48826
48902
  if (runtime.dryRun) {
48827
48903
  print(runtime.io, runtime.json, { ok: true, command: "workflow wait", runId, dryRun: true }, `would wait for signal on KXM run ${runId}`);
48828
48904
  return 0;
@@ -48857,6 +48933,15 @@ async function dispatchAgentCliCommand(runtime, toolName, rawArgs) {
48857
48933
  return 0;
48858
48934
  } catch (error) {
48859
48935
  const message = error instanceof Error ? error.message : String(error);
48936
+ if (error instanceof AgentProjectTokenMissingError) {
48937
+ print(
48938
+ runtime.io,
48939
+ runtime.json,
48940
+ { ok: false, error: error.code, project: error.project, nextAction: "export_kxm_auth_token", detail: message },
48941
+ message
48942
+ );
48943
+ return 2;
48944
+ }
48860
48945
  print(runtime.io, runtime.json, { ok: false, error: "command_failed", detail: message }, `command failed: ${message}`);
48861
48946
  return 1;
48862
48947
  } finally {
@@ -219,7 +219,9 @@ var HubClient = class {
219
219
  options.workflowContext
220
220
  )
221
221
  } : {},
222
- ...options.ttlMs ? { ttlMs: options.ttlMs } : {}
222
+ ...options.ttlMs ? { ttlMs: options.ttlMs } : {},
223
+ ...options.hops !== void 0 ? { hops: options.hops } : {},
224
+ ...options.maxHops !== void 0 ? { maxHops: options.maxHops } : {}
223
225
  });
224
226
  const completed = await this.awaitResponse(
225
227
  message.id,
@@ -981,6 +981,13 @@ var HubHttpError = class extends Error {
981
981
  };
982
982
 
983
983
  // plugins/kxm/src/commands.ts
984
+ function forwardedHops(handling) {
985
+ if (!handling?.length) return void 0;
986
+ return {
987
+ hops: Math.max(...handling.map((message) => message.hops)) + 1,
988
+ maxHops: Math.min(...handling.map((message) => message.maxHops))
989
+ };
990
+ }
984
991
  function requiredString(value, name) {
985
992
  if (typeof value !== "string" || value.trim() === "") throw new Error(`${name} is required`);
986
993
  return value.trim();
@@ -1110,12 +1117,13 @@ var AGENT_COMMANDS = [
1110
1117
  required: ["target", "content"],
1111
1118
  additionalProperties: false
1112
1119
  },
1113
- async execute(client, args) {
1120
+ async execute(client, args, context) {
1114
1121
  const delivery = optionalString2(args.delivery);
1115
1122
  const correlationId = optionalString2(args.correlationId);
1116
1123
  const idempotencyKey = optionalString2(args.idempotencyKey);
1117
1124
  const workflowContext = optionalWorkflowContext(args.workflowContext);
1118
1125
  const message = await client.send({
1126
+ ...forwardedHops(context?.handling),
1119
1127
  target: requiredString(args.target, "target"),
1120
1128
  content: requiredString(args.content, "content"),
1121
1129
  ...delivery ? { delivery } : {},
@@ -1193,6 +1201,7 @@ var AGENT_COMMANDS = [
1193
1201
  const targets = Array.isArray(args.targets) ? args.targets.map((t) => requiredString(t, "target")) : [];
1194
1202
  return {
1195
1203
  responses: await client.fanout({
1204
+ ...forwardedHops(context?.handling),
1196
1205
  targets,
1197
1206
  content: requiredString(args.content, "content"),
1198
1207
  ...optionalString2(args.correlationId) ? { correlationId: optionalString2(args.correlationId) } : {},
@@ -2156,6 +2165,7 @@ export {
2156
2165
  enforceToolPolicy,
2157
2166
  evaluateCircuitBreaker,
2158
2167
  formatRoutingReport,
2168
+ forwardedHops,
2159
2169
  gateWorker,
2160
2170
  generateRoutingReport,
2161
2171
  getCliAgentCommands,