@mrciphersmith/keryx 0.2.128 → 0.2.130

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 (2) hide show
  1. package/dist/cli.js +213 -75
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -51085,7 +51085,7 @@ import path47 from "path";
51085
51085
  // package.json
51086
51086
  var package_default = {
51087
51087
  name: "@mrciphersmith/keryx",
51088
- version: "0.2.128",
51088
+ version: "0.2.130",
51089
51089
  description: "Version-controlled project context for AI coding agents: code graph, architecture wiki, project memory, relevant tests, quality signals, and task flows.",
51090
51090
  private: false,
51091
51091
  publishConfig: {
@@ -85129,6 +85129,8 @@ function busInboxFullNotice(droppedTotal) {
85129
85129
  return `bus: inbox full \u2014 ${droppedTotal} older message(s) dropped
85130
85130
  `;
85131
85131
  }
85132
+ var BUS_WAKE_CAPPED_NOTICE = `\u25C7 a peer message arrived; automatic wakes are capped, so it will be delivered with your next message.
85133
+ `;
85132
85134
  function createLeaseHoldController(opts) {
85133
85135
  let wasHeld = false;
85134
85136
  return {
@@ -86977,7 +86979,8 @@ class SandboxedWebTransport {
86977
86979
  ...request.body !== undefined ? { body: request.body } : {},
86978
86980
  ...request.credential !== undefined ? { credential: request.credential } : {},
86979
86981
  ...request.signal !== undefined ? { signal: request.signal } : {},
86980
- localOnly: request.capability === "local-search"
86982
+ localOnly: request.capability === "local-search",
86983
+ browserHeaders: request.capability === "public-search"
86981
86984
  });
86982
86985
  if (!raw.ok) {
86983
86986
  return {
@@ -87024,7 +87027,8 @@ class SandboxedWebTransport {
87024
87027
  address: target.value.address,
87025
87028
  method: request.method,
87026
87029
  ...request.body !== undefined ? { body: request.body } : {},
87027
- ...request.credential !== undefined ? { credential: request.credential } : {}
87030
+ ...request.credential !== undefined ? { credential: request.credential } : {},
87031
+ ...request.browserHeaders === true ? { browserHeaders: true } : {}
87028
87032
  }, request.signal);
87029
87033
  if (!worker.ok)
87030
87034
  return worker;
@@ -87062,6 +87066,54 @@ import { existsSync as existsSync29 } from "fs";
87062
87066
  import { homedir as homedir7 } from "os";
87063
87067
  var WORKER_TIMEOUT_MS = 1e4;
87064
87068
  var MAX_WORKER_OUTPUT_BYTES = 192000;
87069
+ var BROWSER_USER_AGENTS = [
87070
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
87071
+ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
87072
+ "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36",
87073
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:133.0) Gecko/20100101 Firefox/133.0",
87074
+ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:133.0) Gecko/20100101 Firefox/133.0",
87075
+ "Mozilla/5.0 (X11; Linux x86_64; rv:132.0) Gecko/20100101 Firefox/132.0",
87076
+ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.1 Safari/605.1.15",
87077
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36 Edg/131.0.0.0"
87078
+ ];
87079
+ var BROWSER_ACCEPT_LANGUAGES = [
87080
+ "en-US,en;q=0.9",
87081
+ "en-US,en;q=0.9,ru;q=0.8",
87082
+ "en-GB,en;q=0.9,en-US;q=0.8",
87083
+ "ru-RU,ru;q=0.9,en-US;q=0.8,en;q=0.7",
87084
+ "en-CA,en;q=0.9,en-US;q=0.8"
87085
+ ];
87086
+ var BROWSER_ACCEPT = "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8";
87087
+ var BROWSER_STATIC_HEADERS = {
87088
+ "accept-encoding": "identity",
87089
+ connection: "keep-alive",
87090
+ "upgrade-insecure-requests": "1",
87091
+ "sec-fetch-dest": "document",
87092
+ "sec-fetch-mode": "navigate",
87093
+ "sec-fetch-site": "none",
87094
+ "sec-fetch-user": "?1",
87095
+ "cache-control": "max-age=0"
87096
+ };
87097
+ var BROWSER_HEADERS_SOURCE = [
87098
+ "(function (random) {",
87099
+ " var userAgents = " + JSON.stringify(BROWSER_USER_AGENTS) + ";",
87100
+ " var acceptLanguages = " + JSON.stringify(BROWSER_ACCEPT_LANGUAGES) + ";",
87101
+ " var staticHeaders = " + JSON.stringify(BROWSER_STATIC_HEADERS) + ";",
87102
+ " var pick = function (list) {",
87103
+ " var value = list[Math.floor(random() * list.length)];",
87104
+ " return value === undefined ? list[0] : value;",
87105
+ " };",
87106
+ " var headers = {",
87107
+ " accept: " + JSON.stringify(BROWSER_ACCEPT) + ",",
87108
+ ' "accept-language": pick(acceptLanguages),',
87109
+ ' "user-agent": pick(userAgents),',
87110
+ " };",
87111
+ " for (var name in staticHeaders) { headers[name] = staticHeaders[name]; }",
87112
+ ' if (random() < 0.5) headers.dnt = "1";',
87113
+ " return headers;",
87114
+ "})"
87115
+ ].join(`
87116
+ `);
87065
87117
  var WORKER_SOURCE = String.raw`
87066
87118
  const { request: httpsRequest } = await import("node:https");
87067
87119
  const { request: httpRequest } = await import("node:http");
@@ -87070,7 +87122,10 @@ const input = JSON.parse(await Bun.stdin.text());
87070
87122
  const fail = () => process.stdout.write(JSON.stringify({ ok: false, reason: "request failed or timed out" }));
87071
87123
  try {
87072
87124
  const timer = setTimeout(() => req.destroy(new Error("timeout")), input.timeoutMs);
87073
- const headers = { accept: "text/html, text/plain, application/json, application/xml, application/xhtml+xml" };
87125
+ const browserHeaders = ${BROWSER_HEADERS_SOURCE};
87126
+ const headers = input.browserHeaders
87127
+ ? browserHeaders(Math.random)
87128
+ : { accept: "text/html, text/plain, application/json, application/xml, application/xhtml+xml" };
87074
87129
  const payload = input.body && typeof input.body === "object" ? { ...input.body } : undefined;
87075
87130
  if (input.credential && input.credential.injection === "header") headers[input.credential.name] = input.credential.value;
87076
87131
  if (input.credential && input.credential.injection === "json-body") {
@@ -87294,7 +87349,7 @@ function webSearchTool(service) {
87294
87349
  const response = await service.search(input.query.trim());
87295
87350
  if (!response.ok) {
87296
87351
  return {
87297
- output: response.reason === "no-active-provider" ? "web_search: no active connected provider. Use /search-provider to configure one, test it, then use /search-connect to select it." : response.reason === "search-failed" ? "web_search: search failed. Retry later; do not switch providers yourself." : "web_search: active provider is unavailable; reconnect it with /search-provider before retrying.",
87352
+ output: response.reason === "no-active-provider" ? "web_search: no active connected provider. Use /search-provider to configure one, test it, then use /search-connect to select it." : response.reason === "search-failed" ? `web_search: search failed${response.detail !== undefined && response.detail.length > 0 ? ` (${response.detail})` : ""}. Wait a few minutes before trying again, and do not switch providers yourself.` : "web_search: active provider is unavailable; reconnect it with /search-provider before retrying.",
87298
87353
  isError: true
87299
87354
  };
87300
87355
  }
@@ -88096,7 +88151,7 @@ function rememberExactShellGrant(command, sessionAllow, options) {
88096
88151
  if (!offerExact) {
88097
88152
  return "";
88098
88153
  }
88099
- const stored = allowShellPattern(exact);
88154
+ const stored = allowShellPattern(exact, options?.dir);
88100
88155
  if (stored.length > 0) {
88101
88156
  sessionAllow.add(stored);
88102
88157
  }
@@ -88250,8 +88305,14 @@ var ANOMALY_MARKERS = [
88250
88305
  "/anomaly.js",
88251
88306
  "Unfortunately, bots use DuckDuckGo too"
88252
88307
  ];
88253
- var MIN_GAP_MS = 500;
88254
- var MAX_GAP_MS = 2000;
88308
+ var MIN_GAP_MS = 4000;
88309
+ var MAX_GAP_MS = 8000;
88310
+ var DUCKDUCKGO_ANOMALY_BACKOFF_MS = [5000, 20000];
88311
+ var timing = {
88312
+ minGapMs: MIN_GAP_MS,
88313
+ maxGapMs: MAX_GAP_MS,
88314
+ backoffMs: DUCKDUCKGO_ANOMALY_BACKOFF_MS
88315
+ };
88255
88316
  var lastSearchAt = 0;
88256
88317
  function duckduckgoLiteUrl(query) {
88257
88318
  return `${DUCKDUCKGO_LITE_ORIGIN}?q=${encodeURIComponent(query)}`;
@@ -88267,7 +88328,8 @@ async function maybeDelayDuckDuckGoSearch(signal) {
88267
88328
  lastSearchAt = now;
88268
88329
  return;
88269
88330
  }
88270
- const gap = MIN_GAP_MS + Math.floor(Math.random() * (MAX_GAP_MS - MIN_GAP_MS + 1));
88331
+ const { minGapMs, maxGapMs } = timing;
88332
+ const gap = minGapMs + Math.floor(Math.random() * Math.max(0, maxGapMs - minGapMs + 1));
88271
88333
  const wait = gap - (now - lastSearchAt);
88272
88334
  if (wait > 0)
88273
88335
  await sleep2(wait, signal);
@@ -88387,6 +88449,30 @@ function sleep2(ms, signal) {
88387
88449
  signal?.addEventListener("abort", onAbort, { once: true });
88388
88450
  });
88389
88451
  }
88452
+ async function waitDuckDuckGoBackoff(attempt, signal) {
88453
+ const wait = timing.backoffMs[attempt];
88454
+ if (wait === undefined)
88455
+ return false;
88456
+ if (wait > 0)
88457
+ await sleep2(wait, signal);
88458
+ return true;
88459
+ }
88460
+
88461
+ // src/harness/search/connection-message.ts
88462
+ function describeConnectionFailure(reason) {
88463
+ switch (reason) {
88464
+ case "missing-credential":
88465
+ return "missing credential";
88466
+ case "rate-limited":
88467
+ return "rate limited \u2014 the provider served its bot page to this machine; retrying will not help";
88468
+ case "incompatible-response":
88469
+ return "the endpoint answered, but not with search results";
88470
+ case "transport-failed":
88471
+ default:
88472
+ return "connection validation failed";
88473
+ }
88474
+ }
88475
+ var RATE_LIMITED_SEARCH_ERROR = "rate limited: DuckDuckGo served its bot-check page instead of results, even after retrying. Do not retry or rephrase; wait a few minutes or fetch a known URL directly";
88390
88476
 
88391
88477
  // src/harness/search/registry.ts
88392
88478
  var SEARXNG_DOCS_URL = "https://docs.searxng.org/admin/installation.html";
@@ -88497,29 +88583,31 @@ function createSearchProviderRegistry(transport, resolveCredential = () => {
88497
88583
  query: DUCKDUCKGO_HEALTHCHECK_QUERY
88498
88584
  });
88499
88585
  if (isDuckDuckGoAnomaly(response.status, response.text))
88500
- return { ok: false, reason: "transport-failed" };
88586
+ return { ok: false, reason: "rate-limited" };
88501
88587
  if (!response.ok || response.status < 200 || response.status >= 300)
88502
88588
  return requestFailure(response);
88503
88589
  const parsed = duckduckgoSearchResponse(DUCKDUCKGO_HEALTHCHECK_QUERY, response.text);
88504
- return parsed.ok ? { ok: true } : { ok: false, reason: "transport-failed" };
88590
+ return parsed.ok ? { ok: true } : { ok: false, reason: "rate-limited" };
88505
88591
  },
88506
88592
  async search(_fields, query, signal) {
88507
- await maybeDelayDuckDuckGoSearch(signal);
88508
- const response = await transport.request({
88509
- providerId: "duckduckgo",
88510
- capability: "public-search",
88511
- url: duckduckgoLiteUrl(query),
88512
- method: "GET",
88513
- query,
88514
- ...signal ? { signal } : {}
88515
- });
88516
- if (isDuckDuckGoAnomaly(response.status, response.text)) {
88517
- throw new Error("DuckDuckGo rate-limited this machine");
88593
+ for (let attempt = 0;; attempt += 1) {
88594
+ await maybeDelayDuckDuckGoSearch(signal);
88595
+ const response = await transport.request({
88596
+ providerId: "duckduckgo",
88597
+ capability: "public-search",
88598
+ url: duckduckgoLiteUrl(query),
88599
+ method: "GET",
88600
+ query,
88601
+ ...signal ? { signal } : {}
88602
+ });
88603
+ const parsed = duckduckgoSearchResponse(query, response.text);
88604
+ if (!isDuckDuckGoAnomaly(response.status, response.text) && parsed.ok) {
88605
+ return parsed.value;
88606
+ }
88607
+ if (!await waitDuckDuckGoBackoff(attempt, signal)) {
88608
+ throw new Error(RATE_LIMITED_SEARCH_ERROR);
88609
+ }
88518
88610
  }
88519
- const parsed = duckduckgoSearchResponse(query, response.text);
88520
- if (!parsed.ok)
88521
- throw new Error("DuckDuckGo rate-limited this machine");
88522
- return parsed.value;
88523
88611
  }
88524
88612
  };
88525
88613
  const searxng = {
@@ -88751,8 +88839,12 @@ class SearchProviderController {
88751
88839
  return { ok: false, reason: "no-active-provider" };
88752
88840
  try {
88753
88841
  return { ok: true, value: await descriptor.search(stored?.fields ?? {}, query, signal) };
88754
- } catch {
88755
- return { ok: false, reason: "search-failed" };
88842
+ } catch (error) {
88843
+ return {
88844
+ ok: false,
88845
+ reason: "search-failed",
88846
+ ...error instanceof Error && error.message.length > 0 ? { detail: error.message } : {}
88847
+ };
88756
88848
  }
88757
88849
  }
88758
88850
  config() {
@@ -94114,6 +94206,15 @@ function mountEmptyTranscriptSplash(otui, r, transcript) {
94114
94206
  transcript.remove(box);
94115
94207
  };
94116
94208
  }
94209
+ function createSplashLifecycle(opts) {
94210
+ let remove = opts.initialHistoryLength === 0 ? opts.mount() : undefined;
94211
+ return {
94212
+ removeIfShown() {
94213
+ remove?.();
94214
+ remove = undefined;
94215
+ }
94216
+ };
94217
+ }
94117
94218
 
94118
94219
  // src/tui/workspace-inspector.ts
94119
94220
  var WORKSPACE_COMMAND = "/workspace";
@@ -101903,6 +102004,44 @@ function parseBusCommand(line) {
101903
102004
  return { kind: "error", reason: `unknown /bus subcommand "${first}"` };
101904
102005
  }
101905
102006
 
102007
+ // src/tui/bus-join.ts
102008
+ function decideJoinAdoption(input) {
102009
+ if (input.disabled)
102010
+ return "off";
102011
+ if (input.destroyed)
102012
+ return "leave";
102013
+ return "adopt";
102014
+ }
102015
+ function buildBusJoinCallbacks(deps) {
102016
+ return {
102017
+ onEvent: (event) => {
102018
+ if (deps.isDestroyed())
102019
+ return;
102020
+ deps.onSystemLine(`${formatBusEventLine(event)}
102021
+ `);
102022
+ deps.pushToInbox({ ...event, body: event.body ?? "" });
102023
+ deps.markDelivered();
102024
+ },
102025
+ onPeers: (peers) => {
102026
+ if (deps.isDestroyed())
102027
+ return;
102028
+ deps.setFleetPeers(peers.map((peer) => ({
102029
+ name: peer.record.name,
102030
+ state: peer.state,
102031
+ status: peer.record.status,
102032
+ activity: peer.record.activity
102033
+ })));
102034
+ deps.paintFleet();
102035
+ deps.onInboxSizeObserved();
102036
+ deps.paintHoldBanner();
102037
+ deps.onLeaseHoldPoll();
102038
+ const delivered = deps.getDelivered();
102039
+ deps.onBusWakePoll(delivered);
102040
+ deps.resetDelivered();
102041
+ }
102042
+ };
102043
+ }
102044
+
101906
102045
  // src/tui/shell-exit.ts
101907
102046
  function leaveBusThenRelease(holder) {
101908
102047
  holder.leaveBus();
@@ -102090,6 +102229,9 @@ var SIDE_WORKER_DENIED_TOOL_NAMES = new Set([
102090
102229
  "shell_job_output",
102091
102230
  "bus_send"
102092
102231
  ]);
102232
+ function isToolAvailableToSideWorker(tool) {
102233
+ return tool.definition.risk === "read" && !SIDE_WORKER_DENIED_TOOL_NAMES.has(tool.definition.name);
102234
+ }
102093
102235
  function parseGitHubRemote(remote) {
102094
102236
  const trimmed = remote.trim();
102095
102237
  if (trimmed.length === 0) {
@@ -103057,7 +103199,7 @@ function runSearchProviderTestStep(otui, target, controller, provider, fields, c
103057
103199
  }
103058
103200
  if (!tested.ok) {
103059
103201
  settled = "failure";
103060
- const reason = tested.reason === "missing-credential" ? "missing credential" : "connection validation failed";
103202
+ const reason = describeConnectionFailure(tested.reason);
103061
103203
  status.content = otui.t`${otui.red("\u2717")} ${otui.bold(`'${provider.id}' test failed: ${reason}`)} ${otui.dim("(Esc to go back and retry)")}`;
103062
103204
  return;
103063
103205
  }
@@ -104521,11 +104663,10 @@ async function launchTuiAgentShell(opts) {
104521
104663
  };
104522
104664
  deps = { ...deps, onContextCompaction };
104523
104665
  liveDeps = deps;
104524
- let removeSplash;
104666
+ let splash = undefined;
104525
104667
  const applyOpened = (opened, previewHistory) => {
104526
104668
  if (opened.history.length > 0) {
104527
- removeSplash?.();
104528
- removeSplash = undefined;
104669
+ splash?.removeIfShown();
104529
104670
  }
104530
104671
  liveSession = opened.handle;
104531
104672
  history = previewHistory === true ? opened.history.slice(-SESSION_PREVIEW_MESSAGE_COUNT) : opened.history;
@@ -104683,17 +104824,37 @@ async function launchTuiAgentShell(opts) {
104683
104824
  return true;
104684
104825
  }
104685
104826
  bindSlateToLiveSession();
104686
- if (history.length === 0) {
104687
- removeSplash = mountEmptyTranscriptSplash(otui, r, transcript);
104688
- }
104827
+ splash = createSplashLifecycle({
104828
+ mount: () => mountEmptyTranscriptSplash(otui, r, transcript),
104829
+ initialHistoryLength: history.length
104830
+ });
104689
104831
  if (viewedReadOnly) {
104690
- removeSplash?.();
104691
- removeSplash = undefined;
104832
+ splash.removeIfShown();
104692
104833
  }
104693
104834
  refreshWorkspaceSidebar();
104694
104835
  refreshReviewSidebar();
104695
104836
  (async () => {
104696
104837
  try {
104838
+ const { onEvent, onPeers } = buildBusJoinCallbacks({
104839
+ isDestroyed: () => destroyed,
104840
+ onSystemLine: (line) => io.onSystem?.(line),
104841
+ pushToInbox: (event) => busInbox.push(event),
104842
+ markDelivered: () => {
104843
+ busPollDeliveredEvent = true;
104844
+ },
104845
+ setFleetPeers: (peers) => {
104846
+ busFleetPeers = peers;
104847
+ },
104848
+ paintFleet,
104849
+ onInboxSizeObserved: () => busDropNotifier.onInboxSizeObserved(busInbox.size),
104850
+ paintHoldBanner,
104851
+ onLeaseHoldPoll: () => leaseHoldController?.onPoll(),
104852
+ onBusWakePoll: (delivered) => busWakeController?.onPoll(delivered),
104853
+ getDelivered: () => busPollDeliveredEvent,
104854
+ resetDelivered: () => {
104855
+ busPollDeliveredEvent = false;
104856
+ }
104857
+ });
104697
104858
  const joined = await joinBus({
104698
104859
  cwd: sessionCwd,
104699
104860
  sessionId: liveSession.summary.id,
@@ -104708,37 +104869,15 @@ async function launchTuiAgentShell(opts) {
104708
104869
  }),
104709
104870
  onError: makeBusErrorReporter((line) => io.onSystem?.(`${line}
104710
104871
  `)),
104711
- onEvent: (event) => {
104712
- if (destroyed)
104713
- return;
104714
- io.onSystem?.(`${formatBusEventLine(event)}
104715
- `);
104716
- busInbox.push({ ...event, body: event.body ?? "" });
104717
- busPollDeliveredEvent = true;
104718
- },
104719
- onPeers: (peers) => {
104720
- if (destroyed)
104721
- return;
104722
- busFleetPeers = peers.map((peer) => ({
104723
- name: peer.record.name,
104724
- state: peer.state,
104725
- status: peer.record.status,
104726
- activity: peer.record.activity
104727
- }));
104728
- paintFleet();
104729
- busDropNotifier.onInboxSizeObserved(busInbox.size);
104730
- paintHoldBanner();
104731
- leaseHoldController?.onPoll();
104732
- busWakeController?.onPoll(busPollDeliveredEvent);
104733
- busPollDeliveredEvent = false;
104734
- }
104872
+ onEvent,
104873
+ onPeers
104735
104874
  });
104736
104875
  if ("disabled" in joined) {
104737
104876
  io.onSystem?.(`bus: off (${joined.disabled})
104738
104877
  `);
104739
104878
  return;
104740
104879
  }
104741
- if (destroyed) {
104880
+ if (decideJoinAdoption({ destroyed, disabled: false }) === "leave") {
104742
104881
  joined.leave();
104743
104882
  return;
104744
104883
  }
@@ -105560,7 +105699,7 @@ Staying in the current session.
105560
105699
  let answer = "";
105561
105700
  try {
105562
105701
  const base = await opts.makeAgentDeps(currentSel, liveSlateSession, undefined);
105563
- const tools = base.tools.filter((t) => t.definition.risk === "read" && !SIDE_WORKER_DENIED_TOOL_NAMES.has(t.definition.name));
105702
+ const tools = base.tools.filter(isToolAvailableToSideWorker);
105564
105703
  const sideDeps = {
105565
105704
  ...base,
105566
105705
  tools,
@@ -105636,8 +105775,7 @@ Staying in the current session.
105636
105775
  suggestionGate.cancel("new turn started");
105637
105776
  if (origin === "operator") {
105638
105777
  consecutiveAutoWakes = 0;
105639
- removeSplash?.();
105640
- removeSplash = undefined;
105778
+ splash?.removeIfShown();
105641
105779
  }
105642
105780
  const displayLine = summarizeSubmittedLine(line);
105643
105781
  if (chrome.isBusy()) {
@@ -105988,7 +106126,7 @@ Staying in the current session.
105988
106126
  searchProviderController.configure(providerId, { ...descriptor.defaults, ...args.fields }, args.credential);
105989
106127
  const tested = await searchProviderController.test(providerId);
105990
106128
  if (!tested.ok) {
105991
- const reason = tested.reason === "missing-credential" ? "missing credential" : "connection validation failed";
106129
+ const reason = describeConnectionFailure(tested.reason);
105992
106130
  io.onSystem?.(`Configured '${providerId}' but it is not connected yet: ${reason}. Run /search-provider ${providerId} key=<value> to re-test.
105993
106131
  `);
105994
106132
  return;
@@ -106491,8 +106629,7 @@ Usage: /reasoning <${REASONING_EFFORT_LEVELS.join("|")}>
106491
106629
  cap: () => resolveMaxAutoWake(),
106492
106630
  runWake: () => runLine("", "bus-message"),
106493
106631
  printCapped: () => {
106494
- io.onSystem?.(`\u25C7 a peer message arrived; automatic wakes are capped, so it will be delivered with your next message.
106495
- `);
106632
+ io.onSystem?.(BUS_WAKE_CAPPED_NOTICE);
106496
106633
  },
106497
106634
  hasBusDeps: () => deps.busInbox !== undefined
106498
106635
  });
@@ -107987,8 +108124,8 @@ async function runAgentRepl(lines, rich, deps, metaprojectPort, sessionOpts, ini
107987
108124
  }
107988
108125
  };
107989
108126
  const searchProviderController = createDefaultSearchProviderController();
107990
- const sessionShellAllow = new Set(loadShellPermissions().allow);
107991
- let fingerprintAtStart = shellPermissionsFingerprint();
108127
+ const sessionShellAllow = new Set(loadShellPermissions(configDir).allow);
108128
+ let fingerprintAtStart = shellPermissionsFingerprint(configDir);
107992
108129
  let permissionMigrationShown = false;
107993
108130
  let permissionTamperShown = false;
107994
108131
  const iterator = lines[Symbol.asyncIterator]();
@@ -108225,10 +108362,11 @@ ${GUTTER}${style.yellow(`Run: ${evaled.command}`)} ${style.dim(prompt)}`);
108225
108362
  const approved = always || /^y(es)?$/i.test(answer);
108226
108363
  if (always && approved) {
108227
108364
  const stored = rememberExactShellGrant(evaled.command, sessionShellAllow, {
108228
- publishLease: evaled.publishLease
108365
+ publishLease: evaled.publishLease,
108366
+ ...configDir !== undefined ? { dir: configDir } : {}
108229
108367
  });
108230
108368
  if (stored.length > 0) {
108231
- fingerprintAtStart = shellPermissionsFingerprint();
108369
+ fingerprintAtStart = shellPermissionsFingerprint(configDir);
108232
108370
  }
108233
108371
  out(stored.length > 0 ? style.green(`approved \xB7 remembered \u201C${stored}\u201D
108234
108372
  `) : style.yellow(`approved once \xB7 pattern cannot be remembered
@@ -108825,7 +108963,7 @@ ${GUTTER}${turnSeparator()}
108825
108963
  searchProviderController.configure(providerId, { ...descriptor.defaults, ...args.fields }, args.credential);
108826
108964
  const tested = await searchProviderController.test(providerId);
108827
108965
  if (!tested.ok) {
108828
- const reason = tested.reason === "missing-credential" ? "missing credential" : "connection validation failed";
108966
+ const reason = describeConnectionFailure(tested.reason);
108829
108967
  agentIo.onSystem?.(`Configured '${providerId}' but it is not connected yet: ${reason}. Run /search-provider ${providerId} key=<value> to re-test.
108830
108968
  `);
108831
108969
  continue;
@@ -111528,8 +111666,8 @@ async function runServe(args) {
111528
111666
  resolve();
111529
111667
  });
111530
111668
  };
111531
- process.once("SIGINT", finish);
111532
- process.once("SIGTERM", finish);
111669
+ process.on("SIGINT", finish);
111670
+ process.on("SIGTERM", finish);
111533
111671
  });
111534
111672
  }
111535
111673
  function overlay(stored, parsed, port, runtimeAck) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mrciphersmith/keryx",
3
- "version": "0.2.128",
3
+ "version": "0.2.130",
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": {