@kontextmind/kxm 0.7.96 → 0.7.98

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 (68) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.kxm/workflows/default.yaml +2 -0
  3. package/CHANGELOG.md +61 -2
  4. package/docs/concepts/architecture.md +1 -1
  5. package/docs/concepts/data-and-storage.md +1 -1
  6. package/docs/concepts/trust-model.md +3 -2
  7. package/docs/contracts/routing.md +1 -1
  8. package/docs/contributing/test-matrix.md +6 -5
  9. package/docs/glossary.md +1 -1
  10. package/docs/guides/peer-messaging.md +2 -2
  11. package/docs/guides/pi-workers.md +1 -1
  12. package/docs/guides/webhook-workflows.md +53 -18
  13. package/docs/operations/backup-and-restore.md +43 -24
  14. package/docs/operations/deploy.md +2 -2
  15. package/docs/operations/troubleshooting.md +3 -2
  16. package/docs/reference/cli-reference.md +65 -30
  17. package/docs/reference/config-reference.md +24 -13
  18. package/docs/reference/configuration.md +2 -2
  19. package/docs/reference/harness-routing.md +3 -3
  20. package/docs/reference/http-api.md +7 -7
  21. package/docs/reference/tools.md +1 -1
  22. package/docs/reference/workflow-definitions.md +2 -2
  23. package/docs/start/first-workflow.md +4 -4
  24. package/docs/start/quickstart-claude-code.md +2 -2
  25. package/docs/start/quickstart-pi.md +1 -1
  26. package/examples/workflow-signal.ts +4 -5
  27. package/package.json +1 -1
  28. package/plugins/kxm/.claude-plugin/plugin.json +1 -1
  29. package/plugins/kxm/dist/claude-hook.js +11 -1
  30. package/plugins/kxm/dist/cli.js +552 -228
  31. package/plugins/kxm/dist/client.js +3 -1
  32. package/plugins/kxm/dist/core.js +16 -3
  33. package/plugins/kxm/dist/extension.js +45 -13
  34. package/plugins/kxm/dist/mcp-server.js +20 -4
  35. package/plugins/kxm/dist/runtime-supervisor.js +232 -51
  36. package/plugins/kxm/dist/runtime.js +432 -95
  37. package/plugins/kxm/dist/server.js +122 -20
  38. package/plugins/kxm/package.json +1 -1
  39. package/plugins/kxm/skills/kxm-hub-ops/SKILL.md +4 -3
  40. package/plugins/kxm/skills/kxm-project-setup/SKILL.md +3 -2
  41. package/plugins/kxm/skills/kxm-routing-improve/SKILL.md +3 -0
  42. package/plugins/kxm/skills/kxm-runs/SKILL.md +8 -7
  43. package/plugins/kxm/src/cli/project.ts +22 -13
  44. package/plugins/kxm/src/cli/system.ts +22 -1
  45. package/plugins/kxm/src/cli/workflows.ts +12 -7
  46. package/plugins/kxm/src/cli.ts +30 -8
  47. package/plugins/kxm/src/client.ts +4 -0
  48. package/plugins/kxm/src/commands.ts +23 -1
  49. package/plugins/kxm/src/database.ts +210 -36
  50. package/plugins/kxm/src/engine.ts +117 -2
  51. package/plugins/kxm/src/extension.ts +20 -14
  52. package/plugins/kxm/src/github-watch.ts +8 -5
  53. package/plugins/kxm/src/harness.ts +29 -0
  54. package/plugins/kxm/src/hub-env.ts +19 -1
  55. package/plugins/kxm/src/hub.ts +105 -21
  56. package/plugins/kxm/src/improve-sources.ts +2 -7
  57. package/plugins/kxm/src/init-guide-setup.ts +43 -28
  58. package/plugins/kxm/src/mcp-server.ts +9 -2
  59. package/plugins/kxm/src/oneshot-producer.ts +16 -8
  60. package/plugins/kxm/src/prices.ts +33 -2
  61. package/plugins/kxm/src/routing.ts +13 -7
  62. package/plugins/kxm/src/runtime-store.ts +23 -0
  63. package/plugins/kxm/src/studio-layout.ts +5 -4
  64. package/plugins/kxm/src/template.ts +31 -0
  65. package/plugins/kxm/src/workflow.ts +70 -1
  66. package/plugins/kxm/src/worktree-witness.ts +71 -0
  67. package/schemas/backup-manifest.schema.json +33 -0
  68. package/scripts/smoke-multi-pi.mjs +5 -1
