@kontextmind/kxm 0.7.0 → 0.7.5

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 (78) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.kxm/roles/writer.yaml +2 -0
  3. package/docs/README.md +3 -0
  4. package/docs/adr/ADR-0002-browser-automation-steel-doks.md +103 -0
  5. package/docs/agent-skills.md +19 -2
  6. package/docs/browser-automation.md +116 -0
  7. package/docs/configuration.md +10 -1
  8. package/docs/getting-started.md +21 -0
  9. package/docs/kb/how-credentials-retrieved-safely.md +31 -0
  10. package/docs/kb/how-to-capture-and-annotate-section.md +60 -0
  11. package/docs/kb/how-to-connect-playwright-to-steel.md +54 -0
  12. package/docs/kb/how-to-recover-expired-session-or-orphan.md +54 -0
  13. package/docs/kb/how-to-resume-after-mfa.md +28 -0
  14. package/docs/kb/how-to-take-over-session.md +32 -0
  15. package/docs/kb/why-authentication-disappeared.md +32 -0
  16. package/docs/kb/why-automation-opened-different-browser.md +32 -0
  17. package/docs/kb/why-session-viewer-cannot-control.md +31 -0
  18. package/docs/operations.md +24 -0
  19. package/docs/prompts/browser-annotate-feedback.md +41 -0
  20. package/docs/prompts/browser-diagnose-recover.md +38 -0
  21. package/docs/prompts/browser-explore.md +42 -0
  22. package/docs/prompts/browser-repro-fix.md +48 -0
  23. package/docs/prompts/browser-start.md +41 -0
  24. package/docs/prompts/browser-takeover.md +50 -0
  25. package/docs/skills/repo-work-delivery.md +5 -0
  26. package/docs/skills.md +2 -0
  27. package/docs/troubleshooting.md +22 -1
  28. package/package.json +1 -1
  29. package/plugins/kxm/.claude-plugin/plugin.json +1 -1
  30. package/plugins/kxm/dist/cli.js +2257 -439
  31. package/plugins/kxm/dist/core.js +57 -0
  32. package/plugins/kxm/dist/extension.js +40 -3
  33. package/plugins/kxm/dist/mcp-server.js +1 -1
  34. package/plugins/kxm/dist/runtime.js +572 -17
  35. package/plugins/kxm/dist/server.js +129 -4
  36. package/plugins/kxm/dist/vnext-runtime-supervisor.js +136 -13
  37. package/plugins/kxm/package.json +1 -1
  38. package/plugins/kxm/skills/hints.json +30 -0
  39. package/plugins/kxm/skills/kxm-browser-annotate/SKILL.md +90 -0
  40. package/plugins/kxm/skills/kxm-browser-auth/SKILL.md +47 -0
  41. package/plugins/kxm/skills/kxm-browser-diagnostics/SKILL.md +48 -0
  42. package/plugins/kxm/skills/kxm-browser-explore/SKILL.md +48 -0
  43. package/plugins/kxm/skills/kxm-browser-session/SKILL.md +94 -0
  44. package/plugins/kxm/skills/kxm-browser-takeover/SKILL.md +87 -0
  45. package/plugins/kxm/skills/kxm-browser-verify/SKILL.md +71 -0
  46. package/plugins/kxm/skills/kxm-hub-ops/SKILL.md +9 -0
  47. package/plugins/kxm/skills/kxm-project-setup/SKILL.md +9 -2
  48. package/plugins/kxm/src/autocomplete.ts +1 -1
  49. package/plugins/kxm/src/browser.ts +603 -0
  50. package/plugins/kxm/src/cli.ts +507 -9
  51. package/plugins/kxm/src/completion-install.ts +223 -0
  52. package/plugins/kxm/src/database.ts +1 -1
  53. package/plugins/kxm/src/external-effects.ts +1 -1
  54. package/plugins/kxm/src/hub-env.ts +193 -0
  55. package/plugins/kxm/src/init-guide-setup.ts +547 -0
  56. package/plugins/kxm/src/local-snapshot.ts +1 -1
  57. package/plugins/kxm/src/mcp-server.ts +1 -1
  58. package/plugins/kxm/src/protocol.ts +111 -0
  59. package/plugins/kxm/src/role.ts +335 -0
  60. package/plugins/kxm/src/runtime.ts +1 -0
  61. package/plugins/kxm/src/safety-integrity.ts +76 -0
  62. package/plugins/kxm/src/sqlite.ts +76 -0
  63. package/plugins/kxm/src/store.ts +1 -1
  64. package/plugins/kxm/src/vnext-bindings.ts +1 -1
  65. package/plugins/kxm/src/vnext-config.ts +38 -1
  66. package/plugins/kxm/src/vnext-engine-command.ts +2 -0
  67. package/plugins/kxm/src/vnext-engine.ts +16 -0
  68. package/plugins/kxm/src/vnext-harness.ts +4 -2
  69. package/plugins/kxm/src/vnext-oneshot-process.ts +21 -4
  70. package/plugins/kxm/src/vnext-oneshot-producer.ts +18 -0
  71. package/plugins/kxm/src/vnext-runtime-store.ts +1 -1
  72. package/plugins/kxm/src/workflow-tui.ts +1 -1
  73. package/plugins/kxm/src/workflow.ts +144 -0
  74. package/scripts/kxm-bump-version.mjs +146 -0
  75. package/scripts/kxm-hub.mjs +145 -3
  76. package/scripts/kxm-publish-npm.mjs +3 -1
  77. package/scripts/kxm-release-github.mjs +3 -1
  78. package/scripts/kxm.mjs +0 -0
@@ -15122,6 +15122,19 @@ var OUTPUT_LIMIT = 8 * 1024 * 1024;
15122
15122
  var KILL_GRACE_MS = 250;
15123
15123
  var DRAIN_GRACE_MS = 500;
15124
15124
  var REAP_GRACE_MS = 1e3;
