@kontextmind/kxm 0.7.63 → 0.7.65

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.
@@ -11,7 +11,7 @@
11
11
  "name": "kxm",
12
12
  "source": "./plugins/kxm",
13
13
  "description": "Durable workflows, peer agents, and kxm tui",
14
- "version": "0.7.63",
14
+ "version": "0.7.65",
15
15
  "category": "development",
16
16
  "tags": ["kxm", "multi-agent", "workflows", "mcp"]
17
17
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kontextmind/kxm",
3
- "version": "0.7.63",
3
+ "version": "0.7.65",
4
4
  "description": "KXM local-first multi-agent orchestration and operator dashboard",
5
5
  "type": "module",
6
6
  "author": "KontextMind",
@@ -2,7 +2,7 @@
2
2
  "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
3
3
  "name": "kxm",
4
4
  "displayName": "KXM",
5
- "version": "0.7.63",
5
+ "version": "0.7.65",
6
6
  "description": "Headless multi-agent orchestration, durable workflows, and a live operator dashboard for Pi and Claude Code",
7
7
  "author": {
8
8
  "name": "KontextMind",
@@ -21233,6 +21233,42 @@ function parseCodexOneShotUsage(stdout, stderr) {
21233
21233
  }
21234
21234
  };
21235
21235
  }
