@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.
- package/.claude-plugin/marketplace.json +1 -1
- package/.kxm/roles/writer.yaml +2 -0
- package/docs/README.md +3 -0
- package/docs/adr/ADR-0002-browser-automation-steel-doks.md +103 -0
- package/docs/agent-skills.md +19 -2
- package/docs/browser-automation.md +116 -0
- package/docs/configuration.md +10 -1
- package/docs/getting-started.md +21 -0
- package/docs/kb/how-credentials-retrieved-safely.md +31 -0
- package/docs/kb/how-to-capture-and-annotate-section.md +60 -0
- package/docs/kb/how-to-connect-playwright-to-steel.md +54 -0
- package/docs/kb/how-to-recover-expired-session-or-orphan.md +54 -0
- package/docs/kb/how-to-resume-after-mfa.md +28 -0
- package/docs/kb/how-to-take-over-session.md +32 -0
- package/docs/kb/why-authentication-disappeared.md +32 -0
- package/docs/kb/why-automation-opened-different-browser.md +32 -0
- package/docs/kb/why-session-viewer-cannot-control.md +31 -0
- package/docs/operations.md +24 -0
- package/docs/prompts/browser-annotate-feedback.md +41 -0
- package/docs/prompts/browser-diagnose-recover.md +38 -0
- package/docs/prompts/browser-explore.md +42 -0
- package/docs/prompts/browser-repro-fix.md +48 -0
- package/docs/prompts/browser-start.md +41 -0
- package/docs/prompts/browser-takeover.md +50 -0
- package/docs/skills/repo-work-delivery.md +5 -0
- package/docs/skills.md +2 -0
- package/docs/troubleshooting.md +22 -1
- package/package.json +1 -1
- package/plugins/kxm/.claude-plugin/plugin.json +1 -1
- package/plugins/kxm/dist/cli.js +2257 -439
- package/plugins/kxm/dist/core.js +57 -0
- package/plugins/kxm/dist/extension.js +40 -3
- package/plugins/kxm/dist/mcp-server.js +1 -1
- package/plugins/kxm/dist/runtime.js +572 -17
- package/plugins/kxm/dist/server.js +129 -4
- package/plugins/kxm/dist/vnext-runtime-supervisor.js +136 -13
- package/plugins/kxm/package.json +1 -1
- package/plugins/kxm/skills/hints.json +30 -0
- package/plugins/kxm/skills/kxm-browser-annotate/SKILL.md +90 -0
- package/plugins/kxm/skills/kxm-browser-auth/SKILL.md +47 -0
- package/plugins/kxm/skills/kxm-browser-diagnostics/SKILL.md +48 -0
- package/plugins/kxm/skills/kxm-browser-explore/SKILL.md +48 -0
- package/plugins/kxm/skills/kxm-browser-session/SKILL.md +94 -0
- package/plugins/kxm/skills/kxm-browser-takeover/SKILL.md +87 -0
- package/plugins/kxm/skills/kxm-browser-verify/SKILL.md +71 -0
- package/plugins/kxm/skills/kxm-hub-ops/SKILL.md +9 -0
- package/plugins/kxm/skills/kxm-project-setup/SKILL.md +9 -2
- package/plugins/kxm/src/autocomplete.ts +1 -1
- package/plugins/kxm/src/browser.ts +603 -0
- package/plugins/kxm/src/cli.ts +507 -9
- package/plugins/kxm/src/completion-install.ts +223 -0
- package/plugins/kxm/src/database.ts +1 -1
- package/plugins/kxm/src/external-effects.ts +1 -1
- package/plugins/kxm/src/hub-env.ts +193 -0
- package/plugins/kxm/src/init-guide-setup.ts +547 -0
- package/plugins/kxm/src/local-snapshot.ts +1 -1
- package/plugins/kxm/src/mcp-server.ts +1 -1
- package/plugins/kxm/src/protocol.ts +111 -0
- package/plugins/kxm/src/role.ts +335 -0
- package/plugins/kxm/src/runtime.ts +1 -0
- package/plugins/kxm/src/safety-integrity.ts +76 -0
- package/plugins/kxm/src/sqlite.ts +76 -0
- package/plugins/kxm/src/store.ts +1 -1
- package/plugins/kxm/src/vnext-bindings.ts +1 -1
- package/plugins/kxm/src/vnext-config.ts +38 -1
- package/plugins/kxm/src/vnext-engine-command.ts +2 -0
- package/plugins/kxm/src/vnext-engine.ts +16 -0
- package/plugins/kxm/src/vnext-harness.ts +4 -2
- package/plugins/kxm/src/vnext-oneshot-process.ts +21 -4
- package/plugins/kxm/src/vnext-oneshot-producer.ts +18 -0
- package/plugins/kxm/src/vnext-runtime-store.ts +1 -1
- package/plugins/kxm/src/workflow-tui.ts +1 -1
- package/plugins/kxm/src/workflow.ts +144 -0
- package/scripts/kxm-bump-version.mjs +146 -0
- package/scripts/kxm-hub.mjs +145 -3
- package/scripts/kxm-publish-npm.mjs +3 -1
- package/scripts/kxm-release-github.mjs +3 -1
- package/scripts/kxm.mjs +0 -0
|
@@ -7431,6 +7431,61 @@ function parseBoundedInteger(value, field, fallback, min, max) {
|
|
|
7431
7431
|
}
|
|
7432
7432
|
return value;
|
|
7433
7433
|
}
|
|
7434
|
+
var TERMINAL_RECEIPT_SCHEMA = "kxm.terminal-receipt.v1";
|
|
7435
|
+
var VALID_TERMINAL_STATUSES = /* @__PURE__ */ new Set(["accepted", "audit_escalation", "rejected", "error"]);
|
|
7436
|
+
function validateTerminalReceipt(value) {
|
|
7437
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
7438
|
+
throw new ProtocolError(400, "terminal receipt must be an object", "invalid_terminal_receipt");
|
|
7439
|
+
}
|
|
7440
|
+
const record = value;
|
|
7441
|
+
if (record.schema !== void 0 && record.schema !== TERMINAL_RECEIPT_SCHEMA) {
|
|
7442
|
+
throw new ProtocolError(400, `terminal receipt schema must be ${TERMINAL_RECEIPT_SCHEMA}`, "invalid_terminal_receipt");
|
|
7443
|
+
}
|
|
7444
|
+
const status = record.status;
|
|
7445
|
+
if (!status || !VALID_TERMINAL_STATUSES.has(status)) {
|
|
7446
|
+
throw new ProtocolError(
|
|
7447
|
+
400,
|
|
7448
|
+
`terminal receipt status must be one of: ${Array.from(VALID_TERMINAL_STATUSES).join(", ")}`,
|
|
7449
|
+
"invalid_terminal_receipt"
|
|
7450
|
+
);
|
|
7451
|
+
}
|
|
7452
|
+
const seat = requireString(record.seat, "seat", { max: 64 });
|
|
7453
|
+
const runId = requireString(record.runId, "runId", { max: 128 });
|
|
7454
|
+
const stageId = requireString(record.stageId, "stageId", { max: 128 });
|
|
7455
|
+
const timestamp = requireString(record.timestamp, "timestamp", { max: 64 });
|
|
7456
|
+
const host2 = requireString(record.host, "host", { max: 64 });
|
|
7457
|
+
const model = requireString(record.model, "model", { max: 128 });
|
|
7458
|
+
let evidence;
|
|
7459
|
+
if (record.evidence !== void 0) {
|
|
7460
|
+
if (!record.evidence || typeof record.evidence !== "object" || Array.isArray(record.evidence)) {
|
|
7461
|
+
throw new ProtocolError(400, "terminal receipt evidence must be an object", "invalid_terminal_receipt");
|
|
7462
|
+
}
|
|
7463
|
+
evidence = record.evidence;
|
|
7464
|
+
}
|
|
7465
|
+
let metrics;
|
|
7466
|
+
if (record.metrics !== void 0) {
|
|
7467
|
+
if (!record.metrics || typeof record.metrics !== "object" || Array.isArray(record.metrics)) {
|
|
7468
|
+
throw new ProtocolError(400, "terminal receipt metrics must be an object", "invalid_terminal_receipt");
|
|
7469
|
+
}
|
|
7470
|
+
metrics = record.metrics;
|
|
7471
|
+
}
|
|
7472
|
+
const escalationReason = optionalString(record.escalationReason, "escalationReason", 1024);
|
|
7473
|
+
const ruling = optionalString(record.ruling, "ruling", 2048);
|
|
7474
|
+
return {
|
|
7475
|
+
schema: TERMINAL_RECEIPT_SCHEMA,
|
|
7476
|
+
status,
|
|
7477
|
+
seat,
|
|
7478
|
+
runId,
|
|
7479
|
+
stageId,
|
|
7480
|
+
timestamp,
|
|
7481
|
+
host: host2,
|
|
7482
|
+
model,
|
|
7483
|
+
...evidence ? { evidence } : {},
|
|
7484
|
+
...metrics ? { metrics } : {},
|
|
7485
|
+
...escalationReason ? { escalationReason } : {},
|
|
7486
|
+
...ruling ? { ruling } : {}
|
|
7487
|
+
};
|
|
7488
|
+
}
|
|
7434
7489
|
|
|
7435
7490
|
// plugins/kxm/src/diagnostics.ts
|
|
7436
7491
|
function workflowScopeExtras(operation, assignedCoordinatorName) {
|
|
@@ -7982,12 +8037,17 @@ function parseWorkflowDefinitions(raw, environment = process.env, onWarning) {
|
|
|
7982
8037
|
if (stageMaxTransitions !== void 0 && (!Number.isInteger(stageMaxTransitions) || stageMaxTransitions < 1 || stageMaxTransitions > 100)) {
|
|
7983
8038
|
throw new Error(`stage ${stageId} maxTransitions must be an integer between 1 and 100`);
|
|
7984
8039
|
}
|
|
8040
|
+
const autoResumeLimit = stage.autoResumeLimit;
|
|
8041
|
+
if (autoResumeLimit !== void 0 && (!Number.isInteger(autoResumeLimit) || autoResumeLimit < 1 || autoResumeLimit > 20)) {
|
|
8042
|
+
throw new Error(`stage ${stageId} autoResumeLimit must be an integer between 1 and 20`);
|
|
8043
|
+
}
|
|
7985
8044
|
return {
|
|
7986
8045
|
id: stageId,
|
|
7987
8046
|
label: requireString(stage.label ?? stageId, "stage.label", { max: 128 }),
|
|
7988
8047
|
instructions: requireString(stage.instructions, "stage.instructions", { max: 4e3 }),
|
|
7989
8048
|
requiredEvidence,
|
|
7990
8049
|
maxAttempts,
|
|
8050
|
+
...autoResumeLimit !== void 0 ? { autoResumeLimit } : {},
|
|
7991
8051
|
...area ? { area } : {},
|
|
7992
8052
|
...evidencePolicies ? { evidencePolicies } : {},
|
|
7993
8053
|
...on ? { on } : {},
|
|
@@ -8266,6 +8326,31 @@ function checkpointRun(run, stageId, status, summary, evidence, timestamp, verif
|
|
|
8266
8326
|
delete run.currentStage;
|
|
8267
8327
|
return { retry: false, completed: false, run };
|
|
8268
8328
|
}
|
|
8329
|
+
if (stage.autoResumeLimit !== void 0 && stage.attempts >= stage.autoResumeLimit) {
|
|
8330
|
+
stage.status = "in_progress";
|
|
8331
|
+
const reason = summary || `autoResumeLimit of ${stage.autoResumeLimit} reached on stage ${stage.id}`;
|
|
8332
|
+
const receipt = validateTerminalReceipt({
|
|
8333
|
+
schema: TERMINAL_RECEIPT_SCHEMA,
|
|
8334
|
+
status: "audit_escalation",
|
|
8335
|
+
seat: stage.id,
|
|
8336
|
+
runId: run.id,
|
|
8337
|
+
stageId: stage.id,
|
|
8338
|
+
timestamp,
|
|
8339
|
+
host: "pi",
|
|
8340
|
+
model: "default",
|
|
8341
|
+
escalationReason: reason
|
|
8342
|
+
});
|
|
8343
|
+
const expiresAt = new Date(Date.parse(timestamp) + 24 * 60 * 60 * 1e3).toISOString();
|
|
8344
|
+
waitForWorkflowSignal(run, stage.id, "audit_escalation", reason, timestamp, expiresAt);
|
|
8345
|
+
stage.receipt = receipt;
|
|
8346
|
+
stage.auditEscalation = {
|
|
8347
|
+
reason,
|
|
8348
|
+
timestamp,
|
|
8349
|
+
receipt
|
|
8350
|
+
};
|
|
8351
|
+
return { retry: false, completed: false, run };
|
|
8352
|
+
}
|
|
8353
|
+
stage.status = status;
|
|
8269
8354
|
const outcomeKey = outcome ?? status;
|
|
8270
8355
|
const rule = resolveOutcomeRule(stage, outcomeKey);
|
|
8271
8356
|
if (rule && stage.attempts < stage.maxAttempts) {
|
|
@@ -10833,7 +10918,45 @@ import {
|
|
|
10833
10918
|
writeFileSync as writeFileSync4
|
|
10834
10919
|
} from "node:fs";
|
|
10835
10920
|
import { basename as basename3, dirname as dirname3, join as join4, resolve as resolve4 } from "node:path";
|
|
10836
|
-
|
|
10921
|
+
|
|
10922
|
+
// plugins/kxm/src/sqlite.ts
|
|
10923
|
+
import { createRequire } from "node:module";
|
|
10924
|
+
var requireFromHere = createRequire(import.meta.url);
|
|
10925
|
+
function loadNative() {
|
|
10926
|
+
try {
|
|
10927
|
+
const mod = requireFromHere("node:sqlite");
|
|
10928
|
+
if (mod.DatabaseSync) return { Ctor: mod.DatabaseSync, bun: false };
|
|
10929
|
+
} catch {
|
|
10930
|
+
}
|
|
10931
|
+
try {
|
|
10932
|
+
const mod = requireFromHere("bun:sqlite");
|
|
10933
|
+
const Ctor = mod?.DatabaseSync ?? mod?.Database;
|
|
10934
|
+
if (Ctor) return { Ctor, bun: true };
|
|
10935
|
+
} catch {
|
|
10936
|
+
}
|
|
10937
|
+
throw new Error("kxm: no supported sqlite module found (need node:sqlite or bun:sqlite)");
|
|
10938
|
+
}
|
|
10939
|
+
var native = loadNative();
|
|
10940
|
+
var DatabaseSync = class {
|
|
10941
|
+
inner;
|
|
10942
|
+
constructor(path, options) {
|
|
10943
|
+
let normalized = options;
|
|
10944
|
+
if (native.bun && options) {
|
|
10945
|
+
const { readOnly, ...rest } = options;
|
|
10946
|
+
normalized = readOnly === void 0 ? rest : { ...rest, readonly: readOnly };
|
|
10947
|
+
}
|
|
10948
|
+
this.inner = normalized === void 0 ? new native.Ctor(path) : new native.Ctor(path, normalized);
|
|
10949
|
+
}
|
|
10950
|
+
prepare(sql) {
|
|
10951
|
+
return this.inner.prepare(sql);
|
|
10952
|
+
}
|
|
10953
|
+
exec(sql) {
|
|
10954
|
+
return this.inner.exec(sql);
|
|
10955
|
+
}
|
|
10956
|
+
close() {
|
|
10957
|
+
this.inner.close();
|
|
10958
|
+
}
|
|
10959
|
+
};
|
|
10837
10960
|
|
|
10838
10961
|
// plugins/kxm/src/vnext-config.ts
|
|
10839
10962
|
import { basename as basename2, dirname as dirname2, extname as extname2, isAbsolute, join as join3, relative, resolve as resolve3, sep } from "node:path";
|
|
@@ -11090,7 +11213,9 @@ function parseAgyOneShotUsage(stdout, _stderr) {
|
|
|
11090
11213
|
var READ_ONLY_ONESHOT_ARGS = Object.freeze({
|
|
11091
11214
|
claude: Object.freeze(["--tools", "Read,Glob,Grep", "--restricted", "--safe-mode", "--strict-mcp-config", "--mcp-config", '{"mcpServers":{}}', "--disable-slash-commands", "--no-session-persistence"]),
|
|
11092
11215
|
codex: Object.freeze(["--sandbox", "read-only", "--ignore-user-config", "-c", 'approval_policy="never"']),
|
|
11093
|
-
grok: Object.freeze(["--sandbox", "read-only", "--permission-mode", "plan", "--tools", "Read,Glob,Grep", "--no-subagents", "--disable-web-search"])
|
|
11216
|
+
grok: Object.freeze(["--sandbox", "read-only", "--permission-mode", "plan", "--tools", "Read,Glob,Grep", "--no-subagents", "--disable-web-search"]),
|
|
11217
|
+
agy: Object.freeze(["--mode", "plan", "--sandbox", "--disable-slash-commands"]),
|
|
11218
|
+
kimi: Object.freeze(["--plan"])
|
|
11094
11219
|
});
|
|
11095
11220
|
function oneShotReadOnlyArgs(harness) {
|
|
11096
11221
|
return Object.hasOwn(READ_ONLY_ONESHOT_ARGS, harness) ? READ_ONLY_ONESHOT_ARGS[harness] : void 0;
|
|
@@ -11144,7 +11269,7 @@ var BUILTIN_HARNESSES = Object.freeze([
|
|
|
11144
11269
|
authArgs: ["provider", "list"],
|
|
11145
11270
|
update: { self: ["upgrade"] },
|
|
11146
11271
|
oneShot: {
|
|
11147
|
-
argv: ["--output-format", "stream-json", "-p"],
|
|
11272
|
+
argv: [...oneShotReadOnlyArgs("kimi"), "--output-format", "stream-json", "-p"],
|
|
11148
11273
|
promptVia: "arg",
|
|
11149
11274
|
outputFormat: "stream-json",
|
|
11150
11275
|
usageParser: parseKimiOneShotUsage
|
|
@@ -11207,7 +11332,7 @@ var BUILTIN_HARNESSES = Object.freeze([
|
|
|
11207
11332
|
authArgs: ["models"],
|
|
11208
11333
|
update: { self: ["update"] },
|
|
11209
11334
|
oneShot: {
|
|
11210
|
-
argv: ["--output-format", "json", "-p"],
|
|
11335
|
+
argv: [...oneShotReadOnlyArgs("agy"), "--output-format", "json", "-p"],
|
|
11211
11336
|
promptVia: "arg",
|
|
11212
11337
|
outputFormat: "json",
|
|
11213
11338
|
usageParser: parseAgyOneShotUsage
|
|
@@ -15126,6 +15126,19 @@ var OUTPUT_LIMIT = 8 * 1024 * 1024;
|
|
|
15126
15126
|
var KILL_GRACE_MS = 250;
|
|
15127
15127
|
var DRAIN_GRACE_MS = 500;
|
|
15128
15128
|
var REAP_GRACE_MS = 1e3;
|
|
15129
|
+
function killProcessTree(child, signal = "SIGKILL") {
|
|
15130
|
+
if (process.platform !== "win32" && child.pid) {
|
|
15131
|
+
try {
|
|
15132
|
+
process.kill(-child.pid, signal);
|
|
15133
|
+
return;
|
|
15134
|
+
} catch {
|
|
15135
|
+
}
|
|
15136
|
+
}
|
|
15137
|
+
try {
|
|
15138
|
+
child.kill(signal);
|
|
15139
|
+
} catch {
|
|
15140
|
+
}
|
|
15141
|
+
}
|
|
15129
15142
|
function defaultSpawn(command, args, options) {
|
|
15130
15143
|
if (options.signal?.aborted) {
|
|
15131
15144
|
return Promise.resolve({ stdout: "", stderr: "", code: null, started: false, observedChildExit: false, error: new Error("process_aborted") });
|
|
@@ -15162,11 +15175,7 @@ function defaultSpawn(command, args, options) {
|
|
|
15162
15175
|
resolve7({ stdout: Buffer.concat(stdout).toString("utf8"), stderr: Buffer.concat(stderr).toString("utf8"), code, signal, started, observedChildExit, terminationRequested: stopping, ...error ? { error } : {} });
|
|
15163
15176
|
};
|
|
15164
15177
|
const kill = (requested) => {
|
|
15165
|
-
|
|
15166
|
-
if (process.platform !== "win32" && child.pid) process.kill(-child.pid, requested);
|
|
15167
|
-
else child.kill(requested);
|
|
15168
|
-
} catch {
|
|
15169
|
-
}
|
|
15178
|
+
killProcessTree(child, requested);
|
|
15170
15179
|
};
|
|
15171
15180
|
const stop = (reason) => {
|
|
15172
15181
|
if (finished) return;
|
|
@@ -15493,7 +15502,9 @@ function parseAgyOneShotUsage(stdout, _stderr) {
|
|
|
15493
15502
|
var READ_ONLY_ONESHOT_ARGS = Object.freeze({
|
|
15494
15503
|
claude: Object.freeze(["--tools", "Read,Glob,Grep", "--restricted", "--safe-mode", "--strict-mcp-config", "--mcp-config", '{"mcpServers":{}}', "--disable-slash-commands", "--no-session-persistence"]),
|
|
15495
15504
|
codex: Object.freeze(["--sandbox", "read-only", "--ignore-user-config", "-c", 'approval_policy="never"']),
|
|
15496
|
-
grok: Object.freeze(["--sandbox", "read-only", "--permission-mode", "plan", "--tools", "Read,Glob,Grep", "--no-subagents", "--disable-web-search"])
|
|
15505
|
+
grok: Object.freeze(["--sandbox", "read-only", "--permission-mode", "plan", "--tools", "Read,Glob,Grep", "--no-subagents", "--disable-web-search"]),
|
|
15506
|
+
agy: Object.freeze(["--mode", "plan", "--sandbox", "--disable-slash-commands"]),
|
|
15507
|
+
kimi: Object.freeze(["--plan"])
|
|
15497
15508
|
});
|
|
15498
15509
|
function oneShotReadOnlyArgs(harness) {
|
|
15499
15510
|
return Object.hasOwn(READ_ONLY_ONESHOT_ARGS, harness) ? READ_ONLY_ONESHOT_ARGS[harness] : void 0;
|
|
@@ -15547,7 +15558,7 @@ var BUILTIN_HARNESSES = Object.freeze([
|
|
|
15547
15558
|
authArgs: ["provider", "list"],
|
|
15548
15559
|
update: { self: ["upgrade"] },
|
|
15549
15560
|
oneShot: {
|
|
15550
|
-
argv: ["--output-format", "stream-json", "-p"],
|
|
15561
|
+
argv: [...oneShotReadOnlyArgs("kimi"), "--output-format", "stream-json", "-p"],
|
|
15551
15562
|
promptVia: "arg",
|
|
15552
15563
|
outputFormat: "stream-json",
|
|
15553
15564
|
usageParser: parseKimiOneShotUsage
|
|
@@ -15610,7 +15621,7 @@ var BUILTIN_HARNESSES = Object.freeze([
|
|
|
15610
15621
|
authArgs: ["models"],
|
|
15611
15622
|
update: { self: ["update"] },
|
|
15612
15623
|
oneShot: {
|
|
15613
|
-
argv: ["--output-format", "json", "-p"],
|
|
15624
|
+
argv: [...oneShotReadOnlyArgs("agy"), "--output-format", "json", "-p"],
|
|
15614
15625
|
promptVia: "arg",
|
|
15615
15626
|
outputFormat: "json",
|
|
15616
15627
|
usageParser: parseAgyOneShotUsage
|
|
@@ -16905,7 +16916,7 @@ function validateModelReferences(models, issues) {
|
|
|
16905
16916
|
};
|
|
16906
16917
|
for (const id of models.keys()) walk(id, []);
|
|
16907
16918
|
}
|
|
16908
|
-
function validateBundle(project, repositories, agents, models, workflows, environments, options, gateRegistry) {
|
|
16919
|
+
function validateBundle(project, repositories, agents, models, workflows, environments, options, gateRegistry, projectRoot) {
|
|
16909
16920
|
const issues = [];
|
|
16910
16921
|
const entries = valuesOf(project.value, "repositories").map((candidate) => objectValue(candidate)).filter((candidate) => Boolean(candidate));
|
|
16911
16922
|
const repositoryIds = /* @__PURE__ */ new Set();
|
|
@@ -16973,6 +16984,33 @@ function validateBundle(project, repositories, agents, models, workflows, enviro
|
|
|
16973
16984
|
const defaultWorkflow = stringValue(project.value.defaultWorkflow) ?? "default";
|
|
16974
16985
|
if (!workflows.has(defaultWorkflow)) issues.push(issue2("reference", "default_workflow_unknown", project.logicalPath, `default workflow ${defaultWorkflow} does not exist`));
|
|
16975
16986
|
for (const workflow of workflows.values()) validateWorkflow(workflow, agents, models, repositoryIds, gates, issues);
|
|
16987
|
+
if (projectRoot) {
|
|
16988
|
+
const writerRolePath = join(projectRoot, ".kxm", "roles", "writer.yaml");
|
|
16989
|
+
const implementerAgent = agents.get("implementer") ?? agents.get("writer");
|
|
16990
|
+
if (existsSync2(writerRolePath) && implementerAgent) {
|
|
16991
|
+
try {
|
|
16992
|
+
const rawRole = parseRestrictedYaml2(readFileSync(writerRolePath, "utf8"));
|
|
16993
|
+
const roleObj = objectValue(rawRole);
|
|
16994
|
+
const rosterEntries = valuesOf(roleObj ?? {}, "roster").map((candidate) => objectValue(candidate)).filter((entry) => Boolean(entry));
|
|
16995
|
+
const enabledRosterModels = rosterEntries.filter((entry) => entry.enabled !== false).map((entry) => stringValue(entry.model)).filter((m) => Boolean(m));
|
|
16996
|
+
const agentModelObj = objectValue(implementerAgent.value.model);
|
|
16997
|
+
const agentModelStr = stringValue(implementerAgent.value.model);
|
|
16998
|
+
const agentProvider = agentModelObj ? stringValue(agentModelObj.provider) : void 0;
|
|
16999
|
+
const agentModel = agentModelObj ? stringValue(agentModelObj.model) : agentModelStr;
|
|
17000
|
+
const canonicalAgentModel = agentProvider && agentModel ? `${agentProvider}/${agentModel}` : agentModel;
|
|
17001
|
+
if (enabledRosterModels.length > 0 && canonicalAgentModel) {
|
|
17002
|
+
const matches = enabledRosterModels.some((rm) => rm === canonicalAgentModel || rm === agentModel || rm.endsWith(`/${agentModel}`));
|
|
17003
|
+
if (!matches) {
|
|
17004
|
+
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}`));
|
|
17005
|
+
}
|
|
17006
|
+
}
|
|
17007
|
+
} catch (error) {
|
|
17008
|
+
if (error instanceof VnextConfigError) {
|
|
17009
|
+
issues.push(...error.issues);
|
|
17010
|
+
}
|
|
17011
|
+
}
|
|
17012
|
+
}
|
|
17013
|
+
}
|
|
16976
17014
|
return sortIssues2(issues);
|
|
16977
17015
|
}
|
|
16978
17016
|
function vnextCanonicalJson(value) {
|
|
@@ -17153,7 +17191,7 @@ function loadVnextProject(projectRoot, options = {}) {
|
|
|
17153
17191
|
}
|
|
17154
17192
|
}
|
|
17155
17193
|
if (loadIssues.length > 0) throw new VnextConfigError(loadIssues);
|
|
17156
|
-
const issues = validateBundle(project, repositories, agents, models, workflows, environments, options, gateRegistry);
|
|
17194
|
+
const issues = validateBundle(project, repositories, agents, models, workflows, environments, options, gateRegistry, root);
|
|
17157
17195
|
if (issues.length > 0) throw new VnextConfigError(issues);
|
|
17158
17196
|
const resources = [project, ...repositories.values(), ...agents.values(), ...models.values(), ...workflows.values(), ...environments, ...gateRegistry ? [gateRegistry] : []].sort((left, right) => compareCodeUnits3(left.logicalPath, right.logicalPath));
|
|
17159
17197
|
return {
|
|
@@ -17265,7 +17303,47 @@ import { dirname as dirname4, join as join4, resolve as resolve4 } from "node:pa
|
|
|
17265
17303
|
|
|
17266
17304
|
// plugins/kxm/src/vnext-bindings.ts
|
|
17267
17305
|
import { homedir } from "node:os";
|
|
17268
|
-
|
|
17306
|
+
|
|
17307
|
+
// plugins/kxm/src/sqlite.ts
|
|
17308
|
+
import { createRequire } from "node:module";
|
|
17309
|
+
var requireFromHere = createRequire(import.meta.url);
|
|
17310
|
+
function loadNative() {
|
|
17311
|
+
try {
|
|
17312
|
+
const mod = requireFromHere("node:sqlite");
|
|
17313
|
+
if (mod.DatabaseSync) return { Ctor: mod.DatabaseSync, bun: false };
|
|
17314
|
+
} catch {
|
|
17315
|
+
}
|
|
17316
|
+
try {
|
|
17317
|
+
const mod = requireFromHere("bun:sqlite");
|
|
17318
|
+
const Ctor = mod?.DatabaseSync ?? mod?.Database;
|
|
17319
|
+
if (Ctor) return { Ctor, bun: true };
|
|
17320
|
+
} catch {
|
|
17321
|
+
}
|
|
17322
|
+
throw new Error("kxm: no supported sqlite module found (need node:sqlite or bun:sqlite)");
|
|
17323
|
+
}
|
|
17324
|
+
var native = loadNative();
|
|
17325
|
+
var DatabaseSync = class {
|
|
17326
|
+
inner;
|
|
17327
|
+
constructor(path, options) {
|
|
17328
|
+
let normalized = options;
|
|
17329
|
+
if (native.bun && options) {
|
|
17330
|
+
const { readOnly, ...rest } = options;
|
|
17331
|
+
normalized = readOnly === void 0 ? rest : { ...rest, readonly: readOnly };
|
|
17332
|
+
}
|
|
17333
|
+
this.inner = normalized === void 0 ? new native.Ctor(path) : new native.Ctor(path, normalized);
|
|
17334
|
+
}
|
|
17335
|
+
prepare(sql) {
|
|
17336
|
+
return this.inner.prepare(sql);
|
|
17337
|
+
}
|
|
17338
|
+
exec(sql) {
|
|
17339
|
+
return this.inner.exec(sql);
|
|
17340
|
+
}
|
|
17341
|
+
close() {
|
|
17342
|
+
this.inner.close();
|
|
17343
|
+
}
|
|
17344
|
+
};
|
|
17345
|
+
|
|
17346
|
+
// plugins/kxm/src/vnext-bindings.ts
|
|
17269
17347
|
import { dirname as dirname2, isAbsolute as isAbsolute2, join as join2, parse, relative as relative2, resolve as resolve2, sep as sep2 } from "node:path";
|
|
17270
17348
|
var MAX_BINDING_RECORD_BYTES = 256 * 1024;
|
|
17271
17349
|
var BINDING_LABEL = "Runtime-local repository bindings";
|
|
@@ -17308,7 +17386,6 @@ import {
|
|
|
17308
17386
|
writeFileSync
|
|
17309
17387
|
} from "node:fs";
|
|
17310
17388
|
import { basename as basename2, dirname as dirname3, join as join3, resolve as resolve3 } from "node:path";
|
|
17311
|
-
import { DatabaseSync as DatabaseSync2 } from "node:sqlite";
|
|
17312
17389
|
function databaseError(code, file, message) {
|
|
17313
17390
|
const issue3 = { phase: "semantic", code, file, message };
|
|
17314
17391
|
return new VnextConfigError([issue3]);
|
|
@@ -17388,7 +17465,7 @@ function openDatabase(file, description, spec) {
|
|
|
17388
17465
|
}
|
|
17389
17466
|
}
|
|
17390
17467
|
}
|
|
17391
|
-
const database = new
|
|
17468
|
+
const database = new DatabaseSync(file);
|
|
17392
17469
|
let transaction = false;
|
|
17393
17470
|
try {
|
|
17394
17471
|
database.exec(`PRAGMA busy_timeout = ${spec.timeoutMs ?? 5e3}`);
|
|
@@ -22465,6 +22542,32 @@ function evaluateArtifactsGate(context, definition) {
|
|
|
22465
22542
|
// plugins/kxm/src/vnext-engine-command.ts
|
|
22466
22543
|
import { spawn as spawn2 } from "node:child_process";
|
|
22467
22544
|
import { createHash as createHash8 } from "node:crypto";
|
|
22545
|
+
|
|
22546
|
+
// plugins/kxm/src/safety-integrity.ts
|
|
22547
|
+
var DESTRUCTIVE_COMMAND_PATTERNS = Object.freeze([
|
|
22548
|
+
/^\s*rm\s+.*-[a-zA-Z]*r[a-zA-Z]*f/i,
|
|
22549
|
+
/^\s*rm\s+.*-[a-zA-Z]*f[a-zA-Z]*r/i,
|
|
22550
|
+
/^\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,
|
|
22551
|
+
/^\s*git\s+reset\s+--hard/i,
|
|
22552
|
+
/^\s*git\s+clean\s+-[a-zA-Z]*f/i,
|
|
22553
|
+
/^\s*git\s+checkout\s+--\s+/i,
|
|
22554
|
+
/^\s*git\s+restore\s+(\.|\*|--staged\s+(\.|\*))/i
|
|
22555
|
+
]);
|
|
22556
|
+
function assertCommandSeatbelt(command) {
|
|
22557
|
+
for (const pattern of DESTRUCTIVE_COMMAND_PATTERNS) {
|
|
22558
|
+
if (pattern.test(command)) {
|
|
22559
|
+
throw new Error(
|
|
22560
|
+
`Command blocked by KXM safety seatbelt: "${command}". Destructive workspace mutations require explicit operator override.`
|
|
22561
|
+
);
|
|
22562
|
+
}
|
|
22563
|
+
}
|
|
22564
|
+
}
|
|
22565
|
+
var INSECURE_SSH_HOST_KEY_PATTERNS = Object.freeze([
|
|
22566
|
+
/StrictHostKeyChecking=(accept-new|no|off)/i,
|
|
22567
|
+
/UserKnownHostsFile=\/dev\/null/i
|
|
22568
|
+
]);
|
|
22569
|
+
|
|
22570
|
+
// plugins/kxm/src/vnext-engine-command.ts
|
|
22468
22571
|
var MAX_DIRECT_TIMER_MS = 2147483647;
|
|
22469
22572
|
var COMMAND_TERM_GRACE_MS = 2e3;
|
|
22470
22573
|
var COMMAND_FINAL_WAIT_MS = 2e3;
|
|
@@ -22581,6 +22684,7 @@ var CommandObserver = class {
|
|
|
22581
22684
|
if (!Number.isInteger(this.definition.timeoutMs) || this.definition.timeoutMs < 1 || this.definition.timeoutMs > MAX_DIRECT_TIMER_MS) {
|
|
22582
22685
|
throw runtimeError("run_events_illegal", "command", "command timeoutMs exceeds the direct timer bound");
|
|
22583
22686
|
}
|
|
22687
|
+
assertCommandSeatbelt(this.definition.argv.join(" "));
|
|
22584
22688
|
this.startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
22585
22689
|
let child;
|
|
22586
22690
|
try {
|
|
@@ -23706,6 +23810,7 @@ async function stepLocked(context, runId, producer, token) {
|
|
|
23706
23810
|
return drivePanel(context, prepared.panel, producer);
|
|
23707
23811
|
}
|
|
23708
23812
|
async function runPreparedCommandGate(context, prepared, definition, token) {
|
|
23813
|
+
assertCommandSeatbelt(definition.argv.join(" "));
|
|
23709
23814
|
const storePath = context.eventStore.path;
|
|
23710
23815
|
const observer = createCommandObserver({
|
|
23711
23816
|
definition,
|
|
@@ -25483,6 +25588,24 @@ function createVnextOneShotProducer(options = {}) {
|
|
|
25483
25588
|
"--output-format",
|
|
25484
25589
|
"json"
|
|
25485
25590
|
];
|
|
25591
|
+
} else if (harness === "agy") {
|
|
25592
|
+
args = [
|
|
25593
|
+
"-m",
|
|
25594
|
+
resolved.model,
|
|
25595
|
+
...permissionArgs,
|
|
25596
|
+
"--output-format",
|
|
25597
|
+
"json",
|
|
25598
|
+
"-p"
|
|
25599
|
+
];
|
|
25600
|
+
} else if (harness === "kimi") {
|
|
25601
|
+
args = [
|
|
25602
|
+
"-m",
|
|
25603
|
+
resolved.model,
|
|
25604
|
+
...permissionArgs,
|
|
25605
|
+
"--output-format",
|
|
25606
|
+
"stream-json",
|
|
25607
|
+
"-p"
|
|
25608
|
+
];
|
|
25486
25609
|
} else {
|
|
25487
25610
|
throw new Error(`oneshot_harness_unsupported: ${harness} permission_profile_unaudited`);
|
|
25488
25611
|
}
|
package/plugins/kxm/package.json
CHANGED
|
@@ -68,6 +68,36 @@
|
|
|
68
68
|
"argumentHint": "[trailers|trust|gates|authz|webhooks]",
|
|
69
69
|
"triggers": ["KM-Session", "trust mode", "secret gate", "RLS", "protocol"],
|
|
70
70
|
"complete": ["trailers", "trust", "gates", "authz", "webhooks"]
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
"name": "kxm-browser-session",
|
|
74
|
+
"argumentHint": "[create|inspect|release|cdp]",
|
|
75
|
+
"triggers": ["browser session", "steel session", "remote browser", "launch browser", "cdp endpoint"],
|
|
76
|
+
"complete": ["create session", "inspect session", "release session", "format cdp"]
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
"name": "kxm-browser-takeover",
|
|
80
|
+
"argumentHint": "[request|signal|verify]",
|
|
81
|
+
"triggers": ["human takeover", "mfa login", "takeover url", "browser auth required", "resume browser"],
|
|
82
|
+
"complete": ["request takeover", "signal complete", "verify auth"]
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
"name": "kxm-browser-explore",
|
|
86
|
+
"argumentHint": "[open|snapshot|get|screenshot]",
|
|
87
|
+
"triggers": ["agent-browser", "explore web", "scrape page", "dom snapshot"],
|
|
88
|
+
"complete": ["agent-browser open", "agent-browser snapshot", "agent-browser get", "agent-browser screenshot"]
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
"name": "kxm-browser-verify",
|
|
92
|
+
"argumentHint": "[reproduce|test|assert|trace]",
|
|
93
|
+
"triggers": ["playwright test", "ui reproduction", "regression test", "e2e verify"],
|
|
94
|
+
"complete": ["playwright test", "reproduce issue", "save trace"]
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
"name": "kxm-browser-diagnostics",
|
|
98
|
+
"argumentHint": "[check|cleanup|recover]",
|
|
99
|
+
"triggers": ["browser diagnostics", "cdp failure", "session timeout", "orphaned browser"],
|
|
100
|
+
"complete": ["check health", "cleanup orphans", "recover session"]
|
|
71
101
|
}
|
|
72
102
|
]
|
|
73
103
|
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: kxm-browser-annotate
|
|
3
|
+
description: Capture a visual DOM section or element, attach structured annotations and change requests, and send them back to the agent.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# KXM Browser Section Capture & Visual Annotation Feedback
|
|
7
|
+
|
|
8
|
+
Use this skill to capture visual screenshots of specific UI sections or elements from a Steel browser session, record structured design/code annotations, and feed actionable change requests directly back to an AI coding agent.
|
|
9
|
+
|
|
10
|
+
## Purpose & Scope
|
|
11
|
+
|
|
12
|
+
- Enable human operators and critic agents to visually review web interfaces.
|
|
13
|
+
- Crop or capture specific DOM elements, cards, modals, or viewport bounding boxes.
|
|
14
|
+
- Attach structured notes (e.g. alignment issues, color contrast, missing data, layout bugs) with severity ratings.
|
|
15
|
+
- Provide a standardized Markdown/JSON feedback payload that an agent can parse and implement immediately.
|
|
16
|
+
|
|
17
|
+
## Workflow
|
|
18
|
+
|
|
19
|
+
```text
|
|
20
|
+
1. CAPTURE SECTION
|
|
21
|
+
└─ Use Playwright element.screenshot() or agent-browser screenshot to isolate the target component.
|
|
22
|
+
|
|
23
|
+
2. ATTACH ANNOTATIONS
|
|
24
|
+
└─ Record bounding box / element selector, defect note, and severity rating.
|
|
25
|
+
|
|
26
|
+
3. ASSEMBLE FEEDBACK PACKAGE
|
|
27
|
+
└─ Package screenshot artifact, element selectors, notes, and concrete change list.
|
|
28
|
+
|
|
29
|
+
4. HANDOFF TO AGENT
|
|
30
|
+
└─ Inject formatted visual feedback into agent context or KXM workflow run.
|
|
31
|
+
|
|
32
|
+
5. AGENT IMPLEMENTS FIX
|
|
33
|
+
└─ Agent modifies code, re-captures the section, and verifies the change visually and with Playwright.
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Capturing a Specific Section with Playwright
|
|
37
|
+
|
|
38
|
+
```typescript
|
|
39
|
+
import { chromium } from "playwright";
|
|
40
|
+
import { resolveSteelConfig, formatCDPEndpoint } from "@kontextmind/kxm/runtime";
|
|
41
|
+
|
|
42
|
+
async function captureSection(sessionId: string, selector: string, outputPath: string) {
|
|
43
|
+
const config = resolveSteelConfig();
|
|
44
|
+
const cdpUrl = formatCDPEndpoint({ id: sessionId, websocketUrl: "" }, config);
|
|
45
|
+
|
|
46
|
+
const browser = await chromium.connectOverCDP(cdpUrl);
|
|
47
|
+
const context = browser.contexts()[0] || await browser.newContext();
|
|
48
|
+
const page = context.pages()[0] || await context.newPage();
|
|
49
|
+
|
|
50
|
+
const element = page.locator(selector);
|
|
51
|
+
await element.screenshot({ path: outputPath });
|
|
52
|
+
|
|
53
|
+
await browser.close();
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Structured Feedback Schema
|
|
58
|
+
|
|
59
|
+
```json
|
|
60
|
+
{
|
|
61
|
+
"sessionId": "sess_12345",
|
|
62
|
+
"url": "https://app.example.com/settings/billing",
|
|
63
|
+
"sectionSelector": "[data-testid='subscription-card']",
|
|
64
|
+
"screenshotPath": ".kxm/artifacts/browser/billing-card.png",
|
|
65
|
+
"overallSummary": "Pricing tier badge overflows card boundary on narrow screens",
|
|
66
|
+
"annotations": [
|
|
67
|
+
{
|
|
68
|
+
"label": "Badge Overflow",
|
|
69
|
+
"selector": ".badge-tier",
|
|
70
|
+
"note": "Text overflows container when tier name is 'Enterprise Plus'",
|
|
71
|
+
"severity": "fix"
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
"label": "Button Padding",
|
|
75
|
+
"selector": "button.upgrade-btn",
|
|
76
|
+
"note": "Increase vertical padding from 8px to 12px for touch target compliance",
|
|
77
|
+
"severity": "suggestion"
|
|
78
|
+
}
|
|
79
|
+
],
|
|
80
|
+
"requestedChanges": [
|
|
81
|
+
"Add `overflow: hidden` or `flex-wrap: wrap` to the subscription header container",
|
|
82
|
+
"Update `.badge-tier` CSS to support dynamic text wrapping",
|
|
83
|
+
"Adjust `button.upgrade-btn` padding to `py-3 px-4`"
|
|
84
|
+
]
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Formatting for the Agent
|
|
89
|
+
|
|
90
|
+
Use `formatAnnotationFeedbackPrompt()` from `@kontextmind/kxm/runtime` to render a clean, checklist-driven prompt that the agent executes step-by-step.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: kxm-browser-auth
|
|
3
|
+
description: Retrieve application credentials and manage authenticated browser profiles safely via pass-cli without secret exposure.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# KXM Browser Credentials & Authenticated Profiles
|
|
7
|
+
|
|
8
|
+
Use this skill to retrieve target application credentials and manage browser session state securely using `pass-cli` as the sole authoritative store.
|
|
9
|
+
|
|
10
|
+
## Purpose & Scope
|
|
11
|
+
|
|
12
|
+
- Enforce `pass-cli` as the single source of truth for credentials and API keys.
|
|
13
|
+
- Prevent secrets from leaking into git repositories, logs, prompts, or model-visible tool outputs.
|
|
14
|
+
- Support safe storage and retrieval of session storage state and authenticated profiles.
|
|
15
|
+
|
|
16
|
+
## Credential Retrieval Guidelines
|
|
17
|
+
|
|
18
|
+
### 1. Authoritative Tool: pass-cli
|
|
19
|
+
|
|
20
|
+
Always retrieve credentials and API keys directly from `pass-cli`:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
# Retrieve target login password into an environment variable or piping mechanism
|
|
24
|
+
pass-cli item view --vault-name "<vault>" --item-title "<title>" --field password
|
|
25
|
+
|
|
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
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### 2. Secret Redaction Invariants
|
|
31
|
+
|
|
32
|
+
- **Never** write plain passwords, session tokens, or API keys into markdown docs, commit messages, or prompts.
|
|
33
|
+
- **Never** pass plain credentials as unredacted command line arguments in shared logs.
|
|
34
|
+
- Use environment variable injection (`pass-cli run`) or direct in-memory pipes.
|
|
35
|
+
|
|
36
|
+
### 3. Profile & Storage State Management
|
|
37
|
+
|
|
38
|
+
When an authenticated session state (cookies, local storage) needs to be preserved for subsequent test runs:
|
|
39
|
+
|
|
40
|
+
1. **Extract State**:
|
|
41
|
+
Extract storage state from Playwright via `context.storageState({ path: 'state.json' })` or from Steel via `GET /v1/sessions/:id/context`.
|
|
42
|
+
2. **Encrypt / Store Privately**:
|
|
43
|
+
Store sensitive storage state in git-ignored, private locations (e.g. `.kxm/state/browser/` or as an encrypted secret).
|
|
44
|
+
3. **Session Expiration**:
|
|
45
|
+
Treat cookies as transient. When expired, trigger the `kxm-browser-takeover` flow instead of failing silently.
|
|
46
|
+
4. **Account & Profile Separation**:
|
|
47
|
+
Maintain separate storage states per environment (e.g., `dev`, `staging`, `prod`) and per user role (e.g., `admin`, `viewer`). Never mix profiles across concurrent test runs.
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: kxm-browser-diagnostics
|
|
3
|
+
description: Diagnose and recover from Steel connectivity failures, CDP attachment issues, session timeouts, and orphaned browsers.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# KXM Browser Diagnostics and Recovery
|
|
7
|
+
|
|
8
|
+
Use this skill to investigate and resolve connectivity failures, CDP attachment errors, session timeouts, profile contention, and orphaned browser resources.
|
|
9
|
+
|
|
10
|
+
## Common Failure Modes & Resolutions
|
|
11
|
+
|
|
12
|
+
### 1. Steel API Connectivity / 401 Unauthorized
|
|
13
|
+
|
|
14
|
+
- **Symptom**: `Failed to fetch Steel session (401)` or `Connection refused`.
|
|
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)"`.
|
|
18
|
+
- **Remedy**: Update expired or missing API key in your session environment.
|
|
19
|
+
|
|
20
|
+
### 2. CDP WebSocket Attachment Failure
|
|
21
|
+
|
|
22
|
+
- **Symptom**: `WebSocket connection to wss://... failed: 404/500`.
|
|
23
|
+
- **Diagnosis**:
|
|
24
|
+
- Check if the target session ID has already been released or timed out.
|
|
25
|
+
- Verify ingress WebSocket headers: ensure `nginx.ingress.kubernetes.io/websocket-services` is enabled.
|
|
26
|
+
- **Remedy**: Query `GET /v1/sessions/<id>`. If status is `released`, launch a fresh session.
|
|
27
|
+
|
|
28
|
+
### 3. Session Timeout & Expiration
|
|
29
|
+
|
|
30
|
+
- **Symptom**: Session drops abruptly during human takeover or long idling.
|
|
31
|
+
- **Diagnosis**: Steel enforces a default session timeout (300s–1800s).
|
|
32
|
+
- **Remedy**:
|
|
33
|
+
- If a long human task is required, set a higher initial `timeout` parameter during session creation (e.g. `1800000` ms for 30 minutes).
|
|
34
|
+
- On expiration, do not claim continuity: inform the operator and launch a clean session.
|
|
35
|
+
|
|
36
|
+
### 4. Interactive Takeover Viewer Inaccessible
|
|
37
|
+
|
|
38
|
+
- **Symptom**: `https://steel.kontextmind.com/ui` opens but cannot interact with elements.
|
|
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.
|
|
41
|
+
|
|
42
|
+
### 5. Orphaned Browser Processes & Cleanup
|
|
43
|
+
|
|
44
|
+
- **Symptom**: Node memory pressure or high active session counts.
|
|
45
|
+
- **Diagnosis**: Query active sessions list: `curl -s https://steel.kontextmind.com/v1/sessions`.
|
|
46
|
+
- **Remedy**:
|
|
47
|
+
- Iterate through inactive sessions and post `/release` for each stale ID.
|
|
48
|
+
- Ensure all automation scripts wrap browser usage in `try...finally` to release sessions reliably.
|