@kontextmind/kxm 0.6.0 → 0.7.4

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.
Files changed (136) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.kxm/agents/coordinator.yaml +9 -0
  3. package/.kxm/agents/critic-arch.yaml +13 -0
  4. package/.kxm/agents/critic-cli.yaml +13 -0
  5. package/.kxm/agents/implementer.yaml +13 -0
  6. package/.kxm/gates.yaml +8 -0
  7. package/.kxm/producers.yaml +22 -0
  8. package/.kxm/project.yaml +15 -0
  9. package/.kxm/roles/writer.yaml +7 -0
  10. package/.kxm/workflows/default.yaml +47 -0
  11. package/CHANGELOG.md +39 -7
  12. package/README.md +1 -0
  13. package/docs/README.md +4 -0
  14. package/docs/agent-skills.md +121 -0
  15. package/docs/architecture.md +1 -1
  16. package/docs/assignment-runner.md +21 -8
  17. package/docs/configuration.md +11 -2
  18. package/docs/getting-started.md +21 -0
  19. package/docs/kxm-handbook.md +3 -3
  20. package/docs/operations.md +24 -0
  21. package/docs/operator-pi-packages.md +63 -0
  22. package/docs/skills/repo-work-delivery.md +107 -0
  23. package/docs/skills.md +2 -0
  24. package/docs/test-matrix.md +4 -3
  25. package/docs/troubleshooting.md +41 -1
  26. package/docs/vnext/validation.md +9 -0
  27. package/docs/webhook-workflows.md +2 -2
  28. package/examples/README.md +1 -1
  29. package/package.json +16 -17
  30. package/plugins/kxm/.claude-plugin/plugin.json +1 -1
  31. package/plugins/kxm/README.md +1 -1
  32. package/plugins/kxm/dist/cli.js +8955 -3934
  33. package/plugins/kxm/dist/core.js +271 -34
  34. package/plugins/kxm/dist/extension.js +7759 -86
  35. package/plugins/kxm/dist/mcp-server.js +75 -21
  36. package/plugins/kxm/dist/runtime.js +5637 -1125
  37. package/plugins/kxm/dist/server.js +3125 -2260
  38. package/plugins/kxm/dist/vnext-runtime-supervisor.js +5808 -565
  39. package/plugins/kxm/package.json +1 -1
  40. package/plugins/kxm/skills/SUITE.md +5 -0
  41. package/plugins/kxm/skills/hints.json +73 -0
  42. package/plugins/kxm/skills/kxm/SKILL.md +30 -83
  43. package/plugins/kxm/skills/kxm-context-memory/SKILL.md +69 -0
  44. package/plugins/kxm/skills/kxm-definitions/SKILL.md +65 -0
  45. package/plugins/kxm/skills/kxm-harness-auth/SKILL.md +34 -0
  46. package/plugins/kxm/skills/kxm-harvest/SKILL.md +48 -0
  47. package/plugins/kxm/skills/kxm-hub-ops/SKILL.md +43 -0
  48. package/plugins/kxm/skills/kxm-insights/SKILL.md +48 -0
  49. package/plugins/kxm/skills/kxm-mind/SKILL.md +59 -0
  50. package/plugins/kxm/skills/kxm-peer/SKILL.md +110 -0
  51. package/plugins/kxm/skills/kxm-project-setup/SKILL.md +42 -0
  52. package/plugins/kxm/skills/kxm-projects/SKILL.md +43 -0
  53. package/plugins/kxm/skills/kxm-protocol/SKILL.md +66 -0
  54. package/plugins/kxm/skills/kxm-query/SKILL.md +45 -0
  55. package/plugins/kxm/skills/kxm-routing-improve/SKILL.md +30 -0
  56. package/plugins/kxm/skills/kxm-runs/SKILL.md +29 -0
  57. package/plugins/kxm/skills/kxm-setup/SKILL.md +55 -0
  58. package/plugins/kxm/skills/kxm-skill-lifecycle/SKILL.md +31 -0
  59. package/plugins/kxm/skills/kxm-tasks/SKILL.md +33 -0
  60. package/plugins/kxm/skills/kxm-triage/SKILL.md +47 -0
  61. package/plugins/kxm/skills/kxm-work/SKILL.md +44 -0
  62. package/plugins/kxm/skills/kxm-workflow/SKILL.md +45 -0
  63. package/plugins/kxm/src/autocomplete.ts +9 -3
  64. package/plugins/kxm/src/cli.ts +1637 -73
  65. package/plugins/kxm/src/commands.ts +150 -8
  66. package/plugins/kxm/src/completion-install.ts +223 -0
  67. package/plugins/kxm/src/config.ts +7 -4
  68. package/plugins/kxm/src/context-packet.ts +172 -0
  69. package/plugins/kxm/src/database.ts +1 -1
  70. package/plugins/kxm/src/extension.ts +36 -1
  71. package/plugins/kxm/src/external-effects.ts +357 -8
  72. package/plugins/kxm/src/hub-env.ts +193 -0
  73. package/plugins/kxm/src/hub.ts +2 -4
  74. package/plugins/kxm/src/improve.ts +72 -0
  75. package/plugins/kxm/src/init-guide-setup.ts +547 -0
  76. package/plugins/kxm/src/local-snapshot.ts +1 -1
  77. package/plugins/kxm/src/mcp-server.ts +1 -1
  78. package/plugins/kxm/src/model-inventory.ts +127 -0
  79. package/plugins/kxm/src/policy-draft.d.mts +55 -0
  80. package/plugins/kxm/src/policy-draft.mjs +565 -0
  81. package/plugins/kxm/src/price-calc.ts +17 -18
  82. package/plugins/kxm/src/prices.ts +32 -16
  83. package/plugins/kxm/src/producers.ts +71 -0
  84. package/plugins/kxm/src/protocol.ts +111 -0
  85. package/plugins/kxm/src/restricted-yaml.d.mts +31 -0
  86. package/plugins/kxm/src/restricted-yaml.mjs +145 -0
  87. package/plugins/kxm/src/role.ts +710 -0
  88. package/plugins/kxm/src/routing.ts +99 -1
  89. package/plugins/kxm/src/safety-integrity.ts +76 -0
  90. package/plugins/kxm/src/session-work.ts +9 -2
  91. package/plugins/kxm/src/sqlite.ts +76 -0
  92. package/plugins/kxm/src/store.ts +1 -1
  93. package/plugins/kxm/src/studio-layout.ts +660 -17
  94. package/plugins/kxm/src/suggest.ts +7 -13
  95. package/plugins/kxm/src/telemetry.ts +82 -0
  96. package/plugins/kxm/src/tui.ts +140 -0
  97. package/plugins/kxm/src/vnext-bindings.ts +1 -1
  98. package/plugins/kxm/src/vnext-config.ts +53 -111
  99. package/plugins/kxm/src/vnext-engine-command.ts +2 -0
  100. package/plugins/kxm/src/vnext-engine.ts +214 -62
  101. package/plugins/kxm/src/vnext-harness.ts +267 -83
  102. package/plugins/kxm/src/vnext-oneshot-evidence.ts +85 -0
  103. package/plugins/kxm/src/vnext-oneshot-process.ts +166 -0
  104. package/plugins/kxm/src/vnext-oneshot-producer.ts +179 -224
  105. package/plugins/kxm/src/vnext-runtime-store.ts +36 -2
  106. package/plugins/kxm/src/vnext-runtime-supervisor.ts +120 -5
  107. package/plugins/kxm/src/vnext-runtime.ts +14 -0
  108. package/plugins/kxm/src/workflow-manager.ts +392 -0
  109. package/plugins/kxm/src/workflow-tui.ts +255 -0
  110. package/plugins/kxm/src/workflow.ts +144 -0
  111. package/schemas/policy-draft/README.md +17 -0
  112. package/schemas/policy-draft/model.v2.schema.json +140 -0
  113. package/schemas/policy-draft/role.v2.schema.json +91 -0
  114. package/schemas/vnext/role.schema.json +76 -0
  115. package/schemas/vnext/run-event.schema.json +1 -0
  116. package/scripts/assignment-run.d.mts +1 -1
  117. package/scripts/assignment-run.mjs +44 -35
  118. package/scripts/check-generated.mjs +33 -9
  119. package/scripts/emit-codex-artifacts.mjs +255 -11
  120. package/scripts/harness-run.d.mts +12 -4
  121. package/scripts/harness-run.mjs +65 -17
  122. package/scripts/kxm-bump-version.mjs +146 -0
  123. package/scripts/kxm-hub.mjs +150 -2
  124. package/scripts/kxm-publish-npm.mjs +3 -1
  125. package/scripts/kxm-release-github.mjs +3 -1
  126. package/scripts/kxm.mjs +0 -0
  127. package/scripts/native-critic.d.mts +5 -0
  128. package/scripts/native-critic.mjs +60 -0
  129. package/.kxm/config/README.md +0 -5
  130. package/.kxm/config/agents.json +0 -43
  131. package/.kxm/config/env.example +0 -56
  132. package/.kxm/config/update.example.yaml +0 -9
  133. package/.kxm/config/workflows/fix.json +0 -160
  134. package/.kxm/config/workflows/jira-development.json +0 -116
  135. package/.kxm/config/workflows/provenance-quorum.json +0 -150
  136. package/.kxm/config/workflows/v04-dogfood.json +0 -72
