@mrciphersmith/keryx 0.3.78 → 0.3.79

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/cli.js +237 -28
  2. package/dist/core.js +4 -3
  3. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -1758,7 +1758,8 @@ function isValidIp(value) {
1758
1758
  return false;
1759
1759
  }
1760
1760
  function isWordBoundary(flavour, content, index) {
1761
- return flavour.wordChar.test(wordCharBefore(content, index)) !== flavour.wordChar.test(wordCharAt(content, index));
1761
+ flavour.boundary.lastIndex = index;
1762
+ return flavour.boundary.test(content);
1762
1763
  }
1763
1764
  function wordCharBefore(content, index) {
1764
1765
  if (index <= 0) {
@@ -2498,12 +2499,12 @@ var init_pii = __esm(() => {
2498
2499
  UNICODE_EMAIL = {
2499
2500
  from: /(?:(?<=[\p{L}\p{N}\p{M}_])(?![\p{L}\p{N}\p{M}_])|(?<![\p{L}\p{N}\p{M}_])(?=[\p{L}\p{N}\p{M}_]))[\p{L}\p{N}\p{M}._%+-]+@[\p{L}\p{N}\p{M}.-]+\.[\p{L}\p{M}]{2,}(?![\p{L}\p{N}\p{M}_])/uy,
2500
2501
  localChar: /[\p{L}\p{N}\p{M}._%+-]/u,
2501
- wordChar: /[\p{L}\p{N}\p{M}_]/u
2502
+ boundary: /(?:(?<=[\p{L}\p{N}\p{M}_])(?![\p{L}\p{N}\p{M}_])|(?<![\p{L}\p{N}\p{M}_])(?=[\p{L}\p{N}\p{M}_]))/uy
2502
2503
  };
2503
2504
  ASCII_EMAIL = {
2504
2505
  from: /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b/y,
2505
2506
  localChar: /[A-Za-z0-9._%+-]/,
2506
- wordChar: /\w/
2507
+ boundary: /\b/y
2507
2508
  };
2508
2509
  emailScanner = emailScannerFor(UNICODE_EMAIL);
2509
2510
  asciiEmailScanner = emailScannerFor(ASCII_EMAIL);
@@ -21097,7 +21098,7 @@ var package_default;
21097
21098
  var init_package = __esm(() => {
21098
21099
  package_default = {
21099
21100
  name: "@mrciphersmith/keryx",
21100
- version: "0.3.78",
21101
+ version: "0.3.79",
21101
21102
  description: "Version-controlled project context for AI coding agents: code graph, architecture wiki, project memory, relevant tests, quality signals, and task flows.",
21102
21103
  private: false,
21103
21104
  publishConfig: {
@@ -110125,6 +110126,22 @@ var init_theme_text = __esm(() => {
110125
110126
  init_theme();
110126
110127
  });
110127
110128
 
110129
+ // src/tui/transcript-history.ts
110130
+ function trackTranscriptNode(node, policy) {
110131
+ policies.set(node, policy);
110132
+ }
110133
+ function transcriptNodeProtected(node) {
110134
+ return policies.get(node)?.protected() === true;
110135
+ }
110136
+ function forgetTranscriptNode(node) {
110137
+ policies.get(node)?.evict?.();
110138
+ policies.delete(node);
110139
+ }
110140
+ var TRANSCRIPT_HISTORY_LIMIT = 256, policies;
110141
+ var init_transcript_history = __esm(() => {
110142
+ policies = new WeakMap;
110143
+ });
110144
+
110128
110145
  // src/tui/transcript-blocks.ts
110129
110146
  function createBlockRegistry(options = {}) {
110130
110147
  const maxBlocks = options.maxBlocks ?? DEFAULT_MAX_BLOCKS;
@@ -110185,6 +110202,15 @@ function createBlockRegistry(options = {}) {
110185
110202
  }
110186
110203
  return id;
110187
110204
  },
110205
+ remove: (id) => {
110206
+ const index = blocks.findIndex((block) => block.id === id);
110207
+ if (index < 0)
110208
+ return;
110209
+ blocks.splice(index, 1);
110210
+ if (focusIndex > index)
110211
+ focusIndex -= 1;
110212
+ focusIndex = Math.min(focusIndex, blocks.length - 1);
110213
+ },
110188
110214
  get: (id) => {
110189
110215
  const block = find(id);
110190
110216
  return block === undefined ? undefined : snapshot(block);
@@ -110582,8 +110608,13 @@ function createAssistantMessageStream(otui, renderer, parent) {
110582
110608
  flexDirection: "column",
110583
110609
  flexShrink: 0
110584
110610
  });
110611
+ let live = true;
110612
+ trackTranscriptNode(container, { protected: () => live });
110613
+ const complete = () => {
110614
+ live = false;
110615
+ };
110585
110616
  parent.add(container);
110586
- return { container, segmenter: createStreamSegmenter(), views: [], frozen: 0 };
110617
+ return { complete, container, segmenter: createStreamSegmenter(), views: [], frozen: 0 };
110587
110618
  };
110588
110619
  const paint = (m, segments, frozen) => {
110589
110620
  for (let i = m.frozen;i < segments.length; i++) {
@@ -110626,9 +110657,10 @@ function createAssistantMessageStream(otui, renderer, parent) {
110626
110657
  m.frozen = 0;
110627
110658
  const segments = segmentMarkdown(text);
110628
110659
  paint(m, segments, segments.length);
110660
+ m.complete();
110629
110661
  if (segments.length === 0) {
110630
110662
  try {
110631
- parent.remove(m.container);
110663
+ m.container.destroyRecursively();
110632
110664
  } catch {}
110633
110665
  }
110634
110666
  message = undefined;
@@ -110638,11 +110670,12 @@ function createAssistantMessageStream(otui, renderer, parent) {
110638
110670
  if (message === undefined) {
110639
110671
  return;
110640
110672
  }
110673
+ message.complete();
110641
110674
  for (const view of message.views.splice(0)) {
110642
110675
  view.destroy();
110643
110676
  }
110644
110677
  try {
110645
- parent.remove(message.container);
110678
+ message.container.destroyRecursively();
110646
110679
  } catch {}
110647
110680
  message = undefined;
110648
110681
  },
@@ -110660,6 +110693,8 @@ function createBlockView(otui, renderer, parent, block, options = {}) {
110660
110693
  });
110661
110694
  const header = new otui.TextRenderable(renderer, { id: `${id}-h`, content: "" });
110662
110695
  box.add(header);
110696
+ let protectedView = false;
110697
+ trackTranscriptNode(box, { protected: () => protectedView, evict: () => options.onHistoryEvict?.() });
110663
110698
  parent.add(box);
110664
110699
  let body;
110665
110700
  let bodyText;
@@ -110732,6 +110767,9 @@ function createBlockView(otui, renderer, parent, block, options = {}) {
110732
110767
  return {
110733
110768
  id: block.id,
110734
110769
  render: (state, opts = {}) => {
110770
+ if (box.isDestroyed)
110771
+ return;
110772
+ protectedView = opts.focused === true;
110735
110773
  paintHeader(state, opts.focused === true);
110736
110774
  if (state.collapsed) {
110737
110775
  if (options.collapsedPreview !== undefined) {
@@ -110788,7 +110826,14 @@ function createBlockMount(otui, renderer, parent, registry) {
110788
110826
  }
110789
110827
  const state = registry.get(id);
110790
110828
  if (state !== undefined) {
110791
- views.set(id, createBlockView(otui, renderer, parent, state, options));
110829
+ views.set(id, createBlockView(otui, renderer, parent, state, {
110830
+ ...options,
110831
+ onHistoryEvict: () => {
110832
+ views.delete(id);
110833
+ registry.remove(id);
110834
+ options.onHistoryEvict?.();
110835
+ }
110836
+ }));
110792
110837
  }
110793
110838
  return id;
110794
110839
  },
@@ -111014,6 +111059,7 @@ var init_transcript_blocks = __esm(() => {
111014
111059
  init_md_blocks();
111015
111060
  init_theme();
111016
111061
  init_theme_text();
111062
+ init_transcript_history();
111017
111063
  });
111018
111064
 
111019
111065
  // src/tui/modal-host.ts
@@ -111062,6 +111108,20 @@ function resolveModalInnerWidth(availableWidth) {
111062
111108
  function formatModalFooter(actions) {
111063
111109
  return actions.map((action) => `${action.key} ${action.label}`).join(" \xB7 ");
111064
111110
  }
111111
+ function modalFocusFallback(node) {
111112
+ return modalHandBack.get(node) ?? null;
111113
+ }
111114
+ function focusIsLive(node, root) {
111115
+ let current = node;
111116
+ while (current !== null) {
111117
+ if (current.isDestroyed || !current.visible)
111118
+ return false;
111119
+ if (current === root)
111120
+ return true;
111121
+ current = current.parent;
111122
+ }
111123
+ return false;
111124
+ }
111065
111125
  function destroyModalHost(renderer) {
111066
111126
  hosts.get(renderer)?.unsubTheme();
111067
111127
  }
@@ -111076,6 +111136,10 @@ function containsNode(root, node) {
111076
111136
  }
111077
111137
  return false;
111078
111138
  }
111139
+ function modalHides(renderer, node) {
111140
+ const state = hosts.get(renderer);
111141
+ return state !== undefined && state.open && !containsNode(state.backdrop, node);
111142
+ }
111079
111143
  function resolveInitialTab(tabs, initialTab) {
111080
111144
  const first = tabs[0];
111081
111145
  if (first === undefined) {
@@ -111160,7 +111224,15 @@ function closeHost(state, opts) {
111160
111224
  if (state.savedScrollTop !== undefined && scroll !== undefined) {
111161
111225
  scroll.scrollTop = state.savedScrollTop;
111162
111226
  }
111163
- state.chrome.focusComposer();
111227
+ const r = state.chrome.renderer;
111228
+ const current = r.currentFocusedRenderable;
111229
+ if (current === null || containsNode(state.backdrop, current)) {
111230
+ const previous = state.savedFocus;
111231
+ if (previous !== null && focusIsLive(previous, r.root))
111232
+ previous.focus();
111233
+ else
111234
+ state.chrome.focusComposer();
111235
+ }
111164
111236
  }
111165
111237
  if (opts.runOnClose && onClose !== undefined) {
111166
111238
  onClose();
@@ -111290,6 +111362,7 @@ function ensureHost(otui, chrome) {
111290
111362
  releaseOverlay: undefined,
111291
111363
  unsubKeys: undefined,
111292
111364
  savedScrollTop: undefined,
111365
+ savedFocus: null,
111293
111366
  unsubTheme: () => {}
111294
111367
  };
111295
111368
  state.releaseOverlay = chrome.addOverlaySource(() => state.open, { kind: "modal" });
@@ -111414,6 +111487,8 @@ function openModal(otui, chrome, input) {
111414
111487
  state.onClose = undefined;
111415
111488
  previousClose?.();
111416
111489
  } else {
111490
+ state.savedFocus = chrome.renderer.currentFocusedRenderable;
111491
+ modalHandBack.set(state.tabStrip, state.savedFocus);
111417
111492
  state.savedScrollTop = chrome.scroll.scrollTop;
111418
111493
  chrome.hideMenu();
111419
111494
  chrome.blurComposer();
@@ -111440,7 +111515,7 @@ function openModal(otui, chrome, input) {
111440
111515
  }, 0);
111441
111516
  return makeHandle(state, generation);
111442
111517
  }
111443
- var BACKDROP_ID = "modal-backdrop", PANEL_ID = "modal-panel", BACKDROP_ALPHA = 1, MODAL_PANEL_MARGIN = 1, MODAL_PANEL_CHROME_X = 4, MODAL_PANEL_MIN_WIDTH = 72, MODAL_PANEL_MIN_HEIGHT = 18, MODAL_PANEL_SIZE_RATIO = 0.95, MODAL_PANEL_MAX_HEIGHT_RATIO = 0.85, MODAL_CHROME_ROWS = 5, MODAL_PANEL_WIDTH, MODAL_PANEL_HEIGHT, MODAL_PANEL_INNER_WIDTH, CLOSE_HINT = "[x] esc", DEFAULT_FOOTER, hosts, MODAL_BODY_SCROLLBAR_COLS = 1;
111518
+ var BACKDROP_ID = "modal-backdrop", PANEL_ID = "modal-panel", BACKDROP_ALPHA = 1, MODAL_PANEL_MARGIN = 1, MODAL_PANEL_CHROME_X = 4, MODAL_PANEL_MIN_WIDTH = 72, MODAL_PANEL_MIN_HEIGHT = 18, MODAL_PANEL_SIZE_RATIO = 0.95, MODAL_PANEL_MAX_HEIGHT_RATIO = 0.85, MODAL_CHROME_ROWS = 5, MODAL_PANEL_WIDTH, MODAL_PANEL_HEIGHT, MODAL_PANEL_INNER_WIDTH, CLOSE_HINT = "[x] esc", DEFAULT_FOOTER, modalHandBack, hosts, MODAL_BODY_SCROLLBAR_COLS = 1;
111444
111519
  var init_modal_host = __esm(() => {
111445
111520
  init_theme();
111446
111521
  init_theme_text();
@@ -111452,6 +111527,7 @@ var init_modal_host = __esm(() => {
111452
111527
  { key: "\u2190/\u2192", label: "tabs" },
111453
111528
  { key: "esc", label: "close" }
111454
111529
  ];
111530
+ modalHandBack = new WeakMap;
111455
111531
  hosts = new WeakMap;
111456
111532
  });
111457
111533
 
@@ -134844,14 +134920,60 @@ async function createShellChrome(otui, renderer, opts) {
134844
134920
  let liveStatus;
134845
134921
  const originalTranscriptAdd = transcript.add.bind(transcript);
134846
134922
  const originalTranscriptRemove = transcript.remove.bind(transcript);
134923
+ let historyNotice;
134924
+ let historyAnchor;
134925
+ const restoreHistoryAnchor = () => {
134926
+ const anchor = historyAnchor;
134927
+ historyAnchor = undefined;
134928
+ if (anchor === undefined || anchor.node.isDestroyed || scroll.stickyScroll || scroll.scrollTop !== anchor.scrollTop)
134929
+ return;
134930
+ scroll.scrollTop += anchor.node.y - anchor.y;
134931
+ };
134932
+ r.on(otui.CliRenderEvents.FRAME, restoreHistoryAnchor);
134933
+ const trimHistory = () => {
134934
+ const completed = transcript.getChildren().filter((node) => node !== liveStatus && node !== historyNotice && !transcriptNodeProtected(node));
134935
+ const excess = completed.length - TRANSCRIPT_HISTORY_LIMIT;
134936
+ if (excess <= 0)
134937
+ return;
134938
+ if (!scroll.stickyScroll && historyAnchor === undefined) {
134939
+ const evicted = new Set(completed.slice(0, excess));
134940
+ const node = transcript.getChildren().find((child) => child !== liveStatus && child !== historyNotice && !evicted.has(child) && child.y + child.height > scroll.y && child.y < scroll.y + scroll.height);
134941
+ if (node !== undefined)
134942
+ historyAnchor = { node, y: node.y, scrollTop: scroll.scrollTop };
134943
+ }
134944
+ for (const node of completed.slice(0, excess)) {
134945
+ forgetTranscriptNode(node);
134946
+ node.destroyRecursively();
134947
+ }
134948
+ for (let i = userPrompts.length - 1;i >= 0; i--) {
134949
+ if (userPrompts[i]?.box.isDestroyed)
134950
+ userPrompts.splice(i, 1);
134951
+ }
134952
+ if (pinnedPrompt?.box.isDestroyed) {
134953
+ pinnedPrompt = undefined;
134954
+ promptPin.visible = false;
134955
+ }
134956
+ if (historyNotice === undefined || historyNotice.isDestroyed) {
134957
+ historyNotice = new otui.TextRenderable(r, {
134958
+ id: "transcript-history-notice",
134959
+ content: "Older output is no longer mounted in this view.",
134960
+ fg: getTheme().muted,
134961
+ flexShrink: 0
134962
+ });
134963
+ originalTranscriptAdd(historyNotice, 0);
134964
+ }
134965
+ };
134847
134966
  transcript.add = (child, index) => {
134848
134967
  if (liveStatus !== undefined && child !== liveStatus) {
134849
134968
  originalTranscriptRemove(liveStatus);
134850
134969
  const added = originalTranscriptAdd(child, index);
134851
134970
  originalTranscriptAdd(liveStatus);
134971
+ trimHistory();
134852
134972
  return added;
134853
134973
  }
134854
- return originalTranscriptAdd(child, index);
134974
+ const added = originalTranscriptAdd(child, index);
134975
+ trimHistory();
134976
+ return added;
134855
134977
  };
134856
134978
  const queueDock = new otui.BoxRenderable(r, {
134857
134979
  id: "queue-dock",
@@ -135392,6 +135514,7 @@ async function createShellChrome(otui, renderer, opts) {
135392
135514
  } catch {}
135393
135515
  try {
135394
135516
  r.off(otui.CliRenderEvents.FRAME, syncPromptPin);
135517
+ r.off(otui.CliRenderEvents.FRAME, restoreHistoryAnchor);
135395
135518
  } catch {}
135396
135519
  }
135397
135520
  };
@@ -135404,6 +135527,7 @@ var init_shell_chrome = __esm(() => {
135404
135527
  init_modal_host();
135405
135528
  init_debug_log();
135406
135529
  init_renderer_debug();
135530
+ init_transcript_history();
135407
135531
  SIDEBAR_TEXT_WIDTH = SIDEBAR_WIDTH - SIDEBAR_BORDER_LEFT - SIDEBAR_PADDING_LEFT - SIDEBAR_PADDING_RIGHT;
135408
135532
  SPINNER = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
135409
135533
  });
@@ -136741,7 +136865,7 @@ function openRouting(otui, chrome, options) {
136741
136865
  cwd: options.cwd,
136742
136866
  ...options.userConfigDir !== undefined ? { userConfigDir: options.userConfigDir } : {}
136743
136867
  };
136744
- const loadProviders = options.providers ?? defaultProviders;
136868
+ const loadProviders = options.providers ?? loadRoutingProviders;
136745
136869
  const loadProfiles = options.loadProfiles ?? ((dir) => loadModelProfiles(dir));
136746
136870
  const rendererHint = options.renderer ?? chrome?.renderer;
136747
136871
  const panelRows = typeof rendererHint?.width === "number" && typeof rendererHint.height === "number" ? modalBodyRows(resolveModalPanelSize(rendererHint.width, rendererHint.height).height) : 13;
@@ -136956,7 +137080,7 @@ function describePickerRowProfile(opt, profiles) {
136956
137080
  parts.push("non-chat/free \u2014 never auto-derived");
136957
137081
  return parts.join(" \xB7 ");
136958
137082
  }
136959
- async function defaultProviders() {
137083
+ async function loadRoutingProviders() {
136960
137084
  await Promise.resolve().then(() => init_provider_catalog());
136961
137085
  await Promise.resolve().then(() => init_shell_config());
136962
137086
  const catalog = await loadOrRefreshProviderCatalog({ fetch, env: envWithSavedApiKeys() });
@@ -145390,33 +145514,55 @@ var init_classifier_model = __esm(() => {
145390
145514
  });
145391
145515
 
145392
145516
  // src/tui/routing-classifier-source.ts
145517
+ async function loadTurnProviders(load, signal, timeoutMs) {
145518
+ if (signal?.aborted || timeoutMs <= 0)
145519
+ return;
145520
+ let stop;
145521
+ const stopped = new Promise((resolve) => {
145522
+ stop = () => resolve(undefined);
145523
+ });
145524
+ signal?.addEventListener("abort", stop, { once: true });
145525
+ const timer = setTimeout(stop, timeoutMs);
145526
+ try {
145527
+ return await Promise.race([Promise.resolve().then(load), stopped]);
145528
+ } finally {
145529
+ clearTimeout(timer);
145530
+ signal?.removeEventListener("abort", stop);
145531
+ }
145532
+ }
145393
145533
  async function runRoutingClassifierForTurn(line, opts) {
145394
145534
  if (opts.signal?.aborted)
145395
145535
  return;
145396
145536
  if (!opts.enabled)
145397
145537
  return;
145538
+ const deadline = performance.now() + Math.max(0, opts.timeoutMs ?? CLASSIFY_TURN_TIMEOUT_MS);
145398
145539
  const profiles = loadModelProfiles(opts.userConfigDir);
145540
+ const providers = opts.providers === undefined ? opts.detected : await loadTurnProviders(opts.providers, opts.signal, Math.max(0, deadline - performance.now()));
145541
+ if (providers === undefined)
145542
+ return;
145543
+ if (opts.signal?.aborted)
145544
+ return;
145399
145545
  const env = envWithSavedApiKeys(opts.env ?? process.env);
145400
145546
  const external = await resolveExternalSetting({ cwd: opts.cwd, ...opts.userConfigDir !== undefined ? { dir: opts.userConfigDir } : {} });
145401
145547
  if (opts.signal?.aborted)
145402
145548
  return;
145403
- const externalAllowed = externalAllowedConnectedPredicate(connectedPredicateFrom(opts.detected), external.value === "on", loadExternalProvidersConfig(opts.userConfigDir).config);
145404
- const fallbackModel = selectClassifierModel(opts.detected, profiles, (providerId, modelId) => externalAllowed(providerId, modelId) && (opts.classifierAllowed?.(providerId, modelId) ?? true) && (opts.providerFactory !== undefined || hasCredential(providerId, env)));
145549
+ const externalAllowed = externalAllowedConnectedPredicate(connectedPredicateFrom(providers), external.value === "on", loadExternalProvidersConfig(opts.userConfigDir).config);
145550
+ const fallbackModel = selectClassifierModel(providers, profiles, (providerId, modelId) => externalAllowed(providerId, modelId) && (opts.classifierAllowed?.(providerId, modelId) ?? true) && (opts.providerFactory !== undefined || hasCredential(providerId, env)));
145405
145551
  const classification = await classifyTurn(line, TURN_CLASSIFIER_CATEGORIES, {
145406
- jevEnabled: opts.jevEnabled && opts.jevAllowed !== false,
145552
+ jevEnabled: opts.jevEnabled && opts.jevAllowed !== false && external.value === "on",
145407
145553
  ...opts.signal !== undefined ? { signal: opts.signal } : {},
145408
145554
  fallbackModel: fallbackModel ?? null,
145409
145555
  ...opts.providerFactory !== undefined ? { providerFactory: opts.providerFactory } : {},
145410
145556
  ...opts.env !== undefined ? { env: opts.env } : {},
145411
145557
  ...opts.fetch !== undefined ? { fetch: opts.fetch } : {},
145412
- timeoutMs: opts.timeoutMs ?? CLASSIFY_TURN_TIMEOUT_MS
145558
+ timeoutMs: Math.max(0, deadline - performance.now())
145413
145559
  });
145414
145560
  if (opts.signal?.aborted)
145415
145561
  return;
145416
145562
  if (classification === undefined)
145417
145563
  return;
145418
145564
  const baseline = { providerId: opts.sessionProvider, modelId: opts.sessionModel };
145419
- const skippedJev = opts.jevEnabled && opts.jevAllowed === false ? "jev: caller policy denied" : opts.jevEnabled && resolveJevClassifierCredential(opts.env).key === undefined ? "jev: credentials unavailable" : undefined;
145565
+ const skippedJev = opts.jevEnabled && opts.jevAllowed === false ? "jev: caller policy denied" : opts.jevEnabled && external.value !== "on" ? "jev: external policy denied" : opts.jevEnabled && resolveJevClassifierCredential(opts.env).key === undefined ? "jev: credentials unavailable" : undefined;
145420
145566
  if (!classification.result.ok) {
145421
145567
  const failures = classification.trace.flatMap((stage) => stage.result.ok ? [] : [`${stage.source}: ${stage.result.reason}`]);
145422
145568
  return {
@@ -145436,12 +145582,12 @@ async function runRoutingClassifierForTurn(line, opts) {
145436
145582
  const [project, user] = await Promise.all([loadRoutingConfig("project", location), loadRoutingConfig("user", location)]);
145437
145583
  if (opts.signal?.aborted)
145438
145584
  return;
145439
- const connected = connectedPredicateFrom(opts.detected);
145585
+ const connected = connectedPredicateFrom(providers);
145440
145586
  const available = availablePredicateFromProfiles(profiles);
145441
145587
  let fallbackReason;
145442
145588
  const connectedForTurn = (providerId, modelId) => {
145443
145589
  if (modelId !== undefined) {
145444
- const reason = !connected(providerId, modelId) ? "model not connected" : !available(providerId, modelId) ? "model unavailable" : !(opts.executorAllowed?.(providerId, modelId) ?? true) ? "executor policy denied" : undefined;
145590
+ const reason = !connected(providerId, modelId) ? "model not connected" : !externalAllowed(providerId, modelId) ? "external policy denied" : !available(providerId, modelId) ? "model unavailable" : !(opts.executorAllowed?.(providerId, modelId) ?? true) ? "executor policy denied" : opts.providerFactory === undefined && !hasCredential(providerId, env) ? "credentials unavailable" : undefined;
145445
145591
  if (reason !== undefined) {
145446
145592
  fallbackReason ??= `${providerId}/${modelId}: ${reason}`;
145447
145593
  return false;
@@ -145449,14 +145595,14 @@ async function runRoutingClassifierForTurn(line, opts) {
145449
145595
  return true;
145450
145596
  }
145451
145597
  const defaultId = resolveProviderDefaultModelId(providerId);
145452
- const reason = !connected(providerId) ? "provider not connected" : defaultId === undefined ? "no documented provider default" : !connected(providerId, defaultId) ? "default model not connected" : !available(providerId, defaultId) ? "default model unavailable" : !(opts.executorAllowed?.(providerId, defaultId) ?? true) ? "executor policy denied" : opts.providerFactory === undefined && !hasCredential(providerId, env) ? "credentials unavailable" : undefined;
145598
+ const reason = !connected(providerId) ? "provider not connected" : defaultId === undefined ? "no documented provider default" : !connected(providerId, defaultId) ? "default model not connected" : !externalAllowed(providerId, defaultId) ? "external policy denied" : !available(providerId, defaultId) ? "default model unavailable" : !(opts.executorAllowed?.(providerId, defaultId) ?? true) ? "executor policy denied" : opts.providerFactory === undefined && !hasCredential(providerId, env) ? "credentials unavailable" : undefined;
145453
145599
  if (reason !== undefined) {
145454
145600
  fallbackReason ??= `${providerId} (provider default): ${reason}`;
145455
145601
  return false;
145456
145602
  }
145457
145603
  return true;
145458
145604
  };
145459
- const sessionProvider = opts.detected.find((p) => p.name === opts.sessionProvider);
145605
+ const sessionProvider = providers.find((p) => p.name === opts.sessionProvider);
145460
145606
  const models = sessionProvider?.models ?? [opts.sessionModel];
145461
145607
  const derived = deriveDefaultTable(opts.sessionProvider, models, profiles, opts.sessionModel);
145462
145608
  const resolved = resolveCategoryDetailed(category, { project: project.table, user: user.table, derived }, connectedForTurn, available);
@@ -145479,11 +145625,17 @@ async function runRoutingClassifierForTurn(line, opts) {
145479
145625
  const modelId = resolveProviderDefaultModelId(resolved.assignment.providerId);
145480
145626
  return modelId === undefined ? result : { ...result, routed: { providerId: resolved.assignment.providerId, modelId } };
145481
145627
  }
145628
+ function classificationSource(result) {
145629
+ if (!result.classification.result.ok)
145630
+ return "none";
145631
+ const source = result.classification.result.source;
145632
+ return source === "jev" ? "JEV" : source === "main-model" ? "fallback" : source;
145633
+ }
145482
145634
  function renderRoutingFallbackLine(result) {
145483
145635
  if (result === undefined)
145484
145636
  return "[route fallback: no classifier category; using session model]";
145485
145637
  const target = result.baseline === undefined ? "session model" : `${result.baseline.providerId}/${result.baseline.modelId}`;
145486
- return redactSensitiveText(`[route fallback: ${result.category ?? "no classifier category"} -> ${describeAssignment(result.assignment)}${result.fallbackReason !== undefined ? `; ${result.fallbackReason}` : ""}; using ${target}]`);
145638
+ return redactSensitiveText(`[route fallback: ${result.category ?? "no classifier category"} -> ${describeAssignment(result.assignment)}; classifier: ${classificationSource(result)}${result.fallbackReason !== undefined ? `; ${result.fallbackReason}` : ""}; using ${target}]`);
145487
145639
  }
145488
145640
  function pct6(n) {
145489
145641
  return `${Math.round(n * 100)}%`;
@@ -145491,7 +145643,7 @@ function pct6(n) {
145491
145643
  function renderRoutingTagLine(result) {
145492
145644
  if (result.routed === undefined)
145493
145645
  return;
145494
- const source = result.classification.result.ok ? result.classification.result.source : "none";
145646
+ const source = classificationSource(result);
145495
145647
  const confidence = result.classification.result.ok ? ` ${pct6(result.classification.result.confidence)}` : "";
145496
145648
  return `[${result.category} -> ${result.routed.providerId}/${result.routed.modelId}] (${source}${confidence}${result.fallbackReason !== undefined ? `; fallback: ${result.fallbackReason}` : ""})`;
145497
145649
  }
@@ -151547,6 +151699,53 @@ function onKeypress3(r, handler) {
151547
151699
  r._internalKeyInput.onInternal("keypress", handler);
151548
151700
  return () => r._internalKeyInput.offInternal("keypress", handler);
151549
151701
  }
151702
+ function focusedNode(r) {
151703
+ return r.currentFocusedRenderable;
151704
+ }
151705
+ function canTakeKeyboard(node, root) {
151706
+ let current = node;
151707
+ while (current !== null && current !== undefined) {
151708
+ if (current.isDestroyed === true || current.visible === false) {
151709
+ return false;
151710
+ }
151711
+ if (current === root) {
151712
+ return true;
151713
+ }
151714
+ current = current.parent;
151715
+ }
151716
+ return false;
151717
+ }
151718
+ function resolveHandBack(start, root) {
151719
+ const seen = new Set;
151720
+ let candidate = start;
151721
+ while (candidate !== null && !seen.has(candidate)) {
151722
+ if (canTakeKeyboard(candidate, root)) {
151723
+ return candidate;
151724
+ }
151725
+ seen.add(candidate);
151726
+ candidate = handBackFrom.get(candidate) ?? modalFocusFallback(candidate);
151727
+ }
151728
+ return;
151729
+ }
151730
+ function handBackFocus(r, previous, mounted, title) {
151731
+ if (r.isDestroyed === true) {
151732
+ return;
151733
+ }
151734
+ const current = focusedNode(r);
151735
+ if (current !== null && !mounted.some((node) => containsNode(node, current))) {
151736
+ debugEvent("choice.focus-kept", { title, focused: current.id ?? null });
151737
+ return;
151738
+ }
151739
+ const target = resolveHandBack(previous, r.root);
151740
+ if (target === undefined || modalHides(r, target)) {
151741
+ debugEvent("choice.focus-not-restored", { title, previous: previous?.id ?? null, modal: target !== undefined });
151742
+ return;
151743
+ }
151744
+ try {
151745
+ target.focus();
151746
+ debugEvent("choice.focus-restored", { title, focused: target.id ?? null });
151747
+ } catch {}
151748
+ }
151550
151749
  function dockBusy(dock) {
151551
151750
  return (dockDepth.get(dock) ?? 0) > 0;
151552
151751
  }
@@ -151603,6 +151802,7 @@ function presentComposerChoice(otui, r, dock, request) {
151603
151802
  return Promise.resolve({ kind: "id", id: request.cancelId });
151604
151803
  }
151605
151804
  return new Promise((resolve) => {
151805
+ const previousFocus = focusedNode(r);
151606
151806
  const ownEnabled = request.ownAnswer !== undefined;
151607
151807
  const options = request.options.map((o) => ({
151608
151808
  ...o,
@@ -151653,6 +151853,7 @@ function presentComposerChoice(otui, r, dock, request) {
151653
151853
  scrollY: true,
151654
151854
  contentOptions: { flexDirection: "column" }
151655
151855
  });
151856
+ handBackFrom.set(subtitleScroll, previousFocus);
151656
151857
  for (const [i, line] of subtitleLines.entries()) {
151657
151858
  subtitleScroll.content.add(new otui.TextRenderable(r, {
151658
151859
  id: `ch-sub-line-${i}-${Date.now()}`,
@@ -151676,6 +151877,7 @@ function presentComposerChoice(otui, r, dock, request) {
151676
151877
  contentOptions: { flexDirection: "column" }
151677
151878
  });
151678
151879
  dock.add(optionsScroll);
151880
+ handBackFrom.set(optionsScroll, previousFocus);
151679
151881
  const optionsBox = optionsScroll.content;
151680
151882
  const rows = [];
151681
151883
  const paintOptions = () => {
@@ -151736,6 +151938,7 @@ function presentComposerChoice(otui, r, dock, request) {
151736
151938
  content: otui.t`${dimChunk(otui, picked === undefined ? "Your own answer \u2014 Enter sends, Esc goes back to the options" : `Reason for "${picked.label}" \u2014 Enter sends, Esc goes back to the options`)}`
151737
151939
  });
151738
151940
  ownInput = new otui.InputRenderable(r, { id: `ch-own-input-${Date.now()}`, value: "", marginTop: 0 });
151941
+ handBackFrom.set(ownInput, previousFocus);
151739
151942
  dock.add(ownNote);
151740
151943
  dock.add(ownInput);
151741
151944
  paintTitle();
@@ -151792,7 +151995,8 @@ function presentComposerChoice(otui, r, dock, request) {
151792
151995
  id: rowId,
151793
151996
  width: "100%",
151794
151997
  flexDirection: "column",
151795
- onMouseDown: () => {
151998
+ onMouseDown: (event) => {
151999
+ event.preventDefault();
151796
152000
  finish(o.id, "click");
151797
152001
  }
151798
152002
  });
@@ -151850,6 +152054,9 @@ function presentComposerChoice(otui, r, dock, request) {
151850
152054
  }
151851
152055
  } catch {}
151852
152056
  dock.visible = false;
152057
+ queueMicrotask(() => {
152058
+ handBackFocus(r, previousFocus, mounted, request.title);
152059
+ });
151853
152060
  setTimeout(() => {
151854
152061
  for (const node of mounted) {
151855
152062
  try {
@@ -151959,12 +152166,13 @@ function presentComposerChoice(otui, r, dock, request) {
151959
152166
  });
151960
152167
  });
151961
152168
  }
151962
- var OWN_ROW_ID = "\x00own-answer", DEFAULT_ACCEPT_DELAY_MS = 400, MAX_SUBTITLE_CHARS = 8000, MAX_SUBTITLE_ROWS = 10, SUBTITLE_INLINE_MAX_CHARS = 76, OPTION_ROWS = 2, MAX_OPTIONS_ROWS = 16, dockChain, dockDepth;
152169
+ var OWN_ROW_ID = "\x00own-answer", DEFAULT_ACCEPT_DELAY_MS = 400, MAX_SUBTITLE_CHARS = 8000, MAX_SUBTITLE_ROWS = 10, SUBTITLE_INLINE_MAX_CHARS = 76, OPTION_ROWS = 2, MAX_OPTIONS_ROWS = 16, handBackFrom, dockChain, dockDepth;
151963
152170
  var init_composer_choice = __esm(() => {
151964
152171
  init_theme();
151965
152172
  init_theme_text();
151966
152173
  init_modal_host();
151967
152174
  init_debug_log();
152175
+ handBackFrom = new WeakMap;
151968
152176
  dockChain = new WeakMap;
151969
152177
  dockDepth = new WeakMap;
151970
152178
  });
@@ -164541,6 +164749,7 @@ ${formatThemeList(getThemeId())}`);
164541
164749
  jevEnabled: routingEnabled,
164542
164750
  cwd: opts.session?.cwd ?? process.cwd(),
164543
164751
  detected: opts.detected ?? [],
164752
+ providers: loadRoutingProviders,
164544
164753
  sessionProvider: currentSel.provider,
164545
164754
  sessionModel: currentSel.model
164546
164755
  });
@@ -214877,7 +215086,7 @@ function parseCategory(raw) {
214877
215086
  return;
214878
215087
  return raw;
214879
215088
  }
214880
- async function defaultProviders2() {
215089
+ async function defaultProviders() {
214881
215090
  await Promise.resolve().then(() => init_provider_catalog());
214882
215091
  const catalog = await loadOrRefreshProviderCatalog({ fetch, env: envWithSavedApiKeys() });
214883
215092
  return catalogToFlatPickerProviders(catalog);
@@ -214890,7 +215099,7 @@ function defaultSession(userConfigDir) {
214890
215099
  return { providerId: cfg.provider, modelId: cfg.model };
214891
215100
  }
214892
215101
  async function runList5(args, location, deps) {
214893
- const loadProviders = deps.providers ?? defaultProviders2;
215102
+ const loadProviders = deps.providers ?? defaultProviders;
214894
215103
  const loadSession = deps.session ?? (() => defaultSession(location.userConfigDir));
214895
215104
  const [project, user, providers] = await Promise.all([
214896
215105
  loadRoutingConfig("project", location),
package/dist/core.js CHANGED
@@ -1970,7 +1970,8 @@ function isValidIp(value) {
1970
1970
  return false;
1971
1971
  }
1972
1972
  function isWordBoundary(flavour, content, index) {
1973
- return flavour.wordChar.test(wordCharBefore(content, index)) !== flavour.wordChar.test(wordCharAt(content, index));
1973
+ flavour.boundary.lastIndex = index;
1974
+ return flavour.boundary.test(content);
1974
1975
  }
1975
1976
  function wordCharBefore(content, index) {
1976
1977
  if (index <= 0) {
@@ -2710,12 +2711,12 @@ var init_pii = __esm(() => {
2710
2711
  UNICODE_EMAIL = {
2711
2712
  from: /(?:(?<=[\p{L}\p{N}\p{M}_])(?![\p{L}\p{N}\p{M}_])|(?<![\p{L}\p{N}\p{M}_])(?=[\p{L}\p{N}\p{M}_]))[\p{L}\p{N}\p{M}._%+-]+@[\p{L}\p{N}\p{M}.-]+\.[\p{L}\p{M}]{2,}(?![\p{L}\p{N}\p{M}_])/uy,
2712
2713
  localChar: /[\p{L}\p{N}\p{M}._%+-]/u,
2713
- wordChar: /[\p{L}\p{N}\p{M}_]/u
2714
+ boundary: /(?:(?<=[\p{L}\p{N}\p{M}_])(?![\p{L}\p{N}\p{M}_])|(?<![\p{L}\p{N}\p{M}_])(?=[\p{L}\p{N}\p{M}_]))/uy
2714
2715
  };
2715
2716
  ASCII_EMAIL = {
2716
2717
  from: /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b/y,
2717
2718
  localChar: /[A-Za-z0-9._%+-]/,
2718
- wordChar: /\w/
2719
+ boundary: /\b/y
2719
2720
  };
2720
2721
  emailScanner = emailScannerFor(UNICODE_EMAIL);
2721
2722
  asciiEmailScanner = emailScannerFor(ASCII_EMAIL);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mrciphersmith/keryx",
3
- "version": "0.3.78",
3
+ "version": "0.3.79",
4
4
  "description": "Version-controlled project context for AI coding agents: code graph, architecture wiki, project memory, relevant tests, quality signals, and task flows.",
5
5
  "private": false,
6
6
  "publishConfig": {