@kontextmind/kxm 0.7.0 → 0.7.10
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 +41203 -38364
- 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 +1582 -81
- package/plugins/kxm/dist/server.js +129 -4
- package/plugins/kxm/dist/vnext-runtime-supervisor.js +221 -41
- 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/context-skills.ts +373 -0
- package/plugins/kxm/src/cli/hub.ts +614 -0
- package/plugins/kxm/src/cli/roles.ts +615 -0
- package/plugins/kxm/src/cli/system.ts +906 -0
- package/plugins/kxm/src/cli/tasks.ts +364 -0
- package/plugins/kxm/src/cli/types.ts +270 -0
- package/plugins/kxm/src/cli/vnext.ts +698 -0
- package/plugins/kxm/src/cli/workflows.ts +699 -0
- package/plugins/kxm/src/cli.ts +238 -3791
- 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/model-inventory.ts +8 -8
- package/plugins/kxm/src/modes.ts +348 -0
- package/plugins/kxm/src/protocol.ts +111 -0
- package/plugins/kxm/src/role.ts +335 -0
- package/plugins/kxm/src/runtime.ts +4 -0
- package/plugins/kxm/src/safety-integrity.ts +76 -0
- package/plugins/kxm/src/sqlite.ts +76 -0
- package/plugins/kxm/src/ssh-remote.ts +560 -0
- package/plugins/kxm/src/store.ts +1 -1
- package/plugins/kxm/src/subagent-control.ts +312 -0
- 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 +92 -22
- package/plugins/kxm/src/vnext-oneshot-evidence.ts +39 -7
- package/plugins/kxm/src/vnext-oneshot-process.ts +46 -8
- package/plugins/kxm/src/vnext-oneshot-producer.ts +22 -1
- package/plugins/kxm/src/vnext-pi-producer.ts +11 -7
- package/plugins/kxm/src/vnext-runtime-store.ts +1 -1
- package/plugins/kxm/src/vnext-runtime-supervisor.ts +5 -3
- package/plugins/kxm/src/workflow-tui.ts +1 -1
- package/plugins/kxm/src/workflow.ts +144 -0
- package/schemas/vnext/modes.schema.json +56 -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
|
@@ -2986,7 +2986,7 @@ var require_compile = __commonJS({
|
|
|
2986
2986
|
const schOrFunc = root.refs[ref];
|
|
2987
2987
|
if (schOrFunc)
|
|
2988
2988
|
return schOrFunc;
|
|
2989
|
-
let _sch =
|
|
2989
|
+
let _sch = resolve9.call(this, root, ref);
|
|
2990
2990
|
if (_sch === void 0) {
|
|
2991
2991
|
const schema = (_a = root.localRefs) === null || _a === void 0 ? void 0 : _a[ref];
|
|
2992
2992
|
const { schemaId } = this.opts;
|
|
@@ -3013,7 +3013,7 @@ var require_compile = __commonJS({
|
|
|
3013
3013
|
function sameSchemaEnv(s1, s2) {
|
|
3014
3014
|
return s1.schema === s2.schema && s1.root === s2.root && s1.baseId === s2.baseId;
|
|
3015
3015
|
}
|
|
3016
|
-
function
|
|
3016
|
+
function resolve9(root, ref) {
|
|
3017
3017
|
let sch;
|
|
3018
3018
|
while (typeof (sch = this.refs[ref]) == "string")
|
|
3019
3019
|
ref = sch;
|
|
@@ -3834,11 +3834,11 @@ var require_fast_uri = __commonJS({
|
|
|
3834
3834
|
normalizeString(uri, options);
|
|
3835
3835
|
} else if (typeof uri === "object") {
|
|
3836
3836
|
uri = /** @type {T} */
|
|
3837
|
-
|
|
3837
|
+
parse6(serialize(uri, options), options);
|
|
3838
3838
|
}
|
|
3839
3839
|
return uri;
|
|
3840
3840
|
}
|
|
3841
|
-
function
|
|
3841
|
+
function resolve9(baseURI, relativeURI, options) {
|
|
3842
3842
|
const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
|
|
3843
3843
|
const {
|
|
3844
3844
|
parsed: baseParsed,
|
|
@@ -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 =
|
|
3878
|
-
relative5 =
|
|
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
|
|
4170
|
+
function parse6(uri, opts) {
|
|
4171
4171
|
return parseWithStatus(uri, opts).parsed;
|
|
4172
4172
|
}
|
|
4173
4173
|
function normalizeString(uri, opts) {
|
|
@@ -4200,11 +4200,11 @@ var require_fast_uri = __commonJS({
|
|
|
4200
4200
|
var fastUri = {
|
|
4201
4201
|
SCHEMES,
|
|
4202
4202
|
normalize,
|
|
4203
|
-
resolve:
|
|
4203
|
+
resolve: resolve9,
|
|
4204
4204
|
resolveComponent,
|
|
4205
4205
|
equal,
|
|
4206
4206
|
serialize,
|
|
4207
|
-
parse:
|
|
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
|
|
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 =
|
|
14718
|
+
exports.parse = parse6;
|
|
14719
14719
|
exports.parseAllDocuments = parseAllDocuments;
|
|
14720
14720
|
exports.parseDocument = parseDocument2;
|
|
14721
14721
|
exports.stringify = stringify3;
|
|
@@ -15122,11 +15122,24 @@ 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") });
|
|
15128
15141
|
}
|
|
15129
|
-
return new Promise((
|
|
15142
|
+
return new Promise((resolve9) => {
|
|
15130
15143
|
const stdout = [];
|
|
15131
15144
|
const stderr = [];
|
|
15132
15145
|
let outputBytes = 0;
|
|
@@ -15146,6 +15159,7 @@ function defaultSpawn(command, args, options) {
|
|
|
15146
15159
|
let child;
|
|
15147
15160
|
const finish = () => {
|
|
15148
15161
|
if (finished) return;
|
|
15162
|
+
if (stopping && !killSent) return;
|
|
15149
15163
|
finished = true;
|
|
15150
15164
|
for (const timer of [wallTimer, killTimer, drainTimer, reapTimer]) clearTimeout(timer);
|
|
15151
15165
|
options.signal?.removeEventListener("abort", onAbort);
|
|
@@ -15155,14 +15169,21 @@ function defaultSpawn(command, args, options) {
|
|
|
15155
15169
|
child?.unref();
|
|
15156
15170
|
const started = Boolean(child?.pid);
|
|
15157
15171
|
if (started && !observedChildExit) error ??= new Error("process_exit_unobserved");
|
|
15158
|
-
|
|
15172
|
+
const unverifiedDescendants = stopping || started && !observedChildExit;
|
|
15173
|
+
resolve9({
|
|
15174
|
+
stdout: Buffer.concat(stdout).toString("utf8"),
|
|
15175
|
+
stderr: Buffer.concat(stderr).toString("utf8"),
|
|
15176
|
+
code,
|
|
15177
|
+
signal,
|
|
15178
|
+
started,
|
|
15179
|
+
observedChildExit,
|
|
15180
|
+
terminationRequested: stopping,
|
|
15181
|
+
...unverifiedDescendants ? { unverifiedDescendants: true } : {},
|
|
15182
|
+
...error ? { error } : {}
|
|
15183
|
+
});
|
|
15159
15184
|
};
|
|
15160
15185
|
const kill = (requested) => {
|
|
15161
|
-
|
|
15162
|
-
if (process.platform !== "win32" && child.pid) process.kill(-child.pid, requested);
|
|
15163
|
-
else child.kill(requested);
|
|
15164
|
-
} catch {
|
|
15165
|
-
}
|
|
15186
|
+
killProcessTree(child, requested);
|
|
15166
15187
|
};
|
|
15167
15188
|
const stop = (reason) => {
|
|
15168
15189
|
if (finished) return;
|
|
@@ -15190,7 +15211,7 @@ function defaultSpawn(command, args, options) {
|
|
|
15190
15211
|
cwd: options.cwd,
|
|
15191
15212
|
env: options.env ?? process.env,
|
|
15192
15213
|
stdio: ["pipe", "pipe", "pipe"],
|
|
15193
|
-
shell:
|
|
15214
|
+
shell: options.shell === true,
|
|
15194
15215
|
windowsHide: true,
|
|
15195
15216
|
detached: process.platform !== "win32"
|
|
15196
15217
|
});
|
|
@@ -15234,7 +15255,7 @@ function defaultSpawn(command, args, options) {
|
|
|
15234
15255
|
closed = true;
|
|
15235
15256
|
code = exitCode;
|
|
15236
15257
|
signal = exitSignal;
|
|
15237
|
-
|
|
15258
|
+
finish();
|
|
15238
15259
|
});
|
|
15239
15260
|
options.signal?.addEventListener("abort", onAbort, { once: true });
|
|
15240
15261
|
if (options.signal?.aborted) onAbort();
|
|
@@ -15489,7 +15510,9 @@ function parseAgyOneShotUsage(stdout, _stderr) {
|
|
|
15489
15510
|
var READ_ONLY_ONESHOT_ARGS = Object.freeze({
|
|
15490
15511
|
claude: Object.freeze(["--tools", "Read,Glob,Grep", "--restricted", "--safe-mode", "--strict-mcp-config", "--mcp-config", '{"mcpServers":{}}', "--disable-slash-commands", "--no-session-persistence"]),
|
|
15491
15512
|
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"])
|
|
15513
|
+
grok: Object.freeze(["--sandbox", "read-only", "--permission-mode", "plan", "--tools", "Read,Glob,Grep", "--no-subagents", "--disable-web-search"]),
|
|
15514
|
+
agy: Object.freeze(["--mode", "plan", "--sandbox", "--disable-slash-commands"]),
|
|
15515
|
+
kimi: Object.freeze(["--plan"])
|
|
15493
15516
|
});
|
|
15494
15517
|
function oneShotReadOnlyArgs(harness) {
|
|
15495
15518
|
return Object.hasOwn(READ_ONLY_ONESHOT_ARGS, harness) ? READ_ONLY_ONESHOT_ARGS[harness] : void 0;
|
|
@@ -15543,7 +15566,7 @@ var BUILTIN_HARNESSES = Object.freeze([
|
|
|
15543
15566
|
authArgs: ["provider", "list"],
|
|
15544
15567
|
update: { self: ["upgrade"] },
|
|
15545
15568
|
oneShot: {
|
|
15546
|
-
argv: ["--output-format", "stream-json", "-p"],
|
|
15569
|
+
argv: [...oneShotReadOnlyArgs("kimi"), "--output-format", "stream-json", "-p"],
|
|
15547
15570
|
promptVia: "arg",
|
|
15548
15571
|
outputFormat: "stream-json",
|
|
15549
15572
|
usageParser: parseKimiOneShotUsage
|
|
@@ -15606,7 +15629,7 @@ var BUILTIN_HARNESSES = Object.freeze([
|
|
|
15606
15629
|
authArgs: ["models"],
|
|
15607
15630
|
update: { self: ["update"] },
|
|
15608
15631
|
oneShot: {
|
|
15609
|
-
argv: ["--output-format", "json", "-p"],
|
|
15632
|
+
argv: [...oneShotReadOnlyArgs("agy"), "--output-format", "json", "-p"],
|
|
15610
15633
|
promptVia: "arg",
|
|
15611
15634
|
outputFormat: "json",
|
|
15612
15635
|
usageParser: parseAgyOneShotUsage
|
|
@@ -15673,6 +15696,31 @@ function defaultRunner(env) {
|
|
|
15673
15696
|
}
|
|
15674
15697
|
};
|
|
15675
15698
|
}
|
|
15699
|
+
function commandResultFromSpawn(result) {
|
|
15700
|
+
const errno = result.error && "code" in result.error ? result.error.code : void 0;
|
|
15701
|
+
const error = (typeof errno === "string" && errno ? errno : void 0) ?? result.error?.message ?? (result.observedChildExit !== true ? "auth_probe_exit_unobserved" : void 0);
|
|
15702
|
+
return {
|
|
15703
|
+
ok: result.code === 0 && !error,
|
|
15704
|
+
code: result.code,
|
|
15705
|
+
stdout: result.stdout,
|
|
15706
|
+
stderr: result.stderr,
|
|
15707
|
+
...error ? { error } : {}
|
|
15708
|
+
};
|
|
15709
|
+
}
|
|
15710
|
+
function defaultAsyncRunner(env, signal) {
|
|
15711
|
+
return async (command, args, timeoutMs) => {
|
|
15712
|
+
try {
|
|
15713
|
+
return commandResultFromSpawn(await defaultSpawn(command, args, {
|
|
15714
|
+
env,
|
|
15715
|
+
timeoutMs,
|
|
15716
|
+
signal,
|
|
15717
|
+
shell: harnessSpawnUsesShell(command)
|
|
15718
|
+
}));
|
|
15719
|
+
} catch (error) {
|
|
15720
|
+
return { ok: false, code: null, stdout: "", stderr: "", error: error instanceof Error ? error.message : "spawn_failed" };
|
|
15721
|
+
}
|
|
15722
|
+
};
|
|
15723
|
+
}
|
|
15676
15724
|
function firstLine(text) {
|
|
15677
15725
|
const line = text.split(/\r?\n/).map((candidate) => candidate.trim()).find(Boolean);
|
|
15678
15726
|
return line && line.length <= 200 ? line : void 0;
|
|
@@ -15871,6 +15919,13 @@ function probeHarnesses(options = {}) {
|
|
|
15871
15919
|
harnesses: BUILTIN_HARNESSES.map((entry) => probeEntry(entry, runCommand, timeoutMs, platform, options))
|
|
15872
15920
|
};
|
|
15873
15921
|
}
|
|
15922
|
+
async function probeHarnessesAsync(options = {}) {
|
|
15923
|
+
return replayHarnessProbe(
|
|
15924
|
+
options.runCommand ?? defaultAsyncRunner(options.env ?? process.env, options.signal),
|
|
15925
|
+
(runCommand) => probeHarnesses({ ...options, runCommand }),
|
|
15926
|
+
256
|
|
15927
|
+
);
|
|
15928
|
+
}
|
|
15874
15929
|
function eligibleHarnesses(inventory) {
|
|
15875
15930
|
const eligible = inventory.harnesses.filter((entry) => entry !== void 0 && entry.detected && entry.authenticated === true).map((entry) => entry.id);
|
|
15876
15931
|
if (eligible.length === 0) throw new Error("no_authenticated_harness");
|
|
@@ -16107,35 +16162,31 @@ var PendingHarnessCommand = class {
|
|
|
16107
16162
|
this.key = key;
|
|
16108
16163
|
}
|
|
16109
16164
|
};
|
|
16110
|
-
async function
|
|
16165
|
+
async function replayHarnessProbe(run, execute, limit) {
|
|
16111
16166
|
const observed = /* @__PURE__ */ new Map();
|
|
16112
|
-
|
|
16113
|
-
const result = await defaultSpawn(command, args, { env: options.env, timeoutMs, signal: options.signal });
|
|
16114
|
-
const error = result.error?.message ?? (result.observedChildExit !== true ? "auth_probe_exit_unobserved" : void 0);
|
|
16115
|
-
return {
|
|
16116
|
-
ok: result.code === 0 && !error,
|
|
16117
|
-
code: result.code,
|
|
16118
|
-
stdout: result.stdout,
|
|
16119
|
-
stderr: result.stderr,
|
|
16120
|
-
...error ? { error } : {}
|
|
16121
|
-
};
|
|
16122
|
-
});
|
|
16123
|
-
for (let commands = 0; commands <= 32; commands++) {
|
|
16167
|
+
for (let commands = 0; commands <= limit; commands++) {
|
|
16124
16168
|
try {
|
|
16125
|
-
return
|
|
16169
|
+
return execute((command, args, timeoutMs) => {
|
|
16126
16170
|
const key = JSON.stringify([command, args, timeoutMs]);
|
|
16127
16171
|
const result = observed.get(key);
|
|
16128
16172
|
if (result) return result;
|
|
16129
16173
|
throw new PendingHarnessCommand(command, args, timeoutMs, key);
|
|
16130
|
-
}
|
|
16174
|
+
});
|
|
16131
16175
|
} catch (pending) {
|
|
16132
16176
|
if (!(pending instanceof PendingHarnessCommand)) throw pending;
|
|
16133
|
-
if (commands ===
|
|
16177
|
+
if (commands === limit) throw new Error("auth_probe_command_limit");
|
|
16134
16178
|
observed.set(pending.key, await run(pending.command, pending.args, pending.timeoutMs));
|
|
16135
16179
|
}
|
|
16136
16180
|
}
|
|
16137
16181
|
throw new Error("auth_probe_command_limit");
|
|
16138
16182
|
}
|
|
16183
|
+
async function probeHarnessAssignmentAsync(options) {
|
|
16184
|
+
return replayHarnessProbe(
|
|
16185
|
+
options.runCommand ?? defaultAsyncRunner(options.env ?? process.env, options.signal),
|
|
16186
|
+
(runCommand) => probeHarnessAssignment({ ...options, runCommand }),
|
|
16187
|
+
32
|
|
16188
|
+
);
|
|
16189
|
+
}
|
|
16139
16190
|
function probeHarnessesForModel(modelSpec, options = {}) {
|
|
16140
16191
|
const timeoutMs = options.timeoutMs ?? 3e3;
|
|
16141
16192
|
const runCommand = options.runCommand ?? defaultRunner(options.env ?? process.env);
|
|
@@ -16156,6 +16207,13 @@ function probeHarnessesForModel(modelSpec, options = {}) {
|
|
|
16156
16207
|
)
|
|
16157
16208
|
};
|
|
16158
16209
|
}
|
|
16210
|
+
async function probeHarnessesForModelAsync(modelSpec, options = {}) {
|
|
16211
|
+
return replayHarnessProbe(
|
|
16212
|
+
options.runCommand ?? defaultAsyncRunner(options.env ?? process.env, options.signal),
|
|
16213
|
+
(runCommand) => probeHarnessesForModel(modelSpec, { ...options, runCommand }),
|
|
16214
|
+
256
|
|
16215
|
+
);
|
|
16216
|
+
}
|
|
16159
16217
|
function scopesFor(scope, entry) {
|
|
16160
16218
|
if (scope === "self") return ["self"];
|
|
16161
16219
|
if (scope === "extensions") return entry.update.extensions ? ["extensions"] : [];
|
|
@@ -16914,8 +16972,8 @@ function validateWorkflow(workflow, agents, models, repositories, gates, issues)
|
|
|
16914
16972
|
const writable = Object.values(objectValue(step.repositories) ?? {}).filter((access) => access === "write").length;
|
|
16915
16973
|
if (maxWriteRepositories > writable) issues.push(issue2("semantic", "write_repository_bound_invalid", file, `${stepId} maxWriteRepositories exceeds writable repository scope`));
|
|
16916
16974
|
}
|
|
16917
|
-
const
|
|
16918
|
-
const minimumPassed =
|
|
16975
|
+
const join17 = objectValue(step.join);
|
|
16976
|
+
const minimumPassed = join17 && typeof join17.minimumPassed === "number" ? join17.minimumPassed : void 0;
|
|
16919
16977
|
if (minimumPassed !== void 0 && minimumPassed > maximum) issues.push(issue2("semantic", "join_impossible", file, `${stepId} minimumPassed exceeds assignment maximum`));
|
|
16920
16978
|
const distinctBy = names(assignment?.distinctBy);
|
|
16921
16979
|
if (distinctBy.length > 0) {
|
|
@@ -17060,7 +17118,7 @@ function validateModelReferences(models, issues) {
|
|
|
17060
17118
|
};
|
|
17061
17119
|
for (const id of models.keys()) walk(id, []);
|
|
17062
17120
|
}
|
|
17063
|
-
function validateBundle(project, repositories, agents, models, workflows, environments, options, gateRegistry) {
|
|
17121
|
+
function validateBundle(project, repositories, agents, models, workflows, environments, options, gateRegistry, projectRoot) {
|
|
17064
17122
|
const issues = [];
|
|
17065
17123
|
const entries = valuesOf(project.value, "repositories").map((candidate) => objectValue(candidate)).filter((candidate) => Boolean(candidate));
|
|
17066
17124
|
const repositoryIds = /* @__PURE__ */ new Set();
|
|
@@ -17128,6 +17186,33 @@ function validateBundle(project, repositories, agents, models, workflows, enviro
|
|
|
17128
17186
|
const defaultWorkflow = stringValue(project.value.defaultWorkflow) ?? "default";
|
|
17129
17187
|
if (!workflows.has(defaultWorkflow)) issues.push(issue2("reference", "default_workflow_unknown", project.logicalPath, `default workflow ${defaultWorkflow} does not exist`));
|
|
17130
17188
|
for (const workflow of workflows.values()) validateWorkflow(workflow, agents, models, repositoryIds, gates, issues);
|
|
17189
|
+
if (projectRoot) {
|
|
17190
|
+
const writerRolePath = join(projectRoot, ".kxm", "roles", "writer.yaml");
|
|
17191
|
+
const implementerAgent = agents.get("implementer") ?? agents.get("writer");
|
|
17192
|
+
if (existsSync2(writerRolePath) && implementerAgent) {
|
|
17193
|
+
try {
|
|
17194
|
+
const rawRole = parseRestrictedYaml2(readFileSync(writerRolePath, "utf8"));
|
|
17195
|
+
const roleObj = objectValue(rawRole);
|
|
17196
|
+
const rosterEntries = valuesOf(roleObj ?? {}, "roster").map((candidate) => objectValue(candidate)).filter((entry) => Boolean(entry));
|
|
17197
|
+
const enabledRosterModels = rosterEntries.filter((entry) => entry.enabled !== false).map((entry) => stringValue(entry.model)).filter((m) => Boolean(m));
|
|
17198
|
+
const agentModelObj = objectValue(implementerAgent.value.model);
|
|
17199
|
+
const agentModelStr = stringValue(implementerAgent.value.model);
|
|
17200
|
+
const agentProvider = agentModelObj ? stringValue(agentModelObj.provider) : void 0;
|
|
17201
|
+
const agentModel = agentModelObj ? stringValue(agentModelObj.model) : agentModelStr;
|
|
17202
|
+
const canonicalAgentModel = agentProvider && agentModel ? `${agentProvider}/${agentModel}` : agentModel;
|
|
17203
|
+
if (enabledRosterModels.length > 0 && canonicalAgentModel) {
|
|
17204
|
+
const matches = enabledRosterModels.some((rm) => rm === canonicalAgentModel || rm === agentModel || rm.endsWith(`/${agentModel}`));
|
|
17205
|
+
if (!matches) {
|
|
17206
|
+
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}`));
|
|
17207
|
+
}
|
|
17208
|
+
}
|
|
17209
|
+
} catch (error) {
|
|
17210
|
+
if (error instanceof VnextConfigError) {
|
|
17211
|
+
issues.push(...error.issues);
|
|
17212
|
+
}
|
|
17213
|
+
}
|
|
17214
|
+
}
|
|
17215
|
+
}
|
|
17131
17216
|
return sortIssues2(issues);
|
|
17132
17217
|
}
|
|
17133
17218
|
function vnextCanonicalJson(value) {
|
|
@@ -17308,7 +17393,7 @@ function loadVnextProject(projectRoot, options = {}) {
|
|
|
17308
17393
|
}
|
|
17309
17394
|
}
|
|
17310
17395
|
if (loadIssues.length > 0) throw new VnextConfigError(loadIssues);
|
|
17311
|
-
const issues = validateBundle(project, repositories, agents, models, workflows, environments, options, gateRegistry);
|
|
17396
|
+
const issues = validateBundle(project, repositories, agents, models, workflows, environments, options, gateRegistry, root);
|
|
17312
17397
|
if (issues.length > 0) throw new VnextConfigError(issues);
|
|
17313
17398
|
const resources = [project, ...repositories.values(), ...agents.values(), ...models.values(), ...workflows.values(), ...environments, ...gateRegistry ? [gateRegistry] : []].sort((left, right) => compareCodeUnits3(left.logicalPath, right.logicalPath));
|
|
17314
17399
|
return {
|
|
@@ -17420,7 +17505,47 @@ import { dirname as dirname4, join as join4, resolve as resolve4 } from "node:pa
|
|
|
17420
17505
|
|
|
17421
17506
|
// plugins/kxm/src/vnext-bindings.ts
|
|
17422
17507
|
import { homedir } from "node:os";
|
|
17423
|
-
|
|
17508
|
+
|
|
17509
|
+
// plugins/kxm/src/sqlite.ts
|
|
17510
|
+
import { createRequire } from "node:module";
|
|
17511
|
+
var requireFromHere = createRequire(import.meta.url);
|
|
17512
|
+
function loadNative() {
|
|
17513
|
+
try {
|
|
17514
|
+
const mod = requireFromHere("node:sqlite");
|
|
17515
|
+
if (mod.DatabaseSync) return { Ctor: mod.DatabaseSync, bun: false };
|
|
17516
|
+
} catch {
|
|
17517
|
+
}
|
|
17518
|
+
try {
|
|
17519
|
+
const mod = requireFromHere("bun:sqlite");
|
|
17520
|
+
const Ctor = mod?.DatabaseSync ?? mod?.Database;
|
|
17521
|
+
if (Ctor) return { Ctor, bun: true };
|
|
17522
|
+
} catch {
|
|
17523
|
+
}
|
|
17524
|
+
throw new Error("kxm: no supported sqlite module found (need node:sqlite or bun:sqlite)");
|
|
17525
|
+
}
|
|
17526
|
+
var native = loadNative();
|
|
17527
|
+
var DatabaseSync = class {
|
|
17528
|
+
inner;
|
|
17529
|
+
constructor(path, options) {
|
|
17530
|
+
let normalized = options;
|
|
17531
|
+
if (native.bun && options) {
|
|
17532
|
+
const { readOnly, ...rest } = options;
|
|
17533
|
+
normalized = readOnly === void 0 ? rest : { ...rest, readonly: readOnly };
|
|
17534
|
+
}
|
|
17535
|
+
this.inner = normalized === void 0 ? new native.Ctor(path) : new native.Ctor(path, normalized);
|
|
17536
|
+
}
|
|
17537
|
+
prepare(sql) {
|
|
17538
|
+
return this.inner.prepare(sql);
|
|
17539
|
+
}
|
|
17540
|
+
exec(sql) {
|
|
17541
|
+
return this.inner.exec(sql);
|
|
17542
|
+
}
|
|
17543
|
+
close() {
|
|
17544
|
+
this.inner.close();
|
|
17545
|
+
}
|
|
17546
|
+
};
|
|
17547
|
+
|
|
17548
|
+
// plugins/kxm/src/vnext-bindings.ts
|
|
17424
17549
|
import { dirname as dirname2, isAbsolute as isAbsolute2, join as join2, parse, relative as relative2, resolve as resolve2, sep as sep2 } from "node:path";
|
|
17425
17550
|
var MAX_BINDING_RECORD_BYTES = 256 * 1024;
|
|
17426
17551
|
var BINDING_LABEL = "Runtime-local repository bindings";
|
|
@@ -17464,7 +17589,6 @@ import {
|
|
|
17464
17589
|
writeFileSync
|
|
17465
17590
|
} from "node:fs";
|
|
17466
17591
|
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
17592
|
function databaseError(code, file, message) {
|
|
17469
17593
|
const issue3 = { phase: "semantic", code, file, message };
|
|
17470
17594
|
return new VnextConfigError([issue3]);
|
|
@@ -17544,7 +17668,7 @@ function openDatabase(file, description, spec) {
|
|
|
17544
17668
|
}
|
|
17545
17669
|
}
|
|
17546
17670
|
}
|
|
17547
|
-
const database = new
|
|
17671
|
+
const database = new DatabaseSync(file);
|
|
17548
17672
|
let transaction = false;
|
|
17549
17673
|
try {
|
|
17550
17674
|
database.exec(`PRAGMA busy_timeout = ${spec.timeoutMs ?? 5e3}`);
|
|
@@ -17648,7 +17772,7 @@ function backupDatabaseFile(sourcePath, targetPath, storeId) {
|
|
|
17648
17772
|
if (existsSync3(resolvedTarget)) {
|
|
17649
17773
|
unlinkSync(resolvedTarget);
|
|
17650
17774
|
}
|
|
17651
|
-
const sourceDb = new
|
|
17775
|
+
const sourceDb = new DatabaseSync(resolvedSource);
|
|
17652
17776
|
let schemaVersion = 0;
|
|
17653
17777
|
try {
|
|
17654
17778
|
sourceDb.exec("PRAGMA busy_timeout = 5000");
|
|
@@ -17663,7 +17787,7 @@ function backupDatabaseFile(sourcePath, targetPath, storeId) {
|
|
|
17663
17787
|
} finally {
|
|
17664
17788
|
sourceDb.close();
|
|
17665
17789
|
}
|
|
17666
|
-
const targetDb = new
|
|
17790
|
+
const targetDb = new DatabaseSync(resolvedTarget);
|
|
17667
17791
|
try {
|
|
17668
17792
|
targetDb.exec("PRAGMA busy_timeout = 5000");
|
|
17669
17793
|
if (!checkIntegrity(targetDb)) {
|
|
@@ -17695,7 +17819,7 @@ function restoreDatabaseFile(backupPath, targetPath, storeId, expectedSchemaVers
|
|
|
17695
17819
|
if (!backupStat || !backupStat.isFile() || backupStat.isSymbolicLink()) {
|
|
17696
17820
|
throw databaseError("runtime_path_invalid", resolvedBackup, `backup database ${resolvedBackup} must be a regular file, not a link or directory`);
|
|
17697
17821
|
}
|
|
17698
|
-
const backupDb = new
|
|
17822
|
+
const backupDb = new DatabaseSync(resolvedBackup);
|
|
17699
17823
|
let schemaVersion = 0;
|
|
17700
17824
|
try {
|
|
17701
17825
|
backupDb.exec("PRAGMA busy_timeout = 5000");
|
|
@@ -17735,7 +17859,7 @@ function restoreDatabaseFile(backupPath, targetPath, storeId, expectedSchemaVers
|
|
|
17735
17859
|
chmodSync(resolvedTarget, 384);
|
|
17736
17860
|
} catch {
|
|
17737
17861
|
}
|
|
17738
|
-
const targetDb = new
|
|
17862
|
+
const targetDb = new DatabaseSync(resolvedTarget);
|
|
17739
17863
|
try {
|
|
17740
17864
|
targetDb.exec("PRAGMA busy_timeout = 5000");
|
|
17741
17865
|
if (!checkIntegrity(targetDb)) {
|
|
@@ -20377,7 +20501,7 @@ function compileStep(step, index, stepIndex, requirePlanHash, sink) {
|
|
|
20377
20501
|
const maxAttempts = compileCountField(step.maxAttempts, 1, `${id}.maxAttempts`, id, sink);
|
|
20378
20502
|
const timeoutMs = compileOptionalDuration(step.timeoutMs, `${id}.timeoutMs`, id, sink);
|
|
20379
20503
|
const assignments = compileAssignments(step, id, agent, sink);
|
|
20380
|
-
const
|
|
20504
|
+
const join17 = compileJoin(step, id, sink);
|
|
20381
20505
|
const requiredEvidence = compileEvidence(step, id, sink);
|
|
20382
20506
|
const transitions = compileTransitions(step, id, index, stepIndex, sink);
|
|
20383
20507
|
const outcomes = Object.keys(transitions).sort(compareCodeUnits4);
|
|
@@ -20405,7 +20529,7 @@ function compileStep(step, index, stepIndex, requirePlanHash, sink) {
|
|
|
20405
20529
|
transitions: orderedTransitions,
|
|
20406
20530
|
requiresPlanHash: requirePlanHash.includes(id),
|
|
20407
20531
|
assignments,
|
|
20408
|
-
join:
|
|
20532
|
+
join: join17
|
|
20409
20533
|
};
|
|
20410
20534
|
if (kind === "agent" || kind === "moa") {
|
|
20411
20535
|
if (!agent) return void 0;
|
|
@@ -20455,15 +20579,15 @@ function compileAssignments(step, stepId, primaryAgentId, sink) {
|
|
|
20455
20579
|
};
|
|
20456
20580
|
}
|
|
20457
20581
|
function compileJoin(step, stepId, sink) {
|
|
20458
|
-
const
|
|
20459
|
-
if (!
|
|
20460
|
-
const declared = stringValue2(
|
|
20582
|
+
const join17 = objectValue2(step.join);
|
|
20583
|
+
if (!join17) return { strategy: "all" };
|
|
20584
|
+
const declared = stringValue2(join17.strategy);
|
|
20461
20585
|
const strategy = declared && JOIN_STRATEGIES.has(declared) ? declared : "all";
|
|
20462
|
-
const minimumPassed = compileOptionalCount(
|
|
20586
|
+
const minimumPassed = compileOptionalCount(join17.minimumPassed, `${stepId}.join.minimumPassed`, stepId, sink);
|
|
20463
20587
|
const compiled = {
|
|
20464
20588
|
strategy,
|
|
20465
20589
|
...minimumPassed !== void 0 ? { minimumPassed } : {},
|
|
20466
|
-
...typeof
|
|
20590
|
+
...typeof join17.cancelRemaining === "boolean" ? { cancelRemaining: join17.cancelRemaining } : {}
|
|
20467
20591
|
};
|
|
20468
20592
|
return compiled;
|
|
20469
20593
|
}
|
|
@@ -21758,12 +21882,12 @@ function enqueueVnextScheduledRun(storePath, runId, configRevision, bound, start
|
|
|
21758
21882
|
if (owner.admitted.has(runId) || owner.queue.some((item) => item.runId === runId)) {
|
|
21759
21883
|
return Promise.reject(runtimeError("run_busy", runId, `run ${runId} is already admitted or queued`));
|
|
21760
21884
|
}
|
|
21761
|
-
return new Promise((
|
|
21885
|
+
return new Promise((resolve9, reject) => {
|
|
21762
21886
|
owner.queue.push({
|
|
21763
21887
|
runId,
|
|
21764
21888
|
configRevision,
|
|
21765
21889
|
bound,
|
|
21766
|
-
start: (token) => start(token).then(
|
|
21890
|
+
start: (token) => start(token).then(resolve9, reject),
|
|
21767
21891
|
fail: (error) => reject(error)
|
|
21768
21892
|
});
|
|
21769
21893
|
pump(storePath, owner);
|
|
@@ -22274,9 +22398,28 @@ import { join as join11 } from "node:path";
|
|
|
22274
22398
|
import { createHash as createHash7, randomUUID as randomUUID3 } from "node:crypto";
|
|
22275
22399
|
import { lstat, mkdir, open, rename } from "node:fs/promises";
|
|
22276
22400
|
import { join as join6 } from "node:path";
|
|
22401
|
+
var ONESHOT_EVIDENCE_SCHEMA = "kxm.oneshot-evidence.v2";
|
|
22402
|
+
var RETIRED_ONESHOT_EVIDENCE_SCHEMAS = /* @__PURE__ */ new Set(["kxm.oneshot-evidence.v1"]);
|
|
22277
22403
|
var TEXT_LIMIT = 4 * 1024 * 1024;
|
|
22404
|
+
var ARGV_LIMIT = 64 * 1024;
|
|
22278
22405
|
var RECORD_LIMIT = 16 * 1024 * 1024;
|
|
22279
22406
|
var digest = (value) => `sha256:${createHash7("sha256").update(value).digest("hex")}`;
|
|
22407
|
+
function parseOneShotEvidenceRecord(bytes) {
|
|
22408
|
+
let parsed;
|
|
22409
|
+
try {
|
|
22410
|
+
parsed = JSON.parse(bytes);
|
|
22411
|
+
} catch {
|
|
22412
|
+
throw new Error("oneshot_evidence_record_invalid");
|
|
22413
|
+
}
|
|
22414
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
22415
|
+
throw new Error("oneshot_evidence_record_invalid");
|
|
22416
|
+
}
|
|
22417
|
+
const schema = parsed.schema;
|
|
22418
|
+
if (typeof schema !== "string") throw new Error("oneshot_evidence_record_invalid");
|
|
22419
|
+
if (RETIRED_ONESHOT_EVIDENCE_SCHEMAS.has(schema)) throw new Error("oneshot_evidence_schema_retired");
|
|
22420
|
+
if (schema !== ONESHOT_EVIDENCE_SCHEMA) throw new Error("oneshot_evidence_schema_unknown");
|
|
22421
|
+
return parsed;
|
|
22422
|
+
}
|
|
22280
22423
|
async function beginOneShotEvidence(root, intent, sensitive) {
|
|
22281
22424
|
await mkdir(root, { recursive: true, mode: 448 });
|
|
22282
22425
|
const stat = await lstat(root);
|
|
@@ -22290,11 +22433,11 @@ async function beginOneShotEvidence(root, intent, sensitive) {
|
|
|
22290
22433
|
const redact = (value) => {
|
|
22291
22434
|
let text = value;
|
|
22292
22435
|
for (const secret of secrets) text = text.replaceAll(secret, "[REDACTED]");
|
|
22293
|
-
return text.replace(/(\b(?:authorization|api[_-]?key|access[_-]?token|refresh[_-]?token|password|secret)\b["']?\s*[:=]\s*["']?)(?:Bearer\s+)?[^\s"',;}]+/gi, "$1[REDACTED]");
|
|
22436
|
+
return text.replace(/(\b(?:authorization|api[_-]?key|access[_-]?token|refresh[_-]?token|password|secret)\b["']?\s*[:=]\s*["']?)(?:Bearer\s+)?[^\s"',;}]+/gi, "$1[REDACTED]").replace(/\bBearer\s+[A-Za-z0-9._~+/=-]+/gi, "Bearer [REDACTED]");
|
|
22294
22437
|
};
|
|
22295
|
-
const bounded = (value) => {
|
|
22438
|
+
const bounded = (value, max = TEXT_LIMIT) => {
|
|
22296
22439
|
const bytes = Buffer.from(redact(value));
|
|
22297
|
-
return { text: bytes.subarray(0,
|
|
22440
|
+
return { text: bytes.subarray(0, max).toString("utf8"), truncated: bytes.length > max, sha256: digest(redact(value)) };
|
|
22298
22441
|
};
|
|
22299
22442
|
const write = async (name, value) => {
|
|
22300
22443
|
const current = await lstat(dir);
|
|
@@ -22303,6 +22446,7 @@ async function beginOneShotEvidence(root, intent, sensitive) {
|
|
|
22303
22446
|
}
|
|
22304
22447
|
const bytes = JSON.stringify(value);
|
|
22305
22448
|
if (Buffer.byteLength(bytes) > RECORD_LIMIT) throw new Error("oneshot_evidence_record_limit");
|
|
22449
|
+
parseOneShotEvidenceRecord(bytes);
|
|
22306
22450
|
const target = join6(dir, name);
|
|
22307
22451
|
const partial = `${target}.partial`;
|
|
22308
22452
|
const file = await open(partial, "wx", 384);
|
|
@@ -22324,7 +22468,7 @@ async function beginOneShotEvidence(root, intent, sensitive) {
|
|
|
22324
22468
|
return digest(bytes);
|
|
22325
22469
|
};
|
|
22326
22470
|
await write("intent.json", {
|
|
22327
|
-
schema:
|
|
22471
|
+
schema: ONESHOT_EVIDENCE_SCHEMA,
|
|
22328
22472
|
id,
|
|
22329
22473
|
recordedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
22330
22474
|
runId: intent.runId,
|
|
@@ -22335,9 +22479,10 @@ async function beginOneShotEvidence(root, intent, sensitive) {
|
|
|
22335
22479
|
provider: intent.provider,
|
|
22336
22480
|
requestedModel: intent.model,
|
|
22337
22481
|
cwd: intent.cwd,
|
|
22338
|
-
command: intent.command,
|
|
22339
|
-
argv: bounded(JSON.stringify(intent.args)),
|
|
22482
|
+
command: redact(intent.command),
|
|
22483
|
+
argv: bounded(JSON.stringify(intent.args), ARGV_LIMIT),
|
|
22340
22484
|
stdin: intent.input === void 0 ? null : bounded(intent.input),
|
|
22485
|
+
env: null,
|
|
22341
22486
|
acceptance: false
|
|
22342
22487
|
});
|
|
22343
22488
|
let finished = false;
|
|
@@ -22347,7 +22492,7 @@ async function beginOneShotEvidence(root, intent, sensitive) {
|
|
|
22347
22492
|
if (finished) throw new Error("oneshot_evidence_already_finished");
|
|
22348
22493
|
finished = true;
|
|
22349
22494
|
return write("result.json", {
|
|
22350
|
-
schema:
|
|
22495
|
+
schema: ONESHOT_EVIDENCE_SCHEMA,
|
|
22351
22496
|
id,
|
|
22352
22497
|
recordedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
22353
22498
|
stdout: bounded(result.stdout),
|
|
@@ -22357,6 +22502,7 @@ async function beginOneShotEvidence(root, intent, sensitive) {
|
|
|
22357
22502
|
started: result.started ?? null,
|
|
22358
22503
|
observedChildExit: result.observedChildExit ?? null,
|
|
22359
22504
|
terminationRequested: result.terminationRequested ?? null,
|
|
22505
|
+
unverifiedDescendants: result.unverifiedDescendants ?? null,
|
|
22360
22506
|
error: result.error ? bounded(result.error.message) : null,
|
|
22361
22507
|
observation: bounded(JSON.stringify(observation)),
|
|
22362
22508
|
acceptance: false
|
|
@@ -22912,6 +23058,43 @@ function evaluateArtifactsGate(context, definition) {
|
|
|
22912
23058
|
// plugins/kxm/src/vnext-engine-command.ts
|
|
22913
23059
|
import { spawn as spawn2 } from "node:child_process";
|
|
22914
23060
|
import { createHash as createHash9 } from "node:crypto";
|
|
23061
|
+
|
|
23062
|
+
// plugins/kxm/src/safety-integrity.ts
|
|
23063
|
+
var DESTRUCTIVE_COMMAND_PATTERNS = Object.freeze([
|
|
23064
|
+
/^\s*rm\s+.*-[a-zA-Z]*r[a-zA-Z]*f/i,
|
|
23065
|
+
/^\s*rm\s+.*-[a-zA-Z]*f[a-zA-Z]*r/i,
|
|
23066
|
+
/^\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,
|
|
23067
|
+
/^\s*git\s+reset\s+--hard/i,
|
|
23068
|
+
/^\s*git\s+clean\s+-[a-zA-Z]*f/i,
|
|
23069
|
+
/^\s*git\s+checkout\s+--\s+/i,
|
|
23070
|
+
/^\s*git\s+restore\s+(\.|\*|--staged\s+(\.|\*))/i
|
|
23071
|
+
]);
|
|
23072
|
+
function assertCommandSeatbelt(command) {
|
|
23073
|
+
for (const pattern of DESTRUCTIVE_COMMAND_PATTERNS) {
|
|
23074
|
+
if (pattern.test(command)) {
|
|
23075
|
+
throw new Error(
|
|
23076
|
+
`Command blocked by KXM safety seatbelt: "${command}". Destructive workspace mutations require explicit operator override.`
|
|
23077
|
+
);
|
|
23078
|
+
}
|
|
23079
|
+
}
|
|
23080
|
+
}
|
|
23081
|
+
var INSECURE_SSH_HOST_KEY_PATTERNS = Object.freeze([
|
|
23082
|
+
/StrictHostKeyChecking=(accept-new|no|off)/i,
|
|
23083
|
+
/UserKnownHostsFile=\/dev\/null/i
|
|
23084
|
+
]);
|
|
23085
|
+
function assertPinnedSshHostKeyPolicy(sshArgs) {
|
|
23086
|
+
for (const arg of sshArgs) {
|
|
23087
|
+
for (const pattern of INSECURE_SSH_HOST_KEY_PATTERNS) {
|
|
23088
|
+
if (pattern.test(arg)) {
|
|
23089
|
+
throw new Error(
|
|
23090
|
+
`Insecure SSH host key policy rejected: "${arg}". KXM headless SSH requires pinned host keys with StrictHostKeyChecking=yes and BatchMode=yes.`
|
|
23091
|
+
);
|
|
23092
|
+
}
|
|
23093
|
+
}
|
|
23094
|
+
}
|
|
23095
|
+
}
|
|
23096
|
+
|
|
23097
|
+
// plugins/kxm/src/vnext-engine-command.ts
|
|
22915
23098
|
var MAX_DIRECT_TIMER_MS = 2147483647;
|
|
22916
23099
|
var COMMAND_TERM_GRACE_MS = 2e3;
|
|
22917
23100
|
var COMMAND_FINAL_WAIT_MS = 2e3;
|
|
@@ -22997,8 +23180,8 @@ var CommandObserver = class {
|
|
|
22997
23180
|
this.requestStop("error", "lost-close");
|
|
22998
23181
|
}
|
|
22999
23182
|
run() {
|
|
23000
|
-
return new Promise((
|
|
23001
|
-
this.resolveOutcome =
|
|
23183
|
+
return new Promise((resolve9) => {
|
|
23184
|
+
this.resolveOutcome = resolve9;
|
|
23002
23185
|
try {
|
|
23003
23186
|
this.start();
|
|
23004
23187
|
} catch (error) {
|
|
@@ -23028,6 +23211,7 @@ var CommandObserver = class {
|
|
|
23028
23211
|
if (!Number.isInteger(this.definition.timeoutMs) || this.definition.timeoutMs < 1 || this.definition.timeoutMs > MAX_DIRECT_TIMER_MS) {
|
|
23029
23212
|
throw runtimeError("run_events_illegal", "command", "command timeoutMs exceeds the direct timer bound");
|
|
23030
23213
|
}
|
|
23214
|
+
assertCommandSeatbelt(this.definition.argv.join(" "));
|
|
23031
23215
|
this.startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
23032
23216
|
let child;
|
|
23033
23217
|
try {
|
|
@@ -24153,6 +24337,7 @@ async function stepLocked(context, runId, producer, token) {
|
|
|
24153
24337
|
return drivePanel(context, prepared.panel, producer);
|
|
24154
24338
|
}
|
|
24155
24339
|
async function runPreparedCommandGate(context, prepared, definition, token) {
|
|
24340
|
+
assertCommandSeatbelt(definition.argv.join(" "));
|
|
24156
24341
|
const storePath = context.eventStore.path;
|
|
24157
24342
|
const observer = createCommandObserver({
|
|
24158
24343
|
definition,
|
|
@@ -25930,6 +26115,24 @@ function createVnextOneShotProducer(options = {}) {
|
|
|
25930
26115
|
"--output-format",
|
|
25931
26116
|
"json"
|
|
25932
26117
|
];
|
|
26118
|
+
} else if (harness === "agy") {
|
|
26119
|
+
args = [
|
|
26120
|
+
"-m",
|
|
26121
|
+
resolved.model,
|
|
26122
|
+
...permissionArgs,
|
|
26123
|
+
"--output-format",
|
|
26124
|
+
"json",
|
|
26125
|
+
"-p"
|
|
26126
|
+
];
|
|
26127
|
+
} else if (harness === "kimi") {
|
|
26128
|
+
args = [
|
|
26129
|
+
"-m",
|
|
26130
|
+
resolved.model,
|
|
26131
|
+
...permissionArgs,
|
|
26132
|
+
"--output-format",
|
|
26133
|
+
"stream-json",
|
|
26134
|
+
"-p"
|
|
26135
|
+
];
|
|
25933
26136
|
} else {
|
|
25934
26137
|
throw new Error(`oneshot_harness_unsupported: ${harness} permission_profile_unaudited`);
|
|
25935
26138
|
}
|
|
@@ -25972,7 +26175,8 @@ Return a final JSON object with an "outcome" field chosen from ${JSON.stringify(
|
|
|
25972
26175
|
parsed = { text: "", isError: true };
|
|
25973
26176
|
}
|
|
25974
26177
|
const aborted = request.signal.aborted || procResult.error?.message === "process_aborted";
|
|
25975
|
-
const
|
|
26178
|
+
const unverifiedDescendants = procResult.unverifiedDescendants === true || procResult.terminationRequested === true || procResult.error?.message === "process_exit_unobserved" || procResult.observedChildExit === false && procResult.started !== false;
|
|
26179
|
+
const effectUncertain = unverifiedDescendants || Boolean(procResult.signal);
|
|
25976
26180
|
const transportFailed = procResult.code !== 0 || Boolean(procResult.error) || effectUncertain;
|
|
25977
26181
|
const outcome = aborted ? "cancelled" : transportFailed || parsed.isError ? "failed" : determineOutcome(parsed.text, request.allowedOutcomes);
|
|
25978
26182
|
const providerMetadata = {
|
|
@@ -25984,6 +26188,7 @@ Return a final JSON object with an "outcome" field chosen from ${JSON.stringify(
|
|
|
25984
26188
|
if (procResult.observedChildExit !== void 0) providerMetadata.observedChildExit = procResult.observedChildExit;
|
|
25985
26189
|
if (procResult.started !== void 0) providerMetadata.processStarted = procResult.started;
|
|
25986
26190
|
if (procResult.terminationRequested !== void 0) providerMetadata.terminationRequested = procResult.terminationRequested;
|
|
26191
|
+
if (unverifiedDescendants) providerMetadata.descendantEffects = "unverified";
|
|
25987
26192
|
if (procResult.error) {
|
|
25988
26193
|
const reason = procResult.error.message;
|
|
25989
26194
|
providerMetadata.processError = /^process_(aborted|timeout|output_limit|stdin_error|stdio_error|stdio_unclosed|exit_unobserved)$/.test(reason) ? reason : "process_error";
|
|
@@ -26409,8 +26614,8 @@ async function startVnextRuntimeSupervisorInner(paths, requestedPortOption, now)
|
|
|
26409
26614
|
}
|
|
26410
26615
|
let releaseActive = () => {
|
|
26411
26616
|
};
|
|
26412
|
-
const activePromise = new Promise((
|
|
26413
|
-
releaseActive =
|
|
26617
|
+
const activePromise = new Promise((resolve9) => {
|
|
26618
|
+
releaseActive = resolve9;
|
|
26414
26619
|
});
|
|
26415
26620
|
activeDrives.set(runId, activePromise);
|
|
26416
26621
|
const cleanupActive = () => {
|
|
@@ -26464,7 +26669,6 @@ async function startVnextRuntimeSupervisorInner(paths, requestedPortOption, now)
|
|
|
26464
26669
|
});
|
|
26465
26670
|
} catch (err) {
|
|
26466
26671
|
earlyError = err;
|
|
26467
|
-
drivePromise = Promise.reject(err);
|
|
26468
26672
|
}
|
|
26469
26673
|
await Promise.resolve();
|
|
26470
26674
|
if (earlyError) {
|
|
@@ -26490,6 +26694,7 @@ async function startVnextRuntimeSupervisorInner(paths, requestedPortOption, now)
|
|
|
26490
26694
|
} catch {
|
|
26491
26695
|
}
|
|
26492
26696
|
}
|
|
26697
|
+
}).catch(() => {
|
|
26493
26698
|
});
|
|
26494
26699
|
sendJson(response, 202, {
|
|
26495
26700
|
ok: true,
|
|
@@ -26866,8 +27071,8 @@ var PiSession = class {
|
|
|
26866
27071
|
}
|
|
26867
27072
|
const id = `cmd_${++this.commandCounter}`;
|
|
26868
27073
|
const payload = { ...command, id };
|
|
26869
|
-
return new Promise((
|
|
26870
|
-
this.pendingCommands.set(id, { resolve:
|
|
27074
|
+
return new Promise((resolve9, reject) => {
|
|
27075
|
+
this.pendingCommands.set(id, { resolve: resolve9, reject });
|
|
26871
27076
|
try {
|
|
26872
27077
|
this.process.stdin.write(JSON.stringify(payload) + "\n");
|
|
26873
27078
|
} catch (err) {
|
|
@@ -26888,7 +27093,7 @@ var PiSession = class {
|
|
|
26888
27093
|
return { outcome, text: "aborted", usage: {} };
|
|
26889
27094
|
}
|
|
26890
27095
|
this.status = "busy";
|
|
26891
|
-
return new Promise((
|
|
27096
|
+
return new Promise((resolve9, reject) => {
|
|
26892
27097
|
let signalCleanup;
|
|
26893
27098
|
if (signal) {
|
|
26894
27099
|
const onAbort = () => {
|
|
@@ -26902,7 +27107,7 @@ var PiSession = class {
|
|
|
26902
27107
|
signalCleanup = () => signal.removeEventListener("abort", onAbort);
|
|
26903
27108
|
}
|
|
26904
27109
|
this.activePrompt = {
|
|
26905
|
-
resolve:
|
|
27110
|
+
resolve: resolve9,
|
|
26906
27111
|
reject,
|
|
26907
27112
|
allowedOutcomes,
|
|
26908
27113
|
text: "",
|
|
@@ -26975,19 +27180,21 @@ function createVnextPiProducer(options = {}) {
|
|
|
26975
27180
|
const parsed = parseModelString(defaultModel);
|
|
26976
27181
|
return { provider: parsed.provider, model: parsed.model };
|
|
26977
27182
|
}
|
|
26978
|
-
function checkPiAuth(provider, model) {
|
|
27183
|
+
async function checkPiAuth(provider, model, signal) {
|
|
26979
27184
|
if (options.inventory) {
|
|
26980
27185
|
const piEntry = options.inventory.harnesses.find((h) => h.id === "pi");
|
|
26981
27186
|
if (!piEntry || !piEntry.detected || piEntry.authenticated === false) {
|
|
26982
27187
|
throw new Error("pi_not_authenticated: pi harness not authenticated in inventory");
|
|
26983
27188
|
}
|
|
26984
27189
|
}
|
|
26985
|
-
const probeFn = options.probeHarness ??
|
|
26986
|
-
const probe = probeFn({
|
|
27190
|
+
const probeFn = options.probeHarness ?? probeHarnessAssignmentAsync;
|
|
27191
|
+
const probe = await probeFn({
|
|
26987
27192
|
harness: "pi",
|
|
26988
27193
|
provider,
|
|
26989
27194
|
model,
|
|
26990
|
-
env: options.env
|
|
27195
|
+
env: options.env,
|
|
27196
|
+
signal,
|
|
27197
|
+
timeoutMs: 1e4
|
|
26991
27198
|
});
|
|
26992
27199
|
if (!probe.detected) {
|
|
26993
27200
|
throw new Error(`pi_not_authenticated: pi harness not detected (${probe.issues.join(", ")})`);
|
|
@@ -27063,7 +27270,7 @@ function createVnextPiProducer(options = {}) {
|
|
|
27063
27270
|
async produce(request) {
|
|
27064
27271
|
const startTime = Date.now();
|
|
27065
27272
|
const resolved = resolveModelForRequest(request);
|
|
27066
|
-
checkPiAuth(resolved.provider, resolved.model);
|
|
27273
|
+
await checkPiAuth(resolved.provider, resolved.model, request.signal);
|
|
27067
27274
|
const session = await getOrCreateSession(request, resolved.model);
|
|
27068
27275
|
const promptMessage = request.prompt ?? `Execute step ${request.stepId} (attempt ${request.stepAttempt}) for agent ${request.agentId}. Allowed outcomes: ${request.allowedOutcomes.join(", ")}.`;
|
|
27069
27276
|
let promptResult;
|
|
@@ -27627,21 +27834,1291 @@ function evaluatePromotionPolicy(candidate, policy = "manual_pr", options) {
|
|
|
27627
27834
|
reason: `Unknown promotion policy: ${String(policy)}`
|
|
27628
27835
|
};
|
|
27629
27836
|
}
|
|
27837
|
+
|
|
27838
|
+
// plugins/kxm/src/browser.ts
|
|
27839
|
+
import { execSync } from "node:child_process";
|
|
27840
|
+
var VIEWPORT_PRESETS = Object.freeze({
|
|
27841
|
+
"mobile-sm": { name: "Mobile Small (SE)", category: "mobile", width: 375, height: 667, isMobile: true, hasTouch: true },
|
|
27842
|
+
"mobile": { name: "Mobile (iPhone 16 / 15 Pro)", category: "mobile", width: 393, height: 852, isMobile: true, hasTouch: true },
|
|
27843
|
+
"mobile-lg": { name: "Mobile Large (Pro Max)", category: "mobile", width: 430, height: 932, isMobile: true, hasTouch: true },
|
|
27844
|
+
"pixel": { name: "Google Pixel 8/9", category: "mobile", width: 412, height: 924, isMobile: true, hasTouch: true },
|
|
27845
|
+
"galaxy": { name: "Samsung Galaxy S24", category: "mobile", width: 360, height: 780, isMobile: true, hasTouch: true },
|
|
27846
|
+
"tablet": { name: "Tablet (iPad Air / Mini)", category: "tablet", width: 820, height: 1180, isMobile: true, hasTouch: true },
|
|
27847
|
+
"tablet-lg": { name: "Tablet Large (iPad Pro 12.9)", category: "tablet", width: 1024, height: 1366, isMobile: true, hasTouch: true },
|
|
27848
|
+
"laptop": { name: "Standard Laptop", category: "laptop", width: 1366, height: 768 },
|
|
27849
|
+
"macbook-13": { name: "MacBook Air 13", category: "laptop", width: 1440, height: 900 },
|
|
27850
|
+
"macbook-16": { name: "MacBook Pro 16", category: "laptop", width: 1728, height: 1117 },
|
|
27851
|
+
"desktop": { name: "Desktop FHD (1080p)", category: "desktop", width: 1920, height: 1080 },
|
|
27852
|
+
"desktop-2k": { name: "Desktop QHD (1440p)", category: "desktop", width: 2560, height: 1440 },
|
|
27853
|
+
"desktop-4k": { name: "Desktop 4K UHD", category: "desktop", width: 3840, height: 2160 }
|
|
27854
|
+
});
|
|
27855
|
+
function resolveViewportDimensions(presetOrDims) {
|
|
27856
|
+
if (!presetOrDims) {
|
|
27857
|
+
return { width: 1920, height: 1080 };
|
|
27858
|
+
}
|
|
27859
|
+
if (typeof presetOrDims === "string") {
|
|
27860
|
+
const matched = VIEWPORT_PRESETS[presetOrDims.toLowerCase()];
|
|
27861
|
+
if (matched) {
|
|
27862
|
+
return { width: matched.width, height: matched.height };
|
|
27863
|
+
}
|
|
27864
|
+
return { width: 1920, height: 1080 };
|
|
27865
|
+
}
|
|
27866
|
+
return presetOrDims;
|
|
27867
|
+
}
|
|
27868
|
+
function resolvePassCliApiKey(execFn = (cmd) => execSync(cmd, { encoding: "utf8", stdio: ["pipe", "pipe", "ignore"], timeout: 5e3 })) {
|
|
27869
|
+
if (typeof process === "undefined" || process.env.USE_PASS_CLI === "false") {
|
|
27870
|
+
return void 0;
|
|
27871
|
+
}
|
|
27872
|
+
if (process.env.PASS_CLI_OUTPUT_MOCK) {
|
|
27873
|
+
try {
|
|
27874
|
+
const parsed = JSON.parse(process.env.PASS_CLI_OUTPUT_MOCK);
|
|
27875
|
+
const extraFields = parsed?.item?.content?.extra_fields || [];
|
|
27876
|
+
const customSections = parsed?.item?.content?.content?.Custom?.sections || [];
|
|
27877
|
+
const sectionFields = customSections.flatMap((s) => s.section_fields || []);
|
|
27878
|
+
const allFields = [...extraFields, ...sectionFields];
|
|
27879
|
+
return allFields.find((f) => f.name === "STEEL_API_KEY")?.content?.Hidden;
|
|
27880
|
+
} catch {
|
|
27881
|
+
return void 0;
|
|
27882
|
+
}
|
|
27883
|
+
}
|
|
27884
|
+
try {
|
|
27885
|
+
const output = execFn(
|
|
27886
|
+
'pass-cli item view --vault-name "AI Provider Keys" --item-title "Steel Browser (KontextMind DOKS)" --output json'
|
|
27887
|
+
);
|
|
27888
|
+
const parsed = JSON.parse(output);
|
|
27889
|
+
const extraFields = parsed?.item?.content?.extra_fields || [];
|
|
27890
|
+
const customSections = parsed?.item?.content?.content?.Custom?.sections || [];
|
|
27891
|
+
const sectionFields = customSections.flatMap((s) => s.section_fields || []);
|
|
27892
|
+
const allFields = [...extraFields, ...sectionFields];
|
|
27893
|
+
return allFields.find((f) => f.name === "STEEL_API_KEY")?.content?.Hidden;
|
|
27894
|
+
} catch {
|
|
27895
|
+
return void 0;
|
|
27896
|
+
}
|
|
27897
|
+
}
|
|
27898
|
+
function resolveSteelConfig(overrides) {
|
|
27899
|
+
const apiUrl = overrides?.apiUrl || process.env.STEEL_API_URL || "https://steel.kontextmind.com";
|
|
27900
|
+
const apiKey = overrides?.apiKey || process.env.STEEL_API_KEY || resolvePassCliApiKey();
|
|
27901
|
+
const uiUrl = overrides?.uiUrl || (overrides?.apiUrl ? `${overrides.apiUrl.replace(/\/$/, "")}/ui` : void 0) || process.env.STEEL_UI_URL || `${apiUrl.replace(/\/$/, "")}/ui`;
|
|
27902
|
+
return {
|
|
27903
|
+
apiUrl: apiUrl.replace(/\/$/, ""),
|
|
27904
|
+
apiKey,
|
|
27905
|
+
uiUrl,
|
|
27906
|
+
timeoutMs: overrides?.timeoutMs || 3e5
|
|
27907
|
+
// 5 minutes default
|
|
27908
|
+
};
|
|
27909
|
+
}
|
|
27910
|
+
function formatCDPEndpoint(session, config) {
|
|
27911
|
+
const baseApi = config.apiUrl;
|
|
27912
|
+
const urlObj = new URL(baseApi);
|
|
27913
|
+
const isSecure = urlObj.protocol === "https:";
|
|
27914
|
+
const wsProtocol = isSecure ? "wss:" : "ws:";
|
|
27915
|
+
const host = urlObj.host;
|
|
27916
|
+
const searchParams = new URLSearchParams();
|
|
27917
|
+
searchParams.set("sessionId", session.id);
|
|
27918
|
+
if (config.apiKey) {
|
|
27919
|
+
searchParams.set("apiKey", config.apiKey);
|
|
27920
|
+
}
|
|
27921
|
+
return `${wsProtocol}//${host}/v1/devtools?${searchParams.toString()}`;
|
|
27922
|
+
}
|
|
27923
|
+
function sanitizeLogOutput(input) {
|
|
27924
|
+
if (typeof input === "string") {
|
|
27925
|
+
return input.replace(/apiKey=[^&]+/g, "apiKey=[REDACTED]").replace(/steel_[a-f0-9]+/g, "steel_[REDACTED]");
|
|
27926
|
+
}
|
|
27927
|
+
if (Array.isArray(input)) {
|
|
27928
|
+
return input.map(sanitizeLogOutput);
|
|
27929
|
+
}
|
|
27930
|
+
if (input !== null && typeof input === "object") {
|
|
27931
|
+
const copy = {};
|
|
27932
|
+
for (const [k, v] of Object.entries(input)) {
|
|
27933
|
+
if (/key|secret|token|auth|password/i.test(k) && typeof v === "string") {
|
|
27934
|
+
copy[k] = "[REDACTED]";
|
|
27935
|
+
} else {
|
|
27936
|
+
copy[k] = sanitizeLogOutput(v);
|
|
27937
|
+
}
|
|
27938
|
+
}
|
|
27939
|
+
return copy;
|
|
27940
|
+
}
|
|
27941
|
+
return input;
|
|
27942
|
+
}
|
|
27943
|
+
function createAnnotationFeedback(feedback) {
|
|
27944
|
+
return {
|
|
27945
|
+
...feedback,
|
|
27946
|
+
capturedAt: feedback.capturedAt || (/* @__PURE__ */ new Date()).toISOString()
|
|
27947
|
+
};
|
|
27948
|
+
}
|
|
27949
|
+
function formatAnnotationFeedbackPrompt(feedback) {
|
|
27950
|
+
let output = `## Visual Feedback & Section Annotation
|
|
27951
|
+
|
|
27952
|
+
`;
|
|
27953
|
+
output += `- **Target URL**: ${feedback.url}
|
|
27954
|
+
`;
|
|
27955
|
+
if (feedback.sessionId) {
|
|
27956
|
+
output += `- **Session ID**: ${feedback.sessionId}
|
|
27957
|
+
`;
|
|
27958
|
+
}
|
|
27959
|
+
if (feedback.sectionSelector) {
|
|
27960
|
+
output += `- **Section Target Selector**: \`${feedback.sectionSelector}\`
|
|
27961
|
+
`;
|
|
27962
|
+
}
|
|
27963
|
+
if (feedback.screenshotPath) {
|
|
27964
|
+
output += `- **Screenshot Artifact**: \`${feedback.screenshotPath}\`
|
|
27965
|
+
`;
|
|
27966
|
+
}
|
|
27967
|
+
output += `- **Captured At**: ${feedback.capturedAt}
|
|
27968
|
+
|
|
27969
|
+
`;
|
|
27970
|
+
output += `### Summary
|
|
27971
|
+
${feedback.overallSummary}
|
|
27972
|
+
|
|
27973
|
+
`;
|
|
27974
|
+
if (feedback.annotations.length > 0) {
|
|
27975
|
+
output += `### Annotated Elements & Notes
|
|
27976
|
+
|
|
27977
|
+
`;
|
|
27978
|
+
feedback.annotations.forEach((item, idx) => {
|
|
27979
|
+
output += `${idx + 1}. **${item.label}**`;
|
|
27980
|
+
if (item.severity) {
|
|
27981
|
+
output += ` [${item.severity.toUpperCase()}]`;
|
|
27982
|
+
}
|
|
27983
|
+
output += `
|
|
27984
|
+
- **Note**: ${item.note}
|
|
27985
|
+
`;
|
|
27986
|
+
if (item.selector) {
|
|
27987
|
+
output += ` - **Selector**: \`${item.selector}\`
|
|
27988
|
+
`;
|
|
27989
|
+
}
|
|
27990
|
+
if (item.boundingBox) {
|
|
27991
|
+
output += ` - **Region (Box)**: x=${item.boundingBox.x}, y=${item.boundingBox.y}, w=${item.boundingBox.width}, h=${item.boundingBox.height}
|
|
27992
|
+
`;
|
|
27993
|
+
}
|
|
27994
|
+
});
|
|
27995
|
+
output += `
|
|
27996
|
+
`;
|
|
27997
|
+
}
|
|
27998
|
+
if (feedback.requestedChanges.length > 0) {
|
|
27999
|
+
output += `### Actionable Change List
|
|
28000
|
+
|
|
28001
|
+
`;
|
|
28002
|
+
feedback.requestedChanges.forEach((change, idx) => {
|
|
28003
|
+
output += `- [ ] ${change}
|
|
28004
|
+
`;
|
|
28005
|
+
});
|
|
28006
|
+
}
|
|
28007
|
+
return output;
|
|
28008
|
+
}
|
|
28009
|
+
var SteelClient = class {
|
|
28010
|
+
config;
|
|
28011
|
+
activeSessions = /* @__PURE__ */ new Map();
|
|
28012
|
+
constructor(config) {
|
|
28013
|
+
this.config = resolveSteelConfig(config);
|
|
28014
|
+
}
|
|
28015
|
+
getConfig() {
|
|
28016
|
+
return { ...this.config };
|
|
28017
|
+
}
|
|
28018
|
+
headers() {
|
|
28019
|
+
const h = {
|
|
28020
|
+
"Content-Type": "application/json"
|
|
28021
|
+
};
|
|
28022
|
+
if (this.config.apiKey) {
|
|
28023
|
+
h["x-steel-api-key"] = this.config.apiKey;
|
|
28024
|
+
}
|
|
28025
|
+
return h;
|
|
28026
|
+
}
|
|
28027
|
+
/**
|
|
28028
|
+
* Launch a new Steel browser session on DOKS.
|
|
28029
|
+
*/
|
|
28030
|
+
async createSession(options) {
|
|
28031
|
+
const timeoutMs = options?.timeoutMs ?? this.config.timeoutMs ?? 3e5;
|
|
28032
|
+
const body = {
|
|
28033
|
+
timeout: timeoutMs
|
|
28034
|
+
};
|
|
28035
|
+
const dimensions = options?.dimensions || (options?.viewportPreset ? resolveViewportDimensions(options.viewportPreset) : void 0);
|
|
28036
|
+
if (dimensions) {
|
|
28037
|
+
body.dimensions = dimensions;
|
|
28038
|
+
}
|
|
28039
|
+
if (options?.userAgent) {
|
|
28040
|
+
body.userAgent = options.userAgent;
|
|
28041
|
+
}
|
|
28042
|
+
if (options?.proxy) {
|
|
28043
|
+
body.proxy = options.proxy;
|
|
28044
|
+
}
|
|
28045
|
+
const res = await fetch(`${this.config.apiUrl}/v1/sessions`, {
|
|
28046
|
+
method: "POST",
|
|
28047
|
+
headers: this.headers(),
|
|
28048
|
+
body: JSON.stringify(body)
|
|
28049
|
+
});
|
|
28050
|
+
if (!res.ok) {
|
|
28051
|
+
const errText = await res.text();
|
|
28052
|
+
throw new Error(`Failed to create Steel session (${res.status}): ${errText}`);
|
|
28053
|
+
}
|
|
28054
|
+
const data = await res.json();
|
|
28055
|
+
const session = {
|
|
28056
|
+
id: data.id,
|
|
28057
|
+
createdAt: data.createdAt || (/* @__PURE__ */ new Date()).toISOString(),
|
|
28058
|
+
status: "live",
|
|
28059
|
+
state: "AGENT_CONTROL",
|
|
28060
|
+
websocketUrl: data.websocketUrl || "",
|
|
28061
|
+
debugUrl: data.debugUrl || "",
|
|
28062
|
+
debuggerUrl: data.debuggerUrl || "",
|
|
28063
|
+
sessionViewerUrl: data.sessionViewerUrl || `${this.config.uiUrl}?sessionId=${data.id}`,
|
|
28064
|
+
timeoutMs,
|
|
28065
|
+
lastActiveAt: Date.now(),
|
|
28066
|
+
activeController: "agent",
|
|
28067
|
+
userAgent: data.userAgent
|
|
28068
|
+
};
|
|
28069
|
+
this.activeSessions.set(session.id, session);
|
|
28070
|
+
return session;
|
|
28071
|
+
}
|
|
28072
|
+
/**
|
|
28073
|
+
* Get details of an existing session.
|
|
28074
|
+
*/
|
|
28075
|
+
async getSession(sessionId) {
|
|
28076
|
+
const res = await fetch(`${this.config.apiUrl}/v1/sessions/${encodeURIComponent(sessionId)}`, {
|
|
28077
|
+
method: "GET",
|
|
28078
|
+
headers: this.headers()
|
|
28079
|
+
});
|
|
28080
|
+
if (res.status === 404) {
|
|
28081
|
+
const cached = this.activeSessions.get(sessionId);
|
|
28082
|
+
if (cached) {
|
|
28083
|
+
cached.state = "EXPIRED";
|
|
28084
|
+
cached.status = "released";
|
|
28085
|
+
cached.activeController = "none";
|
|
28086
|
+
}
|
|
28087
|
+
return null;
|
|
28088
|
+
}
|
|
28089
|
+
if (!res.ok) {
|
|
28090
|
+
throw new Error(`Failed to fetch Steel session (${res.status})`);
|
|
28091
|
+
}
|
|
28092
|
+
const data = await res.json();
|
|
28093
|
+
const existing = this.activeSessions.get(sessionId);
|
|
28094
|
+
const session = {
|
|
28095
|
+
id: data.id,
|
|
28096
|
+
createdAt: data.createdAt,
|
|
28097
|
+
status: data.status,
|
|
28098
|
+
state: existing?.state ?? (data.status === "live" ? "AGENT_CONTROL" : "RELEASED"),
|
|
28099
|
+
websocketUrl: data.websocketUrl || existing?.websocketUrl || "",
|
|
28100
|
+
debugUrl: data.debugUrl || existing?.debugUrl || "",
|
|
28101
|
+
debuggerUrl: data.debuggerUrl || existing?.debuggerUrl || "",
|
|
28102
|
+
sessionViewerUrl: existing?.sessionViewerUrl || `${this.config.uiUrl}?sessionId=${data.id}`,
|
|
28103
|
+
timeoutMs: data.timeout || existing?.timeoutMs || 3e5,
|
|
28104
|
+
lastActiveAt: Date.now(),
|
|
28105
|
+
activeController: existing?.activeController ?? (data.status === "live" ? "agent" : "none"),
|
|
28106
|
+
userAgent: data.userAgent
|
|
28107
|
+
};
|
|
28108
|
+
this.activeSessions.set(session.id, session);
|
|
28109
|
+
return session;
|
|
28110
|
+
}
|
|
28111
|
+
/**
|
|
28112
|
+
* Request human takeover for MFA, login, or consent.
|
|
28113
|
+
* Pauses agent automation and sets state to HUMAN_CONTROL.
|
|
28114
|
+
*/
|
|
28115
|
+
requestHumanTakeover(sessionId, reason) {
|
|
28116
|
+
const session = this.activeSessions.get(sessionId);
|
|
28117
|
+
if (!session) {
|
|
28118
|
+
throw new Error(`Session ${sessionId} not tracked or already released`);
|
|
28119
|
+
}
|
|
28120
|
+
if (session.state === "RELEASED" || session.state === "EXPIRED" || session.state === "FAILED") {
|
|
28121
|
+
throw new Error(`Cannot initiate takeover on session in state ${session.state}`);
|
|
28122
|
+
}
|
|
28123
|
+
session.state = "HUMAN_CONTROL";
|
|
28124
|
+
session.activeController = "human";
|
|
28125
|
+
session.takeoverReason = reason;
|
|
28126
|
+
session.lastActiveAt = Date.now();
|
|
28127
|
+
const takeoverUrl = `${this.config.uiUrl}?sessionId=${encodeURIComponent(sessionId)}`;
|
|
28128
|
+
const instructions = `[HUMAN TAKEOVER REQUIRED]
|
|
28129
|
+
Reason: ${reason}
|
|
28130
|
+
Session ID: ${session.id}
|
|
28131
|
+
Takeover URL: ${takeoverUrl}
|
|
28132
|
+
|
|
28133
|
+
Instructions for Operator:
|
|
28134
|
+
1. Open the URL above to access the session UI.
|
|
28135
|
+
2. Perform the required authentication / MFA / consent action.
|
|
28136
|
+
3. Return to the terminal and signal completion. Automation is paused until you confirm.`;
|
|
28137
|
+
return {
|
|
28138
|
+
session,
|
|
28139
|
+
takeoverUrl,
|
|
28140
|
+
instructions
|
|
28141
|
+
};
|
|
28142
|
+
}
|
|
28143
|
+
/**
|
|
28144
|
+
* Signal that human takeover is complete.
|
|
28145
|
+
* Moves state to VERIFY_AUTHENTICATION before transitioning back to AGENT_CONTROL.
|
|
28146
|
+
*/
|
|
28147
|
+
signalHumanComplete(sessionId) {
|
|
28148
|
+
const session = this.activeSessions.get(sessionId);
|
|
28149
|
+
if (!session) {
|
|
28150
|
+
throw new Error(`Session ${sessionId} not tracked or already released`);
|
|
28151
|
+
}
|
|
28152
|
+
if (session.state !== "HUMAN_CONTROL") {
|
|
28153
|
+
throw new Error(`Session ${sessionId} is not in HUMAN_CONTROL state (currently ${session.state})`);
|
|
28154
|
+
}
|
|
28155
|
+
session.state = "VERIFY_AUTHENTICATION";
|
|
28156
|
+
session.activeController = "agent";
|
|
28157
|
+
session.lastActiveAt = Date.now();
|
|
28158
|
+
return {
|
|
28159
|
+
session,
|
|
28160
|
+
state: session.state
|
|
28161
|
+
};
|
|
28162
|
+
}
|
|
28163
|
+
/**
|
|
28164
|
+
* Confirm authentication verification passed and restore AGENT_CONTROL.
|
|
28165
|
+
*/
|
|
28166
|
+
confirmAuthenticationVerified(sessionId) {
|
|
28167
|
+
const session = this.activeSessions.get(sessionId);
|
|
28168
|
+
if (!session) {
|
|
28169
|
+
throw new Error(`Session ${sessionId} not tracked or already released`);
|
|
28170
|
+
}
|
|
28171
|
+
if (session.state !== "VERIFY_AUTHENTICATION") {
|
|
28172
|
+
throw new Error(`Session ${sessionId} is not in VERIFY_AUTHENTICATION state (currently ${session.state})`);
|
|
28173
|
+
}
|
|
28174
|
+
session.state = "AGENT_CONTROL";
|
|
28175
|
+
session.activeController = "agent";
|
|
28176
|
+
session.takeoverReason = void 0;
|
|
28177
|
+
session.lastActiveAt = Date.now();
|
|
28178
|
+
return session;
|
|
28179
|
+
}
|
|
28180
|
+
/**
|
|
28181
|
+
* Gracefully release a session.
|
|
28182
|
+
*/
|
|
28183
|
+
async releaseSession(sessionId) {
|
|
28184
|
+
try {
|
|
28185
|
+
const res = await fetch(`${this.config.apiUrl}/v1/sessions/${encodeURIComponent(sessionId)}/release`, {
|
|
28186
|
+
method: "POST",
|
|
28187
|
+
headers: this.headers()
|
|
28188
|
+
});
|
|
28189
|
+
const session = this.activeSessions.get(sessionId);
|
|
28190
|
+
if (session) {
|
|
28191
|
+
session.status = "released";
|
|
28192
|
+
session.state = "RELEASED";
|
|
28193
|
+
session.activeController = "none";
|
|
28194
|
+
}
|
|
28195
|
+
this.activeSessions.delete(sessionId);
|
|
28196
|
+
return res.ok;
|
|
28197
|
+
} catch {
|
|
28198
|
+
this.activeSessions.delete(sessionId);
|
|
28199
|
+
return false;
|
|
28200
|
+
}
|
|
28201
|
+
}
|
|
28202
|
+
/**
|
|
28203
|
+
* Perform a direct stateless scrape without manual session management.
|
|
28204
|
+
*/
|
|
28205
|
+
async scrape(url) {
|
|
28206
|
+
const res = await fetch(`${this.config.apiUrl}/v1/scrape`, {
|
|
28207
|
+
method: "POST",
|
|
28208
|
+
headers: this.headers(),
|
|
28209
|
+
body: JSON.stringify({ url })
|
|
28210
|
+
});
|
|
28211
|
+
if (!res.ok) {
|
|
28212
|
+
const err = await res.text();
|
|
28213
|
+
throw new Error(`Scrape failed (${res.status}): ${err}`);
|
|
28214
|
+
}
|
|
28215
|
+
return res.json();
|
|
28216
|
+
}
|
|
28217
|
+
/**
|
|
28218
|
+
* Perform a direct screenshot action.
|
|
28219
|
+
*/
|
|
28220
|
+
async screenshot(url, fullPage = false) {
|
|
28221
|
+
const res = await fetch(`${this.config.apiUrl}/v1/screenshot`, {
|
|
28222
|
+
method: "POST",
|
|
28223
|
+
headers: this.headers(),
|
|
28224
|
+
body: JSON.stringify({ url, fullPage })
|
|
28225
|
+
});
|
|
28226
|
+
if (!res.ok) {
|
|
28227
|
+
const err = await res.text();
|
|
28228
|
+
throw new Error(`Screenshot failed (${res.status}): ${err}`);
|
|
28229
|
+
}
|
|
28230
|
+
return res.json();
|
|
28231
|
+
}
|
|
28232
|
+
/**
|
|
28233
|
+
* Detect and list orphaned or timed-out active sessions.
|
|
28234
|
+
*/
|
|
28235
|
+
async checkOrphanedSessions(maxIdleMs = 6e5) {
|
|
28236
|
+
const res = await fetch(`${this.config.apiUrl}/v1/sessions`, {
|
|
28237
|
+
method: "GET",
|
|
28238
|
+
headers: this.headers()
|
|
28239
|
+
});
|
|
28240
|
+
if (!res.ok) {
|
|
28241
|
+
return [];
|
|
28242
|
+
}
|
|
28243
|
+
const data = await res.json();
|
|
28244
|
+
const remoteSessions = data.sessions || [];
|
|
28245
|
+
const now = Date.now();
|
|
28246
|
+
const orphaned = [];
|
|
28247
|
+
for (const rs of remoteSessions) {
|
|
28248
|
+
if (rs.status === "live" || rs.status === "idle") {
|
|
28249
|
+
const tracked = this.activeSessions.get(rs.id);
|
|
28250
|
+
if (!tracked && rs.duration > maxIdleMs) {
|
|
28251
|
+
orphaned.push(rs.id);
|
|
28252
|
+
} else if (tracked && now - tracked.lastActiveAt > maxIdleMs && tracked.state !== "HUMAN_CONTROL") {
|
|
28253
|
+
orphaned.push(rs.id);
|
|
28254
|
+
}
|
|
28255
|
+
}
|
|
28256
|
+
}
|
|
28257
|
+
return orphaned;
|
|
28258
|
+
}
|
|
28259
|
+
};
|
|
28260
|
+
|
|
28261
|
+
// plugins/kxm/src/modes.ts
|
|
28262
|
+
var import_yaml6 = __toESM(require_dist(), 1);
|
|
28263
|
+
import { existsSync as existsSync12, readFileSync as readFileSync10 } from "node:fs";
|
|
28264
|
+
import { join as join15 } from "node:path";
|
|
28265
|
+
var DEFAULT_MODES_CONFIG = Object.freeze({
|
|
28266
|
+
schema: "kxm.modes.v1",
|
|
28267
|
+
majorModes: {
|
|
28268
|
+
coder: {
|
|
28269
|
+
description: "First-pass implementation, bug fixing, and test authoring",
|
|
28270
|
+
baseTools: ["read", "edit", "write", "bash"],
|
|
28271
|
+
contextFiles: ["AGENTS.md"],
|
|
28272
|
+
thinkingLevel: "medium",
|
|
28273
|
+
model: "grok/grok-4.6"
|
|
28274
|
+
},
|
|
28275
|
+
planner: {
|
|
28276
|
+
description: "High-level architectural planning, scoping, and decomposition",
|
|
28277
|
+
baseTools: ["read", "grep", "find"],
|
|
28278
|
+
contextFiles: ["plans/implementation-plan.md"],
|
|
28279
|
+
thinkingLevel: "high",
|
|
28280
|
+
model: "claude/fable"
|
|
28281
|
+
},
|
|
28282
|
+
auditor: {
|
|
28283
|
+
description: "Security, compliance, and code quality verification",
|
|
28284
|
+
baseTools: ["read", "grep"],
|
|
28285
|
+
contextFiles: ["SECURITY.md"],
|
|
28286
|
+
thinkingLevel: "high",
|
|
28287
|
+
model: "codex/gpt-5.6-sol"
|
|
28288
|
+
},
|
|
28289
|
+
browser: {
|
|
28290
|
+
description: "Web application exploration, screenshotting, and UI testing",
|
|
28291
|
+
baseTools: ["read", "bash"],
|
|
28292
|
+
contextFiles: ["docs/browser-automation.md"],
|
|
28293
|
+
thinkingLevel: "medium",
|
|
28294
|
+
model: "grok/grok-4.6"
|
|
28295
|
+
}
|
|
28296
|
+
},
|
|
28297
|
+
domains: {
|
|
28298
|
+
git: {
|
|
28299
|
+
description: "Git version control operations",
|
|
28300
|
+
tools: ["git_status", "git_diff", "git_commit"],
|
|
28301
|
+
promptSnippet: "Follow git branch conventions; never commit directly to main."
|
|
28302
|
+
},
|
|
28303
|
+
k8s: {
|
|
28304
|
+
description: "Kubernetes cluster inspection and deployment",
|
|
28305
|
+
tools: ["kubectl_get", "kubectl_describe"],
|
|
28306
|
+
promptSnippet: "Target local dev cluster; verify namespaces before mutating."
|
|
28307
|
+
},
|
|
28308
|
+
database: {
|
|
28309
|
+
description: "Database queries and schema verification",
|
|
28310
|
+
tools: ["sqlite_query", "sqlite_schema"],
|
|
28311
|
+
promptSnippet: "Database is SQLite at .kxm/state/kxm.db; use read-only queries."
|
|
28312
|
+
},
|
|
28313
|
+
browser: {
|
|
28314
|
+
description: "Remote Steel browser sessions and visual testing",
|
|
28315
|
+
tools: ["steel_session", "steel_scrape", "steel_screenshot"],
|
|
28316
|
+
promptSnippet: "Use Steel on DOKS for browser automation; invoke takeover on MFA."
|
|
28317
|
+
}
|
|
28318
|
+
}
|
|
28319
|
+
});
|
|
28320
|
+
function estimateTokens(charCount) {
|
|
28321
|
+
return Math.ceil(charCount / 3.8);
|
|
28322
|
+
}
|
|
28323
|
+
function loadModesConfig(projectRoot) {
|
|
28324
|
+
if (!projectRoot) {
|
|
28325
|
+
return DEFAULT_MODES_CONFIG;
|
|
28326
|
+
}
|
|
28327
|
+
const modesPath = join15(projectRoot, ".kxm", "modes.yaml");
|
|
28328
|
+
if (!existsSync12(modesPath)) {
|
|
28329
|
+
return DEFAULT_MODES_CONFIG;
|
|
28330
|
+
}
|
|
28331
|
+
try {
|
|
28332
|
+
const raw = readFileSync10(modesPath, "utf8");
|
|
28333
|
+
const parsed = (0, import_yaml6.parse)(raw);
|
|
28334
|
+
if (parsed && parsed.schema === "kxm.modes.v1" && parsed.majorModes) {
|
|
28335
|
+
return {
|
|
28336
|
+
schema: "kxm.modes.v1",
|
|
28337
|
+
majorModes: { ...DEFAULT_MODES_CONFIG.majorModes, ...parsed.majorModes },
|
|
28338
|
+
domains: { ...DEFAULT_MODES_CONFIG.domains, ...parsed.domains }
|
|
28339
|
+
};
|
|
28340
|
+
}
|
|
28341
|
+
} catch {
|
|
28342
|
+
}
|
|
28343
|
+
return DEFAULT_MODES_CONFIG;
|
|
28344
|
+
}
|
|
28345
|
+
function resolveActiveMode(config, majorModeName = "coder", domainNames = []) {
|
|
28346
|
+
const major = config.majorModes[majorModeName] || config.majorModes["coder"] || DEFAULT_MODES_CONFIG.majorModes["coder"];
|
|
28347
|
+
const tools = new Set(major.baseTools);
|
|
28348
|
+
const contextFiles = new Set(major.contextFiles || []);
|
|
28349
|
+
const promptSnippets = [];
|
|
28350
|
+
const enabledDomains = [];
|
|
28351
|
+
if (config.domains) {
|
|
28352
|
+
for (const dName of domainNames) {
|
|
28353
|
+
const d = config.domains[dName.toLowerCase().trim()];
|
|
28354
|
+
if (d) {
|
|
28355
|
+
enabledDomains.push(dName.toLowerCase().trim());
|
|
28356
|
+
d.tools.forEach((t) => tools.add(t));
|
|
28357
|
+
(d.contextFiles || []).forEach((f) => contextFiles.add(f));
|
|
28358
|
+
if (d.promptSnippet) {
|
|
28359
|
+
promptSnippets.push(d.promptSnippet);
|
|
28360
|
+
}
|
|
28361
|
+
}
|
|
28362
|
+
}
|
|
28363
|
+
}
|
|
28364
|
+
return {
|
|
28365
|
+
majorMode: majorModeName,
|
|
28366
|
+
enabledDomains,
|
|
28367
|
+
tools: Array.from(tools),
|
|
28368
|
+
contextFiles: Array.from(contextFiles),
|
|
28369
|
+
promptSnippets,
|
|
28370
|
+
thinkingLevel: major.thinkingLevel,
|
|
28371
|
+
model: major.model || "grok/grok-4.6"
|
|
28372
|
+
};
|
|
28373
|
+
}
|
|
28374
|
+
function calculatePromptFootprint(resolved, projectRoot = process.cwd(), catalog) {
|
|
28375
|
+
const breakdown = [];
|
|
28376
|
+
const baseSystemPromptChars = 3200;
|
|
28377
|
+
breakdown.push({
|
|
28378
|
+
name: `Base System Prompt (${resolved.majorMode})`,
|
|
28379
|
+
chars: baseSystemPromptChars,
|
|
28380
|
+
estimatedTokens: estimateTokens(baseSystemPromptChars)
|
|
28381
|
+
});
|
|
28382
|
+
for (const relPath of resolved.contextFiles) {
|
|
28383
|
+
const fullPath = join15(projectRoot, relPath);
|
|
28384
|
+
let chars = 0;
|
|
28385
|
+
if (existsSync12(fullPath)) {
|
|
28386
|
+
try {
|
|
28387
|
+
chars = readFileSync10(fullPath, "utf8").length;
|
|
28388
|
+
} catch {
|
|
28389
|
+
chars = 0;
|
|
28390
|
+
}
|
|
28391
|
+
}
|
|
28392
|
+
if (chars === 0) {
|
|
28393
|
+
chars = 1500;
|
|
28394
|
+
}
|
|
28395
|
+
breakdown.push({
|
|
28396
|
+
name: `Context File: ${relPath}`,
|
|
28397
|
+
chars,
|
|
28398
|
+
estimatedTokens: estimateTokens(chars)
|
|
28399
|
+
});
|
|
28400
|
+
}
|
|
28401
|
+
if (resolved.promptSnippets.length > 0) {
|
|
28402
|
+
const snippetChars = resolved.promptSnippets.join("\n").length;
|
|
28403
|
+
breakdown.push({
|
|
28404
|
+
name: `Domain Prompts (${resolved.enabledDomains.join(", ")})`,
|
|
28405
|
+
chars: snippetChars,
|
|
28406
|
+
estimatedTokens: estimateTokens(snippetChars)
|
|
28407
|
+
});
|
|
28408
|
+
}
|
|
28409
|
+
const toolSchemaChars = resolved.tools.length * 650;
|
|
28410
|
+
breakdown.push({
|
|
28411
|
+
name: `Tool Schemas (${resolved.tools.length} active tools)`,
|
|
28412
|
+
chars: toolSchemaChars,
|
|
28413
|
+
estimatedTokens: estimateTokens(toolSchemaChars)
|
|
28414
|
+
});
|
|
28415
|
+
const totalChars = breakdown.reduce((sum, item) => sum + item.chars, 0);
|
|
28416
|
+
const totalTokens = breakdown.reduce((sum, item) => sum + item.estimatedTokens, 0);
|
|
28417
|
+
const maxContextWindow = 2e5;
|
|
28418
|
+
const contextWindowRatio = Math.round(totalTokens / maxContextWindow * 1e3) / 10;
|
|
28419
|
+
const cat = catalog || loadPriceCatalog(projectRoot);
|
|
28420
|
+
const rawModel = resolved.model || "grok/grok-4.6";
|
|
28421
|
+
const [prov, mod] = rawModel.includes("/") ? rawModel.split("/", 2) : [void 0, rawModel];
|
|
28422
|
+
const inputCost = cat ? calculateModelCost(cat, {
|
|
28423
|
+
model: mod || rawModel,
|
|
28424
|
+
...prov !== void 0 ? { provider: prov } : {},
|
|
28425
|
+
tokensIn: totalTokens,
|
|
28426
|
+
tokensOut: 0,
|
|
28427
|
+
cacheReadTokens: 0,
|
|
28428
|
+
cacheWriteTokens: 0
|
|
28429
|
+
}) : void 0;
|
|
28430
|
+
const cacheReadCost = cat ? calculateModelCost(cat, {
|
|
28431
|
+
model: mod || rawModel,
|
|
28432
|
+
...prov !== void 0 ? { provider: prov } : {},
|
|
28433
|
+
tokensIn: 0,
|
|
28434
|
+
tokensOut: 0,
|
|
28435
|
+
cacheReadTokens: totalTokens,
|
|
28436
|
+
cacheWriteTokens: 0
|
|
28437
|
+
}) : void 0;
|
|
28438
|
+
const outputEstimateCost = cat ? calculateModelCost(cat, {
|
|
28439
|
+
model: mod || rawModel,
|
|
28440
|
+
...prov !== void 0 ? { provider: prov } : {},
|
|
28441
|
+
tokensIn: 0,
|
|
28442
|
+
tokensOut: 1e3,
|
|
28443
|
+
cacheReadTokens: 0,
|
|
28444
|
+
cacheWriteTokens: 0
|
|
28445
|
+
}) : void 0;
|
|
28446
|
+
return {
|
|
28447
|
+
majorMode: resolved.majorMode,
|
|
28448
|
+
enabledDomains: resolved.enabledDomains,
|
|
28449
|
+
model: resolved.model || "grok/grok-4.6",
|
|
28450
|
+
breakdown,
|
|
28451
|
+
totalChars,
|
|
28452
|
+
totalTokens,
|
|
28453
|
+
contextWindowRatio,
|
|
28454
|
+
projectedCost: {
|
|
28455
|
+
inputCostUsd: inputCost?.costUsd ?? null,
|
|
28456
|
+
cacheReadCostUsd: cacheReadCost?.costUsd ?? null,
|
|
28457
|
+
outputCostEstimateUsd: outputEstimateCost?.costUsd ?? null
|
|
28458
|
+
}
|
|
28459
|
+
};
|
|
28460
|
+
}
|
|
28461
|
+
function formatModesExplainReport(footprint) {
|
|
28462
|
+
const divider = "\u2550".repeat(60);
|
|
28463
|
+
const subDivider = "\u2500".repeat(60);
|
|
28464
|
+
let out = `
|
|
28465
|
+
${divider}
|
|
28466
|
+
`;
|
|
28467
|
+
out += `KXM PRE-FLIGHT CONTEXT EXPLAIN
|
|
28468
|
+
`;
|
|
28469
|
+
out += `${divider}
|
|
28470
|
+
`;
|
|
28471
|
+
out += `Major Mode: ${footprint.majorMode}
|
|
28472
|
+
`;
|
|
28473
|
+
out += `Enabled Domains: ${footprint.enabledDomains.length > 0 ? footprint.enabledDomains.join(", ") : "(none)"}
|
|
28474
|
+
`;
|
|
28475
|
+
out += `Target Model: ${footprint.model}
|
|
28476
|
+
|
|
28477
|
+
`;
|
|
28478
|
+
out += `CONTEXT BREAKDOWN:
|
|
28479
|
+
`;
|
|
28480
|
+
for (const item of footprint.breakdown) {
|
|
28481
|
+
const padName = item.name.padEnd(38, " ");
|
|
28482
|
+
const tokenStr = `${item.estimatedTokens.toLocaleString()} tokens`.padStart(16, " ");
|
|
28483
|
+
out += ` \u2022 ${padName} ${tokenStr}
|
|
28484
|
+
`;
|
|
28485
|
+
}
|
|
28486
|
+
out += `${subDivider}
|
|
28487
|
+
`;
|
|
28488
|
+
const totalPad = `TOTAL PROMPT FOOTPRINT:`.padEnd(38, " ");
|
|
28489
|
+
const totalStr = `${footprint.totalTokens.toLocaleString()} tokens`.padStart(16, " ");
|
|
28490
|
+
out += ` ${totalPad} ${totalStr} (${footprint.contextWindowRatio}% of 200k window)
|
|
28491
|
+
|
|
28492
|
+
`;
|
|
28493
|
+
out += `PROJECTED COSTS (per turn):
|
|
28494
|
+
`;
|
|
28495
|
+
out += ` \u2022 Initial Turn Input Cost: ${footprint.projectedCost.inputCostUsd !== null ? `$${footprint.projectedCost.inputCostUsd.toFixed(4)}` : "unmetered/unknown"}
|
|
28496
|
+
`;
|
|
28497
|
+
out += ` \u2022 Subsequent Cache-Read Cost: ${footprint.projectedCost.cacheReadCostUsd !== null ? `$${footprint.projectedCost.cacheReadCostUsd.toFixed(4)} (approx 90% savings)` : "unmetered/unknown"}
|
|
28498
|
+
`;
|
|
28499
|
+
out += ` \u2022 Output Estimate (1k tokens): ${footprint.projectedCost.outputCostEstimateUsd !== null ? `$${footprint.projectedCost.outputCostEstimateUsd.toFixed(4)}` : "unmetered/unknown"}
|
|
28500
|
+
`;
|
|
28501
|
+
out += `${divider}
|
|
28502
|
+
`;
|
|
28503
|
+
return out;
|
|
28504
|
+
}
|
|
28505
|
+
|
|
28506
|
+
// plugins/kxm/src/ssh-remote.ts
|
|
28507
|
+
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
28508
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync7, readFileSync as readFileSync11, readdirSync as readdirSync5, rmSync as rmSync2, statSync as statSync4 } from "node:fs";
|
|
28509
|
+
import { homedir as homedir2 } from "node:os";
|
|
28510
|
+
import { join as join16, resolve as resolve8 } from "node:path";
|
|
28511
|
+
var MAX_SSH_OUTPUT_BYTES = 50 * 1024;
|
|
28512
|
+
var MAX_SSH_OUTPUT_LINES = 2e3;
|
|
28513
|
+
var DEFAULT_SOCKET_DIR = ".kxm/run/ssh-sockets";
|
|
28514
|
+
var DEFAULT_CONTROL_PERSIST = "10m";
|
|
28515
|
+
function truncateSshOutput(raw) {
|
|
28516
|
+
let text = raw;
|
|
28517
|
+
let truncated = false;
|
|
28518
|
+
if (Buffer.byteLength(text, "utf-8") > MAX_SSH_OUTPUT_BYTES) {
|
|
28519
|
+
const buf = Buffer.from(text, "utf-8");
|
|
28520
|
+
text = buf.subarray(0, MAX_SSH_OUTPUT_BYTES).toString("utf-8");
|
|
28521
|
+
truncated = true;
|
|
28522
|
+
}
|
|
28523
|
+
const lines = text.split("\n");
|
|
28524
|
+
if (lines.length > MAX_SSH_OUTPUT_LINES) {
|
|
28525
|
+
text = lines.slice(0, MAX_SSH_OUTPUT_LINES).join("\n");
|
|
28526
|
+
truncated = true;
|
|
28527
|
+
}
|
|
28528
|
+
if (truncated) {
|
|
28529
|
+
text += `
|
|
28530
|
+
|
|
28531
|
+
[kxm: ssh output truncated to ${MAX_SSH_OUTPUT_BYTES / 1024}KB / ${MAX_SSH_OUTPUT_LINES} lines]`;
|
|
28532
|
+
}
|
|
28533
|
+
return { text, truncated };
|
|
28534
|
+
}
|
|
28535
|
+
function parseSshConfig(configPath) {
|
|
28536
|
+
const targetPath = configPath ?? join16(homedir2(), ".ssh", "config");
|
|
28537
|
+
if (!existsSync13(targetPath)) {
|
|
28538
|
+
return [];
|
|
28539
|
+
}
|
|
28540
|
+
try {
|
|
28541
|
+
const content = readFileSync11(targetPath, "utf-8");
|
|
28542
|
+
const lines = content.split("\n");
|
|
28543
|
+
const hosts = [];
|
|
28544
|
+
let currentHosts = [];
|
|
28545
|
+
for (const rawLine of lines) {
|
|
28546
|
+
const line = rawLine.trim();
|
|
28547
|
+
if (!line || line.startsWith("#")) continue;
|
|
28548
|
+
const spaceIdx = line.search(/\s/);
|
|
28549
|
+
if (spaceIdx === -1) continue;
|
|
28550
|
+
const key = line.slice(0, spaceIdx).trim().toLowerCase();
|
|
28551
|
+
const value = line.slice(spaceIdx + 1).trim();
|
|
28552
|
+
if (key === "host") {
|
|
28553
|
+
const aliases = value.split(/\s+/).filter((a) => a !== "*");
|
|
28554
|
+
currentHosts = [];
|
|
28555
|
+
for (const alias of aliases) {
|
|
28556
|
+
const entry = { alias, identityFiles: [] };
|
|
28557
|
+
currentHosts.push(entry);
|
|
28558
|
+
hosts.push(entry);
|
|
28559
|
+
}
|
|
28560
|
+
} else if (currentHosts.length > 0) {
|
|
28561
|
+
for (const currentHost of currentHosts) {
|
|
28562
|
+
if (key === "hostname") {
|
|
28563
|
+
currentHost.hostName = value;
|
|
28564
|
+
} else if (key === "user") {
|
|
28565
|
+
currentHost.user = value;
|
|
28566
|
+
} else if (key === "port") {
|
|
28567
|
+
const p = Number.parseInt(value, 10);
|
|
28568
|
+
if (!Number.isNaN(p)) currentHost.port = p;
|
|
28569
|
+
} else if (key === "proxyjump") {
|
|
28570
|
+
currentHost.proxyJump = value;
|
|
28571
|
+
} else if (key === "identityfile") {
|
|
28572
|
+
currentHost.identityFiles = currentHost.identityFiles ?? [];
|
|
28573
|
+
currentHost.identityFiles.push(value);
|
|
28574
|
+
}
|
|
28575
|
+
}
|
|
28576
|
+
}
|
|
28577
|
+
}
|
|
28578
|
+
return hosts;
|
|
28579
|
+
} catch {
|
|
28580
|
+
return [];
|
|
28581
|
+
}
|
|
28582
|
+
}
|
|
28583
|
+
function resolveSshHostG(host, execFn = spawnSync3) {
|
|
28584
|
+
try {
|
|
28585
|
+
const result = execFn("ssh", ["-G", host], { encoding: "utf-8" });
|
|
28586
|
+
if (result.status !== 0 || !result.stdout) {
|
|
28587
|
+
return { alias: host, hostName: host };
|
|
28588
|
+
}
|
|
28589
|
+
const lines = String(result.stdout).split("\n");
|
|
28590
|
+
const info = { alias: host, identityFiles: [] };
|
|
28591
|
+
for (const rawLine of lines) {
|
|
28592
|
+
const line = rawLine.trim();
|
|
28593
|
+
if (!line) continue;
|
|
28594
|
+
const spaceIdx = line.indexOf(" ");
|
|
28595
|
+
if (spaceIdx === -1) continue;
|
|
28596
|
+
const key = line.slice(0, spaceIdx).toLowerCase();
|
|
28597
|
+
const value = line.slice(spaceIdx + 1).trim();
|
|
28598
|
+
if (key === "hostname") {
|
|
28599
|
+
info.hostName = value;
|
|
28600
|
+
} else if (key === "user") {
|
|
28601
|
+
info.user = value;
|
|
28602
|
+
} else if (key === "port") {
|
|
28603
|
+
const p = Number.parseInt(value, 10);
|
|
28604
|
+
if (!Number.isNaN(p)) info.port = p;
|
|
28605
|
+
} else if (key === "proxyjump") {
|
|
28606
|
+
if (value && value !== "none") info.proxyJump = value;
|
|
28607
|
+
} else if (key === "identityfile") {
|
|
28608
|
+
info.identityFiles = info.identityFiles ?? [];
|
|
28609
|
+
info.identityFiles.push(value);
|
|
28610
|
+
}
|
|
28611
|
+
}
|
|
28612
|
+
return info;
|
|
28613
|
+
} catch {
|
|
28614
|
+
return { alias: host, hostName: host };
|
|
28615
|
+
}
|
|
28616
|
+
}
|
|
28617
|
+
function ensureSocketDir(socketDir = DEFAULT_SOCKET_DIR) {
|
|
28618
|
+
const resolved = resolve8(socketDir);
|
|
28619
|
+
if (!existsSync13(resolved)) {
|
|
28620
|
+
mkdirSync7(resolved, { recursive: true, mode: 448 });
|
|
28621
|
+
}
|
|
28622
|
+
return resolved;
|
|
28623
|
+
}
|
|
28624
|
+
function buildSshArgs(options) {
|
|
28625
|
+
const socketDir = ensureSocketDir(options.socketDir ?? DEFAULT_SOCKET_DIR);
|
|
28626
|
+
const controlPath = join16(socketDir, "%C");
|
|
28627
|
+
const persist2 = options.controlPersist ?? DEFAULT_CONTROL_PERSIST;
|
|
28628
|
+
const args = [
|
|
28629
|
+
"-o",
|
|
28630
|
+
`ControlMaster=auto`,
|
|
28631
|
+
"-o",
|
|
28632
|
+
`ControlPath=${controlPath}`,
|
|
28633
|
+
"-o",
|
|
28634
|
+
`ControlPersist=${persist2}`
|
|
28635
|
+
];
|
|
28636
|
+
if (options.batchMode !== false) {
|
|
28637
|
+
args.push("-o", "BatchMode=yes");
|
|
28638
|
+
}
|
|
28639
|
+
if (options.strictHostKey !== false) {
|
|
28640
|
+
args.push("-o", "StrictHostKeyChecking=yes");
|
|
28641
|
+
}
|
|
28642
|
+
if (options.extraArgs) {
|
|
28643
|
+
args.push(...options.extraArgs);
|
|
28644
|
+
}
|
|
28645
|
+
assertPinnedSshHostKeyPolicy(args);
|
|
28646
|
+
args.push(options.host);
|
|
28647
|
+
return args;
|
|
28648
|
+
}
|
|
28649
|
+
function checkControlSocket(host, socketDir = DEFAULT_SOCKET_DIR, execFn = spawnSync3) {
|
|
28650
|
+
const resolvedDir = ensureSocketDir(socketDir);
|
|
28651
|
+
const controlPath = join16(resolvedDir, "%C");
|
|
28652
|
+
try {
|
|
28653
|
+
const result = execFn("ssh", ["-O", "check", "-o", `ControlPath=${controlPath}`, host], {
|
|
28654
|
+
encoding: "utf-8"
|
|
28655
|
+
});
|
|
28656
|
+
return result.status === 0;
|
|
28657
|
+
} catch {
|
|
28658
|
+
return false;
|
|
28659
|
+
}
|
|
28660
|
+
}
|
|
28661
|
+
function closeControlSocket(host, socketDir = DEFAULT_SOCKET_DIR, execFn = spawnSync3) {
|
|
28662
|
+
const resolvedDir = ensureSocketDir(socketDir);
|
|
28663
|
+
const controlPath = join16(resolvedDir, "%C");
|
|
28664
|
+
try {
|
|
28665
|
+
const result = execFn("ssh", ["-O", "stop", "-o", `ControlPath=${controlPath}`, host], {
|
|
28666
|
+
encoding: "utf-8"
|
|
28667
|
+
});
|
|
28668
|
+
return result.status === 0;
|
|
28669
|
+
} catch {
|
|
28670
|
+
return false;
|
|
28671
|
+
}
|
|
28672
|
+
}
|
|
28673
|
+
function pruneSocketDir(socketDir = DEFAULT_SOCKET_DIR) {
|
|
28674
|
+
const resolvedDir = ensureSocketDir(socketDir);
|
|
28675
|
+
let removed = 0;
|
|
28676
|
+
try {
|
|
28677
|
+
const entries = readdirSync5(resolvedDir);
|
|
28678
|
+
for (const entry of entries) {
|
|
28679
|
+
const fullPath = join16(resolvedDir, entry);
|
|
28680
|
+
try {
|
|
28681
|
+
const stat = statSync4(fullPath);
|
|
28682
|
+
if (stat.isSocket()) {
|
|
28683
|
+
rmSync2(fullPath, { force: true });
|
|
28684
|
+
removed++;
|
|
28685
|
+
}
|
|
28686
|
+
} catch {
|
|
28687
|
+
}
|
|
28688
|
+
}
|
|
28689
|
+
} catch {
|
|
28690
|
+
}
|
|
28691
|
+
return removed;
|
|
28692
|
+
}
|
|
28693
|
+
function executeSshRun(params) {
|
|
28694
|
+
const startTime = Date.now();
|
|
28695
|
+
const execSyncFn = params.execFn ?? spawnSync3;
|
|
28696
|
+
if (params.action === "info") {
|
|
28697
|
+
if (params.host) {
|
|
28698
|
+
const hostInfo = resolveSshHostG(params.host, execSyncFn);
|
|
28699
|
+
return {
|
|
28700
|
+
ok: true,
|
|
28701
|
+
action: "info",
|
|
28702
|
+
host: params.host,
|
|
28703
|
+
resolvedHost: hostInfo.hostName ?? params.host,
|
|
28704
|
+
user: hostInfo.user,
|
|
28705
|
+
port: hostInfo.port,
|
|
28706
|
+
hosts: [hostInfo],
|
|
28707
|
+
durationMs: Date.now() - startTime
|
|
28708
|
+
};
|
|
28709
|
+
}
|
|
28710
|
+
const discovered = parseSshConfig();
|
|
28711
|
+
return {
|
|
28712
|
+
ok: true,
|
|
28713
|
+
action: "info",
|
|
28714
|
+
hosts: discovered,
|
|
28715
|
+
durationMs: Date.now() - startTime
|
|
28716
|
+
};
|
|
28717
|
+
}
|
|
28718
|
+
if (!params.host) {
|
|
28719
|
+
return {
|
|
28720
|
+
ok: false,
|
|
28721
|
+
action: params.action,
|
|
28722
|
+
error: 'Parameter "host" is required for command and file actions.',
|
|
28723
|
+
durationMs: Date.now() - startTime
|
|
28724
|
+
};
|
|
28725
|
+
}
|
|
28726
|
+
const host = params.host;
|
|
28727
|
+
const socketDir = params.socket_dir ?? DEFAULT_SOCKET_DIR;
|
|
28728
|
+
const wasSocketActive = checkControlSocket(host, socketDir, execSyncFn);
|
|
28729
|
+
if (params.action === "command") {
|
|
28730
|
+
if (!params.command) {
|
|
28731
|
+
return {
|
|
28732
|
+
ok: false,
|
|
28733
|
+
action: "command",
|
|
28734
|
+
host,
|
|
28735
|
+
error: 'Parameter "command" is required for command action.',
|
|
28736
|
+
durationMs: Date.now() - startTime
|
|
28737
|
+
};
|
|
28738
|
+
}
|
|
28739
|
+
try {
|
|
28740
|
+
assertCommandSeatbelt(params.command);
|
|
28741
|
+
} catch (err) {
|
|
28742
|
+
return {
|
|
28743
|
+
ok: false,
|
|
28744
|
+
action: "command",
|
|
28745
|
+
host,
|
|
28746
|
+
error: err.message,
|
|
28747
|
+
durationMs: Date.now() - startTime
|
|
28748
|
+
};
|
|
28749
|
+
}
|
|
28750
|
+
let remoteCommand = params.command;
|
|
28751
|
+
let inputStdin = void 0;
|
|
28752
|
+
if (params.sudo) {
|
|
28753
|
+
const sudoPwd = params.sudo_password ?? params.password;
|
|
28754
|
+
if (sudoPwd) {
|
|
28755
|
+
inputStdin = `${sudoPwd}
|
|
28756
|
+
`;
|
|
28757
|
+
remoteCommand = `sudo -S -p '' -- sh -c ${JSON.stringify(params.command)}`;
|
|
28758
|
+
} else {
|
|
28759
|
+
remoteCommand = `sudo -- sh -c ${JSON.stringify(params.command)}`;
|
|
28760
|
+
}
|
|
28761
|
+
}
|
|
28762
|
+
const useSshPass = Boolean(params.password);
|
|
28763
|
+
const sshArgs = buildSshArgs({
|
|
28764
|
+
host,
|
|
28765
|
+
socketDir,
|
|
28766
|
+
controlPersist: params.control_persist,
|
|
28767
|
+
batchMode: !useSshPass,
|
|
28768
|
+
strictHostKey: params.strict_host_key
|
|
28769
|
+
});
|
|
28770
|
+
sshArgs.push("--", remoteCommand);
|
|
28771
|
+
const spawnEnv = { ...process.env };
|
|
28772
|
+
let binary = "ssh";
|
|
28773
|
+
let finalArgs = sshArgs;
|
|
28774
|
+
if (useSshPass && params.password) {
|
|
28775
|
+
binary = "sshpass";
|
|
28776
|
+
finalArgs = ["-e", "ssh", ...sshArgs];
|
|
28777
|
+
spawnEnv.SSHPASS = params.password;
|
|
28778
|
+
}
|
|
28779
|
+
try {
|
|
28780
|
+
const result = execSyncFn(binary, finalArgs, {
|
|
28781
|
+
encoding: "utf-8",
|
|
28782
|
+
env: spawnEnv,
|
|
28783
|
+
input: inputStdin,
|
|
28784
|
+
timeout: params.timeout_ms ?? 12e4,
|
|
28785
|
+
maxBuffer: 10 * 1024 * 1024
|
|
28786
|
+
});
|
|
28787
|
+
const stdoutRaw = String(result.stdout || "");
|
|
28788
|
+
const stderrRaw = String(result.stderr || "");
|
|
28789
|
+
const sanitizedStderr = stderrRaw.replace(/\[sudo\] password for [^:]+:\s*/gi, "");
|
|
28790
|
+
const stdoutTrunc = truncateSshOutput(stdoutRaw);
|
|
28791
|
+
const stderrTrunc = truncateSshOutput(sanitizedStderr);
|
|
28792
|
+
return {
|
|
28793
|
+
ok: result.status === 0,
|
|
28794
|
+
action: "command",
|
|
28795
|
+
host,
|
|
28796
|
+
exitCode: result.status ?? 1,
|
|
28797
|
+
stdout: stdoutTrunc.text,
|
|
28798
|
+
stderr: stderrTrunc.text,
|
|
28799
|
+
truncated: stdoutTrunc.truncated || stderrTrunc.truncated,
|
|
28800
|
+
socketReused: wasSocketActive,
|
|
28801
|
+
durationMs: Date.now() - startTime,
|
|
28802
|
+
error: result.error ? result.error.message : void 0
|
|
28803
|
+
};
|
|
28804
|
+
} catch (err) {
|
|
28805
|
+
return {
|
|
28806
|
+
ok: false,
|
|
28807
|
+
action: "command",
|
|
28808
|
+
host,
|
|
28809
|
+
error: err.message,
|
|
28810
|
+
durationMs: Date.now() - startTime
|
|
28811
|
+
};
|
|
28812
|
+
}
|
|
28813
|
+
}
|
|
28814
|
+
if (params.action === "file") {
|
|
28815
|
+
if (!params.file_path) {
|
|
28816
|
+
return {
|
|
28817
|
+
ok: false,
|
|
28818
|
+
action: "file",
|
|
28819
|
+
host,
|
|
28820
|
+
error: 'Parameter "file_path" is required for file action.',
|
|
28821
|
+
durationMs: Date.now() - startTime
|
|
28822
|
+
};
|
|
28823
|
+
}
|
|
28824
|
+
const filePath = params.file_path;
|
|
28825
|
+
const op = params.file_op ?? "write";
|
|
28826
|
+
if (op === "read") {
|
|
28827
|
+
const readCmd = params.sudo ? `sudo cat ${JSON.stringify(filePath)}` : `cat ${JSON.stringify(filePath)}`;
|
|
28828
|
+
return executeSshRun({
|
|
28829
|
+
...params,
|
|
28830
|
+
action: "command",
|
|
28831
|
+
command: readCmd
|
|
28832
|
+
});
|
|
28833
|
+
}
|
|
28834
|
+
const content = params.file_content ?? "";
|
|
28835
|
+
const redirectOp = op === "append" ? ">>" : ">";
|
|
28836
|
+
const writeCmd = params.sudo ? `sudo tee ${op === "append" ? "-a " : ""}${JSON.stringify(filePath)} > /dev/null` : `cat ${redirectOp} ${JSON.stringify(filePath)}`;
|
|
28837
|
+
const useSshPass = Boolean(params.password);
|
|
28838
|
+
const sshArgs = buildSshArgs({
|
|
28839
|
+
host,
|
|
28840
|
+
socketDir,
|
|
28841
|
+
controlPersist: params.control_persist,
|
|
28842
|
+
batchMode: !useSshPass,
|
|
28843
|
+
strictHostKey: params.strict_host_key
|
|
28844
|
+
});
|
|
28845
|
+
sshArgs.push("--", writeCmd);
|
|
28846
|
+
const spawnEnv = { ...process.env };
|
|
28847
|
+
let binary = "ssh";
|
|
28848
|
+
let finalArgs = sshArgs;
|
|
28849
|
+
if (useSshPass && params.password) {
|
|
28850
|
+
binary = "sshpass";
|
|
28851
|
+
finalArgs = ["-e", "ssh", ...sshArgs];
|
|
28852
|
+
spawnEnv.SSHPASS = params.password;
|
|
28853
|
+
}
|
|
28854
|
+
try {
|
|
28855
|
+
const result = execSyncFn(binary, finalArgs, {
|
|
28856
|
+
encoding: "utf-8",
|
|
28857
|
+
env: spawnEnv,
|
|
28858
|
+
input: content,
|
|
28859
|
+
timeout: params.timeout_ms ?? 6e4
|
|
28860
|
+
});
|
|
28861
|
+
return {
|
|
28862
|
+
ok: result.status === 0,
|
|
28863
|
+
action: "file",
|
|
28864
|
+
host,
|
|
28865
|
+
exitCode: result.status ?? 1,
|
|
28866
|
+
stdout: String(result.stdout || ""),
|
|
28867
|
+
stderr: String(result.stderr || ""),
|
|
28868
|
+
fileProcessed: filePath,
|
|
28869
|
+
bytesProcessed: Buffer.byteLength(content, "utf-8"),
|
|
28870
|
+
socketReused: wasSocketActive,
|
|
28871
|
+
durationMs: Date.now() - startTime,
|
|
28872
|
+
error: result.error ? result.error.message : void 0
|
|
28873
|
+
};
|
|
28874
|
+
} catch (err) {
|
|
28875
|
+
return {
|
|
28876
|
+
ok: false,
|
|
28877
|
+
action: "file",
|
|
28878
|
+
host,
|
|
28879
|
+
error: err.message,
|
|
28880
|
+
durationMs: Date.now() - startTime
|
|
28881
|
+
};
|
|
28882
|
+
}
|
|
28883
|
+
}
|
|
28884
|
+
return {
|
|
28885
|
+
ok: false,
|
|
28886
|
+
action: params.action,
|
|
28887
|
+
error: `Unknown action: ${params.action}`,
|
|
28888
|
+
durationMs: Date.now() - startTime
|
|
28889
|
+
};
|
|
28890
|
+
}
|
|
28891
|
+
|
|
28892
|
+
// plugins/kxm/src/subagent-control.ts
|
|
28893
|
+
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
28894
|
+
var SUBAGENT_TYPES = Object.freeze([
|
|
28895
|
+
"general",
|
|
28896
|
+
"Explore",
|
|
28897
|
+
"Plan",
|
|
28898
|
+
"Reviewer",
|
|
28899
|
+
"Auditor"
|
|
28900
|
+
]);
|
|
28901
|
+
var DEFAULT_SUBAGENT_MODELS = Object.freeze({
|
|
28902
|
+
general: "grok/grok-4.6",
|
|
28903
|
+
Explore: "openrouter/qwen/qwen3-coder-plus",
|
|
28904
|
+
Plan: "claude/fable",
|
|
28905
|
+
Reviewer: "openai/gpt-5.6-sol",
|
|
28906
|
+
Auditor: "claude/fable"
|
|
28907
|
+
});
|
|
28908
|
+
function narrowSubagentTools(requestedTools, parentTools) {
|
|
28909
|
+
if (!requestedTools || requestedTools.length === 0) {
|
|
28910
|
+
return parentTools ? [...parentTools] : ["read", "grep", "find"];
|
|
28911
|
+
}
|
|
28912
|
+
if (!parentTools || parentTools.length === 0) {
|
|
28913
|
+
return [...requestedTools];
|
|
28914
|
+
}
|
|
28915
|
+
const parentSet = new Set(parentTools);
|
|
28916
|
+
const narrowed = [];
|
|
28917
|
+
for (const tool of requestedTools) {
|
|
28918
|
+
if (parentSet.has(tool)) {
|
|
28919
|
+
narrowed.push(tool);
|
|
28920
|
+
}
|
|
28921
|
+
}
|
|
28922
|
+
if (narrowed.length === 0 && requestedTools.length > 0) {
|
|
28923
|
+
throw new Error(
|
|
28924
|
+
`Cannot spawn subagent: requested tools [${requestedTools.join(", ")}] expand beyond parent capabilities.`
|
|
28925
|
+
);
|
|
28926
|
+
}
|
|
28927
|
+
return narrowed;
|
|
28928
|
+
}
|
|
28929
|
+
var SubagentManager = class {
|
|
28930
|
+
subagents = /* @__PURE__ */ new Map();
|
|
28931
|
+
/**
|
|
28932
|
+
* Spawns a new managed subagent with strict capability boundaries.
|
|
28933
|
+
*/
|
|
28934
|
+
spawn(params) {
|
|
28935
|
+
if (!params.prompt || !params.prompt.trim()) {
|
|
28936
|
+
throw new Error('Subagent "prompt" is required.');
|
|
28937
|
+
}
|
|
28938
|
+
if (!params.description || !params.description.trim()) {
|
|
28939
|
+
throw new Error('Subagent "description" is required.');
|
|
28940
|
+
}
|
|
28941
|
+
const type = params.type ?? "general";
|
|
28942
|
+
const model = params.model ?? DEFAULT_SUBAGENT_MODELS[type] ?? "grok/grok-4.6";
|
|
28943
|
+
const thinking = params.thinking ?? "low";
|
|
28944
|
+
const allowedTools = narrowSubagentTools(params.allowed_tools, params.parent_tools);
|
|
28945
|
+
const id = `ag_${randomUUID5().replaceAll("-", "").slice(0, 10)}`;
|
|
28946
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
28947
|
+
const record2 = {
|
|
28948
|
+
id,
|
|
28949
|
+
description: params.description.trim(),
|
|
28950
|
+
type,
|
|
28951
|
+
model,
|
|
28952
|
+
thinking,
|
|
28953
|
+
allowedTools,
|
|
28954
|
+
status: params.background === false ? "completed" : "running",
|
|
28955
|
+
turnsMax: params.turns ?? 10,
|
|
28956
|
+
turnsCompleted: 0,
|
|
28957
|
+
prompt: params.prompt.trim(),
|
|
28958
|
+
createdAt: now,
|
|
28959
|
+
updatedAt: now,
|
|
28960
|
+
steeringMessages: []
|
|
28961
|
+
};
|
|
28962
|
+
this.subagents.set(id, record2);
|
|
28963
|
+
return record2;
|
|
28964
|
+
}
|
|
28965
|
+
/**
|
|
28966
|
+
* Dispatches an action from the `agent_control` tool.
|
|
28967
|
+
*/
|
|
28968
|
+
control(params) {
|
|
28969
|
+
const action = params.action;
|
|
28970
|
+
if (action === "info") {
|
|
28971
|
+
const kind = params.kind ?? "active";
|
|
28972
|
+
if (kind === "types") {
|
|
28973
|
+
return {
|
|
28974
|
+
ok: true,
|
|
28975
|
+
action: "info",
|
|
28976
|
+
types: SUBAGENT_TYPES
|
|
28977
|
+
};
|
|
28978
|
+
}
|
|
28979
|
+
if (kind === "models") {
|
|
28980
|
+
return {
|
|
28981
|
+
ok: true,
|
|
28982
|
+
action: "info",
|
|
28983
|
+
result: JSON.stringify(DEFAULT_SUBAGENT_MODELS)
|
|
28984
|
+
};
|
|
28985
|
+
}
|
|
28986
|
+
const active = Array.from(this.subagents.values()).map((ag) => ({
|
|
28987
|
+
id: ag.id,
|
|
28988
|
+
description: ag.description,
|
|
28989
|
+
type: ag.type,
|
|
28990
|
+
status: ag.status
|
|
28991
|
+
}));
|
|
28992
|
+
return {
|
|
28993
|
+
ok: true,
|
|
28994
|
+
action: "info",
|
|
28995
|
+
activeAgents: active
|
|
28996
|
+
};
|
|
28997
|
+
}
|
|
28998
|
+
if (!params.agent_id) {
|
|
28999
|
+
return {
|
|
29000
|
+
ok: false,
|
|
29001
|
+
action,
|
|
29002
|
+
error: 'Parameter "agent_id" is required for result, steer, and stop actions.'
|
|
29003
|
+
};
|
|
29004
|
+
}
|
|
29005
|
+
const agent = this.subagents.get(params.agent_id);
|
|
29006
|
+
if (!agent) {
|
|
29007
|
+
return {
|
|
29008
|
+
ok: false,
|
|
29009
|
+
action,
|
|
29010
|
+
agent_id: params.agent_id,
|
|
29011
|
+
error: `Subagent "${params.agent_id}" not found.`
|
|
29012
|
+
};
|
|
29013
|
+
}
|
|
29014
|
+
if (action === "result") {
|
|
29015
|
+
return {
|
|
29016
|
+
ok: true,
|
|
29017
|
+
action: "result",
|
|
29018
|
+
agent_id: agent.id,
|
|
29019
|
+
status: agent.status,
|
|
29020
|
+
result: agent.output ?? `[Subagent is currently ${agent.status}]`
|
|
29021
|
+
};
|
|
29022
|
+
}
|
|
29023
|
+
if (action === "steer") {
|
|
29024
|
+
if (!params.message || !params.message.trim()) {
|
|
29025
|
+
return {
|
|
29026
|
+
ok: false,
|
|
29027
|
+
action: "steer",
|
|
29028
|
+
agent_id: agent.id,
|
|
29029
|
+
error: 'Parameter "message" is required for steer action.'
|
|
29030
|
+
};
|
|
29031
|
+
}
|
|
29032
|
+
if (agent.status === "completed" || agent.status === "stopped" || agent.status === "failed") {
|
|
29033
|
+
return {
|
|
29034
|
+
ok: false,
|
|
29035
|
+
action: "steer",
|
|
29036
|
+
agent_id: agent.id,
|
|
29037
|
+
error: `Cannot steer subagent "${agent.id}" because it is already ${agent.status}.`
|
|
29038
|
+
};
|
|
29039
|
+
}
|
|
29040
|
+
agent.steeringMessages.push({
|
|
29041
|
+
text: params.message.trim(),
|
|
29042
|
+
injectedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
29043
|
+
processed: false
|
|
29044
|
+
});
|
|
29045
|
+
agent.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
29046
|
+
return {
|
|
29047
|
+
ok: true,
|
|
29048
|
+
action: "steer",
|
|
29049
|
+
agent_id: agent.id,
|
|
29050
|
+
status: agent.status,
|
|
29051
|
+
steered: true
|
|
29052
|
+
};
|
|
29053
|
+
}
|
|
29054
|
+
if (action === "stop") {
|
|
29055
|
+
agent.status = "stopped";
|
|
29056
|
+
agent.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
29057
|
+
return {
|
|
29058
|
+
ok: true,
|
|
29059
|
+
action: "stop",
|
|
29060
|
+
agent_id: agent.id,
|
|
29061
|
+
status: "stopped",
|
|
29062
|
+
stopped: true
|
|
29063
|
+
};
|
|
29064
|
+
}
|
|
29065
|
+
return {
|
|
29066
|
+
ok: false,
|
|
29067
|
+
action,
|
|
29068
|
+
error: `Unknown control action "${action}".`
|
|
29069
|
+
};
|
|
29070
|
+
}
|
|
29071
|
+
/**
|
|
29072
|
+
* Retrieves an agent record by ID.
|
|
29073
|
+
*/
|
|
29074
|
+
get(agentId) {
|
|
29075
|
+
return this.subagents.get(agentId);
|
|
29076
|
+
}
|
|
29077
|
+
/**
|
|
29078
|
+
* Marks a subagent as completed with output.
|
|
29079
|
+
*/
|
|
29080
|
+
complete(agentId, output) {
|
|
29081
|
+
const agent = this.subagents.get(agentId);
|
|
29082
|
+
if (agent) {
|
|
29083
|
+
agent.status = "completed";
|
|
29084
|
+
agent.output = output;
|
|
29085
|
+
agent.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
29086
|
+
}
|
|
29087
|
+
}
|
|
29088
|
+
/**
|
|
29089
|
+
* Returns all active running subagents.
|
|
29090
|
+
*/
|
|
29091
|
+
listActive() {
|
|
29092
|
+
return Array.from(this.subagents.values()).filter(
|
|
29093
|
+
(ag) => ag.status === "running" || ag.status === "pending" || ag.status === "blocked"
|
|
29094
|
+
);
|
|
29095
|
+
}
|
|
29096
|
+
};
|
|
27630
29097
|
export {
|
|
27631
29098
|
BUILTIN_HARNESSES,
|
|
27632
29099
|
BUILTIN_HARNESS_IDS,
|
|
27633
29100
|
CANDIDATE_SCHEMA,
|
|
29101
|
+
DEFAULT_CONTROL_PERSIST,
|
|
27634
29102
|
DEFAULT_HARNESS,
|
|
27635
29103
|
DEFAULT_LOG_MAX_BYTES,
|
|
27636
29104
|
DEFAULT_LOG_MAX_FILES,
|
|
29105
|
+
DEFAULT_MODES_CONFIG,
|
|
29106
|
+
DEFAULT_SOCKET_DIR,
|
|
29107
|
+
DEFAULT_SUBAGENT_MODELS,
|
|
27637
29108
|
IMPROVEMENT_REPORT_SCHEMA,
|
|
27638
29109
|
IMPROVEMENT_REPORT_V1_SCHEMA,
|
|
27639
29110
|
LOG_LEVEL_PRIORITY,
|
|
29111
|
+
MAX_SSH_OUTPUT_BYTES,
|
|
29112
|
+
MAX_SSH_OUTPUT_LINES,
|
|
27640
29113
|
NATIVE_HARNESS_PROVIDERS,
|
|
27641
29114
|
PI_ALLOWED_PROVIDERS,
|
|
27642
29115
|
PI_NATIVE_BRAKE_PROVIDERS,
|
|
27643
29116
|
PiSession,
|
|
27644
29117
|
SAFE_HARNESS_COMMAND_ID,
|
|
29118
|
+
SUBAGENT_TYPES,
|
|
29119
|
+
SteelClient,
|
|
29120
|
+
SubagentManager,
|
|
29121
|
+
VIEWPORT_PRESETS,
|
|
27645
29122
|
VNEXT_ABSENT_MEMORY_REVISION,
|
|
27646
29123
|
VNEXT_EVENT_STORE_SCHEMA_VERSION,
|
|
27647
29124
|
VNEXT_REGISTRY_SCHEMA_VERSION,
|
|
@@ -27654,15 +29131,20 @@ export {
|
|
|
27654
29131
|
assertVnextConfigError,
|
|
27655
29132
|
backupDatabaseFile,
|
|
27656
29133
|
buildImprovementReport,
|
|
29134
|
+
buildSshArgs,
|
|
29135
|
+
calculatePromptFootprint,
|
|
27657
29136
|
cancelVnextRun,
|
|
29137
|
+
checkControlSocket,
|
|
27658
29138
|
checkIntegrity,
|
|
27659
29139
|
checkMemoryRevisionDrift,
|
|
27660
29140
|
checkedParent,
|
|
27661
29141
|
checkpointWal,
|
|
27662
29142
|
classifyCandidateKind,
|
|
29143
|
+
closeControlSocket,
|
|
27663
29144
|
closeVnextRuntimeContext,
|
|
27664
29145
|
computeGateEvidenceOutcome,
|
|
27665
29146
|
computeVnextMemoryRevision,
|
|
29147
|
+
createAnnotationFeedback,
|
|
27666
29148
|
createBackup,
|
|
27667
29149
|
createLogger,
|
|
27668
29150
|
createVnextOneShotProducer,
|
|
@@ -27671,15 +29153,21 @@ export {
|
|
|
27671
29153
|
defaultSpawn,
|
|
27672
29154
|
discoverProjectStores,
|
|
27673
29155
|
eligibleHarnesses,
|
|
29156
|
+
ensureSocketDir,
|
|
27674
29157
|
ensureVnextSupervisor,
|
|
27675
29158
|
ensureWalJournalMode,
|
|
29159
|
+
estimateTokens,
|
|
27676
29160
|
evaluatePromotionPolicy,
|
|
29161
|
+
executeSshRun,
|
|
27677
29162
|
fileSha256,
|
|
27678
29163
|
findWinNpmInnerExe,
|
|
27679
29164
|
foldStoredVnextRun,
|
|
29165
|
+
formatAnnotationFeedbackPrompt,
|
|
29166
|
+
formatCDPEndpoint,
|
|
27680
29167
|
formatHarnessInventory,
|
|
27681
29168
|
formatHarnessUpdate,
|
|
27682
29169
|
formatImprovementReport,
|
|
29170
|
+
formatModesExplainReport,
|
|
27683
29171
|
formatPiSessionDisplayName,
|
|
27684
29172
|
formatPiSessionKey,
|
|
27685
29173
|
gateRowContentHash,
|
|
@@ -27691,6 +29179,8 @@ export {
|
|
|
27691
29179
|
isKnownHarnessId,
|
|
27692
29180
|
isVnextRuntimeContextClosed,
|
|
27693
29181
|
isWindowsHarnessShim,
|
|
29182
|
+
loadModesConfig,
|
|
29183
|
+
narrowSubagentTools,
|
|
27694
29184
|
newVnextAssignmentId,
|
|
27695
29185
|
newVnextAttemptId,
|
|
27696
29186
|
newVnextCommandId,
|
|
@@ -27708,26 +29198,37 @@ export {
|
|
|
27708
29198
|
parseGenericOneShotUsage,
|
|
27709
29199
|
parseGrokOneShotUsage,
|
|
27710
29200
|
parseKimiOneShotUsage,
|
|
29201
|
+
parseSshConfig,
|
|
27711
29202
|
persistVnextRunState,
|
|
27712
29203
|
planHarnessUpdate,
|
|
27713
29204
|
probeHarnessAssignment,
|
|
27714
29205
|
probeHarnessAssignmentAsync,
|
|
27715
29206
|
probeHarnesses,
|
|
29207
|
+
probeHarnessesAsync,
|
|
27716
29208
|
probeHarnessesForModel,
|
|
29209
|
+
probeHarnessesForModelAsync,
|
|
27717
29210
|
projectRuntimeKey,
|
|
29211
|
+
pruneSocketDir,
|
|
27718
29212
|
readVnextRunStatus,
|
|
27719
29213
|
readVnextSupervisorToken,
|
|
27720
29214
|
rebuildVnextRunProjection,
|
|
27721
29215
|
redactLogValue,
|
|
27722
29216
|
registerVnextRuntimeCloseHook,
|
|
29217
|
+
resolveActiveMode,
|
|
27723
29218
|
resolveDispatchStatus,
|
|
29219
|
+
resolvePassCliApiKey,
|
|
29220
|
+
resolveSshHostG,
|
|
29221
|
+
resolveSteelConfig,
|
|
29222
|
+
resolveViewportDimensions,
|
|
27724
29223
|
restoreBackup,
|
|
27725
29224
|
restoreDatabaseFile,
|
|
27726
29225
|
rotateLogFiles,
|
|
27727
29226
|
runHarnessUpdate,
|
|
27728
29227
|
runtimeError,
|
|
29228
|
+
sanitizeLogOutput,
|
|
27729
29229
|
startVnextRuntimeSupervisor,
|
|
27730
29230
|
tableColumns,
|
|
29231
|
+
truncateSshOutput,
|
|
27731
29232
|
userTables,
|
|
27732
29233
|
validateHarnessModelPair,
|
|
27733
29234
|
verifyExpectedTables,
|