@kontextmind/kxm 0.7.112 → 0.7.118
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/agents/implementer.yaml +1 -1
- package/.kxm/roles/writer.yaml +1 -1
- package/docs/contributing/assignment-runner.md +3 -3
- package/package.json +1 -1
- package/packages/core/tui/dist/index.js +478 -0
- package/packages/core/tui/src/adapters/claude.ts +131 -0
- package/packages/core/tui/src/adapters/historyExport.ts +35 -0
- package/packages/core/tui/src/adapters/omp.ts +174 -0
- package/packages/core/tui/src/adapters/pi.ts +34 -0
- package/packages/core/tui/src/exports/index.ts +8 -0
- package/packages/core/tui/src/tui/history.ts +106 -0
- package/packages/core/tui/src/tui/modelSelector.ts +43 -0
- package/packages/core/tui/src/tui/optimizer.ts +154 -0
- package/packages/core/tui/src/tui/queue.ts +90 -0
- package/packages/core/tui/src/tui/roleBudget.ts +104 -0
- package/plugins/kxm/.claude-plugin/plugin.json +1 -1
- package/plugins/kxm/dist/cli.js +220 -22
- package/plugins/kxm/dist/mcp-server.js +1 -1
- package/plugins/kxm/dist/runtime-supervisor.js +36 -4
- package/plugins/kxm/dist/runtime.js +36 -4
- package/plugins/kxm/dist/server.js +22 -0
- package/plugins/kxm/package.json +1 -1
- package/plugins/kxm/skills/kxm-harness-auth/SKILL.md +6 -0
- package/plugins/kxm/src/autocomplete.ts +3 -0
- package/plugins/kxm/src/cli/plugins.ts +201 -0
- package/plugins/kxm/src/cli.ts +14 -0
- package/plugins/kxm/src/harness.ts +36 -4
- package/plugins/kxm/src/mcp-server.ts +1 -1
- package/scripts/assignment-run.d.mts +7 -0
- package/scripts/assignment-run.mjs +128 -1
- package/scripts/demo-overlay.mjs +175 -0
- package/scripts/harness-run.mjs +2 -2
- package/scripts/native-critic.mjs +7 -7
package/plugins/kxm/package.json
CHANGED
|
@@ -23,6 +23,12 @@ Issuing a token is an operator action. When kxm_* tools fail with
|
|
|
23
23
|
`No active session token found in env or disk` for such a file even though it
|
|
24
24
|
still blocks the tools.
|
|
25
25
|
|
|
26
|
+
## Plugins
|
|
27
|
+
|
|
28
|
+
| Command | Purpose | Options |
|
|
29
|
+
|---|---|---|
|
|
30
|
+
| `kxm plugin install` | Install KXM plugin for discovered or specified harnesses | `--all`, `--claude`, `--omp`, `--pi`, `--dry-run`, `--json` |
|
|
31
|
+
|
|
26
32
|
## Updates
|
|
27
33
|
|
|
28
34
|
| Command | Purpose |
|
|
@@ -8,6 +8,7 @@ const TOP_LEVEL_COMMANDS = [
|
|
|
8
8
|
"runs",
|
|
9
9
|
"tenant",
|
|
10
10
|
"harness",
|
|
11
|
+
"plugin",
|
|
11
12
|
"update",
|
|
12
13
|
"runtime",
|
|
13
14
|
"trust",
|
|
@@ -38,6 +39,7 @@ const SUBCOMMANDS: Record<string, string[]> = {
|
|
|
38
39
|
runs: ["status", "cancel", "list"],
|
|
39
40
|
tenant: ["status"],
|
|
40
41
|
harness: ["list"],
|
|
42
|
+
plugin: ["install"],
|
|
41
43
|
runtime: ["start", "status", "stop"],
|
|
42
44
|
trust: ["diff", "check"],
|
|
43
45
|
agent: ["worker"],
|
|
@@ -137,6 +139,7 @@ _kxm() {
|
|
|
137
139
|
'run:Create a KXM workflow run'
|
|
138
140
|
'runs:Inspect KXM runs'
|
|
139
141
|
'harness:Detect coding-agent harnesses and auth'
|
|
142
|
+
'plugin:Install or manage KXM harness plugins'
|
|
140
143
|
'update:Update kxm, harness CLIs, and model catalogs'
|
|
141
144
|
'runtime:Manage the KXM Runtime supervisor'
|
|
142
145
|
'trust:Permission-diff trust review'
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import { findKxmRepoRoot } from "../repo-root.ts";
|
|
3
|
+
import { probeHarnesses } from "../harness.ts";
|
|
4
|
+
import { print, type Runtime } from "./types.ts";
|
|
5
|
+
|
|
6
|
+
export type PluginHarnessId = "pi" | "omp" | "claude";
|
|
7
|
+
|
|
8
|
+
export const PLUGIN_SUPPORTED_HARNESSES: readonly PluginHarnessId[] = Object.freeze(["pi", "omp", "claude"]);
|
|
9
|
+
|
|
10
|
+
export interface PluginInstallOptions {
|
|
11
|
+
all?: boolean | undefined;
|
|
12
|
+
claude?: boolean | undefined;
|
|
13
|
+
omp?: boolean | undefined;
|
|
14
|
+
pi?: boolean | undefined;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface PluginInstallHarnessResult {
|
|
18
|
+
harness: string;
|
|
19
|
+
status: "passed" | "failed" | "would" | "skipped";
|
|
20
|
+
command?: string | undefined;
|
|
21
|
+
args?: readonly string[] | undefined;
|
|
22
|
+
detail?: string | undefined;
|
|
23
|
+
error?: string | undefined;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export async function cmdPluginInstall(
|
|
27
|
+
runtime: Runtime,
|
|
28
|
+
options: PluginInstallOptions,
|
|
29
|
+
): Promise<number> {
|
|
30
|
+
const repoRoot = findKxmRepoRoot(import.meta.url);
|
|
31
|
+
const env = runtime.env.PATH ? runtime.env : { ...process.env, ...runtime.env };
|
|
32
|
+
const inventory = probeHarnesses({ env });
|
|
33
|
+
|
|
34
|
+
const isExplicit = Boolean(options.claude || options.omp || options.pi);
|
|
35
|
+
const requestedHarnesses: PluginHarnessId[] = [];
|
|
36
|
+
if (options.pi) requestedHarnesses.push("pi");
|
|
37
|
+
if (options.omp) requestedHarnesses.push("omp");
|
|
38
|
+
if (options.claude) requestedHarnesses.push("claude");
|
|
39
|
+
|
|
40
|
+
const targetHarnesses: readonly PluginHarnessId[] = isExplicit
|
|
41
|
+
? requestedHarnesses
|
|
42
|
+
: PLUGIN_SUPPORTED_HARNESSES;
|
|
43
|
+
|
|
44
|
+
const results: PluginInstallHarnessResult[] = [];
|
|
45
|
+
|
|
46
|
+
for (const harnessId of targetHarnesses) {
|
|
47
|
+
const status = inventory.harnesses.find((h) => h.id === harnessId);
|
|
48
|
+
const detected = Boolean(status?.detected && status?.command);
|
|
49
|
+
|
|
50
|
+
if (!detected) {
|
|
51
|
+
if (isExplicit) {
|
|
52
|
+
results.push({
|
|
53
|
+
harness: harnessId,
|
|
54
|
+
status: "failed",
|
|
55
|
+
error: "harness_not_detected",
|
|
56
|
+
detail: `${harnessId} harness is not installed or not found on PATH`,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const command = status!.command!;
|
|
63
|
+
|
|
64
|
+
if (harnessId === "pi") {
|
|
65
|
+
const args = ["install", repoRoot, "-a"];
|
|
66
|
+
if (runtime.dryRun) {
|
|
67
|
+
results.push({
|
|
68
|
+
harness: "pi",
|
|
69
|
+
status: "would",
|
|
70
|
+
command,
|
|
71
|
+
args,
|
|
72
|
+
detail: `would install kxm plugin into pi: ${command} ${args.join(" ")}`,
|
|
73
|
+
});
|
|
74
|
+
} else {
|
|
75
|
+
const run = runtime.io.spawnSync
|
|
76
|
+
? runtime.io.spawnSync(command, args)
|
|
77
|
+
: spawnSync(command, args, { encoding: "utf8", windowsHide: true, env: runtime.env });
|
|
78
|
+
if (run.error || (run.status !== 0 && run.status !== null)) {
|
|
79
|
+
results.push({
|
|
80
|
+
harness: "pi",
|
|
81
|
+
status: "failed",
|
|
82
|
+
command,
|
|
83
|
+
args,
|
|
84
|
+
error: run.error?.message || "install_failed",
|
|
85
|
+
detail: (run.stderr || run.stdout || "pi install exited with error").trim(),
|
|
86
|
+
});
|
|
87
|
+
} else {
|
|
88
|
+
results.push({
|
|
89
|
+
harness: "pi",
|
|
90
|
+
status: "passed",
|
|
91
|
+
command,
|
|
92
|
+
args,
|
|
93
|
+
detail: "installed kxm plugin into pi",
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
} else if (harnessId === "omp") {
|
|
98
|
+
const args = ["plugin", "install", repoRoot];
|
|
99
|
+
if (runtime.dryRun) {
|
|
100
|
+
results.push({
|
|
101
|
+
harness: "omp",
|
|
102
|
+
status: "would",
|
|
103
|
+
command,
|
|
104
|
+
args,
|
|
105
|
+
detail: `would install kxm plugin into omp: ${command} ${args.join(" ")}`,
|
|
106
|
+
});
|
|
107
|
+
} else {
|
|
108
|
+
const run = runtime.io.spawnSync
|
|
109
|
+
? runtime.io.spawnSync(command, args)
|
|
110
|
+
: spawnSync(command, args, { encoding: "utf8", windowsHide: true, env: runtime.env });
|
|
111
|
+
if (run.error || (run.status !== 0 && run.status !== null)) {
|
|
112
|
+
results.push({
|
|
113
|
+
harness: "omp",
|
|
114
|
+
status: "failed",
|
|
115
|
+
command,
|
|
116
|
+
args,
|
|
117
|
+
error: run.error?.message || "install_failed",
|
|
118
|
+
detail: (run.stderr || run.stdout || "omp plugin install exited with error").trim(),
|
|
119
|
+
});
|
|
120
|
+
} else {
|
|
121
|
+
results.push({
|
|
122
|
+
harness: "omp",
|
|
123
|
+
status: "passed",
|
|
124
|
+
command,
|
|
125
|
+
args,
|
|
126
|
+
detail: "installed kxm plugin into omp",
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
} else if (harnessId === "claude") {
|
|
131
|
+
const marketplaceArgs = ["plugin", "marketplace", "add", repoRoot];
|
|
132
|
+
const installArgs = ["plugin", "install", "kxm", "-y"];
|
|
133
|
+
if (runtime.dryRun) {
|
|
134
|
+
results.push({
|
|
135
|
+
harness: "claude",
|
|
136
|
+
status: "would",
|
|
137
|
+
command,
|
|
138
|
+
args: installArgs,
|
|
139
|
+
detail: `would add marketplace and install kxm plugin into claude: ${command} ${installArgs.join(" ")}`,
|
|
140
|
+
});
|
|
141
|
+
} else {
|
|
142
|
+
if (runtime.io.spawnSync) {
|
|
143
|
+
runtime.io.spawnSync(command, marketplaceArgs);
|
|
144
|
+
} else {
|
|
145
|
+
spawnSync(command, marketplaceArgs, { encoding: "utf8", windowsHide: true, env: runtime.env });
|
|
146
|
+
}
|
|
147
|
+
const run = runtime.io.spawnSync
|
|
148
|
+
? runtime.io.spawnSync(command, installArgs)
|
|
149
|
+
: spawnSync(command, installArgs, { encoding: "utf8", windowsHide: true, env: runtime.env });
|
|
150
|
+
if (run.error || (run.status !== 0 && run.status !== null)) {
|
|
151
|
+
results.push({
|
|
152
|
+
harness: "claude",
|
|
153
|
+
status: "failed",
|
|
154
|
+
command,
|
|
155
|
+
args: installArgs,
|
|
156
|
+
error: run.error?.message || "install_failed",
|
|
157
|
+
detail: (run.stderr || run.stdout || "claude plugin install exited with error").trim(),
|
|
158
|
+
});
|
|
159
|
+
} else {
|
|
160
|
+
results.push({
|
|
161
|
+
harness: "claude",
|
|
162
|
+
status: "passed",
|
|
163
|
+
command,
|
|
164
|
+
args: installArgs,
|
|
165
|
+
detail: "installed kxm plugin into claude",
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (results.length === 0) {
|
|
173
|
+
const errorMsg = "no supported harnesses (pi, omp, claude) detected on PATH";
|
|
174
|
+
print(
|
|
175
|
+
runtime.io,
|
|
176
|
+
runtime.json,
|
|
177
|
+
{ ok: false, command: "plugin install", error: "no_harnesses_detected", results: [] },
|
|
178
|
+
errorMsg,
|
|
179
|
+
);
|
|
180
|
+
return 1;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const ok = results.every((r) => r.status === "passed" || r.status === "would");
|
|
184
|
+
const summary = results
|
|
185
|
+
.map((r) => `${r.harness}: ${r.status}${r.detail ? ` (${r.detail})` : ""}`)
|
|
186
|
+
.join("\n");
|
|
187
|
+
|
|
188
|
+
print(
|
|
189
|
+
runtime.io,
|
|
190
|
+
runtime.json,
|
|
191
|
+
{
|
|
192
|
+
ok,
|
|
193
|
+
command: "plugin install",
|
|
194
|
+
...(runtime.dryRun ? { dryRun: true } : {}),
|
|
195
|
+
results,
|
|
196
|
+
},
|
|
197
|
+
summary,
|
|
198
|
+
);
|
|
199
|
+
|
|
200
|
+
return ok ? 0 : 1;
|
|
201
|
+
}
|
package/plugins/kxm/src/cli.ts
CHANGED
|
@@ -120,6 +120,8 @@ cmdBackup,
|
|
|
120
120
|
cmdKxmRuntime,
|
|
121
121
|
} from "./cli/project.ts";
|
|
122
122
|
|
|
123
|
+
import { cmdPluginInstall } from "./cli/plugins.ts";
|
|
124
|
+
|
|
123
125
|
import {
|
|
124
126
|
cmdStatus,
|
|
125
127
|
cmdDash,
|
|
@@ -185,6 +187,7 @@ const USAGE_ERROR_CODES = new Set([
|
|
|
185
187
|
*/
|
|
186
188
|
const DRY_RUN_COMMANDS: ReadonlySet<string> = new Set([
|
|
187
189
|
"init", "backup", "restore", "run", "explain", "suggest", "update", "dash", "completion", "completion install",
|
|
190
|
+
"plugin install", "plugin",
|
|
188
191
|
"runs status", "runs drive", "runs receipt", "runs cancel", "runs list",
|
|
189
192
|
"tenant status", "models inventory-refresh", "harness list", "auth token",
|
|
190
193
|
"routes list", "routes count", "routes admit", "routes disable",
|
|
@@ -506,6 +509,17 @@ function createProgram(ctx: CliContext, result: { code: number }): Command {
|
|
|
506
509
|
harnessCmd.helpCommand("help", "Show harness help");
|
|
507
510
|
addGlobalOptions(harnessCmd.command("list").description("Show installed harnesses, auth, and native updaters")).action(bind(cmdHarnessList));
|
|
508
511
|
|
|
512
|
+
const pluginCmd = addGlobalOptions(program.command("plugin").description("Install or manage KXM harness plugins"));
|
|
513
|
+
pluginCmd.helpCommand("help", "Show plugin help");
|
|
514
|
+
addGlobalOptions(pluginCmd.command("install", { isDefault: true }).description("Install the KXM plugin into discovered or specified harnesses"))
|
|
515
|
+
.option("--all", "Install plugin for all discovered harnesses (default)")
|
|
516
|
+
.option("--claude", "Install plugin for Claude Code")
|
|
517
|
+
.option("--omp", "Install plugin for Oh My Pi (OMP)")
|
|
518
|
+
.option("--pi", "Install plugin for Pi")
|
|
519
|
+
.action(async function pluginInstallAction(this: Command, options: { all?: boolean; claude?: boolean; omp?: boolean; pi?: boolean }) {
|
|
520
|
+
result.code = await cmdPluginInstall(runtimeFrom(ctx, this), options);
|
|
521
|
+
});
|
|
522
|
+
|
|
509
523
|
const authCmd = addGlobalOptions(program.command("auth").description("Manage credentials, tokens, and authorization"));
|
|
510
524
|
authCmd.helpCommand("help", "Show auth help");
|
|
511
525
|
addGlobalOptions(authCmd.command("token").description("Inspect, issue, or clear local disk session tokens"))
|
|
@@ -508,6 +508,7 @@ const READ_ONLY_ONESHOT_ARGS = Object.freeze({
|
|
|
508
508
|
// AGENTS.md/CLAUDE.md context discovery, and an ephemeral session. `--no-tools` alone
|
|
509
509
|
// is not enough: extensions and hooks can still run with their own permissions.
|
|
510
510
|
pi: Object.freeze(["--no-tools", "--no-extensions", "--no-skills", "--no-prompt-templates", "--no-context-files", "--no-session"]),
|
|
511
|
+
omp: Object.freeze(["--no-tools", "--no-extensions", "--no-skills", "--no-prompt-templates", "--no-context-files", "--no-session"]),
|
|
511
512
|
claude: Object.freeze(["--tools", "Read,Glob,Grep", "--restricted", "--safe-mode", "--permission-mode", "plan", "--permission-prompts", "none", "--strict-mcp-config", "--mcp-config", '{"mcpServers":{}}', "--disable-slash-commands", "--no-session-persistence"]),
|
|
512
513
|
codex: Object.freeze(["--sandbox", "read-only", "--ignore-user-config", "-c", 'approval_policy="never"']),
|
|
513
514
|
grok: Object.freeze(["--sandbox", "read-only", "--permission-mode", "plan", "--tools", "Read,Glob,Grep", "--no-subagents", "--disable-web-search"]),
|
|
@@ -537,6 +538,7 @@ export function oneShotReadOnlyArgs(harness: string): readonly string[] | undefi
|
|
|
537
538
|
*/
|
|
538
539
|
const WRITER_ONESHOT_ARGS = Object.freeze({
|
|
539
540
|
pi: Object.freeze(["-a", "--no-extensions", "--no-skills", "--no-prompt-templates", "--no-session"]),
|
|
541
|
+
omp: Object.freeze(["-a", "--no-extensions", "--no-skills", "--no-prompt-templates", "--no-session"]),
|
|
540
542
|
grok: Object.freeze(["--always-approve", "--no-subagents", "--disable-web-search"]),
|
|
541
543
|
});
|
|
542
544
|
|
|
@@ -568,6 +570,26 @@ export const BUILTIN_HARNESSES: readonly HarnessCatalogEntry[] = Object.freeze([
|
|
|
568
570
|
usageParser: parsePiOneShotUsage,
|
|
569
571
|
},
|
|
570
572
|
},
|
|
573
|
+
{
|
|
574
|
+
id: "omp",
|
|
575
|
+
label: "Oh My Pi",
|
|
576
|
+
default: false,
|
|
577
|
+
mode: "headless",
|
|
578
|
+
commands: ["omp"],
|
|
579
|
+
versionArgs: ["--version"],
|
|
580
|
+
authArgs: ["models", "--json"],
|
|
581
|
+
update: {
|
|
582
|
+
self: ["update"],
|
|
583
|
+
extensions: ["plugin", "upgrade"],
|
|
584
|
+
models: ["models", "refresh"],
|
|
585
|
+
},
|
|
586
|
+
oneShot: {
|
|
587
|
+
argv: ["-p", ...oneShotReadOnlyArgs("omp")!, "--mode", "json"],
|
|
588
|
+
promptVia: "arg",
|
|
589
|
+
outputFormat: "json",
|
|
590
|
+
usageParser: parsePiOneShotUsage,
|
|
591
|
+
},
|
|
592
|
+
},
|
|
571
593
|
{
|
|
572
594
|
id: "claude",
|
|
573
595
|
label: "Claude Code",
|
|
@@ -857,6 +879,16 @@ function interpretAuth(id: string, result: HarnessCommandResult): Pick<HarnessSt
|
|
|
857
879
|
if (commandSucceeded(result) && lines.some((line) => line === GROK_LOGIN_LINE)) return { authenticated: true, issues: [] };
|
|
858
880
|
return { authenticated: null, issues: ["auth_unparsed"] };
|
|
859
881
|
}
|
|
882
|
+
if (id === "omp") {
|
|
883
|
+
if (commandSucceeded(result)) {
|
|
884
|
+
const payload = parseJsonObject(result.stdout);
|
|
885
|
+
if (payload && Array.isArray(payload.models) && payload.models.length > 0) {
|
|
886
|
+
return { authenticated: true, issues: [] };
|
|
887
|
+
}
|
|
888
|
+
if (commandSucceeded(result)) return { authenticated: true, issues: [] };
|
|
889
|
+
}
|
|
890
|
+
return { authenticated: null, issues: ["auth_unparsed"] };
|
|
891
|
+
}
|
|
860
892
|
if (id === "agy") {
|
|
861
893
|
const text = `${result.stdout}\n${result.stderr}`;
|
|
862
894
|
if (!commandSucceeded(result) || !text.trim()) {
|
|
@@ -1118,7 +1150,7 @@ export function validateHarnessModelPair(
|
|
|
1118
1150
|
return { valid: true };
|
|
1119
1151
|
}
|
|
1120
1152
|
|
|
1121
|
-
if (harnessId === "pi") {
|
|
1153
|
+
if (harnessId === "pi" || harnessId === "omp") {
|
|
1122
1154
|
if (!provider && model?.includes("/")) {
|
|
1123
1155
|
const idx = model.indexOf("/");
|
|
1124
1156
|
provider = model.slice(0, idx).toLowerCase();
|
|
@@ -1127,17 +1159,17 @@ export function validateHarnessModelPair(
|
|
|
1127
1159
|
if (!provider) return { valid: true };
|
|
1128
1160
|
const blocked = (message: string): HarnessModelValidation => ({ valid: false, issue: "pi_native_impersonation_blocked", message });
|
|
1129
1161
|
if (PI_NATIVE_BRAKE_PROVIDERS.includes(provider)) {
|
|
1130
|
-
return blocked(
|
|
1162
|
+
return blocked(`${harnessId} must not impersonate native provider ${provider}; use the native harness`);
|
|
1131
1163
|
}
|
|
1132
1164
|
if (provider === "antigravity" && model && PI_ANTIGRAVITY_MODEL_ID.test(model)) return { valid: true };
|
|
1133
1165
|
const owned = Object.hasOwn(PI_VENDOR_OWNED_PROVIDERS, provider) ? PI_VENDOR_OWNED_PROVIDERS[provider] : undefined;
|
|
1134
1166
|
if (owned) {
|
|
1135
|
-
return blocked(
|
|
1167
|
+
return blocked(`${harnessId} provider ${provider} bills native vendor ${owned} for ${model ?? "this model"}; use the native harness`);
|
|
1136
1168
|
}
|
|
1137
1169
|
const segment = model?.includes("/") ? model.slice(0, model.indexOf("/")).toLowerCase() : undefined;
|
|
1138
1170
|
const vendor = segment && Object.hasOwn(PI_VENDOR_SEGMENT_ALIASES, segment) ? PI_VENDOR_SEGMENT_ALIASES[segment] : segment;
|
|
1139
1171
|
if (vendor && PI_NATIVE_BRAKE_PROVIDERS.includes(vendor)) {
|
|
1140
|
-
return blocked(
|
|
1172
|
+
return blocked(`${harnessId} must not bill native vendor ${vendor} through ${provider}; use the native harness`);
|
|
1141
1173
|
}
|
|
1142
1174
|
return { valid: true };
|
|
1143
1175
|
}
|
|
@@ -11,7 +11,7 @@ import { deliverInboxNotification } from "./inbox.ts";
|
|
|
11
11
|
import type { HubEvent, MessageRecord } from "./protocol.ts";
|
|
12
12
|
import { sessionTokenFixHint } from "./session-token-hint.ts";
|
|
13
13
|
|
|
14
|
-
const VERSION = "0.7.
|
|
14
|
+
const VERSION = "0.7.118";
|
|
15
15
|
const CONFIGURE_PLUGIN = "/plugin configure kxm@kxm";
|
|
16
16
|
const inbox = new Map<string, MessageRecord>();
|
|
17
17
|
const notifiedInbox = new Set<string>();
|
|
@@ -339,6 +339,13 @@ export function changeReport(request: { taskDir: string }, deps?: Record<string,
|
|
|
339
339
|
ranking: "not-ranked";
|
|
340
340
|
orchestration_usage: "unavailable";
|
|
341
341
|
};
|
|
342
|
+
export const ROUTING_EXPORT_SCHEMA: "kxm.assignment-routing-export.v1";
|
|
343
|
+
export function exportAssignmentRoutingRecords(request: { taskDir: string }, deps?: Record<string, unknown>): {
|
|
344
|
+
schema: "kxm.assignment-routing-export.v1";
|
|
345
|
+
task_id: string;
|
|
346
|
+
records: Array<Record<string, any>>;
|
|
347
|
+
skipped: Array<{ assignment_id: string | null; reason: "acceptance_record_invalid" | "no_routing_record" | "routing_record_invalid" | "manifest_invalid" }>;
|
|
348
|
+
};
|
|
342
349
|
export function writeCurrentPlan(request: {
|
|
343
350
|
taskDir: string; plan: string; sha256: string; baseCommit: string; expectedGeneration: number; settledDecisions?: string[];
|
|
344
351
|
}, deps?: Record<string, unknown>): {
|
|
@@ -2330,7 +2330,7 @@ export async function observeAssignment(outputDir, deps = {}) {
|
|
|
2330
2330
|
return appended;
|
|
2331
2331
|
}
|
|
2332
2332
|
|
|
2333
|
-
const CLI_USAGE = "usage: assignment-run.mjs run --manifest <absolute-path> | observe --record-dir <absolute-path> | witness --record-dir <absolute-path> | accept --task-dir <absolute-path> --commit <commit> --record-dir <absolute-path> --critic <absolute-path> --critic <absolute-path> [--observed-pr <id>] [--observed-ci <id>] | attribute --task-dir <absolute-path> --record-dir <absolute-path> --class <orchestration|model|environment|unclassified> --explanation-file <absolute-path> | observe-cost --task-dir <absolute-path> --input <absolute-path> | plan-current --task-dir <absolute-path> --plan <absolute-path> --sha256 <hex64> --base-commit <commit> --expected-generation <n> | change-report --task-dir <absolute-path>";
|
|
2333
|
+
const CLI_USAGE = "usage: assignment-run.mjs run --manifest <absolute-path> | observe --record-dir <absolute-path> | witness --record-dir <absolute-path> | accept --task-dir <absolute-path> --commit <commit> --record-dir <absolute-path> --critic <absolute-path> --critic <absolute-path> [--observed-pr <id>] [--observed-ci <id>] | attribute --task-dir <absolute-path> --record-dir <absolute-path> --class <orchestration|model|environment|unclassified> --explanation-file <absolute-path> | observe-cost --task-dir <absolute-path> --input <absolute-path> | plan-current --task-dir <absolute-path> --plan <absolute-path> --sha256 <hex64> --base-commit <commit> --expected-generation <n> | change-report --task-dir <absolute-path> | routing-export --task-dir <absolute-path>";
|
|
2334
2334
|
|
|
2335
2335
|
const WITNESS_RECEIPT_KEYS = Object.freeze([
|
|
2336
2336
|
"schema",
|
|
@@ -4293,8 +4293,135 @@ export function writeCurrentPlan(request, deps = {}) {
|
|
|
4293
4293
|
} catch (error) { throw observationFailure(error, "plan_ref_invalid"); }
|
|
4294
4294
|
}
|
|
4295
4295
|
|
|
4296
|
+
export const ROUTING_EXPORT_SCHEMA = "kxm.assignment-routing-export.v1";
|
|
4297
|
+
|
|
4298
|
+
function contractExportable(contract) {
|
|
4299
|
+
return Boolean(contract)
|
|
4300
|
+
&& typeof contract === "object"
|
|
4301
|
+
&& !Array.isArray(contract)
|
|
4302
|
+
&& typeof contract.boundary === "string"
|
|
4303
|
+
&& Array.isArray(contract.deliverables)
|
|
4304
|
+
&& contract.witness
|
|
4305
|
+
&& typeof contract.witness === "object"
|
|
4306
|
+
&& !Array.isArray(contract.witness)
|
|
4307
|
+
&& typeof contract.witness.id === "string"
|
|
4308
|
+
&& Array.isArray(contract.deferred);
|
|
4309
|
+
}
|
|
4310
|
+
|
|
4311
|
+
export function exportAssignmentRoutingRecords(request, deps = {}) {
|
|
4312
|
+
const io = ioDeps(deps);
|
|
4313
|
+
const taskDir = observationTask(request.taskDir, io);
|
|
4314
|
+
const taskId = basename(taskDir);
|
|
4315
|
+
const skipped = [];
|
|
4316
|
+
const acceptedIds = new Set();
|
|
4317
|
+
const acceptedPath = join(taskDir, ACCEPTED_FILENAME);
|
|
4318
|
+
if (lstatOrNull(acceptedPath, io.lstatSync)) {
|
|
4319
|
+
try {
|
|
4320
|
+
const value = privateRecord(acceptedPath, io).value;
|
|
4321
|
+
const valid = value.schema === ACCEPTED_SCHEMA
|
|
4322
|
+
&& value.task_id === taskId
|
|
4323
|
+
&& typeof value.writer?.assignment_id === "string"
|
|
4324
|
+
&& Array.isArray(value.critics);
|
|
4325
|
+
if (!valid) throw new Error("acceptance invalid");
|
|
4326
|
+
acceptedIds.add(value.writer.assignment_id);
|
|
4327
|
+
for (const critic of value.critics) {
|
|
4328
|
+
if (typeof critic?.assignment_id === "string") acceptedIds.add(critic.assignment_id);
|
|
4329
|
+
}
|
|
4330
|
+
} catch {
|
|
4331
|
+
skipped.push({ assignment_id: null, reason: "acceptance_record_invalid" });
|
|
4332
|
+
}
|
|
4333
|
+
}
|
|
4334
|
+
const collected = [];
|
|
4335
|
+
const dirs = listDirectAssignmentDirs(taskDir, io).sort((left, right) => left.assignment_id < right.assignment_id ? -1 : left.assignment_id > right.assignment_id ? 1 : 0);
|
|
4336
|
+
for (const { assignment_id, path } of dirs) {
|
|
4337
|
+
const routingPath = join(path, "routing-record.json");
|
|
4338
|
+
if (!lstatOrNull(routingPath, io.lstatSync)) {
|
|
4339
|
+
skipped.push({ assignment_id, reason: "no_routing_record" });
|
|
4340
|
+
continue;
|
|
4341
|
+
}
|
|
4342
|
+
let record;
|
|
4343
|
+
try {
|
|
4344
|
+
record = parseRoutingRecord(privateRecord(routingPath, io).value);
|
|
4345
|
+
if (record.workflowRunId !== assignment_id) throw new Error("workflowRunId");
|
|
4346
|
+
} catch {
|
|
4347
|
+
skipped.push({ assignment_id, reason: "routing_record_invalid" });
|
|
4348
|
+
continue;
|
|
4349
|
+
}
|
|
4350
|
+
let contract;
|
|
4351
|
+
try {
|
|
4352
|
+
const manifest = privateRecord(join(path, "manifest.json"), io).value;
|
|
4353
|
+
contract = manifest.contract;
|
|
4354
|
+
if (!contractExportable(contract)) throw new Error("contract");
|
|
4355
|
+
} catch {
|
|
4356
|
+
skipped.push({ assignment_id, reason: "manifest_invalid" });
|
|
4357
|
+
continue;
|
|
4358
|
+
}
|
|
4359
|
+
collected.push({ id: assignment_id, record, contract });
|
|
4360
|
+
}
|
|
4361
|
+
const supersededBy = new Map();
|
|
4362
|
+
for (const entry of collected) {
|
|
4363
|
+
const reworkOf = entry.record.providerMetadata?.rework_of;
|
|
4364
|
+
if (typeof reworkOf === "string" && !supersededBy.has(reworkOf)) supersededBy.set(reworkOf, entry.id);
|
|
4365
|
+
}
|
|
4366
|
+
const records = [];
|
|
4367
|
+
for (const entry of collected) {
|
|
4368
|
+
const kind = entry.record.stageId;
|
|
4369
|
+
const role = entry.record.agentRole;
|
|
4370
|
+
const askSha256 = sha256Bytes(Buffer.from(JSON.stringify(["kxm.assignment-ask.v1", kind, role])));
|
|
4371
|
+
const objectiveSha256 = sha256Bytes(Buffer.from(JSON.stringify([
|
|
4372
|
+
"kxm.assignment-brief.v1",
|
|
4373
|
+
kind,
|
|
4374
|
+
entry.contract.boundary,
|
|
4375
|
+
entry.contract.deliverables,
|
|
4376
|
+
entry.contract.witness.id,
|
|
4377
|
+
entry.contract.deferred,
|
|
4378
|
+
])));
|
|
4379
|
+
const finalOutcome = acceptedIds.has(entry.id) ? "accepted" : supersededBy.has(entry.id) ? "failed" : "pending";
|
|
4380
|
+
try {
|
|
4381
|
+
records.push(parseRoutingRecord({
|
|
4382
|
+
...entry.record,
|
|
4383
|
+
workflowRunId: taskId,
|
|
4384
|
+
finalOutcome,
|
|
4385
|
+
providerMetadata: {
|
|
4386
|
+
...entry.record.providerMetadata,
|
|
4387
|
+
workflowId: "assignment-runner",
|
|
4388
|
+
askSha256,
|
|
4389
|
+
objectiveSha256,
|
|
4390
|
+
stepWrites: WRITER_KINDS.includes(kind),
|
|
4391
|
+
assignmentId: entry.id,
|
|
4392
|
+
...(finalOutcome === "failed" ? { supersededBy: supersededBy.get(entry.id) } : {}),
|
|
4393
|
+
},
|
|
4394
|
+
}));
|
|
4395
|
+
} catch {
|
|
4396
|
+
skipped.push({ assignment_id: entry.id, reason: "routing_record_invalid" });
|
|
4397
|
+
}
|
|
4398
|
+
}
|
|
4399
|
+
return { schema: ROUTING_EXPORT_SCHEMA, task_id: taskId, records, skipped };
|
|
4400
|
+
}
|
|
4401
|
+
|
|
4296
4402
|
export async function main(argv = process.argv, io = { stdin: process.stdin, stdout: process.stdout, stderr: process.stderr }) {
|
|
4297
4403
|
const args = argv.slice(2).filter((arg) => arg !== "--");
|
|
4404
|
+
if (args[0] === "routing-export") {
|
|
4405
|
+
try {
|
|
4406
|
+
const values = {};
|
|
4407
|
+
for (let i = 1; i < args.length; i += 2) {
|
|
4408
|
+
if (args[i] !== "--task-dir" || values[args[i]] !== undefined || args[i + 1] === undefined) throw failClosed("invalid report/plan arguments", "manifest_invalid");
|
|
4409
|
+
values[args[i]] = args[i + 1];
|
|
4410
|
+
}
|
|
4411
|
+
if (values["--task-dir"] === undefined) throw failClosed("missing report/plan arguments", "manifest_invalid");
|
|
4412
|
+
const result = exportAssignmentRoutingRecords({ taskDir: values["--task-dir"] });
|
|
4413
|
+
for (const record of result.records) io.stdout.write(`${JSON.stringify(record)}\n`);
|
|
4414
|
+
io.stderr.write(`${JSON.stringify({ schema: result.schema, task_id: result.task_id, records: result.records.length, skipped: result.skipped })}\n`);
|
|
4415
|
+
process.exitCode = 0;
|
|
4416
|
+
return result;
|
|
4417
|
+
} catch (error) {
|
|
4418
|
+
const failure = observationFailure(error);
|
|
4419
|
+
io.stderr.write(`${failure.runnerCode}: assignment routing export refused\n`);
|
|
4420
|
+
process.exitCode = 1;
|
|
4421
|
+
throw failure;
|
|
4422
|
+
}
|
|
4423
|
+
}
|
|
4424
|
+
|
|
4298
4425
|
if (["change-report", "plan-current"].includes(args[0])) {
|
|
4299
4426
|
try {
|
|
4300
4427
|
const flags = args[0] === "change-report" ? ["--task-dir"] : ["--task-dir", "--plan", "--sha256", "--base-commit", "--expected-generation"];
|