pi-advisor-flow 0.7.0 → 0.8.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 +19 -18
- package/README.md +9 -8
- package/dist/index.js +1390 -1068
- package/extensions/index.ts +2 -1
- package/package.json +48 -35
- package/src/attachments.ts +11 -10
- package/src/commands/activation-preparation.ts +1 -0
- package/src/commands/activation.ts +2 -0
- package/src/commands/lifecycle.ts +1 -0
- package/src/commands/manual-command.ts +7 -1
- package/src/commands/manual-consultation.ts +102 -92
- package/src/commands/manual-progress.ts +7 -5
- package/src/commands/model-commands.ts +1 -1
- package/src/commands/model-options.ts +14 -3
- package/src/commands/model-picker.ts +1 -0
- package/src/commands/registration.ts +1 -0
- package/src/commands/renderers.ts +67 -55
- package/src/commands/runtime.ts +16 -14
- package/src/commands/settings-commands.ts +10 -4
- package/src/commands/settings-persistence.ts +16 -2
- package/src/commands/types.ts +1 -0
- package/src/commands.ts +0 -1
- package/src/config/args.ts +1 -1
- package/src/config/defaults.ts +8 -1
- package/src/config/schema.ts +42 -17
- package/src/config/state.ts +12 -3
- package/src/config/storage.ts +34 -23
- package/src/config/types.ts +16 -3
- package/src/config/validation.ts +33 -21
- package/src/config.ts +3 -0
- package/src/content-utils.ts +24 -4
- package/src/conversation.ts +16 -15
- package/src/git.ts +10 -3
- package/src/herdr-block.ts +83 -0
- package/src/herdr-shared.ts +108 -0
- package/src/herdr.ts +30 -192
- package/src/jev/client.ts +75 -85
- package/src/jev/failure.ts +19 -0
- package/src/jev/key-store.ts +26 -11
- package/src/jev/ledger.ts +58 -64
- package/src/jev/questions.ts +26 -23
- package/src/jev/state.ts +15 -12
- package/src/jev/transport.ts +8 -3
- package/src/model-stream.ts +35 -37
- package/src/outcomes.ts +15 -11
- package/src/pi-settings.ts +4 -9
- package/src/preferences.ts +4 -2
- package/src/redaction.ts +10 -12
- package/src/scout-context.ts +59 -60
- package/src/scout-curation.ts +4 -13
- package/src/scout-groups.ts +178 -175
- package/src/scout-protocol.ts +74 -70
- package/src/scout-reconstruct.ts +1 -1
- package/src/scout.ts +115 -73
- package/src/session-state.ts +64 -49
- package/src/tool-result-cap.ts +18 -17
- package/src/tools/consult-context.ts +7 -15
- package/src/tools/consultation.ts +14 -4
- package/src/tools/gate-policy.ts +1 -0
- package/src/tools/gate-protocol.ts +36 -23
- package/src/tools/jev-filter.ts +43 -41
- package/src/tools/jev-turn-gate.ts +29 -9
- package/src/tools/loop-gate.ts +31 -20
- package/src/tools/model-access.ts +46 -0
- package/src/tools/prompts.ts +20 -25
- package/src/tools/register-ask-advisor.ts +36 -16
- package/src/tools/register-lifecycle.ts +22 -0
- package/src/tools/register-outcome.ts +2 -0
- package/src/tools/register-renderers.ts +146 -128
- package/src/tools/registration.ts +12 -14
- package/src/tools/render-advisor-result.ts +35 -22
- package/src/tools/render-common.ts +16 -19
- package/src/tools/scout-status.ts +42 -27
- package/src/tools/types.ts +1 -0
- package/src/tools.ts +0 -1
- package/src/ui/jev-setup-submenu.ts +65 -59
- package/src/ui/manual-dialog-render.ts +1 -0
- package/src/ui/manual-dialog.ts +39 -43
- package/src/ui/masked-input.ts +5 -11
- package/src/ui/model-multi-selector.ts +9 -0
- package/src/ui/model-selector-adapter.ts +31 -0
- package/src/ui/model-selector.ts +14 -161
- package/src/ui/searchable-model-list.ts +230 -0
- package/src/ui/settings-formatting.ts +24 -14
- package/src/ui/settings-items.ts +38 -1
- package/src/ui/settings-list-adapter.ts +15 -12
- package/src/ui/settings-mutations.ts +157 -70
- package/src/ui/settings-selector.ts +18 -9
- package/src/ui/text-setting-submenu.ts +3 -6
- package/src/ui/types.ts +14 -0
- package/src/ui.ts +0 -1
- package/src/usage.ts +26 -19
package/dist/index.js
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
// src/config/types.ts
|
|
2
|
+
import {
|
|
3
|
+
DEFAULT_MAX_BYTES as PI_DEFAULT_MAX_BYTES,
|
|
4
|
+
DEFAULT_MAX_LINES as PI_DEFAULT_MAX_LINES
|
|
5
|
+
} from "@earendil-works/pi-coding-agent";
|
|
2
6
|
import {
|
|
3
7
|
DEFAULT_MAX_BYTES,
|
|
4
8
|
DEFAULT_MAX_LINES
|
|
5
9
|
} from "@earendil-works/pi-coding-agent";
|
|
6
10
|
var DEFAULT_CONTEXT_MAX_CHARS = 15000;
|
|
7
11
|
var MAX_CONTEXT_MAX_CHARS = Number.MAX_SAFE_INTEGER;
|
|
8
|
-
var DEFAULT_ADVISOR_TOOL_RESULT_MAX_LINES =
|
|
9
|
-
var DEFAULT_ADVISOR_TOOL_RESULT_MAX_BYTES =
|
|
12
|
+
var DEFAULT_ADVISOR_TOOL_RESULT_MAX_LINES = PI_DEFAULT_MAX_LINES;
|
|
13
|
+
var DEFAULT_ADVISOR_TOOL_RESULT_MAX_BYTES = PI_DEFAULT_MAX_BYTES;
|
|
10
14
|
var DEFAULT_ADVISOR_GIT_CONTEXT_MAX_CHARS = 20000;
|
|
11
15
|
var DEFAULT_JEV_MODEL = "jev-latest";
|
|
12
16
|
var DEFAULT_JEV_TIMEOUT_MS = 8000;
|
|
@@ -51,6 +55,7 @@ var advisorBlockOnBlockedRef = true;
|
|
|
51
55
|
var advisorAutoLoopGateRef = true;
|
|
52
56
|
var advisorLoopThresholdRef = 3;
|
|
53
57
|
var advisorMaxCallsPerSessionRef;
|
|
58
|
+
var advisorModelWhitelistRef = [];
|
|
54
59
|
var advisorSessionSummaryRef = false;
|
|
55
60
|
var simpleModeRef = false;
|
|
56
61
|
var alwaysOnRef = false;
|
|
@@ -129,6 +134,11 @@ var setAdvisorLoopThresholdRef = (value) => {
|
|
|
129
134
|
var setAdvisorMaxCallsPerSessionRef = (value) => {
|
|
130
135
|
advisorMaxCallsPerSessionRef = value;
|
|
131
136
|
};
|
|
137
|
+
var setAdvisorModelWhitelistRef = (models) => {
|
|
138
|
+
advisorModelWhitelistRef = [
|
|
139
|
+
...new Set(models.map((model) => model.trim()).filter(Boolean))
|
|
140
|
+
];
|
|
141
|
+
};
|
|
132
142
|
var setAdvisorSessionSummaryRef = (enabled) => {
|
|
133
143
|
advisorSessionSummaryRef = enabled;
|
|
134
144
|
};
|
|
@@ -241,6 +251,7 @@ var getAdvisorSettings = () => ({
|
|
|
241
251
|
jevTurnGateNoulThreshold: advisorJevTurnGateNoulThresholdRef,
|
|
242
252
|
loopThreshold: advisorLoopThresholdRef,
|
|
243
253
|
maxCallsPerSession: advisorMaxCallsPerSessionRef,
|
|
254
|
+
modelWhitelist: [...advisorModelWhitelistRef],
|
|
244
255
|
outcomeLogging: advisorOutcomeLoggingRef,
|
|
245
256
|
planGate: advisorPlanGateRef,
|
|
246
257
|
redactSecrets: advisorRedactSecretsRef,
|
|
@@ -261,17 +272,45 @@ var splitRef = (ref) => {
|
|
|
261
272
|
return i === -1 ? ["openai-codex", ref] : [ref.slice(0, i), ref.slice(i + 1)];
|
|
262
273
|
};
|
|
263
274
|
|
|
275
|
+
// src/content-utils.ts
|
|
276
|
+
var isRecord = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
277
|
+
var isRecordOf = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
278
|
+
var isString = (value) => typeof value === "string";
|
|
279
|
+
var isNumber = (value) => typeof value === "number";
|
|
280
|
+
var isBoolean = (value) => typeof value === "boolean";
|
|
281
|
+
var byteLength = (value) => Buffer.byteLength(value, "utf-8");
|
|
282
|
+
var contentParts = (content) => {
|
|
283
|
+
if (isString(content)) {
|
|
284
|
+
return [content];
|
|
285
|
+
}
|
|
286
|
+
return Array.isArray(content) ? content : [];
|
|
287
|
+
};
|
|
288
|
+
var capUtf8Bytes = (value, maxBytes) => {
|
|
289
|
+
let result = "";
|
|
290
|
+
let used = 0;
|
|
291
|
+
for (const character of value) {
|
|
292
|
+
const characterBytes = byteLength(character);
|
|
293
|
+
if (used + characterBytes > maxBytes) {
|
|
294
|
+
break;
|
|
295
|
+
}
|
|
296
|
+
result += character;
|
|
297
|
+
used += characterBytes;
|
|
298
|
+
}
|
|
299
|
+
return result;
|
|
300
|
+
};
|
|
301
|
+
|
|
264
302
|
// src/git.ts
|
|
265
303
|
import { execFileSync } from "node:child_process";
|
|
266
304
|
var GIT_CONTEXT_LEVELS = ["off", "summary", "full"];
|
|
267
|
-
var
|
|
305
|
+
var GIT_CONTEXT_LEVEL_SET = new Set(GIT_CONTEXT_LEVELS);
|
|
306
|
+
var isValidGitContextLevel = (value) => isString(value) && GIT_CONTEXT_LEVEL_SET.has(value);
|
|
268
307
|
var LEVEL_RANK = {
|
|
269
308
|
full: 2,
|
|
270
309
|
off: 0,
|
|
271
310
|
summary: 1
|
|
272
311
|
};
|
|
273
312
|
var clampGitContextLevel = (requested, allowed) => LEVEL_RANK[requested] <= LEVEL_RANK[allowed] ? requested : allowed;
|
|
274
|
-
var escapeRepositoryText = (value) => value.
|
|
313
|
+
var escapeRepositoryText = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">");
|
|
275
314
|
var TRUNCATION_NOTICE = `
|
|
276
315
|
[Repository context truncated: it exceeded the configured limit.]`;
|
|
277
316
|
var capRepositoryContext = (value, maxChars) => {
|
|
@@ -296,7 +335,7 @@ var deadlineRunner = () => {
|
|
|
296
335
|
}
|
|
297
336
|
return execFileSync("git", args, {
|
|
298
337
|
cwd,
|
|
299
|
-
encoding: "
|
|
338
|
+
encoding: "utf-8",
|
|
300
339
|
maxBuffer: GIT_MAX_BUFFER,
|
|
301
340
|
shell: false,
|
|
302
341
|
stdio: ["ignore", "pipe", "pipe"],
|
|
@@ -369,17 +408,21 @@ ${patch}` : "Patch: (no tracked-file content changes)");
|
|
|
369
408
|
|
|
370
409
|
// src/config/schema.ts
|
|
371
410
|
var configuredModelRef = (value) => value?.trim() || undefined;
|
|
411
|
+
var isNonEmptyModel = (model) => typeof model === "string" && model.trim().length > 0;
|
|
412
|
+
var isValidAdvisorModelWhitelist = (value) => Array.isArray(value) && value.every(isNonEmptyModel);
|
|
413
|
+
var ADVISOR_TOOL_POLICY_NAMES = new Set(ADVISOR_TOOL_POLICIES);
|
|
372
414
|
var isValidAdvisorToolPolicies = (value) => {
|
|
373
415
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
374
416
|
return false;
|
|
375
417
|
}
|
|
376
|
-
return Object.entries(value).every(([toolName, policy]) => toolName.trim().length > 0 &&
|
|
418
|
+
return Object.entries(value).every(([toolName, policy]) => toolName.trim().length > 0 && isString(policy) && ADVISOR_TOOL_POLICY_NAMES.has(policy));
|
|
377
419
|
};
|
|
378
420
|
var nonNegativeSafeInteger = (value) => typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
|
|
379
421
|
var isValidContextMaxChars = (value) => typeof value === "number" && Number.isSafeInteger(value) && value >= 0 && value <= MAX_CONTEXT_MAX_CHARS;
|
|
380
422
|
var isValidLoopThreshold = (value) => typeof value === "number" && Number.isSafeInteger(value) && value >= 2;
|
|
381
423
|
var isValidMaxCallsPerSession = (value) => nonNegativeSafeInteger(value);
|
|
382
|
-
var
|
|
424
|
+
var GATE_FAILURE_MODE_NAMES = new Set(GATE_FAILURE_MODES);
|
|
425
|
+
var isValidGateFailureMode = (value) => isString(value) && GATE_FAILURE_MODE_NAMES.has(value);
|
|
383
426
|
var isValidToolResultMaxLines = (value) => nonNegativeSafeInteger(value);
|
|
384
427
|
var isValidToolResultMaxBytes = (value) => nonNegativeSafeInteger(value);
|
|
385
428
|
var isValidJevTimeoutMs = (value) => typeof value === "number" && Number.isSafeInteger(value) && value >= 1;
|
|
@@ -387,7 +430,8 @@ var isValidJevDigestMaxChars = (value) => nonNegativeSafeInteger(value);
|
|
|
387
430
|
var isValidJevPricePerMtok = (value) => typeof value === "number" && Number.isFinite(value) && value > 0;
|
|
388
431
|
var isValidJevSkipConfidence = (value) => typeof value === "number" && Number.isFinite(value) && value >= 0.5 && value <= 1;
|
|
389
432
|
var isValidJevNoulMargin = (value) => typeof value === "number" && Number.isFinite(value) && value >= 0 && value <= 0.5;
|
|
390
|
-
var
|
|
433
|
+
var JEV_TRANSPORT_NAMES = new Set(JEV_TRANSPORTS);
|
|
434
|
+
var isValidJevTransport = (value) => isString(value) && JEV_TRANSPORT_NAMES.has(value);
|
|
391
435
|
var CONFIG_SCHEMA = {
|
|
392
436
|
advisor: {
|
|
393
437
|
accepted: "a provider/model string",
|
|
@@ -546,6 +590,13 @@ var CONFIG_SCHEMA = {
|
|
|
546
590
|
type: "number",
|
|
547
591
|
validate: isValidMaxCallsPerSession
|
|
548
592
|
},
|
|
593
|
+
advisorModelWhitelist: {
|
|
594
|
+
accepted: "an array of non-empty provider/model strings",
|
|
595
|
+
current: () => [...advisorModelWhitelistRef],
|
|
596
|
+
persisted: true,
|
|
597
|
+
type: "array",
|
|
598
|
+
validate: isValidAdvisorModelWhitelist
|
|
599
|
+
},
|
|
549
600
|
advisorOutcomeLogging: {
|
|
550
601
|
accepted: "true or false",
|
|
551
602
|
current: () => advisorOutcomeLoggingRef,
|
|
@@ -660,12 +711,13 @@ var CONFIG_SCHEMA = {
|
|
|
660
711
|
type: "boolean"
|
|
661
712
|
}
|
|
662
713
|
};
|
|
663
|
-
var
|
|
714
|
+
var configKeys = Object.keys(CONFIG_SCHEMA);
|
|
715
|
+
var SAVED_CONFIG_KEYS = configKeys.filter((key) => CONFIG_SCHEMA[key].persisted);
|
|
664
716
|
var SCHEMA_BY_KEY = CONFIG_SCHEMA;
|
|
665
717
|
|
|
666
718
|
// src/config/validation.ts
|
|
667
|
-
var CONFIG_KEYS = new Set(
|
|
668
|
-
var keysOfType = (type) =>
|
|
719
|
+
var CONFIG_KEYS = new Set(configKeys);
|
|
720
|
+
var keysOfType = (type) => configKeys.filter((key) => SCHEMA_BY_KEY[key].type === type);
|
|
669
721
|
var BOOLEAN_CONFIG_KEYS = keysOfType("boolean");
|
|
670
722
|
var STRING_CONFIG_KEYS = keysOfType("string");
|
|
671
723
|
var invalidConfigValue = (path, key, accepted) => {
|
|
@@ -674,14 +726,14 @@ var invalidConfigValue = (path, key, accepted) => {
|
|
|
674
726
|
var unknownConfigKeys = (config) => Object.keys(config).filter((key) => !CONFIG_KEYS.has(key));
|
|
675
727
|
var validateStringValues = (config, path) => {
|
|
676
728
|
for (const key of STRING_CONFIG_KEYS) {
|
|
677
|
-
if (config[key] !== undefined &&
|
|
729
|
+
if (config[key] !== undefined && !isString(config[key])) {
|
|
678
730
|
invalidConfigValue(path, key, SCHEMA_BY_KEY[key].accepted);
|
|
679
731
|
}
|
|
680
732
|
}
|
|
681
733
|
};
|
|
682
734
|
var validateBooleanValues = (config, path) => {
|
|
683
735
|
for (const key of BOOLEAN_CONFIG_KEYS) {
|
|
684
|
-
if (config[key] !== undefined &&
|
|
736
|
+
if (config[key] !== undefined && !isBoolean(config[key])) {
|
|
685
737
|
invalidConfigValue(path, key, SCHEMA_BY_KEY[key].accepted);
|
|
686
738
|
}
|
|
687
739
|
}
|
|
@@ -694,6 +746,14 @@ var validateNumericValues = (config, path) => {
|
|
|
694
746
|
}
|
|
695
747
|
}
|
|
696
748
|
};
|
|
749
|
+
var validateArrayValues = (config, path) => {
|
|
750
|
+
for (const key of keysOfType("array")) {
|
|
751
|
+
const isValid = SCHEMA_BY_KEY[key].validate;
|
|
752
|
+
if (config[key] !== undefined && isValid !== undefined && !isValid(config[key])) {
|
|
753
|
+
invalidConfigValue(path, key, SCHEMA_BY_KEY[key].accepted);
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
};
|
|
697
757
|
var validateEnumValues = (config, path) => {
|
|
698
758
|
for (const key of keysOfType("enum")) {
|
|
699
759
|
const isValid = SCHEMA_BY_KEY[key].validate;
|
|
@@ -711,20 +771,20 @@ var validateObjectValues = (config, path) => {
|
|
|
711
771
|
}
|
|
712
772
|
};
|
|
713
773
|
var validateConfig = (value, path = "advisor.json") => {
|
|
714
|
-
if (!
|
|
774
|
+
if (!isRecord(value)) {
|
|
715
775
|
throw new TypeError(`Invalid advisor configuration at ${path}: expected a JSON object.`);
|
|
716
776
|
}
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
validateEnumValues(
|
|
777
|
+
validateStringValues(value, path);
|
|
778
|
+
validateBooleanValues(value, path);
|
|
779
|
+
validateNumericValues(value, path);
|
|
780
|
+
validateObjectValues(value, path);
|
|
781
|
+
validateArrayValues(value, path);
|
|
782
|
+
validateEnumValues(value, path);
|
|
723
783
|
return true;
|
|
724
784
|
};
|
|
725
785
|
|
|
726
786
|
// src/config/args.ts
|
|
727
|
-
var ARGUMENT_WHITESPACE = /\s
|
|
787
|
+
var ARGUMENT_WHITESPACE = /\s+/u;
|
|
728
788
|
var parseArgs = (args) => {
|
|
729
789
|
let nextExecutor = executorRef;
|
|
730
790
|
let nextAdvisor = advisorRef;
|
|
@@ -753,10 +813,7 @@ var parseArgs = (args) => {
|
|
|
753
813
|
// src/config/storage.ts
|
|
754
814
|
import { existsSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
755
815
|
import { join } from "node:path";
|
|
756
|
-
import {
|
|
757
|
-
CONFIG_DIR_NAME,
|
|
758
|
-
getAgentDir
|
|
759
|
-
} from "@earendil-works/pi-coding-agent";
|
|
816
|
+
import { CONFIG_DIR_NAME, getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
760
817
|
|
|
761
818
|
// src/config/defaults.ts
|
|
762
819
|
var resetDefaults = () => {
|
|
@@ -775,6 +832,7 @@ var resetDefaults = () => {
|
|
|
775
832
|
setAdvisorAutoLoopGateRef(true);
|
|
776
833
|
setAdvisorLoopThresholdRef(3);
|
|
777
834
|
setAdvisorMaxCallsPerSessionRef(undefined);
|
|
835
|
+
setAdvisorModelWhitelistRef([]);
|
|
778
836
|
setAdvisorSessionSummaryRef(false);
|
|
779
837
|
setSimpleModeRef(false);
|
|
780
838
|
setAlwaysOnRef(false);
|
|
@@ -830,6 +888,7 @@ var applyConfig = (config) => {
|
|
|
830
888
|
applyOptionalConfig(config, "advisorAutoLoopGate", setAdvisorAutoLoopGateRef);
|
|
831
889
|
applyOptionalConfig(config, "advisorLoopThreshold", setAdvisorLoopThresholdRef);
|
|
832
890
|
applyOptionalConfig(config, "advisorMaxCallsPerSession", setAdvisorMaxCallsPerSessionRef);
|
|
891
|
+
applyOptionalConfig(config, "advisorModelWhitelist", setAdvisorModelWhitelistRef);
|
|
833
892
|
applyOptionalConfig(config, "advisorSessionSummary", setAdvisorSessionSummaryRef);
|
|
834
893
|
applyOptionalConfig(config, "advisorScoutEnabled", setAdvisorScoutEnabledRef);
|
|
835
894
|
applyOptionalConfig(config, "showUsageDetails", setShowUsageDetailsRef);
|
|
@@ -866,14 +925,16 @@ var sameConfigValue = (left, right) => JSON.stringify(left) === JSON.stringify(r
|
|
|
866
925
|
var RESERVED_ADVISOR_JSON_KEYS = new Set(["typesafe_api_key"]);
|
|
867
926
|
var readExistingConfig = (path) => {
|
|
868
927
|
try {
|
|
869
|
-
const parsed = JSON.parse(readFileSync(path, "
|
|
870
|
-
return
|
|
928
|
+
const parsed = JSON.parse(readFileSync(path, "utf-8"));
|
|
929
|
+
return isRecord(parsed) ? parsed : {};
|
|
871
930
|
} catch {
|
|
872
931
|
return {};
|
|
873
932
|
}
|
|
874
933
|
};
|
|
875
934
|
var shouldPersistConfigKey = (key, persistAdvisor, persistExecutor) => (key !== "advisor" || persistAdvisor) && (key !== "executor" || persistExecutor);
|
|
876
|
-
var applyChangedConfigValues = (
|
|
935
|
+
var applyChangedConfigValues = (existing, current, changedKeys, persistAdvisor, persistExecutor) => {
|
|
936
|
+
const dropped = new Set;
|
|
937
|
+
const data = { ...existing };
|
|
877
938
|
for (const key of changedKeys) {
|
|
878
939
|
if (!shouldPersistConfigKey(key, persistAdvisor, persistExecutor)) {
|
|
879
940
|
continue;
|
|
@@ -881,16 +942,26 @@ var applyChangedConfigValues = (data, current, changedKeys, persistAdvisor, pers
|
|
|
881
942
|
const value = current[key];
|
|
882
943
|
const isEmptyModelRef = (key === "advisor" || key === "executor") && !value;
|
|
883
944
|
if (value === undefined || isEmptyModelRef) {
|
|
884
|
-
|
|
945
|
+
dropped.add(key);
|
|
885
946
|
} else {
|
|
886
947
|
data[key] = value;
|
|
887
948
|
}
|
|
888
949
|
}
|
|
950
|
+
if (dropped.size === 0) {
|
|
951
|
+
return data;
|
|
952
|
+
}
|
|
953
|
+
const retained = {};
|
|
954
|
+
for (const key of Object.keys(data)) {
|
|
955
|
+
if (!dropped.has(key)) {
|
|
956
|
+
retained[key] = data[key];
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
return retained;
|
|
889
960
|
};
|
|
890
961
|
var loadedConfigState;
|
|
891
962
|
var loadedConfigPath;
|
|
892
963
|
var readConfig = (path) => {
|
|
893
|
-
const config = JSON.parse(readFileSync(path, "
|
|
964
|
+
const config = JSON.parse(readFileSync(path, "utf-8"));
|
|
894
965
|
validateConfig(config, path);
|
|
895
966
|
return config;
|
|
896
967
|
};
|
|
@@ -948,17 +1019,16 @@ var saveConfig = (_ctx, options = {}) => {
|
|
|
948
1019
|
if (changedKeys.length === 0) {
|
|
949
1020
|
return path;
|
|
950
1021
|
}
|
|
951
|
-
const data =
|
|
952
|
-
applyChangedConfigValues(data, current, changedKeys, persistAdvisor, persistExecutor);
|
|
1022
|
+
const data = applyChangedConfigValues(existing, current, changedKeys, persistAdvisor, persistExecutor);
|
|
953
1023
|
writeFileSync(path, `${JSON.stringify(data, null, 2)}
|
|
954
1024
|
`);
|
|
955
1025
|
resetConfigCache();
|
|
956
1026
|
const nextLoadedState = { ...current };
|
|
957
1027
|
if (!persistAdvisor) {
|
|
958
|
-
nextLoadedState.advisor = baseline ? baseline.advisor : configuredModelRef(
|
|
1028
|
+
nextLoadedState.advisor = baseline ? baseline.advisor : configuredModelRef(isString(existing.advisor) ? existing.advisor : undefined);
|
|
959
1029
|
}
|
|
960
1030
|
if (!persistExecutor) {
|
|
961
|
-
nextLoadedState.executor = baseline ? baseline.executor : configuredModelRef(
|
|
1031
|
+
nextLoadedState.executor = baseline ? baseline.executor : configuredModelRef(isString(existing.executor) ? existing.executor : undefined);
|
|
962
1032
|
}
|
|
963
1033
|
loadedConfigState = nextLoadedState;
|
|
964
1034
|
loadedConfigPath = path;
|
|
@@ -998,7 +1068,7 @@ var selectedEffort = (choice) => {
|
|
|
998
1068
|
return effort === DEFAULT_EFFORT_LEVEL ? undefined : effort;
|
|
999
1069
|
};
|
|
1000
1070
|
var ADVISOR_ACTIVATION_EXPLANATION = "The Advisor is a second-opinion model that reviews the Executor's context and returns risks, alternatives, and verification steps without changing files or running tools.";
|
|
1001
|
-
var ARGUMENT_WHITESPACE2 = /\s
|
|
1071
|
+
var ARGUMENT_WHITESPACE2 = /\s+/u;
|
|
1002
1072
|
var hasModelOverride = (args, key) => args.trim().split(ARGUMENT_WHITESPACE2).some((token) => {
|
|
1003
1073
|
const [tokenKey, value] = token.split("=");
|
|
1004
1074
|
return tokenKey === key && Boolean(value);
|
|
@@ -1045,11 +1115,18 @@ var findConfiguredModel = (ctx, ref) => {
|
|
|
1045
1115
|
return ctx.modelRegistry.find(provider, modelId);
|
|
1046
1116
|
};
|
|
1047
1117
|
var getAvailableModelRefs = (ctx) => {
|
|
1048
|
-
if (
|
|
1118
|
+
if (!ctx.modelRegistry.getAvailable) {
|
|
1049
1119
|
return;
|
|
1050
1120
|
}
|
|
1051
1121
|
return ctx.modelRegistry.getAvailable().map((model) => `${model.provider}/${model.id}`);
|
|
1052
1122
|
};
|
|
1123
|
+
var getConfiguredModelRefs = (ctx) => {
|
|
1124
|
+
const registry = ctx.modelRegistry;
|
|
1125
|
+
const models = registry?.getAvailable ? registry.getAvailable() : [];
|
|
1126
|
+
return [
|
|
1127
|
+
...new Set(models.map((model) => `${model.provider}/${model.id}`))
|
|
1128
|
+
].toSorted((left, right) => left.localeCompare(right));
|
|
1129
|
+
};
|
|
1053
1130
|
var isSelectableModel = (ctx, ref, availableRefs) => {
|
|
1054
1131
|
if (!ref) {
|
|
1055
1132
|
return false;
|
|
@@ -1084,18 +1161,39 @@ var planActivationModels = (ctx, executor, advisor, pendingExecutorRef, persiste
|
|
|
1084
1161
|
};
|
|
1085
1162
|
};
|
|
1086
1163
|
|
|
1087
|
-
// src/ui/model-selector.ts
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
}
|
|
1164
|
+
// src/ui/model-selector-adapter.ts
|
|
1165
|
+
class ModelSelectorAdapter {
|
|
1166
|
+
list;
|
|
1167
|
+
constructor(list) {
|
|
1168
|
+
this.list = list;
|
|
1169
|
+
}
|
|
1170
|
+
get focused() {
|
|
1171
|
+
return this.list.focused;
|
|
1172
|
+
}
|
|
1173
|
+
set focused(value) {
|
|
1174
|
+
this.list.focused = value;
|
|
1175
|
+
}
|
|
1176
|
+
invalidate() {
|
|
1177
|
+
this.list.invalidate();
|
|
1178
|
+
}
|
|
1179
|
+
render(width) {
|
|
1180
|
+
return this.list.render(width);
|
|
1181
|
+
}
|
|
1182
|
+
handleInput(keyData) {
|
|
1183
|
+
this.list.handleInput(keyData);
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1187
|
+
// src/ui/searchable-model-list.ts
|
|
1188
|
+
import { fuzzyFilter, Input, truncateToWidth } from "@earendil-works/pi-tui";
|
|
1093
1189
|
|
|
1094
|
-
class
|
|
1190
|
+
class SearchableModelList {
|
|
1095
1191
|
tui;
|
|
1096
1192
|
searchInput;
|
|
1097
1193
|
allOptions;
|
|
1098
1194
|
currentOption;
|
|
1195
|
+
multiSelect;
|
|
1196
|
+
selected = new Set;
|
|
1099
1197
|
filteredOptions;
|
|
1100
1198
|
selectedIndex = 0;
|
|
1101
1199
|
title;
|
|
@@ -1112,13 +1210,30 @@ class SearchableModelSelector {
|
|
|
1112
1210
|
this.searchInput.focused = val;
|
|
1113
1211
|
}
|
|
1114
1212
|
constructor(options) {
|
|
1213
|
+
this.multiSelect = options.multiSelect;
|
|
1115
1214
|
this.tui = options.tui;
|
|
1116
1215
|
this.title = options.title;
|
|
1117
|
-
|
|
1118
|
-
this.
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1216
|
+
const [requestedCurrentOption] = options.currentOptions;
|
|
1217
|
+
this.currentOption = !this.multiSelect && requestedCurrentOption && options.allOptions.includes(requestedCurrentOption) ? requestedCurrentOption : undefined;
|
|
1218
|
+
let allOptions;
|
|
1219
|
+
if (this.multiSelect) {
|
|
1220
|
+
allOptions = [...new Set(options.allOptions)].toSorted((left, right) => left.localeCompare(right));
|
|
1221
|
+
} else if (this.currentOption) {
|
|
1222
|
+
allOptions = [
|
|
1223
|
+
this.currentOption,
|
|
1224
|
+
...options.allOptions.filter((item) => item !== this.currentOption)
|
|
1225
|
+
];
|
|
1226
|
+
} else {
|
|
1227
|
+
allOptions = [...options.allOptions];
|
|
1228
|
+
}
|
|
1229
|
+
this.allOptions = allOptions;
|
|
1230
|
+
if (this.multiSelect) {
|
|
1231
|
+
for (const value of options.currentOptions) {
|
|
1232
|
+
if (this.allOptions.includes(value)) {
|
|
1233
|
+
this.selected.add(value);
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
1236
|
+
}
|
|
1122
1237
|
this.theme = options.theme;
|
|
1123
1238
|
this.keybindings = options.keybindings;
|
|
1124
1239
|
this.onSelect = options.onSelect;
|
|
@@ -1133,8 +1248,7 @@ class SearchableModelSelector {
|
|
|
1133
1248
|
const lines = [this.theme.fg("border", "─".repeat(width))];
|
|
1134
1249
|
lines.push(` ${this.theme.fg("accent", this.theme.bold(this.title))}`);
|
|
1135
1250
|
const inputLines = this.searchInput.render(Math.max(1, width - 10));
|
|
1136
|
-
lines.push(` ${this.theme.fg("accent", "Search: ")}${inputLines[0] || ""}
|
|
1137
|
-
lines.push("");
|
|
1251
|
+
lines.push(` ${this.theme.fg("accent", "Search: ")}${inputLines[0] || ""}`, "");
|
|
1138
1252
|
const query = this.searchInput.getValue().trim();
|
|
1139
1253
|
this.filteredOptions = query ? fuzzyFilter(this.allOptions, query, (item) => item) : this.allOptions;
|
|
1140
1254
|
this.selectedIndex = Math.min(this.selectedIndex, Math.max(0, this.filteredOptions.length - 1));
|
|
@@ -1146,21 +1260,13 @@ class SearchableModelSelector {
|
|
|
1146
1260
|
const startIndex = Math.max(0, Math.min(this.selectedIndex - Math.floor(maxVisible / 2), total - maxVisible));
|
|
1147
1261
|
const endIndex = Math.min(startIndex + maxVisible, total);
|
|
1148
1262
|
for (let i = startIndex;i < endIndex; i += 1) {
|
|
1149
|
-
|
|
1150
|
-
const tick = item === this.currentOption ? "✓ " : " ";
|
|
1151
|
-
if (i === this.selectedIndex) {
|
|
1152
|
-
lines.push(` ${this.theme.fg("accent", "→ ")}${this.theme.fg("accent", `${tick}${item}`)}`);
|
|
1153
|
-
} else {
|
|
1154
|
-
lines.push(` ${this.theme.fg("text", `${tick}${item}`)}`);
|
|
1155
|
-
}
|
|
1263
|
+
lines.push(this.renderOption(this.filteredOptions[i], i));
|
|
1156
1264
|
}
|
|
1157
1265
|
if (total > maxVisible) {
|
|
1158
|
-
lines.push(
|
|
1266
|
+
lines.push(` ${this.theme.fg("muted", ` (${this.selectedIndex + 1}/${total})`)}`);
|
|
1159
1267
|
}
|
|
1160
1268
|
}
|
|
1161
|
-
lines.push("");
|
|
1162
|
-
lines.push(` ${this.theme.fg("dim", "Type to search · ↑↓: navigate · Enter: select · Esc: cancel")}`);
|
|
1163
|
-
lines.push(this.theme.fg("border", "─".repeat(width)));
|
|
1269
|
+
lines.push("", ` ${this.theme.fg("dim", this.interactionHint())}`, this.theme.fg("border", "─".repeat(width)));
|
|
1164
1270
|
return lines.map((line) => truncateToWidth(line, width));
|
|
1165
1271
|
}
|
|
1166
1272
|
handleInput(keyData) {
|
|
@@ -1172,10 +1278,27 @@ class SearchableModelSelector {
|
|
|
1172
1278
|
this.moveSelection(1);
|
|
1173
1279
|
return;
|
|
1174
1280
|
}
|
|
1281
|
+
if (this.multiSelect && keyData === " ") {
|
|
1282
|
+
const item = this.filteredOptions[this.selectedIndex];
|
|
1283
|
+
if (item) {
|
|
1284
|
+
if (this.selected.has(item)) {
|
|
1285
|
+
this.selected.delete(item);
|
|
1286
|
+
} else {
|
|
1287
|
+
this.selected.add(item);
|
|
1288
|
+
}
|
|
1289
|
+
}
|
|
1290
|
+
this.tui.requestRender();
|
|
1291
|
+
return;
|
|
1292
|
+
}
|
|
1175
1293
|
if (this.matchesAction(keyData, "tui.select.confirm", `
|
|
1176
1294
|
`) || keyData === "\r") {
|
|
1177
|
-
if (this.
|
|
1178
|
-
this.onSelect(this.
|
|
1295
|
+
if (this.multiSelect) {
|
|
1296
|
+
this.onSelect(this.allOptions.filter((item) => this.selected.has(item)));
|
|
1297
|
+
} else {
|
|
1298
|
+
const selectedOption = this.filteredOptions[this.selectedIndex];
|
|
1299
|
+
if (selectedOption !== undefined) {
|
|
1300
|
+
this.onSelect([selectedOption]);
|
|
1301
|
+
}
|
|
1179
1302
|
}
|
|
1180
1303
|
return;
|
|
1181
1304
|
}
|
|
@@ -1187,6 +1310,19 @@ class SearchableModelSelector {
|
|
|
1187
1310
|
this.selectedIndex = 0;
|
|
1188
1311
|
this.tui.requestRender();
|
|
1189
1312
|
}
|
|
1313
|
+
interactionHint() {
|
|
1314
|
+
return this.multiSelect ? "Type to search · ↑↓: navigate · Space: toggle · Enter: apply · Esc: cancel" : "Type to search · ↑↓: navigate · Enter: select · Esc: cancel";
|
|
1315
|
+
}
|
|
1316
|
+
renderOption(item, index) {
|
|
1317
|
+
let tick = " ";
|
|
1318
|
+
if (this.multiSelect ? this.selected.has(item) : item === this.currentOption) {
|
|
1319
|
+
tick = "✓ ";
|
|
1320
|
+
}
|
|
1321
|
+
if (index === this.selectedIndex) {
|
|
1322
|
+
return ` ${this.theme.fg("accent", `→ ${tick}${item}`)}`;
|
|
1323
|
+
}
|
|
1324
|
+
return ` ${this.theme.fg("text", `${tick}${item}`)}`;
|
|
1325
|
+
}
|
|
1190
1326
|
matchesAction(keyData, action, fallback) {
|
|
1191
1327
|
return this.keybindings.matches(keyData, action) || keyData === fallback;
|
|
1192
1328
|
}
|
|
@@ -1206,6 +1342,22 @@ class SearchableModelSelector {
|
|
|
1206
1342
|
}
|
|
1207
1343
|
}
|
|
1208
1344
|
|
|
1345
|
+
// src/ui/model-selector.ts
|
|
1346
|
+
class SearchableModelSelector extends ModelSelectorAdapter {
|
|
1347
|
+
constructor(options) {
|
|
1348
|
+
super(new SearchableModelList({
|
|
1349
|
+
...options,
|
|
1350
|
+
currentOptions: options.currentOption ? [options.currentOption] : [],
|
|
1351
|
+
multiSelect: false,
|
|
1352
|
+
onSelect: ([value]) => {
|
|
1353
|
+
if (value !== undefined) {
|
|
1354
|
+
options.onSelect(value);
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
}));
|
|
1358
|
+
}
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1209
1361
|
// src/commands/model-picker.ts
|
|
1210
1362
|
var selectAdvisorModels = async (ctx, options) => {
|
|
1211
1363
|
if (!ctx.hasUI) {
|
|
@@ -1268,43 +1420,20 @@ var selectAdvisorModels = async (ctx, options) => {
|
|
|
1268
1420
|
return { advisor, advisorEffort, executor, executorEffort };
|
|
1269
1421
|
};
|
|
1270
1422
|
|
|
1271
|
-
// src/herdr.ts
|
|
1423
|
+
// src/herdr-shared.ts
|
|
1272
1424
|
import net from "node:net";
|
|
1273
1425
|
|
|
1274
|
-
// src/content-utils.ts
|
|
1275
|
-
var isRecord = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
1276
|
-
var byteLength = (value) => Buffer.byteLength(value, "utf8");
|
|
1277
|
-
var contentParts = (content) => {
|
|
1278
|
-
if (typeof content === "string") {
|
|
1279
|
-
return [content];
|
|
1280
|
-
}
|
|
1281
|
-
return Array.isArray(content) ? content : [];
|
|
1282
|
-
};
|
|
1283
|
-
var capUtf8Bytes = (value, maxBytes) => {
|
|
1284
|
-
let result = "";
|
|
1285
|
-
let used = 0;
|
|
1286
|
-
for (const character of value) {
|
|
1287
|
-
const characterBytes = byteLength(character);
|
|
1288
|
-
if (used + characterBytes > maxBytes) {
|
|
1289
|
-
break;
|
|
1290
|
-
}
|
|
1291
|
-
result += character;
|
|
1292
|
-
used += characterBytes;
|
|
1293
|
-
}
|
|
1294
|
-
return result;
|
|
1295
|
-
};
|
|
1296
|
-
|
|
1297
1426
|
// src/redaction.ts
|
|
1298
1427
|
var REDACTION_MARKER = "[REDACTED SECRET]";
|
|
1299
|
-
var PEM_BEGIN_PATTERN = /-----BEGIN(?: [A-Z0-9]+)? PRIVATE KEY-----/
|
|
1300
|
-
var PEM_END_PATTERN = /-----END(?: [A-Z0-9]+)? PRIVATE KEY-----/
|
|
1428
|
+
var PEM_BEGIN_PATTERN = /-----BEGIN(?: [A-Z0-9]+)? PRIVATE KEY-----/giu;
|
|
1429
|
+
var PEM_END_PATTERN = /-----END(?: [A-Z0-9]+)? PRIVATE KEY-----/iu;
|
|
1301
1430
|
var SECRET_PATTERNS = [
|
|
1302
|
-
/-----BEGIN(?: [A-Z0-9]+)? PRIVATE KEY-----[\s\S]*?-----END(?: [A-Z0-9]+)? PRIVATE KEY-----/
|
|
1303
|
-
/\bBearer\s+[A-Za-z0-9._~+/=-]{8,}/
|
|
1304
|
-
/\b(?:api[_-]?key|token|secret|password|passwd)\s*[:=]\s*(?:"[^"]*"|'[^']*'|[^\s"'&,;)}\]]+)/
|
|
1305
|
-
/([a-z][a-z0-9+.-]*:\/\/)[^\s/@:]+:[^\s/@]+@/
|
|
1306
|
-
/\b(?:AKIA|ASIA)[A-Z0-9]{16}\b/
|
|
1307
|
-
/\b(?:aws_secret_access_key|aws_session_token)\s*[:=]\s*[^\s"'&,;)}\]]+/
|
|
1431
|
+
/-----BEGIN(?: [A-Z0-9]+)? PRIVATE KEY-----[\s\S]*?-----END(?: [A-Z0-9]+)? PRIVATE KEY-----/giu,
|
|
1432
|
+
/\bBearer\s+[A-Za-z0-9._~+/=-]{8,}/giu,
|
|
1433
|
+
/\b(?:api[_-]?key|token|secret|password|passwd)\s*[:=]\s*(?:"[^"]*"|'[^']*'|[^\s"'&,;)}\]]+)/giu,
|
|
1434
|
+
/(?<scheme>[a-z][a-z0-9+.-]*:\/\/)[^\s/@:]+:[^\s/@]+@/giu,
|
|
1435
|
+
/\b(?:AKIA|ASIA)[A-Z0-9]{16}\b/gu,
|
|
1436
|
+
/\b(?:aws_secret_access_key|aws_session_token)\s*[:=]\s*[^\s"'&,;)}\]]+/giu
|
|
1308
1437
|
];
|
|
1309
1438
|
var redactUnterminatedPem = (value) => {
|
|
1310
1439
|
const begins = [...value.matchAll(PEM_BEGIN_PATTERN)];
|
|
@@ -1318,7 +1447,7 @@ var redactUnterminatedPem = (value) => {
|
|
|
1318
1447
|
var redactSecrets = (value) => {
|
|
1319
1448
|
let redacted = redactUnterminatedPem(value);
|
|
1320
1449
|
for (const pattern of SECRET_PATTERNS) {
|
|
1321
|
-
redacted = redacted.replace(pattern, (_match, scheme) =>
|
|
1450
|
+
redacted = redacted.replace(pattern, (_match, scheme) => isString(scheme) ? `${scheme}${REDACTION_MARKER}@` : REDACTION_MARKER);
|
|
1322
1451
|
}
|
|
1323
1452
|
return redacted;
|
|
1324
1453
|
};
|
|
@@ -1327,11 +1456,11 @@ var redactAndCapText = (value, maxBytes, redact = true) => {
|
|
|
1327
1456
|
return capUtf8Bytes(source, maxBytes);
|
|
1328
1457
|
};
|
|
1329
1458
|
|
|
1330
|
-
// src/herdr.ts
|
|
1459
|
+
// src/herdr-shared.ts
|
|
1331
1460
|
var HERDR_NOTIFICATION_METHOD = "notification.show";
|
|
1332
1461
|
var SOURCE = "pi-advisor:advisor-activity";
|
|
1333
|
-
var BLOCK_SOURCE = "pi-advisor:advisor-block";
|
|
1334
1462
|
var NOTIFICATION_SOURCE = "pi-advisor:advisor-notification";
|
|
1463
|
+
var BLOCK_SOURCE = "pi-advisor:advisor-block";
|
|
1335
1464
|
var HERDR_PI_SOURCE = "herdr:pi";
|
|
1336
1465
|
var sequence = Date.now() * 1000;
|
|
1337
1466
|
var nextSequence = () => {
|
|
@@ -1348,17 +1477,7 @@ var safeEmitBlocked = (active, label = "Advisor blocked") => {
|
|
|
1348
1477
|
} catch {}
|
|
1349
1478
|
};
|
|
1350
1479
|
var isControlCharacter = (character) => character <= "\x1F" || character === "";
|
|
1351
|
-
var cleanNotification = (value, max) => [...redactSecrets(value)].map((character) => isControlCharacter(character) ? " " : character).join("").
|
|
1352
|
-
var createHerdrNotificationRequest = (title, body) => ({
|
|
1353
|
-
id: `${NOTIFICATION_SOURCE}:${nextSequence()}`,
|
|
1354
|
-
method: HERDR_NOTIFICATION_METHOD,
|
|
1355
|
-
params: {
|
|
1356
|
-
body: cleanNotification(body, 240),
|
|
1357
|
-
position: "top-left",
|
|
1358
|
-
sound: "request",
|
|
1359
|
-
title: cleanNotification(title, 80)
|
|
1360
|
-
}
|
|
1361
|
-
});
|
|
1480
|
+
var cleanNotification = (value, max) => [...redactSecrets(value)].map((character) => isControlCharacter(character) ? " " : character).join("").replaceAll(/\s+/gu, " ").trim().slice(0, max);
|
|
1362
1481
|
var sendToHerdr = (request) => {
|
|
1363
1482
|
if (process.env.HERDR_ENV !== "1") {
|
|
1364
1483
|
return;
|
|
@@ -1378,61 +1497,18 @@ var sendToHerdr = (request) => {
|
|
|
1378
1497
|
socket.once("error", () => socket.destroy());
|
|
1379
1498
|
socket.once("close", () => clearTimeout(timeout));
|
|
1380
1499
|
};
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
}
|
|
1390
|
-
start() {
|
|
1391
|
-
if (!this.enabled()) {
|
|
1392
|
-
return;
|
|
1393
|
-
}
|
|
1394
|
-
this.#activeConsultations += 1;
|
|
1395
|
-
if (this.#activeConsultations === 1) {
|
|
1396
|
-
this.safeReport(false);
|
|
1397
|
-
}
|
|
1398
|
-
}
|
|
1399
|
-
finish() {
|
|
1400
|
-
if (this.#activeConsultations === 0) {
|
|
1401
|
-
return;
|
|
1402
|
-
}
|
|
1403
|
-
this.#activeConsultations -= 1;
|
|
1404
|
-
if (this.#activeConsultations === 0) {
|
|
1405
|
-
this.safeReport(true);
|
|
1406
|
-
}
|
|
1407
|
-
}
|
|
1408
|
-
clear() {
|
|
1409
|
-
if (this.#activeConsultations === 0) {
|
|
1410
|
-
return;
|
|
1411
|
-
}
|
|
1412
|
-
this.#activeConsultations = 0;
|
|
1413
|
-
this.safeReport(true);
|
|
1414
|
-
}
|
|
1415
|
-
safeReport(clear) {
|
|
1416
|
-
try {
|
|
1417
|
-
this.report(this.request(clear));
|
|
1418
|
-
} catch {}
|
|
1419
|
-
}
|
|
1420
|
-
request(clear) {
|
|
1421
|
-
return {
|
|
1422
|
-
id: `${SOURCE}:${nextSequence()}`,
|
|
1423
|
-
method: "pane.report_metadata",
|
|
1424
|
-
params: {
|
|
1425
|
-
agent: "pi",
|
|
1426
|
-
applies_to_source: HERDR_PI_SOURCE,
|
|
1427
|
-
pane_id: process.env.HERDR_PANE_ID ?? "",
|
|
1428
|
-
source: SOURCE,
|
|
1429
|
-
...clear ? { clear_state_labels: true } : { state_labels: { working: "seeking advice" } },
|
|
1430
|
-
seq: nextSequence()
|
|
1431
|
-
}
|
|
1432
|
-
};
|
|
1500
|
+
var createHerdrNotificationRequest = (title, body) => ({
|
|
1501
|
+
id: `${NOTIFICATION_SOURCE}:${nextSequence()}`,
|
|
1502
|
+
method: HERDR_NOTIFICATION_METHOD,
|
|
1503
|
+
params: {
|
|
1504
|
+
body: cleanNotification(body, 240),
|
|
1505
|
+
position: "top-left",
|
|
1506
|
+
sound: "request",
|
|
1507
|
+
title: cleanNotification(title, 80)
|
|
1433
1508
|
}
|
|
1434
|
-
}
|
|
1509
|
+
});
|
|
1435
1510
|
|
|
1511
|
+
// src/herdr-block.ts
|
|
1436
1512
|
class HerdrAdvisorBlock {
|
|
1437
1513
|
#blocked = false;
|
|
1438
1514
|
report;
|
|
@@ -1492,6 +1568,60 @@ class HerdrAdvisorBlock {
|
|
|
1492
1568
|
} catch {}
|
|
1493
1569
|
}
|
|
1494
1570
|
}
|
|
1571
|
+
|
|
1572
|
+
// src/herdr.ts
|
|
1573
|
+
var metadataRequest = (clear) => ({
|
|
1574
|
+
id: `${SOURCE}:${nextSequence()}`,
|
|
1575
|
+
method: "pane.report_metadata",
|
|
1576
|
+
params: {
|
|
1577
|
+
agent: "pi",
|
|
1578
|
+
applies_to_source: HERDR_PI_SOURCE,
|
|
1579
|
+
pane_id: process.env.HERDR_PANE_ID ?? "",
|
|
1580
|
+
source: SOURCE,
|
|
1581
|
+
...clear ? { clear_state_labels: true } : { state_labels: { working: "seeking advice" } },
|
|
1582
|
+
seq: nextSequence()
|
|
1583
|
+
}
|
|
1584
|
+
});
|
|
1585
|
+
|
|
1586
|
+
class HerdrAdvisorActivity {
|
|
1587
|
+
#activeConsultations = 0;
|
|
1588
|
+
report;
|
|
1589
|
+
enabled;
|
|
1590
|
+
constructor(report = sendToHerdr, enabled = () => true) {
|
|
1591
|
+
this.report = report;
|
|
1592
|
+
this.enabled = enabled;
|
|
1593
|
+
}
|
|
1594
|
+
start() {
|
|
1595
|
+
if (!this.enabled()) {
|
|
1596
|
+
return;
|
|
1597
|
+
}
|
|
1598
|
+
this.#activeConsultations += 1;
|
|
1599
|
+
if (this.#activeConsultations === 1) {
|
|
1600
|
+
this.safeReport(false);
|
|
1601
|
+
}
|
|
1602
|
+
}
|
|
1603
|
+
finish() {
|
|
1604
|
+
if (this.#activeConsultations === 0) {
|
|
1605
|
+
return;
|
|
1606
|
+
}
|
|
1607
|
+
this.#activeConsultations -= 1;
|
|
1608
|
+
if (this.#activeConsultations === 0) {
|
|
1609
|
+
this.safeReport(true);
|
|
1610
|
+
}
|
|
1611
|
+
}
|
|
1612
|
+
clear() {
|
|
1613
|
+
if (this.#activeConsultations === 0) {
|
|
1614
|
+
return;
|
|
1615
|
+
}
|
|
1616
|
+
this.#activeConsultations = 0;
|
|
1617
|
+
this.safeReport(true);
|
|
1618
|
+
}
|
|
1619
|
+
safeReport(clear) {
|
|
1620
|
+
try {
|
|
1621
|
+
this.report(metadataRequest(clear));
|
|
1622
|
+
} catch {}
|
|
1623
|
+
}
|
|
1624
|
+
}
|
|
1495
1625
|
var notifyHerdrAdvisorFailure = (title, body) => {
|
|
1496
1626
|
if (!getAdvisorSettings().herdrIntegration) {
|
|
1497
1627
|
return;
|
|
@@ -1507,15 +1637,14 @@ var herdrAdvisorBlock = new HerdrAdvisorBlock(sendToHerdr, () => getAdvisorSetti
|
|
|
1507
1637
|
import { randomUUID } from "node:crypto";
|
|
1508
1638
|
|
|
1509
1639
|
// src/model-stream.ts
|
|
1510
|
-
import {
|
|
1511
|
-
stream
|
|
1512
|
-
} from "@earendil-works/pi-ai/compat";
|
|
1640
|
+
import { stream } from "@earendil-works/pi-ai/compat";
|
|
1513
1641
|
var resolveConfiguredModel = async (ctx, ref, label) => {
|
|
1514
1642
|
if (!ref) {
|
|
1515
1643
|
throw new Error(`${label} model not configured`);
|
|
1516
1644
|
}
|
|
1517
1645
|
const [provider, modelId] = splitRef(ref);
|
|
1518
|
-
const
|
|
1646
|
+
const lookup = [provider, modelId];
|
|
1647
|
+
const model = ctx.modelRegistry.find(...lookup);
|
|
1519
1648
|
if (!model) {
|
|
1520
1649
|
throw new Error(`${label} model not found: ${ref}`);
|
|
1521
1650
|
}
|
|
@@ -1535,16 +1664,16 @@ var resolveConfiguredModel = async (ctx, ref, label) => {
|
|
|
1535
1664
|
};
|
|
1536
1665
|
};
|
|
1537
1666
|
var ADVISOR_STREAM_UPDATE_INTERVAL_MS = 90;
|
|
1538
|
-
var
|
|
1667
|
+
var defaultScheduler = {
|
|
1539
1668
|
clearTimeout,
|
|
1540
1669
|
now: Date.now,
|
|
1541
1670
|
setTimeout: (callback, delay) => setTimeout(callback, delay)
|
|
1542
|
-
}
|
|
1671
|
+
};
|
|
1672
|
+
var createCoalescedUpdate = (publish, intervalMs = ADVISOR_STREAM_UPDATE_INTERVAL_MS, scheduler = defaultScheduler) => {
|
|
1543
1673
|
if (!Number.isFinite(intervalMs) || intervalMs <= 0) {
|
|
1544
1674
|
throw new Error("Coalesced update interval must be positive and finite.");
|
|
1545
1675
|
}
|
|
1546
1676
|
let closed = false;
|
|
1547
|
-
let hasPending = false;
|
|
1548
1677
|
let pending;
|
|
1549
1678
|
let lastPublishedAt;
|
|
1550
1679
|
let timer;
|
|
@@ -1558,12 +1687,11 @@ var createCoalescedUpdate = (publish, intervalMs = ADVISOR_STREAM_UPDATE_INTERVA
|
|
|
1558
1687
|
};
|
|
1559
1688
|
const publishPending = () => {
|
|
1560
1689
|
timer = undefined;
|
|
1561
|
-
if (!
|
|
1690
|
+
if (!pending) {
|
|
1562
1691
|
return;
|
|
1563
1692
|
}
|
|
1564
|
-
const value = pending;
|
|
1693
|
+
const { value } = pending;
|
|
1565
1694
|
pending = undefined;
|
|
1566
|
-
hasPending = false;
|
|
1567
1695
|
lastPublishedAt = scheduler.now();
|
|
1568
1696
|
try {
|
|
1569
1697
|
publish(value);
|
|
@@ -1589,7 +1717,6 @@ var createCoalescedUpdate = (publish, intervalMs = ADVISOR_STREAM_UPDATE_INTERVA
|
|
|
1589
1717
|
closed = true;
|
|
1590
1718
|
clearTimer();
|
|
1591
1719
|
pending = undefined;
|
|
1592
|
-
hasPending = false;
|
|
1593
1720
|
},
|
|
1594
1721
|
flush: () => {
|
|
1595
1722
|
if (!closed) {
|
|
@@ -1606,8 +1733,7 @@ var createCoalescedUpdate = (publish, intervalMs = ADVISOR_STREAM_UPDATE_INTERVA
|
|
|
1606
1733
|
if (publishFailed) {
|
|
1607
1734
|
throw publishError;
|
|
1608
1735
|
}
|
|
1609
|
-
pending = value;
|
|
1610
|
-
hasPending = true;
|
|
1736
|
+
pending = { value };
|
|
1611
1737
|
if (timer === undefined) {
|
|
1612
1738
|
schedule();
|
|
1613
1739
|
}
|
|
@@ -1620,14 +1746,17 @@ var createCoalescedUpdate = (publish, intervalMs = ADVISOR_STREAM_UPDATE_INTERVA
|
|
|
1620
1746
|
var collectTextStream = async (resolved, options, streamModel = stream) => {
|
|
1621
1747
|
let thinking = "";
|
|
1622
1748
|
let text = "";
|
|
1623
|
-
const
|
|
1749
|
+
const streamOptions = {
|
|
1624
1750
|
apiKey: resolved.apiKey,
|
|
1625
1751
|
env: resolved.env,
|
|
1626
1752
|
headers: resolved.headers,
|
|
1627
1753
|
reasoning: options.reasoning,
|
|
1628
|
-
...options.reasoning === undefined ? {} : { reasoningEffort: options.reasoning },
|
|
1629
1754
|
signal: options.signal
|
|
1630
|
-
}
|
|
1755
|
+
};
|
|
1756
|
+
if (options.reasoning !== undefined) {
|
|
1757
|
+
streamOptions.reasoningEffort = options.reasoning;
|
|
1758
|
+
}
|
|
1759
|
+
const eventStream = streamModel(resolved.model, { messages: options.messages, systemPrompt: options.systemPrompt }, streamOptions);
|
|
1631
1760
|
for await (const event of eventStream) {
|
|
1632
1761
|
if (event.type === "thinking_delta") {
|
|
1633
1762
|
thinking += event.delta;
|
|
@@ -1661,21 +1790,21 @@ import { lstat, open, realpath } from "node:fs/promises";
|
|
|
1661
1790
|
import { isAbsolute, relative, resolve } from "node:path";
|
|
1662
1791
|
var ADVISOR_FILE_MAX_BYTES = 8 * 1024;
|
|
1663
1792
|
var ADVISOR_FILES_TOTAL_MAX_BYTES = 24 * 1024;
|
|
1664
|
-
var PATH_SEGMENTS = /[\\/]
|
|
1793
|
+
var PATH_SEGMENTS = /[\\/]/u;
|
|
1665
1794
|
var within = (root, candidate) => {
|
|
1666
1795
|
const path = relative(root, candidate);
|
|
1667
1796
|
return path !== "" && !path.startsWith("..") && !path.includes("../");
|
|
1668
1797
|
};
|
|
1669
1798
|
var normalizeRelativePath = (root, path) => relative(root, resolve(root, path));
|
|
1670
1799
|
var normalizeRequestedPath = (root, value) => {
|
|
1671
|
-
if (
|
|
1800
|
+
if (!isString(value) || !value || isAbsolute(value) || value.split(PATH_SEGMENTS).includes("..")) {
|
|
1672
1801
|
return;
|
|
1673
1802
|
}
|
|
1674
1803
|
return normalizeRelativePath(root, value);
|
|
1675
1804
|
};
|
|
1676
1805
|
var git = (cwd, args) => execFileSync2("git", args, {
|
|
1677
1806
|
cwd,
|
|
1678
|
-
encoding: "
|
|
1807
|
+
encoding: "utf-8",
|
|
1679
1808
|
maxBuffer: 16 * 1024 * 1024,
|
|
1680
1809
|
shell: false,
|
|
1681
1810
|
stdio: ["ignore", "pipe", "pipe"],
|
|
@@ -1686,7 +1815,7 @@ var repositoryRoot = (cwd) => {
|
|
|
1686
1815
|
try {
|
|
1687
1816
|
return realpath(git(cwd, ["rev-parse", "--show-toplevel"]).trim());
|
|
1688
1817
|
} catch {
|
|
1689
|
-
return Promise.resolve(
|
|
1818
|
+
return Promise.resolve();
|
|
1690
1819
|
}
|
|
1691
1820
|
};
|
|
1692
1821
|
var untracked = (cwd, path) => {
|
|
@@ -1726,7 +1855,7 @@ var readAttachment = async (root, normalizedName, redact, available) => {
|
|
|
1726
1855
|
if (!within(root, resolved)) {
|
|
1727
1856
|
return;
|
|
1728
1857
|
}
|
|
1729
|
-
const flags = constants.O_NOFOLLOW ? constants.O_RDONLY
|
|
1858
|
+
const flags = constants.O_NOFOLLOW ? constants.O_RDONLY + constants.O_NOFOLLOW : constants.O_RDONLY;
|
|
1730
1859
|
const file = await open(resolved, flags);
|
|
1731
1860
|
try {
|
|
1732
1861
|
const openedStats = await file.stat();
|
|
@@ -1735,13 +1864,13 @@ var readAttachment = async (root, normalizedName, redact, available) => {
|
|
|
1735
1864
|
}
|
|
1736
1865
|
const buffer = Buffer.alloc(available + 1);
|
|
1737
1866
|
const { bytesRead } = await file.read(buffer, 0, buffer.length, 0);
|
|
1738
|
-
const raw = buffer.subarray(0, bytesRead).toString("
|
|
1867
|
+
const raw = buffer.subarray(0, bytesRead).toString("utf-8");
|
|
1739
1868
|
if (raw.includes("\x00")) {
|
|
1740
1869
|
return;
|
|
1741
1870
|
}
|
|
1742
1871
|
const text = redactAndCapText(raw, available, redact);
|
|
1743
1872
|
return {
|
|
1744
|
-
bytes: Buffer.byteLength(text, "
|
|
1873
|
+
bytes: Buffer.byteLength(text, "utf-8"),
|
|
1745
1874
|
path: normalizedName,
|
|
1746
1875
|
text
|
|
1747
1876
|
};
|
|
@@ -1814,9 +1943,9 @@ var readProjectPreferences = async (ctx, maxBytes = PREFERENCES_MAX_BYTES, redac
|
|
|
1814
1943
|
try {
|
|
1815
1944
|
const buffer = Buffer.alloc(maxBytes + 1);
|
|
1816
1945
|
const { bytesRead } = await file.read(buffer, 0, buffer.length, 0);
|
|
1817
|
-
const source = buffer.subarray(0, bytesRead).toString("
|
|
1946
|
+
const source = buffer.subarray(0, bytesRead).toString("utf-8");
|
|
1818
1947
|
const capped = redactAndCapText(source, maxBytes, redact);
|
|
1819
|
-
return { bytes: Buffer.byteLength(capped, "
|
|
1948
|
+
return { bytes: Buffer.byteLength(capped, "utf-8"), text: capped };
|
|
1820
1949
|
} finally {
|
|
1821
1950
|
await file.close();
|
|
1822
1951
|
}
|
|
@@ -1828,6 +1957,19 @@ import { createHash } from "node:crypto";
|
|
|
1828
1957
|
|
|
1829
1958
|
// src/tool-result-cap.ts
|
|
1830
1959
|
var OMITTED_MARKER = "[... omitted tool-result section ...]";
|
|
1960
|
+
var collect = (candidates, maxEntries, maxContentBytes) => {
|
|
1961
|
+
const selected = [];
|
|
1962
|
+
let used = 0;
|
|
1963
|
+
for (const line of candidates.slice(0, maxEntries)) {
|
|
1964
|
+
const next = used + byteLength(line) + (selected.length ? 1 : 0);
|
|
1965
|
+
if (next > maxContentBytes) {
|
|
1966
|
+
break;
|
|
1967
|
+
}
|
|
1968
|
+
selected.push(line);
|
|
1969
|
+
used = next;
|
|
1970
|
+
}
|
|
1971
|
+
return selected;
|
|
1972
|
+
};
|
|
1831
1973
|
var capToolResult = (value, maxLines = DEFAULT_ADVISOR_TOOL_RESULT_MAX_LINES, maxBytes = DEFAULT_ADVISOR_TOOL_RESULT_MAX_BYTES) => {
|
|
1832
1974
|
const lines = value.split(`
|
|
1833
1975
|
`);
|
|
@@ -1863,19 +2005,6 @@ var capToolResult = (value, maxLines = DEFAULT_ADVISOR_TOOL_RESULT_MAX_LINES, ma
|
|
|
1863
2005
|
}
|
|
1864
2006
|
const headCount = Math.floor((maxLines - 1) / 2);
|
|
1865
2007
|
const tailCount = maxLines - 1 - headCount;
|
|
1866
|
-
const collect = (candidates, maxEntries, maxContentBytes) => {
|
|
1867
|
-
const selected = [];
|
|
1868
|
-
let used = 0;
|
|
1869
|
-
for (const line of candidates.slice(0, maxEntries)) {
|
|
1870
|
-
const next = used + byteLength(line) + (selected.length ? 1 : 0);
|
|
1871
|
-
if (next > maxContentBytes) {
|
|
1872
|
-
break;
|
|
1873
|
-
}
|
|
1874
|
-
selected.push(line);
|
|
1875
|
-
used = next;
|
|
1876
|
-
}
|
|
1877
|
-
return selected;
|
|
1878
|
-
};
|
|
1879
2008
|
const availableBytes = maxBytes - markerBytes - 2;
|
|
1880
2009
|
const head = collect(lines, headCount, Math.floor(availableBytes / 2));
|
|
1881
2010
|
const tail = collect(lines.slice(Math.max(head.length, lines.length - tailCount)), tailCount, availableBytes - byteLength(head.join(`
|
|
@@ -1893,13 +2022,13 @@ var capToolResult = (value, maxLines = DEFAULT_ADVISOR_TOOL_RESULT_MAX_LINES, ma
|
|
|
1893
2022
|
|
|
1894
2023
|
// src/conversation.ts
|
|
1895
2024
|
var textFromPart = (part) => {
|
|
1896
|
-
if (
|
|
2025
|
+
if (isString(part)) {
|
|
1897
2026
|
return part;
|
|
1898
2027
|
}
|
|
1899
|
-
if (!
|
|
2028
|
+
if (!isRecordOf(part) || part.type !== "text") {
|
|
1900
2029
|
return "";
|
|
1901
2030
|
}
|
|
1902
|
-
return
|
|
2031
|
+
return isString(part.text) ? part.text : "";
|
|
1903
2032
|
};
|
|
1904
2033
|
var textFrom = (content) => contentParts(content).map(textFromPart).join(`
|
|
1905
2034
|
`).trim();
|
|
@@ -1910,10 +2039,10 @@ var assistantEntry = (message, policies, redact) => {
|
|
|
1910
2039
|
parts.push(redact ? redactSecrets(text) : text);
|
|
1911
2040
|
}
|
|
1912
2041
|
for (const part of contentParts(message.content)) {
|
|
1913
|
-
if (!
|
|
2042
|
+
if (!isRecordOf(part) || part.type !== "toolCall") {
|
|
1914
2043
|
continue;
|
|
1915
2044
|
}
|
|
1916
|
-
const toolName =
|
|
2045
|
+
const toolName = isString(part.name) ? part.name : "unknown";
|
|
1917
2046
|
const policy = policies[toolName] ?? "full";
|
|
1918
2047
|
if (policy === "exclude") {
|
|
1919
2048
|
parts.push(`[Tool Call: ${toolName}] (excluded by Advisor tool policy)`);
|
|
@@ -1931,7 +2060,7 @@ var assistantEntry = (message, policies, redact) => {
|
|
|
1931
2060
|
};
|
|
1932
2061
|
var toolResultEntry = (message, toolResultMaxLines, toolResultMaxBytes, policies, redact) => {
|
|
1933
2062
|
const status = message.isError ? "error" : "success";
|
|
1934
|
-
const toolName =
|
|
2063
|
+
const toolName = isString(message.toolName) ? message.toolName : "unknown";
|
|
1935
2064
|
const policy = policies[toolName] ?? "full";
|
|
1936
2065
|
const source = textFrom(message.content);
|
|
1937
2066
|
if (policy === "exclude") {
|
|
@@ -1947,13 +2076,13 @@ var toolResultEntry = (message, toolResultMaxLines, toolResultMaxBytes, policies
|
|
|
1947
2076
|
${capped.content}`;
|
|
1948
2077
|
};
|
|
1949
2078
|
var conversationEntry = (entry, toolResultMaxLines, toolResultMaxBytes, policies, redact) => {
|
|
1950
|
-
if (!
|
|
2079
|
+
if (!isRecordOf(entry)) {
|
|
1951
2080
|
return;
|
|
1952
2081
|
}
|
|
1953
|
-
if (entry.type === "compaction" &&
|
|
2082
|
+
if (entry.type === "compaction" && isString(entry.summary)) {
|
|
1954
2083
|
return `[System Compaction Summary]: ${redact ? redactSecrets(entry.summary) : entry.summary}`;
|
|
1955
2084
|
}
|
|
1956
|
-
if (entry.type !== "message" || !
|
|
2085
|
+
if (entry.type !== "message" || !isRecordOf(entry.message)) {
|
|
1957
2086
|
return;
|
|
1958
2087
|
}
|
|
1959
2088
|
const { message } = entry;
|
|
@@ -2018,30 +2147,29 @@ var invalid = (message) => ({
|
|
|
2018
2147
|
reason: "invalid-protocol"
|
|
2019
2148
|
});
|
|
2020
2149
|
var toolCalls = (message) => contentParts(message.content).filter((part) => isRecord(part) && part.type === "toolCall");
|
|
2021
|
-
var toolCallId = (part) =>
|
|
2022
|
-
var
|
|
2023
|
-
const
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
const failure = indexEntry(entries[index], index, callOwners, resultsByCall, state);
|
|
2028
|
-
if (failure) {
|
|
2029
|
-
return failure;
|
|
2150
|
+
var toolCallId = (part) => isString(part.id) ? part.id : undefined;
|
|
2151
|
+
var indexAssistantCalls = (message, index, callOwners) => {
|
|
2152
|
+
for (const call of toolCalls(message)) {
|
|
2153
|
+
const id = toolCallId(call);
|
|
2154
|
+
if (!id || callOwners.has(id)) {
|
|
2155
|
+
return invalid(`Assistant tool calls at context entry ${index} have missing or duplicate IDs.`);
|
|
2030
2156
|
}
|
|
2157
|
+
callOwners.set(id, {
|
|
2158
|
+
index,
|
|
2159
|
+
name: isString(call.name) ? call.name : "unknown"
|
|
2160
|
+
});
|
|
2031
2161
|
}
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2162
|
+
return;
|
|
2163
|
+
};
|
|
2164
|
+
var indexToolResult = (entry, message, index, resultsByCall) => {
|
|
2165
|
+
const id = message.toolCallId;
|
|
2166
|
+
if (!isString(id)) {
|
|
2167
|
+
return invalid(`Tool result at context entry ${index} has no tool-call ID.`);
|
|
2036
2168
|
}
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
resultsByCall
|
|
2042
|
-
},
|
|
2043
|
-
ok: true
|
|
2044
|
-
};
|
|
2169
|
+
const results = resultsByCall.get(id) ?? [];
|
|
2170
|
+
results.push({ entry, index });
|
|
2171
|
+
resultsByCall.set(id, results);
|
|
2172
|
+
return;
|
|
2045
2173
|
};
|
|
2046
2174
|
var indexEntry = (entry, index, callOwners, resultsByCall, state) => {
|
|
2047
2175
|
if (entry.type !== "message" || !isRecord(entry.message)) {
|
|
@@ -2058,28 +2186,32 @@ var indexEntry = (entry, index, callOwners, resultsByCall, state) => {
|
|
|
2058
2186
|
}
|
|
2059
2187
|
return;
|
|
2060
2188
|
};
|
|
2061
|
-
var
|
|
2062
|
-
const
|
|
2063
|
-
|
|
2064
|
-
|
|
2189
|
+
var indexToolCalls = (entries) => {
|
|
2190
|
+
const callOwners = new Map;
|
|
2191
|
+
const resultsByCall = new Map;
|
|
2192
|
+
const state = { latestUserIndex: -1 };
|
|
2193
|
+
for (let index = 0;index < entries.length; index += 1) {
|
|
2194
|
+
const entry = entries[index];
|
|
2195
|
+
if (isRecord(entry)) {
|
|
2196
|
+
const failure = indexEntry(entry, index, callOwners, resultsByCall, state);
|
|
2197
|
+
if (failure) {
|
|
2198
|
+
return failure;
|
|
2199
|
+
}
|
|
2200
|
+
}
|
|
2065
2201
|
}
|
|
2066
|
-
const results
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
return;
|
|
2070
|
-
};
|
|
2071
|
-
var indexAssistantCalls = (message, index, callOwners) => {
|
|
2072
|
-
for (const call of toolCalls(message)) {
|
|
2073
|
-
const id = toolCallId(call);
|
|
2074
|
-
if (!id || callOwners.has(id)) {
|
|
2075
|
-
return invalid(`Assistant tool calls at context entry ${index} have missing or duplicate IDs.`);
|
|
2202
|
+
for (const [id, results] of resultsByCall) {
|
|
2203
|
+
if (results.length > 1) {
|
|
2204
|
+
return invalid(`Tool call ${id} has duplicate result messages.`);
|
|
2076
2205
|
}
|
|
2077
|
-
callOwners.set(id, {
|
|
2078
|
-
index,
|
|
2079
|
-
name: typeof call.name === "string" ? call.name : "unknown"
|
|
2080
|
-
});
|
|
2081
2206
|
}
|
|
2082
|
-
return
|
|
2207
|
+
return {
|
|
2208
|
+
index: {
|
|
2209
|
+
callOwners,
|
|
2210
|
+
latestUserIndex: state.latestUserIndex,
|
|
2211
|
+
resultsByCall
|
|
2212
|
+
},
|
|
2213
|
+
ok: true
|
|
2214
|
+
};
|
|
2083
2215
|
};
|
|
2084
2216
|
|
|
2085
2217
|
// src/scout-types.ts
|
|
@@ -2091,8 +2223,8 @@ var SCOUT_SELECTION_MAX_IDS = 32;
|
|
|
2091
2223
|
var SCOUT_SYNTHESIS_MAX_BYTES = 4 * 1024;
|
|
2092
2224
|
|
|
2093
2225
|
// src/scout-groups.ts
|
|
2094
|
-
var SPEAKER_PREFIX = /^(User|Executor):\s
|
|
2095
|
-
var boundedLabel = (value) => [...value.
|
|
2226
|
+
var SPEAKER_PREFIX = /^(?<speaker>User|Executor):\s*/u;
|
|
2227
|
+
var boundedLabel = (value) => [...value.replaceAll(/\s+/gu, " ").trim()].slice(0, SCOUT_LABEL_MAX_CHARS).join("");
|
|
2096
2228
|
var labelFor = (kind, content) => {
|
|
2097
2229
|
const preview = boundedLabel(content.replace(SPEAKER_PREFIX, ""));
|
|
2098
2230
|
const prefix = {
|
|
@@ -2123,10 +2255,8 @@ var createGroup = (originalIndex, entryIds, kind, content, required) => ({
|
|
|
2123
2255
|
originalIndex,
|
|
2124
2256
|
required
|
|
2125
2257
|
});
|
|
2126
|
-
var pendingAdvisorArguments = (
|
|
2127
|
-
|
|
2128
|
-
return {};
|
|
2129
|
-
}
|
|
2258
|
+
var pendingAdvisorArguments = (part) => {
|
|
2259
|
+
const value = isRecord(part.arguments) ? part.arguments : {};
|
|
2130
2260
|
const allowed = {};
|
|
2131
2261
|
for (const key of ["gitContext", "question"]) {
|
|
2132
2262
|
if (key in value) {
|
|
@@ -2139,41 +2269,17 @@ var pendingInvocationDisclosure = (entry, invocationId, toolResultMaxLines, tool
|
|
|
2139
2269
|
if (!isRecord(entry.message)) {
|
|
2140
2270
|
return disclosed;
|
|
2141
2271
|
}
|
|
2142
|
-
const
|
|
2272
|
+
const message = entry.message;
|
|
2273
|
+
const content = contentParts(message.content).map((part) => {
|
|
2143
2274
|
if (!isRecord(part) || part.type !== "toolCall" || toolCallId(part) !== invocationId || part.name !== "ask_advisor") {
|
|
2144
2275
|
return part;
|
|
2145
2276
|
}
|
|
2146
|
-
return { ...part, arguments: pendingAdvisorArguments(part
|
|
2277
|
+
return { ...part, arguments: pendingAdvisorArguments(part) };
|
|
2147
2278
|
});
|
|
2148
|
-
return conversationEntry({ ...entry, message: { ...
|
|
2149
|
-
};
|
|
2150
|
-
var toolExchangeGroup = (entry, index, entryId, disclosed, immediate, indexed, consumedResultIndexes, caps) => {
|
|
2151
|
-
const calls = toolCalls(entry.message);
|
|
2152
|
-
const callIds = calls.map(toolCallId);
|
|
2153
|
-
const adjacentFailure = adjacentResultMismatch(immediate, index, callIds, indexed.callOwners);
|
|
2154
|
-
if (adjacentFailure) {
|
|
2155
|
-
return { kind: "invalid", message: adjacentFailure };
|
|
2156
|
-
}
|
|
2157
|
-
const missing = new Set;
|
|
2158
|
-
const resultParts = [];
|
|
2159
|
-
const resultEntryIds = [];
|
|
2160
|
-
const matchFailure = collectResults(calls, callIds, index, indexed.resultsByCall, consumedResultIndexes, caps, missing, resultParts, resultEntryIds);
|
|
2161
|
-
if (matchFailure) {
|
|
2162
|
-
return { kind: "invalid", message: matchFailure };
|
|
2163
|
-
}
|
|
2164
|
-
if (missing.size > 0) {
|
|
2165
|
-
return missingOutcome(entry, index, entryId, disclosed, callIds, missing, resultParts, resultEntryIds, caps);
|
|
2166
|
-
}
|
|
2167
|
-
return {
|
|
2168
|
-
group: createGroup(index, [entryId, ...resultEntryIds], "tool-exchange", [disclosed, ...resultParts].join(`
|
|
2169
|
-
|
|
2170
|
-
`), false),
|
|
2171
|
-
kind: "group"
|
|
2172
|
-
};
|
|
2279
|
+
return conversationEntry({ ...entry, message: { ...message, content } }, toolResultMaxLines, toolResultMaxBytes, policies, redact);
|
|
2173
2280
|
};
|
|
2174
2281
|
var adjacentResultMismatch = (immediate, index, callIds, callOwners) => {
|
|
2175
|
-
|
|
2176
|
-
if (next?.type === "message" && isRecord(next.message) && next.message.role === "toolResult" && typeof next.message.toolCallId === "string" && !callIds.includes(next.message.toolCallId) && !callOwners.has(next.message.toolCallId)) {
|
|
2282
|
+
if (immediate?.type === "message" && isRecord(immediate.message) && immediate.message.role === "toolResult" && isString(immediate.message.toolCallId) && !callIds.includes(immediate.message.toolCallId) && !callOwners.has(immediate.message.toolCallId)) {
|
|
2177
2283
|
return `Tool result at context entry ${index + 1} does not match its adjacent assistant group.`;
|
|
2178
2284
|
}
|
|
2179
2285
|
return;
|
|
@@ -2189,7 +2295,8 @@ var collectResults = (calls, callIds, index, resultsByCall, consumedResultIndexe
|
|
|
2189
2295
|
return `Tool result at context entry ${resultMatch.index} precedes its assistant call.`;
|
|
2190
2296
|
}
|
|
2191
2297
|
const resultMessage = resultMatch.entry.message;
|
|
2192
|
-
const
|
|
2298
|
+
const call = calls[callIndex];
|
|
2299
|
+
const expectedName = isString(call.name) ? call.name : "unknown";
|
|
2193
2300
|
if (!isRecord(resultMessage) || resultMessage.toolName !== expectedName) {
|
|
2194
2301
|
return `Tool result at context entry ${resultMatch.index} conflicts with call ${callId}.`;
|
|
2195
2302
|
}
|
|
@@ -2198,7 +2305,7 @@ var collectResults = (calls, callIds, index, resultsByCall, consumedResultIndexe
|
|
|
2198
2305
|
if (resultText) {
|
|
2199
2306
|
resultParts.push(resultText);
|
|
2200
2307
|
}
|
|
2201
|
-
resultEntryIds.push(
|
|
2308
|
+
resultEntryIds.push(isString(resultMatch.entry.id) ? resultMatch.entry.id : String(resultMatch.index));
|
|
2202
2309
|
}
|
|
2203
2310
|
return;
|
|
2204
2311
|
};
|
|
@@ -2221,37 +2328,47 @@ var missingOutcome = (entry, index, entryId, disclosed, callIds, missing, result
|
|
|
2221
2328
|
kind: "group"
|
|
2222
2329
|
};
|
|
2223
2330
|
};
|
|
2224
|
-
var
|
|
2225
|
-
const
|
|
2226
|
-
const
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
protocolOmittedCount += 1;
|
|
2241
|
-
protocolOmittedBytes += outcome.bytes;
|
|
2242
|
-
} else if (outcome.kind === "group") {
|
|
2243
|
-
groups.push(outcome.group);
|
|
2244
|
-
}
|
|
2331
|
+
var toolExchangeGroup = (entry, message, index, entryId, disclosed, immediate, indexed, consumedResultIndexes, caps) => {
|
|
2332
|
+
const calls = toolCalls(message);
|
|
2333
|
+
const callIds = calls.map(toolCallId).filter(isString);
|
|
2334
|
+
const adjacentFailure = adjacentResultMismatch(immediate, index, callIds, indexed.callOwners);
|
|
2335
|
+
if (adjacentFailure) {
|
|
2336
|
+
return { kind: "invalid", message: adjacentFailure };
|
|
2337
|
+
}
|
|
2338
|
+
const missing = new Set;
|
|
2339
|
+
const resultParts = [];
|
|
2340
|
+
const resultEntryIds = [];
|
|
2341
|
+
const matchFailure = collectResults(calls, callIds, index, indexed.resultsByCall, consumedResultIndexes, caps, missing, resultParts, resultEntryIds);
|
|
2342
|
+
if (matchFailure) {
|
|
2343
|
+
return { kind: "invalid", message: matchFailure };
|
|
2344
|
+
}
|
|
2345
|
+
if (missing.size > 0) {
|
|
2346
|
+
return missingOutcome(entry, index, entryId, disclosed, callIds, missing, resultParts, resultEntryIds, caps);
|
|
2245
2347
|
}
|
|
2246
2348
|
return {
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2349
|
+
group: createGroup(index, [entryId, ...resultEntryIds], "tool-exchange", [disclosed, ...resultParts].join(`
|
|
2350
|
+
|
|
2351
|
+
`), false),
|
|
2352
|
+
kind: "group"
|
|
2251
2353
|
};
|
|
2252
2354
|
};
|
|
2355
|
+
var ownerOf = (message, callOwners) => isString(message.toolCallId) ? callOwners.get(message.toolCallId) : undefined;
|
|
2356
|
+
var contentPartsOf = (message) => Array.isArray(message.content) ? message.content : [];
|
|
2357
|
+
var hasCalls = (message) => contentPartsOf(message).some((part) => isRecord(part) && part.type === "toolCall");
|
|
2358
|
+
var toolResultOutcome = (message, index, disclosed, callOwners, consumedResultIndexes) => {
|
|
2359
|
+
if (consumedResultIndexes.has(index)) {
|
|
2360
|
+
return { kind: "skipped" };
|
|
2361
|
+
}
|
|
2362
|
+
if (ownerOf(message, callOwners)) {
|
|
2363
|
+
return {
|
|
2364
|
+
kind: "invalid",
|
|
2365
|
+
message: `Tool result at context entry ${index} precedes or conflicts with its retained call.`
|
|
2366
|
+
};
|
|
2367
|
+
}
|
|
2368
|
+
return { bytes: byteLength(disclosed), kind: "omitted" };
|
|
2369
|
+
};
|
|
2253
2370
|
var groupForEntry = (entry, index, disclosed, immediate, indexed, consumedResultIndexes, caps) => {
|
|
2254
|
-
const entryId =
|
|
2371
|
+
const entryId = isString(entry.id) ? entry.id : String(index);
|
|
2255
2372
|
if (entry.type !== "message" || !isRecord(entry.message)) {
|
|
2256
2373
|
return {
|
|
2257
2374
|
group: createGroup(index, [entryId], "compaction", disclosed, false),
|
|
@@ -2277,23 +2394,42 @@ var groupForEntry = (entry, index, disclosed, immediate, indexed, consumedResult
|
|
|
2277
2394
|
kind: "group"
|
|
2278
2395
|
};
|
|
2279
2396
|
}
|
|
2280
|
-
return toolExchangeGroup(entry, index, entryId, disclosed, immediate, indexed, consumedResultIndexes, caps);
|
|
2397
|
+
return toolExchangeGroup(entry, message, index, entryId, disclosed, immediate, indexed, consumedResultIndexes, caps);
|
|
2281
2398
|
};
|
|
2282
|
-
var
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2399
|
+
var buildGroups = (entries, indexed, caps) => {
|
|
2400
|
+
const groups = [];
|
|
2401
|
+
const consumedResultIndexes = new Set;
|
|
2402
|
+
let protocolOmittedBytes = 0;
|
|
2403
|
+
let protocolOmittedCount = 0;
|
|
2404
|
+
for (let index = 0;index < entries.length; index += 1) {
|
|
2405
|
+
const entry = entries[index];
|
|
2406
|
+
if (!isRecord(entry)) {
|
|
2407
|
+
continue;
|
|
2408
|
+
}
|
|
2409
|
+
const disclosed = conversationEntry(entry, caps.toolResultMaxLines, caps.toolResultMaxBytes, caps.policies, caps.redact);
|
|
2410
|
+
if (!disclosed) {
|
|
2411
|
+
continue;
|
|
2412
|
+
}
|
|
2413
|
+
const next = entries[index + 1];
|
|
2414
|
+
const immediate = isRecord(next) ? next : undefined;
|
|
2415
|
+
const outcome = groupForEntry(entry, index, disclosed, immediate, indexed, consumedResultIndexes, caps);
|
|
2416
|
+
if (outcome.kind === "invalid") {
|
|
2417
|
+
return invalid(outcome.message);
|
|
2418
|
+
}
|
|
2419
|
+
if (outcome.kind === "omitted") {
|
|
2420
|
+
protocolOmittedCount += 1;
|
|
2421
|
+
protocolOmittedBytes += outcome.bytes;
|
|
2422
|
+
} else if (outcome.kind === "group") {
|
|
2423
|
+
groups.push(outcome.group);
|
|
2424
|
+
}
|
|
2291
2425
|
}
|
|
2292
|
-
return {
|
|
2426
|
+
return {
|
|
2427
|
+
groups,
|
|
2428
|
+
ok: true,
|
|
2429
|
+
protocolOmittedBytes,
|
|
2430
|
+
protocolOmittedCount
|
|
2431
|
+
};
|
|
2293
2432
|
};
|
|
2294
|
-
var ownerOf = (message, callOwners) => typeof message.toolCallId === "string" ? callOwners.get(message.toolCallId) : undefined;
|
|
2295
|
-
var hasCalls = (message) => contentPartsOf(message).some((part) => isRecord(part) && part.type === "toolCall");
|
|
2296
|
-
var contentPartsOf = (message) => Array.isArray(message.content) ? message.content : [];
|
|
2297
2433
|
|
|
2298
2434
|
// src/scout-reconstruct.ts
|
|
2299
2435
|
var prefixWithinCharBudget = (value, maxChars) => {
|
|
@@ -2308,7 +2444,7 @@ var prefixWithinCharBudget = (value, maxChars) => {
|
|
|
2308
2444
|
};
|
|
2309
2445
|
var reconstructScoutConversation = (manifest, selectedIds, synthesis, maxChars = Number.MAX_SAFE_INTEGER) => {
|
|
2310
2446
|
const selected = new Set(selectedIds);
|
|
2311
|
-
const evidence = manifest.groups.filter((group) => group.required || selected.has(group.id)).
|
|
2447
|
+
const evidence = manifest.groups.filter((group) => group.required || selected.has(group.id)).toSorted((left, right) => left.originalIndex - right.originalIndex).map((group) => group.content);
|
|
2312
2448
|
const evidenceText = evidence.join(`
|
|
2313
2449
|
|
|
2314
2450
|
`);
|
|
@@ -2333,15 +2469,106 @@ ${synthesis.trim()}` : undefined;
|
|
|
2333
2469
|
return `${evidenceText}${separator}${prefixWithinCharBudget(inference, remaining)}`;
|
|
2334
2470
|
};
|
|
2335
2471
|
|
|
2472
|
+
// src/scout-context.ts
|
|
2473
|
+
var resolveCaps = (options) => ({
|
|
2474
|
+
currentInvocationId: options.currentInvocationId,
|
|
2475
|
+
maxConversationChars: options.maxConversationChars,
|
|
2476
|
+
maxGroupBytes: options.maxGroupBytes ?? SCOUT_GROUP_MAX_BYTES,
|
|
2477
|
+
maxGroups: options.maxGroups ?? SCOUT_MANIFEST_MAX_GROUPS,
|
|
2478
|
+
maxManifestBytes: options.maxManifestBytes ?? options.maxBytes ?? SCOUT_MANIFEST_MAX_BYTES,
|
|
2479
|
+
policies: options.policies ?? advisorToolPoliciesRef,
|
|
2480
|
+
redact: options.redact ?? advisorRedactSecretsRef,
|
|
2481
|
+
toolResultMaxBytes: options.toolResultMaxBytes ?? advisorToolResultMaxBytesRef,
|
|
2482
|
+
toolResultMaxLines: options.toolResultMaxLines ?? advisorToolResultMaxLinesRef
|
|
2483
|
+
});
|
|
2484
|
+
var contentChars = (items) => items.reduce((sum, group) => sum + group.content.length, 0) + Math.max(0, items.length - 1) * 2;
|
|
2485
|
+
var requiredOverflow = (required, caps) => {
|
|
2486
|
+
if (required.some((group) => group.bytes > caps.maxGroupBytes) || required.length > caps.maxGroups || required.reduce((sum, group) => sum + groupWireBytes(group), 0) > caps.maxManifestBytes) {
|
|
2487
|
+
return {
|
|
2488
|
+
message: "Required Scout context exceeds the Scout manifest transport limit.",
|
|
2489
|
+
ok: false,
|
|
2490
|
+
reason: "required-group-overflow"
|
|
2491
|
+
};
|
|
2492
|
+
}
|
|
2493
|
+
if (caps.maxConversationChars !== undefined && contentChars(required) > caps.maxConversationChars) {
|
|
2494
|
+
return {
|
|
2495
|
+
message: "Required Scout context exceeds the Advisor conversation budget.",
|
|
2496
|
+
ok: false,
|
|
2497
|
+
reason: "required-group-overflow"
|
|
2498
|
+
};
|
|
2499
|
+
}
|
|
2500
|
+
return;
|
|
2501
|
+
};
|
|
2502
|
+
var fits = (selected, caps) => selected.length <= caps.maxGroups && selected.reduce((sum, group) => sum + groupWireBytes(group), 0) <= caps.maxManifestBytes && (caps.maxConversationChars === undefined || contentChars(selected) <= caps.maxConversationChars);
|
|
2503
|
+
var fitToBudget = (built, caps) => {
|
|
2504
|
+
const { groups, protocolOmittedBytes, protocolOmittedCount } = built;
|
|
2505
|
+
const availableCount = groups.length + protocolOmittedCount;
|
|
2506
|
+
const availableBytes = groups.reduce((sum, group) => sum + groupWireBytes(group), 0) + protocolOmittedBytes;
|
|
2507
|
+
if (caps.maxManifestBytes <= 0) {
|
|
2508
|
+
return {
|
|
2509
|
+
manifest: {
|
|
2510
|
+
availableBytes: 0,
|
|
2511
|
+
availableCount: 0,
|
|
2512
|
+
groups: [],
|
|
2513
|
+
omittedBytes: 0,
|
|
2514
|
+
omittedCount: 0
|
|
2515
|
+
},
|
|
2516
|
+
ok: true
|
|
2517
|
+
};
|
|
2518
|
+
}
|
|
2519
|
+
const required = groups.filter((group) => group.required);
|
|
2520
|
+
const overflow = requiredOverflow(required, caps);
|
|
2521
|
+
if (overflow) {
|
|
2522
|
+
return overflow;
|
|
2523
|
+
}
|
|
2524
|
+
const selected = groups.filter((group) => group.required || group.bytes <= caps.maxGroupBytes);
|
|
2525
|
+
while (!fits(selected, caps)) {
|
|
2526
|
+
const optionalIndex = selected.findIndex((group) => !group.required);
|
|
2527
|
+
if (optionalIndex === -1) {
|
|
2528
|
+
return {
|
|
2529
|
+
message: "Required Scout context exceeds fixed manifest limits.",
|
|
2530
|
+
ok: false,
|
|
2531
|
+
reason: "required-group-overflow"
|
|
2532
|
+
};
|
|
2533
|
+
}
|
|
2534
|
+
selected.splice(optionalIndex, 1);
|
|
2535
|
+
}
|
|
2536
|
+
const selectedIds = new Set(selected.map((group) => group.id));
|
|
2537
|
+
const omitted = groups.filter((group) => !selectedIds.has(group.id));
|
|
2538
|
+
return {
|
|
2539
|
+
manifest: {
|
|
2540
|
+
availableBytes,
|
|
2541
|
+
availableCount,
|
|
2542
|
+
groups: selected,
|
|
2543
|
+
omittedBytes: protocolOmittedBytes + omitted.reduce((sum, group) => sum + group.bytes, 0),
|
|
2544
|
+
omittedCount: protocolOmittedCount + omitted.length
|
|
2545
|
+
},
|
|
2546
|
+
ok: true
|
|
2547
|
+
};
|
|
2548
|
+
};
|
|
2549
|
+
var buildScoutManifest = (ctx, options = {}) => {
|
|
2550
|
+
const entries = ctx.sessionManager.buildContextEntries();
|
|
2551
|
+
const caps = resolveCaps(options);
|
|
2552
|
+
const indexed = indexToolCalls(entries);
|
|
2553
|
+
if (!indexed.ok) {
|
|
2554
|
+
return indexed;
|
|
2555
|
+
}
|
|
2556
|
+
const built = buildGroups(entries, indexed.index, caps);
|
|
2557
|
+
if (!built.ok) {
|
|
2558
|
+
return built;
|
|
2559
|
+
}
|
|
2560
|
+
return fitToBudget(built, caps);
|
|
2561
|
+
};
|
|
2562
|
+
|
|
2336
2563
|
// src/usage.ts
|
|
2337
|
-
var finite = (value) =>
|
|
2564
|
+
var finite = (value) => isNumber(value) && Number.isFinite(value) && value >= 0 ? value : undefined;
|
|
2338
2565
|
var add = (left, right) => left === undefined || right === undefined ? left ?? right : left + right;
|
|
2339
2566
|
var costFields = ["input", "output", "cacheRead", "cacheWrite", "total"];
|
|
2340
2567
|
var snapshotAdvisorUsage = (usage) => {
|
|
2341
|
-
if (!
|
|
2568
|
+
if (!isRecordOf(usage)) {
|
|
2342
2569
|
return;
|
|
2343
2570
|
}
|
|
2344
|
-
const cost =
|
|
2571
|
+
const cost = isRecordOf(usage.cost) ? usage.cost : undefined;
|
|
2345
2572
|
const snapshot = {
|
|
2346
2573
|
cacheRead: finite(usage.cacheRead),
|
|
2347
2574
|
cacheWrite: finite(usage.cacheWrite),
|
|
@@ -2356,21 +2583,19 @@ var snapshotAdvisorUsage = (usage) => {
|
|
|
2356
2583
|
var advisorUsageCost = (usage) => snapshotAdvisorUsage(usage)?.cost;
|
|
2357
2584
|
var advisorUsageForPi = (usage) => {
|
|
2358
2585
|
const snapshot = snapshotAdvisorUsage(usage);
|
|
2359
|
-
if (!(snapshot &&
|
|
2586
|
+
if (!(snapshot && isRecordOf(usage))) {
|
|
2360
2587
|
return;
|
|
2361
2588
|
}
|
|
2362
|
-
const cost =
|
|
2589
|
+
const cost = isRecordOf(usage.cost) ? usage.cost : undefined;
|
|
2363
2590
|
const input = snapshot.input ?? 0;
|
|
2364
2591
|
const output = snapshot.output ?? 0;
|
|
2365
2592
|
const cacheRead = snapshot.cacheRead ?? 0;
|
|
2366
2593
|
const cacheWrite = snapshot.cacheWrite ?? 0;
|
|
2367
2594
|
const cacheWrite1h = finite(usage.cacheWrite1h);
|
|
2368
2595
|
const reasoning = finite(usage.reasoning);
|
|
2369
|
-
|
|
2596
|
+
const piUsage = {
|
|
2370
2597
|
cacheRead,
|
|
2371
2598
|
cacheWrite,
|
|
2372
|
-
...cacheWrite1h === undefined ? {} : { cacheWrite1h },
|
|
2373
|
-
...reasoning === undefined ? {} : { reasoning },
|
|
2374
2599
|
cost: {
|
|
2375
2600
|
cacheRead: finite(cost?.cacheRead) ?? 0,
|
|
2376
2601
|
cacheWrite: finite(cost?.cacheWrite) ?? 0,
|
|
@@ -2382,6 +2607,13 @@ var advisorUsageForPi = (usage) => {
|
|
|
2382
2607
|
output,
|
|
2383
2608
|
totalTokens: snapshot.totalTokens ?? input + output + cacheRead + cacheWrite
|
|
2384
2609
|
};
|
|
2610
|
+
if (cacheWrite1h !== undefined) {
|
|
2611
|
+
piUsage.cacheWrite1h = cacheWrite1h;
|
|
2612
|
+
}
|
|
2613
|
+
if (reasoning !== undefined) {
|
|
2614
|
+
piUsage.reasoning = reasoning;
|
|
2615
|
+
}
|
|
2616
|
+
return piUsage;
|
|
2385
2617
|
};
|
|
2386
2618
|
var emptyAdvisorUsageTotals = () => ({
|
|
2387
2619
|
calls: 0,
|
|
@@ -2476,8 +2708,8 @@ var defaultDependencies = {
|
|
|
2476
2708
|
collect: collectTextStream,
|
|
2477
2709
|
resolve: resolveConfiguredModel
|
|
2478
2710
|
};
|
|
2479
|
-
var byteLength2 = (value) => Buffer.byteLength(value, "
|
|
2480
|
-
var AUTH_ERROR_PATTERN = /api key|auth|login|credential/
|
|
2711
|
+
var byteLength2 = (value) => Buffer.byteLength(value, "utf-8");
|
|
2712
|
+
var AUTH_ERROR_PATTERN = /api key|auth|login|credential/iu;
|
|
2481
2713
|
var manifestMessage = (manifest) => ({
|
|
2482
2714
|
content: [
|
|
2483
2715
|
{
|
|
@@ -2494,6 +2726,7 @@ var manifestMessage = (manifest) => ({
|
|
|
2494
2726
|
role: "user",
|
|
2495
2727
|
timestamp: Date.now()
|
|
2496
2728
|
});
|
|
2729
|
+
var isStringArray = (value) => Array.isArray(value) && value.every(isString);
|
|
2497
2730
|
var parseScoutSelection = (text, manifest) => {
|
|
2498
2731
|
if (!text.trim()) {
|
|
2499
2732
|
throw new Error("Scout returned an empty response.");
|
|
@@ -2504,18 +2737,17 @@ var parseScoutSelection = (text, manifest) => {
|
|
|
2504
2737
|
} catch (error) {
|
|
2505
2738
|
throw new Error("Scout response is not a JSON object.", { cause: error });
|
|
2506
2739
|
}
|
|
2507
|
-
if (!
|
|
2740
|
+
if (!isRecord(value)) {
|
|
2508
2741
|
throw new Error("Scout response must be a JSON object.");
|
|
2509
2742
|
}
|
|
2510
|
-
const
|
|
2511
|
-
const keys = Object.keys(record).sort();
|
|
2743
|
+
const keys = Object.keys(value).toSorted();
|
|
2512
2744
|
if (keys.length !== 2 || keys[0] !== "selectedIds" || keys[1] !== "synthesis") {
|
|
2513
2745
|
throw new Error("Scout response must contain only selectedIds and synthesis.");
|
|
2514
2746
|
}
|
|
2515
|
-
if (!(
|
|
2747
|
+
if (!isStringArray(value.selectedIds)) {
|
|
2516
2748
|
throw new Error("Scout selectedIds must be an array of strings.");
|
|
2517
2749
|
}
|
|
2518
|
-
const selectedIds =
|
|
2750
|
+
const { selectedIds } = value;
|
|
2519
2751
|
if (new Set(selectedIds).size !== selectedIds.length) {
|
|
2520
2752
|
throw new Error("Scout selected duplicate group IDs.");
|
|
2521
2753
|
}
|
|
@@ -2529,15 +2761,15 @@ var parseScoutSelection = (text, manifest) => {
|
|
|
2529
2761
|
const optionalIds = knownSelectedIds.filter((id) => !required.has(id)).slice(0, SCOUT_SELECTION_MAX_IDS - requiredIds.length);
|
|
2530
2762
|
const retained = new Set([...requiredIds, ...optionalIds]);
|
|
2531
2763
|
const normalizedIds = manifest.groups.filter((group) => retained.has(group.id)).map((group) => group.id);
|
|
2532
|
-
if (
|
|
2533
|
-
throw new
|
|
2764
|
+
if (!isString(value.synthesis)) {
|
|
2765
|
+
throw new TypeError("Scout synthesis must be a string.");
|
|
2534
2766
|
}
|
|
2535
|
-
if (byteLength2(
|
|
2767
|
+
if (byteLength2(value.synthesis) > SCOUT_SYNTHESIS_MAX_BYTES) {
|
|
2536
2768
|
throw new Error(`Scout synthesis exceeds ${SCOUT_SYNTHESIS_MAX_BYTES} UTF-8 bytes.`);
|
|
2537
2769
|
}
|
|
2538
2770
|
return {
|
|
2539
2771
|
selectedIds: normalizedIds,
|
|
2540
|
-
synthesis: knownSelectedIds.length > 0 ?
|
|
2772
|
+
synthesis: knownSelectedIds.length > 0 ? value.synthesis : ""
|
|
2541
2773
|
};
|
|
2542
2774
|
};
|
|
2543
2775
|
var baseMetrics = (manifest, startedAt) => ({
|
|
@@ -2556,6 +2788,61 @@ var classifyResolutionError = (message) => {
|
|
|
2556
2788
|
}
|
|
2557
2789
|
return "provider-error";
|
|
2558
2790
|
};
|
|
2791
|
+
var setupAbortWatch = (parentSignal, timeoutMs) => {
|
|
2792
|
+
const controller = new AbortController;
|
|
2793
|
+
let timedOut = false;
|
|
2794
|
+
const abortFromParent = () => controller.abort(parentSignal?.reason);
|
|
2795
|
+
parentSignal?.addEventListener("abort", abortFromParent, { once: true });
|
|
2796
|
+
const timer = setTimeout(() => {
|
|
2797
|
+
timedOut = true;
|
|
2798
|
+
controller.abort(new Error("Scout timed out."));
|
|
2799
|
+
}, timeoutMs);
|
|
2800
|
+
timer.unref?.();
|
|
2801
|
+
const { promise: abortPromise, reject: rejectOnAbort } = Promise.withResolvers();
|
|
2802
|
+
const onControllerAbort = () => rejectOnAbort(controller.signal.reason ?? new Error("Scout aborted."));
|
|
2803
|
+
controller.signal.addEventListener("abort", onControllerAbort, {
|
|
2804
|
+
once: true
|
|
2805
|
+
});
|
|
2806
|
+
return {
|
|
2807
|
+
abortPromise,
|
|
2808
|
+
controller,
|
|
2809
|
+
teardown: () => {
|
|
2810
|
+
clearTimeout(timer);
|
|
2811
|
+
parentSignal?.removeEventListener("abort", abortFromParent);
|
|
2812
|
+
controller.signal.removeEventListener("abort", onControllerAbort);
|
|
2813
|
+
},
|
|
2814
|
+
wasTimedOut: () => timedOut
|
|
2815
|
+
};
|
|
2816
|
+
};
|
|
2817
|
+
var streamScoutResponse = async (dependencies, resolved, manifest, parentSignal, timeoutMs, publish) => {
|
|
2818
|
+
const { abortPromise, controller, teardown, wasTimedOut } = setupAbortWatch(parentSignal, timeoutMs);
|
|
2819
|
+
try {
|
|
2820
|
+
const collection = dependencies.collect(resolved, {
|
|
2821
|
+
messages: [manifestMessage(manifest)],
|
|
2822
|
+
onChunk: (thinking, text) => {
|
|
2823
|
+
if (!controller.signal.aborted) {
|
|
2824
|
+
publish({ model: executorRef, text, thinking, type: "chunk" });
|
|
2825
|
+
}
|
|
2826
|
+
},
|
|
2827
|
+
reasoning: executorEffortRef,
|
|
2828
|
+
signal: controller.signal,
|
|
2829
|
+
systemPrompt: SCOUT_SYSTEM
|
|
2830
|
+
});
|
|
2831
|
+
return {
|
|
2832
|
+
ok: true,
|
|
2833
|
+
streamed: await Promise.race([collection, abortPromise])
|
|
2834
|
+
};
|
|
2835
|
+
} catch (error) {
|
|
2836
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2837
|
+
return wasTimedOut() ? {
|
|
2838
|
+
category: "timeout",
|
|
2839
|
+
message: `Scout timed out after ${timeoutMs} ms.`,
|
|
2840
|
+
ok: false
|
|
2841
|
+
} : { category: "provider-error", message, ok: false };
|
|
2842
|
+
} finally {
|
|
2843
|
+
teardown();
|
|
2844
|
+
}
|
|
2845
|
+
};
|
|
2559
2846
|
var runAdvisorScout = async (ctx, manifest, parentSignal, onEvent, timeoutMs = SCOUT_TIMEOUT_MS, dependencies = defaultDependencies) => {
|
|
2560
2847
|
const startedAt = Date.now();
|
|
2561
2848
|
const publish = (event) => {
|
|
@@ -2593,169 +2880,40 @@ var runAdvisorScout = async (ctx, manifest, parentSignal, onEvent, timeoutMs = S
|
|
|
2593
2880
|
return cancelled();
|
|
2594
2881
|
}
|
|
2595
2882
|
publish({ model: executorRef, type: "call" });
|
|
2596
|
-
const
|
|
2597
|
-
|
|
2598
|
-
const abortFromParent = () => controller.abort(parentSignal?.reason);
|
|
2599
|
-
parentSignal?.addEventListener("abort", abortFromParent, { once: true });
|
|
2600
|
-
const timer = setTimeout(() => {
|
|
2601
|
-
timedOut = true;
|
|
2602
|
-
controller.abort(new Error("Scout timed out."));
|
|
2603
|
-
}, timeoutMs);
|
|
2604
|
-
timer.unref?.();
|
|
2605
|
-
let rejectOnAbort;
|
|
2606
|
-
const onControllerAbort = () => rejectOnAbort?.(controller.signal.reason ?? new Error("Scout aborted."));
|
|
2607
|
-
const abortPromise = new Promise((_resolve, reject) => {
|
|
2608
|
-
rejectOnAbort = reject;
|
|
2609
|
-
controller.signal.addEventListener("abort", onControllerAbort, {
|
|
2610
|
-
once: true
|
|
2611
|
-
});
|
|
2612
|
-
});
|
|
2613
|
-
const teardown = () => {
|
|
2614
|
-
clearTimeout(timer);
|
|
2615
|
-
parentSignal?.removeEventListener("abort", abortFromParent);
|
|
2616
|
-
controller.signal.removeEventListener("abort", onControllerAbort);
|
|
2617
|
-
};
|
|
2618
|
-
let streamed;
|
|
2619
|
-
try {
|
|
2620
|
-
const collection = dependencies.collect(resolved, {
|
|
2621
|
-
messages: [manifestMessage(manifest)],
|
|
2622
|
-
onChunk: (thinking, text) => {
|
|
2623
|
-
if (!controller.signal.aborted) {
|
|
2624
|
-
publish({ model: executorRef, text, thinking, type: "chunk" });
|
|
2625
|
-
}
|
|
2626
|
-
},
|
|
2627
|
-
reasoning: executorEffortRef,
|
|
2628
|
-
signal: controller.signal,
|
|
2629
|
-
systemPrompt: SCOUT_SYSTEM
|
|
2630
|
-
});
|
|
2631
|
-
streamed = await Promise.race([collection, abortPromise]);
|
|
2632
|
-
} catch (error) {
|
|
2633
|
-
teardown();
|
|
2883
|
+
const streamed = await streamScoutResponse(dependencies, resolved, manifest, parentSignal, timeoutMs, publish);
|
|
2884
|
+
if (!streamed.ok) {
|
|
2634
2885
|
if (parentSignal?.aborted) {
|
|
2635
2886
|
return cancelled();
|
|
2636
2887
|
}
|
|
2637
|
-
|
|
2638
|
-
return timedOut ? fallback("timeout", `Scout timed out after ${timeoutMs} ms.`) : fallback("provider-error", message);
|
|
2888
|
+
return fallback(streamed.category, streamed.message);
|
|
2639
2889
|
}
|
|
2640
|
-
teardown();
|
|
2641
2890
|
if (parentSignal?.aborted) {
|
|
2642
2891
|
return cancelled();
|
|
2643
2892
|
}
|
|
2644
|
-
let selection;
|
|
2645
|
-
try {
|
|
2646
|
-
selection = parseScoutSelection(streamed.text, manifest);
|
|
2647
|
-
} catch (error) {
|
|
2648
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
2649
|
-
return fallback(streamed.text.trim() ? "invalid-selection" : "empty-response", message, snapshotAdvisorUsage(streamed.usage));
|
|
2650
|
-
}
|
|
2651
|
-
const outcome = {
|
|
2652
|
-
conversation: reconstructScoutConversation(manifest, selection.selectedIds, selection.synthesis),
|
|
2653
|
-
metrics: {
|
|
2654
|
-
...baseMetrics(manifest, startedAt),
|
|
2655
|
-
selectedCount: new Set([
|
|
2656
|
-
...selection.selectedIds,
|
|
2657
|
-
...manifest.groups.filter((group) => group.required).map((group) => group.id)
|
|
2658
|
-
]).size,
|
|
2659
|
-
usage: snapshotAdvisorUsage(streamed.usage)
|
|
2660
|
-
},
|
|
2661
|
-
model: executorRef,
|
|
2662
|
-
ok: true,
|
|
2663
|
-
selectedLabels: manifest.groups.filter((group) => group.required || selection.selectedIds.includes(group.id)).map((group) => group.label),
|
|
2664
|
-
selection
|
|
2665
|
-
};
|
|
2666
|
-
publish({ outcome, type: "success" });
|
|
2667
|
-
return outcome;
|
|
2668
|
-
};
|
|
2669
|
-
|
|
2670
|
-
// src/scout-context.ts
|
|
2671
|
-
var resolveCaps = (options) => ({
|
|
2672
|
-
currentInvocationId: options.currentInvocationId,
|
|
2673
|
-
maxConversationChars: options.maxConversationChars,
|
|
2674
|
-
maxGroupBytes: options.maxGroupBytes ?? SCOUT_GROUP_MAX_BYTES,
|
|
2675
|
-
maxGroups: options.maxGroups ?? SCOUT_MANIFEST_MAX_GROUPS,
|
|
2676
|
-
maxManifestBytes: options.maxManifestBytes ?? options.maxBytes ?? SCOUT_MANIFEST_MAX_BYTES,
|
|
2677
|
-
policies: options.policies ?? advisorToolPoliciesRef,
|
|
2678
|
-
redact: options.redact ?? advisorRedactSecretsRef,
|
|
2679
|
-
toolResultMaxBytes: options.toolResultMaxBytes ?? advisorToolResultMaxBytesRef,
|
|
2680
|
-
toolResultMaxLines: options.toolResultMaxLines ?? advisorToolResultMaxLinesRef
|
|
2681
|
-
});
|
|
2682
|
-
var buildScoutManifest = (ctx, options = {}) => {
|
|
2683
|
-
const entries = ctx.sessionManager.buildContextEntries();
|
|
2684
|
-
const caps = resolveCaps(options);
|
|
2685
|
-
const indexed = indexToolCalls(entries);
|
|
2686
|
-
if (!indexed.ok) {
|
|
2687
|
-
return indexed;
|
|
2688
|
-
}
|
|
2689
|
-
const built = buildGroups(entries, indexed.index, caps);
|
|
2690
|
-
if (!built.ok) {
|
|
2691
|
-
return built;
|
|
2692
|
-
}
|
|
2693
|
-
return fitToBudget(built, caps);
|
|
2694
|
-
};
|
|
2695
|
-
var fits = (selected, caps) => selected.length <= caps.maxGroups && selected.reduce((sum, group) => sum + groupWireBytes(group), 0) <= caps.maxManifestBytes && (caps.maxConversationChars === undefined || contentChars(selected) <= caps.maxConversationChars);
|
|
2696
|
-
var contentChars = (items) => items.reduce((sum, group) => sum + group.content.length, 0) + Math.max(0, items.length - 1) * 2;
|
|
2697
|
-
var fitToBudget = (built, caps) => {
|
|
2698
|
-
const { groups, protocolOmittedBytes, protocolOmittedCount } = built;
|
|
2699
|
-
const availableCount = groups.length + protocolOmittedCount;
|
|
2700
|
-
const availableBytes = groups.reduce((sum, group) => sum + groupWireBytes(group), 0) + protocolOmittedBytes;
|
|
2701
|
-
if (caps.maxManifestBytes <= 0) {
|
|
2702
|
-
return {
|
|
2703
|
-
manifest: {
|
|
2704
|
-
availableBytes: 0,
|
|
2705
|
-
availableCount: 0,
|
|
2706
|
-
groups: [],
|
|
2707
|
-
omittedBytes: 0,
|
|
2708
|
-
omittedCount: 0
|
|
2709
|
-
},
|
|
2710
|
-
ok: true
|
|
2711
|
-
};
|
|
2712
|
-
}
|
|
2713
|
-
const required = groups.filter((group) => group.required);
|
|
2714
|
-
const overflow = requiredOverflow(required, caps);
|
|
2715
|
-
if (overflow) {
|
|
2716
|
-
return overflow;
|
|
2717
|
-
}
|
|
2718
|
-
const selected = groups.filter((group) => group.required || group.bytes <= caps.maxGroupBytes);
|
|
2719
|
-
while (!fits(selected, caps)) {
|
|
2720
|
-
const optionalIndex = selected.findIndex((group) => !group.required);
|
|
2721
|
-
if (optionalIndex < 0) {
|
|
2722
|
-
return {
|
|
2723
|
-
message: "Required Scout context exceeds fixed manifest limits.",
|
|
2724
|
-
ok: false,
|
|
2725
|
-
reason: "required-group-overflow"
|
|
2726
|
-
};
|
|
2727
|
-
}
|
|
2728
|
-
selected.splice(optionalIndex, 1);
|
|
2729
|
-
}
|
|
2730
|
-
const selectedIds = new Set(selected.map((group) => group.id));
|
|
2731
|
-
const omitted = groups.filter((group) => !selectedIds.has(group.id));
|
|
2732
|
-
return {
|
|
2733
|
-
manifest: {
|
|
2734
|
-
availableBytes,
|
|
2735
|
-
availableCount,
|
|
2736
|
-
groups: selected,
|
|
2737
|
-
omittedBytes: protocolOmittedBytes + omitted.reduce((sum, group) => sum + group.bytes, 0),
|
|
2738
|
-
omittedCount: protocolOmittedCount + omitted.length
|
|
2893
|
+
let selection;
|
|
2894
|
+
try {
|
|
2895
|
+
selection = parseScoutSelection(streamed.streamed.text, manifest);
|
|
2896
|
+
} catch (error) {
|
|
2897
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2898
|
+
return fallback(streamed.streamed.text.trim() ? "invalid-selection" : "empty-response", message, snapshotAdvisorUsage(streamed.streamed.usage));
|
|
2899
|
+
}
|
|
2900
|
+
const outcome = {
|
|
2901
|
+
conversation: reconstructScoutConversation(manifest, selection.selectedIds, selection.synthesis),
|
|
2902
|
+
metrics: {
|
|
2903
|
+
...baseMetrics(manifest, startedAt),
|
|
2904
|
+
selectedCount: new Set([
|
|
2905
|
+
...selection.selectedIds,
|
|
2906
|
+
...manifest.groups.filter((group) => group.required).map((group) => group.id)
|
|
2907
|
+
]).size,
|
|
2908
|
+
usage: snapshotAdvisorUsage(streamed.streamed.usage)
|
|
2739
2909
|
},
|
|
2740
|
-
|
|
2910
|
+
model: executorRef,
|
|
2911
|
+
ok: true,
|
|
2912
|
+
selectedLabels: manifest.groups.filter((group) => group.required || selection.selectedIds.includes(group.id)).map((group) => group.label),
|
|
2913
|
+
selection
|
|
2741
2914
|
};
|
|
2742
|
-
};
|
|
2743
|
-
|
|
2744
|
-
if (required.some((group) => group.bytes > caps.maxGroupBytes) || required.length > caps.maxGroups || required.reduce((sum, group) => sum + groupWireBytes(group), 0) > caps.maxManifestBytes) {
|
|
2745
|
-
return {
|
|
2746
|
-
message: "Required Scout context exceeds the Scout manifest transport limit.",
|
|
2747
|
-
ok: false,
|
|
2748
|
-
reason: "required-group-overflow"
|
|
2749
|
-
};
|
|
2750
|
-
}
|
|
2751
|
-
if (caps.maxConversationChars !== undefined && contentChars(required) > caps.maxConversationChars) {
|
|
2752
|
-
return {
|
|
2753
|
-
message: "Required Scout context exceeds the Advisor conversation budget.",
|
|
2754
|
-
ok: false,
|
|
2755
|
-
reason: "required-group-overflow"
|
|
2756
|
-
};
|
|
2757
|
-
}
|
|
2758
|
-
return;
|
|
2915
|
+
publish({ outcome, type: "success" });
|
|
2916
|
+
return outcome;
|
|
2759
2917
|
};
|
|
2760
2918
|
|
|
2761
2919
|
// src/scout-curation.ts
|
|
@@ -2788,7 +2946,7 @@ var curateAdvisorConversation = async (ctx, legacyConversation, signal, onScout,
|
|
|
2788
2946
|
onScout?.({ outcome: scout, type: "fallback" });
|
|
2789
2947
|
return { conversation: legacyConversation, scout };
|
|
2790
2948
|
}
|
|
2791
|
-
const outcome = await runScout(ctx, built.manifest, signal, onScout
|
|
2949
|
+
const outcome = await runScout(ctx, built.manifest, signal, onScout);
|
|
2792
2950
|
if (!outcome.ok && outcome.cancelled) {
|
|
2793
2951
|
throw signal?.reason instanceof Error ? signal.reason : new Error("Advisor operation cancelled during Scout.");
|
|
2794
2952
|
}
|
|
@@ -2839,26 +2997,21 @@ ${question}` : ""}`;
|
|
|
2839
2997
|
return text.trim() || "No conversation context is available. State that you cannot review without context.";
|
|
2840
2998
|
};
|
|
2841
2999
|
var advisorGitContextBudget = (contextMaxChars, gitContextMaxChars) => Math.min(gitContextMaxChars, Math.floor(contextMaxChars / 2));
|
|
3000
|
+
var LEVEL_WITHHELD = {
|
|
3001
|
+
collected: true,
|
|
3002
|
+
"no-changes": false
|
|
3003
|
+
};
|
|
3004
|
+
var STATUS_NOTES = {
|
|
3005
|
+
disabled: "Repository context was disabled or had no disclosure budget; it was withheld. Do not assume the working tree is clean.",
|
|
3006
|
+
failed: "Repository context could not be collected. Do not assume the working tree is clean.",
|
|
3007
|
+
"no-changes": "The working tree has no uncommitted changes.",
|
|
3008
|
+
"not-a-repository": "No Git repository is available for this session."
|
|
3009
|
+
};
|
|
2842
3010
|
var gitContextNote = (result, requested, allowed) => {
|
|
2843
3011
|
if (requested !== allowed && LEVEL_WITHHELD[result.status]) {
|
|
2844
3012
|
return `Repository context was limited to "${allowed}" by user configuration; a fuller view was requested but withheld.`;
|
|
2845
3013
|
}
|
|
2846
|
-
|
|
2847
|
-
case "disabled":
|
|
2848
|
-
return "Repository context was disabled or had no disclosure budget; it was withheld. Do not assume the working tree is clean.";
|
|
2849
|
-
case "no-changes":
|
|
2850
|
-
return "The working tree has no uncommitted changes.";
|
|
2851
|
-
case "not-a-repository":
|
|
2852
|
-
return "No Git repository is available for this session.";
|
|
2853
|
-
case "failed":
|
|
2854
|
-
return "Repository context could not be collected. Do not assume the working tree is clean.";
|
|
2855
|
-
default:
|
|
2856
|
-
return;
|
|
2857
|
-
}
|
|
2858
|
-
};
|
|
2859
|
-
var LEVEL_WITHHELD = {
|
|
2860
|
-
collected: true,
|
|
2861
|
-
"no-changes": false
|
|
3014
|
+
return STATUS_NOTES[result.status];
|
|
2862
3015
|
};
|
|
2863
3016
|
var advisorRepositoryContext = (result, requested, allowed, budget) => {
|
|
2864
3017
|
const note = gitContextNote(result, requested, allowed);
|
|
@@ -2937,9 +3090,10 @@ var assembleConsultationContext = async (options) => {
|
|
|
2937
3090
|
};
|
|
2938
3091
|
|
|
2939
3092
|
// src/tools/gate-protocol.ts
|
|
2940
|
-
var
|
|
2941
|
-
var
|
|
2942
|
-
var
|
|
3093
|
+
var namedGroups = (match) => match.groups ?? {};
|
|
3094
|
+
var DECISION_LINE = /^Decision\s*:\s*(?<decision>proceed|revise|blocked)\s*$/iu;
|
|
3095
|
+
var CODE_FENCE = /^ {0,3}(?<marker>`{3,}|~{3,})(?<suffix>.*)$/u;
|
|
3096
|
+
var LINE_BREAK = /\r?\n/u;
|
|
2943
3097
|
var advanceFence = (openingFence, marker, suffix) => {
|
|
2944
3098
|
if (!openingFence) {
|
|
2945
3099
|
if (marker[0] === "`" && suffix.includes("`")) {
|
|
@@ -2975,7 +3129,7 @@ var parseAutomaticDecision = (text) => {
|
|
|
2975
3129
|
ok: false
|
|
2976
3130
|
};
|
|
2977
3131
|
}
|
|
2978
|
-
const decision = match
|
|
3132
|
+
const decision = namedGroups(match).decision.toLowerCase();
|
|
2979
3133
|
let openingFence;
|
|
2980
3134
|
const decisions = [];
|
|
2981
3135
|
let pendingFencedDecisions = [];
|
|
@@ -2983,7 +3137,8 @@ var parseAutomaticDecision = (text) => {
|
|
|
2983
3137
|
const trimmed = line.trim();
|
|
2984
3138
|
const fence = CODE_FENCE.exec(line);
|
|
2985
3139
|
if (fence) {
|
|
2986
|
-
const
|
|
3140
|
+
const groups = namedGroups(fence);
|
|
3141
|
+
const { closed, openingFence: nextOpeningFence } = advanceFence(openingFence, groups.marker, groups.suffix);
|
|
2987
3142
|
openingFence = nextOpeningFence;
|
|
2988
3143
|
if (closed) {
|
|
2989
3144
|
pendingFencedDecisions = [];
|
|
@@ -2994,7 +3149,7 @@ var parseAutomaticDecision = (text) => {
|
|
|
2994
3149
|
if (!subsequent) {
|
|
2995
3150
|
continue;
|
|
2996
3151
|
}
|
|
2997
|
-
const repeated = subsequent
|
|
3152
|
+
const repeated = namedGroups(subsequent).decision.trim().toLowerCase();
|
|
2998
3153
|
if (openingFence) {
|
|
2999
3154
|
pendingFencedDecisions.push(repeated);
|
|
3000
3155
|
} else {
|
|
@@ -3004,16 +3159,14 @@ var parseAutomaticDecision = (text) => {
|
|
|
3004
3159
|
if (openingFence) {
|
|
3005
3160
|
decisions.push(...pendingFencedDecisions);
|
|
3006
3161
|
}
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
}
|
|
3016
|
-
return {
|
|
3162
|
+
const [repeated] = decisions;
|
|
3163
|
+
if (repeated !== undefined) {
|
|
3164
|
+
return repeated === decision ? {
|
|
3165
|
+
category: "duplicate-decision",
|
|
3166
|
+
markdown: text,
|
|
3167
|
+
message: "Advisor gate response contains duplicate decision lines.",
|
|
3168
|
+
ok: false
|
|
3169
|
+
} : {
|
|
3017
3170
|
category: "contradictory-decision",
|
|
3018
3171
|
markdown: text,
|
|
3019
3172
|
message: "Advisor gate response contains contradictory decision lines.",
|
|
@@ -3033,6 +3186,35 @@ var adviceForGateText = (result) => `**Decision: ${result.decision}**
|
|
|
3033
3186
|
|
|
3034
3187
|
${result.markdown}`;
|
|
3035
3188
|
|
|
3189
|
+
// src/tools/model-access.ts
|
|
3190
|
+
var currentModelRef = (ctx) => {
|
|
3191
|
+
const { model } = ctx;
|
|
3192
|
+
return model ? `${model.provider}/${model.id}` : undefined;
|
|
3193
|
+
};
|
|
3194
|
+
var advisorModelAccess = (ctx) => {
|
|
3195
|
+
const modelRef = currentModelRef(ctx);
|
|
3196
|
+
if (advisorModelWhitelistRef.length === 0) {
|
|
3197
|
+
return modelRef ? { allowed: true, modelRef } : { allowed: true };
|
|
3198
|
+
}
|
|
3199
|
+
if (modelRef && advisorModelWhitelistRef.includes(modelRef)) {
|
|
3200
|
+
return { allowed: true, modelRef };
|
|
3201
|
+
}
|
|
3202
|
+
const current = modelRef ?? "no current model";
|
|
3203
|
+
const denial = {
|
|
3204
|
+
allowed: false,
|
|
3205
|
+
reason: `Advisor calls are restricted to the configured model whitelist (${advisorModelWhitelistRef.join(", ")}). Current model: ${current}.`
|
|
3206
|
+
};
|
|
3207
|
+
if (modelRef) {
|
|
3208
|
+
denial.modelRef = modelRef;
|
|
3209
|
+
}
|
|
3210
|
+
return denial;
|
|
3211
|
+
};
|
|
3212
|
+
var advisorModelIsAllowed = (ctx) => advisorModelAccess(ctx).allowed;
|
|
3213
|
+
var advisorModelAccessReason = (ctx) => {
|
|
3214
|
+
const access = advisorModelAccess(ctx);
|
|
3215
|
+
return access.allowed ? undefined : access.reason;
|
|
3216
|
+
};
|
|
3217
|
+
|
|
3036
3218
|
// src/tools/consultation.ts
|
|
3037
3219
|
class AdvisorNoAdviceError extends Error {
|
|
3038
3220
|
constructor() {
|
|
@@ -3046,6 +3228,10 @@ ${item.text}
|
|
|
3046
3228
|
var collectAdvisorResponse = async (options) => {
|
|
3047
3229
|
const { ctx, question, signal, systemPrompt } = options;
|
|
3048
3230
|
loadConfig(ctx);
|
|
3231
|
+
const accessReason = advisorModelAccessReason(ctx);
|
|
3232
|
+
if (accessReason) {
|
|
3233
|
+
throw new Error(accessReason);
|
|
3234
|
+
}
|
|
3049
3235
|
const resolved = await resolveConfiguredModel(ctx, advisorRef, "Advisor");
|
|
3050
3236
|
const context = await assembleConsultationContext(options);
|
|
3051
3237
|
const outboundQuestion = advisorRedactSecretsRef && question !== undefined ? redactSecrets(question) : question;
|
|
@@ -3072,17 +3258,20 @@ var collectAdvisorResponse = async (options) => {
|
|
|
3072
3258
|
if (!markdown.trim()) {
|
|
3073
3259
|
throw new AdvisorNoAdviceError;
|
|
3074
3260
|
}
|
|
3075
|
-
|
|
3076
|
-
draftBytes: context.draftText ? Buffer.byteLength(context.draftText, "
|
|
3261
|
+
const response = {
|
|
3262
|
+
draftBytes: context.draftText ? Buffer.byteLength(context.draftText, "utf-8") : undefined,
|
|
3077
3263
|
markdown,
|
|
3078
3264
|
model: advisorRef,
|
|
3079
3265
|
preferenceBytes: context.preferences?.bytes,
|
|
3080
3266
|
thinkingText: streamed.thinking,
|
|
3081
3267
|
trackedBytes: context.tracked.reduce((sum, item) => sum + item.bytes, 0) || undefined,
|
|
3082
3268
|
untrackedBytes: context.untracked.reduce((sum, item) => sum + item.bytes, 0) || undefined,
|
|
3083
|
-
usage: streamed.usage
|
|
3084
|
-
...context.scout ? { scout: context.scout } : {}
|
|
3269
|
+
usage: streamed.usage
|
|
3085
3270
|
};
|
|
3271
|
+
if (context.scout) {
|
|
3272
|
+
response.scout = context.scout;
|
|
3273
|
+
}
|
|
3274
|
+
return response;
|
|
3086
3275
|
};
|
|
3087
3276
|
var consultAdvisor = async (ctx, question, signal, onChunk, trigger = "executor-requested", gitContext, draft, includeUntracked, includeTracked, onScout, currentInvocationId) => {
|
|
3088
3277
|
const result = await collectAdvisorResponse({
|
|
@@ -3164,8 +3353,8 @@ var settingsIdentity = (path) => {
|
|
|
3164
3353
|
};
|
|
3165
3354
|
var readHideThinking = (path) => {
|
|
3166
3355
|
try {
|
|
3167
|
-
const parsed = JSON.parse(readFileSync2(path, "
|
|
3168
|
-
return
|
|
3356
|
+
const parsed = JSON.parse(readFileSync2(path, "utf-8"));
|
|
3357
|
+
return isRecord(parsed) && parsed.hideThinkingBlock === true;
|
|
3169
3358
|
} catch {
|
|
3170
3359
|
return false;
|
|
3171
3360
|
}
|
|
@@ -3201,6 +3390,9 @@ var SPINNER_FRAMES = [
|
|
|
3201
3390
|
];
|
|
3202
3391
|
var THINKING_PREFIX = " ";
|
|
3203
3392
|
var THINKING_PREFIX_WIDTH = visibleWidth(THINKING_PREFIX);
|
|
3393
|
+
var noop = () => {
|
|
3394
|
+
return;
|
|
3395
|
+
};
|
|
3204
3396
|
|
|
3205
3397
|
class ThinkingMarkdown {
|
|
3206
3398
|
markdown;
|
|
@@ -3222,18 +3414,14 @@ class ThinkingMarkdown {
|
|
|
3222
3414
|
this.markdown.invalidate();
|
|
3223
3415
|
}
|
|
3224
3416
|
}
|
|
3225
|
-
|
|
3226
|
-
|
|
3227
|
-
|
|
3228
|
-
|
|
3229
|
-
|
|
3230
|
-
}
|
|
3231
|
-
|
|
3232
|
-
|
|
3233
|
-
}
|
|
3234
|
-
invalidate() {}
|
|
3235
|
-
}
|
|
3236
|
-
var renderThinkingMarkdown = (thinking, theme) => piHideThinkingEnabled() ? new HiddenThinkingLabel(theme) : new ThinkingMarkdown(thinking, theme);
|
|
3417
|
+
var hiddenThinkingLabel = (theme) => {
|
|
3418
|
+
const label = theme.fg("thinkingText", `${THINKING_PREFIX}Thinking…`);
|
|
3419
|
+
return {
|
|
3420
|
+
invalidate: noop,
|
|
3421
|
+
render: () => [label]
|
|
3422
|
+
};
|
|
3423
|
+
};
|
|
3424
|
+
var renderThinkingMarkdown = (thinking, theme) => piHideThinkingEnabled() ? hiddenThinkingLabel(theme) : new ThinkingMarkdown(thinking, theme);
|
|
3237
3425
|
var resolveAdvisorRequest = (question) => question?.trim() || undefined;
|
|
3238
3426
|
var renderAdvisorCallBox = (question, theme) => {
|
|
3239
3427
|
const box = new Box(1, 1, (text) => theme.bg("customMessageBg", text));
|
|
@@ -3244,7 +3432,7 @@ ${theme.fg("dim", ` ${question}`)}` : `${label} ${title}`, 0, 0));
|
|
|
3244
3432
|
return box;
|
|
3245
3433
|
};
|
|
3246
3434
|
var COLLAPSED_ADVICE_LINES = 12;
|
|
3247
|
-
var SOUND_VERDICT = /^Verdict:\s*sound
|
|
3435
|
+
var SOUND_VERDICT = /^Verdict:\s*sound$/u;
|
|
3248
3436
|
var hasSoundVerdict = (advice) => SOUND_VERDICT.test((advice.split(`
|
|
3249
3437
|
`).find((line) => line.trim()) ?? "").trim());
|
|
3250
3438
|
var renderAdvisorResponseHeader = (sound, theme) => sound ? theme.fg("accent", theme.bold("◆ ADVISOR · SOUND")) : theme.fg("warning", theme.bold("◆ ADVISOR RESPONSE"));
|
|
@@ -3372,13 +3560,27 @@ var scoutTitle = (scout, frame) => {
|
|
|
3372
3560
|
}
|
|
3373
3561
|
return "◆ SCOUT · FALLBACK";
|
|
3374
3562
|
};
|
|
3563
|
+
var scoutSummaryLine = (scout) => ` ${scout.model}${scout.selectedCount === undefined ? "" : ` · ${scout.selectedCount} kept / ${Math.max(0, (scout.availableCount ?? 0) - scout.selectedCount)} omitted`}${scout.latencyMs === undefined ? "" : ` · ${(scout.latencyMs / 1000).toFixed(1)}s`}`;
|
|
3564
|
+
var scoutExpandedLines = (scout, expanded, theme) => {
|
|
3565
|
+
const lines = [];
|
|
3566
|
+
if (expanded && scout.selectedLabels?.length) {
|
|
3567
|
+
lines.push(theme.fg("dim", ` Selected: ${scout.selectedLabels.join("; ")}`));
|
|
3568
|
+
}
|
|
3569
|
+
if (expanded && scout.synthesis) {
|
|
3570
|
+
lines.push(theme.fg("dim", ` Scout synthesis (untrusted inference): ${scout.synthesis}`));
|
|
3571
|
+
}
|
|
3572
|
+
if (expanded && scout.omittedBeforeScout) {
|
|
3573
|
+
lines.push(theme.fg("dim", ` ${scout.omittedBeforeScout} group(s) omitted before Scout`));
|
|
3574
|
+
}
|
|
3575
|
+
return lines;
|
|
3576
|
+
};
|
|
3375
3577
|
var renderScoutDetails = (box, scout, expanded, theme) => {
|
|
3376
3578
|
const active = scout.status === "calling" || scout.status === "streaming";
|
|
3377
3579
|
const frame = SPINNER_FRAMES[Math.floor(Date.now() / 80) % SPINNER_FRAMES.length];
|
|
3378
3580
|
const title = scoutTitle(scout, frame);
|
|
3379
3581
|
const lines = [
|
|
3380
3582
|
theme.fg(scout.status === "fallback" || scout.status === "cancelled" ? "warning" : "accent", theme.bold(title)),
|
|
3381
|
-
theme.fg("dim",
|
|
3583
|
+
theme.fg("dim", scoutSummaryLine(scout))
|
|
3382
3584
|
];
|
|
3383
3585
|
if (getAdvisorSettings().showUsageDetails) {
|
|
3384
3586
|
const usage = formatAdvisorUsage(scout.usage);
|
|
@@ -3395,16 +3597,7 @@ var renderScoutDetails = (box, scout, expanded, theme) => {
|
|
|
3395
3597
|
if (thinking.trim()) {
|
|
3396
3598
|
box.addChild(renderThinkingMarkdown(thinking, theme));
|
|
3397
3599
|
}
|
|
3398
|
-
const expandedLines =
|
|
3399
|
-
if (expanded && scout.selectedLabels?.length) {
|
|
3400
|
-
expandedLines.push(theme.fg("dim", ` Selected: ${scout.selectedLabels.join("; ")}`));
|
|
3401
|
-
}
|
|
3402
|
-
if (expanded && scout.synthesis) {
|
|
3403
|
-
expandedLines.push(theme.fg("dim", ` Scout synthesis (untrusted inference): ${scout.synthesis}`));
|
|
3404
|
-
}
|
|
3405
|
-
if (expanded && scout.omittedBeforeScout) {
|
|
3406
|
-
expandedLines.push(theme.fg("dim", ` ${scout.omittedBeforeScout} group(s) omitted before Scout`));
|
|
3407
|
-
}
|
|
3600
|
+
const expandedLines = scoutExpandedLines(scout, expanded, theme);
|
|
3408
3601
|
if (expandedLines.length > 0) {
|
|
3409
3602
|
box.addChild(new Text2(expandedLines.join(`
|
|
3410
3603
|
`), 0, 0));
|
|
@@ -3434,6 +3627,31 @@ var addJevUsage = (totals, usage) => {
|
|
|
3434
3627
|
totals.inputTokens += usage.inputTokens;
|
|
3435
3628
|
totals.outputTokens += usage.outputTokens;
|
|
3436
3629
|
};
|
|
3630
|
+
var savingsLine = (costs, skipped) => {
|
|
3631
|
+
if (costs.length === 0) {
|
|
3632
|
+
return "Estimated saving from skips: unavailable — no observed consultation cost this session";
|
|
3633
|
+
}
|
|
3634
|
+
const mean = costs.reduce((sum, cost) => sum + cost, 0) / costs.length;
|
|
3635
|
+
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`;
|
|
3636
|
+
};
|
|
3637
|
+
var formatJevTokens = (usage) => `↑${formatTokenCount(usage.inputTokens + usage.outputTokens)}`;
|
|
3638
|
+
var markdownCosts = (invocations) => invocations.filter((item) => item.kind === "markdown" && typeof item.cost === "number").map((item) => item.cost);
|
|
3639
|
+
var filterLine = (filter) => {
|
|
3640
|
+
const head = `${filter.screened} screened (${filter.allowed} allowed, ${filter.skipped} skipped${filter.repeatSkipped > 0 ? ` [${filter.repeatSkipped} repeat]` : ""})`;
|
|
3641
|
+
const parts = [head];
|
|
3642
|
+
if (filter.overrides > 0) {
|
|
3643
|
+
parts.push(`${filter.overrides} override${filter.overrides === 1 ? "" : "s"}`);
|
|
3644
|
+
}
|
|
3645
|
+
if (filter.failures > 0) {
|
|
3646
|
+
parts.push(`${filter.failures} failure${filter.failures === 1 ? "" : "s"}`);
|
|
3647
|
+
}
|
|
3648
|
+
return `Jev filter: ${parts.join(", ")}`;
|
|
3649
|
+
};
|
|
3650
|
+
var gateLine = (gate, invocations) => {
|
|
3651
|
+
const consultationCosts = invocations.filter((item) => item.trigger === "turn-gate" && typeof item.cost === "number").map((item) => item.cost);
|
|
3652
|
+
const gateSpend = consultationCosts.reduce((sum, cost) => sum + cost, 0);
|
|
3653
|
+
return `Turn gate: ${gate.checks} check${gate.checks === 1 ? "" : "s"} (Jev ${formatJevTokens(gate.usage)} · $${gate.usage.cost.toFixed(4)}), ${gate.consultations} consultation${gate.consultations === 1 ? "" : "s"} ($${gateSpend.toFixed(4)})`;
|
|
3654
|
+
};
|
|
3437
3655
|
|
|
3438
3656
|
class AdvisorJevLedgerState {
|
|
3439
3657
|
#ledger = freshJevLedger();
|
|
@@ -3455,10 +3673,11 @@ class AdvisorJevLedgerState {
|
|
|
3455
3673
|
if (repeat) {
|
|
3456
3674
|
this.#ledger.filter.repeatSkipped += 1;
|
|
3457
3675
|
}
|
|
3458
|
-
|
|
3459
|
-
|
|
3460
|
-
|
|
3461
|
-
}
|
|
3676
|
+
const skip = { turn };
|
|
3677
|
+
if (normalizedQuestion) {
|
|
3678
|
+
skip.normalizedQuestion = normalizedQuestion;
|
|
3679
|
+
}
|
|
3680
|
+
this.#lastSkip = skip;
|
|
3462
3681
|
}
|
|
3463
3682
|
recordFilterOverride() {
|
|
3464
3683
|
this.#ledger.filter.overrides += 1;
|
|
@@ -3492,20 +3711,19 @@ class AdvisorJevLedgerState {
|
|
|
3492
3711
|
if (filter.overrides > 0) {
|
|
3493
3712
|
parts.push(`${filter.overrides} override${filter.overrides === 1 ? "" : "s"}`);
|
|
3494
3713
|
}
|
|
3495
|
-
lines.push(`Consultation dedup: ${parts.join(", ")}
|
|
3496
|
-
lines.push(this.#savingsLine(this.#markdownCosts(invocations), filter.skipped));
|
|
3714
|
+
lines.push(`Consultation dedup: ${parts.join(", ")}`, savingsLine(markdownCosts(invocations), filter.skipped));
|
|
3497
3715
|
} else if (this.#filterActive()) {
|
|
3498
|
-
lines.push(
|
|
3716
|
+
lines.push(filterLine(filter));
|
|
3499
3717
|
const jevTokens = usage.inputTokens + usage.outputTokens;
|
|
3500
3718
|
if (jevTokens > 0) {
|
|
3501
|
-
lines.push(`Jev cost: ${
|
|
3719
|
+
lines.push(`Jev cost: ${formatJevTokens(usage)} tokens · $${usage.cost.toFixed(4)} (input only; output free)`);
|
|
3502
3720
|
}
|
|
3503
3721
|
if (filter.skipped > 0) {
|
|
3504
|
-
lines.push(
|
|
3722
|
+
lines.push(savingsLine(markdownCosts(invocations), filter.skipped));
|
|
3505
3723
|
}
|
|
3506
3724
|
}
|
|
3507
3725
|
if (gate.checks > 0 || gate.consultations > 0) {
|
|
3508
|
-
lines.push(
|
|
3726
|
+
lines.push(gateLine(gate, invocations));
|
|
3509
3727
|
}
|
|
3510
3728
|
return lines;
|
|
3511
3729
|
}
|
|
@@ -3513,39 +3731,10 @@ class AdvisorJevLedgerState {
|
|
|
3513
3731
|
const { filter } = this.#ledger;
|
|
3514
3732
|
return filter.screened > 0 || filter.overrides > 0 || filter.failures > 0;
|
|
3515
3733
|
}
|
|
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
3734
|
}
|
|
3546
3735
|
|
|
3547
3736
|
// src/session-state.ts
|
|
3548
|
-
var WHITESPACE = /\s
|
|
3737
|
+
var WHITESPACE = /\s/u;
|
|
3549
3738
|
var TIMESTAMP_KEYS = new Set([
|
|
3550
3739
|
"createdat",
|
|
3551
3740
|
"date",
|
|
@@ -3555,7 +3744,7 @@ var TIMESTAMP_KEYS = new Set([
|
|
|
3555
3744
|
"updatedat"
|
|
3556
3745
|
]);
|
|
3557
3746
|
var REQUEST_ID_KEYS = new Set(["correlationid", "requestid", "traceid"]);
|
|
3558
|
-
var normalizedKey = (key) => key.
|
|
3747
|
+
var normalizedKey = (key) => key.replaceAll(/[-_]/gu, "").toLowerCase();
|
|
3559
3748
|
var isVolatileKey = (key, keys) => keys.has(normalizedKey(key));
|
|
3560
3749
|
var normalizeShellWhitespace = (command) => {
|
|
3561
3750
|
let result = "";
|
|
@@ -3588,10 +3777,10 @@ var normalizeShellWhitespace = (command) => {
|
|
|
3588
3777
|
}
|
|
3589
3778
|
return result;
|
|
3590
3779
|
};
|
|
3591
|
-
var normalizeString = (value) => value.
|
|
3780
|
+
var normalizeString = (value) => value.replaceAll(/\/(?:private\/)?tmp\/[^\s/]+/gu, "/tmp/<temporary>").replaceAll(/\/var\/folders\/[^\s/]+/gu, "/var/folders/<temporary>");
|
|
3592
3781
|
var normalizeToolInput = (toolName, input) => {
|
|
3593
3782
|
const visit = (value, key) => {
|
|
3594
|
-
if (
|
|
3783
|
+
if (isString(value)) {
|
|
3595
3784
|
if (key && isVolatileKey(key, TIMESTAMP_KEYS)) {
|
|
3596
3785
|
return "<timestamp>";
|
|
3597
3786
|
}
|
|
@@ -3604,9 +3793,8 @@ var normalizeToolInput = (toolName, input) => {
|
|
|
3604
3793
|
if (Array.isArray(value)) {
|
|
3605
3794
|
return value.map((item) => visit(item));
|
|
3606
3795
|
}
|
|
3607
|
-
if (value
|
|
3608
|
-
|
|
3609
|
-
return Object.fromEntries(Object.keys(record).sort().map((childKey) => [childKey, visit(record[childKey], childKey)]));
|
|
3796
|
+
if (isRecordOf(value)) {
|
|
3797
|
+
return Object.fromEntries(Object.keys(value).toSorted().map((childKey) => [childKey, visit(value[childKey], childKey)]));
|
|
3610
3798
|
}
|
|
3611
3799
|
return value;
|
|
3612
3800
|
};
|
|
@@ -3713,11 +3901,11 @@ class AdvisorSessionState {
|
|
|
3713
3901
|
addAdvisorUsage(this.#usage.totals, record.usage);
|
|
3714
3902
|
}
|
|
3715
3903
|
issueAdvice(id, advice, trigger, draft = false, normalizedQuestion) {
|
|
3716
|
-
|
|
3717
|
-
|
|
3718
|
-
|
|
3719
|
-
|
|
3720
|
-
|
|
3904
|
+
const issued = { advice, trigger };
|
|
3905
|
+
if (normalizedQuestion) {
|
|
3906
|
+
issued.normalizedQuestion = normalizedQuestion;
|
|
3907
|
+
}
|
|
3908
|
+
this.#ledger.issued.set(id, issued);
|
|
3721
3909
|
this.#ledger.lastAdvice = advice;
|
|
3722
3910
|
if (draft) {
|
|
3723
3911
|
this.#ledger.draftConsultations += 1;
|
|
@@ -3740,9 +3928,9 @@ class AdvisorSessionState {
|
|
|
3740
3928
|
return false;
|
|
3741
3929
|
}
|
|
3742
3930
|
const mentioned = paths.every((path) => {
|
|
3743
|
-
const escaped = path.
|
|
3744
|
-
const boundary =
|
|
3745
|
-
return new RegExp(boundary).test(advice);
|
|
3931
|
+
const escaped = path.replaceAll(/[.*+?^${}()|[\]\\]/gu, "\\$&");
|
|
3932
|
+
const boundary = `(^|[\\s"'\`()\\[])${escaped}(?=$|[\\s"'\`),;:!?\\]]|\\.(?=\\s|$))`;
|
|
3933
|
+
return new RegExp(boundary, "u").test(advice);
|
|
3746
3934
|
});
|
|
3747
3935
|
if (!mentioned) {
|
|
3748
3936
|
return false;
|
|
@@ -3867,6 +4055,16 @@ var notify = (ctx, message, level) => {
|
|
|
3867
4055
|
ctx.ui.notify(message, level);
|
|
3868
4056
|
}
|
|
3869
4057
|
};
|
|
4058
|
+
var reportManualBudgetExhausted = (ctx) => {
|
|
4059
|
+
const message = "Advisor call budget exhausted for this session.";
|
|
4060
|
+
notify(ctx, message, "warning");
|
|
4061
|
+
notifyHerdrAdvisorFailure("Advisor budget exhausted", message);
|
|
4062
|
+
};
|
|
4063
|
+
var requestManualRender = (ctx) => {
|
|
4064
|
+
if (ctx.hasUI) {
|
|
4065
|
+
ctx.ui.setStatus("advisor-manual", undefined);
|
|
4066
|
+
}
|
|
4067
|
+
};
|
|
3870
4068
|
|
|
3871
4069
|
class CommandRuntime {
|
|
3872
4070
|
advisorSessionState;
|
|
@@ -3874,7 +4072,9 @@ class CommandRuntime {
|
|
|
3874
4072
|
manualProgress = new Map;
|
|
3875
4073
|
manualProgressTimers = new Map;
|
|
3876
4074
|
pi;
|
|
4075
|
+
reportManualBudgetExhausted = reportManualBudgetExhausted;
|
|
3877
4076
|
requestAdvisor;
|
|
4077
|
+
requestManualRender = requestManualRender;
|
|
3878
4078
|
scoutStatus;
|
|
3879
4079
|
manualProgressSequence = 0;
|
|
3880
4080
|
pendingExecutorModelRef;
|
|
@@ -3883,7 +4083,7 @@ class CommandRuntime {
|
|
|
3883
4083
|
this.pi = pi;
|
|
3884
4084
|
this.advisorSessionState = dependencies.sessionState ?? advisorSessionState;
|
|
3885
4085
|
this.scoutStatus = dependencies.statusManager ?? new ScoutStatusManager(false);
|
|
3886
|
-
this.requestAdvisor = dependencies.consult ?? ((ctx, question, signal, onChunk, onScout, gitContext) => consultAdvisor(ctx, question, signal, onChunk, "manual", gitContext, undefined, undefined, undefined, onScout
|
|
4086
|
+
this.requestAdvisor = dependencies.consult ?? ((ctx, question, signal, onChunk, onScout, gitContext) => consultAdvisor(ctx, question, signal, onChunk, "manual", gitContext, undefined, undefined, undefined, onScout));
|
|
3887
4087
|
}
|
|
3888
4088
|
flowEnabled() {
|
|
3889
4089
|
return this.pi.getActiveTools().includes("ask_advisor");
|
|
@@ -3905,16 +4105,6 @@ class CommandRuntime {
|
|
|
3905
4105
|
ctx.ui.setStatus("advisor-usage", getAdvisorSettings().showUsageFooter ? this.advisorSessionState.usageStatus() : undefined);
|
|
3906
4106
|
}
|
|
3907
4107
|
}
|
|
3908
|
-
reportManualBudgetExhausted(ctx) {
|
|
3909
|
-
const message = "Advisor call budget exhausted for this session.";
|
|
3910
|
-
notify(ctx, message, "warning");
|
|
3911
|
-
notifyHerdrAdvisorFailure("Advisor budget exhausted", message);
|
|
3912
|
-
}
|
|
3913
|
-
requestManualRender(ctx) {
|
|
3914
|
-
if (ctx.hasUI) {
|
|
3915
|
-
ctx.ui.setStatus("advisor-manual", undefined);
|
|
3916
|
-
}
|
|
3917
|
-
}
|
|
3918
4108
|
}
|
|
3919
4109
|
var createCommandRuntime = (pi, dependencies = {}) => new CommandRuntime(pi, dependencies);
|
|
3920
4110
|
|
|
@@ -4102,11 +4292,7 @@ var registerCommandLifecycle = (runtime, activateAdvisor) => {
|
|
|
4102
4292
|
};
|
|
4103
4293
|
|
|
4104
4294
|
// src/ui/manual-dialog.ts
|
|
4105
|
-
import {
|
|
4106
|
-
Editor,
|
|
4107
|
-
Key,
|
|
4108
|
-
matchesKey
|
|
4109
|
-
} from "@earendil-works/pi-tui";
|
|
4295
|
+
import { Editor, Key, matchesKey } from "@earendil-works/pi-tui";
|
|
4110
4296
|
|
|
4111
4297
|
// src/ui/manual-dialog-render.ts
|
|
4112
4298
|
import {
|
|
@@ -4202,6 +4388,7 @@ var renderManualAdvisorDialog = (view) => {
|
|
|
4202
4388
|
|
|
4203
4389
|
// src/ui/manual-dialog.ts
|
|
4204
4390
|
var TUI_INPUT_TAB = ["tui", "input", "tab"].join(".");
|
|
4391
|
+
var isShiftTab = (keyData) => matchesKey(keyData, Key.shift("tab"));
|
|
4205
4392
|
|
|
4206
4393
|
class ManualAdvisorDialog {
|
|
4207
4394
|
options;
|
|
@@ -4224,7 +4411,7 @@ class ManualAdvisorDialog {
|
|
|
4224
4411
|
}
|
|
4225
4412
|
constructor(options) {
|
|
4226
4413
|
this.options = options;
|
|
4227
|
-
this.gitLevels = GIT_CONTEXT_LEVELS.filter((level) => clampGitContextLevel(level, options.gitContext) === level).
|
|
4414
|
+
this.gitLevels = GIT_CONTEXT_LEVELS.filter((level) => clampGitContextLevel(level, options.gitContext) === level).toReversed();
|
|
4228
4415
|
this.gitIndex = Math.max(0, this.gitLevels.indexOf(options.gitContext));
|
|
4229
4416
|
const editorTheme = {
|
|
4230
4417
|
borderColor: (text) => options.theme.fg("border", text),
|
|
@@ -4256,7 +4443,7 @@ class ManualAdvisorDialog {
|
|
|
4256
4443
|
this.cancel();
|
|
4257
4444
|
return;
|
|
4258
4445
|
}
|
|
4259
|
-
if (
|
|
4446
|
+
if (isShiftTab(keyData)) {
|
|
4260
4447
|
this.changeFocus(-1);
|
|
4261
4448
|
return;
|
|
4262
4449
|
}
|
|
@@ -4264,19 +4451,12 @@ class ManualAdvisorDialog {
|
|
|
4264
4451
|
this.changeFocus(1);
|
|
4265
4452
|
return;
|
|
4266
4453
|
}
|
|
4267
|
-
|
|
4268
|
-
|
|
4269
|
-
|
|
4270
|
-
|
|
4271
|
-
|
|
4272
|
-
|
|
4273
|
-
return;
|
|
4274
|
-
case "actions":
|
|
4275
|
-
this.handleActionInput(keyData);
|
|
4276
|
-
return;
|
|
4277
|
-
default:
|
|
4278
|
-
return;
|
|
4279
|
-
}
|
|
4454
|
+
const handlers = {
|
|
4455
|
+
actions: (key) => this.handleActionInput(key),
|
|
4456
|
+
editor: (key) => this.handleEditorInput(key),
|
|
4457
|
+
git: (key) => this.handleGitInput(key)
|
|
4458
|
+
};
|
|
4459
|
+
handlers[this.focusTarget](keyData);
|
|
4280
4460
|
}
|
|
4281
4461
|
render(width) {
|
|
4282
4462
|
const renderWidth = Math.max(1, Math.floor(width));
|
|
@@ -4384,9 +4564,6 @@ class ManualAdvisorDialog {
|
|
|
4384
4564
|
isTab(keyData) {
|
|
4385
4565
|
return this.matches(keyData, TUI_INPUT_TAB, Key.tab) && !matchesKey(keyData, Key.shift("tab"));
|
|
4386
4566
|
}
|
|
4387
|
-
isShiftTab(keyData) {
|
|
4388
|
-
return matchesKey(keyData, Key.shift("tab"));
|
|
4389
|
-
}
|
|
4390
4567
|
isCancel(keyData) {
|
|
4391
4568
|
return matchesKey(keyData, Key.escape) || this.options.keybindings.matches(keyData, "tui.select.cancel");
|
|
4392
4569
|
}
|
|
@@ -4398,10 +4575,13 @@ class ManualAdvisorDialog {
|
|
|
4398
4575
|
return;
|
|
4399
4576
|
}
|
|
4400
4577
|
this.state.completed = true;
|
|
4401
|
-
|
|
4402
|
-
gitContext: this.gitLevels[this.gitIndex] ?? "off"
|
|
4403
|
-
|
|
4404
|
-
|
|
4578
|
+
const request = {
|
|
4579
|
+
gitContext: this.gitLevels[this.gitIndex] ?? "off"
|
|
4580
|
+
};
|
|
4581
|
+
if (message) {
|
|
4582
|
+
request.message = message;
|
|
4583
|
+
}
|
|
4584
|
+
this.options.onSubmit(request);
|
|
4405
4585
|
}
|
|
4406
4586
|
cancel() {
|
|
4407
4587
|
if (this.state.completed) {
|
|
@@ -4412,31 +4592,31 @@ class ManualAdvisorDialog {
|
|
|
4412
4592
|
}
|
|
4413
4593
|
}
|
|
4414
4594
|
|
|
4415
|
-
// src/jev/
|
|
4416
|
-
class
|
|
4595
|
+
// src/jev/failure.ts
|
|
4596
|
+
class JevFailureError extends Error {
|
|
4417
4597
|
category;
|
|
4418
4598
|
constructor(category, message) {
|
|
4419
4599
|
super(message);
|
|
4420
|
-
this.name = "
|
|
4600
|
+
this.name = "JevFailureError";
|
|
4421
4601
|
this.category = category;
|
|
4422
4602
|
}
|
|
4423
4603
|
}
|
|
4604
|
+
|
|
4605
|
+
// src/jev/client.ts
|
|
4424
4606
|
var ENDPOINTS = {
|
|
4425
4607
|
openrouter: "https://openrouter.ai/api/alpha/decisions",
|
|
4426
4608
|
typesafe: "https://api.typesafe.ai/v1/systemone"
|
|
4427
4609
|
};
|
|
4610
|
+
var range = (from, to) => Array.from({ length: to - from + 1 }, (_, index) => from + index);
|
|
4428
4611
|
var RETRYABLE_STATUSES = new Set([408, 429, ...range(500, 599)]);
|
|
4429
4612
|
var RETRY_BACKOFF_MS = 250;
|
|
4430
4613
|
var MAX_ATTEMPTS = 2;
|
|
4431
|
-
|
|
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;
|
|
4614
|
+
var finiteTokens = (value) => isNumber(value) && Number.isFinite(value) && value >= 0 ? value : 0;
|
|
4435
4615
|
var errorDetail = (error) => {
|
|
4436
|
-
if (
|
|
4616
|
+
if (isString(error)) {
|
|
4437
4617
|
return error;
|
|
4438
4618
|
}
|
|
4439
|
-
if (error &&
|
|
4619
|
+
if (isRecordOf(error) && "message" in error) {
|
|
4440
4620
|
return String(error.message);
|
|
4441
4621
|
}
|
|
4442
4622
|
return "";
|
|
@@ -4450,7 +4630,22 @@ var statusCategory = (status) => {
|
|
|
4450
4630
|
}
|
|
4451
4631
|
return "error";
|
|
4452
4632
|
};
|
|
4633
|
+
var isObjectLike = (value) => typeof value === "object";
|
|
4453
4634
|
var openRouterModelId = (model) => model.includes("/") ? model : `~typesafe/${model}`;
|
|
4635
|
+
var connectionFailure = (error) => {
|
|
4636
|
+
const message = redactSecrets(error instanceof Error ? error.message : String(error));
|
|
4637
|
+
return {
|
|
4638
|
+
failure: new JevFailureError("network", `Jev connection failed: ${message}`)
|
|
4639
|
+
};
|
|
4640
|
+
};
|
|
4641
|
+
var sleepWithAbort = (signal, ms) => new Promise((resolve) => {
|
|
4642
|
+
const timer = setTimeout(resolve, ms);
|
|
4643
|
+
timer.unref?.();
|
|
4644
|
+
signal.addEventListener("abort", () => {
|
|
4645
|
+
clearTimeout(timer);
|
|
4646
|
+
resolve();
|
|
4647
|
+
}, { once: true });
|
|
4648
|
+
});
|
|
4454
4649
|
|
|
4455
4650
|
class JevClient {
|
|
4456
4651
|
#apiKey;
|
|
@@ -4499,7 +4694,7 @@ class JevClient {
|
|
|
4499
4694
|
if (!outcome.retryable || attempt >= MAX_ATTEMPTS) {
|
|
4500
4695
|
break;
|
|
4501
4696
|
}
|
|
4502
|
-
await
|
|
4697
|
+
await sleepWithAbort(deadline.signal, RETRY_BACKOFF_MS);
|
|
4503
4698
|
if (deadline.signal.aborted) {
|
|
4504
4699
|
break;
|
|
4505
4700
|
}
|
|
@@ -4509,11 +4704,11 @@ class JevClient {
|
|
|
4509
4704
|
if (signal?.aborted && !deadlineHit) {
|
|
4510
4705
|
throw error;
|
|
4511
4706
|
}
|
|
4512
|
-
if (error instanceof
|
|
4707
|
+
if (error instanceof JevFailureError) {
|
|
4513
4708
|
throw error;
|
|
4514
4709
|
}
|
|
4515
4710
|
const message = redactSecrets(error instanceof Error ? error.message : String(error));
|
|
4516
|
-
throw deadlineHit ? new
|
|
4711
|
+
throw deadlineHit ? new JevFailureError("timeout", `Jev call exceeded its ${this.#timeoutMs} ms wall-time budget.`) : new JevFailureError("error", message);
|
|
4517
4712
|
} finally {
|
|
4518
4713
|
clearTimeout(timer);
|
|
4519
4714
|
signal?.removeEventListener("abort", abortFromCaller);
|
|
@@ -4524,7 +4719,7 @@ class JevClient {
|
|
|
4524
4719
|
return this.#result(outcome);
|
|
4525
4720
|
}
|
|
4526
4721
|
if (deadlineHit && outcome.failure?.category !== "auth") {
|
|
4527
|
-
throw new
|
|
4722
|
+
throw new JevFailureError("timeout", `Jev call exceeded its ${this.#timeoutMs} ms wall-time budget.`);
|
|
4528
4723
|
}
|
|
4529
4724
|
if (outcome.failure) {
|
|
4530
4725
|
throw outcome.failure;
|
|
@@ -4532,21 +4727,7 @@ class JevClient {
|
|
|
4532
4727
|
if (signal?.aborted) {
|
|
4533
4728
|
throw new Error("Jev call aborted by the caller.");
|
|
4534
4729
|
}
|
|
4535
|
-
throw new
|
|
4536
|
-
}
|
|
4537
|
-
async#backoff(signal) {
|
|
4538
|
-
if (signal.aborted) {
|
|
4539
|
-
return;
|
|
4540
|
-
}
|
|
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 });
|
|
4549
|
-
});
|
|
4730
|
+
throw new JevFailureError("error", "Jev call failed.");
|
|
4550
4731
|
}
|
|
4551
4732
|
async#attempt(body, signal) {
|
|
4552
4733
|
if (signal.aborted) {
|
|
@@ -4569,7 +4750,7 @@ class JevClient {
|
|
|
4569
4750
|
}
|
|
4570
4751
|
return {
|
|
4571
4752
|
retryable: true,
|
|
4572
|
-
...
|
|
4753
|
+
...connectionFailure(error)
|
|
4573
4754
|
};
|
|
4574
4755
|
}
|
|
4575
4756
|
if (response.ok) {
|
|
@@ -4581,23 +4762,17 @@ class JevClient {
|
|
|
4581
4762
|
retryable: RETRYABLE_STATUSES.has(response.status)
|
|
4582
4763
|
};
|
|
4583
4764
|
}
|
|
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
4765
|
async#failureFromStatus(response) {
|
|
4591
4766
|
let detail = "";
|
|
4592
4767
|
try {
|
|
4593
4768
|
const parsed = await response.json();
|
|
4594
|
-
const error = parsed
|
|
4769
|
+
const error = isRecord(parsed) ? parsed.error : undefined;
|
|
4595
4770
|
detail = errorDetail(error);
|
|
4596
4771
|
} catch {
|
|
4597
4772
|
detail = "";
|
|
4598
4773
|
}
|
|
4599
4774
|
const message = redactSecrets(`Jev ${this.#transportLabel()} request failed with HTTP ${response.status}${detail ? `: ${detail}` : ""}.`);
|
|
4600
|
-
return new
|
|
4775
|
+
return new JevFailureError(statusCategory(response.status), message);
|
|
4601
4776
|
}
|
|
4602
4777
|
#transportLabel() {
|
|
4603
4778
|
return this.#endpoint === ENDPOINTS.openrouter ? "OpenRouter" : "TypeSafe";
|
|
@@ -4608,23 +4783,25 @@ class JevClient {
|
|
|
4608
4783
|
parsed = await response.json();
|
|
4609
4784
|
} catch (error) {
|
|
4610
4785
|
return {
|
|
4611
|
-
failure: new
|
|
4786
|
+
failure: new JevFailureError("malformed", `Jev response was not JSON: ${redactSecrets(error instanceof Error ? error.message : String(error))}`)
|
|
4612
4787
|
};
|
|
4613
4788
|
}
|
|
4614
|
-
|
|
4615
|
-
if (!record || typeof record !== "object" || !record.answers || typeof record.answers !== "object") {
|
|
4789
|
+
if (!isRecord(parsed) || !parsed.answers || !isObjectLike(parsed.answers)) {
|
|
4616
4790
|
return {
|
|
4617
|
-
failure: new
|
|
4791
|
+
failure: new JevFailureError("malformed", "Jev response did not include an answers object.")
|
|
4618
4792
|
};
|
|
4619
4793
|
}
|
|
4620
4794
|
return {
|
|
4621
|
-
answers:
|
|
4622
|
-
model:
|
|
4623
|
-
usage:
|
|
4795
|
+
answers: parsed.answers,
|
|
4796
|
+
model: isString(parsed.model) ? parsed.model : this.#model,
|
|
4797
|
+
usage: isRecordOf(parsed.usage) ? {
|
|
4798
|
+
input_tokens: parsed.usage.input_tokens,
|
|
4799
|
+
output_tokens: parsed.usage.output_tokens
|
|
4800
|
+
} : undefined
|
|
4624
4801
|
};
|
|
4625
4802
|
}
|
|
4626
4803
|
#result(outcome) {
|
|
4627
|
-
const usage = outcome
|
|
4804
|
+
const { usage } = outcome;
|
|
4628
4805
|
const inputTokens = finiteTokens(usage?.input_tokens);
|
|
4629
4806
|
const outputTokens = finiteTokens(usage?.output_tokens);
|
|
4630
4807
|
const price = this.#pricePerMtok ?? advisorJevPricePerMtokRef;
|
|
@@ -4663,12 +4840,15 @@ var TYPESAFE_KEY_NAME = "typesafe-api-key";
|
|
|
4663
4840
|
var TYPESAFE_KEY_CONFIG_FIELD = "typesafe_api_key";
|
|
4664
4841
|
var KEY_FILE_MODE = 384;
|
|
4665
4842
|
var keyFilePath = () => join4(getAgentDir3(), "typesafe_api_key");
|
|
4666
|
-
var runtimeSecrets = () =>
|
|
4843
|
+
var runtimeSecrets = () => {
|
|
4844
|
+
const bun = globalThis;
|
|
4845
|
+
return bun.Bun?.secrets;
|
|
4846
|
+
};
|
|
4667
4847
|
var normalizeKey = (value) => value?.trim() || undefined;
|
|
4668
4848
|
var readAdvisorJsonConfig = () => readExistingConfig(join4(getAgentDir3(), "advisor.json"));
|
|
4669
4849
|
var defaultReadFileStore = () => {
|
|
4670
4850
|
try {
|
|
4671
|
-
return normalizeKey(readFileSync3(keyFilePath(), "
|
|
4851
|
+
return normalizeKey(readFileSync3(keyFilePath(), "utf-8"));
|
|
4672
4852
|
} catch {
|
|
4673
4853
|
return;
|
|
4674
4854
|
}
|
|
@@ -4708,7 +4888,7 @@ var resolveTypeSafeKey = async (deps = {}) => {
|
|
|
4708
4888
|
}
|
|
4709
4889
|
const config = (deps.readAdvisorJson ?? readAdvisorJsonConfig)();
|
|
4710
4890
|
const staged = config[TYPESAFE_KEY_CONFIG_FIELD];
|
|
4711
|
-
if (
|
|
4891
|
+
if (isString(staged)) {
|
|
4712
4892
|
const fromConfig = normalizeKey(staged);
|
|
4713
4893
|
if (fromConfig) {
|
|
4714
4894
|
return { key: fromConfig, source: "advisor-json" };
|
|
@@ -4793,8 +4973,8 @@ var removeTypeSafeKeyFromAdvisorJson = () => {
|
|
|
4793
4973
|
if (!(TYPESAFE_KEY_CONFIG_FIELD in existing)) {
|
|
4794
4974
|
return { message: "No plaintext key in advisor.json.", ok: true };
|
|
4795
4975
|
}
|
|
4796
|
-
|
|
4797
|
-
writeFileSync2(path, `${JSON.stringify(
|
|
4976
|
+
const retained = Object.fromEntries(Object.entries(existing).filter(([key]) => key !== TYPESAFE_KEY_CONFIG_FIELD));
|
|
4977
|
+
writeFileSync2(path, `${JSON.stringify(retained, null, 2)}
|
|
4798
4978
|
`);
|
|
4799
4979
|
resetConfigCache();
|
|
4800
4980
|
return { message: "Plaintext key removed from advisor.json.", ok: true };
|
|
@@ -4836,15 +5016,14 @@ var screeningQuestions = {
|
|
|
4836
5016
|
type: "score"
|
|
4837
5017
|
}
|
|
4838
5018
|
};
|
|
4839
|
-
var NUMERIC_KEY_PATTERN = /^\d
|
|
4840
|
-
var
|
|
4841
|
-
var finiteNumber = (value) => typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
5019
|
+
var NUMERIC_KEY_PATTERN = /^\d+$/u;
|
|
5020
|
+
var finiteNumber = (value) => isNumber(value) && Number.isFinite(value) ? value : undefined;
|
|
4842
5021
|
var lowestStakesProbability = (answer) => {
|
|
4843
|
-
if (!
|
|
5022
|
+
if (!isRecordOf(answer)) {
|
|
4844
5023
|
return;
|
|
4845
5024
|
}
|
|
4846
|
-
const probabilities =
|
|
4847
|
-
const legend =
|
|
5025
|
+
const probabilities = isRecordOf(answer.probabilities) ? answer.probabilities : {};
|
|
5026
|
+
const legend = isRecordOf(answer.legend) ? answer.legend : undefined;
|
|
4848
5027
|
if (legend) {
|
|
4849
5028
|
const exact = Object.keys(legend).find((key) => legend[key] === STAKES_RUBRIC[0]);
|
|
4850
5029
|
if (exact) {
|
|
@@ -4855,12 +5034,17 @@ var lowestStakesProbability = (answer) => {
|
|
|
4855
5034
|
if (numericKeys.length === 0) {
|
|
4856
5035
|
return;
|
|
4857
5036
|
}
|
|
4858
|
-
|
|
5037
|
+
let [lowest] = numericKeys;
|
|
5038
|
+
for (const key of numericKeys) {
|
|
5039
|
+
if (Number(key) < Number(lowest)) {
|
|
5040
|
+
lowest = key;
|
|
5041
|
+
}
|
|
5042
|
+
}
|
|
4859
5043
|
return finiteNumber(probabilities[lowest]);
|
|
4860
5044
|
};
|
|
4861
|
-
var selfAnswerableNoul = (answer) =>
|
|
5045
|
+
var selfAnswerableNoul = (answer) => isRecordOf(answer) ? finiteNumber(answer.noul) : undefined;
|
|
4862
5046
|
var composeScreeningVerdict = (answers, { noulMargin, skipConfidence }) => {
|
|
4863
|
-
if (!
|
|
5047
|
+
if (!isRecordOf(answers)) {
|
|
4864
5048
|
return { skip: false };
|
|
4865
5049
|
}
|
|
4866
5050
|
const negligibleMass = lowestStakesProbability(answers.stakes);
|
|
@@ -4874,29 +5058,29 @@ var composeScreeningVerdict = (answers, { noulMargin, skipConfidence }) => {
|
|
|
4874
5058
|
};
|
|
4875
5059
|
};
|
|
4876
5060
|
var composeTurnGateVerdict = (answers, threshold) => {
|
|
4877
|
-
if (!
|
|
5061
|
+
if (!isRecordOf(answers)) {
|
|
4878
5062
|
return false;
|
|
4879
5063
|
}
|
|
4880
5064
|
const answer = answers.should_consult;
|
|
4881
|
-
const noul =
|
|
5065
|
+
const noul = isRecordOf(answer) ? finiteNumber(answer.noul) : undefined;
|
|
4882
5066
|
return noul !== undefined && noul >= threshold;
|
|
4883
5067
|
};
|
|
4884
5068
|
|
|
4885
5069
|
// src/jev/state.ts
|
|
4886
5070
|
var JEV_TEXT_CAP_BYTES = 8 * 1024;
|
|
4887
5071
|
var buildJevState = (ctx, input = {}) => {
|
|
4888
|
-
const
|
|
5072
|
+
const fields = new Map([["role", "executor"]]);
|
|
4889
5073
|
if (input.question) {
|
|
4890
|
-
|
|
5074
|
+
fields.set("executor_question", redactAndCapText(input.question, JEV_TEXT_CAP_BYTES, advisorRedactSecretsRef));
|
|
4891
5075
|
}
|
|
4892
5076
|
if (input.draft) {
|
|
4893
|
-
|
|
5077
|
+
fields.set("executor_draft", redactAndCapText(input.draft, JEV_TEXT_CAP_BYTES, advisorRedactSecretsRef));
|
|
4894
5078
|
}
|
|
4895
5079
|
const digest = recentConversation(ctx, advisorJevDigestMaxCharsRef);
|
|
4896
5080
|
if (digest) {
|
|
4897
|
-
|
|
5081
|
+
fields.set("recent_conversation", digest);
|
|
4898
5082
|
}
|
|
4899
|
-
return
|
|
5083
|
+
return Object.fromEntries(fields);
|
|
4900
5084
|
};
|
|
4901
5085
|
|
|
4902
5086
|
// src/jev/transport.ts
|
|
@@ -4911,11 +5095,14 @@ var resolveJevTransport = async (ctx, deps = {}) => {
|
|
|
4911
5095
|
const resolveTypesafe = deps.resolveTypesafe ?? resolveTypeSafeKey;
|
|
4912
5096
|
const resolution = await resolveTypesafe();
|
|
4913
5097
|
if (resolution.key) {
|
|
4914
|
-
|
|
5098
|
+
const credentials = {
|
|
4915
5099
|
apiKey: resolution.key,
|
|
4916
|
-
...resolution.source ? { source: resolution.source } : {},
|
|
4917
5100
|
transport: "typesafe"
|
|
4918
5101
|
};
|
|
5102
|
+
if (resolution.source) {
|
|
5103
|
+
credentials.source = resolution.source;
|
|
5104
|
+
}
|
|
5105
|
+
return credentials;
|
|
4919
5106
|
}
|
|
4920
5107
|
}
|
|
4921
5108
|
if (preference === "typesafe") {
|
|
@@ -4946,7 +5133,7 @@ var createOutageNotifier = (format) => {
|
|
|
4946
5133
|
};
|
|
4947
5134
|
|
|
4948
5135
|
// src/tools/jev-filter.ts
|
|
4949
|
-
var normalizeScreeningQuestion = (question) => question?.trim().toLowerCase().
|
|
5136
|
+
var normalizeScreeningQuestion = (question) => question?.trim().toLowerCase().replaceAll(/\s+/gu, " ") || undefined;
|
|
4950
5137
|
var REATTACHED_ADVICE_CAP_BYTES = 4 * 1024;
|
|
4951
5138
|
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
5139
|
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.
|
|
@@ -4956,30 +5143,6 @@ var outageNotifier = createOutageNotifier((category, message) => `Advisor Jev fi
|
|
|
4956
5143
|
var notifyOutageOnce = outageNotifier.notify;
|
|
4957
5144
|
var resetJevOutageNotification = outageNotifier.reset;
|
|
4958
5145
|
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
5146
|
var bypassOutcome = (session, options, normalizedQuestion) => {
|
|
4984
5147
|
const lastSkip = session.lastJevSkip;
|
|
4985
5148
|
if (options.force) {
|
|
@@ -5027,7 +5190,7 @@ var screenWithJev = async (ctx, session, options, deps, normalizedQuestion) => {
|
|
|
5027
5190
|
return allow();
|
|
5028
5191
|
} catch (error) {
|
|
5029
5192
|
session.recordJevFilterFailure();
|
|
5030
|
-
if (error instanceof
|
|
5193
|
+
if (error instanceof JevFailureError) {
|
|
5031
5194
|
notifyOutageOnce(ctx, error.category, error.message);
|
|
5032
5195
|
} else if (options.signal?.aborted) {
|
|
5033
5196
|
throw error;
|
|
@@ -5037,10 +5200,34 @@ var screenWithJev = async (ctx, session, options, deps, normalizedQuestion) => {
|
|
|
5037
5200
|
return allow();
|
|
5038
5201
|
}
|
|
5039
5202
|
};
|
|
5203
|
+
var screenConsultation = (ctx, session, options, deps = {}) => {
|
|
5204
|
+
if (isSimpleMode()) {
|
|
5205
|
+
return Promise.resolve(allow());
|
|
5206
|
+
}
|
|
5207
|
+
const normalizedQuestion = normalizeScreeningQuestion(options.question);
|
|
5208
|
+
const bypass = bypassOutcome(session, options, normalizedQuestion);
|
|
5209
|
+
if (bypass) {
|
|
5210
|
+
return Promise.resolve(bypass);
|
|
5211
|
+
}
|
|
5212
|
+
const reattached = session.reattachedAdviceFor(normalizedQuestion);
|
|
5213
|
+
if (reattached) {
|
|
5214
|
+
session.recordJevFilterSkipped(true, normalizedQuestion);
|
|
5215
|
+
return Promise.resolve({
|
|
5216
|
+
decision: "skip",
|
|
5217
|
+
kind: "repeat",
|
|
5218
|
+
reason: "already answered earlier in this session",
|
|
5219
|
+
reattachedAdvice: reattached.slice(0, REATTACHED_ADVICE_CAP_BYTES)
|
|
5220
|
+
});
|
|
5221
|
+
}
|
|
5222
|
+
if (!advisorJevFilterEnabledRef) {
|
|
5223
|
+
return Promise.resolve(allow());
|
|
5224
|
+
}
|
|
5225
|
+
return screenWithJev(ctx, session, options, deps, normalizedQuestion);
|
|
5226
|
+
};
|
|
5040
5227
|
var screeningSkipText = (outcome) => outcome.kind === "repeat" && outcome.reattachedAdvice ? repeatSkipText(outcome.reattachedAdvice) : SCREENED_SKIP_TEXT;
|
|
5041
5228
|
|
|
5042
5229
|
// src/commands/manual-consultation.ts
|
|
5043
|
-
var startManualConsultation = (runtime, ctx, question, controller, scoutStatusToken, progress, gitContext) => {
|
|
5230
|
+
var startManualConsultation = async (runtime, ctx, question, controller, scoutStatusToken, progress, gitContext) => {
|
|
5044
5231
|
herdrAdvisorActivity.start();
|
|
5045
5232
|
progress.phase = "preparing";
|
|
5046
5233
|
runtime.requestManualRender(ctx);
|
|
@@ -5055,23 +5242,24 @@ var startManualConsultation = (runtime, ctx, question, controller, scoutStatusTo
|
|
|
5055
5242
|
runtime.manualProgressTimers.set(controller, timer);
|
|
5056
5243
|
}
|
|
5057
5244
|
let scoutDetails;
|
|
5058
|
-
|
|
5059
|
-
|
|
5060
|
-
|
|
5061
|
-
|
|
5062
|
-
|
|
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;
|
|
5245
|
+
try {
|
|
5246
|
+
const { adviceId, markdown, usage } = await runtime.requestAdvisor(ctx, question, controller.signal, (thinking, text) => {
|
|
5247
|
+
if (controller.signal.aborted) {
|
|
5248
|
+
return;
|
|
5249
|
+
}
|
|
5071
5250
|
progress.phase = "active";
|
|
5251
|
+
progress.thinking = thinking;
|
|
5252
|
+
progress.text = text;
|
|
5072
5253
|
runtime.requestManualRender(ctx);
|
|
5073
|
-
}
|
|
5074
|
-
|
|
5254
|
+
}, (event) => {
|
|
5255
|
+
if (!controller.signal.aborted) {
|
|
5256
|
+
runtime.scoutStatus.update(ctx, scoutStatusToken, event);
|
|
5257
|
+
scoutDetails = appendScoutLifecycleEntry(runtime.pi, event, scoutDetails);
|
|
5258
|
+
progress.scout = scoutDetails;
|
|
5259
|
+
progress.phase = "active";
|
|
5260
|
+
runtime.requestManualRender(ctx);
|
|
5261
|
+
}
|
|
5262
|
+
}, gitContext);
|
|
5075
5263
|
if (controller.signal.aborted) {
|
|
5076
5264
|
return;
|
|
5077
5265
|
}
|
|
@@ -5085,28 +5273,31 @@ var startManualConsultation = (runtime, ctx, question, controller, scoutStatusTo
|
|
|
5085
5273
|
trigger: "manual",
|
|
5086
5274
|
usage
|
|
5087
5275
|
});
|
|
5088
|
-
if (
|
|
5276
|
+
if (isString(adviceId)) {
|
|
5089
5277
|
runtime.advisorSessionState.issueAdvice(adviceId, markdown, "manual", false, normalizeScreeningQuestion(question));
|
|
5090
5278
|
}
|
|
5091
5279
|
runtime.updateAdvisorUsageStatus(ctx);
|
|
5280
|
+
const details = {
|
|
5281
|
+
advisor: advisorRef,
|
|
5282
|
+
question,
|
|
5283
|
+
text: markdown
|
|
5284
|
+
};
|
|
5092
5285
|
const normalizedUsage = snapshotAdvisorUsage(usage);
|
|
5286
|
+
if (normalizedUsage) {
|
|
5287
|
+
details.usage = normalizedUsage;
|
|
5288
|
+
}
|
|
5093
5289
|
runtime.pi.sendMessage({
|
|
5094
5290
|
content: `Manual Advisor consultation${question ? ` (${question})` : ""} — for your awareness; no action or follow-up consultation is needed unless the user asks:
|
|
5095
5291
|
|
|
5096
5292
|
${markdown}`,
|
|
5097
5293
|
customType: "advisor-manual-result",
|
|
5098
|
-
details
|
|
5099
|
-
advisor: advisorRef,
|
|
5100
|
-
question,
|
|
5101
|
-
text: markdown,
|
|
5102
|
-
...normalizedUsage ? { usage: normalizedUsage } : {}
|
|
5103
|
-
},
|
|
5294
|
+
details,
|
|
5104
5295
|
display: true
|
|
5105
5296
|
}, {
|
|
5106
5297
|
deliverAs: "steer",
|
|
5107
5298
|
triggerTurn: true
|
|
5108
5299
|
});
|
|
5109
|
-
}
|
|
5300
|
+
} catch (error) {
|
|
5110
5301
|
if (controller.signal.aborted) {
|
|
5111
5302
|
return;
|
|
5112
5303
|
}
|
|
@@ -5132,7 +5323,7 @@ ${markdown}`,
|
|
|
5132
5323
|
}, { deliverAs: "steer", triggerTurn: true });
|
|
5133
5324
|
notify(ctx, `Advisor consultation failed: ${message}`, "error");
|
|
5134
5325
|
notifyHerdrAdvisorFailure("Advisor consultation failed", message);
|
|
5135
|
-
}
|
|
5326
|
+
} finally {
|
|
5136
5327
|
if (controller.signal.aborted) {
|
|
5137
5328
|
progress.phase = "cancelled";
|
|
5138
5329
|
}
|
|
@@ -5145,7 +5336,7 @@ ${markdown}`,
|
|
|
5145
5336
|
runtime.scoutStatus.release(ctx, scoutStatusToken);
|
|
5146
5337
|
runtime.manualConsultations.delete(controller);
|
|
5147
5338
|
herdrAdvisorActivity.finish();
|
|
5148
|
-
}
|
|
5339
|
+
}
|
|
5149
5340
|
};
|
|
5150
5341
|
|
|
5151
5342
|
// src/commands/manual-command.ts
|
|
@@ -5156,6 +5347,11 @@ var registerManualCommand = (runtime) => {
|
|
|
5156
5347
|
if (!loadCommandConfig(ctx)) {
|
|
5157
5348
|
return;
|
|
5158
5349
|
}
|
|
5350
|
+
const accessReason = advisorModelAccessReason(ctx);
|
|
5351
|
+
if (accessReason) {
|
|
5352
|
+
notify(ctx, accessReason, "warning");
|
|
5353
|
+
return;
|
|
5354
|
+
}
|
|
5159
5355
|
if (!(isSimpleMode() || runtime.advisorSessionState.canConsult(getAdvisorMaxCallsPerSession()))) {
|
|
5160
5356
|
runtime.reportManualBudgetExhausted(ctx);
|
|
5161
5357
|
return;
|
|
@@ -5305,41 +5501,40 @@ class ManualAdvisorProgressComponent {
|
|
|
5305
5501
|
}
|
|
5306
5502
|
return box.render(width);
|
|
5307
5503
|
}
|
|
5308
|
-
invalidate
|
|
5504
|
+
invalidate = noop;
|
|
5309
5505
|
}
|
|
5310
5506
|
|
|
5311
5507
|
// src/commands/renderers.ts
|
|
5312
|
-
var
|
|
5313
|
-
|
|
5314
|
-
|
|
5315
|
-
|
|
5316
|
-
|
|
5317
|
-
|
|
5318
|
-
|
|
5319
|
-
|
|
5320
|
-
|
|
5321
|
-
|
|
5322
|
-
|
|
5323
|
-
|
|
5324
|
-
|
|
5325
|
-
|
|
5326
|
-
|
|
5327
|
-
|
|
5328
|
-
|
|
5329
|
-
box.addChild(new Text4(theme.fg("dim", ` Usage: ${usage}`), 0, 0));
|
|
5330
|
-
}
|
|
5508
|
+
var manualCallRenderer = (runtime) => (entry, { expanded }, theme) => {
|
|
5509
|
+
const { progressId, question } = entry.data ?? {};
|
|
5510
|
+
const progress = progressId ? runtime.manualProgress.get(progressId) : undefined;
|
|
5511
|
+
return progress ? new ManualAdvisorProgressComponent(question, progress, Boolean(expanded), theme) : renderAdvisorCallBox(question, theme);
|
|
5512
|
+
};
|
|
5513
|
+
var manualResultRenderer = (message, { expanded }, theme) => {
|
|
5514
|
+
const { details } = message;
|
|
5515
|
+
const box = new Box2(1, 1, (text) => theme.bg("customMessageBg", text));
|
|
5516
|
+
const advice = details?.text ?? (isString(message.content) ? message.content : "(Advisor returned no advice.)");
|
|
5517
|
+
box.addChild(new Text4(renderAdvisorResponseHeader(hasSoundVerdict(advice), theme), 0, 0));
|
|
5518
|
+
if (details?.advisor) {
|
|
5519
|
+
box.addChild(new Text4(theme.fg("dim", ` ${details.advisor}`), 0, 0));
|
|
5520
|
+
}
|
|
5521
|
+
if (getAdvisorSettings().showUsageDetails) {
|
|
5522
|
+
const usage = formatAdvisorUsage(details?.usage);
|
|
5523
|
+
if (usage) {
|
|
5524
|
+
box.addChild(new Text4(theme.fg("dim", ` Usage: ${usage}`), 0, 0));
|
|
5331
5525
|
}
|
|
5332
|
-
|
|
5333
|
-
|
|
5334
|
-
|
|
5526
|
+
}
|
|
5527
|
+
box.addChild(new Markdown3(adviceForDisplay(advice, expanded), 0, 0, getMarkdownTheme3()));
|
|
5528
|
+
return box;
|
|
5529
|
+
};
|
|
5530
|
+
var registerCommandRenderers = (runtime) => {
|
|
5531
|
+
runtime.pi.registerEntryRenderer?.("advisor-manual-call", manualCallRenderer(runtime));
|
|
5532
|
+
runtime.pi.registerMessageRenderer?.("advisor-manual-result", manualResultRenderer);
|
|
5335
5533
|
};
|
|
5336
5534
|
|
|
5337
5535
|
// src/ui/settings-selector.ts
|
|
5338
5536
|
import { getSettingsListTheme } from "@earendil-works/pi-coding-agent";
|
|
5339
|
-
import {
|
|
5340
|
-
SettingsList,
|
|
5341
|
-
truncateToWidth as truncateToWidth7
|
|
5342
|
-
} from "@earendil-works/pi-tui";
|
|
5537
|
+
import { SettingsList, truncateToWidth as truncateToWidth7 } from "@earendil-works/pi-tui";
|
|
5343
5538
|
|
|
5344
5539
|
// src/ui/settings-formatting.ts
|
|
5345
5540
|
import { visibleWidth as visibleWidth3 } from "@earendil-works/pi-tui";
|
|
@@ -5357,7 +5552,7 @@ var SIMPLE_MODE_GRADIENT_COLORS = [
|
|
|
5357
5552
|
var withCurrentValue = (current, values) => values.includes(current) ? values : [current, ...values];
|
|
5358
5553
|
var numericValues = (current, values) => {
|
|
5359
5554
|
const all = values.includes(current) ? values : [...values, current];
|
|
5360
|
-
return all.
|
|
5555
|
+
return all.toSorted((a, b) => a - b).map(String);
|
|
5361
5556
|
};
|
|
5362
5557
|
var maxCallValues = (current) => {
|
|
5363
5558
|
const values = ["0", "1", "2", "3", "5", "10", "25", "50", "∞"];
|
|
@@ -5374,9 +5569,18 @@ var maxCallValues = (current) => {
|
|
|
5374
5569
|
};
|
|
5375
5570
|
var settingValue = (value, defaultValue) => value ?? defaultValue ? "On" : "Off";
|
|
5376
5571
|
var currentContextLabel = (presets, contextMaxChars) => presets.find((preset) => preset.value === contextMaxChars)?.label ?? String(contextMaxChars);
|
|
5572
|
+
var closestPresetIndex = (presets, contextMaxChars) => {
|
|
5573
|
+
let closestIndex = 0;
|
|
5574
|
+
for (let index = 0;index < presets.length; index += 1) {
|
|
5575
|
+
if (Math.abs(presets[index].value - contextMaxChars) < Math.abs(presets[closestIndex].value - contextMaxChars)) {
|
|
5576
|
+
closestIndex = index;
|
|
5577
|
+
}
|
|
5578
|
+
}
|
|
5579
|
+
return closestIndex;
|
|
5580
|
+
};
|
|
5377
5581
|
var contextDescription = (presets, contextMaxChars) => {
|
|
5378
5582
|
const exactIndex = presets.findIndex((preset) => preset.value === contextMaxChars);
|
|
5379
|
-
const selectedIndex = exactIndex
|
|
5583
|
+
const selectedIndex = exactIndex === -1 ? closestPresetIndex(presets, contextMaxChars) : exactIndex;
|
|
5380
5584
|
const selectedPreset = presets[selectedIndex];
|
|
5381
5585
|
const isFullContext = selectedPreset?.value === Number.MAX_SAFE_INTEGER || selectedPreset?.label.toUpperCase() === "FULL" || selectedPreset?.label.toUpperCase() === "ALL";
|
|
5382
5586
|
const progress = isFullContext ? 1 : selectedIndex / Math.max(1, presets.length - 1);
|
|
@@ -5394,7 +5598,7 @@ var contextDescription = (presets, contextMaxChars) => {
|
|
|
5394
5598
|
const markerColumn = meterPrefix.length + marker;
|
|
5395
5599
|
const labelStart = Math.max(0, Math.min(meterPrefix.length + meter.length + 2 - labelWidth, markerColumn - Math.floor((labelWidth - 1) / 2)));
|
|
5396
5600
|
const markerLabel = `${" ".repeat(labelStart)}${label}`;
|
|
5397
|
-
const description = exactIndex
|
|
5601
|
+
const description = exactIndex === -1 ? "Custom context limit." : selectedPreset?.description;
|
|
5398
5602
|
return `${description ?? "Custom context limit."}
|
|
5399
5603
|
${meterPrefix}${meter} full
|
|
5400
5604
|
${markerLabel}`;
|
|
@@ -5419,12 +5623,11 @@ var rainbowGradient = (text, startedAt) => {
|
|
|
5419
5623
|
}).join("").concat("\x1B[0m");
|
|
5420
5624
|
};
|
|
5421
5625
|
|
|
5626
|
+
// src/ui/settings-items.ts
|
|
5627
|
+
import { getKeybindings } from "@earendil-works/pi-tui";
|
|
5628
|
+
|
|
5422
5629
|
// 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";
|
|
5630
|
+
import { Key as Key2, matchesKey as matchesKey2, truncateToWidth as truncateToWidth5 } from "@earendil-works/pi-tui";
|
|
5428
5631
|
|
|
5429
5632
|
// node_modules/@typesafe-ai/sdk/dist/index.mjs
|
|
5430
5633
|
var range2 = (from, to) => Array.from({ length: to - from }, (_, i) => from + i);
|
|
@@ -5470,10 +5673,7 @@ var describeRuntime = () => {
|
|
|
5470
5673
|
var RUNTIME = describeRuntime();
|
|
5471
5674
|
|
|
5472
5675
|
// src/ui/masked-input.ts
|
|
5473
|
-
import {
|
|
5474
|
-
Input as Input2,
|
|
5475
|
-
truncateToWidth as truncateToWidth4
|
|
5476
|
-
} from "@earendil-works/pi-tui";
|
|
5676
|
+
import { Input as Input2, truncateToWidth as truncateToWidth4 } from "@earendil-works/pi-tui";
|
|
5477
5677
|
|
|
5478
5678
|
class MaskedInput {
|
|
5479
5679
|
input;
|
|
@@ -5515,19 +5715,36 @@ class MaskedInput {
|
|
|
5515
5715
|
}
|
|
5516
5716
|
|
|
5517
5717
|
// src/ui/jev-setup-submenu.ts
|
|
5718
|
+
var ACTION_LABELS = {
|
|
5719
|
+
disable: "Disable",
|
|
5720
|
+
"disable-clear": "Disable and clear stored key",
|
|
5721
|
+
done: "Done",
|
|
5722
|
+
"enter-key": "Enter a TypeSafe API key",
|
|
5723
|
+
"verify-again": "Verify again",
|
|
5724
|
+
"verify-enable": "Verify and enable"
|
|
5725
|
+
};
|
|
5726
|
+
var fireAndForget = async (action) => {
|
|
5727
|
+
try {
|
|
5728
|
+
await action;
|
|
5729
|
+
} catch {}
|
|
5730
|
+
};
|
|
5518
5731
|
var transportLabel = (credentials) => {
|
|
5519
5732
|
if (credentials.transport === "openrouter") {
|
|
5520
5733
|
return "OpenRouter (reusing pi login)";
|
|
5521
5734
|
}
|
|
5522
5735
|
switch (credentials.source) {
|
|
5523
|
-
case "advisor-json":
|
|
5736
|
+
case "advisor-json": {
|
|
5524
5737
|
return "TypeSafe (key: advisor.json — plaintext, not recommended)";
|
|
5525
|
-
|
|
5738
|
+
}
|
|
5739
|
+
case "bun-secrets": {
|
|
5526
5740
|
return "TypeSafe (key: Bun.secrets)";
|
|
5527
|
-
|
|
5741
|
+
}
|
|
5742
|
+
case "file": {
|
|
5528
5743
|
return "TypeSafe (key: stored file, mode 0600)";
|
|
5529
|
-
|
|
5744
|
+
}
|
|
5745
|
+
default: {
|
|
5530
5746
|
return "TypeSafe (key: TYPESAFE_API_KEY)";
|
|
5747
|
+
}
|
|
5531
5748
|
}
|
|
5532
5749
|
};
|
|
5533
5750
|
var defaultVerify = async (credentials) => {
|
|
@@ -5566,9 +5783,7 @@ class JevSetupSubmenu {
|
|
|
5566
5783
|
onSubmit: (value) => this.submitEnteredKey(value),
|
|
5567
5784
|
placeholder: "Paste a TypeSafe API key"
|
|
5568
5785
|
});
|
|
5569
|
-
this.refresh()
|
|
5570
|
-
return;
|
|
5571
|
-
});
|
|
5786
|
+
fireAndForget(this.refresh());
|
|
5572
5787
|
}
|
|
5573
5788
|
get focused() {
|
|
5574
5789
|
return this._focused;
|
|
@@ -5609,24 +5824,20 @@ class JevSetupSubmenu {
|
|
|
5609
5824
|
""
|
|
5610
5825
|
];
|
|
5611
5826
|
if (this.credentials) {
|
|
5612
|
-
lines.push(` Transport: ${transportLabel(this.credentials)}`);
|
|
5613
|
-
lines.push(` Filter: ${enabled ? "On" : "Off"}`);
|
|
5827
|
+
lines.push(` Transport: ${transportLabel(this.credentials)}`, ` Filter: ${enabled ? "On" : "Off"}`);
|
|
5614
5828
|
} else if (this.mode === "verifying") {
|
|
5615
5829
|
lines.push(" Checking available Jev credentials…");
|
|
5616
5830
|
} 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.");
|
|
5831
|
+
lines.push(" No Jev credentials found. Enter a TypeSafe API key below, or add", " an OpenRouter login in pi; it is reused automatically.");
|
|
5619
5832
|
}
|
|
5620
5833
|
if (this.notice) {
|
|
5621
|
-
lines.push("");
|
|
5622
|
-
lines.push(theme.fg("warning", ` ${this.notice}`));
|
|
5834
|
+
lines.push("", theme.fg("warning", ` ${this.notice}`));
|
|
5623
5835
|
}
|
|
5624
5836
|
lines.push("");
|
|
5625
5837
|
if (this.mode === "verifying") {
|
|
5626
5838
|
lines.push(" Verifying with a live Jev call…");
|
|
5627
5839
|
} 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"));
|
|
5840
|
+
lines.push(` ${this.maskedInput.render(Math.max(10, width - 4))[0] ?? ""}`, theme.fg("dim", " Enter: verify · Esc: cancel"));
|
|
5630
5841
|
} else {
|
|
5631
5842
|
for (const [index, label] of this.labels().entries()) {
|
|
5632
5843
|
const prefix = index === this.selectedIndex ? "→ " : " ";
|
|
@@ -5648,19 +5859,8 @@ class JevSetupSubmenu {
|
|
|
5648
5859
|
actions.push("done");
|
|
5649
5860
|
return actions;
|
|
5650
5861
|
}
|
|
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
5862
|
labels() {
|
|
5662
|
-
|
|
5663
|
-
return this.actions().map((action) => labels[action]);
|
|
5863
|
+
return this.actions().map((action) => ACTION_LABELS[action]);
|
|
5664
5864
|
}
|
|
5665
5865
|
async refresh() {
|
|
5666
5866
|
const wasVerifying = this.mode === "verifying";
|
|
@@ -5677,30 +5877,25 @@ class JevSetupSubmenu {
|
|
|
5677
5877
|
this.options.tui.requestRender();
|
|
5678
5878
|
}
|
|
5679
5879
|
activate(action) {
|
|
5680
|
-
|
|
5681
|
-
|
|
5682
|
-
|
|
5683
|
-
|
|
5684
|
-
|
|
5685
|
-
|
|
5686
|
-
|
|
5687
|
-
|
|
5688
|
-
|
|
5689
|
-
|
|
5690
|
-
|
|
5691
|
-
|
|
5692
|
-
|
|
5693
|
-
|
|
5694
|
-
|
|
5695
|
-
|
|
5696
|
-
|
|
5697
|
-
|
|
5698
|
-
|
|
5699
|
-
return;
|
|
5700
|
-
});
|
|
5701
|
-
return;
|
|
5702
|
-
default:
|
|
5703
|
-
return;
|
|
5880
|
+
if (action === "done") {
|
|
5881
|
+
this.options.done();
|
|
5882
|
+
return;
|
|
5883
|
+
}
|
|
5884
|
+
if (action === "enter-key") {
|
|
5885
|
+
this.mode = "key-entry";
|
|
5886
|
+
return;
|
|
5887
|
+
}
|
|
5888
|
+
if (action === "disable") {
|
|
5889
|
+
this.notice = undefined;
|
|
5890
|
+
this.options.done("Off");
|
|
5891
|
+
return;
|
|
5892
|
+
}
|
|
5893
|
+
if (action === "disable-clear") {
|
|
5894
|
+
fireAndForget(this.disableAndClear());
|
|
5895
|
+
return;
|
|
5896
|
+
}
|
|
5897
|
+
if (action === "verify-again" || action === "verify-enable") {
|
|
5898
|
+
fireAndForget(this.verifyAndEnable());
|
|
5704
5899
|
}
|
|
5705
5900
|
}
|
|
5706
5901
|
async disableAndClear() {
|
|
@@ -5772,11 +5967,15 @@ class JevSetupSubmenu {
|
|
|
5772
5967
|
}
|
|
5773
5968
|
}
|
|
5774
5969
|
|
|
5970
|
+
// src/ui/model-multi-selector.ts
|
|
5971
|
+
class SearchableModelMultiSelector extends ModelSelectorAdapter {
|
|
5972
|
+
constructor(options) {
|
|
5973
|
+
super(new SearchableModelList(options));
|
|
5974
|
+
}
|
|
5975
|
+
}
|
|
5976
|
+
|
|
5775
5977
|
// src/ui/text-setting-submenu.ts
|
|
5776
|
-
import {
|
|
5777
|
-
Input as Input3,
|
|
5778
|
-
truncateToWidth as truncateToWidth6
|
|
5779
|
-
} from "@earendil-works/pi-tui";
|
|
5978
|
+
import { Input as Input3, truncateToWidth as truncateToWidth6 } from "@earendil-works/pi-tui";
|
|
5780
5979
|
|
|
5781
5980
|
class TextSettingSubmenu {
|
|
5782
5981
|
input = new Input3;
|
|
@@ -5939,8 +6138,28 @@ var jevItems = (settings, theme, tui) => [
|
|
|
5939
6138
|
])
|
|
5940
6139
|
}
|
|
5941
6140
|
];
|
|
6141
|
+
var advisorModelWhitelistItem = (settings, modelRefs, keybindings, theme, tui) => ({
|
|
6142
|
+
currentValue: settings.modelWhitelist?.length ? settings.modelWhitelist.join(", ") : "Any model",
|
|
6143
|
+
description: "Only the exact provider/model references listed here may call the Advisor; an empty list allows every model.",
|
|
6144
|
+
id: "modelWhitelist",
|
|
6145
|
+
label: "Advisor model whitelist",
|
|
6146
|
+
submenu: (_currentValue, done) => new SearchableModelMultiSelector({
|
|
6147
|
+
allOptions: [
|
|
6148
|
+
...new Set([...modelRefs ?? [], ...settings.modelWhitelist ?? []])
|
|
6149
|
+
],
|
|
6150
|
+
currentOptions: settings.modelWhitelist ?? [],
|
|
6151
|
+
keybindings: keybindings ?? getKeybindings(),
|
|
6152
|
+
multiSelect: true,
|
|
6153
|
+
onCancel: done,
|
|
6154
|
+
onSelect: (values) => done(values.join(",")),
|
|
6155
|
+
theme,
|
|
6156
|
+
title: "Advisor model whitelist",
|
|
6157
|
+
tui
|
|
6158
|
+
})
|
|
6159
|
+
});
|
|
5942
6160
|
var createSettingsItems = ({
|
|
5943
6161
|
effortLevels,
|
|
6162
|
+
modelWhitelist,
|
|
5944
6163
|
presets,
|
|
5945
6164
|
settings,
|
|
5946
6165
|
theme,
|
|
@@ -5970,6 +6189,7 @@ var createSettingsItems = ({
|
|
|
5970
6189
|
}
|
|
5971
6190
|
];
|
|
5972
6191
|
if (settings.simpleMode) {
|
|
6192
|
+
items.push(modelWhitelist);
|
|
5973
6193
|
return items;
|
|
5974
6194
|
}
|
|
5975
6195
|
items.push({
|
|
@@ -5978,7 +6198,7 @@ var createSettingsItems = ({
|
|
|
5978
6198
|
id: "effort",
|
|
5979
6199
|
label: "Advisor reasoning",
|
|
5980
6200
|
values: withCurrentValue(currentEffort(settings.effort), effortLevels)
|
|
5981
|
-
}, toggle("scoutEnabled", "Experimental Advisor Scout", "Enable the experimental Scout before Advisor calls.", settings.scoutEnabled, false), toggle("showUsageDetails", "Show usage and cost details", "Show token usage and cost details in Advisor responses.", settings.showUsageDetails, true), toggle("showUsageFooter", "Show usage in footer", "Show the current Advisor usage summary in the footer.", settings.showUsageFooter, false), toggle("planGate", "Plan gate", "Ask the Advisor to review implementation plans.", settings.planGate, true), toggle("failureGate", "Failure gate", "Ask the Advisor to review repeated failures.", settings.failureGate, true), toggle("completionGate", "Completion gate", "Ask the Advisor to review work before declaring success.", settings.completionGate, true), toggle("collapseResponses", "Collapse long responses", "Collapse long Advisor responses in the transcript.", settings.collapseResponses, false), {
|
|
6201
|
+
}, modelWhitelist, toggle("scoutEnabled", "Experimental Advisor Scout", "Enable the experimental Scout before Advisor calls.", settings.scoutEnabled, false), toggle("showUsageDetails", "Show usage and cost details", "Show token usage and cost details in Advisor responses.", settings.showUsageDetails, true), toggle("showUsageFooter", "Show usage in footer", "Show the current Advisor usage summary in the footer.", settings.showUsageFooter, false), toggle("planGate", "Plan gate", "Ask the Advisor to review implementation plans.", settings.planGate, true), toggle("failureGate", "Failure gate", "Ask the Advisor to review repeated failures.", settings.failureGate, true), toggle("completionGate", "Completion gate", "Ask the Advisor to review work before declaring success.", settings.completionGate, true), toggle("collapseResponses", "Collapse long responses", "Collapse long Advisor responses in the transcript.", settings.collapseResponses, false), {
|
|
5982
6202
|
currentValue: settings.customRule || "None",
|
|
5983
6203
|
description: "Add a rule that triggers Advisor involvement.",
|
|
5984
6204
|
id: "customRule",
|
|
@@ -6080,10 +6300,7 @@ var createSettingsItems = ({
|
|
|
6080
6300
|
};
|
|
6081
6301
|
|
|
6082
6302
|
// src/ui/settings-list-adapter.ts
|
|
6083
|
-
import {
|
|
6084
|
-
Key as Key3,
|
|
6085
|
-
matchesKey as matchesKey3
|
|
6086
|
-
} from "@earendil-works/pi-tui";
|
|
6303
|
+
import { Key as Key3, matchesKey as matchesKey3 } from "@earendil-works/pi-tui";
|
|
6087
6304
|
|
|
6088
6305
|
class SettingsListAdapter {
|
|
6089
6306
|
list;
|
|
@@ -6111,7 +6328,7 @@ class SettingsListAdapter {
|
|
|
6111
6328
|
}
|
|
6112
6329
|
setSelectedId(items, selectedId) {
|
|
6113
6330
|
const selectedIndex = items.findIndex((item) => item.id === selectedId);
|
|
6114
|
-
if (selectedIndex
|
|
6331
|
+
if (selectedIndex !== -1) {
|
|
6115
6332
|
this.privateFields().selectedIndex = selectedIndex;
|
|
6116
6333
|
}
|
|
6117
6334
|
}
|
|
@@ -6154,102 +6371,160 @@ class SettingsListAdapter {
|
|
|
6154
6371
|
}
|
|
6155
6372
|
|
|
6156
6373
|
// src/ui/settings-mutations.ts
|
|
6157
|
-
var
|
|
6158
|
-
"scoutEnabled",
|
|
6159
|
-
"showUsageDetails",
|
|
6160
|
-
"showUsageFooter",
|
|
6161
|
-
"planGate",
|
|
6162
|
-
"failureGate",
|
|
6163
|
-
"completionGate",
|
|
6164
|
-
"collapseResponses",
|
|
6165
|
-
"blockOnBlocked",
|
|
6374
|
+
var BOOLEAN_SETTING_FIELDS = [
|
|
6166
6375
|
"autoLoopGate",
|
|
6167
|
-
"
|
|
6376
|
+
"blockOnBlocked",
|
|
6377
|
+
"collapseResponses",
|
|
6378
|
+
"completionGate",
|
|
6379
|
+
"failureGate",
|
|
6168
6380
|
"herdrIntegration",
|
|
6381
|
+
"outcomeLogging",
|
|
6382
|
+
"planGate",
|
|
6169
6383
|
"redactSecrets",
|
|
6384
|
+
"scoutEnabled",
|
|
6385
|
+
"sessionSummary",
|
|
6386
|
+
"showUsageDetails",
|
|
6387
|
+
"showUsageFooter",
|
|
6170
6388
|
"trackedFileContent",
|
|
6171
|
-
"untrackedContent"
|
|
6172
|
-
|
|
6173
|
-
|
|
6174
|
-
var
|
|
6389
|
+
"untrackedContent"
|
|
6390
|
+
];
|
|
6391
|
+
var BOOLEAN_SETTING_IDS = new Set(BOOLEAN_SETTING_FIELDS);
|
|
6392
|
+
var parseModelWhitelist = (value) => [
|
|
6393
|
+
...new Set(value.split(",").map((model) => model.trim()).filter(Boolean))
|
|
6394
|
+
];
|
|
6395
|
+
var applyCoreMutation = (settings, id, value, presets) => {
|
|
6175
6396
|
switch (id) {
|
|
6176
|
-
case "context":
|
|
6397
|
+
case "context": {
|
|
6177
6398
|
settings.contextMaxChars = presets.find((preset) => preset.label === value)?.value ?? settings.contextMaxChars;
|
|
6178
|
-
|
|
6179
|
-
|
|
6399
|
+
return true;
|
|
6400
|
+
}
|
|
6401
|
+
case "simpleMode": {
|
|
6180
6402
|
settings.simpleMode = value === "On";
|
|
6181
|
-
|
|
6182
|
-
|
|
6403
|
+
return true;
|
|
6404
|
+
}
|
|
6405
|
+
case "alwaysOn": {
|
|
6183
6406
|
settings.alwaysOn = value === "On";
|
|
6184
|
-
|
|
6185
|
-
|
|
6407
|
+
return true;
|
|
6408
|
+
}
|
|
6409
|
+
case "effort": {
|
|
6186
6410
|
settings.effort = value;
|
|
6187
|
-
|
|
6188
|
-
|
|
6411
|
+
return true;
|
|
6412
|
+
}
|
|
6413
|
+
case "customRule": {
|
|
6189
6414
|
settings.customRule = value.trim() || undefined;
|
|
6190
|
-
|
|
6191
|
-
|
|
6415
|
+
return true;
|
|
6416
|
+
}
|
|
6417
|
+
case "toolPolicies": {
|
|
6192
6418
|
settings.toolPolicies = JSON.parse(value);
|
|
6193
|
-
|
|
6194
|
-
|
|
6419
|
+
return true;
|
|
6420
|
+
}
|
|
6421
|
+
case "loopThreshold": {
|
|
6195
6422
|
settings.loopThreshold = Number(value.replace("After ", "").replace(" repeats", ""));
|
|
6196
|
-
|
|
6197
|
-
|
|
6423
|
+
return true;
|
|
6424
|
+
}
|
|
6425
|
+
case "maxCallsPerSession": {
|
|
6198
6426
|
settings.maxCallsPerSession = value === "∞" ? undefined : Number(value);
|
|
6199
|
-
|
|
6200
|
-
|
|
6427
|
+
return true;
|
|
6428
|
+
}
|
|
6429
|
+
case "modelWhitelist": {
|
|
6430
|
+
settings.modelWhitelist = parseModelWhitelist(value);
|
|
6431
|
+
return true;
|
|
6432
|
+
}
|
|
6433
|
+
case "failureMode": {
|
|
6201
6434
|
settings.failureMode = value;
|
|
6202
|
-
|
|
6203
|
-
|
|
6435
|
+
return true;
|
|
6436
|
+
}
|
|
6437
|
+
case "gitContext": {
|
|
6204
6438
|
settings.gitContext = value;
|
|
6205
|
-
|
|
6206
|
-
|
|
6439
|
+
return true;
|
|
6440
|
+
}
|
|
6441
|
+
default: {
|
|
6442
|
+
return false;
|
|
6443
|
+
}
|
|
6444
|
+
}
|
|
6445
|
+
};
|
|
6446
|
+
var applyNumericMutation = (settings, id, value) => {
|
|
6447
|
+
switch (id) {
|
|
6448
|
+
case "toolResultMaxLines": {
|
|
6207
6449
|
settings.toolResultMaxLines = Number(value);
|
|
6208
|
-
|
|
6209
|
-
|
|
6450
|
+
return true;
|
|
6451
|
+
}
|
|
6452
|
+
case "toolResultMaxBytes": {
|
|
6210
6453
|
settings.toolResultMaxBytes = Number(value);
|
|
6211
|
-
|
|
6212
|
-
|
|
6454
|
+
return true;
|
|
6455
|
+
}
|
|
6456
|
+
case "gitContextMaxChars": {
|
|
6213
6457
|
settings.gitContextMaxChars = Number(value);
|
|
6214
|
-
|
|
6215
|
-
|
|
6458
|
+
return true;
|
|
6459
|
+
}
|
|
6460
|
+
default: {
|
|
6461
|
+
return false;
|
|
6462
|
+
}
|
|
6463
|
+
}
|
|
6464
|
+
};
|
|
6465
|
+
var applyJevMutation = (settings, id, value) => {
|
|
6466
|
+
switch (id) {
|
|
6467
|
+
case "jevFilter": {
|
|
6216
6468
|
settings.jevFilterEnabled = value === "On";
|
|
6217
|
-
|
|
6218
|
-
|
|
6469
|
+
return true;
|
|
6470
|
+
}
|
|
6471
|
+
case "jevFilterSkipConfidence": {
|
|
6219
6472
|
settings.jevFilterSkipConfidence = Number(value);
|
|
6220
|
-
|
|
6221
|
-
|
|
6473
|
+
return true;
|
|
6474
|
+
}
|
|
6475
|
+
case "jevFilterNoulMargin": {
|
|
6222
6476
|
settings.jevFilterNoulMargin = Number(value);
|
|
6223
|
-
|
|
6224
|
-
|
|
6477
|
+
return true;
|
|
6478
|
+
}
|
|
6479
|
+
case "jevFilterOverrideWindow": {
|
|
6225
6480
|
settings.jevFilterOverrideWindow = Number(value.replace(" turns", ""));
|
|
6226
|
-
|
|
6227
|
-
|
|
6228
|
-
|
|
6229
|
-
|
|
6230
|
-
|
|
6481
|
+
return true;
|
|
6482
|
+
}
|
|
6483
|
+
case "jevTurnGateEveryTurns": {
|
|
6484
|
+
settings.jevTurnGateEveryTurns = value === "Off" ? 0 : Number(value.replaceAll(/[^0-9]/gu, ""));
|
|
6485
|
+
return true;
|
|
6486
|
+
}
|
|
6487
|
+
case "jevTurnGateNoulThreshold": {
|
|
6231
6488
|
settings.jevTurnGateNoulThreshold = Number(value);
|
|
6232
|
-
|
|
6233
|
-
|
|
6489
|
+
return true;
|
|
6490
|
+
}
|
|
6491
|
+
case "jevModel": {
|
|
6234
6492
|
settings.jevModel = value.trim() || DEFAULT_JEV_MODEL;
|
|
6235
|
-
|
|
6236
|
-
|
|
6493
|
+
return true;
|
|
6494
|
+
}
|
|
6495
|
+
case "jevTimeoutMs": {
|
|
6237
6496
|
settings.jevTimeoutMs = Number(value);
|
|
6238
|
-
|
|
6239
|
-
|
|
6497
|
+
return true;
|
|
6498
|
+
}
|
|
6499
|
+
case "jevDigestMaxChars": {
|
|
6240
6500
|
settings.jevDigestMaxChars = Number(value);
|
|
6241
|
-
|
|
6242
|
-
|
|
6501
|
+
return true;
|
|
6502
|
+
}
|
|
6503
|
+
case "jevPricePerMtok": {
|
|
6243
6504
|
settings.jevPricePerMtok = Number(value);
|
|
6244
|
-
|
|
6245
|
-
|
|
6505
|
+
return true;
|
|
6506
|
+
}
|
|
6507
|
+
case "jevTransport": {
|
|
6246
6508
|
settings.jevTransport = value;
|
|
6247
|
-
|
|
6248
|
-
|
|
6249
|
-
|
|
6250
|
-
|
|
6251
|
-
|
|
6252
|
-
|
|
6509
|
+
return true;
|
|
6510
|
+
}
|
|
6511
|
+
default: {
|
|
6512
|
+
return false;
|
|
6513
|
+
}
|
|
6514
|
+
}
|
|
6515
|
+
};
|
|
6516
|
+
var mutateAdvisorSettings = (settings, id, value, presets) => {
|
|
6517
|
+
if (applyCoreMutation(settings, id, value, presets)) {
|
|
6518
|
+
return;
|
|
6519
|
+
}
|
|
6520
|
+
if (applyNumericMutation(settings, id, value)) {
|
|
6521
|
+
return;
|
|
6522
|
+
}
|
|
6523
|
+
if (applyJevMutation(settings, id, value)) {
|
|
6524
|
+
return;
|
|
6525
|
+
}
|
|
6526
|
+
if (BOOLEAN_SETTING_IDS.has(id)) {
|
|
6527
|
+
settings[id] = value === "On";
|
|
6253
6528
|
}
|
|
6254
6529
|
};
|
|
6255
6530
|
|
|
@@ -6280,7 +6555,7 @@ class AdvisorSettingsSelector {
|
|
|
6280
6555
|
label: String(configuredContext),
|
|
6281
6556
|
value: configuredContext
|
|
6282
6557
|
}
|
|
6283
|
-
].
|
|
6558
|
+
].toSorted((a, b) => a.value - b.value);
|
|
6284
6559
|
if (this.settings.simpleMode) {
|
|
6285
6560
|
this.startSimpleModeGradient();
|
|
6286
6561
|
}
|
|
@@ -6311,8 +6586,10 @@ class AdvisorSettingsSelector {
|
|
|
6311
6586
|
}
|
|
6312
6587
|
return defaultLabel(text, selected);
|
|
6313
6588
|
};
|
|
6589
|
+
const modelWhitelist = advisorModelWhitelistItem(this.settings, this.options.modelRefs, this.options.keybindings, this.options.theme, this.options.tui);
|
|
6314
6590
|
const items = createSettingsItems({
|
|
6315
6591
|
effortLevels: this.options.effortLevels,
|
|
6592
|
+
modelWhitelist,
|
|
6316
6593
|
presets: this.presets,
|
|
6317
6594
|
settings: this.settings,
|
|
6318
6595
|
theme: this.options.theme,
|
|
@@ -6352,16 +6629,16 @@ class AdvisorSettingsSelector {
|
|
|
6352
6629
|
(this.options.onChange ?? this.options.onSave)?.({
|
|
6353
6630
|
...this.settings,
|
|
6354
6631
|
showUsageDetails: this.settings.showUsageDetails ?? true,
|
|
6355
|
-
toolPolicies: { ...this.settings.toolPolicies
|
|
6632
|
+
toolPolicies: { ...this.settings.toolPolicies }
|
|
6356
6633
|
});
|
|
6357
|
-
if (id === "context" || id === "simpleMode" || id === "customRule" || id === "toolPolicies") {
|
|
6634
|
+
if (id === "context" || id === "simpleMode" || id === "customRule" || id === "modelWhitelist" || id === "toolPolicies") {
|
|
6358
6635
|
this.settingsList = this.createSettingsList(id);
|
|
6359
6636
|
}
|
|
6360
6637
|
}
|
|
6361
6638
|
}
|
|
6362
6639
|
|
|
6363
6640
|
// src/commands/settings-persistence.ts
|
|
6364
|
-
var
|
|
6641
|
+
var applySessionSettings = (settings) => {
|
|
6365
6642
|
setAdvisorEffortRef(settings.effort === "Default (Model Default)" ? undefined : settings.effort);
|
|
6366
6643
|
setContextMaxCharsRef(settings.contextMaxChars);
|
|
6367
6644
|
setAdvisorPlanGateRef(settings.planGate);
|
|
@@ -6373,6 +6650,7 @@ var applyAdvisorSettings = (settings) => {
|
|
|
6373
6650
|
setAdvisorAutoLoopGateRef(settings.autoLoopGate ?? true);
|
|
6374
6651
|
setAdvisorLoopThresholdRef(settings.loopThreshold ?? 3);
|
|
6375
6652
|
setAdvisorMaxCallsPerSessionRef(settings.maxCallsPerSession);
|
|
6653
|
+
setAdvisorModelWhitelistRef(settings.modelWhitelist ?? []);
|
|
6376
6654
|
setAdvisorSessionSummaryRef(settings.sessionSummary ?? false);
|
|
6377
6655
|
setAdvisorScoutEnabledRef(settings.scoutEnabled ?? false);
|
|
6378
6656
|
setShowUsageDetailsRef(settings.showUsageDetails ?? true);
|
|
@@ -6381,6 +6659,8 @@ var applyAdvisorSettings = (settings) => {
|
|
|
6381
6659
|
setAlwaysOnRef(settings.alwaysOn ?? false);
|
|
6382
6660
|
setAdvisorFailureModeRef(settings.failureMode ?? "block-session");
|
|
6383
6661
|
setAdvisorHerdrIntegrationRef(settings.herdrIntegration ?? true);
|
|
6662
|
+
};
|
|
6663
|
+
var applyJevSettings = (settings) => {
|
|
6384
6664
|
setAdvisorJevFilterEnabledRef(settings.jevFilterEnabled ?? false);
|
|
6385
6665
|
setAdvisorJevFilterSkipConfidenceRef(settings.jevFilterSkipConfidence ?? DEFAULT_JEV_FILTER_SKIP_CONFIDENCE);
|
|
6386
6666
|
setAdvisorJevFilterNoulMarginRef(settings.jevFilterNoulMargin ?? DEFAULT_JEV_FILTER_NOUL_MARGIN);
|
|
@@ -6392,6 +6672,8 @@ var applyAdvisorSettings = (settings) => {
|
|
|
6392
6672
|
setAdvisorJevTransportRef(settings.jevTransport ?? DEFAULT_JEV_TRANSPORT);
|
|
6393
6673
|
setAdvisorJevTurnGateEveryTurnsRef(settings.jevTurnGateEveryTurns ?? DEFAULT_JEV_TURN_GATE_EVERY_TURNS);
|
|
6394
6674
|
setAdvisorJevTurnGateNoulThresholdRef(settings.jevTurnGateNoulThreshold ?? DEFAULT_JEV_TURN_GATE_NOUL_THRESHOLD);
|
|
6675
|
+
};
|
|
6676
|
+
var applyDisclosureSettings = (settings) => {
|
|
6395
6677
|
setAdvisorToolResultMaxLinesRef(settings.toolResultMaxLines ?? 2000);
|
|
6396
6678
|
setAdvisorToolResultMaxBytesRef(settings.toolResultMaxBytes ?? 50 * 1024);
|
|
6397
6679
|
setAdvisorRedactSecretsRef(settings.redactSecrets ?? false);
|
|
@@ -6402,6 +6684,11 @@ var applyAdvisorSettings = (settings) => {
|
|
|
6402
6684
|
setAdvisorUntrackedContentRef(settings.untrackedContent ?? false);
|
|
6403
6685
|
setAdvisorOutcomeLoggingRef(settings.outcomeLogging ?? false);
|
|
6404
6686
|
};
|
|
6687
|
+
var applyAdvisorSettings = (settings) => {
|
|
6688
|
+
applySessionSettings(settings);
|
|
6689
|
+
applyJevSettings(settings);
|
|
6690
|
+
applyDisclosureSettings(settings);
|
|
6691
|
+
};
|
|
6405
6692
|
var saveAdvisorSettings = (ctx, settings) => {
|
|
6406
6693
|
applyAdvisorSettings(settings);
|
|
6407
6694
|
const persisted = getPersistedModelRefs();
|
|
@@ -6421,10 +6708,12 @@ var registerSettingsCommands = (runtime) => {
|
|
|
6421
6708
|
return;
|
|
6422
6709
|
}
|
|
6423
6710
|
const initial = getAdvisorSettings();
|
|
6424
|
-
await ctx.ui.custom((tui, theme,
|
|
6711
|
+
await ctx.ui.custom((tui, theme, keybindings, done) => new AdvisorSettingsSelector({
|
|
6425
6712
|
effortLevels: EFFORT_LEVELS,
|
|
6426
6713
|
initial,
|
|
6427
|
-
|
|
6714
|
+
keybindings,
|
|
6715
|
+
modelRefs: getConfiguredModelRefs(ctx),
|
|
6716
|
+
onCancel: () => done(undefined),
|
|
6428
6717
|
onChange: (settings) => {
|
|
6429
6718
|
try {
|
|
6430
6719
|
saveAdvisorSettings(ctx, settings);
|
|
@@ -6484,6 +6773,7 @@ import {
|
|
|
6484
6773
|
writeFile
|
|
6485
6774
|
} from "node:fs/promises";
|
|
6486
6775
|
import { join as join5 } from "node:path";
|
|
6776
|
+
import { setTimeout as sleep } from "node:timers/promises";
|
|
6487
6777
|
import { getAgentDir as getAgentDir4 } from "@earendil-works/pi-coding-agent";
|
|
6488
6778
|
var ADOPTIONS = [
|
|
6489
6779
|
"followed",
|
|
@@ -6499,6 +6789,7 @@ var VALIDATIONS = [
|
|
|
6499
6789
|
var MAX_LOG_BYTES = 1024 * 1024;
|
|
6500
6790
|
var statePath = () => join5(getAgentDir4(), "advisor-outcomes-salt");
|
|
6501
6791
|
var outcomeLogPath = () => join5(getAgentDir4(), "advisor-outcomes.jsonl");
|
|
6792
|
+
var isErrnoException = (error) => error instanceof Error && ("code" in error);
|
|
6502
6793
|
var salt = async () => {
|
|
6503
6794
|
const path = statePath();
|
|
6504
6795
|
await mkdir(getAgentDir4(), { mode: 448, recursive: true });
|
|
@@ -6510,7 +6801,7 @@ var salt = async () => {
|
|
|
6510
6801
|
}
|
|
6511
6802
|
await unlink(path);
|
|
6512
6803
|
} catch (error) {
|
|
6513
|
-
if (error.code !== "ENOENT") {
|
|
6804
|
+
if (!isErrnoException(error) || error.code !== "ENOENT") {
|
|
6514
6805
|
throw error;
|
|
6515
6806
|
}
|
|
6516
6807
|
}
|
|
@@ -6521,7 +6812,7 @@ var salt = async () => {
|
|
|
6521
6812
|
await link(temporary, path);
|
|
6522
6813
|
return value;
|
|
6523
6814
|
} catch (error) {
|
|
6524
|
-
if (error.code !== "EEXIST") {
|
|
6815
|
+
if (!isErrnoException(error) || error.code !== "EEXIST") {
|
|
6525
6816
|
throw error;
|
|
6526
6817
|
}
|
|
6527
6818
|
} finally {
|
|
@@ -6553,7 +6844,7 @@ var withOutcomeLock = async (run) => {
|
|
|
6553
6844
|
}
|
|
6554
6845
|
}
|
|
6555
6846
|
} catch (error) {
|
|
6556
|
-
if (error.code !== "EEXIST") {
|
|
6847
|
+
if (!isErrnoException(error) || error.code !== "EEXIST") {
|
|
6557
6848
|
throw error;
|
|
6558
6849
|
}
|
|
6559
6850
|
const observed = await stat(lockPath).catch(() => {
|
|
@@ -6570,7 +6861,7 @@ var withOutcomeLock = async (run) => {
|
|
|
6570
6861
|
}
|
|
6571
6862
|
continue;
|
|
6572
6863
|
}
|
|
6573
|
-
await
|
|
6864
|
+
await sleep(5);
|
|
6574
6865
|
}
|
|
6575
6866
|
}
|
|
6576
6867
|
throw new Error("Timed out waiting to append an Advisor outcome.");
|
|
@@ -6596,11 +6887,8 @@ var appendOutcome = async (record) => {
|
|
|
6596
6887
|
}
|
|
6597
6888
|
throw error;
|
|
6598
6889
|
});
|
|
6599
|
-
|
|
6600
|
-
|
|
6601
|
-
} else {
|
|
6602
|
-
await appendFile(path, line, { encoding: "utf8", mode: 384 });
|
|
6603
|
-
}
|
|
6890
|
+
const overflow = currentBytes + Buffer.byteLength(line) > MAX_LOG_BYTES;
|
|
6891
|
+
await (overflow ? writeFile(path, line, { encoding: "utf-8", mode: 384 }) : appendFile(path, line, { encoding: "utf-8", mode: 384 }));
|
|
6604
6892
|
await chmod(path, 384);
|
|
6605
6893
|
return next;
|
|
6606
6894
|
});
|
|
@@ -6698,7 +6986,7 @@ var handleJevTurnEnd = async (registration, ctx) => {
|
|
|
6698
6986
|
if (interval <= 0 || session.turnsSinceConsultation <= 0 || session.turnsSinceConsultation % interval !== 0) {
|
|
6699
6987
|
return;
|
|
6700
6988
|
}
|
|
6701
|
-
if (isSimpleMode() || session.blocked || !registration.activeTools().includes("ask_advisor") || !session.canConsult(getAdvisorMaxCallsPerSession())) {
|
|
6989
|
+
if (isSimpleMode() || session.blocked || !registration.activeTools().includes("ask_advisor") || !advisorModelIsAllowed(ctx) || !session.canConsult(getAdvisorMaxCallsPerSession())) {
|
|
6702
6990
|
return;
|
|
6703
6991
|
}
|
|
6704
6992
|
const consult = registration.deps?.consult ?? registration.consult;
|
|
@@ -6758,7 +7046,7 @@ var handleJevTurnEnd = async (registration, ctx) => {
|
|
|
6758
7046
|
}
|
|
6759
7047
|
} catch (error) {
|
|
6760
7048
|
session.recordJevGateFailure();
|
|
6761
|
-
if (error instanceof
|
|
7049
|
+
if (error instanceof JevFailureError) {
|
|
6762
7050
|
notifyFailureOnce(ctx, error.category, error.message);
|
|
6763
7051
|
} else if (!ctx.signal?.aborted) {
|
|
6764
7052
|
notifyFailureOnce(ctx, "error", error instanceof Error ? error.message : String(error));
|
|
@@ -6773,9 +7061,7 @@ var registerJevTurnGate = (on, registration) => {
|
|
|
6773
7061
|
import { Type } from "typebox";
|
|
6774
7062
|
|
|
6775
7063
|
// src/tools/render-advisor-result.ts
|
|
6776
|
-
import {
|
|
6777
|
-
getMarkdownTheme as getMarkdownTheme4
|
|
6778
|
-
} from "@earendil-works/pi-coding-agent";
|
|
7064
|
+
import { getMarkdownTheme as getMarkdownTheme4 } from "@earendil-works/pi-coding-agent";
|
|
6779
7065
|
import { Box as Box3, Markdown as Markdown4, Spacer, Text as Text5 } from "@earendil-works/pi-tui";
|
|
6780
7066
|
var advisorResultDetails = (result) => result.details;
|
|
6781
7067
|
var syncRenderPhase = (context, phase) => {
|
|
@@ -6832,6 +7118,24 @@ var renderPartialAdvisorResult = (box, result, expanded, theme, context) => {
|
|
|
6832
7118
|
box.addChild(new Markdown4(adviceForDisplay(details.text, expanded), 0, 0, getMarkdownTheme4()));
|
|
6833
7119
|
}
|
|
6834
7120
|
};
|
|
7121
|
+
var thinkingPreview = (details) => details?.thinking?.trim() ? `${details.thinking.slice(0, 300)}${details.thinking.length > 300 ? "…" : ""}` : "";
|
|
7122
|
+
var finalResultLines = (details, advice, theme) => {
|
|
7123
|
+
const lines = [renderAdvisorResponseHeader(hasSoundVerdict(advice), theme)];
|
|
7124
|
+
if (details?.advisor) {
|
|
7125
|
+
lines.push(theme.fg("dim", ` ${details.advisor}`));
|
|
7126
|
+
}
|
|
7127
|
+
if (getAdvisorSettings().showUsageDetails) {
|
|
7128
|
+
const usage = formatAdvisorUsage(details?.usage);
|
|
7129
|
+
if (usage) {
|
|
7130
|
+
lines.push(theme.fg("dim", ` Usage: ${usage}`));
|
|
7131
|
+
}
|
|
7132
|
+
}
|
|
7133
|
+
const attachments = attachmentLabels(details);
|
|
7134
|
+
if (attachments.length) {
|
|
7135
|
+
lines.push(theme.fg("dim", ` ${attachments.join(" · ")}`));
|
|
7136
|
+
}
|
|
7137
|
+
return lines;
|
|
7138
|
+
};
|
|
6835
7139
|
var renderFinalAdvisorResult = (box, result, expanded, theme, context) => {
|
|
6836
7140
|
syncRenderPhase(context, "final");
|
|
6837
7141
|
if (context.state.timerId) {
|
|
@@ -6854,21 +7158,8 @@ var renderFinalAdvisorResult = (box, result, expanded, theme, context) => {
|
|
|
6854
7158
|
return;
|
|
6855
7159
|
}
|
|
6856
7160
|
const advice = details?.text || textFrom(result.content);
|
|
6857
|
-
const
|
|
6858
|
-
|
|
6859
|
-
lines.push(theme.fg("dim", ` ${details.advisor}`));
|
|
6860
|
-
}
|
|
6861
|
-
if (getAdvisorSettings().showUsageDetails) {
|
|
6862
|
-
const usage = formatAdvisorUsage(details?.usage);
|
|
6863
|
-
if (usage) {
|
|
6864
|
-
lines.push(theme.fg("dim", ` Usage: ${usage}`));
|
|
6865
|
-
}
|
|
6866
|
-
}
|
|
6867
|
-
const attachments = attachmentLabels(details);
|
|
6868
|
-
if (attachments.length) {
|
|
6869
|
-
lines.push(theme.fg("dim", ` ${attachments.join(" · ")}`));
|
|
6870
|
-
}
|
|
6871
|
-
const thinking = details?.thinking?.trim() ? `${details.thinking.slice(0, 300)}${details.thinking.length > 300 ? "…" : ""}` : "";
|
|
7161
|
+
const thinking = thinkingPreview(details);
|
|
7162
|
+
const lines = finalResultLines(details, advice, theme);
|
|
6872
7163
|
const displayAdvice = advice || "(Advisor returned no advice.)";
|
|
6873
7164
|
box.addChild(new Text5(lines.join(`
|
|
6874
7165
|
`), 0, 0));
|
|
@@ -6891,6 +7182,12 @@ var renderAdvisorResult = (result, { isPartial, expanded }, theme, context) => {
|
|
|
6891
7182
|
};
|
|
6892
7183
|
|
|
6893
7184
|
// src/tools/register-ask-advisor.ts
|
|
7185
|
+
var assertAdvisorModelAccess = (ctx) => {
|
|
7186
|
+
const accessReason = advisorModelAccessReason(ctx);
|
|
7187
|
+
if (accessReason) {
|
|
7188
|
+
throw new Error(accessReason);
|
|
7189
|
+
}
|
|
7190
|
+
};
|
|
6894
7191
|
var claimTrackedHandoff = (session, includeTrackedFiles) => {
|
|
6895
7192
|
if (!includeTrackedFiles?.length) {
|
|
6896
7193
|
return;
|
|
@@ -6913,6 +7210,7 @@ var registerAskAdvisorTool = ({
|
|
|
6913
7210
|
description: "Consult the on-demand Advisor model for strategic guidance. Call with an empty object for a contextual review; attach an optional draft for concrete plan or completion review. If the Advisor explicitly names a missing file, you may make a sequential follow-up call with includeTrackedFiles when enabled and relevant.",
|
|
6914
7211
|
async execute(_id, params, signal, onUpdate, ctx) {
|
|
6915
7212
|
reservedCalls.delete(_id);
|
|
7213
|
+
assertAdvisorModelAccess(ctx);
|
|
6916
7214
|
if (!(isSimpleMode() || session.canConsult(getAdvisorMaxCallsPerSession()))) {
|
|
6917
7215
|
throw new Error("Advisor call budget exhausted for this session.");
|
|
6918
7216
|
}
|
|
@@ -6985,7 +7283,22 @@ var registerAskAdvisorTool = ({
|
|
|
6985
7283
|
const usage = snapshotAdvisorUsage(result.usage);
|
|
6986
7284
|
const piUsage = advisorUsageForPi(result.usage);
|
|
6987
7285
|
updateAdvisorUsageStatus(ctx, session);
|
|
6988
|
-
|
|
7286
|
+
const details = {
|
|
7287
|
+
adviceId: result.adviceId,
|
|
7288
|
+
advisor: result.model,
|
|
7289
|
+
draftBytes: result.draftBytes,
|
|
7290
|
+
preferenceBytes: result.preferenceBytes,
|
|
7291
|
+
question: resolveAdvisorRequest(params.question),
|
|
7292
|
+
scout: scoutDetails,
|
|
7293
|
+
text: result.markdown,
|
|
7294
|
+
thinking: result.thinkingText,
|
|
7295
|
+
trackedBytes: result.trackedBytes,
|
|
7296
|
+
untrackedBytes: result.untrackedBytes
|
|
7297
|
+
};
|
|
7298
|
+
if (usage) {
|
|
7299
|
+
details.usage = usage;
|
|
7300
|
+
}
|
|
7301
|
+
const response = {
|
|
6989
7302
|
content: [
|
|
6990
7303
|
{
|
|
6991
7304
|
text: `Advisor (${result.model})
|
|
@@ -6994,21 +7307,12 @@ ${result.markdown}`,
|
|
|
6994
7307
|
type: "text"
|
|
6995
7308
|
}
|
|
6996
7309
|
],
|
|
6997
|
-
details
|
|
6998
|
-
adviceId: result.adviceId,
|
|
6999
|
-
advisor: result.model,
|
|
7000
|
-
draftBytes: result.draftBytes,
|
|
7001
|
-
preferenceBytes: result.preferenceBytes,
|
|
7002
|
-
question: resolveAdvisorRequest(params.question),
|
|
7003
|
-
scout: scoutDetails,
|
|
7004
|
-
text: result.markdown,
|
|
7005
|
-
thinking: result.thinkingText,
|
|
7006
|
-
trackedBytes: result.trackedBytes,
|
|
7007
|
-
untrackedBytes: result.untrackedBytes,
|
|
7008
|
-
...usage ? { usage } : {}
|
|
7009
|
-
},
|
|
7010
|
-
...piUsage ? { usage: piUsage } : {}
|
|
7310
|
+
details
|
|
7011
7311
|
};
|
|
7312
|
+
if (piUsage) {
|
|
7313
|
+
response.usage = piUsage;
|
|
7314
|
+
}
|
|
7315
|
+
return response;
|
|
7012
7316
|
} catch (error) {
|
|
7013
7317
|
coalescedUpdate.flush();
|
|
7014
7318
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -7076,27 +7380,31 @@ var sendAutomaticGateCall = (pi, event) => {
|
|
|
7076
7380
|
}, { deliverAs: "steer" });
|
|
7077
7381
|
};
|
|
7078
7382
|
var sendAutomaticGateFailure = (pi, markdown, usage) => {
|
|
7079
|
-
const
|
|
7383
|
+
const details = { text: markdown };
|
|
7384
|
+
if (usage) {
|
|
7385
|
+
details.usage = usage;
|
|
7386
|
+
}
|
|
7080
7387
|
pi.sendMessage({
|
|
7081
7388
|
content: markdown,
|
|
7082
7389
|
customType: "advisor-loop-result",
|
|
7083
|
-
details
|
|
7084
|
-
text: markdown,
|
|
7085
|
-
...normalizedUsage ? { usage: normalizedUsage } : {}
|
|
7086
|
-
},
|
|
7390
|
+
details,
|
|
7087
7391
|
display: true
|
|
7088
7392
|
}, { deliverAs: "steer" });
|
|
7089
7393
|
};
|
|
7090
7394
|
var sendAutomaticGateResult = (pi, result) => {
|
|
7395
|
+
const details = {
|
|
7396
|
+
advisor: result.model,
|
|
7397
|
+
decision: result.decision,
|
|
7398
|
+
text: result.markdown
|
|
7399
|
+
};
|
|
7400
|
+
const normalizedUsage = snapshotAdvisorUsage(result.usage);
|
|
7401
|
+
if (normalizedUsage) {
|
|
7402
|
+
details.usage = normalizedUsage;
|
|
7403
|
+
}
|
|
7091
7404
|
pi.sendMessage({
|
|
7092
7405
|
content: adviceForGateText(result),
|
|
7093
7406
|
customType: "advisor-loop-result",
|
|
7094
|
-
details
|
|
7095
|
-
advisor: result.model,
|
|
7096
|
-
decision: result.decision,
|
|
7097
|
-
text: result.markdown,
|
|
7098
|
-
...snapshotAdvisorUsage(result.usage) ? { usage: snapshotAdvisorUsage(result.usage) } : {}
|
|
7099
|
-
},
|
|
7407
|
+
details,
|
|
7100
7408
|
display: true
|
|
7101
7409
|
}, { deliverAs: "steer" });
|
|
7102
7410
|
};
|
|
@@ -7112,7 +7420,7 @@ var applyGateDecision = (pi, ctx, session, result, reason, failureMode) => {
|
|
|
7112
7420
|
});
|
|
7113
7421
|
updateAdvisorUsageStatus(ctx, session);
|
|
7114
7422
|
const failure = failureEffect(result.category, result.message, ctx, session, failureMode);
|
|
7115
|
-
sendAutomaticGateFailure(pi, `**Advisor gate failure (${result.category}):** ${result.message}`, result.usage);
|
|
7423
|
+
sendAutomaticGateFailure(pi, `**Advisor gate failure (${result.category}):** ${result.message}`, snapshotAdvisorUsage(result.usage));
|
|
7116
7424
|
return failure.block ? { block: true, reason: `${reason}
|
|
7117
7425
|
${failure.reason}` } : undefined;
|
|
7118
7426
|
}
|
|
@@ -7139,7 +7447,7 @@ ${failure.reason}` } : undefined;
|
|
|
7139
7447
|
return { block: true, reason: gateReason };
|
|
7140
7448
|
};
|
|
7141
7449
|
var handleAutomaticGate = async (pi, event, ctx, session, runGate, scoutStatus) => {
|
|
7142
|
-
if (isSimpleMode() || event.toolName === "ask_advisor" || !advisorAutoLoopGateRef || !session.recordToolCall(event.toolName, event.input, advisorLoopThresholdRef)) {
|
|
7450
|
+
if (isSimpleMode() || event.toolName === "ask_advisor" || !advisorModelIsAllowed(ctx) || !advisorAutoLoopGateRef || !session.recordToolCall(event.toolName, event.input, advisorLoopThresholdRef)) {
|
|
7143
7451
|
return;
|
|
7144
7452
|
}
|
|
7145
7453
|
const reason = `Advisor loop gate: normalized signature for ${event.toolName} repeated ${advisorLoopThresholdRef} times without a materially different tool action.`;
|
|
@@ -7181,6 +7489,16 @@ var handleAutomaticGate = async (pi, event, ctx, session, runGate, scoutStatus)
|
|
|
7181
7489
|
};
|
|
7182
7490
|
|
|
7183
7491
|
// src/tools/register-lifecycle.ts
|
|
7492
|
+
var modelAccessBlock = (toolName, ctx) => {
|
|
7493
|
+
const access = advisorModelAccess(ctx);
|
|
7494
|
+
if (access.allowed || toolName !== "ask_advisor") {
|
|
7495
|
+
return;
|
|
7496
|
+
}
|
|
7497
|
+
return {
|
|
7498
|
+
block: true,
|
|
7499
|
+
reason: access.reason
|
|
7500
|
+
};
|
|
7501
|
+
};
|
|
7184
7502
|
var registerToolLifecycle = ({
|
|
7185
7503
|
pi,
|
|
7186
7504
|
reservedCalls,
|
|
@@ -7218,6 +7536,9 @@ var registerToolLifecycle = ({
|
|
|
7218
7536
|
return;
|
|
7219
7537
|
}
|
|
7220
7538
|
loadConfig(ctx);
|
|
7539
|
+
if (!advisorModelAccess(ctx).allowed) {
|
|
7540
|
+
return;
|
|
7541
|
+
}
|
|
7221
7542
|
const guidelines = advisorInvocationGuidelines();
|
|
7222
7543
|
const budget = isSimpleMode() ? undefined : session.remainingCalls(getAdvisorMaxCallsPerSession());
|
|
7223
7544
|
if (budget !== undefined) {
|
|
@@ -7245,6 +7566,10 @@ ${guidelines.map((rule) => `- ${rule}`).join(`
|
|
|
7245
7566
|
if (!loadConfigOrSkipGating(ctx)) {
|
|
7246
7567
|
return;
|
|
7247
7568
|
}
|
|
7569
|
+
const accessBlock = modelAccessBlock(event.toolName, ctx);
|
|
7570
|
+
if (accessBlock) {
|
|
7571
|
+
return accessBlock;
|
|
7572
|
+
}
|
|
7248
7573
|
const reservation = reserveAdvisorCall(event, ctx, session, reservedCalls);
|
|
7249
7574
|
if (event.toolName === "ask_advisor") {
|
|
7250
7575
|
return reservation;
|
|
@@ -7338,65 +7663,59 @@ var registerOutcomeTool = ({
|
|
|
7338
7663
|
};
|
|
7339
7664
|
|
|
7340
7665
|
// src/tools/register-renderers.ts
|
|
7341
|
-
import {
|
|
7342
|
-
getMarkdownTheme as getMarkdownTheme5
|
|
7343
|
-
} from "@earendil-works/pi-coding-agent";
|
|
7666
|
+
import { getMarkdownTheme as getMarkdownTheme5 } from "@earendil-works/pi-coding-agent";
|
|
7344
7667
|
import { Box as Box4, Markdown as Markdown5, Text as Text7 } from "@earendil-works/pi-tui";
|
|
7668
|
+
var addUsageLine = (box, details, theme) => {
|
|
7669
|
+
if (!getAdvisorSettings().showUsageDetails) {
|
|
7670
|
+
return;
|
|
7671
|
+
}
|
|
7672
|
+
const usageText = formatAdvisorUsage(details?.usage);
|
|
7673
|
+
if (usageText) {
|
|
7674
|
+
box.addChild(new Text7(theme.fg("dim", ` Usage: ${usageText}`), 0, 0));
|
|
7675
|
+
}
|
|
7676
|
+
};
|
|
7677
|
+
var callQuestionRenderer = (message, _options, theme) => renderAdvisorCallBox(message.details?.question, theme);
|
|
7678
|
+
var scoutResultRenderer = (entry, { expanded }, theme) => {
|
|
7679
|
+
const box = new Box4(1, 1, (text) => theme.bg("customMessageBg", text));
|
|
7680
|
+
renderScoutDetails(box, entry.data, Boolean(expanded), theme);
|
|
7681
|
+
return box;
|
|
7682
|
+
};
|
|
7683
|
+
var turnGateResultRenderer = (message, { expanded }, theme) => {
|
|
7684
|
+
const { details } = message;
|
|
7685
|
+
const box = new Box4(1, 1, (text) => theme.bg("customMessageBg", text));
|
|
7686
|
+
box.addChild(new Text7(theme.fg("warning", theme.bold("◆ ADVISOR · TURN REVIEW")), 0, 0));
|
|
7687
|
+
if (details?.advisor) {
|
|
7688
|
+
box.addChild(new Text7(theme.fg("dim", ` ${details.advisor}`), 0, 0));
|
|
7689
|
+
}
|
|
7690
|
+
addUsageLine(box, details, theme);
|
|
7691
|
+
if (details?.text) {
|
|
7692
|
+
box.addChild(new Markdown5(adviceForDisplay(details.text, Boolean(expanded)), 0, 0, getMarkdownTheme5()));
|
|
7693
|
+
} else {
|
|
7694
|
+
box.addChild(new Text7(theme.fg("error", isString(message.content) ? message.content : "Advisor turn review failed."), 0, 0));
|
|
7695
|
+
}
|
|
7696
|
+
return box;
|
|
7697
|
+
};
|
|
7698
|
+
var loopResultRenderer = (message, { expanded }, theme) => {
|
|
7699
|
+
const { details } = message;
|
|
7700
|
+
const box = new Box4(1, 1, (text) => theme.bg("customMessageBg", text));
|
|
7701
|
+
box.addChild(new Text7(theme.fg("warning", theme.bold(`◆ ADVISOR GATE: ${details?.decision ?? "failure"}`)), 0, 0));
|
|
7702
|
+
if (details?.advisor) {
|
|
7703
|
+
box.addChild(new Text7(theme.fg("dim", ` ${details.advisor}`), 0, 0));
|
|
7704
|
+
}
|
|
7705
|
+
addUsageLine(box, details, theme);
|
|
7706
|
+
if (details?.text) {
|
|
7707
|
+
box.addChild(new Markdown5(adviceForDisplay(details.text, Boolean(expanded)), 0, 0, getMarkdownTheme5()));
|
|
7708
|
+
} else {
|
|
7709
|
+
box.addChild(new Text7(theme.fg("error", isString(message.content) ? message.content : "Advisor gate failed."), 0, 0));
|
|
7710
|
+
}
|
|
7711
|
+
return box;
|
|
7712
|
+
};
|
|
7345
7713
|
var registerToolRenderers = (pi) => {
|
|
7346
|
-
pi.registerEntryRenderer?.("advisor-scout-result",
|
|
7347
|
-
|
|
7348
|
-
|
|
7349
|
-
|
|
7350
|
-
|
|
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
|
-
});
|
|
7376
|
-
pi.registerMessageRenderer?.("advisor-loop-call", (message, _options, theme) => {
|
|
7377
|
-
const details = message.details;
|
|
7378
|
-
return renderAdvisorCallBox(details?.question, theme);
|
|
7379
|
-
});
|
|
7380
|
-
pi.registerMessageRenderer?.("advisor-loop-result", (message, { expanded }, theme) => {
|
|
7381
|
-
const details = message.details;
|
|
7382
|
-
const box = new Box4(1, 1, (text) => theme.bg("customMessageBg", text));
|
|
7383
|
-
box.addChild(new Text7(theme.fg("warning", theme.bold(`◆ ADVISOR GATE: ${details?.decision ?? "failure"}`)), 0, 0));
|
|
7384
|
-
if (details?.advisor) {
|
|
7385
|
-
box.addChild(new Text7(theme.fg("dim", ` ${details.advisor}`), 0, 0));
|
|
7386
|
-
}
|
|
7387
|
-
if (getAdvisorSettings().showUsageDetails) {
|
|
7388
|
-
const usage = formatAdvisorUsage(details?.usage);
|
|
7389
|
-
if (usage) {
|
|
7390
|
-
box.addChild(new Text7(theme.fg("dim", ` Usage: ${usage}`), 0, 0));
|
|
7391
|
-
}
|
|
7392
|
-
}
|
|
7393
|
-
if (details?.text) {
|
|
7394
|
-
box.addChild(new Markdown5(adviceForDisplay(details.text, Boolean(expanded)), 0, 0, getMarkdownTheme5()));
|
|
7395
|
-
} else {
|
|
7396
|
-
box.addChild(new Text7(theme.fg("error", typeof message.content === "string" ? message.content : "Advisor gate failed."), 0, 0));
|
|
7397
|
-
}
|
|
7398
|
-
return box;
|
|
7399
|
-
});
|
|
7714
|
+
pi.registerEntryRenderer?.("advisor-scout-result", scoutResultRenderer);
|
|
7715
|
+
pi.registerMessageRenderer?.("advisor-turn-gate-call", callQuestionRenderer);
|
|
7716
|
+
pi.registerMessageRenderer?.("advisor-turn-gate-result", turnGateResultRenderer);
|
|
7717
|
+
pi.registerMessageRenderer?.("advisor-loop-call", callQuestionRenderer);
|
|
7718
|
+
pi.registerMessageRenderer?.("advisor-loop-result", loopResultRenderer);
|
|
7400
7719
|
};
|
|
7401
7720
|
|
|
7402
7721
|
// src/tools/registration.ts
|
|
@@ -7415,20 +7734,23 @@ var registerAdvisorTool = (pi, session = advisorSessionState, dependencies = {})
|
|
|
7415
7734
|
registerToolLifecycle(registration);
|
|
7416
7735
|
registerAskAdvisorTool(registration);
|
|
7417
7736
|
registerOutcomeTool(registration);
|
|
7418
|
-
|
|
7737
|
+
const turnGate = {
|
|
7419
7738
|
activeTools: () => pi.getActiveTools(),
|
|
7420
7739
|
consult: registration.consult,
|
|
7421
|
-
...dependencies.turnGateDeps ? { deps: dependencies.turnGateDeps } : {},
|
|
7422
7740
|
send: (message) => pi.sendMessage(message, { deliverAs: "steer" }),
|
|
7423
7741
|
session
|
|
7424
|
-
}
|
|
7742
|
+
};
|
|
7743
|
+
if (dependencies.turnGateDeps) {
|
|
7744
|
+
turnGate.deps = dependencies.turnGateDeps;
|
|
7745
|
+
}
|
|
7746
|
+
registerJevTurnGate((event, handler) => pi.on(event, handler), turnGate);
|
|
7425
7747
|
};
|
|
7426
7748
|
|
|
7427
7749
|
// extensions/index.ts
|
|
7428
7750
|
var consultAdvisor2 = (...args) => consultAdvisor(...args);
|
|
7429
7751
|
var parseAutomaticDecision2 = (...args) => parseAutomaticDecision(...args);
|
|
7430
7752
|
var runAdvisorGate2 = (...args) => runAdvisorGate(...args);
|
|
7431
|
-
function
|
|
7753
|
+
function registerPiAdvisor(pi) {
|
|
7432
7754
|
const sessionState = new AdvisorSessionState;
|
|
7433
7755
|
const scoutStatus = new ScoutStatusManager;
|
|
7434
7756
|
setHerdrBlockedEmitter((active, label) => pi.events.emit("herdr:blocked", { active, label }));
|
|
@@ -7442,7 +7764,7 @@ function extensions_default(pi) {
|
|
|
7442
7764
|
}
|
|
7443
7765
|
export {
|
|
7444
7766
|
consultAdvisor2 as consultAdvisor,
|
|
7445
|
-
|
|
7767
|
+
registerPiAdvisor as default,
|
|
7446
7768
|
parseAutomaticDecision2 as parseAutomaticDecision,
|
|
7447
7769
|
runAdvisorGate2 as runAdvisorGate
|
|
7448
7770
|
};
|