@kontextmind/kxm 0.7.64 → 0.7.66

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.64",
14
+ "version": "0.7.66",
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.64",
3
+ "version": "0.7.66",
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.64",
5
+ "version": "0.7.66",
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.64";
17124
+ var VERSION = "0.7.66";
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
  {
@@ -26475,6 +26516,9 @@ function createKxmOneShotProducer(options = {}) {
26475
26516
  }
26476
26517
  function resolveModelForRequest(request, harness) {
26477
26518
  if (request.model) {
26519
+ if (request.provider) {
26520
+ return { provider: request.provider.toLowerCase(), model: request.model, thinking: request.thinking };
26521
+ }
26478
26522
  const parsed2 = parseModelString(request.model, harness);
26479
26523
  return {
26480
26524
  provider: request.provider?.toLowerCase() ?? parsed2.provider,
@@ -26485,6 +26529,9 @@ function createKxmOneShotProducer(options = {}) {
26485
26529
  if (options.resolveModel) {
26486
26530
  const resolved = options.resolveModel(request.agentId, request.runId);
26487
26531
  if (resolved && resolved.model) {
26532
+ if (resolved.provider) {
26533
+ return { provider: resolved.provider.toLowerCase(), model: resolved.model, thinking: resolved.thinking };
26534
+ }
26488
26535
  const parsed2 = parseModelString(resolved.model, harness);
26489
26536
  return {
26490
26537
  provider: resolved.provider?.toLowerCase() ?? parsed2.provider,
@@ -26594,6 +26641,14 @@ function createKxmOneShotProducer(options = {}) {
26594
26641
  "stream-json",
26595
26642
  "-p"
26596
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
+ ];
26597
26652
  } else {
26598
26653
  throw new Error(`oneshot_harness_unsupported: ${harness} permission_profile_unaudited`);
26599
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
  {
@@ -26987,6 +27028,9 @@ function createKxmOneShotProducer(options = {}) {
26987
27028
  }
26988
27029
  function resolveModelForRequest(request, harness) {
26989
27030
  if (request.model) {
27031
+ if (request.provider) {
27032
+ return { provider: request.provider.toLowerCase(), model: request.model, thinking: request.thinking };
27033
+ }
26990
27034
  const parsed2 = parseModelString(request.model, harness);
26991
27035
  return {
26992
27036
  provider: request.provider?.toLowerCase() ?? parsed2.provider,
@@ -26997,6 +27041,9 @@ function createKxmOneShotProducer(options = {}) {
26997
27041
  if (options.resolveModel) {
26998
27042
  const resolved = options.resolveModel(request.agentId, request.runId);
26999
27043
  if (resolved && resolved.model) {
27044
+ if (resolved.provider) {
27045
+ return { provider: resolved.provider.toLowerCase(), model: resolved.model, thinking: resolved.thinking };
27046
+ }
27000
27047
  const parsed2 = parseModelString(resolved.model, harness);
27001
27048
  return {
27002
27049
  provider: resolved.provider?.toLowerCase() ?? parsed2.provider,
@@ -27106,6 +27153,14 @@ function createKxmOneShotProducer(options = {}) {
27106
27153
  "stream-json",
27107
27154
  "-p"
27108
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
+ ];
27109
27164
  } else {
27110
27165
  throw new Error(`oneshot_harness_unsupported: ${harness} permission_profile_unaudited`);
27111
27166
  }
@@ -30371,6 +30426,7 @@ export {
30371
30426
  parseGenericOneShotUsage,
30372
30427
  parseGrokOneShotUsage,
30373
30428
  parseKimiOneShotUsage,
30429
+ parsePiOneShotUsage,
30374
30430
  parseSshConfig,
30375
30431
  persistKxmRunState,
30376
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.64",
3
+ "version": "0.7.66",
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
  {
@@ -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.64";
11
+ const VERSION = "0.7.66";
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
  }