pi-advisor-flow 0.11.2 → 0.12.0

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/index.js CHANGED
@@ -27,7 +27,8 @@ var DEFAULT_JEV_TRANSPORT = "auto";
27
27
  var JEV_TRANSPORTS = [
28
28
  "auto",
29
29
  "typesafe",
30
- "openrouter"
30
+ "openrouter",
31
+ "openai-decisions"
31
32
  ];
32
33
  var ADVISOR_TOOL_POLICIES = [
33
34
  "full",
@@ -4628,7 +4629,9 @@ class AdvisorFollowUpCache {
4628
4629
  var freshJevUsage = () => ({
4629
4630
  cost: 0,
4630
4631
  inputTokens: 0,
4631
- outputTokens: 0
4632
+ knownCostCalls: 0,
4633
+ outputTokens: 0,
4634
+ unavailableCostCalls: 0
4632
4635
  });
4633
4636
  var freshJevLedger = () => ({
4634
4637
  filter: {
@@ -4643,7 +4646,12 @@ var freshJevLedger = () => ({
4643
4646
  usage: freshJevUsage()
4644
4647
  });
4645
4648
  var addJevUsage = (totals, usage) => {
4646
- totals.cost += usage.cost;
4649
+ if (usage.cost === undefined) {
4650
+ totals.unavailableCostCalls += 1;
4651
+ } else {
4652
+ totals.cost += usage.cost;
4653
+ totals.knownCostCalls += 1;
4654
+ }
4647
4655
  totals.inputTokens += usage.inputTokens;
4648
4656
  totals.outputTokens += usage.outputTokens;
4649
4657
  };
@@ -4655,6 +4663,12 @@ var savingsLine = (costs, skipped) => {
4655
4663
  return `Estimated saving from skips: ≤ $${(mean * skipped).toFixed(4)} — upper bound; assumes each skipped consultation would have cost this session's mean allowed-consultation cost ($${mean.toFixed(4)}), which the skipped calls would likely have undercut`;
4656
4664
  };
4657
4665
  var formatJevTokens = (usage) => `↑${formatTokenCount(usage.inputTokens + usage.outputTokens)}`;
4666
+ var jevCostText = (usage) => {
4667
+ if (usage.unavailableCostCalls > 0) {
4668
+ return usage.knownCostCalls > 0 ? `cost unavailable (known-provider estimates $${usage.cost.toFixed(4)}; Decisions billing unconfirmed)` : "cost unavailable (Decisions billing unconfirmed)";
4669
+ }
4670
+ return `$${usage.cost.toFixed(4)}`;
4671
+ };
4658
4672
  var markdownCosts = (invocations) => invocations.filter((item) => item.kind === "markdown" && typeof item.cost === "number").map((item) => item.cost);
4659
4673
  var filterLine = (filter) => {
4660
4674
  const head = `${filter.screened} screened (${filter.allowed} allowed, ${filter.skipped} skipped${filter.repeatSkipped > 0 ? ` [${filter.repeatSkipped} repeat]` : ""})`;
@@ -4665,12 +4679,12 @@ var filterLine = (filter) => {
4665
4679
  if (filter.failures > 0) {
4666
4680
  parts.push(`${filter.failures} failure${filter.failures === 1 ? "" : "s"}`);
4667
4681
  }
4668
- return `Jev filter: ${parts.join(", ")}`;
4682
+ return `Jev/Decisions filter: ${parts.join(", ")}`;
4669
4683
  };
4670
4684
  var gateLine = (gate, invocations) => {
4671
4685
  const consultationCosts = invocations.filter((item) => item.trigger === "turn-gate" && typeof item.cost === "number").map((item) => item.cost);
4672
4686
  const gateSpend = consultationCosts.reduce((sum, cost) => sum + cost, 0);
4673
- return `Turn gate: ${gate.checks} check${gate.checks === 1 ? "" : "s"} (Jev ${formatJevTokens(gate.usage)} · $${gate.usage.cost.toFixed(4)}), ${gate.consultations} consultation${gate.consultations === 1 ? "" : "s"} ($${gateSpend.toFixed(4)})`;
4687
+ return `Turn gate: ${gate.checks} check${gate.checks === 1 ? "" : "s"} (Jev/Decisions ${formatJevTokens(gate.usage)} · ${jevCostText(gate.usage)}), ${gate.consultations} consultation${gate.consultations === 1 ? "" : "s"} ($${gateSpend.toFixed(4)})`;
4674
4688
  };
4675
4689
 
4676
4690
  class AdvisorJevLedgerState {
@@ -4735,8 +4749,9 @@ class AdvisorJevLedgerState {
4735
4749
  } else if (this.#filterActive()) {
4736
4750
  lines.push(filterLine(filter));
4737
4751
  const jevTokens = usage.inputTokens + usage.outputTokens;
4738
- if (jevTokens > 0) {
4739
- lines.push(`Jev cost: ${formatJevTokens(usage)} tokens · $${usage.cost.toFixed(4)} (input only; output free)`);
4752
+ if (jevTokens > 0 || usage.unavailableCostCalls > 0) {
4753
+ const cost = usage.unavailableCostCalls > 0 ? jevCostText(usage) : `$${usage.cost.toFixed(4)} (input only; output free)`;
4754
+ lines.push(`Jev/Decisions cost: ${formatJevTokens(usage)} tokens · ${cost}`);
4740
4755
  }
4741
4756
  if (filter.skipped > 0) {
4742
4757
  lines.push(savingsLine(markdownCosts(invocations), filter.skipped));
@@ -5702,8 +5717,206 @@ class JevFailureError extends Error {
5702
5717
  }
5703
5718
  }
5704
5719
 
5720
+ // src/jev/decisions-client.ts
5721
+ var OPENAI_DECISIONS_ENDPOINT = "https://api.openai.com/v1/decisions";
5722
+ var OPENAI_DECISIONS_MODEL = "gpt-6-luna";
5723
+ var formatEntry = (entry) => {
5724
+ if (isString(entry)) {
5725
+ return entry;
5726
+ }
5727
+ return entry === undefined ? "" : JSON.stringify(entry);
5728
+ };
5729
+ var predicateInstructions = (question) => {
5730
+ const parts = [formatEntry(question.instructions)];
5731
+ if (question.type === "noul") {
5732
+ if (question.criteria?.true !== undefined) {
5733
+ parts.push(`A true answer means: ${formatEntry(question.criteria.true)}`);
5734
+ }
5735
+ if (question.criteria?.false !== undefined) {
5736
+ parts.push(`A false answer means: ${formatEntry(question.criteria.false)}`);
5737
+ }
5738
+ }
5739
+ return parts.filter(Boolean).join(`
5740
+
5741
+ `);
5742
+ };
5743
+ var scoreLevelLabel = (description, index) => {
5744
+ const text = formatEntry(description);
5745
+ const colon = text.indexOf(":");
5746
+ const label = (colon === -1 ? text : text.slice(0, colon)).trim();
5747
+ return label || `Level ${index}`;
5748
+ };
5749
+ var decisionQuestion = (name, question) => {
5750
+ if (question.type === "noul") {
5751
+ return {
5752
+ instructions: predicateInstructions(question),
5753
+ name,
5754
+ type: "predicate"
5755
+ };
5756
+ }
5757
+ if (question.type === "score") {
5758
+ const labels = new Set;
5759
+ const levels = question.criteria.map((description, index) => {
5760
+ const label = scoreLevelLabel(description, index);
5761
+ if (labels.has(label)) {
5762
+ throw new JevFailureError("malformed", `Jev score question ${name} has duplicate level labels.`);
5763
+ }
5764
+ labels.add(label);
5765
+ return { description: formatEntry(description), label };
5766
+ });
5767
+ return {
5768
+ instructions: formatEntry(question.instructions),
5769
+ levels,
5770
+ name,
5771
+ type: "score"
5772
+ };
5773
+ }
5774
+ throw new JevFailureError("malformed", `Jev question ${name} has an unsupported type.`);
5775
+ };
5776
+ var formatDecisionsInput = (state) => {
5777
+ if (isString(state)) {
5778
+ return state;
5779
+ }
5780
+ return JSON.stringify(state, null, 2);
5781
+ };
5782
+ var buildDecisionsRequest = (state, questions) => {
5783
+ if (!isRecordOf(questions) || Object.keys(questions).length === 0) {
5784
+ throw new JevFailureError("malformed", "Jev questions are empty.");
5785
+ }
5786
+ return {
5787
+ input: formatDecisionsInput(state),
5788
+ model: OPENAI_DECISIONS_MODEL,
5789
+ questions: Object.entries(questions).map(([name, question]) => decisionQuestion(name, question))
5790
+ };
5791
+ };
5792
+
5793
+ // src/jev/decisions-response.ts
5794
+ var malformedResponse = (detail) => new JevFailureError("malformed", `OpenAI Decisions response ${detail}.`);
5795
+ var boundedProbability = (value) => isNumber(value) && Number.isFinite(value) && value >= 0 && value <= 1 ? value : undefined;
5796
+ var normalizePredicateAnswer = (answer) => {
5797
+ const probability = boundedProbability(answer.probability);
5798
+ if (probability === undefined) {
5799
+ throw malformedResponse("has an invalid predicate probability");
5800
+ }
5801
+ return { noul: probability, type: "noul" };
5802
+ };
5803
+ var normalizeScoreAnswer = (answer, question) => {
5804
+ if (!Array.isArray(answer.probabilities)) {
5805
+ throw malformedResponse("has no score probability array");
5806
+ }
5807
+ if (answer.probabilities.length !== question.levels.length) {
5808
+ throw malformedResponse("has an incomplete score probability array");
5809
+ }
5810
+ const probabilities = {};
5811
+ const legend = {};
5812
+ const seenLabels = new Set;
5813
+ const seenValues = new Set;
5814
+ let probabilityTotal = 0;
5815
+ for (const entry of answer.probabilities) {
5816
+ if (!isRecord(entry)) {
5817
+ throw malformedResponse("has an invalid score probability entry");
5818
+ }
5819
+ const { label, value } = entry;
5820
+ const probability = boundedProbability(entry.probability);
5821
+ if (!isString(label) || !isNumber(value) || !Number.isSafeInteger(value) || value < 0 || value >= question.levels.length || probability === undefined || seenLabels.has(label) || seenValues.has(value)) {
5822
+ throw malformedResponse("has an invalid or duplicate score level");
5823
+ }
5824
+ const level = question.levels[value];
5825
+ if (label !== level.label) {
5826
+ throw malformedResponse("has an ambiguous score label/value pair");
5827
+ }
5828
+ seenLabels.add(label);
5829
+ seenValues.add(value);
5830
+ probabilities[String(value)] = probability;
5831
+ legend[String(value)] = level.description;
5832
+ probabilityTotal += probability;
5833
+ }
5834
+ if (Math.abs(probabilityTotal - 1) > 0.02 || question.levels.some((level, index) => !seenValues.has(index) || !seenLabels.has(level.label))) {
5835
+ throw malformedResponse("has an incomplete or ambiguous score distribution");
5836
+ }
5837
+ return { legend, probabilities, type: "score" };
5838
+ };
5839
+ var usageTokenCount = (value) => isNumber(value) && Number.isSafeInteger(value) && value >= 0 ? value : undefined;
5840
+ var parseDecisionsApiResponse = async (response) => {
5841
+ let value;
5842
+ try {
5843
+ value = await response.json();
5844
+ } catch {
5845
+ throw malformedResponse("was not valid JSON");
5846
+ }
5847
+ if (!isRecord(value)) {
5848
+ throw malformedResponse("is not an object");
5849
+ }
5850
+ const { answers: rawAnswers, model, usage } = value;
5851
+ if (!Array.isArray(rawAnswers)) {
5852
+ throw malformedResponse("did not include an answers array");
5853
+ }
5854
+ if (!isRecord(usage)) {
5855
+ throw malformedResponse("has malformed usage");
5856
+ }
5857
+ if (model !== undefined && !isString(model)) {
5858
+ throw malformedResponse("has an invalid model name");
5859
+ }
5860
+ const answers = [];
5861
+ for (const answer of rawAnswers) {
5862
+ if (!isRecord(answer)) {
5863
+ throw malformedResponse("contains an invalid answer");
5864
+ }
5865
+ answers.push(answer);
5866
+ }
5867
+ return { answers, model: isString(model) ? model : undefined, usage };
5868
+ };
5869
+ var normalizeAnswers = (responseAnswers, questions) => {
5870
+ const expected = new Map(questions.map((question) => [question.name, question]));
5871
+ if (expected.size !== questions.length || expected.size === 0) {
5872
+ throw malformedResponse("has duplicate or missing requested question names");
5873
+ }
5874
+ const received = new Map;
5875
+ for (const answer of responseAnswers) {
5876
+ const { name } = answer;
5877
+ if (!isString(name) || !name) {
5878
+ throw malformedResponse("contains an unnamed answer");
5879
+ }
5880
+ if (!expected.has(name)) {
5881
+ throw malformedResponse("contains an unexpected answer");
5882
+ }
5883
+ if (received.has(name)) {
5884
+ throw malformedResponse("contains duplicate answer names");
5885
+ }
5886
+ if (answer.type === "refusal") {
5887
+ throw malformedResponse("contains a refusal");
5888
+ }
5889
+ received.set(name, answer);
5890
+ }
5891
+ if (received.size !== expected.size) {
5892
+ throw malformedResponse("is missing one or more named answers");
5893
+ }
5894
+ const answers = {};
5895
+ for (const question of questions) {
5896
+ const answer = received.get(question.name);
5897
+ if (!answer || answer.type !== question.type) {
5898
+ throw malformedResponse("has an answer with the wrong type");
5899
+ }
5900
+ answers[question.name] = question.type === "predicate" ? normalizePredicateAnswer(answer) : normalizeScoreAnswer(answer, question);
5901
+ }
5902
+ return answers;
5903
+ };
5904
+ var normalizeDecisionsResponse = (response, questions) => {
5905
+ const inputTokens = usageTokenCount(response.usage.input_tokens);
5906
+ const outputTokens = usageTokenCount(response.usage.output_tokens);
5907
+ if (inputTokens === undefined || outputTokens === undefined) {
5908
+ throw malformedResponse("has malformed usage");
5909
+ }
5910
+ return {
5911
+ answers: normalizeAnswers(response.answers, questions),
5912
+ model: response.model || OPENAI_DECISIONS_MODEL,
5913
+ usage: { inputTokens, outputTokens }
5914
+ };
5915
+ };
5916
+
5705
5917
  // src/jev/client.ts
5706
5918
  var ENDPOINTS = {
5919
+ "openai-decisions": OPENAI_DECISIONS_ENDPOINT,
5707
5920
  openrouter: "https://openrouter.ai/api/alpha/decisions",
5708
5921
  typesafe: "https://api.typesafe.ai/v1/systemone"
5709
5922
  };
@@ -5730,8 +5943,19 @@ var statusCategory = (status) => {
5730
5943
  }
5731
5944
  return "error";
5732
5945
  };
5733
- var isObjectLike = (value) => typeof value === "object";
5734
5946
  var openRouterModelId = (model) => model.includes("/") ? model : `~typesafe/${model}`;
5947
+ var buildClientRequest = (transport, model, state, questions) => {
5948
+ if (transport === "openai-decisions") {
5949
+ const request = buildDecisionsRequest(state, questions);
5950
+ return {
5951
+ body: JSON.stringify(request),
5952
+ decisionQuestions: request.questions
5953
+ };
5954
+ }
5955
+ return {
5956
+ body: JSON.stringify({ model, questions, state })
5957
+ };
5958
+ };
5735
5959
  var connectionFailure = (error) => {
5736
5960
  const message = redactSecrets(error instanceof Error ? error.message : String(error));
5737
5961
  return {
@@ -5759,6 +5983,7 @@ class JevClient {
5759
5983
  #model;
5760
5984
  #pricePerMtok;
5761
5985
  #timeoutMs;
5986
+ #transport;
5762
5987
  constructor({
5763
5988
  apiKey,
5764
5989
  fetch,
@@ -5770,11 +5995,19 @@ class JevClient {
5770
5995
  this.#apiKey = apiKey;
5771
5996
  this.#endpoint = ENDPOINTS[transport];
5772
5997
  this.#fetch = fetch ?? globalThis.fetch.bind(globalThis);
5773
- this.#model = transport === "openrouter" ? openRouterModelId(model) : model;
5998
+ if (transport === "openai-decisions") {
5999
+ this.#model = OPENAI_DECISIONS_MODEL;
6000
+ } else if (transport === "openrouter") {
6001
+ this.#model = openRouterModelId(model);
6002
+ } else {
6003
+ this.#model = model;
6004
+ }
5774
6005
  this.#pricePerMtok = pricePerMtok;
5775
6006
  this.#timeoutMs = timeoutMs;
6007
+ this.#transport = transport;
5776
6008
  }
5777
6009
  async ask(state, questions, signal) {
6010
+ const { body, decisionQuestions } = buildClientRequest(this.#transport, this.#model, state, questions);
5778
6011
  const deadline = new AbortController;
5779
6012
  const abortFromCaller = () => deadline.abort(signal?.reason);
5780
6013
  signal?.addEventListener("abort", abortFromCaller, { once: true });
@@ -5787,15 +6020,10 @@ class JevClient {
5787
6020
  deadline.abort(new Error("Jev wall-time budget elapsed"));
5788
6021
  }, this.#timeoutMs);
5789
6022
  timer.unref?.();
5790
- const body = JSON.stringify({
5791
- model: this.#model,
5792
- questions,
5793
- state
5794
- });
5795
6023
  try {
5796
6024
  let outcome = {};
5797
6025
  for (let attempt = 1;; attempt += 1) {
5798
- outcome = await this.#attempt(body, deadline.signal);
6026
+ outcome = await this.#attempt(body, deadline.signal, decisionQuestions);
5799
6027
  if (!outcome.retryable || attempt >= MAX_ATTEMPTS || deadline.signal.aborted) {
5800
6028
  break;
5801
6029
  }
@@ -5834,7 +6062,7 @@ class JevClient {
5834
6062
  }
5835
6063
  throw new JevFailureError("error", "Jev call failed.");
5836
6064
  }
5837
- async#attempt(body, signal) {
6065
+ async#attempt(body, signal, decisionQuestions) {
5838
6066
  if (signal.aborted) {
5839
6067
  return { retryable: false };
5840
6068
  }
@@ -5859,7 +6087,7 @@ class JevClient {
5859
6087
  };
5860
6088
  }
5861
6089
  if (response.ok) {
5862
- return this.#parseSuccess(response);
6090
+ return this.#parseSuccess(response, decisionQuestions);
5863
6091
  }
5864
6092
  const failure = await this.#failureFromStatus(response);
5865
6093
  return {
@@ -5880,9 +6108,23 @@ class JevClient {
5880
6108
  return new JevFailureError(statusCategory(response.status), message);
5881
6109
  }
5882
6110
  #transportLabel() {
5883
- return this.#endpoint === ENDPOINTS.openrouter ? "OpenRouter" : "TypeSafe";
6111
+ if (this.#transport === "openrouter") {
6112
+ return "OpenRouter";
6113
+ }
6114
+ return this.#transport === "openai-decisions" ? "OpenAI Decisions" : "TypeSafe";
5884
6115
  }
5885
- async#parseSuccess(response) {
6116
+ async#parseSuccess(response, decisionQuestions) {
6117
+ if (this.#transport === "openai-decisions") {
6118
+ const normalized = normalizeDecisionsResponse(await parseDecisionsApiResponse(response), decisionQuestions ?? []);
6119
+ return {
6120
+ answers: normalized.answers,
6121
+ model: normalized.model,
6122
+ usage: {
6123
+ input_tokens: normalized.usage.inputTokens,
6124
+ output_tokens: normalized.usage.outputTokens
6125
+ }
6126
+ };
6127
+ }
5886
6128
  let parsed;
5887
6129
  try {
5888
6130
  parsed = await response.json();
@@ -5891,7 +6133,7 @@ class JevClient {
5891
6133
  failure: new JevFailureError("malformed", `Jev response was not JSON: ${redactSecrets(error instanceof Error ? error.message : String(error))}`)
5892
6134
  };
5893
6135
  }
5894
- if (!isRecord(parsed) || !parsed.answers || !isObjectLike(parsed.answers)) {
6136
+ if (!isRecord(parsed) || !isRecord(parsed.answers)) {
5895
6137
  return {
5896
6138
  failure: new JevFailureError("malformed", "Jev response did not include an answers object.")
5897
6139
  };
@@ -5914,7 +6156,7 @@ class JevClient {
5914
6156
  answers: outcome.answers ?? {},
5915
6157
  model: outcome.model ?? this.#model,
5916
6158
  usage: {
5917
- cost: inputTokens / 1e6 * price,
6159
+ cost: this.#transport === "openai-decisions" ? undefined : inputTokens / 1e6 * price,
5918
6160
  inputTokens,
5919
6161
  outputTokens
5920
6162
  }
@@ -6096,7 +6338,7 @@ var consumePlaintextKeyWarning = () => {
6096
6338
  return;
6097
6339
  }
6098
6340
  warnedPlaintextKey = true;
6099
- return `Advisor is using a plaintext ${TYPESAFE_KEY_CONFIG_FIELD} from advisor.json; this is not recommended. Open /advisor-settings → Jev consultation filter to migrate it into a secure store, or use the ${TYPESAFE_KEY_ENV_VAR} environment variable.`;
6341
+ return `Advisor is using a plaintext ${TYPESAFE_KEY_CONFIG_FIELD} from advisor.json; this is not recommended. Open /advisor-settings → Jev/Decisions consultation filter to migrate it into a secure store, or use the ${TYPESAFE_KEY_ENV_VAR} environment variable.`;
6100
6342
  };
6101
6343
 
6102
6344
  // src/jev/questions.ts
@@ -6188,33 +6430,195 @@ var buildJevState = (ctx, input = {}) => {
6188
6430
  return Object.fromEntries(fields);
6189
6431
  };
6190
6432
 
6433
+ // src/jev/openai-key-store.ts
6434
+ import {
6435
+ chmodSync as chmodSync2,
6436
+ existsSync as existsSync4,
6437
+ readFileSync as readFileSync4,
6438
+ rmSync as rmSync2,
6439
+ writeFileSync as writeFileSync3
6440
+ } from "node:fs";
6441
+ import { join as join5 } from "node:path";
6442
+ import { getAgentDir as getAgentDir5 } from "@earendil-works/pi-coding-agent";
6443
+ var OPENAI_DECISIONS_KEY_SERVICE = "pi-advisor";
6444
+ var OPENAI_DECISIONS_KEY_NAME = "openai-decisions-api-key";
6445
+ var KEY_FILE_MODE2 = 384;
6446
+ var runtimeSecrets2 = () => {
6447
+ const bun = globalThis;
6448
+ return bun.Bun?.secrets;
6449
+ };
6450
+ var keyFilePath2 = () => join5(getAgentDir5(), "openai_api_key");
6451
+ var normalizeKey2 = (value) => value?.trim() || undefined;
6452
+ var readDefaultFileStore = () => {
6453
+ try {
6454
+ return normalizeKey2(readFileSync4(keyFilePath2(), "utf-8"));
6455
+ } catch {
6456
+ return;
6457
+ }
6458
+ };
6459
+ var writeDefaultFileStore = (key) => {
6460
+ const path = keyFilePath2();
6461
+ writeFileSync3(path, `${key}
6462
+ `, { mode: KEY_FILE_MODE2 });
6463
+ chmodSync2(path, KEY_FILE_MODE2);
6464
+ };
6465
+ var deleteDefaultFileStore = () => rmSync2(keyFilePath2(), { force: true });
6466
+ var messageOf2 = (error) => redactSecrets(error instanceof Error ? error.message : String(error));
6467
+ var resolveOpenAIDecisionsKey = async (deps = {}) => {
6468
+ const secrets = deps.secrets === undefined ? runtimeSecrets2() : deps.secrets;
6469
+ if (secrets) {
6470
+ let secretKey;
6471
+ try {
6472
+ secretKey = normalizeKey2(await secrets.get({
6473
+ name: OPENAI_DECISIONS_KEY_NAME,
6474
+ service: OPENAI_DECISIONS_KEY_SERVICE
6475
+ }));
6476
+ } catch {
6477
+ secretKey = undefined;
6478
+ }
6479
+ if (secretKey) {
6480
+ return { key: secretKey, source: "openai-bun-secrets" };
6481
+ }
6482
+ }
6483
+ const key = normalizeKey2((deps.readFileStore ?? readDefaultFileStore)());
6484
+ return key ? { key, source: "openai-file" } : {};
6485
+ };
6486
+ var writeOpenAIDecisionsKey = async (key, deps = {}) => {
6487
+ const normalized = normalizeKey2(key);
6488
+ if (!normalized) {
6489
+ return { message: "The OpenAI API key is empty.", ok: false };
6490
+ }
6491
+ const secrets = deps.secrets === undefined ? runtimeSecrets2() : deps.secrets;
6492
+ if (secrets) {
6493
+ try {
6494
+ await secrets.set({
6495
+ name: OPENAI_DECISIONS_KEY_NAME,
6496
+ service: OPENAI_DECISIONS_KEY_SERVICE,
6497
+ value: normalized
6498
+ });
6499
+ return { message: "OpenAI API key stored in Bun.secrets.", ok: true };
6500
+ } catch (error) {
6501
+ return {
6502
+ message: `Storing the OpenAI API key in Bun.secrets failed: ${messageOf2(error)}. Configure OPENAI_API_KEY in Pi or retry secure storage.`,
6503
+ ok: false
6504
+ };
6505
+ }
6506
+ }
6507
+ try {
6508
+ (deps.writeFileStore ?? writeDefaultFileStore)(normalized);
6509
+ return {
6510
+ message: "OpenAI API key stored in the Pi agent directory (mode 0600).",
6511
+ ok: true
6512
+ };
6513
+ } catch (error) {
6514
+ return {
6515
+ message: `Storing the OpenAI API key failed: ${messageOf2(error)}. Configure OPENAI_API_KEY in Pi or retry secure storage.`,
6516
+ ok: false
6517
+ };
6518
+ }
6519
+ };
6520
+ var clearOpenAIDecisionsKey = async (deps = {}) => {
6521
+ let firstError;
6522
+ const secrets = deps.secrets === undefined ? runtimeSecrets2() : deps.secrets;
6523
+ if (secrets) {
6524
+ try {
6525
+ await secrets.delete({
6526
+ name: OPENAI_DECISIONS_KEY_NAME,
6527
+ service: OPENAI_DECISIONS_KEY_SERVICE
6528
+ });
6529
+ } catch (error) {
6530
+ firstError = messageOf2(error);
6531
+ }
6532
+ }
6533
+ try {
6534
+ if (deps.deleteFileStore) {
6535
+ deps.deleteFileStore();
6536
+ } else if (existsSync4(keyFilePath2())) {
6537
+ deleteDefaultFileStore();
6538
+ }
6539
+ } catch (error) {
6540
+ firstError ??= messageOf2(error);
6541
+ }
6542
+ if (firstError) {
6543
+ return {
6544
+ message: `Clearing the OpenAI API key failed: ${firstError}.`,
6545
+ ok: false
6546
+ };
6547
+ }
6548
+ return { message: "Stored OpenAI API key cleared.", ok: true };
6549
+ };
6550
+
6191
6551
  // src/jev/transport.ts
6192
6552
  var OPENROUTER_PROVIDER = "openrouter";
6553
+ var OPENAI_PROVIDER = "openai";
6554
+ var OPENAI_API_KEY_SOURCES = new Map([
6555
+ ["OPENAI_API_KEY", "openai-env"],
6556
+ ["stored credential", "openai-provider-credential"]
6557
+ ]);
6558
+ var openAiPlatformCredentials = async (ctx, deps) => {
6559
+ let result;
6560
+ try {
6561
+ result = deps.getProviderAuth ? await deps.getProviderAuth(OPENAI_PROVIDER) : await ctx?.modelRegistry?.getProviderAuth(OPENAI_PROVIDER);
6562
+ } catch {
6563
+ result = undefined;
6564
+ }
6565
+ const source = result?.source;
6566
+ const credentialSource = source ? OPENAI_API_KEY_SOURCES.get(source) : undefined;
6567
+ const apiKey = result?.auth.apiKey?.trim();
6568
+ if (credentialSource && apiKey) {
6569
+ return {
6570
+ apiKey,
6571
+ source: credentialSource,
6572
+ transport: "openai-decisions"
6573
+ };
6574
+ }
6575
+ try {
6576
+ const stored = await (deps.resolveOpenAIKey ?? resolveOpenAIDecisionsKey)();
6577
+ const storedKey = stored.key?.trim();
6578
+ return storedKey && stored.source ? {
6579
+ apiKey: storedKey,
6580
+ source: stored.source,
6581
+ transport: "openai-decisions"
6582
+ } : undefined;
6583
+ } catch {
6584
+ return;
6585
+ }
6586
+ };
6193
6587
  var openRouterKey = async (ctx, deps) => {
6194
6588
  const key = deps.getProviderKey ? await deps.getProviderKey(OPENROUTER_PROVIDER) : await ctx?.modelRegistry?.getApiKeyForProvider(OPENROUTER_PROVIDER);
6195
6589
  return key?.trim() || undefined;
6196
6590
  };
6591
+ var typeSafeCredentials = async (deps) => {
6592
+ const resolution = await (deps.resolveTypesafe ?? resolveTypeSafeKey)();
6593
+ if (!resolution.key) {
6594
+ return;
6595
+ }
6596
+ const credentials = {
6597
+ apiKey: resolution.key,
6598
+ transport: "typesafe"
6599
+ };
6600
+ if (resolution.source) {
6601
+ credentials.source = resolution.source;
6602
+ }
6603
+ return credentials;
6604
+ };
6605
+ var resolveJevTransportFor = async (transport, ctx, deps = {}) => {
6606
+ if (transport === "typesafe") {
6607
+ return typeSafeCredentials(deps);
6608
+ }
6609
+ if (transport === "openrouter") {
6610
+ const key = await openRouterKey(ctx, deps);
6611
+ return key ? { apiKey: key, transport } : undefined;
6612
+ }
6613
+ return openAiPlatformCredentials(ctx, deps);
6614
+ };
6197
6615
  var resolveJevTransport = async (ctx, deps = {}) => {
6198
6616
  const preference = advisorJevTransportRef;
6199
- if (preference !== "openrouter") {
6200
- const resolveTypesafe = deps.resolveTypesafe ?? resolveTypeSafeKey;
6201
- const resolution = await resolveTypesafe();
6202
- if (resolution.key) {
6203
- const credentials = {
6204
- apiKey: resolution.key,
6205
- transport: "typesafe"
6206
- };
6207
- if (resolution.source) {
6208
- credentials.source = resolution.source;
6209
- }
6210
- return credentials;
6211
- }
6617
+ if (preference !== "auto") {
6618
+ return resolveJevTransportFor(preference, ctx, deps);
6212
6619
  }
6213
- if (preference === "typesafe") {
6214
- return;
6215
- }
6216
- const openrouter = await openRouterKey(ctx, deps);
6217
- return openrouter ? { apiKey: openrouter, transport: "openrouter" } : undefined;
6620
+ const typesafe = await typeSafeCredentials(deps);
6621
+ return typesafe ?? resolveJevTransportFor("openrouter", ctx, deps);
6218
6622
  };
6219
6623
 
6220
6624
  // src/tools/outage-notifier.ts
@@ -6244,7 +6648,7 @@ var SCREENED_SKIP_TEXT = "Advisor consultation skipped (screened out): the stake
6244
6648
  var repeatSkipText = (advice) => `Advisor consultation skipped (already answered): this question was answered earlier in this session; the earlier advice is reattached below. Consult again only if the situation has materially changed.
6245
6649
 
6246
6650
  ${advice}`;
6247
- var outageNotifier = createOutageNotifier((category, message) => `Advisor Jev filter failed (${category}); allowing consultations. ${message}`);
6651
+ var outageNotifier = createOutageNotifier((category, message) => `Advisor Jev/Decisions filter failed (${category}); allowing consultations. ${message}`);
6248
6652
  var notifyOutageOnce = outageNotifier.notify;
6249
6653
  var resetJevOutageNotification = outageNotifier.reset;
6250
6654
  var allow = () => ({ decision: "allow" });
@@ -6266,7 +6670,7 @@ var screenWithJev = async (ctx, session, options, deps, normalizedQuestion) => {
6266
6670
  const credentials = await (deps.resolveTransport ?? resolveJevTransport)(ctx);
6267
6671
  if (!credentials) {
6268
6672
  session.recordJevFilterFailure();
6269
- notifyOutageOnce(ctx, "missing-key", "No Jev credentials resolved (no TypeSafe key and no OpenRouter login).");
6673
+ notifyOutageOnce(ctx, "missing-key", "No credentials resolved for the selected Jev/Decisions provider.");
6270
6674
  return allow();
6271
6675
  }
6272
6676
  if (credentials.source === "advisor-json") {
@@ -6604,9 +7008,9 @@ import {
6604
7008
  unlink,
6605
7009
  writeFile
6606
7010
  } from "node:fs/promises";
6607
- import { join as join5 } from "node:path";
7011
+ import { join as join6 } from "node:path";
6608
7012
  import { setTimeout as sleep } from "node:timers/promises";
6609
- import { getAgentDir as getAgentDir5 } from "@earendil-works/pi-coding-agent";
7013
+ import { getAgentDir as getAgentDir6 } from "@earendil-works/pi-coding-agent";
6610
7014
  var ADOPTIONS = [
6611
7015
  "followed",
6612
7016
  "not-followed",
@@ -6626,12 +7030,12 @@ var OUTCOME_TRIGGERS = [
6626
7030
  ];
6627
7031
  var MAX_LOG_BYTES = 1024 * 1024;
6628
7032
  var OUTCOME_LOG_MAX_BYTES = MAX_LOG_BYTES;
6629
- var statePath = () => join5(getAgentDir5(), "advisor-outcomes-salt");
6630
- var outcomeLogPath = () => join5(getAgentDir5(), "advisor-outcomes.jsonl");
7033
+ var statePath = () => join6(getAgentDir6(), "advisor-outcomes-salt");
7034
+ var outcomeLogPath = () => join6(getAgentDir6(), "advisor-outcomes.jsonl");
6631
7035
  var isErrnoException = (error) => error instanceof Error && ("code" in error);
6632
7036
  var salt = async () => {
6633
7037
  const path = statePath();
6634
- await mkdir(getAgentDir5(), { mode: 448, recursive: true });
7038
+ await mkdir(getAgentDir6(), { mode: 448, recursive: true });
6635
7039
  for (let attempt = 0;attempt < 20; attempt += 1) {
6636
7040
  try {
6637
7041
  const existing = await readFile(path);
@@ -6708,7 +7112,7 @@ var withOutcomeLock = async (run) => {
6708
7112
  var adviceDigest = (advice, key) => createHmac("sha256", key).update(advice).digest("hex").slice(0, 16);
6709
7113
  var appendOutcome = async (record) => {
6710
7114
  const path = outcomeLogPath();
6711
- await mkdir(getAgentDir5(), { mode: 448, recursive: true });
7115
+ await mkdir(getAgentDir6(), { mode: 448, recursive: true });
6712
7116
  return withOutcomeLock(async () => {
6713
7117
  const next = {
6714
7118
  adoption: record.adoption,
@@ -7096,7 +7500,10 @@ var rainbowGradient = (text, startedAt) => {
7096
7500
  import { getKeybindings } from "@earendil-works/pi-tui";
7097
7501
 
7098
7502
  // src/ui/jev-setup-submenu.ts
7099
- import { Key as Key2, matchesKey as matchesKey2, truncateToWidth as truncateToWidth5 } from "@earendil-works/pi-tui";
7503
+ import { Key as Key2, matchesKey as matchesKey2 } from "@earendil-works/pi-tui";
7504
+
7505
+ // src/ui/jev-setup-submenu-view.ts
7506
+ import { truncateToWidth as truncateToWidth4 } from "@earendil-works/pi-tui";
7100
7507
 
7101
7508
  // node_modules/@typesafe-ai/sdk/dist/index.mjs
7102
7509
  var range2 = (from, to) => Array.from({ length: to - from }, (_, i) => from + i);
@@ -7141,8 +7548,157 @@ var describeRuntime = () => {
7141
7548
  };
7142
7549
  var RUNTIME = describeRuntime();
7143
7550
 
7551
+ // src/ui/jev-setup-support.ts
7552
+ var fireAndForget = async (action, onError) => {
7553
+ try {
7554
+ await action;
7555
+ } catch (error) {
7556
+ const message = error instanceof Error ? error.message : String(error);
7557
+ onError(redactSecrets(message));
7558
+ }
7559
+ };
7560
+ var providerName = (transport) => {
7561
+ if (transport === "typesafe") {
7562
+ return "TypeSafe Jev";
7563
+ }
7564
+ if (transport === "openrouter") {
7565
+ return "OpenRouter (Pi login)";
7566
+ }
7567
+ return `OpenAI Decisions (${OPENAI_DECISIONS_MODEL})`;
7568
+ };
7569
+ var transportLabel = (credentials) => {
7570
+ if (credentials.transport === "openrouter") {
7571
+ return "OpenRouter (reusing Pi login)";
7572
+ }
7573
+ if (credentials.transport === "openai-decisions") {
7574
+ let source = "verified API key";
7575
+ switch (credentials.source) {
7576
+ case "openai-bun-secrets": {
7577
+ source = "Bun.secrets";
7578
+ break;
7579
+ }
7580
+ case "openai-env": {
7581
+ source = "OPENAI_API_KEY";
7582
+ break;
7583
+ }
7584
+ case "openai-file": {
7585
+ source = "stored file, mode 0600";
7586
+ break;
7587
+ }
7588
+ case "openai-provider-credential": {
7589
+ source = "Pi stored API key";
7590
+ break;
7591
+ }
7592
+ default: {
7593
+ break;
7594
+ }
7595
+ }
7596
+ return `OpenAI Decisions (${OPENAI_DECISIONS_MODEL}; key: ${source})`;
7597
+ }
7598
+ switch (credentials.source) {
7599
+ case "advisor-json": {
7600
+ return "TypeSafe (key: advisor.json — plaintext, not recommended)";
7601
+ }
7602
+ case "bun-secrets": {
7603
+ return "TypeSafe (key: Bun.secrets)";
7604
+ }
7605
+ case "file": {
7606
+ return "TypeSafe (key: stored file, mode 0600)";
7607
+ }
7608
+ default: {
7609
+ return "TypeSafe (key: TYPESAFE_API_KEY)";
7610
+ }
7611
+ }
7612
+ };
7613
+ var canClearCredential = (credentials) => credentials.source === "bun-secrets" || credentials.source === "file" || credentials.source === "openai-bun-secrets" || credentials.source === "openai-file";
7614
+ var canReplaceWithEnteredKey = (credentials) => credentials.transport === "typesafe" || credentials.transport === "openai-decisions" && (credentials.source === "openai-bun-secrets" || credentials.source === "openai-file");
7615
+ var defaultVerify = async (credentials) => {
7616
+ const client = jevClientFromCredentials(credentials);
7617
+ try {
7618
+ await client.ask({ purpose: "pi-advisor setup verification" }, { verified: noul("Answer yes.") });
7619
+ return { ok: true };
7620
+ } catch (error) {
7621
+ return {
7622
+ message: error instanceof Error ? error.message : String(error),
7623
+ ok: false
7624
+ };
7625
+ }
7626
+ };
7627
+ var defaultSetupDeps = {
7628
+ clearStoredKey: (transport) => transport === "openai-decisions" ? clearOpenAIDecisionsKey() : clearKeyTypeSafeKey(),
7629
+ removePlaintextKey: removeTypeSafeKeyFromAdvisorJson,
7630
+ resolveTransport: (transport) => transport ? resolveJevTransportFor(transport) : resolveJevTransport(),
7631
+ verify: defaultVerify,
7632
+ writeKey: (key, transport) => transport === "openai-decisions" ? writeOpenAIDecisionsKey(key) : writeKeyTypeSafeKey(key)
7633
+ };
7634
+
7635
+ // src/ui/jev-setup-submenu-view.ts
7636
+ var setupActions = (state) => {
7637
+ const actions = ["typesafe", "openrouter", "openai-decisions"];
7638
+ if (state.canEnterKey) {
7639
+ actions.push("enter-key");
7640
+ }
7641
+ if (state.filterEnabled) {
7642
+ actions.push("disable");
7643
+ }
7644
+ if (state.credentials && canClearCredential(state.credentials)) {
7645
+ actions.push("clear-stored-key");
7646
+ }
7647
+ actions.push("done");
7648
+ return actions;
7649
+ };
7650
+ var setupLabels = (state) => setupActions(state).map((action) => {
7651
+ if (action === "clear-stored-key") {
7652
+ return state.filterEnabled ? "Disable and clear stored key" : "Clear stored key";
7653
+ }
7654
+ if (action === "disable") {
7655
+ return "Disable";
7656
+ }
7657
+ if (action === "done") {
7658
+ return "Done";
7659
+ }
7660
+ if (action === "enter-key") {
7661
+ return state.selectedTransport === "openai-decisions" ? "Enter an OpenAI Platform API key" : "Enter a TypeSafe API key";
7662
+ }
7663
+ return providerName(action);
7664
+ });
7665
+ var renderJevSetup = (options, state, maskedInput, width) => {
7666
+ const { theme } = options;
7667
+ const lines = [
7668
+ theme.fg("accent", theme.bold(" Jev/Decisions consultation filter")),
7669
+ "",
7670
+ ` Filter: ${state.filterEnabled ? "On" : "Off"}`
7671
+ ];
7672
+ if (state.credentials) {
7673
+ lines.push(` Credential: ${transportLabel(state.credentials)}`);
7674
+ } else if (state.selectedTransport) {
7675
+ lines.push(` Selected provider: ${providerName(state.selectedTransport)}`);
7676
+ } else {
7677
+ lines.push(` Saved transport: ${options.currentTransport}`);
7678
+ }
7679
+ if (state.notice) {
7680
+ lines.push("", theme.fg("warning", ` ${state.notice}`));
7681
+ }
7682
+ lines.push("");
7683
+ if (state.mode === "resolving") {
7684
+ lines.push(" Checking the selected provider credentials…");
7685
+ } else if (state.mode === "verifying") {
7686
+ lines.push(" Verifying with a live Jev call…");
7687
+ } else if (state.mode === "clearing") {
7688
+ lines.push(" Clearing the selected provider's stored key…");
7689
+ } else if (state.mode === "key-entry") {
7690
+ lines.push(` ${providerName(state.selectedTransport ?? "typesafe")}`, state.selectedTransport === "openai-decisions" ? " No OpenAI Platform API key found. ChatGPT subscription OAuth is not accepted." : " No TypeSafe API key found.", ` ${maskedInput.render(Math.max(10, width - 4))[0] ?? ""}`, theme.fg("dim", " Enter: verify and securely store · Esc: return"));
7691
+ } else {
7692
+ for (const [index, label] of setupLabels(state).entries()) {
7693
+ const prefix = index === state.selectedIndex ? "→ " : " ";
7694
+ lines.push(`${prefix}${label}`);
7695
+ }
7696
+ }
7697
+ return lines.map((line) => truncateToWidth4(line, width));
7698
+ };
7699
+
7144
7700
  // src/ui/masked-input.ts
7145
- import { Input as Input2, truncateToWidth as truncateToWidth4 } from "@earendil-works/pi-tui";
7701
+ import { Input as Input2, truncateToWidth as truncateToWidth5 } from "@earendil-works/pi-tui";
7146
7702
 
7147
7703
  class MaskedInput {
7148
7704
  input;
@@ -7179,80 +7735,30 @@ class MaskedInput {
7179
7735
  const { cursor } = this.input;
7180
7736
  const masked = `${"•".repeat(Math.min(cursor, value.length))}█${"•".repeat(Math.max(0, value.length - cursor))}`;
7181
7737
  const placeholder = value.length === 0 && this.options.placeholder ? this.options.placeholder : masked;
7182
- return [truncateToWidth4(placeholder, Math.max(1, width))];
7738
+ return [truncateToWidth5(placeholder, Math.max(1, width))];
7183
7739
  }
7184
7740
  }
7185
7741
 
7186
7742
  // src/ui/jev-setup-submenu.ts
7187
- var ACTION_LABELS = {
7188
- disable: "Disable",
7189
- "disable-clear": "Disable and clear stored key",
7190
- done: "Done",
7191
- "enter-key": "Enter a TypeSafe API key",
7192
- "verify-again": "Verify again",
7193
- "verify-enable": "Verify and enable"
7194
- };
7195
- var fireAndForget = async (action) => {
7196
- try {
7197
- await action;
7198
- } catch {}
7199
- };
7200
- var transportLabel = (credentials) => {
7201
- if (credentials.transport === "openrouter") {
7202
- return "OpenRouter (reusing pi login)";
7203
- }
7204
- switch (credentials.source) {
7205
- case "advisor-json": {
7206
- return "TypeSafe (key: advisor.json — plaintext, not recommended)";
7207
- }
7208
- case "bun-secrets": {
7209
- return "TypeSafe (key: Bun.secrets)";
7210
- }
7211
- case "file": {
7212
- return "TypeSafe (key: stored file, mode 0600)";
7213
- }
7214
- default: {
7215
- return "TypeSafe (key: TYPESAFE_API_KEY)";
7216
- }
7217
- }
7218
- };
7219
- var defaultVerify = async (credentials) => {
7220
- const client = jevClientFromCredentials(credentials);
7221
- try {
7222
- await client.ask({ purpose: "pi-advisor setup verification" }, { verified: noul("Answer yes.") });
7223
- return { ok: true };
7224
- } catch (error) {
7225
- return {
7226
- message: error instanceof Error ? error.message : String(error),
7227
- ok: false
7228
- };
7229
- }
7230
- };
7231
-
7232
7743
  class JevSetupSubmenu {
7233
7744
  options;
7234
7745
  deps;
7235
7746
  maskedInput;
7236
7747
  credentials;
7748
+ selectedTransport;
7237
7749
  mode = "menu";
7238
7750
  notice;
7239
7751
  selectedIndex = 0;
7752
+ filterEnabled;
7753
+ canEnterKey = false;
7754
+ pendingSelectionRefresh = false;
7240
7755
  _focused = true;
7241
7756
  constructor(options, deps = {}) {
7242
7757
  this.options = options;
7243
- this.deps = {
7244
- clearStoredKey: deps.clearStoredKey ?? clearKeyTypeSafeKey,
7245
- removePlaintextKey: deps.removePlaintextKey ?? removeTypeSafeKeyFromAdvisorJson,
7246
- resolveTransport: deps.resolveTransport ?? (() => resolveJevTransport()),
7247
- verify: deps.verify ?? defaultVerify,
7248
- writeKey: deps.writeKey ?? writeKeyTypeSafeKey
7249
- };
7250
- this.maskedInput = new MaskedInput({
7251
- onEscape: () => this.options.done(),
7252
- onSubmit: (value) => this.submitEnteredKey(value),
7253
- placeholder: "Paste a TypeSafe API key"
7254
- });
7255
- fireAndForget(this.refresh());
7758
+ this.filterEnabled = options.currentValue === "On";
7759
+ this.deps = { ...defaultSetupDeps, ...options.setupDeps, ...deps };
7760
+ this.maskedInput = this.createMaskedInput("Paste a TypeSafe API key");
7761
+ fireAndForget(this.refresh(), this.reportBackgroundError);
7256
7762
  }
7257
7763
  get focused() {
7258
7764
  return this._focused;
@@ -7273,7 +7779,7 @@ class JevSetupSubmenu {
7273
7779
  if (this.mode !== "menu") {
7274
7780
  return;
7275
7781
  }
7276
- const actions = this.actions();
7782
+ const actions = setupActions(this.viewState());
7277
7783
  if (matchesKey2(keyData, Key2.down) || keyData === "\x1B[B" || keyData === "\x1BOB") {
7278
7784
  this.selectedIndex = (this.selectedIndex + 1) % actions.length;
7279
7785
  } else if (matchesKey2(keyData, Key2.up) || keyData === "\x1B[A" || keyData === "\x1BOA") {
@@ -7286,157 +7792,228 @@ class JevSetupSubmenu {
7286
7792
  this.options.tui.requestRender();
7287
7793
  }
7288
7794
  render(width) {
7289
- const { theme } = this.options;
7290
- const enabled = this.options.currentValue === "On";
7291
- const lines = [
7292
- theme.fg("accent", theme.bold(" Jev consultation filter")),
7293
- ""
7294
- ];
7295
- if (this.credentials) {
7296
- lines.push(` Transport: ${transportLabel(this.credentials)}`, ` Filter: ${enabled ? "On" : "Off"}`);
7297
- } else if (this.mode === "verifying") {
7298
- lines.push(" Checking available Jev credentials…");
7299
- } else {
7300
- lines.push(" No Jev credentials found. Enter a TypeSafe API key below, or add", " an OpenRouter login in pi; it is reused automatically.");
7301
- }
7302
- if (this.notice) {
7303
- lines.push("", theme.fg("warning", ` ${this.notice}`));
7304
- }
7305
- lines.push("");
7306
- if (this.mode === "verifying") {
7307
- lines.push(" Verifying with a live Jev call…");
7308
- } else if (this.mode === "key-entry") {
7309
- lines.push(` ${this.maskedInput.render(Math.max(10, width - 4))[0] ?? ""}`, theme.fg("dim", " Enter: verify · Esc: cancel"));
7310
- } else {
7311
- for (const [index, label] of this.labels().entries()) {
7312
- const prefix = index === this.selectedIndex ? "→ " : " ";
7313
- lines.push(`${prefix}${label}`);
7314
- }
7315
- }
7316
- return lines.map((line) => truncateToWidth5(line, width));
7795
+ return renderJevSetup(this.options, this.viewState(), this.maskedInput, width);
7317
7796
  }
7318
- actions() {
7319
- if (!this.credentials) {
7320
- return this.options.currentValue === "On" ? ["enter-key", "disable", "done"] : ["enter-key", "done"];
7321
- }
7322
- const enabled = this.options.currentValue === "On";
7323
- const actions = [enabled ? "verify-again" : "verify-enable"];
7324
- actions.push("disable");
7325
- if (this.credentials.source === "bun-secrets" || this.credentials.source === "file") {
7326
- actions.push("disable-clear");
7327
- }
7328
- actions.push("done");
7329
- return actions;
7797
+ viewState() {
7798
+ return {
7799
+ canEnterKey: this.canEnterKey,
7800
+ credentials: this.credentials,
7801
+ filterEnabled: this.filterEnabled,
7802
+ mode: this.mode,
7803
+ notice: this.notice,
7804
+ selectedIndex: this.selectedIndex,
7805
+ selectedTransport: this.selectedTransport
7806
+ };
7330
7807
  }
7331
- labels() {
7332
- return this.actions().map((action) => ACTION_LABELS[action]);
7808
+ createMaskedInput(placeholder) {
7809
+ return new MaskedInput({
7810
+ onEscape: () => this.cancelKeyEntry(),
7811
+ onSubmit: (value) => this.submitEnteredKey(value),
7812
+ placeholder
7813
+ });
7333
7814
  }
7334
7815
  async refresh() {
7335
- const wasVerifying = this.mode === "verifying";
7336
- this.mode = "verifying";
7337
- if (!wasVerifying) {
7816
+ this.mode = "resolving";
7817
+ this.options.tui.requestRender();
7818
+ try {
7819
+ const credentials = await this.deps.resolveTransport(this.options.currentTransport === "auto" ? undefined : this.options.currentTransport);
7820
+ this.credentials = credentials;
7821
+ this.selectedTransport = credentials?.transport;
7822
+ this.mode = "menu";
7823
+ this.selectedIndex = 0;
7824
+ if (credentials?.source === "advisor-json") {
7825
+ this.notice = consumePlaintextKeyWarning();
7826
+ }
7338
7827
  this.options.tui.requestRender();
7828
+ } catch (error) {
7829
+ this.reportBackgroundError(error instanceof Error ? error.message : String(error));
7339
7830
  }
7340
- this.credentials = await this.deps.resolveTransport?.();
7341
- this.mode = "menu";
7342
- this.selectedIndex = 0;
7343
- if (this.credentials?.source === "advisor-json") {
7344
- this.notice ??= consumePlaintextKeyWarning();
7345
- }
7346
- this.options.tui.requestRender();
7347
7831
  }
7348
7832
  activate(action) {
7349
7833
  if (action === "done") {
7350
- this.options.done();
7351
- return;
7352
- }
7353
- if (action === "enter-key") {
7354
- this.mode = "key-entry";
7834
+ this.closeSetup();
7355
7835
  return;
7356
7836
  }
7357
7837
  if (action === "disable") {
7358
7838
  this.notice = undefined;
7359
- this.options.done("Off");
7839
+ if (this.commitSelection({
7840
+ enabled: false,
7841
+ transport: this.options.currentTransport
7842
+ }, "Off")) {
7843
+ this.filterEnabled = false;
7844
+ }
7360
7845
  return;
7361
7846
  }
7362
- if (action === "disable-clear") {
7363
- fireAndForget(this.disableAndClear());
7847
+ if (action === "clear-stored-key") {
7848
+ fireAndForget(this.disableAndClear(), this.reportBackgroundError);
7364
7849
  return;
7365
7850
  }
7366
- if (action === "verify-again" || action === "verify-enable") {
7367
- fireAndForget(this.verifyAndEnable());
7851
+ if (action === "enter-key") {
7852
+ if (this.selectedTransport) {
7853
+ this.openKeyEntry(this.selectedTransport);
7854
+ }
7855
+ return;
7368
7856
  }
7857
+ fireAndForget(this.selectProvider(action), this.reportBackgroundError);
7369
7858
  }
7370
- async disableAndClear() {
7371
- const clear = this.deps.clearStoredKey;
7372
- if (!clear) {
7859
+ reportBackgroundError = (message) => {
7860
+ this.mode = "menu";
7861
+ this.notice = `Setup failed: ${redactSecrets(message)}`;
7862
+ this.options.tui.requestRender();
7863
+ };
7864
+ async selectProvider(transport) {
7865
+ this.selectedTransport = transport;
7866
+ this.mode = "resolving";
7867
+ this.notice = undefined;
7868
+ this.canEnterKey = false;
7869
+ this.options.tui.requestRender();
7870
+ let credentials;
7871
+ try {
7872
+ credentials = await this.deps.resolveTransport(transport);
7873
+ } catch (error) {
7874
+ this.mode = "menu";
7875
+ this.notice = `Credential lookup failed: ${redactSecrets(error instanceof Error ? error.message : String(error))}`;
7876
+ this.options.tui.requestRender();
7373
7877
  return;
7374
7878
  }
7375
- const result = await clear();
7376
- this.notice = result.message;
7377
- if (!result.ok) {
7879
+ this.credentials = credentials;
7880
+ this.mode = "menu";
7881
+ if (!credentials) {
7882
+ if (transport === "typesafe" || transport === "openai-decisions") {
7883
+ this.notice = undefined;
7884
+ this.openKeyEntry(transport);
7885
+ } else {
7886
+ this.notice = "No OpenRouter Pi login found. Add one in Pi and retry.";
7887
+ }
7378
7888
  this.options.tui.requestRender();
7379
7889
  return;
7380
7890
  }
7381
- this.credentials = undefined;
7382
- this.options.done("Off");
7891
+ if (credentials.transport !== transport) {
7892
+ this.notice = "The selected provider did not resolve matching credentials.";
7893
+ this.options.tui.requestRender();
7894
+ return;
7895
+ }
7896
+ await this.verifyAndEnable(credentials);
7383
7897
  }
7384
- async verifyAndEnable() {
7385
- const { credentials } = this;
7386
- if (!(credentials && this.deps.verify)) {
7898
+ openKeyEntry(transport) {
7899
+ this.selectedTransport = transport;
7900
+ this.mode = "key-entry";
7901
+ const placeholder = transport === "openai-decisions" ? "Paste an OpenAI Platform API key" : "Paste a TypeSafe API key";
7902
+ this.maskedInput = this.createMaskedInput(placeholder);
7903
+ this.maskedInput.focused = this._focused;
7904
+ this.options.tui.requestRender();
7905
+ }
7906
+ cancelKeyEntry() {
7907
+ this.mode = "menu";
7908
+ this.selectedTransport = undefined;
7909
+ this.maskedInput.setValue("");
7910
+ this.options.tui.requestRender();
7911
+ }
7912
+ async submitEnteredKey(value) {
7913
+ const transport = this.selectedTransport;
7914
+ const key = value.trim();
7915
+ this.maskedInput.setValue("");
7916
+ if (!transport || !key) {
7387
7917
  return;
7388
7918
  }
7919
+ await this.verifyAndEnable({ apiKey: key, transport }, key);
7920
+ }
7921
+ async verifyAndEnable(credentials, enteredKey) {
7389
7922
  this.mode = "verifying";
7390
7923
  this.notice = undefined;
7391
7924
  this.options.tui.requestRender();
7392
7925
  const outcome = await this.deps.verify(credentials);
7393
- this.mode = "menu";
7394
7926
  if (!outcome.ok) {
7395
7927
  this.notice = `Verification failed: ${outcome.message ?? "unknown error"}`;
7928
+ this.mode = enteredKey ? "key-entry" : "menu";
7929
+ this.canEnterKey = !enteredKey && canReplaceWithEnteredKey(credentials);
7396
7930
  this.options.tui.requestRender();
7397
7931
  return;
7398
7932
  }
7399
- if (credentials.transport === "typesafe" && credentials.source === "advisor-json") {
7400
- const stored = await this.deps.writeKey?.(credentials.apiKey);
7401
- if (!stored?.ok) {
7402
- this.notice = stored?.message ?? "Storing the key failed; the plaintext advisor.json key keeps working.";
7933
+ const keyToStore = enteredKey ?? (credentials.transport === "typesafe" && credentials.source === "advisor-json" ? credentials.apiKey : undefined);
7934
+ if (keyToStore) {
7935
+ const stored = await this.deps.writeKey(keyToStore, credentials.transport);
7936
+ if (!stored.ok) {
7937
+ this.notice = stored.message;
7938
+ this.mode = "key-entry";
7939
+ this.canEnterKey = false;
7403
7940
  this.options.tui.requestRender();
7404
7941
  return;
7405
7942
  }
7406
- const removed = this.deps.removePlaintextKey?.();
7407
- this.notice = removed?.ok ? "Key moved from advisor.json into the secure store." : `Stored securely, but ${removed?.message ?? "removing the plaintext copy failed; remove it yourself."}`;
7943
+ if (credentials.transport === "typesafe" && credentials.source === "advisor-json") {
7944
+ const removed = this.deps.removePlaintextKey();
7945
+ this.notice = removed.ok ? "Key moved from advisor.json into the secure store." : `Stored securely, but ${removed.message}`;
7946
+ } else {
7947
+ this.notice = `${stored.message} Verification succeeded.`;
7948
+ }
7408
7949
  }
7409
- this.options.done("On");
7410
- }
7411
- async submitEnteredKey(value) {
7412
- const key = value.trim();
7413
- if (!key) {
7950
+ this.credentials = credentials;
7951
+ const saved = this.commitSelection({ enabled: true, transport: credentials.transport }, "On");
7952
+ if (!saved) {
7953
+ this.mode = "menu";
7954
+ this.notice ??= "Provider verified, but settings could not be saved.";
7955
+ this.options.tui.requestRender();
7414
7956
  return;
7415
7957
  }
7416
- if (!this.deps.verify) {
7958
+ this.filterEnabled = true;
7959
+ this.mode = "menu";
7960
+ this.canEnterKey = false;
7961
+ this.options.tui.requestRender();
7962
+ }
7963
+ commitSelection(selection, legacyValue) {
7964
+ if (this.options.onSelection) {
7965
+ if (!this.options.onSelection(selection)) {
7966
+ return false;
7967
+ }
7968
+ this.closeSetup(true);
7969
+ return true;
7970
+ }
7971
+ this.options.done(legacyValue);
7972
+ return true;
7973
+ }
7974
+ closeSetup(refreshSettings = false) {
7975
+ this.options.done();
7976
+ if (refreshSettings || this.pendingSelectionRefresh) {
7977
+ this.options.afterSelection?.();
7978
+ }
7979
+ this.pendingSelectionRefresh = false;
7980
+ }
7981
+ async disableAndClear() {
7982
+ const { credentials } = this;
7983
+ if (!(credentials && canClearCredential(credentials))) {
7417
7984
  return;
7418
7985
  }
7419
- this.mode = "verifying";
7986
+ if (this.options.onSelection) {
7987
+ const saved = this.options.onSelection({
7988
+ enabled: false,
7989
+ transport: this.options.currentTransport
7990
+ });
7991
+ if (!saved) {
7992
+ this.mode = "menu";
7993
+ this.notice = "Could not save the disabled state; the key was not cleared.";
7994
+ this.options.tui.requestRender();
7995
+ return;
7996
+ }
7997
+ this.filterEnabled = false;
7998
+ this.pendingSelectionRefresh = true;
7999
+ }
8000
+ this.mode = "clearing";
8001
+ this.notice = undefined;
7420
8002
  this.options.tui.requestRender();
7421
- const outcome = await this.deps.verify({
7422
- apiKey: key,
7423
- transport: "typesafe"
7424
- });
8003
+ const result = await this.deps.clearStoredKey(credentials.transport);
7425
8004
  this.mode = "menu";
7426
- if (!outcome.ok) {
7427
- this.notice = `Verification failed: ${outcome.message ?? "unknown error"}`;
8005
+ this.notice = result.message;
8006
+ if (!result.ok) {
7428
8007
  this.options.tui.requestRender();
7429
8008
  return;
7430
8009
  }
7431
- const stored = await this.deps.writeKey?.(key);
7432
- if (!stored?.ok) {
7433
- this.notice = stored?.message ?? "Storing the key failed.";
7434
- this.options.tui.requestRender();
7435
- return;
8010
+ this.credentials = undefined;
8011
+ if (this.options.onSelection) {
8012
+ this.closeSetup();
8013
+ } else if (this.commitSelection({ enabled: false, transport: this.options.currentTransport }, "Off")) {
8014
+ this.filterEnabled = false;
7436
8015
  }
7437
- this.notice = `${stored.message} Verification succeeded.`;
7438
- await this.refresh();
7439
- this.options.done("On");
8016
+ this.options.tui.requestRender();
7440
8017
  }
7441
8018
  }
7442
8019
 
@@ -7524,13 +8101,22 @@ var scoutTimeoutItem = (settings) => {
7524
8101
  values: numericValues(timeoutMs, [5000, 1e4, 15000, 30000, 45000, 60000, 90000, 120000])
7525
8102
  };
7526
8103
  };
7527
- var jevItems = (settings, theme, tui) => [
8104
+ var jevItems = (settings, theme, tui, jevSetupDeps, onJevSetup, afterJevSetup) => [
7528
8105
  {
7529
8106
  currentValue: settingValue(settings.jevFilterEnabled, false),
7530
- description: "Screen low-stakes ask_advisor consultations with Jev; guided setup verifies credentials.",
8107
+ description: "Screen low-stakes ask_advisor consultations with Jev/Decisions; guided setup verifies credentials.",
7531
8108
  id: "jevFilter",
7532
- label: "Jev consultation filter",
7533
- submenu: (currentValue, done) => new JevSetupSubmenu({ currentValue, done, theme, tui })
8109
+ label: "Jev/Decisions consultation filter",
8110
+ submenu: (currentValue, done) => new JevSetupSubmenu({
8111
+ afterSelection: afterJevSetup,
8112
+ currentTransport: settings.jevTransport ?? DEFAULT_JEV_TRANSPORT,
8113
+ currentValue,
8114
+ done,
8115
+ onSelection: onJevSetup,
8116
+ setupDeps: jevSetupDeps,
8117
+ theme,
8118
+ tui
8119
+ })
7534
8120
  },
7535
8121
  {
7536
8122
  currentValue: String(settings.jevFilterSkipConfidence ?? DEFAULT_JEV_FILTER_SKIP_CONFIDENCE),
@@ -7584,7 +8170,7 @@ var jevItems = (settings, theme, tui) => [
7584
8170
  },
7585
8171
  {
7586
8172
  currentValue: String(settings.jevPricePerMtok ?? DEFAULT_JEV_PRICE_PER_MTOK),
7587
- description: "Assumed TypeSafe price per million input tokens for cost lines.",
8173
+ description: "TypeSafe/OpenRouter estimated input price per million tokens; ignored for OpenAI Decisions, whose cost is unavailable.",
7588
8174
  id: "jevPricePerMtok",
7589
8175
  label: "Jev price/Mtok",
7590
8176
  values: numericValues(settings.jevPricePerMtok ?? DEFAULT_JEV_PRICE_PER_MTOK, [0.01, 0.02, 0.042, 0.05, 0.1])
@@ -7593,7 +8179,7 @@ var jevItems = (settings, theme, tui) => [
7593
8179
  currentValue: settings.jevTurnGateEveryTurns === 0 || settings.jevTurnGateEveryTurns === undefined ? "Off" : `every ${settings.jevTurnGateEveryTurns} turns`,
7594
8180
  description: "Proactively consult the Advisor every N turns without a consultation (0 = off).",
7595
8181
  id: "jevTurnGateEveryTurns",
7596
- label: "Jev turn gate",
8182
+ label: "Jev/Decisions turn gate",
7597
8183
  values: [
7598
8184
  "Off",
7599
8185
  "every 3 turns",
@@ -7606,19 +8192,8 @@ var jevItems = (settings, theme, tui) => [
7606
8192
  currentValue: String(settings.jevTurnGateNoulThreshold ?? DEFAULT_JEV_TURN_GATE_NOUL_THRESHOLD),
7607
8193
  description: "Jev confidence required before the turn gate interrupts with advice.",
7608
8194
  id: "jevTurnGateNoulThreshold",
7609
- label: "Jev turn-gate threshold",
8195
+ label: "Jev/Decisions turn-gate threshold",
7610
8196
  values: numericValues(settings.jevTurnGateNoulThreshold ?? DEFAULT_JEV_TURN_GATE_NOUL_THRESHOLD, [0.6, 0.7, 0.8, 0.85, 0.9, 0.95])
7611
- },
7612
- {
7613
- currentValue: settings.jevTransport ?? DEFAULT_JEV_TRANSPORT,
7614
- description: "How Jev calls travel: auto reuses an OpenRouter login when no TypeSafe key is set.",
7615
- id: "jevTransport",
7616
- label: "Jev transport",
7617
- values: withCurrentValue(settings.jevTransport ?? DEFAULT_JEV_TRANSPORT, [
7618
- "auto",
7619
- "typesafe",
7620
- "openrouter"
7621
- ])
7622
8197
  }
7623
8198
  ];
7624
8199
  var advisorFallbackModelItem = (settings, modelRefs, keybindings, theme, tui) => ({
@@ -7665,6 +8240,9 @@ var createSettingsItems = ({
7665
8240
  modelWhitelist,
7666
8241
  presets,
7667
8242
  settings,
8243
+ afterJevSetup,
8244
+ jevSetupDeps,
8245
+ onJevSetup,
7668
8246
  theme,
7669
8247
  tui
7670
8248
  }) => {
@@ -7799,7 +8377,7 @@ var createSettingsItems = ({
7799
8377
  title: "Tool disclosure policies",
7800
8378
  tui
7801
8379
  })
7802
- }, toggle("trackedFileContent", "Tracked file content", "Allow tracked file contents to be sent with Advisor context.", settings.trackedFileContent, false), toggle("untrackedContent", "Untracked file content", "Allow untracked file contents to be sent with Advisor context.", settings.untrackedContent, false), toggle("outcomeLogging", "Outcome logging (global)", "Allow anonymized Advisor outcomes to be logged globally.", settings.outcomeLogging, false), ...jevItems(settings, theme, tui));
8380
+ }, toggle("trackedFileContent", "Tracked file content", "Allow tracked file contents to be sent with Advisor context.", settings.trackedFileContent, false), toggle("untrackedContent", "Untracked file content", "Allow untracked file contents to be sent with Advisor context.", settings.untrackedContent, false), toggle("outcomeLogging", "Outcome logging (global)", "Allow anonymized Advisor outcomes to be logged globally.", settings.outcomeLogging, false), ...jevItems(settings, theme, tui, jevSetupDeps, onJevSetup, afterJevSetup));
7803
8381
  return items;
7804
8382
  };
7805
8383
 
@@ -8018,10 +8596,6 @@ var applyJevMutation = (settings, id, value) => {
8018
8596
  settings.jevPricePerMtok = Number(value);
8019
8597
  return true;
8020
8598
  }
8021
- case "jevTransport": {
8022
- settings.jevTransport = value;
8023
- return true;
8024
- }
8025
8599
  default: {
8026
8600
  return false;
8027
8601
  }
@@ -8103,9 +8677,15 @@ class AdvisorSettingsSelector {
8103
8677
  const fallbackModel = advisorFallbackModelItem(this.settings, this.options.modelRefs, this.options.keybindings, this.options.theme, this.options.tui);
8104
8678
  const modelWhitelist = advisorModelWhitelistItem(this.settings, this.options.modelRefs, this.options.keybindings, this.options.theme, this.options.tui);
8105
8679
  const items = createSettingsItems({
8680
+ afterJevSetup: () => {
8681
+ this.settingsList = this.createSettingsList("jevFilter");
8682
+ this.options.tui.requestRender();
8683
+ },
8106
8684
  effortLevels: this.options.effortLevels,
8107
8685
  fallbackModel,
8686
+ jevSetupDeps: this.options.jevSetupDeps,
8108
8687
  modelWhitelist,
8688
+ onJevSetup: (selection) => this.applyJevSetup(selection),
8109
8689
  presets: this.presets,
8110
8690
  settings: this.settings,
8111
8691
  theme: this.options.theme,
@@ -8118,6 +8698,25 @@ class AdvisorSettingsSelector {
8118
8698
  }
8119
8699
  return adapter;
8120
8700
  }
8701
+ applyJevSetup(selection) {
8702
+ const updated = {
8703
+ ...this.settings,
8704
+ jevFilterEnabled: selection.enabled,
8705
+ jevTransport: selection.transport,
8706
+ showUsageDetails: this.settings.showUsageDetails ?? true,
8707
+ toolPolicies: { ...this.settings.toolPolicies }
8708
+ };
8709
+ try {
8710
+ const result = this.options.onJevSetup ? this.options.onJevSetup(selection, updated) : (this.options.onChange ?? this.options.onSave)?.(updated);
8711
+ if (result === false) {
8712
+ return false;
8713
+ }
8714
+ } catch {
8715
+ return false;
8716
+ }
8717
+ Object.assign(this.settings, updated);
8718
+ return true;
8719
+ }
8121
8720
  startSimpleModeGradient() {
8122
8721
  this.stopSimpleModeGradient();
8123
8722
  this.simpleModeGradientStartedAt = Date.now();
@@ -8209,7 +8808,7 @@ var applyAdvisorSettings = (settings) => {
8209
8808
  applyJevSettings(settings);
8210
8809
  applyDisclosureSettings(settings);
8211
8810
  };
8212
- var saveAdvisorSettings = (ctx, settings) => {
8811
+ var saveAdvisorSettings = (ctx, settings, options = {}) => {
8213
8812
  const previous = getAdvisorSettings();
8214
8813
  try {
8215
8814
  applyAdvisorSettings(settings);
@@ -8218,7 +8817,9 @@ var saveAdvisorSettings = (ctx, settings) => {
8218
8817
  persistAdvisor: Boolean(persisted.advisor),
8219
8818
  persistExecutor: Boolean(persisted.executor)
8220
8819
  });
8221
- saveGlobalOutcomeLogging(settings.outcomeLogging ?? false);
8820
+ if (!options.skipOutcomeLogging) {
8821
+ saveGlobalOutcomeLogging(settings.outcomeLogging ?? false);
8822
+ }
8222
8823
  } catch (error) {
8223
8824
  applyAdvisorSettings(previous);
8224
8825
  throw error;
@@ -8237,6 +8838,9 @@ var registerSettingsCommands = (runtime) => {
8237
8838
  await ctx.ui.custom((tui, theme, keybindings, done) => new AdvisorSettingsSelector({
8238
8839
  effortLevels: EFFORT_LEVELS,
8239
8840
  initial,
8841
+ jevSetupDeps: {
8842
+ resolveTransport: (transport) => transport ? resolveJevTransportFor(transport, ctx) : resolveJevTransport(ctx)
8843
+ },
8240
8844
  keybindings,
8241
8845
  modelRefs: getConfiguredModelRefs(ctx),
8242
8846
  onCancel: () => done(undefined),
@@ -8250,6 +8854,27 @@ var registerSettingsCommands = (runtime) => {
8250
8854
  ctx.ui.notify(`Could not save Advisor settings: ${message}`, "error");
8251
8855
  }
8252
8856
  },
8857
+ onJevSetup: (selection, settings) => {
8858
+ try {
8859
+ saveAdvisorSettings(ctx, {
8860
+ ...settings,
8861
+ jevFilterEnabled: selection.enabled,
8862
+ jevTransport: selection.transport
8863
+ }, { skipOutcomeLogging: true });
8864
+ } catch (error) {
8865
+ const message = error instanceof Error ? error.message : String(error);
8866
+ ctx.ui.notify(`Could not save Advisor settings: ${message}`, "error");
8867
+ return false;
8868
+ }
8869
+ try {
8870
+ runtime.updateSameModelNotice(ctx);
8871
+ runtime.updateAdvisorUsageStatus(ctx);
8872
+ } catch (error) {
8873
+ const message = error instanceof Error ? error.message : String(error);
8874
+ ctx.ui.notify(`Advisor settings were saved, but the status refresh failed: ${message}`, "warning");
8875
+ }
8876
+ return true;
8877
+ },
8253
8878
  presets: CONTEXT_PRESETS,
8254
8879
  theme,
8255
8880
  tui
@@ -8370,7 +8995,7 @@ var turnGateQuestion = {
8370
8995
  instructions: "Should a senior engineering advisor be consulted right now, before the executor continues? Judge from `recent_conversation`.",
8371
8996
  type: "noul"
8372
8997
  };
8373
- var outageNotifier2 = createOutageNotifier((category, message) => `Advisor Jev turn gate failed (${category}); continuing without a proactive consultation. ${message}`);
8998
+ var outageNotifier2 = createOutageNotifier((category, message) => `Advisor Jev/Decisions turn gate failed (${category}); continuing without a proactive consultation. ${message}`);
8374
8999
  var notifyFailureOnce = outageNotifier2.notify;
8375
9000
  var shouldRunTurnGate = (registration, ctx, session) => {
8376
9001
  const interval = advisorJevTurnGateEveryTurnsRef;
@@ -8396,7 +9021,7 @@ var handleJevTurnEnd = async (registration, ctx) => {
8396
9021
  const credentials = await (deps.resolveTransport ?? resolveJevTransport)(ctx);
8397
9022
  if (!credentials) {
8398
9023
  session.recordJevGateFailure();
8399
- notifyFailureOnce(ctx, "missing-key", "No Jev credentials resolved (no TypeSafe key and no OpenRouter login).");
9024
+ notifyFailureOnce(ctx, "missing-key", "No credentials resolved for the selected Jev/Decisions provider.");
8400
9025
  return;
8401
9026
  }
8402
9027
  const client = jevClientFromCredentials(credentials, deps.fetch);