@mrciphersmith/keryx 0.2.49 → 0.2.51

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -186,14 +186,14 @@ var init_config_dir = __esm(() => {
186
186
  // src/lib/fs.ts
187
187
  var exports_fs = {};
188
188
  __export(exports_fs, {
189
- writeFileAtomic: () => writeFileAtomic,
190
- withFileLock: () => withFileLock2,
191
- toPosix: () => toPosix,
192
- pathExists: () => pathExists,
193
- isPathInside: () => isPathInside,
194
- isNotFound: () => isNotFound,
189
+ DEFAULT_LOCK_STALE_MS: () => DEFAULT_LOCK_STALE_MS,
195
190
  isLockHeld: () => isLockHeld,
196
- DEFAULT_LOCK_STALE_MS: () => DEFAULT_LOCK_STALE_MS
191
+ isNotFound: () => isNotFound,
192
+ isPathInside: () => isPathInside,
193
+ pathExists: () => pathExists,
194
+ toPosix: () => toPosix,
195
+ withFileLock: () => withFileLock2,
196
+ writeFileAtomic: () => writeFileAtomic
197
197
  });
198
198
  import { randomUUID as randomUUID4 } from "crypto";
199
199
  import { access, mkdir, readFile, rename, rm, stat, utimes, writeFile } from "fs/promises";
@@ -5372,13 +5372,13 @@ var init_service2 = __esm(() => {
5372
5372
  // src/lib/shell-config.ts
5373
5373
  var exports_shell_config = {};
5374
5374
  __export(exports_shell_config, {
5375
- shellConfigPath: () => shellConfigPath,
5376
- saveShellConfig: () => saveShellConfig,
5377
- saveProviderBaseUrl: () => saveProviderBaseUrl,
5378
- saveApiKey: () => saveApiKey,
5379
- loadShellConfig: () => loadShellConfig,
5375
+ applySavedApiKeys: () => applySavedApiKeys,
5380
5376
  envWithSavedApiKeys: () => envWithSavedApiKeys,
5381
- applySavedApiKeys: () => applySavedApiKeys
5377
+ loadShellConfig: () => loadShellConfig,
5378
+ saveApiKey: () => saveApiKey,
5379
+ saveProviderBaseUrl: () => saveProviderBaseUrl,
5380
+ saveShellConfig: () => saveShellConfig,
5381
+ shellConfigPath: () => shellConfigPath
5382
5382
  });
5383
5383
  import { existsSync as existsSync7 } from "fs";
5384
5384
  import path35 from "path";
@@ -5987,6 +5987,40 @@ function createTreesitterSpec(cwd, config) {
5987
5987
  load: (ctx) => new TreesitterAdapter(cwd, config, ctx.dep)
5988
5988
  };
5989
5989
  }
5990
+ async function loadTreesitterDepLiteral() {
5991
+ try {
5992
+ return await import("web-tree-sitter");
5993
+ } catch {
5994
+ return;
5995
+ }
5996
+ }
5997
+ async function resolveTreesitterCapability(cwd, config, resolve = resolveCapability) {
5998
+ const spec = createTreesitterSpec(cwd, config);
5999
+ try {
6000
+ if (!await isCapabilityEnabled(cwd, spec.id)) {
6001
+ return null;
6002
+ }
6003
+ const literalDep = await loadTreesitterDepLiteral();
6004
+ if (literalDep === undefined) {
6005
+ return await resolve(cwd, spec);
6006
+ }
6007
+ const adapter = spec.load({ dep: literalDep, asset: null });
6008
+ let available;
6009
+ try {
6010
+ available = await adapter.isAvailable();
6011
+ } catch {
6012
+ warnCapabilityDegraded(spec.id, "adapter availability check threw");
6013
+ return null;
6014
+ }
6015
+ if (!available) {
6016
+ warnCapabilityDegraded(spec.id, "adapter reported unavailable");
6017
+ return null;
6018
+ }
6019
+ return adapter;
6020
+ } catch {
6021
+ return null;
6022
+ }
6023
+ }
5990
6024
 
5991
6025
  class TreesitterAdapter {
5992
6026
  cwd;
@@ -6102,6 +6136,8 @@ function compareCalls2(a, b) {
6102
6136
  return a.kind < b.kind ? -1 : a.kind > b.kind ? 1 : 0;
6103
6137
  }
6104
6138
  var init_adapter = __esm(() => {
6139
+ init_seam();
6140
+ init_warn_once();
6105
6141
  init_extract();
6106
6142
  init_grammars();
6107
6143
  });
@@ -6113,13 +6149,13 @@ __export(exports_enrich, {
6113
6149
  });
6114
6150
  import { mkdir as mkdir18, writeFile as writeFile20 } from "fs/promises";
6115
6151
  import path48 from "path";
6116
- async function enrichBuildWithSymbols(cwd, files, resolve = resolveCapability) {
6152
+ async function enrichBuildWithSymbols(cwd, files, resolve) {
6117
6153
  const config = await loadGdgraphConfig(cwd);
6118
- const spec = createTreesitterSpec(cwd, {
6154
+ const treesitterConfig = {
6119
6155
  languages: config.treesitter.languages,
6120
6156
  grammarsPath: config.treesitter.grammarsPath
6121
- });
6122
- const adapter = await resolve(cwd, spec);
6157
+ };
6158
+ const adapter = resolve ? await resolve(cwd, createTreesitterSpec(cwd, treesitterConfig)) : await resolveTreesitterCapability(cwd, treesitterConfig);
6123
6159
  if (!adapter) {
6124
6160
  return { enriched: false, symbols: 0, calls: 0 };
6125
6161
  }
@@ -6746,10 +6782,10 @@ var init_target = () => {};
6746
6782
  // src/gdgraph/query.ts
6747
6783
  var exports_query = {};
6748
6784
  __export(exports_query, {
6749
- loadGraph: () => loadGraph,
6750
- getOrphans: () => getOrphans,
6785
+ getAffected: () => getAffected,
6751
6786
  getCycles: () => getCycles,
6752
- getAffected: () => getAffected
6787
+ getOrphans: () => getOrphans,
6788
+ loadGraph: () => loadGraph
6753
6789
  });
6754
6790
  import { readFile as readFile24 } from "fs/promises";
6755
6791
  import path51 from "path";
@@ -7388,12 +7424,12 @@ var init_repomap = __esm(() => {
7388
7424
  // src/sync/provenance.ts
7389
7425
  var exports_provenance = {};
7390
7426
  __export(exports_provenance, {
7391
- recordProvenance: () => recordProvenance,
7392
- readProvenance: () => readProvenance,
7393
- provenancePath: () => provenancePath,
7394
- gitHead: () => gitHead,
7427
+ SYNCED_MODULES: () => SYNCED_MODULES,
7395
7428
  gitCmd: () => gitCmd,
7396
- SYNCED_MODULES: () => SYNCED_MODULES
7429
+ gitHead: () => gitHead,
7430
+ provenancePath: () => provenancePath,
7431
+ readProvenance: () => readProvenance,
7432
+ recordProvenance: () => recordProvenance
7397
7433
  });
7398
7434
  import { mkdir as mkdir21, readFile as readFile25, writeFile as writeFile23 } from "fs/promises";
7399
7435
  import path54 from "path";
@@ -7455,11 +7491,11 @@ var init_provenance = __esm(() => {
7455
7491
  // src/ctx/orient.ts
7456
7492
  var exports_orient = {};
7457
7493
  __export(exports_orient, {
7458
- wikiContext: () => wikiContext,
7459
- uncommittedCodeCount: () => uncommittedCodeCount,
7460
- metaprojectIndexContext: () => metaprojectIndexContext,
7494
+ buildOrientation: () => buildOrientation,
7461
7495
  graphContext: () => graphContext,
7462
- buildOrientation: () => buildOrientation
7496
+ metaprojectIndexContext: () => metaprojectIndexContext,
7497
+ uncommittedCodeCount: () => uncommittedCodeCount,
7498
+ wikiContext: () => wikiContext
7463
7499
  });
7464
7500
  import path55 from "path";
7465
7501
  import { spawn as spawn2 } from "child_process";
@@ -7666,9 +7702,9 @@ var init_orient = __esm(() => {
7666
7702
  // src/gdgraph/symbols-capability.ts
7667
7703
  var exports_symbols_capability = {};
7668
7704
  __export(exports_symbols_capability, {
7669
- setTreesitterEnabled: () => setTreesitterEnabled,
7705
+ TREESITTER_CAPABILITY: () => TREESITTER_CAPABILITY,
7670
7706
  isTreesitterEnabled: () => isTreesitterEnabled,
7671
- TREESITTER_CAPABILITY: () => TREESITTER_CAPABILITY
7707
+ setTreesitterEnabled: () => setTreesitterEnabled
7672
7708
  });
7673
7709
  function treesitterEntry(enabled) {
7674
7710
  return {
@@ -8081,9 +8117,9 @@ function titleSimilarity(a, b) {
8081
8117
  // src/wiki/collect.ts
8082
8118
  var exports_collect = {};
8083
8119
  __export(exports_collect, {
8084
- keyFilesForPage: () => keyFilesForPage,
8120
+ collectPages: () => collectPages,
8085
8121
  computeModuleKeyFiles: () => computeModuleKeyFiles,
8086
- collectPages: () => collectPages
8122
+ keyFilesForPage: () => keyFilesForPage
8087
8123
  });
8088
8124
  import { readFile as readFile29, readdir as readdir6 } from "fs/promises";
8089
8125
  import path58 from "path";
@@ -8581,18 +8617,18 @@ var init_backlinks = __esm(() => {
8581
8617
  // src/wiki/service.ts
8582
8618
  var exports_service = {};
8583
8619
  __export(exports_service, {
8584
- wikiValidate: () => wikiValidate,
8585
- wikiStatus: () => wikiStatus,
8586
- wikiPruneOrphans: () => wikiPruneOrphans,
8587
- wikiPagesForFile: () => wikiPagesForFile,
8588
- wikiGenerateIndex: () => wikiGenerateIndex,
8589
- wikiCreatePage: () => wikiCreatePage,
8590
- wikiCollect: () => wikiCollect,
8591
- wikiCheckLinks: () => wikiCheckLinks,
8592
- validModuleNames: () => validModuleNames,
8593
- moduleNameFromProjectPath: () => moduleNameFromProjectPath,
8620
+ createGdWikiService: () => createGdWikiService,
8594
8621
  extractModuleApi: () => extractModuleApi,
8595
- createGdWikiService: () => createGdWikiService
8622
+ moduleNameFromProjectPath: () => moduleNameFromProjectPath,
8623
+ validModuleNames: () => validModuleNames,
8624
+ wikiCheckLinks: () => wikiCheckLinks,
8625
+ wikiCollect: () => wikiCollect,
8626
+ wikiCreatePage: () => wikiCreatePage,
8627
+ wikiGenerateIndex: () => wikiGenerateIndex,
8628
+ wikiPagesForFile: () => wikiPagesForFile,
8629
+ wikiPruneOrphans: () => wikiPruneOrphans,
8630
+ wikiStatus: () => wikiStatus,
8631
+ wikiValidate: () => wikiValidate
8596
8632
  });
8597
8633
  import { spawn as spawn3 } from "child_process";
8598
8634
  import { mkdir as mkdir23, readFile as readFile30, readdir as readdir7, rm as rm3, writeFile as writeFile25 } from "fs/promises";
@@ -11454,9 +11490,396 @@ var init_anthropic_provider = __esm(() => {
11454
11490
  };
11455
11491
  });
11456
11492
 
11493
+ // src/harness/provider/compat/openai-compat-provider.ts
11494
+ function isPlainObject4(value) {
11495
+ return typeof value === "object" && value !== null && !Array.isArray(value);
11496
+ }
11497
+ function asRecord3(value) {
11498
+ return isPlainObject4(value) ? value : {};
11499
+ }
11500
+ function asArray(value) {
11501
+ return Array.isArray(value) ? value : [];
11502
+ }
11503
+ function asString3(value) {
11504
+ return typeof value === "string" ? value : undefined;
11505
+ }
11506
+ function asNumber3(value) {
11507
+ return typeof value === "number" && Number.isFinite(value) ? value : undefined;
11508
+ }
11509
+ function retryableFor2(kind, fallback) {
11510
+ const concrete = defaultRetryable(kind);
11511
+ return concrete === undefined ? fallback : concrete;
11512
+ }
11513
+ function mergeUsage2(promptTokens, completionTokens, totalTokens) {
11514
+ const usage = { exact: true };
11515
+ if (promptTokens !== undefined) {
11516
+ usage.inputTokens = promptTokens;
11517
+ }
11518
+ if (completionTokens !== undefined) {
11519
+ usage.outputTokens = completionTokens;
11520
+ }
11521
+ if (totalTokens !== undefined) {
11522
+ usage.totalTokens = totalTokens;
11523
+ } else if (promptTokens !== undefined || completionTokens !== undefined) {
11524
+ usage.totalTokens = (promptTokens ?? 0) + (completionTokens ?? 0);
11525
+ }
11526
+ return usage;
11527
+ }
11528
+ function classifyHttpError2(status) {
11529
+ if (status >= 500) {
11530
+ return { kind: "unavailable", retryable: retryableFor2("unavailable", true), message: "" };
11531
+ }
11532
+ return { kind: "invalid_request", retryable: retryableFor2("invalid_request", false), message: "" };
11533
+ }
11534
+
11535
+ class OpenAiCompatEngine {
11536
+ deps;
11537
+ identity;
11538
+ constructor(deps, identity) {
11539
+ this.deps = deps;
11540
+ this.identity = identity;
11541
+ }
11542
+ get label() {
11543
+ return this.identity.providerLabel ?? this.identity.providerId;
11544
+ }
11545
+ describe() {
11546
+ const capabilities = {
11547
+ streaming: true,
11548
+ toolCalls: true,
11549
+ parallelToolCalls: true,
11550
+ structuredOutput: false,
11551
+ reasoningMetadata: false,
11552
+ promptCaching: false,
11553
+ vision: false,
11554
+ tokenCounting: false,
11555
+ modelListing: false
11556
+ };
11557
+ return {
11558
+ capabilities,
11559
+ descriptor: { providerId: this.identity.providerId, providerRevision: this.identity.providerRevision }
11560
+ };
11561
+ }
11562
+ descriptorDocument() {
11563
+ return {
11564
+ schemaVersion: 1,
11565
+ providerId: this.identity.providerId,
11566
+ providerRevision: this.identity.providerRevision,
11567
+ models: [{ modelId: this.identity.defaultModel.modelId, revision: this.identity.defaultModel.revision }],
11568
+ capabilities: {
11569
+ streaming: true,
11570
+ tools: true,
11571
+ parallelToolCalls: true,
11572
+ cancellation: true,
11573
+ structuredOutput: false
11574
+ },
11575
+ remoteState: { storage: false, retention: false, continuation: false }
11576
+ };
11577
+ }
11578
+ async* stream(request, opts) {
11579
+ let sequence = 0;
11580
+ const stamp = (body) => ({ ...body, sequence: sequence++, attemptId: opts.attemptId });
11581
+ const errorEvent = (error) => stamp({ kind: "provider_error", error });
11582
+ const grant = this.deps.grant;
11583
+ if (grant === undefined || grant.network !== true) {
11584
+ yield errorEvent({
11585
+ kind: "invalid_request",
11586
+ retryable: retryableFor2("invalid_request", false),
11587
+ message: `a network capability grant is required to reach the ${this.label} API`
11588
+ });
11589
+ return;
11590
+ }
11591
+ const baseUrl = grant.baseUrl ?? this.identity.defaultBaseUrl;
11592
+ let host;
11593
+ try {
11594
+ host = new URL(baseUrl).hostname;
11595
+ } catch {
11596
+ host = baseUrl;
11597
+ }
11598
+ const permitted = !isPrivateEgressHost(host) || grant.allowLoopback === true && isLoopbackHost(host);
11599
+ if (!permitted) {
11600
+ yield errorEvent({
11601
+ kind: "invalid_request",
11602
+ retryable: retryableFor2("invalid_request", false),
11603
+ message: `egress to a private/loopback/link-local/metadata host is denied: ${host}`
11604
+ });
11605
+ return;
11606
+ }
11607
+ const url = `${baseUrl.replace(/\/+$/, "")}${grant.chatPath ?? "/v1/chat/completions"}`;
11608
+ const messages = [];
11609
+ if (request.systemInstruction.length > 0) {
11610
+ messages.push({ role: "system", content: request.systemInstruction });
11611
+ }
11612
+ for (const linked of linkToolCalls(request.messages)) {
11613
+ const message = linked.message;
11614
+ if (message.role === "tool") {
11615
+ if (linked.linkedToolCallId !== undefined) {
11616
+ messages.push({ role: "tool", tool_call_id: linked.linkedToolCallId, content: message.content });
11617
+ continue;
11618
+ }
11619
+ messages.push({ role: "user", content: `Tool result:
11620
+ ${message.content}` });
11621
+ continue;
11622
+ }
11623
+ if (message.role === "assistant" && message.content.length === 0 && linked.linkedCalls.length === 0) {
11624
+ continue;
11625
+ }
11626
+ if (message.role === "assistant" && linked.linkedCalls.length > 0) {
11627
+ messages.push({
11628
+ role: "assistant",
11629
+ content: message.content,
11630
+ tool_calls: linked.linkedCalls.map((call) => ({
11631
+ id: call.id,
11632
+ type: "function",
11633
+ function: { name: call.name, arguments: call.arguments }
11634
+ }))
11635
+ });
11636
+ continue;
11637
+ }
11638
+ messages.push({ role: message.role, content: message.content });
11639
+ }
11640
+ const payload = {
11641
+ model: request.modelId,
11642
+ stream: true,
11643
+ messages,
11644
+ ...request.tools !== undefined ? {
11645
+ tools: request.tools.map((tool) => ({
11646
+ type: "function",
11647
+ function: {
11648
+ name: tool.name,
11649
+ ...tool.description !== undefined ? { description: tool.description } : {},
11650
+ parameters: tool.inputSchema
11651
+ }
11652
+ }))
11653
+ } : {}
11654
+ };
11655
+ const headers = { "content-type": "application/json" };
11656
+ if (grant.apiKey !== undefined && grant.apiKey.length > 0) {
11657
+ headers.authorization = `Bearer ${grant.apiKey}`;
11658
+ }
11659
+ if (grant.headers !== undefined) {
11660
+ Object.assign(headers, grant.headers);
11661
+ }
11662
+ const init = {
11663
+ method: "POST",
11664
+ headers,
11665
+ body: JSON.stringify(payload),
11666
+ ...opts.signal !== undefined ? { signal: opts.signal } : {}
11667
+ };
11668
+ let response;
11669
+ try {
11670
+ response = await this.deps.fetch(url, init);
11671
+ } catch (cause) {
11672
+ if (opts.signal?.aborted === true) {
11673
+ yield errorEvent({ kind: "cancelled", retryable: retryableFor2("cancelled", false), message: "attempt cancelled" });
11674
+ return;
11675
+ }
11676
+ yield errorEvent({
11677
+ kind: "unavailable",
11678
+ retryable: retryableFor2("unavailable", true),
11679
+ message: `network request to the ${this.label} API failed: ${String(cause)}`
11680
+ });
11681
+ return;
11682
+ }
11683
+ if (!response.ok) {
11684
+ const error = classifyHttpError2(response.status);
11685
+ let providerMessage = `${this.label} API returned HTTP ${response.status}`;
11686
+ try {
11687
+ const parsed = asRecord3(JSON.parse(await response.text()));
11688
+ const detail = asString3(asRecord3(parsed.error).message);
11689
+ if (detail !== undefined && detail.length > 0) {
11690
+ providerMessage = detail;
11691
+ }
11692
+ } catch {}
11693
+ error.message = providerMessage;
11694
+ yield stamp({ kind: "provider_error", error });
11695
+ return;
11696
+ }
11697
+ let bodyText;
11698
+ try {
11699
+ bodyText = await response.text();
11700
+ } catch (cause) {
11701
+ const aborted = opts.signal?.aborted === true || typeof cause === "object" && cause !== null && cause.name === "AbortError";
11702
+ if (aborted) {
11703
+ yield errorEvent({ kind: "cancelled", retryable: retryableFor2("cancelled", false), message: "attempt cancelled" });
11704
+ return;
11705
+ }
11706
+ yield errorEvent({
11707
+ kind: "malformed",
11708
+ retryable: retryableFor2("malformed", false),
11709
+ message: `${this.label} SSE body read failed: ${String(cause)}`
11710
+ });
11711
+ return;
11712
+ }
11713
+ if (bodyText.length === 0) {
11714
+ yield errorEvent({
11715
+ kind: "malformed",
11716
+ retryable: retryableFor2("malformed", false),
11717
+ message: "empty response body"
11718
+ });
11719
+ return;
11720
+ }
11721
+ const parser = new AnthropicSSEParser;
11722
+ const records = parser.push(bodyText);
11723
+ const torn = parser.flush();
11724
+ const bodies = [];
11725
+ let sawStart = false;
11726
+ let sawFinish = false;
11727
+ let sawDone = false;
11728
+ let malformed;
11729
+ const pendingTools = new Map;
11730
+ const toolCallKey = (toolCall) => {
11731
+ const index = asNumber3(toolCall.index);
11732
+ if (index !== undefined) {
11733
+ return `idx:${index}`;
11734
+ }
11735
+ const id = asString3(toolCall.id);
11736
+ if (id !== undefined && id.length > 0) {
11737
+ return `id:${id}`;
11738
+ }
11739
+ return "idx:0";
11740
+ };
11741
+ const flushPendingToolEnds = () => {
11742
+ for (const acc of pendingTools.values()) {
11743
+ if (acc.ended) {
11744
+ continue;
11745
+ }
11746
+ if (!acc.started) {
11747
+ const startBody = { kind: "tool_call_start", toolCallId: acc.id };
11748
+ if (acc.name.length > 0) {
11749
+ startBody.toolName = acc.name;
11750
+ }
11751
+ bodies.push(startBody);
11752
+ acc.started = true;
11753
+ }
11754
+ bodies.push({ kind: "tool_call_end", toolCallId: acc.id, input: acc.arguments });
11755
+ acc.ended = true;
11756
+ }
11757
+ pendingTools.clear();
11758
+ };
11759
+ for (const record of records) {
11760
+ const trimmed = record.data.trim();
11761
+ if (trimmed === "[DONE]") {
11762
+ sawDone = true;
11763
+ flushPendingToolEnds();
11764
+ continue;
11765
+ }
11766
+ if (!sawStart) {
11767
+ sawStart = true;
11768
+ bodies.push({ kind: "model_start" });
11769
+ }
11770
+ let parsed;
11771
+ try {
11772
+ parsed = JSON.parse(record.data);
11773
+ } catch {
11774
+ malformed = {
11775
+ kind: "malformed",
11776
+ retryable: retryableFor2("malformed", false),
11777
+ message: `${this.label} SSE data line was not valid JSON`
11778
+ };
11779
+ break;
11780
+ }
11781
+ const data = asRecord3(parsed);
11782
+ if (data.usage !== undefined) {
11783
+ const usage = asRecord3(data.usage);
11784
+ bodies.push({
11785
+ kind: "usage_update",
11786
+ usage: mergeUsage2(asNumber3(usage.prompt_tokens), asNumber3(usage.completion_tokens), asNumber3(usage.total_tokens))
11787
+ });
11788
+ }
11789
+ const choice0 = asRecord3(asArray(data.choices)[0]);
11790
+ const delta = asRecord3(choice0.delta);
11791
+ const reasoning = asString3(delta.reasoning) ?? asString3(delta.reasoning_content);
11792
+ if (reasoning !== undefined && reasoning.length > 0) {
11793
+ bodies.push({ kind: "reasoning_delta", text: reasoning });
11794
+ }
11795
+ const content = asString3(delta.content);
11796
+ if (content !== undefined && content.length > 0) {
11797
+ bodies.push({ kind: "text_delta", text: content });
11798
+ }
11799
+ for (const rawToolCall of asArray(delta.tool_calls)) {
11800
+ const toolCall = asRecord3(rawToolCall);
11801
+ const fn = asRecord3(toolCall.function);
11802
+ const toolCallId = asString3(toolCall.id);
11803
+ const toolName = asString3(fn.name);
11804
+ const argumentsFragment = asString3(fn.arguments) ?? "";
11805
+ const key = toolCallKey(toolCall);
11806
+ let acc = pendingTools.get(key);
11807
+ if (acc === undefined) {
11808
+ acc = {
11809
+ id: toolCallId ?? `call_${key.replace(/[^a-zA-Z0-9_:-]/g, "_")}`,
11810
+ name: toolName ?? "",
11811
+ arguments: "",
11812
+ started: false,
11813
+ ended: false
11814
+ };
11815
+ pendingTools.set(key, acc);
11816
+ }
11817
+ if (toolCallId !== undefined && toolCallId.length > 0) {
11818
+ acc.id = toolCallId;
11819
+ }
11820
+ if (toolName !== undefined && toolName.length > 0) {
11821
+ acc.name = toolName;
11822
+ }
11823
+ if (!acc.started) {
11824
+ const startBody = { kind: "tool_call_start", toolCallId: acc.id };
11825
+ if (acc.name.length > 0) {
11826
+ startBody.toolName = acc.name;
11827
+ }
11828
+ bodies.push(startBody);
11829
+ acc.started = true;
11830
+ }
11831
+ if (argumentsFragment.length > 0) {
11832
+ acc.arguments += argumentsFragment;
11833
+ bodies.push({
11834
+ kind: "tool_call_delta",
11835
+ toolCallId: acc.id,
11836
+ inputDelta: argumentsFragment
11837
+ });
11838
+ }
11839
+ }
11840
+ const finishReason = asString3(choice0.finish_reason);
11841
+ if (finishReason !== undefined && finishReason.length > 0) {
11842
+ sawFinish = true;
11843
+ flushPendingToolEnds();
11844
+ }
11845
+ }
11846
+ if (malformed === undefined && torn.length > 0) {
11847
+ malformed = {
11848
+ kind: "malformed",
11849
+ retryable: retryableFor2("malformed", false),
11850
+ message: `${this.label} SSE stream ended mid-record (torn stream)`
11851
+ };
11852
+ }
11853
+ if (malformed === undefined) {
11854
+ flushPendingToolEnds();
11855
+ }
11856
+ if (malformed === undefined && sawStart && (sawDone || sawFinish)) {
11857
+ bodies.push({ kind: "model_end" });
11858
+ }
11859
+ for (const body of bodies) {
11860
+ if (opts.signal?.aborted === true) {
11861
+ yield errorEvent({ kind: "cancelled", retryable: retryableFor2("cancelled", false), message: "attempt cancelled" });
11862
+ return;
11863
+ }
11864
+ yield stamp(body);
11865
+ }
11866
+ if (opts.signal?.aborted === true) {
11867
+ yield errorEvent({ kind: "cancelled", retryable: retryableFor2("cancelled", false), message: "attempt cancelled" });
11868
+ return;
11869
+ }
11870
+ if (malformed !== undefined) {
11871
+ yield stamp({ kind: "provider_error", error: malformed });
11872
+ }
11873
+ }
11874
+ }
11875
+ var init_openai_compat_provider = __esm(() => {
11876
+ init_guard2();
11877
+ init_provider_port();
11878
+ });
11879
+
11457
11880
  // src/harness/provider/fake-provider.ts
11458
11881
  import { createHash as createHash5 } from "crypto";
11459
- function isPlainObject4(value) {
11882
+ function isPlainObject5(value) {
11460
11883
  return typeof value === "object" && value !== null && !Array.isArray(value);
11461
11884
  }
11462
11885
  function canonicalize(value) {
@@ -11483,7 +11906,7 @@ function requestHashOf(request) {
11483
11906
  }
11484
11907
  function extractUnknownExtensions(payload) {
11485
11908
  const provider = payload.provider;
11486
- if (!isPlainObject4(provider)) {
11909
+ if (!isPlainObject5(provider)) {
11487
11910
  return;
11488
11911
  }
11489
11912
  const extensions = {};
@@ -11561,7 +11984,7 @@ class FakeProvider {
11561
11984
  }
11562
11985
  case "tool_call": {
11563
11986
  const input2 = payload.input;
11564
- if (!isPlainObject4(input2)) {
11987
+ if (!isPlainObject5(input2)) {
11565
11988
  yield emit({
11566
11989
  kind: "provider_error",
11567
11990
  error: {
@@ -11587,7 +12010,7 @@ class FakeProvider {
11587
12010
  }
11588
12011
  case "finish": {
11589
12012
  const usage = payload.usage;
11590
- if (isPlainObject4(usage)) {
12013
+ if (isPlainObject5(usage)) {
11591
12014
  yield emit({ kind: "usage_update", usage: mapUsage(usage) });
11592
12015
  }
11593
12016
  const body = { kind: "model_end" };
@@ -11615,49 +12038,143 @@ var init_fake_provider = __esm(() => {
11615
12038
  init_provider_port();
11616
12039
  });
11617
12040
 
11618
- // src/harness/provider/ollama/ollama-provider.ts
11619
- function isPlainObject5(value) {
12041
+ // src/harness/provider/gemini/gemini-provider.ts
12042
+ function isPlainObject6(value) {
11620
12043
  return typeof value === "object" && value !== null && !Array.isArray(value);
11621
12044
  }
11622
- function asRecord3(value) {
11623
- return isPlainObject5(value) ? value : {};
12045
+ function asRecord4(value) {
12046
+ return isPlainObject6(value) ? value : {};
11624
12047
  }
11625
- function asArray(value) {
12048
+ function asArray2(value) {
11626
12049
  return Array.isArray(value) ? value : [];
11627
12050
  }
11628
- function asString3(value) {
12051
+ function asString4(value) {
11629
12052
  return typeof value === "string" ? value : undefined;
11630
12053
  }
11631
- function asNumber3(value) {
12054
+ function asNumber4(value) {
11632
12055
  return typeof value === "number" && Number.isFinite(value) ? value : undefined;
11633
12056
  }
11634
- function retryableFor2(kind, fallback) {
12057
+ function asBoolean(value) {
12058
+ return value === true;
12059
+ }
12060
+ function toGeminiContents(messages) {
12061
+ const out = [];
12062
+ for (const linked of linkToolCalls(messages)) {
12063
+ const message = linked.message;
12064
+ if (message.role === "system") {
12065
+ continue;
12066
+ }
12067
+ if (message.role === "assistant" && message.content.length === 0 && linked.linkedCalls.length === 0) {
12068
+ continue;
12069
+ }
12070
+ if (message.role === "tool" && linked.linkedToolCallId !== undefined) {
12071
+ const call = linked.message.toolCallId;
12072
+ out.push({
12073
+ role: "user",
12074
+ parts: [
12075
+ {
12076
+ functionResponse: {
12077
+ name: findToolName(messages, linked.linkedToolCallId) ?? "",
12078
+ id: call,
12079
+ response: parseFunctionResponse(message.content)
12080
+ }
12081
+ }
12082
+ ]
12083
+ });
12084
+ continue;
12085
+ }
12086
+ if (message.role === "assistant" && linked.linkedCalls.length > 0) {
12087
+ const parts = [];
12088
+ if (message.content.length > 0) {
12089
+ parts.push({ text: message.content });
12090
+ }
12091
+ for (const call of linked.linkedCalls) {
12092
+ parts.push({
12093
+ functionCall: { name: call.name, id: call.id, args: parseToolInput2(call.arguments) }
12094
+ });
12095
+ }
12096
+ out.push({ role: "model", parts });
12097
+ continue;
12098
+ }
12099
+ out.push({
12100
+ role: message.role === "assistant" ? "model" : "user",
12101
+ parts: [{ text: message.content }]
12102
+ });
12103
+ }
12104
+ return out;
12105
+ }
12106
+ function findToolName(messages, toolCallId) {
12107
+ for (const message of messages) {
12108
+ if (message.role !== "assistant" || !Array.isArray(message.toolCalls)) {
12109
+ continue;
12110
+ }
12111
+ const match = message.toolCalls.find((call) => call.id === toolCallId);
12112
+ if (match !== undefined) {
12113
+ return match.name;
12114
+ }
12115
+ }
12116
+ return;
12117
+ }
12118
+ function parseFunctionResponse(rawContent) {
12119
+ if (rawContent.trim().length === 0) {
12120
+ return {};
12121
+ }
12122
+ try {
12123
+ const parsed = JSON.parse(rawContent);
12124
+ return isPlainObject6(parsed) ? parsed : { result: parsed };
12125
+ } catch {
12126
+ return { result: rawContent };
12127
+ }
12128
+ }
12129
+ function parseToolInput2(rawArguments) {
12130
+ if (rawArguments.trim().length === 0) {
12131
+ return {};
12132
+ }
12133
+ try {
12134
+ return asRecord4(JSON.parse(rawArguments));
12135
+ } catch {
12136
+ return {};
12137
+ }
12138
+ }
12139
+ function retryableFor3(kind, fallback) {
11635
12140
  const concrete = defaultRetryable(kind);
11636
12141
  return concrete === undefined ? fallback : concrete;
11637
12142
  }
11638
- function mergeUsage2(promptTokens, completionTokens, totalTokens) {
12143
+ function mergeUsage3(promptTokens, candidatesTokens, totalTokens) {
11639
12144
  const usage = { exact: true };
11640
12145
  if (promptTokens !== undefined) {
11641
12146
  usage.inputTokens = promptTokens;
11642
12147
  }
11643
- if (completionTokens !== undefined) {
11644
- usage.outputTokens = completionTokens;
12148
+ if (candidatesTokens !== undefined) {
12149
+ usage.outputTokens = candidatesTokens;
11645
12150
  }
11646
12151
  if (totalTokens !== undefined) {
11647
12152
  usage.totalTokens = totalTokens;
11648
- } else if (promptTokens !== undefined || completionTokens !== undefined) {
11649
- usage.totalTokens = (promptTokens ?? 0) + (completionTokens ?? 0);
12153
+ } else if (promptTokens !== undefined || candidatesTokens !== undefined) {
12154
+ usage.totalTokens = (promptTokens ?? 0) + (candidatesTokens ?? 0);
11650
12155
  }
11651
12156
  return usage;
11652
12157
  }
11653
- function classifyHttpError2(status) {
11654
- if (status >= 500) {
11655
- return { kind: "unavailable", retryable: retryableFor2("unavailable", true), message: "" };
12158
+ function classifyGeminiError(httpStatus, rpcStatus) {
12159
+ if (rpcStatus === "UNAUTHENTICATED" || httpStatus === 401 || httpStatus === 403) {
12160
+ return { kind: "authentication", retryable: retryableFor3("authentication", false), message: "" };
11656
12161
  }
11657
- return { kind: "invalid_request", retryable: retryableFor2("invalid_request", false), message: "" };
12162
+ if (rpcStatus === "RESOURCE_EXHAUSTED" || httpStatus === 429) {
12163
+ return { kind: "rate_limit", retryable: retryableFor3("rate_limit", true), message: "" };
12164
+ }
12165
+ if (rpcStatus === "UNAVAILABLE" || httpStatus >= 500) {
12166
+ return { kind: "unavailable", retryable: retryableFor3("unavailable", true), message: "" };
12167
+ }
12168
+ if (rpcStatus === "INVALID_ARGUMENT" || httpStatus === 400) {
12169
+ return { kind: "invalid_request", retryable: retryableFor3("invalid_request", false), message: "" };
12170
+ }
12171
+ if (httpStatus >= 400) {
12172
+ return { kind: "invalid_request", retryable: retryableFor3("invalid_request", false), message: "" };
12173
+ }
12174
+ return { kind: "unknown", retryable: retryableFor3("unknown", false), message: "" };
11658
12175
  }
11659
12176
 
11660
- class OllamaProvider {
12177
+ class GeminiProvider {
11661
12178
  deps;
11662
12179
  constructor(deps) {
11663
12180
  this.deps = deps;
@@ -11667,22 +12184,22 @@ class OllamaProvider {
11667
12184
  streaming: true,
11668
12185
  toolCalls: true,
11669
12186
  parallelToolCalls: true,
11670
- structuredOutput: false,
11671
- reasoningMetadata: false,
11672
- promptCaching: false,
11673
- vision: false,
12187
+ structuredOutput: true,
12188
+ reasoningMetadata: true,
12189
+ promptCaching: true,
12190
+ vision: true,
11674
12191
  tokenCounting: false,
11675
12192
  modelListing: false
11676
12193
  };
11677
12194
  return {
11678
12195
  capabilities,
11679
- descriptor: { providerId: "ollama", providerRevision: PROVIDER_REVISION2 }
12196
+ descriptor: { providerId: "gemini", providerRevision: PROVIDER_REVISION2 }
11680
12197
  };
11681
12198
  }
11682
12199
  descriptorDocument() {
11683
12200
  return {
11684
12201
  schemaVersion: 1,
11685
- providerId: "ollama",
12202
+ providerId: "gemini",
11686
12203
  providerRevision: PROVIDER_REVISION2,
11687
12204
  models: [{ modelId: DEFAULT_MODEL2.modelId, revision: DEFAULT_MODEL2.revision }],
11688
12205
  capabilities: {
@@ -11690,7 +12207,7 @@ class OllamaProvider {
11690
12207
  tools: true,
11691
12208
  parallelToolCalls: true,
11692
12209
  cancellation: true,
11693
- structuredOutput: false
12210
+ structuredOutput: true
11694
12211
  },
11695
12212
  remoteState: { storage: false, retention: false, continuation: false }
11696
12213
  };
@@ -11700,11 +12217,12 @@ class OllamaProvider {
11700
12217
  const stamp = (body) => ({ ...body, sequence: sequence++, attemptId: opts.attemptId });
11701
12218
  const errorEvent = (error) => stamp({ kind: "provider_error", error });
11702
12219
  const grant = this.deps.grant;
11703
- if (grant === undefined || grant.network !== true) {
12220
+ const redact = (message) => grant !== undefined && grant.apiKey.length > 0 ? message.split(grant.apiKey).join("[redacted]") : message;
12221
+ if (grant === undefined || grant.network !== true || typeof grant.apiKey !== "string" || grant.apiKey.length === 0) {
11704
12222
  yield errorEvent({
11705
- kind: "invalid_request",
11706
- retryable: retryableFor2("invalid_request", false),
11707
- message: "a network capability grant is required to reach the Ollama API"
12223
+ kind: "authentication",
12224
+ retryable: retryableFor3("authentication", false),
12225
+ message: "network capability grant with an apiKey is required to reach the Gemini API"
11708
12226
  });
11709
12227
  return;
11710
12228
  }
@@ -11715,70 +12233,459 @@ class OllamaProvider {
11715
12233
  } catch {
11716
12234
  host = baseUrl;
11717
12235
  }
11718
- const permitted = !isPrivateEgressHost(host) || grant.allowLoopback === true && isLoopbackHost(host);
11719
- if (!permitted) {
12236
+ if (isPrivateEgressHost(host)) {
11720
12237
  yield errorEvent({
11721
12238
  kind: "invalid_request",
11722
- retryable: retryableFor2("invalid_request", false),
11723
- message: `egress to a private/loopback/link-local/metadata host is denied: ${host}`
12239
+ retryable: retryableFor3("invalid_request", false),
12240
+ message: redact(`egress to a private/loopback/link-local/metadata host is denied: ${host}`)
11724
12241
  });
11725
12242
  return;
11726
12243
  }
11727
- const url = `${baseUrl.replace(/\/+$/, "")}${grant.chatPath ?? "/v1/chat/completions"}`;
11728
- const messages = [];
11729
- if (request.systemInstruction.length > 0) {
11730
- messages.push({ role: "system", content: request.systemInstruction });
12244
+ const url = `${baseUrl.replace(/\/+$/, "")}/v1beta/models/${encodeURIComponent(request.modelId)}:streamGenerateContent?alt=sse`;
12245
+ const headers = {
12246
+ "x-goog-api-key": grant.apiKey,
12247
+ "content-type": "application/json"
12248
+ };
12249
+ const payload = {
12250
+ contents: toGeminiContents(request.messages),
12251
+ ...request.systemInstruction.length > 0 ? { systemInstruction: { parts: [{ text: request.systemInstruction }] } } : {},
12252
+ ...request.tools !== undefined && request.tools.length > 0 ? {
12253
+ tools: [
12254
+ {
12255
+ functionDeclarations: request.tools.map((tool) => ({
12256
+ name: tool.name,
12257
+ ...tool.description !== undefined ? { description: tool.description } : {},
12258
+ parameters: tool.inputSchema
12259
+ }))
12260
+ }
12261
+ ]
12262
+ } : {},
12263
+ generationConfig: {
12264
+ maxOutputTokens: request.budget.maxOutputTokens,
12265
+ ...request.options?.temperature !== undefined ? { temperature: request.options.temperature } : {}
12266
+ }
12267
+ };
12268
+ const init = {
12269
+ method: "POST",
12270
+ headers,
12271
+ body: JSON.stringify(payload),
12272
+ ...opts.signal !== undefined ? { signal: opts.signal } : {}
12273
+ };
12274
+ let response;
12275
+ try {
12276
+ response = await this.deps.fetch(url, init);
12277
+ } catch (cause) {
12278
+ if (opts.signal?.aborted === true) {
12279
+ yield errorEvent({ kind: "cancelled", retryable: retryableFor3("cancelled", false), message: "attempt cancelled" });
12280
+ return;
12281
+ }
12282
+ yield errorEvent({
12283
+ kind: "unavailable",
12284
+ retryable: retryableFor3("unavailable", true),
12285
+ message: redact(`network request to the Gemini API failed: ${String(cause)}`)
12286
+ });
12287
+ return;
11731
12288
  }
11732
- for (const linked of linkToolCalls(request.messages)) {
11733
- const message = linked.message;
11734
- if (message.role === "tool") {
11735
- if (linked.linkedToolCallId !== undefined) {
11736
- messages.push({ role: "tool", tool_call_id: linked.linkedToolCallId, content: message.content });
12289
+ if (!response.ok) {
12290
+ let error;
12291
+ let providerMessage = `Gemini API returned HTTP ${response.status}`;
12292
+ try {
12293
+ const parsed = asRecord4(JSON.parse(await response.text()));
12294
+ const envelope = asRecord4(parsed.error);
12295
+ const rpcStatus = asString4(envelope.status);
12296
+ const httpCode = asNumber4(envelope.code) ?? response.status;
12297
+ error = classifyGeminiError(httpCode, rpcStatus);
12298
+ const detail = asString4(envelope.message);
12299
+ if (detail !== undefined && detail.length > 0) {
12300
+ providerMessage = detail;
12301
+ }
12302
+ } catch {
12303
+ error = classifyGeminiError(response.status, undefined);
12304
+ }
12305
+ error.message = redact(providerMessage);
12306
+ yield stamp({ kind: "provider_error", error });
12307
+ return;
12308
+ }
12309
+ let bodyText;
12310
+ try {
12311
+ bodyText = await response.text();
12312
+ } catch (cause) {
12313
+ const aborted = opts.signal?.aborted === true || typeof cause === "object" && cause !== null && cause.name === "AbortError";
12314
+ if (aborted) {
12315
+ yield errorEvent({ kind: "cancelled", retryable: retryableFor3("cancelled", false), message: "attempt cancelled" });
12316
+ return;
12317
+ }
12318
+ yield errorEvent({
12319
+ kind: "malformed",
12320
+ retryable: retryableFor3("malformed", false),
12321
+ message: redact(`Gemini SSE body read failed: ${String(cause)}`)
12322
+ });
12323
+ return;
12324
+ }
12325
+ if (bodyText.length === 0) {
12326
+ yield errorEvent({
12327
+ kind: "malformed",
12328
+ retryable: retryableFor3("malformed", false),
12329
+ message: redact("empty response body")
12330
+ });
12331
+ return;
12332
+ }
12333
+ const parser = new AnthropicSSEParser;
12334
+ const records = parser.push(bodyText);
12335
+ const torn = parser.flush();
12336
+ const bodies = [];
12337
+ let sawFirstChunk = false;
12338
+ let sawFinish = false;
12339
+ let malformed;
12340
+ let promptTokens;
12341
+ let candidatesTokens;
12342
+ let totalTokens;
12343
+ let cachedContentTokens;
12344
+ let thoughtsTokens;
12345
+ for (const record of records) {
12346
+ let parsed;
12347
+ try {
12348
+ parsed = JSON.parse(record.data);
12349
+ } catch {
12350
+ malformed = {
12351
+ kind: "malformed",
12352
+ retryable: retryableFor3("malformed", false),
12353
+ message: redact("Gemini SSE data line was not valid JSON")
12354
+ };
12355
+ break;
12356
+ }
12357
+ const chunk = asRecord4(parsed);
12358
+ if (!sawFirstChunk) {
12359
+ sawFirstChunk = true;
12360
+ bodies.push({ kind: "model_start" });
12361
+ }
12362
+ const candidates = asArray2(chunk.candidates);
12363
+ const firstCandidate = asRecord4(candidates[0]);
12364
+ const content = asRecord4(firstCandidate.content);
12365
+ const parts = asArray2(content.parts);
12366
+ for (const rawPart of parts) {
12367
+ const part = asRecord4(rawPart);
12368
+ const text = asString4(part.text);
12369
+ if (text !== undefined) {
12370
+ if (asBoolean(part.thought)) {
12371
+ bodies.push({ kind: "reasoning_delta", text });
12372
+ } else {
12373
+ bodies.push({ kind: "text_delta", text });
12374
+ }
11737
12375
  continue;
11738
12376
  }
11739
- messages.push({ role: "user", content: `Tool result:
11740
- ${message.content}` });
11741
- continue;
12377
+ const functionCall = asRecord4(part.functionCall);
12378
+ const callName = asString4(functionCall.name);
12379
+ if (callName !== undefined) {
12380
+ const callId = asString4(functionCall.id) ?? callName;
12381
+ const argsInput = JSON.stringify(asRecord4(functionCall.args));
12382
+ bodies.push({ kind: "tool_call_start", toolCallId: callId, toolName: callName });
12383
+ bodies.push({ kind: "tool_call_end", toolCallId: callId, input: argsInput });
12384
+ }
11742
12385
  }
11743
- if (message.role === "assistant" && message.content.length === 0 && linked.linkedCalls.length === 0) {
12386
+ const usageMetadata = asRecord4(chunk.usageMetadata);
12387
+ if (Object.keys(usageMetadata).length > 0) {
12388
+ promptTokens = asNumber4(usageMetadata.promptTokenCount) ?? promptTokens;
12389
+ candidatesTokens = asNumber4(usageMetadata.candidatesTokenCount) ?? candidatesTokens;
12390
+ totalTokens = asNumber4(usageMetadata.totalTokenCount) ?? totalTokens;
12391
+ cachedContentTokens = asNumber4(usageMetadata.cachedContentTokenCount) ?? cachedContentTokens;
12392
+ thoughtsTokens = asNumber4(usageMetadata.thoughtsTokenCount) ?? thoughtsTokens;
12393
+ }
12394
+ const finishReason = asString4(firstCandidate.finishReason);
12395
+ if (finishReason !== undefined && finishReason.length > 0) {
12396
+ sawFinish = true;
12397
+ }
12398
+ }
12399
+ if (promptTokens !== undefined || candidatesTokens !== undefined || totalTokens !== undefined) {
12400
+ const usage = mergeUsage3(promptTokens, candidatesTokens, totalTokens);
12401
+ const unknownExtensions = {};
12402
+ if (cachedContentTokens !== undefined) {
12403
+ unknownExtensions["gemini.cached_content_tokens"] = cachedContentTokens;
12404
+ }
12405
+ if (thoughtsTokens !== undefined) {
12406
+ unknownExtensions["gemini.thoughts_tokens"] = thoughtsTokens;
12407
+ }
12408
+ bodies.push({
12409
+ kind: "usage_update",
12410
+ usage,
12411
+ ...Object.keys(unknownExtensions).length > 0 ? { unknownExtensions } : {}
12412
+ });
12413
+ }
12414
+ if (sawFinish) {
12415
+ bodies.push({ kind: "model_end" });
12416
+ }
12417
+ if (malformed === undefined) {
12418
+ if (torn.length > 0) {
12419
+ malformed = {
12420
+ kind: "malformed",
12421
+ retryable: retryableFor3("malformed", false),
12422
+ message: redact("Gemini SSE stream ended mid-record (torn stream)")
12423
+ };
12424
+ } else if (sawFirstChunk && !sawFinish) {
12425
+ malformed = {
12426
+ kind: "malformed",
12427
+ retryable: retryableFor3("malformed", false),
12428
+ message: redact("Gemini SSE stream ended before a finishReason was reported (truncated stream)")
12429
+ };
12430
+ }
12431
+ }
12432
+ for (const body of bodies) {
12433
+ if (opts.signal?.aborted === true) {
12434
+ yield errorEvent({ kind: "cancelled", retryable: retryableFor3("cancelled", false), message: "attempt cancelled" });
12435
+ return;
12436
+ }
12437
+ yield stamp(body);
12438
+ }
12439
+ if (opts.signal?.aborted === true) {
12440
+ yield errorEvent({ kind: "cancelled", retryable: retryableFor3("cancelled", false), message: "attempt cancelled" });
12441
+ return;
12442
+ }
12443
+ if (malformed !== undefined) {
12444
+ yield stamp({ kind: "provider_error", error: malformed });
12445
+ }
12446
+ }
12447
+ }
12448
+ var DEFAULT_BASE_URL2 = "https://generativelanguage.googleapis.com", PROVIDER_REVISION2 = "gemini-2026-08-20", DEFAULT_MODEL2;
12449
+ var init_gemini_provider = __esm(() => {
12450
+ init_guard2();
12451
+ init_provider_port();
12452
+ DEFAULT_MODEL2 = {
12453
+ modelId: "gemini-2.5-flash",
12454
+ revision: "2.5"
12455
+ };
12456
+ });
12457
+
12458
+ // src/harness/provider/ollama/ollama-provider.ts
12459
+ class OllamaProvider {
12460
+ engine;
12461
+ constructor(deps) {
12462
+ this.engine = new OpenAiCompatEngine(deps, {
12463
+ defaultBaseUrl: DEFAULT_BASE_URL3,
12464
+ providerRevision: PROVIDER_REVISION3,
12465
+ providerId: "ollama",
12466
+ providerLabel: "Ollama",
12467
+ defaultModel: DEFAULT_MODEL3
12468
+ });
12469
+ }
12470
+ describe() {
12471
+ return this.engine.describe();
12472
+ }
12473
+ descriptorDocument() {
12474
+ return this.engine.descriptorDocument();
12475
+ }
12476
+ stream(request, opts) {
12477
+ return this.engine.stream(request, opts);
12478
+ }
12479
+ }
12480
+ var DEFAULT_BASE_URL3 = "http://localhost:11434", PROVIDER_REVISION3 = "ollama-2024-10-22", DEFAULT_MODEL3;
12481
+ var init_ollama_provider = __esm(() => {
12482
+ init_openai_compat_provider();
12483
+ DEFAULT_MODEL3 = {
12484
+ modelId: "llama3.1:latest",
12485
+ revision: "latest"
12486
+ };
12487
+ });
12488
+
12489
+ // src/harness/provider/openai/openai-provider.ts
12490
+ function isPlainObject7(value) {
12491
+ return typeof value === "object" && value !== null && !Array.isArray(value);
12492
+ }
12493
+ function asRecord5(value) {
12494
+ return isPlainObject7(value) ? value : {};
12495
+ }
12496
+ function asString5(value) {
12497
+ return typeof value === "string" ? value : undefined;
12498
+ }
12499
+ function asNumber5(value) {
12500
+ return typeof value === "number" && Number.isFinite(value) ? value : undefined;
12501
+ }
12502
+ function toResponsesInput(messages) {
12503
+ const out = [];
12504
+ for (const linked of linkToolCalls(messages)) {
12505
+ const message = linked.message;
12506
+ if (message.role === "tool") {
12507
+ if (linked.linkedToolCallId !== undefined) {
12508
+ out.push({ type: "function_call_output", call_id: linked.linkedToolCallId, output: message.content });
11744
12509
  continue;
11745
12510
  }
11746
- if (message.role === "assistant" && linked.linkedCalls.length > 0) {
11747
- messages.push({
12511
+ out.push({ type: "message", role: "user", content: [{ type: "input_text", text: message.content }] });
12512
+ continue;
12513
+ }
12514
+ if (message.role === "assistant" && message.content.length === 0 && linked.linkedCalls.length === 0) {
12515
+ continue;
12516
+ }
12517
+ if (message.role === "assistant" && linked.linkedCalls.length > 0) {
12518
+ if (message.content.length > 0) {
12519
+ out.push({
12520
+ type: "message",
11748
12521
  role: "assistant",
11749
- content: message.content,
11750
- tool_calls: linked.linkedCalls.map((call) => ({
11751
- id: call.id,
11752
- type: "function",
11753
- function: { name: call.name, arguments: call.arguments }
11754
- }))
12522
+ content: [{ type: "output_text", text: message.content }]
11755
12523
  });
11756
- continue;
11757
12524
  }
11758
- messages.push({ role: message.role, content: message.content });
12525
+ for (const call of linked.linkedCalls) {
12526
+ out.push({ type: "function_call", call_id: call.id, name: call.name, arguments: call.arguments });
12527
+ }
12528
+ continue;
12529
+ }
12530
+ out.push({
12531
+ type: "message",
12532
+ role: message.role === "assistant" ? "assistant" : "user",
12533
+ content: [{ type: message.role === "assistant" ? "output_text" : "input_text", text: message.content }]
12534
+ });
12535
+ }
12536
+ return out;
12537
+ }
12538
+ function retryableFor4(kind, fallback) {
12539
+ const concrete = defaultRetryable(kind);
12540
+ return concrete === undefined ? fallback : concrete;
12541
+ }
12542
+ function mergeUsage4(inputTokens, outputTokens, totalTokens) {
12543
+ const usage = { exact: true };
12544
+ if (inputTokens !== undefined) {
12545
+ usage.inputTokens = inputTokens;
12546
+ }
12547
+ if (outputTokens !== undefined) {
12548
+ usage.outputTokens = outputTokens;
12549
+ }
12550
+ if (totalTokens !== undefined) {
12551
+ usage.totalTokens = totalTokens;
12552
+ } else if (inputTokens !== undefined || outputTokens !== undefined) {
12553
+ usage.totalTokens = (inputTokens ?? 0) + (outputTokens ?? 0);
12554
+ }
12555
+ return usage;
12556
+ }
12557
+ function extractErrorFields(data) {
12558
+ const nested = asRecord5(data.error);
12559
+ const code = asString5(nested.code) ?? asString5(data.code);
12560
+ const message = asString5(nested.message) ?? asString5(data.message);
12561
+ const param = asString5(nested.param) ?? asString5(data.param);
12562
+ const out = {};
12563
+ if (code !== undefined) {
12564
+ out.code = code;
12565
+ }
12566
+ if (message !== undefined) {
12567
+ out.message = message;
12568
+ }
12569
+ if (param !== undefined) {
12570
+ out.param = param;
12571
+ }
12572
+ return out;
12573
+ }
12574
+ function classifyHttpError3(status, headers, code) {
12575
+ if (status === 401) {
12576
+ return { kind: "authentication", retryable: retryableFor4("authentication", false), message: "" };
12577
+ }
12578
+ if (status === 429) {
12579
+ const error = { kind: "rate_limit", retryable: retryableFor4("rate_limit", true), message: "" };
12580
+ const retryAfter = headers.get("retry-after");
12581
+ const seconds = retryAfter === null ? undefined : Number.parseInt(retryAfter, 10);
12582
+ if (seconds !== undefined && Number.isFinite(seconds)) {
12583
+ error.retryAfterMs = seconds * 1000;
12584
+ }
12585
+ return error;
12586
+ }
12587
+ if (status === 400) {
12588
+ if (code === "context_length_exceeded") {
12589
+ return { kind: "context_overflow", retryable: retryableFor4("context_overflow", false), message: "" };
11759
12590
  }
12591
+ return { kind: "invalid_request", retryable: retryableFor4("invalid_request", false), message: "" };
12592
+ }
12593
+ if (status >= 500) {
12594
+ return { kind: "unavailable", retryable: retryableFor4("unavailable", true), message: "" };
12595
+ }
12596
+ if (status >= 400) {
12597
+ return { kind: "invalid_request", retryable: retryableFor4("invalid_request", false), message: "" };
12598
+ }
12599
+ return { kind: "unknown", retryable: retryableFor4("unknown", false), message: "" };
12600
+ }
12601
+
12602
+ class OpenAiProvider {
12603
+ deps;
12604
+ constructor(deps) {
12605
+ this.deps = deps;
12606
+ }
12607
+ describe() {
12608
+ const capabilities = {
12609
+ streaming: true,
12610
+ toolCalls: true,
12611
+ parallelToolCalls: true,
12612
+ structuredOutput: true,
12613
+ reasoningMetadata: true,
12614
+ promptCaching: true,
12615
+ vision: false,
12616
+ tokenCounting: false,
12617
+ modelListing: false
12618
+ };
12619
+ return {
12620
+ capabilities,
12621
+ descriptor: { providerId: "openai", providerRevision: PROVIDER_REVISION4 }
12622
+ };
12623
+ }
12624
+ descriptorDocument() {
12625
+ return {
12626
+ schemaVersion: 1,
12627
+ providerId: "openai",
12628
+ providerRevision: PROVIDER_REVISION4,
12629
+ models: [{ modelId: DEFAULT_MODEL4.modelId, revision: DEFAULT_MODEL4.revision }],
12630
+ capabilities: {
12631
+ streaming: true,
12632
+ tools: true,
12633
+ parallelToolCalls: true,
12634
+ cancellation: true,
12635
+ structuredOutput: true
12636
+ },
12637
+ remoteState: { storage: false, retention: false, continuation: false }
12638
+ };
12639
+ }
12640
+ async* stream(request, opts) {
12641
+ let sequence = 0;
12642
+ const stamp = (body) => ({ ...body, sequence: sequence++, attemptId: opts.attemptId });
12643
+ const errorEvent = (error) => stamp({ kind: "provider_error", error });
12644
+ const grant = this.deps.grant;
12645
+ const redact = (message) => grant !== undefined && grant.apiKey.length > 0 ? message.split(grant.apiKey).join("[redacted]") : message;
12646
+ if (grant === undefined || grant.network !== true || typeof grant.apiKey !== "string" || grant.apiKey.length === 0) {
12647
+ yield errorEvent({
12648
+ kind: "authentication",
12649
+ retryable: retryableFor4("authentication", false),
12650
+ message: "network capability grant with an apiKey is required to reach the OpenAI API"
12651
+ });
12652
+ return;
12653
+ }
12654
+ const baseUrl = grant.baseUrl ?? DEFAULT_BASE_URL4;
12655
+ let host;
12656
+ try {
12657
+ host = new URL(baseUrl).hostname;
12658
+ } catch {
12659
+ host = baseUrl;
12660
+ }
12661
+ if (isPrivateEgressHost(host)) {
12662
+ yield errorEvent({
12663
+ kind: "invalid_request",
12664
+ retryable: retryableFor4("invalid_request", false),
12665
+ message: redact(`egress to a private/loopback/link-local/metadata host is denied: ${host}`)
12666
+ });
12667
+ return;
12668
+ }
12669
+ const url = `${baseUrl.replace(/\/+$/, "")}/v1/responses`;
12670
+ const headers = {
12671
+ authorization: `Bearer ${grant.apiKey}`,
12672
+ "content-type": "application/json"
12673
+ };
11760
12674
  const payload = {
11761
12675
  model: request.modelId,
12676
+ instructions: request.systemInstruction,
12677
+ input: toResponsesInput(request.messages),
11762
12678
  stream: true,
11763
- messages,
11764
12679
  ...request.tools !== undefined ? {
11765
12680
  tools: request.tools.map((tool) => ({
11766
12681
  type: "function",
11767
- function: {
11768
- name: tool.name,
11769
- ...tool.description !== undefined ? { description: tool.description } : {},
11770
- parameters: tool.inputSchema
11771
- }
11772
- }))
12682
+ name: tool.name,
12683
+ ...tool.description !== undefined ? { description: tool.description } : {},
12684
+ parameters: tool.inputSchema
12685
+ })),
12686
+ parallel_tool_calls: true
11773
12687
  } : {}
11774
12688
  };
11775
- const headers = { "content-type": "application/json" };
11776
- if (grant.apiKey !== undefined && grant.apiKey.length > 0) {
11777
- headers.authorization = `Bearer ${grant.apiKey}`;
11778
- }
11779
- if (grant.headers !== undefined) {
11780
- Object.assign(headers, grant.headers);
11781
- }
11782
12689
  const init = {
11783
12690
  method: "POST",
11784
12691
  headers,
@@ -11790,27 +12697,28 @@ ${message.content}` });
11790
12697
  response = await this.deps.fetch(url, init);
11791
12698
  } catch (cause) {
11792
12699
  if (opts.signal?.aborted === true) {
11793
- yield errorEvent({ kind: "cancelled", retryable: retryableFor2("cancelled", false), message: "attempt cancelled" });
12700
+ yield errorEvent({ kind: "cancelled", retryable: retryableFor4("cancelled", false), message: "attempt cancelled" });
11794
12701
  return;
11795
12702
  }
11796
12703
  yield errorEvent({
11797
12704
  kind: "unavailable",
11798
- retryable: retryableFor2("unavailable", true),
11799
- message: `network request to the Ollama API failed: ${String(cause)}`
12705
+ retryable: retryableFor4("unavailable", true),
12706
+ message: redact(`network request to the OpenAI API failed: ${String(cause)}`)
11800
12707
  });
11801
12708
  return;
11802
12709
  }
11803
12710
  if (!response.ok) {
11804
- const error = classifyHttpError2(response.status);
11805
- let providerMessage = `Ollama API returned HTTP ${response.status}`;
12711
+ let bodyRecord = {};
11806
12712
  try {
11807
- const parsed = asRecord3(JSON.parse(await response.text()));
11808
- const detail = asString3(asRecord3(parsed.error).message);
11809
- if (detail !== undefined && detail.length > 0) {
11810
- providerMessage = detail;
11811
- }
12713
+ bodyRecord = asRecord5(JSON.parse(await response.text()));
11812
12714
  } catch {}
11813
- error.message = providerMessage;
12715
+ const fields = extractErrorFields(bodyRecord);
12716
+ const error = classifyHttpError3(response.status, response.headers, fields.code);
12717
+ let providerMessage = `OpenAI API returned HTTP ${response.status}`;
12718
+ if (fields.message !== undefined && fields.message.length > 0) {
12719
+ providerMessage = fields.message;
12720
+ }
12721
+ error.message = redact(providerMessage);
11814
12722
  yield stamp({ kind: "provider_error", error });
11815
12723
  return;
11816
12724
  }
@@ -11820,21 +12728,21 @@ ${message.content}` });
11820
12728
  } catch (cause) {
11821
12729
  const aborted = opts.signal?.aborted === true || typeof cause === "object" && cause !== null && cause.name === "AbortError";
11822
12730
  if (aborted) {
11823
- yield errorEvent({ kind: "cancelled", retryable: retryableFor2("cancelled", false), message: "attempt cancelled" });
12731
+ yield errorEvent({ kind: "cancelled", retryable: retryableFor4("cancelled", false), message: "attempt cancelled" });
11824
12732
  return;
11825
12733
  }
11826
12734
  yield errorEvent({
11827
12735
  kind: "malformed",
11828
- retryable: retryableFor2("malformed", false),
11829
- message: `Ollama SSE body read failed: ${String(cause)}`
12736
+ retryable: retryableFor4("malformed", false),
12737
+ message: redact(`OpenAI SSE body read failed: ${String(cause)}`)
11830
12738
  });
11831
12739
  return;
11832
12740
  }
11833
12741
  if (bodyText.length === 0) {
11834
12742
  yield errorEvent({
11835
12743
  kind: "malformed",
11836
- retryable: retryableFor2("malformed", false),
11837
- message: "empty response body"
12744
+ retryable: retryableFor4("malformed", false),
12745
+ message: redact("empty response body")
11838
12746
  });
11839
12747
  return;
11840
12748
  }
@@ -11842,149 +12750,193 @@ ${message.content}` });
11842
12750
  const records = parser.push(bodyText);
11843
12751
  const torn = parser.flush();
11844
12752
  const bodies = [];
12753
+ const pendingTools = new Map;
11845
12754
  let sawStart = false;
11846
- let sawFinish = false;
11847
- let sawDone = false;
12755
+ let sawCompleted = false;
11848
12756
  let malformed;
11849
- const pendingTools = new Map;
11850
- const toolCallKey = (toolCall) => {
11851
- const index = asNumber3(toolCall.index);
11852
- if (index !== undefined) {
11853
- return `idx:${index}`;
11854
- }
11855
- const id = asString3(toolCall.id);
11856
- if (id !== undefined && id.length > 0) {
11857
- return `id:${id}`;
11858
- }
11859
- return "idx:0";
11860
- };
11861
- const flushPendingToolEnds = () => {
11862
- for (const acc of pendingTools.values()) {
11863
- if (acc.ended) {
11864
- continue;
11865
- }
11866
- if (!acc.started) {
11867
- const startBody = { kind: "tool_call_start", toolCallId: acc.id };
11868
- if (acc.name.length > 0) {
11869
- startBody.toolName = acc.name;
11870
- }
11871
- bodies.push(startBody);
11872
- acc.started = true;
11873
- }
11874
- bodies.push({ kind: "tool_call_end", toolCallId: acc.id, input: acc.arguments });
11875
- acc.ended = true;
11876
- }
11877
- pendingTools.clear();
11878
- };
12757
+ let terminalError;
11879
12758
  for (const record of records) {
11880
- const trimmed = record.data.trim();
11881
- if (trimmed === "[DONE]") {
11882
- sawDone = true;
11883
- flushPendingToolEnds();
11884
- continue;
11885
- }
11886
- if (!sawStart) {
11887
- sawStart = true;
11888
- bodies.push({ kind: "model_start" });
11889
- }
11890
12759
  let parsed;
11891
12760
  try {
11892
12761
  parsed = JSON.parse(record.data);
11893
12762
  } catch {
11894
12763
  malformed = {
11895
12764
  kind: "malformed",
11896
- retryable: retryableFor2("malformed", false),
11897
- message: "Ollama SSE data line was not valid JSON"
12765
+ retryable: retryableFor4("malformed", false),
12766
+ message: redact("OpenAI SSE data line was not valid JSON")
11898
12767
  };
11899
12768
  break;
11900
12769
  }
11901
- const data = asRecord3(parsed);
11902
- if (data.usage !== undefined) {
11903
- const usage = asRecord3(data.usage);
11904
- bodies.push({
11905
- kind: "usage_update",
11906
- usage: mergeUsage2(asNumber3(usage.prompt_tokens), asNumber3(usage.completion_tokens), asNumber3(usage.total_tokens))
11907
- });
11908
- }
11909
- const choice0 = asRecord3(asArray(data.choices)[0]);
11910
- const delta = asRecord3(choice0.delta);
11911
- const reasoning = asString3(delta.reasoning) ?? asString3(delta.reasoning_content);
11912
- if (reasoning !== undefined && reasoning.length > 0) {
11913
- bodies.push({ kind: "reasoning_delta", text: reasoning });
11914
- }
11915
- const content = asString3(delta.content);
11916
- if (content !== undefined && content.length > 0) {
11917
- bodies.push({ kind: "text_delta", text: content });
11918
- }
11919
- for (const rawToolCall of asArray(delta.tool_calls)) {
11920
- const toolCall = asRecord3(rawToolCall);
11921
- const fn = asRecord3(toolCall.function);
11922
- const toolCallId = asString3(toolCall.id);
11923
- const toolName = asString3(fn.name);
11924
- const argumentsFragment = asString3(fn.arguments) ?? "";
11925
- const key = toolCallKey(toolCall);
11926
- let acc = pendingTools.get(key);
11927
- if (acc === undefined) {
11928
- acc = {
11929
- id: toolCallId ?? `call_${key.replace(/[^a-zA-Z0-9_:-]/g, "_")}`,
11930
- name: toolName ?? "",
11931
- arguments: "",
11932
- started: false,
11933
- ended: false
11934
- };
11935
- pendingTools.set(key, acc);
12770
+ const data = asRecord5(parsed);
12771
+ const eventType = asString5(data.type) ?? asString5(record.event);
12772
+ switch (eventType) {
12773
+ case "response.created": {
12774
+ break;
11936
12775
  }
11937
- if (toolCallId !== undefined && toolCallId.length > 0) {
11938
- acc.id = toolCallId;
12776
+ case "response.output_item.added": {
12777
+ const item = asRecord5(data.item);
12778
+ if (asString5(item.type) === "function_call") {
12779
+ if (!sawStart) {
12780
+ sawStart = true;
12781
+ bodies.push({ kind: "model_start" });
12782
+ }
12783
+ const itemId = asString5(data.item_id) ?? asString5(item.id) ?? "";
12784
+ const callId = asString5(item.call_id) ?? itemId;
12785
+ const toolName = asString5(item.name);
12786
+ pendingTools.set(itemId, { callId, name: toolName ?? "", arguments: asString5(item.arguments) ?? "" });
12787
+ const startBody = { kind: "tool_call_start", toolCallId: callId };
12788
+ if (toolName !== undefined) {
12789
+ startBody.toolName = toolName;
12790
+ }
12791
+ bodies.push(startBody);
12792
+ }
12793
+ break;
11939
12794
  }
11940
- if (toolName !== undefined && toolName.length > 0) {
11941
- acc.name = toolName;
12795
+ case "response.output_text.delta": {
12796
+ if (!sawStart) {
12797
+ sawStart = true;
12798
+ bodies.push({ kind: "model_start" });
12799
+ }
12800
+ const text = asString5(data.delta);
12801
+ const body = { kind: "text_delta" };
12802
+ if (text !== undefined) {
12803
+ body.text = text;
12804
+ }
12805
+ bodies.push(body);
12806
+ break;
11942
12807
  }
11943
- if (!acc.started) {
11944
- const startBody = { kind: "tool_call_start", toolCallId: acc.id };
11945
- if (acc.name.length > 0) {
11946
- startBody.toolName = acc.name;
12808
+ case "response.reasoning_summary_text.delta": {
12809
+ if (!sawStart) {
12810
+ sawStart = true;
12811
+ bodies.push({ kind: "model_start" });
11947
12812
  }
11948
- bodies.push(startBody);
11949
- acc.started = true;
12813
+ const text = asString5(data.delta);
12814
+ if (text !== undefined && text.length > 0) {
12815
+ bodies.push({ kind: "reasoning_delta", text });
12816
+ }
12817
+ break;
11950
12818
  }
11951
- if (argumentsFragment.length > 0) {
11952
- acc.arguments += argumentsFragment;
11953
- bodies.push({
11954
- kind: "tool_call_delta",
11955
- toolCallId: acc.id,
11956
- inputDelta: argumentsFragment
11957
- });
12819
+ case "response.function_call_arguments.delta": {
12820
+ const itemId = asString5(data.item_id) ?? "";
12821
+ const fragment = asString5(data.delta) ?? "";
12822
+ const pending = pendingTools.get(itemId);
12823
+ const toolCallId = pending?.callId ?? itemId;
12824
+ if (pending !== undefined) {
12825
+ pending.arguments += fragment;
12826
+ }
12827
+ if (fragment.length > 0) {
12828
+ bodies.push({ kind: "tool_call_delta", toolCallId, inputDelta: fragment });
12829
+ }
12830
+ break;
12831
+ }
12832
+ case "response.function_call_arguments.done": {
12833
+ const itemId = asString5(data.item_id) ?? "";
12834
+ const pending = pendingTools.get(itemId);
12835
+ const fullArguments = asString5(data.arguments) ?? pending?.arguments ?? "";
12836
+ const toolCallId = pending?.callId ?? itemId;
12837
+ bodies.push({ kind: "tool_call_end", toolCallId, input: fullArguments });
12838
+ pendingTools.delete(itemId);
12839
+ break;
11958
12840
  }
12841
+ case "response.output_item.done": {
12842
+ const item = asRecord5(data.item);
12843
+ if (asString5(item.type) === "function_call") {
12844
+ const itemId = asString5(data.item_id) ?? asString5(item.id) ?? "";
12845
+ const pending = pendingTools.get(itemId);
12846
+ if (pending !== undefined) {
12847
+ const fullArguments = asString5(item.arguments) ?? pending.arguments;
12848
+ bodies.push({ kind: "tool_call_end", toolCallId: pending.callId, input: fullArguments });
12849
+ pendingTools.delete(itemId);
12850
+ }
12851
+ }
12852
+ break;
12853
+ }
12854
+ case "response.completed": {
12855
+ sawCompleted = true;
12856
+ const usage = asRecord5(asRecord5(data.response).usage);
12857
+ const inputTokens = asNumber5(usage.input_tokens);
12858
+ const outputTokens = asNumber5(usage.output_tokens);
12859
+ const totalTokens = asNumber5(usage.total_tokens);
12860
+ const reasoningTokens = asNumber5(asRecord5(usage.output_tokens_details).reasoning_tokens);
12861
+ const usageBody = {
12862
+ kind: "usage_update",
12863
+ usage: mergeUsage4(inputTokens, outputTokens, totalTokens)
12864
+ };
12865
+ if (reasoningTokens !== undefined) {
12866
+ usageBody.unknownExtensions = { "openai.reasoning_tokens": reasoningTokens };
12867
+ }
12868
+ bodies.push(usageBody);
12869
+ bodies.push({ kind: "model_end" });
12870
+ break;
12871
+ }
12872
+ case "response.failed":
12873
+ case "response.incomplete": {
12874
+ const fields = extractErrorFields(asRecord5(data.response));
12875
+ const code = fields.code;
12876
+ let kind = "unknown";
12877
+ if (code === "context_length_exceeded") {
12878
+ kind = "context_overflow";
12879
+ } else if (code !== undefined && code.length > 0) {
12880
+ kind = "invalid_request";
12881
+ }
12882
+ let message = fields.message;
12883
+ if (message === undefined || message.length === 0) {
12884
+ message = kind === "context_overflow" ? "OpenAI Responses API returned an error with no message; likely context-length overflow" : "OpenAI Responses API returned an error with no message";
12885
+ }
12886
+ terminalError = { kind, retryable: retryableFor4(kind, false), message: redact(message) };
12887
+ break;
12888
+ }
12889
+ case "error": {
12890
+ const fields = extractErrorFields(data);
12891
+ const code = fields.code;
12892
+ let kind = "unknown";
12893
+ if (code === "context_length_exceeded") {
12894
+ kind = "context_overflow";
12895
+ } else if (code !== undefined && code.length > 0) {
12896
+ kind = "invalid_request";
12897
+ }
12898
+ let message = fields.message;
12899
+ if (message === undefined || message.length === 0) {
12900
+ message = kind === "context_overflow" ? "OpenAI Responses API returned an error with no message; likely context-length overflow" : "OpenAI Responses API returned an error with no message; likely context-length overflow (unconfirmed cause)";
12901
+ }
12902
+ terminalError = { kind, retryable: retryableFor4(kind, false), message: redact(message) };
12903
+ break;
12904
+ }
12905
+ default:
12906
+ break;
11959
12907
  }
11960
- const finishReason = asString3(choice0.finish_reason);
11961
- if (finishReason !== undefined && finishReason.length > 0) {
11962
- sawFinish = true;
11963
- flushPendingToolEnds();
12908
+ if (terminalError !== undefined) {
12909
+ break;
11964
12910
  }
11965
12911
  }
11966
- if (malformed === undefined && torn.length > 0) {
11967
- malformed = {
11968
- kind: "malformed",
11969
- retryable: retryableFor2("malformed", false),
11970
- message: "Ollama SSE stream ended mid-record (torn stream)"
11971
- };
11972
- }
11973
- if (malformed === undefined) {
11974
- flushPendingToolEnds();
11975
- }
11976
- if (malformed === undefined && sawStart && (sawDone || sawFinish)) {
11977
- bodies.push({ kind: "model_end" });
12912
+ if (malformed === undefined && terminalError === undefined) {
12913
+ if (torn.length > 0) {
12914
+ malformed = {
12915
+ kind: "malformed",
12916
+ retryable: retryableFor4("malformed", false),
12917
+ message: redact("OpenAI SSE stream ended mid-record (torn stream)")
12918
+ };
12919
+ } else if (sawStart && !sawCompleted) {
12920
+ malformed = {
12921
+ kind: "malformed",
12922
+ retryable: retryableFor4("malformed", false),
12923
+ message: redact("OpenAI SSE stream ended before response.completed (truncated stream)")
12924
+ };
12925
+ }
11978
12926
  }
11979
12927
  for (const body of bodies) {
11980
12928
  if (opts.signal?.aborted === true) {
11981
- yield errorEvent({ kind: "cancelled", retryable: retryableFor2("cancelled", false), message: "attempt cancelled" });
12929
+ yield errorEvent({ kind: "cancelled", retryable: retryableFor4("cancelled", false), message: "attempt cancelled" });
11982
12930
  return;
11983
12931
  }
11984
12932
  yield stamp(body);
11985
12933
  }
11986
12934
  if (opts.signal?.aborted === true) {
11987
- yield errorEvent({ kind: "cancelled", retryable: retryableFor2("cancelled", false), message: "attempt cancelled" });
12935
+ yield errorEvent({ kind: "cancelled", retryable: retryableFor4("cancelled", false), message: "attempt cancelled" });
12936
+ return;
12937
+ }
12938
+ if (terminalError !== undefined) {
12939
+ yield stamp({ kind: "provider_error", error: terminalError });
11988
12940
  return;
11989
12941
  }
11990
12942
  if (malformed !== undefined) {
@@ -11992,13 +12944,13 @@ ${message.content}` });
11992
12944
  }
11993
12945
  }
11994
12946
  }
11995
- var DEFAULT_BASE_URL2 = "http://localhost:11434", PROVIDER_REVISION2 = "ollama-2024-10-22", DEFAULT_MODEL2;
11996
- var init_ollama_provider = __esm(() => {
12947
+ var DEFAULT_BASE_URL4 = "https://api.openai.com", PROVIDER_REVISION4 = "openai-responses-2026-08", DEFAULT_MODEL4;
12948
+ var init_openai_provider = __esm(() => {
11997
12949
  init_guard2();
11998
12950
  init_provider_port();
11999
- DEFAULT_MODEL2 = {
12000
- modelId: "llama3.1:latest",
12001
- revision: "latest"
12951
+ DEFAULT_MODEL4 = {
12952
+ modelId: "gpt-4.1",
12953
+ revision: "2026-08"
12002
12954
  };
12003
12955
  });
12004
12956
 
@@ -12012,6 +12964,20 @@ function makeProvider(name, _model, opts) {
12012
12964
  }
12013
12965
  return new AnthropicProvider({ fetch: opts.fetch, grant: { network: true, apiKey } });
12014
12966
  }
12967
+ if (name === "openai") {
12968
+ const apiKey = env.OPENAI_API_KEY;
12969
+ if (apiKey === undefined || apiKey.length === 0) {
12970
+ return new FakeProvider([]);
12971
+ }
12972
+ return new OpenAiProvider({ fetch: opts.fetch, grant: { network: true, apiKey } });
12973
+ }
12974
+ if (name === "gemini") {
12975
+ const apiKey = env.GEMINI_API_KEY !== undefined && env.GEMINI_API_KEY.length > 0 ? env.GEMINI_API_KEY : env.GOOGLE_API_KEY;
12976
+ if (apiKey === undefined || apiKey.length === 0) {
12977
+ return new FakeProvider([]);
12978
+ }
12979
+ return new GeminiProvider({ fetch: opts.fetch, grant: { network: true, apiKey } });
12980
+ }
12015
12981
  if (name === "ollama") {
12016
12982
  return new OllamaProvider({
12017
12983
  fetch: opts.fetch,
@@ -12032,18 +12998,26 @@ function makeProvider(name, _model, opts) {
12032
12998
  ...compat.chatPath !== undefined ? { chatPath: compat.chatPath } : {},
12033
12999
  ...apiKey !== undefined ? { apiKey } : {}
12034
13000
  };
12035
- return new OllamaProvider({
12036
- fetch: opts.fetch,
12037
- grant
12038
- });
13001
+ return new OpenAiCompatEngine({ fetch: opts.fetch, grant }, OLLAMA_COMPAT_IDENTITY);
12039
13002
  }
12040
13003
  return new FakeProvider([]);
12041
13004
  }
13005
+ var OLLAMA_COMPAT_IDENTITY;
12042
13006
  var init_make_provider = __esm(() => {
12043
13007
  init_providers();
12044
13008
  init_anthropic_provider();
13009
+ init_openai_compat_provider();
12045
13010
  init_fake_provider();
13011
+ init_gemini_provider();
12046
13012
  init_ollama_provider();
13013
+ init_openai_provider();
13014
+ OLLAMA_COMPAT_IDENTITY = {
13015
+ defaultBaseUrl: "http://localhost:11434",
13016
+ providerRevision: "ollama-2024-10-22",
13017
+ providerId: "ollama",
13018
+ providerLabel: "Ollama",
13019
+ defaultModel: { modelId: "llama3.1:latest", revision: "latest" }
13020
+ };
12047
13021
  });
12048
13022
 
12049
13023
  // src/harness/provider/single-turn.ts
@@ -12062,6 +13036,14 @@ function hasCredential(provider, env) {
12062
13036
  const key = env.ANTHROPIC_API_KEY;
12063
13037
  return key !== undefined && key.length > 0;
12064
13038
  }
13039
+ if (provider === "openai") {
13040
+ const key = env.OPENAI_API_KEY;
13041
+ return key !== undefined && key.length > 0;
13042
+ }
13043
+ if (provider === "gemini") {
13044
+ const key = env.GEMINI_API_KEY ?? env.GOOGLE_API_KEY;
13045
+ return key !== undefined && key.length > 0;
13046
+ }
12065
13047
  const compat = providerByName(provider);
12066
13048
  if (compat) {
12067
13049
  if (compat.requiresApiKey === false) {
@@ -12078,6 +13060,8 @@ function hasCredential(provider, env) {
12078
13060
  function keyedProviderCandidates() {
12079
13061
  return [
12080
13062
  "anthropic",
13063
+ "openai",
13064
+ "gemini",
12081
13065
  ...OPENAI_COMPAT_PROVIDERS.filter((provider) => provider.requiresApiKey !== false).map((p) => p.name)
12082
13066
  ];
12083
13067
  }
@@ -12153,7 +13137,9 @@ var init_single_turn = __esm(() => {
12153
13137
  init_shell_config();
12154
13138
  DEFAULT_MODELS = {
12155
13139
  anthropic: "claude-haiku-4-5-20251001",
12156
- ollama: "llama3.2"
13140
+ ollama: "llama3.2",
13141
+ openai: "gpt-5.6-luna",
13142
+ gemini: "gemini-2.5-flash-lite"
12157
13143
  };
12158
13144
  });
12159
13145
 
@@ -13196,23 +14182,23 @@ var init_slate = __esm(() => {
13196
14182
  // src/flow/store.ts
13197
14183
  var exports_store = {};
13198
14184
  __export(exports_store, {
13199
- writeFlow: () => writeFlow,
13200
- slugify: () => slugify2,
13201
- resolveFlowDir: () => resolveFlowDir,
13202
- readFlow: () => readFlow,
13203
- readAcCriteria: () => readAcCriteria,
13204
- nextFlowId: () => nextFlowId,
13205
- migrateFlow: () => migrateFlow,
13206
- listFlowDirs: () => listFlowDirs,
13207
- groupFlowDirsById: () => groupFlowDirsById,
13208
- flowsRoot: () => flowsRoot,
13209
- flowIdOf: () => flowIdOf,
13210
- duplicateFlowIds: () => duplicateFlowIds,
13211
- deriveFlowWork: () => deriveFlowWork,
13212
- assertAcIntact: () => assertAcIntact,
13213
- appendJournal: () => appendJournal,
14185
+ acChecksum: () => acChecksum,
13214
14186
  acPath: () => acPath,
13215
- acChecksum: () => acChecksum
14187
+ appendJournal: () => appendJournal,
14188
+ assertAcIntact: () => assertAcIntact,
14189
+ deriveFlowWork: () => deriveFlowWork,
14190
+ duplicateFlowIds: () => duplicateFlowIds,
14191
+ flowIdOf: () => flowIdOf,
14192
+ flowsRoot: () => flowsRoot,
14193
+ groupFlowDirsById: () => groupFlowDirsById,
14194
+ listFlowDirs: () => listFlowDirs,
14195
+ migrateFlow: () => migrateFlow,
14196
+ nextFlowId: () => nextFlowId,
14197
+ readAcCriteria: () => readAcCriteria,
14198
+ readFlow: () => readFlow,
14199
+ resolveFlowDir: () => resolveFlowDir,
14200
+ slugify: () => slugify2,
14201
+ writeFlow: () => writeFlow
13216
14202
  });
13217
14203
  import { createHash as createHash7 } from "crypto";
13218
14204
  import { appendFile as appendFile2, mkdir as mkdir26, readFile as readFile32, readdir as readdir8 } from "fs/promises";
@@ -16069,7 +17055,7 @@ function renderWrapUpMemoryEntry(input2) {
16069
17055
 
16070
17056
  Version: 0.1.0
16071
17057
  Type: task-note
16072
- Status: draft
17058
+ Status: accepted
16073
17059
  Confidence: medium
16074
17060
 
16075
17061
  ## Summary
@@ -16169,7 +17155,7 @@ function renderWrapUpDecisionPage(input2) {
16169
17155
 
16170
17156
  Version: 0.1.0
16171
17157
  Type: decision
16172
- Status: draft
17158
+ Status: accepted
16173
17159
 
16174
17160
  ## Summary
16175
17161
 
@@ -17897,7 +18883,7 @@ function buildAgentSystemInstruction(orient, ctx = {}) {
17897
18883
  Project orientation (trusted context):
17898
18884
  ${trimmed}`;
17899
18885
  }
17900
- function parseToolInput2(raw) {
18886
+ function parseToolInput3(raw) {
17901
18887
  const text = raw.trim();
17902
18888
  if (text.length === 0) {
17903
18889
  return {};
@@ -17940,7 +18926,7 @@ function stableStringify2(value) {
17940
18926
  return `{${keys.map((k) => `${JSON.stringify(k)}:${stableStringify2(obj[k])}`).join(",")}}`;
17941
18927
  }
17942
18928
  function toolCallHash(name, input2) {
17943
- const parsed = parseToolInput2(input2);
18929
+ const parsed = parseToolInput3(input2);
17944
18930
  return `${name}\x00${stableStringify2(parsed)}`;
17945
18931
  }
17946
18932
  function budgetUsed(state) {
@@ -18371,7 +19357,7 @@ ${modelOutput}` : modelOutput,
18371
19357
  }
18372
19358
  if (options.slateSession !== undefined && options.slateSession.opened === true) {
18373
19359
  try {
18374
- const touchedPaths = extractTouchedFromToolInput(call.name, parseToolInput2(call.input));
19360
+ const touchedPaths = extractTouchedFromToolInput(call.name, parseToolInput3(call.input));
18375
19361
  const touch = await recordSlateTouch(options.slateSession.dir, touchedPaths, {
18376
19362
  runtime: { provider: deps.providerId, model: deps.modelId }
18377
19363
  });
@@ -18630,7 +19616,7 @@ async function executeCall(call, toolByName, requestApproval, permissionMode, on
18630
19616
  if (tool === undefined) {
18631
19617
  return { output: `unknown tool: ${call.name}`, isError: true };
18632
19618
  }
18633
- const input2 = parseToolInput2(call.input);
19619
+ const input2 = parseToolInput3(call.input);
18634
19620
  const validation = validateAgainstSchemaObject(tool.definition.inputSchema, input2);
18635
19621
  if (!validation.valid) {
18636
19622
  const detail = validation.errors.map((e) => `${e.path}: ${e.message}`).join("; ");
@@ -23336,7 +24322,7 @@ var init_github = __esm(() => {
23336
24322
  });
23337
24323
 
23338
24324
  // src/harness/tool/metaproject-adapter.ts
23339
- import { readFile as readFile55 } from "fs/promises";
24325
+ import { readdir as readdir14, readFile as readFile55 } from "fs/promises";
23340
24326
  import { isAbsolute, join, relative, resolve } from "path";
23341
24327
  function errorMessage(cause) {
23342
24328
  return cause instanceof Error ? cause.message : String(cause);
@@ -23357,6 +24343,131 @@ function confineToWiki(cwd, candidate) {
23357
24343
  }
23358
24344
  return target;
23359
24345
  }
24346
+ function confineToSkills(cwd, candidate) {
24347
+ const skillsRoot = join(cwd, ".metaproject", "skills", "gdskills");
24348
+ const target = resolve(cwd, candidate);
24349
+ const rel = relative(skillsRoot, target);
24350
+ if (rel === "") {
24351
+ return null;
24352
+ }
24353
+ if (rel.startsWith("..") || isAbsolute(rel)) {
24354
+ return null;
24355
+ }
24356
+ return target;
24357
+ }
24358
+ function stripSkillFieldQuotes(value) {
24359
+ if (value.length >= 2) {
24360
+ const first = value[0];
24361
+ const last = value[value.length - 1];
24362
+ if (first === '"' && last === '"' || first === "'" && last === "'") {
24363
+ return value.slice(1, -1);
24364
+ }
24365
+ }
24366
+ return value;
24367
+ }
24368
+ function parseSkillFrontmatter(content) {
24369
+ if (!content.startsWith("---")) {
24370
+ return {};
24371
+ }
24372
+ const end = content.indexOf(`
24373
+ ---`, 3);
24374
+ if (end === -1) {
24375
+ return {};
24376
+ }
24377
+ const lines = content.slice(3, end).split(`
24378
+ `);
24379
+ let description;
24380
+ const triggers = [];
24381
+ let inTriggers = false;
24382
+ for (const line of lines) {
24383
+ const descMatch = /^description:\s*(.*)$/.exec(line);
24384
+ if (descMatch !== null && descMatch[1] !== undefined) {
24385
+ description = stripSkillFieldQuotes(descMatch[1].trim());
24386
+ inTriggers = false;
24387
+ continue;
24388
+ }
24389
+ if (/^triggers:\s*$/.test(line)) {
24390
+ inTriggers = true;
24391
+ continue;
24392
+ }
24393
+ if (inTriggers) {
24394
+ const itemMatch = /^\s+-\s*(.+)$/.exec(line);
24395
+ if (itemMatch !== null && itemMatch[1] !== undefined) {
24396
+ triggers.push(stripSkillFieldQuotes(itemMatch[1].trim()));
24397
+ continue;
24398
+ }
24399
+ inTriggers = false;
24400
+ }
24401
+ }
24402
+ return { ...description !== undefined ? { description } : {}, ...triggers.length > 0 ? { triggers } : {} };
24403
+ }
24404
+ async function parseCatalogSummaries(cwd) {
24405
+ const summaries = new Map;
24406
+ let content;
24407
+ try {
24408
+ content = await readFile55(join(cwd, ".metaproject", "skills", "catalog.md"), "utf8");
24409
+ } catch {
24410
+ return summaries;
24411
+ }
24412
+ const rowPattern = /^\|\s*([^|]+?)\s*\|\s*([^|]+?)\s*\|\s*([^|]+?)\s*\|\s*([^|]+?)\s*\|\s*$/;
24413
+ for (const line of content.split(`
24414
+ `)) {
24415
+ const match = rowPattern.exec(line);
24416
+ if (match === null) {
24417
+ continue;
24418
+ }
24419
+ const [, name, , purpose] = match;
24420
+ if (name === undefined || purpose === undefined || name === "Skill" || /^-+$/.test(name)) {
24421
+ continue;
24422
+ }
24423
+ summaries.set(name, purpose);
24424
+ }
24425
+ return summaries;
24426
+ }
24427
+ async function walkSkillCatalog(cwd) {
24428
+ const root = join(cwd, ".metaproject", "skills", "gdskills");
24429
+ const entries = [];
24430
+ let categoryDirs;
24431
+ try {
24432
+ categoryDirs = await readdir14(root, { withFileTypes: true });
24433
+ } catch {
24434
+ return [];
24435
+ }
24436
+ const catalogSummaries = await parseCatalogSummaries(cwd);
24437
+ for (const categoryDir of categoryDirs) {
24438
+ if (!categoryDir.isDirectory()) {
24439
+ continue;
24440
+ }
24441
+ const categoryPath = join(root, categoryDir.name);
24442
+ let skillDirs;
24443
+ try {
24444
+ skillDirs = await readdir14(categoryPath, { withFileTypes: true });
24445
+ } catch {
24446
+ continue;
24447
+ }
24448
+ for (const skillDir of skillDirs) {
24449
+ if (!skillDir.isDirectory()) {
24450
+ continue;
24451
+ }
24452
+ const skillMdPath = join(categoryPath, skillDir.name, "SKILL.md");
24453
+ let content;
24454
+ try {
24455
+ content = await readFile55(skillMdPath, "utf8");
24456
+ } catch {
24457
+ continue;
24458
+ }
24459
+ const { description, triggers } = parseSkillFrontmatter(content);
24460
+ entries.push({
24461
+ name: skillDir.name,
24462
+ path: relative(cwd, skillMdPath),
24463
+ category: categoryDir.name,
24464
+ description: description ?? catalogSummaries.get(skillDir.name) ?? "",
24465
+ ...triggers !== undefined ? { triggers } : {}
24466
+ });
24467
+ }
24468
+ }
24469
+ return entries.sort((a, b) => a.path.localeCompare(b.path));
24470
+ }
23360
24471
  function createMetaprojectAdapter(cwd, overrides = {}) {
23361
24472
  const deps = { ...DEFAULT_DEPS, ...overrides };
23362
24473
  const gdgraph = deps.createGdgraphService();
@@ -23624,6 +24735,36 @@ function createMetaprojectAdapter(cwd, overrides = {}) {
23624
24735
  } catch (cause) {
23625
24736
  return { file: input2.file, backlinks: [], error: errorMessage(cause) };
23626
24737
  }
24738
+ },
24739
+ async skillsCatalog() {
24740
+ const skills = await walkSkillCatalog(cwd);
24741
+ return { skills, generatedAt: deps.now() };
24742
+ },
24743
+ async loadSkill(input2) {
24744
+ const catalog = await walkSkillCatalog(cwd);
24745
+ const byName = catalog.find((entry) => entry.name === input2.name);
24746
+ if (byName !== undefined) {
24747
+ try {
24748
+ const content = await readFile55(join(cwd, byName.path), "utf8");
24749
+ return { name: input2.name, path: byName.path, content, found: true };
24750
+ } catch {
24751
+ return { name: input2.name, path: "", content: "", found: false };
24752
+ }
24753
+ }
24754
+ const confined = confineToSkills(cwd, input2.name);
24755
+ if (confined === null) {
24756
+ return { name: input2.name, path: "", content: "", found: false };
24757
+ }
24758
+ const byPath = catalog.find((entry) => join(cwd, entry.path) === confined);
24759
+ if (byPath === undefined) {
24760
+ return { name: input2.name, path: "", content: "", found: false };
24761
+ }
24762
+ try {
24763
+ const content = await readFile55(confined, "utf8");
24764
+ return { name: input2.name, path: byPath.path, content, found: true };
24765
+ } catch {
24766
+ return { name: input2.name, path: "", content: "", found: false };
24767
+ }
23627
24768
  }
23628
24769
  };
23629
24770
  }
@@ -23660,6 +24801,7 @@ var init_metaproject_adapter = __esm(() => {
23660
24801
  }),
23661
24802
  wikiAsk,
23662
24803
  wikiPagesForFile,
24804
+ now: () => new Date().toISOString(),
23663
24805
  repomapCompute: async (cwd, options) => {
23664
24806
  const [graph, config] = await Promise.all([loadGraph(cwd), loadGdgraphConfig(cwd)]);
23665
24807
  return computeRepomap(graph, config, options);
@@ -23734,6 +24876,24 @@ function formatWiki(result) {
23734
24876
  }
23735
24877
  return { output: result.content.length > 0 ? result.content : "(empty page)", isError: false };
23736
24878
  }
24879
+ function formatSkillsCatalog(result) {
24880
+ if (result.skills.length === 0) {
24881
+ return { output: "No skills found under .metaproject/skills/gdskills/.", isError: false };
24882
+ }
24883
+ const lines = result.skills.map((skill2) => {
24884
+ const triggers = skill2.triggers !== undefined && skill2.triggers.length > 0 ? ` [triggers: ${skill2.triggers.join(", ")}]` : "";
24885
+ return ` - ${skill2.name} (${skill2.category}) \u2014 ${skill2.description || "(no description)"}${triggers}
24886
+ ${skill2.path}`;
24887
+ });
24888
+ return { output: [`Skills (${result.skills.length}):`, ...lines].join(`
24889
+ `), isError: false };
24890
+ }
24891
+ function formatSkillLoad(result) {
24892
+ if (!result.found) {
24893
+ return { output: `skill_load: no skill found for '${result.name}'.`, isError: true };
24894
+ }
24895
+ return { output: result.content, isError: false };
24896
+ }
23737
24897
  function formatPath(result) {
23738
24898
  if (result.error !== undefined) {
23739
24899
  return { output: `graph_path failed: ${result.error}`, isError: true };
@@ -23847,7 +25007,7 @@ function toInteractiveTools(ops, port) {
23847
25007
  invoke: (input2) => op.invoke(port, input2)
23848
25008
  }));
23849
25009
  }
23850
- var AFFECTED_OUTPUT_SCHEMA, QUERY_OUTPUT_SCHEMA, MEMORY_OUTPUT_SCHEMA, WIKI_OUTPUT_SCHEMA, SEARCH_OUTPUT_SCHEMA, PATH_OUTPUT_SCHEMA, TEST_RELATED_OUTPUT_SCHEMA, HEALTH_OUTPUT_SCHEMA, SYMBOL_OUTPUT_SCHEMA, REPOMAP_OUTPUT_SCHEMA, WIKI_ASK_OUTPUT_SCHEMA, WIKI_BACKLINKS_OUTPUT_SCHEMA, FLOW_STATUS_OUTPUT_SCHEMA, METAPROJECT_OPERATIONS;
25010
+ var AFFECTED_OUTPUT_SCHEMA, QUERY_OUTPUT_SCHEMA, MEMORY_OUTPUT_SCHEMA, WIKI_OUTPUT_SCHEMA, SEARCH_OUTPUT_SCHEMA, PATH_OUTPUT_SCHEMA, TEST_RELATED_OUTPUT_SCHEMA, HEALTH_OUTPUT_SCHEMA, SYMBOL_OUTPUT_SCHEMA, REPOMAP_OUTPUT_SCHEMA, WIKI_ASK_OUTPUT_SCHEMA, WIKI_BACKLINKS_OUTPUT_SCHEMA, FLOW_STATUS_OUTPUT_SCHEMA, SKILLS_CATALOG_OUTPUT_SCHEMA, SKILL_LOAD_OUTPUT_SCHEMA, METAPROJECT_OPERATIONS;
23851
25011
  var init_metaproject_operations = __esm(() => {
23852
25012
  AFFECTED_OUTPUT_SCHEMA = {
23853
25013
  type: "object",
@@ -23984,6 +25144,24 @@ var init_metaproject_operations = __esm(() => {
23984
25144
  },
23985
25145
  required: ["flows"]
23986
25146
  };
25147
+ SKILLS_CATALOG_OUTPUT_SCHEMA = {
25148
+ type: "object",
25149
+ properties: {
25150
+ skills: { type: "array" },
25151
+ generatedAt: { type: "string" }
25152
+ },
25153
+ required: ["skills", "generatedAt"]
25154
+ };
25155
+ SKILL_LOAD_OUTPUT_SCHEMA = {
25156
+ type: "object",
25157
+ properties: {
25158
+ name: { type: "string" },
25159
+ path: { type: "string" },
25160
+ content: { type: "string" },
25161
+ found: { type: "boolean" }
25162
+ },
25163
+ required: ["name", "path", "content", "found"]
25164
+ };
23987
25165
  METAPROJECT_OPERATIONS = [
23988
25166
  {
23989
25167
  name: "search_code",
@@ -24260,6 +25438,47 @@ var init_metaproject_operations = __esm(() => {
24260
25438
  const id = typeof input2.id === "string" && input2.id.trim().length > 0 ? input2.id.trim() : undefined;
24261
25439
  return formatFlowStatus(await port.flowStatus(id !== undefined ? { id } : {}));
24262
25440
  }
25441
+ },
25442
+ {
25443
+ name: "skills_catalog",
25444
+ risk: "read",
25445
+ module: "gdskills",
25446
+ description: "List every skill discovered under .metaproject/skills/gdskills/ \u2014 name, category, description, and " + "triggers for each. Use this instead of reading .metaproject/index.md or catalog.md to find which skill " + "applies to a task. No input.",
25447
+ inputSchema: {
25448
+ type: "object",
25449
+ properties: {},
25450
+ additionalProperties: false
25451
+ },
25452
+ outputSchema: SKILLS_CATALOG_OUTPUT_SCHEMA,
25453
+ invoke: async (port) => {
25454
+ if (port.skillsCatalog === undefined) {
25455
+ return { output: "skills_catalog is not available in this session.", isError: true };
25456
+ }
25457
+ return formatSkillsCatalog(await port.skillsCatalog({}));
25458
+ }
25459
+ },
25460
+ {
25461
+ name: "skill_load",
25462
+ risk: "read",
25463
+ module: "gdskills",
25464
+ description: `Load one skill's full SKILL.md content by name (e.g. "flow-orchestrator") or exact project-relative ` + "path, as returned by `skills_catalog`. Input: { name: string }.",
25465
+ inputSchema: {
25466
+ type: "object",
25467
+ properties: { name: { type: "string" } },
25468
+ required: ["name"],
25469
+ additionalProperties: false
25470
+ },
25471
+ outputSchema: SKILL_LOAD_OUTPUT_SCHEMA,
25472
+ invoke: async (port, input2) => {
25473
+ if (port.loadSkill === undefined) {
25474
+ return { output: "skill_load is not available in this session.", isError: true };
25475
+ }
25476
+ const name = requireString(input2, "name", "skill_load");
25477
+ if ("error" in name) {
25478
+ return name.error;
25479
+ }
25480
+ return formatSkillLoad(await port.loadSkill({ name: name.value }));
25481
+ }
24263
25482
  }
24264
25483
  ];
24265
25484
  });
@@ -24513,30 +25732,30 @@ var init_staleness2 = __esm(() => {
24513
25732
  // src/wiki/enrich.ts
24514
25733
  var exports_enrich2 = {};
24515
25734
  __export(exports_enrich2, {
24516
- wikiEnrich: () => wikiEnrich,
24517
- validateEnrichedMarkdown: () => validateEnrichedMarkdown,
24518
- setFrontmatterStatus: () => setFrontmatterStatus,
24519
- selectPages: () => selectPages,
24520
- saveResumeState: () => saveResumeState,
24521
- resumeStatePath: () => resumeStatePath,
24522
- resolveEnrichProviderModel: () => resolveEnrichProviderModel,
24523
- repairEnrichedFrontmatter: () => repairEnrichedFrontmatter,
24524
- planWikiEnrich: () => planWikiEnrich,
24525
- parseBatchResponse: () => parseBatchResponse,
24526
- mapPool: () => mapPool,
24527
- loadResumeState: () => loadResumeState,
24528
- isWikiEnrichIntent: () => isWikiEnrichIntent,
24529
- hasYamlFrontmatter: () => hasYamlFrontmatter,
24530
- hasCredential: () => hasCredential,
24531
- groupLightPagesIntoBatches: () => groupLightPagesIntoBatches,
24532
- extractYamlFrontmatterBlock: () => extractYamlFrontmatterBlock,
24533
- ensureWikiFrontmatter: () => ensureWikiFrontmatter,
24534
- defaultEnrichProgress: () => defaultEnrichProgress,
24535
- buildBatchUserPrompt: () => buildBatchUserPrompt,
24536
- batchGroupKey: () => batchGroupKey,
24537
- MAX_CONCURRENCY: () => MAX_CONCURRENCY,
25735
+ DEFAULT_CONCURRENCY: () => DEFAULT_CONCURRENCY,
24538
25736
  DEFAULT_MAX_OUTPUT_TOKENS: () => DEFAULT_MAX_OUTPUT_TOKENS,
24539
- DEFAULT_CONCURRENCY: () => DEFAULT_CONCURRENCY
25737
+ MAX_CONCURRENCY: () => MAX_CONCURRENCY,
25738
+ batchGroupKey: () => batchGroupKey,
25739
+ buildBatchUserPrompt: () => buildBatchUserPrompt,
25740
+ defaultEnrichProgress: () => defaultEnrichProgress,
25741
+ ensureWikiFrontmatter: () => ensureWikiFrontmatter,
25742
+ extractYamlFrontmatterBlock: () => extractYamlFrontmatterBlock,
25743
+ groupLightPagesIntoBatches: () => groupLightPagesIntoBatches,
25744
+ hasCredential: () => hasCredential,
25745
+ hasYamlFrontmatter: () => hasYamlFrontmatter,
25746
+ isWikiEnrichIntent: () => isWikiEnrichIntent,
25747
+ loadResumeState: () => loadResumeState,
25748
+ mapPool: () => mapPool,
25749
+ parseBatchResponse: () => parseBatchResponse,
25750
+ planWikiEnrich: () => planWikiEnrich,
25751
+ repairEnrichedFrontmatter: () => repairEnrichedFrontmatter,
25752
+ resolveEnrichProviderModel: () => resolveEnrichProviderModel,
25753
+ resumeStatePath: () => resumeStatePath,
25754
+ saveResumeState: () => saveResumeState,
25755
+ selectPages: () => selectPages,
25756
+ setFrontmatterStatus: () => setFrontmatterStatus,
25757
+ validateEnrichedMarkdown: () => validateEnrichedMarkdown,
25758
+ wikiEnrich: () => wikiEnrich
24540
25759
  });
24541
25760
  import { existsSync as existsSync18, mkdirSync as mkdirSync6, readFileSync as readFileSync7, writeFileSync as writeFileSync6 } from "fs";
24542
25761
  import { readFile as readFile57, writeFile as writeFile36 } from "fs/promises";
@@ -25588,9 +26807,9 @@ var init_enrich2 = __esm(() => {
25588
26807
  // src/sync/hooks.ts
25589
26808
  var exports_hooks = {};
25590
26809
  __export(exports_hooks, {
25591
- uninstallSyncHooks: () => uninstallSyncHooks,
26810
+ SYNC_HOOKS: () => SYNC_HOOKS,
25592
26811
  installSyncHooks: () => installSyncHooks,
25593
- SYNC_HOOKS: () => SYNC_HOOKS
26812
+ uninstallSyncHooks: () => uninstallSyncHooks
25594
26813
  });
25595
26814
  import { chmod as chmod3, mkdir as mkdir42, readFile as readFile59, writeFile as writeFile38 } from "fs/promises";
25596
26815
  import path111 from "path";
@@ -25773,8 +26992,8 @@ var init_reflect = __esm(() => {
25773
26992
  // src/lib/narrate.ts
25774
26993
  var exports_narrate = {};
25775
26994
  __export(exports_narrate, {
25776
- narrate: () => narrate,
25777
- formatMissingCredentialHint: () => formatMissingCredentialHint
26995
+ formatMissingCredentialHint: () => formatMissingCredentialHint,
26996
+ narrate: () => narrate
25778
26997
  });
25779
26998
  function formatMissingCredentialHint(provider) {
25780
26999
  const env = envWithSavedApiKeys(process.env);
@@ -26206,9 +27425,9 @@ var init_memory = __esm(() => {
26206
27425
  // src/lib/contained-path.ts
26207
27426
  var exports_contained_path = {};
26208
27427
  __export(exports_contained_path, {
26209
- resolveProjectRoot: () => resolveProjectRoot2,
27428
+ resolveContainedPath: () => resolveContainedPath,
26210
27429
  resolveContainedPathSync: () => resolveContainedPathSync,
26211
- resolveContainedPath: () => resolveContainedPath
27430
+ resolveProjectRoot: () => resolveProjectRoot2
26212
27431
  });
26213
27432
  import { realpath as realpath3 } from "fs/promises";
26214
27433
  import { existsSync as existsSync21, realpathSync as realpathSync2 } from "fs";
@@ -32398,10 +33617,72 @@ var GENERIC_RUNTIME = {
32398
33617
  validate: mcpValidate("generic"),
32399
33618
  hasManaged: mcpHasManaged
32400
33619
  };
33620
+ function readVscodeServers(settings) {
33621
+ return typeof settings.servers === "object" && settings.servers !== null && !Array.isArray(settings.servers) ? { ...settings.servers } : {};
33622
+ }
33623
+ function vscodeManagedEntry(projectRoot) {
33624
+ return {
33625
+ type: "stdio",
33626
+ ...mcpServerEntry(projectRoot),
33627
+ [MCP_MANAGED_KEY]: MCP_MANAGED_SENTINEL
33628
+ };
33629
+ }
33630
+ function vscodeMerge(settings, projectRoot) {
33631
+ const servers = readVscodeServers(settings);
33632
+ servers[MCP_SERVER_NAME] = vscodeManagedEntry(projectRoot);
33633
+ settings.servers = servers;
33634
+ return settings;
33635
+ }
33636
+ function vscodeStrip(settings) {
33637
+ if (typeof settings.servers !== "object" || settings.servers === null || Array.isArray(settings.servers)) {
33638
+ return settings;
33639
+ }
33640
+ const servers = { ...settings.servers };
33641
+ if (isManagedEntry(servers[MCP_SERVER_NAME])) {
33642
+ delete servers[MCP_SERVER_NAME];
33643
+ }
33644
+ if (Object.keys(servers).length > 0)
33645
+ settings.servers = servers;
33646
+ else
33647
+ delete settings.servers;
33648
+ return settings;
33649
+ }
33650
+ function vscodeValidate(settings) {
33651
+ const errors = [];
33652
+ const servers = settings.servers;
33653
+ if (typeof servers !== "object" || servers === null || Array.isArray(servers)) {
33654
+ errors.push("vscode: servers is missing or not an object");
33655
+ return errors;
33656
+ }
33657
+ const entry = servers[MCP_SERVER_NAME];
33658
+ if (typeof entry !== "object" || entry === null || Array.isArray(entry)) {
33659
+ errors.push(`vscode: missing servers.${MCP_SERVER_NAME} entry`);
33660
+ return errors;
33661
+ }
33662
+ if (entry.type !== "stdio") {
33663
+ errors.push(`vscode: servers.${MCP_SERVER_NAME}.type must be "stdio"`);
33664
+ }
33665
+ if (entry.command !== MCP_SERVER_COMMAND) {
33666
+ errors.push(`vscode: servers.${MCP_SERVER_NAME}.command must be "${MCP_SERVER_COMMAND}"`);
33667
+ }
33668
+ return errors;
33669
+ }
33670
+ function vscodeHasManaged(settings) {
33671
+ return isManagedEntry(readVscodeServers(settings)[MCP_SERVER_NAME]);
33672
+ }
33673
+ var VSCODE_RUNTIME = {
33674
+ id: "vscode",
33675
+ settingsPath: (root) => path40.join(root, ".vscode", "mcp.json"),
33676
+ merge: vscodeMerge,
33677
+ strip: vscodeStrip,
33678
+ validate: vscodeValidate,
33679
+ hasManaged: vscodeHasManaged
33680
+ };
32401
33681
  var MCP_CLIENT_RUNTIMES = [
32402
33682
  CURSOR_RUNTIME,
32403
33683
  CLAUDE_RUNTIME2,
32404
33684
  OPENCODE_RUNTIME,
33685
+ VSCODE_RUNTIME,
32405
33686
  GENERIC_RUNTIME
32406
33687
  ];
32407
33688
  var ALL_RUNTIME_IDS = ["cursor", "claude", "opencode"];
@@ -32428,6 +33709,21 @@ function renderMcpClientSnippet(projectRoot) {
32428
33709
  return `${JSON.stringify({ mcpServers: { [MCP_SERVER_NAME]: mcpServerEntry(projectRoot) } }, null, 2)}
32429
33710
  `;
32430
33711
  }
33712
+ async function mcpClientStatus(projectRoot, ids = mcpRuntimeIds()) {
33713
+ const absoluteProjectRoot = path40.resolve(projectRoot);
33714
+ const { runtimes } = resolveMcpRuntimes(ids);
33715
+ const statuses = [];
33716
+ for (const runtime of runtimes) {
33717
+ const file = runtime.settingsPath(absoluteProjectRoot);
33718
+ if (file === null) {
33719
+ statuses.push({ id: runtime.id, filePath: null, connected: false });
33720
+ continue;
33721
+ }
33722
+ const settings = await readSettings2(file);
33723
+ statuses.push({ id: runtime.id, filePath: file, connected: runtime.hasManaged(settings) });
33724
+ }
33725
+ return statuses;
33726
+ }
32431
33727
  async function readSettings2(file) {
32432
33728
  if (!await pathExists(file)) {
32433
33729
  return {};
@@ -32471,7 +33767,7 @@ function buildMcpModuleEntry() {
32471
33767
  expose: {
32472
33768
  tools: true,
32473
33769
  resources: true,
32474
- modules: ["gdgraph", "gdctx", "security", "flow", "memory", "health", "testing", "wiki", "standard", "sac"]
33770
+ modules: ["gdgraph", "gdctx", "security", "flow", "memory", "health", "testing", "wiki", "standard", "sac", "gdskills"]
32475
33771
  }
32476
33772
  };
32477
33773
  }
@@ -32499,16 +33795,18 @@ protocol adapter \u2014 it defines no new module logic.
32499
33795
  requires \`http.enabled=true\` in this module's manifest entry).
32500
33796
  - \`keryx mcp serve --cwd <project-root>\` \u2014 expose a specific project,
32501
33797
  independent of the MCP client's launch directory.
32502
- - \`keryx mcp install --runtime <cursor|claude|opencode|generic|all> [--dry-run]\` \u2014
33798
+ - \`keryx mcp install --runtime <cursor|claude|opencode|vscode|generic|all> [--dry-run]\` \u2014
32503
33799
  wire this project into an editor/agent: writes a project-local client
32504
33800
  config (cursor \u2192 \`.cursor/mcp.json\`, claude \u2192 \`.mcp.json\`, opencode \u2192
32505
- \`opencode.json\`) and sets \`modules.mcp.enabled=true\`. \`--dry-run\`
32506
- prints the change without writing anything. This is the command to run
32507
- when a user asks to "connect" or "enable" MCP for this project \u2014 it is
32508
- the full, real setup step; hand-editing a client config file directly is
32509
- unnecessary and skips setting \`modules.mcp.enabled\`. \`all\` expands to
32510
- cursor + claude + opencode.
32511
- - \`keryx mcp uninstall --runtime <cursor|claude|opencode|generic|all>\` \u2014
33801
+ \`opencode.json\`, vscode \u2192 \`.vscode/mcp.json\`) and sets
33802
+ \`modules.mcp.enabled=true\`. \`--dry-run\` prints the change without
33803
+ writing anything. This is the command to run when a user asks to
33804
+ "connect" or "enable" MCP for this project \u2014 it is the full, real setup
33805
+ step; hand-editing a client config file directly is unnecessary and skips
33806
+ setting \`modules.mcp.enabled\`. \`all\` expands to cursor + claude +
33807
+ opencode; \`vscode\` is opt-in only (not bundled into \`all\`) \u2014 request it
33808
+ explicitly with \`--runtime vscode\`.
33809
+ - \`keryx mcp uninstall --runtime <cursor|claude|opencode|vscode|generic|all>\` \u2014
32512
33810
  remove the managed client config again.
32513
33811
  - **codex CLI**: not a \`--runtime\` here \u2014 codex's client config is a single
32514
33812
  GLOBAL \`~/.codex/config.toml\`, not a project-local file, and it already
@@ -32790,7 +34088,14 @@ skill's Reporting section).
32790
34088
  }
32791
34089
 
32792
34090
  // src/commands/init.ts
32793
- var MCP_INIT_RUNTIMES = ["cursor", "claude", "opencode", "generic", "skip"];
34091
+ var MCP_INIT_RUNTIMES = [
34092
+ "cursor",
34093
+ "claude",
34094
+ "opencode",
34095
+ "vscode",
34096
+ "generic",
34097
+ "skip"
34098
+ ];
32794
34099
  async function initCommand(args) {
32795
34100
  const options = parseInitArgs(args);
32796
34101
  if (options.help) {
@@ -35962,7 +37267,7 @@ the derived layers in step.
35962
37267
  init_args();
35963
37268
  init_fs();
35964
37269
  init_json();
35965
- import { readdir as readdir17, readFile as readFile64 } from "fs/promises";
37270
+ import { readdir as readdir18, readFile as readFile64 } from "fs/promises";
35966
37271
  import path120 from "path";
35967
37272
 
35968
37273
  // src/gdskills/contracts.ts
@@ -36094,7 +37399,7 @@ async function validateValue(value, schema, valuePath, errors, rootSchema, schem
36094
37399
  });
36095
37400
  }
36096
37401
  }
36097
- if (isPlainObject6(value)) {
37402
+ if (isPlainObject8(value)) {
36098
37403
  const required2 = schema.required ?? [];
36099
37404
  for (const key of required2) {
36100
37405
  if (!(key in value)) {
@@ -36114,7 +37419,7 @@ async function validateValue(value, schema, valuePath, errors, rootSchema, schem
36114
37419
  path: `${valuePath}.${key}`,
36115
37420
  message: "Additional property is not allowed"
36116
37421
  });
36117
- } else if (isPlainObject6(schema.additionalProperties)) {
37422
+ } else if (isPlainObject8(schema.additionalProperties)) {
36118
37423
  await validateValue(nestedValue, schema.additionalProperties, `${valuePath}.${key}`, errors, rootSchema, schemaCache);
36119
37424
  }
36120
37425
  }
@@ -36173,11 +37478,11 @@ function matchesType3(value, type) {
36173
37478
  if (entry === "integer")
36174
37479
  return Number.isInteger(value);
36175
37480
  if (entry === "object")
36176
- return isPlainObject6(value);
37481
+ return isPlainObject8(value);
36177
37482
  return typeof value === entry;
36178
37483
  });
36179
37484
  }
36180
- function isPlainObject6(value) {
37485
+ function isPlainObject8(value) {
36181
37486
  return typeof value === "object" && value !== null && !Array.isArray(value);
36182
37487
  }
36183
37488
  function formatType(type) {
@@ -36594,7 +37899,7 @@ function unique(values) {
36594
37899
  // src/gdskills/export.ts
36595
37900
  init_fs();
36596
37901
  init_json();
36597
- import { copyFile as copyFile3, mkdir as mkdir45, readdir as readdir15, writeFile as writeFile40 } from "fs/promises";
37902
+ import { copyFile as copyFile3, mkdir as mkdir45, readdir as readdir16, writeFile as writeFile40 } from "fs/promises";
36598
37903
  import path117 from "path";
36599
37904
 
36600
37905
  // src/gdskills/resolve.ts
@@ -36644,7 +37949,7 @@ async function normalizePackagePath(candidate) {
36644
37949
  // src/gdskills/export-plugin.ts
36645
37950
  init_fs();
36646
37951
  init_json();
36647
- import { copyFile as copyFile2, mkdir as mkdir44, readdir as readdir14, readFile as readFile62, writeFile as writeFile39 } from "fs/promises";
37952
+ import { copyFile as copyFile2, mkdir as mkdir44, readdir as readdir15, readFile as readFile62, writeFile as writeFile39 } from "fs/promises";
36648
37953
  import path116 from "path";
36649
37954
  var SAFE_DIRS = ["references", "templates", "assets", "scripts"];
36650
37955
  function skillTitle(skillMd, fallback) {
@@ -36666,7 +37971,7 @@ function skillVersion(skillMd) {
36666
37971
  return skillMd.match(/^Version:\s*(.+)$/m)?.[1]?.trim() ?? "0.1.0";
36667
37972
  }
36668
37973
  async function listFiles(root) {
36669
- const entries = await readdir14(root, { withFileTypes: true });
37974
+ const entries = await readdir15(root, { withFileTypes: true });
36670
37975
  const files = [];
36671
37976
  for (const entry of entries) {
36672
37977
  const full = path116.join(root, entry.name);
@@ -36856,7 +38161,7 @@ async function copyDirectoryIfExists(sourceDir, targetDir) {
36856
38161
  }
36857
38162
  }
36858
38163
  async function listFiles2(root) {
36859
- const entries = await readdir15(root, { withFileTypes: true });
38164
+ const entries = await readdir16(root, { withFileTypes: true });
36860
38165
  const files = [];
36861
38166
  for (const entry of entries) {
36862
38167
  const entryPath = path117.join(root, entry.name);
@@ -36877,7 +38182,7 @@ function inferModuleFromPackageRoot(packageRoot) {
36877
38182
 
36878
38183
  // src/gdskills/sync.ts
36879
38184
  init_fs();
36880
- import { copyFile as copyFile4, mkdir as mkdir46, readdir as readdir16, writeFile as writeFile41 } from "fs/promises";
38185
+ import { copyFile as copyFile4, mkdir as mkdir46, readdir as readdir17, writeFile as writeFile41 } from "fs/promises";
36881
38186
  import path118 from "path";
36882
38187
  async function syncRuntimeSkills(projectRoot, options) {
36883
38188
  const metaprojectRoot = path118.join(projectRoot, ".metaproject");
@@ -36942,7 +38247,7 @@ function validateSyncTarget(projectRoot, targetRoot) {
36942
38247
  }
36943
38248
  }
36944
38249
  async function listSkillArtifactDirs(sourceRoot) {
36945
- const entries = await readdir16(sourceRoot, { withFileTypes: true });
38250
+ const entries = await readdir17(sourceRoot, { withFileTypes: true });
36946
38251
  const dirs = [];
36947
38252
  for (const entry of entries) {
36948
38253
  if (!entry.isDirectory()) {
@@ -36972,7 +38277,7 @@ async function copyDirectory(sourceDir, targetDir) {
36972
38277
  }
36973
38278
  }
36974
38279
  async function listFiles3(root) {
36975
- const entries = await readdir16(root, { withFileTypes: true });
38280
+ const entries = await readdir17(root, { withFileTypes: true });
36976
38281
  const files = [];
36977
38282
  for (const entry of entries) {
36978
38283
  const entryPath = path118.join(root, entry.name);
@@ -38150,7 +39455,7 @@ async function listJsonFiles(root) {
38150
39455
  if (!await pathExists(root)) {
38151
39456
  return [];
38152
39457
  }
38153
- const entries = await readdir17(root, { withFileTypes: true });
39458
+ const entries = await readdir18(root, { withFileTypes: true });
38154
39459
  return entries.filter((entry) => entry.isFile() && entry.name.endsWith(".json")).map((entry) => path120.join(root, entry.name));
38155
39460
  }
38156
39461
  function countVerificationStatuses(reports) {
@@ -38326,14 +39631,14 @@ init_service6();
38326
39631
  // src/health/history.ts
38327
39632
  init_fs();
38328
39633
  init_util();
38329
- import { readdir as readdir18, readFile as readFile65 } from "fs/promises";
39634
+ import { readdir as readdir19, readFile as readFile65 } from "fs/promises";
38330
39635
  import path121 from "path";
38331
39636
  async function loadHistory(cwd, limit = 20) {
38332
39637
  const dir = path121.join(dataRoot2(cwd), "history");
38333
39638
  if (!await pathExists(dir)) {
38334
39639
  return [];
38335
39640
  }
38336
- const files = (await readdir18(dir)).filter((file) => file.endsWith(".json")).sort();
39641
+ const files = (await readdir19(dir)).filter((file) => file.endsWith(".json")).sort();
38337
39642
  const points = [];
38338
39643
  for (const file of files.slice(-limit)) {
38339
39644
  try {
@@ -39320,7 +40625,7 @@ init_args();
39320
40625
  init_validator();
39321
40626
  init_fs();
39322
40627
  init_store2();
39323
- import { mkdir as mkdir48, readFile as readFile66, readdir as readdir19 } from "fs/promises";
40628
+ import { mkdir as mkdir48, readFile as readFile66, readdir as readdir20 } from "fs/promises";
39324
40629
  import path124 from "path";
39325
40630
 
39326
40631
  // src/review/types.ts
@@ -39748,7 +41053,7 @@ async function resolveReviewPackagePath(cwd, ref) {
39748
41053
  if (!await pathExists(reviewsDir)) {
39749
41054
  continue;
39750
41055
  }
39751
- for (const entry of await readdir19(reviewsDir, { withFileTypes: true })) {
41056
+ for (const entry of await readdir20(reviewsDir, { withFileTypes: true })) {
39752
41057
  if (entry.isDirectory() && entry.name === ref) {
39753
41058
  return path124.join(reviewsDir, entry.name);
39754
41059
  }
@@ -40135,7 +41440,7 @@ var SCHEMA_REGISTRY2 = {
40135
41440
 
40136
41441
  // src/standard/validate.ts
40137
41442
  var DATE_TIME_PATTERN3 = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2})$/;
40138
- function isPlainObject7(value) {
41443
+ function isPlainObject9(value) {
40139
41444
  return typeof value === "object" && value !== null && !Array.isArray(value);
40140
41445
  }
40141
41446
  function matchesType4(value, type) {
@@ -40148,7 +41453,7 @@ function matchesType4(value, type) {
40148
41453
  if (entry === "integer")
40149
41454
  return Number.isInteger(value);
40150
41455
  if (entry === "object")
40151
- return isPlainObject7(value);
41456
+ return isPlainObject9(value);
40152
41457
  return typeof value === entry;
40153
41458
  });
40154
41459
  }
@@ -40223,7 +41528,7 @@ function walk4(value, schema, valuePath, rootSchema, errors) {
40223
41528
  errors.push({ path: valuePath, message: "Expected array items to be unique" });
40224
41529
  }
40225
41530
  }
40226
- if (isPlainObject7(value)) {
41531
+ if (isPlainObject9(value)) {
40227
41532
  for (const key of schema.required ?? []) {
40228
41533
  if (!(key in value)) {
40229
41534
  errors.push({ path: `${valuePath}.${key}`, message: "Missing required property" });
@@ -40236,7 +41541,7 @@ function walk4(value, schema, valuePath, rootSchema, errors) {
40236
41541
  walk4(nested, nestedSchema, `${valuePath}.${key}`, rootSchema, errors);
40237
41542
  } else if (schema.additionalProperties === false) {
40238
41543
  errors.push({ path: `${valuePath}.${key}`, message: "Additional property is not allowed" });
40239
- } else if (isPlainObject7(schema.additionalProperties)) {
41544
+ } else if (isPlainObject9(schema.additionalProperties)) {
40240
41545
  walk4(nested, schema.additionalProperties, `${valuePath}.${key}`, rootSchema, errors);
40241
41546
  }
40242
41547
  }
@@ -40319,7 +41624,7 @@ async function validateWorkspace2(cwd) {
40319
41624
  errors.push(issue("module-schema", `module "${key}" ${schemaError.path.replace(/^\$\.?/, "") || "(root)"}: ${schemaError.message}`, `Fix the modules.${key} entry in metaproject.json.`));
40320
41625
  }
40321
41626
  }
40322
- if (isPlainObject7(manifest.paths)) {
41627
+ if (isPlainObject9(manifest.paths)) {
40323
41628
  for (const [key, value] of Object.entries(manifest.paths)) {
40324
41629
  if (typeof value !== "string") {
40325
41630
  continue;
@@ -40427,7 +41732,7 @@ async function runCapabilities(cwd) {
40427
41732
  init_fs();
40428
41733
  init_json();
40429
41734
  import path128 from "path";
40430
- import { readdir as readdir20 } from "fs/promises";
41735
+ import { readdir as readdir21 } from "fs/promises";
40431
41736
  function llmsPath(cwd) {
40432
41737
  return path128.join(cwd, ".metaproject", "llms.txt");
40433
41738
  }
@@ -40480,7 +41785,7 @@ async function collectArtifactIndex(cwd) {
40480
41785
  const walk5 = async (dir) => {
40481
41786
  let entries;
40482
41787
  try {
40483
- entries = await readdir20(dir, { withFileTypes: true });
41788
+ entries = await readdir21(dir, { withFileTypes: true });
40484
41789
  } catch {
40485
41790
  return;
40486
41791
  }
@@ -41251,7 +42556,7 @@ Usage:
41251
42556
  }
41252
42557
 
41253
42558
  // src/commands/security.ts
41254
- import { mkdir as mkdir51, readdir as readdir21, readFile as readFile72, writeFile as writeFile45 } from "fs/promises";
42559
+ import { mkdir as mkdir51, readdir as readdir22, readFile as readFile72, writeFile as writeFile45 } from "fs/promises";
41255
42560
  import path131 from "path";
41256
42561
  import { fileURLToPath as fileURLToPath6 } from "url";
41257
42562
  init_args();
@@ -41718,7 +43023,7 @@ async function collectManifestFiles(target) {
41718
43023
  }
41719
43024
  let entries;
41720
43025
  try {
41721
- entries = await readdir21(target, { withFileTypes: true });
43026
+ entries = await readdir22(target, { withFileTypes: true });
41722
43027
  } catch {
41723
43028
  return [target];
41724
43029
  }
@@ -42574,10 +43879,10 @@ async function resolveLedgerState(ledger, checkpointPath, verifier) {
42574
43879
  }
42575
43880
  var policyRefPattern = /^\.\/.+/;
42576
43881
  var isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
42577
- var asString4 = (value) => typeof value === "string" && value.length > 0;
43882
+ var asString6 = (value) => typeof value === "string" && value.length > 0;
42578
43883
  var isBaselineArtifact = (artifact) => artifact.schemaVersion === "1.0" && artifact.kind === "deterministic-baseline" && isImmutableVersion2(artifact.version) && artifact.selection === "eligible-ids-in-input-order";
42579
- var isCandidateArtifact = (artifact) => artifact.schemaVersion === "1.0" && artifact.kind === "offline-selection-advisor" && isImmutableVersion2(artifact.version) && asString4(artifact.output) && typeof artifact.mutations === "boolean";
42580
- var isEvaluationReport = (report) => report.schemaVersion === "1.0" && asString4(report.status) && asString4(report.baselineVersion) && asString4(report.baselineDigest) && reportHashPattern.test(report.baselineDigest) && asString4(report.candidateVersion) && asString4(report.candidateDigest) && reportHashPattern.test(report.candidateDigest) && asString4(report.corpusVersion) && asString4(report.corpusDigest) && reportHashPattern.test(report.corpusDigest) && asString4(report.reportDigest) && reportHashPattern.test(report.reportDigest) && asString4(report.sandboxProfileDigest) && reportHashPattern.test(report.sandboxProfileDigest) && isRecord3(report.train) && typeof report.train.status === "string" && typeof report.train.cases === "number" && isRecord3(report.holdout) && typeof report.holdout.status === "string" && typeof report.holdout.cases === "number" && isRecord3(report.adversarial) && typeof report.adversarial.status === "string" && typeof report.adversarial.cases === "number" && Array.isArray(report.candidateSelectedIds) && Array.isArray(report.reasons);
43884
+ var isCandidateArtifact = (artifact) => artifact.schemaVersion === "1.0" && artifact.kind === "offline-selection-advisor" && isImmutableVersion2(artifact.version) && asString6(artifact.output) && typeof artifact.mutations === "boolean";
43885
+ var isEvaluationReport = (report) => report.schemaVersion === "1.0" && asString6(report.status) && asString6(report.baselineVersion) && asString6(report.baselineDigest) && reportHashPattern.test(report.baselineDigest) && asString6(report.candidateVersion) && asString6(report.candidateDigest) && reportHashPattern.test(report.candidateDigest) && asString6(report.corpusVersion) && asString6(report.corpusDigest) && reportHashPattern.test(report.corpusDigest) && asString6(report.reportDigest) && reportHashPattern.test(report.reportDigest) && asString6(report.sandboxProfileDigest) && reportHashPattern.test(report.sandboxProfileDigest) && isRecord3(report.train) && typeof report.train.status === "string" && typeof report.train.cases === "number" && isRecord3(report.holdout) && typeof report.holdout.status === "string" && typeof report.holdout.cases === "number" && isRecord3(report.adversarial) && typeof report.adversarial.status === "string" && typeof report.adversarial.cases === "number" && Array.isArray(report.candidateSelectedIds) && Array.isArray(report.reasons);
42581
43886
  var isConfigRecord = (value) => typeof value.enabled === "boolean" && typeof value.killSwitch === "boolean";
42582
43887
  async function readPinnedJson(input2) {
42583
43888
  const ref = await resolveWorkspaceReference({ workspaceRoot: input2.workspaceRoot, kind: "artifact", uri: input2.uri });
@@ -42627,7 +43932,7 @@ async function resolvePolicySelection(workspaceRoot, canonicalFallback) {
42627
43932
  const corpusDigest = configJson.corpusDigest;
42628
43933
  const evaluationRef = configJson.evaluationReportRef;
42629
43934
  const evaluationDigest = configJson.evaluationDigest;
42630
- if (!asString4(candidateRef) || !asString4(candidateDigest) || !asString4(baselineRef) || !asString4(baselineDigest) || !asString4(corpusRef) || !asString4(corpusDigest) || !asString4(evaluationRef) || !asString4(evaluationDigest))
43935
+ if (!asString6(candidateRef) || !asString6(candidateDigest) || !asString6(baselineRef) || !asString6(baselineDigest) || !asString6(corpusRef) || !asString6(corpusDigest) || !asString6(evaluationRef) || !asString6(evaluationDigest))
42631
43936
  return canonicalFallback;
42632
43937
  if (!workspacePathPattern3.test(candidateRef) || !workspacePathPattern3.test(baselineRef) || !workspacePathPattern3.test(corpusRef) || !workspacePathPattern3.test(evaluationRef))
42633
43938
  return canonicalFallback;
@@ -42742,7 +44047,7 @@ async function diagnosePolicyReadiness(workspaceRoot) {
42742
44047
  const corpusDigest = configJson.corpusDigest;
42743
44048
  const evaluationRef = configJson.evaluationReportRef;
42744
44049
  const evaluationDigest = configJson.evaluationDigest;
42745
- if (!asString4(candidateRef) || !asString4(candidateDigest) || !asString4(baselineRef) || !asString4(baselineDigest) || !asString4(corpusRef) || !asString4(corpusDigest) || !asString4(evaluationRef) || !asString4(evaluationDigest)) {
44050
+ if (!asString6(candidateRef) || !asString6(candidateDigest) || !asString6(baselineRef) || !asString6(baselineDigest) || !asString6(corpusRef) || !asString6(corpusDigest) || !asString6(evaluationRef) || !asString6(evaluationDigest)) {
42746
44051
  fail("config-pins", "pins are present but not non-empty strings");
42747
44052
  return finalize(true, enabled, killSwitch);
42748
44053
  }
@@ -43437,7 +44742,7 @@ function buildToolRegistry() {
43437
44742
  // src/mcp/resources.ts
43438
44743
  init_fs();
43439
44744
  import path135 from "path";
43440
- import { readdir as readdir22, readFile as readFile76, stat as stat7 } from "fs/promises";
44745
+ import { readdir as readdir23, readFile as readFile76, stat as stat7 } from "fs/promises";
43441
44746
  var URI_PREFIX = "metaproject://";
43442
44747
  function mimeForPath(filePath) {
43443
44748
  if (filePath.endsWith(".json") || filePath.endsWith(".jsonl")) {
@@ -43464,7 +44769,7 @@ async function walkFiles(root) {
43464
44769
  const out = [];
43465
44770
  let entries;
43466
44771
  try {
43467
- entries = await readdir22(root, { withFileTypes: true });
44772
+ entries = await readdir23(root, { withFileTypes: true });
43468
44773
  } catch {
43469
44774
  return [];
43470
44775
  }
@@ -43486,7 +44791,7 @@ async function listArtifacts(cwd) {
43486
44791
  const listings = [];
43487
44792
  let modules;
43488
44793
  try {
43489
- modules = await readdir22(base, { withFileTypes: true });
44794
+ modules = await readdir23(base, { withFileTypes: true });
43490
44795
  } catch {
43491
44796
  return [];
43492
44797
  }
@@ -43778,7 +45083,7 @@ async function mcpCommand(args2 = [], cwd = process.cwd()) {
43778
45083
  printMcpHelp();
43779
45084
  process.exitCode = 1;
43780
45085
  }
43781
- var RUNTIME_USAGE = `<cursor|claude|opencode|generic|all>`;
45086
+ var RUNTIME_USAGE = `<cursor|claude|opencode|vscode|generic|all>`;
43782
45087
  function parseRequestedRuntimes(args2, fallback) {
43783
45088
  const runtimeArg = optionValue(args2, "--runtime") ?? fallback;
43784
45089
  return runtimeArg.split(",").map((s) => s.trim()).filter(Boolean);
@@ -43862,7 +45167,7 @@ function printMcpHelp() {
43862
45167
  { flag: "--dry-run", desc: "install only: print the planned change and write nothing." }
43863
45168
  ]);
43864
45169
  heading("Notes");
43865
- console.log(` ${style.dim("`install` writes a project-local MCP client config (cursor \u2192 .cursor/mcp.json, claude \u2192 .mcp.json, opencode \u2192 opencode.json), sets modules.mcp.enabled=true, and prints a snippet for `generic`.")}`);
45170
+ console.log(` ${style.dim("`install` writes a project-local MCP client config (cursor \u2192 .cursor/mcp.json, claude \u2192 .mcp.json, opencode \u2192 opencode.json, vscode \u2192 .vscode/mcp.json), sets modules.mcp.enabled=true, and prints a snippet for `generic`. `vscode` is opt-in only \u2014 not included in `all`.")}`);
43866
45171
  console.log(` ${style.dim("Requires the optional @modelcontextprotocol/sdk to serve. Disabled by default (modules.mcp.enabled=false). `install` only probes the SDK \u2014 it never installs it or opens a network connection.")}`);
43867
45172
  }
43868
45173
 
@@ -45328,6 +46633,9 @@ function buildDefaultMaskProviders(openaiCompat) {
45328
46633
  out.push({ envKey: p.envKey, baseUrl: p.baseUrl });
45329
46634
  }
45330
46635
  out.push({ envKey: "ANTHROPIC_API_KEY", baseUrl: "https://api.anthropic.com" });
46636
+ out.push({ envKey: "OPENAI_API_KEY", baseUrl: "https://api.openai.com" });
46637
+ out.push({ envKey: "GEMINI_API_KEY", baseUrl: "https://generativelanguage.googleapis.com" });
46638
+ out.push({ envKey: "GOOGLE_API_KEY", baseUrl: "https://generativelanguage.googleapis.com" });
45331
46639
  return out;
45332
46640
  }
45333
46641
  function parseMaskMode(raw) {
@@ -45827,6 +47135,8 @@ init_workspace_service();
45827
47135
  var HARNESS_PROVIDER_OPTIONS = [
45828
47136
  "fake",
45829
47137
  "anthropic",
47138
+ "openai",
47139
+ "gemini",
45830
47140
  "ollama",
45831
47141
  ...OPENAI_COMPAT_PROVIDERS.map((provider) => provider.name)
45832
47142
  ];
@@ -45992,6 +47302,20 @@ async function harnessCommand(args2, deps) {
45992
47302
  return;
45993
47303
  }
45994
47304
  }
47305
+ if (provider === "openai") {
47306
+ const apiKey = env.OPENAI_API_KEY;
47307
+ if (apiKey === undefined || apiKey.length === 0) {
47308
+ console.log("OPENAI_API_KEY is not set: the openai provider is required to have a credential and fails closed (no network was contacted).");
47309
+ return;
47310
+ }
47311
+ }
47312
+ if (provider === "gemini") {
47313
+ const apiKey = env.GEMINI_API_KEY !== undefined && env.GEMINI_API_KEY.length > 0 ? env.GEMINI_API_KEY : env.GOOGLE_API_KEY;
47314
+ if (apiKey === undefined || apiKey.length === 0) {
47315
+ console.log("GEMINI_API_KEY (or GOOGLE_API_KEY) is not set: the gemini provider is required to have a credential and fails closed (no network was contacted).");
47316
+ return;
47317
+ }
47318
+ }
45995
47319
  const providerPort = makeProvider(provider, model, {
45996
47320
  fetch: fetchImpl,
45997
47321
  env,
@@ -46597,7 +47921,7 @@ async function buildApprovalContext(port, command) {
46597
47921
  import { randomUUID as randomUUID20 } from "crypto";
46598
47922
 
46599
47923
  // src/harness/tool/builtin/interactive-tools.ts
46600
- import { readdir as readdir23 } from "fs/promises";
47924
+ import { readdir as readdir24 } from "fs/promises";
46601
47925
  import { isAbsolute as isAbsolute2, relative as relative2, resolve as resolve2, sep } from "path";
46602
47926
  import { realpathSync as realpathSync4 } from "fs";
46603
47927
  var MAX_READ_BYTES = 20000;
@@ -46649,7 +47973,7 @@ function builtinReadOnlyTools(root) {
46649
47973
  return { output: `path escapes the project root: ${requested}`, isError: true };
46650
47974
  }
46651
47975
  try {
46652
- const entries = await readdir23(target, { withFileTypes: true });
47976
+ const entries = await readdir24(target, { withFileTypes: true });
46653
47977
  const lines = entries.map((e) => e.isDirectory() ? `${e.name}/` : e.name).sort();
46654
47978
  return { output: lines.length > 0 ? lines.join(`
46655
47979
  `) : "(empty)", isError: false };
@@ -50330,7 +51654,7 @@ function buildClaudeResumeArgv(sessionRef, message2, input2) {
50330
51654
  function asObject(value) {
50331
51655
  return typeof value === "object" && value !== null && !Array.isArray(value) ? value : undefined;
50332
51656
  }
50333
- function asString5(value) {
51657
+ function asString7(value) {
50334
51658
  return typeof value === "string" ? value : undefined;
50335
51659
  }
50336
51660
  function asFiniteNumber(value) {
@@ -50343,16 +51667,16 @@ function truncate3(text, limit) {
50343
51667
  var CLAUDE_UNMAPPED_SYSTEM_SUBTYPES = ["hook_started", "hook_response"];
50344
51668
  var CLAUDE_UNMAPPED_LINE_TYPES = ["rate_limit_event"];
50345
51669
  function systemEvents(obj) {
50346
- const subtype = asString5(obj.subtype);
51670
+ const subtype = asString7(obj.subtype);
50347
51671
  if (subtype === "init") {
50348
- const sessionRef = asString5(obj.session_id);
51672
+ const sessionRef = asString7(obj.session_id);
50349
51673
  return [sessionRef === undefined ? { kind: "child_started" } : { kind: "child_started", sessionRef }];
50350
51674
  }
50351
51675
  if (subtype === "api_retry") {
50352
51676
  const attempt = asFiniteNumber(obj.attempt);
50353
51677
  const max = asFiniteNumber(obj.max_retries);
50354
51678
  const status = asFiniteNumber(obj.error_status);
50355
- const error2 = asString5(obj.error);
51679
+ const error2 = asString7(obj.error);
50356
51680
  const parts = [
50357
51681
  attempt === undefined ? "api retry" : `api retry ${attempt}${max === undefined ? "" : `/${max}`}`,
50358
51682
  status === undefined ? undefined : `status ${status}`,
@@ -50363,32 +51687,32 @@ function systemEvents(obj) {
50363
51687
  return [];
50364
51688
  }
50365
51689
  function toolResultDetail(content) {
50366
- const direct = asString5(content);
51690
+ const direct = asString7(content);
50367
51691
  if (direct !== undefined)
50368
51692
  return truncate3(direct, TOOL_DETAIL_LIMIT);
50369
51693
  if (!Array.isArray(content))
50370
51694
  return;
50371
- const text = content.map((entry) => asString5(asObject(entry)?.text)).filter((entry) => entry !== undefined).join(`
51695
+ const text = content.map((entry) => asString7(asObject(entry)?.text)).filter((entry) => entry !== undefined).join(`
50372
51696
  `);
50373
51697
  return text.length === 0 ? undefined : truncate3(text, TOOL_DETAIL_LIMIT);
50374
51698
  }
50375
51699
  function assistantBlockEvent(block) {
50376
- switch (asString5(block.type)) {
51700
+ switch (asString7(block.type)) {
50377
51701
  case "tool_use": {
50378
- const name = asString5(block.name) ?? "unknown";
51702
+ const name = asString7(block.name) ?? "unknown";
50379
51703
  const detail = block.input === undefined ? undefined : truncate3(JSON.stringify(block.input), TOOL_DETAIL_LIMIT);
50380
51704
  return detail === undefined ? { kind: "tool_call", name } : { kind: "tool_call", name, detail };
50381
51705
  }
50382
51706
  case "text":
50383
- return { kind: "assistant_text", text: asString5(block.text) ?? "" };
51707
+ return { kind: "assistant_text", text: asString7(block.text) ?? "" };
50384
51708
  case "thinking":
50385
- return { kind: "thinking", text: asString5(block.thinking) ?? asString5(block.text) ?? "" };
51709
+ return { kind: "thinking", text: asString7(block.thinking) ?? asString7(block.text) ?? "" };
50386
51710
  default:
50387
51711
  return;
50388
51712
  }
50389
51713
  }
50390
51714
  function userBlockEvent(block) {
50391
- if (asString5(block.type) !== "tool_result")
51715
+ if (asString7(block.type) !== "tool_result")
50392
51716
  return;
50393
51717
  const detail = toolResultDetail(block.content);
50394
51718
  return detail === undefined ? { kind: "tool_result" } : { kind: "tool_result", detail };
@@ -50426,14 +51750,14 @@ function usageEvent(obj) {
50426
51750
  };
50427
51751
  }
50428
51752
  function describeFailure(obj) {
50429
- const subtype = asString5(obj.subtype) ?? "unknown";
51753
+ const subtype = asString7(obj.subtype) ?? "unknown";
50430
51754
  const parts = [`result.subtype "${subtype}"`];
50431
- const text = asString5(obj.result);
51755
+ const text = asString7(obj.result);
50432
51756
  if (text !== undefined && text.length > 0)
50433
51757
  parts.push(truncate3(text, TOOL_DETAIL_LIMIT));
50434
51758
  const errors = obj.errors;
50435
51759
  if (Array.isArray(errors)) {
50436
- const flat = errors.map((entry) => asString5(entry)).filter((entry) => entry !== undefined);
51760
+ const flat = errors.map((entry) => asString7(entry)).filter((entry) => entry !== undefined);
50437
51761
  if (flat.length > 0)
50438
51762
  parts.push(truncate3(flat.join("; "), TOOL_DETAIL_LIMIT));
50439
51763
  }
@@ -50443,10 +51767,10 @@ function describeFailure(obj) {
50443
51767
  return parts.join(" \u2014 ");
50444
51768
  }
50445
51769
  function resultEvents(obj) {
50446
- const subtype = asString5(obj.subtype);
51770
+ const subtype = asString7(obj.subtype);
50447
51771
  const succeeded = subtype === undefined ? obj.is_error === false : subtype === "success";
50448
51772
  const terminal = succeeded ? (() => {
50449
- const text = asString5(obj.result);
51773
+ const text = asString7(obj.result);
50450
51774
  return text === undefined ? { kind: "child_finished" } : { kind: "child_finished", text };
50451
51775
  })() : { kind: "child_failed", message: describeFailure(obj) };
50452
51776
  const usage = usageEvent(obj);
@@ -50465,7 +51789,7 @@ function parseClaudeEvents(line) {
50465
51789
  const obj = asObject(parsed);
50466
51790
  if (obj === undefined)
50467
51791
  return [];
50468
- switch (asString5(obj.type)) {
51792
+ switch (asString7(obj.type)) {
50469
51793
  case "system":
50470
51794
  return systemEvents(obj);
50471
51795
  case "assistant":
@@ -50495,13 +51819,13 @@ function isRecognisedClaudeLine(line) {
50495
51819
  const obj = asObject(parsed);
50496
51820
  if (obj === undefined)
50497
51821
  return false;
50498
- const type = asString5(obj.type);
51822
+ const type = asString7(obj.type);
50499
51823
  if (type === undefined)
50500
51824
  return false;
50501
51825
  if (CLAUDE_UNMAPPED_LINE_TYPES.includes(type))
50502
51826
  return true;
50503
51827
  if (type === "system") {
50504
- const subtype = asString5(obj.subtype) ?? "";
51828
+ const subtype = asString7(obj.subtype) ?? "";
50505
51829
  return subtype === "init" || subtype === "api_retry" || CLAUDE_UNMAPPED_SYSTEM_SUBTYPES.includes(subtype);
50506
51830
  }
50507
51831
  return type === "assistant" || type === "user" || type === "result";
@@ -50595,9 +51919,9 @@ function parseCodexEvents(line) {
50595
51919
  const record = readJsonObject(line);
50596
51920
  if (record === undefined)
50597
51921
  return [];
50598
- switch (asString6(record.type)) {
51922
+ switch (asString8(record.type)) {
50599
51923
  case "thread.started": {
50600
- const threadId = asString6(record.thread_id);
51924
+ const threadId = asString8(record.thread_id);
50601
51925
  return [threadId === undefined ? { kind: "child_started" } : { kind: "child_started", sessionRef: threadId }];
50602
51926
  }
50603
51927
  case "turn.started":
@@ -50615,7 +51939,7 @@ function parseCodexEvents(line) {
50615
51939
  case "turn.failed":
50616
51940
  return [{ kind: "child_failed", message: parseFailureMessage(record.error) }];
50617
51941
  case "error":
50618
- return [{ kind: "retry", message: asString6(record.message) ?? "codex reported a non-terminal error" }];
51942
+ return [{ kind: "retry", message: asString8(record.message) ?? "codex reported a non-terminal error" }];
50619
51943
  default:
50620
51944
  return [];
50621
51945
  }
@@ -50636,7 +51960,7 @@ function isRecognisedCodexLine(line) {
50636
51960
  const record = readJsonObject(line);
50637
51961
  if (record === undefined)
50638
51962
  return false;
50639
- const type = asString6(record.type);
51963
+ const type = asString8(record.type);
50640
51964
  return type !== undefined && RECOGNISED_TYPES.has(type);
50641
51965
  }
50642
51966
  var NARRATED_FAILURE_LINE = /^\s*(error\b|usage:)/i;
@@ -50696,25 +52020,25 @@ function readJsonObject(line) {
50696
52020
  return;
50697
52021
  }
50698
52022
  }
50699
- function asString6(value) {
52023
+ function asString8(value) {
50700
52024
  return typeof value === "string" && value.length > 0 ? value : undefined;
50701
52025
  }
50702
- function asNumber4(value) {
52026
+ function asNumber6(value) {
50703
52027
  return typeof value === "number" && Number.isFinite(value) ? value : undefined;
50704
52028
  }
50705
52029
  function parseCompletedItem(raw) {
50706
52030
  if (typeof raw !== "object" || raw === null)
50707
52031
  return [];
50708
52032
  const item = raw;
50709
- switch (asString6(item.type)) {
52033
+ switch (asString8(item.type)) {
50710
52034
  case "command_execution": {
50711
- const command = asString6(item.command);
52035
+ const command = asString8(item.command);
50712
52036
  return [
50713
52037
  command === undefined ? { kind: "tool_call", name: "command_execution" } : { kind: "tool_call", name: "command_execution", detail: command }
50714
52038
  ];
50715
52039
  }
50716
52040
  case "agent_message": {
50717
- const text = asString6(item.text);
52041
+ const text = asString8(item.text);
50718
52042
  return text === undefined ? [] : [{ kind: "assistant_text", text }];
50719
52043
  }
50720
52044
  default:
@@ -50725,8 +52049,8 @@ function parseUsage(raw) {
50725
52049
  if (typeof raw !== "object" || raw === null)
50726
52050
  return;
50727
52051
  const usage = raw;
50728
- const inputTokens = asNumber4(usage.input_tokens);
50729
- const outputTokens = asNumber4(usage.output_tokens);
52052
+ const inputTokens = asNumber6(usage.input_tokens);
52053
+ const outputTokens = asNumber6(usage.output_tokens);
50730
52054
  if (inputTokens === undefined && outputTokens === undefined)
50731
52055
  return;
50732
52056
  return {
@@ -50737,11 +52061,11 @@ function parseUsage(raw) {
50737
52061
  }
50738
52062
  function parseFailureMessage(raw) {
50739
52063
  if (typeof raw === "object" && raw !== null) {
50740
- const message2 = asString6(raw.message);
52064
+ const message2 = asString8(raw.message);
50741
52065
  if (message2 !== undefined)
50742
52066
  return message2;
50743
52067
  }
50744
- return asString6(raw) ?? "codex reported a failed turn without a message";
52068
+ return asString8(raw) ?? "codex reported a failed turn without a message";
50745
52069
  }
50746
52070
  function lastTerminalEvent(events) {
50747
52071
  for (let i = events.length - 1;i >= 0; i -= 1) {
@@ -51627,7 +52951,7 @@ import { spawnSync as spawnSync2 } from "child_process";
51627
52951
  // package.json
51628
52952
  var package_default = {
51629
52953
  name: "@mrciphersmith/keryx",
51630
- version: "0.2.49",
52954
+ version: "0.2.51",
51631
52955
  description: "Version-controlled project context for AI coding agents: code graph, architecture wiki, project memory, relevant tests, quality signals, and task flows.",
51632
52956
  private: false,
51633
52957
  publishConfig: {
@@ -52888,7 +54212,7 @@ init_store3();
52888
54212
  init_proposal_lifecycle();
52889
54213
  init_workspace_service();
52890
54214
  import { randomUUID as randomUUID24 } from "crypto";
52891
- import { readdir as readdir24 } from "fs/promises";
54215
+ import { readdir as readdir25 } from "fs/promises";
52892
54216
  import path148 from "path";
52893
54217
 
52894
54218
  // src/sac/lifecycle-flag.ts
@@ -52938,6 +54262,7 @@ async function computeLifecycleFlags(cwd, now = () => new Date) {
52938
54262
  }
52939
54263
 
52940
54264
  // src/sac/catch-up.ts
54265
+ init_proposal_evidence();
52941
54266
  async function buildCatchUp(input2) {
52942
54267
  const [proposals, sessionCategories, lifecycleFlagsAll] = await Promise.all([
52943
54268
  collectProposals(input2.cwd, input2.workspaceId),
@@ -52957,8 +54282,20 @@ async function collectProposals(cwd, workspaceId) {
52957
54282
  const scoped = workspaceId === undefined ? groups : groups.filter((group) => group.workspace.id === workspaceId);
52958
54283
  const flattened = scoped.flatMap((group) => group.proposals.map((proposal) => ({ group, proposal })));
52959
54284
  return Promise.all(flattened.map(async ({ group, proposal }) => {
52960
- const fresh = await proposalService.isEvidenceFresh(proposal, actor);
52961
- return { type: "proposal", workspaceId: group.workspace.id, proposalId: proposal.id, fresh };
54285
+ const [fresh, note2] = await Promise.all([
54286
+ proposalService.isEvidenceFresh(proposal, actor),
54287
+ readSidecarNote(cwd, group.workspace.id, proposal.id)
54288
+ ]);
54289
+ return {
54290
+ type: "proposal",
54291
+ workspaceId: group.workspace.id,
54292
+ proposalId: proposal.id,
54293
+ fresh,
54294
+ kind: proposal.kind,
54295
+ author: proposal.author,
54296
+ createdAt: proposal.createdAt,
54297
+ note: note2
54298
+ };
52962
54299
  }));
52963
54300
  }
52964
54301
  async function classifySession(session) {
@@ -53019,7 +54356,7 @@ async function isSlateEngaged(dir) {
53019
54356
  if (await pathExists(path148.join(dir, "terminal-state.json")))
53020
54357
  return true;
53021
54358
  try {
53022
- const entries = await readdir24(path148.join(dir, "slate-archive"));
54359
+ const entries = await readdir25(path148.join(dir, "slate-archive"));
53023
54360
  return entries.length > 0;
53024
54361
  } catch {
53025
54362
  return false;
@@ -53047,7 +54384,7 @@ async function readNewestUnboundCandidate(dir) {
53047
54384
  const archiveDir = path148.join(dir, "slate-archive");
53048
54385
  let entries;
53049
54386
  try {
53050
- entries = (await readdir24(archiveDir)).filter((name) => name.endsWith("-unbound-candidate.json"));
54387
+ entries = (await readdir25(archiveDir)).filter((name) => name.endsWith("-unbound-candidate.json"));
53051
54388
  } catch {
53052
54389
  return;
53053
54390
  }
@@ -53084,7 +54421,7 @@ async function readNewestWrapUpOutcome(dir) {
53084
54421
  const archiveDir = path148.join(dir, "slate-archive");
53085
54422
  let entries;
53086
54423
  try {
53087
- entries = (await readdir24(archiveDir)).filter((name) => name.endsWith("-wrap-up-outcome.json"));
54424
+ entries = (await readdir25(archiveDir)).filter((name) => name.endsWith("-wrap-up-outcome.json"));
53088
54425
  } catch {
53089
54426
  return;
53090
54427
  }
@@ -53529,7 +54866,7 @@ function openFlows(otui, chrome, options) {
53529
54866
 
53530
54867
  // src/tui/busy-dispatch.ts
53531
54868
  function classifyBusyDispatch(params) {
53532
- const { line, commandName, isSessionInfo, isFlows, isWorkspace, isReview } = params;
54869
+ const { line, commandName, isSessionInfo, isFlows, isWorkspace, isReview, isMcp } = params;
53533
54870
  if (commandName === "/exit")
53534
54871
  return "exit";
53535
54872
  if (commandName === "/help")
@@ -53548,7 +54885,7 @@ function classifyBusyDispatch(params) {
53548
54885
  return "copy";
53549
54886
  if (commandName === "/mode")
53550
54887
  return "mode";
53551
- const isBusyReadonlyCommand = isSessionInfo || isFlows || isWorkspace || isReview;
54888
+ const isBusyReadonlyCommand = isSessionInfo || isFlows || isWorkspace || isReview || isMcp;
53552
54889
  if (isBusyReadonlyCommand && isSessionInfo)
53553
54890
  return "session-info";
53554
54891
  if (isBusyReadonlyCommand && isFlows)
@@ -53557,6 +54894,8 @@ function classifyBusyDispatch(params) {
53557
54894
  return "workspace";
53558
54895
  if (isBusyReadonlyCommand && isReview)
53559
54896
  return "review";
54897
+ if (isBusyReadonlyCommand && isMcp)
54898
+ return "mcp";
53560
54899
  if (commandName !== undefined || line.startsWith("/"))
53561
54900
  return "deferred";
53562
54901
  return "not-a-command";
@@ -53816,7 +55155,8 @@ function openWorkspace(otui, chrome, options) {
53816
55155
  var REVIEW_COMMAND = "/review";
53817
55156
  var REVIEW_FOOTER = [
53818
55157
  { key: "[/]", label: "item" },
53819
- { key: "a y", label: "accept" },
55158
+ { key: "a y", label: "accept proposal" },
55159
+ { key: "d y", label: "decline proposal" },
53820
55160
  { key: "\u2191/\u2193", label: "scroll" },
53821
55161
  { key: "\u2190/\u2192", label: "tabs" },
53822
55162
  { key: "esc", label: "close" }
@@ -53834,7 +55174,7 @@ var TYPE_LABEL = {
53834
55174
  function summarizeReviewItem(item) {
53835
55175
  switch (item.type) {
53836
55176
  case "proposal":
53837
- return `${item.proposalId} in ${item.workspaceId}${item.fresh ? "" : " (stale)"}`;
55177
+ return `${item.kind} ${item.proposalId} in ${item.workspaceId}${item.fresh ? "" : " (stale)"}`;
53838
55178
  case "blocked":
53839
55179
  return `${item.sessionId} \u2014 ${item.terminalState.reason}`;
53840
55180
  case "unbound-candidate":
@@ -53858,9 +55198,13 @@ function describeReviewItem(item) {
53858
55198
  return [
53859
55199
  `Proposal ${item.proposalId}`,
53860
55200
  `Workspace ${item.workspaceId}`,
55201
+ `Kind ${item.kind}`,
55202
+ `Author ${item.author}`,
55203
+ `Created ${item.createdAt}`,
53861
55204
  `Evidence ${item.fresh ? "fresh" : "stale \u2014 evidence has drifted since this proposal was created; re-run wrap-up before deciding"}`,
55205
+ ...item.note !== undefined ? ["", `Note ${item.note}`] : [],
53862
55206
  "",
53863
- `Reject/dismiss from a terminal: keryx workspace review ${item.workspaceId} ${item.proposalId} --decision <rejected|dismissed>`
55207
+ `Dismiss (archive with no decision) from a terminal: keryx workspace review ${item.workspaceId} ${item.proposalId} --decision dismissed`
53864
55208
  ];
53865
55209
  case "blocked":
53866
55210
  return [
@@ -53913,25 +55257,37 @@ function describeGroupOutcome(g) {
53913
55257
  }
53914
55258
  }
53915
55259
  }
55260
+ var DECISION_VERB = { accept: "Accept", decline: "Decline" };
55261
+ var DECISION_ING = { accept: "Accepting", decline: "Declining" };
55262
+ var DECISION_DONE = { accept: "Accepted", decline: "Declined" };
55263
+ var DECISION_COMMAND = {
55264
+ accept: "running `keryx workspace confirm-review` then `keryx workspace review`",
55265
+ decline: "running `keryx workspace review --decision rejected`"
55266
+ };
53916
55267
  function formatReviewDetailLines(item, status) {
53917
55268
  if (item === undefined) {
53918
55269
  return ["No item selected.", "", "Press Enter (or click a row) on the Review tab to view one."];
53919
55270
  }
53920
55271
  const lines = describeReviewItem(item);
53921
55272
  if (item.type !== "proposal") {
55273
+ if (status.kind === "unavailable") {
55274
+ return [...lines, "", `[${status.decision === "accept" ? "a" : "d"}] does nothing here \u2014 accept/decline only apply to a pending proposal, not to this item.`];
55275
+ }
53922
55276
  return lines;
53923
55277
  }
53924
55278
  const withAction = [...lines, ""];
53925
55279
  if (status.kind === "armed") {
53926
- withAction.push("Press [y] to CONFIRM accept, any other key cancels.");
55280
+ withAction.push(`Press [y] to CONFIRM ${status.decision}, any other key cancels.`);
53927
55281
  } else if (status.kind === "running") {
53928
- withAction.push("Accepting\u2026 running `keryx workspace confirm-review` then `keryx workspace review`.");
55282
+ withAction.push(`${DECISION_ING[status.decision]}\u2026 ${DECISION_COMMAND[status.decision]}.`);
53929
55283
  } else if (status.kind === "done" && status.outcome.ok) {
53930
- withAction.push("\u2713 Accepted.");
55284
+ withAction.push(`\u2713 ${DECISION_DONE[status.decision]}.`);
53931
55285
  } else if (status.kind === "done" && !status.outcome.ok) {
53932
- withAction.push(`\u2717 Accept failed: ${status.outcome.message}`);
55286
+ withAction.push(`\u2717 ${DECISION_VERB[status.decision]} failed: ${status.outcome.message}`);
55287
+ } else if (status.kind === "unavailable") {
55288
+ withAction.push(`[${status.decision === "accept" ? "a" : "d"}] does nothing \u2014 no ${status.decision} handler is configured for this modal.`);
53933
55289
  } else {
53934
- withAction.push("[a] Accept this proposal");
55290
+ withAction.push("[a] Accept this proposal [d] Decline this proposal");
53935
55291
  }
53936
55292
  return withAction;
53937
55293
  }
@@ -54028,17 +55384,19 @@ function presentReview(openModal2, otui, chrome, options) {
54028
55384
  status = { kind: "idle" };
54029
55385
  paintSelection();
54030
55386
  };
54031
- const runAccept = () => {
55387
+ const handlerFor = (decision) => decision === "accept" ? options.acceptProposal : options.declineProposal;
55388
+ const runDecision = (decision) => {
54032
55389
  const item = items[selected];
54033
- if (item === undefined || item.type !== "proposal" || options.acceptProposal === undefined) {
55390
+ const run = handlerFor(decision);
55391
+ if (item === undefined || item.type !== "proposal" || run === undefined) {
54034
55392
  return;
54035
55393
  }
54036
- status = { kind: "running" };
55394
+ status = { kind: "running", decision };
54037
55395
  paintSelection();
54038
- options.acceptProposal(item).then((outcome) => {
54039
- status = { kind: "done", outcome };
55396
+ run(item).then((outcome) => {
55397
+ status = { kind: "done", decision, outcome };
54040
55398
  if (outcome.ok) {
54041
- options.onAccepted?.(item);
55399
+ options.onResolved?.(item);
54042
55400
  }
54043
55401
  paintSelection();
54044
55402
  });
@@ -54078,7 +55436,7 @@ function presentReview(openModal2, otui, chrome, options) {
54078
55436
  const onDetail = handle.activeTab() === "detail";
54079
55437
  if (onDetail && status.kind === "armed") {
54080
55438
  if (token === "y") {
54081
- runAccept();
55439
+ runDecision(status.decision);
54082
55440
  } else {
54083
55441
  status = { kind: "idle" };
54084
55442
  paintSelection();
@@ -54097,8 +55455,9 @@ function presentReview(openModal2, otui, chrome, options) {
54097
55455
  handle.setTab("detail");
54098
55456
  return;
54099
55457
  }
54100
- if (onDetail && token === "a" && items[selected]?.type === "proposal" && options.acceptProposal !== undefined && status.kind !== "running") {
54101
- status = { kind: "armed" };
55458
+ if (onDetail && (token === "a" || token === "d") && status.kind !== "running") {
55459
+ const decision = token === "a" ? "accept" : "decline";
55460
+ status = items[selected]?.type === "proposal" && handlerFor(decision) !== undefined ? { kind: "armed", decision } : { kind: "unavailable", decision };
54102
55461
  paintSelection();
54103
55462
  return;
54104
55463
  }
@@ -54162,6 +55521,228 @@ async function acceptProposalViaShell(run, workspaceId, proposalId) {
54162
55521
  }
54163
55522
  return { ok: true };
54164
55523
  }
55524
+ async function declineProposalViaShell(run, workspaceId, proposalId) {
55525
+ const decline = await run(`keryx workspace review ${shQuote(workspaceId)} ${shQuote(proposalId)} --decision rejected`);
55526
+ if (decline.isError) {
55527
+ return { ok: false, message: decline.output };
55528
+ }
55529
+ return { ok: true };
55530
+ }
55531
+
55532
+ // src/tui/mcp-inspector.ts
55533
+ var MCP_INSPECTOR_FOOTER = [
55534
+ { key: "\u2191/\u2193", label: "select" },
55535
+ { key: "c/d", label: "connect/disconnect" },
55536
+ { key: "y", label: "confirm" },
55537
+ { key: "\u2190/\u2192", label: "tabs" },
55538
+ { key: "esc", label: "close" }
55539
+ ];
55540
+ var MCP_TOOLS_COMMAND = "/mcp";
55541
+ function isMcpToolsCommand(line) {
55542
+ const token = line.trim().split(/\s+/)[0] ?? "";
55543
+ return token === MCP_TOOLS_COMMAND;
55544
+ }
55545
+ var RUNTIME_LABELS = {
55546
+ cursor: "Cursor",
55547
+ claude: "Claude Code",
55548
+ opencode: "opencode",
55549
+ vscode: "VS Code",
55550
+ generic: "Generic (manual)"
55551
+ };
55552
+ function runtimeLabel(id) {
55553
+ return RUNTIME_LABELS[id] ?? id;
55554
+ }
55555
+ function formatToolsListLines(tools) {
55556
+ if (tools.length === 0) {
55557
+ return ["No tools available."];
55558
+ }
55559
+ return tools.map((tool) => {
55560
+ const risk = (tool.risk ?? "read").padEnd(6);
55561
+ const name = tool.name.padEnd(28);
55562
+ return `${name} ${risk} ${tool.description ?? ""}`.trimEnd();
55563
+ });
55564
+ }
55565
+ function isActionable(id) {
55566
+ return id !== "generic";
55567
+ }
55568
+ function formatMcpListLines(runtimes, selected, status) {
55569
+ if (runtimes.length === 0) {
55570
+ return ["No MCP client runtimes registered."];
55571
+ }
55572
+ return runtimes.map((runtime, index) => {
55573
+ const mark = index === selected ? ">" : " ";
55574
+ const label = runtimeLabel(runtime.id).padEnd(20);
55575
+ const statusText = runtime.connected ? "\u25CF connected" : "\u25CB not connected";
55576
+ let action = "";
55577
+ if (!isActionable(runtime.id)) {
55578
+ action = " (copy snippet manually)";
55579
+ } else if (status.kind === "armed" && status.target.id === runtime.id) {
55580
+ action = ` [press y to ${status.target.action}]`;
55581
+ } else if (status.kind === "running" && status.target.id === runtime.id) {
55582
+ action = ` ${status.target.action === "connect" ? "connecting\u2026" : "disconnecting\u2026"}`;
55583
+ } else if (status.kind === "done" && status.target.id === runtime.id) {
55584
+ action = status.outcome.ok ? " \u2713 done" : ` \u2717 ${status.outcome.message}`;
55585
+ } else {
55586
+ action = runtime.connected ? " [d] disconnect" : " [c] connect";
55587
+ }
55588
+ return `${mark} ${label} ${statusText}${action}`;
55589
+ });
55590
+ }
55591
+ function presentMcpTools(openModal2, otui, chrome, options) {
55592
+ const runtimes = options.runtimes.map((r) => ({ ...r }));
55593
+ let mcpSelected = 0;
55594
+ let toolsScroll = 0;
55595
+ let mcpScroll = 0;
55596
+ let status = { kind: "idle" };
55597
+ let toolsNode;
55598
+ let mcpNode;
55599
+ let unsubscribeKey;
55600
+ const rendererHint = options.renderer ?? chrome?.renderer;
55601
+ const bodyRows = options.visibleRows ?? (typeof rendererHint?.width === "number" && typeof rendererHint.height === "number" ? modalBodyRows(resolveModalPanelSize(rendererHint.width, rendererHint.height).height) : 13);
55602
+ const toolLines = () => formatToolsListLines(options.tools);
55603
+ const mcpLines = () => formatMcpListLines(runtimes, mcpSelected, status);
55604
+ const paint = () => {
55605
+ toolsScroll = clampScroll3(toolsScroll, toolLines().length, bodyRows);
55606
+ mcpScroll = scrollToReveal3(mcpSelected, mcpScroll, bodyRows);
55607
+ mcpScroll = clampScroll3(mcpScroll, mcpLines().length, bodyRows);
55608
+ if (toolsNode !== undefined) {
55609
+ toolsNode.content = windowLines3(toolLines(), toolsScroll, bodyRows).join(`
55610
+ `);
55611
+ }
55612
+ if (mcpNode !== undefined) {
55613
+ mcpNode.content = windowLines3(mcpLines(), mcpScroll, bodyRows).join(`
55614
+ `);
55615
+ }
55616
+ };
55617
+ const moveMcpSelection = (next) => {
55618
+ if (runtimes.length === 0) {
55619
+ return;
55620
+ }
55621
+ const clamped = Math.min(runtimes.length - 1, Math.max(0, next));
55622
+ if (clamped === mcpSelected) {
55623
+ return;
55624
+ }
55625
+ mcpSelected = clamped;
55626
+ status = { kind: "idle" };
55627
+ paint();
55628
+ };
55629
+ const runAction = () => {
55630
+ if (status.kind !== "armed") {
55631
+ return;
55632
+ }
55633
+ const target = status.target;
55634
+ status = { kind: "running", target };
55635
+ paint();
55636
+ const fn = target.action === "connect" ? options.connect : options.disconnect;
55637
+ fn(target.id).then((outcome) => {
55638
+ status = { kind: "done", target, outcome };
55639
+ if (outcome.ok) {
55640
+ const row = runtimes.find((r) => r.id === target.id);
55641
+ if (row !== undefined) {
55642
+ row.connected = target.action === "connect";
55643
+ }
55644
+ }
55645
+ options.onStatusChange?.(runtimes);
55646
+ paint();
55647
+ });
55648
+ };
55649
+ const handle = openModal2(otui, chrome, {
55650
+ title: "Tools & MCP",
55651
+ tabs: [
55652
+ { id: "tools", label: "Tools" },
55653
+ { id: "mcp", label: "MCP" }
55654
+ ],
55655
+ initialTab: "tools",
55656
+ footer: MCP_INSPECTOR_FOOTER,
55657
+ renderTab: (tabId, body, ctx) => {
55658
+ const renderer = options.renderer ?? chrome?.renderer;
55659
+ const parent = body;
55660
+ const ctor = otui.TextRenderable;
55661
+ if (parent.add === undefined || ctor === undefined) {
55662
+ return;
55663
+ }
55664
+ if (tabId === "tools") {
55665
+ toolsScroll = clampScroll3(toolsScroll, toolLines().length, bodyRows);
55666
+ toolsNode = new ctor(renderer, { id: "mcp-tools-body", content: windowLines3(toolLines(), toolsScroll, bodyRows).join(`
55667
+ `) });
55668
+ parent.add(toolsNode);
55669
+ return;
55670
+ }
55671
+ mcpScroll = scrollToReveal3(mcpSelected, mcpScroll, bodyRows);
55672
+ mcpNode = new ctor(renderer, { id: "mcp-mcp-body", content: windowLines3(mcpLines(), mcpScroll, bodyRows).join(`
55673
+ `) });
55674
+ parent.add(mcpNode);
55675
+ },
55676
+ onClose: () => {
55677
+ unsubscribeKey?.();
55678
+ }
55679
+ });
55680
+ if (handle === undefined) {
55681
+ return;
55682
+ }
55683
+ if (options.onKeypress !== undefined) {
55684
+ unsubscribeKey = options.onKeypress((key) => {
55685
+ const token = key.name || key.sequence;
55686
+ const onMcp = handle.activeTab() === "mcp";
55687
+ if (onMcp && status.kind === "armed") {
55688
+ if (token === "y") {
55689
+ runAction();
55690
+ } else {
55691
+ status = { kind: "idle" };
55692
+ paint();
55693
+ }
55694
+ return;
55695
+ }
55696
+ if (onMcp && token === "c") {
55697
+ const row = runtimes[mcpSelected];
55698
+ if (row !== undefined && isActionable(row.id) && !row.connected && status.kind !== "running") {
55699
+ status = { kind: "armed", target: { id: row.id, action: "connect" } };
55700
+ paint();
55701
+ }
55702
+ return;
55703
+ }
55704
+ if (onMcp && token === "d") {
55705
+ const row = runtimes[mcpSelected];
55706
+ if (row !== undefined && isActionable(row.id) && row.connected && status.kind !== "running") {
55707
+ status = { kind: "armed", target: { id: row.id, action: "disconnect" } };
55708
+ paint();
55709
+ }
55710
+ return;
55711
+ }
55712
+ if (token === "up" || token === "k") {
55713
+ if (onMcp) {
55714
+ moveMcpSelection(mcpSelected - 1);
55715
+ } else {
55716
+ toolsScroll = clampScroll3(toolsScroll - 1, toolLines().length, bodyRows);
55717
+ paint();
55718
+ }
55719
+ return;
55720
+ }
55721
+ if (token === "down" || token === "j") {
55722
+ if (onMcp) {
55723
+ moveMcpSelection(mcpSelected + 1);
55724
+ } else {
55725
+ toolsScroll = clampScroll3(toolsScroll + 1, toolLines().length, bodyRows);
55726
+ paint();
55727
+ }
55728
+ return;
55729
+ }
55730
+ if (token === "pageup" || token === "pagedown") {
55731
+ const step = token === "pageup" ? -bodyRows : bodyRows;
55732
+ if (onMcp) {
55733
+ mcpScroll = clampScroll3(mcpScroll + step, mcpLines().length, bodyRows);
55734
+ } else {
55735
+ toolsScroll = clampScroll3(toolsScroll + step, toolLines().length, bodyRows);
55736
+ }
55737
+ paint();
55738
+ }
55739
+ });
55740
+ }
55741
+ return handle;
55742
+ }
55743
+ function openMcpTools(otui, chrome, options) {
55744
+ return presentMcpTools((hostOtui, hostChrome, input2) => openModal(hostOtui, hostChrome, input2), otui, chrome, options);
55745
+ }
54165
55746
 
54166
55747
  // src/tui/tui-shell.ts
54167
55748
  init_slate();
@@ -54532,6 +56113,11 @@ var AGENT_SLASH_COMMANDS = [
54532
56113
  description: "Show project-wide items needing review (proposals, blocked sessions)",
54533
56114
  modes: AGENT_ONLY
54534
56115
  },
56116
+ {
56117
+ name: "/mcp",
56118
+ description: "Show available tools and MCP client connect status",
56119
+ modes: AGENT_ONLY
56120
+ },
54535
56121
  {
54536
56122
  name: "/compact",
54537
56123
  description: "Compact model context \u2014 /compact [focus] (archive kept)",
@@ -54921,6 +56507,38 @@ init_providers();
54921
56507
  init_patch_risk();
54922
56508
  init_shell_config();
54923
56509
 
56510
+ // src/mcp-client/wire.ts
56511
+ var STANDARD_ELICITATION_PARAM_KEYS = new Set(["message", "requestedSchema"]);
56512
+
56513
+ // src/mcp-client/elicitation.ts
56514
+ init_command_risk();
56515
+ function extractCodexCommand(vendor) {
56516
+ const raw = vendor.codex_command;
56517
+ if (!Array.isArray(raw))
56518
+ return;
56519
+ return raw.every((entry) => typeof entry === "string") ? raw : undefined;
56520
+ }
56521
+ var SHELL_BASENAMES = new Set(["zsh", "bash", "sh", "ksh", "dash", "fish"]);
56522
+ var MCP_ELICITATION_TOOL_PREFIX = "mcp_elicitation:";
56523
+ function describeElicitationPrompt(tool, inputJson) {
56524
+ if (!tool.startsWith(MCP_ELICITATION_TOOL_PREFIX))
56525
+ return;
56526
+ let parsed;
56527
+ try {
56528
+ parsed = JSON.parse(inputJson);
56529
+ } catch {
56530
+ return { message: inputJson, command: undefined };
56531
+ }
56532
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
56533
+ return { message: inputJson, command: undefined };
56534
+ }
56535
+ const obj = parsed;
56536
+ const message2 = typeof obj.message === "string" && obj.message.trim().length > 0 ? obj.message : "codex is requesting approval for an action";
56537
+ const vendor = typeof obj.vendor === "object" && obj.vendor !== null ? obj.vendor : {};
56538
+ const command = extractCodexCommand(vendor)?.join(" ");
56539
+ return { message: message2, command };
56540
+ }
56541
+
54924
56542
  // src/lib/permission-mode-config.ts
54925
56543
  init_permission_mode();
54926
56544
  init_config_dir();
@@ -55078,6 +56696,10 @@ function onKeypress2(r, handler) {
55078
56696
  return () => r._internalKeyInput.offInternal("keypress", handler);
55079
56697
  }
55080
56698
  function showComposerChoice(otui, r, dock, request) {
56699
+ if (dock.visible === true) {
56700
+ request.onBusy?.();
56701
+ return Promise.resolve(request.cancelId);
56702
+ }
55081
56703
  return new Promise((resolve3) => {
55082
56704
  const options = request.options.map((o) => ({
55083
56705
  ...o,
@@ -58556,6 +60178,275 @@ ${rows.join(`
58556
60178
  (none)`}
58557
60179
  `;
58558
60180
  }
60181
+ async function selectSearchProviderAndReport(controller, onSystem, providerId) {
60182
+ const result = await controller.select(providerId);
60183
+ if (!result.ok) {
60184
+ if (result.reason === "not-configured") {
60185
+ onSystem?.(`Cannot select '${providerId}': provider is not configured.
60186
+ `);
60187
+ } else if (result.reason === "not-connected") {
60188
+ onSystem?.(`Cannot select '${providerId}': provider is not connected (run /search-provider ${providerId} <params> to test).
60189
+ `);
60190
+ } else {
60191
+ onSystem?.(`Cannot select '${providerId}': ${result.reason}.
60192
+ `);
60193
+ }
60194
+ return;
60195
+ }
60196
+ onSystem?.(`Search provider '${providerId}' selected.
60197
+ `);
60198
+ }
60199
+ function promptSearchFieldStep(otui, r, field3, value) {
60200
+ return new Promise((resolve3) => {
60201
+ const box = overlayBox(otui, r, "search-field-picker");
60202
+ r.root.add(box);
60203
+ box.add(new otui.TextRenderable(r, { id: "sf-title", content: otui.t`${otui.bold(field3.label)} ${otui.dim(`${field3.required ? "required" : "optional"} \xB7 Enter \xB7 Esc to go back`)}` }));
60204
+ const input2 = new otui.InputRenderable(r, { id: "sf-input", value, marginTop: 1 });
60205
+ box.add(input2);
60206
+ input2.focus();
60207
+ const cleanup = () => {
60208
+ unsub();
60209
+ input2.blur();
60210
+ r.root.remove(box);
60211
+ };
60212
+ const unsub = onKeypress4(r, (key) => {
60213
+ if (key.name === "escape") {
60214
+ cleanup();
60215
+ resolve3({ kind: "back" });
60216
+ key.preventDefault();
60217
+ key.stopPropagation();
60218
+ }
60219
+ });
60220
+ input2.on(otui.InputRenderableEvents.ENTER, () => {
60221
+ const entered = input2.value.trim();
60222
+ cleanup();
60223
+ resolve3({ kind: "value", value: entered });
60224
+ });
60225
+ });
60226
+ }
60227
+ function promptSearchCredentialStep(otui, r, opts) {
60228
+ return new Promise((resolve3) => {
60229
+ const box = overlayBox(otui, r, "search-credential-picker");
60230
+ r.root.add(box);
60231
+ box.add(new otui.TextRenderable(r, { id: "sc-title", content: otui.t`${otui.bold(`Paste your ${opts.label}`)} ${otui.dim("(Enter \xB7 Esc to go back)")}` }));
60232
+ box.add(new otui.TextRenderable(r, {
60233
+ id: "sc-note",
60234
+ content: otui.t`${otui.dim("Saved to your keryx config dir (owner-only, 0600)")}`,
60235
+ marginTop: 1
60236
+ }));
60237
+ const keyInput = new otui.InputRenderable(r, { id: "sc-input", placeholder: "...", marginTop: 1 });
60238
+ box.add(keyInput);
60239
+ keyInput.focus();
60240
+ const cleanup = () => {
60241
+ unsub();
60242
+ keyInput.blur();
60243
+ r.root.remove(box);
60244
+ };
60245
+ const unsub = onKeypress4(r, (key) => {
60246
+ if (key.name === "escape") {
60247
+ cleanup();
60248
+ resolve3({ kind: "back" });
60249
+ key.preventDefault();
60250
+ key.stopPropagation();
60251
+ }
60252
+ });
60253
+ keyInput.on(otui.InputRenderableEvents.ENTER, () => {
60254
+ const value = keyInput.value.trim();
60255
+ cleanup();
60256
+ resolve3(value.length > 0 ? { kind: "key", value } : { kind: "skip" });
60257
+ });
60258
+ });
60259
+ }
60260
+ function promptSetActiveProviderStep(otui, r) {
60261
+ return new Promise((resolve3) => {
60262
+ const box = overlayBox(otui, r, "search-active-picker");
60263
+ r.root.add(box);
60264
+ box.add(new otui.TextRenderable(r, { id: "sa-title", content: otui.t`${otui.bold("Set as active provider after a successful test?")} ${otui.dim("(\u2191/\u2193, Enter \xB7 Esc to go back)")}` }));
60265
+ const select = new otui.SelectRenderable(r, {
60266
+ id: "sa-select",
60267
+ width: 60,
60268
+ height: selectBoxHeight(2, true),
60269
+ options: [
60270
+ { name: "Yes", description: "select it once the test passes" },
60271
+ { name: "No", description: "leave it configured but inactive" }
60272
+ ],
60273
+ selectedTextColor: "#ffd166"
60274
+ });
60275
+ box.add(select);
60276
+ select.focus();
60277
+ const cleanup = () => {
60278
+ unsub();
60279
+ select.blur();
60280
+ r.root.remove(box);
60281
+ };
60282
+ const unsub = onKeypress4(r, (key) => {
60283
+ if (key.name === "escape") {
60284
+ cleanup();
60285
+ resolve3(undefined);
60286
+ key.preventDefault();
60287
+ key.stopPropagation();
60288
+ }
60289
+ });
60290
+ select.on(otui.SelectRenderableEvents.ITEM_SELECTED, () => {
60291
+ const chosen = select.getSelectedOption();
60292
+ cleanup();
60293
+ resolve3(chosen === null ? undefined : chosen.name === "Yes");
60294
+ });
60295
+ });
60296
+ }
60297
+ function pickSearchProviderStep(otui, r, providers) {
60298
+ return new Promise((resolve3) => {
60299
+ const box = overlayBox(otui, r, "search-provider-picker");
60300
+ r.root.add(box);
60301
+ box.add(new otui.TextRenderable(r, { id: "spp-title", content: otui.t`${otui.bold("Select a search provider")} ${otui.dim("(\u2191/\u2193, Enter \xB7 Esc to cancel)")}` }));
60302
+ const labelOf = (p) => `${p.id} (${p.displayName})`;
60303
+ const select = new otui.SelectRenderable(r, {
60304
+ id: "spp-select",
60305
+ width: 60,
60306
+ height: selectBoxHeight(providers.length, true),
60307
+ showScrollIndicator: true,
60308
+ options: providers.map((p) => ({ name: labelOf(p), description: p.kind })),
60309
+ selectedTextColor: "#ffd166"
60310
+ });
60311
+ box.add(select);
60312
+ select.focus();
60313
+ const cleanup = () => {
60314
+ unsub();
60315
+ select.blur();
60316
+ r.root.remove(box);
60317
+ };
60318
+ const unsub = onKeypress4(r, (key) => {
60319
+ if (key.name === "escape") {
60320
+ cleanup();
60321
+ resolve3(undefined);
60322
+ key.preventDefault();
60323
+ key.stopPropagation();
60324
+ }
60325
+ });
60326
+ select.on(otui.SelectRenderableEvents.ITEM_SELECTED, () => {
60327
+ const chosen = select.getSelectedOption();
60328
+ cleanup();
60329
+ resolve3(chosen === null ? undefined : providers.find((p) => labelOf(p) === chosen.name));
60330
+ });
60331
+ });
60332
+ }
60333
+ async function runSearchProviderFieldsStep(otui, r, provider, seed) {
60334
+ const subSteps = [
60335
+ ...provider.fields.map((field3) => ({ kind: "field", field: field3 })),
60336
+ ...provider.credentialSchema.required ? [{ kind: "credential" }] : [],
60337
+ { kind: "toggle" }
60338
+ ];
60339
+ const fields = { ...seed.fields };
60340
+ let credential2 = seed.credential;
60341
+ let setActive = seed.setActive;
60342
+ let index = 0;
60343
+ while (index < subSteps.length) {
60344
+ const step = subSteps[index];
60345
+ if (step === undefined)
60346
+ break;
60347
+ if (step.kind === "field") {
60348
+ const result = await promptSearchFieldStep(otui, r, step.field, fields[step.field.id] ?? step.field.defaultValue ?? "");
60349
+ if (result.kind === "back") {
60350
+ index -= 1;
60351
+ if (index < 0)
60352
+ return { kind: "back" };
60353
+ continue;
60354
+ }
60355
+ fields[step.field.id] = result.value;
60356
+ index += 1;
60357
+ continue;
60358
+ }
60359
+ if (step.kind === "credential") {
60360
+ const result = await promptSearchCredentialStep(otui, r, {
60361
+ label: provider.credentialSchema.label ?? `${provider.displayName} credential`
60362
+ });
60363
+ if (result.kind === "back") {
60364
+ index -= 1;
60365
+ if (index < 0)
60366
+ return { kind: "back" };
60367
+ continue;
60368
+ }
60369
+ credential2 = result.kind === "key" ? result.value : undefined;
60370
+ index += 1;
60371
+ continue;
60372
+ }
60373
+ const toggle = await promptSetActiveProviderStep(otui, r);
60374
+ if (toggle === undefined) {
60375
+ index -= 1;
60376
+ if (index < 0)
60377
+ return { kind: "back" };
60378
+ continue;
60379
+ }
60380
+ setActive = toggle;
60381
+ index += 1;
60382
+ }
60383
+ return { kind: "done", fields, credential: credential2, setActive };
60384
+ }
60385
+ function runSearchProviderTestStep(otui, r, controller, provider, fields, credential2, setActive) {
60386
+ return new Promise((resolve3) => {
60387
+ const box = overlayBox(otui, r, "search-test-picker");
60388
+ r.root.add(box);
60389
+ const status = new otui.TextRenderable(r, { id: "st-title", content: otui.t`${otui.bold(`Testing '${provider.id}'`)} ${otui.dim("...")}` });
60390
+ box.add(status);
60391
+ let settled;
60392
+ const cleanup = () => {
60393
+ unsub();
60394
+ r.root.remove(box);
60395
+ };
60396
+ const unsub = onKeypress4(r, (key) => {
60397
+ if (settled === "failure" && key.name === "escape") {
60398
+ cleanup();
60399
+ resolve3("retry");
60400
+ key.preventDefault();
60401
+ key.stopPropagation();
60402
+ } else if (settled === "success" && (key.name === "return" || key.name === "linefeed" || key.name === "kpenter")) {
60403
+ cleanup();
60404
+ resolve3("done");
60405
+ key.preventDefault();
60406
+ key.stopPropagation();
60407
+ }
60408
+ });
60409
+ (async () => {
60410
+ controller.configure(provider.id, { ...provider.defaults, ...fields }, credential2);
60411
+ const tested = await controller.test(provider.id);
60412
+ if (!tested.ok) {
60413
+ settled = "failure";
60414
+ const reason = tested.reason === "missing-credential" ? "missing credential" : "connection validation failed";
60415
+ status.content = otui.t`${otui.red("\u2717")} ${otui.bold(`'${provider.id}' test failed: ${reason}`)} ${otui.dim("(Esc to go back and retry)")}`;
60416
+ return;
60417
+ }
60418
+ settled = "success";
60419
+ if (!setActive) {
60420
+ status.content = otui.t`${otui.green("\u2713")} ${otui.bold(`'${provider.id}' configured and tested successfully`)} ${otui.dim("(Enter to close)")}`;
60421
+ return;
60422
+ }
60423
+ const selected = await controller.select(provider.id);
60424
+ status.content = selected.ok ? otui.t`${otui.green("\u2713")} ${otui.bold(`'${provider.id}' configured, tested, and set as active`)} ${otui.dim("(Enter to close)")}` : otui.t`${otui.green("\u2713")} ${otui.bold(`'${provider.id}' configured and tested`)} ${otui.dim(`but could not be set active (${selected.reason ?? "unknown"})`)} ${otui.dim("(Enter to close)")}`;
60425
+ })();
60426
+ });
60427
+ }
60428
+ async function searchProviderWizardInTui(otui, r, controller) {
60429
+ const providers = controller.configurable();
60430
+ providerLoop:
60431
+ while (true) {
60432
+ const provider = await pickSearchProviderStep(otui, r, providers);
60433
+ if (provider === undefined) {
60434
+ return;
60435
+ }
60436
+ let seed = { fields: { ...provider.defaults }, credential: undefined, setActive: false };
60437
+ while (true) {
60438
+ const step2 = await runSearchProviderFieldsStep(otui, r, provider, seed);
60439
+ if (step2.kind === "back") {
60440
+ continue providerLoop;
60441
+ }
60442
+ seed = { fields: step2.fields, credential: step2.credential, setActive: step2.setActive };
60443
+ const result = await runSearchProviderTestStep(otui, r, controller, provider, seed.fields, seed.credential, seed.setActive);
60444
+ if (result === "done") {
60445
+ return;
60446
+ }
60447
+ }
60448
+ }
60449
+ }
58559
60450
  function promptBaseUrlStep(otui, r, label, baseUrl2) {
58560
60451
  return new Promise((resolve3) => {
58561
60452
  const box = overlayBox(otui, r, "base-url-picker");
@@ -59032,7 +60923,13 @@ async function launchTuiAgentShell(opts) {
59032
60923
  const sbContext = new otui.TextRenderable(r, { id: "sb-ctx-v", content: otui.t`${otui.dim("0 tokens")}` });
59033
60924
  sidebar.add(sbContext);
59034
60925
  sidebar.add(new otui.TextRenderable(r, { id: "sb-tools-k", content: otui.t`${otui.dim("Tools")}`, marginTop: 1 }));
59035
- sidebar.add(new otui.TextRenderable(r, { id: "sb-tools-v", content: otui.t`${otui.dim(`${deps.tools.length} available`)}` }));
60926
+ sidebar.add(new otui.TextRenderable(r, {
60927
+ id: "sb-tools-v",
60928
+ content: otui.t`${otui.dim(`${deps.tools.length} available`)}`,
60929
+ onMouseDown: () => {
60930
+ showTools();
60931
+ }
60932
+ }));
59036
60933
  sidebar.add(new otui.TextRenderable(r, { id: "sb-status-k", content: otui.t`${otui.dim("Status")}`, marginTop: 1 }));
59037
60934
  const sbWorkers = new otui.TextRenderable(r, {
59038
60935
  id: "sb-status-v",
@@ -59332,6 +61229,51 @@ async function launchTuiAgentShell(opts) {
59332
61229
  }));
59333
61230
  return id === "allow";
59334
61231
  }
61232
+ if (tool.startsWith(MCP_ELICITATION_TOOL_PREFIX)) {
61233
+ const described = describeElicitationPrompt(tool, inputJson) ?? { message: inputJson, command: undefined };
61234
+ transcript.add(new otui.TextRenderable(r, {
61235
+ id: `ap${uid++}`,
61236
+ content: otui.t`${otui.yellow("\u2699 codex is requesting approval")}`
61237
+ }));
61238
+ transcript.add(new otui.TextRenderable(r, { id: `ap${uid++}`, content: otui.t`${otui.dim(described.message)}` }));
61239
+ if (described.command !== undefined) {
61240
+ transcript.add(new otui.TextRenderable(r, {
61241
+ id: `ap${uid++}`,
61242
+ content: otui.t`${otui.dim(`command: ${described.command}`)}`
61243
+ }));
61244
+ }
61245
+ if (meta?.destructive === true) {
61246
+ transcript.add(new otui.TextRenderable(r, {
61247
+ id: `ap${uid++}`,
61248
+ content: otui.t`${otui.yellow("deletes a file, touches .git/, or touches many files in one call")}`
61249
+ }));
61250
+ }
61251
+ if (meta?.credentials === true) {
61252
+ transcript.add(new otui.TextRenderable(r, {
61253
+ id: `ap${uid++}`,
61254
+ content: otui.t`${otui.yellow("touches the agent's own permission/credential files")}`
61255
+ }));
61256
+ }
61257
+ chrome.hideMenu();
61258
+ setMainAgent("blocked", "approval");
61259
+ const elicitationSubtitle = described.message.length > 80 ? `${described.message.slice(0, 77)}\u2026` : described.message;
61260
+ const id = await showComposerChoice(otui, r, chrome.dock, {
61261
+ title: "Approve codex elicitation?",
61262
+ subtitle: elicitationSubtitle,
61263
+ cancelId: "deny",
61264
+ options: [
61265
+ { id: "allow", label: "Approve", description: "codex proceeds with this action", recommended: true },
61266
+ { id: "deny", label: "Deny", description: "codex's action is refused" }
61267
+ ]
61268
+ });
61269
+ input2.focus();
61270
+ setMainAgent("running", id === "allow" ? "write" : "denied");
61271
+ transcript.add(new otui.TextRenderable(r, {
61272
+ id: `ap${uid++}`,
61273
+ content: id === "allow" ? otui.t`${otui.green("\u25C7 codex elicitation approved")}` : otui.t`${otui.red("\u25C7 codex elicitation denied")}`
61274
+ }));
61275
+ return id === "allow";
61276
+ }
59335
61277
  const ev = evaluateShellApproval({
59336
61278
  inputJson,
59337
61279
  ...meta !== undefined ? { meta } : {},
@@ -59762,7 +61704,8 @@ Staying in the current session.
59762
61704
  openReview(otui, chrome, {
59763
61705
  items,
59764
61706
  acceptProposal: (item) => acceptProposalViaShell(makeCommandRunner(cwd), item.workspaceId, item.proposalId),
59765
- onAccepted: () => {
61707
+ declineProposal: (item) => declineProposalViaShell(makeCommandRunner(cwd), item.workspaceId, item.proposalId),
61708
+ onResolved: () => {
59766
61709
  refreshReviewSidebar();
59767
61710
  },
59768
61711
  renderer: r,
@@ -59770,6 +61713,38 @@ Staying in the current session.
59770
61713
  });
59771
61714
  })();
59772
61715
  };
61716
+ const showTools = () => {
61717
+ (async () => {
61718
+ const cwd = inspectorCwd();
61719
+ const runtimes = await mcpClientStatus(cwd, mcpRuntimeIds());
61720
+ openMcpTools(otui, chrome, {
61721
+ tools: deps.tools.map((t) => t.definition),
61722
+ runtimes,
61723
+ connect: async (id) => {
61724
+ try {
61725
+ const report = await installMcpClient(cwd, [id]);
61726
+ const outcome = report.outcomes[0];
61727
+ if (outcome !== undefined && outcome.errors.length > 0) {
61728
+ return { ok: false, message: outcome.errors.join("; ") };
61729
+ }
61730
+ return { ok: true };
61731
+ } catch (error2) {
61732
+ return { ok: false, message: error2 instanceof Error ? error2.message : String(error2) };
61733
+ }
61734
+ },
61735
+ disconnect: async (id) => {
61736
+ try {
61737
+ await uninstallMcpClient(cwd, [id]);
61738
+ return { ok: true };
61739
+ } catch (error2) {
61740
+ return { ok: false, message: error2 instanceof Error ? error2.message : String(error2) };
61741
+ }
61742
+ },
61743
+ renderer: r,
61744
+ ...inspectorKeys
61745
+ });
61746
+ })();
61747
+ };
59773
61748
  const runModeCommand = (line) => {
59774
61749
  const modeArgs = line.trim().split(/\s+/).slice(1).filter((p) => p.length > 0);
59775
61750
  const wanted = modeArgs[0] ?? "";
@@ -59777,6 +61752,7 @@ Staying in the current session.
59777
61752
  const applyMode = async (next) => {
59778
61753
  if (next === "auto") {
59779
61754
  chrome.hideMenu();
61755
+ let blockedByOpenDialog = false;
59780
61756
  const confirmId = await chrome.withOverlay(() => showComposerChoice(otui, r, chrome.dock, {
59781
61757
  title: "Switch to auto mode?",
59782
61758
  subtitle: "Skips confirmation for EVERY action, including destructive commands. Only credential-touching commands still ask.",
@@ -59784,9 +61760,16 @@ Staying in the current session.
59784
61760
  options: [
59785
61761
  { id: "confirm", label: "Confirm", description: "I understand the risk" },
59786
61762
  { id: "cancel", label: "Cancel", description: "Keep the current mode", recommended: true }
59787
- ]
61763
+ ],
61764
+ onBusy: () => {
61765
+ blockedByOpenDialog = true;
61766
+ chrome.showToast("Answer the open approval first, then retry /mode.");
61767
+ }
59788
61768
  }));
59789
61769
  input2.focus();
61770
+ if (blockedByOpenDialog) {
61771
+ return;
61772
+ }
59790
61773
  if (confirmId !== "confirm") {
59791
61774
  chrome.showToast("Cancelled \u2014 mode unchanged.");
59792
61775
  return;
@@ -59816,6 +61799,7 @@ Staying in the current session.
59816
61799
  const stored = getProjectPermissionMode(sessionCwd);
59817
61800
  chrome.hideMenu();
59818
61801
  (async () => {
61802
+ let blockedByOpenDialog = false;
59819
61803
  const id = await chrome.withOverlay(() => showComposerChoice(otui, r, chrome.dock, {
59820
61804
  title: `Permission mode (current: ${permissionMode})`,
59821
61805
  subtitle: stored !== undefined ? `Project default: ${stored}` : "No project default set.",
@@ -59825,9 +61809,16 @@ Staying in the current session.
59825
61809
  label: m,
59826
61810
  description: MODE_PICKER_DESCRIPTIONS[m],
59827
61811
  recommended: m === permissionMode
59828
- }))
61812
+ })),
61813
+ onBusy: () => {
61814
+ blockedByOpenDialog = true;
61815
+ chrome.showToast("Answer the open approval first, then retry /mode.");
61816
+ }
59829
61817
  }));
59830
61818
  input2.focus();
61819
+ if (blockedByOpenDialog) {
61820
+ return;
61821
+ }
59831
61822
  if (isPermissionMode(id) && id !== permissionMode) {
59832
61823
  await applyMode(id);
59833
61824
  }
@@ -60225,7 +62216,8 @@ Staying in the current session.
60225
62216
  isSessionInfo: isSessionInfoCommand(line),
60226
62217
  isFlows: isFlowsCommand(line),
60227
62218
  isWorkspace: isWorkspaceCommand(line),
60228
- isReview: isReviewCommand(line)
62219
+ isReview: isReviewCommand(line),
62220
+ isMcp: isMcpToolsCommand(line)
60229
62221
  });
60230
62222
  switch (decision) {
60231
62223
  case "exit": {
@@ -60333,6 +62325,10 @@ Staying in the current session.
60333
62325
  showReview();
60334
62326
  return;
60335
62327
  }
62328
+ case "mcp": {
62329
+ showTools();
62330
+ return;
62331
+ }
60336
62332
  case "deferred": {
60337
62333
  transcript.add(new otui.TextRenderable(r, {
60338
62334
  id: `c${uid++}`,
@@ -60356,6 +62352,7 @@ Staying in the current session.
60356
62352
  return;
60357
62353
  }
60358
62354
  (async () => {
62355
+ let blockedByOpenDialog = false;
60359
62356
  const chosen = await showComposerChoice(otui, r, chrome.dock, {
60360
62357
  title: "Main agent is busy",
60361
62358
  subtitle: line,
@@ -60363,8 +62360,17 @@ Staying in the current session.
60363
62360
  { id: "main", label: "Main queue", description: "queue for the main agent; remove/edit/force later", recommended: true },
60364
62361
  { id: "side", label: "Side-1", description: "read-only answer, outside main history (as before)" }
60365
62362
  ],
60366
- cancelId: "side"
62363
+ cancelId: "side",
62364
+ onBusy: () => {
62365
+ blockedByOpenDialog = true;
62366
+ chrome.showToast("Answer the open approval first, then resend.");
62367
+ }
60367
62368
  });
62369
+ if (blockedByOpenDialog) {
62370
+ input2.value = line;
62371
+ input2.focus();
62372
+ return;
62373
+ }
60368
62374
  if (chosen === "main") {
60369
62375
  const id = `mq${mainQueueSeq++}`;
60370
62376
  mainQueue.push({ id, question: line, displayQuestion: displayLine });
@@ -60459,7 +62465,8 @@ Staying in the current session.
60459
62465
  const args2 = parseSearchProviderArgs(line.slice(16));
60460
62466
  const all = searchProviderController.configurable();
60461
62467
  if (args2.providerId === undefined) {
60462
- io.onSystem?.(describeSearchProviderList("Search providers (use /search-provider <id> [key=...]):", all));
62468
+ await chrome.withOverlay(() => searchProviderWizardInTui(otui, r, searchProviderController));
62469
+ input2.focus();
60463
62470
  return;
60464
62471
  }
60465
62472
  const descriptor = all.find((candidate) => candidate.id === args2.providerId);
@@ -60488,11 +62495,18 @@ Staying in the current session.
60488
62495
  const providerId = args2.providerId;
60489
62496
  if (providerId === undefined) {
60490
62497
  const selectable = searchProviderController.selectable();
60491
- io.onSystem?.(describeSearchProviderList("Connected search providers (use /search-connect <id> to select):", selectable));
60492
62498
  if (selectable.length === 0) {
62499
+ io.onSystem?.(describeSearchProviderList("Connected search providers (use /search-connect <id> to select):", selectable));
60493
62500
  io.onSystem?.(`No connected search providers found. Run /search-provider first.
60494
62501
  `);
62502
+ return;
62503
+ }
62504
+ const picked = await chrome.withOverlay(() => pickSearchProviderStep(otui, r, selectable));
62505
+ input2.focus();
62506
+ if (picked === undefined) {
62507
+ return;
60495
62508
  }
62509
+ await selectSearchProviderAndReport(searchProviderController, io.onSystem, picked.id);
60496
62510
  return;
60497
62511
  }
60498
62512
  const normalizedProviderId = searchProviderController.configurable().find((candidate) => candidate.id === providerId)?.id;
@@ -60501,22 +62515,7 @@ Staying in the current session.
60501
62515
  `);
60502
62516
  return;
60503
62517
  }
60504
- const result = await searchProviderController.select(normalizedProviderId);
60505
- if (!result.ok) {
60506
- if (result.reason === "not-configured") {
60507
- io.onSystem?.(`Cannot select '${providerId}': provider is not configured.
60508
- `);
60509
- } else if (result.reason === "not-connected") {
60510
- io.onSystem?.(`Cannot select '${providerId}': provider is not connected (run /search-provider ${providerId} <params> to test).
60511
- `);
60512
- } else {
60513
- io.onSystem?.(`Cannot select '${providerId}': ${result.reason}.
60514
- `);
60515
- }
60516
- return;
60517
- }
60518
- io.onSystem?.(`Search provider '${providerId}' selected.
60519
- `);
62518
+ await selectSearchProviderAndReport(searchProviderController, io.onSystem, normalizedProviderId);
60520
62519
  })();
60521
62520
  return;
60522
62521
  }
@@ -60550,6 +62549,10 @@ Staying in the current session.
60550
62549
  showReview();
60551
62550
  return;
60552
62551
  }
62552
+ if (isMcpToolsCommand(command.name)) {
62553
+ showTools();
62554
+ return;
62555
+ }
60553
62556
  if (command.name === "/copy") {
60554
62557
  const target = newestBlock();
60555
62558
  if (target === undefined || !copyBlock(target.id)) {
@@ -61256,6 +63259,8 @@ init_guard2();
61256
63259
  init_providers();
61257
63260
  var DEFAULT_OLLAMA_BASE_URL = "http://localhost:11434";
61258
63261
  var ANTHROPIC_MODELS = ["claude-sonnet-5", "claude-opus-4-8", "claude-haiku-4-5"];
63262
+ var OPENAI_MODELS = ["gpt-5.6", "gpt-5.6-terra", "gpt-5.6-luna"];
63263
+ var GEMINI_MODELS = ["gemini-3.7-flash", "gemini-2.5-pro", "gemini-2.5-flash-lite"];
61259
63264
  var FAKE_MODELS = ["fake-echo"];
61260
63265
  function isEmbeddingModel(model) {
61261
63266
  const name = typeof model.name === "string" ? model.name.toLowerCase() : "";
@@ -61316,6 +63321,14 @@ async function detectProviders(deps) {
61316
63321
  if (typeof anthropicKey === "string" && anthropicKey.length > 0) {
61317
63322
  detected.push({ name: "anthropic", models: [...ANTHROPIC_MODELS] });
61318
63323
  }
63324
+ const openaiKey = deps.env.OPENAI_API_KEY;
63325
+ if (typeof openaiKey === "string" && openaiKey.length > 0) {
63326
+ detected.push({ name: "openai", models: [...OPENAI_MODELS] });
63327
+ }
63328
+ const geminiKey = deps.env.GEMINI_API_KEY ?? deps.env.GOOGLE_API_KEY;
63329
+ if (typeof geminiKey === "string" && geminiKey.length > 0) {
63330
+ detected.push({ name: "gemini", models: [...GEMINI_MODELS] });
63331
+ }
61319
63332
  for (const p of OPENAI_COMPAT_PROVIDERS) {
61320
63333
  if (!isProviderPlatformSupported(p, platform)) {
61321
63334
  continue;
@@ -62154,6 +64167,37 @@ ${GUTTER}${style.dim("[y/N] ")}`);
62154
64167
  const approved2 = /^y(es)?$/i.test(answer2);
62155
64168
  out(approved2 ? style.green(`approved
62156
64169
  `) : style.red(`denied
64170
+ `));
64171
+ if (!approved2) {
64172
+ return false;
64173
+ }
64174
+ return meta?.fingerprint !== undefined ? { approved: true, fingerprint: meta.fingerprint } : true;
64175
+ }
64176
+ if (tool.startsWith(MCP_ELICITATION_TOOL_PREFIX)) {
64177
+ const described = describeElicitationPrompt(tool, input2) ?? { message: input2, command: undefined };
64178
+ out(`
64179
+ ${GUTTER}${style.yellow("Approve codex elicitation?")}
64180
+ `);
64181
+ out(`${indentBlock(described.message, GUTTER)}
64182
+ `);
64183
+ if (described.command !== undefined) {
64184
+ out(`${GUTTER}${style.dim(`command: ${described.command}`)}
64185
+ `);
64186
+ }
64187
+ if (meta?.destructive === true) {
64188
+ out(`${GUTTER}${style.yellow("deletes a file, touches .git/, or touches many files in one call")}
64189
+ `);
64190
+ }
64191
+ if (meta?.credentials === true) {
64192
+ out(`${GUTTER}${style.yellow("touches the agent's own permission/credential files")}
64193
+ `);
64194
+ }
64195
+ out(`
64196
+ ${GUTTER}${style.dim("[y/N] ")}`);
64197
+ const answer2 = (await readLine() ?? "").trim();
64198
+ const approved2 = /^y(es)?$/i.test(answer2);
64199
+ out(approved2 ? style.green(`approved
64200
+ `) : style.red(`denied
62157
64201
  `));
62158
64202
  if (!approved2) {
62159
64203
  return false;
@@ -65129,7 +67173,7 @@ function printHelp17() {
65129
67173
 
65130
67174
  // src/commands/update.ts
65131
67175
  import { spawn as spawn5 } from "child_process";
65132
- import { chmod as chmod4, mkdir as mkdir55, readFile as readFile79, readdir as readdir25, writeFile as writeFile49 } from "fs/promises";
67176
+ import { chmod as chmod4, mkdir as mkdir55, readFile as readFile79, readdir as readdir26, writeFile as writeFile49 } from "fs/promises";
65133
67177
  import { access as access4, constants as constants2, existsSync as existsSync30 } from "fs";
65134
67178
  import path156 from "path";
65135
67179
  import { fileURLToPath as fileURLToPath7 } from "url";
@@ -65481,7 +67525,7 @@ async function collectTasksDashboardData(metaprojectRoot) {
65481
67525
  }
65482
67526
  let dirEntries;
65483
67527
  try {
65484
- dirEntries = (await readdir25(flowsRoot2, { withFileTypes: true })).filter((entry) => entry.isDirectory() && /^\d{3}-/.test(entry.name)).map((entry) => entry.name).sort();
67528
+ dirEntries = (await readdir26(flowsRoot2, { withFileTypes: true })).filter((entry) => entry.isDirectory() && /^\d{3}-/.test(entry.name)).map((entry) => entry.name).sort();
65485
67529
  } catch {
65486
67530
  return null;
65487
67531
  }
@@ -65775,7 +67819,7 @@ async function collectMarkdownPages(root, hrefPrefix) {
65775
67819
  return pages;
65776
67820
  }
65777
67821
  async function listMarkdownFiles(root) {
65778
- const entries = await readdir25(root, { withFileTypes: true });
67822
+ const entries = await readdir26(root, { withFileTypes: true });
65779
67823
  const files = [];
65780
67824
  for (const entry of entries) {
65781
67825
  const fullPath = path156.join(root, entry.name);
@@ -66215,7 +68259,7 @@ async function runPostUpdateHooks(projectRoot) {
66215
68259
  if (!await pathExists(hooksDir)) {
66216
68260
  return;
66217
68261
  }
66218
- const entries = (await readdir25(hooksDir)).sort();
68262
+ const entries = (await readdir26(hooksDir)).sort();
66219
68263
  for (const entry of entries) {
66220
68264
  const hookPath = path156.join(hooksDir, entry);
66221
68265
  try {
@@ -67921,7 +69965,7 @@ function oracleRates(score) {
67921
69965
  rates.recall = deriveRate(score.truePositives, score.goldSize, ORACLE_RELIABILITY);
67922
69966
  return Object.keys(rates).length > 0 ? rates : undefined;
67923
69967
  }
67924
- var DEFAULT_MODEL3 = "gdgraph-oracle";
69968
+ var DEFAULT_MODEL5 = "gdgraph-oracle";
67925
69969
  var GOLD_KIND_LABELS = {
67926
69970
  "co-change": "co-change prediction",
67927
69971
  dependency: "graph correctness"
@@ -67960,7 +70004,7 @@ function scoreGoldRun(input2, named, options = {}) {
67960
70004
  variant: "baseline",
67961
70005
  run_id: `${taskId}#1`,
67962
70006
  ladder: options.ladder ?? "metastore",
67963
- model: options.model ?? DEFAULT_MODEL3,
70007
+ model: options.model ?? DEFAULT_MODEL5,
67964
70008
  cacheState: options.cacheState ?? "unknown",
67965
70009
  leakageAssertion: options.leakageAssertion ?? "not-applicable",
67966
70010
  caseKind: "deterministic",
@@ -68204,7 +70248,7 @@ function buildEvidenceBundle(input2, options = {}) {
68204
70248
  run: {
68205
70249
  target: score.target,
68206
70250
  variant,
68207
- model: options.model ?? DEFAULT_MODEL3,
70251
+ model: options.model ?? DEFAULT_MODEL5,
68208
70252
  seed,
68209
70253
  cacheState: options.cacheState ?? "unknown",
68210
70254
  startedAt: timestamp,
@@ -69195,7 +71239,7 @@ keryx workspace list-proposals [<workspace-id>]`);
69195
71239
  }
69196
71240
  function renderCatchUp(report, includeLifecycleFlags = true) {
69197
71241
  const sections = [];
69198
- sections.push(renderSection("Pending proposals", report.proposals, (item) => `- Accept, reject, or dismiss proposal ${item.proposalId} in workspace ${item.workspaceId}? ` + `Recommendation: ${item.fresh ? "evidence is fresh \u2014 review now (`keryx workspace review " + item.workspaceId + " " + item.proposalId + " --decision <accepted|rejected|dismissed>`)" : "evidence has drifted since this proposal was created \u2014 treat as stale, re-run wrap-up before deciding"}.`));
71242
+ sections.push(renderSection("Pending proposals", report.proposals, (item) => `- Accept, reject, or dismiss ${item.kind} proposal ${item.proposalId} in workspace ${item.workspaceId}` + `${item.note !== undefined ? `: "${item.note}"` : ""}? ` + `Recommendation: ${item.fresh ? "evidence is fresh \u2014 review now (`keryx workspace review " + item.workspaceId + " " + item.proposalId + " --decision <accepted|rejected|dismissed>`)" : "evidence has drifted since this proposal was created \u2014 treat as stale, re-run wrap-up before deciding"}.`));
69199
71243
  sections.push(renderSection("Blocked sessions (stopped unattended)", report.blocked, (item) => `- Session ${item.sessionId} stopped unattended (${item.terminalState.reason}) at ${item.terminalState.occurredAt}. Resume it, or archive and move on? ` + `Recommendation: \`keryx shell -r ${item.sessionId}\` to resume and unblock it.`));
69200
71244
  sections.push(renderSection("Unbound candidates (wrap-up ran, no workspace bound)", report.unboundCandidates, (item) => `- Session ${item.sessionId} produced untriaged seeds with no workspace bound (${item.summary}). Bind to a workspace and propose, or discard? ` + `Recommendation: pick a workspace, then \`keryx workspace propose <workspace-id> --kind <kind> --session ${item.sessionId}\` (evidence: ${item.evidencePath}).`));
69201
71245
  sections.push(renderSection("Unknown (no resolution recorded)", report.unknown, (item) => `- Session ${item.sessionId} was last seen ${item.lastSeenAt} with no proposal, terminal state, or unbound-candidate artifact recorded. Investigate, or ignore? ` + `Recommendation: \`keryx sessions list\` / \`keryx shell -r ${item.sessionId}\` to see what happened.`));
@@ -69403,6 +71447,6 @@ if (import.meta.main) {
69403
71447
  });
69404
71448
  }
69405
71449
  export {
69406
- main,
69407
- CLI_ROUTES
71450
+ CLI_ROUTES,
71451
+ main
69408
71452
  };