@@ -1,4 +1,7 @@
1
1
  import { spawnSync } from "node:child_process";
2
+ import { existsSync } from "node:fs";
3
+ import { win32 as win32Path } from "node:path";
4
+ import { defaultSpawn } from "./vnext-oneshot-process.ts";
2
5
 
3
6
  export const DEFAULT_HARNESS = "pi";
4
7
  export type HarnessMode = "headless" | "either";
@@ -27,6 +30,7 @@ export interface OneShotParsedOutput {
27
30
  text: string;
28
31
  usage?: OneShotUsage | undefined;
29
32
  outcome?: string | undefined;
33
+ effectiveModel?: string | undefined;
30
34
  isError?: boolean | undefined;
31
35
  errorMessage?: string | undefined;
32
36
  }
@@ -35,7 +39,7 @@ export interface HarnessOneShotConfig {
35
39
  argv: readonly string[];
36
40
  promptVia: OneShotPromptVia;
37
41
  outputFormat: "json" | "text" | "stream-json" | "jsonl";
38
- usageParser: (stdout: string, stderr: string) => OneShotParsedOutput;
42
+ usageParser: (stdout: string, stderr: string, requestedModel?: string) => OneShotParsedOutput;
39
43
  }
40
44
 
41
45
  export interface HarnessCatalogEntry {
@@ -55,9 +59,11 @@ export interface HarnessCatalogEntry {
55
59
  }
56
60
 
57
61
  export interface HarnessProbeOptions {
58
- env?: NodeJS.ProcessEnv;
59
- runCommand?: (command: string, args: readonly string[], timeoutMs: number) => HarnessCommandResult;
60
- timeoutMs?: number;
62
+ env?: NodeJS.ProcessEnv | undefined;
63
+ runCommand?: ((command: string, args: readonly string[], timeoutMs: number) => HarnessCommandResult) | undefined;
64
+ timeoutMs?: number | undefined;
65
+ platform?: NodeJS.Platform | undefined;
66
+ existsSync?: ((path: string) => boolean) | undefined;
61
67
  }
62
68
 
63
69
  export interface HarnessDispatchStatus {
@@ -73,6 +79,7 @@ export interface HarnessStatus {
73
79
  mode: HarnessMode;
74
80
  detected: boolean;
75
81
  authenticated: boolean | null;
82
+ authMethod?: "claude.ai" | "ChatGPT" | "api-key" | "antigravity-oauth" | undefined;
76
83
  dispatch?: HarnessDispatchStatus | undefined;
77
84
  command?: string;
78
85
  version?: string;
@@ -85,10 +92,10 @@ export interface HarnessInventory {
85
92
  harnesses: readonly HarnessStatus[];
86
93
  }
87
94
 
88
- const UNKNOWN_AUTH_HARNESSES = new Set(["gemini", "deepseek"]);
95
+ const UNKNOWN_AUTH_HARNESSES = new Set(["deepseek"]);
89
96
  const GROK_LOGIN_LINE = "You are logged in with grok.com.";
90
97
  /** Tab-separated `id<TAB>label` rows from the committed `agy models` probe. */
91
- const AGY_MODEL_ROW = /^[a-z0-9][a-z0-9.+_-]*\t+\S/im;
98
+ const AGY_MODEL_ROW = /^[a-z0-9]+(?:[._-][a-z0-9]+)+\s+\S/im;
92
99
  const CODEX_CHATGPT_LINE = "Logged in using ChatGPT";
93
100
  const CODEX_API_KEY_PREFIX = "Logged in using an API key";
94
101
  const CODEX_NEGATIVE_LINE = "Not logged in";
@@ -99,7 +106,6 @@ export const NATIVE_HARNESS_PROVIDERS: Readonly<Record<string, string>> = Object
99
106
  codex: "openai",
100
107
  grok: "xai",
101
108
  agy: "google",
102
- gemini: "google",
103
109
  kimi: "moonshot",
104
110
  deepseek: "deepseek",
105
111
  });
@@ -143,7 +149,11 @@ export interface HarnessUpdateStep {
143
149
  detail?: string;
144
150
  }
145
151
 
146
- export function parseClaudeOneShotUsage(stdout: string, stderr: string): OneShotParsedOutput {
152
+ function reportedModelId(value: unknown): string | undefined {
153
+ return typeof value === "string" && /^[a-z0-9][a-z0-9._:/-]{0,199}$/i.test(value) ? value : undefined;
154
+ }
155
+
156
+ export function parseClaudeOneShotUsage(stdout: string, stderr: string, requestedModel?: string): OneShotParsedOutput {
147
157
  const trimmed = stdout.trim();
148
158
  if (!trimmed) {
149
159
  return {
@@ -155,26 +165,28 @@ export function parseClaudeOneShotUsage(stdout: string, stderr: string): OneShot
155
165
  try {
156
166
  const payload = JSON.parse(trimmed) as Record<string, unknown>;
157
167
  const usage = (payload.usage as Record<string, unknown> | undefined) ?? {};
158
- let modelUsageDetail: Record<string, unknown> | undefined;
159
- if (payload.modelUsage && typeof payload.modelUsage === "object") {
160
- const values = Object.values(payload.modelUsage as Record<string, unknown>);
161
- if (values.length > 0 && values[0] && typeof values[0] === "object") {
162
- modelUsageDetail = values[0] as Record<string, unknown>;
163
- }
164
- }
165
- const tokensIn = (typeof modelUsageDetail?.inputTokens === "number" ? modelUsageDetail.inputTokens : undefined)
166
- ?? (typeof usage.input_tokens === "number" ? usage.input_tokens : null);
167
- const tokensOut = (typeof modelUsageDetail?.outputTokens === "number" ? modelUsageDetail.outputTokens : undefined)
168
- ?? (typeof usage.output_tokens === "number" ? usage.output_tokens : null);
169
- const cacheReadTokens = (typeof modelUsageDetail?.cacheReadInputTokens === "number" ? modelUsageDetail.cacheReadInputTokens : undefined)
170
- ?? (typeof usage.cache_read_input_tokens === "number" ? usage.cache_read_input_tokens : null);
171
- const cacheWriteTokens = (typeof modelUsageDetail?.cacheCreationInputTokens === "number" ? modelUsageDetail.cacheCreationInputTokens : undefined)
172
- ?? (typeof usage.cache_creation_input_tokens === "number" ? usage.cache_creation_input_tokens : null);
168
+ const entries = payload.modelUsage && typeof payload.modelUsage === "object" && !Array.isArray(payload.modelUsage)
169
+ ? Object.entries(payload.modelUsage as Record<string, unknown>).filter(([, value]) => value && typeof value === "object") : [];
170
+ const matches = entries.filter(([model]) => model === requestedModel
171
+ || (requestedModel && (model.startsWith(`${requestedModel}-`) || model.startsWith(`claude-${requestedModel}-`))));
172
+ const selected = matches.length === 1 ? matches[0] : entries.length === 1 ? entries[0] : undefined;
173
+ const modelUsageDetail = selected?.[1] as Record<string, unknown> | undefined;
174
+ const effectiveModel = reportedModelId(payload.model) ?? reportedModelId(selected?.[0]);
175
+ // Top-level usage is the attempt total; an auxiliary model is not the task's model.
176
+ const tokensIn = (typeof usage.input_tokens === "number" ? usage.input_tokens : undefined)
177
+ ?? (typeof modelUsageDetail?.inputTokens === "number" ? modelUsageDetail.inputTokens : null);
178
+ const tokensOut = (typeof usage.output_tokens === "number" ? usage.output_tokens : undefined)
179
+ ?? (typeof modelUsageDetail?.outputTokens === "number" ? modelUsageDetail.outputTokens : null);
180
+ const cacheReadTokens = (typeof usage.cache_read_input_tokens === "number" ? usage.cache_read_input_tokens : undefined)
181
+ ?? (typeof modelUsageDetail?.cacheReadInputTokens === "number" ? modelUsageDetail.cacheReadInputTokens : null);
182
+ const cacheWriteTokens = (typeof usage.cache_creation_input_tokens === "number" ? usage.cache_creation_input_tokens : undefined)
183
+ ?? (typeof modelUsageDetail?.cacheCreationInputTokens === "number" ? modelUsageDetail.cacheCreationInputTokens : null);
173
184
  const costUsd = typeof payload.total_cost_usd === "number"
174
185
  ? payload.total_cost_usd
175
186
  : (typeof modelUsageDetail?.costUSD === "number" ? modelUsageDetail.costUSD : null);
176
187
 
177
- const isError = Boolean(payload.is_error || payload.error);
188
+ const isError = Boolean(payload.is_error || payload.error
189
+ || ["error", "aborted", "failed", "max_tokens", "length"].includes(String(payload.stopReason ?? payload.stop_reason ?? "")));
178
190
  const text = typeof payload.result === "string" ? payload.result : (typeof payload.text === "string" ? payload.text : "");
179
191
  const errorMessage = isError
180
192
  ? (typeof payload.error === "string"
@@ -186,6 +198,7 @@ export function parseClaudeOneShotUsage(stdout: string, stderr: string): OneShot
186
198
 
187
199
  return {
188
200
  text,
201
+ effectiveModel,
189
202
  isError,
190
203
  errorMessage,
191
204
  usage: {
@@ -193,18 +206,25 @@ export function parseClaudeOneShotUsage(stdout: string, stderr: string): OneShot
193
206
  tokensOut,
194
207
  cacheReadTokens,
195
208
  cacheWriteTokens,
196
- contextTokens: tokensIn,
209
+ contextTokens: null,
197
210
  costUsd,
198
211
  },
199
212
  };
200
213
  } catch {
201
214
  return {
202
215
  text: trimmed,
216
+ isError: true,
217
+ errorMessage: "invalid Claude JSON response",
203
218
  usage: {},
204
219
  };
205
220
  }
206
221
  }
207
222
 
223
+ /** Grok's observed one-shot JSON uses the same usage/modelUsage envelope. */
224
+ export function parseGrokOneShotUsage(stdout: string, stderr: string, requestedModel?: string): OneShotParsedOutput {
225
+ return parseClaudeOneShotUsage(stdout, stderr, requestedModel);
226
+ }
227
+
208
228
  export function parseCodexOneShotUsage(stdout: string, stderr: string): OneShotParsedOutput {
209
229
  const lines = stdout.split(/\r?\n/).map((l) => l.trim()).filter(Boolean);
210
230
  if (lines.length === 0) {
@@ -238,7 +258,7 @@ export function parseCodexOneShotUsage(stdout: string, stderr: string): OneShotP
238
258
  ? ((ev.error as Record<string, unknown>).message as string)
239
259
  : "turn_failed");
240
260
  }
241
- if (ev.item && typeof ev.item === "object" && (ev.item as Record<string, unknown>).type === "agent_message") {
261
+ if (ev.type === "item.completed" && ev.item && typeof ev.item === "object" && (ev.item as Record<string, unknown>).type === "agent_message") {
242
262
  const t = (ev.item as Record<string, unknown>).text;
243
263
  if (typeof t === "string") messageTexts.push(t);
244
264
  }
@@ -247,7 +267,8 @@ export function parseCodexOneShotUsage(stdout: string, stderr: string): OneShotP
247
267
  }
248
268
  }
249
269
 
250
- const text = messageTexts.join("\n").trim();
270
+ const text = (messageTexts.at(-1) ?? "").trim();
271
+ if (!events.some((event) => event.type === "turn.completed") || !text) hasError = true;
251
272
  const tokensIn = typeof usage?.input_tokens === "number" ? usage.input_tokens : null;
252
273
  const tokensOut = typeof usage?.output_tokens === "number" ? usage.output_tokens : null;
253
274
  const cacheReadTokens = typeof usage?.cached_input_tokens === "number" ? usage.cached_input_tokens : null;
@@ -262,7 +283,7 @@ export function parseCodexOneShotUsage(stdout: string, stderr: string): OneShotP
262
283
  tokensOut,
263
284
  cacheReadTokens,
264
285
  cacheWriteTokens,
265
- contextTokens: tokensIn,
286
+ contextTokens: null,
266
287
  costUsd: null,
267
288
  },
268
289
  };
@@ -279,7 +300,15 @@ export function parseGenericOneShotUsage(stdout: string, _stderr: string): OneSh
279
300
  : (typeof rec.text === "string"
280
301
  ? rec.text
281
302
  : (typeof rec.response === "string" ? rec.response : trimmed));
282
- return { text, usage: {} };
303
+ const u = rec.usage && typeof rec.usage === "object" ? rec.usage as Record<string, unknown> : {};
304
+ return { text, effectiveModel: reportedModelId(rec.model), isError: Boolean(rec.is_error || rec.error || rec.success === false), usage: {
305
+ tokensIn: typeof u.input_tokens === "number" ? u.input_tokens : null,
306
+ tokensOut: typeof u.output_tokens === "number" ? u.output_tokens : null,
307
+ cacheReadTokens: typeof u.cache_read_input_tokens === "number" ? u.cache_read_input_tokens : null,
308
+ cacheWriteTokens: typeof u.cache_creation_input_tokens === "number" ? u.cache_creation_input_tokens : null,
309
+ contextTokens: null,
310
+ costUsd: typeof rec.total_cost_usd === "number" ? rec.total_cost_usd : null,
311
+ } };
283
312
  }
284
313
  } catch {
285
314
  // not json
@@ -381,6 +410,18 @@ export function parseAgyOneShotUsage(stdout: string, _stderr: string): OneShotPa
381
410
  }