21236
+ function parsePiOneShotUsage(stdout, _stderr) {
21237
+ let finalMessage;
21238
+ for (const line of stdout.split("\n")) {
21239
+ const trimmed = line.trim();
21240
+ if (!trimmed.startsWith("{")) continue;
21241
+ try {
21242
+ const event = JSON.parse(trimmed);
21243
+ if (event.type !== "message_end" && event.type !== "turn_end") continue;
21244
+ const message = event.message;
21245
+ if (!message || message.role !== "assistant") continue;
21246
+ finalMessage = message;
21247
+ } catch {
21248
+ }
21249
+ }
21250
+ if (!finalMessage) {
21251
+ return { text: "", isError: true, usage: {} };
21252
+ }
21253
+ const content = Array.isArray(finalMessage.content) ? finalMessage.content : [];
21254
+ const text = content.map((part) => part && typeof part === "object" && part.type === "text" ? String(part.text ?? "") : "").filter((part) => part.length > 0).join("\n");
21255
+ const effectiveModel = reportedModelId(finalMessage.model);
21256
+ const rawUsage = finalMessage.usage;
21257
+ const usage = rawUsage && typeof rawUsage === "object" ? {
21258
+ tokensIn: typeof rawUsage.input === "number" ? rawUsage.input : null,
21259
+ tokensOut: typeof rawUsage.output === "number" ? rawUsage.output : null,
21260
+ cacheReadTokens: typeof rawUsage.cacheRead === "number" ? rawUsage.cacheRead : null,
21261
+ cacheWriteTokens: typeof rawUsage.cacheWrite === "number" ? rawUsage.cacheWrite : null,
21262
+ contextTokens: null,
21263
+ costUsd: null
21264
+ } : {};
21265
+ const stopReason = typeof finalMessage.stopReason === "string" ? finalMessage.stopReason : void 0;
21266
+ const terminalError = stopReason === "error" || stopReason === "aborted";
21267
+ if (terminalError || text.length === 0) {
21268
+ return { text, isError: true, ...effectiveModel !== void 0 ? { effectiveModel } : {}, usage };
21269
+ }
21270
+ return { text, ...effectiveModel !== void 0 ? { effectiveModel } : {}, usage };
21271
+ }
21236
21272
  function parseGenericOneShotUsage(stdout, _stderr) {
21237
21273
  const trimmed = stdout.trim();
21238
21274
  try {
@@ -21334,6 +21370,11 @@ function parseAgyOneShotUsage(stdout, _stderr) {
21334
21370
  };
21335
21371
  }
21336
21372
  var READ_ONLY_ONESHOT_ARGS = Object.freeze({
21373
+ // pi: total containment for a one-shot witness — no tools (built-in, extension and
21374
+ // custom), no ambient extension/hook discovery, no skills, no prompt templates, no
21375
+ // AGENTS.md/CLAUDE.md context discovery, and an ephemeral session. `--no-tools` alone
21376
+ // is not enough: extensions and hooks can still run with their own permissions.
21377
+ pi: Object.freeze(["--no-tools", "--no-extensions", "--no-skills", "--no-prompt-templates", "--no-context-files", "--no-session"]),
21337
21378
  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"]),
21338
21379
  codex: Object.freeze(["--sandbox", "read-only", "--ignore-user-config", "-c", 'approval_policy="never"']),
21339
21380
  grok: Object.freeze(["--sandbox", "read-only", "--permission-mode", "plan", "--tools", "Read,Glob,Grep", "--no-subagents", "--disable-web-search"]),
@@ -21360,7 +21401,7 @@ var BUILTIN_HARNESSES = Object.freeze([
21360
21401
  argv: ["-p", "--mode", "json"],
21361
21402
  promptVia: "arg",
21362
21403
  outputFormat: "json",
21363
- usageParser: parseGenericOneShotUsage
21404
+ usageParser: parsePiOneShotUsage
21364
21405
  }
21365
21406
  },
21366
21407
  {
@@ -17121,7 +17121,7 @@ async function deliverInboxNotification(messageId, delivered, notify) {
17121
17121
  }
17122
17122
 
17123
17123
  // plugins/kxm/src/mcp-server.ts
17124
- var VERSION = "0.7.63";
17124
+ var VERSION = "0.7.65";
17125
17125
  var inbox = /* @__PURE__ */ new Map();
17126
17126
  var notifiedInbox = /* @__PURE__ */ new Set();
17127
17127
  var meshClient;
@@ -15431,6 +15431,42 @@ function parseCodexOneShotUsage(stdout, stderr) {
15431
15431
  }
15432
15432
  };
15433
15433
  }
15434
+ function parsePiOneShotUsage(stdout, _stderr) {
15435
+ let finalMessage;
15436
+ for (const line of stdout.split("\n")) {
15437
+ const trimmed = line.trim();
15438
+ if (!trimmed.startsWith("{")) continue;
15439
+ try {
15440
+ const event = JSON.parse(trimmed);
15441
+ if (event.type !== "message_end" && event.type !== "turn_end") continue;
15442
+ const message = event.message;
15443
+ if (!message || message.role !== "assistant") continue;
15444
+ finalMessage = message;
15445
+ } catch {
15446
+ }
15447
+ }
15448
+ if (!finalMessage) {
15449
+ return { text: "", isError: true, usage: {} };
15450
+ }
15451
+ const content = Array.isArray(finalMessage.content) ? finalMessage.content : [];
15452
+ const text = content.map((part) => part && typeof part === "object" && part.type === "text" ? String(part.text ?? "") : "").filter((part) => part.length > 0).join("\n");
15453
+ const effectiveModel = reportedModelId(finalMessage.model);
15454
+ const rawUsage = finalMessage.usage;
15455
+ const usage = rawUsage && typeof rawUsage === "object" ? {
15456
+ tokensIn: typeof rawUsage.input === "number" ? rawUsage.input : null,
15457
+ tokensOut: typeof rawUsage.output === "number" ? rawUsage.output : null,
15458
+ cacheReadTokens: typeof rawUsage.cacheRead === "number" ? rawUsage.cacheRead : null,
15459
+ cacheWriteTokens: typeof rawUsage.cacheWrite === "number" ? rawUsage.cacheWrite : null,
15460
+ contextTokens: null,
15461
+ costUsd: null
15462
+ } : {};
15463
+ const stopReason = typeof finalMessage.stopReason === "string" ? finalMessage.stopReason : void 0;
15464
+ const terminalError = stopReason === "error" || stopReason === "aborted";
15465
+ if (terminalError || text.length === 0) {
15466
+ return { text, isError: true, ...effectiveModel !== void 0 ? { effectiveModel } : {}, usage };
15467
+ }
15468
+ return { text, ...effectiveModel !== void 0 ? { effectiveModel } : {}, usage };
15469
+ }
15434
15470
  function parseGenericOneShotUsage(stdout, _stderr) {
15435
15471
  const trimmed = stdout.trim();
15436
15472
  try {
@@ -15532,6 +15568,11 @@ function parseAgyOneShotUsage(stdout, _stderr) {
15532
15568
  };
15533
15569
  }
15534
15570
  var READ_ONLY_ONESHOT_ARGS = Object.freeze({
15571
+ // pi: total containment for a one-shot witness — no tools (built-in, extension and
15572
+ // custom), no ambient extension/hook discovery, no skills, no prompt templates, no
15573
+ // AGENTS.md/CLAUDE.md context discovery, and an ephemeral session. `--no-tools` alone
15574
+ // is not enough: extensions and hooks can still run with their own permissions.
15575
+ pi: Object.freeze(["--no-tools", "--no-extensions", "--no-skills", "--no-prompt-templates", "--no-context-files", "--no-session"]),
15535
15576
  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"]),
15536
15577
  codex: Object.freeze(["--sandbox", "read-only", "--ignore-user-config", "-c", 'approval_policy="never"']),
15537
15578
  grok: Object.freeze(["--sandbox", "read-only", "--permission-mode", "plan", "--tools", "Read,Glob,Grep", "--no-subagents", "--disable-web-search"]),
@@ -15558,7 +15599,7 @@ var BUILTIN_HARNESSES = Object.freeze([
15558
15599
  argv: ["-p", "--mode", "json"],
15559
15600
  promptVia: "arg",
15560
15601
  outputFormat: "json",
15561
- usageParser: parseGenericOneShotUsage
15602
+ usageParser: parsePiOneShotUsage
15562
15603
  }
15563
15604
  },
15564
15605
  {
@@ -16102,8 +16143,9 @@ function probeHarnessAssignment(options) {
16102
16143
  };
16103
16144
  }
16104
16145
  const authArgs = ["auth", "check"];
16105
- if (options.model) {
16106
- authArgs.push("--model", options.model);
16146
+ const qualifiedModel = options.model ? options.provider && options.model.startsWith(`${options.provider}/`) ? options.model : options.provider ? `${options.provider}/${options.model}` : options.model : void 0;
16147
+ if (qualifiedModel) {
16148
+ authArgs.push("--model", qualifiedModel);
16107
16149
  } else if (options.provider) {
16108
16150
  authArgs.push("--provider", options.provider);
16109
16151
  }
@@ -26474,6 +26516,9 @@ function createKxmOneShotProducer(options = {}) {
26474
26516
  }
26475
26517
  function resolveModelForRequest(request, harness) {
26476
26518
  if (request.model) {
26519
+ if (request.provider) {
26520
+ return { provider: request.provider.toLowerCase(), model: request.model, thinking: request.thinking };
26521
+ }
26477
26522
  const parsed2 = parseModelString(request.model, harness);
26478
26523
  return {
26479
26524
  provider: request.provider?.toLowerCase() ?? parsed2.provider,
@@ -26484,6 +26529,9 @@ function createKxmOneShotProducer(options = {}) {
26484
26529
  if (options.resolveModel) {
26485
26530
  const resolved = options.resolveModel(request.agentId, request.runId);
26486
26531
  if (resolved && resolved.model) {
26532
+ if (resolved.provider) {
26533
+ return { provider: resolved.provider.toLowerCase(), model: resolved.model, thinking: resolved.thinking };
26534
+ }
26487
26535
  const parsed2 = parseModelString(resolved.model, harness);
26488
26536
  return {
26489
26537
  provider: resolved.provider?.toLowerCase() ?? parsed2.provider,
@@ -26593,6 +26641,14 @@ function createKxmOneShotProducer(options = {}) {
26593
26641
  "stream-json",
26594
26642
  "-p"
26595
26643
  ];
26644
+ } else if (harness === "pi") {
26645
+ const qualified = resolved.provider && resolved.model.startsWith(`${resolved.provider}/`) ? resolved.model : resolved.provider ? `${resolved.provider}/${resolved.model}` : resolved.model;
26646
+ args = [
26647
+ "--model",
26648
+ qualified,
26649
+ ...permissionArgs,
26650
+ ...oneShot.argv
26651
+ ];
26596
26652
  } else {
26597
26653
  throw new Error(`oneshot_harness_unsupported: ${harness} permission_profile_unaudited`);
26598
26654
  }
@@ -15428,6 +15428,42 @@ function parseCodexOneShotUsage(stdout, stderr) {
15428
15428
  }
15429
15429
  };
15430
15430
  }
15431
+ function parsePiOneShotUsage(stdout, _stderr) {
15432
+ let finalMessage;
15433
+ for (const line of stdout.split("\n")) {
15434
+ const trimmed = line.trim();
15435
+ if (!trimmed.startsWith("{")) continue;
15436
+ try {
15437
+ const event = JSON.parse(trimmed);
15438
+ if (event.type !== "message_end" && event.type !== "turn_end") continue;
15439
+ const message = event.message;
15440
+ if (!message || message.role !== "assistant") continue;
15441
+ finalMessage = message;
15442
+ } catch {
15443
+ }
15444
+ }
15445
+ if (!finalMessage) {
15446
+ return { text: "", isError: true, usage: {} };
15447
+ }
15448
+ const content = Array.isArray(finalMessage.content) ? finalMessage.content : [];
15449
+ const text = content.map((part) => part && typeof part === "object" && part.type === "text" ? String(part.text ?? "") : "").filter((part) => part.length > 0).join("\n");
15450
+ const effectiveModel = reportedModelId(finalMessage.model);
15451
+ const rawUsage = finalMessage.usage;
15452
+ const usage = rawUsage && typeof rawUsage === "object" ? {
15453
+ tokensIn: typeof rawUsage.input === "number" ? rawUsage.input : null,
15454
+ tokensOut: typeof rawUsage.output === "number" ? rawUsage.output : null,
15455
+ cacheReadTokens: typeof rawUsage.cacheRead === "number" ? rawUsage.cacheRead : null,
15456
+ cacheWriteTokens: typeof rawUsage.cacheWrite === "number" ? rawUsage.cacheWrite : null,
15457
+ contextTokens: null,
15458
+ costUsd: null
15459
+ } : {};
15460
+ const stopReason = typeof finalMessage.stopReason === "string" ? finalMessage.stopReason : void 0;
15461
+ const terminalError = stopReason === "error" || stopReason === "aborted";
15462
+ if (terminalError || text.length === 0) {
15463
+ return { text, isError: true, ...effectiveModel !== void 0 ? { effectiveModel } : {}, usage };
15464
+ }
15465
+ return { text, ...effectiveModel !== void 0 ? { effectiveModel } : {}, usage };
15466
+ }
15431
15467
  function parseGenericOneShotUsage(stdout, _stderr) {
15432
15468
  const trimmed = stdout.trim();
15433
15469
  try {
@@ -15529,6 +15565,11 @@ function parseAgyOneShotUsage(stdout, _stderr) {
15529
15565
  };
15530
15566
  }
15531
15567
  var READ_ONLY_ONESHOT_ARGS = Object.freeze({
15568
+ // pi: total containment for a one-shot witness — no tools (built-in, extension and
15569
+ // custom), no ambient extension/hook discovery, no skills, no prompt templates, no
15570
+ // AGENTS.md/CLAUDE.md context discovery, and an ephemeral session. `--no-tools` alone
15571
+ // is not enough: extensions and hooks can still run with their own permissions.
15572
+ pi: Object.freeze(["--no-tools", "--no-extensions", "--no-skills", "--no-prompt-templates", "--no-context-files", "--no-session"]),
15532
15573
  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"]),
15533
15574
  codex: Object.freeze(["--sandbox", "read-only", "--ignore-user-config", "-c", 'approval_policy="never"']),
15534
15575
  grok: Object.freeze(["--sandbox", "read-only", "--permission-mode", "plan", "--tools", "Read,Glob,Grep", "--no-subagents", "--disable-web-search"]),
@@ -15555,7 +15596,7 @@ var BUILTIN_HARNESSES = Object.freeze([
15555
15596
  argv: ["-p", "--mode", "json"],
15556
15597
  promptVia: "arg",
15557
15598
  outputFormat: "json",
15558
- usageParser: parseGenericOneShotUsage
15599
+ usageParser: parsePiOneShotUsage
15559
15600
  }
15560
15601
  },
15561
15602
  {
@@ -16125,8 +16166,9 @@ function probeHarnessAssignment(options) {
16125
16166
  };
16126
16167
  }
16127
16168
  const authArgs = ["auth", "check"];
16128
- if (options.model) {
16129
- authArgs.push("--model", options.model);
16169
+ const qualifiedModel = options.model ? options.provider && options.model.startsWith(`${options.provider}/`) ? options.model : options.provider ? `${options.provider}/${options.model}` : options.model : void 0;
16170
+ if (qualifiedModel) {
16171
+ authArgs.push("--model", qualifiedModel);
16130
16172
  } else if (options.provider) {
16131
16173
  authArgs.push("--provider", options.provider);
16132
16174
  }
@@ -26986,6 +27028,9 @@ function createKxmOneShotProducer(options = {}) {
26986
27028
  }
26987
27029
  function resolveModelForRequest(request, harness) {
26988
27030
  if (request.model) {
27031
+ if (request.provider) {
27032
+ return { provider: request.provider.toLowerCase(), model: request.model, thinking: request.thinking };
27033
+ }
26989
27034
  const parsed2 = parseModelString(request.model, harness);
26990
27035
  return {
26991
27036
  provider: request.provider?.toLowerCase() ?? parsed2.provider,
@@ -26996,6 +27041,9 @@ function createKxmOneShotProducer(options = {}) {
26996
27041
  if (options.resolveModel) {
26997
27042
  const resolved = options.resolveModel(request.agentId, request.runId);
26998
27043
  if (resolved && resolved.model) {
27044
+ if (resolved.provider) {
27045
+ return { provider: resolved.provider.toLowerCase(), model: resolved.model, thinking: resolved.thinking };
27046
+ }
26999
27047
  const parsed2 = parseModelString(resolved.model, harness);
27000
27048
  return {
27001
27049
  provider: resolved.provider?.toLowerCase() ?? parsed2.provider,
@@ -27105,6 +27153,14 @@ function createKxmOneShotProducer(options = {}) {
27105
27153
  "stream-json",
27106
27154
  "-p"
27107
27155
  ];
27156
+ } else if (harness === "pi") {
27157
+ const qualified = resolved.provider && resolved.model.startsWith(`${resolved.provider}/`) ? resolved.model : resolved.provider ? `${resolved.provider}/${resolved.model}` : resolved.model;
27158
+ args = [
27159
+ "--model",
27160
+ qualified,
27161
+ ...permissionArgs,
27162
+ ...oneShot.argv
27163
+ ];
27108
27164
  } else {
27109
27165
  throw new Error(`oneshot_harness_unsupported: ${harness} permission_profile_unaudited`);
27110
27166
  }
@@ -30370,6 +30426,7 @@ export {
30370
30426
  parseGenericOneShotUsage,
30371
30427
  parseGrokOneShotUsage,
30372
30428
  parseKimiOneShotUsage,
30429
+ parsePiOneShotUsage,
30373
30430
  parseSshConfig,
30374
30431
  persistKxmRunState,
30375
30432
  planHarnessUpdate,
@@ -11136,6 +11136,42 @@ function parseCodexOneShotUsage(stdout, stderr) {
11136
11136
  }
11137
11137
  };
11138
11138
  }
11139
+ function parsePiOneShotUsage(stdout, _stderr) {
11140
+ let finalMessage;
11141
+ for (const line of stdout.split("\n")) {
11142
+ const trimmed = line.trim();
11143
+ if (!trimmed.startsWith("{")) continue;
11144
+ try {
11145
+ const event = JSON.parse(trimmed);
11146
+ if (event.type !== "message_end" && event.type !== "turn_end") continue;
11147
+ const message = event.message;
11148
+ if (!message || message.role !== "assistant") continue;
11149
+ finalMessage = message;
11150
+ } catch {
11151
+ }
11152
+ }
11153
+ if (!finalMessage) {
11154
+ return { text: "", isError: true, usage: {} };
11155
+ }
11156
+ const content = Array.isArray(finalMessage.content) ? finalMessage.content : [];
11157
+ const text2 = content.map((part) => part && typeof part === "object" && part.type === "text" ? String(part.text ?? "") : "").filter((part) => part.length > 0).join("\n");
11158
+ const effectiveModel = reportedModelId(finalMessage.model);
11159
+ const rawUsage = finalMessage.usage;
11160
+ const usage = rawUsage && typeof rawUsage === "object" ? {
11161
+ tokensIn: typeof rawUsage.input === "number" ? rawUsage.input : null,
11162
+ tokensOut: typeof rawUsage.output === "number" ? rawUsage.output : null,
11163
+ cacheReadTokens: typeof rawUsage.cacheRead === "number" ? rawUsage.cacheRead : null,
11164
+ cacheWriteTokens: typeof rawUsage.cacheWrite === "number" ? rawUsage.cacheWrite : null,
11165
+ contextTokens: null,
11166
+ costUsd: null
11167
+ } : {};
11168
+ const stopReason = typeof finalMessage.stopReason === "string" ? finalMessage.stopReason : void 0;
11169
+ const terminalError = stopReason === "error" || stopReason === "aborted";
11170
+ if (terminalError || text2.length === 0) {
11171
+ return { text: text2, isError: true, ...effectiveModel !== void 0 ? { effectiveModel } : {}, usage };
11172
+ }
11173
+ return { text: text2, ...effectiveModel !== void 0 ? { effectiveModel } : {}, usage };
11174
+ }
11139
11175
  function parseGenericOneShotUsage(stdout, _stderr) {
11140
11176
  const trimmed = stdout.trim();
11141
11177
  try {
@@ -11237,6 +11273,11 @@ function parseAgyOneShotUsage(stdout, _stderr) {
11237
11273
  };
11238
11274
  }
11239
11275
  var READ_ONLY_ONESHOT_ARGS = Object.freeze({
11276
+ // pi: total containment for a one-shot witness — no tools (built-in, extension and
11277
+ // custom), no ambient extension/hook discovery, no skills, no prompt templates, no
11278
+ // AGENTS.md/CLAUDE.md context discovery, and an ephemeral session. `--no-tools` alone
11279
+ // is not enough: extensions and hooks can still run with their own permissions.
11280
+ pi: Object.freeze(["--no-tools", "--no-extensions", "--no-skills", "--no-prompt-templates", "--no-context-files", "--no-session"]),
11240
11281
  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"]),
11241
11282
  codex: Object.freeze(["--sandbox", "read-only", "--ignore-user-config", "-c", 'approval_policy="never"']),
11242
11283
  grok: Object.freeze(["--sandbox", "read-only", "--permission-mode", "plan", "--tools", "Read,Glob,Grep", "--no-subagents", "--disable-web-search"]),
@@ -11263,7 +11304,7 @@ var BUILTIN_HARNESSES = Object.freeze([
11263
11304
  argv: ["-p", "--mode", "json"],
11264
11305
  promptVia: "arg",
11265
11306
  outputFormat: "json",
11266
- usageParser: parseGenericOneShotUsage
11307
+ usageParser: parsePiOneShotUsage
11267
11308
  }
11268
11309
  },
11269
11310
  {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-plugin",
3
- "version": "0.7.63",
3
+ "version": "0.7.65",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "engines": {
@@ -297,6 +297,67 @@ export function parseCodexOneShotUsage(stdout: string, stderr: string): OneShotP
297
297
  };
298
298
  }
299
299
 
300
+ /**
301
+ * Parse pi's `--mode json` output: one NDJSON event object per line. The reply text is
302
+ * the text parts of the final assistant message (`message_end`/`turn_end` carry it
303
+ * complete, after the deltas); usage and the effective model ride the same message.
304
+ * Anything unparseable falls back to the raw stream so a shape change is visible in the
305
+ * recorded text rather than silently empty.
306
+ */
307
+ export function parsePiOneShotUsage(stdout: string, _stderr: string): OneShotParsedOutput {
308
+ // Select the FINAL assistant message first, then extract every field from that one
309
+ // message atomically. Carrying text, usage or model forward from an earlier message
310
+ // let a truncated or empty final turn inherit a previous turn's PASS text and usage.
311
+ let finalMessage: Record<string, unknown> | undefined;
312
+ for (const line of stdout.split("\n")) {
313
+ const trimmed = line.trim();
314
+ if (!trimmed.startsWith("{")) continue;
315
+ try {
316
+ const event = JSON.parse(trimmed) as Record<string, unknown>;
317
+ if (event.type !== "message_end" && event.type !== "turn_end") continue;
318
+ const message = event.message as Record<string, unknown> | undefined;
319
+ if (!message || message.role !== "assistant") continue;
320
+ finalMessage = message;
321
+ } catch {
322
+ // skip malformed lines; the stream is append-only and a partial line means truncation
323
+ }
324
+ }
325
+ if (!finalMessage) {
326
+ // No assistant message at all: fail closed with empty text. Returning the raw stream
327
+ // would hand back thinking deltas or diagnostics as if they were the answer.
328
+ return { text: "", isError: true, usage: {} };
329
+ }
330
+ const content = Array.isArray(finalMessage.content) ? finalMessage.content : [];
331
+ const text = content
332
+ .map((part) => (part && typeof part === "object" && (part as Record<string, unknown>).type === "text"
333
+ ? String((part as Record<string, unknown>).text ?? "")
334
+ : ""))
335
+ .filter((part) => part.length > 0)
336
+ .join("\n");
337
+ // pi can exit zero on a failed turn; the stop reason is the fact that matters.
338
+ // Metadata comes from the selected final message even on a failed turn: usage and
339
+ // model are facts about what ran, not claims about the outcome, and dropping them
340
+ // turned a real 7/3-token failure into null/null with model "unknown".
341
+ const effectiveModel = reportedModelId(finalMessage.model);
342
+ const rawUsage = finalMessage.usage as Record<string, unknown> | undefined;
343
+ const usage: OneShotUsage = rawUsage && typeof rawUsage === "object"
344
+ ? {
345
+ tokensIn: typeof rawUsage.input === "number" ? rawUsage.input : null,
346
+ tokensOut: typeof rawUsage.output === "number" ? rawUsage.output : null,
347
+ cacheReadTokens: typeof rawUsage.cacheRead === "number" ? rawUsage.cacheRead : null,
348
+ cacheWriteTokens: typeof rawUsage.cacheWrite === "number" ? rawUsage.cacheWrite : null,
349
+ contextTokens: null,
350
+ costUsd: null,
351
+ }
352
+ : {};
353
+ const stopReason = typeof finalMessage.stopReason === "string" ? finalMessage.stopReason : undefined;
354
+ const terminalError = stopReason === "error" || stopReason === "aborted";
355
+ if (terminalError || text.length === 0) {
356
+ return { text, isError: true, ...(effectiveModel !== undefined ? { effectiveModel } : {}), usage };
357
+ }
358
+ return { text, ...(effectiveModel !== undefined ? { effectiveModel } : {}), usage };
359
+ }
360
+
300
361
  export function parseGenericOneShotUsage(stdout: string, _stderr: string): OneShotParsedOutput {
301
362
  const trimmed = stdout.trim();
302
363
  try {
@@ -419,6 +480,11 @@ export function parseAgyOneShotUsage(stdout: string, _stderr: string): OneShotPa
419
480
 
420
481
  /** Built-in harnesses. Unknown ids fail closed. Model lists live in `.kxm/models/*.yaml`, not here. */
421
482
  const READ_ONLY_ONESHOT_ARGS = Object.freeze({
483
+ // pi: total containment for a one-shot witness — no tools (built-in, extension and
484
+ // custom), no ambient extension/hook discovery, no skills, no prompt templates, no
485
+ // AGENTS.md/CLAUDE.md context discovery, and an ephemeral session. `--no-tools` alone
486
+ // is not enough: extensions and hooks can still run with their own permissions.
487
+ pi: Object.freeze(["--no-tools", "--no-extensions", "--no-skills", "--no-prompt-templates", "--no-context-files", "--no-session"]),
422
488
  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"]),
423
489
  codex: Object.freeze(["--sandbox", "read-only", "--ignore-user-config", "-c", 'approval_policy="never"']),
424
490
  grok: Object.freeze(["--sandbox", "read-only", "--permission-mode", "plan", "--tools", "Read,Glob,Grep", "--no-subagents", "--disable-web-search"]),
@@ -447,7 +513,7 @@ export const BUILTIN_HARNESSES: readonly HarnessCatalogEntry[] = Object.freeze([
447
513
  argv: ["-p", "--mode", "json"],
448
514
  promptVia: "arg",
449
515
  outputFormat: "json",
450
- usageParser: parseGenericOneShotUsage,
516
+ usageParser: parsePiOneShotUsage,
451
517
  },
452
518
  },
453
519
  {
@@ -1118,8 +1184,17 @@ export function probeHarnessAssignment(options: HarnessAssignmentProbeOptions):
1118
1184
  }
1119
1185
 
1120
1186
  const authArgs = ["auth", "check"];
1121
- if (options.model) {
1122
- authArgs.push("--model", options.model);
1187
+ // `pi auth check --model` requires a provider-qualified id: a bare model answers
1188
+ // status "invalid", which reads as not_authenticated and hides the real state. When
1189
+ // the caller holds provider and bare model separately (the engine's request shape),
1190
+ // qualify the id; a model that already carries its namespace goes through as-is.
1191
+ const qualifiedModel = options.model
1192
+ ? (options.provider && options.model.startsWith(`${options.provider}/`)
1193
+ ? options.model
1194
+ : options.provider ? `${options.provider}/${options.model}` : options.model)
1195
+ : undefined;
1196
+ if (qualifiedModel) {
1197
+ authArgs.push("--model", qualifiedModel);
1123
1198
  } else if (options.provider) {
1124
1199
  authArgs.push("--provider", options.provider);
1125
1200
  }
@@ -8,7 +8,7 @@ import { AGENT_COMMANDS_MAP, enforceToolPolicy, getMcpTools, reconcileInbox } fr
8
8
  import { deliverInboxNotification } from "./inbox.ts";
9
9
  import type { HubEvent, MessageRecord } from "./protocol.ts";
10
10
 
11
- const VERSION = "0.7.63";
11
+ const VERSION = "0.7.65";
12
12
  const inbox = new Map<string, MessageRecord>();
13
13
  const notifiedInbox = new Set<string>();
14
14
  let meshClient: HubClient | undefined;
@@ -100,6 +100,13 @@ export function createKxmOneShotProducer(options: KxmOneShotProducerOptions = {}
100
100
  harness: string,
101
101
  ): { provider: string; model: string; thinking?: string | undefined } {
102
102
  if (request.model) {
103
+ // A separate provider field means the model id is bare and may itself be
104
+ // namespaced (openrouter + qwen/qwen3-coder-plus); reparsing it as a
105
+ // provider/model selector strips the namespace — the same defect #262 fixed
106
+ // in the pi producer. Only parse when the provider must come from the string.
107
+ if (request.provider) {
108
+ return { provider: request.provider.toLowerCase(), model: request.model, thinking: request.thinking };
109
+ }
103
110
  const parsed = parseModelString(request.model, harness);
104
111
  return {
105
112
  provider: request.provider?.toLowerCase() ?? parsed.provider,
@@ -110,6 +117,9 @@ export function createKxmOneShotProducer(options: KxmOneShotProducerOptions = {}
110
117
  if (options.resolveModel) {
111
118
  const resolved = options.resolveModel(request.agentId, request.runId);
112
119
  if (resolved && resolved.model) {
120
+ if (resolved.provider) {
121
+ return { provider: resolved.provider.toLowerCase(), model: resolved.model, thinking: resolved.thinking };
122
+ }
113
123
  const parsed = parseModelString(resolved.model, harness);
114
124
  return {
115
125
  provider: resolved.provider?.toLowerCase() ?? parsed.provider,
@@ -213,6 +223,20 @@ export function createKxmOneShotProducer(options: KxmOneShotProducerOptions = {}
213
223
  "stream-json",
214
224
  "-p",
215
225
  ];
226
+ } else if (harness === "pi") {
227
+ // pi's model flag takes the provider-qualified id — exactly the auth probe's
228
+ // rule (#265): prefix with the provider unless the model already starts with it.
229
+ // A bare "contains a slash" test is wrong for provider-local namespaces
230
+ // (openrouter + qwen/qwen3-coder-plus must spawn as the full three-part id).
231
+ const qualified = resolved.provider && resolved.model.startsWith(`${resolved.provider}/`)
232
+ ? resolved.model
233
+ : resolved.provider ? `${resolved.provider}/${resolved.model}` : resolved.model;
234
+ args = [
235
+ "--model",
236
+ qualified,
237
+ ...permissionArgs,
238
+ ...oneShot.argv,
239
+ ];
216
240
  } else {
217
241
  throw new Error(`oneshot_harness_unsupported: ${harness} permission_profile_unaudited`);
218
242
  }