@kontextmind/kxm 0.7.7 → 0.7.9
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/package.json +1 -1
- package/plugins/kxm/.claude-plugin/plugin.json +1 -1
- package/plugins/kxm/dist/cli.js +250 -38
- package/plugins/kxm/dist/mcp-server.js +1 -1
- package/plugins/kxm/dist/runtime.js +318 -33
- package/plugins/kxm/dist/vnext-runtime-supervisor.js +85 -28
- package/plugins/kxm/package.json +1 -1
- package/plugins/kxm/src/cli/system.ts +3 -3
- package/plugins/kxm/src/cli/tasks.ts +2 -2
- package/plugins/kxm/src/cli/vnext.ts +2 -2
- package/plugins/kxm/src/mcp-server.ts +1 -1
- package/plugins/kxm/src/model-inventory.ts +8 -8
- package/plugins/kxm/src/runtime.ts +1 -0
- package/plugins/kxm/src/subagent-control.ts +312 -0
- package/plugins/kxm/src/vnext-harness.ts +88 -20
- package/plugins/kxm/src/vnext-oneshot-evidence.ts +39 -7
- package/plugins/kxm/src/vnext-oneshot-process.ts +25 -4
- package/plugins/kxm/src/vnext-oneshot-producer.ts +4 -1
- package/plugins/kxm/src/vnext-pi-producer.ts +11 -7
- package/plugins/kxm/src/vnext-runtime-supervisor.ts +5 -3
|
@@ -15163,6 +15163,7 @@ function defaultSpawn(command, args, options) {
|
|
|
15163
15163
|
let child;
|
|
15164
15164
|
const finish = () => {
|
|
15165
15165
|
if (finished) return;
|
|
15166
|
+
if (stopping && !killSent) return;
|
|
15166
15167
|
finished = true;
|
|
15167
15168
|
for (const timer of [wallTimer, killTimer, drainTimer, reapTimer]) clearTimeout(timer);
|
|
15168
15169
|
options.signal?.removeEventListener("abort", onAbort);
|
|
@@ -15172,7 +15173,18 @@ function defaultSpawn(command, args, options) {
|
|
|
15172
15173
|
child?.unref();
|
|
15173
15174
|
const started = Boolean(child?.pid);
|
|
15174
15175
|
if (started && !observedChildExit) error ??= new Error("process_exit_unobserved");
|
|
15175
|
-
|
|
15176
|
+
const unverifiedDescendants = stopping || started && !observedChildExit;
|
|
15177
|
+
resolve7({
|
|
15178
|
+
stdout: Buffer.concat(stdout).toString("utf8"),
|
|
15179
|
+
stderr: Buffer.concat(stderr).toString("utf8"),
|
|
15180
|
+
code,
|
|
15181
|
+
signal,
|
|
15182
|
+
started,
|
|
15183
|
+
observedChildExit,
|
|
15184
|
+
terminationRequested: stopping,
|
|
15185
|
+
...unverifiedDescendants ? { unverifiedDescendants: true } : {},
|
|
15186
|
+
...error ? { error } : {}
|
|
15187
|
+
});
|
|
15176
15188
|
};
|
|
15177
15189
|
const kill = (requested) => {
|
|
15178
15190
|
killProcessTree(child, requested);
|
|
@@ -15203,7 +15215,7 @@ function defaultSpawn(command, args, options) {
|
|
|
15203
15215
|
cwd: options.cwd,
|
|
15204
15216
|
env: options.env ?? process.env,
|
|
15205
15217
|
stdio: ["pipe", "pipe", "pipe"],
|
|
15206
|
-
shell:
|
|
15218
|
+
shell: options.shell === true,
|
|
15207
15219
|
windowsHide: true,
|
|
15208
15220
|
detached: process.platform !== "win32"
|
|
15209
15221
|
});
|
|
@@ -15247,7 +15259,7 @@ function defaultSpawn(command, args, options) {
|
|
|
15247
15259
|
closed = true;
|
|
15248
15260
|
code = exitCode;
|
|
15249
15261
|
signal = exitSignal;
|
|
15250
|
-
|
|
15262
|
+
finish();
|
|
15251
15263
|
});
|
|
15252
15264
|
options.signal?.addEventListener("abort", onAbort, { once: true });
|
|
15253
15265
|
if (options.signal?.aborted) onAbort();
|
|
@@ -15688,6 +15700,31 @@ function defaultRunner(env) {
|
|
|
15688
15700
|
}
|
|
15689
15701
|
};
|
|
15690
15702
|
}
|
|
15703
|
+
function commandResultFromSpawn(result) {
|
|
15704
|
+
const errno = result.error && "code" in result.error ? result.error.code : void 0;
|
|
15705
|
+
const error = (typeof errno === "string" && errno ? errno : void 0) ?? result.error?.message ?? (result.observedChildExit !== true ? "auth_probe_exit_unobserved" : void 0);
|
|
15706
|
+
return {
|
|
15707
|
+
ok: result.code === 0 && !error,
|
|
15708
|
+
code: result.code,
|
|
15709
|
+
stdout: result.stdout,
|
|
15710
|
+
stderr: result.stderr,
|
|
15711
|
+
...error ? { error } : {}
|
|
15712
|
+
};
|
|
15713
|
+
}
|
|
15714
|
+
function defaultAsyncRunner(env, signal) {
|
|
15715
|
+
return async (command, args, timeoutMs) => {
|
|
15716
|
+
try {
|
|
15717
|
+
return commandResultFromSpawn(await defaultSpawn(command, args, {
|
|
15718
|
+
env,
|
|
15719
|
+
timeoutMs,
|
|
15720
|
+
signal,
|
|
15721
|
+
shell: harnessSpawnUsesShell(command)
|
|
15722
|
+
}));
|
|
15723
|
+
} catch (error) {
|
|
15724
|
+
return { ok: false, code: null, stdout: "", stderr: "", error: error instanceof Error ? error.message : "spawn_failed" };
|
|
15725
|
+
}
|
|
15726
|
+
};
|
|
15727
|
+
}
|
|
15691
15728
|
function firstLine(text) {
|
|
15692
15729
|
const line = text.split(/\r?\n/).map((candidate) => candidate.trim()).find(Boolean);
|
|
15693
15730
|
return line && line.length <= 200 ? line : void 0;
|
|
@@ -16103,35 +16140,31 @@ var PendingHarnessCommand = class {
|
|
|
16103
16140
|
this.key = key;
|
|
16104
16141
|
}
|
|
16105
16142
|
};
|
|
16106
|
-
async function
|
|
16143
|
+
async function replayHarnessProbe(run, execute, limit) {
|
|
16107
16144
|
const observed = /* @__PURE__ */ new Map();
|
|
16108
|
-
|
|
16109
|
-
const result = await defaultSpawn(command, args, { env: options.env, timeoutMs, signal: options.signal });
|
|
16110
|
-
const error = result.error?.message ?? (result.observedChildExit !== true ? "auth_probe_exit_unobserved" : void 0);
|
|
16111
|
-
return {
|
|
16112
|
-
ok: result.code === 0 && !error,
|
|
16113
|
-
code: result.code,
|
|
16114
|
-
stdout: result.stdout,
|
|
16115
|
-
stderr: result.stderr,
|
|
16116
|
-
...error ? { error } : {}
|
|
16117
|
-
};
|
|
16118
|
-
});
|
|
16119
|
-
for (let commands = 0; commands <= 32; commands++) {
|
|
16145
|
+
for (let commands = 0; commands <= limit; commands++) {
|
|
16120
16146
|
try {
|
|
16121
|
-
return
|
|
16147
|
+
return execute((command, args, timeoutMs) => {
|
|
16122
16148
|
const key = JSON.stringify([command, args, timeoutMs]);
|
|
16123
16149
|
const result = observed.get(key);
|
|
16124
16150
|
if (result) return result;
|
|
16125
16151
|
throw new PendingHarnessCommand(command, args, timeoutMs, key);
|
|
16126
|
-
}
|
|
16152
|
+
});
|
|
16127
16153
|
} catch (pending) {
|
|
16128
16154
|
if (!(pending instanceof PendingHarnessCommand)) throw pending;
|
|
16129
|
-
if (commands ===
|
|
16155
|
+
if (commands === limit) throw new Error("auth_probe_command_limit");
|
|
16130
16156
|
observed.set(pending.key, await run(pending.command, pending.args, pending.timeoutMs));
|
|
16131
16157
|
}
|
|
16132
16158
|
}
|
|
16133
16159
|
throw new Error("auth_probe_command_limit");
|
|
16134
16160
|
}
|
|
16161
|
+
async function probeHarnessAssignmentAsync(options) {
|
|
16162
|
+
return replayHarnessProbe(
|
|
16163
|
+
options.runCommand ?? defaultAsyncRunner(options.env ?? process.env, options.signal),
|
|
16164
|
+
(runCommand) => probeHarnessAssignment({ ...options, runCommand }),
|
|
16165
|
+
32
|
|
16166
|
+
);
|
|
16167
|
+
}
|
|
16135
16168
|
|
|
16136
16169
|
// plugins/kxm/src/vnext-config.ts
|
|
16137
16170
|
var VnextConfigError = class extends Error {
|
|
@@ -21904,9 +21937,28 @@ import { join as join11 } from "node:path";
|
|
|
21904
21937
|
import { createHash as createHash6, randomUUID as randomUUID3 } from "node:crypto";
|
|
21905
21938
|
import { lstat, mkdir, open, rename } from "node:fs/promises";
|
|
21906
21939
|
import { join as join6 } from "node:path";
|
|
21940
|
+
var ONESHOT_EVIDENCE_SCHEMA = "kxm.oneshot-evidence.v2";
|
|
21941
|
+
var RETIRED_ONESHOT_EVIDENCE_SCHEMAS = /* @__PURE__ */ new Set(["kxm.oneshot-evidence.v1"]);
|
|
21907
21942
|
var TEXT_LIMIT = 4 * 1024 * 1024;
|
|
21943
|
+
var ARGV_LIMIT = 64 * 1024;
|
|
21908
21944
|
var RECORD_LIMIT = 16 * 1024 * 1024;
|
|
21909
21945
|
var digest = (value) => `sha256:${createHash6("sha256").update(value).digest("hex")}`;
|
|
21946
|
+
function parseOneShotEvidenceRecord(bytes) {
|
|
21947
|
+
let parsed;
|
|
21948
|
+
try {
|
|
21949
|
+
parsed = JSON.parse(bytes);
|
|
21950
|
+
} catch {
|
|
21951
|
+
throw new Error("oneshot_evidence_record_invalid");
|
|
21952
|
+
}
|
|
21953
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
21954
|
+
throw new Error("oneshot_evidence_record_invalid");
|
|
21955
|
+
}
|
|
21956
|
+
const schema = parsed.schema;
|
|
21957
|
+
if (typeof schema !== "string") throw new Error("oneshot_evidence_record_invalid");
|
|
21958
|
+
if (RETIRED_ONESHOT_EVIDENCE_SCHEMAS.has(schema)) throw new Error("oneshot_evidence_schema_retired");
|
|
21959
|
+
if (schema !== ONESHOT_EVIDENCE_SCHEMA) throw new Error("oneshot_evidence_schema_unknown");
|
|
21960
|
+
return parsed;
|
|
21961
|
+
}
|
|
21910
21962
|
async function beginOneShotEvidence(root, intent, sensitive) {
|
|
21911
21963
|
await mkdir(root, { recursive: true, mode: 448 });
|
|
21912
21964
|
const stat = await lstat(root);
|
|
@@ -21920,11 +21972,11 @@ async function beginOneShotEvidence(root, intent, sensitive) {
|
|
|
21920
21972
|
const redact = (value) => {
|
|
21921
21973
|
let text = value;
|
|
21922
21974
|
for (const secret of secrets) text = text.replaceAll(secret, "[REDACTED]");
|
|
21923
|
-
return text.replace(/(\b(?:authorization|api[_-]?key|access[_-]?token|refresh[_-]?token|password|secret)\b["']?\s*[:=]\s*["']?)(?:Bearer\s+)?[^\s"',;}]+/gi, "$1[REDACTED]");
|
|
21975
|
+
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]");
|
|
21924
21976
|
};
|
|
21925
|
-
const bounded = (value) => {
|
|
21977
|
+
const bounded = (value, max = TEXT_LIMIT) => {
|
|
21926
21978
|
const bytes = Buffer.from(redact(value));
|
|
21927
|
-
return { text: bytes.subarray(0,
|
|
21979
|
+
return { text: bytes.subarray(0, max).toString("utf8"), truncated: bytes.length > max, sha256: digest(redact(value)) };
|
|
21928
21980
|
};
|
|
21929
21981
|
const write = async (name, value) => {
|
|
21930
21982
|
const current = await lstat(dir);
|
|
@@ -21933,6 +21985,7 @@ async function beginOneShotEvidence(root, intent, sensitive) {
|
|
|
21933
21985
|
}
|
|
21934
21986
|
const bytes = JSON.stringify(value);
|
|
21935
21987
|
if (Buffer.byteLength(bytes) > RECORD_LIMIT) throw new Error("oneshot_evidence_record_limit");
|
|
21988
|
+
parseOneShotEvidenceRecord(bytes);
|
|
21936
21989
|
const target = join6(dir, name);
|
|
21937
21990
|
const partial = `${target}.partial`;
|
|
21938
21991
|
const file = await open(partial, "wx", 384);
|
|
@@ -21954,7 +22007,7 @@ async function beginOneShotEvidence(root, intent, sensitive) {
|
|
|
21954
22007
|
return digest(bytes);
|
|
21955
22008
|
};
|
|
21956
22009
|
await write("intent.json", {
|
|
21957
|
-
schema:
|
|
22010
|
+
schema: ONESHOT_EVIDENCE_SCHEMA,
|
|
21958
22011
|
id,
|
|
21959
22012
|
recordedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
21960
22013
|
runId: intent.runId,
|
|
@@ -21965,9 +22018,10 @@ async function beginOneShotEvidence(root, intent, sensitive) {
|
|
|
21965
22018
|
provider: intent.provider,
|
|
21966
22019
|
requestedModel: intent.model,
|
|
21967
22020
|
cwd: intent.cwd,
|
|
21968
|
-
command: intent.command,
|
|
21969
|
-
argv: bounded(JSON.stringify(intent.args)),
|
|
22021
|
+
command: redact(intent.command),
|
|
22022
|
+
argv: bounded(JSON.stringify(intent.args), ARGV_LIMIT),
|
|
21970
22023
|
stdin: intent.input === void 0 ? null : bounded(intent.input),
|
|
22024
|
+
env: null,
|
|
21971
22025
|
acceptance: false
|
|
21972
22026
|
});
|
|
21973
22027
|
let finished = false;
|
|
@@ -21977,7 +22031,7 @@ async function beginOneShotEvidence(root, intent, sensitive) {
|
|
|
21977
22031
|
if (finished) throw new Error("oneshot_evidence_already_finished");
|
|
21978
22032
|
finished = true;
|
|
21979
22033
|
return write("result.json", {
|
|
21980
|
-
schema:
|
|
22034
|
+
schema: ONESHOT_EVIDENCE_SCHEMA,
|
|
21981
22035
|
id,
|
|
21982
22036
|
recordedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
21983
22037
|
stdout: bounded(result.stdout),
|
|
@@ -21987,6 +22041,7 @@ async function beginOneShotEvidence(root, intent, sensitive) {
|
|
|
21987
22041
|
started: result.started ?? null,
|
|
21988
22042
|
observedChildExit: result.observedChildExit ?? null,
|
|
21989
22043
|
terminationRequested: result.terminationRequested ?? null,
|
|
22044
|
+
unverifiedDescendants: result.unverifiedDescendants ?? null,
|
|
21990
22045
|
error: result.error ? bounded(result.error.message) : null,
|
|
21991
22046
|
observation: bounded(JSON.stringify(observation)),
|
|
21992
22047
|
acceptance: false
|
|
@@ -25648,7 +25703,8 @@ Return a final JSON object with an "outcome" field chosen from ${JSON.stringify(
|
|
|
25648
25703
|
parsed = { text: "", isError: true };
|
|
25649
25704
|
}
|
|
25650
25705
|
const aborted = request.signal.aborted || procResult.error?.message === "process_aborted";
|
|
25651
|
-
const
|
|
25706
|
+
const unverifiedDescendants = procResult.unverifiedDescendants === true || procResult.terminationRequested === true || procResult.error?.message === "process_exit_unobserved" || procResult.observedChildExit === false && procResult.started !== false;
|
|
25707
|
+
const effectUncertain = unverifiedDescendants || Boolean(procResult.signal);
|
|
25652
25708
|
const transportFailed = procResult.code !== 0 || Boolean(procResult.error) || effectUncertain;
|
|
25653
25709
|
const outcome = aborted ? "cancelled" : transportFailed || parsed.isError ? "failed" : determineOutcome(parsed.text, request.allowedOutcomes);
|
|
25654
25710
|
const providerMetadata = {
|
|
@@ -25660,6 +25716,7 @@ Return a final JSON object with an "outcome" field chosen from ${JSON.stringify(
|
|
|
25660
25716
|
if (procResult.observedChildExit !== void 0) providerMetadata.observedChildExit = procResult.observedChildExit;
|
|
25661
25717
|
if (procResult.started !== void 0) providerMetadata.processStarted = procResult.started;
|
|
25662
25718
|
if (procResult.terminationRequested !== void 0) providerMetadata.terminationRequested = procResult.terminationRequested;
|
|
25719
|
+
if (unverifiedDescendants) providerMetadata.descendantEffects = "unverified";
|
|
25663
25720
|
if (procResult.error) {
|
|
25664
25721
|
const reason = procResult.error.message;
|
|
25665
25722
|
providerMetadata.processError = /^process_(aborted|timeout|output_limit|stdin_error|stdio_error|stdio_unclosed|exit_unobserved)$/.test(reason) ? reason : "process_error";
|
|
@@ -26140,7 +26197,6 @@ async function startVnextRuntimeSupervisorInner(paths, requestedPortOption, now)
|
|
|
26140
26197
|
});
|
|
26141
26198
|
} catch (err) {
|
|
26142
26199
|
earlyError = err;
|
|
26143
|
-
drivePromise = Promise.reject(err);
|
|
26144
26200
|
}
|
|
26145
26201
|
await Promise.resolve();
|
|
26146
26202
|
if (earlyError) {
|
|
@@ -26166,6 +26222,7 @@ async function startVnextRuntimeSupervisorInner(paths, requestedPortOption, now)
|
|
|
26166
26222
|
} catch {
|
|
26167
26223
|
}
|
|
26168
26224
|
}
|
|
26225
|
+
}).catch(() => {
|
|
26169
26226
|
});
|
|
26170
26227
|
sendJson(response, 202, {
|
|
26171
26228
|
ok: true,
|
package/plugins/kxm/package.json
CHANGED
|
@@ -81,7 +81,7 @@ import { loadKxmUpdateConfig } from "../kxm-update-config.ts";
|
|
|
81
81
|
import {
|
|
82
82
|
formatHarnessUpdate,
|
|
83
83
|
planHarnessUpdate,
|
|
84
|
-
|
|
84
|
+
probeHarnessesAsync,
|
|
85
85
|
runHarnessUpdate,
|
|
86
86
|
type HarnessUpdateScope,
|
|
87
87
|
} from "../vnext-harness.ts";
|
|
@@ -399,7 +399,7 @@ export async function cmdUpdate(runtime: Runtime, harness: string | undefined, o
|
|
|
399
399
|
return kxmApply?.ok === false ? 1 : 0;
|
|
400
400
|
}
|
|
401
401
|
const scope: HarnessUpdateScope = options.self ? "self" : options.extensions ? "extensions" : options.models ? "models" : "all";
|
|
402
|
-
const inventory =
|
|
402
|
+
const inventory = await probeHarnessesAsync({ env: runtime.env });
|
|
403
403
|
const planned = planHarnessUpdate(inventory, { ...(harness ? { harness } : {}), scope });
|
|
404
404
|
const steps = runHarnessUpdate(planned, { env: runtime.env, dryRun: runtime.dryRun });
|
|
405
405
|
const failed = steps.some((step) => step.outcome === "failed") || kxmApply?.ok === false;
|
|
@@ -755,7 +755,7 @@ export async function maybeOfferGuideSetup(runtime: Runtime): Promise<void> {
|
|
|
755
755
|
if (runtime.env[GUIDE_SETUP_OPT_OUT_ENV]?.trim()) return;
|
|
756
756
|
let inventory;
|
|
757
757
|
try {
|
|
758
|
-
inventory =
|
|
758
|
+
inventory = await probeHarnessesAsync({ env: runtime.env });
|
|
759
759
|
} catch {
|
|
760
760
|
return;
|
|
761
761
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
2
2
|
import { basename, join, resolve } from "node:path";
|
|
3
3
|
import { parse as parseYaml } from "yaml";
|
|
4
|
-
import {
|
|
4
|
+
import { probeHarnessesAsync } from "../vnext-harness.ts";
|
|
5
5
|
import { suggestWorkflowAndRoles } from "../suggest.ts";
|
|
6
6
|
import {
|
|
7
7
|
createGoal,
|
|
@@ -32,7 +32,7 @@ export async function cmdSuggest(runtime: Runtime, promptParts: string[]): Promi
|
|
|
32
32
|
runtime.io.stderr("prompt must be non-empty\n");
|
|
33
33
|
return 2;
|
|
34
34
|
}
|
|
35
|
-
const inventory =
|
|
35
|
+
const inventory = await probeHarnessesAsync({ env: runtime.env });
|
|
36
36
|
const availableHarnesses = inventory.harnesses.map((h) => ({
|
|
37
37
|
harness: h.id,
|
|
38
38
|
auth: h.authenticated === true ? "authenticated" : "unauthenticated",
|
|
@@ -25,7 +25,7 @@ import {
|
|
|
25
25
|
import { vnextRuntimePaths } from "../vnext-runtime-store.ts";
|
|
26
26
|
import {
|
|
27
27
|
formatHarnessInventory,
|
|
28
|
-
|
|
28
|
+
probeHarnessesAsync,
|
|
29
29
|
} from "../vnext-harness.ts";
|
|
30
30
|
import {
|
|
31
31
|
fetchLatestKxmVersion,
|
|
@@ -580,7 +580,7 @@ export async function cmdVnextRunList(runtime: Runtime): Promise<number> {
|
|
|
580
580
|
}
|
|
581
581
|
|
|
582
582
|
export async function cmdHarnessList(runtime: Runtime): Promise<number> {
|
|
583
|
-
const inventory =
|
|
583
|
+
const inventory = await probeHarnessesAsync({ env: runtime.env });
|
|
584
584
|
print(runtime.io, runtime.json, { ok: true, command: "harness list", ...inventory }, formatHarnessInventory(inventory));
|
|
585
585
|
return 0;
|
|
586
586
|
}
|
|
@@ -7,7 +7,7 @@ import { AGENT_COMMANDS_MAP, enforceToolPolicy, getMcpTools, reconcileInbox } fr
|
|
|
7
7
|
import { deliverInboxNotification } from "./inbox.ts";
|
|
8
8
|
import type { HubEvent, MessageRecord } from "./protocol.ts";
|
|
9
9
|
|
|
10
|
-
const VERSION = "0.7.
|
|
10
|
+
const VERSION = "0.7.9";
|
|
11
11
|
const inbox = new Map<string, MessageRecord>();
|
|
12
12
|
const notifiedInbox = new Set<string>();
|
|
13
13
|
let meshClient: HubClient | undefined;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { mkdirSync, writeFileSync } from "node:fs";
|
|
2
|
-
import { spawnSync } from "node:child_process";
|
|
3
2
|
import { join } from "node:path";
|
|
4
3
|
import { stringify } from "yaml";
|
|
4
|
+
import { defaultSpawn } from "./vnext-oneshot-process.ts";
|
|
5
5
|
|
|
6
6
|
export interface InventoryPrice {
|
|
7
7
|
inputPerMillion: number | null;
|
|
@@ -95,10 +95,10 @@ export async function refreshModelInventory(options: { outputRoot: string; env?:
|
|
|
95
95
|
if (item.fast === true) current.capabilities.speed = { fast: true, tiers: ["fast"], source };
|
|
96
96
|
byId.set(key, current);
|
|
97
97
|
};
|
|
98
|
-
const native = (command: string, args: string[], source: string) => {
|
|
99
|
-
const result =
|
|
100
|
-
const ok = result.
|
|
101
|
-
sources[source] = { url: `${command} ${args.join(" ")}`, ok, ...(ok ? {} : { error: (result.stderr || "command failed").trim().slice(0, 240) }) };
|
|
98
|
+
const native = async (command: string, args: string[], source: string) => {
|
|
99
|
+
const result = await defaultSpawn(command, args, { env, timeoutMs: 20_000 });
|
|
100
|
+
const ok = result.code === 0 && !result.error;
|
|
101
|
+
sources[source] = { url: `${command} ${args.join(" ")}`, ok, ...(ok ? {} : { error: (result.stderr || result.error?.message || "command failed").trim().slice(0, 240) }) };
|
|
102
102
|
if (!ok) return;
|
|
103
103
|
for (const line of (result.stdout || "").split(/\r?\n/)) {
|
|
104
104
|
const parts = line.trim().split(/\s+/);
|
|
@@ -109,9 +109,9 @@ export async function refreshModelInventory(options: { outputRoot: string; env?:
|
|
|
109
109
|
}
|
|
110
110
|
}
|
|
111
111
|
};
|
|
112
|
-
native("pi", ["--list-models"], "pi");
|
|
113
|
-
native("grok", ["models"], "grok");
|
|
114
|
-
native("agy", ["models"], "agy");
|
|
112
|
+
await native("pi", ["--list-models"], "pi");
|
|
113
|
+
await native("grok", ["models"], "grok");
|
|
114
|
+
await native("agy", ["models"], "agy");
|
|
115
115
|
const openrouter = await fetchModels(env.KXM_OPENROUTER_MODELS_URL?.trim() || OR_URL, env.OPENROUTER_API_KEY);
|
|
116
116
|
const openrouterUrl = env.KXM_OPENROUTER_MODELS_URL?.trim() || OR_URL;
|
|
117
117
|
sources.openrouter = { url: openrouterUrl, ok: !openrouter.error, ...(openrouter.error ? { error: openrouter.error } : {}) };
|
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Supervisory agent control, in-process real-time steering, and work-splitting.
|
|
3
|
+
*
|
|
4
|
+
* Implements:
|
|
5
|
+
* - Dual-tool control plane: `agent` (spawn) and `agent_control` (manage, poll, steer, stop).
|
|
6
|
+
* - Strict capability narrowing via `allowed_tools[]` allowlists.
|
|
7
|
+
* - Mid-flight steering instruction injection.
|
|
8
|
+
* - Subagent state lifecycle management (working, blocked, idle, done, error).
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { randomUUID } from "node:crypto";
|
|
12
|
+
import { assertCommandSeatbelt } from "./safety-integrity.ts";
|
|
13
|
+
|
|
14
|
+
export type SubagentType = "general" | "Explore" | "Plan" | "Reviewer" | "Auditor";
|
|
15
|
+
export type SubagentStatus = "pending" | "running" | "blocked" | "completed" | "stopped" | "failed";
|
|
16
|
+
export type ThinkingLevel = "off" | "minimal" | "low" | "medium" | "high";
|
|
17
|
+
|
|
18
|
+
export interface AgentSpawnParams {
|
|
19
|
+
prompt: string;
|
|
20
|
+
description: string;
|
|
21
|
+
type?: SubagentType | undefined;
|
|
22
|
+
model?: string | undefined;
|
|
23
|
+
thinking?: ThinkingLevel | undefined;
|
|
24
|
+
allowed_tools?: string[] | undefined;
|
|
25
|
+
turns?: number | undefined;
|
|
26
|
+
background?: boolean | undefined;
|
|
27
|
+
parent_tools?: readonly string[] | undefined;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface AgentControlParams {
|
|
31
|
+
action: "info" | "result" | "steer" | "stop";
|
|
32
|
+
kind?: "types" | "models" | "active" | undefined;
|
|
33
|
+
agent_id?: string | undefined;
|
|
34
|
+
message?: string | undefined;
|
|
35
|
+
turns?: number | undefined;
|
|
36
|
+
verbose?: boolean | undefined;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface SubagentRecord {
|
|
40
|
+
id: string;
|
|
41
|
+
description: string;
|
|
42
|
+
type: SubagentType;
|
|
43
|
+
model: string;
|
|
44
|
+
thinking: ThinkingLevel;
|
|
45
|
+
allowedTools: string[];
|
|
46
|
+
status: SubagentStatus;
|
|
47
|
+
turnsMax: number;
|
|
48
|
+
turnsCompleted: number;
|
|
49
|
+
prompt: string;
|
|
50
|
+
createdAt: string;
|
|
51
|
+
updatedAt: string;
|
|
52
|
+
steeringMessages: Array<{ text: string; injectedAt: string; processed: boolean }>;
|
|
53
|
+
output?: string | undefined;
|
|
54
|
+
error?: string | undefined;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface SubagentExecutionReceipt {
|
|
58
|
+
ok: boolean;
|
|
59
|
+
action: string;
|
|
60
|
+
agent_id?: string | undefined;
|
|
61
|
+
status?: SubagentStatus | undefined;
|
|
62
|
+
activeAgents?: Array<{ id: string; description: string; type: SubagentType; status: SubagentStatus }> | undefined;
|
|
63
|
+
types?: readonly SubagentType[] | undefined;
|
|
64
|
+
result?: string | undefined;
|
|
65
|
+
steered?: boolean | undefined;
|
|
66
|
+
stopped?: boolean | undefined;
|
|
67
|
+
error?: string | undefined;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export const SUBAGENT_TYPES: readonly SubagentType[] = Object.freeze([
|
|
71
|
+
"general",
|
|
72
|
+
"Explore",
|
|
73
|
+
"Plan",
|
|
74
|
+
"Reviewer",
|
|
75
|
+
"Auditor",
|
|
76
|
+
]);
|
|
77
|
+
|
|
78
|
+
export const DEFAULT_SUBAGENT_MODELS: Readonly<Record<SubagentType, string>> = Object.freeze({
|
|
79
|
+
general: "grok/grok-4.6",
|
|
80
|
+
Explore: "openrouter/qwen/qwen3-coder-plus",
|
|
81
|
+
Plan: "claude/fable",
|
|
82
|
+
Reviewer: "openai/gpt-5.6-sol",
|
|
83
|
+
Auditor: "claude/fable",
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Validates and intersects child allowed tools against the parent tool surface.
|
|
88
|
+
* Enforces the safety invariant that a child can only narrow, never expand authority.
|
|
89
|
+
*/
|
|
90
|
+
export function narrowSubagentTools(
|
|
91
|
+
requestedTools: readonly string[] | undefined,
|
|
92
|
+
parentTools?: readonly string[] | undefined,
|
|
93
|
+
): string[] {
|
|
94
|
+
if (!requestedTools || requestedTools.length === 0) {
|
|
95
|
+
return parentTools ? [...parentTools] : ["read", "grep", "find"];
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (!parentTools || parentTools.length === 0) {
|
|
99
|
+
return [...requestedTools];
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const parentSet = new Set(parentTools);
|
|
103
|
+
const narrowed: string[] = [];
|
|
104
|
+
|
|
105
|
+
for (const tool of requestedTools) {
|
|
106
|
+
if (parentSet.has(tool)) {
|
|
107
|
+
narrowed.push(tool);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (narrowed.length === 0 && requestedTools.length > 0) {
|
|
112
|
+
throw new Error(
|
|
113
|
+
`Cannot spawn subagent: requested tools [${requestedTools.join(", ")}] expand beyond parent capabilities.`,
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return narrowed;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* In-Process Subagent Manager.
|
|
122
|
+
*/
|
|
123
|
+
export class SubagentManager {
|
|
124
|
+
private readonly subagents = new Map<string, SubagentRecord>();
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Spawns a new managed subagent with strict capability boundaries.
|
|
128
|
+
*/
|
|
129
|
+
spawn(params: AgentSpawnParams): SubagentRecord {
|
|
130
|
+
if (!params.prompt || !params.prompt.trim()) {
|
|
131
|
+
throw new Error('Subagent "prompt" is required.');
|
|
132
|
+
}
|
|
133
|
+
if (!params.description || !params.description.trim()) {
|
|
134
|
+
throw new Error('Subagent "description" is required.');
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const type = params.type ?? "general";
|
|
138
|
+
const model = params.model ?? DEFAULT_SUBAGENT_MODELS[type] ?? "grok/grok-4.6";
|
|
139
|
+
const thinking = params.thinking ?? "low";
|
|
140
|
+
const allowedTools = narrowSubagentTools(params.allowed_tools, params.parent_tools);
|
|
141
|
+
const id = `ag_${randomUUID().replaceAll("-", "").slice(0, 10)}`;
|
|
142
|
+
const now = new Date().toISOString();
|
|
143
|
+
|
|
144
|
+
const record: SubagentRecord = {
|
|
145
|
+
id,
|
|
146
|
+
description: params.description.trim(),
|
|
147
|
+
type,
|
|
148
|
+
model,
|
|
149
|
+
thinking,
|
|
150
|
+
allowedTools,
|
|
151
|
+
status: params.background === false ? "completed" : "running",
|
|
152
|
+
turnsMax: params.turns ?? 10,
|
|
153
|
+
turnsCompleted: 0,
|
|
154
|
+
prompt: params.prompt.trim(),
|
|
155
|
+
createdAt: now,
|
|
156
|
+
updatedAt: now,
|
|
157
|
+
steeringMessages: [],
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
this.subagents.set(id, record);
|
|
161
|
+
return record;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Dispatches an action from the `agent_control` tool.
|
|
166
|
+
*/
|
|
167
|
+
control(params: AgentControlParams): SubagentExecutionReceipt {
|
|
168
|
+
const action = params.action;
|
|
169
|
+
|
|
170
|
+
// 1. Action: "info"
|
|
171
|
+
if (action === "info") {
|
|
172
|
+
const kind = params.kind ?? "active";
|
|
173
|
+
if (kind === "types") {
|
|
174
|
+
return {
|
|
175
|
+
ok: true,
|
|
176
|
+
action: "info",
|
|
177
|
+
types: SUBAGENT_TYPES,
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
if (kind === "models") {
|
|
181
|
+
return {
|
|
182
|
+
ok: true,
|
|
183
|
+
action: "info",
|
|
184
|
+
result: JSON.stringify(DEFAULT_SUBAGENT_MODELS),
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
const active = Array.from(this.subagents.values()).map((ag) => ({
|
|
188
|
+
id: ag.id,
|
|
189
|
+
description: ag.description,
|
|
190
|
+
type: ag.type,
|
|
191
|
+
status: ag.status,
|
|
192
|
+
}));
|
|
193
|
+
return {
|
|
194
|
+
ok: true,
|
|
195
|
+
action: "info",
|
|
196
|
+
activeAgents: active,
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (!params.agent_id) {
|
|
201
|
+
return {
|
|
202
|
+
ok: false,
|
|
203
|
+
action,
|
|
204
|
+
error: 'Parameter "agent_id" is required for result, steer, and stop actions.',
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const agent = this.subagents.get(params.agent_id);
|
|
209
|
+
if (!agent) {
|
|
210
|
+
return {
|
|
211
|
+
ok: false,
|
|
212
|
+
action,
|
|
213
|
+
agent_id: params.agent_id,
|
|
214
|
+
error: `Subagent "${params.agent_id}" not found.`,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// 2. Action: "result"
|
|
219
|
+
if (action === "result") {
|
|
220
|
+
return {
|
|
221
|
+
ok: true,
|
|
222
|
+
action: "result",
|
|
223
|
+
agent_id: agent.id,
|
|
224
|
+
status: agent.status,
|
|
225
|
+
result: agent.output ?? `[Subagent is currently ${agent.status}]`,
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// 3. Action: "steer"
|
|
230
|
+
if (action === "steer") {
|
|
231
|
+
if (!params.message || !params.message.trim()) {
|
|
232
|
+
return {
|
|
233
|
+
ok: false,
|
|
234
|
+
action: "steer",
|
|
235
|
+
agent_id: agent.id,
|
|
236
|
+
error: 'Parameter "message" is required for steer action.',
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (agent.status === "completed" || agent.status === "stopped" || agent.status === "failed") {
|
|
241
|
+
return {
|
|
242
|
+
ok: false,
|
|
243
|
+
action: "steer",
|
|
244
|
+
agent_id: agent.id,
|
|
245
|
+
error: `Cannot steer subagent "${agent.id}" because it is already ${agent.status}.`,
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
agent.steeringMessages.push({
|
|
250
|
+
text: params.message.trim(),
|
|
251
|
+
injectedAt: new Date().toISOString(),
|
|
252
|
+
processed: false,
|
|
253
|
+
});
|
|
254
|
+
agent.updatedAt = new Date().toISOString();
|
|
255
|
+
|
|
256
|
+
return {
|
|
257
|
+
ok: true,
|
|
258
|
+
action: "steer",
|
|
259
|
+
agent_id: agent.id,
|
|
260
|
+
status: agent.status,
|
|
261
|
+
steered: true,
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// 4. Action: "stop"
|
|
266
|
+
if (action === "stop") {
|
|
267
|
+
agent.status = "stopped";
|
|
268
|
+
agent.updatedAt = new Date().toISOString();
|
|
269
|
+
return {
|
|
270
|
+
ok: true,
|
|
271
|
+
action: "stop",
|
|
272
|
+
agent_id: agent.id,
|
|
273
|
+
status: "stopped",
|
|
274
|
+
stopped: true,
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
return {
|
|
279
|
+
ok: false,
|
|
280
|
+
action,
|
|
281
|
+
error: `Unknown control action "${action}".`,
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Retrieves an agent record by ID.
|
|
287
|
+
*/
|
|
288
|
+
get(agentId: string): SubagentRecord | undefined {
|
|
289
|
+
return this.subagents.get(agentId);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Marks a subagent as completed with output.
|
|
294
|
+
*/
|
|
295
|
+
complete(agentId: string, output: string): void {
|
|
296
|
+
const agent = this.subagents.get(agentId);
|
|
297
|
+
if (agent) {
|
|
298
|
+
agent.status = "completed";
|
|
299
|
+
agent.output = output;
|
|
300
|
+
agent.updatedAt = new Date().toISOString();
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Returns all active running subagents.
|
|
306
|
+
*/
|
|
307
|
+
listActive(): SubagentRecord[] {
|
|
308
|
+
return Array.from(this.subagents.values()).filter(
|
|
309
|
+
(ag) => ag.status === "running" || ag.status === "pending" || ag.status === "blocked",
|
|
310
|
+
);
|
|
311
|
+
}
|
|
312
|
+
}
|