pi-advisor-flow 0.6.0 → 0.7.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/CHANGELOG.md +16 -0
- package/README.md +1 -0
- package/dist/index.js +1816 -154
- package/package.json +11 -9
- package/src/commands/manual-consultation.ts +16 -2
- package/src/commands/settings-persistence.ts +48 -0
- package/src/commands/types.ts +1 -0
- package/src/config/defaults.ts +75 -0
- package/src/config/schema.ts +112 -0
- package/src/config/state.ts +69 -0
- package/src/config/storage.ts +7 -2
- package/src/config/types.ts +29 -0
- package/src/jev/client.ts +348 -0
- package/src/jev/key-store.ts +252 -0
- package/src/jev/ledger.ts +234 -0
- package/src/jev/questions.ts +119 -0
- package/src/jev/state.ts +47 -0
- package/src/jev/transport.ts +58 -0
- package/src/outcomes.ts +5 -1
- package/src/session-state.ts +99 -5
- package/src/tools/jev-filter.ts +202 -0
- package/src/tools/jev-turn-gate.ts +183 -0
- package/src/tools/loop-gate.ts +1 -0
- package/src/tools/outage-notifier.ts +30 -0
- package/src/tools/register-ask-advisor.ts +37 -1
- package/src/tools/register-renderers.ts +58 -0
- package/src/tools/registration.ts +17 -0
- package/src/tools/render-advisor-result.ts +43 -14
- package/src/tools/types.ts +10 -0
- package/src/ui/jev-setup-submenu.ts +344 -0
- package/src/ui/masked-input.ts +68 -0
- package/src/ui/settings-items.ts +162 -1
- package/src/ui/settings-mutations.ts +35 -0
- package/src/ui/types.ts +11 -0
- package/src/usage.ts +3 -1
package/dist/index.js
CHANGED
|
@@ -8,6 +8,21 @@ var MAX_CONTEXT_MAX_CHARS = Number.MAX_SAFE_INTEGER;
|
|
|
8
8
|
var DEFAULT_ADVISOR_TOOL_RESULT_MAX_LINES = DEFAULT_MAX_LINES;
|
|
9
9
|
var DEFAULT_ADVISOR_TOOL_RESULT_MAX_BYTES = DEFAULT_MAX_BYTES;
|
|
10
10
|
var DEFAULT_ADVISOR_GIT_CONTEXT_MAX_CHARS = 20000;
|
|
11
|
+
var DEFAULT_JEV_MODEL = "jev-latest";
|
|
12
|
+
var DEFAULT_JEV_TIMEOUT_MS = 8000;
|
|
13
|
+
var DEFAULT_JEV_DIGEST_MAX_CHARS = 4000;
|
|
14
|
+
var DEFAULT_JEV_PRICE_PER_MTOK = 0.042;
|
|
15
|
+
var DEFAULT_JEV_FILTER_SKIP_CONFIDENCE = 0.85;
|
|
16
|
+
var DEFAULT_JEV_FILTER_NOUL_MARGIN = 0.35;
|
|
17
|
+
var DEFAULT_JEV_FILTER_OVERRIDE_WINDOW = 10;
|
|
18
|
+
var DEFAULT_JEV_TURN_GATE_EVERY_TURNS = 0;
|
|
19
|
+
var DEFAULT_JEV_TURN_GATE_NOUL_THRESHOLD = 0.8;
|
|
20
|
+
var DEFAULT_JEV_TRANSPORT = "auto";
|
|
21
|
+
var JEV_TRANSPORTS = [
|
|
22
|
+
"auto",
|
|
23
|
+
"typesafe",
|
|
24
|
+
"openrouter"
|
|
25
|
+
];
|
|
11
26
|
var ADVISOR_TOOL_POLICIES = [
|
|
12
27
|
"full",
|
|
13
28
|
"summary",
|
|
@@ -41,6 +56,17 @@ var simpleModeRef = false;
|
|
|
41
56
|
var alwaysOnRef = false;
|
|
42
57
|
var advisorFailureModeRef = "block-session";
|
|
43
58
|
var advisorHerdrIntegrationRef = true;
|
|
59
|
+
var advisorJevFilterEnabledRef = false;
|
|
60
|
+
var advisorJevFilterSkipConfidenceRef = DEFAULT_JEV_FILTER_SKIP_CONFIDENCE;
|
|
61
|
+
var advisorJevFilterNoulMarginRef = DEFAULT_JEV_FILTER_NOUL_MARGIN;
|
|
62
|
+
var advisorJevFilterOverrideWindowRef = DEFAULT_JEV_FILTER_OVERRIDE_WINDOW;
|
|
63
|
+
var advisorJevModelRef = DEFAULT_JEV_MODEL;
|
|
64
|
+
var advisorJevTimeoutMsRef = DEFAULT_JEV_TIMEOUT_MS;
|
|
65
|
+
var advisorJevDigestMaxCharsRef = DEFAULT_JEV_DIGEST_MAX_CHARS;
|
|
66
|
+
var advisorJevPricePerMtokRef = DEFAULT_JEV_PRICE_PER_MTOK;
|
|
67
|
+
var advisorJevTransportRef = DEFAULT_JEV_TRANSPORT;
|
|
68
|
+
var advisorJevTurnGateEveryTurnsRef = DEFAULT_JEV_TURN_GATE_EVERY_TURNS;
|
|
69
|
+
var advisorJevTurnGateNoulThresholdRef = DEFAULT_JEV_TURN_GATE_NOUL_THRESHOLD;
|
|
44
70
|
var advisorToolResultMaxLinesRef = DEFAULT_ADVISOR_TOOL_RESULT_MAX_LINES;
|
|
45
71
|
var advisorToolResultMaxBytesRef = DEFAULT_ADVISOR_TOOL_RESULT_MAX_BYTES;
|
|
46
72
|
var advisorRedactSecretsRef = false;
|
|
@@ -119,6 +145,39 @@ var setAdvisorFailureModeRef = (value) => {
|
|
|
119
145
|
var setAdvisorHerdrIntegrationRef = (enabled) => {
|
|
120
146
|
advisorHerdrIntegrationRef = enabled;
|
|
121
147
|
};
|
|
148
|
+
var setAdvisorJevFilterEnabledRef = (enabled) => {
|
|
149
|
+
advisorJevFilterEnabledRef = enabled;
|
|
150
|
+
};
|
|
151
|
+
var setAdvisorJevFilterSkipConfidenceRef = (value) => {
|
|
152
|
+
advisorJevFilterSkipConfidenceRef = value;
|
|
153
|
+
};
|
|
154
|
+
var setAdvisorJevFilterNoulMarginRef = (value) => {
|
|
155
|
+
advisorJevFilterNoulMarginRef = value;
|
|
156
|
+
};
|
|
157
|
+
var setAdvisorJevFilterOverrideWindowRef = (value) => {
|
|
158
|
+
advisorJevFilterOverrideWindowRef = value;
|
|
159
|
+
};
|
|
160
|
+
var setAdvisorJevModelRef = (model) => {
|
|
161
|
+
advisorJevModelRef = model?.trim() || DEFAULT_JEV_MODEL;
|
|
162
|
+
};
|
|
163
|
+
var setAdvisorJevTimeoutMsRef = (value) => {
|
|
164
|
+
advisorJevTimeoutMsRef = value;
|
|
165
|
+
};
|
|
166
|
+
var setAdvisorJevDigestMaxCharsRef = (value) => {
|
|
167
|
+
advisorJevDigestMaxCharsRef = value;
|
|
168
|
+
};
|
|
169
|
+
var setAdvisorJevPricePerMtokRef = (value) => {
|
|
170
|
+
advisorJevPricePerMtokRef = value;
|
|
171
|
+
};
|
|
172
|
+
var setAdvisorJevTransportRef = (value) => {
|
|
173
|
+
advisorJevTransportRef = value;
|
|
174
|
+
};
|
|
175
|
+
var setAdvisorJevTurnGateEveryTurnsRef = (value) => {
|
|
176
|
+
advisorJevTurnGateEveryTurnsRef = value;
|
|
177
|
+
};
|
|
178
|
+
var setAdvisorJevTurnGateNoulThresholdRef = (value) => {
|
|
179
|
+
advisorJevTurnGateNoulThresholdRef = value;
|
|
180
|
+
};
|
|
122
181
|
var setAdvisorToolResultMaxLinesRef = (value) => {
|
|
123
182
|
advisorToolResultMaxLinesRef = value;
|
|
124
183
|
};
|
|
@@ -169,6 +228,17 @@ var getAdvisorSettings = () => ({
|
|
|
169
228
|
gitContext: advisorGitContextRef,
|
|
170
229
|
gitContextMaxChars: advisorGitContextMaxCharsRef,
|
|
171
230
|
herdrIntegration: advisorHerdrIntegrationRef,
|
|
231
|
+
jevDigestMaxChars: advisorJevDigestMaxCharsRef,
|
|
232
|
+
jevFilterEnabled: advisorJevFilterEnabledRef,
|
|
233
|
+
jevFilterNoulMargin: advisorJevFilterNoulMarginRef,
|
|
234
|
+
jevFilterOverrideWindow: advisorJevFilterOverrideWindowRef,
|
|
235
|
+
jevFilterSkipConfidence: advisorJevFilterSkipConfidenceRef,
|
|
236
|
+
jevModel: advisorJevModelRef,
|
|
237
|
+
jevPricePerMtok: advisorJevPricePerMtokRef,
|
|
238
|
+
jevTimeoutMs: advisorJevTimeoutMsRef,
|
|
239
|
+
jevTransport: advisorJevTransportRef,
|
|
240
|
+
jevTurnGateEveryTurns: advisorJevTurnGateEveryTurnsRef,
|
|
241
|
+
jevTurnGateNoulThreshold: advisorJevTurnGateNoulThresholdRef,
|
|
172
242
|
loopThreshold: advisorLoopThresholdRef,
|
|
173
243
|
maxCallsPerSession: advisorMaxCallsPerSessionRef,
|
|
174
244
|
outcomeLogging: advisorOutcomeLoggingRef,
|
|
@@ -312,6 +382,12 @@ var isValidMaxCallsPerSession = (value) => nonNegativeSafeInteger(value);
|
|
|
312
382
|
var isValidGateFailureMode = (value) => typeof value === "string" && GATE_FAILURE_MODES.includes(value);
|
|
313
383
|
var isValidToolResultMaxLines = (value) => nonNegativeSafeInteger(value);
|
|
314
384
|
var isValidToolResultMaxBytes = (value) => nonNegativeSafeInteger(value);
|
|
385
|
+
var isValidJevTimeoutMs = (value) => typeof value === "number" && Number.isSafeInteger(value) && value >= 1;
|
|
386
|
+
var isValidJevDigestMaxChars = (value) => nonNegativeSafeInteger(value);
|
|
387
|
+
var isValidJevPricePerMtok = (value) => typeof value === "number" && Number.isFinite(value) && value > 0;
|
|
388
|
+
var isValidJevSkipConfidence = (value) => typeof value === "number" && Number.isFinite(value) && value >= 0.5 && value <= 1;
|
|
389
|
+
var isValidJevNoulMargin = (value) => typeof value === "number" && Number.isFinite(value) && value >= 0 && value <= 0.5;
|
|
390
|
+
var isValidJevTransport = (value) => typeof value === "string" && JEV_TRANSPORTS.includes(value);
|
|
315
391
|
var CONFIG_SCHEMA = {
|
|
316
392
|
advisor: {
|
|
317
393
|
accepted: "a provider/model string",
|
|
@@ -381,6 +457,81 @@ var CONFIG_SCHEMA = {
|
|
|
381
457
|
persisted: true,
|
|
382
458
|
type: "boolean"
|
|
383
459
|
},
|
|
460
|
+
advisorJevDigestMaxChars: {
|
|
461
|
+
accepted: "a non-negative safe integer",
|
|
462
|
+
current: () => advisorJevDigestMaxCharsRef,
|
|
463
|
+
persisted: true,
|
|
464
|
+
type: "number",
|
|
465
|
+
validate: isValidJevDigestMaxChars
|
|
466
|
+
},
|
|
467
|
+
advisorJevFilterEnabled: {
|
|
468
|
+
accepted: "true or false",
|
|
469
|
+
current: () => advisorJevFilterEnabledRef,
|
|
470
|
+
persisted: true,
|
|
471
|
+
type: "boolean"
|
|
472
|
+
},
|
|
473
|
+
advisorJevFilterNoulMargin: {
|
|
474
|
+
accepted: "a number from 0 through 0.5",
|
|
475
|
+
current: () => advisorJevFilterNoulMarginRef,
|
|
476
|
+
persisted: true,
|
|
477
|
+
type: "number",
|
|
478
|
+
validate: isValidJevNoulMargin
|
|
479
|
+
},
|
|
480
|
+
advisorJevFilterOverrideWindow: {
|
|
481
|
+
accepted: "a non-negative safe integer",
|
|
482
|
+
current: () => advisorJevFilterOverrideWindowRef,
|
|
483
|
+
persisted: true,
|
|
484
|
+
type: "number",
|
|
485
|
+
validate: nonNegativeSafeInteger
|
|
486
|
+
},
|
|
487
|
+
advisorJevFilterSkipConfidence: {
|
|
488
|
+
accepted: "a number between 0.5 and 1 inclusive",
|
|
489
|
+
current: () => advisorJevFilterSkipConfidenceRef,
|
|
490
|
+
persisted: true,
|
|
491
|
+
type: "number",
|
|
492
|
+
validate: isValidJevSkipConfidence
|
|
493
|
+
},
|
|
494
|
+
advisorJevModel: {
|
|
495
|
+
accepted: "a non-empty string",
|
|
496
|
+
current: () => advisorJevModelRef,
|
|
497
|
+
persisted: true,
|
|
498
|
+
type: "string"
|
|
499
|
+
},
|
|
500
|
+
advisorJevPricePerMtok: {
|
|
501
|
+
accepted: "a positive number",
|
|
502
|
+
current: () => advisorJevPricePerMtokRef,
|
|
503
|
+
persisted: true,
|
|
504
|
+
type: "number",
|
|
505
|
+
validate: isValidJevPricePerMtok
|
|
506
|
+
},
|
|
507
|
+
advisorJevTimeoutMs: {
|
|
508
|
+
accepted: "a positive safe integer",
|
|
509
|
+
current: () => advisorJevTimeoutMsRef,
|
|
510
|
+
persisted: true,
|
|
511
|
+
type: "number",
|
|
512
|
+
validate: isValidJevTimeoutMs
|
|
513
|
+
},
|
|
514
|
+
advisorJevTransport: {
|
|
515
|
+
accepted: JEV_TRANSPORTS.join(", "),
|
|
516
|
+
current: () => advisorJevTransportRef,
|
|
517
|
+
persisted: true,
|
|
518
|
+
type: "enum",
|
|
519
|
+
validate: isValidJevTransport
|
|
520
|
+
},
|
|
521
|
+
advisorJevTurnGateEveryTurns: {
|
|
522
|
+
accepted: "a non-negative safe integer (0 disables the turn gate)",
|
|
523
|
+
current: () => advisorJevTurnGateEveryTurnsRef,
|
|
524
|
+
persisted: true,
|
|
525
|
+
type: "number",
|
|
526
|
+
validate: nonNegativeSafeInteger
|
|
527
|
+
},
|
|
528
|
+
advisorJevTurnGateNoulThreshold: {
|
|
529
|
+
accepted: "a number between 0.5 and 1 inclusive",
|
|
530
|
+
current: () => advisorJevTurnGateNoulThresholdRef,
|
|
531
|
+
persisted: true,
|
|
532
|
+
type: "number",
|
|
533
|
+
validate: isValidJevSkipConfidence
|
|
534
|
+
},
|
|
384
535
|
advisorLoopThreshold: {
|
|
385
536
|
accepted: "a safe integer of at least 2",
|
|
386
537
|
current: () => advisorLoopThresholdRef,
|
|
@@ -629,6 +780,17 @@ var resetDefaults = () => {
|
|
|
629
780
|
setAlwaysOnRef(false);
|
|
630
781
|
setAdvisorFailureModeRef("block-session");
|
|
631
782
|
setAdvisorHerdrIntegrationRef(true);
|
|
783
|
+
setAdvisorJevModelRef(DEFAULT_JEV_MODEL);
|
|
784
|
+
setAdvisorJevFilterEnabledRef(false);
|
|
785
|
+
setAdvisorJevFilterSkipConfidenceRef(DEFAULT_JEV_FILTER_SKIP_CONFIDENCE);
|
|
786
|
+
setAdvisorJevFilterNoulMarginRef(DEFAULT_JEV_FILTER_NOUL_MARGIN);
|
|
787
|
+
setAdvisorJevFilterOverrideWindowRef(DEFAULT_JEV_FILTER_OVERRIDE_WINDOW);
|
|
788
|
+
setAdvisorJevTimeoutMsRef(DEFAULT_JEV_TIMEOUT_MS);
|
|
789
|
+
setAdvisorJevDigestMaxCharsRef(DEFAULT_JEV_DIGEST_MAX_CHARS);
|
|
790
|
+
setAdvisorJevPricePerMtokRef(DEFAULT_JEV_PRICE_PER_MTOK);
|
|
791
|
+
setAdvisorJevTransportRef(DEFAULT_JEV_TRANSPORT);
|
|
792
|
+
setAdvisorJevTurnGateEveryTurnsRef(DEFAULT_JEV_TURN_GATE_EVERY_TURNS);
|
|
793
|
+
setAdvisorJevTurnGateNoulThresholdRef(DEFAULT_JEV_TURN_GATE_NOUL_THRESHOLD);
|
|
632
794
|
setAdvisorToolResultMaxLinesRef(DEFAULT_ADVISOR_TOOL_RESULT_MAX_LINES);
|
|
633
795
|
setAdvisorToolResultMaxBytesRef(DEFAULT_ADVISOR_TOOL_RESULT_MAX_BYTES);
|
|
634
796
|
setAdvisorRedactSecretsRef(false);
|
|
@@ -676,6 +838,17 @@ var applyConfig = (config) => {
|
|
|
676
838
|
applyOptionalConfig(config, "alwaysOn", setAlwaysOnRef);
|
|
677
839
|
applyOptionalConfig(config, "gateFailureMode", setAdvisorFailureModeRef);
|
|
678
840
|
applyOptionalConfig(config, "advisorHerdrIntegration", setAdvisorHerdrIntegrationRef);
|
|
841
|
+
applyOptionalConfig(config, "advisorJevFilterEnabled", setAdvisorJevFilterEnabledRef);
|
|
842
|
+
applyOptionalConfig(config, "advisorJevFilterSkipConfidence", setAdvisorJevFilterSkipConfidenceRef);
|
|
843
|
+
applyOptionalConfig(config, "advisorJevFilterNoulMargin", setAdvisorJevFilterNoulMarginRef);
|
|
844
|
+
applyOptionalConfig(config, "advisorJevFilterOverrideWindow", setAdvisorJevFilterOverrideWindowRef);
|
|
845
|
+
applyNonEmptyStringConfig(config.advisorJevModel, setAdvisorJevModelRef);
|
|
846
|
+
applyOptionalConfig(config, "advisorJevTimeoutMs", setAdvisorJevTimeoutMsRef);
|
|
847
|
+
applyOptionalConfig(config, "advisorJevDigestMaxChars", setAdvisorJevDigestMaxCharsRef);
|
|
848
|
+
applyOptionalConfig(config, "advisorJevPricePerMtok", setAdvisorJevPricePerMtokRef);
|
|
849
|
+
applyOptionalConfig(config, "advisorJevTransport", setAdvisorJevTransportRef);
|
|
850
|
+
applyOptionalConfig(config, "advisorJevTurnGateEveryTurns", setAdvisorJevTurnGateEveryTurnsRef);
|
|
851
|
+
applyOptionalConfig(config, "advisorJevTurnGateNoulThreshold", setAdvisorJevTurnGateNoulThresholdRef);
|
|
679
852
|
applyOptionalConfig(config, "advisorToolResultMaxLines", setAdvisorToolResultMaxLinesRef);
|
|
680
853
|
applyOptionalConfig(config, "advisorToolResultMaxBytes", setAdvisorToolResultMaxBytesRef);
|
|
681
854
|
applyOptionalConfig(config, "advisorRedactSecrets", setAdvisorRedactSecretsRef);
|
|
@@ -690,6 +863,7 @@ var applyConfig = (config) => {
|
|
|
690
863
|
// src/config/storage.ts
|
|
691
864
|
var currentConfigState = () => Object.fromEntries(SAVED_CONFIG_KEYS.map((key) => [key, CONFIG_SCHEMA[key].current()]));
|
|
692
865
|
var sameConfigValue = (left, right) => JSON.stringify(left) === JSON.stringify(right);
|
|
866
|
+
var RESERVED_ADVISOR_JSON_KEYS = new Set(["typesafe_api_key"]);
|
|
693
867
|
var readExistingConfig = (path) => {
|
|
694
868
|
try {
|
|
695
869
|
const parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
@@ -751,7 +925,7 @@ var loadConfig = (_ctx) => {
|
|
|
751
925
|
setPersistedModelRefs(configuredModelRef(globalConfig?.advisor), configuredModelRef(globalConfig?.executor));
|
|
752
926
|
if (globalConfig) {
|
|
753
927
|
applyConfig(globalConfig);
|
|
754
|
-
const unknownKeys = unknownConfigKeys(globalConfig);
|
|
928
|
+
const unknownKeys = unknownConfigKeys(globalConfig).filter((key) => !RESERVED_ADVISOR_JSON_KEYS.has(key));
|
|
755
929
|
const warningIdentity = `${global}:${configIdentity(global)}`;
|
|
756
930
|
if (unknownKeys.length > 0 && _ctx.hasUI && !warnedUnknownConfigIdentities.has(warningIdentity)) {
|
|
757
931
|
_ctx.ui.notify(`Advisor configuration at ${global} contains unrecognized key(s) ${unknownKeys.map((key) => JSON.stringify(key)).join(", ")}. They were preserved but ignored; check for typos or upgrade pi-advisor.`, "warning");
|
|
@@ -2231,7 +2405,7 @@ var addAdvisorUsage = (totals, usage) => {
|
|
|
2231
2405
|
totals.costCalls += 1;
|
|
2232
2406
|
}
|
|
2233
2407
|
};
|
|
2234
|
-
var
|
|
2408
|
+
var formatTokenCount = (value) => {
|
|
2235
2409
|
if (value < 1000) {
|
|
2236
2410
|
return String(value);
|
|
2237
2411
|
}
|
|
@@ -2246,6 +2420,7 @@ var formatTokens = (value) => {
|
|
|
2246
2420
|
}
|
|
2247
2421
|
return `${Math.round(value / 1e6)}M`;
|
|
2248
2422
|
};
|
|
2423
|
+
var formatTokens = formatTokenCount;
|
|
2249
2424
|
var formatCost = (value) => `$${value.toFixed(4)}`;
|
|
2250
2425
|
var formatUsageFields = (usage) => {
|
|
2251
2426
|
const tokens = [
|
|
@@ -3236,6 +3411,139 @@ var renderScoutDetails = (box, scout, expanded, theme) => {
|
|
|
3236
3411
|
}
|
|
3237
3412
|
};
|
|
3238
3413
|
|
|
3414
|
+
// src/jev/ledger.ts
|
|
3415
|
+
var freshJevUsage = () => ({
|
|
3416
|
+
cost: 0,
|
|
3417
|
+
inputTokens: 0,
|
|
3418
|
+
outputTokens: 0
|
|
3419
|
+
});
|
|
3420
|
+
var freshJevLedger = () => ({
|
|
3421
|
+
filter: {
|
|
3422
|
+
allowed: 0,
|
|
3423
|
+
failures: 0,
|
|
3424
|
+
overrides: 0,
|
|
3425
|
+
repeatSkipped: 0,
|
|
3426
|
+
screened: 0,
|
|
3427
|
+
skipped: 0
|
|
3428
|
+
},
|
|
3429
|
+
gate: { checks: 0, consultations: 0, failures: 0, usage: freshJevUsage() },
|
|
3430
|
+
usage: freshJevUsage()
|
|
3431
|
+
});
|
|
3432
|
+
var addJevUsage = (totals, usage) => {
|
|
3433
|
+
totals.cost += usage.cost;
|
|
3434
|
+
totals.inputTokens += usage.inputTokens;
|
|
3435
|
+
totals.outputTokens += usage.outputTokens;
|
|
3436
|
+
};
|
|
3437
|
+
|
|
3438
|
+
class AdvisorJevLedgerState {
|
|
3439
|
+
#ledger = freshJevLedger();
|
|
3440
|
+
#lastSkip;
|
|
3441
|
+
reset() {
|
|
3442
|
+
this.#ledger = freshJevLedger();
|
|
3443
|
+
this.#lastSkip = undefined;
|
|
3444
|
+
}
|
|
3445
|
+
get lastSkip() {
|
|
3446
|
+
return this.#lastSkip;
|
|
3447
|
+
}
|
|
3448
|
+
recordFilterAllowed() {
|
|
3449
|
+
this.#ledger.filter.allowed += 1;
|
|
3450
|
+
this.#ledger.filter.screened += 1;
|
|
3451
|
+
}
|
|
3452
|
+
recordFilterSkipped(repeat, normalizedQuestion, turn) {
|
|
3453
|
+
this.#ledger.filter.skipped += 1;
|
|
3454
|
+
this.#ledger.filter.screened += 1;
|
|
3455
|
+
if (repeat) {
|
|
3456
|
+
this.#ledger.filter.repeatSkipped += 1;
|
|
3457
|
+
}
|
|
3458
|
+
this.#lastSkip = {
|
|
3459
|
+
...normalizedQuestion ? { normalizedQuestion } : {},
|
|
3460
|
+
turn
|
|
3461
|
+
};
|
|
3462
|
+
}
|
|
3463
|
+
recordFilterOverride() {
|
|
3464
|
+
this.#ledger.filter.overrides += 1;
|
|
3465
|
+
}
|
|
3466
|
+
recordFilterFailure() {
|
|
3467
|
+
this.#ledger.filter.failures += 1;
|
|
3468
|
+
}
|
|
3469
|
+
recordFilterUsage(usage) {
|
|
3470
|
+
addJevUsage(this.#ledger.usage, usage);
|
|
3471
|
+
}
|
|
3472
|
+
recordGateCheck(usage) {
|
|
3473
|
+
this.#ledger.gate.checks += 1;
|
|
3474
|
+
if (usage) {
|
|
3475
|
+
addJevUsage(this.#ledger.gate.usage, usage);
|
|
3476
|
+
}
|
|
3477
|
+
}
|
|
3478
|
+
recordGateConsultation() {
|
|
3479
|
+
this.#ledger.gate.consultations += 1;
|
|
3480
|
+
}
|
|
3481
|
+
recordGateFailure() {
|
|
3482
|
+
this.#ledger.gate.failures += 1;
|
|
3483
|
+
}
|
|
3484
|
+
summaryLines(invocations) {
|
|
3485
|
+
const lines = [];
|
|
3486
|
+
const { filter, gate, usage } = this.#ledger;
|
|
3487
|
+
const nonRepeatJevActivity = filter.allowed + (filter.skipped - filter.repeatSkipped) + filter.failures;
|
|
3488
|
+
if (nonRepeatJevActivity === 0 && filter.repeatSkipped > 0) {
|
|
3489
|
+
const parts = [
|
|
3490
|
+
`${filter.repeatSkipped} repeat question${filter.repeatSkipped === 1 ? "" : "s"} skipped, earlier advice reattached`
|
|
3491
|
+
];
|
|
3492
|
+
if (filter.overrides > 0) {
|
|
3493
|
+
parts.push(`${filter.overrides} override${filter.overrides === 1 ? "" : "s"}`);
|
|
3494
|
+
}
|
|
3495
|
+
lines.push(`Consultation dedup: ${parts.join(", ")}`);
|
|
3496
|
+
lines.push(this.#savingsLine(this.#markdownCosts(invocations), filter.skipped));
|
|
3497
|
+
} else if (this.#filterActive()) {
|
|
3498
|
+
lines.push(this.#filterLine(filter));
|
|
3499
|
+
const jevTokens = usage.inputTokens + usage.outputTokens;
|
|
3500
|
+
if (jevTokens > 0) {
|
|
3501
|
+
lines.push(`Jev cost: ${this.#formatJevTokens(usage)} tokens · $${usage.cost.toFixed(4)} (input only; output free)`);
|
|
3502
|
+
}
|
|
3503
|
+
if (filter.skipped > 0) {
|
|
3504
|
+
lines.push(this.#savingsLine(this.#markdownCosts(invocations), filter.skipped));
|
|
3505
|
+
}
|
|
3506
|
+
}
|
|
3507
|
+
if (gate.checks > 0 || gate.consultations > 0) {
|
|
3508
|
+
lines.push(this.#gateLine(gate, invocations));
|
|
3509
|
+
}
|
|
3510
|
+
return lines;
|
|
3511
|
+
}
|
|
3512
|
+
#filterActive() {
|
|
3513
|
+
const { filter } = this.#ledger;
|
|
3514
|
+
return filter.screened > 0 || filter.overrides > 0 || filter.failures > 0;
|
|
3515
|
+
}
|
|
3516
|
+
#savingsLine(markdownCosts, skipped) {
|
|
3517
|
+
if (markdownCosts.length === 0) {
|
|
3518
|
+
return "Estimated saving from skips: unavailable — no observed consultation cost this session";
|
|
3519
|
+
}
|
|
3520
|
+
const mean = markdownCosts.reduce((sum, cost) => sum + cost, 0) / markdownCosts.length;
|
|
3521
|
+
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`;
|
|
3522
|
+
}
|
|
3523
|
+
#gateLine(gate, invocations) {
|
|
3524
|
+
const consultationCosts = invocations.filter((item) => item.trigger === "turn-gate" && typeof item.cost === "number").map((item) => item.cost);
|
|
3525
|
+
const gateSpend = consultationCosts.reduce((sum, cost) => sum + cost, 0);
|
|
3526
|
+
return `Turn gate: ${gate.checks} check${gate.checks === 1 ? "" : "s"} (Jev ${this.#formatJevTokens(gate.usage)} · $${gate.usage.cost.toFixed(4)}), ${gate.consultations} consultation${gate.consultations === 1 ? "" : "s"} ($${gateSpend.toFixed(4)})`;
|
|
3527
|
+
}
|
|
3528
|
+
#formatJevTokens(usage) {
|
|
3529
|
+
return `↑${formatTokenCount(usage.inputTokens + usage.outputTokens)}`;
|
|
3530
|
+
}
|
|
3531
|
+
#markdownCosts(invocations) {
|
|
3532
|
+
return invocations.filter((item) => item.kind === "markdown" && typeof item.cost === "number").map((item) => item.cost);
|
|
3533
|
+
}
|
|
3534
|
+
#filterLine(filter) {
|
|
3535
|
+
const head = `${filter.screened} screened (${filter.allowed} allowed, ${filter.skipped} skipped${filter.repeatSkipped > 0 ? ` [${filter.repeatSkipped} repeat]` : ""})`;
|
|
3536
|
+
const parts = [head];
|
|
3537
|
+
if (filter.overrides > 0) {
|
|
3538
|
+
parts.push(`${filter.overrides} override${filter.overrides === 1 ? "" : "s"}`);
|
|
3539
|
+
}
|
|
3540
|
+
if (filter.failures > 0) {
|
|
3541
|
+
parts.push(`${filter.failures} failure${filter.failures === 1 ? "" : "s"}`);
|
|
3542
|
+
}
|
|
3543
|
+
return `Jev filter: ${parts.join(", ")}`;
|
|
3544
|
+
}
|
|
3545
|
+
}
|
|
3546
|
+
|
|
3239
3547
|
// src/session-state.ts
|
|
3240
3548
|
var WHITESPACE = /\s/;
|
|
3241
3549
|
var TIMESTAMP_KEYS = new Set([
|
|
@@ -3327,12 +3635,18 @@ class AdvisorSessionState {
|
|
|
3327
3635
|
#ledger = freshAdviceLedger();
|
|
3328
3636
|
#usage = freshUsage();
|
|
3329
3637
|
#consumedCalls = 0;
|
|
3638
|
+
#jev = new AdvisorJevLedgerState;
|
|
3639
|
+
#sessionTurnOrdinal = 0;
|
|
3640
|
+
#turnsSinceConsultation = 0;
|
|
3330
3641
|
resetTask() {
|
|
3331
3642
|
this.#repetition = freshRepetition();
|
|
3332
3643
|
this.#blockedReason = undefined;
|
|
3333
3644
|
this.#ledger = freshAdviceLedger();
|
|
3334
3645
|
this.#usage = freshUsage();
|
|
3335
3646
|
this.#consumedCalls = 0;
|
|
3647
|
+
this.#jev.reset();
|
|
3648
|
+
this.#sessionTurnOrdinal = 0;
|
|
3649
|
+
this.#turnsSinceConsultation = 0;
|
|
3336
3650
|
}
|
|
3337
3651
|
clearBlocked() {
|
|
3338
3652
|
this.#blockedReason = undefined;
|
|
@@ -3375,6 +3689,19 @@ class AdvisorSessionState {
|
|
|
3375
3689
|
get consumedCalls() {
|
|
3376
3690
|
return this.#consumedCalls;
|
|
3377
3691
|
}
|
|
3692
|
+
recordCompletedTurn() {
|
|
3693
|
+
this.#sessionTurnOrdinal += 1;
|
|
3694
|
+
this.#turnsSinceConsultation += 1;
|
|
3695
|
+
}
|
|
3696
|
+
resetTurnsSinceConsultation() {
|
|
3697
|
+
this.#turnsSinceConsultation = 0;
|
|
3698
|
+
}
|
|
3699
|
+
get sessionTurnOrdinal() {
|
|
3700
|
+
return this.#sessionTurnOrdinal;
|
|
3701
|
+
}
|
|
3702
|
+
get turnsSinceConsultation() {
|
|
3703
|
+
return this.#turnsSinceConsultation;
|
|
3704
|
+
}
|
|
3378
3705
|
get usageTotals() {
|
|
3379
3706
|
return { ...this.#usage.totals };
|
|
3380
3707
|
}
|
|
@@ -3385,13 +3712,28 @@ class AdvisorSessionState {
|
|
|
3385
3712
|
this.#usage.invocations.push(record);
|
|
3386
3713
|
addAdvisorUsage(this.#usage.totals, record.usage);
|
|
3387
3714
|
}
|
|
3388
|
-
issueAdvice(id, advice, trigger, draft = false) {
|
|
3389
|
-
this.#ledger.issued.set(id, {
|
|
3715
|
+
issueAdvice(id, advice, trigger, draft = false, normalizedQuestion) {
|
|
3716
|
+
this.#ledger.issued.set(id, {
|
|
3717
|
+
advice,
|
|
3718
|
+
...normalizedQuestion ? { normalizedQuestion } : {},
|
|
3719
|
+
trigger
|
|
3720
|
+
});
|
|
3390
3721
|
this.#ledger.lastAdvice = advice;
|
|
3391
3722
|
if (draft) {
|
|
3392
3723
|
this.#ledger.draftConsultations += 1;
|
|
3393
3724
|
}
|
|
3394
3725
|
}
|
|
3726
|
+
reattachedAdviceFor(normalizedQuestion) {
|
|
3727
|
+
if (!normalizedQuestion) {
|
|
3728
|
+
return;
|
|
3729
|
+
}
|
|
3730
|
+
for (const entry of this.#ledger.issued.values()) {
|
|
3731
|
+
if (entry.normalizedQuestion === normalizedQuestion) {
|
|
3732
|
+
return entry.advice;
|
|
3733
|
+
}
|
|
3734
|
+
}
|
|
3735
|
+
return;
|
|
3736
|
+
}
|
|
3395
3737
|
claimTrackedFiles(paths) {
|
|
3396
3738
|
const advice = this.#ledger.lastAdvice;
|
|
3397
3739
|
if (!advice || paths.length === 0) {
|
|
@@ -3452,14 +3794,43 @@ class AdvisorSessionState {
|
|
|
3452
3794
|
return [
|
|
3453
3795
|
"manual",
|
|
3454
3796
|
"executor-requested",
|
|
3797
|
+
"turn-gate",
|
|
3455
3798
|
"repeated-tool-call",
|
|
3456
3799
|
"completion-review",
|
|
3457
3800
|
"custom-rule"
|
|
3458
3801
|
].filter((trigger) => this.#countTrigger(trigger) > 0).join(", ") || "none";
|
|
3459
3802
|
}
|
|
3803
|
+
recordJevFilterAllowed() {
|
|
3804
|
+
this.#jev.recordFilterAllowed();
|
|
3805
|
+
}
|
|
3806
|
+
recordJevFilterSkipped(repeat, normalizedQuestion) {
|
|
3807
|
+
this.#jev.recordFilterSkipped(repeat, normalizedQuestion, this.#sessionTurnOrdinal);
|
|
3808
|
+
}
|
|
3809
|
+
get lastJevSkip() {
|
|
3810
|
+
return this.#jev.lastSkip;
|
|
3811
|
+
}
|
|
3812
|
+
recordJevFilterOverride() {
|
|
3813
|
+
this.#jev.recordFilterOverride();
|
|
3814
|
+
}
|
|
3815
|
+
recordJevFilterFailure() {
|
|
3816
|
+
this.#jev.recordFilterFailure();
|
|
3817
|
+
}
|
|
3818
|
+
recordJevFilterUsage(usage) {
|
|
3819
|
+
this.#jev.recordFilterUsage(usage);
|
|
3820
|
+
}
|
|
3821
|
+
recordJevGateCheck(usage) {
|
|
3822
|
+
this.#jev.recordGateCheck(usage);
|
|
3823
|
+
}
|
|
3824
|
+
recordJevGateConsultation() {
|
|
3825
|
+
this.#jev.recordGateConsultation();
|
|
3826
|
+
}
|
|
3827
|
+
recordJevGateFailure() {
|
|
3828
|
+
this.#jev.recordGateFailure();
|
|
3829
|
+
}
|
|
3460
3830
|
summary(limit) {
|
|
3461
3831
|
const { invocations, totals } = this.#usage;
|
|
3462
|
-
|
|
3832
|
+
const jevLines = this.#jev.summaryLines(invocations);
|
|
3833
|
+
if (invocations.length === 0 && this.#repetition.interventions === 0 && jevLines.length === 0) {
|
|
3463
3834
|
return;
|
|
3464
3835
|
}
|
|
3465
3836
|
const markdown = invocations.filter((item) => item.kind === "markdown");
|
|
@@ -3480,7 +3851,8 @@ class AdvisorSessionState {
|
|
|
3480
3851
|
`Gate decisions: ${this.#decisionsLine()}`,
|
|
3481
3852
|
`Loop matching: normalized tool signatures; ${this.#repetition.interventions} gate intervention${this.#repetition.interventions === 1 ? "" : "s"}`,
|
|
3482
3853
|
`Execution effects: ${effects("tool-blocked")} tool blocked, ${effects("session-blocked")} sessions blocked, ${effects("continued")} continued`,
|
|
3483
|
-
`Failures: ${failures.length ? failures.join(", ") : "none"}
|
|
3854
|
+
`Failures: ${failures.length ? failures.join(", ") : "none"}`,
|
|
3855
|
+
...jevLines
|
|
3484
3856
|
].join(`
|
|
3485
3857
|
`);
|
|
3486
3858
|
}
|
|
@@ -4040,138 +4412,770 @@ class ManualAdvisorDialog {
|
|
|
4040
4412
|
}
|
|
4041
4413
|
}
|
|
4042
4414
|
|
|
4043
|
-
// src/
|
|
4044
|
-
|
|
4045
|
-
|
|
4046
|
-
|
|
4047
|
-
|
|
4048
|
-
|
|
4049
|
-
|
|
4050
|
-
|
|
4051
|
-
|
|
4052
|
-
|
|
4415
|
+
// src/jev/client.ts
|
|
4416
|
+
class JevFailure extends Error {
|
|
4417
|
+
category;
|
|
4418
|
+
constructor(category, message) {
|
|
4419
|
+
super(message);
|
|
4420
|
+
this.name = "JevFailure";
|
|
4421
|
+
this.category = category;
|
|
4422
|
+
}
|
|
4423
|
+
}
|
|
4424
|
+
var ENDPOINTS = {
|
|
4425
|
+
openrouter: "https://openrouter.ai/api/alpha/decisions",
|
|
4426
|
+
typesafe: "https://api.typesafe.ai/v1/systemone"
|
|
4427
|
+
};
|
|
4428
|
+
var RETRYABLE_STATUSES = new Set([408, 429, ...range(500, 599)]);
|
|
4429
|
+
var RETRY_BACKOFF_MS = 250;
|
|
4430
|
+
var MAX_ATTEMPTS = 2;
|
|
4431
|
+
function range(from, to) {
|
|
4432
|
+
return Array.from({ length: to - from + 1 }, (_, index) => from + index);
|
|
4433
|
+
}
|
|
4434
|
+
var finiteTokens = (value) => typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : 0;
|
|
4435
|
+
var errorDetail = (error) => {
|
|
4436
|
+
if (typeof error === "string") {
|
|
4437
|
+
return error;
|
|
4438
|
+
}
|
|
4439
|
+
if (error && typeof error === "object" && "message" in error) {
|
|
4440
|
+
return String(error.message);
|
|
4441
|
+
}
|
|
4442
|
+
return "";
|
|
4443
|
+
};
|
|
4444
|
+
var statusCategory = (status) => {
|
|
4445
|
+
if (status === 401 || status === 403) {
|
|
4446
|
+
return "auth";
|
|
4447
|
+
}
|
|
4448
|
+
if (status === 400 || status === 422) {
|
|
4449
|
+
return "malformed";
|
|
4450
|
+
}
|
|
4451
|
+
return "error";
|
|
4452
|
+
};
|
|
4453
|
+
var openRouterModelId = (model) => model.includes("/") ? model : `~typesafe/${model}`;
|
|
4454
|
+
|
|
4455
|
+
class JevClient {
|
|
4456
|
+
#apiKey;
|
|
4457
|
+
#endpoint;
|
|
4458
|
+
#fetch;
|
|
4459
|
+
#model;
|
|
4460
|
+
#pricePerMtok;
|
|
4461
|
+
#timeoutMs;
|
|
4462
|
+
constructor({
|
|
4463
|
+
apiKey,
|
|
4464
|
+
fetch,
|
|
4465
|
+
model,
|
|
4466
|
+
pricePerMtok,
|
|
4467
|
+
timeoutMs,
|
|
4468
|
+
transport
|
|
4469
|
+
}) {
|
|
4470
|
+
this.#apiKey = apiKey;
|
|
4471
|
+
this.#endpoint = ENDPOINTS[transport];
|
|
4472
|
+
this.#fetch = fetch ?? globalThis.fetch.bind(globalThis);
|
|
4473
|
+
this.#model = transport === "openrouter" ? openRouterModelId(model) : model;
|
|
4474
|
+
this.#pricePerMtok = pricePerMtok;
|
|
4475
|
+
this.#timeoutMs = timeoutMs;
|
|
4476
|
+
}
|
|
4477
|
+
async ask(state, questions, signal) {
|
|
4478
|
+
const deadline = new AbortController;
|
|
4479
|
+
const abortFromCaller = () => deadline.abort(signal?.reason);
|
|
4480
|
+
signal?.addEventListener("abort", abortFromCaller, { once: true });
|
|
4481
|
+
if (signal?.aborted) {
|
|
4482
|
+
abortFromCaller();
|
|
4483
|
+
}
|
|
4484
|
+
let deadlineHit = false;
|
|
4485
|
+
const timer = setTimeout(() => {
|
|
4486
|
+
deadlineHit = true;
|
|
4487
|
+
deadline.abort(new Error("Jev wall-time budget elapsed"));
|
|
4488
|
+
}, this.#timeoutMs);
|
|
4489
|
+
timer.unref?.();
|
|
4490
|
+
const body = JSON.stringify({
|
|
4491
|
+
model: this.#model,
|
|
4492
|
+
questions,
|
|
4493
|
+
state
|
|
4494
|
+
});
|
|
4495
|
+
try {
|
|
4496
|
+
let outcome = {};
|
|
4497
|
+
for (let attempt = 1;; attempt += 1) {
|
|
4498
|
+
outcome = await this.#attempt(body, deadline.signal);
|
|
4499
|
+
if (!outcome.retryable || attempt >= MAX_ATTEMPTS) {
|
|
4500
|
+
break;
|
|
4501
|
+
}
|
|
4502
|
+
await this.#backoff(deadline.signal);
|
|
4503
|
+
if (deadline.signal.aborted) {
|
|
4504
|
+
break;
|
|
4505
|
+
}
|
|
4053
4506
|
}
|
|
4054
|
-
|
|
4055
|
-
}
|
|
4056
|
-
|
|
4507
|
+
return this.#settle(outcome, deadlineHit, signal);
|
|
4508
|
+
} catch (error) {
|
|
4509
|
+
if (signal?.aborted && !deadlineHit) {
|
|
4510
|
+
throw error;
|
|
4511
|
+
}
|
|
4512
|
+
if (error instanceof JevFailure) {
|
|
4513
|
+
throw error;
|
|
4514
|
+
}
|
|
4515
|
+
const message = redactSecrets(error instanceof Error ? error.message : String(error));
|
|
4516
|
+
throw deadlineHit ? new JevFailure("timeout", `Jev call exceeded its ${this.#timeoutMs} ms wall-time budget.`) : new JevFailure("error", message);
|
|
4517
|
+
} finally {
|
|
4518
|
+
clearTimeout(timer);
|
|
4519
|
+
signal?.removeEventListener("abort", abortFromCaller);
|
|
4520
|
+
}
|
|
4057
4521
|
}
|
|
4058
|
-
|
|
4059
|
-
|
|
4060
|
-
|
|
4061
|
-
return;
|
|
4522
|
+
#settle(outcome, deadlineHit, signal) {
|
|
4523
|
+
if (outcome.answers) {
|
|
4524
|
+
return this.#result(outcome);
|
|
4062
4525
|
}
|
|
4063
|
-
|
|
4064
|
-
|
|
4065
|
-
progress.text = text;
|
|
4066
|
-
runtime.requestManualRender(ctx);
|
|
4067
|
-
}, (event) => {
|
|
4068
|
-
if (!controller.signal.aborted) {
|
|
4069
|
-
runtime.scoutStatus.update(ctx, scoutStatusToken, event);
|
|
4070
|
-
scoutDetails = appendScoutLifecycleEntry(runtime.pi, event, scoutDetails);
|
|
4071
|
-
progress.scout = scoutDetails;
|
|
4072
|
-
progress.phase = "active";
|
|
4073
|
-
runtime.requestManualRender(ctx);
|
|
4526
|
+
if (deadlineHit && outcome.failure?.category !== "auth") {
|
|
4527
|
+
throw new JevFailure("timeout", `Jev call exceeded its ${this.#timeoutMs} ms wall-time budget.`);
|
|
4074
4528
|
}
|
|
4075
|
-
|
|
4076
|
-
|
|
4077
|
-
return;
|
|
4529
|
+
if (outcome.failure) {
|
|
4530
|
+
throw outcome.failure;
|
|
4078
4531
|
}
|
|
4079
|
-
|
|
4080
|
-
|
|
4081
|
-
|
|
4082
|
-
|
|
4083
|
-
|
|
4084
|
-
|
|
4085
|
-
|
|
4086
|
-
usage
|
|
4087
|
-
});
|
|
4088
|
-
runtime.updateAdvisorUsageStatus(ctx);
|
|
4089
|
-
const normalizedUsage = snapshotAdvisorUsage(usage);
|
|
4090
|
-
runtime.pi.sendMessage({
|
|
4091
|
-
content: `Manual Advisor consultation${question ? ` (${question})` : ""}:
|
|
4092
|
-
|
|
4093
|
-
${markdown}`,
|
|
4094
|
-
customType: "advisor-manual-result",
|
|
4095
|
-
details: {
|
|
4096
|
-
advisor: advisorRef,
|
|
4097
|
-
question,
|
|
4098
|
-
text: markdown,
|
|
4099
|
-
...normalizedUsage ? { usage: normalizedUsage } : {}
|
|
4100
|
-
},
|
|
4101
|
-
display: true
|
|
4102
|
-
}, {
|
|
4103
|
-
deliverAs: "steer",
|
|
4104
|
-
triggerTurn: true
|
|
4105
|
-
});
|
|
4106
|
-
}).catch((error) => {
|
|
4107
|
-
if (controller.signal.aborted) {
|
|
4532
|
+
if (signal?.aborted) {
|
|
4533
|
+
throw new Error("Jev call aborted by the caller.");
|
|
4534
|
+
}
|
|
4535
|
+
throw new JevFailure("error", "Jev call failed.");
|
|
4536
|
+
}
|
|
4537
|
+
async#backoff(signal) {
|
|
4538
|
+
if (signal.aborted) {
|
|
4108
4539
|
return;
|
|
4109
4540
|
}
|
|
4110
|
-
|
|
4111
|
-
|
|
4112
|
-
|
|
4113
|
-
|
|
4114
|
-
|
|
4115
|
-
|
|
4116
|
-
|
|
4117
|
-
|
|
4541
|
+
await new Promise((resolve) => {
|
|
4542
|
+
const timer = setTimeout(resolve, RETRY_BACKOFF_MS);
|
|
4543
|
+
timer.unref?.();
|
|
4544
|
+
const onAbort = () => {
|
|
4545
|
+
clearTimeout(timer);
|
|
4546
|
+
resolve();
|
|
4547
|
+
};
|
|
4548
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
4118
4549
|
});
|
|
4119
|
-
|
|
4120
|
-
|
|
4121
|
-
|
|
4122
|
-
|
|
4123
|
-
details: {
|
|
4124
|
-
advisor: advisorRef,
|
|
4125
|
-
text: `**Advisor consultation failed:** ${message}`
|
|
4126
|
-
},
|
|
4127
|
-
display: true
|
|
4128
|
-
}, { deliverAs: "steer", triggerTurn: true });
|
|
4129
|
-
notify(ctx, `Advisor consultation failed: ${message}`, "error");
|
|
4130
|
-
notifyHerdrAdvisorFailure("Advisor consultation failed", message);
|
|
4131
|
-
}).finally(() => {
|
|
4132
|
-
if (controller.signal.aborted) {
|
|
4133
|
-
progress.phase = "cancelled";
|
|
4134
|
-
}
|
|
4135
|
-
const timer = runtime.manualProgressTimers.get(controller);
|
|
4136
|
-
if (timer) {
|
|
4137
|
-
clearInterval(timer);
|
|
4138
|
-
runtime.manualProgressTimers.delete(controller);
|
|
4550
|
+
}
|
|
4551
|
+
async#attempt(body, signal) {
|
|
4552
|
+
if (signal.aborted) {
|
|
4553
|
+
return { retryable: false };
|
|
4139
4554
|
}
|
|
4140
|
-
|
|
4141
|
-
|
|
4142
|
-
|
|
4143
|
-
|
|
4144
|
-
|
|
4145
|
-
}
|
|
4146
|
-
|
|
4147
|
-
|
|
4148
|
-
|
|
4149
|
-
|
|
4150
|
-
|
|
4151
|
-
|
|
4152
|
-
if (
|
|
4153
|
-
return;
|
|
4154
|
-
}
|
|
4155
|
-
if (!(isSimpleMode() || runtime.advisorSessionState.canConsult(getAdvisorMaxCallsPerSession()))) {
|
|
4156
|
-
runtime.reportManualBudgetExhausted(ctx);
|
|
4157
|
-
return;
|
|
4555
|
+
let response;
|
|
4556
|
+
try {
|
|
4557
|
+
response = await this.#fetch(this.#endpoint, {
|
|
4558
|
+
body,
|
|
4559
|
+
headers: {
|
|
4560
|
+
authorization: `Bearer ${this.#apiKey}`,
|
|
4561
|
+
"content-type": "application/json"
|
|
4562
|
+
},
|
|
4563
|
+
method: "POST",
|
|
4564
|
+
signal
|
|
4565
|
+
});
|
|
4566
|
+
} catch (error) {
|
|
4567
|
+
if (signal.aborted) {
|
|
4568
|
+
return { retryable: false };
|
|
4158
4569
|
}
|
|
4159
|
-
|
|
4160
|
-
|
|
4161
|
-
|
|
4162
|
-
|
|
4163
|
-
|
|
4164
|
-
|
|
4165
|
-
|
|
4166
|
-
|
|
4167
|
-
|
|
4168
|
-
|
|
4169
|
-
|
|
4170
|
-
|
|
4171
|
-
|
|
4172
|
-
|
|
4173
|
-
|
|
4174
|
-
|
|
4570
|
+
return {
|
|
4571
|
+
retryable: true,
|
|
4572
|
+
...this.#connectionFailure(error)
|
|
4573
|
+
};
|
|
4574
|
+
}
|
|
4575
|
+
if (response.ok) {
|
|
4576
|
+
return this.#parseSuccess(response);
|
|
4577
|
+
}
|
|
4578
|
+
const failure = await this.#failureFromStatus(response);
|
|
4579
|
+
return {
|
|
4580
|
+
failure,
|
|
4581
|
+
retryable: RETRYABLE_STATUSES.has(response.status)
|
|
4582
|
+
};
|
|
4583
|
+
}
|
|
4584
|
+
#connectionFailure(error) {
|
|
4585
|
+
const message = redactSecrets(error instanceof Error ? error.message : String(error));
|
|
4586
|
+
return {
|
|
4587
|
+
failure: new JevFailure("network", `Jev connection failed: ${message}`)
|
|
4588
|
+
};
|
|
4589
|
+
}
|
|
4590
|
+
async#failureFromStatus(response) {
|
|
4591
|
+
let detail = "";
|
|
4592
|
+
try {
|
|
4593
|
+
const parsed = await response.json();
|
|
4594
|
+
const error = parsed?.error;
|
|
4595
|
+
detail = errorDetail(error);
|
|
4596
|
+
} catch {
|
|
4597
|
+
detail = "";
|
|
4598
|
+
}
|
|
4599
|
+
const message = redactSecrets(`Jev ${this.#transportLabel()} request failed with HTTP ${response.status}${detail ? `: ${detail}` : ""}.`);
|
|
4600
|
+
return new JevFailure(statusCategory(response.status), message);
|
|
4601
|
+
}
|
|
4602
|
+
#transportLabel() {
|
|
4603
|
+
return this.#endpoint === ENDPOINTS.openrouter ? "OpenRouter" : "TypeSafe";
|
|
4604
|
+
}
|
|
4605
|
+
async#parseSuccess(response) {
|
|
4606
|
+
let parsed;
|
|
4607
|
+
try {
|
|
4608
|
+
parsed = await response.json();
|
|
4609
|
+
} catch (error) {
|
|
4610
|
+
return {
|
|
4611
|
+
failure: new JevFailure("malformed", `Jev response was not JSON: ${redactSecrets(error instanceof Error ? error.message : String(error))}`)
|
|
4612
|
+
};
|
|
4613
|
+
}
|
|
4614
|
+
const record = parsed;
|
|
4615
|
+
if (!record || typeof record !== "object" || !record.answers || typeof record.answers !== "object") {
|
|
4616
|
+
return {
|
|
4617
|
+
failure: new JevFailure("malformed", "Jev response did not include an answers object.")
|
|
4618
|
+
};
|
|
4619
|
+
}
|
|
4620
|
+
return {
|
|
4621
|
+
answers: record.answers,
|
|
4622
|
+
model: typeof record.model === "string" ? record.model : this.#model,
|
|
4623
|
+
usage: record.usage
|
|
4624
|
+
};
|
|
4625
|
+
}
|
|
4626
|
+
#result(outcome) {
|
|
4627
|
+
const usage = outcome.usage;
|
|
4628
|
+
const inputTokens = finiteTokens(usage?.input_tokens);
|
|
4629
|
+
const outputTokens = finiteTokens(usage?.output_tokens);
|
|
4630
|
+
const price = this.#pricePerMtok ?? advisorJevPricePerMtokRef;
|
|
4631
|
+
return {
|
|
4632
|
+
answers: outcome.answers ?? {},
|
|
4633
|
+
model: outcome.model ?? this.#model,
|
|
4634
|
+
usage: {
|
|
4635
|
+
cost: inputTokens / 1e6 * price,
|
|
4636
|
+
inputTokens,
|
|
4637
|
+
outputTokens
|
|
4638
|
+
}
|
|
4639
|
+
};
|
|
4640
|
+
}
|
|
4641
|
+
}
|
|
4642
|
+
var jevClientFromCredentials = (credentials, fetch) => new JevClient({
|
|
4643
|
+
apiKey: credentials.apiKey,
|
|
4644
|
+
fetch,
|
|
4645
|
+
model: advisorJevModelRef,
|
|
4646
|
+
timeoutMs: advisorJevTimeoutMsRef,
|
|
4647
|
+
transport: credentials.transport
|
|
4648
|
+
});
|
|
4649
|
+
|
|
4650
|
+
// src/jev/key-store.ts
|
|
4651
|
+
import {
|
|
4652
|
+
chmodSync,
|
|
4653
|
+
existsSync as existsSync3,
|
|
4654
|
+
readFileSync as readFileSync3,
|
|
4655
|
+
rmSync,
|
|
4656
|
+
writeFileSync as writeFileSync2
|
|
4657
|
+
} from "node:fs";
|
|
4658
|
+
import { join as join4 } from "node:path";
|
|
4659
|
+
import { getAgentDir as getAgentDir3 } from "@earendil-works/pi-coding-agent";
|
|
4660
|
+
var TYPESAFE_KEY_ENV_VAR = "TYPESAFE_API_KEY";
|
|
4661
|
+
var TYPESAFE_KEY_SERVICE = "pi-advisor";
|
|
4662
|
+
var TYPESAFE_KEY_NAME = "typesafe-api-key";
|
|
4663
|
+
var TYPESAFE_KEY_CONFIG_FIELD = "typesafe_api_key";
|
|
4664
|
+
var KEY_FILE_MODE = 384;
|
|
4665
|
+
var keyFilePath = () => join4(getAgentDir3(), "typesafe_api_key");
|
|
4666
|
+
var runtimeSecrets = () => globalThis.Bun?.secrets;
|
|
4667
|
+
var normalizeKey = (value) => value?.trim() || undefined;
|
|
4668
|
+
var readAdvisorJsonConfig = () => readExistingConfig(join4(getAgentDir3(), "advisor.json"));
|
|
4669
|
+
var defaultReadFileStore = () => {
|
|
4670
|
+
try {
|
|
4671
|
+
return normalizeKey(readFileSync3(keyFilePath(), "utf8"));
|
|
4672
|
+
} catch {
|
|
4673
|
+
return;
|
|
4674
|
+
}
|
|
4675
|
+
};
|
|
4676
|
+
var defaultWriteFileStore = (key) => {
|
|
4677
|
+
const path = keyFilePath();
|
|
4678
|
+
writeFileSync2(path, `${key}
|
|
4679
|
+
`, { mode: KEY_FILE_MODE });
|
|
4680
|
+
chmodSync(path, KEY_FILE_MODE);
|
|
4681
|
+
};
|
|
4682
|
+
var defaultDeleteFileStore = () => {
|
|
4683
|
+
rmSync(keyFilePath(), { force: true });
|
|
4684
|
+
};
|
|
4685
|
+
var messageOf = (error) => redactSecrets(error instanceof Error ? error.message : String(error));
|
|
4686
|
+
var resolveTypeSafeKey = async (deps = {}) => {
|
|
4687
|
+
const secrets = deps.secrets === undefined ? runtimeSecrets() : deps.secrets;
|
|
4688
|
+
if (secrets) {
|
|
4689
|
+
try {
|
|
4690
|
+
const stored = normalizeKey(await secrets.get({
|
|
4691
|
+
name: TYPESAFE_KEY_NAME,
|
|
4692
|
+
service: TYPESAFE_KEY_SERVICE
|
|
4693
|
+
}));
|
|
4694
|
+
if (stored) {
|
|
4695
|
+
return { key: stored, source: "bun-secrets" };
|
|
4696
|
+
}
|
|
4697
|
+
} catch {}
|
|
4698
|
+
}
|
|
4699
|
+
const env = deps.env ?? process.env;
|
|
4700
|
+
const fromEnv = normalizeKey(env[TYPESAFE_KEY_ENV_VAR]);
|
|
4701
|
+
if (fromEnv) {
|
|
4702
|
+
return { key: fromEnv, source: "env" };
|
|
4703
|
+
}
|
|
4704
|
+
const readFileStore = deps.readFileStore ?? defaultReadFileStore;
|
|
4705
|
+
const fromFile = normalizeKey(readFileStore());
|
|
4706
|
+
if (fromFile) {
|
|
4707
|
+
return { key: fromFile, source: "file" };
|
|
4708
|
+
}
|
|
4709
|
+
const config = (deps.readAdvisorJson ?? readAdvisorJsonConfig)();
|
|
4710
|
+
const staged = config[TYPESAFE_KEY_CONFIG_FIELD];
|
|
4711
|
+
if (typeof staged === "string") {
|
|
4712
|
+
const fromConfig = normalizeKey(staged);
|
|
4713
|
+
if (fromConfig) {
|
|
4714
|
+
return { key: fromConfig, source: "advisor-json" };
|
|
4715
|
+
}
|
|
4716
|
+
}
|
|
4717
|
+
return {};
|
|
4718
|
+
};
|
|
4719
|
+
var writeKeyTypeSafeKey = async (key, deps = {}) => {
|
|
4720
|
+
const normalized = normalizeKey(key);
|
|
4721
|
+
if (!normalized) {
|
|
4722
|
+
return { message: "The key is empty.", ok: false };
|
|
4723
|
+
}
|
|
4724
|
+
const secrets = deps.secrets === undefined ? runtimeSecrets() : deps.secrets;
|
|
4725
|
+
if (secrets) {
|
|
4726
|
+
try {
|
|
4727
|
+
await secrets.set({
|
|
4728
|
+
name: TYPESAFE_KEY_NAME,
|
|
4729
|
+
service: TYPESAFE_KEY_SERVICE,
|
|
4730
|
+
value: normalized
|
|
4731
|
+
});
|
|
4732
|
+
return { message: "Key stored in Bun.secrets.", ok: true };
|
|
4733
|
+
} catch (error) {
|
|
4734
|
+
return {
|
|
4735
|
+
message: `Storing the key in Bun.secrets failed: ${messageOf(error)}. Alternatively set the ${TYPESAFE_KEY_ENV_VAR} environment variable in your shell profile.`,
|
|
4736
|
+
ok: false
|
|
4737
|
+
};
|
|
4738
|
+
}
|
|
4739
|
+
}
|
|
4740
|
+
try {
|
|
4741
|
+
(deps.writeFileStore ?? defaultWriteFileStore)(normalized);
|
|
4742
|
+
return {
|
|
4743
|
+
message: "Key stored in ~/.pi/agent/typesafe_api_key (mode 0600).",
|
|
4744
|
+
ok: true
|
|
4745
|
+
};
|
|
4746
|
+
} catch (error) {
|
|
4747
|
+
return {
|
|
4748
|
+
message: `Storing the key failed: ${messageOf(error)}. Alternatively set the ${TYPESAFE_KEY_ENV_VAR} environment variable in your shell profile.`,
|
|
4749
|
+
ok: false
|
|
4750
|
+
};
|
|
4751
|
+
}
|
|
4752
|
+
};
|
|
4753
|
+
var clearKeyTypeSafeKey = async (deps = {}) => {
|
|
4754
|
+
let clearedSomething = false;
|
|
4755
|
+
let firstError;
|
|
4756
|
+
const secrets = deps.secrets === undefined ? runtimeSecrets() : deps.secrets;
|
|
4757
|
+
if (secrets) {
|
|
4758
|
+
try {
|
|
4759
|
+
await secrets.delete({
|
|
4760
|
+
name: TYPESAFE_KEY_NAME,
|
|
4761
|
+
service: TYPESAFE_KEY_SERVICE
|
|
4762
|
+
});
|
|
4763
|
+
clearedSomething = true;
|
|
4764
|
+
} catch (error) {
|
|
4765
|
+
firstError = messageOf(error);
|
|
4766
|
+
}
|
|
4767
|
+
}
|
|
4768
|
+
try {
|
|
4769
|
+
if (deps.deleteFileStore) {
|
|
4770
|
+
deps.deleteFileStore();
|
|
4771
|
+
} else if (existsSync3(keyFilePath())) {
|
|
4772
|
+
defaultDeleteFileStore();
|
|
4773
|
+
}
|
|
4774
|
+
clearedSomething = true;
|
|
4775
|
+
} catch (error) {
|
|
4776
|
+
firstError ??= messageOf(error);
|
|
4777
|
+
}
|
|
4778
|
+
if (firstError) {
|
|
4779
|
+
return {
|
|
4780
|
+
message: `Clearing the stored key failed: ${firstError}.`,
|
|
4781
|
+
ok: false
|
|
4782
|
+
};
|
|
4783
|
+
}
|
|
4784
|
+
return {
|
|
4785
|
+
message: `Stored key cleared.${clearedSomething ? "" : ` Nothing was stored; unset ${TYPESAFE_KEY_ENV_VAR} and remove ${TYPESAFE_KEY_CONFIG_FIELD} from advisor.json yourself if you use them.`}`,
|
|
4786
|
+
ok: true
|
|
4787
|
+
};
|
|
4788
|
+
};
|
|
4789
|
+
var removeTypeSafeKeyFromAdvisorJson = () => {
|
|
4790
|
+
try {
|
|
4791
|
+
const path = join4(getAgentDir3(), "advisor.json");
|
|
4792
|
+
const existing = readExistingConfig(path);
|
|
4793
|
+
if (!(TYPESAFE_KEY_CONFIG_FIELD in existing)) {
|
|
4794
|
+
return { message: "No plaintext key in advisor.json.", ok: true };
|
|
4795
|
+
}
|
|
4796
|
+
delete existing[TYPESAFE_KEY_CONFIG_FIELD];
|
|
4797
|
+
writeFileSync2(path, `${JSON.stringify(existing, null, 2)}
|
|
4798
|
+
`);
|
|
4799
|
+
resetConfigCache();
|
|
4800
|
+
return { message: "Plaintext key removed from advisor.json.", ok: true };
|
|
4801
|
+
} catch (error) {
|
|
4802
|
+
return {
|
|
4803
|
+
message: `Removing the plaintext key failed: ${messageOf(error)}.`,
|
|
4804
|
+
ok: false
|
|
4805
|
+
};
|
|
4806
|
+
}
|
|
4807
|
+
};
|
|
4808
|
+
var warnedPlaintextKey = false;
|
|
4809
|
+
var consumePlaintextKeyWarning = () => {
|
|
4810
|
+
if (warnedPlaintextKey) {
|
|
4811
|
+
return;
|
|
4812
|
+
}
|
|
4813
|
+
warnedPlaintextKey = true;
|
|
4814
|
+
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.`;
|
|
4815
|
+
};
|
|
4816
|
+
|
|
4817
|
+
// src/jev/questions.ts
|
|
4818
|
+
var STAKES_RUBRIC = [
|
|
4819
|
+
"Negligible: routine, low-risk, mechanical, or reversible; a wrong call costs little and is easy to undo.",
|
|
4820
|
+
"Moderate: some risk or rework, but bounded and recoverable.",
|
|
4821
|
+
"High: material consequences for correctness, security, cost, user trust, or irreversibility."
|
|
4822
|
+
];
|
|
4823
|
+
var EVIDENCE_RULE = "Judge from `executor_question` and `executor_draft` when present, otherwise from `recent_conversation`; when both are absent, `recent_conversation` is the evidence to judge from.";
|
|
4824
|
+
var screeningQuestions = {
|
|
4825
|
+
self_answerable: {
|
|
4826
|
+
criteria: {
|
|
4827
|
+
false: "The executor needs the Advisor's second opinion.",
|
|
4828
|
+
true: "The executor can resolve this alone with available tools and context."
|
|
4829
|
+
},
|
|
4830
|
+
instructions: `Can the executor confidently resolve this request alone, using available tools and context? ${EVIDENCE_RULE}`,
|
|
4831
|
+
type: "noul"
|
|
4832
|
+
},
|
|
4833
|
+
stakes: {
|
|
4834
|
+
criteria: [...STAKES_RUBRIC],
|
|
4835
|
+
instructions: `How material are the stakes of the decision behind this consultation request? ${EVIDENCE_RULE}`,
|
|
4836
|
+
type: "score"
|
|
4837
|
+
}
|
|
4838
|
+
};
|
|
4839
|
+
var NUMERIC_KEY_PATTERN = /^\d+$/;
|
|
4840
|
+
var isRecord2 = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
4841
|
+
var finiteNumber = (value) => typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
4842
|
+
var lowestStakesProbability = (answer) => {
|
|
4843
|
+
if (!isRecord2(answer)) {
|
|
4844
|
+
return;
|
|
4845
|
+
}
|
|
4846
|
+
const probabilities = isRecord2(answer.probabilities) ? answer.probabilities : {};
|
|
4847
|
+
const legend = isRecord2(answer.legend) ? answer.legend : undefined;
|
|
4848
|
+
if (legend) {
|
|
4849
|
+
const exact = Object.keys(legend).find((key) => legend[key] === STAKES_RUBRIC[0]);
|
|
4850
|
+
if (exact) {
|
|
4851
|
+
return finiteNumber(probabilities[exact]);
|
|
4852
|
+
}
|
|
4853
|
+
}
|
|
4854
|
+
const numericKeys = Object.keys(probabilities).filter((key) => NUMERIC_KEY_PATTERN.test(key));
|
|
4855
|
+
if (numericKeys.length === 0) {
|
|
4856
|
+
return;
|
|
4857
|
+
}
|
|
4858
|
+
const lowest = numericKeys.reduce((left, right) => Number(left) <= Number(right) ? left : right);
|
|
4859
|
+
return finiteNumber(probabilities[lowest]);
|
|
4860
|
+
};
|
|
4861
|
+
var selfAnswerableNoul = (answer) => isRecord2(answer) ? finiteNumber(answer.noul) : undefined;
|
|
4862
|
+
var composeScreeningVerdict = (answers, { noulMargin, skipConfidence }) => {
|
|
4863
|
+
if (!isRecord2(answers)) {
|
|
4864
|
+
return { skip: false };
|
|
4865
|
+
}
|
|
4866
|
+
const negligibleMass = lowestStakesProbability(answers.stakes);
|
|
4867
|
+
const noul = selfAnswerableNoul(answers.self_answerable);
|
|
4868
|
+
if (negligibleMass === undefined || noul === undefined) {
|
|
4869
|
+
return { skip: false };
|
|
4870
|
+
}
|
|
4871
|
+
const confidentlySelfAnswerable = noul >= 0.5 + noulMargin;
|
|
4872
|
+
return {
|
|
4873
|
+
skip: negligibleMass >= skipConfidence && confidentlySelfAnswerable
|
|
4874
|
+
};
|
|
4875
|
+
};
|
|
4876
|
+
var composeTurnGateVerdict = (answers, threshold) => {
|
|
4877
|
+
if (!isRecord2(answers)) {
|
|
4878
|
+
return false;
|
|
4879
|
+
}
|
|
4880
|
+
const answer = answers.should_consult;
|
|
4881
|
+
const noul = isRecord2(answer) ? finiteNumber(answer.noul) : undefined;
|
|
4882
|
+
return noul !== undefined && noul >= threshold;
|
|
4883
|
+
};
|
|
4884
|
+
|
|
4885
|
+
// src/jev/state.ts
|
|
4886
|
+
var JEV_TEXT_CAP_BYTES = 8 * 1024;
|
|
4887
|
+
var buildJevState = (ctx, input = {}) => {
|
|
4888
|
+
const state = { role: "executor" };
|
|
4889
|
+
if (input.question) {
|
|
4890
|
+
state.executor_question = redactAndCapText(input.question, JEV_TEXT_CAP_BYTES, advisorRedactSecretsRef);
|
|
4891
|
+
}
|
|
4892
|
+
if (input.draft) {
|
|
4893
|
+
state.executor_draft = redactAndCapText(input.draft, JEV_TEXT_CAP_BYTES, advisorRedactSecretsRef);
|
|
4894
|
+
}
|
|
4895
|
+
const digest = recentConversation(ctx, advisorJevDigestMaxCharsRef);
|
|
4896
|
+
if (digest) {
|
|
4897
|
+
state.recent_conversation = digest;
|
|
4898
|
+
}
|
|
4899
|
+
return state;
|
|
4900
|
+
};
|
|
4901
|
+
|
|
4902
|
+
// src/jev/transport.ts
|
|
4903
|
+
var OPENROUTER_PROVIDER = "openrouter";
|
|
4904
|
+
var openRouterKey = async (ctx, deps) => {
|
|
4905
|
+
const key = deps.getProviderKey ? await deps.getProviderKey(OPENROUTER_PROVIDER) : await ctx?.modelRegistry?.getApiKeyForProvider(OPENROUTER_PROVIDER);
|
|
4906
|
+
return key?.trim() || undefined;
|
|
4907
|
+
};
|
|
4908
|
+
var resolveJevTransport = async (ctx, deps = {}) => {
|
|
4909
|
+
const preference = advisorJevTransportRef;
|
|
4910
|
+
if (preference !== "openrouter") {
|
|
4911
|
+
const resolveTypesafe = deps.resolveTypesafe ?? resolveTypeSafeKey;
|
|
4912
|
+
const resolution = await resolveTypesafe();
|
|
4913
|
+
if (resolution.key) {
|
|
4914
|
+
return {
|
|
4915
|
+
apiKey: resolution.key,
|
|
4916
|
+
...resolution.source ? { source: resolution.source } : {},
|
|
4917
|
+
transport: "typesafe"
|
|
4918
|
+
};
|
|
4919
|
+
}
|
|
4920
|
+
}
|
|
4921
|
+
if (preference === "typesafe") {
|
|
4922
|
+
return;
|
|
4923
|
+
}
|
|
4924
|
+
const openrouter = await openRouterKey(ctx, deps);
|
|
4925
|
+
return openrouter ? { apiKey: openrouter, transport: "openrouter" } : undefined;
|
|
4926
|
+
};
|
|
4927
|
+
|
|
4928
|
+
// src/tools/outage-notifier.ts
|
|
4929
|
+
var createOutageNotifier = (format) => {
|
|
4930
|
+
let lastKey;
|
|
4931
|
+
return {
|
|
4932
|
+
notify: (ctx, category, message) => {
|
|
4933
|
+
const key = `${category}:${message}`;
|
|
4934
|
+
if (key === lastKey) {
|
|
4935
|
+
return;
|
|
4936
|
+
}
|
|
4937
|
+
lastKey = key;
|
|
4938
|
+
if (ctx.hasUI) {
|
|
4939
|
+
ctx.ui.notify(format(category, message), "warning");
|
|
4940
|
+
}
|
|
4941
|
+
},
|
|
4942
|
+
reset: () => {
|
|
4943
|
+
lastKey = undefined;
|
|
4944
|
+
}
|
|
4945
|
+
};
|
|
4946
|
+
};
|
|
4947
|
+
|
|
4948
|
+
// src/tools/jev-filter.ts
|
|
4949
|
+
var normalizeScreeningQuestion = (question) => question?.trim().toLowerCase().replace(/\s+/g, " ") || undefined;
|
|
4950
|
+
var REATTACHED_ADVICE_CAP_BYTES = 4 * 1024;
|
|
4951
|
+
var SCREENED_SKIP_TEXT = "Advisor consultation skipped (screened out): the stakes are low and you can resolve this yourself with available tools and context. Proceed on your own judgment with what you already have.";
|
|
4952
|
+
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.
|
|
4953
|
+
|
|
4954
|
+
${advice}`;
|
|
4955
|
+
var outageNotifier = createOutageNotifier((category, message) => `Advisor Jev filter failed (${category}); allowing consultations. ${message}`);
|
|
4956
|
+
var notifyOutageOnce = outageNotifier.notify;
|
|
4957
|
+
var resetJevOutageNotification = outageNotifier.reset;
|
|
4958
|
+
var allow = () => ({ decision: "allow" });
|
|
4959
|
+
var screenConsultation = (ctx, session, options, deps = {}) => {
|
|
4960
|
+
if (isSimpleMode()) {
|
|
4961
|
+
return Promise.resolve(allow());
|
|
4962
|
+
}
|
|
4963
|
+
const normalizedQuestion = normalizeScreeningQuestion(options.question);
|
|
4964
|
+
const bypass = bypassOutcome(session, options, normalizedQuestion);
|
|
4965
|
+
if (bypass) {
|
|
4966
|
+
return Promise.resolve(bypass);
|
|
4967
|
+
}
|
|
4968
|
+
const reattached = session.reattachedAdviceFor(normalizedQuestion);
|
|
4969
|
+
if (reattached) {
|
|
4970
|
+
session.recordJevFilterSkipped(true, normalizedQuestion);
|
|
4971
|
+
return Promise.resolve({
|
|
4972
|
+
decision: "skip",
|
|
4973
|
+
kind: "repeat",
|
|
4974
|
+
reason: "already answered earlier in this session",
|
|
4975
|
+
reattachedAdvice: reattached.slice(0, REATTACHED_ADVICE_CAP_BYTES)
|
|
4976
|
+
});
|
|
4977
|
+
}
|
|
4978
|
+
if (!advisorJevFilterEnabledRef) {
|
|
4979
|
+
return Promise.resolve(allow());
|
|
4980
|
+
}
|
|
4981
|
+
return screenWithJev(ctx, session, options, deps, normalizedQuestion);
|
|
4982
|
+
};
|
|
4983
|
+
var bypassOutcome = (session, options, normalizedQuestion) => {
|
|
4984
|
+
const lastSkip = session.lastJevSkip;
|
|
4985
|
+
if (options.force) {
|
|
4986
|
+
if (lastSkip?.normalizedQuestion !== undefined && lastSkip.normalizedQuestion === normalizedQuestion) {
|
|
4987
|
+
session.recordJevFilterOverride();
|
|
4988
|
+
}
|
|
4989
|
+
return allow();
|
|
4990
|
+
}
|
|
4991
|
+
if (normalizedQuestion !== undefined && lastSkip?.normalizedQuestion === normalizedQuestion && session.sessionTurnOrdinal - lastSkip.turn <= advisorJevFilterOverrideWindowRef) {
|
|
4992
|
+
session.recordJevFilterOverride();
|
|
4993
|
+
return allow();
|
|
4994
|
+
}
|
|
4995
|
+
return;
|
|
4996
|
+
};
|
|
4997
|
+
var screenWithJev = async (ctx, session, options, deps, normalizedQuestion) => {
|
|
4998
|
+
const credentials = await (deps.resolveTransport ?? resolveJevTransport)(ctx);
|
|
4999
|
+
if (!credentials) {
|
|
5000
|
+
session.recordJevFilterFailure();
|
|
5001
|
+
notifyOutageOnce(ctx, "missing-key", "No Jev credentials resolved (no TypeSafe key and no OpenRouter login).");
|
|
5002
|
+
return allow();
|
|
5003
|
+
}
|
|
5004
|
+
if (credentials.source === "advisor-json") {
|
|
5005
|
+
const warning = consumePlaintextKeyWarning();
|
|
5006
|
+
if (warning && ctx.hasUI) {
|
|
5007
|
+
ctx.ui.notify(warning, "warning");
|
|
5008
|
+
}
|
|
5009
|
+
}
|
|
5010
|
+
const client = jevClientFromCredentials(credentials, deps.fetch);
|
|
5011
|
+
try {
|
|
5012
|
+
const result = await client.ask(buildJevState(ctx, options), screeningQuestions, options.signal);
|
|
5013
|
+
session.recordJevFilterUsage(result.usage);
|
|
5014
|
+
const verdict = composeScreeningVerdict(result.answers, {
|
|
5015
|
+
noulMargin: advisorJevFilterNoulMarginRef,
|
|
5016
|
+
skipConfidence: advisorJevFilterSkipConfidenceRef
|
|
5017
|
+
});
|
|
5018
|
+
if (verdict.skip) {
|
|
5019
|
+
session.recordJevFilterSkipped(false, normalizedQuestion);
|
|
5020
|
+
return {
|
|
5021
|
+
decision: "skip",
|
|
5022
|
+
kind: "screened",
|
|
5023
|
+
reason: "low stakes and resolvable without a consultation"
|
|
5024
|
+
};
|
|
5025
|
+
}
|
|
5026
|
+
session.recordJevFilterAllowed();
|
|
5027
|
+
return allow();
|
|
5028
|
+
} catch (error) {
|
|
5029
|
+
session.recordJevFilterFailure();
|
|
5030
|
+
if (error instanceof JevFailure) {
|
|
5031
|
+
notifyOutageOnce(ctx, error.category, error.message);
|
|
5032
|
+
} else if (options.signal?.aborted) {
|
|
5033
|
+
throw error;
|
|
5034
|
+
} else {
|
|
5035
|
+
notifyOutageOnce(ctx, "error", error instanceof Error ? error.message : String(error));
|
|
5036
|
+
}
|
|
5037
|
+
return allow();
|
|
5038
|
+
}
|
|
5039
|
+
};
|
|
5040
|
+
var screeningSkipText = (outcome) => outcome.kind === "repeat" && outcome.reattachedAdvice ? repeatSkipText(outcome.reattachedAdvice) : SCREENED_SKIP_TEXT;
|
|
5041
|
+
|
|
5042
|
+
// src/commands/manual-consultation.ts
|
|
5043
|
+
var startManualConsultation = (runtime, ctx, question, controller, scoutStatusToken, progress, gitContext) => {
|
|
5044
|
+
herdrAdvisorActivity.start();
|
|
5045
|
+
progress.phase = "preparing";
|
|
5046
|
+
runtime.requestManualRender(ctx);
|
|
5047
|
+
if (ctx.hasUI) {
|
|
5048
|
+
const timer = setInterval(() => {
|
|
5049
|
+
if (controller.signal.aborted || runtime.manualConsultations.get(controller) !== scoutStatusToken) {
|
|
5050
|
+
clearInterval(timer);
|
|
5051
|
+
return;
|
|
5052
|
+
}
|
|
5053
|
+
runtime.requestManualRender(ctx);
|
|
5054
|
+
}, 80);
|
|
5055
|
+
runtime.manualProgressTimers.set(controller, timer);
|
|
5056
|
+
}
|
|
5057
|
+
let scoutDetails;
|
|
5058
|
+
return runtime.requestAdvisor(ctx, question, controller.signal, (thinking, text) => {
|
|
5059
|
+
if (controller.signal.aborted) {
|
|
5060
|
+
return;
|
|
5061
|
+
}
|
|
5062
|
+
progress.phase = "active";
|
|
5063
|
+
progress.thinking = thinking;
|
|
5064
|
+
progress.text = text;
|
|
5065
|
+
runtime.requestManualRender(ctx);
|
|
5066
|
+
}, (event) => {
|
|
5067
|
+
if (!controller.signal.aborted) {
|
|
5068
|
+
runtime.scoutStatus.update(ctx, scoutStatusToken, event);
|
|
5069
|
+
scoutDetails = appendScoutLifecycleEntry(runtime.pi, event, scoutDetails);
|
|
5070
|
+
progress.scout = scoutDetails;
|
|
5071
|
+
progress.phase = "active";
|
|
5072
|
+
runtime.requestManualRender(ctx);
|
|
5073
|
+
}
|
|
5074
|
+
}, gitContext).then(({ adviceId, markdown, usage }) => {
|
|
5075
|
+
if (controller.signal.aborted) {
|
|
5076
|
+
return;
|
|
5077
|
+
}
|
|
5078
|
+
progress.phase = "complete";
|
|
5079
|
+
runtime.advisorSessionState.resetTurnsSinceConsultation();
|
|
5080
|
+
runtime.advisorSessionState.recordInvocation({
|
|
5081
|
+
cost: advisorUsageCost(usage),
|
|
5082
|
+
executionEffect: "continued",
|
|
5083
|
+
kind: "markdown",
|
|
5084
|
+
model: advisorRef,
|
|
5085
|
+
trigger: "manual",
|
|
5086
|
+
usage
|
|
5087
|
+
});
|
|
5088
|
+
if (typeof adviceId === "string") {
|
|
5089
|
+
runtime.advisorSessionState.issueAdvice(adviceId, markdown, "manual", false, normalizeScreeningQuestion(question));
|
|
5090
|
+
}
|
|
5091
|
+
runtime.updateAdvisorUsageStatus(ctx);
|
|
5092
|
+
const normalizedUsage = snapshotAdvisorUsage(usage);
|
|
5093
|
+
runtime.pi.sendMessage({
|
|
5094
|
+
content: `Manual Advisor consultation${question ? ` (${question})` : ""} — for your awareness; no action or follow-up consultation is needed unless the user asks:
|
|
5095
|
+
|
|
5096
|
+
${markdown}`,
|
|
5097
|
+
customType: "advisor-manual-result",
|
|
5098
|
+
details: {
|
|
5099
|
+
advisor: advisorRef,
|
|
5100
|
+
question,
|
|
5101
|
+
text: markdown,
|
|
5102
|
+
...normalizedUsage ? { usage: normalizedUsage } : {}
|
|
5103
|
+
},
|
|
5104
|
+
display: true
|
|
5105
|
+
}, {
|
|
5106
|
+
deliverAs: "steer",
|
|
5107
|
+
triggerTurn: true
|
|
5108
|
+
});
|
|
5109
|
+
}).catch((error) => {
|
|
5110
|
+
if (controller.signal.aborted) {
|
|
5111
|
+
return;
|
|
5112
|
+
}
|
|
5113
|
+
progress.phase = "error";
|
|
5114
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
5115
|
+
runtime.advisorSessionState.resetTurnsSinceConsultation();
|
|
5116
|
+
runtime.advisorSessionState.recordInvocation({
|
|
5117
|
+
executionEffect: "continued",
|
|
5118
|
+
failure: "provider-error",
|
|
5119
|
+
kind: "markdown",
|
|
5120
|
+
model: advisorRef,
|
|
5121
|
+
trigger: "manual"
|
|
5122
|
+
});
|
|
5123
|
+
runtime.updateAdvisorUsageStatus(ctx);
|
|
5124
|
+
runtime.pi.sendMessage({
|
|
5125
|
+
content: `Manual Advisor consultation failed: ${message}`,
|
|
5126
|
+
customType: "advisor-manual-result",
|
|
5127
|
+
details: {
|
|
5128
|
+
advisor: advisorRef,
|
|
5129
|
+
text: `**Advisor consultation failed:** ${message}`
|
|
5130
|
+
},
|
|
5131
|
+
display: true
|
|
5132
|
+
}, { deliverAs: "steer", triggerTurn: true });
|
|
5133
|
+
notify(ctx, `Advisor consultation failed: ${message}`, "error");
|
|
5134
|
+
notifyHerdrAdvisorFailure("Advisor consultation failed", message);
|
|
5135
|
+
}).finally(() => {
|
|
5136
|
+
if (controller.signal.aborted) {
|
|
5137
|
+
progress.phase = "cancelled";
|
|
5138
|
+
}
|
|
5139
|
+
const timer = runtime.manualProgressTimers.get(controller);
|
|
5140
|
+
if (timer) {
|
|
5141
|
+
clearInterval(timer);
|
|
5142
|
+
runtime.manualProgressTimers.delete(controller);
|
|
5143
|
+
}
|
|
5144
|
+
runtime.requestManualRender(ctx);
|
|
5145
|
+
runtime.scoutStatus.release(ctx, scoutStatusToken);
|
|
5146
|
+
runtime.manualConsultations.delete(controller);
|
|
5147
|
+
herdrAdvisorActivity.finish();
|
|
5148
|
+
});
|
|
5149
|
+
};
|
|
5150
|
+
|
|
5151
|
+
// src/commands/manual-command.ts
|
|
5152
|
+
var registerManualCommand = (runtime) => {
|
|
5153
|
+
runtime.pi.registerCommand("advisor-manual", {
|
|
5154
|
+
description: "Consult the Advisor in parallel; accepts an optional focused question and fans its response out to the Executor",
|
|
5155
|
+
handler: async (args, ctx) => {
|
|
5156
|
+
if (!loadCommandConfig(ctx)) {
|
|
5157
|
+
return;
|
|
5158
|
+
}
|
|
5159
|
+
if (!(isSimpleMode() || runtime.advisorSessionState.canConsult(getAdvisorMaxCallsPerSession()))) {
|
|
5160
|
+
runtime.reportManualBudgetExhausted(ctx);
|
|
5161
|
+
return;
|
|
5162
|
+
}
|
|
5163
|
+
let gitContext;
|
|
5164
|
+
let question;
|
|
5165
|
+
if (ctx.mode === "tui") {
|
|
5166
|
+
const request = await ctx.ui.custom((tui, theme, keybindings, done) => new ManualAdvisorDialog({
|
|
5167
|
+
gitContext: getAdvisorSettings().gitContext,
|
|
5168
|
+
initialMessage: args,
|
|
5169
|
+
keybindings,
|
|
5170
|
+
onCancel: () => done(undefined),
|
|
5171
|
+
onSubmit: done,
|
|
5172
|
+
theme,
|
|
5173
|
+
tui
|
|
5174
|
+
}), {
|
|
5175
|
+
overlay: true,
|
|
5176
|
+
overlayOptions: {
|
|
5177
|
+
anchor: "center",
|
|
5178
|
+
margin: 2,
|
|
4175
5179
|
maxHeight: "80%",
|
|
4176
5180
|
minWidth: 56,
|
|
4177
5181
|
width: 76
|
|
@@ -4334,7 +5338,7 @@ var registerCommandRenderers = (runtime) => {
|
|
|
4334
5338
|
import { getSettingsListTheme } from "@earendil-works/pi-coding-agent";
|
|
4335
5339
|
import {
|
|
4336
5340
|
SettingsList,
|
|
4337
|
-
truncateToWidth as
|
|
5341
|
+
truncateToWidth as truncateToWidth7
|
|
4338
5342
|
} from "@earendil-works/pi-tui";
|
|
4339
5343
|
|
|
4340
5344
|
// src/ui/settings-formatting.ts
|
|
@@ -4415,14 +5419,367 @@ var rainbowGradient = (text, startedAt) => {
|
|
|
4415
5419
|
}).join("").concat("\x1B[0m");
|
|
4416
5420
|
};
|
|
4417
5421
|
|
|
4418
|
-
// src/ui/
|
|
5422
|
+
// src/ui/jev-setup-submenu.ts
|
|
5423
|
+
import {
|
|
5424
|
+
Key as Key2,
|
|
5425
|
+
matchesKey as matchesKey2,
|
|
5426
|
+
truncateToWidth as truncateToWidth5
|
|
5427
|
+
} from "@earendil-works/pi-tui";
|
|
5428
|
+
|
|
5429
|
+
// node_modules/@typesafe-ai/sdk/dist/index.mjs
|
|
5430
|
+
var range2 = (from, to) => Array.from({ length: to - from }, (_, i) => from + i);
|
|
5431
|
+
var DEFAULT_RETRY_POLICY = {
|
|
5432
|
+
maxRetries: 2,
|
|
5433
|
+
backoffInitialMs: 500,
|
|
5434
|
+
backoffMaxMs: 5000,
|
|
5435
|
+
backoffJitter: 0.25,
|
|
5436
|
+
httpStatuses: /* @__PURE__ */ new Set([
|
|
5437
|
+
408,
|
|
5438
|
+
429,
|
|
5439
|
+
...range2(500, 600)
|
|
5440
|
+
]),
|
|
5441
|
+
respectRetryAfter: true,
|
|
5442
|
+
maxRetryAfterMs: 60000,
|
|
5443
|
+
apiConnectionError: true,
|
|
5444
|
+
apiTimeoutError: true
|
|
5445
|
+
};
|
|
5446
|
+
DEFAULT_RETRY_POLICY.maxRetries;
|
|
5447
|
+
var noul = (instructions = null, criteria) => ({
|
|
5448
|
+
type: "noul",
|
|
5449
|
+
instructions,
|
|
5450
|
+
criteria
|
|
5451
|
+
});
|
|
5452
|
+
var g = globalThis;
|
|
5453
|
+
var isBrowser = () => typeof g.window !== "undefined" && typeof g.window.document !== "undefined" && typeof g.navigator !== "undefined";
|
|
5454
|
+
var describeRuntime = () => {
|
|
5455
|
+
const platform = g.process?.platform && g.process?.arch ? ` (${g.process.platform}; ${g.process.arch})` : "";
|
|
5456
|
+
if (g.Bun?.version)
|
|
5457
|
+
return `bun/${g.Bun.version}${platform}`;
|
|
5458
|
+
if (g.Deno?.version?.deno)
|
|
5459
|
+
return `deno/${g.Deno.version.deno}${platform}`;
|
|
5460
|
+
if (g.EdgeRuntime !== undefined)
|
|
5461
|
+
return "vercel-edge";
|
|
5462
|
+
if (g.navigator?.userAgent === "Cloudflare-Workers")
|
|
5463
|
+
return "cloudflare-workers";
|
|
5464
|
+
if (g.process?.versions?.node)
|
|
5465
|
+
return `node/${g.process.versions.node}${platform}`;
|
|
5466
|
+
if (isBrowser())
|
|
5467
|
+
return "browser";
|
|
5468
|
+
return "unknown";
|
|
5469
|
+
};
|
|
5470
|
+
var RUNTIME = describeRuntime();
|
|
5471
|
+
|
|
5472
|
+
// src/ui/masked-input.ts
|
|
4419
5473
|
import {
|
|
4420
5474
|
Input as Input2,
|
|
4421
5475
|
truncateToWidth as truncateToWidth4
|
|
4422
5476
|
} from "@earendil-works/pi-tui";
|
|
4423
5477
|
|
|
5478
|
+
class MaskedInput {
|
|
5479
|
+
input;
|
|
5480
|
+
options;
|
|
5481
|
+
_focused = true;
|
|
5482
|
+
constructor(options = {}) {
|
|
5483
|
+
this.options = options;
|
|
5484
|
+
this.input = new Input2({ placeholder: options.placeholder });
|
|
5485
|
+
this.input.focused = true;
|
|
5486
|
+
this.input.onSubmit = (value) => options.onSubmit?.(value);
|
|
5487
|
+
this.input.onEscape = () => options.onEscape?.();
|
|
5488
|
+
}
|
|
5489
|
+
get focused() {
|
|
5490
|
+
return this._focused;
|
|
5491
|
+
}
|
|
5492
|
+
set focused(value) {
|
|
5493
|
+
this._focused = value;
|
|
5494
|
+
this.input.focused = value;
|
|
5495
|
+
}
|
|
5496
|
+
getValue() {
|
|
5497
|
+
return this.input.getValue();
|
|
5498
|
+
}
|
|
5499
|
+
setValue(value) {
|
|
5500
|
+
this.input.setValue(value);
|
|
5501
|
+
}
|
|
5502
|
+
handleInput(keyData) {
|
|
5503
|
+
this.input.handleInput(keyData);
|
|
5504
|
+
}
|
|
5505
|
+
invalidate() {
|
|
5506
|
+
this.input.invalidate();
|
|
5507
|
+
}
|
|
5508
|
+
render(width) {
|
|
5509
|
+
const value = this.input.getValue();
|
|
5510
|
+
const { cursor } = this.input;
|
|
5511
|
+
const masked = `${"•".repeat(Math.min(cursor, value.length))}█${"•".repeat(Math.max(0, value.length - cursor))}`;
|
|
5512
|
+
const placeholder = value.length === 0 && this.options.placeholder ? this.options.placeholder : masked;
|
|
5513
|
+
return [truncateToWidth4(placeholder, Math.max(1, width))];
|
|
5514
|
+
}
|
|
5515
|
+
}
|
|
5516
|
+
|
|
5517
|
+
// src/ui/jev-setup-submenu.ts
|
|
5518
|
+
var transportLabel = (credentials) => {
|
|
5519
|
+
if (credentials.transport === "openrouter") {
|
|
5520
|
+
return "OpenRouter (reusing pi login)";
|
|
5521
|
+
}
|
|
5522
|
+
switch (credentials.source) {
|
|
5523
|
+
case "advisor-json":
|
|
5524
|
+
return "TypeSafe (key: advisor.json — plaintext, not recommended)";
|
|
5525
|
+
case "bun-secrets":
|
|
5526
|
+
return "TypeSafe (key: Bun.secrets)";
|
|
5527
|
+
case "file":
|
|
5528
|
+
return "TypeSafe (key: stored file, mode 0600)";
|
|
5529
|
+
default:
|
|
5530
|
+
return "TypeSafe (key: TYPESAFE_API_KEY)";
|
|
5531
|
+
}
|
|
5532
|
+
};
|
|
5533
|
+
var defaultVerify = async (credentials) => {
|
|
5534
|
+
const client = jevClientFromCredentials(credentials);
|
|
5535
|
+
try {
|
|
5536
|
+
await client.ask({ purpose: "pi-advisor setup verification" }, { verified: noul("Answer yes.") });
|
|
5537
|
+
return { ok: true };
|
|
5538
|
+
} catch (error) {
|
|
5539
|
+
return {
|
|
5540
|
+
message: error instanceof Error ? error.message : String(error),
|
|
5541
|
+
ok: false
|
|
5542
|
+
};
|
|
5543
|
+
}
|
|
5544
|
+
};
|
|
5545
|
+
|
|
5546
|
+
class JevSetupSubmenu {
|
|
5547
|
+
options;
|
|
5548
|
+
deps;
|
|
5549
|
+
maskedInput;
|
|
5550
|
+
credentials;
|
|
5551
|
+
mode = "menu";
|
|
5552
|
+
notice;
|
|
5553
|
+
selectedIndex = 0;
|
|
5554
|
+
_focused = true;
|
|
5555
|
+
constructor(options, deps = {}) {
|
|
5556
|
+
this.options = options;
|
|
5557
|
+
this.deps = {
|
|
5558
|
+
clearStoredKey: deps.clearStoredKey ?? clearKeyTypeSafeKey,
|
|
5559
|
+
removePlaintextKey: deps.removePlaintextKey ?? removeTypeSafeKeyFromAdvisorJson,
|
|
5560
|
+
resolveTransport: deps.resolveTransport ?? (() => resolveJevTransport()),
|
|
5561
|
+
verify: deps.verify ?? defaultVerify,
|
|
5562
|
+
writeKey: deps.writeKey ?? writeKeyTypeSafeKey
|
|
5563
|
+
};
|
|
5564
|
+
this.maskedInput = new MaskedInput({
|
|
5565
|
+
onEscape: () => this.options.done(),
|
|
5566
|
+
onSubmit: (value) => this.submitEnteredKey(value),
|
|
5567
|
+
placeholder: "Paste a TypeSafe API key"
|
|
5568
|
+
});
|
|
5569
|
+
this.refresh().catch(() => {
|
|
5570
|
+
return;
|
|
5571
|
+
});
|
|
5572
|
+
}
|
|
5573
|
+
get focused() {
|
|
5574
|
+
return this._focused;
|
|
5575
|
+
}
|
|
5576
|
+
set focused(value) {
|
|
5577
|
+
this._focused = value;
|
|
5578
|
+
this.maskedInput.focused = value;
|
|
5579
|
+
}
|
|
5580
|
+
invalidate() {
|
|
5581
|
+
this.maskedInput.invalidate();
|
|
5582
|
+
}
|
|
5583
|
+
handleInput(keyData) {
|
|
5584
|
+
if (this.mode === "key-entry") {
|
|
5585
|
+
this.maskedInput.handleInput(keyData);
|
|
5586
|
+
this.options.tui.requestRender();
|
|
5587
|
+
return;
|
|
5588
|
+
}
|
|
5589
|
+
if (this.mode !== "menu") {
|
|
5590
|
+
return;
|
|
5591
|
+
}
|
|
5592
|
+
const actions = this.actions();
|
|
5593
|
+
if (matchesKey2(keyData, Key2.down) || keyData === "\x1B[B" || keyData === "\x1BOB") {
|
|
5594
|
+
this.selectedIndex = (this.selectedIndex + 1) % actions.length;
|
|
5595
|
+
} else if (matchesKey2(keyData, Key2.up) || keyData === "\x1B[A" || keyData === "\x1BOA") {
|
|
5596
|
+
this.selectedIndex = (this.selectedIndex - 1 + actions.length) % actions.length;
|
|
5597
|
+
} else if (matchesKey2(keyData, Key2.enter) || keyData === "\r") {
|
|
5598
|
+
this.activate(actions[this.selectedIndex]);
|
|
5599
|
+
} else {
|
|
5600
|
+
return;
|
|
5601
|
+
}
|
|
5602
|
+
this.options.tui.requestRender();
|
|
5603
|
+
}
|
|
5604
|
+
render(width) {
|
|
5605
|
+
const { theme } = this.options;
|
|
5606
|
+
const enabled = this.options.currentValue === "On";
|
|
5607
|
+
const lines = [
|
|
5608
|
+
theme.fg("accent", theme.bold(" Jev consultation filter")),
|
|
5609
|
+
""
|
|
5610
|
+
];
|
|
5611
|
+
if (this.credentials) {
|
|
5612
|
+
lines.push(` Transport: ${transportLabel(this.credentials)}`);
|
|
5613
|
+
lines.push(` Filter: ${enabled ? "On" : "Off"}`);
|
|
5614
|
+
} else if (this.mode === "verifying") {
|
|
5615
|
+
lines.push(" Checking available Jev credentials…");
|
|
5616
|
+
} else {
|
|
5617
|
+
lines.push(" No Jev credentials found. Enter a TypeSafe API key below, or add");
|
|
5618
|
+
lines.push(" an OpenRouter login in pi; it is reused automatically.");
|
|
5619
|
+
}
|
|
5620
|
+
if (this.notice) {
|
|
5621
|
+
lines.push("");
|
|
5622
|
+
lines.push(theme.fg("warning", ` ${this.notice}`));
|
|
5623
|
+
}
|
|
5624
|
+
lines.push("");
|
|
5625
|
+
if (this.mode === "verifying") {
|
|
5626
|
+
lines.push(" Verifying with a live Jev call…");
|
|
5627
|
+
} else if (this.mode === "key-entry") {
|
|
5628
|
+
lines.push(` ${this.maskedInput.render(Math.max(10, width - 4))[0] ?? ""}`);
|
|
5629
|
+
lines.push(theme.fg("dim", " Enter: verify · Esc: cancel"));
|
|
5630
|
+
} else {
|
|
5631
|
+
for (const [index, label] of this.labels().entries()) {
|
|
5632
|
+
const prefix = index === this.selectedIndex ? "→ " : " ";
|
|
5633
|
+
lines.push(`${prefix}${label}`);
|
|
5634
|
+
}
|
|
5635
|
+
}
|
|
5636
|
+
return lines.map((line) => truncateToWidth5(line, width));
|
|
5637
|
+
}
|
|
5638
|
+
actions() {
|
|
5639
|
+
if (!this.credentials) {
|
|
5640
|
+
return ["enter-key", "done"];
|
|
5641
|
+
}
|
|
5642
|
+
const enabled = this.options.currentValue === "On";
|
|
5643
|
+
const actions = [enabled ? "verify-again" : "verify-enable"];
|
|
5644
|
+
actions.push("disable");
|
|
5645
|
+
if (this.credentials.source === "bun-secrets" || this.credentials.source === "file") {
|
|
5646
|
+
actions.push("disable-clear");
|
|
5647
|
+
}
|
|
5648
|
+
actions.push("done");
|
|
5649
|
+
return actions;
|
|
5650
|
+
}
|
|
5651
|
+
actionLabels() {
|
|
5652
|
+
return {
|
|
5653
|
+
disable: "Disable",
|
|
5654
|
+
"disable-clear": "Disable and clear stored key",
|
|
5655
|
+
done: "Done",
|
|
5656
|
+
"enter-key": "Enter a TypeSafe API key",
|
|
5657
|
+
"verify-again": "Verify again",
|
|
5658
|
+
"verify-enable": "Verify and enable"
|
|
5659
|
+
};
|
|
5660
|
+
}
|
|
5661
|
+
labels() {
|
|
5662
|
+
const labels = this.actionLabels();
|
|
5663
|
+
return this.actions().map((action) => labels[action]);
|
|
5664
|
+
}
|
|
5665
|
+
async refresh() {
|
|
5666
|
+
const wasVerifying = this.mode === "verifying";
|
|
5667
|
+
this.mode = "verifying";
|
|
5668
|
+
if (!wasVerifying) {
|
|
5669
|
+
this.options.tui.requestRender();
|
|
5670
|
+
}
|
|
5671
|
+
this.credentials = await this.deps.resolveTransport?.();
|
|
5672
|
+
this.mode = "menu";
|
|
5673
|
+
this.selectedIndex = 0;
|
|
5674
|
+
if (this.credentials?.source === "advisor-json") {
|
|
5675
|
+
this.notice ??= consumePlaintextKeyWarning();
|
|
5676
|
+
}
|
|
5677
|
+
this.options.tui.requestRender();
|
|
5678
|
+
}
|
|
5679
|
+
activate(action) {
|
|
5680
|
+
switch (action) {
|
|
5681
|
+
case "done":
|
|
5682
|
+
this.options.done();
|
|
5683
|
+
return;
|
|
5684
|
+
case "enter-key":
|
|
5685
|
+
this.mode = "key-entry";
|
|
5686
|
+
return;
|
|
5687
|
+
case "disable":
|
|
5688
|
+
this.notice = undefined;
|
|
5689
|
+
this.options.done("Off");
|
|
5690
|
+
return;
|
|
5691
|
+
case "disable-clear":
|
|
5692
|
+
this.disableAndClear().catch(() => {
|
|
5693
|
+
return;
|
|
5694
|
+
});
|
|
5695
|
+
return;
|
|
5696
|
+
case "verify-again":
|
|
5697
|
+
case "verify-enable":
|
|
5698
|
+
this.verifyAndEnable().catch(() => {
|
|
5699
|
+
return;
|
|
5700
|
+
});
|
|
5701
|
+
return;
|
|
5702
|
+
default:
|
|
5703
|
+
return;
|
|
5704
|
+
}
|
|
5705
|
+
}
|
|
5706
|
+
async disableAndClear() {
|
|
5707
|
+
const clear = this.deps.clearStoredKey;
|
|
5708
|
+
if (!clear) {
|
|
5709
|
+
return;
|
|
5710
|
+
}
|
|
5711
|
+
const result = await clear();
|
|
5712
|
+
this.credentials = undefined;
|
|
5713
|
+
this.notice = result.message;
|
|
5714
|
+
this.options.done("Off");
|
|
5715
|
+
}
|
|
5716
|
+
async verifyAndEnable() {
|
|
5717
|
+
const { credentials } = this;
|
|
5718
|
+
if (!(credentials && this.deps.verify)) {
|
|
5719
|
+
return;
|
|
5720
|
+
}
|
|
5721
|
+
this.mode = "verifying";
|
|
5722
|
+
this.notice = undefined;
|
|
5723
|
+
this.options.tui.requestRender();
|
|
5724
|
+
const outcome = await this.deps.verify(credentials);
|
|
5725
|
+
this.mode = "menu";
|
|
5726
|
+
if (!outcome.ok) {
|
|
5727
|
+
this.notice = `Verification failed: ${outcome.message ?? "unknown error"}`;
|
|
5728
|
+
this.options.tui.requestRender();
|
|
5729
|
+
return;
|
|
5730
|
+
}
|
|
5731
|
+
if (credentials.transport === "typesafe" && credentials.source === "advisor-json") {
|
|
5732
|
+
const stored = await this.deps.writeKey?.(credentials.apiKey);
|
|
5733
|
+
if (!stored?.ok) {
|
|
5734
|
+
this.notice = stored?.message ?? "Storing the key failed; the plaintext advisor.json key keeps working.";
|
|
5735
|
+
this.options.tui.requestRender();
|
|
5736
|
+
return;
|
|
5737
|
+
}
|
|
5738
|
+
const removed = this.deps.removePlaintextKey?.();
|
|
5739
|
+
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."}`;
|
|
5740
|
+
}
|
|
5741
|
+
this.options.done("On");
|
|
5742
|
+
}
|
|
5743
|
+
async submitEnteredKey(value) {
|
|
5744
|
+
const key = value.trim();
|
|
5745
|
+
if (!key) {
|
|
5746
|
+
return;
|
|
5747
|
+
}
|
|
5748
|
+
if (!this.deps.verify) {
|
|
5749
|
+
return;
|
|
5750
|
+
}
|
|
5751
|
+
this.mode = "verifying";
|
|
5752
|
+
this.options.tui.requestRender();
|
|
5753
|
+
const outcome = await this.deps.verify({
|
|
5754
|
+
apiKey: key,
|
|
5755
|
+
transport: "typesafe"
|
|
5756
|
+
});
|
|
5757
|
+
this.mode = "menu";
|
|
5758
|
+
if (!outcome.ok) {
|
|
5759
|
+
this.notice = `Verification failed: ${outcome.message ?? "unknown error"}`;
|
|
5760
|
+
this.options.tui.requestRender();
|
|
5761
|
+
return;
|
|
5762
|
+
}
|
|
5763
|
+
const stored = await this.deps.writeKey?.(key);
|
|
5764
|
+
if (!stored?.ok) {
|
|
5765
|
+
this.notice = stored?.message ?? "Storing the key failed.";
|
|
5766
|
+
this.options.tui.requestRender();
|
|
5767
|
+
return;
|
|
5768
|
+
}
|
|
5769
|
+
this.notice = `${stored.message} Verification succeeded.`;
|
|
5770
|
+
await this.refresh();
|
|
5771
|
+
this.options.done("On");
|
|
5772
|
+
}
|
|
5773
|
+
}
|
|
5774
|
+
|
|
5775
|
+
// src/ui/text-setting-submenu.ts
|
|
5776
|
+
import {
|
|
5777
|
+
Input as Input3,
|
|
5778
|
+
truncateToWidth as truncateToWidth6
|
|
5779
|
+
} from "@earendil-works/pi-tui";
|
|
5780
|
+
|
|
4424
5781
|
class TextSettingSubmenu {
|
|
4425
|
-
input = new
|
|
5782
|
+
input = new Input3;
|
|
4426
5783
|
options;
|
|
4427
5784
|
_focused = true;
|
|
4428
5785
|
error;
|
|
@@ -4466,7 +5823,7 @@ class TextSettingSubmenu {
|
|
|
4466
5823
|
lines.push(theme.fg("error", ` ${this.error}`));
|
|
4467
5824
|
}
|
|
4468
5825
|
lines.push("", theme.fg("dim", " Enter: apply · Esc: cancel"));
|
|
4469
|
-
return lines.map((line) =>
|
|
5826
|
+
return lines.map((line) => truncateToWidth6(line, width));
|
|
4470
5827
|
}
|
|
4471
5828
|
handleInput(keyData) {
|
|
4472
5829
|
const before = this.input.getValue();
|
|
@@ -4485,6 +5842,103 @@ var toggle = (id, label, description, value, defaultValue) => ({
|
|
|
4485
5842
|
label,
|
|
4486
5843
|
values: TOGGLE_VALUES
|
|
4487
5844
|
});
|
|
5845
|
+
var jevItems = (settings, theme, tui) => [
|
|
5846
|
+
{
|
|
5847
|
+
currentValue: settingValue(settings.jevFilterEnabled, false),
|
|
5848
|
+
description: "Screen low-stakes ask_advisor consultations with Jev; guided setup verifies credentials.",
|
|
5849
|
+
id: "jevFilter",
|
|
5850
|
+
label: "Jev consultation filter",
|
|
5851
|
+
submenu: (currentValue, done) => new JevSetupSubmenu({ currentValue, done, theme, tui })
|
|
5852
|
+
},
|
|
5853
|
+
{
|
|
5854
|
+
currentValue: String(settings.jevFilterSkipConfidence ?? DEFAULT_JEV_FILTER_SKIP_CONFIDENCE),
|
|
5855
|
+
description: "Required probability on negligible stakes before a consultation is skipped.",
|
|
5856
|
+
id: "jevFilterSkipConfidence",
|
|
5857
|
+
label: "Jev skip confidence",
|
|
5858
|
+
values: numericValues(settings.jevFilterSkipConfidence ?? DEFAULT_JEV_FILTER_SKIP_CONFIDENCE, [0.6, 0.7, 0.75, 0.8, 0.85, 0.9, 0.95])
|
|
5859
|
+
},
|
|
5860
|
+
{
|
|
5861
|
+
currentValue: String(settings.jevFilterNoulMargin ?? DEFAULT_JEV_FILTER_NOUL_MARGIN),
|
|
5862
|
+
description: "Extra margin over a coin flip required on self-answerability before skipping.",
|
|
5863
|
+
id: "jevFilterNoulMargin",
|
|
5864
|
+
label: "Jev Noul margin",
|
|
5865
|
+
values: numericValues(settings.jevFilterNoulMargin ?? DEFAULT_JEV_FILTER_NOUL_MARGIN, [0.1, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45])
|
|
5866
|
+
},
|
|
5867
|
+
{
|
|
5868
|
+
currentValue: `${settings.jevFilterOverrideWindow ?? DEFAULT_JEV_FILTER_OVERRIDE_WINDOW} turns`,
|
|
5869
|
+
description: "Turns after a skip during which the same question passes automatically.",
|
|
5870
|
+
id: "jevFilterOverrideWindow",
|
|
5871
|
+
label: "Jev override window",
|
|
5872
|
+
values: numericValues(settings.jevFilterOverrideWindow ?? DEFAULT_JEV_FILTER_OVERRIDE_WINDOW, [0, 3, 5, 10, 20, 50]).map((value) => `${value} turns`)
|
|
5873
|
+
},
|
|
5874
|
+
{
|
|
5875
|
+
currentValue: settings.jevModel ?? DEFAULT_JEV_MODEL,
|
|
5876
|
+
description: "TypeSafe Jev model used for screening and turn-gate checks.",
|
|
5877
|
+
id: "jevModel",
|
|
5878
|
+
label: "Jev model",
|
|
5879
|
+
submenu: (_currentValue, done) => new TextSettingSubmenu({
|
|
5880
|
+
description: "Enter a TypeSafe model name (for example jev-latest or jev-1.13.0).",
|
|
5881
|
+
initial: settings.jevModel ?? DEFAULT_JEV_MODEL,
|
|
5882
|
+
onCancel: done,
|
|
5883
|
+
onSubmit: (value) => ({ value: value.trim() || DEFAULT_JEV_MODEL }),
|
|
5884
|
+
theme,
|
|
5885
|
+
title: "Jev model",
|
|
5886
|
+
tui
|
|
5887
|
+
})
|
|
5888
|
+
},
|
|
5889
|
+
{
|
|
5890
|
+
currentValue: String(settings.jevTimeoutMs ?? DEFAULT_JEV_TIMEOUT_MS),
|
|
5891
|
+
description: "Total wall-time budget for one Jev call, including retries.",
|
|
5892
|
+
id: "jevTimeoutMs",
|
|
5893
|
+
label: "Jev timeout ms",
|
|
5894
|
+
values: numericValues(settings.jevTimeoutMs ?? DEFAULT_JEV_TIMEOUT_MS, [1000, 2000, 5000, 8000, 15000, 30000])
|
|
5895
|
+
},
|
|
5896
|
+
{
|
|
5897
|
+
currentValue: String(settings.jevDigestMaxChars ?? DEFAULT_JEV_DIGEST_MAX_CHARS),
|
|
5898
|
+
description: "Conversation characters sent to Jev as screening evidence.",
|
|
5899
|
+
id: "jevDigestMaxChars",
|
|
5900
|
+
label: "Jev digest chars",
|
|
5901
|
+
values: numericValues(settings.jevDigestMaxChars ?? DEFAULT_JEV_DIGEST_MAX_CHARS, [0, 1000, 2000, 4000, 8000, 15000])
|
|
5902
|
+
},
|
|
5903
|
+
{
|
|
5904
|
+
currentValue: String(settings.jevPricePerMtok ?? DEFAULT_JEV_PRICE_PER_MTOK),
|
|
5905
|
+
description: "Assumed TypeSafe price per million input tokens for cost lines.",
|
|
5906
|
+
id: "jevPricePerMtok",
|
|
5907
|
+
label: "Jev price/Mtok",
|
|
5908
|
+
values: numericValues(settings.jevPricePerMtok ?? DEFAULT_JEV_PRICE_PER_MTOK, [0.01, 0.02, 0.042, 0.05, 0.1])
|
|
5909
|
+
},
|
|
5910
|
+
{
|
|
5911
|
+
currentValue: settings.jevTurnGateEveryTurns === 0 || settings.jevTurnGateEveryTurns === undefined ? "Off" : `every ${settings.jevTurnGateEveryTurns} turns`,
|
|
5912
|
+
description: "Proactively consult the Advisor every N turns without a consultation (0 = off).",
|
|
5913
|
+
id: "jevTurnGateEveryTurns",
|
|
5914
|
+
label: "Jev turn gate",
|
|
5915
|
+
values: [
|
|
5916
|
+
"Off",
|
|
5917
|
+
"every 3 turns",
|
|
5918
|
+
"every 5 turns",
|
|
5919
|
+
"every 10 turns",
|
|
5920
|
+
"every 20 turns"
|
|
5921
|
+
]
|
|
5922
|
+
},
|
|
5923
|
+
{
|
|
5924
|
+
currentValue: String(settings.jevTurnGateNoulThreshold ?? DEFAULT_JEV_TURN_GATE_NOUL_THRESHOLD),
|
|
5925
|
+
description: "Jev confidence required before the turn gate interrupts with advice.",
|
|
5926
|
+
id: "jevTurnGateNoulThreshold",
|
|
5927
|
+
label: "Jev turn-gate threshold",
|
|
5928
|
+
values: numericValues(settings.jevTurnGateNoulThreshold ?? DEFAULT_JEV_TURN_GATE_NOUL_THRESHOLD, [0.6, 0.7, 0.8, 0.85, 0.9, 0.95])
|
|
5929
|
+
},
|
|
5930
|
+
{
|
|
5931
|
+
currentValue: settings.jevTransport ?? DEFAULT_JEV_TRANSPORT,
|
|
5932
|
+
description: "How Jev calls travel: auto reuses an OpenRouter login when no TypeSafe key is set.",
|
|
5933
|
+
id: "jevTransport",
|
|
5934
|
+
label: "Jev transport",
|
|
5935
|
+
values: withCurrentValue(settings.jevTransport ?? DEFAULT_JEV_TRANSPORT, [
|
|
5936
|
+
"auto",
|
|
5937
|
+
"typesafe",
|
|
5938
|
+
"openrouter"
|
|
5939
|
+
])
|
|
5940
|
+
}
|
|
5941
|
+
];
|
|
4488
5942
|
var createSettingsItems = ({
|
|
4489
5943
|
effortLevels,
|
|
4490
5944
|
presets,
|
|
@@ -4621,14 +6075,14 @@ var createSettingsItems = ({
|
|
|
4621
6075
|
title: "Tool disclosure policies",
|
|
4622
6076
|
tui
|
|
4623
6077
|
})
|
|
4624
|
-
}, 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));
|
|
6078
|
+
}, 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));
|
|
4625
6079
|
return items;
|
|
4626
6080
|
};
|
|
4627
6081
|
|
|
4628
6082
|
// src/ui/settings-list-adapter.ts
|
|
4629
6083
|
import {
|
|
4630
|
-
Key as
|
|
4631
|
-
matchesKey as
|
|
6084
|
+
Key as Key3,
|
|
6085
|
+
matchesKey as matchesKey3
|
|
4632
6086
|
} from "@earendil-works/pi-tui";
|
|
4633
6087
|
|
|
4634
6088
|
class SettingsListAdapter {
|
|
@@ -4663,9 +6117,9 @@ class SettingsListAdapter {
|
|
|
4663
6117
|
}
|
|
4664
6118
|
changeWithArrow(keyData, onChange) {
|
|
4665
6119
|
let direction = 0;
|
|
4666
|
-
if (
|
|
6120
|
+
if (matchesKey3(keyData, Key3.left) || keyData === "\x1B[D") {
|
|
4667
6121
|
direction = -1;
|
|
4668
|
-
} else if (
|
|
6122
|
+
} else if (matchesKey3(keyData, Key3.right) || keyData === "\x1B[C") {
|
|
4669
6123
|
direction = 1;
|
|
4670
6124
|
}
|
|
4671
6125
|
if (direction === 0) {
|
|
@@ -4758,6 +6212,39 @@ var mutateAdvisorSettings = (settings, id, value, presets) => {
|
|
|
4758
6212
|
case "gitContextMaxChars":
|
|
4759
6213
|
settings.gitContextMaxChars = Number(value);
|
|
4760
6214
|
break;
|
|
6215
|
+
case "jevFilter":
|
|
6216
|
+
settings.jevFilterEnabled = value === "On";
|
|
6217
|
+
break;
|
|
6218
|
+
case "jevFilterSkipConfidence":
|
|
6219
|
+
settings.jevFilterSkipConfidence = Number(value);
|
|
6220
|
+
break;
|
|
6221
|
+
case "jevFilterNoulMargin":
|
|
6222
|
+
settings.jevFilterNoulMargin = Number(value);
|
|
6223
|
+
break;
|
|
6224
|
+
case "jevFilterOverrideWindow":
|
|
6225
|
+
settings.jevFilterOverrideWindow = Number(value.replace(" turns", ""));
|
|
6226
|
+
break;
|
|
6227
|
+
case "jevTurnGateEveryTurns":
|
|
6228
|
+
settings.jevTurnGateEveryTurns = value === "Off" ? 0 : Number(value.replace(/[^0-9]/g, ""));
|
|
6229
|
+
break;
|
|
6230
|
+
case "jevTurnGateNoulThreshold":
|
|
6231
|
+
settings.jevTurnGateNoulThreshold = Number(value);
|
|
6232
|
+
break;
|
|
6233
|
+
case "jevModel":
|
|
6234
|
+
settings.jevModel = value.trim() || DEFAULT_JEV_MODEL;
|
|
6235
|
+
break;
|
|
6236
|
+
case "jevTimeoutMs":
|
|
6237
|
+
settings.jevTimeoutMs = Number(value);
|
|
6238
|
+
break;
|
|
6239
|
+
case "jevDigestMaxChars":
|
|
6240
|
+
settings.jevDigestMaxChars = Number(value);
|
|
6241
|
+
break;
|
|
6242
|
+
case "jevPricePerMtok":
|
|
6243
|
+
settings.jevPricePerMtok = Number(value);
|
|
6244
|
+
break;
|
|
6245
|
+
case "jevTransport":
|
|
6246
|
+
settings.jevTransport = value;
|
|
6247
|
+
break;
|
|
4761
6248
|
default:
|
|
4762
6249
|
if (BOOLEAN_SETTING_IDS.has(id)) {
|
|
4763
6250
|
settings[id] = value === "On";
|
|
@@ -4807,7 +6294,7 @@ class AdvisorSettingsSelector {
|
|
|
4807
6294
|
}
|
|
4808
6295
|
render(width) {
|
|
4809
6296
|
const border = this.options.theme.fg("border", "─".repeat(Math.max(1, width)));
|
|
4810
|
-
return [border, ...this.settingsList.render(width), border].map((line) =>
|
|
6297
|
+
return [border, ...this.settingsList.render(width), border].map((line) => truncateToWidth7(line, width));
|
|
4811
6298
|
}
|
|
4812
6299
|
handleInput(keyData) {
|
|
4813
6300
|
if (!this.settingsList.changeWithArrow(keyData, (id, value) => this.change(id, value))) {
|
|
@@ -4894,6 +6381,17 @@ var applyAdvisorSettings = (settings) => {
|
|
|
4894
6381
|
setAlwaysOnRef(settings.alwaysOn ?? false);
|
|
4895
6382
|
setAdvisorFailureModeRef(settings.failureMode ?? "block-session");
|
|
4896
6383
|
setAdvisorHerdrIntegrationRef(settings.herdrIntegration ?? true);
|
|
6384
|
+
setAdvisorJevFilterEnabledRef(settings.jevFilterEnabled ?? false);
|
|
6385
|
+
setAdvisorJevFilterSkipConfidenceRef(settings.jevFilterSkipConfidence ?? DEFAULT_JEV_FILTER_SKIP_CONFIDENCE);
|
|
6386
|
+
setAdvisorJevFilterNoulMarginRef(settings.jevFilterNoulMargin ?? DEFAULT_JEV_FILTER_NOUL_MARGIN);
|
|
6387
|
+
setAdvisorJevFilterOverrideWindowRef(settings.jevFilterOverrideWindow ?? DEFAULT_JEV_FILTER_OVERRIDE_WINDOW);
|
|
6388
|
+
setAdvisorJevModelRef(settings.jevModel ?? DEFAULT_JEV_MODEL);
|
|
6389
|
+
setAdvisorJevTimeoutMsRef(settings.jevTimeoutMs ?? DEFAULT_JEV_TIMEOUT_MS);
|
|
6390
|
+
setAdvisorJevDigestMaxCharsRef(settings.jevDigestMaxChars ?? DEFAULT_JEV_DIGEST_MAX_CHARS);
|
|
6391
|
+
setAdvisorJevPricePerMtokRef(settings.jevPricePerMtok ?? DEFAULT_JEV_PRICE_PER_MTOK);
|
|
6392
|
+
setAdvisorJevTransportRef(settings.jevTransport ?? DEFAULT_JEV_TRANSPORT);
|
|
6393
|
+
setAdvisorJevTurnGateEveryTurnsRef(settings.jevTurnGateEveryTurns ?? DEFAULT_JEV_TURN_GATE_EVERY_TURNS);
|
|
6394
|
+
setAdvisorJevTurnGateNoulThresholdRef(settings.jevTurnGateNoulThreshold ?? DEFAULT_JEV_TURN_GATE_NOUL_THRESHOLD);
|
|
4897
6395
|
setAdvisorToolResultMaxLinesRef(settings.toolResultMaxLines ?? 2000);
|
|
4898
6396
|
setAdvisorToolResultMaxBytesRef(settings.toolResultMaxBytes ?? 50 * 1024);
|
|
4899
6397
|
setAdvisorRedactSecretsRef(settings.redactSecrets ?? false);
|
|
@@ -4985,8 +6483,8 @@ import {
|
|
|
4985
6483
|
unlink,
|
|
4986
6484
|
writeFile
|
|
4987
6485
|
} from "node:fs/promises";
|
|
4988
|
-
import { join as
|
|
4989
|
-
import { getAgentDir as
|
|
6486
|
+
import { join as join5 } from "node:path";
|
|
6487
|
+
import { getAgentDir as getAgentDir4 } from "@earendil-works/pi-coding-agent";
|
|
4990
6488
|
var ADOPTIONS = [
|
|
4991
6489
|
"followed",
|
|
4992
6490
|
"not-followed",
|
|
@@ -4999,11 +6497,11 @@ var VALIDATIONS = [
|
|
|
4999
6497
|
"unknown"
|
|
5000
6498
|
];
|
|
5001
6499
|
var MAX_LOG_BYTES = 1024 * 1024;
|
|
5002
|
-
var statePath = () =>
|
|
5003
|
-
var outcomeLogPath = () =>
|
|
6500
|
+
var statePath = () => join5(getAgentDir4(), "advisor-outcomes-salt");
|
|
6501
|
+
var outcomeLogPath = () => join5(getAgentDir4(), "advisor-outcomes.jsonl");
|
|
5004
6502
|
var salt = async () => {
|
|
5005
6503
|
const path = statePath();
|
|
5006
|
-
await mkdir(
|
|
6504
|
+
await mkdir(getAgentDir4(), { mode: 448, recursive: true });
|
|
5007
6505
|
for (let attempt = 0;attempt < 20; attempt += 1) {
|
|
5008
6506
|
try {
|
|
5009
6507
|
const existing = await readFile(path);
|
|
@@ -5080,7 +6578,7 @@ var withOutcomeLock = async (run) => {
|
|
|
5080
6578
|
var adviceDigest = (advice, key) => createHmac("sha256", key).update(advice).digest("hex").slice(0, 16);
|
|
5081
6579
|
var appendOutcome = async (record) => {
|
|
5082
6580
|
const path = outcomeLogPath();
|
|
5083
|
-
await mkdir(
|
|
6581
|
+
await mkdir(getAgentDir4(), { mode: 448, recursive: true });
|
|
5084
6582
|
return withOutcomeLock(async () => {
|
|
5085
6583
|
const next = {
|
|
5086
6584
|
adoption: record.adoption,
|
|
@@ -5108,9 +6606,6 @@ var appendOutcome = async (record) => {
|
|
|
5108
6606
|
});
|
|
5109
6607
|
};
|
|
5110
6608
|
|
|
5111
|
-
// src/tools/register-ask-advisor.ts
|
|
5112
|
-
import { Type } from "typebox";
|
|
5113
|
-
|
|
5114
6609
|
// src/tools/gate-policy.ts
|
|
5115
6610
|
var updateAdvisorUsageStatus = (ctx, session) => {
|
|
5116
6611
|
if (ctx.hasUI) {
|
|
@@ -5184,6 +6679,99 @@ var reserveAdvisorCall = (event, ctx, session, reservedCalls) => {
|
|
|
5184
6679
|
return {};
|
|
5185
6680
|
};
|
|
5186
6681
|
|
|
6682
|
+
// src/tools/jev-turn-gate.ts
|
|
6683
|
+
var turnGateQuestion = {
|
|
6684
|
+
criteria: {
|
|
6685
|
+
false: "The executor is progressing soundly on work that matches the user's intent; interrupting would add nothing material.",
|
|
6686
|
+
true: "The executor is approaching a material decision, repeating a failure, about to claim success without validation, or drifting from the user's intent; a second opinion now would change what happens next."
|
|
6687
|
+
},
|
|
6688
|
+
instructions: "Should a senior engineering advisor be consulted right now, before the executor continues? Judge from `recent_conversation`.",
|
|
6689
|
+
type: "noul"
|
|
6690
|
+
};
|
|
6691
|
+
var outageNotifier2 = createOutageNotifier((category, message) => `Advisor Jev turn gate failed (${category}); continuing without a proactive consultation. ${message}`);
|
|
6692
|
+
var notifyFailureOnce = outageNotifier2.notify;
|
|
6693
|
+
var resetJevTurnGateNotification = outageNotifier2.reset;
|
|
6694
|
+
var handleJevTurnEnd = async (registration, ctx) => {
|
|
6695
|
+
const { session } = registration;
|
|
6696
|
+
session.recordCompletedTurn();
|
|
6697
|
+
const interval = advisorJevTurnGateEveryTurnsRef;
|
|
6698
|
+
if (interval <= 0 || session.turnsSinceConsultation <= 0 || session.turnsSinceConsultation % interval !== 0) {
|
|
6699
|
+
return;
|
|
6700
|
+
}
|
|
6701
|
+
if (isSimpleMode() || session.blocked || !registration.activeTools().includes("ask_advisor") || !session.canConsult(getAdvisorMaxCallsPerSession())) {
|
|
6702
|
+
return;
|
|
6703
|
+
}
|
|
6704
|
+
const consult = registration.deps?.consult ?? registration.consult;
|
|
6705
|
+
const deps = registration.deps ?? {};
|
|
6706
|
+
try {
|
|
6707
|
+
const credentials = await (deps.resolveTransport ?? resolveJevTransport)(ctx);
|
|
6708
|
+
if (!credentials) {
|
|
6709
|
+
session.recordJevGateFailure();
|
|
6710
|
+
notifyFailureOnce(ctx, "missing-key", "No Jev credentials resolved (no TypeSafe key and no OpenRouter login).");
|
|
6711
|
+
return;
|
|
6712
|
+
}
|
|
6713
|
+
const client = jevClientFromCredentials(credentials, deps.fetch);
|
|
6714
|
+
const result = await client.ask(buildJevState(ctx, {}), {
|
|
6715
|
+
should_consult: turnGateQuestion
|
|
6716
|
+
});
|
|
6717
|
+
session.recordJevGateCheck(result.usage);
|
|
6718
|
+
const shouldConsult = composeTurnGateVerdict(result.answers, advisorJevTurnGateNoulThresholdRef);
|
|
6719
|
+
if (!shouldConsult) {
|
|
6720
|
+
return;
|
|
6721
|
+
}
|
|
6722
|
+
session.consumeCall();
|
|
6723
|
+
herdrAdvisorActivity.start();
|
|
6724
|
+
registration.send({
|
|
6725
|
+
content: "Proactive Advisor turn review",
|
|
6726
|
+
customType: "advisor-turn-gate-call",
|
|
6727
|
+
details: {
|
|
6728
|
+
question: `Turn gate: ${session.turnsSinceConsultation} turns without a consultation`,
|
|
6729
|
+
turn: session.sessionTurnOrdinal
|
|
6730
|
+
},
|
|
6731
|
+
display: true
|
|
6732
|
+
});
|
|
6733
|
+
try {
|
|
6734
|
+
const consulted = await consult(ctx, undefined, ctx.signal, undefined, "turn-gate");
|
|
6735
|
+
session.recordJevGateConsultation();
|
|
6736
|
+
session.resetTurnsSinceConsultation();
|
|
6737
|
+
session.recordInvocation({
|
|
6738
|
+
cost: advisorUsageCost(consulted.usage),
|
|
6739
|
+
executionEffect: "continued",
|
|
6740
|
+
kind: "markdown",
|
|
6741
|
+
model: consulted.model,
|
|
6742
|
+
trigger: "turn-gate",
|
|
6743
|
+
usage: consulted.usage
|
|
6744
|
+
});
|
|
6745
|
+
updateAdvisorUsageStatus(ctx, session);
|
|
6746
|
+
registration.send({
|
|
6747
|
+
content: consulted.markdown,
|
|
6748
|
+
customType: "advisor-turn-gate-result",
|
|
6749
|
+
details: {
|
|
6750
|
+
advisor: consulted.model,
|
|
6751
|
+
text: consulted.markdown,
|
|
6752
|
+
usage: consulted.usage
|
|
6753
|
+
},
|
|
6754
|
+
display: true
|
|
6755
|
+
});
|
|
6756
|
+
} finally {
|
|
6757
|
+
herdrAdvisorActivity.finish();
|
|
6758
|
+
}
|
|
6759
|
+
} catch (error) {
|
|
6760
|
+
session.recordJevGateFailure();
|
|
6761
|
+
if (error instanceof JevFailure) {
|
|
6762
|
+
notifyFailureOnce(ctx, error.category, error.message);
|
|
6763
|
+
} else if (!ctx.signal?.aborted) {
|
|
6764
|
+
notifyFailureOnce(ctx, "error", error instanceof Error ? error.message : String(error));
|
|
6765
|
+
}
|
|
6766
|
+
}
|
|
6767
|
+
};
|
|
6768
|
+
var registerJevTurnGate = (on, registration) => {
|
|
6769
|
+
on("turn_end", (_event, ctx) => handleJevTurnEnd(registration, ctx));
|
|
6770
|
+
};
|
|
6771
|
+
|
|
6772
|
+
// src/tools/register-ask-advisor.ts
|
|
6773
|
+
import { Type } from "typebox";
|
|
6774
|
+
|
|
5187
6775
|
// src/tools/render-advisor-result.ts
|
|
5188
6776
|
import {
|
|
5189
6777
|
getMarkdownTheme as getMarkdownTheme4
|
|
@@ -5197,6 +6785,22 @@ var syncRenderPhase = (context, phase) => {
|
|
|
5197
6785
|
}
|
|
5198
6786
|
context.state.phase = phase;
|
|
5199
6787
|
};
|
|
6788
|
+
var attachmentLabels = (details) => [
|
|
6789
|
+
details?.draftBytes ? `Draft attached · ${details.draftBytes} B` : undefined,
|
|
6790
|
+
details?.preferenceBytes ? `Project preferences attached · ${details.preferenceBytes} B` : undefined,
|
|
6791
|
+
details?.trackedBytes ? `Tracked files attached · ${details.trackedBytes} B` : undefined,
|
|
6792
|
+
details?.untrackedBytes ? `Untracked files attached · ${details.untrackedBytes} B` : undefined
|
|
6793
|
+
].filter((label) => label !== undefined);
|
|
6794
|
+
var renderJevSkipBox = (box, result, expanded, theme) => {
|
|
6795
|
+
const details = advisorResultDetails(result);
|
|
6796
|
+
const lines = [
|
|
6797
|
+
theme.fg("dim", theme.bold("◆ ADVISOR · SKIPPED")),
|
|
6798
|
+
theme.fg("dim", ` ${details?.jev?.reason ?? ""}`)
|
|
6799
|
+
];
|
|
6800
|
+
box.addChild(new Text5(lines.join(`
|
|
6801
|
+
`), 0, 0));
|
|
6802
|
+
box.addChild(new Markdown4(adviceForDisplay(textFrom(result.content), expanded), 0, 0, getMarkdownTheme4()));
|
|
6803
|
+
};
|
|
5200
6804
|
var renderPartialAdvisorResult = (box, result, expanded, theme, context) => {
|
|
5201
6805
|
const details = advisorResultDetails(result);
|
|
5202
6806
|
if (details?.scout) {
|
|
@@ -5235,6 +6839,10 @@ var renderFinalAdvisorResult = (box, result, expanded, theme, context) => {
|
|
|
5235
6839
|
context.state.timerId = undefined;
|
|
5236
6840
|
}
|
|
5237
6841
|
const details = advisorResultDetails(result);
|
|
6842
|
+
if (details?.jev?.skipped) {
|
|
6843
|
+
renderJevSkipBox(box, result, expanded, theme);
|
|
6844
|
+
return;
|
|
6845
|
+
}
|
|
5238
6846
|
if (details?.scout) {
|
|
5239
6847
|
context.state.scout = details.scout;
|
|
5240
6848
|
}
|
|
@@ -5256,12 +6864,7 @@ var renderFinalAdvisorResult = (box, result, expanded, theme, context) => {
|
|
|
5256
6864
|
lines.push(theme.fg("dim", ` Usage: ${usage}`));
|
|
5257
6865
|
}
|
|
5258
6866
|
}
|
|
5259
|
-
const attachments =
|
|
5260
|
-
details?.draftBytes ? `Draft attached · ${details.draftBytes} B` : undefined,
|
|
5261
|
-
details?.preferenceBytes ? `Project preferences attached · ${details.preferenceBytes} B` : undefined,
|
|
5262
|
-
details?.trackedBytes ? `Tracked files attached · ${details.trackedBytes} B` : undefined,
|
|
5263
|
-
details?.untrackedBytes ? `Untracked files attached · ${details.untrackedBytes} B` : undefined
|
|
5264
|
-
].filter(Boolean);
|
|
6867
|
+
const attachments = attachmentLabels(details);
|
|
5265
6868
|
if (attachments.length) {
|
|
5266
6869
|
lines.push(theme.fg("dim", ` ${attachments.join(" · ")}`));
|
|
5267
6870
|
}
|
|
@@ -5303,6 +6906,7 @@ var registerAskAdvisorTool = ({
|
|
|
5303
6906
|
consult: requestAdvisor,
|
|
5304
6907
|
pi,
|
|
5305
6908
|
reservedCalls,
|
|
6909
|
+
screen,
|
|
5306
6910
|
session
|
|
5307
6911
|
}) => {
|
|
5308
6912
|
pi.registerTool({
|
|
@@ -5312,9 +6916,31 @@ var registerAskAdvisorTool = ({
|
|
|
5312
6916
|
if (!(isSimpleMode() || session.canConsult(getAdvisorMaxCallsPerSession()))) {
|
|
5313
6917
|
throw new Error("Advisor call budget exhausted for this session.");
|
|
5314
6918
|
}
|
|
6919
|
+
const normalizedQuestion = normalizeScreeningQuestion(resolveAdvisorRequest(params.question));
|
|
6920
|
+
const screening = await screen(ctx, session, {
|
|
6921
|
+
draft: params.draft,
|
|
6922
|
+
force: params.force,
|
|
6923
|
+
question: resolveAdvisorRequest(params.question),
|
|
6924
|
+
signal
|
|
6925
|
+
});
|
|
6926
|
+
if (screening.decision === "skip") {
|
|
6927
|
+
const skipText = screeningSkipText(screening);
|
|
6928
|
+
return {
|
|
6929
|
+
content: [{ text: skipText, type: "text" }],
|
|
6930
|
+
details: {
|
|
6931
|
+
jev: {
|
|
6932
|
+
kind: screening.kind,
|
|
6933
|
+
reason: screening.reason,
|
|
6934
|
+
skipped: true
|
|
6935
|
+
},
|
|
6936
|
+
text: skipText
|
|
6937
|
+
}
|
|
6938
|
+
};
|
|
6939
|
+
}
|
|
5315
6940
|
claimTrackedHandoff(session, params.includeTrackedFiles);
|
|
5316
6941
|
if (!isSimpleMode()) {
|
|
5317
6942
|
session.consumeCall();
|
|
6943
|
+
session.resetTurnsSinceConsultation();
|
|
5318
6944
|
}
|
|
5319
6945
|
herdrAdvisorActivity.start();
|
|
5320
6946
|
let scoutDetails;
|
|
@@ -5347,7 +6973,7 @@ var registerAskAdvisorTool = ({
|
|
|
5347
6973
|
});
|
|
5348
6974
|
}, _id);
|
|
5349
6975
|
flushUpdate();
|
|
5350
|
-
session.issueAdvice(result.adviceId, result.markdown, result.trigger, Boolean(result.draftBytes));
|
|
6976
|
+
session.issueAdvice(result.adviceId, result.markdown, result.trigger, Boolean(result.draftBytes), normalizedQuestion);
|
|
5351
6977
|
session.recordInvocation({
|
|
5352
6978
|
cost: advisorUsageCost(result.usage),
|
|
5353
6979
|
executionEffect: "continued",
|
|
@@ -5408,6 +7034,9 @@ ${result.markdown}`,
|
|
|
5408
7034
|
draft: Type.Optional(Type.String({
|
|
5409
7035
|
description: "Concise untrusted draft for plan or completion review; claims are not verification evidence."
|
|
5410
7036
|
})),
|
|
7037
|
+
force: Type.Optional(Type.Boolean({
|
|
7038
|
+
description: "Set true only when you judge a decision genuinely material after a consultation was screened out; bypasses screening."
|
|
7039
|
+
})),
|
|
5411
7040
|
gitContext: Type.Optional(Type.Union([Type.Literal("none"), Type.Literal("summary"), Type.Literal("full")], {
|
|
5412
7041
|
description: "How much of the working tree to include. Use full when the review depends on the exact code changes, such as a completion review. Use summary for changed file names only, or none when the question is not about the current changes. The user's configured allowance is the ceiling and a larger request is narrowed to it."
|
|
5413
7042
|
})),
|
|
@@ -5520,6 +7149,7 @@ var handleAutomaticGate = async (pi, event, ctx, session, runGate, scoutStatus)
|
|
|
5520
7149
|
return failure.block ? { block: true, reason: failure.reason } : undefined;
|
|
5521
7150
|
}
|
|
5522
7151
|
session.consumeCall();
|
|
7152
|
+
session.resetTurnsSinceConsultation();
|
|
5523
7153
|
herdrAdvisorActivity.start();
|
|
5524
7154
|
let scoutDetails;
|
|
5525
7155
|
const scoutStatusToken = Symbol("automatic-gate-scout");
|
|
@@ -5719,6 +7349,30 @@ var registerToolRenderers = (pi) => {
|
|
|
5719
7349
|
renderScoutDetails(box, scout, Boolean(expanded), theme);
|
|
5720
7350
|
return box;
|
|
5721
7351
|
});
|
|
7352
|
+
pi.registerMessageRenderer?.("advisor-turn-gate-call", (message, _options, theme) => {
|
|
7353
|
+
const details = message.details;
|
|
7354
|
+
return renderAdvisorCallBox(details?.question, theme);
|
|
7355
|
+
});
|
|
7356
|
+
pi.registerMessageRenderer?.("advisor-turn-gate-result", (message, { expanded }, theme) => {
|
|
7357
|
+
const details = message.details;
|
|
7358
|
+
const box = new Box4(1, 1, (text) => theme.bg("customMessageBg", text));
|
|
7359
|
+
box.addChild(new Text7(theme.fg("warning", theme.bold("◆ ADVISOR · TURN REVIEW")), 0, 0));
|
|
7360
|
+
if (details?.advisor) {
|
|
7361
|
+
box.addChild(new Text7(theme.fg("dim", ` ${details.advisor}`), 0, 0));
|
|
7362
|
+
}
|
|
7363
|
+
if (getAdvisorSettings().showUsageDetails) {
|
|
7364
|
+
const usage = formatAdvisorUsage(details?.usage);
|
|
7365
|
+
if (usage) {
|
|
7366
|
+
box.addChild(new Text7(theme.fg("dim", ` Usage: ${usage}`), 0, 0));
|
|
7367
|
+
}
|
|
7368
|
+
}
|
|
7369
|
+
if (details?.text) {
|
|
7370
|
+
box.addChild(new Markdown5(adviceForDisplay(details.text, Boolean(expanded)), 0, 0, getMarkdownTheme5()));
|
|
7371
|
+
} else {
|
|
7372
|
+
box.addChild(new Text7(theme.fg("error", typeof message.content === "string" ? message.content : "Advisor turn review failed."), 0, 0));
|
|
7373
|
+
}
|
|
7374
|
+
return box;
|
|
7375
|
+
});
|
|
5722
7376
|
pi.registerMessageRenderer?.("advisor-loop-call", (message, _options, theme) => {
|
|
5723
7377
|
const details = message.details;
|
|
5724
7378
|
return renderAdvisorCallBox(details?.question, theme);
|
|
@@ -5754,12 +7408,20 @@ var registerAdvisorTool = (pi, session = advisorSessionState, dependencies = {})
|
|
|
5754
7408
|
reservedCalls: new Set,
|
|
5755
7409
|
runGate: dependencies.runGate ?? runAdvisorGate,
|
|
5756
7410
|
scoutStatus: dependencies.statusManager ?? new ScoutStatusManager,
|
|
7411
|
+
screen: dependencies.screen ?? screenConsultation,
|
|
5757
7412
|
session
|
|
5758
7413
|
};
|
|
5759
7414
|
registerToolRenderers(pi);
|
|
5760
7415
|
registerToolLifecycle(registration);
|
|
5761
7416
|
registerAskAdvisorTool(registration);
|
|
5762
7417
|
registerOutcomeTool(registration);
|
|
7418
|
+
registerJevTurnGate((event, handler) => pi.on(event, handler), {
|
|
7419
|
+
activeTools: () => pi.getActiveTools(),
|
|
7420
|
+
consult: registration.consult,
|
|
7421
|
+
...dependencies.turnGateDeps ? { deps: dependencies.turnGateDeps } : {},
|
|
7422
|
+
send: (message) => pi.sendMessage(message, { deliverAs: "steer" }),
|
|
7423
|
+
session
|
|
7424
|
+
});
|
|
5763
7425
|
};
|
|
5764
7426
|
|
|
5765
7427
|
// extensions/index.ts
|