@kontextmind/kxm 0.7.95 → 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 (158) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.kxm/README.md +39 -9
  3. package/CHANGELOG.md +23 -1
  4. package/README.md +147 -257
  5. package/SECURITY.md +21 -12
  6. package/docs/README.md +133 -54
  7. package/docs/adr/ADR-0002-browser-automation-steel-doks.md +24 -18
  8. package/docs/adr/ADR-0003-sqlite-only-store.md +100 -0
  9. package/docs/adr/ADR-0004-edge-identity-authentik.md +99 -0
  10. package/docs/adr/README.md +33 -0
  11. package/docs/concepts/architecture.md +262 -0
  12. package/docs/concepts/data-and-storage.md +194 -0
  13. package/docs/concepts/trust-model.md +153 -0
  14. package/docs/contracts/README.md +22 -14
  15. package/docs/contracts/effects-and-recovery.md +3 -0
  16. package/docs/contracts/migration.md +2 -2
  17. package/docs/contracts/routing.md +6 -5
  18. package/docs/contributing/assignment-runner.md +388 -0
  19. package/docs/contributing/ci-and-release.md +231 -0
  20. package/docs/contributing/development.md +362 -0
  21. package/docs/contributing/harness-routing-internals.md +192 -0
  22. package/docs/{packages.md → contributing/packages.md} +13 -15
  23. package/docs/{skills → contributing}/repo-work-delivery.md +20 -21
  24. package/docs/contributing/test-matrix.md +208 -0
  25. package/docs/{tui-components.md → contributing/tui-components.md} +30 -22
  26. package/docs/contributing/writing-docs.md +340 -0
  27. package/docs/glossary.md +471 -0
  28. package/docs/guides/agent-skills.md +137 -0
  29. package/docs/guides/browser-automation.md +160 -0
  30. package/docs/guides/context-and-memory.md +352 -0
  31. package/docs/guides/continuous-improvement.md +228 -0
  32. package/docs/guides/governed-skills.md +173 -0
  33. package/docs/guides/nous-providers.md +186 -0
  34. package/docs/guides/peer-messaging.md +304 -0
  35. package/docs/guides/pi-workers.md +219 -0
  36. package/docs/guides/provenance-gates.md +313 -0
  37. package/docs/guides/webhook-workflows.md +399 -0
  38. package/docs/kb/how-credentials-retrieved-safely.md +38 -12
  39. package/docs/kb/how-to-capture-and-annotate-section.md +15 -13
  40. package/docs/kb/how-to-connect-playwright-to-steel.md +16 -11
  41. package/docs/kb/how-to-recover-expired-session-or-orphan.md +26 -16
  42. package/docs/kb/how-to-resume-after-mfa.md +19 -11
  43. package/docs/kb/how-to-take-over-session.md +17 -13
  44. package/docs/kb/why-authentication-disappeared.md +22 -14
  45. package/docs/kb/why-automation-opened-different-browser.md +23 -14
  46. package/docs/kb/why-session-viewer-cannot-control.md +13 -12
  47. package/docs/operations/backup-and-restore.md +248 -0
  48. package/docs/operations/deploy.md +307 -0
  49. package/docs/operations/monitoring.md +209 -0
  50. package/docs/operations/runtime-sync.md +192 -0
  51. package/docs/operations/troubleshooting.md +266 -0
  52. package/docs/operations/upgrade.md +124 -0
  53. package/docs/prompts/browser-annotate-feedback.md +7 -7
  54. package/docs/prompts/browser-diagnose-recover.md +11 -10
  55. package/docs/prompts/browser-explore.md +7 -7
  56. package/docs/prompts/browser-repro-fix.md +7 -7
  57. package/docs/prompts/browser-start.md +12 -11
  58. package/docs/prompts/browser-takeover.md +8 -8
  59. package/docs/{cli-reference.md → reference/cli-reference.md} +88 -46
  60. package/docs/{config-reference.md → reference/config-reference.md} +159 -148
  61. package/docs/reference/configuration.md +299 -0
  62. package/docs/reference/harness-routing.md +508 -0
  63. package/docs/reference/http-api.md +203 -0
  64. package/docs/reference/tools.md +370 -0
  65. package/docs/{workflow-guide.md → reference/workflow-catalog.md} +92 -153
  66. package/docs/reference/workflow-definitions.md +286 -0
  67. package/docs/start/first-workflow.md +287 -0
  68. package/docs/start/install.md +146 -0
  69. package/docs/start/quickstart-claude-code.md +405 -0
  70. package/docs/start/quickstart-pi.md +213 -0
  71. package/docs/templates/README.md +78 -73
  72. package/docs/templates/adr.md +13 -13
  73. package/docs/templates/architecture.md +55 -71
  74. package/docs/templates/bug-fix.md +13 -16
  75. package/docs/templates/feature.md +14 -19
  76. package/docs/templates/handoff.md +44 -46
  77. package/docs/templates/postmortem.md +30 -43
  78. package/docs/templates/research.md +15 -20
  79. package/docs/templates/review.md +49 -50
  80. package/docs/templates/runbook.md +38 -30
  81. package/docs/templates/test-plan.md +16 -23
  82. package/docs/templates/test-report.md +14 -17
  83. package/examples/README.md +9 -5
  84. package/examples/provenance-workflow.json +1 -1
  85. package/examples/webhook-workflows/jira-development.json +59 -0
  86. package/examples/webhook-workflows/jira-issue-updated.json +12 -0
  87. package/examples/workflow-signal.ts +4 -5
  88. package/package.json +1 -1
  89. package/packages/core/tui/README.md +1 -1
  90. package/plugins/kxm/.claude-plugin/plugin.json +1 -1
  91. package/plugins/kxm/README.md +31 -32
  92. package/plugins/kxm/dist/claude-hook.js +11 -1
  93. package/plugins/kxm/dist/cli.js +164 -79
  94. package/plugins/kxm/dist/client.js +3 -1
  95. package/plugins/kxm/dist/core.js +11 -1
  96. package/plugins/kxm/dist/extension.js +45 -13
  97. package/plugins/kxm/dist/mcp-server.js +20 -4
  98. package/plugins/kxm/dist/runtime-supervisor.js +1 -3
  99. package/plugins/kxm/dist/runtime.js +18 -4
  100. package/plugins/kxm/dist/server.js +115 -20
  101. package/plugins/kxm/package.json +1 -1
  102. package/plugins/kxm/skills/kxm/references/protocol.md +3 -1
  103. package/plugins/kxm/skills/kxm-browser-auth/SKILL.md +1 -1
  104. package/plugins/kxm/skills/kxm-browser-diagnostics/SKILL.md +5 -5
  105. package/plugins/kxm/skills/kxm-browser-explore/SKILL.md +2 -2
  106. package/plugins/kxm/skills/kxm-browser-session/SKILL.md +10 -13
  107. package/plugins/kxm/skills/kxm-browser-takeover/SKILL.md +1 -1
  108. package/plugins/kxm/skills/kxm-browser-verify/SKILL.md +1 -1
  109. package/plugins/kxm/skills/kxm-context-memory/SKILL.md +13 -4
  110. package/plugins/kxm/skills/kxm-hub-ops/SKILL.md +3 -1
  111. package/plugins/kxm/skills/kxm-mind-setup/SKILL.md +2 -1
  112. package/plugins/kxm/skills/kxm-project-setup/SKILL.md +31 -54
  113. package/plugins/kxm/skills/kxm-projects/SKILL.md +1 -1
  114. package/plugins/kxm/skills/kxm-protocol/SKILL.md +1 -1
  115. package/plugins/kxm/skills/kxm-routing-improve/SKILL.md +15 -7
  116. package/plugins/kxm/skills/kxm-runs/SKILL.md +11 -5
  117. package/plugins/kxm/skills/kxm-session/SKILL.md +1 -1
  118. package/plugins/kxm/skills/kxm-tasks/SKILL.md +9 -7
  119. package/plugins/kxm/skills/kxm-workflow/SKILL.md +10 -2
  120. package/plugins/kxm/src/cli/system.ts +1 -1
  121. package/plugins/kxm/src/cli/workflows.ts +12 -7
  122. package/plugins/kxm/src/cli.ts +22 -8
  123. package/plugins/kxm/src/client.ts +4 -0
  124. package/plugins/kxm/src/commands.ts +23 -1
  125. package/plugins/kxm/src/extension.ts +20 -14
  126. package/plugins/kxm/src/github-watch.ts +8 -5
  127. package/plugins/kxm/src/hub-env.ts +19 -1
  128. package/plugins/kxm/src/hub.ts +105 -21
  129. package/plugins/kxm/src/improve-sources.ts +2 -7
  130. package/plugins/kxm/src/init-guide-setup.ts +1 -1
  131. package/plugins/kxm/src/mcp-server.ts +9 -2
  132. package/plugins/kxm/src/modes.ts +1 -1
  133. package/plugins/kxm/src/runtime-store.ts +23 -0
  134. package/plugins/kxm/src/workflow.ts +70 -1
  135. package/schemas/README.md +1 -1
  136. package/scripts/smoke-multi-pi.mjs +5 -1
  137. package/docs/agent-communication-envelopes-and-gates.md +0 -553
  138. package/docs/agent-skills.md +0 -198
  139. package/docs/architecture.md +0 -245
  140. package/docs/assignment-runner.md +0 -264
  141. package/docs/browser-automation.md +0 -139
  142. package/docs/configuration.md +0 -437
  143. package/docs/continuous-improvement.md +0 -226
  144. package/docs/getting-started.md +0 -277
  145. package/docs/harness-routing.md +0 -616
  146. package/docs/kb/qa-authentik-authentication.md +0 -97
  147. package/docs/kb/qa-extension-install-and-hub-bootstrap.md +0 -85
  148. package/docs/kb/qa-hub-on-a-public-host.md +0 -48
  149. package/docs/kb/qa-sqlite-vs-duckdb.md +0 -35
  150. package/docs/kb/qa-what-the-hub-stores.md +0 -64
  151. package/docs/kxm-handbook.md +0 -1181
  152. package/docs/operations.md +0 -510
  153. package/docs/operator-pi-packages.md +0 -67
  154. package/docs/provenance-gates.md +0 -295
  155. package/docs/skills.md +0 -47
  156. package/docs/test-matrix.md +0 -132
  157. package/docs/troubleshooting.md +0 -322
  158. package/docs/webhook-workflows.md +0 -240
