@kontextmind/kxm 0.7.4 → 0.7.6

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 (40) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/docs/README.md +1 -0
  3. package/docs/adr/ADR-0002-browser-automation-steel-doks.md +103 -0
  4. package/docs/agent-skills.md +14 -0
  5. package/docs/browser-automation.md +116 -0
  6. package/docs/kb/how-credentials-retrieved-safely.md +31 -0
  7. package/docs/kb/how-to-capture-and-annotate-section.md +60 -0
  8. package/docs/kb/how-to-connect-playwright-to-steel.md +54 -0
  9. package/docs/kb/how-to-recover-expired-session-or-orphan.md +54 -0
  10. package/docs/kb/how-to-resume-after-mfa.md +28 -0
  11. package/docs/kb/how-to-take-over-session.md +32 -0
  12. package/docs/kb/why-authentication-disappeared.md +32 -0
  13. package/docs/kb/why-automation-opened-different-browser.md +32 -0
  14. package/docs/kb/why-session-viewer-cannot-control.md +31 -0
  15. package/docs/prompts/browser-annotate-feedback.md +41 -0
  16. package/docs/prompts/browser-diagnose-recover.md +38 -0
  17. package/docs/prompts/browser-explore.md +42 -0
  18. package/docs/prompts/browser-repro-fix.md +48 -0
  19. package/docs/prompts/browser-start.md +41 -0
  20. package/docs/prompts/browser-takeover.md +50 -0
  21. package/package.json +1 -1
  22. package/plugins/kxm/.claude-plugin/plugin.json +1 -1
  23. package/plugins/kxm/dist/cli.js +421 -155
  24. package/plugins/kxm/dist/mcp-server.js +1 -1
  25. package/plugins/kxm/dist/runtime.js +699 -16
  26. package/plugins/kxm/package.json +1 -1
  27. package/plugins/kxm/skills/hints.json +30 -0
  28. package/plugins/kxm/skills/kxm-browser-annotate/SKILL.md +90 -0
  29. package/plugins/kxm/skills/kxm-browser-auth/SKILL.md +47 -0
  30. package/plugins/kxm/skills/kxm-browser-diagnostics/SKILL.md +48 -0
  31. package/plugins/kxm/skills/kxm-browser-explore/SKILL.md +48 -0
  32. package/plugins/kxm/skills/kxm-browser-session/SKILL.md +94 -0
  33. package/plugins/kxm/skills/kxm-browser-takeover/SKILL.md +87 -0
  34. package/plugins/kxm/skills/kxm-browser-verify/SKILL.md +71 -0
  35. package/plugins/kxm/src/browser.ts +603 -0
  36. package/plugins/kxm/src/cli.ts +39 -0
  37. package/plugins/kxm/src/mcp-server.ts +1 -1
  38. package/plugins/kxm/src/modes.ts +348 -0
  39. package/plugins/kxm/src/runtime.ts +2 -0
  40. package/schemas/vnext/modes.schema.json +56 -0
@@ -3834,7 +3834,7 @@ var require_fast_uri = __commonJS({
3834
3834
  normalizeString(uri, options);
3835
3835
  } else if (typeof uri === "object") {
3836
3836
  uri = /** @type {T} */
3837
- parse5(serialize(uri, options), options);
3837
+ parse6(serialize(uri, options), options);
3838
3838
  }
3839
3839
  return uri;
3840
3840
  }