15125
+ function killProcessTree(child, signal = "SIGKILL") {
15126
+ if (process.platform !== "win32" && child.pid) {
15127
+ try {
15128
+ process.kill(-child.pid, signal);
15129
+ return;
15130
+ } catch {
15131
+ }
15132
+ }
15133
+ try {
15134
+ child.kill(signal);
15135
+ } catch {
15136
+ }
15137
+ }
15125
15138
  function defaultSpawn(command, args, options) {
15126
15139
  if (options.signal?.aborted) {
15127
15140
  return Promise.resolve({ stdout: "", stderr: "", code: null, started: false, observedChildExit: false, error: new Error("process_aborted") });
@@ -15158,11 +15171,7 @@ function defaultSpawn(command, args, options) {
15158
15171
  resolve8({ stdout: Buffer.concat(stdout).toString("utf8"), stderr: Buffer.concat(stderr).toString("utf8"), code, signal, started, observedChildExit, terminationRequested: stopping, ...error ? { error } : {} });
15159
15172
  };
15160
15173
  const kill = (requested) => {
15161
- try {
15162
- if (process.platform !== "win32" && child.pid) process.kill(-child.pid, requested);
15163
- else child.kill(requested);
15164
- } catch {
15165
- }
15174
+ killProcessTree(child, requested);
15166
15175
  };
15167
15176
  const stop = (reason) => {
15168
15177
  if (finished) return;
@@ -15489,7 +15498,9 @@ function parseAgyOneShotUsage(stdout, _stderr) {
15489
15498
  var READ_ONLY_ONESHOT_ARGS = Object.freeze({
15490
15499
  claude: Object.freeze(["--tools", "Read,Glob,Grep", "--restricted", "--safe-mode", "--strict-mcp-config", "--mcp-config", '{"mcpServers":{}}', "--disable-slash-commands", "--no-session-persistence"]),
15491
15500
  codex: Object.freeze(["--sandbox", "read-only", "--ignore-user-config", "-c", 'approval_policy="never"']),
15492
- grok: Object.freeze(["--sandbox", "read-only", "--permission-mode", "plan", "--tools", "Read,Glob,Grep", "--no-subagents", "--disable-web-search"])
15501
+ grok: Object.freeze(["--sandbox", "read-only", "--permission-mode", "plan", "--tools", "Read,Glob,Grep", "--no-subagents", "--disable-web-search"]),
15502
+ agy: Object.freeze(["--mode", "plan", "--sandbox", "--disable-slash-commands"]),
15503
+ kimi: Object.freeze(["--plan"])
15493
15504
  });
15494
15505
  function oneShotReadOnlyArgs(harness) {
15495
15506
  return Object.hasOwn(READ_ONLY_ONESHOT_ARGS, harness) ? READ_ONLY_ONESHOT_ARGS[harness] : void 0;
@@ -15543,7 +15554,7 @@ var BUILTIN_HARNESSES = Object.freeze([
15543
15554
  authArgs: ["provider", "list"],
15544
15555
  update: { self: ["upgrade"] },
15545
15556
  oneShot: {
15546
- argv: ["--output-format", "stream-json", "-p"],
15557
+ argv: [...oneShotReadOnlyArgs("kimi"), "--output-format", "stream-json", "-p"],
15547
15558
  promptVia: "arg",
15548
15559
  outputFormat: "stream-json",
15549
15560
  usageParser: parseKimiOneShotUsage
@@ -15606,7 +15617,7 @@ var BUILTIN_HARNESSES = Object.freeze([
15606
15617
  authArgs: ["models"],
15607
15618
  update: { self: ["update"] },
15608
15619
  oneShot: {
15609
- argv: ["--output-format", "json", "-p"],
15620
+ argv: [...oneShotReadOnlyArgs("agy"), "--output-format", "json", "-p"],
15610
15621
  promptVia: "arg",
15611
15622
  outputFormat: "json",
15612
15623
  usageParser: parseAgyOneShotUsage
@@ -17060,7 +17071,7 @@ function validateModelReferences(models, issues) {
17060
17071
  };
17061
17072
  for (const id of models.keys()) walk(id, []);
17062
17073
  }
17063
- function validateBundle(project, repositories, agents, models, workflows, environments, options, gateRegistry) {
17074
+ function validateBundle(project, repositories, agents, models, workflows, environments, options, gateRegistry, projectRoot) {
17064
17075
  const issues = [];
17065
17076
  const entries = valuesOf(project.value, "repositories").map((candidate) => objectValue(candidate)).filter((candidate) => Boolean(candidate));
17066
17077
  const repositoryIds = /* @__PURE__ */ new Set();
@@ -17128,6 +17139,33 @@ function validateBundle(project, repositories, agents, models, workflows, enviro
17128
17139
  const defaultWorkflow = stringValue(project.value.defaultWorkflow) ?? "default";
17129
17140
  if (!workflows.has(defaultWorkflow)) issues.push(issue2("reference", "default_workflow_unknown", project.logicalPath, `default workflow ${defaultWorkflow} does not exist`));
17130
17141
  for (const workflow of workflows.values()) validateWorkflow(workflow, agents, models, repositoryIds, gates, issues);
17142
+ if (projectRoot) {
17143
+ const writerRolePath = join(projectRoot, ".kxm", "roles", "writer.yaml");
17144
+ const implementerAgent = agents.get("implementer") ?? agents.get("writer");
17145
+ if (existsSync2(writerRolePath) && implementerAgent) {
17146
+ try {
17147
+ const rawRole = parseRestrictedYaml2(readFileSync(writerRolePath, "utf8"));
17148
+ const roleObj = objectValue(rawRole);
17149
+ const rosterEntries = valuesOf(roleObj ?? {}, "roster").map((candidate) => objectValue(candidate)).filter((entry) => Boolean(entry));
17150
+ const enabledRosterModels = rosterEntries.filter((entry) => entry.enabled !== false).map((entry) => stringValue(entry.model)).filter((m) => Boolean(m));
17151
+ const agentModelObj = objectValue(implementerAgent.value.model);
17152
+ const agentModelStr = stringValue(implementerAgent.value.model);
17153
+ const agentProvider = agentModelObj ? stringValue(agentModelObj.provider) : void 0;
17154
+ const agentModel = agentModelObj ? stringValue(agentModelObj.model) : agentModelStr;
17155
+ const canonicalAgentModel = agentProvider && agentModel ? `${agentProvider}/${agentModel}` : agentModel;
17156
+ if (enabledRosterModels.length > 0 && canonicalAgentModel) {
17157
+ const matches = enabledRosterModels.some((rm) => rm === canonicalAgentModel || rm === agentModel || rm.endsWith(`/${agentModel}`));
17158
+ if (!matches) {
17159
+ issues.push(issue2("semantic", "role_roster_conflicts_with_agent", ".kxm/roles/writer.yaml", `role roster in .kxm/roles/writer.yaml does not include agent model ${canonicalAgentModel} from ${implementerAgent.logicalPath}`));
17160
+ }
17161
+ }
17162
+ } catch (error) {
17163
+ if (error instanceof VnextConfigError) {
17164
+ issues.push(...error.issues);
17165
+ }
17166
+ }
17167
+ }
17168
+ }
17131
17169
  return sortIssues2(issues);
17132
17170
  }
17133
17171
  function vnextCanonicalJson(value) {
@@ -17308,7 +17346,7 @@ function loadVnextProject(projectRoot, options = {}) {
17308
17346
  }
17309
17347
  }
17310
17348
  if (loadIssues.length > 0) throw new VnextConfigError(loadIssues);
17311
- const issues = validateBundle(project, repositories, agents, models, workflows, environments, options, gateRegistry);
17349
+ const issues = validateBundle(project, repositories, agents, models, workflows, environments, options, gateRegistry, root);
17312
17350
  if (issues.length > 0) throw new VnextConfigError(issues);
17313
17351
  const resources = [project, ...repositories.values(), ...agents.values(), ...models.values(), ...workflows.values(), ...environments, ...gateRegistry ? [gateRegistry] : []].sort((left, right) => compareCodeUnits3(left.logicalPath, right.logicalPath));
17314
17352
  return {
@@ -17420,7 +17458,47 @@ import { dirname as dirname4, join as join4, resolve as resolve4 } from "node:pa
17420
17458
 
17421
17459
  // plugins/kxm/src/vnext-bindings.ts
17422
17460
  import { homedir } from "node:os";
17423
- import { DatabaseSync } from "node:sqlite";
17461
+
17462
+ // plugins/kxm/src/sqlite.ts
17463
+ import { createRequire } from "node:module";
17464
+ var requireFromHere = createRequire(import.meta.url);
17465
+ function loadNative() {
17466
+ try {
17467
+ const mod = requireFromHere("node:sqlite");
17468
+ if (mod.DatabaseSync) return { Ctor: mod.DatabaseSync, bun: false };
17469
+ } catch {
17470
+ }
17471
+ try {
17472
+ const mod = requireFromHere("bun:sqlite");
17473
+ const Ctor = mod?.DatabaseSync ?? mod?.Database;
17474
+ if (Ctor) return { Ctor, bun: true };
17475
+ } catch {
17476
+ }
17477
+ throw new Error("kxm: no supported sqlite module found (need node:sqlite or bun:sqlite)");
17478
+ }
17479
+ var native = loadNative();
17480
+ var DatabaseSync = class {
17481
+ inner;
17482
+ constructor(path, options) {
17483
+ let normalized = options;
17484
+ if (native.bun && options) {
17485
+ const { readOnly, ...rest } = options;
17486
+ normalized = readOnly === void 0 ? rest : { ...rest, readonly: readOnly };
17487
+ }
17488
+ this.inner = normalized === void 0 ? new native.Ctor(path) : new native.Ctor(path, normalized);
17489
+ }
17490
+ prepare(sql) {
17491
+ return this.inner.prepare(sql);
17492
+ }
17493
+ exec(sql) {
17494
+ return this.inner.exec(sql);
17495
+ }
17496
+ close() {
17497
+ this.inner.close();
17498
+ }
17499
+ };
17500
+
17501
+ // plugins/kxm/src/vnext-bindings.ts
17424
17502
  import { dirname as dirname2, isAbsolute as isAbsolute2, join as join2, parse, relative as relative2, resolve as resolve2, sep as sep2 } from "node:path";
17425
17503
  var MAX_BINDING_RECORD_BYTES = 256 * 1024;
17426
17504
  var BINDING_LABEL = "Runtime-local repository bindings";
@@ -17464,7 +17542,6 @@ import {
17464
17542
  writeFileSync
17465
17543
  } from "node:fs";
17466
17544
  import { basename as basename2, dirname as dirname3, join as join3, resolve as resolve3 } from "node:path";
17467
- import { DatabaseSync as DatabaseSync2 } from "node:sqlite";
17468
17545
  function databaseError(code, file, message) {
17469
17546
  const issue3 = { phase: "semantic", code, file, message };
17470
17547
  return new VnextConfigError([issue3]);
@@ -17544,7 +17621,7 @@ function openDatabase(file, description, spec) {
17544
17621
  }
17545
17622
  }
17546
17623
  }
17547
- const database = new DatabaseSync2(file);
17624
+ const database = new DatabaseSync(file);
17548
17625
  let transaction = false;
17549
17626
  try {
17550
17627
  database.exec(`PRAGMA busy_timeout = ${spec.timeoutMs ?? 5e3}`);
@@ -17648,7 +17725,7 @@ function backupDatabaseFile(sourcePath, targetPath, storeId) {
17648
17725
  if (existsSync3(resolvedTarget)) {
17649
17726
  unlinkSync(resolvedTarget);
17650
17727
  }
17651
- const sourceDb = new DatabaseSync2(resolvedSource);
17728
+ const sourceDb = new DatabaseSync(resolvedSource);
17652
17729
  let schemaVersion = 0;
17653
17730
  try {
17654
17731
  sourceDb.exec("PRAGMA busy_timeout = 5000");
@@ -17663,7 +17740,7 @@ function backupDatabaseFile(sourcePath, targetPath, storeId) {
17663
17740
  } finally {
17664
17741
  sourceDb.close();
17665
17742
  }
17666
- const targetDb = new DatabaseSync2(resolvedTarget);
17743
+ const targetDb = new DatabaseSync(resolvedTarget);
17667
17744
  try {
17668
17745
  targetDb.exec("PRAGMA busy_timeout = 5000");
17669
17746
  if (!checkIntegrity(targetDb)) {
@@ -17695,7 +17772,7 @@ function restoreDatabaseFile(backupPath, targetPath, storeId, expectedSchemaVers
17695
17772
  if (!backupStat || !backupStat.isFile() || backupStat.isSymbolicLink()) {
17696
17773
  throw databaseError("runtime_path_invalid", resolvedBackup, `backup database ${resolvedBackup} must be a regular file, not a link or directory`);
17697
17774
  }
17698
- const backupDb = new DatabaseSync2(resolvedBackup);
17775
+ const backupDb = new DatabaseSync(resolvedBackup);
17699
17776
  let schemaVersion = 0;
17700
17777
  try {
17701
17778
  backupDb.exec("PRAGMA busy_timeout = 5000");
@@ -17735,7 +17812,7 @@ function restoreDatabaseFile(backupPath, targetPath, storeId, expectedSchemaVers
17735
17812
  chmodSync(resolvedTarget, 384);
17736
17813
  } catch {
17737
17814
  }
17738
- const targetDb = new DatabaseSync2(resolvedTarget);
17815
+ const targetDb = new DatabaseSync(resolvedTarget);
17739
17816
  try {
17740
17817
  targetDb.exec("PRAGMA busy_timeout = 5000");
17741
17818
  if (!checkIntegrity(targetDb)) {
@@ -22912,6 +22989,32 @@ function evaluateArtifactsGate(context, definition) {
22912
22989
  // plugins/kxm/src/vnext-engine-command.ts
22913
22990
  import { spawn as spawn2 } from "node:child_process";
22914
22991
  import { createHash as createHash9 } from "node:crypto";
22992
+
22993
+ // plugins/kxm/src/safety-integrity.ts
22994
+ var DESTRUCTIVE_COMMAND_PATTERNS = Object.freeze([
22995
+ /^\s*rm\s+.*-[a-zA-Z]*r[a-zA-Z]*f/i,
22996
+ /^\s*rm\s+.*-[a-zA-Z]*f[a-zA-Z]*r/i,
22997
+ /^\s*rm\s+.*(-[a-zA-Z]*r[a-zA-Z]*\s+.*-[a-zA-Z]*f[a-zA-Z]*|-[a-zA-Z]*f[a-zA-Z]*\s+.*-[a-zA-Z]*r[a-zA-Z]*)/i,
22998
+ /^\s*git\s+reset\s+--hard/i,
22999
+ /^\s*git\s+clean\s+-[a-zA-Z]*f/i,
23000
+ /^\s*git\s+checkout\s+--\s+/i,
23001
+ /^\s*git\s+restore\s+(\.|\*|--staged\s+(\.|\*))/i
23002
+ ]);
23003
+ function assertCommandSeatbelt(command) {
23004
+ for (const pattern of DESTRUCTIVE_COMMAND_PATTERNS) {
23005
+ if (pattern.test(command)) {
23006
+ throw new Error(
23007
+ `Command blocked by KXM safety seatbelt: "${command}". Destructive workspace mutations require explicit operator override.`
23008
+ );
23009
+ }
23010
+ }
23011
+ }
23012
+ var INSECURE_SSH_HOST_KEY_PATTERNS = Object.freeze([
23013
+ /StrictHostKeyChecking=(accept-new|no|off)/i,
23014
+ /UserKnownHostsFile=\/dev\/null/i
23015
+ ]);
23016
+
23017
+ // plugins/kxm/src/vnext-engine-command.ts
22915
23018
  var MAX_DIRECT_TIMER_MS = 2147483647;
22916
23019
  var COMMAND_TERM_GRACE_MS = 2e3;
22917
23020
  var COMMAND_FINAL_WAIT_MS = 2e3;
@@ -23028,6 +23131,7 @@ var CommandObserver = class {
23028
23131
  if (!Number.isInteger(this.definition.timeoutMs) || this.definition.timeoutMs < 1 || this.definition.timeoutMs > MAX_DIRECT_TIMER_MS) {
23029
23132
  throw runtimeError("run_events_illegal", "command", "command timeoutMs exceeds the direct timer bound");
23030
23133
  }
23134
+ assertCommandSeatbelt(this.definition.argv.join(" "));
23031
23135
  this.startedAt = (/* @__PURE__ */ new Date()).toISOString();
23032
23136
  let child;
23033
23137
  try {
@@ -24153,6 +24257,7 @@ async function stepLocked(context, runId, producer, token) {
24153
24257
  return drivePanel(context, prepared.panel, producer);
24154
24258
  }
24155
24259
  async function runPreparedCommandGate(context, prepared, definition, token) {
24260
+ assertCommandSeatbelt(definition.argv.join(" "));
24156
24261
  const storePath = context.eventStore.path;
24157
24262
  const observer = createCommandObserver({
24158
24263
  definition,
@@ -25930,6 +26035,24 @@ function createVnextOneShotProducer(options = {}) {
25930
26035
  "--output-format",
25931
26036
  "json"
25932
26037
  ];
26038
+ } else if (harness === "agy") {
26039
+ args = [
26040
+ "-m",
26041
+ resolved.model,
26042
+ ...permissionArgs,
26043
+ "--output-format",
26044
+ "json",
26045
+ "-p"
26046
+ ];
26047
+ } else if (harness === "kimi") {
26048
+ args = [
26049
+ "-m",
26050
+ resolved.model,
26051
+ ...permissionArgs,
26052
+ "--output-format",
26053
+ "stream-json",
26054
+ "-p"
26055
+ ];
25933
26056
  } else {
25934
26057
  throw new Error(`oneshot_harness_unsupported: ${harness} permission_profile_unaudited`);
25935
26058
  }
@@ -27627,6 +27750,429 @@ function evaluatePromotionPolicy(candidate, policy = "manual_pr", options) {
27627
27750
  reason: `Unknown promotion policy: ${String(policy)}`
27628
27751
  };
27629
27752
  }
27753
+
27754
+ // plugins/kxm/src/browser.ts
27755
+ import { execSync } from "node:child_process";
27756
+ var VIEWPORT_PRESETS = Object.freeze({
27757
+ "mobile-sm": { name: "Mobile Small (SE)", category: "mobile", width: 375, height: 667, isMobile: true, hasTouch: true },
27758
+ "mobile": { name: "Mobile (iPhone 16 / 15 Pro)", category: "mobile", width: 393, height: 852, isMobile: true, hasTouch: true },
27759
+ "mobile-lg": { name: "Mobile Large (Pro Max)", category: "mobile", width: 430, height: 932, isMobile: true, hasTouch: true },
27760
+ "pixel": { name: "Google Pixel 8/9", category: "mobile", width: 412, height: 924, isMobile: true, hasTouch: true },
27761
+ "galaxy": { name: "Samsung Galaxy S24", category: "mobile", width: 360, height: 780, isMobile: true, hasTouch: true },
27762
+ "tablet": { name: "Tablet (iPad Air / Mini)", category: "tablet", width: 820, height: 1180, isMobile: true, hasTouch: true },
27763
+ "tablet-lg": { name: "Tablet Large (iPad Pro 12.9)", category: "tablet", width: 1024, height: 1366, isMobile: true, hasTouch: true },
27764
+ "laptop": { name: "Standard Laptop", category: "laptop", width: 1366, height: 768 },
27765
+ "macbook-13": { name: "MacBook Air 13", category: "laptop", width: 1440, height: 900 },
27766
+ "macbook-16": { name: "MacBook Pro 16", category: "laptop", width: 1728, height: 1117 },
27767
+ "desktop": { name: "Desktop FHD (1080p)", category: "desktop", width: 1920, height: 1080 },
27768
+ "desktop-2k": { name: "Desktop QHD (1440p)", category: "desktop", width: 2560, height: 1440 },
27769
+ "desktop-4k": { name: "Desktop 4K UHD", category: "desktop", width: 3840, height: 2160 }
27770
+ });
27771
+ function resolveViewportDimensions(presetOrDims) {
27772
+ if (!presetOrDims) {
27773
+ return { width: 1920, height: 1080 };
27774
+ }
27775
+ if (typeof presetOrDims === "string") {
27776
+ const matched = VIEWPORT_PRESETS[presetOrDims.toLowerCase()];
27777
+ if (matched) {
27778
+ return { width: matched.width, height: matched.height };
27779
+ }
27780
+ return { width: 1920, height: 1080 };
27781
+ }
27782
+ return presetOrDims;
27783
+ }
27784
+ function resolvePassCliApiKey(execFn = (cmd) => execSync(cmd, { encoding: "utf8", stdio: ["pipe", "pipe", "ignore"], timeout: 5e3 })) {
27785
+ if (typeof process === "undefined" || process.env.USE_PASS_CLI === "false") {
27786
+ return void 0;
27787
+ }
27788
+ if (process.env.PASS_CLI_OUTPUT_MOCK) {
27789
+ try {
27790
+ const parsed = JSON.parse(process.env.PASS_CLI_OUTPUT_MOCK);
27791
+ const extraFields = parsed?.item?.content?.extra_fields || [];
27792
+ const customSections = parsed?.item?.content?.content?.Custom?.sections || [];
27793
+ const sectionFields = customSections.flatMap((s) => s.section_fields || []);
27794
+ const allFields = [...extraFields, ...sectionFields];
27795
+ return allFields.find((f) => f.name === "STEEL_API_KEY")?.content?.Hidden;
27796
+ } catch {
27797
+ return void 0;
27798
+ }
27799
+ }
27800
+ try {
27801
+ const output = execFn(
27802
+ 'pass-cli item view --vault-name "AI Provider Keys" --item-title "Steel Browser (KontextMind DOKS)" --output json'
27803
+ );
27804
+ const parsed = JSON.parse(output);
27805
+ const extraFields = parsed?.item?.content?.extra_fields || [];
27806
+ const customSections = parsed?.item?.content?.content?.Custom?.sections || [];
27807
+ const sectionFields = customSections.flatMap((s) => s.section_fields || []);
27808
+ const allFields = [...extraFields, ...sectionFields];
27809
+ return allFields.find((f) => f.name === "STEEL_API_KEY")?.content?.Hidden;
27810
+ } catch {
27811
+ return void 0;
27812
+ }
27813
+ }
27814
+ function resolveSteelConfig(overrides) {
27815
+ const apiUrl = overrides?.apiUrl || process.env.STEEL_API_URL || "https://steel.kontextmind.com";
27816
+ const apiKey = overrides?.apiKey || process.env.STEEL_API_KEY || resolvePassCliApiKey();
27817
+ const uiUrl = overrides?.uiUrl || (overrides?.apiUrl ? `${overrides.apiUrl.replace(/\/$/, "")}/ui` : void 0) || process.env.STEEL_UI_URL || `${apiUrl.replace(/\/$/, "")}/ui`;
27818
+ return {
27819
+ apiUrl: apiUrl.replace(/\/$/, ""),
27820
+ apiKey,
27821
+ uiUrl,
27822
+ timeoutMs: overrides?.timeoutMs || 3e5
27823
+ // 5 minutes default
27824
+ };
27825
+ }
27826
+ function formatCDPEndpoint(session, config) {
27827
+ const baseApi = config.apiUrl;
27828
+ const urlObj = new URL(baseApi);
27829
+ const isSecure = urlObj.protocol === "https:";
27830
+ const wsProtocol = isSecure ? "wss:" : "ws:";
27831
+ const host = urlObj.host;
27832
+ const searchParams = new URLSearchParams();
27833
+ searchParams.set("sessionId", session.id);
27834
+ if (config.apiKey) {
27835
+ searchParams.set("apiKey", config.apiKey);
27836
+ }
27837
+ return `${wsProtocol}//${host}/v1/devtools?${searchParams.toString()}`;
27838
+ }
27839
+ function sanitizeLogOutput(input) {
27840
+ if (typeof input === "string") {
27841
+ return input.replace(/apiKey=[^&]+/g, "apiKey=[REDACTED]").replace(/steel_[a-f0-9]+/g, "steel_[REDACTED]");
27842
+ }
27843
+ if (Array.isArray(input)) {
27844
+ return input.map(sanitizeLogOutput);
27845
+ }
27846
+ if (input !== null && typeof input === "object") {
27847
+ const copy = {};
27848
+ for (const [k, v] of Object.entries(input)) {
27849
+ if (/key|secret|token|auth|password/i.test(k) && typeof v === "string") {
27850
+ copy[k] = "[REDACTED]";
27851
+ } else {
27852
+ copy[k] = sanitizeLogOutput(v);
27853
+ }
27854
+ }
27855
+ return copy;
27856
+ }
27857
+ return input;
27858
+ }
27859
+ function createAnnotationFeedback(feedback) {
27860
+ return {
27861
+ ...feedback,
27862
+ capturedAt: feedback.capturedAt || (/* @__PURE__ */ new Date()).toISOString()
27863
+ };
27864
+ }
27865
+ function formatAnnotationFeedbackPrompt(feedback) {
27866
+ let output = `## Visual Feedback & Section Annotation
27867
+
27868
+ `;
27869
+ output += `- **Target URL**: ${feedback.url}
27870
+ `;
27871
+ if (feedback.sessionId) {
27872
+ output += `- **Session ID**: ${feedback.sessionId}
27873
+ `;
27874
+ }
27875
+ if (feedback.sectionSelector) {
27876
+ output += `- **Section Target Selector**: \`${feedback.sectionSelector}\`
27877
+ `;
27878
+ }
27879
+ if (feedback.screenshotPath) {
27880
+ output += `- **Screenshot Artifact**: \`${feedback.screenshotPath}\`
27881
+ `;
27882
+ }
27883
+ output += `- **Captured At**: ${feedback.capturedAt}
27884
+
27885
+ `;
27886
+ output += `### Summary
27887
+ ${feedback.overallSummary}
27888
+
27889
+ `;
27890
+ if (feedback.annotations.length > 0) {
27891
+ output += `### Annotated Elements & Notes
27892
+
27893
+ `;
27894
+ feedback.annotations.forEach((item, idx) => {
27895
+ output += `${idx + 1}. **${item.label}**`;
27896
+ if (item.severity) {
27897
+ output += ` [${item.severity.toUpperCase()}]`;
27898
+ }
27899
+ output += `
27900
+ - **Note**: ${item.note}
27901
+ `;
27902
+ if (item.selector) {
27903
+ output += ` - **Selector**: \`${item.selector}\`
27904
+ `;
27905
+ }
27906
+ if (item.boundingBox) {
27907
+ output += ` - **Region (Box)**: x=${item.boundingBox.x}, y=${item.boundingBox.y}, w=${item.boundingBox.width}, h=${item.boundingBox.height}
27908
+ `;
27909
+ }
27910
+ });
27911
+ output += `
27912
+ `;
27913
+ }
27914
+ if (feedback.requestedChanges.length > 0) {
27915
+ output += `### Actionable Change List
27916
+
27917
+ `;
27918
+ feedback.requestedChanges.forEach((change, idx) => {
27919
+ output += `- [ ] ${change}
27920
+ `;
27921
+ });
27922
+ }
27923
+ return output;
27924
+ }
27925
+ var SteelClient = class {
27926
+ config;
27927
+ activeSessions = /* @__PURE__ */ new Map();
27928
+ constructor(config) {
27929
+ this.config = resolveSteelConfig(config);
27930
+ }
27931
+ getConfig() {
27932
+ return { ...this.config };
27933
+ }
27934
+ headers() {
27935
+ const h = {
27936
+ "Content-Type": "application/json"
27937
+ };
27938
+ if (this.config.apiKey) {
27939
+ h["x-steel-api-key"] = this.config.apiKey;
27940
+ }
27941
+ return h;
27942
+ }
27943
+ /**
27944
+ * Launch a new Steel browser session on DOKS.
27945
+ */
27946
+ async createSession(options) {
27947
+ const timeoutMs = options?.timeoutMs ?? this.config.timeoutMs ?? 3e5;
27948
+ const body = {
27949
+ timeout: timeoutMs
27950
+ };
27951
+ const dimensions = options?.dimensions || (options?.viewportPreset ? resolveViewportDimensions(options.viewportPreset) : void 0);
27952
+ if (dimensions) {
27953
+ body.dimensions = dimensions;
27954
+ }
27955
+ if (options?.userAgent) {
27956
+ body.userAgent = options.userAgent;
27957
+ }
27958
+ if (options?.proxy) {
27959
+ body.proxy = options.proxy;
27960
+ }
27961
+ const res = await fetch(`${this.config.apiUrl}/v1/sessions`, {
27962
+ method: "POST",
27963
+ headers: this.headers(),
27964
+ body: JSON.stringify(body)
27965
+ });
27966
+ if (!res.ok) {
27967
+ const errText = await res.text();
27968
+ throw new Error(`Failed to create Steel session (${res.status}): ${errText}`);
27969
+ }
27970
+ const data = await res.json();
27971
+ const session = {
27972
+ id: data.id,
27973
+ createdAt: data.createdAt || (/* @__PURE__ */ new Date()).toISOString(),
27974
+ status: "live",
27975
+ state: "AGENT_CONTROL",
27976
+ websocketUrl: data.websocketUrl || "",
27977
+ debugUrl: data.debugUrl || "",
27978
+ debuggerUrl: data.debuggerUrl || "",
27979
+ sessionViewerUrl: data.sessionViewerUrl || `${this.config.uiUrl}?sessionId=${data.id}`,
27980
+ timeoutMs,
27981
+ lastActiveAt: Date.now(),
27982
+ activeController: "agent",
27983
+ userAgent: data.userAgent
27984
+ };
27985
+ this.activeSessions.set(session.id, session);
27986
+ return session;
27987
+ }
27988
+ /**
27989
+ * Get details of an existing session.
27990
+ */
27991
+ async getSession(sessionId) {
27992
+ const res = await fetch(`${this.config.apiUrl}/v1/sessions/${encodeURIComponent(sessionId)}`, {
27993
+ method: "GET",
27994
+ headers: this.headers()
27995
+ });
27996
+ if (res.status === 404) {
27997
+ const cached = this.activeSessions.get(sessionId);
27998
+ if (cached) {
27999
+ cached.state = "EXPIRED";
28000
+ cached.status = "released";
28001
+ cached.activeController = "none";
28002
+ }
28003
+ return null;
28004
+ }
28005
+ if (!res.ok) {
28006
+ throw new Error(`Failed to fetch Steel session (${res.status})`);
28007
+ }
28008
+ const data = await res.json();
28009
+ const existing = this.activeSessions.get(sessionId);
28010
+ const session = {
28011
+ id: data.id,
28012
+ createdAt: data.createdAt,
28013
+ status: data.status,
28014
+ state: existing?.state ?? (data.status === "live" ? "AGENT_CONTROL" : "RELEASED"),
28015
+ websocketUrl: data.websocketUrl || existing?.websocketUrl || "",
28016
+ debugUrl: data.debugUrl || existing?.debugUrl || "",
28017
+ debuggerUrl: data.debuggerUrl || existing?.debuggerUrl || "",
28018
+ sessionViewerUrl: existing?.sessionViewerUrl || `${this.config.uiUrl}?sessionId=${data.id}`,
28019
+ timeoutMs: data.timeout || existing?.timeoutMs || 3e5,
28020
+ lastActiveAt: Date.now(),
28021
+ activeController: existing?.activeController ?? (data.status === "live" ? "agent" : "none"),
28022
+ userAgent: data.userAgent
28023
+ };
28024
+ this.activeSessions.set(session.id, session);
28025
+ return session;
28026
+ }
28027
+ /**
28028
+ * Request human takeover for MFA, login, or consent.
28029
+ * Pauses agent automation and sets state to HUMAN_CONTROL.
28030
+ */
28031
+ requestHumanTakeover(sessionId, reason) {
28032
+ const session = this.activeSessions.get(sessionId);
28033
+ if (!session) {
28034
+ throw new Error(`Session ${sessionId} not tracked or already released`);
28035
+ }
28036
+ if (session.state === "RELEASED" || session.state === "EXPIRED" || session.state === "FAILED") {
28037
+ throw new Error(`Cannot initiate takeover on session in state ${session.state}`);
28038
+ }
28039
+ session.state = "HUMAN_CONTROL";
28040
+ session.activeController = "human";
28041
+ session.takeoverReason = reason;
28042
+ session.lastActiveAt = Date.now();
28043
+ const takeoverUrl = `${this.config.uiUrl}?sessionId=${encodeURIComponent(sessionId)}`;
28044
+ const instructions = `[HUMAN TAKEOVER REQUIRED]
28045
+ Reason: ${reason}
28046
+ Session ID: ${session.id}
28047
+ Takeover URL: ${takeoverUrl}
28048
+
28049
+ Instructions for Operator:
28050
+ 1. Open the URL above to access the session UI.
28051
+ 2. Perform the required authentication / MFA / consent action.
28052
+ 3. Return to the terminal and signal completion. Automation is paused until you confirm.`;
28053
+ return {
28054
+ session,
28055
+ takeoverUrl,
28056
+ instructions
28057
+ };
28058
+ }
28059
+ /**
28060
+ * Signal that human takeover is complete.
28061
+ * Moves state to VERIFY_AUTHENTICATION before transitioning back to AGENT_CONTROL.
28062
+ */
28063
+ signalHumanComplete(sessionId) {
28064
+ const session = this.activeSessions.get(sessionId);
28065
+ if (!session) {
28066
+ throw new Error(`Session ${sessionId} not tracked or already released`);
28067
+ }
28068
+ if (session.state !== "HUMAN_CONTROL") {
28069
+ throw new Error(`Session ${sessionId} is not in HUMAN_CONTROL state (currently ${session.state})`);
28070
+ }
28071
+ session.state = "VERIFY_AUTHENTICATION";
28072
+ session.activeController = "agent";
28073
+ session.lastActiveAt = Date.now();
28074
+ return {
28075
+ session,
28076
+ state: session.state
28077
+ };
28078
+ }
28079
+ /**
28080
+ * Confirm authentication verification passed and restore AGENT_CONTROL.
28081
+ */
28082
+ confirmAuthenticationVerified(sessionId) {
28083
+ const session = this.activeSessions.get(sessionId);
28084
+ if (!session) {
28085
+ throw new Error(`Session ${sessionId} not tracked or already released`);
28086
+ }
28087
+ if (session.state !== "VERIFY_AUTHENTICATION") {
28088
+ throw new Error(`Session ${sessionId} is not in VERIFY_AUTHENTICATION state (currently ${session.state})`);
28089
+ }
28090
+ session.state = "AGENT_CONTROL";
28091
+ session.activeController = "agent";
28092
+ session.takeoverReason = void 0;
28093
+ session.lastActiveAt = Date.now();
28094
+ return session;
28095
+ }
28096
+ /**
28097
+ * Gracefully release a session.
28098
+ */
28099
+ async releaseSession(sessionId) {
28100
+ try {
28101
+ const res = await fetch(`${this.config.apiUrl}/v1/sessions/${encodeURIComponent(sessionId)}/release`, {
28102
+ method: "POST",
28103
+ headers: this.headers()
28104
+ });
28105
+ const session = this.activeSessions.get(sessionId);
28106
+ if (session) {
28107
+ session.status = "released";
28108
+ session.state = "RELEASED";
28109
+ session.activeController = "none";
28110
+ }
28111
+ this.activeSessions.delete(sessionId);
28112
+ return res.ok;
28113
+ } catch {
28114
+ this.activeSessions.delete(sessionId);
28115
+ return false;
28116
+ }
28117
+ }
28118
+ /**
28119
+ * Perform a direct stateless scrape without manual session management.
28120
+ */
28121
+ async scrape(url) {
28122
+ const res = await fetch(`${this.config.apiUrl}/v1/scrape`, {
28123
+ method: "POST",
28124
+ headers: this.headers(),
28125
+ body: JSON.stringify({ url })
28126
+ });
28127
+ if (!res.ok) {
28128
+ const err = await res.text();
28129
+ throw new Error(`Scrape failed (${res.status}): ${err}`);
28130
+ }
28131
+ return res.json();
28132
+ }
28133
+ /**
28134
+ * Perform a direct screenshot action.
28135
+ */
28136
+ async screenshot(url, fullPage = false) {
28137
+ const res = await fetch(`${this.config.apiUrl}/v1/screenshot`, {
28138
+ method: "POST",
28139
+ headers: this.headers(),
28140
+ body: JSON.stringify({ url, fullPage })
28141
+ });
28142
+ if (!res.ok) {
28143
+ const err = await res.text();
28144
+ throw new Error(`Screenshot failed (${res.status}): ${err}`);
28145
+ }
28146
+ return res.json();
28147
+ }
28148
+ /**
28149
+ * Detect and list orphaned or timed-out active sessions.
28150
+ */
28151
+ async checkOrphanedSessions(maxIdleMs = 6e5) {
28152
+ const res = await fetch(`${this.config.apiUrl}/v1/sessions`, {
28153
+ method: "GET",
28154
+ headers: this.headers()
28155
+ });
28156
+ if (!res.ok) {
28157
+ return [];
28158
+ }
28159
+ const data = await res.json();
28160
+ const remoteSessions = data.sessions || [];
28161
+ const now = Date.now();
28162
+ const orphaned = [];
28163
+ for (const rs of remoteSessions) {
28164
+ if (rs.status === "live" || rs.status === "idle") {
28165
+ const tracked = this.activeSessions.get(rs.id);
28166
+ if (!tracked && rs.duration > maxIdleMs) {
28167
+ orphaned.push(rs.id);
28168
+ } else if (tracked && now - tracked.lastActiveAt > maxIdleMs && tracked.state !== "HUMAN_CONTROL") {
28169
+ orphaned.push(rs.id);
28170
+ }
28171
+ }
28172
+ }
28173
+ return orphaned;
28174
+ }
28175
+ };
27630
28176
  export {
27631
28177
  BUILTIN_HARNESSES,
27632
28178
  BUILTIN_HARNESS_IDS,
@@ -27642,6 +28188,8 @@ export {
27642
28188
  PI_NATIVE_BRAKE_PROVIDERS,
27643
28189
  PiSession,
27644
28190
  SAFE_HARNESS_COMMAND_ID,
28191
+ SteelClient,
28192
+ VIEWPORT_PRESETS,
27645
28193
  VNEXT_ABSENT_MEMORY_REVISION,
27646
28194
  VNEXT_EVENT_STORE_SCHEMA_VERSION,
27647
28195
  VNEXT_REGISTRY_SCHEMA_VERSION,
@@ -27663,6 +28211,7 @@ export {
27663
28211
  closeVnextRuntimeContext,
27664
28212
  computeGateEvidenceOutcome,
27665
28213
  computeVnextMemoryRevision,
28214
+ createAnnotationFeedback,
27666
28215
  createBackup,
27667
28216
  createLogger,
27668
28217
  createVnextOneShotProducer,
@@ -27677,6 +28226,8 @@ export {
27677
28226
  fileSha256,
27678
28227
  findWinNpmInnerExe,
27679
28228
  foldStoredVnextRun,
28229
+ formatAnnotationFeedbackPrompt,
28230
+ formatCDPEndpoint,
27680
28231
  formatHarnessInventory,
27681
28232
  formatHarnessUpdate,
27682
28233
  formatImprovementReport,
@@ -27721,11 +28272,15 @@ export {
27721
28272
  redactLogValue,
27722
28273
  registerVnextRuntimeCloseHook,
27723
28274
  resolveDispatchStatus,
28275
+ resolvePassCliApiKey,
28276
+ resolveSteelConfig,
28277
+ resolveViewportDimensions,
27724
28278
  restoreBackup,
27725
28279
  restoreDatabaseFile,
27726
28280
  rotateLogFiles,
27727
28281
  runHarnessUpdate,
27728
28282
  runtimeError,
28283
+ sanitizeLogOutput,
27729
28284
  startVnextRuntimeSupervisor,
27730
28285
  tableColumns,
27731
28286
  userTables,