382
411
 
383
412
  /** Built-in harnesses. Unknown ids fail closed. Model lists live in `.kxm/models/*.yaml`, not here. */
413
+ const READ_ONLY_ONESHOT_ARGS = Object.freeze({
414
+ claude: Object.freeze(["--tools", "Read,Glob,Grep", "--restricted", "--safe-mode", "--strict-mcp-config", "--mcp-config", '{"mcpServers":{}}', "--disable-slash-commands", "--no-session-persistence"]),
415
+ codex: Object.freeze(["--sandbox", "read-only", "--ignore-user-config", "-c", 'approval_policy="never"']),
416
+ grok: Object.freeze(["--sandbox", "read-only", "--permission-mode", "plan", "--tools", "Read,Glob,Grep", "--no-subagents", "--disable-web-search"]),
417
+ agy: Object.freeze(["--mode", "plan", "--sandbox", "--disable-slash-commands"]),
418
+ kimi: Object.freeze(["--plan"]),
419
+ });
420
+
421
+ export function oneShotReadOnlyArgs(harness: string): readonly string[] | undefined {
422
+ return Object.hasOwn(READ_ONLY_ONESHOT_ARGS, harness) ? READ_ONLY_ONESHOT_ARGS[harness as keyof typeof READ_ONLY_ONESHOT_ARGS] : undefined;
423
+ }
424
+
384
425
  export const BUILTIN_HARNESSES: readonly HarnessCatalogEntry[] = Object.freeze([
385
426
  {
386
427
  id: "pi",
@@ -414,7 +455,7 @@ export const BUILTIN_HARNESSES: readonly HarnessCatalogEntry[] = Object.freeze([
414
455
  extensions: ["plugin", "update", "kxm", "-y"],
415
456
  },
416
457
  oneShot: {
417
- argv: ["-p", "--output-format", "json"],
458
+ argv: ["-p", ...oneShotReadOnlyArgs("claude")!, "--output-format", "json"],
418
459
  promptVia: "stdin",
419
460
  outputFormat: "json",
420
461
  usageParser: parseClaudeOneShotUsage,
@@ -430,7 +471,7 @@ export const BUILTIN_HARNESSES: readonly HarnessCatalogEntry[] = Object.freeze([
430
471
  authArgs: ["provider", "list"],
431
472
  update: { self: ["upgrade"] },
432
473
  oneShot: {
433
- argv: ["--output-format", "stream-json", "-p"],
474
+ argv: [...oneShotReadOnlyArgs("kimi")!, "--output-format", "stream-json", "-p"],
434
475
  promptVia: "arg",
435
476
  outputFormat: "stream-json",
436
477
  usageParser: parseKimiOneShotUsage,
@@ -446,21 +487,12 @@ export const BUILTIN_HARNESSES: readonly HarnessCatalogEntry[] = Object.freeze([
446
487
  authArgs: ["login", "status"],
447
488
  update: { self: ["update"] },
448
489
  oneShot: {
449
- argv: ["exec", "--json", "-"],
490
+ argv: ["exec", ...oneShotReadOnlyArgs("codex")!, "--json", "-"],
450
491
  promptVia: "stdin",
451
492
  outputFormat: "json",
452
493
  usageParser: parseCodexOneShotUsage,
453
494
  },
454
495
  },
455
- {
456
- id: "gemini",
457
- label: "Gemini CLI",
458
- default: false,
459
- mode: "either",
460
- commands: ["gemini"],
461
- versionArgs: ["--version"],
462
- update: { self: ["update"] },
463
- },
464
496
  {
465
497
  id: "deepseek",
466
498
  label: "DeepSeek",
@@ -486,10 +518,10 @@ export const BUILTIN_HARNESSES: readonly HarnessCatalogEntry[] = Object.freeze([
486
518
  authArgs: ["models"],
487
519
  update: { self: ["update"] },
488
520
  oneShot: {
489
- argv: ["--output-format", "json"],
490
- promptVia: "stdin",
521
+ argv: [...oneShotReadOnlyArgs("grok")!, "--output-format", "json", "--single"],
522
+ promptVia: "arg",
491
523
  outputFormat: "json",
492
- usageParser: parseClaudeOneShotUsage,
524
+ usageParser: parseGrokOneShotUsage,
493
525
  },
494
526
  },
495
527
  {
@@ -502,7 +534,7 @@ export const BUILTIN_HARNESSES: readonly HarnessCatalogEntry[] = Object.freeze([
502
534
  authArgs: ["models"],
503
535
  update: { self: ["update"] },
504
536
  oneShot: {
505
- argv: ["--output-format", "json", "-p"],
537
+ argv: [...oneShotReadOnlyArgs("agy")!, "--output-format", "json", "-p"],
506
538
  promptVia: "arg",
507
539
  outputFormat: "json",
508
540
  usageParser: parseAgyOneShotUsage,
@@ -518,6 +550,54 @@ export function isKnownHarnessId(id: string, extra: Iterable<string> = []): bool
518
550
  return false;
519
551
  }
520
552
 
553
+ /** Bare catalog command ids only; never a path or shell metacharacter. */
554
+ export const SAFE_HARNESS_COMMAND_ID = /^[A-Za-z][A-Za-z0-9_-]*$/;
555
+
556
+ export function harnessCommandCandidates(
557
+ command: string,
558
+ platform: NodeJS.Platform = process.platform,
559
+ ): readonly string[] {
560
+ if (platform !== "win32" || !SAFE_HARNESS_COMMAND_ID.test(command)) return [command];
561
+ return [command, `${command}.exe`, `${command}.cmd`];
562
+ }
563
+
564
+ export function isWindowsHarnessShim(command: string): boolean {
565
+ return /\.(cmd|bat)$/i.test(command.replace(/\\/g, "/").split("/").pop() ?? command);
566
+ }
567
+
568
+ /** Shell only for allowlisted `name.cmd` on win32. Bare ids stay spawn-without-shell. */
569
+ export function harnessSpawnUsesShell(command: string, platform: NodeJS.Platform = process.platform): boolean {
570
+ if (platform !== "win32") return false;
571
+ const base = command.replace(/\\/g, "/").split("/").pop() ?? "";
572
+ return /^[A-Za-z][A-Za-z0-9_-]*\.cmd$/i.test(base);
573
+ }
574
+
575
+ /** Allowlisted npm-global inner `.exe`, relative to the directory that contains `name.cmd`. */
576
+ export const WIN_NPM_INNER_EXE: Readonly<Record<string, readonly string[]>> = Object.freeze({
577
+ claude: Object.freeze(["node_modules", "@anthropic-ai", "claude-code", "bin", "claude.exe"]),
578
+ });
579
+
580
+ export function findWinNpmInnerExe(
581
+ cliId: string,
582
+ options: {
583
+ platform?: NodeJS.Platform | undefined;
584
+ pathEnv?: string | undefined;
585
+ existsSync?: ((path: string) => boolean) | undefined;
586
+ } = {},
587
+ ): string | undefined {
588
+ if ((options.platform ?? process.platform) !== "win32") return undefined;
589
+ const segments = WIN_NPM_INNER_EXE[cliId];
590
+ if (!segments) return undefined;
591
+ const exists = options.existsSync ?? existsSync;
592
+ const pathEnv = options.pathEnv ?? process.env.PATH ?? "";
593
+ for (const dir of pathEnv.split(";").filter(Boolean)) {
594
+ if (!exists(win32Path.join(dir, `${cliId}.cmd`))) continue;
595
+ const inner = win32Path.join(dir, ...segments);
596
+ if (exists(inner)) return inner;
597
+ }
598
+ return undefined;
599
+ }
600
+
521
601
  function defaultRunner(env: NodeJS.ProcessEnv): (command: string, args: readonly string[], timeoutMs: number) => HarnessCommandResult {
522
602
  return (command, args, timeoutMs) => {
523
603
  try {
@@ -526,6 +606,7 @@ function defaultRunner(env: NodeJS.ProcessEnv): (command: string, args: readonly
526
606
  timeout: timeoutMs,
527
607
  windowsHide: true,
528
608
  env,
609
+ shell: harnessSpawnUsesShell(command),
529
610
  });
530
611
  if (result.error) {
531
612
  const code = (result.error as NodeJS.ErrnoException).code;
@@ -588,7 +669,7 @@ function isCodexApiKeyLine(line: string): boolean {
588
669
  return line === CODEX_API_KEY_PREFIX || line.startsWith(`${CODEX_API_KEY_PREFIX} - `);
589
670
  }
590
671
 
591
- function interpretAuth(id: string, result: HarnessCommandResult): { authenticated: boolean | null; issues: string[] } {
672
+ function interpretAuth(id: string, result: HarnessCommandResult): Pick<HarnessStatus, "authenticated" | "authMethod"> & { issues: string[] } {
592
673
  const conflict = authConflictIssue(result);
593
674
  if (conflict) return { authenticated: null, issues: [conflict] };
594
675
  if (result.error) return { authenticated: null, issues: ["auth_probe_error"] };
@@ -597,7 +678,10 @@ function interpretAuth(id: string, result: HarnessCommandResult): { authenticate
597
678
  if (id === "claude") {
598
679
  const payload = parseJsonObject(result.stdout);
599
680
  if (payload && payload.loggedIn === false) return { authenticated: false, issues: ["not_authenticated"] };
600
- if (commandSucceeded(result) && payload && payload.loggedIn === true) return { authenticated: true, issues: [] };
681
+ if (commandSucceeded(result) && payload && payload.loggedIn === true) return {
682
+ authenticated: true, issues: [],
683
+ ...(payload.authMethod === "claude.ai" || payload.authMethod === "api-key" ? { authMethod: payload.authMethod } : {}),
684
+ };
601
685
  return { authenticated: null, issues: ["auth_unparsed"] };
602
686
  }
603
687
  if (id === "codex") {
@@ -605,10 +689,10 @@ function interpretAuth(id: string, result: HarnessCommandResult): { authenticate
605
689
  return { authenticated: false, issues: ["not_authenticated"] };
606
690
  }
607
691
  if (commandSucceeded(result) && lines.some((line) => line === CODEX_CHATGPT_LINE)) {
608
- return { authenticated: true, issues: [] };
692
+ return { authenticated: true, authMethod: "ChatGPT", issues: [] };
609
693
  }
610
694
  if (commandSucceeded(result) && lines.some((line) => isCodexApiKeyLine(line))) {
611
- return { authenticated: true, issues: ["auth_api_key"] };
695
+ return { authenticated: true, authMethod: "api-key", issues: ["auth_api_key"] };
612
696
  }
613
697
  return { authenticated: null, issues: ["auth_unparsed"] };
614
698
  }
@@ -621,7 +705,7 @@ function interpretAuth(id: string, result: HarnessCommandResult): { authenticate
621
705
  if (!commandSucceeded(result) || !text.trim()) {
622
706
  return { authenticated: false, issues: ["not_authenticated"] };
623
707
  }
624
- if (AGY_MODEL_ROW.test(text)) return { authenticated: true, issues: [] };
708
+ if (AGY_MODEL_ROW.test(text)) return { authenticated: true, authMethod: "antigravity-oauth", issues: [] };
625
709
  return { authenticated: null, issues: ["auth_unparsed"] };
626
710
  }
627
711
  if (id === "kimi") {
@@ -652,37 +736,81 @@ export function resolveDispatchStatus(
652
736
  return {
653
737
  status: "no",
654
738
  supported: false,
655
- reason: entry.id === "gemini" ? "deprecated_client" : "no_headless_mode",
739
+ reason: "no_headless_mode",
656
740
  };
657
741
  }
742
+ if (entry.id !== "pi" && !oneShotReadOnlyArgs(entry.id)) {
743
+ return { status: "no", supported: false, reason: "permission_profile_unaudited" };
744
+ }
658
745
  if (authenticated === false) {
659
746
  return { status: "no", supported: false, reason: "not_authenticated" };
660
747
  }
661
748
  if (authenticated === null) {
662
- return { status: "no", supported: false, reason: issues[0] ?? "auth_unknown" };
749
+ const authIssue = issues.find((issue) => issue === "auth_context_required" || issue === "auth_unknown" || issue === "auth_unparsed" || issue.startsWith("auth_"));
750
+ return { status: "no", supported: false, reason: authIssue ?? issues[0] ?? "auth_unknown" };
663
751
  }
664
752
  return { status: "yes", supported: true };
665
753
  }
666
754
 
755
+ function tryHarnessCommand(
756
+ candidate: string,
757
+ entry: HarnessCatalogEntry,
758
+ runCommand: (command: string, args: readonly string[], timeoutMs: number) => HarnessCommandResult,
759
+ timeoutMs: number,
760
+ ): { command: string; version: string | undefined } | undefined {
761
+ const result = runCommand(candidate, entry.versionArgs, timeoutMs);
762
+ if (result.error === "ENOENT") return undefined;
763
+ if (result.error && result.code === null && !result.stdout && !result.stderr) return undefined;
764
+ return {
765
+ command: candidate,
766
+ version: firstLine(result.stdout) ?? firstLine(result.stderr),
767
+ };
768
+ }
769
+
770
+ function detectHarnessCommand(
771
+ entry: HarnessCatalogEntry,
772
+ runCommand: (command: string, args: readonly string[], timeoutMs: number) => HarnessCommandResult,
773
+ timeoutMs: number,
774
+ platform: NodeJS.Platform,
775
+ probe: Pick<HarnessProbeOptions, "env" | "existsSync"> = {},
776
+ ): { command: string; version: string | undefined } | undefined {
777
+ const candidates = entry.commands.flatMap((base) => [...harnessCommandCandidates(base, platform)]);
778
+ for (const candidate of candidates) {
779
+ if (isWindowsHarnessShim(candidate)) continue;
780
+ const found = tryHarnessCommand(candidate, entry, runCommand, timeoutMs);
781
+ if (found) return found;
782
+ }
783
+ const inner = findWinNpmInnerExe(entry.id, {
784
+ platform,
785
+ pathEnv: probe.env?.PATH ?? process.env.PATH ?? "",
786
+ existsSync: probe.existsSync,
787
+ });
788
+ if (inner) {
789
+ const found = tryHarnessCommand(inner, entry, runCommand, timeoutMs);
790
+ if (found) return found;
791
+ }
792
+ for (const candidate of candidates) {
793
+ if (!isWindowsHarnessShim(candidate)) continue;
794
+ const found = tryHarnessCommand(candidate, entry, runCommand, timeoutMs);
795
+ if (found) return found;
796
+ }
797
+ return undefined;
798
+ }
799
+
667
800
  function probeEntry(
668
801
  entry: HarnessCatalogEntry,
669
802
  runCommand: (command: string, args: readonly string[], timeoutMs: number) => HarnessCommandResult,
670
803
  timeoutMs: number,
804
+ platform: NodeJS.Platform,
805
+ probe: Pick<HarnessProbeOptions, "env" | "existsSync"> = {},
671
806
  ): HarnessStatus {
672
807
  const issues: string[] = [];
673
- let detected = false;
674
- let command: string | undefined;
675
- let version: string | undefined;
676
- for (const candidate of entry.commands) {
677
- const result = runCommand(candidate, entry.versionArgs, timeoutMs);
678
- if (result.error === "ENOENT") continue;
679
- if (result.error && result.code === null && !result.stdout && !result.stderr) continue;
680
- detected = true;
681
- command = candidate;
682
- version = firstLine(result.stdout) ?? firstLine(result.stderr);
683
- break;
684
- }
808
+ const found = detectHarnessCommand(entry, runCommand, timeoutMs, platform, probe);
809
+ const detected = Boolean(found);
810
+ const command = found?.command;
811
+ const version = found?.version;
685
812
  let authenticated: boolean | null = null;
813
+ let authMethod: HarnessStatus["authMethod"];
686
814
  if (!detected) authenticated = false;
687
815
  else if (entry.id === "pi") {
688
816
  authenticated = null;
@@ -691,10 +819,14 @@ function probeEntry(
691
819
  authenticated = null;
692
820
  if (UNKNOWN_AUTH_HARNESSES.has(entry.id)) issues.push("auth_unknown");
693
821
  } else if (command) {
694
- const parsed = interpretAuth(entry.id, runCommand(command, entry.authArgs, timeoutMs));
822
+ // Auth CLIs may initialize hooks/OAuth state and exceed the short binary-detection timeout.
823
+ // Keep detection bounded while allowing the documented auth probe to complete.
824
+ const parsed = interpretAuth(entry.id, runCommand(command, entry.authArgs, Math.max(timeoutMs, 10_000)));
695
825
  authenticated = parsed.authenticated;
826
+ authMethod = parsed.authMethod;
696
827
  issues.push(...parsed.issues);
697
828
  }
829
+ if (command && isWindowsHarnessShim(command)) issues.push("windows_shim");
698
830
  const dispatch = resolveDispatchStatus(entry, detected, authenticated, issues);
699
831
  return {
700
832
  id: entry.id,
@@ -703,6 +835,7 @@ function probeEntry(
703
835
  mode: entry.mode,
704
836
  detected,
705
837
  authenticated,
838
+ ...(authMethod ? { authMethod } : {}),
706
839
  dispatch,
707
840
  ...(command ? { command } : {}),
708
841
  ...(version ? { version } : {}),
@@ -718,15 +851,16 @@ function probeEntry(
718
851
  export function probeHarnesses(options: HarnessProbeOptions = {}): HarnessInventory {
719
852
  const timeoutMs = options.timeoutMs ?? 3000;
720
853
  const runCommand = options.runCommand ?? defaultRunner(options.env ?? process.env);
854
+ const platform = options.platform ?? process.platform;
721
855
  return {
722
856
  defaultHarness: DEFAULT_HARNESS,
723
- harnesses: BUILTIN_HARNESSES.map((entry) => probeEntry(entry, runCommand, timeoutMs)),
857
+ harnesses: BUILTIN_HARNESSES.map((entry) => probeEntry(entry, runCommand, timeoutMs, platform, options)),
724
858
  };
725
859
  }
726
860
 
727
861
  export function eligibleHarnesses(inventory: HarnessInventory): readonly string[] {
728
862
  const eligible = inventory.harnesses
729
- .filter((entry) => entry.detected && entry.authenticated === true)
863
+ .filter((entry) => entry !== undefined && entry.detected && entry.authenticated === true)
730
864
  .map((entry) => entry.id);
731
865
  if (eligible.length === 0) throw new Error("no_authenticated_harness");
732
866
  return eligible;
@@ -785,7 +919,7 @@ export function validateHarnessModelPair(
785
919
  return { valid: true };
786
920
  }
787
921
 
788
- if (harnessId === "agy" || harnessId === "gemini") {
922
+ if (harnessId === "agy") {
789
923
  if (provider && provider !== "google") {
790
924
  return { valid: false, issue: "harness_unhosted_model", message: `harness ${harnessId} does not host provider ${provider}` };
791
925
  }
@@ -799,7 +933,7 @@ export function validateHarnessModelPair(
799
933
  if (provider && provider !== "moonshot") {
800
934
  return { valid: false, issue: "harness_unhosted_model", message: `harness kimi does not host provider ${provider}` };
801
935
  }
802
- if (model && !/^(kimi|moonshot)-/i.test(model)) {
936
+ if (model && !/^(kimi|moonshot)(?:-|$)/i.test(model) && !/^kimi-for-coding(?:-|$)/i.test(model)) {
803
937
  return { valid: false, issue: "harness_unhosted_model", message: `harness kimi only hosts kimi/moonshot models, received ${model}` };
804
938
  }
805
939
  return { valid: true };
@@ -843,11 +977,14 @@ export interface HarnessAssignmentProbeOptions {
843
977
  env?: NodeJS.ProcessEnv | undefined;
844
978
  runCommand?: ((command: string, args: readonly string[], timeoutMs: number) => HarnessCommandResult) | undefined;
845
979
  timeoutMs?: number | undefined;
980
+ platform?: NodeJS.Platform | undefined;
981
+ existsSync?: ((path: string) => boolean) | undefined;
846
982
  }
847
983
 
848
984
  export function probeHarnessAssignment(options: HarnessAssignmentProbeOptions): HarnessStatus {
849
985
  const timeoutMs = options.timeoutMs ?? 3000;
850
986
  const runCommand = options.runCommand ?? defaultRunner(options.env ?? process.env);
987
+ const platform = options.platform ?? process.platform;
851
988
  const entry = BUILTIN_HARNESSES.find((candidate) => candidate.id === options.harness);
852
989
  if (!entry) {
853
990
  return {
@@ -884,18 +1021,11 @@ export function probeHarnessAssignment(options: HarnessAssignmentProbeOptions):
884
1021
  }
885
1022
  }
886
1023
 
887
- let detected = false;
888
- let command: string | undefined;
889
- let version: string | undefined;
890
- for (const candidate of entry.commands) {
891
- const result = runCommand(candidate, entry.versionArgs, timeoutMs);
892
- if (result.error === "ENOENT") continue;
893
- if (result.error && result.code === null && !result.stdout && !result.stderr) continue;
894
- detected = true;
895
- command = candidate;
896
- version = firstLine(result.stdout) ?? firstLine(result.stderr);
897
- break;
898
- }
1024
+ const found = detectHarnessCommand(entry, runCommand, timeoutMs, platform, options);
1025
+ const detected = Boolean(found);
1026
+ const command = found?.command;
1027
+ const version = found?.version;
1028
+ const shimIssue = command && isWindowsHarnessShim(command) ? ["windows_shim"] : [];
899
1029
 
900
1030
  if (!detected || !command) {
901
1031
  return {
@@ -932,7 +1062,7 @@ export function probeHarnessAssignment(options: HarnessAssignmentProbeOptions):
932
1062
  extensions: Boolean(entry.update.extensions?.length),
933
1063
  models: Boolean(entry.update.models?.length),
934
1064
  },
935
- issues: ["auth_context_required"],
1065
+ issues: ["auth_context_required", ...shimIssue],
936
1066
  };
937
1067
  }
938
1068
 
@@ -960,6 +1090,7 @@ export function probeHarnessAssignment(options: HarnessAssignmentProbeOptions):
960
1090
  authenticated = false;
961
1091
  issues.push("not_authenticated");
962
1092
  }
1093
+ issues.push(...shimIssue);
963
1094
 
964
1095
  const dispatch = resolveDispatchStatus(entry, true, authenticated, issues);
965
1096
 
@@ -982,7 +1113,56 @@ export function probeHarnessAssignment(options: HarnessAssignmentProbeOptions):
982
1113
  };
983
1114
  }
984
1115
 
985
- return probeEntry(entry, runCommand, timeoutMs);
1116
+ return probeEntry(entry, runCommand, timeoutMs, platform);
1117
+ }
1118
+
1119
+ export interface AsyncHarnessAssignmentProbeOptions extends Omit<HarnessAssignmentProbeOptions, "runCommand"> {
1120
+ signal?: AbortSignal | undefined;
1121
+ runCommand?: ((command: string, args: readonly string[], timeoutMs: number) => HarnessCommandResult | Promise<HarnessCommandResult>) | undefined;
1122
+ }
1123
+
1124
+ class PendingHarnessCommand {
1125
+ command: string;
1126
+ args: readonly string[];
1127
+ timeoutMs: number;
1128
+ key: string;
1129
+ constructor(command: string, args: readonly string[], timeoutMs: number, key: string) {
1130
+ this.command = command;
1131
+ this.args = [...args];
1132
+ this.timeoutMs = timeoutMs;
1133
+ this.key = key;
1134
+ }
1135
+ }
1136
+
1137
+ /**
1138
+ * Execute the existing probe policy without blocking the Runtime event loop.
1139
+ * Replay pure policy decisions from per-call command observations, yielding for
1140
+ * every missing observation. This reuses the exact auth/model/brake parsers,
1141
+ * deduplicates repeated detection, and never caches auth across assignments.
1142
+ */
1143
+ export async function probeHarnessAssignmentAsync(options: AsyncHarnessAssignmentProbeOptions): Promise<HarnessStatus> {
1144
+ const observed = new Map<string, HarnessCommandResult>();
1145
+ const run = options.runCommand ?? (async (command, args, timeoutMs) => {
1146
+ const result = await defaultSpawn(command, args, { env: options.env, timeoutMs, signal: options.signal });
1147
+ const error = result.error?.message ?? (result.observedChildExit !== true ? "auth_probe_exit_unobserved" : undefined);
1148
+ return { ok: result.code === 0 && !error, code: result.code, stdout: result.stdout, stderr: result.stderr,
1149
+ ...(error ? { error } : {}) };
1150
+ });
1151
+ for (let commands = 0; commands <= 32; commands++) {
1152
+ try {
1153
+ return probeHarnessAssignment({ ...options, runCommand(command, args, timeoutMs) {
1154
+ const key = JSON.stringify([command, args, timeoutMs]);
1155
+ const result = observed.get(key);
1156
+ if (result) return result;
1157
+ throw new PendingHarnessCommand(command, args, timeoutMs, key);
1158
+ } });
1159
+ } catch (pending) {
1160
+ if (!(pending instanceof PendingHarnessCommand)) throw pending;
1161
+ if (commands === 32) throw new Error("auth_probe_command_limit");
1162
+ observed.set(pending.key, await run(pending.command, pending.args, pending.timeoutMs));
1163
+ }
1164
+ }
1165
+ throw new Error("auth_probe_command_limit");
986
1166
  }
987
1167
 
988
1168
  export function probeHarnessesForModel(
@@ -991,6 +1171,7 @@ export function probeHarnessesForModel(
991
1171
  ): HarnessInventory {
992
1172
  const timeoutMs = options.timeoutMs ?? 3000;
993
1173
  const runCommand = options.runCommand ?? defaultRunner(options.env ?? process.env);
1174
+ const platform = options.platform ?? process.platform;
994
1175
  return {
995
1176
  defaultHarness: DEFAULT_HARNESS,
996
1177
  harnesses: BUILTIN_HARNESSES.map((entry) =>
@@ -1000,6 +1181,9 @@ export function probeHarnessesForModel(
1000
1181
  model: modelSpec.model,
1001
1182
  runCommand,
1002
1183
  timeoutMs,
1184
+ platform,
1185
+ env: options.env,
1186
+ existsSync: options.existsSync,
1003
1187
  })
1004
1188
  ),
1005
1189
  };