@@ -3874,8 +3874,8 @@ var require_fast_uri = __commonJS({
3874
3874
  function resolveComponent(base, relative5, options, skipNormalization) {
3875
3875
  const target = {};
3876
3876
  if (!skipNormalization) {
3877
- base = parse5(serialize(base, options), options);
3878
- relative5 = parse5(serialize(relative5, options), options);
3877
+ base = parse6(serialize(base, options), options);
3878
+ relative5 = parse6(serialize(relative5, options), options);
3879
3879
  }
3880
3880
  options = options || {};
3881
3881
  if (!options.tolerant && relative5.scheme) {
@@ -4167,7 +4167,7 @@ var require_fast_uri = __commonJS({
4167
4167
  }
4168
4168
  return { parsed, malformedAuthorityOrPort, malformedPercentEncoding, malformedSchemeSpecific, malformedHost, malformedScheme };
4169
4169
  }
4170
- function parse5(uri, opts) {
4170
+ function parse6(uri, opts) {
4171
4171
  return parseWithStatus(uri, opts).parsed;
4172
4172
  }
4173
4173
  function normalizeString(uri, opts) {
@@ -4204,7 +4204,7 @@ var require_fast_uri = __commonJS({
4204
4204
  resolveComponent,
4205
4205
  equal,
4206
4206
  serialize,
4207
- parse: parse5
4207
+ parse: parse6
4208
4208
  };
4209
4209
  module.exports = fastUri;
4210
4210
  module.exports.default = fastUri;
@@ -14674,7 +14674,7 @@ var require_public_api = __commonJS({
14674
14674
  }
14675
14675
  return doc;
14676
14676
  }
14677
- function parse5(src, reviver, options) {
14677
+ function parse6(src, reviver, options) {
14678
14678
  let _reviver = void 0;
14679
14679
  if (typeof reviver === "function") {
14680
14680
  _reviver = reviver;
@@ -14715,7 +14715,7 @@ var require_public_api = __commonJS({
14715
14715
  return value.toString(options);
14716
14716
  return new Document.Document(value, _replacer, options).toString(options);
14717
14717
  }
14718
- exports.parse = parse5;
14718
+ exports.parse = parse6;
14719
14719
  exports.parseAllDocuments = parseAllDocuments;
14720
14720
  exports.parseDocument = parseDocument2;
14721
14721
  exports.stringify = stringify3;
@@ -16925,8 +16925,8 @@ function validateWorkflow(workflow, agents, models, repositories, gates, issues)
16925
16925
  const writable = Object.values(objectValue(step.repositories) ?? {}).filter((access) => access === "write").length;
16926
16926
  if (maxWriteRepositories > writable) issues.push(issue2("semantic", "write_repository_bound_invalid", file, `${stepId} maxWriteRepositories exceeds writable repository scope`));
16927
16927
  }
16928
- const join15 = objectValue(step.join);
16929
- const minimumPassed = join15 && typeof join15.minimumPassed === "number" ? join15.minimumPassed : void 0;
16928
+ const join16 = objectValue(step.join);
16929
+ const minimumPassed = join16 && typeof join16.minimumPassed === "number" ? join16.minimumPassed : void 0;
16930
16930
  if (minimumPassed !== void 0 && minimumPassed > maximum) issues.push(issue2("semantic", "join_impossible", file, `${stepId} minimumPassed exceeds assignment maximum`));
16931
16931
  const distinctBy = names(assignment?.distinctBy);
16932
16932
  if (distinctBy.length > 0) {
@@ -20454,7 +20454,7 @@ function compileStep(step, index, stepIndex, requirePlanHash, sink) {
20454
20454
  const maxAttempts = compileCountField(step.maxAttempts, 1, `${id}.maxAttempts`, id, sink);
20455
20455
  const timeoutMs = compileOptionalDuration(step.timeoutMs, `${id}.timeoutMs`, id, sink);
20456
20456
  const assignments = compileAssignments(step, id, agent, sink);
20457
- const join15 = compileJoin(step, id, sink);
20457
+ const join16 = compileJoin(step, id, sink);
20458
20458
  const requiredEvidence = compileEvidence(step, id, sink);
20459
20459
  const transitions = compileTransitions(step, id, index, stepIndex, sink);
20460
20460
  const outcomes = Object.keys(transitions).sort(compareCodeUnits4);
@@ -20482,7 +20482,7 @@ function compileStep(step, index, stepIndex, requirePlanHash, sink) {
20482
20482
  transitions: orderedTransitions,
20483
20483
  requiresPlanHash: requirePlanHash.includes(id),
20484
20484
  assignments,
20485
- join: join15
20485
+ join: join16
20486
20486
  };
20487
20487
  if (kind === "agent" || kind === "moa") {
20488
20488
  if (!agent) return void 0;
@@ -20532,15 +20532,15 @@ function compileAssignments(step, stepId, primaryAgentId, sink) {
20532
20532
  };
20533
20533
  }
20534
20534
  function compileJoin(step, stepId, sink) {
20535
- const join15 = objectValue2(step.join);
20536
- if (!join15) return { strategy: "all" };
20537
- const declared = stringValue2(join15.strategy);
20535
+ const join16 = objectValue2(step.join);
20536
+ if (!join16) return { strategy: "all" };
20537
+ const declared = stringValue2(join16.strategy);
20538
20538
  const strategy = declared && JOIN_STRATEGIES.has(declared) ? declared : "all";
20539
- const minimumPassed = compileOptionalCount(join15.minimumPassed, `${stepId}.join.minimumPassed`, stepId, sink);
20539
+ const minimumPassed = compileOptionalCount(join16.minimumPassed, `${stepId}.join.minimumPassed`, stepId, sink);
20540
20540
  const compiled = {
20541
20541
  strategy,
20542
20542
  ...minimumPassed !== void 0 ? { minimumPassed } : {},
20543
- ...typeof join15.cancelRemaining === "boolean" ? { cancelRemaining: join15.cancelRemaining } : {}
20543
+ ...typeof join16.cancelRemaining === "boolean" ? { cancelRemaining: join16.cancelRemaining } : {}
20544
20544
  };
20545
20545
  return compiled;
20546
20546
  }
@@ -27750,6 +27750,674 @@ function evaluatePromotionPolicy(candidate, policy = "manual_pr", options) {
27750
27750
  reason: `Unknown promotion policy: ${String(policy)}`
27751
27751
  };
27752
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
+ };
28176
+
28177
+ // plugins/kxm/src/modes.ts
28178
+ var import_yaml6 = __toESM(require_dist(), 1);
28179
+ import { existsSync as existsSync12, readFileSync as readFileSync10 } from "node:fs";
28180
+ import { join as join15 } from "node:path";
28181
+ var DEFAULT_MODES_CONFIG = Object.freeze({
28182
+ schema: "kxm.modes.v1",
28183
+ majorModes: {
28184
+ coder: {
28185
+ description: "First-pass implementation, bug fixing, and test authoring",
28186
+ baseTools: ["read", "edit", "write", "bash"],
28187
+ contextFiles: ["AGENTS.md"],
28188
+ thinkingLevel: "medium",
28189
+ model: "grok/grok-4.6"
28190
+ },
28191
+ planner: {
28192
+ description: "High-level architectural planning, scoping, and decomposition",
28193
+ baseTools: ["read", "grep", "find"],
28194
+ contextFiles: ["plans/implementation-plan.md"],
28195
+ thinkingLevel: "high",
28196
+ model: "claude/fable"
28197
+ },
28198
+ auditor: {
28199
+ description: "Security, compliance, and code quality verification",
28200
+ baseTools: ["read", "grep"],
28201
+ contextFiles: ["SECURITY.md"],
28202
+ thinkingLevel: "high",
28203
+ model: "codex/gpt-5.6-sol"
28204
+ },
28205
+ browser: {
28206
+ description: "Web application exploration, screenshotting, and UI testing",
28207
+ baseTools: ["read", "bash"],
28208
+ contextFiles: ["docs/browser-automation.md"],
28209
+ thinkingLevel: "medium",
28210
+ model: "grok/grok-4.6"
28211
+ }
28212
+ },
28213
+ domains: {
28214
+ git: {
28215
+ description: "Git version control operations",
28216
+ tools: ["git_status", "git_diff", "git_commit"],
28217
+ promptSnippet: "Follow git branch conventions; never commit directly to main."
28218
+ },
28219
+ k8s: {
28220
+ description: "Kubernetes cluster inspection and deployment",
28221
+ tools: ["kubectl_get", "kubectl_describe"],
28222
+ promptSnippet: "Target local dev cluster; verify namespaces before mutating."
28223
+ },
28224
+ database: {
28225
+ description: "Database queries and schema verification",
28226
+ tools: ["sqlite_query", "sqlite_schema"],
28227
+ promptSnippet: "Database is SQLite at .kxm/state/kxm.db; use read-only queries."
28228
+ },
28229
+ browser: {
28230
+ description: "Remote Steel browser sessions and visual testing",
28231
+ tools: ["steel_session", "steel_scrape", "steel_screenshot"],
28232
+ promptSnippet: "Use Steel on DOKS for browser automation; invoke takeover on MFA."
28233
+ }
28234
+ }
28235
+ });
28236
+ function estimateTokens(charCount) {
28237
+ return Math.ceil(charCount / 3.8);
28238
+ }
28239
+ function loadModesConfig(projectRoot) {
28240
+ if (!projectRoot) {
28241
+ return DEFAULT_MODES_CONFIG;
28242
+ }
28243
+ const modesPath = join15(projectRoot, ".kxm", "modes.yaml");
28244
+ if (!existsSync12(modesPath)) {
28245
+ return DEFAULT_MODES_CONFIG;
28246
+ }
28247
+ try {
28248
+ const raw = readFileSync10(modesPath, "utf8");
28249
+ const parsed = (0, import_yaml6.parse)(raw);
28250
+ if (parsed && parsed.schema === "kxm.modes.v1" && parsed.majorModes) {
28251
+ return {
28252
+ schema: "kxm.modes.v1",
28253
+ majorModes: { ...DEFAULT_MODES_CONFIG.majorModes, ...parsed.majorModes },
28254
+ domains: { ...DEFAULT_MODES_CONFIG.domains, ...parsed.domains }
28255
+ };
28256
+ }
28257
+ } catch {
28258
+ }
28259
+ return DEFAULT_MODES_CONFIG;
28260
+ }
28261
+ function resolveActiveMode(config, majorModeName = "coder", domainNames = []) {
28262
+ const major = config.majorModes[majorModeName] || config.majorModes["coder"] || DEFAULT_MODES_CONFIG.majorModes["coder"];
28263
+ const tools = new Set(major.baseTools);
28264
+ const contextFiles = new Set(major.contextFiles || []);
28265
+ const promptSnippets = [];
28266
+ const enabledDomains = [];
28267
+ if (config.domains) {
28268
+ for (const dName of domainNames) {
28269
+ const d = config.domains[dName.toLowerCase().trim()];
28270
+ if (d) {
28271
+ enabledDomains.push(dName.toLowerCase().trim());
28272
+ d.tools.forEach((t) => tools.add(t));
28273
+ (d.contextFiles || []).forEach((f) => contextFiles.add(f));
28274
+ if (d.promptSnippet) {
28275
+ promptSnippets.push(d.promptSnippet);
28276
+ }
28277
+ }
28278
+ }
28279
+ }
28280
+ return {
28281
+ majorMode: majorModeName,
28282
+ enabledDomains,
28283
+ tools: Array.from(tools),
28284
+ contextFiles: Array.from(contextFiles),
28285
+ promptSnippets,
28286
+ thinkingLevel: major.thinkingLevel,
28287
+ model: major.model || "grok/grok-4.6"
28288
+ };
28289
+ }
28290
+ function calculatePromptFootprint(resolved, projectRoot = process.cwd(), catalog) {
28291
+ const breakdown = [];
28292
+ const baseSystemPromptChars = 3200;
28293
+ breakdown.push({
28294
+ name: `Base System Prompt (${resolved.majorMode})`,
28295
+ chars: baseSystemPromptChars,
28296
+ estimatedTokens: estimateTokens(baseSystemPromptChars)
28297
+ });
28298
+ for (const relPath of resolved.contextFiles) {
28299
+ const fullPath = join15(projectRoot, relPath);
28300
+ let chars = 0;
28301
+ if (existsSync12(fullPath)) {
28302
+ try {
28303
+ chars = readFileSync10(fullPath, "utf8").length;
28304
+ } catch {
28305
+ chars = 0;
28306
+ }
28307
+ }
28308
+ if (chars === 0) {
28309
+ chars = 1500;
28310
+ }
28311
+ breakdown.push({
28312
+ name: `Context File: ${relPath}`,
28313
+ chars,
28314
+ estimatedTokens: estimateTokens(chars)
28315
+ });
28316
+ }
28317
+ if (resolved.promptSnippets.length > 0) {
28318
+ const snippetChars = resolved.promptSnippets.join("\n").length;
28319
+ breakdown.push({
28320
+ name: `Domain Prompts (${resolved.enabledDomains.join(", ")})`,
28321
+ chars: snippetChars,
28322
+ estimatedTokens: estimateTokens(snippetChars)
28323
+ });
28324
+ }
28325
+ const toolSchemaChars = resolved.tools.length * 650;
28326
+ breakdown.push({
28327
+ name: `Tool Schemas (${resolved.tools.length} active tools)`,
28328
+ chars: toolSchemaChars,
28329
+ estimatedTokens: estimateTokens(toolSchemaChars)
28330
+ });
28331
+ const totalChars = breakdown.reduce((sum, item) => sum + item.chars, 0);
28332
+ const totalTokens = breakdown.reduce((sum, item) => sum + item.estimatedTokens, 0);
28333
+ const maxContextWindow = 2e5;
28334
+ const contextWindowRatio = Math.round(totalTokens / maxContextWindow * 1e3) / 10;
28335
+ const cat = catalog || loadPriceCatalog(projectRoot);
28336
+ const rawModel = resolved.model || "grok/grok-4.6";
28337
+ const [prov, mod] = rawModel.includes("/") ? rawModel.split("/", 2) : [void 0, rawModel];
28338
+ const inputCost = cat ? calculateModelCost(cat, {
28339
+ model: mod || rawModel,
28340
+ ...prov !== void 0 ? { provider: prov } : {},
28341
+ tokensIn: totalTokens,
28342
+ tokensOut: 0,
28343
+ cacheReadTokens: 0,
28344
+ cacheWriteTokens: 0
28345
+ }) : void 0;
28346
+ const cacheReadCost = cat ? calculateModelCost(cat, {
28347
+ model: mod || rawModel,
28348
+ ...prov !== void 0 ? { provider: prov } : {},
28349
+ tokensIn: 0,
28350
+ tokensOut: 0,
28351
+ cacheReadTokens: totalTokens,
28352
+ cacheWriteTokens: 0
28353
+ }) : void 0;
28354
+ const outputEstimateCost = cat ? calculateModelCost(cat, {
28355
+ model: mod || rawModel,
28356
+ ...prov !== void 0 ? { provider: prov } : {},
28357
+ tokensIn: 0,
28358
+ tokensOut: 1e3,
28359
+ cacheReadTokens: 0,
28360
+ cacheWriteTokens: 0
28361
+ }) : void 0;
28362
+ return {
28363
+ majorMode: resolved.majorMode,
28364
+ enabledDomains: resolved.enabledDomains,
28365
+ model: resolved.model || "grok/grok-4.6",
28366
+ breakdown,
28367
+ totalChars,
28368
+ totalTokens,
28369
+ contextWindowRatio,
28370
+ projectedCost: {
28371
+ inputCostUsd: inputCost?.costUsd ?? null,
28372
+ cacheReadCostUsd: cacheReadCost?.costUsd ?? null,
28373
+ outputCostEstimateUsd: outputEstimateCost?.costUsd ?? null
28374
+ }
28375
+ };
28376
+ }
28377
+ function formatModesExplainReport(footprint) {
28378
+ const divider = "\u2550".repeat(60);
28379
+ const subDivider = "\u2500".repeat(60);
28380
+ let out = `
28381
+ ${divider}
28382
+ `;
28383
+ out += `KXM PRE-FLIGHT CONTEXT EXPLAIN
28384
+ `;
28385
+ out += `${divider}
28386
+ `;
28387
+ out += `Major Mode: ${footprint.majorMode}
28388
+ `;
28389
+ out += `Enabled Domains: ${footprint.enabledDomains.length > 0 ? footprint.enabledDomains.join(", ") : "(none)"}
28390
+ `;
28391
+ out += `Target Model: ${footprint.model}
28392
+
28393
+ `;
28394
+ out += `CONTEXT BREAKDOWN:
28395
+ `;
28396
+ for (const item of footprint.breakdown) {
28397
+ const padName = item.name.padEnd(38, " ");
28398
+ const tokenStr = `${item.estimatedTokens.toLocaleString()} tokens`.padStart(16, " ");
28399
+ out += ` \u2022 ${padName} ${tokenStr}
28400
+ `;
28401
+ }
28402
+ out += `${subDivider}
28403
+ `;
28404
+ const totalPad = `TOTAL PROMPT FOOTPRINT:`.padEnd(38, " ");
28405
+ const totalStr = `${footprint.totalTokens.toLocaleString()} tokens`.padStart(16, " ");
28406
+ out += ` ${totalPad} ${totalStr} (${footprint.contextWindowRatio}% of 200k window)
28407
+
28408
+ `;
28409
+ out += `PROJECTED COSTS (per turn):
28410
+ `;
28411
+ out += ` \u2022 Initial Turn Input Cost: ${footprint.projectedCost.inputCostUsd !== null ? `$${footprint.projectedCost.inputCostUsd.toFixed(4)}` : "unmetered/unknown"}
28412
+ `;
28413
+ out += ` \u2022 Subsequent Cache-Read Cost: ${footprint.projectedCost.cacheReadCostUsd !== null ? `$${footprint.projectedCost.cacheReadCostUsd.toFixed(4)} (approx 90% savings)` : "unmetered/unknown"}
28414
+ `;
28415
+ out += ` \u2022 Output Estimate (1k tokens): ${footprint.projectedCost.outputCostEstimateUsd !== null ? `$${footprint.projectedCost.outputCostEstimateUsd.toFixed(4)}` : "unmetered/unknown"}
28416
+ `;
28417
+ out += `${divider}
28418
+ `;
28419
+ return out;
28420
+ }
27753
28421
  export {
27754
28422
  BUILTIN_HARNESSES,
27755
28423
  BUILTIN_HARNESS_IDS,
@@ -27757,6 +28425,7 @@ export {
27757
28425
  DEFAULT_HARNESS,
27758
28426
  DEFAULT_LOG_MAX_BYTES,
27759
28427
  DEFAULT_LOG_MAX_FILES,
28428
+ DEFAULT_MODES_CONFIG,
27760
28429
  IMPROVEMENT_REPORT_SCHEMA,
27761
28430
  IMPROVEMENT_REPORT_V1_SCHEMA,
27762
28431
  LOG_LEVEL_PRIORITY,
@@ -27765,6 +28434,8 @@ export {
27765
28434
  PI_NATIVE_BRAKE_PROVIDERS,
27766
28435
  PiSession,
27767
28436
  SAFE_HARNESS_COMMAND_ID,
28437
+ SteelClient,
28438
+ VIEWPORT_PRESETS,
27768
28439
  VNEXT_ABSENT_MEMORY_REVISION,
27769
28440
  VNEXT_EVENT_STORE_SCHEMA_VERSION,
27770
28441
  VNEXT_REGISTRY_SCHEMA_VERSION,
@@ -27777,6 +28448,7 @@ export {
27777
28448
  assertVnextConfigError,
27778
28449
  backupDatabaseFile,
27779
28450
  buildImprovementReport,
28451
+ calculatePromptFootprint,
27780
28452
  cancelVnextRun,
27781
28453
  checkIntegrity,
27782
28454
  checkMemoryRevisionDrift,
@@ -27786,6 +28458,7 @@ export {
27786
28458
  closeVnextRuntimeContext,
27787
28459
  computeGateEvidenceOutcome,
27788
28460
  computeVnextMemoryRevision,
28461
+ createAnnotationFeedback,
27789
28462
  createBackup,
27790
28463
  createLogger,
27791
28464
  createVnextOneShotProducer,
@@ -27796,13 +28469,17 @@ export {
27796
28469
  eligibleHarnesses,
27797
28470
  ensureVnextSupervisor,
27798
28471
  ensureWalJournalMode,
28472
+ estimateTokens,
27799
28473
  evaluatePromotionPolicy,
27800
28474
  fileSha256,
27801
28475
  findWinNpmInnerExe,
27802
28476
  foldStoredVnextRun,
28477
+ formatAnnotationFeedbackPrompt,
28478
+ formatCDPEndpoint,
27803
28479
  formatHarnessInventory,
27804
28480
  formatHarnessUpdate,
27805
28481
  formatImprovementReport,
28482
+ formatModesExplainReport,
27806
28483
  formatPiSessionDisplayName,
27807
28484
  formatPiSessionKey,
27808
28485
  gateRowContentHash,
@@ -27814,6 +28491,7 @@ export {
27814
28491
  isKnownHarnessId,
27815
28492
  isVnextRuntimeContextClosed,
27816
28493
  isWindowsHarnessShim,
28494
+ loadModesConfig,
27817
28495
  newVnextAssignmentId,
27818
28496
  newVnextAttemptId,
27819
28497
  newVnextCommandId,
@@ -27843,12 +28521,17 @@ export {
27843
28521
  rebuildVnextRunProjection,
27844
28522
  redactLogValue,
27845
28523
  registerVnextRuntimeCloseHook,
28524
+ resolveActiveMode,
27846
28525
  resolveDispatchStatus,
28526
+ resolvePassCliApiKey,
28527
+ resolveSteelConfig,
28528
+ resolveViewportDimensions,
27847
28529
  restoreBackup,
27848
28530
  restoreDatabaseFile,
27849
28531
  rotateLogFiles,
27850
28532
  runHarnessUpdate,
27851
28533
  runtimeError,
28534
+ sanitizeLogOutput,
27852
28535
  startVnextRuntimeSupervisor,
27853
28536
  tableColumns,
27854
28537
  userTables,