@@ -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,
@@ -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.95";
17299
+ var VERSION = "0.7.97";
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 {
@@ -17440,9 +17440,6 @@ import { createHash as createHash4, randomUUID } from "node:crypto";
17440
17440
  import { existsSync as existsSync5, lstatSync as lstatSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync3, realpathSync as realpathSync2, writeFileSync as writeFileSync2 } from "node:fs";
17441
17441
  import { dirname as dirname5, join as join5, resolve as resolve4 } from "node:path";
17442
17442
 
17443
- // plugins/kxm/src/bindings.ts
17444
- import { homedir } from "node:os";
17445
-
17446
17443
  // plugins/kxm/src/sqlite.ts
17447
17444
  import { createRequire } from "node:module";
17448
17445
  var requireFromHere = createRequire(import.meta.url);
@@ -17483,6 +17480,7 @@ var DatabaseSync = class {
17483
17480
  };
17484
17481
 
17485
17482
  // plugins/kxm/src/bindings.ts
17483
+ import { homedir } from "node:os";
17486
17484
  import { dirname as dirname3, isAbsolute as isAbsolute2, join as join3, parse, relative as relative2, resolve as resolve2, sep as sep2 } from "node:path";
17487
17485
  var MAX_BINDING_RECORD_BYTES = 256 * 1024;
17488
17486
  var BINDING_LABEL = "Runtime-local repository bindings";
@@ -17610,9 +17610,6 @@ import { createHash as createHash5, randomUUID } from "node:crypto";
17610
17610
  import { existsSync as existsSync5, lstatSync as lstatSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync3, realpathSync as realpathSync2, writeFileSync as writeFileSync2 } from "node:fs";
17611
17611
  import { dirname as dirname5, join as join5, resolve as resolve4 } from "node:path";
17612
17612
 
17613
- // plugins/kxm/src/bindings.ts
17614
- import { homedir } from "node:os";
17615
-
17616
17613
  // plugins/kxm/src/sqlite.ts
17617
17614
  import { createRequire } from "node:module";
17618
17615
  var requireFromHere = createRequire(import.meta.url);
@@ -17653,6 +17650,7 @@ var DatabaseSync = class {
17653
17650
  };
17654
17651
 
17655
17652
  // plugins/kxm/src/bindings.ts
17653
+ import { homedir } from "node:os";
17656
17654
  import { dirname as dirname3, isAbsolute as isAbsolute2, join as join3, parse, relative as relative2, resolve as resolve2, sep as sep2 } from "node:path";
17657
17655
  var MAX_BINDING_RECORD_BYTES = 256 * 1024;
17658
17656
  var BINDING_LABEL = "Runtime-local repository bindings";
@@ -18555,6 +18553,20 @@ function projectRuntimeKey(projectRoot) {
18555
18553
  const folded = process.platform === "win32" ? canonical.toLocaleLowerCase("en-US") : canonical;
18556
18554
  return createHash5("sha256").update(folded, "utf8").digest("hex").slice(0, 24);
18557
18555
  }
18556
+ function kxmProjectRunEventsPath(projectRoot, env) {
18557
+ return join5(kxmRuntimePaths({ env }).projectsDir, projectRuntimeKey(projectRoot), "run-events.db");
18558
+ }
18559
+ function projectRuntimeOwnsRun(projectRoot, runId, env) {
18560
+ const path = kxmProjectRunEventsPath(projectRoot, env);
18561
+ if (!existsSync5(path)) return false;
18562
+ const database = new DatabaseSync(path, { readOnly: true });
18563
+ try {
18564
+ database.exec("PRAGMA busy_timeout = 5000");
18565
+ return database.prepare("SELECT 1 FROM runs WHERE run_id = ?").get(runId) !== void 0;
18566
+ } finally {
18567
+ database.close();
18568
+ }
18569
+ }
18558
18570
  var KXM_REGISTRY_SCHEMA_VERSION = 1;
18559
18571
  var REGISTRY_TABLES = {
18560
18572
  supervisor: ["singleton_id", "runtime_id", "pid", "port", "token_hash", "started_at", "heartbeat_at", "state"],
@@ -32067,7 +32079,7 @@ var DEFAULT_MODES_CONFIG = Object.freeze({
32067
32079
  browser: {
32068
32080
  description: "Web application exploration, screenshotting, and UI testing",
32069
32081
  baseTools: ["read", "bash"],
32070
- contextFiles: ["docs/browser-automation.md"],
32082
+ contextFiles: ["docs/guides/browser-automation.md"],
32071
32083
  thinkingLevel: "medium",
32072
32084
  model: "grok/grok-4.6"
32073
32085
  }
@@ -33023,6 +33035,7 @@ export {
33023
33035
  kxmMonotonicNs,
33024
33036
  kxmPolicyRevisions,
33025
33037
  kxmProjectAdmissionLimits,
33038
+ kxmProjectRunEventsPath,
33026
33039
  kxmRunRevisionDrift,
33027
33040
  kxmRuntimePaths,
33028
33041
  kxmRuntimeRequest,
@@ -33062,6 +33075,7 @@ export {
33062
33075
  probeHarnessesForModelAsync,
33063
33076
  projectKxmRunReadOnly,
33064
33077
  projectRuntimeKey,
33078
+ projectRuntimeOwnsRun,
33065
33079
  pruneSocketDir,
33066
33080
  readKxmRunStatus,
33067
33081
  readKxmSupervisorToken,
@@ -14779,7 +14779,7 @@ var require_dist = __commonJS({
14779
14779
  });
14780
14780
 
14781
14781
  // plugins/kxm/src/hub.ts
14782
- import { createHash as createHash5, createHmac } from "node:crypto";
14782
+ import { createHash as createHash5, createHmac as createHmac2 } from "node:crypto";
14783
14783
  import { existsSync as existsSync6 } from "node:fs";
14784
14784
  import { createServer } from "node:http";
14785
14785
  import { isIP } from "node:net";
@@ -15650,7 +15650,7 @@ function workflowScopeExtras(operation, assignedCoordinatorName) {
15650
15650
  import { createHash as createHash3, randomUUID as randomUUID2, timingSafeEqual } from "node:crypto";
15651
15651
 
15652
15652
  // plugins/kxm/src/workflow.ts
15653
- import { createHash as createHash2 } from "node:crypto";
15653
+ import { createHash as createHash2, createHmac } from "node:crypto";
15654
15654
 
15655
15655
  // plugins/kxm/src/relevance.ts
15656
15656
  var RELEVANCE_STOPWORDS = Object.freeze(/* @__PURE__ */ new Set([
@@ -16572,6 +16572,30 @@ function canonicalWorkflowDefinitionJson(definition) {
16572
16572
  function workflowDefinitionHash(definition) {
16573
16573
  return createHash2("sha256").update(canonicalWorkflowDefinitionJson(definition), "utf8").digest("hex");
16574
16574
  }
16575
+ var WORKFLOW_WEBHOOK_SIGNATURE_VERSION = "kxm-webhook-v1";
16576
+ var WORKFLOW_WEBHOOK_MAX_SKEW_SECONDS = 300;
16577
+ function workflowWebhookSignedMaterial(scope, timestamp, deliveryId, body) {
16578
+ const fields = [
16579
+ WORKFLOW_WEBHOOK_SIGNATURE_VERSION,
16580
+ scope.runId === void 0 ? "start" : "signal",
16581
+ timestamp,
16582
+ deliveryId,
16583
+ scope.definitionId,
16584
+ scope.runId ?? "",
16585
+ scope.signalKey ?? ""
16586
+ ];
16587
+ if (fields.some((field) => /[\r\n]/.test(field))) {
16588
+ throw new ProtocolError(400, "webhook signature fields must not contain line breaks", "webhook_signature_field_invalid");
16589
+ }
16590
+ return Buffer.concat([
16591
+ Buffer.from(`${fields.join("\n")}
16592
+ `, "utf8"),
16593
+ typeof body === "string" ? Buffer.from(body, "utf8") : Buffer.from(body)
16594
+ ]);
16595
+ }
16596
+ function workflowWebhookSignature(secret, scope, timestamp, deliveryId, body) {
16597
+ return `sha256=${createHmac("sha256", secret).update(workflowWebhookSignedMaterial(scope, timestamp, deliveryId, body)).digest("hex")}`;
16598
+ }
16575
16599
  function resolveOutcomeRule(stage, outcomeKey) {
16576
16600
  const raw = stage.on?.[outcomeKey];
16577
16601
  return raw === void 0 ? void 0 : normalizeOutcomeValue(raw, `stage ${stage.id} on.${outcomeKey}`);
@@ -16895,6 +16919,13 @@ var HubHttpError = class extends Error {
16895
16919
  };
16896
16920
 
16897
16921
  // plugins/kxm/src/commands.ts
16922
+ function forwardedHops(handling) {
16923
+ if (!handling?.length) return void 0;
16924
+ return {
16925
+ hops: Math.max(...handling.map((message) => message.hops)) + 1,
16926
+ maxHops: Math.min(...handling.map((message) => message.maxHops))
16927
+ };
16928
+ }
16898
16929
  function requiredString(value, name) {
16899
16930
  if (typeof value !== "string" || value.trim() === "") throw new Error(`${name} is required`);
16900
16931
  return value.trim();
@@ -17024,12 +17055,13 @@ var AGENT_COMMANDS = [
17024
17055
  required: ["target", "content"],
17025
17056
  additionalProperties: false
17026
17057
  },
17027
- async execute(client, args) {
17058
+ async execute(client, args, context) {
17028
17059
  const delivery = optionalString2(args.delivery);
17029
17060
  const correlationId = optionalString2(args.correlationId);
17030
17061
  const idempotencyKey = optionalString2(args.idempotencyKey);
17031
17062
  const workflowContext = optionalWorkflowContext(args.workflowContext);
17032
17063
  const message = await client.send({
17064
+ ...forwardedHops(context?.handling),
17033
17065
  target: requiredString(args.target, "target"),
17034
17066
  content: requiredString(args.content, "content"),
17035
17067
  ...delivery ? { delivery } : {},
@@ -17107,6 +17139,7 @@ var AGENT_COMMANDS = [
17107
17139
  const targets = Array.isArray(args.targets) ? args.targets.map((t) => requiredString(t, "target")) : [];
17108
17140
  return {
17109
17141
  responses: await client.fanout({
17142
+ ...forwardedHops(context?.handling),
17110
17143
  targets,
17111
17144
  content: requiredString(args.content, "content"),
17112
17145
  ...optionalString2(args.correlationId) ? { correlationId: optionalString2(args.correlationId) } : {},
@@ -21089,20 +21122,59 @@ data: ${JSON.stringify({ type: "ops", project, topic, at: nowIso() })}
21089
21122
  const candidate = payload.webhookEvent ?? payload.event;
21090
21123
  return typeof candidate === "string" && candidate.trim() ? candidate.trim() : void 0;
21091
21124
  }
21092
- function verifyWebhookSignature(request, body, secret) {
21093
- const signature = request.headers["x-hub-signature-256"] ?? request.headers["x-hub-signature"];
21094
- if (typeof signature !== "string") {
21095
- throw new ProtocolError(401, "webhook signature is required", "webhook_signature_missing");
21096
- }
21125
+ function requireSha256Signature(signature) {
21097
21126
  const separator = signature.indexOf("=");
21098
21127
  const algorithm = separator > 0 ? signature.slice(0, separator).toLowerCase() : "";
21099
21128
  if (algorithm !== "sha256") {
21100
21129
  throw new ProtocolError(401, "webhook signature must use sha256", "webhook_signature_unsupported");
21101
21130
  }
21102
- const expected = `sha256=${createHmac("sha256", secret).update(body).digest("hex")}`;
21131
+ }
21132
+ function verifyProviderWebhookSignature(request, body, definition) {
21133
+ if (definition.source === "generic") {
21134
+ throw new ProtocolError(
21135
+ 401,
21136
+ "generic workflow webhooks require x-kxm-signature over the timestamp, delivery ID, definition, and body",
21137
+ "webhook_signature_missing"
21138
+ );
21139
+ }
21140
+ const signature = request.headers["x-hub-signature-256"] ?? request.headers["x-hub-signature"];
21141
+ if (typeof signature !== "string") {
21142
+ throw new ProtocolError(401, "webhook signature is required", "webhook_signature_missing");
21143
+ }
21144
+ requireSha256Signature(signature);
21145
+ const expected = `sha256=${createHmac2("sha256", definition.secret).update(body).digest("hex")}`;
21103
21146
  if (!safeTokenEqual(signature, expected)) {
21104
21147
  throw new ProtocolError(401, "webhook signature is invalid", "webhook_signature_invalid");
21105
21148
  }
21149
+ const deliveryHeader = definition.source === "jira" ? request.headers["x-atlassian-webhook-identifier"] : request.headers["x-github-delivery"];
21150
+ return requireString(deliveryHeader, "webhook delivery identifier", { max: 128 });
21151
+ }
21152
+ function verifyKxmWebhookSignature(request, body, secret, scope) {
21153
+ const signature = request.headers["x-kxm-signature"];
21154
+ if (typeof signature !== "string") {
21155
+ throw new ProtocolError(
21156
+ 401,
21157
+ "KXM webhooks require x-kxm-signature over the timestamp, delivery ID, route, and body",
21158
+ "webhook_signature_missing"
21159
+ );
21160
+ }
21161
+ requireSha256Signature(signature);
21162
+ const timestamp = request.headers["x-kxm-timestamp"];
21163
+ if (typeof timestamp !== "string" || !/^[0-9]{1,12}$/.test(timestamp)) {
21164
+ throw new ProtocolError(401, "x-kxm-timestamp must be Unix seconds", "webhook_timestamp_invalid");
21165
+ }
21166
+ const deliveryId = requireString(request.headers["x-kxm-delivery-id"], "webhook delivery identifier", { max: 128 });
21167
+ if (!safeTokenEqual(signature, workflowWebhookSignature(secret, scope, timestamp, deliveryId, body))) {
21168
+ throw new ProtocolError(401, "webhook signature is invalid", "webhook_signature_invalid");
21169
+ }
21170
+ if (Math.abs(Date.now() / 1e3 - Number(timestamp)) > WORKFLOW_WEBHOOK_MAX_SKEW_SECONDS) {
21171
+ throw new ProtocolError(
21172
+ 401,
21173
+ `webhook timestamp is outside the ${WORKFLOW_WEBHOOK_MAX_SKEW_SECONDS}-second window; re-sign at send time`,
21174
+ "webhook_timestamp_expired"
21175
+ );
21176
+ }
21177
+ return deliveryId;
21106
21178
  }
21107
21179
  function workflowPrompt(definition, runId, payload) {
21108
21180
  const rendered = renderWorkflowPrompt(definition.promptTemplate, payload);
@@ -21125,7 +21197,7 @@ data: ${JSON.stringify({ type: "ops", project, topic, at: nowIso() })}
21125
21197
  stageList,
21126
21198
  "",
21127
21199
  `At every stage, record material knowledge with kxm_workflow_record in one of these categories: ${JOURNAL_CATEGORIES.join(", ")}. Pass the stageId the entry belongs to; the hub binds the attempt and, when you omit area, uses the stage's declared area.`,
21128
- "Keep repository-local configuration in .kxm/config, logs in .kxm/logs, and durable workflow artifacts in .kxm/assets; never commit runtime logs, state, or secrets.",
21200
+ "Keep reviewed configuration as YAML directly under .kxm/ (such as .kxm/project.yaml and .kxm/workflows/), logs in .kxm/logs, and durable workflow artifacts in .kxm/assets. Never create .kxm/config, which KXM refuses, and never commit runtime logs, .kxm/state, or secrets.",
21129
21201
  "Complete each stage with kxm_workflow_checkpoint. Supply evidence as an object whose keys exactly match the stage's required evidence keys. Unrelated keys never satisfy a requirement. A warning or failure must be corrected and checkpointed again until it passes or the attempt limit is reached.",
21130
21202
  "For a peer-evidence requirement, send or fan out with workflowContext containing this run ID, the exact stage ID, requirement key, and current 1-based attempt. At checkpoint, cite only the returned message IDs under evidenceRefs; the hub derives producer and reply provenance.",
21131
21203
  "When an external system must finish asynchronously, call kxm_workflow_wait with a stable signal key and any already-verified keyed evidence. That evidence is accumulated with the signed callback before the stage can pass; then settle the turn.",
@@ -21463,14 +21535,17 @@ data: ${JSON.stringify({ type: "ops", project, topic, at: nowIso() })}
21463
21535
  const definition = webhookWorkflows2.get(definitionId);
21464
21536
  if (!definition) throw new ProtocolError(404, "webhook workflow not found", "webhook_not_found");
21465
21537
  const rawBody = await readBody(request);
21466
- verifyWebhookSignature(request, rawBody, definition.signalSecret ?? definition.secret);
21538
+ const deliveryId = verifyKxmWebhookSignature(
21539
+ request,
21540
+ rawBody,
21541
+ definition.signalSecret ?? definition.secret,
21542
+ { definitionId: definition.id, runId, signalKey }
21543
+ );
21467
21544
  const body = parseJsonBody(rawBody);
21468
21545
  const run = workflowRuns.get(runId);
21469
21546
  if (!run || run.definitionId !== definition.id) {
21470
21547
  throw new ProtocolError(404, "workflow run not found", "workflow_not_found");
21471
21548
  }
21472
- const deliveryHeader = request.headers["x-atlassian-webhook-identifier"] ?? request.headers["x-github-delivery"] ?? request.headers["x-kxm-delivery-id"];
21473
- const deliveryId = requireString(deliveryHeader, "webhook delivery identifier", { max: 128 });
21474
21549
  const payloadHash = createHash5("sha256").update(rawBody).digest("hex");
21475
21550
  const existingReceipt = run.signalReceipts?.find((receipt2) => receipt2.deliveryId === deliveryId);
21476
21551
  if (existingReceipt) {
@@ -21616,7 +21691,8 @@ data: ${JSON.stringify({ type: "ops", project, topic, at: nowIso() })}
21616
21691
  const definition = webhookWorkflows2.get(definitionId);
21617
21692
  if (!definition) throw new ProtocolError(404, "webhook workflow not found", "webhook_not_found");
21618
21693
  const rawBody = await readBody(request);
21619
- verifyWebhookSignature(request, rawBody, definition.secret);
21694
+ const bodyOnlySignature = request.headers["x-kxm-signature"] === void 0;
21695
+ const deliveryId = bodyOnlySignature ? verifyProviderWebhookSignature(request, rawBody, definition) : verifyKxmWebhookSignature(request, rawBody, definition.secret, { definitionId: definition.id });
21620
21696
  const payload = parseJsonBody(rawBody);
21621
21697
  const event = webhookEvent(request, payload);
21622
21698
  if (definition.event && event !== definition.event) {
@@ -21627,15 +21703,30 @@ data: ${JSON.stringify({ type: "ops", project, topic, at: nowIso() })}
21627
21703
  response.writeHead(204, { "cache-control": "no-store" }).end();
21628
21704
  return;
21629
21705
  }
21630
- const deliveryHeader = request.headers["x-atlassian-webhook-identifier"] ?? request.headers["x-github-delivery"] ?? request.headers["x-kxm-delivery-id"];
21631
- const deliveryId = requireString(deliveryHeader, "webhook delivery identifier", { max: 128 });
21706
+ const payloadHash = createHash5("sha256").update(rawBody).digest("hex");
21632
21707
  const existing = [...workflowRuns.values()].find(
21633
21708
  (run2) => run2.definitionId === definition.id && run2.deliveryId === deliveryId
21634
21709
  );
21635
21710
  if (existing) {
21636
- json(response, 200, { run: existing, duplicate: true });
21711
+ if (existing.payloadHash !== payloadHash) {
21712
+ throw new ProtocolError(
21713
+ 409,
21714
+ "webhook delivery identifier was already used for a different body",
21715
+ "webhook_delivery_conflict"
21716
+ );
21717
+ }
21718
+ json(response, 200, { duplicate: true, runId: existing.id, status: existing.status });
21637
21719
  return;
21638
21720
  }
21721
+ if (bodyOnlySignature && [...workflowRuns.values()].some(
21722
+ (run2) => run2.definitionId === definition.id && run2.payloadHash === payloadHash
21723
+ )) {
21724
+ throw new ProtocolError(
21725
+ 409,
21726
+ "this signed body already started a run under another delivery identifier",
21727
+ "webhook_payload_replayed"
21728
+ );
21729
+ }
21639
21730
  const target = findKnownTarget(definition.project, definition.target);
21640
21731
  const createdAt = nowIso();
21641
21732
  const runId = newId("run");
@@ -21685,7 +21776,7 @@ data: ${JSON.stringify({ type: "ops", project, topic, at: nowIso() })}
21685
21776
  definitionId: definition.id,
21686
21777
  source: definition.source,
21687
21778
  deliveryId,
21688
- payloadHash: createHash5("sha256").update(rawBody).digest("hex"),
21779
+ payloadHash,
21689
21780
  definitionHash: workflowDefinitionHash(definition),
21690
21781
  ...event ? { event } : {},
21691
21782
  project: definition.project,
@@ -21716,7 +21807,7 @@ data: ${JSON.stringify({ type: "ops", project, topic, at: nowIso() })}
21716
21807
  project: definition.project,
21717
21808
  target: target.id
21718
21809
  });
21719
- json(response, 202, { run, duplicate: false });
21810
+ json(response, 202, { run, runId: run.id, duplicate: false });
21720
21811
  return;
21721
21812
  }
21722
21813
  if (method === "GET" && url.pathname === "/metrics") {
@@ -22656,7 +22747,11 @@ data: ${JSON.stringify({ agent: publicAgent(current, staleAfterMs) })}
22656
22747
  const hops = parseBoundedInteger(body.hops, "hops", 0, 0, 100);
22657
22748
  const maxHops = parseBoundedInteger(body.maxHops, "maxHops", DEFAULT_MAX_HOPS, 1, 20);
22658
22749
  if (hops >= maxHops) {
22659
- throw new ProtocolError(400, `hop limit reached (${hops}/${maxHops})`, "hop_limit_reached");
22750
+ throw new ProtocolError(
22751
+ 400,
22752
+ `hop limit reached (${hops}/${maxHops}): this request would extend a chain of forwarded requests past its limit; answer the inbound request directly`,
22753
+ "hop_limit_reached"
22754
+ );
22660
22755
  }
22661
22756
  const ttlMs = parseBoundedInteger(
22662
22757
  body.ttlMs,
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-plugin",
3
- "version": "0.7.95",
3
+ "version": "0.7.97",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "engines": {
@@ -112,7 +112,9 @@ token before committing anything shared — a refusal means stop, not retry.
112
112
  - `replied`: contains the recipient's final reply.
113
113
  - `cancelled`: sender cancelled queued or delivered work.
114
114
  - `expired`: the request exceeded its TTL before a reply.
115
- - `error`: terminal failure.
115
+ - `error`: reserved. The protocol declares it, but the hub never sets it, so
116
+ it is not a failure path to wait for. A `kxm_fanout` entry with
117
+ `status: "error"` reports a local send or wait failure, not this state.
116
118
 
117
119
  Messages use a 24-hour default TTL and terminal records are retained for seven days by default. TTL begins when the message is sent and includes queued time; normally omit it for model work. A fanout's local wait ending does not change message state and returns a recoverable pending handle. Inspect it with `kxm_get` or repeat the exact request using the same correlation and idempotency prefix. Never create a replacement key while the original is pending, and never count a pending peer as workflow evidence. A stable `idempotencyKey` deduplicates an exact retry from the same sender. Durable `kxm_fanout` calls should use the workflow run ID as `correlationId` and a stage-specific `idempotencyKeyPrefix`; the client scopes the resulting key by correlation and normalized target. State is persisted in SQLite by the standard hub executable.
118
120
 
@@ -24,7 +24,7 @@ Always retrieve credentials and API keys directly from `pass-cli`:
24
24
  pass-cli item view --vault-name "<vault>" --item-title "<title>" --field password
25
25
 
26
26
  # Retrieve Steel infrastructure API key
27
- pass-cli item view --vault-name "AI Provider Keys" --item-title "Steel Browser (KontextMind DOKS)" --field STEEL_API_KEY
27
+ pass-cli item view --vault-name "<vault>" --item-title "<steel-item>" --field STEEL_API_KEY
28
28
  ```
29
29
 
30
30
  ### 2. Secret Redaction Invariants
@@ -13,8 +13,8 @@ Use this skill to investigate and resolve connectivity failures, CDP attachment
13
13
 
14
14
  - **Symptom**: `Failed to fetch Steel session (401)` or `Connection refused`.
15
15
  - **Diagnosis**:
16
- - Verify Steel API endpoint is reachable: `curl -sI https://steel.kontextmind.com/v1/health`.
17
- - Check `STEEL_API_KEY` in `pass-cli`: `pass-cli item view --vault-name "AI Provider Keys" --item-title "Steel Browser (KontextMind DOKS)"`.
16
+ - Verify Steel API endpoint is reachable: `curl -sI "$STEEL_API_URL/v1/health"`.
17
+ - Check that `STEEL_API_KEY` is set in the environment (`test -n "$STEEL_API_KEY" && echo set`); never print its value.
18
18
  - **Remedy**: Update expired or missing API key in your session environment.
19
19
 
20
20
  ### 2. CDP WebSocket Attachment Failure
@@ -35,14 +35,14 @@ Use this skill to investigate and resolve connectivity failures, CDP attachment
35
35
 
36
36
  ### 4. Interactive Takeover Viewer Inaccessible
37
37
 
38
- - **Symptom**: `https://steel.kontextmind.com/ui` opens but cannot interact with elements.
38
+ - **Symptom**: `$STEEL_UI_URL` opens but cannot interact with elements.
39
39
  - **Diagnosis**: Self-hosted Steel OSS serves the session screencast and devtools.
40
- - **Remedy**: Connect directly to the devtools inspector URL: `https://steel.kontextmind.com/v1/devtools/inspector.html` or open the browser devtools panel to perform input actions.
40
+ - **Remedy**: Connect directly to the devtools inspector URL: `$STEEL_API_URL/v1/devtools/inspector.html` or open the browser devtools panel to perform input actions.
41
41
 
42
42
  ### 5. Orphaned Browser Processes & Cleanup
43
43
 
44
44
  - **Symptom**: Node memory pressure or high active session counts.
45
- - **Diagnosis**: Query active sessions list: `curl -s https://steel.kontextmind.com/v1/sessions`.
45
+ - **Diagnosis**: Query active sessions list: `printf 'x-steel-api-key: %s\n' "$STEEL_API_KEY" | curl -sS -H @- "$STEEL_API_URL/v1/sessions"`.
46
46
  - **Remedy**:
47
47
  - Iterate through inactive sessions and post `/release` for each stale ID.
48
48
  - Ensure all automation scripts wrap browser usage in `try...finally` to release sessions reliably.