@@ -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,
@@ -461,6 +461,8 @@ function compareRoutingRecords(records) {
461
461
  const blocked = settled.filter((record) => record.finalOutcome === "blocked").length;
462
462
  const failed = settled.filter((record) => record.finalOutcome === "failed").length;
463
463
  const reworked = records.filter((record) => record.retries > 0 || record.transitions > 0).length;
464
+ const missingCostRuns = records.filter((record) => typeof record.costUsd !== "number" || !Number.isFinite(record.costUsd)).length;
465
+ const summedCost = records.reduce((sum, record) => sum + (typeof record.costUsd === "number" && Number.isFinite(record.costUsd) ? record.costUsd : 0), 0);
464
466
  return {
465
467
  behavioralSha256,
466
468
  runs: records.length,
@@ -468,7 +470,8 @@ function compareRoutingRecords(records) {
468
470
  blocked,
469
471
  failed,
470
472
  reworkRate: records.length === 0 ? 0 : Math.round(reworked / records.length * 100) / 100,
471
- totalCostUsd: Math.round(records.reduce((sum, record) => sum + (record.costUsd ?? 0), 0) * 1e4) / 1e4,
473
+ totalCostUsd: missingCostRuns > 0 ? null : Math.round(summedCost * 1e4) / 1e4,
474
+ missingCostRuns,
472
475
  totalTokensIn: records.reduce((sum, record) => sum + (record.tokensIn ?? 0), 0),
473
476
  totalTokensOut: records.reduce((sum, record) => sum + (record.tokensOut ?? 0), 0),
474
477
  totalHumanInterventions: records.reduce((sum, record) => sum + record.humanInterventions, 0)
@@ -652,7 +655,7 @@ function generateRoutingReport(records, options = {}) {
652
655
  medianContextTokens = contextVals.length % 2 !== 0 ? contextVals[mid] : Math.round((contextVals[mid - 1] + contextVals[mid]) / 2);
653
656
  }
654
657
  const meteredCostUsd = Math.round(meteredCostTotal * 1e4) / 1e4;
655
- const costPerAcceptedUsd = acceptedCount > 0 ? meteredCostUsd > 0 || unmeteredAttempts > 0 ? Math.round(meteredCostUsd / acceptedCount * 1e4) / 1e4 : unknownCostAttempts === attempts ? null : 0 : null;
658
+ const costPerAcceptedUsd = acceptedCount > 0 ? unknownCostAttempts > 0 ? null : meteredCostUsd > 0 || unmeteredAttempts > 0 ? Math.round(meteredCostUsd / acceptedCount * 1e4) / 1e4 : 0 : null;
656
659
  const flagged = unknownCostAttempts > 0;
657
660
  let equivalentListCostUsd = void 0;
658
661
  if (options.includeEquivalentListCost && options.catalog) {
@@ -981,6 +984,13 @@ var HubHttpError = class extends Error {
981
984
  };
982
985
 
983
986
  // plugins/kxm/src/commands.ts
987
+ function forwardedHops(handling) {
988
+ if (!handling?.length) return void 0;
989
+ return {
990
+ hops: Math.max(...handling.map((message) => message.hops)) + 1,
991
+ maxHops: Math.min(...handling.map((message) => message.maxHops))
992
+ };
993
+ }
984
994
  function requiredString(value, name) {
985
995
  if (typeof value !== "string" || value.trim() === "") throw new Error(`${name} is required`);
986
996
  return value.trim();
@@ -1110,12 +1120,13 @@ var AGENT_COMMANDS = [
1110
1120
  required: ["target", "content"],
1111
1121
  additionalProperties: false
1112
1122
  },
1113
- async execute(client, args) {
1123
+ async execute(client, args, context) {
1114
1124
  const delivery = optionalString2(args.delivery);
1115
1125
  const correlationId = optionalString2(args.correlationId);
1116
1126
  const idempotencyKey = optionalString2(args.idempotencyKey);
1117
1127
  const workflowContext = optionalWorkflowContext(args.workflowContext);
1118
1128
  const message = await client.send({
1129
+ ...forwardedHops(context?.handling),
1119
1130
  target: requiredString(args.target, "target"),
1120
1131
  content: requiredString(args.content, "content"),
1121
1132
  ...delivery ? { delivery } : {},
@@ -1193,6 +1204,7 @@ var AGENT_COMMANDS = [
1193
1204
  const targets = Array.isArray(args.targets) ? args.targets.map((t) => requiredString(t, "target")) : [];
1194
1205
  return {
1195
1206
  responses: await client.fanout({
1207
+ ...forwardedHops(context?.handling),
1196
1208
  targets,
1197
1209
  content: requiredString(args.content, "content"),
1198
1210
  ...optionalString2(args.correlationId) ? { correlationId: optionalString2(args.correlationId) } : {},
@@ -2156,6 +2168,7 @@ export {
2156
2168
  enforceToolPolicy,
2157
2169
  evaluateCircuitBreaker,
2158
2170
  formatRoutingReport,
2171
+ forwardedHops,
2159
2172
  gateWorker,
2160
2173
  generateRoutingReport,
2161
2174
  getCliAgentCommands,
@@ -14799,7 +14799,9 @@ var HubClient = class {
14799
14799
  options.workflowContext
14800
14800
  )
14801
14801
  } : {},
14802
- ...options.ttlMs ? { ttlMs: options.ttlMs } : {}
14802
+ ...options.ttlMs ? { ttlMs: options.ttlMs } : {},
14803
+ ...options.hops !== void 0 ? { hops: options.hops } : {},
14804
+ ...options.maxHops !== void 0 ? { maxHops: options.maxHops } : {}
14803
14805
  });
14804
14806
  const completed = await this.awaitResponse(
14805
14807
  message.id,
@@ -15106,6 +15108,13 @@ async function hubJsonRequest(options, path, init, headers) {
15106
15108
  }
15107
15109
 
15108
15110
  // plugins/kxm/src/commands.ts
15111
+ function forwardedHops(handling) {
15112
+ if (!handling?.length) return void 0;
15113
+ return {
15114
+ hops: Math.max(...handling.map((message) => message.hops)) + 1,
15115
+ maxHops: Math.min(...handling.map((message) => message.maxHops))
15116
+ };
15117
+ }
15109
15118
  function requiredString(value, name) {
15110
15119
  if (typeof value !== "string" || value.trim() === "") throw new Error(`${name} is required`);
15111
15120
  return value.trim();
@@ -15235,12 +15244,13 @@ var AGENT_COMMANDS = [
15235
15244
  required: ["target", "content"],
15236
15245
  additionalProperties: false
15237
15246
  },
15238
- async execute(client, args) {
15247
+ async execute(client, args, context) {
15239
15248
  const delivery = optionalString(args.delivery);
15240
15249
  const correlationId = optionalString(args.correlationId);
15241
15250
  const idempotencyKey = optionalString(args.idempotencyKey);
15242
15251
  const workflowContext = optionalWorkflowContext(args.workflowContext);
15243
15252
  const message = await client.send({
15253
+ ...forwardedHops(context?.handling),
15244
15254
  target: requiredString(args.target, "target"),
15245
15255
  content: requiredString(args.content, "content"),
15246
15256
  ...delivery ? { delivery } : {},
@@ -15318,6 +15328,7 @@ var AGENT_COMMANDS = [
15318
15328
  const targets = Array.isArray(args.targets) ? args.targets.map((t) => requiredString(t, "target")) : [];
15319
15329
  return {
15320
15330
  responses: await client.fanout({
15331
+ ...forwardedHops(context?.handling),
15321
15332
  targets,
15322
15333
  content: requiredString(args.content, "content"),
15323
15334
  ...optionalString(args.correlationId) ? { correlationId: optionalString(args.correlationId) } : {},
@@ -16370,6 +16381,24 @@ function resolveHubCredentials(options = {}) {
16370
16381
  written: needsPersist === true
16371
16382
  };
16372
16383
  }
16384
+ function resolveAgentHubAuthToken(env, project) {
16385
+ const envToken = env.KXM_AUTH_TOKEN?.trim();
16386
+ if (envToken) return envToken;
16387
+ const tokens = readHubEnvRecord(env)?.projectTokens;
16388
+ if (!tokens || !Object.hasOwn(tokens, project)) return void 0;
16389
+ return tokens[project]?.trim() || void 0;
16390
+ }
16391
+ var AgentProjectTokenMissingError = class extends Error {
16392
+ code = "project_token_missing";
16393
+ project;
16394
+ constructor(project) {
16395
+ super(
16396
+ `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.`
16397
+ );
16398
+ this.name = "AgentProjectTokenMissingError";
16399
+ this.project = project;
16400
+ }
16401
+ };
16373
16402
 
16374
16403
  // plugins/kxm/src/repo-root.ts
16375
16404
  import { existsSync as existsSync5 } from "node:fs";
@@ -38060,13 +38089,11 @@ function piMeshExtension(pi) {
38060
38089
  removeMatchingLegacyRecoveryContext();
38061
38090
  const purpose = process.env.KXM_AGENT_PURPOSE ?? "General-purpose coding agent";
38062
38091
  const model = ctx2.model ? `${ctx2.model.provider}/${ctx2.model.id}` : void 0;
38063
- let autoStartToken;
38064
38092
  try {
38065
38093
  const config2 = loadKxmConfig(ctx2.cwd);
38066
38094
  if (hubAutoStartMode(config2) === "background") {
38067
38095
  const ensured = await ensureHubRunning({ config: config2, cwd: ctx2.cwd });
38068
38096
  if (ensured.status === "started") {
38069
- autoStartToken = ensured.authToken;
38070
38097
  ctx2.ui.notify(
38071
38098
  `kxm hub started in the background (pid ${ensured.pid}); logs: ${ensured.logPath}` + (ensured.authTokenSource === "generated" ? "; new admin token generated and persisted to user state" : ""),
38072
38099
  "info"
@@ -38078,21 +38105,25 @@ function piMeshExtension(pi) {
38078
38105
  } catch (error2) {
38079
38106
  ctx2.ui.notify(`kxm hub auto-start failed: ${error2 instanceof Error ? error2.message : String(error2)}`, "warning");
38080
38107
  }
38081
- const envAuthToken = process.env.KXM_AUTH_TOKEN?.trim();
38082
- let hubAuthToken = envAuthToken || autoStartToken;
38108
+ let hubAuthToken;
38109
+ try {
38110
+ hubAuthToken = resolveAgentHubAuthToken(process.env, project);
38111
+ } catch (error2) {
38112
+ ctx2.ui.notify(`kxm could not read persisted hub credentials: ${error2 instanceof Error ? error2.message : String(error2)}`, "error");
38113
+ await applySessionChrome(ctx2, event, true);
38114
+ return;
38115
+ }
38083
38116
  if (!hubAuthToken) {
38084
- try {
38085
- hubAuthToken = readHubEnvRecord()?.authToken?.trim() || void 0;
38086
- } catch (error2) {
38087
- ctx2.ui.notify(`kxm could not read persisted hub credentials: ${error2 instanceof Error ? error2.message : String(error2)}`, "warning");
38088
- }
38117
+ ctx2.ui.notify(new AgentProjectTokenMissingError(project).message, "error");
38118
+ await applySessionChrome(ctx2, event, true);
38119
+ return;
38089
38120
  }
38090
38121
  client = new HubClient({
38091
38122
  serverUrl,
38092
38123
  name,
38093
38124
  purpose,
38094
38125
  project,
38095
- ...hubAuthToken ? { authToken: hubAuthToken } : {},
38126
+ authToken: hubAuthToken,
38096
38127
  ...model ? { model } : {}
38097
38128
  });
38098
38129
  notify = (message, type) => ctx2.ui.notify(message, type);
@@ -38248,9 +38279,10 @@ function piMeshExtension(pi) {
38248
38279
  if (!policy.allowed) {
38249
38280
  throw new Error(`tool_policy_denied: ${policy.detail ?? policy.error}`);
38250
38281
  }
38282
+ const handling = activeInbound ? [activeInbound] : [];
38251
38283
  return result(
38252
38284
  await workflowCall(
38253
- () => cmd.execute(requireClient(), params ?? {}, { signal })
38285
+ () => cmd.execute(requireClient(), params ?? {}, { signal, handling })
38254
38286
  )
38255
38287
  );
38256
38288
  }
@@ -15999,7 +15999,9 @@ var HubClient = class {
15999
15999
  options.workflowContext
16000
16000
  )
16001
16001
  } : {},
16002
- ...options.ttlMs ? { ttlMs: options.ttlMs } : {}
16002
+ ...options.ttlMs ? { ttlMs: options.ttlMs } : {},
16003
+ ...options.hops !== void 0 ? { hops: options.hops } : {},
16004
+ ...options.maxHops !== void 0 ? { maxHops: options.maxHops } : {}
16003
16005
  });
16004
16006
  const completed = await this.awaitResponse(
16005
16007
  message.id,
@@ -16398,6 +16400,13 @@ function defaultProjectName(cwd, env = process.env) {
16398
16400
  import { chmodSync, existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync3, unlinkSync, writeFileSync as writeFileSync2 } from "node:fs";
16399
16401
  import { homedir as homedir2 } from "node:os";
16400
16402
  import { dirname as dirname2, join as join3, resolve as resolve2 } from "node:path";
16403
+ function forwardedHops(handling) {
16404
+ if (!handling?.length) return void 0;
16405
+ return {
16406
+ hops: Math.max(...handling.map((message) => message.hops)) + 1,
16407
+ maxHops: Math.min(...handling.map((message) => message.maxHops))
16408
+ };
16409
+ }
16401
16410
  function requiredString(value, name) {
16402
16411
  if (typeof value !== "string" || value.trim() === "") throw new Error(`${name} is required`);
16403
16412
  return value.trim();
@@ -16527,12 +16536,13 @@ var AGENT_COMMANDS = [
16527
16536
  required: ["target", "content"],
16528
16537
  additionalProperties: false
16529
16538
  },
16530
- async execute(client, args) {
16539
+ async execute(client, args, context) {
16531
16540
  const delivery = optionalString(args.delivery);
16532
16541
  const correlationId = optionalString(args.correlationId);
16533
16542
  const idempotencyKey = optionalString(args.idempotencyKey);
16534
16543
  const workflowContext = optionalWorkflowContext(args.workflowContext);
16535
16544
  const message = await client.send({
16545
+ ...forwardedHops(context?.handling),
16536
16546
  target: requiredString(args.target, "target"),
16537
16547
  content: requiredString(args.content, "content"),
16538
16548
  ...delivery ? { delivery } : {},
@@ -16610,6 +16620,7 @@ var AGENT_COMMANDS = [
16610
16620
  const targets = Array.isArray(args.targets) ? args.targets.map((t) => requiredString(t, "target")) : [];
16611
16621
  return {
16612
16622
  responses: await client.fanout({
16623
+ ...forwardedHops(context?.handling),
16613
16624
  targets,
16614
16625
  content: requiredString(args.content, "content"),
16615
16626
  ...optionalString(args.correlationId) ? { correlationId: optionalString(args.correlationId) } : {},
@@ -17285,7 +17296,7 @@ function sessionTokenFixHint(policy) {
17285
17296
  }
17286
17297
 
17287
17298
  // plugins/kxm/src/mcp-server.ts
17288
- var VERSION = "0.7.96";
17299
+ var VERSION = "0.7.98";
17289
17300
  var CONFIGURE_PLUGIN = "/plugin configure kxm@kxm";
17290
17301
  var inbox = /* @__PURE__ */ new Map();
17291
17302
  var notifiedInbox = /* @__PURE__ */ new Set();
@@ -17442,7 +17453,12 @@ mcp.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
17442
17453
  const cmd = AGENT_COMMANDS_MAP.get(request.params.name);
17443
17454
  if (!cmd) throw new Error(`unknown tool: ${request.params.name}`);
17444
17455
  const args = asRecord2(request.params.arguments);
17445
- const result = await cmd.execute(client, args, { signal: extra.signal, inbox, notifiedInbox });
17456
+ const result = await cmd.execute(client, args, {
17457
+ signal: extra.signal,
17458
+ inbox,
17459
+ notifiedInbox,
17460
+ handling: [...inbox.values()]
17461
+ });
17446
17462
  return textResult(result);
17447
17463
  } catch (error2) {
17448
17464
  return {