pi-advisor-flow 0.7.0 → 0.8.1-dev.1
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 +27 -18
- package/README.md +9 -8
- package/dist/index.js +1968 -1081
- package/extensions/index.ts +2 -1
- package/package.json +48 -35
- package/src/attachments.ts +11 -10
- package/src/child-session.ts +15 -0
- package/src/commands/activation-preparation.ts +1 -0
- package/src/commands/activation.ts +12 -3
- package/src/commands/lifecycle.ts +7 -1
- 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 +128 -77
- 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,55 @@ 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/child-session.ts
|
|
276
|
+
var isMarkedSubagent = () => process.env.PI_SUBAGENT_CHILD === "1";
|
|
277
|
+
var effectiveExecutorRef = (ctx) => {
|
|
278
|
+
if (isMarkedSubagent() && ctx.model) {
|
|
279
|
+
return `${ctx.model.provider}/${ctx.model.id}`;
|
|
280
|
+
}
|
|
281
|
+
return executorRef;
|
|
282
|
+
};
|
|
283
|
+
var effectiveExecutorEffort = (ctx) => isMarkedSubagent() ? ctx.thinkingLevel : executorEffortRef;
|
|
284
|
+
|
|
285
|
+
// src/content-utils.ts
|
|
286
|
+
var isRecord = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
287
|
+
var isRecordOf = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
288
|
+
var isString = (value) => typeof value === "string";
|
|
289
|
+
var isNumber = (value) => typeof value === "number";
|
|
290
|
+
var isBoolean = (value) => typeof value === "boolean";
|
|
291
|
+
var byteLength = (value) => Buffer.byteLength(value, "utf-8");
|
|
292
|
+
var contentParts = (content) => {
|
|
293
|
+
if (isString(content)) {
|
|
294
|
+
return [content];
|
|
295
|
+
}
|
|
296
|
+
return Array.isArray(content) ? content : [];
|
|
297
|
+
};
|
|
298
|
+
var capUtf8Bytes = (value, maxBytes) => {
|
|
299
|
+
let result = "";
|
|
300
|
+
let used = 0;
|
|
301
|
+
for (const character of value) {
|
|
302
|
+
const characterBytes = byteLength(character);
|
|
303
|
+
if (used + characterBytes > maxBytes) {
|
|
304
|
+
break;
|
|
305
|
+
}
|
|
306
|
+
result += character;
|
|
307
|
+
used += characterBytes;
|
|
308
|
+
}
|
|
309
|
+
return result;
|
|
310
|
+
};
|
|
311
|
+
|
|
264
312
|
// src/git.ts
|
|
265
313
|
import { execFileSync } from "node:child_process";
|
|
266
314
|
var GIT_CONTEXT_LEVELS = ["off", "summary", "full"];
|
|
267
|
-
var
|
|
315
|
+
var GIT_CONTEXT_LEVEL_SET = new Set(GIT_CONTEXT_LEVELS);
|
|
316
|
+
var isValidGitContextLevel = (value) => isString(value) && GIT_CONTEXT_LEVEL_SET.has(value);
|
|
268
317
|
var LEVEL_RANK = {
|
|
269
318
|
full: 2,
|
|
270
319
|
off: 0,
|
|
271
320
|
summary: 1
|
|
272
321
|
};
|
|
273
322
|
var clampGitContextLevel = (requested, allowed) => LEVEL_RANK[requested] <= LEVEL_RANK[allowed] ? requested : allowed;
|
|
274
|
-
var escapeRepositoryText = (value) => value.
|
|
323
|
+
var escapeRepositoryText = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">");
|
|
275
324
|
var TRUNCATION_NOTICE = `
|
|
276
325
|
[Repository context truncated: it exceeded the configured limit.]`;
|
|
277
326
|
var capRepositoryContext = (value, maxChars) => {
|
|
@@ -296,7 +345,7 @@ var deadlineRunner = () => {
|
|
|
296
345
|
}
|
|
297
346
|
return execFileSync("git", args, {
|
|
298
347
|
cwd,
|
|
299
|
-
encoding: "
|
|
348
|
+
encoding: "utf-8",
|
|
300
349
|
maxBuffer: GIT_MAX_BUFFER,
|
|
301
350
|
shell: false,
|
|
302
351
|
stdio: ["ignore", "pipe", "pipe"],
|
|
@@ -369,17 +418,21 @@ ${patch}` : "Patch: (no tracked-file content changes)");
|
|
|
369
418
|
|
|
370
419
|
// src/config/schema.ts
|
|
371
420
|
var configuredModelRef = (value) => value?.trim() || undefined;
|
|
421
|
+
var isNonEmptyModel = (model) => typeof model === "string" && model.trim().length > 0;
|
|
422
|
+
var isValidAdvisorModelWhitelist = (value) => Array.isArray(value) && value.every(isNonEmptyModel);
|
|
423
|
+
var ADVISOR_TOOL_POLICY_NAMES = new Set(ADVISOR_TOOL_POLICIES);
|
|
372
424
|
var isValidAdvisorToolPolicies = (value) => {
|
|
373
425
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
374
426
|
return false;
|
|
375
427
|
}
|
|
376
|
-
return Object.entries(value).every(([toolName, policy]) => toolName.trim().length > 0 &&
|
|
428
|
+
return Object.entries(value).every(([toolName, policy]) => toolName.trim().length > 0 && isString(policy) && ADVISOR_TOOL_POLICY_NAMES.has(policy));
|
|
377
429
|
};
|
|
378
430
|
var nonNegativeSafeInteger = (value) => typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
|
|
379
431
|
var isValidContextMaxChars = (value) => typeof value === "number" && Number.isSafeInteger(value) && value >= 0 && value <= MAX_CONTEXT_MAX_CHARS;
|
|
380
432
|
var isValidLoopThreshold = (value) => typeof value === "number" && Number.isSafeInteger(value) && value >= 2;
|
|
381
433
|
var isValidMaxCallsPerSession = (value) => nonNegativeSafeInteger(value);
|
|
382
|
-
var
|
|
434
|
+
var GATE_FAILURE_MODE_NAMES = new Set(GATE_FAILURE_MODES);
|
|
435
|
+
var isValidGateFailureMode = (value) => isString(value) && GATE_FAILURE_MODE_NAMES.has(value);
|
|
383
436
|
var isValidToolResultMaxLines = (value) => nonNegativeSafeInteger(value);
|
|
384
437
|
var isValidToolResultMaxBytes = (value) => nonNegativeSafeInteger(value);
|
|
385
438
|
var isValidJevTimeoutMs = (value) => typeof value === "number" && Number.isSafeInteger(value) && value >= 1;
|
|
@@ -387,7 +440,8 @@ var isValidJevDigestMaxChars = (value) => nonNegativeSafeInteger(value);
|
|
|
387
440
|
var isValidJevPricePerMtok = (value) => typeof value === "number" && Number.isFinite(value) && value > 0;
|
|
388
441
|
var isValidJevSkipConfidence = (value) => typeof value === "number" && Number.isFinite(value) && value >= 0.5 && value <= 1;
|
|
389
442
|
var isValidJevNoulMargin = (value) => typeof value === "number" && Number.isFinite(value) && value >= 0 && value <= 0.5;
|
|
390
|
-
var
|
|
443
|
+
var JEV_TRANSPORT_NAMES = new Set(JEV_TRANSPORTS);
|
|
444
|
+
var isValidJevTransport = (value) => isString(value) && JEV_TRANSPORT_NAMES.has(value);
|
|
391
445
|
var CONFIG_SCHEMA = {
|
|
392
446
|
advisor: {
|
|
393
447
|
accepted: "a provider/model string",
|
|
@@ -546,6 +600,13 @@ var CONFIG_SCHEMA = {
|
|
|
546
600
|
type: "number",
|
|
547
601
|
validate: isValidMaxCallsPerSession
|
|
548
602
|
},
|
|
603
|
+
advisorModelWhitelist: {
|
|
604
|
+
accepted: "an array of non-empty provider/model strings",
|
|
605
|
+
current: () => [...advisorModelWhitelistRef],
|
|
606
|
+
persisted: true,
|
|
607
|
+
type: "array",
|
|
608
|
+
validate: isValidAdvisorModelWhitelist
|
|
609
|
+
},
|
|
549
610
|
advisorOutcomeLogging: {
|
|
550
611
|
accepted: "true or false",
|
|
551
612
|
current: () => advisorOutcomeLoggingRef,
|
|
@@ -660,12 +721,13 @@ var CONFIG_SCHEMA = {
|
|
|
660
721
|
type: "boolean"
|
|
661
722
|
}
|
|
662
723
|
};
|
|
663
|
-
var
|
|
724
|
+
var configKeys = Object.keys(CONFIG_SCHEMA);
|
|
725
|
+
var SAVED_CONFIG_KEYS = configKeys.filter((key) => CONFIG_SCHEMA[key].persisted);
|
|
664
726
|
var SCHEMA_BY_KEY = CONFIG_SCHEMA;
|
|
665
727
|
|
|
666
728
|
// src/config/validation.ts
|
|
667
|
-
var CONFIG_KEYS = new Set(
|
|
668
|
-
var keysOfType = (type) =>
|
|
729
|
+
var CONFIG_KEYS = new Set(configKeys);
|
|
730
|
+
var keysOfType = (type) => configKeys.filter((key) => SCHEMA_BY_KEY[key].type === type);
|
|
669
731
|
var BOOLEAN_CONFIG_KEYS = keysOfType("boolean");
|
|
670
732
|
var STRING_CONFIG_KEYS = keysOfType("string");
|
|
671
733
|
var invalidConfigValue = (path, key, accepted) => {
|
|
@@ -674,14 +736,14 @@ var invalidConfigValue = (path, key, accepted) => {
|
|
|
674
736
|
var unknownConfigKeys = (config) => Object.keys(config).filter((key) => !CONFIG_KEYS.has(key));
|
|
675
737
|
var validateStringValues = (config, path) => {
|
|
676
738
|
for (const key of STRING_CONFIG_KEYS) {
|
|
677
|
-
if (config[key] !== undefined &&
|
|
739
|
+
if (config[key] !== undefined && !isString(config[key])) {
|
|
678
740
|
invalidConfigValue(path, key, SCHEMA_BY_KEY[key].accepted);
|
|
679
741
|
}
|
|
680
742
|
}
|
|
681
743
|
};
|
|
682
744
|
var validateBooleanValues = (config, path) => {
|
|
683
745
|
for (const key of BOOLEAN_CONFIG_KEYS) {
|
|
684
|
-
if (config[key] !== undefined &&
|
|
746
|
+
if (config[key] !== undefined && !isBoolean(config[key])) {
|
|
685
747
|
invalidConfigValue(path, key, SCHEMA_BY_KEY[key].accepted);
|
|
686
748
|
}
|
|
687
749
|
}
|
|
@@ -694,6 +756,14 @@ var validateNumericValues = (config, path) => {
|
|
|
694
756
|
}
|
|
695
757
|
}
|
|
696
758
|
};
|
|
759
|
+
var validateArrayValues = (config, path) => {
|
|
760
|
+
for (const key of keysOfType("array")) {
|
|
761
|
+
const isValid = SCHEMA_BY_KEY[key].validate;
|
|
762
|
+
if (config[key] !== undefined && isValid !== undefined && !isValid(config[key])) {
|
|
763
|
+
invalidConfigValue(path, key, SCHEMA_BY_KEY[key].accepted);
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
};
|
|
697
767
|
var validateEnumValues = (config, path) => {
|
|
698
768
|
for (const key of keysOfType("enum")) {
|
|
699
769
|
const isValid = SCHEMA_BY_KEY[key].validate;
|
|
@@ -711,20 +781,20 @@ var validateObjectValues = (config, path) => {
|
|
|
711
781
|
}
|
|
712
782
|
};
|
|
713
783
|
var validateConfig = (value, path = "advisor.json") => {
|
|
714
|
-
if (!
|
|
784
|
+
if (!isRecord(value)) {
|
|
715
785
|
throw new TypeError(`Invalid advisor configuration at ${path}: expected a JSON object.`);
|
|
716
786
|
}
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
validateEnumValues(
|
|
787
|
+
validateStringValues(value, path);
|
|
788
|
+
validateBooleanValues(value, path);
|
|
789
|
+
validateNumericValues(value, path);
|
|
790
|
+
validateObjectValues(value, path);
|
|
791
|
+
validateArrayValues(value, path);
|
|
792
|
+
validateEnumValues(value, path);
|
|
723
793
|
return true;
|
|
724
794
|
};
|
|
725
795
|
|
|
726
796
|
// src/config/args.ts
|
|
727
|
-
var ARGUMENT_WHITESPACE = /\s
|
|
797
|
+
var ARGUMENT_WHITESPACE = /\s+/u;
|
|
728
798
|
var parseArgs = (args) => {
|
|
729
799
|
let nextExecutor = executorRef;
|
|
730
800
|
let nextAdvisor = advisorRef;
|
|
@@ -753,10 +823,7 @@ var parseArgs = (args) => {
|
|
|
753
823
|
// src/config/storage.ts
|
|
754
824
|
import { existsSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
755
825
|
import { join } from "node:path";
|
|
756
|
-
import {
|
|
757
|
-
CONFIG_DIR_NAME,
|
|
758
|
-
getAgentDir
|
|
759
|
-
} from "@earendil-works/pi-coding-agent";
|
|
826
|
+
import { CONFIG_DIR_NAME, getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
760
827
|
|
|
761
828
|
// src/config/defaults.ts
|
|
762
829
|
var resetDefaults = () => {
|
|
@@ -775,6 +842,7 @@ var resetDefaults = () => {
|
|
|
775
842
|
setAdvisorAutoLoopGateRef(true);
|
|
776
843
|
setAdvisorLoopThresholdRef(3);
|
|
777
844
|
setAdvisorMaxCallsPerSessionRef(undefined);
|
|
845
|
+
setAdvisorModelWhitelistRef([]);
|
|
778
846
|
setAdvisorSessionSummaryRef(false);
|
|
779
847
|
setSimpleModeRef(false);
|
|
780
848
|
setAlwaysOnRef(false);
|
|
@@ -830,6 +898,7 @@ var applyConfig = (config) => {
|
|
|
830
898
|
applyOptionalConfig(config, "advisorAutoLoopGate", setAdvisorAutoLoopGateRef);
|
|
831
899
|
applyOptionalConfig(config, "advisorLoopThreshold", setAdvisorLoopThresholdRef);
|
|
832
900
|
applyOptionalConfig(config, "advisorMaxCallsPerSession", setAdvisorMaxCallsPerSessionRef);
|
|
901
|
+
applyOptionalConfig(config, "advisorModelWhitelist", setAdvisorModelWhitelistRef);
|
|
833
902
|
applyOptionalConfig(config, "advisorSessionSummary", setAdvisorSessionSummaryRef);
|
|
834
903
|
applyOptionalConfig(config, "advisorScoutEnabled", setAdvisorScoutEnabledRef);
|
|
835
904
|
applyOptionalConfig(config, "showUsageDetails", setShowUsageDetailsRef);
|
|
@@ -866,14 +935,16 @@ var sameConfigValue = (left, right) => JSON.stringify(left) === JSON.stringify(r
|
|
|
866
935
|
var RESERVED_ADVISOR_JSON_KEYS = new Set(["typesafe_api_key"]);
|
|
867
936
|
var readExistingConfig = (path) => {
|
|
868
937
|
try {
|
|
869
|
-
const parsed = JSON.parse(readFileSync(path, "
|
|
870
|
-
return
|
|
938
|
+
const parsed = JSON.parse(readFileSync(path, "utf-8"));
|
|
939
|
+
return isRecord(parsed) ? parsed : {};
|
|
871
940
|
} catch {
|
|
872
941
|
return {};
|
|
873
942
|
}
|
|
874
943
|
};
|
|
875
944
|
var shouldPersistConfigKey = (key, persistAdvisor, persistExecutor) => (key !== "advisor" || persistAdvisor) && (key !== "executor" || persistExecutor);
|
|
876
|
-
var applyChangedConfigValues = (
|
|
945
|
+
var applyChangedConfigValues = (existing, current, changedKeys, persistAdvisor, persistExecutor) => {
|
|
946
|
+
const dropped = new Set;
|
|
947
|
+
const data = { ...existing };
|
|
877
948
|
for (const key of changedKeys) {
|
|
878
949
|
if (!shouldPersistConfigKey(key, persistAdvisor, persistExecutor)) {
|
|
879
950
|
continue;
|
|
@@ -881,16 +952,26 @@ var applyChangedConfigValues = (data, current, changedKeys, persistAdvisor, pers
|
|
|
881
952
|
const value = current[key];
|
|
882
953
|
const isEmptyModelRef = (key === "advisor" || key === "executor") && !value;
|
|
883
954
|
if (value === undefined || isEmptyModelRef) {
|
|
884
|
-
|
|
955
|
+
dropped.add(key);
|
|
885
956
|
} else {
|
|
886
957
|
data[key] = value;
|
|
887
958
|
}
|
|
888
959
|
}
|
|
960
|
+
if (dropped.size === 0) {
|
|
961
|
+
return data;
|
|
962
|
+
}
|
|
963
|
+
const retained = {};
|
|
964
|
+
for (const key of Object.keys(data)) {
|
|
965
|
+
if (!dropped.has(key)) {
|
|
966
|
+
retained[key] = data[key];
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
return retained;
|
|
889
970
|
};
|
|
890
971
|
var loadedConfigState;
|
|
891
972
|
var loadedConfigPath;
|
|
892
973
|
var readConfig = (path) => {
|
|
893
|
-
const config = JSON.parse(readFileSync(path, "
|
|
974
|
+
const config = JSON.parse(readFileSync(path, "utf-8"));
|
|
894
975
|
validateConfig(config, path);
|
|
895
976
|
return config;
|
|
896
977
|
};
|
|
@@ -948,17 +1029,16 @@ var saveConfig = (_ctx, options = {}) => {
|
|
|
948
1029
|
if (changedKeys.length === 0) {
|
|
949
1030
|
return path;
|
|
950
1031
|
}
|
|
951
|
-
const data =
|
|
952
|
-
applyChangedConfigValues(data, current, changedKeys, persistAdvisor, persistExecutor);
|
|
1032
|
+
const data = applyChangedConfigValues(existing, current, changedKeys, persistAdvisor, persistExecutor);
|
|
953
1033
|
writeFileSync(path, `${JSON.stringify(data, null, 2)}
|
|
954
1034
|
`);
|
|
955
1035
|
resetConfigCache();
|
|
956
1036
|
const nextLoadedState = { ...current };
|
|
957
1037
|
if (!persistAdvisor) {
|
|
958
|
-
nextLoadedState.advisor = baseline ? baseline.advisor : configuredModelRef(
|
|
1038
|
+
nextLoadedState.advisor = baseline ? baseline.advisor : configuredModelRef(isString(existing.advisor) ? existing.advisor : undefined);
|
|
959
1039
|
}
|
|
960
1040
|
if (!persistExecutor) {
|
|
961
|
-
nextLoadedState.executor = baseline ? baseline.executor : configuredModelRef(
|
|
1041
|
+
nextLoadedState.executor = baseline ? baseline.executor : configuredModelRef(isString(existing.executor) ? existing.executor : undefined);
|
|
962
1042
|
}
|
|
963
1043
|
loadedConfigState = nextLoadedState;
|
|
964
1044
|
loadedConfigPath = path;
|
|
@@ -998,7 +1078,7 @@ var selectedEffort = (choice) => {
|
|
|
998
1078
|
return effort === DEFAULT_EFFORT_LEVEL ? undefined : effort;
|
|
999
1079
|
};
|
|
1000
1080
|
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
|
|
1081
|
+
var ARGUMENT_WHITESPACE2 = /\s+/u;
|
|
1002
1082
|
var hasModelOverride = (args, key) => args.trim().split(ARGUMENT_WHITESPACE2).some((token) => {
|
|
1003
1083
|
const [tokenKey, value] = token.split("=");
|
|
1004
1084
|
return tokenKey === key && Boolean(value);
|
|
@@ -1045,11 +1125,18 @@ var findConfiguredModel = (ctx, ref) => {
|
|
|
1045
1125
|
return ctx.modelRegistry.find(provider, modelId);
|
|
1046
1126
|
};
|
|
1047
1127
|
var getAvailableModelRefs = (ctx) => {
|
|
1048
|
-
if (
|
|
1128
|
+
if (!ctx.modelRegistry.getAvailable) {
|
|
1049
1129
|
return;
|
|
1050
1130
|
}
|
|
1051
1131
|
return ctx.modelRegistry.getAvailable().map((model) => `${model.provider}/${model.id}`);
|
|
1052
1132
|
};
|
|
1133
|
+
var getConfiguredModelRefs = (ctx) => {
|
|
1134
|
+
const registry = ctx.modelRegistry;
|
|
1135
|
+
const models = registry?.getAvailable ? registry.getAvailable() : [];
|
|
1136
|
+
return [
|
|
1137
|
+
...new Set(models.map((model) => `${model.provider}/${model.id}`))
|
|
1138
|
+
].toSorted((left, right) => left.localeCompare(right));
|
|
1139
|
+
};
|
|
1053
1140
|
var isSelectableModel = (ctx, ref, availableRefs) => {
|
|
1054
1141
|
if (!ref) {
|
|
1055
1142
|
return false;
|
|
@@ -1084,18 +1171,39 @@ var planActivationModels = (ctx, executor, advisor, pendingExecutorRef, persiste
|
|
|
1084
1171
|
};
|
|
1085
1172
|
};
|
|
1086
1173
|
|
|
1087
|
-
// src/ui/model-selector.ts
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
}
|
|
1174
|
+
// src/ui/model-selector-adapter.ts
|
|
1175
|
+
class ModelSelectorAdapter {
|
|
1176
|
+
list;
|
|
1177
|
+
constructor(list) {
|
|
1178
|
+
this.list = list;
|
|
1179
|
+
}
|
|
1180
|
+
get focused() {
|
|
1181
|
+
return this.list.focused;
|
|
1182
|
+
}
|
|
1183
|
+
set focused(value) {
|
|
1184
|
+
this.list.focused = value;
|
|
1185
|
+
}
|
|
1186
|
+
invalidate() {
|
|
1187
|
+
this.list.invalidate();
|
|
1188
|
+
}
|
|
1189
|
+
render(width) {
|
|
1190
|
+
return this.list.render(width);
|
|
1191
|
+
}
|
|
1192
|
+
handleInput(keyData) {
|
|
1193
|
+
this.list.handleInput(keyData);
|
|
1194
|
+
}
|
|
1195
|
+
}
|
|
1093
1196
|
|
|
1094
|
-
|
|
1197
|
+
// src/ui/searchable-model-list.ts
|
|
1198
|
+
import { fuzzyFilter, Input, truncateToWidth } from "@earendil-works/pi-tui";
|
|
1199
|
+
|
|
1200
|
+
class SearchableModelList {
|
|
1095
1201
|
tui;
|
|
1096
1202
|
searchInput;
|
|
1097
1203
|
allOptions;
|
|
1098
1204
|
currentOption;
|
|
1205
|
+
multiSelect;
|
|
1206
|
+
selected = new Set;
|
|
1099
1207
|
filteredOptions;
|
|
1100
1208
|
selectedIndex = 0;
|
|
1101
1209
|
title;
|
|
@@ -1112,13 +1220,30 @@ class SearchableModelSelector {
|
|
|
1112
1220
|
this.searchInput.focused = val;
|
|
1113
1221
|
}
|
|
1114
1222
|
constructor(options) {
|
|
1223
|
+
this.multiSelect = options.multiSelect;
|
|
1115
1224
|
this.tui = options.tui;
|
|
1116
1225
|
this.title = options.title;
|
|
1117
|
-
|
|
1118
|
-
this.
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1226
|
+
const [requestedCurrentOption] = options.currentOptions;
|
|
1227
|
+
this.currentOption = !this.multiSelect && requestedCurrentOption && options.allOptions.includes(requestedCurrentOption) ? requestedCurrentOption : undefined;
|
|
1228
|
+
let allOptions;
|
|
1229
|
+
if (this.multiSelect) {
|
|
1230
|
+
allOptions = [...new Set(options.allOptions)].toSorted((left, right) => left.localeCompare(right));
|
|
1231
|
+
} else if (this.currentOption) {
|
|
1232
|
+
allOptions = [
|
|
1233
|
+
this.currentOption,
|
|
1234
|
+
...options.allOptions.filter((item) => item !== this.currentOption)
|
|
1235
|
+
];
|
|
1236
|
+
} else {
|
|
1237
|
+
allOptions = [...options.allOptions];
|
|
1238
|
+
}
|
|
1239
|
+
this.allOptions = allOptions;
|
|
1240
|
+
if (this.multiSelect) {
|
|
1241
|
+
for (const value of options.currentOptions) {
|
|
1242
|
+
if (this.allOptions.includes(value)) {
|
|
1243
|
+
this.selected.add(value);
|
|
1244
|
+
}
|
|
1245
|
+
}
|
|
1246
|
+
}
|
|
1122
1247
|
this.theme = options.theme;
|
|
1123
1248
|
this.keybindings = options.keybindings;
|
|
1124
1249
|
this.onSelect = options.onSelect;
|
|
@@ -1133,8 +1258,7 @@ class SearchableModelSelector {
|
|
|
1133
1258
|
const lines = [this.theme.fg("border", "─".repeat(width))];
|
|
1134
1259
|
lines.push(` ${this.theme.fg("accent", this.theme.bold(this.title))}`);
|
|
1135
1260
|
const inputLines = this.searchInput.render(Math.max(1, width - 10));
|
|
1136
|
-
lines.push(` ${this.theme.fg("accent", "Search: ")}${inputLines[0] || ""}
|
|
1137
|
-
lines.push("");
|
|
1261
|
+
lines.push(` ${this.theme.fg("accent", "Search: ")}${inputLines[0] || ""}`, "");
|
|
1138
1262
|
const query = this.searchInput.getValue().trim();
|
|
1139
1263
|
this.filteredOptions = query ? fuzzyFilter(this.allOptions, query, (item) => item) : this.allOptions;
|
|
1140
1264
|
this.selectedIndex = Math.min(this.selectedIndex, Math.max(0, this.filteredOptions.length - 1));
|
|
@@ -1146,21 +1270,13 @@ class SearchableModelSelector {
|
|
|
1146
1270
|
const startIndex = Math.max(0, Math.min(this.selectedIndex - Math.floor(maxVisible / 2), total - maxVisible));
|
|
1147
1271
|
const endIndex = Math.min(startIndex + maxVisible, total);
|
|
1148
1272
|
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
|
-
}
|
|
1273
|
+
lines.push(this.renderOption(this.filteredOptions[i], i));
|
|
1156
1274
|
}
|
|
1157
1275
|
if (total > maxVisible) {
|
|
1158
|
-
lines.push(
|
|
1276
|
+
lines.push(` ${this.theme.fg("muted", ` (${this.selectedIndex + 1}/${total})`)}`);
|
|
1159
1277
|
}
|
|
1160
1278
|
}
|
|
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)));
|
|
1279
|
+
lines.push("", ` ${this.theme.fg("dim", this.interactionHint())}`, this.theme.fg("border", "─".repeat(width)));
|
|
1164
1280
|
return lines.map((line) => truncateToWidth(line, width));
|
|
1165
1281
|
}
|
|
1166
1282
|
handleInput(keyData) {
|
|
@@ -1172,10 +1288,27 @@ class SearchableModelSelector {
|
|
|
1172
1288
|
this.moveSelection(1);
|
|
1173
1289
|
return;
|
|
1174
1290
|
}
|
|
1291
|
+
if (this.multiSelect && keyData === " ") {
|
|
1292
|
+
const item = this.filteredOptions[this.selectedIndex];
|
|
1293
|
+
if (item) {
|
|
1294
|
+
if (this.selected.has(item)) {
|
|
1295
|
+
this.selected.delete(item);
|
|
1296
|
+
} else {
|
|
1297
|
+
this.selected.add(item);
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
this.tui.requestRender();
|
|
1301
|
+
return;
|
|
1302
|
+
}
|
|
1175
1303
|
if (this.matchesAction(keyData, "tui.select.confirm", `
|
|
1176
1304
|
`) || keyData === "\r") {
|
|
1177
|
-
if (this.
|
|
1178
|
-
this.onSelect(this.
|
|
1305
|
+
if (this.multiSelect) {
|
|
1306
|
+
this.onSelect(this.allOptions.filter((item) => this.selected.has(item)));
|
|
1307
|
+
} else {
|
|
1308
|
+
const selectedOption = this.filteredOptions[this.selectedIndex];
|
|
1309
|
+
if (selectedOption !== undefined) {
|
|
1310
|
+
this.onSelect([selectedOption]);
|
|
1311
|
+
}
|
|
1179
1312
|
}
|
|
1180
1313
|
return;
|
|
1181
1314
|
}
|
|
@@ -1187,6 +1320,19 @@ class SearchableModelSelector {
|
|
|
1187
1320
|
this.selectedIndex = 0;
|
|
1188
1321
|
this.tui.requestRender();
|
|
1189
1322
|
}
|
|
1323
|
+
interactionHint() {
|
|
1324
|
+
return this.multiSelect ? "Type to search · ↑↓: navigate · Space: toggle · Enter: apply · Esc: cancel" : "Type to search · ↑↓: navigate · Enter: select · Esc: cancel";
|
|
1325
|
+
}
|
|
1326
|
+
renderOption(item, index) {
|
|
1327
|
+
let tick = " ";
|
|
1328
|
+
if (this.multiSelect ? this.selected.has(item) : item === this.currentOption) {
|
|
1329
|
+
tick = "✓ ";
|
|
1330
|
+
}
|
|
1331
|
+
if (index === this.selectedIndex) {
|
|
1332
|
+
return ` ${this.theme.fg("accent", `→ ${tick}${item}`)}`;
|
|
1333
|
+
}
|
|
1334
|
+
return ` ${this.theme.fg("text", `${tick}${item}`)}`;
|
|
1335
|
+
}
|
|
1190
1336
|
matchesAction(keyData, action, fallback) {
|
|
1191
1337
|
return this.keybindings.matches(keyData, action) || keyData === fallback;
|
|
1192
1338
|
}
|
|
@@ -1206,6 +1352,22 @@ class SearchableModelSelector {
|
|
|
1206
1352
|
}
|
|
1207
1353
|
}
|
|
1208
1354
|
|
|
1355
|
+
// src/ui/model-selector.ts
|
|
1356
|
+
class SearchableModelSelector extends ModelSelectorAdapter {
|
|
1357
|
+
constructor(options) {
|
|
1358
|
+
super(new SearchableModelList({
|
|
1359
|
+
...options,
|
|
1360
|
+
currentOptions: options.currentOption ? [options.currentOption] : [],
|
|
1361
|
+
multiSelect: false,
|
|
1362
|
+
onSelect: ([value]) => {
|
|
1363
|
+
if (value !== undefined) {
|
|
1364
|
+
options.onSelect(value);
|
|
1365
|
+
}
|
|
1366
|
+
}
|
|
1367
|
+
}));
|
|
1368
|
+
}
|
|
1369
|
+
}
|
|
1370
|
+
|
|
1209
1371
|
// src/commands/model-picker.ts
|
|
1210
1372
|
var selectAdvisorModels = async (ctx, options) => {
|
|
1211
1373
|
if (!ctx.hasUI) {
|
|
@@ -1268,43 +1430,20 @@ var selectAdvisorModels = async (ctx, options) => {
|
|
|
1268
1430
|
return { advisor, advisorEffort, executor, executorEffort };
|
|
1269
1431
|
};
|
|
1270
1432
|
|
|
1271
|
-
// src/herdr.ts
|
|
1433
|
+
// src/herdr-shared.ts
|
|
1272
1434
|
import net from "node:net";
|
|
1273
1435
|
|
|
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
1436
|
// src/redaction.ts
|
|
1298
1437
|
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-----/
|
|
1438
|
+
var PEM_BEGIN_PATTERN = /-----BEGIN(?: [A-Z0-9]+)? PRIVATE KEY-----/giu;
|
|
1439
|
+
var PEM_END_PATTERN = /-----END(?: [A-Z0-9]+)? PRIVATE KEY-----/iu;
|
|
1301
1440
|
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"'&,;)}\]]+/
|
|
1441
|
+
/-----BEGIN(?: [A-Z0-9]+)? PRIVATE KEY-----[\s\S]*?-----END(?: [A-Z0-9]+)? PRIVATE KEY-----/giu,
|
|
1442
|
+
/\bBearer\s+[A-Za-z0-9._~+/=-]{8,}/giu,
|
|
1443
|
+
/\b(?:api[_-]?key|token|secret|password|passwd)\s*[:=]\s*(?:"[^"]*"|'[^']*'|[^\s"'&,;)}\]]+)/giu,
|
|
1444
|
+
/(?<scheme>[a-z][a-z0-9+.-]*:\/\/)[^\s/@:]+:[^\s/@]+@/giu,
|
|
1445
|
+
/\b(?:AKIA|ASIA)[A-Z0-9]{16}\b/gu,
|
|
1446
|
+
/\b(?:aws_secret_access_key|aws_session_token)\s*[:=]\s*[^\s"'&,;)}\]]+/giu
|
|
1308
1447
|
];
|
|
1309
1448
|
var redactUnterminatedPem = (value) => {
|
|
1310
1449
|
const begins = [...value.matchAll(PEM_BEGIN_PATTERN)];
|
|
@@ -1318,7 +1457,7 @@ var redactUnterminatedPem = (value) => {
|
|
|
1318
1457
|
var redactSecrets = (value) => {
|
|
1319
1458
|
let redacted = redactUnterminatedPem(value);
|
|
1320
1459
|
for (const pattern of SECRET_PATTERNS) {
|
|
1321
|
-
redacted = redacted.replace(pattern, (_match, scheme) =>
|
|
1460
|
+
redacted = redacted.replace(pattern, (_match, scheme) => isString(scheme) ? `${scheme}${REDACTION_MARKER}@` : REDACTION_MARKER);
|
|
1322
1461
|
}
|
|
1323
1462
|
return redacted;
|
|
1324
1463
|
};
|
|
@@ -1327,11 +1466,11 @@ var redactAndCapText = (value, maxBytes, redact = true) => {
|
|
|
1327
1466
|
return capUtf8Bytes(source, maxBytes);
|
|
1328
1467
|
};
|
|
1329
1468
|
|
|
1330
|
-
// src/herdr.ts
|
|
1469
|
+
// src/herdr-shared.ts
|
|
1331
1470
|
var HERDR_NOTIFICATION_METHOD = "notification.show";
|
|
1332
1471
|
var SOURCE = "pi-advisor:advisor-activity";
|
|
1333
|
-
var BLOCK_SOURCE = "pi-advisor:advisor-block";
|
|
1334
1472
|
var NOTIFICATION_SOURCE = "pi-advisor:advisor-notification";
|
|
1473
|
+
var BLOCK_SOURCE = "pi-advisor:advisor-block";
|
|
1335
1474
|
var HERDR_PI_SOURCE = "herdr:pi";
|
|
1336
1475
|
var sequence = Date.now() * 1000;
|
|
1337
1476
|
var nextSequence = () => {
|
|
@@ -1348,17 +1487,7 @@ var safeEmitBlocked = (active, label = "Advisor blocked") => {
|
|
|
1348
1487
|
} catch {}
|
|
1349
1488
|
};
|
|
1350
1489
|
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
|
-
});
|
|
1490
|
+
var cleanNotification = (value, max) => [...redactSecrets(value)].map((character) => isControlCharacter(character) ? " " : character).join("").replaceAll(/\s+/gu, " ").trim().slice(0, max);
|
|
1362
1491
|
var sendToHerdr = (request) => {
|
|
1363
1492
|
if (process.env.HERDR_ENV !== "1") {
|
|
1364
1493
|
return;
|
|
@@ -1378,61 +1507,18 @@ var sendToHerdr = (request) => {
|
|
|
1378
1507
|
socket.once("error", () => socket.destroy());
|
|
1379
1508
|
socket.once("close", () => clearTimeout(timeout));
|
|
1380
1509
|
};
|
|
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
|
-
};
|
|
1510
|
+
var createHerdrNotificationRequest = (title, body) => ({
|
|
1511
|
+
id: `${NOTIFICATION_SOURCE}:${nextSequence()}`,
|
|
1512
|
+
method: HERDR_NOTIFICATION_METHOD,
|
|
1513
|
+
params: {
|
|
1514
|
+
body: cleanNotification(body, 240),
|
|
1515
|
+
position: "top-left",
|
|
1516
|
+
sound: "request",
|
|
1517
|
+
title: cleanNotification(title, 80)
|
|
1433
1518
|
}
|
|
1434
|
-
}
|
|
1519
|
+
});
|
|
1435
1520
|
|
|
1521
|
+
// src/herdr-block.ts
|
|
1436
1522
|
class HerdrAdvisorBlock {
|
|
1437
1523
|
#blocked = false;
|
|
1438
1524
|
report;
|
|
@@ -1492,6 +1578,60 @@ class HerdrAdvisorBlock {
|
|
|
1492
1578
|
} catch {}
|
|
1493
1579
|
}
|
|
1494
1580
|
}
|
|
1581
|
+
|
|
1582
|
+
// src/herdr.ts
|
|
1583
|
+
var metadataRequest = (clear) => ({
|
|
1584
|
+
id: `${SOURCE}:${nextSequence()}`,
|
|
1585
|
+
method: "pane.report_metadata",
|
|
1586
|
+
params: {
|
|
1587
|
+
agent: "pi",
|
|
1588
|
+
applies_to_source: HERDR_PI_SOURCE,
|
|
1589
|
+
pane_id: process.env.HERDR_PANE_ID ?? "",
|
|
1590
|
+
source: SOURCE,
|
|
1591
|
+
...clear ? { clear_state_labels: true } : { state_labels: { working: "seeking advice" } },
|
|
1592
|
+
seq: nextSequence()
|
|
1593
|
+
}
|
|
1594
|
+
});
|
|
1595
|
+
|
|
1596
|
+
class HerdrAdvisorActivity {
|
|
1597
|
+
#activeConsultations = 0;
|
|
1598
|
+
report;
|
|
1599
|
+
enabled;
|
|
1600
|
+
constructor(report = sendToHerdr, enabled = () => true) {
|
|
1601
|
+
this.report = report;
|
|
1602
|
+
this.enabled = enabled;
|
|
1603
|
+
}
|
|
1604
|
+
start() {
|
|
1605
|
+
if (!this.enabled()) {
|
|
1606
|
+
return;
|
|
1607
|
+
}
|
|
1608
|
+
this.#activeConsultations += 1;
|
|
1609
|
+
if (this.#activeConsultations === 1) {
|
|
1610
|
+
this.safeReport(false);
|
|
1611
|
+
}
|
|
1612
|
+
}
|
|
1613
|
+
finish() {
|
|
1614
|
+
if (this.#activeConsultations === 0) {
|
|
1615
|
+
return;
|
|
1616
|
+
}
|
|
1617
|
+
this.#activeConsultations -= 1;
|
|
1618
|
+
if (this.#activeConsultations === 0) {
|
|
1619
|
+
this.safeReport(true);
|
|
1620
|
+
}
|
|
1621
|
+
}
|
|
1622
|
+
clear() {
|
|
1623
|
+
if (this.#activeConsultations === 0) {
|
|
1624
|
+
return;
|
|
1625
|
+
}
|
|
1626
|
+
this.#activeConsultations = 0;
|
|
1627
|
+
this.safeReport(true);
|
|
1628
|
+
}
|
|
1629
|
+
safeReport(clear) {
|
|
1630
|
+
try {
|
|
1631
|
+
this.report(metadataRequest(clear));
|
|
1632
|
+
} catch {}
|
|
1633
|
+
}
|
|
1634
|
+
}
|
|
1495
1635
|
var notifyHerdrAdvisorFailure = (title, body) => {
|
|
1496
1636
|
if (!getAdvisorSettings().herdrIntegration) {
|
|
1497
1637
|
return;
|
|
@@ -1507,15 +1647,14 @@ var herdrAdvisorBlock = new HerdrAdvisorBlock(sendToHerdr, () => getAdvisorSetti
|
|
|
1507
1647
|
import { randomUUID } from "node:crypto";
|
|
1508
1648
|
|
|
1509
1649
|
// src/model-stream.ts
|
|
1510
|
-
import {
|
|
1511
|
-
stream
|
|
1512
|
-
} from "@earendil-works/pi-ai/compat";
|
|
1650
|
+
import { stream } from "@earendil-works/pi-ai/compat";
|
|
1513
1651
|
var resolveConfiguredModel = async (ctx, ref, label) => {
|
|
1514
1652
|
if (!ref) {
|
|
1515
1653
|
throw new Error(`${label} model not configured`);
|
|
1516
1654
|
}
|
|
1517
1655
|
const [provider, modelId] = splitRef(ref);
|
|
1518
|
-
const
|
|
1656
|
+
const lookup = [provider, modelId];
|
|
1657
|
+
const model = ctx.modelRegistry.find(...lookup);
|
|
1519
1658
|
if (!model) {
|
|
1520
1659
|
throw new Error(`${label} model not found: ${ref}`);
|
|
1521
1660
|
}
|
|
@@ -1535,16 +1674,16 @@ var resolveConfiguredModel = async (ctx, ref, label) => {
|
|
|
1535
1674
|
};
|
|
1536
1675
|
};
|
|
1537
1676
|
var ADVISOR_STREAM_UPDATE_INTERVAL_MS = 90;
|
|
1538
|
-
var
|
|
1677
|
+
var defaultScheduler = {
|
|
1539
1678
|
clearTimeout,
|
|
1540
1679
|
now: Date.now,
|
|
1541
1680
|
setTimeout: (callback, delay) => setTimeout(callback, delay)
|
|
1542
|
-
}
|
|
1681
|
+
};
|
|
1682
|
+
var createCoalescedUpdate = (publish, intervalMs = ADVISOR_STREAM_UPDATE_INTERVAL_MS, scheduler = defaultScheduler) => {
|
|
1543
1683
|
if (!Number.isFinite(intervalMs) || intervalMs <= 0) {
|
|
1544
1684
|
throw new Error("Coalesced update interval must be positive and finite.");
|
|
1545
1685
|
}
|
|
1546
1686
|
let closed = false;
|
|
1547
|
-
let hasPending = false;
|
|
1548
1687
|
let pending;
|
|
1549
1688
|
let lastPublishedAt;
|
|
1550
1689
|
let timer;
|
|
@@ -1558,12 +1697,11 @@ var createCoalescedUpdate = (publish, intervalMs = ADVISOR_STREAM_UPDATE_INTERVA
|
|
|
1558
1697
|
};
|
|
1559
1698
|
const publishPending = () => {
|
|
1560
1699
|
timer = undefined;
|
|
1561
|
-
if (!
|
|
1700
|
+
if (!pending) {
|
|
1562
1701
|
return;
|
|
1563
1702
|
}
|
|
1564
|
-
const value = pending;
|
|
1703
|
+
const { value } = pending;
|
|
1565
1704
|
pending = undefined;
|
|
1566
|
-
hasPending = false;
|
|
1567
1705
|
lastPublishedAt = scheduler.now();
|
|
1568
1706
|
try {
|
|
1569
1707
|
publish(value);
|
|
@@ -1589,7 +1727,6 @@ var createCoalescedUpdate = (publish, intervalMs = ADVISOR_STREAM_UPDATE_INTERVA
|
|
|
1589
1727
|
closed = true;
|
|
1590
1728
|
clearTimer();
|
|
1591
1729
|
pending = undefined;
|
|
1592
|
-
hasPending = false;
|
|
1593
1730
|
},
|
|
1594
1731
|
flush: () => {
|
|
1595
1732
|
if (!closed) {
|
|
@@ -1606,8 +1743,7 @@ var createCoalescedUpdate = (publish, intervalMs = ADVISOR_STREAM_UPDATE_INTERVA
|
|
|
1606
1743
|
if (publishFailed) {
|
|
1607
1744
|
throw publishError;
|
|
1608
1745
|
}
|
|
1609
|
-
pending = value;
|
|
1610
|
-
hasPending = true;
|
|
1746
|
+
pending = { value };
|
|
1611
1747
|
if (timer === undefined) {
|
|
1612
1748
|
schedule();
|
|
1613
1749
|
}
|
|
@@ -1620,14 +1756,17 @@ var createCoalescedUpdate = (publish, intervalMs = ADVISOR_STREAM_UPDATE_INTERVA
|
|
|
1620
1756
|
var collectTextStream = async (resolved, options, streamModel = stream) => {
|
|
1621
1757
|
let thinking = "";
|
|
1622
1758
|
let text = "";
|
|
1623
|
-
const
|
|
1759
|
+
const streamOptions = {
|
|
1624
1760
|
apiKey: resolved.apiKey,
|
|
1625
1761
|
env: resolved.env,
|
|
1626
1762
|
headers: resolved.headers,
|
|
1627
1763
|
reasoning: options.reasoning,
|
|
1628
|
-
...options.reasoning === undefined ? {} : { reasoningEffort: options.reasoning },
|
|
1629
1764
|
signal: options.signal
|
|
1630
|
-
}
|
|
1765
|
+
};
|
|
1766
|
+
if (options.reasoning !== undefined) {
|
|
1767
|
+
streamOptions.reasoningEffort = options.reasoning;
|
|
1768
|
+
}
|
|
1769
|
+
const eventStream = streamModel(resolved.model, { messages: options.messages, systemPrompt: options.systemPrompt }, streamOptions);
|
|
1631
1770
|
for await (const event of eventStream) {
|
|
1632
1771
|
if (event.type === "thinking_delta") {
|
|
1633
1772
|
thinking += event.delta;
|
|
@@ -1661,21 +1800,21 @@ import { lstat, open, realpath } from "node:fs/promises";
|
|
|
1661
1800
|
import { isAbsolute, relative, resolve } from "node:path";
|
|
1662
1801
|
var ADVISOR_FILE_MAX_BYTES = 8 * 1024;
|
|
1663
1802
|
var ADVISOR_FILES_TOTAL_MAX_BYTES = 24 * 1024;
|
|
1664
|
-
var PATH_SEGMENTS = /[\\/]
|
|
1803
|
+
var PATH_SEGMENTS = /[\\/]/u;
|
|
1665
1804
|
var within = (root, candidate) => {
|
|
1666
1805
|
const path = relative(root, candidate);
|
|
1667
1806
|
return path !== "" && !path.startsWith("..") && !path.includes("../");
|
|
1668
1807
|
};
|
|
1669
1808
|
var normalizeRelativePath = (root, path) => relative(root, resolve(root, path));
|
|
1670
1809
|
var normalizeRequestedPath = (root, value) => {
|
|
1671
|
-
if (
|
|
1810
|
+
if (!isString(value) || !value || isAbsolute(value) || value.split(PATH_SEGMENTS).includes("..")) {
|
|
1672
1811
|
return;
|
|
1673
1812
|
}
|
|
1674
1813
|
return normalizeRelativePath(root, value);
|
|
1675
1814
|
};
|
|
1676
1815
|
var git = (cwd, args) => execFileSync2("git", args, {
|
|
1677
1816
|
cwd,
|
|
1678
|
-
encoding: "
|
|
1817
|
+
encoding: "utf-8",
|
|
1679
1818
|
maxBuffer: 16 * 1024 * 1024,
|
|
1680
1819
|
shell: false,
|
|
1681
1820
|
stdio: ["ignore", "pipe", "pipe"],
|
|
@@ -1686,7 +1825,7 @@ var repositoryRoot = (cwd) => {
|
|
|
1686
1825
|
try {
|
|
1687
1826
|
return realpath(git(cwd, ["rev-parse", "--show-toplevel"]).trim());
|
|
1688
1827
|
} catch {
|
|
1689
|
-
return Promise.resolve(
|
|
1828
|
+
return Promise.resolve();
|
|
1690
1829
|
}
|
|
1691
1830
|
};
|
|
1692
1831
|
var untracked = (cwd, path) => {
|
|
@@ -1726,7 +1865,7 @@ var readAttachment = async (root, normalizedName, redact, available) => {
|
|
|
1726
1865
|
if (!within(root, resolved)) {
|
|
1727
1866
|
return;
|
|
1728
1867
|
}
|
|
1729
|
-
const flags = constants.O_NOFOLLOW ? constants.O_RDONLY
|
|
1868
|
+
const flags = constants.O_NOFOLLOW ? constants.O_RDONLY + constants.O_NOFOLLOW : constants.O_RDONLY;
|
|
1730
1869
|
const file = await open(resolved, flags);
|
|
1731
1870
|
try {
|
|
1732
1871
|
const openedStats = await file.stat();
|
|
@@ -1735,13 +1874,13 @@ var readAttachment = async (root, normalizedName, redact, available) => {
|
|
|
1735
1874
|
}
|
|
1736
1875
|
const buffer = Buffer.alloc(available + 1);
|
|
1737
1876
|
const { bytesRead } = await file.read(buffer, 0, buffer.length, 0);
|
|
1738
|
-
const raw = buffer.subarray(0, bytesRead).toString("
|
|
1877
|
+
const raw = buffer.subarray(0, bytesRead).toString("utf-8");
|
|
1739
1878
|
if (raw.includes("\x00")) {
|
|
1740
1879
|
return;
|
|
1741
1880
|
}
|
|
1742
1881
|
const text = redactAndCapText(raw, available, redact);
|
|
1743
1882
|
return {
|
|
1744
|
-
bytes: Buffer.byteLength(text, "
|
|
1883
|
+
bytes: Buffer.byteLength(text, "utf-8"),
|
|
1745
1884
|
path: normalizedName,
|
|
1746
1885
|
text
|
|
1747
1886
|
};
|
|
@@ -1814,9 +1953,9 @@ var readProjectPreferences = async (ctx, maxBytes = PREFERENCES_MAX_BYTES, redac
|
|
|
1814
1953
|
try {
|
|
1815
1954
|
const buffer = Buffer.alloc(maxBytes + 1);
|
|
1816
1955
|
const { bytesRead } = await file.read(buffer, 0, buffer.length, 0);
|
|
1817
|
-
const source = buffer.subarray(0, bytesRead).toString("
|
|
1956
|
+
const source = buffer.subarray(0, bytesRead).toString("utf-8");
|
|
1818
1957
|
const capped = redactAndCapText(source, maxBytes, redact);
|
|
1819
|
-
return { bytes: Buffer.byteLength(capped, "
|
|
1958
|
+
return { bytes: Buffer.byteLength(capped, "utf-8"), text: capped };
|
|
1820
1959
|
} finally {
|
|
1821
1960
|
await file.close();
|
|
1822
1961
|
}
|
|
@@ -1828,6 +1967,19 @@ import { createHash } from "node:crypto";
|
|
|
1828
1967
|
|
|
1829
1968
|
// src/tool-result-cap.ts
|
|
1830
1969
|
var OMITTED_MARKER = "[... omitted tool-result section ...]";
|
|
1970
|
+
var collect = (candidates, maxEntries, maxContentBytes) => {
|
|
1971
|
+
const selected = [];
|
|
1972
|
+
let used = 0;
|
|
1973
|
+
for (const line of candidates.slice(0, maxEntries)) {
|
|
1974
|
+
const next = used + byteLength(line) + (selected.length ? 1 : 0);
|
|
1975
|
+
if (next > maxContentBytes) {
|
|
1976
|
+
break;
|
|
1977
|
+
}
|
|
1978
|
+
selected.push(line);
|
|
1979
|
+
used = next;
|
|
1980
|
+
}
|
|
1981
|
+
return selected;
|
|
1982
|
+
};
|
|
1831
1983
|
var capToolResult = (value, maxLines = DEFAULT_ADVISOR_TOOL_RESULT_MAX_LINES, maxBytes = DEFAULT_ADVISOR_TOOL_RESULT_MAX_BYTES) => {
|
|
1832
1984
|
const lines = value.split(`
|
|
1833
1985
|
`);
|
|
@@ -1863,19 +2015,6 @@ var capToolResult = (value, maxLines = DEFAULT_ADVISOR_TOOL_RESULT_MAX_LINES, ma
|
|
|
1863
2015
|
}
|
|
1864
2016
|
const headCount = Math.floor((maxLines - 1) / 2);
|
|
1865
2017
|
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
2018
|
const availableBytes = maxBytes - markerBytes - 2;
|
|
1880
2019
|
const head = collect(lines, headCount, Math.floor(availableBytes / 2));
|
|
1881
2020
|
const tail = collect(lines.slice(Math.max(head.length, lines.length - tailCount)), tailCount, availableBytes - byteLength(head.join(`
|
|
@@ -1893,13 +2032,13 @@ var capToolResult = (value, maxLines = DEFAULT_ADVISOR_TOOL_RESULT_MAX_LINES, ma
|
|
|
1893
2032
|
|
|
1894
2033
|
// src/conversation.ts
|
|
1895
2034
|
var textFromPart = (part) => {
|
|
1896
|
-
if (
|
|
2035
|
+
if (isString(part)) {
|
|
1897
2036
|
return part;
|
|
1898
2037
|
}
|
|
1899
|
-
if (!
|
|
2038
|
+
if (!isRecordOf(part) || part.type !== "text") {
|
|
1900
2039
|
return "";
|
|
1901
2040
|
}
|
|
1902
|
-
return
|
|
2041
|
+
return isString(part.text) ? part.text : "";
|
|
1903
2042
|
};
|
|
1904
2043
|
var textFrom = (content) => contentParts(content).map(textFromPart).join(`
|
|
1905
2044
|
`).trim();
|
|
@@ -1910,10 +2049,10 @@ var assistantEntry = (message, policies, redact) => {
|
|
|
1910
2049
|
parts.push(redact ? redactSecrets(text) : text);
|
|
1911
2050
|
}
|
|
1912
2051
|
for (const part of contentParts(message.content)) {
|
|
1913
|
-
if (!
|
|
2052
|
+
if (!isRecordOf(part) || part.type !== "toolCall") {
|
|
1914
2053
|
continue;
|
|
1915
2054
|
}
|
|
1916
|
-
const toolName =
|
|
2055
|
+
const toolName = isString(part.name) ? part.name : "unknown";
|
|
1917
2056
|
const policy = policies[toolName] ?? "full";
|
|
1918
2057
|
if (policy === "exclude") {
|
|
1919
2058
|
parts.push(`[Tool Call: ${toolName}] (excluded by Advisor tool policy)`);
|
|
@@ -1931,15 +2070,15 @@ var assistantEntry = (message, policies, redact) => {
|
|
|
1931
2070
|
};
|
|
1932
2071
|
var toolResultEntry = (message, toolResultMaxLines, toolResultMaxBytes, policies, redact) => {
|
|
1933
2072
|
const status = message.isError ? "error" : "success";
|
|
1934
|
-
const toolName =
|
|
2073
|
+
const toolName = isString(message.toolName) ? message.toolName : "unknown";
|
|
1935
2074
|
const policy = policies[toolName] ?? "full";
|
|
1936
2075
|
const source = textFrom(message.content);
|
|
1937
2076
|
if (policy === "exclude") {
|
|
1938
2077
|
return `[Tool Result for ${toolName}] (excluded by Advisor tool policy)`;
|
|
1939
2078
|
}
|
|
1940
2079
|
if (policy === "summary") {
|
|
1941
|
-
const
|
|
1942
|
-
return `[Tool Result for ${toolName}] (output omitted by Advisor tool policy: summary; status: ${status}; ${
|
|
2080
|
+
const capped2 = capToolResult(source, toolResultMaxLines, toolResultMaxBytes);
|
|
2081
|
+
return `[Tool Result for ${toolName}] (output omitted by Advisor tool policy: summary; status: ${status}; ${capped2.totalLines} lines, ${capped2.totalBytes} bytes; source output was${capped2.truncated ? "" : " not"} truncated)`;
|
|
1943
2082
|
}
|
|
1944
2083
|
const disclosed = redact ? redactSecrets(source) : source;
|
|
1945
2084
|
const capped = capToolResult(disclosed, toolResultMaxLines, toolResultMaxBytes);
|
|
@@ -1947,13 +2086,13 @@ var toolResultEntry = (message, toolResultMaxLines, toolResultMaxBytes, policies
|
|
|
1947
2086
|
${capped.content}`;
|
|
1948
2087
|
};
|
|
1949
2088
|
var conversationEntry = (entry, toolResultMaxLines, toolResultMaxBytes, policies, redact) => {
|
|
1950
|
-
if (!
|
|
2089
|
+
if (!isRecordOf(entry)) {
|
|
1951
2090
|
return;
|
|
1952
2091
|
}
|
|
1953
|
-
if (entry.type === "compaction" &&
|
|
2092
|
+
if (entry.type === "compaction" && isString(entry.summary)) {
|
|
1954
2093
|
return `[System Compaction Summary]: ${redact ? redactSecrets(entry.summary) : entry.summary}`;
|
|
1955
2094
|
}
|
|
1956
|
-
if (entry.type !== "message" || !
|
|
2095
|
+
if (entry.type !== "message" || !isRecordOf(entry.message)) {
|
|
1957
2096
|
return;
|
|
1958
2097
|
}
|
|
1959
2098
|
const { message } = entry;
|
|
@@ -1979,17 +2118,17 @@ var selectRecentEntries = (entries, maxChars) => {
|
|
|
1979
2118
|
}
|
|
1980
2119
|
const newestTruncated = "[Newest entry truncated]";
|
|
1981
2120
|
if (entries.length === 1) {
|
|
1982
|
-
const
|
|
1983
|
-
return `${
|
|
2121
|
+
const prefix2 = `${newestTruncated}${separator}`;
|
|
2122
|
+
return `${prefix2}${entries[0].slice(0, Math.max(0, maxChars - prefix2.length))}`.slice(0, maxChars);
|
|
1984
2123
|
}
|
|
1985
2124
|
const selected = [];
|
|
1986
2125
|
let selectedLength = 0;
|
|
1987
2126
|
for (let index = entries.length - 1;index >= 0; index -= 1) {
|
|
1988
2127
|
const entry = entries[index];
|
|
1989
2128
|
const candidateCount = selected.length + 1;
|
|
1990
|
-
const
|
|
2129
|
+
const omitted2 = entries.length - candidateCount;
|
|
1991
2130
|
const candidateLength = selectedLength + entry.length + (selected.length > 0 ? separator.length : 0);
|
|
1992
|
-
if (omissionMarker(
|
|
2131
|
+
if (omissionMarker(omitted2).length + separator.length + candidateLength > maxChars) {
|
|
1993
2132
|
break;
|
|
1994
2133
|
}
|
|
1995
2134
|
selected.unshift(entry);
|
|
@@ -2018,30 +2157,29 @@ var invalid = (message) => ({
|
|
|
2018
2157
|
reason: "invalid-protocol"
|
|
2019
2158
|
});
|
|
2020
2159
|
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;
|
|
2030
|
-
}
|
|
2031
|
-
}
|
|
2032
|
-
for (const [id, results] of resultsByCall) {
|
|
2033
|
-
if (results.length > 1) {
|
|
2034
|
-
return invalid(`Tool call ${id} has duplicate result messages.`);
|
|
2160
|
+
var toolCallId = (part) => isString(part.id) ? part.id : undefined;
|
|
2161
|
+
var indexAssistantCalls = (message, index, callOwners) => {
|
|
2162
|
+
for (const call of toolCalls(message)) {
|
|
2163
|
+
const id = toolCallId(call);
|
|
2164
|
+
if (!id || callOwners.has(id)) {
|
|
2165
|
+
return invalid(`Assistant tool calls at context entry ${index} have missing or duplicate IDs.`);
|
|
2035
2166
|
}
|
|
2167
|
+
callOwners.set(id, {
|
|
2168
|
+
index,
|
|
2169
|
+
name: isString(call.name) ? call.name : "unknown"
|
|
2170
|
+
});
|
|
2036
2171
|
}
|
|
2037
|
-
return
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
}
|
|
2043
|
-
|
|
2044
|
-
|
|
2172
|
+
return;
|
|
2173
|
+
};
|
|
2174
|
+
var indexToolResult = (entry, message, index, resultsByCall) => {
|
|
2175
|
+
const id = message.toolCallId;
|
|
2176
|
+
if (!isString(id)) {
|
|
2177
|
+
return invalid(`Tool result at context entry ${index} has no tool-call ID.`);
|
|
2178
|
+
}
|
|
2179
|
+
const results = resultsByCall.get(id) ?? [];
|
|
2180
|
+
results.push({ entry, index });
|
|
2181
|
+
resultsByCall.set(id, results);
|
|
2182
|
+
return;
|
|
2045
2183
|
};
|
|
2046
2184
|
var indexEntry = (entry, index, callOwners, resultsByCall, state) => {
|
|
2047
2185
|
if (entry.type !== "message" || !isRecord(entry.message)) {
|
|
@@ -2058,28 +2196,32 @@ var indexEntry = (entry, index, callOwners, resultsByCall, state) => {
|
|
|
2058
2196
|
}
|
|
2059
2197
|
return;
|
|
2060
2198
|
};
|
|
2061
|
-
var
|
|
2062
|
-
const
|
|
2063
|
-
|
|
2064
|
-
|
|
2199
|
+
var indexToolCalls = (entries) => {
|
|
2200
|
+
const callOwners = new Map;
|
|
2201
|
+
const resultsByCall = new Map;
|
|
2202
|
+
const state = { latestUserIndex: -1 };
|
|
2203
|
+
for (let index = 0;index < entries.length; index += 1) {
|
|
2204
|
+
const entry = entries[index];
|
|
2205
|
+
if (isRecord(entry)) {
|
|
2206
|
+
const failure = indexEntry(entry, index, callOwners, resultsByCall, state);
|
|
2207
|
+
if (failure) {
|
|
2208
|
+
return failure;
|
|
2209
|
+
}
|
|
2210
|
+
}
|
|
2065
2211
|
}
|
|
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.`);
|
|
2212
|
+
for (const [id, results] of resultsByCall) {
|
|
2213
|
+
if (results.length > 1) {
|
|
2214
|
+
return invalid(`Tool call ${id} has duplicate result messages.`);
|
|
2076
2215
|
}
|
|
2077
|
-
callOwners.set(id, {
|
|
2078
|
-
index,
|
|
2079
|
-
name: typeof call.name === "string" ? call.name : "unknown"
|
|
2080
|
-
});
|
|
2081
2216
|
}
|
|
2082
|
-
return
|
|
2217
|
+
return {
|
|
2218
|
+
index: {
|
|
2219
|
+
callOwners,
|
|
2220
|
+
latestUserIndex: state.latestUserIndex,
|
|
2221
|
+
resultsByCall
|
|
2222
|
+
},
|
|
2223
|
+
ok: true
|
|
2224
|
+
};
|
|
2083
2225
|
};
|
|
2084
2226
|
|
|
2085
2227
|
// src/scout-types.ts
|
|
@@ -2091,8 +2233,8 @@ var SCOUT_SELECTION_MAX_IDS = 32;
|
|
|
2091
2233
|
var SCOUT_SYNTHESIS_MAX_BYTES = 4 * 1024;
|
|
2092
2234
|
|
|
2093
2235
|
// src/scout-groups.ts
|
|
2094
|
-
var SPEAKER_PREFIX = /^(User|Executor):\s
|
|
2095
|
-
var boundedLabel = (value) => [...value.
|
|
2236
|
+
var SPEAKER_PREFIX = /^(?<speaker>User|Executor):\s*/u;
|
|
2237
|
+
var boundedLabel = (value) => [...value.replaceAll(/\s+/gu, " ").trim()].slice(0, SCOUT_LABEL_MAX_CHARS).join("");
|
|
2096
2238
|
var labelFor = (kind, content) => {
|
|
2097
2239
|
const preview = boundedLabel(content.replace(SPEAKER_PREFIX, ""));
|
|
2098
2240
|
const prefix = {
|
|
@@ -2123,10 +2265,8 @@ var createGroup = (originalIndex, entryIds, kind, content, required) => ({
|
|
|
2123
2265
|
originalIndex,
|
|
2124
2266
|
required
|
|
2125
2267
|
});
|
|
2126
|
-
var pendingAdvisorArguments = (
|
|
2127
|
-
|
|
2128
|
-
return {};
|
|
2129
|
-
}
|
|
2268
|
+
var pendingAdvisorArguments = (part) => {
|
|
2269
|
+
const value = isRecord(part.arguments) ? part.arguments : {};
|
|
2130
2270
|
const allowed = {};
|
|
2131
2271
|
for (const key of ["gitContext", "question"]) {
|
|
2132
2272
|
if (key in value) {
|
|
@@ -2139,41 +2279,17 @@ var pendingInvocationDisclosure = (entry, invocationId, toolResultMaxLines, tool
|
|
|
2139
2279
|
if (!isRecord(entry.message)) {
|
|
2140
2280
|
return disclosed;
|
|
2141
2281
|
}
|
|
2142
|
-
const
|
|
2282
|
+
const message = entry.message;
|
|
2283
|
+
const content = contentParts(message.content).map((part) => {
|
|
2143
2284
|
if (!isRecord(part) || part.type !== "toolCall" || toolCallId(part) !== invocationId || part.name !== "ask_advisor") {
|
|
2144
2285
|
return part;
|
|
2145
2286
|
}
|
|
2146
|
-
return { ...part, arguments: pendingAdvisorArguments(part
|
|
2287
|
+
return { ...part, arguments: pendingAdvisorArguments(part) };
|
|
2147
2288
|
});
|
|
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
|
-
};
|
|
2289
|
+
return conversationEntry({ ...entry, message: { ...message, content } }, toolResultMaxLines, toolResultMaxBytes, policies, redact);
|
|
2173
2290
|
};
|
|
2174
2291
|
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)) {
|
|
2292
|
+
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
2293
|
return `Tool result at context entry ${index + 1} does not match its adjacent assistant group.`;
|
|
2178
2294
|
}
|
|
2179
2295
|
return;
|
|
@@ -2189,7 +2305,8 @@ var collectResults = (calls, callIds, index, resultsByCall, consumedResultIndexe
|
|
|
2189
2305
|
return `Tool result at context entry ${resultMatch.index} precedes its assistant call.`;
|
|
2190
2306
|
}
|
|
2191
2307
|
const resultMessage = resultMatch.entry.message;
|
|
2192
|
-
const
|
|
2308
|
+
const call = calls[callIndex];
|
|
2309
|
+
const expectedName = isString(call.name) ? call.name : "unknown";
|
|
2193
2310
|
if (!isRecord(resultMessage) || resultMessage.toolName !== expectedName) {
|
|
2194
2311
|
return `Tool result at context entry ${resultMatch.index} conflicts with call ${callId}.`;
|
|
2195
2312
|
}
|
|
@@ -2198,7 +2315,7 @@ var collectResults = (calls, callIds, index, resultsByCall, consumedResultIndexe
|
|
|
2198
2315
|
if (resultText) {
|
|
2199
2316
|
resultParts.push(resultText);
|
|
2200
2317
|
}
|
|
2201
|
-
resultEntryIds.push(
|
|
2318
|
+
resultEntryIds.push(isString(resultMatch.entry.id) ? resultMatch.entry.id : String(resultMatch.index));
|
|
2202
2319
|
}
|
|
2203
2320
|
return;
|
|
2204
2321
|
};
|
|
@@ -2221,37 +2338,47 @@ var missingOutcome = (entry, index, entryId, disclosed, callIds, missing, result
|
|
|
2221
2338
|
kind: "group"
|
|
2222
2339
|
};
|
|
2223
2340
|
};
|
|
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
|
-
}
|
|
2341
|
+
var toolExchangeGroup = (entry, message, index, entryId, disclosed, immediate, indexed, consumedResultIndexes, caps) => {
|
|
2342
|
+
const calls = toolCalls(message);
|
|
2343
|
+
const callIds = calls.map(toolCallId).filter(isString);
|
|
2344
|
+
const adjacentFailure = adjacentResultMismatch(immediate, index, callIds, indexed.callOwners);
|
|
2345
|
+
if (adjacentFailure) {
|
|
2346
|
+
return { kind: "invalid", message: adjacentFailure };
|
|
2347
|
+
}
|
|
2348
|
+
const missing = new Set;
|
|
2349
|
+
const resultParts = [];
|
|
2350
|
+
const resultEntryIds = [];
|
|
2351
|
+
const matchFailure = collectResults(calls, callIds, index, indexed.resultsByCall, consumedResultIndexes, caps, missing, resultParts, resultEntryIds);
|
|
2352
|
+
if (matchFailure) {
|
|
2353
|
+
return { kind: "invalid", message: matchFailure };
|
|
2354
|
+
}
|
|
2355
|
+
if (missing.size > 0) {
|
|
2356
|
+
return missingOutcome(entry, index, entryId, disclosed, callIds, missing, resultParts, resultEntryIds, caps);
|
|
2245
2357
|
}
|
|
2246
2358
|
return {
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2359
|
+
group: createGroup(index, [entryId, ...resultEntryIds], "tool-exchange", [disclosed, ...resultParts].join(`
|
|
2360
|
+
|
|
2361
|
+
`), false),
|
|
2362
|
+
kind: "group"
|
|
2251
2363
|
};
|
|
2252
2364
|
};
|
|
2365
|
+
var ownerOf = (message, callOwners) => isString(message.toolCallId) ? callOwners.get(message.toolCallId) : undefined;
|
|
2366
|
+
var contentPartsOf = (message) => Array.isArray(message.content) ? message.content : [];
|
|
2367
|
+
var hasCalls = (message) => contentPartsOf(message).some((part) => isRecord(part) && part.type === "toolCall");
|
|
2368
|
+
var toolResultOutcome = (message, index, disclosed, callOwners, consumedResultIndexes) => {
|
|
2369
|
+
if (consumedResultIndexes.has(index)) {
|
|
2370
|
+
return { kind: "skipped" };
|
|
2371
|
+
}
|
|
2372
|
+
if (ownerOf(message, callOwners)) {
|
|
2373
|
+
return {
|
|
2374
|
+
kind: "invalid",
|
|
2375
|
+
message: `Tool result at context entry ${index} precedes or conflicts with its retained call.`
|
|
2376
|
+
};
|
|
2377
|
+
}
|
|
2378
|
+
return { bytes: byteLength(disclosed), kind: "omitted" };
|
|
2379
|
+
};
|
|
2253
2380
|
var groupForEntry = (entry, index, disclosed, immediate, indexed, consumedResultIndexes, caps) => {
|
|
2254
|
-
const entryId =
|
|
2381
|
+
const entryId = isString(entry.id) ? entry.id : String(index);
|
|
2255
2382
|
if (entry.type !== "message" || !isRecord(entry.message)) {
|
|
2256
2383
|
return {
|
|
2257
2384
|
group: createGroup(index, [entryId], "compaction", disclosed, false),
|
|
@@ -2277,23 +2404,42 @@ var groupForEntry = (entry, index, disclosed, immediate, indexed, consumedResult
|
|
|
2277
2404
|
kind: "group"
|
|
2278
2405
|
};
|
|
2279
2406
|
}
|
|
2280
|
-
return toolExchangeGroup(entry, index, entryId, disclosed, immediate, indexed, consumedResultIndexes, caps);
|
|
2407
|
+
return toolExchangeGroup(entry, message, index, entryId, disclosed, immediate, indexed, consumedResultIndexes, caps);
|
|
2281
2408
|
};
|
|
2282
|
-
var
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2409
|
+
var buildGroups = (entries, indexed, caps) => {
|
|
2410
|
+
const groups = [];
|
|
2411
|
+
const consumedResultIndexes = new Set;
|
|
2412
|
+
let protocolOmittedBytes = 0;
|
|
2413
|
+
let protocolOmittedCount = 0;
|
|
2414
|
+
for (let index = 0;index < entries.length; index += 1) {
|
|
2415
|
+
const entry = entries[index];
|
|
2416
|
+
if (!isRecord(entry)) {
|
|
2417
|
+
continue;
|
|
2418
|
+
}
|
|
2419
|
+
const disclosed = conversationEntry(entry, caps.toolResultMaxLines, caps.toolResultMaxBytes, caps.policies, caps.redact);
|
|
2420
|
+
if (!disclosed) {
|
|
2421
|
+
continue;
|
|
2422
|
+
}
|
|
2423
|
+
const next = entries[index + 1];
|
|
2424
|
+
const immediate = isRecord(next) ? next : undefined;
|
|
2425
|
+
const outcome = groupForEntry(entry, index, disclosed, immediate, indexed, consumedResultIndexes, caps);
|
|
2426
|
+
if (outcome.kind === "invalid") {
|
|
2427
|
+
return invalid(outcome.message);
|
|
2428
|
+
}
|
|
2429
|
+
if (outcome.kind === "omitted") {
|
|
2430
|
+
protocolOmittedCount += 1;
|
|
2431
|
+
protocolOmittedBytes += outcome.bytes;
|
|
2432
|
+
} else if (outcome.kind === "group") {
|
|
2433
|
+
groups.push(outcome.group);
|
|
2434
|
+
}
|
|
2291
2435
|
}
|
|
2292
|
-
return {
|
|
2436
|
+
return {
|
|
2437
|
+
groups,
|
|
2438
|
+
ok: true,
|
|
2439
|
+
protocolOmittedBytes,
|
|
2440
|
+
protocolOmittedCount
|
|
2441
|
+
};
|
|
2293
2442
|
};
|
|
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
2443
|
|
|
2298
2444
|
// src/scout-reconstruct.ts
|
|
2299
2445
|
var prefixWithinCharBudget = (value, maxChars) => {
|
|
@@ -2308,7 +2454,7 @@ var prefixWithinCharBudget = (value, maxChars) => {
|
|
|
2308
2454
|
};
|
|
2309
2455
|
var reconstructScoutConversation = (manifest, selectedIds, synthesis, maxChars = Number.MAX_SAFE_INTEGER) => {
|
|
2310
2456
|
const selected = new Set(selectedIds);
|
|
2311
|
-
const evidence = manifest.groups.filter((group) => group.required || selected.has(group.id)).
|
|
2457
|
+
const evidence = manifest.groups.filter((group) => group.required || selected.has(group.id)).toSorted((left, right) => left.originalIndex - right.originalIndex).map((group) => group.content);
|
|
2312
2458
|
const evidenceText = evidence.join(`
|
|
2313
2459
|
|
|
2314
2460
|
`);
|
|
@@ -2333,15 +2479,106 @@ ${synthesis.trim()}` : undefined;
|
|
|
2333
2479
|
return `${evidenceText}${separator}${prefixWithinCharBudget(inference, remaining)}`;
|
|
2334
2480
|
};
|
|
2335
2481
|
|
|
2482
|
+
// src/scout-context.ts
|
|
2483
|
+
var resolveCaps = (options) => ({
|
|
2484
|
+
currentInvocationId: options.currentInvocationId,
|
|
2485
|
+
maxConversationChars: options.maxConversationChars,
|
|
2486
|
+
maxGroupBytes: options.maxGroupBytes ?? SCOUT_GROUP_MAX_BYTES,
|
|
2487
|
+
maxGroups: options.maxGroups ?? SCOUT_MANIFEST_MAX_GROUPS,
|
|
2488
|
+
maxManifestBytes: options.maxManifestBytes ?? options.maxBytes ?? SCOUT_MANIFEST_MAX_BYTES,
|
|
2489
|
+
policies: options.policies ?? advisorToolPoliciesRef,
|
|
2490
|
+
redact: options.redact ?? advisorRedactSecretsRef,
|
|
2491
|
+
toolResultMaxBytes: options.toolResultMaxBytes ?? advisorToolResultMaxBytesRef,
|
|
2492
|
+
toolResultMaxLines: options.toolResultMaxLines ?? advisorToolResultMaxLinesRef
|
|
2493
|
+
});
|
|
2494
|
+
var contentChars = (items) => items.reduce((sum, group) => sum + group.content.length, 0) + Math.max(0, items.length - 1) * 2;
|
|
2495
|
+
var requiredOverflow = (required, caps) => {
|
|
2496
|
+
if (required.some((group) => group.bytes > caps.maxGroupBytes) || required.length > caps.maxGroups || required.reduce((sum, group) => sum + groupWireBytes(group), 0) > caps.maxManifestBytes) {
|
|
2497
|
+
return {
|
|
2498
|
+
message: "Required Scout context exceeds the Scout manifest transport limit.",
|
|
2499
|
+
ok: false,
|
|
2500
|
+
reason: "required-group-overflow"
|
|
2501
|
+
};
|
|
2502
|
+
}
|
|
2503
|
+
if (caps.maxConversationChars !== undefined && contentChars(required) > caps.maxConversationChars) {
|
|
2504
|
+
return {
|
|
2505
|
+
message: "Required Scout context exceeds the Advisor conversation budget.",
|
|
2506
|
+
ok: false,
|
|
2507
|
+
reason: "required-group-overflow"
|
|
2508
|
+
};
|
|
2509
|
+
}
|
|
2510
|
+
return;
|
|
2511
|
+
};
|
|
2512
|
+
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);
|
|
2513
|
+
var fitToBudget = (built, caps) => {
|
|
2514
|
+
const { groups, protocolOmittedBytes, protocolOmittedCount } = built;
|
|
2515
|
+
const availableCount = groups.length + protocolOmittedCount;
|
|
2516
|
+
const availableBytes = groups.reduce((sum, group) => sum + groupWireBytes(group), 0) + protocolOmittedBytes;
|
|
2517
|
+
if (caps.maxManifestBytes <= 0) {
|
|
2518
|
+
return {
|
|
2519
|
+
manifest: {
|
|
2520
|
+
availableBytes: 0,
|
|
2521
|
+
availableCount: 0,
|
|
2522
|
+
groups: [],
|
|
2523
|
+
omittedBytes: 0,
|
|
2524
|
+
omittedCount: 0
|
|
2525
|
+
},
|
|
2526
|
+
ok: true
|
|
2527
|
+
};
|
|
2528
|
+
}
|
|
2529
|
+
const required = groups.filter((group) => group.required);
|
|
2530
|
+
const overflow = requiredOverflow(required, caps);
|
|
2531
|
+
if (overflow) {
|
|
2532
|
+
return overflow;
|
|
2533
|
+
}
|
|
2534
|
+
const selected = groups.filter((group) => group.required || group.bytes <= caps.maxGroupBytes);
|
|
2535
|
+
while (!fits(selected, caps)) {
|
|
2536
|
+
const optionalIndex = selected.findIndex((group) => !group.required);
|
|
2537
|
+
if (optionalIndex === -1) {
|
|
2538
|
+
return {
|
|
2539
|
+
message: "Required Scout context exceeds fixed manifest limits.",
|
|
2540
|
+
ok: false,
|
|
2541
|
+
reason: "required-group-overflow"
|
|
2542
|
+
};
|
|
2543
|
+
}
|
|
2544
|
+
selected.splice(optionalIndex, 1);
|
|
2545
|
+
}
|
|
2546
|
+
const selectedIds = new Set(selected.map((group) => group.id));
|
|
2547
|
+
const omitted = groups.filter((group) => !selectedIds.has(group.id));
|
|
2548
|
+
return {
|
|
2549
|
+
manifest: {
|
|
2550
|
+
availableBytes,
|
|
2551
|
+
availableCount,
|
|
2552
|
+
groups: selected,
|
|
2553
|
+
omittedBytes: protocolOmittedBytes + omitted.reduce((sum, group) => sum + group.bytes, 0),
|
|
2554
|
+
omittedCount: protocolOmittedCount + omitted.length
|
|
2555
|
+
},
|
|
2556
|
+
ok: true
|
|
2557
|
+
};
|
|
2558
|
+
};
|
|
2559
|
+
var buildScoutManifest = (ctx, options = {}) => {
|
|
2560
|
+
const entries = ctx.sessionManager.buildContextEntries();
|
|
2561
|
+
const caps = resolveCaps(options);
|
|
2562
|
+
const indexed = indexToolCalls(entries);
|
|
2563
|
+
if (!indexed.ok) {
|
|
2564
|
+
return indexed;
|
|
2565
|
+
}
|
|
2566
|
+
const built = buildGroups(entries, indexed.index, caps);
|
|
2567
|
+
if (!built.ok) {
|
|
2568
|
+
return built;
|
|
2569
|
+
}
|
|
2570
|
+
return fitToBudget(built, caps);
|
|
2571
|
+
};
|
|
2572
|
+
|
|
2336
2573
|
// src/usage.ts
|
|
2337
|
-
var finite = (value) =>
|
|
2574
|
+
var finite = (value) => isNumber(value) && Number.isFinite(value) && value >= 0 ? value : undefined;
|
|
2338
2575
|
var add = (left, right) => left === undefined || right === undefined ? left ?? right : left + right;
|
|
2339
2576
|
var costFields = ["input", "output", "cacheRead", "cacheWrite", "total"];
|
|
2340
2577
|
var snapshotAdvisorUsage = (usage) => {
|
|
2341
|
-
if (!
|
|
2578
|
+
if (!isRecordOf(usage)) {
|
|
2342
2579
|
return;
|
|
2343
2580
|
}
|
|
2344
|
-
const cost =
|
|
2581
|
+
const cost = isRecordOf(usage.cost) ? usage.cost : undefined;
|
|
2345
2582
|
const snapshot = {
|
|
2346
2583
|
cacheRead: finite(usage.cacheRead),
|
|
2347
2584
|
cacheWrite: finite(usage.cacheWrite),
|
|
@@ -2356,21 +2593,19 @@ var snapshotAdvisorUsage = (usage) => {
|
|
|
2356
2593
|
var advisorUsageCost = (usage) => snapshotAdvisorUsage(usage)?.cost;
|
|
2357
2594
|
var advisorUsageForPi = (usage) => {
|
|
2358
2595
|
const snapshot = snapshotAdvisorUsage(usage);
|
|
2359
|
-
if (!(snapshot &&
|
|
2596
|
+
if (!(snapshot && isRecordOf(usage))) {
|
|
2360
2597
|
return;
|
|
2361
2598
|
}
|
|
2362
|
-
const cost =
|
|
2599
|
+
const cost = isRecordOf(usage.cost) ? usage.cost : undefined;
|
|
2363
2600
|
const input = snapshot.input ?? 0;
|
|
2364
2601
|
const output = snapshot.output ?? 0;
|
|
2365
2602
|
const cacheRead = snapshot.cacheRead ?? 0;
|
|
2366
2603
|
const cacheWrite = snapshot.cacheWrite ?? 0;
|
|
2367
2604
|
const cacheWrite1h = finite(usage.cacheWrite1h);
|
|
2368
2605
|
const reasoning = finite(usage.reasoning);
|
|
2369
|
-
|
|
2606
|
+
const piUsage = {
|
|
2370
2607
|
cacheRead,
|
|
2371
2608
|
cacheWrite,
|
|
2372
|
-
...cacheWrite1h === undefined ? {} : { cacheWrite1h },
|
|
2373
|
-
...reasoning === undefined ? {} : { reasoning },
|
|
2374
2609
|
cost: {
|
|
2375
2610
|
cacheRead: finite(cost?.cacheRead) ?? 0,
|
|
2376
2611
|
cacheWrite: finite(cost?.cacheWrite) ?? 0,
|
|
@@ -2382,6 +2617,13 @@ var advisorUsageForPi = (usage) => {
|
|
|
2382
2617
|
output,
|
|
2383
2618
|
totalTokens: snapshot.totalTokens ?? input + output + cacheRead + cacheWrite
|
|
2384
2619
|
};
|
|
2620
|
+
if (cacheWrite1h !== undefined) {
|
|
2621
|
+
piUsage.cacheWrite1h = cacheWrite1h;
|
|
2622
|
+
}
|
|
2623
|
+
if (reasoning !== undefined) {
|
|
2624
|
+
piUsage.reasoning = reasoning;
|
|
2625
|
+
}
|
|
2626
|
+
return piUsage;
|
|
2385
2627
|
};
|
|
2386
2628
|
var emptyAdvisorUsageTotals = () => ({
|
|
2387
2629
|
calls: 0,
|
|
@@ -2476,8 +2718,8 @@ var defaultDependencies = {
|
|
|
2476
2718
|
collect: collectTextStream,
|
|
2477
2719
|
resolve: resolveConfiguredModel
|
|
2478
2720
|
};
|
|
2479
|
-
var byteLength2 = (value) => Buffer.byteLength(value, "
|
|
2480
|
-
var AUTH_ERROR_PATTERN = /api key|auth|login|credential/
|
|
2721
|
+
var byteLength2 = (value) => Buffer.byteLength(value, "utf-8");
|
|
2722
|
+
var AUTH_ERROR_PATTERN = /api key|auth|login|credential/iu;
|
|
2481
2723
|
var manifestMessage = (manifest) => ({
|
|
2482
2724
|
content: [
|
|
2483
2725
|
{
|
|
@@ -2494,6 +2736,7 @@ var manifestMessage = (manifest) => ({
|
|
|
2494
2736
|
role: "user",
|
|
2495
2737
|
timestamp: Date.now()
|
|
2496
2738
|
});
|
|
2739
|
+
var isStringArray = (value) => Array.isArray(value) && value.every(isString);
|
|
2497
2740
|
var parseScoutSelection = (text, manifest) => {
|
|
2498
2741
|
if (!text.trim()) {
|
|
2499
2742
|
throw new Error("Scout returned an empty response.");
|
|
@@ -2504,18 +2747,17 @@ var parseScoutSelection = (text, manifest) => {
|
|
|
2504
2747
|
} catch (error) {
|
|
2505
2748
|
throw new Error("Scout response is not a JSON object.", { cause: error });
|
|
2506
2749
|
}
|
|
2507
|
-
if (!
|
|
2750
|
+
if (!isRecord(value)) {
|
|
2508
2751
|
throw new Error("Scout response must be a JSON object.");
|
|
2509
2752
|
}
|
|
2510
|
-
const
|
|
2511
|
-
const keys = Object.keys(record).sort();
|
|
2753
|
+
const keys = Object.keys(value).toSorted();
|
|
2512
2754
|
if (keys.length !== 2 || keys[0] !== "selectedIds" || keys[1] !== "synthesis") {
|
|
2513
2755
|
throw new Error("Scout response must contain only selectedIds and synthesis.");
|
|
2514
2756
|
}
|
|
2515
|
-
if (!(
|
|
2757
|
+
if (!isStringArray(value.selectedIds)) {
|
|
2516
2758
|
throw new Error("Scout selectedIds must be an array of strings.");
|
|
2517
2759
|
}
|
|
2518
|
-
const selectedIds =
|
|
2760
|
+
const { selectedIds } = value;
|
|
2519
2761
|
if (new Set(selectedIds).size !== selectedIds.length) {
|
|
2520
2762
|
throw new Error("Scout selected duplicate group IDs.");
|
|
2521
2763
|
}
|
|
@@ -2529,15 +2771,15 @@ var parseScoutSelection = (text, manifest) => {
|
|
|
2529
2771
|
const optionalIds = knownSelectedIds.filter((id) => !required.has(id)).slice(0, SCOUT_SELECTION_MAX_IDS - requiredIds.length);
|
|
2530
2772
|
const retained = new Set([...requiredIds, ...optionalIds]);
|
|
2531
2773
|
const normalizedIds = manifest.groups.filter((group) => retained.has(group.id)).map((group) => group.id);
|
|
2532
|
-
if (
|
|
2533
|
-
throw new
|
|
2774
|
+
if (!isString(value.synthesis)) {
|
|
2775
|
+
throw new TypeError("Scout synthesis must be a string.");
|
|
2534
2776
|
}
|
|
2535
|
-
if (byteLength2(
|
|
2777
|
+
if (byteLength2(value.synthesis) > SCOUT_SYNTHESIS_MAX_BYTES) {
|
|
2536
2778
|
throw new Error(`Scout synthesis exceeds ${SCOUT_SYNTHESIS_MAX_BYTES} UTF-8 bytes.`);
|
|
2537
2779
|
}
|
|
2538
2780
|
return {
|
|
2539
2781
|
selectedIds: normalizedIds,
|
|
2540
|
-
synthesis: knownSelectedIds.length > 0 ?
|
|
2782
|
+
synthesis: knownSelectedIds.length > 0 ? value.synthesis : ""
|
|
2541
2783
|
};
|
|
2542
2784
|
};
|
|
2543
2785
|
var baseMetrics = (manifest, startedAt) => ({
|
|
@@ -2556,6 +2798,61 @@ var classifyResolutionError = (message) => {
|
|
|
2556
2798
|
}
|
|
2557
2799
|
return "provider-error";
|
|
2558
2800
|
};
|
|
2801
|
+
var setupAbortWatch = (parentSignal, timeoutMs) => {
|
|
2802
|
+
const controller = new AbortController;
|
|
2803
|
+
let timedOut = false;
|
|
2804
|
+
const abortFromParent = () => controller.abort(parentSignal?.reason);
|
|
2805
|
+
parentSignal?.addEventListener("abort", abortFromParent, { once: true });
|
|
2806
|
+
const timer = setTimeout(() => {
|
|
2807
|
+
timedOut = true;
|
|
2808
|
+
controller.abort(new Error("Scout timed out."));
|
|
2809
|
+
}, timeoutMs);
|
|
2810
|
+
timer.unref?.();
|
|
2811
|
+
const { promise: abortPromise, reject: rejectOnAbort } = Promise.withResolvers();
|
|
2812
|
+
const onControllerAbort = () => rejectOnAbort(controller.signal.reason ?? new Error("Scout aborted."));
|
|
2813
|
+
controller.signal.addEventListener("abort", onControllerAbort, {
|
|
2814
|
+
once: true
|
|
2815
|
+
});
|
|
2816
|
+
return {
|
|
2817
|
+
abortPromise,
|
|
2818
|
+
controller,
|
|
2819
|
+
teardown: () => {
|
|
2820
|
+
clearTimeout(timer);
|
|
2821
|
+
parentSignal?.removeEventListener("abort", abortFromParent);
|
|
2822
|
+
controller.signal.removeEventListener("abort", onControllerAbort);
|
|
2823
|
+
},
|
|
2824
|
+
wasTimedOut: () => timedOut
|
|
2825
|
+
};
|
|
2826
|
+
};
|
|
2827
|
+
var streamScoutResponse = async (dependencies, resolved, executorModel, executorEffort, manifest, parentSignal, timeoutMs, publish) => {
|
|
2828
|
+
const { abortPromise, controller, teardown, wasTimedOut } = setupAbortWatch(parentSignal, timeoutMs);
|
|
2829
|
+
try {
|
|
2830
|
+
const collection = dependencies.collect(resolved, {
|
|
2831
|
+
messages: [manifestMessage(manifest)],
|
|
2832
|
+
onChunk: (thinking, text) => {
|
|
2833
|
+
if (!controller.signal.aborted) {
|
|
2834
|
+
publish({ model: executorModel, text, thinking, type: "chunk" });
|
|
2835
|
+
}
|
|
2836
|
+
},
|
|
2837
|
+
reasoning: executorEffort,
|
|
2838
|
+
signal: controller.signal,
|
|
2839
|
+
systemPrompt: SCOUT_SYSTEM
|
|
2840
|
+
});
|
|
2841
|
+
return {
|
|
2842
|
+
ok: true,
|
|
2843
|
+
streamed: await Promise.race([collection, abortPromise])
|
|
2844
|
+
};
|
|
2845
|
+
} catch (error) {
|
|
2846
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2847
|
+
return wasTimedOut() ? {
|
|
2848
|
+
category: "timeout",
|
|
2849
|
+
message: `Scout timed out after ${timeoutMs} ms.`,
|
|
2850
|
+
ok: false
|
|
2851
|
+
} : { category: "provider-error", message, ok: false };
|
|
2852
|
+
} finally {
|
|
2853
|
+
teardown();
|
|
2854
|
+
}
|
|
2855
|
+
};
|
|
2559
2856
|
var runAdvisorScout = async (ctx, manifest, parentSignal, onEvent, timeoutMs = SCOUT_TIMEOUT_MS, dependencies = defaultDependencies) => {
|
|
2560
2857
|
const startedAt = Date.now();
|
|
2561
2858
|
const publish = (event) => {
|
|
@@ -2566,22 +2863,24 @@ var runAdvisorScout = async (ctx, manifest, parentSignal, onEvent, timeoutMs = S
|
|
|
2566
2863
|
return { cancelled: true, ok: false };
|
|
2567
2864
|
};
|
|
2568
2865
|
const fallback = (category, message, usage) => {
|
|
2569
|
-
const
|
|
2866
|
+
const outcome2 = {
|
|
2570
2867
|
category,
|
|
2571
2868
|
message,
|
|
2572
2869
|
metrics: usage === undefined ? baseMetrics(manifest, startedAt) : { ...baseMetrics(manifest, startedAt), usage },
|
|
2573
|
-
model:
|
|
2870
|
+
model: effectiveExecutorRef(ctx),
|
|
2574
2871
|
ok: false
|
|
2575
2872
|
};
|
|
2576
|
-
publish({ outcome, type: "fallback" });
|
|
2577
|
-
return
|
|
2873
|
+
publish({ outcome: outcome2, type: "fallback" });
|
|
2874
|
+
return outcome2;
|
|
2578
2875
|
};
|
|
2579
2876
|
if (parentSignal?.aborted) {
|
|
2580
2877
|
return cancelled();
|
|
2581
2878
|
}
|
|
2879
|
+
const executorModel = effectiveExecutorRef(ctx);
|
|
2880
|
+
const executorEffort = effectiveExecutorEffort(ctx);
|
|
2582
2881
|
let resolved;
|
|
2583
2882
|
try {
|
|
2584
|
-
resolved = await dependencies.resolve(ctx,
|
|
2883
|
+
resolved = await dependencies.resolve(ctx, executorModel, "Scout");
|
|
2585
2884
|
} catch (error) {
|
|
2586
2885
|
if (parentSignal?.aborted) {
|
|
2587
2886
|
return cancelled();
|
|
@@ -2592,61 +2891,23 @@ var runAdvisorScout = async (ctx, manifest, parentSignal, onEvent, timeoutMs = S
|
|
|
2592
2891
|
if (parentSignal?.aborted) {
|
|
2593
2892
|
return cancelled();
|
|
2594
2893
|
}
|
|
2595
|
-
publish({ model:
|
|
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();
|
|
2894
|
+
publish({ model: executorModel, type: "call" });
|
|
2895
|
+
const streamed = await streamScoutResponse(dependencies, resolved, executorModel, executorEffort, manifest, parentSignal, timeoutMs, publish);
|
|
2896
|
+
if (!streamed.ok) {
|
|
2634
2897
|
if (parentSignal?.aborted) {
|
|
2635
2898
|
return cancelled();
|
|
2636
2899
|
}
|
|
2637
|
-
|
|
2638
|
-
return timedOut ? fallback("timeout", `Scout timed out after ${timeoutMs} ms.`) : fallback("provider-error", message);
|
|
2900
|
+
return fallback(streamed.category, streamed.message);
|
|
2639
2901
|
}
|
|
2640
|
-
teardown();
|
|
2641
2902
|
if (parentSignal?.aborted) {
|
|
2642
2903
|
return cancelled();
|
|
2643
2904
|
}
|
|
2644
2905
|
let selection;
|
|
2645
2906
|
try {
|
|
2646
|
-
selection = parseScoutSelection(streamed.text, manifest);
|
|
2907
|
+
selection = parseScoutSelection(streamed.streamed.text, manifest);
|
|
2647
2908
|
} catch (error) {
|
|
2648
2909
|
const message = error instanceof Error ? error.message : String(error);
|
|
2649
|
-
return fallback(streamed.text.trim() ? "invalid-selection" : "empty-response", message, snapshotAdvisorUsage(streamed.usage));
|
|
2910
|
+
return fallback(streamed.streamed.text.trim() ? "invalid-selection" : "empty-response", message, snapshotAdvisorUsage(streamed.streamed.usage));
|
|
2650
2911
|
}
|
|
2651
2912
|
const outcome = {
|
|
2652
2913
|
conversation: reconstructScoutConversation(manifest, selection.selectedIds, selection.synthesis),
|
|
@@ -2656,9 +2917,9 @@ var runAdvisorScout = async (ctx, manifest, parentSignal, onEvent, timeoutMs = S
|
|
|
2656
2917
|
...selection.selectedIds,
|
|
2657
2918
|
...manifest.groups.filter((group) => group.required).map((group) => group.id)
|
|
2658
2919
|
]).size,
|
|
2659
|
-
usage: snapshotAdvisorUsage(streamed.usage)
|
|
2920
|
+
usage: snapshotAdvisorUsage(streamed.streamed.usage)
|
|
2660
2921
|
},
|
|
2661
|
-
model:
|
|
2922
|
+
model: executorModel,
|
|
2662
2923
|
ok: true,
|
|
2663
2924
|
selectedLabels: manifest.groups.filter((group) => group.required || selection.selectedIds.includes(group.id)).map((group) => group.label),
|
|
2664
2925
|
selection
|
|
@@ -2667,97 +2928,6 @@ var runAdvisorScout = async (ctx, manifest, parentSignal, onEvent, timeoutMs = S
|
|
|
2667
2928
|
return outcome;
|
|
2668
2929
|
};
|
|
2669
2930
|
|
|
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
|
|
2739
|
-
},
|
|
2740
|
-
ok: true
|
|
2741
|
-
};
|
|
2742
|
-
};
|
|
2743
|
-
var requiredOverflow = (required, caps) => {
|
|
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;
|
|
2759
|
-
};
|
|
2760
|
-
|
|
2761
2931
|
// src/scout-curation.ts
|
|
2762
2932
|
var curateAdvisorConversation = async (ctx, legacyConversation, signal, onScout, enabled = advisorScoutEnabledRef, runScout = runAdvisorScout, currentInvocationId, maxChars) => {
|
|
2763
2933
|
if (!enabled) {
|
|
@@ -2788,7 +2958,7 @@ var curateAdvisorConversation = async (ctx, legacyConversation, signal, onScout,
|
|
|
2788
2958
|
onScout?.({ outcome: scout, type: "fallback" });
|
|
2789
2959
|
return { conversation: legacyConversation, scout };
|
|
2790
2960
|
}
|
|
2791
|
-
const outcome = await runScout(ctx, built.manifest, signal, onScout
|
|
2961
|
+
const outcome = await runScout(ctx, built.manifest, signal, onScout);
|
|
2792
2962
|
if (!outcome.ok && outcome.cancelled) {
|
|
2793
2963
|
throw signal?.reason instanceof Error ? signal.reason : new Error("Advisor operation cancelled during Scout.");
|
|
2794
2964
|
}
|
|
@@ -2800,12 +2970,12 @@ var curateAdvisorConversation = async (ctx, legacyConversation, signal, onScout,
|
|
|
2800
2970
|
};
|
|
2801
2971
|
|
|
2802
2972
|
// src/tools/prompts.ts
|
|
2803
|
-
var advisorMessageText = (conversation, question, changes, draft, preferences,
|
|
2973
|
+
var advisorMessageText = (conversation, question, changes, draft, preferences, untracked2, tracked2) => {
|
|
2804
2974
|
const safeConversation = escapeRepositoryText(conversation);
|
|
2805
2975
|
const safeDraft = draft ? escapeRepositoryText(draft) : undefined;
|
|
2806
2976
|
const safePreferences = preferences ? escapeRepositoryText(preferences) : undefined;
|
|
2807
|
-
const safeUntracked = (
|
|
2808
|
-
const safeTracked = (
|
|
2977
|
+
const safeUntracked = (untracked2 ?? []).map(escapeRepositoryText);
|
|
2978
|
+
const safeTracked = (tracked2 ?? []).map(escapeRepositoryText);
|
|
2809
2979
|
const text = `${safeConversation ? `<conversation>
|
|
2810
2980
|
${safeConversation}
|
|
2811
2981
|
</conversation>` : ""}${changes ? `
|
|
@@ -2839,26 +3009,21 @@ ${question}` : ""}`;
|
|
|
2839
3009
|
return text.trim() || "No conversation context is available. State that you cannot review without context.";
|
|
2840
3010
|
};
|
|
2841
3011
|
var advisorGitContextBudget = (contextMaxChars, gitContextMaxChars) => Math.min(gitContextMaxChars, Math.floor(contextMaxChars / 2));
|
|
3012
|
+
var LEVEL_WITHHELD = {
|
|
3013
|
+
collected: true,
|
|
3014
|
+
"no-changes": false
|
|
3015
|
+
};
|
|
3016
|
+
var STATUS_NOTES = {
|
|
3017
|
+
disabled: "Repository context was disabled or had no disclosure budget; it was withheld. Do not assume the working tree is clean.",
|
|
3018
|
+
failed: "Repository context could not be collected. Do not assume the working tree is clean.",
|
|
3019
|
+
"no-changes": "The working tree has no uncommitted changes.",
|
|
3020
|
+
"not-a-repository": "No Git repository is available for this session."
|
|
3021
|
+
};
|
|
2842
3022
|
var gitContextNote = (result, requested, allowed) => {
|
|
2843
3023
|
if (requested !== allowed && LEVEL_WITHHELD[result.status]) {
|
|
2844
3024
|
return `Repository context was limited to "${allowed}" by user configuration; a fuller view was requested but withheld.`;
|
|
2845
3025
|
}
|
|
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
|
|
3026
|
+
return STATUS_NOTES[result.status];
|
|
2862
3027
|
};
|
|
2863
3028
|
var advisorRepositoryContext = (result, requested, allowed, budget) => {
|
|
2864
3029
|
const note = gitContextNote(result, requested, allowed);
|
|
@@ -2923,23 +3088,24 @@ var assembleConsultationContext = async (options) => {
|
|
|
2923
3088
|
const curated = await curateAdvisorConversation(ctx, legacyConversation, options.signal, options.onScout, advisorScoutEnabledRef, runAdvisorScout, options.currentInvocationId, conversationBudget);
|
|
2924
3089
|
const preferences = await readProjectPreferences(ctx, ATTACHMENT_TEXT_MAX_BYTES, advisorRedactSecretsRef);
|
|
2925
3090
|
const draftText = options.draft ? redactAndCapText(options.draft, ATTACHMENT_TEXT_MAX_BYTES, advisorRedactSecretsRef) : undefined;
|
|
2926
|
-
const
|
|
2927
|
-
const
|
|
3091
|
+
const untracked2 = await readUntrackedFiles(ctx.cwd, options.includeUntracked ?? [], advisorUntrackedContentRef, advisorRedactSecretsRef);
|
|
3092
|
+
const tracked2 = await readTrackedFiles(ctx.cwd, options.includeTracked ?? [], advisorTrackedFileContentRef, advisorRedactSecretsRef, Math.max(0, ATTACHMENTS_TOTAL_MAX_BYTES - untracked2.reduce((sum, item) => sum + item.bytes, 0)));
|
|
2928
3093
|
return {
|
|
2929
3094
|
changeText,
|
|
2930
3095
|
conversation: curated.conversation,
|
|
2931
3096
|
draftText,
|
|
2932
3097
|
preferences,
|
|
2933
3098
|
scout: curated.scout,
|
|
2934
|
-
tracked,
|
|
2935
|
-
untracked
|
|
3099
|
+
tracked: tracked2,
|
|
3100
|
+
untracked: untracked2
|
|
2936
3101
|
};
|
|
2937
3102
|
};
|
|
2938
3103
|
|
|
2939
3104
|
// src/tools/gate-protocol.ts
|
|
2940
|
-
var
|
|
2941
|
-
var
|
|
2942
|
-
var
|
|
3105
|
+
var namedGroups = (match) => match.groups ?? {};
|
|
3106
|
+
var DECISION_LINE = /^Decision\s*:\s*(?<decision>proceed|revise|blocked)\s*$/iu;
|
|
3107
|
+
var CODE_FENCE = /^ {0,3}(?<marker>`{3,}|~{3,})(?<suffix>.*)$/u;
|
|
3108
|
+
var LINE_BREAK = /\r?\n/u;
|
|
2943
3109
|
var advanceFence = (openingFence, marker, suffix) => {
|
|
2944
3110
|
if (!openingFence) {
|
|
2945
3111
|
if (marker[0] === "`" && suffix.includes("`")) {
|
|
@@ -2975,7 +3141,7 @@ var parseAutomaticDecision = (text) => {
|
|
|
2975
3141
|
ok: false
|
|
2976
3142
|
};
|
|
2977
3143
|
}
|
|
2978
|
-
const decision = match
|
|
3144
|
+
const decision = namedGroups(match).decision.toLowerCase();
|
|
2979
3145
|
let openingFence;
|
|
2980
3146
|
const decisions = [];
|
|
2981
3147
|
let pendingFencedDecisions = [];
|
|
@@ -2983,7 +3149,8 @@ var parseAutomaticDecision = (text) => {
|
|
|
2983
3149
|
const trimmed = line.trim();
|
|
2984
3150
|
const fence = CODE_FENCE.exec(line);
|
|
2985
3151
|
if (fence) {
|
|
2986
|
-
const
|
|
3152
|
+
const groups = namedGroups(fence);
|
|
3153
|
+
const { closed, openingFence: nextOpeningFence } = advanceFence(openingFence, groups.marker, groups.suffix);
|
|
2987
3154
|
openingFence = nextOpeningFence;
|
|
2988
3155
|
if (closed) {
|
|
2989
3156
|
pendingFencedDecisions = [];
|
|
@@ -2994,26 +3161,24 @@ var parseAutomaticDecision = (text) => {
|
|
|
2994
3161
|
if (!subsequent) {
|
|
2995
3162
|
continue;
|
|
2996
3163
|
}
|
|
2997
|
-
const
|
|
3164
|
+
const repeated2 = namedGroups(subsequent).decision.trim().toLowerCase();
|
|
2998
3165
|
if (openingFence) {
|
|
2999
|
-
pendingFencedDecisions.push(
|
|
3166
|
+
pendingFencedDecisions.push(repeated2);
|
|
3000
3167
|
} else {
|
|
3001
|
-
decisions.push(
|
|
3168
|
+
decisions.push(repeated2);
|
|
3002
3169
|
}
|
|
3003
3170
|
}
|
|
3004
3171
|
if (openingFence) {
|
|
3005
3172
|
decisions.push(...pendingFencedDecisions);
|
|
3006
3173
|
}
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
}
|
|
3016
|
-
return {
|
|
3174
|
+
const [repeated] = decisions;
|
|
3175
|
+
if (repeated !== undefined) {
|
|
3176
|
+
return repeated === decision ? {
|
|
3177
|
+
category: "duplicate-decision",
|
|
3178
|
+
markdown: text,
|
|
3179
|
+
message: "Advisor gate response contains duplicate decision lines.",
|
|
3180
|
+
ok: false
|
|
3181
|
+
} : {
|
|
3017
3182
|
category: "contradictory-decision",
|
|
3018
3183
|
markdown: text,
|
|
3019
3184
|
message: "Advisor gate response contains contradictory decision lines.",
|
|
@@ -3033,6 +3198,35 @@ var adviceForGateText = (result) => `**Decision: ${result.decision}**
|
|
|
3033
3198
|
|
|
3034
3199
|
${result.markdown}`;
|
|
3035
3200
|
|
|
3201
|
+
// src/tools/model-access.ts
|
|
3202
|
+
var currentModelRef = (ctx) => {
|
|
3203
|
+
const { model } = ctx;
|
|
3204
|
+
return model ? `${model.provider}/${model.id}` : undefined;
|
|
3205
|
+
};
|
|
3206
|
+
var advisorModelAccess = (ctx) => {
|
|
3207
|
+
const modelRef = currentModelRef(ctx);
|
|
3208
|
+
if (advisorModelWhitelistRef.length === 0) {
|
|
3209
|
+
return modelRef ? { allowed: true, modelRef } : { allowed: true };
|
|
3210
|
+
}
|
|
3211
|
+
if (modelRef && advisorModelWhitelistRef.includes(modelRef)) {
|
|
3212
|
+
return { allowed: true, modelRef };
|
|
3213
|
+
}
|
|
3214
|
+
const current = modelRef ?? "no current model";
|
|
3215
|
+
const denial = {
|
|
3216
|
+
allowed: false,
|
|
3217
|
+
reason: `Advisor calls are restricted to the configured model whitelist (${advisorModelWhitelistRef.join(", ")}). Current model: ${current}.`
|
|
3218
|
+
};
|
|
3219
|
+
if (modelRef) {
|
|
3220
|
+
denial.modelRef = modelRef;
|
|
3221
|
+
}
|
|
3222
|
+
return denial;
|
|
3223
|
+
};
|
|
3224
|
+
var advisorModelIsAllowed = (ctx) => advisorModelAccess(ctx).allowed;
|
|
3225
|
+
var advisorModelAccessReason = (ctx) => {
|
|
3226
|
+
const access = advisorModelAccess(ctx);
|
|
3227
|
+
return access.allowed ? undefined : access.reason;
|
|
3228
|
+
};
|
|
3229
|
+
|
|
3036
3230
|
// src/tools/consultation.ts
|
|
3037
3231
|
class AdvisorNoAdviceError extends Error {
|
|
3038
3232
|
constructor() {
|
|
@@ -3046,6 +3240,10 @@ ${item.text}
|
|
|
3046
3240
|
var collectAdvisorResponse = async (options) => {
|
|
3047
3241
|
const { ctx, question, signal, systemPrompt } = options;
|
|
3048
3242
|
loadConfig(ctx);
|
|
3243
|
+
const accessReason = advisorModelAccessReason(ctx);
|
|
3244
|
+
if (accessReason) {
|
|
3245
|
+
throw new Error(accessReason);
|
|
3246
|
+
}
|
|
3049
3247
|
const resolved = await resolveConfiguredModel(ctx, advisorRef, "Advisor");
|
|
3050
3248
|
const context = await assembleConsultationContext(options);
|
|
3051
3249
|
const outboundQuestion = advisorRedactSecretsRef && question !== undefined ? redactSecrets(question) : question;
|
|
@@ -3072,17 +3270,20 @@ var collectAdvisorResponse = async (options) => {
|
|
|
3072
3270
|
if (!markdown.trim()) {
|
|
3073
3271
|
throw new AdvisorNoAdviceError;
|
|
3074
3272
|
}
|
|
3075
|
-
|
|
3076
|
-
draftBytes: context.draftText ? Buffer.byteLength(context.draftText, "
|
|
3273
|
+
const response = {
|
|
3274
|
+
draftBytes: context.draftText ? Buffer.byteLength(context.draftText, "utf-8") : undefined,
|
|
3077
3275
|
markdown,
|
|
3078
3276
|
model: advisorRef,
|
|
3079
3277
|
preferenceBytes: context.preferences?.bytes,
|
|
3080
3278
|
thinkingText: streamed.thinking,
|
|
3081
3279
|
trackedBytes: context.tracked.reduce((sum, item) => sum + item.bytes, 0) || undefined,
|
|
3082
3280
|
untrackedBytes: context.untracked.reduce((sum, item) => sum + item.bytes, 0) || undefined,
|
|
3083
|
-
usage: streamed.usage
|
|
3084
|
-
...context.scout ? { scout: context.scout } : {}
|
|
3281
|
+
usage: streamed.usage
|
|
3085
3282
|
};
|
|
3283
|
+
if (context.scout) {
|
|
3284
|
+
response.scout = context.scout;
|
|
3285
|
+
}
|
|
3286
|
+
return response;
|
|
3086
3287
|
};
|
|
3087
3288
|
var consultAdvisor = async (ctx, question, signal, onChunk, trigger = "executor-requested", gitContext, draft, includeUntracked, includeTracked, onScout, currentInvocationId) => {
|
|
3088
3289
|
const result = await collectAdvisorResponse({
|
|
@@ -3164,8 +3365,8 @@ var settingsIdentity = (path) => {
|
|
|
3164
3365
|
};
|
|
3165
3366
|
var readHideThinking = (path) => {
|
|
3166
3367
|
try {
|
|
3167
|
-
const parsed = JSON.parse(readFileSync2(path, "
|
|
3168
|
-
return
|
|
3368
|
+
const parsed = JSON.parse(readFileSync2(path, "utf-8"));
|
|
3369
|
+
return isRecord(parsed) && parsed.hideThinkingBlock === true;
|
|
3169
3370
|
} catch {
|
|
3170
3371
|
return false;
|
|
3171
3372
|
}
|
|
@@ -3201,6 +3402,9 @@ var SPINNER_FRAMES = [
|
|
|
3201
3402
|
];
|
|
3202
3403
|
var THINKING_PREFIX = " ";
|
|
3203
3404
|
var THINKING_PREFIX_WIDTH = visibleWidth(THINKING_PREFIX);
|
|
3405
|
+
var noop = () => {
|
|
3406
|
+
return;
|
|
3407
|
+
};
|
|
3204
3408
|
|
|
3205
3409
|
class ThinkingMarkdown {
|
|
3206
3410
|
markdown;
|
|
@@ -3222,18 +3426,14 @@ class ThinkingMarkdown {
|
|
|
3222
3426
|
this.markdown.invalidate();
|
|
3223
3427
|
}
|
|
3224
3428
|
}
|
|
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);
|
|
3429
|
+
var hiddenThinkingLabel = (theme) => {
|
|
3430
|
+
const label = theme.fg("thinkingText", `${THINKING_PREFIX}Thinking…`);
|
|
3431
|
+
return {
|
|
3432
|
+
invalidate: noop,
|
|
3433
|
+
render: () => [label]
|
|
3434
|
+
};
|
|
3435
|
+
};
|
|
3436
|
+
var renderThinkingMarkdown = (thinking, theme) => piHideThinkingEnabled() ? hiddenThinkingLabel(theme) : new ThinkingMarkdown(thinking, theme);
|
|
3237
3437
|
var resolveAdvisorRequest = (question) => question?.trim() || undefined;
|
|
3238
3438
|
var renderAdvisorCallBox = (question, theme) => {
|
|
3239
3439
|
const box = new Box(1, 1, (text) => theme.bg("customMessageBg", text));
|
|
@@ -3244,7 +3444,7 @@ ${theme.fg("dim", ` ${question}`)}` : `${label} ${title}`, 0, 0));
|
|
|
3244
3444
|
return box;
|
|
3245
3445
|
};
|
|
3246
3446
|
var COLLAPSED_ADVICE_LINES = 12;
|
|
3247
|
-
var SOUND_VERDICT = /^Verdict:\s*sound
|
|
3447
|
+
var SOUND_VERDICT = /^Verdict:\s*sound$/u;
|
|
3248
3448
|
var hasSoundVerdict = (advice) => SOUND_VERDICT.test((advice.split(`
|
|
3249
3449
|
`).find((line) => line.trim()) ?? "").trim());
|
|
3250
3450
|
var renderAdvisorResponseHeader = (sound, theme) => sound ? theme.fg("accent", theme.bold("◆ ADVISOR · SOUND")) : theme.fg("warning", theme.bold("◆ ADVISOR RESPONSE"));
|
|
@@ -3370,7 +3570,21 @@ var scoutTitle = (scout, frame) => {
|
|
|
3370
3570
|
if (scout.status === "cancelled") {
|
|
3371
3571
|
return "◆ SCOUT · CANCELLED";
|
|
3372
3572
|
}
|
|
3373
|
-
return "◆ SCOUT · FALLBACK";
|
|
3573
|
+
return "◆ SCOUT · FALLBACK";
|
|
3574
|
+
};
|
|
3575
|
+
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`}`;
|
|
3576
|
+
var scoutExpandedLines = (scout, expanded, theme) => {
|
|
3577
|
+
const lines = [];
|
|
3578
|
+
if (expanded && scout.selectedLabels?.length) {
|
|
3579
|
+
lines.push(theme.fg("dim", ` Selected: ${scout.selectedLabels.join("; ")}`));
|
|
3580
|
+
}
|
|
3581
|
+
if (expanded && scout.synthesis) {
|
|
3582
|
+
lines.push(theme.fg("dim", ` Scout synthesis (untrusted inference): ${scout.synthesis}`));
|
|
3583
|
+
}
|
|
3584
|
+
if (expanded && scout.omittedBeforeScout) {
|
|
3585
|
+
lines.push(theme.fg("dim", ` ${scout.omittedBeforeScout} group(s) omitted before Scout`));
|
|
3586
|
+
}
|
|
3587
|
+
return lines;
|
|
3374
3588
|
};
|
|
3375
3589
|
var renderScoutDetails = (box, scout, expanded, theme) => {
|
|
3376
3590
|
const active = scout.status === "calling" || scout.status === "streaming";
|
|
@@ -3378,7 +3592,7 @@ var renderScoutDetails = (box, scout, expanded, theme) => {
|
|
|
3378
3592
|
const title = scoutTitle(scout, frame);
|
|
3379
3593
|
const lines = [
|
|
3380
3594
|
theme.fg(scout.status === "fallback" || scout.status === "cancelled" ? "warning" : "accent", theme.bold(title)),
|
|
3381
|
-
theme.fg("dim",
|
|
3595
|
+
theme.fg("dim", scoutSummaryLine(scout))
|
|
3382
3596
|
];
|
|
3383
3597
|
if (getAdvisorSettings().showUsageDetails) {
|
|
3384
3598
|
const usage = formatAdvisorUsage(scout.usage);
|
|
@@ -3395,16 +3609,7 @@ var renderScoutDetails = (box, scout, expanded, theme) => {
|
|
|
3395
3609
|
if (thinking.trim()) {
|
|
3396
3610
|
box.addChild(renderThinkingMarkdown(thinking, theme));
|
|
3397
3611
|
}
|
|
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
|
-
}
|
|
3612
|
+
const expandedLines = scoutExpandedLines(scout, expanded, theme);
|
|
3408
3613
|
if (expandedLines.length > 0) {
|
|
3409
3614
|
box.addChild(new Text2(expandedLines.join(`
|
|
3410
3615
|
`), 0, 0));
|
|
@@ -3434,6 +3639,31 @@ var addJevUsage = (totals, usage) => {
|
|
|
3434
3639
|
totals.inputTokens += usage.inputTokens;
|
|
3435
3640
|
totals.outputTokens += usage.outputTokens;
|
|
3436
3641
|
};
|
|
3642
|
+
var savingsLine = (costs, skipped) => {
|
|
3643
|
+
if (costs.length === 0) {
|
|
3644
|
+
return "Estimated saving from skips: unavailable — no observed consultation cost this session";
|
|
3645
|
+
}
|
|
3646
|
+
const mean = costs.reduce((sum, cost) => sum + cost, 0) / costs.length;
|
|
3647
|
+
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`;
|
|
3648
|
+
};
|
|
3649
|
+
var formatJevTokens = (usage) => `↑${formatTokenCount(usage.inputTokens + usage.outputTokens)}`;
|
|
3650
|
+
var markdownCosts = (invocations) => invocations.filter((item) => item.kind === "markdown" && typeof item.cost === "number").map((item) => item.cost);
|
|
3651
|
+
var filterLine = (filter) => {
|
|
3652
|
+
const head = `${filter.screened} screened (${filter.allowed} allowed, ${filter.skipped} skipped${filter.repeatSkipped > 0 ? ` [${filter.repeatSkipped} repeat]` : ""})`;
|
|
3653
|
+
const parts = [head];
|
|
3654
|
+
if (filter.overrides > 0) {
|
|
3655
|
+
parts.push(`${filter.overrides} override${filter.overrides === 1 ? "" : "s"}`);
|
|
3656
|
+
}
|
|
3657
|
+
if (filter.failures > 0) {
|
|
3658
|
+
parts.push(`${filter.failures} failure${filter.failures === 1 ? "" : "s"}`);
|
|
3659
|
+
}
|
|
3660
|
+
return `Jev filter: ${parts.join(", ")}`;
|
|
3661
|
+
};
|
|
3662
|
+
var gateLine = (gate, invocations) => {
|
|
3663
|
+
const consultationCosts = invocations.filter((item) => item.trigger === "turn-gate" && typeof item.cost === "number").map((item) => item.cost);
|
|
3664
|
+
const gateSpend = consultationCosts.reduce((sum, cost) => sum + cost, 0);
|
|
3665
|
+
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)})`;
|
|
3666
|
+
};
|
|
3437
3667
|
|
|
3438
3668
|
class AdvisorJevLedgerState {
|
|
3439
3669
|
#ledger = freshJevLedger();
|
|
@@ -3455,10 +3685,11 @@ class AdvisorJevLedgerState {
|
|
|
3455
3685
|
if (repeat) {
|
|
3456
3686
|
this.#ledger.filter.repeatSkipped += 1;
|
|
3457
3687
|
}
|
|
3458
|
-
|
|
3459
|
-
|
|
3460
|
-
|
|
3461
|
-
}
|
|
3688
|
+
const skip = { turn };
|
|
3689
|
+
if (normalizedQuestion) {
|
|
3690
|
+
skip.normalizedQuestion = normalizedQuestion;
|
|
3691
|
+
}
|
|
3692
|
+
this.#lastSkip = skip;
|
|
3462
3693
|
}
|
|
3463
3694
|
recordFilterOverride() {
|
|
3464
3695
|
this.#ledger.filter.overrides += 1;
|
|
@@ -3492,20 +3723,19 @@ class AdvisorJevLedgerState {
|
|
|
3492
3723
|
if (filter.overrides > 0) {
|
|
3493
3724
|
parts.push(`${filter.overrides} override${filter.overrides === 1 ? "" : "s"}`);
|
|
3494
3725
|
}
|
|
3495
|
-
lines.push(`Consultation dedup: ${parts.join(", ")}
|
|
3496
|
-
lines.push(this.#savingsLine(this.#markdownCosts(invocations), filter.skipped));
|
|
3726
|
+
lines.push(`Consultation dedup: ${parts.join(", ")}`, savingsLine(markdownCosts(invocations), filter.skipped));
|
|
3497
3727
|
} else if (this.#filterActive()) {
|
|
3498
|
-
lines.push(
|
|
3728
|
+
lines.push(filterLine(filter));
|
|
3499
3729
|
const jevTokens = usage.inputTokens + usage.outputTokens;
|
|
3500
3730
|
if (jevTokens > 0) {
|
|
3501
|
-
lines.push(`Jev cost: ${
|
|
3731
|
+
lines.push(`Jev cost: ${formatJevTokens(usage)} tokens · $${usage.cost.toFixed(4)} (input only; output free)`);
|
|
3502
3732
|
}
|
|
3503
3733
|
if (filter.skipped > 0) {
|
|
3504
|
-
lines.push(
|
|
3734
|
+
lines.push(savingsLine(markdownCosts(invocations), filter.skipped));
|
|
3505
3735
|
}
|
|
3506
3736
|
}
|
|
3507
3737
|
if (gate.checks > 0 || gate.consultations > 0) {
|
|
3508
|
-
lines.push(
|
|
3738
|
+
lines.push(gateLine(gate, invocations));
|
|
3509
3739
|
}
|
|
3510
3740
|
return lines;
|
|
3511
3741
|
}
|
|
@@ -3513,39 +3743,10 @@ class AdvisorJevLedgerState {
|
|
|
3513
3743
|
const { filter } = this.#ledger;
|
|
3514
3744
|
return filter.screened > 0 || filter.overrides > 0 || filter.failures > 0;
|
|
3515
3745
|
}
|
|
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
3746
|
}
|
|
3546
3747
|
|
|
3547
3748
|
// src/session-state.ts
|
|
3548
|
-
var WHITESPACE = /\s
|
|
3749
|
+
var WHITESPACE = /\s/u;
|
|
3549
3750
|
var TIMESTAMP_KEYS = new Set([
|
|
3550
3751
|
"createdat",
|
|
3551
3752
|
"date",
|
|
@@ -3555,7 +3756,7 @@ var TIMESTAMP_KEYS = new Set([
|
|
|
3555
3756
|
"updatedat"
|
|
3556
3757
|
]);
|
|
3557
3758
|
var REQUEST_ID_KEYS = new Set(["correlationid", "requestid", "traceid"]);
|
|
3558
|
-
var normalizedKey = (key) => key.
|
|
3759
|
+
var normalizedKey = (key) => key.replaceAll(/[-_]/gu, "").toLowerCase();
|
|
3559
3760
|
var isVolatileKey = (key, keys) => keys.has(normalizedKey(key));
|
|
3560
3761
|
var normalizeShellWhitespace = (command) => {
|
|
3561
3762
|
let result = "";
|
|
@@ -3588,10 +3789,10 @@ var normalizeShellWhitespace = (command) => {
|
|
|
3588
3789
|
}
|
|
3589
3790
|
return result;
|
|
3590
3791
|
};
|
|
3591
|
-
var normalizeString = (value) => value.
|
|
3792
|
+
var normalizeString = (value) => value.replaceAll(/\/(?:private\/)?tmp\/[^\s/]+/gu, "/tmp/<temporary>").replaceAll(/\/var\/folders\/[^\s/]+/gu, "/var/folders/<temporary>");
|
|
3592
3793
|
var normalizeToolInput = (toolName, input) => {
|
|
3593
3794
|
const visit = (value, key) => {
|
|
3594
|
-
if (
|
|
3795
|
+
if (isString(value)) {
|
|
3595
3796
|
if (key && isVolatileKey(key, TIMESTAMP_KEYS)) {
|
|
3596
3797
|
return "<timestamp>";
|
|
3597
3798
|
}
|
|
@@ -3604,9 +3805,8 @@ var normalizeToolInput = (toolName, input) => {
|
|
|
3604
3805
|
if (Array.isArray(value)) {
|
|
3605
3806
|
return value.map((item) => visit(item));
|
|
3606
3807
|
}
|
|
3607
|
-
if (value
|
|
3608
|
-
|
|
3609
|
-
return Object.fromEntries(Object.keys(record).sort().map((childKey) => [childKey, visit(record[childKey], childKey)]));
|
|
3808
|
+
if (isRecordOf(value)) {
|
|
3809
|
+
return Object.fromEntries(Object.keys(value).toSorted().map((childKey) => [childKey, visit(value[childKey], childKey)]));
|
|
3610
3810
|
}
|
|
3611
3811
|
return value;
|
|
3612
3812
|
};
|
|
@@ -3713,11 +3913,11 @@ class AdvisorSessionState {
|
|
|
3713
3913
|
addAdvisorUsage(this.#usage.totals, record.usage);
|
|
3714
3914
|
}
|
|
3715
3915
|
issueAdvice(id, advice, trigger, draft = false, normalizedQuestion) {
|
|
3716
|
-
|
|
3717
|
-
|
|
3718
|
-
|
|
3719
|
-
|
|
3720
|
-
|
|
3916
|
+
const issued = { advice, trigger };
|
|
3917
|
+
if (normalizedQuestion) {
|
|
3918
|
+
issued.normalizedQuestion = normalizedQuestion;
|
|
3919
|
+
}
|
|
3920
|
+
this.#ledger.issued.set(id, issued);
|
|
3721
3921
|
this.#ledger.lastAdvice = advice;
|
|
3722
3922
|
if (draft) {
|
|
3723
3923
|
this.#ledger.draftConsultations += 1;
|
|
@@ -3740,9 +3940,9 @@ class AdvisorSessionState {
|
|
|
3740
3940
|
return false;
|
|
3741
3941
|
}
|
|
3742
3942
|
const mentioned = paths.every((path) => {
|
|
3743
|
-
const escaped = path.
|
|
3744
|
-
const boundary =
|
|
3745
|
-
return new RegExp(boundary).test(advice);
|
|
3943
|
+
const escaped = path.replaceAll(/[.*+?^${}()|[\]\\]/gu, "\\$&");
|
|
3944
|
+
const boundary = `(^|[\\s"'\`()\\[])${escaped}(?=$|[\\s"'\`),;:!?\\]]|\\.(?=\\s|$))`;
|
|
3945
|
+
return new RegExp(boundary, "u").test(advice);
|
|
3746
3946
|
});
|
|
3747
3947
|
if (!mentioned) {
|
|
3748
3948
|
return false;
|
|
@@ -3867,6 +4067,16 @@ var notify = (ctx, message, level) => {
|
|
|
3867
4067
|
ctx.ui.notify(message, level);
|
|
3868
4068
|
}
|
|
3869
4069
|
};
|
|
4070
|
+
var reportManualBudgetExhausted = (ctx) => {
|
|
4071
|
+
const message = "Advisor call budget exhausted for this session.";
|
|
4072
|
+
notify(ctx, message, "warning");
|
|
4073
|
+
notifyHerdrAdvisorFailure("Advisor budget exhausted", message);
|
|
4074
|
+
};
|
|
4075
|
+
var requestManualRender = (ctx) => {
|
|
4076
|
+
if (ctx.hasUI) {
|
|
4077
|
+
ctx.ui.setStatus("advisor-manual", undefined);
|
|
4078
|
+
}
|
|
4079
|
+
};
|
|
3870
4080
|
|
|
3871
4081
|
class CommandRuntime {
|
|
3872
4082
|
advisorSessionState;
|
|
@@ -3874,7 +4084,9 @@ class CommandRuntime {
|
|
|
3874
4084
|
manualProgress = new Map;
|
|
3875
4085
|
manualProgressTimers = new Map;
|
|
3876
4086
|
pi;
|
|
4087
|
+
reportManualBudgetExhausted = reportManualBudgetExhausted;
|
|
3877
4088
|
requestAdvisor;
|
|
4089
|
+
requestManualRender = requestManualRender;
|
|
3878
4090
|
scoutStatus;
|
|
3879
4091
|
manualProgressSequence = 0;
|
|
3880
4092
|
pendingExecutorModelRef;
|
|
@@ -3883,7 +4095,7 @@ class CommandRuntime {
|
|
|
3883
4095
|
this.pi = pi;
|
|
3884
4096
|
this.advisorSessionState = dependencies.sessionState ?? advisorSessionState;
|
|
3885
4097
|
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
|
|
4098
|
+
this.requestAdvisor = dependencies.consult ?? ((ctx, question, signal, onChunk, onScout, gitContext) => consultAdvisor(ctx, question, signal, onChunk, "manual", gitContext, undefined, undefined, undefined, onScout));
|
|
3887
4099
|
}
|
|
3888
4100
|
flowEnabled() {
|
|
3889
4101
|
return this.pi.getActiveTools().includes("ask_advisor");
|
|
@@ -3905,16 +4117,6 @@ class CommandRuntime {
|
|
|
3905
4117
|
ctx.ui.setStatus("advisor-usage", getAdvisorSettings().showUsageFooter ? this.advisorSessionState.usageStatus() : undefined);
|
|
3906
4118
|
}
|
|
3907
4119
|
}
|
|
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
4120
|
}
|
|
3919
4121
|
var createCommandRuntime = (pi, dependencies = {}) => new CommandRuntime(pi, dependencies);
|
|
3920
4122
|
|
|
@@ -3983,7 +4185,7 @@ var resolveActivationModels = async (runtime, ctx) => {
|
|
|
3983
4185
|
if (!(advisorAuth.ok && advisorAuth.apiKey)) {
|
|
3984
4186
|
return { error: `No API key for Advisor ${advisorRef}` };
|
|
3985
4187
|
}
|
|
3986
|
-
if (!await runtime.setExecutorModel(executor)) {
|
|
4188
|
+
if (!isMarkedSubagent() && !await runtime.setExecutorModel(executor)) {
|
|
3987
4189
|
return { error: `No API key for Executor ${executorRef}` };
|
|
3988
4190
|
}
|
|
3989
4191
|
return {};
|
|
@@ -4029,7 +4231,7 @@ var activateAdvisor = async (runtime, args, ctx, announce = true) => {
|
|
|
4029
4231
|
saveConfig(ctx, { persistAdvisor: true, persistExecutor: true });
|
|
4030
4232
|
}
|
|
4031
4233
|
runtime.pendingExecutorModelRef = undefined;
|
|
4032
|
-
if (executorEffortRef) {
|
|
4234
|
+
if (executorEffortRef && !isMarkedSubagent()) {
|
|
4033
4235
|
runtime.pi.setThinkingLevel(executorEffortRef);
|
|
4034
4236
|
}
|
|
4035
4237
|
if (!runtime.flowEnabled()) {
|
|
@@ -4040,20 +4242,22 @@ var activateAdvisor = async (runtime, args, ctx, announce = true) => {
|
|
|
4040
4242
|
]);
|
|
4041
4243
|
}
|
|
4042
4244
|
if (announce) {
|
|
4245
|
+
const activeExecutorRef = effectiveExecutorRef(ctx);
|
|
4246
|
+
const activeExecutorEffort = effectiveExecutorEffort(ctx);
|
|
4043
4247
|
notify(ctx, `${ADVISOR_ACTIVATION_EXPLANATION}
|
|
4044
4248
|
|
|
4045
|
-
Advisor flow ready — Executor: ${
|
|
4249
|
+
Advisor flow ready — Executor: ${activeExecutorRef} (thinking: ${activeExecutorEffort || "default"}) · Advisor: ${advisorRef} (thinking: ${advisorEffortRef || "default"})`, "info");
|
|
4046
4250
|
}
|
|
4047
4251
|
};
|
|
4048
4252
|
|
|
4049
4253
|
// src/commands/lifecycle.ts
|
|
4050
|
-
var registerCommandLifecycle = (runtime,
|
|
4254
|
+
var registerCommandLifecycle = (runtime, activateAdvisor2) => {
|
|
4051
4255
|
runtime.pi.on("session_start", async (_event, ctx) => {
|
|
4052
4256
|
runtime.pendingExecutorModelRef = undefined;
|
|
4053
4257
|
try {
|
|
4054
4258
|
loadConfig(ctx);
|
|
4055
4259
|
if (alwaysOnRef) {
|
|
4056
|
-
await
|
|
4260
|
+
await activateAdvisor2("", ctx, false);
|
|
4057
4261
|
}
|
|
4058
4262
|
} catch (error) {
|
|
4059
4263
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -4061,7 +4265,7 @@ var registerCommandLifecycle = (runtime, activateAdvisor) => {
|
|
|
4061
4265
|
}
|
|
4062
4266
|
});
|
|
4063
4267
|
runtime.pi.on("model_select", (event, ctx) => {
|
|
4064
|
-
if (event.source !== "set" || runtime.suppressModelSelectionSync) {
|
|
4268
|
+
if (event.source !== "set" || runtime.suppressModelSelectionSync || isMarkedSubagent()) {
|
|
4065
4269
|
return;
|
|
4066
4270
|
}
|
|
4067
4271
|
const selected = `${event.model.provider}/${event.model.id}`;
|
|
@@ -4102,11 +4306,7 @@ var registerCommandLifecycle = (runtime, activateAdvisor) => {
|
|
|
4102
4306
|
};
|
|
4103
4307
|
|
|
4104
4308
|
// src/ui/manual-dialog.ts
|
|
4105
|
-
import {
|
|
4106
|
-
Editor,
|
|
4107
|
-
Key,
|
|
4108
|
-
matchesKey
|
|
4109
|
-
} from "@earendil-works/pi-tui";
|
|
4309
|
+
import { Editor, Key, matchesKey } from "@earendil-works/pi-tui";
|
|
4110
4310
|
|
|
4111
4311
|
// src/ui/manual-dialog-render.ts
|
|
4112
4312
|
import {
|
|
@@ -4202,6 +4402,7 @@ var renderManualAdvisorDialog = (view) => {
|
|
|
4202
4402
|
|
|
4203
4403
|
// src/ui/manual-dialog.ts
|
|
4204
4404
|
var TUI_INPUT_TAB = ["tui", "input", "tab"].join(".");
|
|
4405
|
+
var isShiftTab = (keyData) => matchesKey(keyData, Key.shift("tab"));
|
|
4205
4406
|
|
|
4206
4407
|
class ManualAdvisorDialog {
|
|
4207
4408
|
options;
|
|
@@ -4224,7 +4425,7 @@ class ManualAdvisorDialog {
|
|
|
4224
4425
|
}
|
|
4225
4426
|
constructor(options) {
|
|
4226
4427
|
this.options = options;
|
|
4227
|
-
this.gitLevels = GIT_CONTEXT_LEVELS.filter((level) => clampGitContextLevel(level, options.gitContext) === level).
|
|
4428
|
+
this.gitLevels = GIT_CONTEXT_LEVELS.filter((level) => clampGitContextLevel(level, options.gitContext) === level).toReversed();
|
|
4228
4429
|
this.gitIndex = Math.max(0, this.gitLevels.indexOf(options.gitContext));
|
|
4229
4430
|
const editorTheme = {
|
|
4230
4431
|
borderColor: (text) => options.theme.fg("border", text),
|
|
@@ -4256,7 +4457,7 @@ class ManualAdvisorDialog {
|
|
|
4256
4457
|
this.cancel();
|
|
4257
4458
|
return;
|
|
4258
4459
|
}
|
|
4259
|
-
if (
|
|
4460
|
+
if (isShiftTab(keyData)) {
|
|
4260
4461
|
this.changeFocus(-1);
|
|
4261
4462
|
return;
|
|
4262
4463
|
}
|
|
@@ -4264,19 +4465,12 @@ class ManualAdvisorDialog {
|
|
|
4264
4465
|
this.changeFocus(1);
|
|
4265
4466
|
return;
|
|
4266
4467
|
}
|
|
4267
|
-
|
|
4268
|
-
|
|
4269
|
-
|
|
4270
|
-
|
|
4271
|
-
|
|
4272
|
-
|
|
4273
|
-
return;
|
|
4274
|
-
case "actions":
|
|
4275
|
-
this.handleActionInput(keyData);
|
|
4276
|
-
return;
|
|
4277
|
-
default:
|
|
4278
|
-
return;
|
|
4279
|
-
}
|
|
4468
|
+
const handlers = {
|
|
4469
|
+
actions: (key) => this.handleActionInput(key),
|
|
4470
|
+
editor: (key) => this.handleEditorInput(key),
|
|
4471
|
+
git: (key) => this.handleGitInput(key)
|
|
4472
|
+
};
|
|
4473
|
+
handlers[this.focusTarget](keyData);
|
|
4280
4474
|
}
|
|
4281
4475
|
render(width) {
|
|
4282
4476
|
const renderWidth = Math.max(1, Math.floor(width));
|
|
@@ -4384,9 +4578,6 @@ class ManualAdvisorDialog {
|
|
|
4384
4578
|
isTab(keyData) {
|
|
4385
4579
|
return this.matches(keyData, TUI_INPUT_TAB, Key.tab) && !matchesKey(keyData, Key.shift("tab"));
|
|
4386
4580
|
}
|
|
4387
|
-
isShiftTab(keyData) {
|
|
4388
|
-
return matchesKey(keyData, Key.shift("tab"));
|
|
4389
|
-
}
|
|
4390
4581
|
isCancel(keyData) {
|
|
4391
4582
|
return matchesKey(keyData, Key.escape) || this.options.keybindings.matches(keyData, "tui.select.cancel");
|
|
4392
4583
|
}
|
|
@@ -4398,10 +4589,13 @@ class ManualAdvisorDialog {
|
|
|
4398
4589
|
return;
|
|
4399
4590
|
}
|
|
4400
4591
|
this.state.completed = true;
|
|
4401
|
-
|
|
4402
|
-
gitContext: this.gitLevels[this.gitIndex] ?? "off"
|
|
4403
|
-
|
|
4404
|
-
|
|
4592
|
+
const request = {
|
|
4593
|
+
gitContext: this.gitLevels[this.gitIndex] ?? "off"
|
|
4594
|
+
};
|
|
4595
|
+
if (message) {
|
|
4596
|
+
request.message = message;
|
|
4597
|
+
}
|
|
4598
|
+
this.options.onSubmit(request);
|
|
4405
4599
|
}
|
|
4406
4600
|
cancel() {
|
|
4407
4601
|
if (this.state.completed) {
|
|
@@ -4412,31 +4606,31 @@ class ManualAdvisorDialog {
|
|
|
4412
4606
|
}
|
|
4413
4607
|
}
|
|
4414
4608
|
|
|
4415
|
-
// src/jev/
|
|
4416
|
-
class
|
|
4609
|
+
// src/jev/failure.ts
|
|
4610
|
+
class JevFailureError extends Error {
|
|
4417
4611
|
category;
|
|
4418
4612
|
constructor(category, message) {
|
|
4419
4613
|
super(message);
|
|
4420
|
-
this.name = "
|
|
4614
|
+
this.name = "JevFailureError";
|
|
4421
4615
|
this.category = category;
|
|
4422
4616
|
}
|
|
4423
4617
|
}
|
|
4618
|
+
|
|
4619
|
+
// src/jev/client.ts
|
|
4424
4620
|
var ENDPOINTS = {
|
|
4425
4621
|
openrouter: "https://openrouter.ai/api/alpha/decisions",
|
|
4426
4622
|
typesafe: "https://api.typesafe.ai/v1/systemone"
|
|
4427
4623
|
};
|
|
4624
|
+
var range = (from, to) => Array.from({ length: to - from + 1 }, (_, index) => from + index);
|
|
4428
4625
|
var RETRYABLE_STATUSES = new Set([408, 429, ...range(500, 599)]);
|
|
4429
4626
|
var RETRY_BACKOFF_MS = 250;
|
|
4430
4627
|
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;
|
|
4628
|
+
var finiteTokens = (value) => isNumber(value) && Number.isFinite(value) && value >= 0 ? value : 0;
|
|
4435
4629
|
var errorDetail = (error) => {
|
|
4436
|
-
if (
|
|
4630
|
+
if (isString(error)) {
|
|
4437
4631
|
return error;
|
|
4438
4632
|
}
|
|
4439
|
-
if (error &&
|
|
4633
|
+
if (isRecordOf(error) && "message" in error) {
|
|
4440
4634
|
return String(error.message);
|
|
4441
4635
|
}
|
|
4442
4636
|
return "";
|
|
@@ -4450,7 +4644,22 @@ var statusCategory = (status) => {
|
|
|
4450
4644
|
}
|
|
4451
4645
|
return "error";
|
|
4452
4646
|
};
|
|
4647
|
+
var isObjectLike = (value) => typeof value === "object";
|
|
4453
4648
|
var openRouterModelId = (model) => model.includes("/") ? model : `~typesafe/${model}`;
|
|
4649
|
+
var connectionFailure = (error) => {
|
|
4650
|
+
const message = redactSecrets(error instanceof Error ? error.message : String(error));
|
|
4651
|
+
return {
|
|
4652
|
+
failure: new JevFailureError("network", `Jev connection failed: ${message}`)
|
|
4653
|
+
};
|
|
4654
|
+
};
|
|
4655
|
+
var sleepWithAbort = (signal, ms) => new Promise((resolve2) => {
|
|
4656
|
+
const timer = setTimeout(resolve2, ms);
|
|
4657
|
+
timer.unref?.();
|
|
4658
|
+
signal.addEventListener("abort", () => {
|
|
4659
|
+
clearTimeout(timer);
|
|
4660
|
+
resolve2();
|
|
4661
|
+
}, { once: true });
|
|
4662
|
+
});
|
|
4454
4663
|
|
|
4455
4664
|
class JevClient {
|
|
4456
4665
|
#apiKey;
|
|
@@ -4499,7 +4708,7 @@ class JevClient {
|
|
|
4499
4708
|
if (!outcome.retryable || attempt >= MAX_ATTEMPTS) {
|
|
4500
4709
|
break;
|
|
4501
4710
|
}
|
|
4502
|
-
await
|
|
4711
|
+
await sleepWithAbort(deadline.signal, RETRY_BACKOFF_MS);
|
|
4503
4712
|
if (deadline.signal.aborted) {
|
|
4504
4713
|
break;
|
|
4505
4714
|
}
|
|
@@ -4509,11 +4718,11 @@ class JevClient {
|
|
|
4509
4718
|
if (signal?.aborted && !deadlineHit) {
|
|
4510
4719
|
throw error;
|
|
4511
4720
|
}
|
|
4512
|
-
if (error instanceof
|
|
4721
|
+
if (error instanceof JevFailureError) {
|
|
4513
4722
|
throw error;
|
|
4514
4723
|
}
|
|
4515
4724
|
const message = redactSecrets(error instanceof Error ? error.message : String(error));
|
|
4516
|
-
throw deadlineHit ? new
|
|
4725
|
+
throw deadlineHit ? new JevFailureError("timeout", `Jev call exceeded its ${this.#timeoutMs} ms wall-time budget.`) : new JevFailureError("error", message);
|
|
4517
4726
|
} finally {
|
|
4518
4727
|
clearTimeout(timer);
|
|
4519
4728
|
signal?.removeEventListener("abort", abortFromCaller);
|
|
@@ -4524,7 +4733,7 @@ class JevClient {
|
|
|
4524
4733
|
return this.#result(outcome);
|
|
4525
4734
|
}
|
|
4526
4735
|
if (deadlineHit && outcome.failure?.category !== "auth") {
|
|
4527
|
-
throw new
|
|
4736
|
+
throw new JevFailureError("timeout", `Jev call exceeded its ${this.#timeoutMs} ms wall-time budget.`);
|
|
4528
4737
|
}
|
|
4529
4738
|
if (outcome.failure) {
|
|
4530
4739
|
throw outcome.failure;
|
|
@@ -4532,21 +4741,7 @@ class JevClient {
|
|
|
4532
4741
|
if (signal?.aborted) {
|
|
4533
4742
|
throw new Error("Jev call aborted by the caller.");
|
|
4534
4743
|
}
|
|
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
|
-
});
|
|
4744
|
+
throw new JevFailureError("error", "Jev call failed.");
|
|
4550
4745
|
}
|
|
4551
4746
|
async#attempt(body, signal) {
|
|
4552
4747
|
if (signal.aborted) {
|
|
@@ -4569,7 +4764,7 @@ class JevClient {
|
|
|
4569
4764
|
}
|
|
4570
4765
|
return {
|
|
4571
4766
|
retryable: true,
|
|
4572
|
-
...
|
|
4767
|
+
...connectionFailure(error)
|
|
4573
4768
|
};
|
|
4574
4769
|
}
|
|
4575
4770
|
if (response.ok) {
|
|
@@ -4581,23 +4776,17 @@ class JevClient {
|
|
|
4581
4776
|
retryable: RETRYABLE_STATUSES.has(response.status)
|
|
4582
4777
|
};
|
|
4583
4778
|
}
|
|
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
4779
|
async#failureFromStatus(response) {
|
|
4591
4780
|
let detail = "";
|
|
4592
4781
|
try {
|
|
4593
4782
|
const parsed = await response.json();
|
|
4594
|
-
const error = parsed
|
|
4783
|
+
const error = isRecord(parsed) ? parsed.error : undefined;
|
|
4595
4784
|
detail = errorDetail(error);
|
|
4596
4785
|
} catch {
|
|
4597
4786
|
detail = "";
|
|
4598
4787
|
}
|
|
4599
4788
|
const message = redactSecrets(`Jev ${this.#transportLabel()} request failed with HTTP ${response.status}${detail ? `: ${detail}` : ""}.`);
|
|
4600
|
-
return new
|
|
4789
|
+
return new JevFailureError(statusCategory(response.status), message);
|
|
4601
4790
|
}
|
|
4602
4791
|
#transportLabel() {
|
|
4603
4792
|
return this.#endpoint === ENDPOINTS.openrouter ? "OpenRouter" : "TypeSafe";
|
|
@@ -4608,23 +4797,25 @@ class JevClient {
|
|
|
4608
4797
|
parsed = await response.json();
|
|
4609
4798
|
} catch (error) {
|
|
4610
4799
|
return {
|
|
4611
|
-
failure: new
|
|
4800
|
+
failure: new JevFailureError("malformed", `Jev response was not JSON: ${redactSecrets(error instanceof Error ? error.message : String(error))}`)
|
|
4612
4801
|
};
|
|
4613
4802
|
}
|
|
4614
|
-
|
|
4615
|
-
if (!record || typeof record !== "object" || !record.answers || typeof record.answers !== "object") {
|
|
4803
|
+
if (!isRecord(parsed) || !parsed.answers || !isObjectLike(parsed.answers)) {
|
|
4616
4804
|
return {
|
|
4617
|
-
failure: new
|
|
4805
|
+
failure: new JevFailureError("malformed", "Jev response did not include an answers object.")
|
|
4618
4806
|
};
|
|
4619
4807
|
}
|
|
4620
4808
|
return {
|
|
4621
|
-
answers:
|
|
4622
|
-
model:
|
|
4623
|
-
usage:
|
|
4809
|
+
answers: parsed.answers,
|
|
4810
|
+
model: isString(parsed.model) ? parsed.model : this.#model,
|
|
4811
|
+
usage: isRecordOf(parsed.usage) ? {
|
|
4812
|
+
input_tokens: parsed.usage.input_tokens,
|
|
4813
|
+
output_tokens: parsed.usage.output_tokens
|
|
4814
|
+
} : undefined
|
|
4624
4815
|
};
|
|
4625
4816
|
}
|
|
4626
4817
|
#result(outcome) {
|
|
4627
|
-
const usage = outcome
|
|
4818
|
+
const { usage } = outcome;
|
|
4628
4819
|
const inputTokens = finiteTokens(usage?.input_tokens);
|
|
4629
4820
|
const outputTokens = finiteTokens(usage?.output_tokens);
|
|
4630
4821
|
const price = this.#pricePerMtok ?? advisorJevPricePerMtokRef;
|
|
@@ -4663,12 +4854,15 @@ var TYPESAFE_KEY_NAME = "typesafe-api-key";
|
|
|
4663
4854
|
var TYPESAFE_KEY_CONFIG_FIELD = "typesafe_api_key";
|
|
4664
4855
|
var KEY_FILE_MODE = 384;
|
|
4665
4856
|
var keyFilePath = () => join4(getAgentDir3(), "typesafe_api_key");
|
|
4666
|
-
var runtimeSecrets = () =>
|
|
4857
|
+
var runtimeSecrets = () => {
|
|
4858
|
+
const bun = globalThis;
|
|
4859
|
+
return bun.Bun?.secrets;
|
|
4860
|
+
};
|
|
4667
4861
|
var normalizeKey = (value) => value?.trim() || undefined;
|
|
4668
4862
|
var readAdvisorJsonConfig = () => readExistingConfig(join4(getAgentDir3(), "advisor.json"));
|
|
4669
4863
|
var defaultReadFileStore = () => {
|
|
4670
4864
|
try {
|
|
4671
|
-
return normalizeKey(readFileSync3(keyFilePath(), "
|
|
4865
|
+
return normalizeKey(readFileSync3(keyFilePath(), "utf-8"));
|
|
4672
4866
|
} catch {
|
|
4673
4867
|
return;
|
|
4674
4868
|
}
|
|
@@ -4708,7 +4902,7 @@ var resolveTypeSafeKey = async (deps = {}) => {
|
|
|
4708
4902
|
}
|
|
4709
4903
|
const config = (deps.readAdvisorJson ?? readAdvisorJsonConfig)();
|
|
4710
4904
|
const staged = config[TYPESAFE_KEY_CONFIG_FIELD];
|
|
4711
|
-
if (
|
|
4905
|
+
if (isString(staged)) {
|
|
4712
4906
|
const fromConfig = normalizeKey(staged);
|
|
4713
4907
|
if (fromConfig) {
|
|
4714
4908
|
return { key: fromConfig, source: "advisor-json" };
|
|
@@ -4793,8 +4987,8 @@ var removeTypeSafeKeyFromAdvisorJson = () => {
|
|
|
4793
4987
|
if (!(TYPESAFE_KEY_CONFIG_FIELD in existing)) {
|
|
4794
4988
|
return { message: "No plaintext key in advisor.json.", ok: true };
|
|
4795
4989
|
}
|
|
4796
|
-
|
|
4797
|
-
writeFileSync2(path, `${JSON.stringify(
|
|
4990
|
+
const retained = Object.fromEntries(Object.entries(existing).filter(([key]) => key !== TYPESAFE_KEY_CONFIG_FIELD));
|
|
4991
|
+
writeFileSync2(path, `${JSON.stringify(retained, null, 2)}
|
|
4798
4992
|
`);
|
|
4799
4993
|
resetConfigCache();
|
|
4800
4994
|
return { message: "Plaintext key removed from advisor.json.", ok: true };
|
|
@@ -4836,15 +5030,14 @@ var screeningQuestions = {
|
|
|
4836
5030
|
type: "score"
|
|
4837
5031
|
}
|
|
4838
5032
|
};
|
|
4839
|
-
var NUMERIC_KEY_PATTERN = /^\d
|
|
4840
|
-
var
|
|
4841
|
-
var finiteNumber = (value) => typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
5033
|
+
var NUMERIC_KEY_PATTERN = /^\d+$/u;
|
|
5034
|
+
var finiteNumber = (value) => isNumber(value) && Number.isFinite(value) ? value : undefined;
|
|
4842
5035
|
var lowestStakesProbability = (answer) => {
|
|
4843
|
-
if (!
|
|
5036
|
+
if (!isRecordOf(answer)) {
|
|
4844
5037
|
return;
|
|
4845
5038
|
}
|
|
4846
|
-
const probabilities =
|
|
4847
|
-
const legend =
|
|
5039
|
+
const probabilities = isRecordOf(answer.probabilities) ? answer.probabilities : {};
|
|
5040
|
+
const legend = isRecordOf(answer.legend) ? answer.legend : undefined;
|
|
4848
5041
|
if (legend) {
|
|
4849
5042
|
const exact = Object.keys(legend).find((key) => legend[key] === STAKES_RUBRIC[0]);
|
|
4850
5043
|
if (exact) {
|
|
@@ -4855,12 +5048,17 @@ var lowestStakesProbability = (answer) => {
|
|
|
4855
5048
|
if (numericKeys.length === 0) {
|
|
4856
5049
|
return;
|
|
4857
5050
|
}
|
|
4858
|
-
|
|
5051
|
+
let [lowest] = numericKeys;
|
|
5052
|
+
for (const key of numericKeys) {
|
|
5053
|
+
if (Number(key) < Number(lowest)) {
|
|
5054
|
+
lowest = key;
|
|
5055
|
+
}
|
|
5056
|
+
}
|
|
4859
5057
|
return finiteNumber(probabilities[lowest]);
|
|
4860
5058
|
};
|
|
4861
|
-
var selfAnswerableNoul = (answer) =>
|
|
5059
|
+
var selfAnswerableNoul = (answer) => isRecordOf(answer) ? finiteNumber(answer.noul) : undefined;
|
|
4862
5060
|
var composeScreeningVerdict = (answers, { noulMargin, skipConfidence }) => {
|
|
4863
|
-
if (!
|
|
5061
|
+
if (!isRecordOf(answers)) {
|
|
4864
5062
|
return { skip: false };
|
|
4865
5063
|
}
|
|
4866
5064
|
const negligibleMass = lowestStakesProbability(answers.stakes);
|
|
@@ -4874,29 +5072,29 @@ var composeScreeningVerdict = (answers, { noulMargin, skipConfidence }) => {
|
|
|
4874
5072
|
};
|
|
4875
5073
|
};
|
|
4876
5074
|
var composeTurnGateVerdict = (answers, threshold) => {
|
|
4877
|
-
if (!
|
|
5075
|
+
if (!isRecordOf(answers)) {
|
|
4878
5076
|
return false;
|
|
4879
5077
|
}
|
|
4880
5078
|
const answer = answers.should_consult;
|
|
4881
|
-
const noul =
|
|
5079
|
+
const noul = isRecordOf(answer) ? finiteNumber(answer.noul) : undefined;
|
|
4882
5080
|
return noul !== undefined && noul >= threshold;
|
|
4883
5081
|
};
|
|
4884
5082
|
|
|
4885
5083
|
// src/jev/state.ts
|
|
4886
5084
|
var JEV_TEXT_CAP_BYTES = 8 * 1024;
|
|
4887
5085
|
var buildJevState = (ctx, input = {}) => {
|
|
4888
|
-
const
|
|
5086
|
+
const fields = new Map([["role", "executor"]]);
|
|
4889
5087
|
if (input.question) {
|
|
4890
|
-
|
|
5088
|
+
fields.set("executor_question", redactAndCapText(input.question, JEV_TEXT_CAP_BYTES, advisorRedactSecretsRef));
|
|
4891
5089
|
}
|
|
4892
5090
|
if (input.draft) {
|
|
4893
|
-
|
|
5091
|
+
fields.set("executor_draft", redactAndCapText(input.draft, JEV_TEXT_CAP_BYTES, advisorRedactSecretsRef));
|
|
4894
5092
|
}
|
|
4895
5093
|
const digest = recentConversation(ctx, advisorJevDigestMaxCharsRef);
|
|
4896
5094
|
if (digest) {
|
|
4897
|
-
|
|
5095
|
+
fields.set("recent_conversation", digest);
|
|
4898
5096
|
}
|
|
4899
|
-
return
|
|
5097
|
+
return Object.fromEntries(fields);
|
|
4900
5098
|
};
|
|
4901
5099
|
|
|
4902
5100
|
// src/jev/transport.ts
|
|
@@ -4911,11 +5109,14 @@ var resolveJevTransport = async (ctx, deps = {}) => {
|
|
|
4911
5109
|
const resolveTypesafe = deps.resolveTypesafe ?? resolveTypeSafeKey;
|
|
4912
5110
|
const resolution = await resolveTypesafe();
|
|
4913
5111
|
if (resolution.key) {
|
|
4914
|
-
|
|
5112
|
+
const credentials = {
|
|
4915
5113
|
apiKey: resolution.key,
|
|
4916
|
-
...resolution.source ? { source: resolution.source } : {},
|
|
4917
5114
|
transport: "typesafe"
|
|
4918
5115
|
};
|
|
5116
|
+
if (resolution.source) {
|
|
5117
|
+
credentials.source = resolution.source;
|
|
5118
|
+
}
|
|
5119
|
+
return credentials;
|
|
4919
5120
|
}
|
|
4920
5121
|
}
|
|
4921
5122
|
if (preference === "typesafe") {
|
|
@@ -4946,7 +5147,7 @@ var createOutageNotifier = (format) => {
|
|
|
4946
5147
|
};
|
|
4947
5148
|
|
|
4948
5149
|
// src/tools/jev-filter.ts
|
|
4949
|
-
var normalizeScreeningQuestion = (question) => question?.trim().toLowerCase().
|
|
5150
|
+
var normalizeScreeningQuestion = (question) => question?.trim().toLowerCase().replaceAll(/\s+/gu, " ") || undefined;
|
|
4950
5151
|
var REATTACHED_ADVICE_CAP_BYTES = 4 * 1024;
|
|
4951
5152
|
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
5153
|
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 +5157,6 @@ var outageNotifier = createOutageNotifier((category, message) => `Advisor Jev fi
|
|
|
4956
5157
|
var notifyOutageOnce = outageNotifier.notify;
|
|
4957
5158
|
var resetJevOutageNotification = outageNotifier.reset;
|
|
4958
5159
|
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
5160
|
var bypassOutcome = (session, options, normalizedQuestion) => {
|
|
4984
5161
|
const lastSkip = session.lastJevSkip;
|
|
4985
5162
|
if (options.force) {
|
|
@@ -5027,7 +5204,7 @@ var screenWithJev = async (ctx, session, options, deps, normalizedQuestion) => {
|
|
|
5027
5204
|
return allow();
|
|
5028
5205
|
} catch (error) {
|
|
5029
5206
|
session.recordJevFilterFailure();
|
|
5030
|
-
if (error instanceof
|
|
5207
|
+
if (error instanceof JevFailureError) {
|
|
5031
5208
|
notifyOutageOnce(ctx, error.category, error.message);
|
|
5032
5209
|
} else if (options.signal?.aborted) {
|
|
5033
5210
|
throw error;
|
|
@@ -5037,10 +5214,34 @@ var screenWithJev = async (ctx, session, options, deps, normalizedQuestion) => {
|
|
|
5037
5214
|
return allow();
|
|
5038
5215
|
}
|
|
5039
5216
|
};
|
|
5217
|
+
var screenConsultation = (ctx, session, options, deps = {}) => {
|
|
5218
|
+
if (isSimpleMode()) {
|
|
5219
|
+
return Promise.resolve(allow());
|
|
5220
|
+
}
|
|
5221
|
+
const normalizedQuestion = normalizeScreeningQuestion(options.question);
|
|
5222
|
+
const bypass = bypassOutcome(session, options, normalizedQuestion);
|
|
5223
|
+
if (bypass) {
|
|
5224
|
+
return Promise.resolve(bypass);
|
|
5225
|
+
}
|
|
5226
|
+
const reattached = session.reattachedAdviceFor(normalizedQuestion);
|
|
5227
|
+
if (reattached) {
|
|
5228
|
+
session.recordJevFilterSkipped(true, normalizedQuestion);
|
|
5229
|
+
return Promise.resolve({
|
|
5230
|
+
decision: "skip",
|
|
5231
|
+
kind: "repeat",
|
|
5232
|
+
reason: "already answered earlier in this session",
|
|
5233
|
+
reattachedAdvice: reattached.slice(0, REATTACHED_ADVICE_CAP_BYTES)
|
|
5234
|
+
});
|
|
5235
|
+
}
|
|
5236
|
+
if (!advisorJevFilterEnabledRef) {
|
|
5237
|
+
return Promise.resolve(allow());
|
|
5238
|
+
}
|
|
5239
|
+
return screenWithJev(ctx, session, options, deps, normalizedQuestion);
|
|
5240
|
+
};
|
|
5040
5241
|
var screeningSkipText = (outcome) => outcome.kind === "repeat" && outcome.reattachedAdvice ? repeatSkipText(outcome.reattachedAdvice) : SCREENED_SKIP_TEXT;
|
|
5041
5242
|
|
|
5042
5243
|
// src/commands/manual-consultation.ts
|
|
5043
|
-
var startManualConsultation = (runtime, ctx, question, controller, scoutStatusToken, progress, gitContext) => {
|
|
5244
|
+
var startManualConsultation = async (runtime, ctx, question, controller, scoutStatusToken, progress, gitContext) => {
|
|
5044
5245
|
herdrAdvisorActivity.start();
|
|
5045
5246
|
progress.phase = "preparing";
|
|
5046
5247
|
runtime.requestManualRender(ctx);
|
|
@@ -5055,23 +5256,24 @@ var startManualConsultation = (runtime, ctx, question, controller, scoutStatusTo
|
|
|
5055
5256
|
runtime.manualProgressTimers.set(controller, timer);
|
|
5056
5257
|
}
|
|
5057
5258
|
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;
|
|
5259
|
+
try {
|
|
5260
|
+
const { adviceId, markdown, usage } = await runtime.requestAdvisor(ctx, question, controller.signal, (thinking, text) => {
|
|
5261
|
+
if (controller.signal.aborted) {
|
|
5262
|
+
return;
|
|
5263
|
+
}
|
|
5071
5264
|
progress.phase = "active";
|
|
5265
|
+
progress.thinking = thinking;
|
|
5266
|
+
progress.text = text;
|
|
5072
5267
|
runtime.requestManualRender(ctx);
|
|
5073
|
-
}
|
|
5074
|
-
|
|
5268
|
+
}, (event) => {
|
|
5269
|
+
if (!controller.signal.aborted) {
|
|
5270
|
+
runtime.scoutStatus.update(ctx, scoutStatusToken, event);
|
|
5271
|
+
scoutDetails = appendScoutLifecycleEntry(runtime.pi, event, scoutDetails);
|
|
5272
|
+
progress.scout = scoutDetails;
|
|
5273
|
+
progress.phase = "active";
|
|
5274
|
+
runtime.requestManualRender(ctx);
|
|
5275
|
+
}
|
|
5276
|
+
}, gitContext);
|
|
5075
5277
|
if (controller.signal.aborted) {
|
|
5076
5278
|
return;
|
|
5077
5279
|
}
|
|
@@ -5085,28 +5287,31 @@ var startManualConsultation = (runtime, ctx, question, controller, scoutStatusTo
|
|
|
5085
5287
|
trigger: "manual",
|
|
5086
5288
|
usage
|
|
5087
5289
|
});
|
|
5088
|
-
if (
|
|
5290
|
+
if (isString(adviceId)) {
|
|
5089
5291
|
runtime.advisorSessionState.issueAdvice(adviceId, markdown, "manual", false, normalizeScreeningQuestion(question));
|
|
5090
5292
|
}
|
|
5091
5293
|
runtime.updateAdvisorUsageStatus(ctx);
|
|
5294
|
+
const details = {
|
|
5295
|
+
advisor: advisorRef,
|
|
5296
|
+
question,
|
|
5297
|
+
text: markdown
|
|
5298
|
+
};
|
|
5092
5299
|
const normalizedUsage = snapshotAdvisorUsage(usage);
|
|
5300
|
+
if (normalizedUsage) {
|
|
5301
|
+
details.usage = normalizedUsage;
|
|
5302
|
+
}
|
|
5093
5303
|
runtime.pi.sendMessage({
|
|
5094
5304
|
content: `Manual Advisor consultation${question ? ` (${question})` : ""} — for your awareness; no action or follow-up consultation is needed unless the user asks:
|
|
5095
5305
|
|
|
5096
5306
|
${markdown}`,
|
|
5097
5307
|
customType: "advisor-manual-result",
|
|
5098
|
-
details
|
|
5099
|
-
advisor: advisorRef,
|
|
5100
|
-
question,
|
|
5101
|
-
text: markdown,
|
|
5102
|
-
...normalizedUsage ? { usage: normalizedUsage } : {}
|
|
5103
|
-
},
|
|
5308
|
+
details,
|
|
5104
5309
|
display: true
|
|
5105
5310
|
}, {
|
|
5106
5311
|
deliverAs: "steer",
|
|
5107
5312
|
triggerTurn: true
|
|
5108
5313
|
});
|
|
5109
|
-
}
|
|
5314
|
+
} catch (error) {
|
|
5110
5315
|
if (controller.signal.aborted) {
|
|
5111
5316
|
return;
|
|
5112
5317
|
}
|
|
@@ -5132,7 +5337,7 @@ ${markdown}`,
|
|
|
5132
5337
|
}, { deliverAs: "steer", triggerTurn: true });
|
|
5133
5338
|
notify(ctx, `Advisor consultation failed: ${message}`, "error");
|
|
5134
5339
|
notifyHerdrAdvisorFailure("Advisor consultation failed", message);
|
|
5135
|
-
}
|
|
5340
|
+
} finally {
|
|
5136
5341
|
if (controller.signal.aborted) {
|
|
5137
5342
|
progress.phase = "cancelled";
|
|
5138
5343
|
}
|
|
@@ -5145,7 +5350,7 @@ ${markdown}`,
|
|
|
5145
5350
|
runtime.scoutStatus.release(ctx, scoutStatusToken);
|
|
5146
5351
|
runtime.manualConsultations.delete(controller);
|
|
5147
5352
|
herdrAdvisorActivity.finish();
|
|
5148
|
-
}
|
|
5353
|
+
}
|
|
5149
5354
|
};
|
|
5150
5355
|
|
|
5151
5356
|
// src/commands/manual-command.ts
|
|
@@ -5156,6 +5361,11 @@ var registerManualCommand = (runtime) => {
|
|
|
5156
5361
|
if (!loadCommandConfig(ctx)) {
|
|
5157
5362
|
return;
|
|
5158
5363
|
}
|
|
5364
|
+
const accessReason = advisorModelAccessReason(ctx);
|
|
5365
|
+
if (accessReason) {
|
|
5366
|
+
notify(ctx, accessReason, "warning");
|
|
5367
|
+
return;
|
|
5368
|
+
}
|
|
5159
5369
|
if (!(isSimpleMode() || runtime.advisorSessionState.canConsult(getAdvisorMaxCallsPerSession()))) {
|
|
5160
5370
|
runtime.reportManualBudgetExhausted(ctx);
|
|
5161
5371
|
return;
|
|
@@ -5305,41 +5515,40 @@ class ManualAdvisorProgressComponent {
|
|
|
5305
5515
|
}
|
|
5306
5516
|
return box.render(width);
|
|
5307
5517
|
}
|
|
5308
|
-
invalidate
|
|
5518
|
+
invalidate = noop;
|
|
5309
5519
|
}
|
|
5310
5520
|
|
|
5311
5521
|
// 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
|
-
}
|
|
5522
|
+
var manualCallRenderer = (runtime) => (entry, { expanded }, theme) => {
|
|
5523
|
+
const { progressId, question } = entry.data ?? {};
|
|
5524
|
+
const progress = progressId ? runtime.manualProgress.get(progressId) : undefined;
|
|
5525
|
+
return progress ? new ManualAdvisorProgressComponent(question, progress, Boolean(expanded), theme) : renderAdvisorCallBox(question, theme);
|
|
5526
|
+
};
|
|
5527
|
+
var manualResultRenderer = (message, { expanded }, theme) => {
|
|
5528
|
+
const { details } = message;
|
|
5529
|
+
const box = new Box2(1, 1, (text) => theme.bg("customMessageBg", text));
|
|
5530
|
+
const advice = details?.text ?? (isString(message.content) ? message.content : "(Advisor returned no advice.)");
|
|
5531
|
+
box.addChild(new Text4(renderAdvisorResponseHeader(hasSoundVerdict(advice), theme), 0, 0));
|
|
5532
|
+
if (details?.advisor) {
|
|
5533
|
+
box.addChild(new Text4(theme.fg("dim", ` ${details.advisor}`), 0, 0));
|
|
5534
|
+
}
|
|
5535
|
+
if (getAdvisorSettings().showUsageDetails) {
|
|
5536
|
+
const usage = formatAdvisorUsage(details?.usage);
|
|
5537
|
+
if (usage) {
|
|
5538
|
+
box.addChild(new Text4(theme.fg("dim", ` Usage: ${usage}`), 0, 0));
|
|
5331
5539
|
}
|
|
5332
|
-
|
|
5333
|
-
|
|
5334
|
-
|
|
5540
|
+
}
|
|
5541
|
+
box.addChild(new Markdown3(adviceForDisplay(advice, expanded), 0, 0, getMarkdownTheme3()));
|
|
5542
|
+
return box;
|
|
5543
|
+
};
|
|
5544
|
+
var registerCommandRenderers = (runtime) => {
|
|
5545
|
+
runtime.pi.registerEntryRenderer?.("advisor-manual-call", manualCallRenderer(runtime));
|
|
5546
|
+
runtime.pi.registerMessageRenderer?.("advisor-manual-result", manualResultRenderer);
|
|
5335
5547
|
};
|
|
5336
5548
|
|
|
5337
5549
|
// src/ui/settings-selector.ts
|
|
5338
5550
|
import { getSettingsListTheme } from "@earendil-works/pi-coding-agent";
|
|
5339
|
-
import {
|
|
5340
|
-
SettingsList,
|
|
5341
|
-
truncateToWidth as truncateToWidth7
|
|
5342
|
-
} from "@earendil-works/pi-tui";
|
|
5551
|
+
import { SettingsList, truncateToWidth as truncateToWidth7 } from "@earendil-works/pi-tui";
|
|
5343
5552
|
|
|
5344
5553
|
// src/ui/settings-formatting.ts
|
|
5345
5554
|
import { visibleWidth as visibleWidth3 } from "@earendil-works/pi-tui";
|
|
@@ -5357,7 +5566,7 @@ var SIMPLE_MODE_GRADIENT_COLORS = [
|
|
|
5357
5566
|
var withCurrentValue = (current, values) => values.includes(current) ? values : [current, ...values];
|
|
5358
5567
|
var numericValues = (current, values) => {
|
|
5359
5568
|
const all = values.includes(current) ? values : [...values, current];
|
|
5360
|
-
return all.
|
|
5569
|
+
return all.toSorted((a, b) => a - b).map(String);
|
|
5361
5570
|
};
|
|
5362
5571
|
var maxCallValues = (current) => {
|
|
5363
5572
|
const values = ["0", "1", "2", "3", "5", "10", "25", "50", "∞"];
|
|
@@ -5374,9 +5583,18 @@ var maxCallValues = (current) => {
|
|
|
5374
5583
|
};
|
|
5375
5584
|
var settingValue = (value, defaultValue) => value ?? defaultValue ? "On" : "Off";
|
|
5376
5585
|
var currentContextLabel = (presets, contextMaxChars) => presets.find((preset) => preset.value === contextMaxChars)?.label ?? String(contextMaxChars);
|
|
5586
|
+
var closestPresetIndex = (presets, contextMaxChars) => {
|
|
5587
|
+
let closestIndex = 0;
|
|
5588
|
+
for (let index = 0;index < presets.length; index += 1) {
|
|
5589
|
+
if (Math.abs(presets[index].value - contextMaxChars) < Math.abs(presets[closestIndex].value - contextMaxChars)) {
|
|
5590
|
+
closestIndex = index;
|
|
5591
|
+
}
|
|
5592
|
+
}
|
|
5593
|
+
return closestIndex;
|
|
5594
|
+
};
|
|
5377
5595
|
var contextDescription = (presets, contextMaxChars) => {
|
|
5378
5596
|
const exactIndex = presets.findIndex((preset) => preset.value === contextMaxChars);
|
|
5379
|
-
const selectedIndex = exactIndex
|
|
5597
|
+
const selectedIndex = exactIndex === -1 ? closestPresetIndex(presets, contextMaxChars) : exactIndex;
|
|
5380
5598
|
const selectedPreset = presets[selectedIndex];
|
|
5381
5599
|
const isFullContext = selectedPreset?.value === Number.MAX_SAFE_INTEGER || selectedPreset?.label.toUpperCase() === "FULL" || selectedPreset?.label.toUpperCase() === "ALL";
|
|
5382
5600
|
const progress = isFullContext ? 1 : selectedIndex / Math.max(1, presets.length - 1);
|
|
@@ -5394,7 +5612,7 @@ var contextDescription = (presets, contextMaxChars) => {
|
|
|
5394
5612
|
const markerColumn = meterPrefix.length + marker;
|
|
5395
5613
|
const labelStart = Math.max(0, Math.min(meterPrefix.length + meter.length + 2 - labelWidth, markerColumn - Math.floor((labelWidth - 1) / 2)));
|
|
5396
5614
|
const markerLabel = `${" ".repeat(labelStart)}${label}`;
|
|
5397
|
-
const description = exactIndex
|
|
5615
|
+
const description = exactIndex === -1 ? "Custom context limit." : selectedPreset?.description;
|
|
5398
5616
|
return `${description ?? "Custom context limit."}
|
|
5399
5617
|
${meterPrefix}${meter} full
|
|
5400
5618
|
${markerLabel}`;
|
|
@@ -5419,14 +5637,63 @@ var rainbowGradient = (text, startedAt) => {
|
|
|
5419
5637
|
}).join("").concat("\x1B[0m");
|
|
5420
5638
|
};
|
|
5421
5639
|
|
|
5640
|
+
// src/ui/settings-items.ts
|
|
5641
|
+
import { getKeybindings } from "@earendil-works/pi-tui";
|
|
5642
|
+
|
|
5422
5643
|
// 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";
|
|
5644
|
+
import { Key as Key2, matchesKey as matchesKey2, truncateToWidth as truncateToWidth5 } from "@earendil-works/pi-tui";
|
|
5428
5645
|
|
|
5429
5646
|
// node_modules/@typesafe-ai/sdk/dist/index.mjs
|
|
5647
|
+
var requestIdFrom = (headers) => headers.get("x-typesafe-request-id") ?? undefined;
|
|
5648
|
+
var APIPromise = class APIPromise2 extends Promise {
|
|
5649
|
+
#responsePromise;
|
|
5650
|
+
#parseResponse;
|
|
5651
|
+
#parsed;
|
|
5652
|
+
constructor(responsePromise, parseResponse) {
|
|
5653
|
+
super((resolve2) => resolve2(undefined));
|
|
5654
|
+
this.#responsePromise = responsePromise;
|
|
5655
|
+
this.#parseResponse = parseResponse;
|
|
5656
|
+
}
|
|
5657
|
+
asResponse() {
|
|
5658
|
+
return this.#responsePromise;
|
|
5659
|
+
}
|
|
5660
|
+
async withResponse() {
|
|
5661
|
+
const [data, response] = await Promise.all([this.#parse(), this.#responsePromise]);
|
|
5662
|
+
return {
|
|
5663
|
+
data,
|
|
5664
|
+
response,
|
|
5665
|
+
requestId: requestIdFrom(response.headers)
|
|
5666
|
+
};
|
|
5667
|
+
}
|
|
5668
|
+
map(fn) {
|
|
5669
|
+
return new APIPromise2(this.#responsePromise, () => this.#parse().then(fn));
|
|
5670
|
+
}
|
|
5671
|
+
#parse() {
|
|
5672
|
+
this.#parsed ??= this.#responsePromise.then(this.#parseResponse);
|
|
5673
|
+
return this.#parsed;
|
|
5674
|
+
}
|
|
5675
|
+
then(onfulfilled, onrejected) {
|
|
5676
|
+
return this.#parse().then(onfulfilled, onrejected);
|
|
5677
|
+
}
|
|
5678
|
+
catch(onrejected) {
|
|
5679
|
+
return this.#parse().catch(onrejected);
|
|
5680
|
+
}
|
|
5681
|
+
finally(onfinally) {
|
|
5682
|
+
return this.#parse().finally(onfinally);
|
|
5683
|
+
}
|
|
5684
|
+
};
|
|
5685
|
+
var ENV = {
|
|
5686
|
+
apiKey: "TYPESAFE_API_KEY",
|
|
5687
|
+
baseURL: "TYPESAFE_BASE_URL",
|
|
5688
|
+
defaultModel: "TYPESAFE_DEFAULT_MODEL",
|
|
5689
|
+
logLevel: "TYPESAFE_LOG_LEVEL"
|
|
5690
|
+
};
|
|
5691
|
+
var readEnv = (name) => {
|
|
5692
|
+
if (typeof process === "undefined" || !process.env)
|
|
5693
|
+
return;
|
|
5694
|
+
return process.env[name]?.trim() || undefined;
|
|
5695
|
+
};
|
|
5696
|
+
var fromCodeOrEnv = (fromCode, envVar) => fromCode ?? readEnv(envVar);
|
|
5430
5697
|
var range2 = (from, to) => Array.from({ length: to - from }, (_, i) => from + i);
|
|
5431
5698
|
var DEFAULT_RETRY_POLICY = {
|
|
5432
5699
|
maxRetries: 2,
|
|
@@ -5444,11 +5711,239 @@ var DEFAULT_RETRY_POLICY = {
|
|
|
5444
5711
|
apiTimeoutError: true
|
|
5445
5712
|
};
|
|
5446
5713
|
DEFAULT_RETRY_POLICY.maxRetries;
|
|
5714
|
+
var isRetryableStatus = (status, policy = DEFAULT_RETRY_POLICY) => policy.httpStatuses.has(status);
|
|
5715
|
+
var parseRetryAfter = (headers, now = Date.now()) => {
|
|
5716
|
+
const ms = Number(headers.get("retry-after-ms"));
|
|
5717
|
+
if (headers.has("retry-after-ms") && Number.isFinite(ms) && ms >= 0)
|
|
5718
|
+
return ms;
|
|
5719
|
+
const raw = headers.get("retry-after");
|
|
5720
|
+
if (raw === null)
|
|
5721
|
+
return;
|
|
5722
|
+
const seconds = Number(raw);
|
|
5723
|
+
if (Number.isFinite(seconds))
|
|
5724
|
+
return seconds >= 0 ? seconds * 1000 : undefined;
|
|
5725
|
+
const date = Date.parse(raw);
|
|
5726
|
+
if (!Number.isNaN(date))
|
|
5727
|
+
return Math.max(0, date - now);
|
|
5728
|
+
};
|
|
5729
|
+
var retryDelayMs = (attempt, headers, policy = DEFAULT_RETRY_POLICY, random = Math.random) => {
|
|
5730
|
+
if (policy.respectRetryAfter && headers !== undefined) {
|
|
5731
|
+
const retryAfter = parseRetryAfter(headers);
|
|
5732
|
+
if (retryAfter !== undefined && retryAfter <= policy.maxRetryAfterMs)
|
|
5733
|
+
return retryAfter;
|
|
5734
|
+
}
|
|
5735
|
+
const exponential = Math.min(policy.backoffInitialMs * 2 ** attempt, policy.backoffMaxMs);
|
|
5736
|
+
return Math.round(exponential * (1 - random() * policy.backoffJitter));
|
|
5737
|
+
};
|
|
5738
|
+
var sleep = (ms, signal) => new Promise((resolve2, reject) => {
|
|
5739
|
+
if (signal?.aborted)
|
|
5740
|
+
return reject(signal.reason);
|
|
5741
|
+
const onAbort = () => {
|
|
5742
|
+
clearTimeout(timer);
|
|
5743
|
+
reject(signal?.reason);
|
|
5744
|
+
};
|
|
5745
|
+
const timer = setTimeout(() => {
|
|
5746
|
+
signal?.removeEventListener("abort", onAbort);
|
|
5747
|
+
resolve2();
|
|
5748
|
+
}, ms);
|
|
5749
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
5750
|
+
});
|
|
5751
|
+
var TypeSafeError = class extends Error {
|
|
5752
|
+
constructor(message, options) {
|
|
5753
|
+
super(message, options);
|
|
5754
|
+
this.name = new.target.name;
|
|
5755
|
+
}
|
|
5756
|
+
};
|
|
5757
|
+
var isRecord2 = (value) => typeof value === "object" && value !== null;
|
|
5758
|
+
var extractMessage = (body) => {
|
|
5759
|
+
if (typeof body === "string")
|
|
5760
|
+
return body || undefined;
|
|
5761
|
+
if (!isRecord2(body))
|
|
5762
|
+
return;
|
|
5763
|
+
const { error, message, detail } = body;
|
|
5764
|
+
if (typeof error === "string")
|
|
5765
|
+
return error;
|
|
5766
|
+
if (isRecord2(error) && typeof error.message === "string")
|
|
5767
|
+
return error.message;
|
|
5768
|
+
if (typeof message === "string")
|
|
5769
|
+
return message;
|
|
5770
|
+
if (typeof detail === "string")
|
|
5771
|
+
return detail;
|
|
5772
|
+
if (isRecord2(detail) && typeof detail.message === "string")
|
|
5773
|
+
return detail.message;
|
|
5774
|
+
if (Array.isArray(detail))
|
|
5775
|
+
return describeValidationErrors(detail);
|
|
5776
|
+
};
|
|
5777
|
+
var describeValidationErrors = (errors) => {
|
|
5778
|
+
const parts = errors.flatMap((e) => {
|
|
5779
|
+
if (!isRecord2(e) || typeof e.msg !== "string")
|
|
5780
|
+
return [];
|
|
5781
|
+
const loc = Array.isArray(e.loc) ? e.loc.filter((x) => x !== "body").join(".") : "";
|
|
5782
|
+
return [loc ? `${loc}: ${e.msg}` : e.msg];
|
|
5783
|
+
});
|
|
5784
|
+
return parts.length > 0 ? parts.join("; ") : undefined;
|
|
5785
|
+
};
|
|
5786
|
+
var MAX_RAW_BODY_IN_MESSAGE = 200;
|
|
5787
|
+
var APIError = class APIError2 extends TypeSafeError {
|
|
5788
|
+
status;
|
|
5789
|
+
headers;
|
|
5790
|
+
body;
|
|
5791
|
+
requestId;
|
|
5792
|
+
constructor(status, body, headers, message) {
|
|
5793
|
+
super(message ?? APIError2.describe(status, body));
|
|
5794
|
+
this.status = status;
|
|
5795
|
+
this.body = body;
|
|
5796
|
+
this.headers = headers;
|
|
5797
|
+
this.requestId = requestIdFrom(headers);
|
|
5798
|
+
}
|
|
5799
|
+
static describe(status, body) {
|
|
5800
|
+
const detail = extractMessage(body);
|
|
5801
|
+
if (detail)
|
|
5802
|
+
return `${status} ${detail}`;
|
|
5803
|
+
if (body === undefined)
|
|
5804
|
+
return `${status} status code (no body)`;
|
|
5805
|
+
const raw = typeof body === "string" ? body : JSON.stringify(body);
|
|
5806
|
+
return `${status} ${raw.length > MAX_RAW_BODY_IN_MESSAGE ? `${raw.slice(0, MAX_RAW_BODY_IN_MESSAGE)}…` : raw}`;
|
|
5807
|
+
}
|
|
5808
|
+
static fromResponse(status, body, headers) {
|
|
5809
|
+
if (status === 400)
|
|
5810
|
+
return new BadRequestError(status, body, headers);
|
|
5811
|
+
if (status === 401)
|
|
5812
|
+
return new AuthenticationError(status, body, headers);
|
|
5813
|
+
if (status === 403)
|
|
5814
|
+
return new PermissionDeniedError(status, body, headers);
|
|
5815
|
+
if (status === 404)
|
|
5816
|
+
return new NotFoundError(status, body, headers);
|
|
5817
|
+
if (status === 422)
|
|
5818
|
+
return new UnprocessableEntityError(status, body, headers);
|
|
5819
|
+
if (status === 429)
|
|
5820
|
+
return new RateLimitError(status, body, headers);
|
|
5821
|
+
if (status >= 500)
|
|
5822
|
+
return new InternalServerError(status, body, headers);
|
|
5823
|
+
return new APIError2(status, body, headers);
|
|
5824
|
+
}
|
|
5825
|
+
};
|
|
5826
|
+
var BadRequestError = class extends APIError {
|
|
5827
|
+
};
|
|
5828
|
+
var AuthenticationError = class extends APIError {
|
|
5829
|
+
};
|
|
5830
|
+
var PermissionDeniedError = class extends APIError {
|
|
5831
|
+
};
|
|
5832
|
+
var NotFoundError = class extends APIError {
|
|
5833
|
+
};
|
|
5834
|
+
var UnprocessableEntityError = class extends APIError {
|
|
5835
|
+
};
|
|
5836
|
+
var RateLimitError = class extends APIError {
|
|
5837
|
+
retryAfterMs = parseRetryAfter(this.headers);
|
|
5838
|
+
};
|
|
5839
|
+
var InternalServerError = class extends APIError {
|
|
5840
|
+
};
|
|
5841
|
+
var APIConnectionError = class extends TypeSafeError {
|
|
5842
|
+
constructor(message = "Connection error.", options) {
|
|
5843
|
+
super(message, options);
|
|
5844
|
+
}
|
|
5845
|
+
};
|
|
5846
|
+
var APITimeoutError = class extends APIConnectionError {
|
|
5847
|
+
timeoutMs;
|
|
5848
|
+
constructor(timeoutMs, options) {
|
|
5849
|
+
super(`Request timed out after ${timeoutMs}ms.`, options);
|
|
5850
|
+
this.timeoutMs = timeoutMs;
|
|
5851
|
+
}
|
|
5852
|
+
};
|
|
5853
|
+
var APIUserAbortError = class extends TypeSafeError {
|
|
5854
|
+
constructor(message = "Request was aborted.", options) {
|
|
5855
|
+
super(message, options);
|
|
5856
|
+
}
|
|
5857
|
+
};
|
|
5858
|
+
var LOG_LEVELS = [
|
|
5859
|
+
"debug",
|
|
5860
|
+
"info",
|
|
5861
|
+
"warn",
|
|
5862
|
+
"error",
|
|
5863
|
+
"off"
|
|
5864
|
+
];
|
|
5865
|
+
var DEFAULT_LOG_LEVEL = "warn";
|
|
5866
|
+
var isLogLevel = (value) => LOG_LEVELS.includes(value);
|
|
5867
|
+
var parseLogLevel = (value, source) => {
|
|
5868
|
+
if (isLogLevel(value))
|
|
5869
|
+
return value;
|
|
5870
|
+
throw new TypeSafeError(`Invalid log level "${value}" from ${source}. Expected one of: ${LOG_LEVELS.join(", ")}.`);
|
|
5871
|
+
};
|
|
5872
|
+
var PREFIX = "[typesafe-sdk]";
|
|
5873
|
+
var consoleLogger = {
|
|
5874
|
+
debug: (message, ...args) => console.debug(`${PREFIX} ${message}`, ...args),
|
|
5875
|
+
info: (message, ...args) => console.info(`${PREFIX} ${message}`, ...args),
|
|
5876
|
+
warn: (message, ...args) => console.warn(`${PREFIX} ${message}`, ...args),
|
|
5877
|
+
error: (message, ...args) => console.error(`${PREFIX} ${message}`, ...args)
|
|
5878
|
+
};
|
|
5879
|
+
var RANK = {
|
|
5880
|
+
debug: 0,
|
|
5881
|
+
info: 1,
|
|
5882
|
+
warn: 2,
|
|
5883
|
+
error: 3,
|
|
5884
|
+
off: 4
|
|
5885
|
+
};
|
|
5886
|
+
var drop = () => {};
|
|
5887
|
+
var withLevel = (sink, level) => {
|
|
5888
|
+
const enabled = (at) => RANK[at] >= RANK[level];
|
|
5889
|
+
return {
|
|
5890
|
+
debug: enabled("debug") ? (message, ...args) => sink.debug(message, ...args) : drop,
|
|
5891
|
+
info: enabled("info") ? (message, ...args) => sink.info(message, ...args) : drop,
|
|
5892
|
+
warn: enabled("warn") ? (message, ...args) => sink.warn(message, ...args) : drop,
|
|
5893
|
+
error: enabled("error") ? (message, ...args) => sink.error(message, ...args) : drop
|
|
5894
|
+
};
|
|
5895
|
+
};
|
|
5896
|
+
var KEY_HEADERS = /* @__PURE__ */ new Set([
|
|
5897
|
+
"authorization",
|
|
5898
|
+
"proxy-authorization",
|
|
5899
|
+
"x-api-key"
|
|
5900
|
+
]);
|
|
5901
|
+
var OPAQUE_HEADERS = /* @__PURE__ */ new Set(["cookie", "set-cookie"]);
|
|
5902
|
+
var redactKey = (value) => {
|
|
5903
|
+
const [scheme, secret] = value.includes(" ") ? value.split(/\s+/, 2) : [undefined, value];
|
|
5904
|
+
const tail = secret && secret.length > 8 ? secret.slice(-4) : "";
|
|
5905
|
+
return `${scheme ? `${scheme} ` : ""}***${tail}`;
|
|
5906
|
+
};
|
|
5907
|
+
var redact = (name, value) => {
|
|
5908
|
+
const lower = name.toLowerCase();
|
|
5909
|
+
if (KEY_HEADERS.has(lower))
|
|
5910
|
+
return redactKey(value);
|
|
5911
|
+
if (OPAQUE_HEADERS.has(lower))
|
|
5912
|
+
return "***";
|
|
5913
|
+
return value;
|
|
5914
|
+
};
|
|
5915
|
+
var redactHeaders = (headers) => Object.fromEntries(Object.entries(headers).map(([name, value]) => [name, redact(name, value)]));
|
|
5447
5916
|
var noul = (instructions = null, criteria) => ({
|
|
5448
5917
|
type: "noul",
|
|
5449
5918
|
instructions,
|
|
5450
5919
|
criteria
|
|
5451
5920
|
});
|
|
5921
|
+
var validateQuestions = (questions) => {
|
|
5922
|
+
if (Object.keys(questions).length === 0)
|
|
5923
|
+
throw new TypeSafeError("At least one question is required.");
|
|
5924
|
+
for (const [name, question] of Object.entries(questions)) {
|
|
5925
|
+
if (question.type !== "score")
|
|
5926
|
+
continue;
|
|
5927
|
+
if (!Array.isArray(question.criteria))
|
|
5928
|
+
throw new TypeSafeError(`Score question "${name}" has criteria that are not a list; score criteria must be a list of descriptions indexed by score from zero.`);
|
|
5929
|
+
if (question.criteria.length < 2)
|
|
5930
|
+
throw new TypeSafeError(`Score question "${name}" has ${question.criteria.length} criteria; at least two scores are required.`);
|
|
5931
|
+
}
|
|
5932
|
+
};
|
|
5933
|
+
var Models = class {
|
|
5934
|
+
#transport;
|
|
5935
|
+
constructor(transport) {
|
|
5936
|
+
this.#transport = transport;
|
|
5937
|
+
}
|
|
5938
|
+
list(options = {}) {
|
|
5939
|
+
return this.#transport.request("GET", "/v1/models", options).map(unwrapModels);
|
|
5940
|
+
}
|
|
5941
|
+
};
|
|
5942
|
+
var unwrapModels = (wire) => {
|
|
5943
|
+
if (Array.isArray(wire?.models))
|
|
5944
|
+
return wire.models;
|
|
5945
|
+
throw new TypeSafeError("Unexpected response shape from GET /v1/models; expected { models: [...] }.");
|
|
5946
|
+
};
|
|
5452
5947
|
var g = globalThis;
|
|
5453
5948
|
var isBrowser = () => typeof g.window !== "undefined" && typeof g.window.document !== "undefined" && typeof g.navigator !== "undefined";
|
|
5454
5949
|
var describeRuntime = () => {
|
|
@@ -5467,13 +5962,283 @@ var describeRuntime = () => {
|
|
|
5467
5962
|
return "browser";
|
|
5468
5963
|
return "unknown";
|
|
5469
5964
|
};
|
|
5965
|
+
var VERSION = "0.6.0";
|
|
5966
|
+
var missingApiKey = () => {
|
|
5967
|
+
throw new TypeSafeError(`No API key was provided. Pass \`apiKey\` to the TypeSafeClient constructor or set the ${ENV.apiKey} environment variable.`);
|
|
5968
|
+
};
|
|
5969
|
+
var missingFetch = () => {
|
|
5970
|
+
throw new TypeSafeError("No global `fetch` is available in this runtime. Pass a `fetch` implementation to the TypeSafeClient constructor.");
|
|
5971
|
+
};
|
|
5972
|
+
var refuseBrowser = () => {
|
|
5973
|
+
throw new TypeSafeError("TypeSafeClient is running in a browser, which would expose your API key to anyone using the page. Call the API from a server instead, or pass `dangerouslyAllowBrowser: true` if you understand the risk.");
|
|
5974
|
+
};
|
|
5975
|
+
var defaultFetch = (input, init) => globalThis.fetch(input, init);
|
|
5976
|
+
var assertNonNegativeInteger = (name, value) => {
|
|
5977
|
+
if (!Number.isInteger(value) || value < 0)
|
|
5978
|
+
throw new TypeSafeError(`\`${name}\` must be a non-negative integer, got ${String(value)}.`);
|
|
5979
|
+
return value;
|
|
5980
|
+
};
|
|
5981
|
+
var assertPositiveMs = (name, value) => {
|
|
5982
|
+
if (!Number.isFinite(value) || value <= 0)
|
|
5983
|
+
throw new TypeSafeError(`\`${name}\` must be a positive number of milliseconds, got ${String(value)}.`);
|
|
5984
|
+
return value;
|
|
5985
|
+
};
|
|
5986
|
+
var assertNonNegativeMs = (name, value) => {
|
|
5987
|
+
if (!Number.isFinite(value) || value < 0)
|
|
5988
|
+
throw new TypeSafeError(`\`${name}\` must be a non-negative number of milliseconds, got ${String(value)}.`);
|
|
5989
|
+
return value;
|
|
5990
|
+
};
|
|
5991
|
+
var assertFraction = (name, value) => {
|
|
5992
|
+
if (!Number.isFinite(value) || value < 0 || value > 1)
|
|
5993
|
+
throw new TypeSafeError(`\`${name}\` must be between 0 and 1, got ${String(value)}.`);
|
|
5994
|
+
return value;
|
|
5995
|
+
};
|
|
5996
|
+
var assertStatusSet = (name, statuses) => {
|
|
5997
|
+
for (const status of statuses)
|
|
5998
|
+
if (!Number.isInteger(status) || status < 100 || status > 999)
|
|
5999
|
+
throw new TypeSafeError(`\`${name}\` must contain HTTP status codes, got ${String(status)}.`);
|
|
6000
|
+
return statuses;
|
|
6001
|
+
};
|
|
6002
|
+
var resolveRetryPolicy = (base, overrides) => {
|
|
6003
|
+
const o = overrides ?? {};
|
|
6004
|
+
return {
|
|
6005
|
+
maxRetries: o.maxRetries === undefined ? base.maxRetries : assertNonNegativeInteger("retry.maxRetries", o.maxRetries),
|
|
6006
|
+
backoffInitialMs: o.backoffInitialMs === undefined ? base.backoffInitialMs : assertNonNegativeMs("retry.backoffInitialMs", o.backoffInitialMs),
|
|
6007
|
+
backoffMaxMs: o.backoffMaxMs === undefined ? base.backoffMaxMs : assertNonNegativeMs("retry.backoffMaxMs", o.backoffMaxMs),
|
|
6008
|
+
backoffJitter: o.backoffJitter === undefined ? base.backoffJitter : assertFraction("retry.backoffJitter", o.backoffJitter),
|
|
6009
|
+
httpStatuses: new Set(o.httpStatuses === undefined ? base.httpStatuses : assertStatusSet("retry.httpStatuses", o.httpStatuses)),
|
|
6010
|
+
respectRetryAfter: o.respectRetryAfter ?? base.respectRetryAfter,
|
|
6011
|
+
maxRetryAfterMs: o.maxRetryAfterMs === undefined ? base.maxRetryAfterMs : assertNonNegativeMs("retry.maxRetryAfterMs", o.maxRetryAfterMs),
|
|
6012
|
+
apiConnectionError: o.apiConnectionError ?? base.apiConnectionError,
|
|
6013
|
+
apiTimeoutError: o.apiTimeoutError ?? base.apiTimeoutError
|
|
6014
|
+
};
|
|
6015
|
+
};
|
|
6016
|
+
var isRetryableError = (err, policy) => {
|
|
6017
|
+
if (err instanceof APITimeoutError)
|
|
6018
|
+
return policy.apiTimeoutError;
|
|
6019
|
+
if (err instanceof APIConnectionError)
|
|
6020
|
+
return policy.apiConnectionError;
|
|
6021
|
+
return false;
|
|
6022
|
+
};
|
|
6023
|
+
var resolveLogLevel = (fromCode) => {
|
|
6024
|
+
if (fromCode !== undefined)
|
|
6025
|
+
return parseLogLevel(fromCode, "the `logLevel` option");
|
|
6026
|
+
const fromEnv = readEnv(ENV.logLevel);
|
|
6027
|
+
if (fromEnv !== undefined)
|
|
6028
|
+
return parseLogLevel(fromEnv, ENV.logLevel);
|
|
6029
|
+
return DEFAULT_LOG_LEVEL;
|
|
6030
|
+
};
|
|
6031
|
+
var stripTrailingSlashes = (url) => url.replace(/\/+$/, "");
|
|
6032
|
+
var mergeHeaders = (...sources) => {
|
|
6033
|
+
const entries = /* @__PURE__ */ new Map;
|
|
6034
|
+
for (const source of sources)
|
|
6035
|
+
for (const [name, value] of Object.entries(source))
|
|
6036
|
+
if (value === undefined)
|
|
6037
|
+
entries.delete(name.toLowerCase());
|
|
6038
|
+
else
|
|
6039
|
+
entries.set(name.toLowerCase(), [name, value]);
|
|
6040
|
+
return Object.fromEntries(entries.values());
|
|
6041
|
+
};
|
|
6042
|
+
var bufferResponse = async (response, signal) => {
|
|
6043
|
+
const reader = response.clone().body?.getReader();
|
|
6044
|
+
if (!reader)
|
|
6045
|
+
return;
|
|
6046
|
+
const cancel = () => {
|
|
6047
|
+
reader.cancel(signal.reason).catch(() => {});
|
|
6048
|
+
response.body?.cancel(signal.reason).catch(() => {});
|
|
6049
|
+
};
|
|
6050
|
+
signal.addEventListener("abort", cancel, { once: true });
|
|
6051
|
+
try {
|
|
6052
|
+
if (signal.aborted)
|
|
6053
|
+
cancel();
|
|
6054
|
+
signal.throwIfAborted();
|
|
6055
|
+
while (!(await reader.read()).done)
|
|
6056
|
+
signal.throwIfAborted();
|
|
6057
|
+
signal.throwIfAborted();
|
|
6058
|
+
} finally {
|
|
6059
|
+
signal.removeEventListener("abort", cancel);
|
|
6060
|
+
reader.releaseLock();
|
|
6061
|
+
}
|
|
6062
|
+
};
|
|
5470
6063
|
var RUNTIME = describeRuntime();
|
|
6064
|
+
var TypeSafeClient = class {
|
|
6065
|
+
#apiKey;
|
|
6066
|
+
baseURL;
|
|
6067
|
+
defaultModel;
|
|
6068
|
+
logLevel;
|
|
6069
|
+
logger;
|
|
6070
|
+
retry;
|
|
6071
|
+
timeout;
|
|
6072
|
+
defaultHeaders;
|
|
6073
|
+
fetch;
|
|
6074
|
+
models;
|
|
6075
|
+
#requestCount = 0;
|
|
6076
|
+
constructor(config = {}) {
|
|
6077
|
+
if (isBrowser() && !config.dangerouslyAllowBrowser)
|
|
6078
|
+
refuseBrowser();
|
|
6079
|
+
this.#apiKey = fromCodeOrEnv(config.apiKey, ENV.apiKey) ?? missingApiKey();
|
|
6080
|
+
this.baseURL = stripTrailingSlashes(fromCodeOrEnv(config.baseURL, ENV.baseURL) ?? "https://api.typesafe.ai");
|
|
6081
|
+
this.defaultModel = fromCodeOrEnv(config.defaultModel, ENV.defaultModel) ?? "jev-latest";
|
|
6082
|
+
this.logLevel = resolveLogLevel(config.logLevel);
|
|
6083
|
+
this.logger = withLevel(config.logger ?? consoleLogger, this.logLevel);
|
|
6084
|
+
this.retry = resolveRetryPolicy(DEFAULT_RETRY_POLICY, config.retry);
|
|
6085
|
+
this.timeout = assertPositiveMs("timeout", config.timeout ?? 1e4);
|
|
6086
|
+
this.defaultHeaders = { ...config.defaultHeaders };
|
|
6087
|
+
if (config.fetch === undefined && typeof globalThis.fetch !== "function")
|
|
6088
|
+
missingFetch();
|
|
6089
|
+
this.fetch = config.fetch ?? defaultFetch;
|
|
6090
|
+
const transport = {
|
|
6091
|
+
request: (method, path, options) => this.#request(method, path, options),
|
|
6092
|
+
defaultModel: this.defaultModel
|
|
6093
|
+
};
|
|
6094
|
+
this.models = new Models(transport);
|
|
6095
|
+
}
|
|
6096
|
+
systemOne(request, options = {}) {
|
|
6097
|
+
validateQuestions(request.questions);
|
|
6098
|
+
const body = {
|
|
6099
|
+
...request,
|
|
6100
|
+
model: request.model ?? this.defaultModel
|
|
6101
|
+
};
|
|
6102
|
+
return this.#request("POST", "/v1/systemone", {
|
|
6103
|
+
...options,
|
|
6104
|
+
body
|
|
6105
|
+
});
|
|
6106
|
+
}
|
|
6107
|
+
#request(method, path, options = {}) {
|
|
6108
|
+
const resolved = {
|
|
6109
|
+
method,
|
|
6110
|
+
path,
|
|
6111
|
+
body: options.body,
|
|
6112
|
+
headers: mergeHeaders(this.defaultHeaders, options.headers ?? {}),
|
|
6113
|
+
signal: options.signal,
|
|
6114
|
+
timeout: options.timeout === undefined ? this.timeout : assertPositiveMs("timeout", options.timeout),
|
|
6115
|
+
retry: resolveRetryPolicy(this.retry, options.retry)
|
|
6116
|
+
};
|
|
6117
|
+
const tag = `#${++this.#requestCount} ${method} ${path}`;
|
|
6118
|
+
return new APIPromise(this.fetchWithRetries(tag, resolved), async (res) => {
|
|
6119
|
+
const parsed = await parseBody(res);
|
|
6120
|
+
this.logger.debug(`${tag} <- body`, parsed);
|
|
6121
|
+
return parsed;
|
|
6122
|
+
});
|
|
6123
|
+
}
|
|
6124
|
+
async fetchWithRetries(tag, req) {
|
|
6125
|
+
const url = `${this.baseURL}${req.path}`;
|
|
6126
|
+
const headers = mergeHeaders(req.headers, {
|
|
6127
|
+
Authorization: `Bearer ${this.#apiKey}`,
|
|
6128
|
+
Accept: "application/json",
|
|
6129
|
+
"User-Agent": `typesafe-sdk/${VERSION}`,
|
|
6130
|
+
"X-TypeSafe-SDK": `typesafe-sdk/${VERSION}`,
|
|
6131
|
+
"X-TypeSafe-Runtime": RUNTIME,
|
|
6132
|
+
"Content-Type": req.body === undefined ? undefined : "application/json",
|
|
6133
|
+
"X-TypeSafe-Retry-Count": undefined
|
|
6134
|
+
});
|
|
6135
|
+
const body = req.body === undefined ? undefined : JSON.stringify(req.body);
|
|
6136
|
+
for (let attempt = 0;; attempt++) {
|
|
6137
|
+
const retriesLeft = req.retry.maxRetries - attempt;
|
|
6138
|
+
const attemptHeaders = attempt === 0 ? headers : {
|
|
6139
|
+
...headers,
|
|
6140
|
+
"X-TypeSafe-Retry-Count": String(attempt)
|
|
6141
|
+
};
|
|
6142
|
+
this.logger.debug(`${tag} -> ${url}`, {
|
|
6143
|
+
headers: redactHeaders(attemptHeaders),
|
|
6144
|
+
body: req.body
|
|
6145
|
+
});
|
|
6146
|
+
const started = Date.now();
|
|
6147
|
+
let res;
|
|
6148
|
+
try {
|
|
6149
|
+
res = await this.attempt(tag, url, {
|
|
6150
|
+
method: req.method,
|
|
6151
|
+
headers: attemptHeaders,
|
|
6152
|
+
body
|
|
6153
|
+
}, req);
|
|
6154
|
+
} catch (err) {
|
|
6155
|
+
if (err instanceof APIUserAbortError || retriesLeft <= 0)
|
|
6156
|
+
throw err;
|
|
6157
|
+
if (!isRetryableError(err, req.retry))
|
|
6158
|
+
throw err;
|
|
6159
|
+
await this.backOff(tag, attempt, retriesLeft, err.message, undefined, req);
|
|
6160
|
+
continue;
|
|
6161
|
+
}
|
|
6162
|
+
const requestId = requestIdFrom(res.headers);
|
|
6163
|
+
this.logger.info(`${tag} <- ${res.status} in ${Date.now() - started}ms${requestId ? ` (request ${requestId})` : ""}`);
|
|
6164
|
+
if (res.ok)
|
|
6165
|
+
return res;
|
|
6166
|
+
const errorBody = await parseBody(res);
|
|
6167
|
+
this.logger.debug(`${tag} <- error body`, errorBody);
|
|
6168
|
+
const error = APIError.fromResponse(res.status, errorBody, res.headers);
|
|
6169
|
+
if (retriesLeft <= 0 || !isRetryableStatus(res.status, req.retry))
|
|
6170
|
+
throw error;
|
|
6171
|
+
await this.backOff(tag, attempt, retriesLeft, `${res.status}`, res.headers, req);
|
|
6172
|
+
}
|
|
6173
|
+
}
|
|
6174
|
+
async attempt(tag, url, init, { signal, timeout }) {
|
|
6175
|
+
const controller = new AbortController;
|
|
6176
|
+
const abortFromCaller = () => controller.abort(signal?.reason);
|
|
6177
|
+
if (signal?.aborted)
|
|
6178
|
+
abortFromCaller();
|
|
6179
|
+
signal?.addEventListener("abort", abortFromCaller, { once: true });
|
|
6180
|
+
let timedOut = false;
|
|
6181
|
+
const timer = setTimeout(() => {
|
|
6182
|
+
timedOut = true;
|
|
6183
|
+
controller.abort();
|
|
6184
|
+
}, timeout);
|
|
6185
|
+
const started = Date.now();
|
|
6186
|
+
const elapsed = () => `${Date.now() - started}ms`;
|
|
6187
|
+
try {
|
|
6188
|
+
const response = await this.fetch(url, {
|
|
6189
|
+
...init,
|
|
6190
|
+
signal: controller.signal
|
|
6191
|
+
});
|
|
6192
|
+
await bufferResponse(response, controller.signal);
|
|
6193
|
+
return response;
|
|
6194
|
+
} catch (err) {
|
|
6195
|
+
if (signal?.aborted) {
|
|
6196
|
+
this.logger.info(`${tag} aborted by caller after ${elapsed()}`);
|
|
6197
|
+
throw new APIUserAbortError(undefined, { cause: err });
|
|
6198
|
+
}
|
|
6199
|
+
if (timedOut) {
|
|
6200
|
+
this.logger.info(`${tag} timed out after ${elapsed()}`);
|
|
6201
|
+
throw new APITimeoutError(timeout, { cause: err });
|
|
6202
|
+
}
|
|
6203
|
+
this.logger.info(`${tag} connection error after ${elapsed()}`, err);
|
|
6204
|
+
throw new APIConnectionError(err instanceof Error ? `Connection error: ${err.message}` : undefined, { cause: err });
|
|
6205
|
+
} finally {
|
|
6206
|
+
clearTimeout(timer);
|
|
6207
|
+
signal?.removeEventListener("abort", abortFromCaller);
|
|
6208
|
+
}
|
|
6209
|
+
}
|
|
6210
|
+
async backOff(tag, attempt, retriesLeft, reason, headers, { retry, signal }) {
|
|
6211
|
+
const delay = retryDelayMs(attempt, headers, retry);
|
|
6212
|
+
const nth = attempt + 1;
|
|
6213
|
+
const total = attempt + retriesLeft;
|
|
6214
|
+
this.logger.info(`${tag} retrying in ${delay}ms (retry ${nth}/${total}) after ${reason}`);
|
|
6215
|
+
try {
|
|
6216
|
+
await sleep(delay, signal);
|
|
6217
|
+
} catch (err) {
|
|
6218
|
+
this.logger.info(`${tag} aborted by caller while waiting to retry`);
|
|
6219
|
+
throw new APIUserAbortError(undefined, { cause: err });
|
|
6220
|
+
}
|
|
6221
|
+
}
|
|
6222
|
+
};
|
|
6223
|
+
var parseBody = async (res) => {
|
|
6224
|
+
const text = await res.text();
|
|
6225
|
+
if (text.length === 0)
|
|
6226
|
+
return;
|
|
6227
|
+
if ((res.headers.get("content-type") ?? "").includes("application/json"))
|
|
6228
|
+
try {
|
|
6229
|
+
return JSON.parse(text);
|
|
6230
|
+
} catch {
|
|
6231
|
+
return text;
|
|
6232
|
+
}
|
|
6233
|
+
try {
|
|
6234
|
+
return JSON.parse(text);
|
|
6235
|
+
} catch {
|
|
6236
|
+
return text;
|
|
6237
|
+
}
|
|
6238
|
+
};
|
|
5471
6239
|
|
|
5472
6240
|
// src/ui/masked-input.ts
|
|
5473
|
-
import {
|
|
5474
|
-
Input as Input2,
|
|
5475
|
-
truncateToWidth as truncateToWidth4
|
|
5476
|
-
} from "@earendil-works/pi-tui";
|
|
6241
|
+
import { Input as Input2, truncateToWidth as truncateToWidth4 } from "@earendil-works/pi-tui";
|
|
5477
6242
|
|
|
5478
6243
|
class MaskedInput {
|
|
5479
6244
|
input;
|
|
@@ -5515,19 +6280,36 @@ class MaskedInput {
|
|
|
5515
6280
|
}
|
|
5516
6281
|
|
|
5517
6282
|
// src/ui/jev-setup-submenu.ts
|
|
6283
|
+
var ACTION_LABELS = {
|
|
6284
|
+
disable: "Disable",
|
|
6285
|
+
"disable-clear": "Disable and clear stored key",
|
|
6286
|
+
done: "Done",
|
|
6287
|
+
"enter-key": "Enter a TypeSafe API key",
|
|
6288
|
+
"verify-again": "Verify again",
|
|
6289
|
+
"verify-enable": "Verify and enable"
|
|
6290
|
+
};
|
|
6291
|
+
var fireAndForget = async (action) => {
|
|
6292
|
+
try {
|
|
6293
|
+
await action;
|
|
6294
|
+
} catch {}
|
|
6295
|
+
};
|
|
5518
6296
|
var transportLabel = (credentials) => {
|
|
5519
6297
|
if (credentials.transport === "openrouter") {
|
|
5520
6298
|
return "OpenRouter (reusing pi login)";
|
|
5521
6299
|
}
|
|
5522
6300
|
switch (credentials.source) {
|
|
5523
|
-
case "advisor-json":
|
|
6301
|
+
case "advisor-json": {
|
|
5524
6302
|
return "TypeSafe (key: advisor.json — plaintext, not recommended)";
|
|
5525
|
-
|
|
6303
|
+
}
|
|
6304
|
+
case "bun-secrets": {
|
|
5526
6305
|
return "TypeSafe (key: Bun.secrets)";
|
|
5527
|
-
|
|
6306
|
+
}
|
|
6307
|
+
case "file": {
|
|
5528
6308
|
return "TypeSafe (key: stored file, mode 0600)";
|
|
5529
|
-
|
|
6309
|
+
}
|
|
6310
|
+
default: {
|
|
5530
6311
|
return "TypeSafe (key: TYPESAFE_API_KEY)";
|
|
6312
|
+
}
|
|
5531
6313
|
}
|
|
5532
6314
|
};
|
|
5533
6315
|
var defaultVerify = async (credentials) => {
|
|
@@ -5566,9 +6348,7 @@ class JevSetupSubmenu {
|
|
|
5566
6348
|
onSubmit: (value) => this.submitEnteredKey(value),
|
|
5567
6349
|
placeholder: "Paste a TypeSafe API key"
|
|
5568
6350
|
});
|
|
5569
|
-
this.refresh()
|
|
5570
|
-
return;
|
|
5571
|
-
});
|
|
6351
|
+
fireAndForget(this.refresh());
|
|
5572
6352
|
}
|
|
5573
6353
|
get focused() {
|
|
5574
6354
|
return this._focused;
|
|
@@ -5609,24 +6389,20 @@ class JevSetupSubmenu {
|
|
|
5609
6389
|
""
|
|
5610
6390
|
];
|
|
5611
6391
|
if (this.credentials) {
|
|
5612
|
-
lines.push(` Transport: ${transportLabel(this.credentials)}`);
|
|
5613
|
-
lines.push(` Filter: ${enabled ? "On" : "Off"}`);
|
|
6392
|
+
lines.push(` Transport: ${transportLabel(this.credentials)}`, ` Filter: ${enabled ? "On" : "Off"}`);
|
|
5614
6393
|
} else if (this.mode === "verifying") {
|
|
5615
6394
|
lines.push(" Checking available Jev credentials…");
|
|
5616
6395
|
} 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.");
|
|
6396
|
+
lines.push(" No Jev credentials found. Enter a TypeSafe API key below, or add", " an OpenRouter login in pi; it is reused automatically.");
|
|
5619
6397
|
}
|
|
5620
6398
|
if (this.notice) {
|
|
5621
|
-
lines.push("");
|
|
5622
|
-
lines.push(theme.fg("warning", ` ${this.notice}`));
|
|
6399
|
+
lines.push("", theme.fg("warning", ` ${this.notice}`));
|
|
5623
6400
|
}
|
|
5624
6401
|
lines.push("");
|
|
5625
6402
|
if (this.mode === "verifying") {
|
|
5626
6403
|
lines.push(" Verifying with a live Jev call…");
|
|
5627
6404
|
} 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"));
|
|
6405
|
+
lines.push(` ${this.maskedInput.render(Math.max(10, width - 4))[0] ?? ""}`, theme.fg("dim", " Enter: verify · Esc: cancel"));
|
|
5630
6406
|
} else {
|
|
5631
6407
|
for (const [index, label] of this.labels().entries()) {
|
|
5632
6408
|
const prefix = index === this.selectedIndex ? "→ " : " ";
|
|
@@ -5648,19 +6424,8 @@ class JevSetupSubmenu {
|
|
|
5648
6424
|
actions.push("done");
|
|
5649
6425
|
return actions;
|
|
5650
6426
|
}
|
|
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
6427
|
labels() {
|
|
5662
|
-
|
|
5663
|
-
return this.actions().map((action) => labels[action]);
|
|
6428
|
+
return this.actions().map((action) => ACTION_LABELS[action]);
|
|
5664
6429
|
}
|
|
5665
6430
|
async refresh() {
|
|
5666
6431
|
const wasVerifying = this.mode === "verifying";
|
|
@@ -5677,30 +6442,25 @@ class JevSetupSubmenu {
|
|
|
5677
6442
|
this.options.tui.requestRender();
|
|
5678
6443
|
}
|
|
5679
6444
|
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;
|
|
6445
|
+
if (action === "done") {
|
|
6446
|
+
this.options.done();
|
|
6447
|
+
return;
|
|
6448
|
+
}
|
|
6449
|
+
if (action === "enter-key") {
|
|
6450
|
+
this.mode = "key-entry";
|
|
6451
|
+
return;
|
|
6452
|
+
}
|
|
6453
|
+
if (action === "disable") {
|
|
6454
|
+
this.notice = undefined;
|
|
6455
|
+
this.options.done("Off");
|
|
6456
|
+
return;
|
|
6457
|
+
}
|
|
6458
|
+
if (action === "disable-clear") {
|
|
6459
|
+
fireAndForget(this.disableAndClear());
|
|
6460
|
+
return;
|
|
6461
|
+
}
|
|
6462
|
+
if (action === "verify-again" || action === "verify-enable") {
|
|
6463
|
+
fireAndForget(this.verifyAndEnable());
|
|
5704
6464
|
}
|
|
5705
6465
|
}
|
|
5706
6466
|
async disableAndClear() {
|
|
@@ -5772,11 +6532,15 @@ class JevSetupSubmenu {
|
|
|
5772
6532
|
}
|
|
5773
6533
|
}
|
|
5774
6534
|
|
|
6535
|
+
// src/ui/model-multi-selector.ts
|
|
6536
|
+
class SearchableModelMultiSelector extends ModelSelectorAdapter {
|
|
6537
|
+
constructor(options) {
|
|
6538
|
+
super(new SearchableModelList(options));
|
|
6539
|
+
}
|
|
6540
|
+
}
|
|
6541
|
+
|
|
5775
6542
|
// src/ui/text-setting-submenu.ts
|
|
5776
|
-
import {
|
|
5777
|
-
Input as Input3,
|
|
5778
|
-
truncateToWidth as truncateToWidth6
|
|
5779
|
-
} from "@earendil-works/pi-tui";
|
|
6543
|
+
import { Input as Input3, truncateToWidth as truncateToWidth6 } from "@earendil-works/pi-tui";
|
|
5780
6544
|
|
|
5781
6545
|
class TextSettingSubmenu {
|
|
5782
6546
|
input = new Input3;
|
|
@@ -5939,8 +6703,28 @@ var jevItems = (settings, theme, tui) => [
|
|
|
5939
6703
|
])
|
|
5940
6704
|
}
|
|
5941
6705
|
];
|
|
6706
|
+
var advisorModelWhitelistItem = (settings, modelRefs, keybindings, theme, tui) => ({
|
|
6707
|
+
currentValue: settings.modelWhitelist?.length ? settings.modelWhitelist.join(", ") : "Any model",
|
|
6708
|
+
description: "Only the exact provider/model references listed here may call the Advisor; an empty list allows every model.",
|
|
6709
|
+
id: "modelWhitelist",
|
|
6710
|
+
label: "Advisor model whitelist",
|
|
6711
|
+
submenu: (_currentValue, done) => new SearchableModelMultiSelector({
|
|
6712
|
+
allOptions: [
|
|
6713
|
+
...new Set([...modelRefs ?? [], ...settings.modelWhitelist ?? []])
|
|
6714
|
+
],
|
|
6715
|
+
currentOptions: settings.modelWhitelist ?? [],
|
|
6716
|
+
keybindings: keybindings ?? getKeybindings(),
|
|
6717
|
+
multiSelect: true,
|
|
6718
|
+
onCancel: done,
|
|
6719
|
+
onSelect: (values) => done(values.join(",")),
|
|
6720
|
+
theme,
|
|
6721
|
+
title: "Advisor model whitelist",
|
|
6722
|
+
tui
|
|
6723
|
+
})
|
|
6724
|
+
});
|
|
5942
6725
|
var createSettingsItems = ({
|
|
5943
6726
|
effortLevels,
|
|
6727
|
+
modelWhitelist,
|
|
5944
6728
|
presets,
|
|
5945
6729
|
settings,
|
|
5946
6730
|
theme,
|
|
@@ -5970,6 +6754,7 @@ var createSettingsItems = ({
|
|
|
5970
6754
|
}
|
|
5971
6755
|
];
|
|
5972
6756
|
if (settings.simpleMode) {
|
|
6757
|
+
items.push(modelWhitelist);
|
|
5973
6758
|
return items;
|
|
5974
6759
|
}
|
|
5975
6760
|
items.push({
|
|
@@ -5978,7 +6763,7 @@ var createSettingsItems = ({
|
|
|
5978
6763
|
id: "effort",
|
|
5979
6764
|
label: "Advisor reasoning",
|
|
5980
6765
|
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), {
|
|
6766
|
+
}, 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
6767
|
currentValue: settings.customRule || "None",
|
|
5983
6768
|
description: "Add a rule that triggers Advisor involvement.",
|
|
5984
6769
|
id: "customRule",
|
|
@@ -6080,10 +6865,7 @@ var createSettingsItems = ({
|
|
|
6080
6865
|
};
|
|
6081
6866
|
|
|
6082
6867
|
// src/ui/settings-list-adapter.ts
|
|
6083
|
-
import {
|
|
6084
|
-
Key as Key3,
|
|
6085
|
-
matchesKey as matchesKey3
|
|
6086
|
-
} from "@earendil-works/pi-tui";
|
|
6868
|
+
import { Key as Key3, matchesKey as matchesKey3 } from "@earendil-works/pi-tui";
|
|
6087
6869
|
|
|
6088
6870
|
class SettingsListAdapter {
|
|
6089
6871
|
list;
|
|
@@ -6111,7 +6893,7 @@ class SettingsListAdapter {
|
|
|
6111
6893
|
}
|
|
6112
6894
|
setSelectedId(items, selectedId) {
|
|
6113
6895
|
const selectedIndex = items.findIndex((item) => item.id === selectedId);
|
|
6114
|
-
if (selectedIndex
|
|
6896
|
+
if (selectedIndex !== -1) {
|
|
6115
6897
|
this.privateFields().selectedIndex = selectedIndex;
|
|
6116
6898
|
}
|
|
6117
6899
|
}
|
|
@@ -6154,102 +6936,160 @@ class SettingsListAdapter {
|
|
|
6154
6936
|
}
|
|
6155
6937
|
|
|
6156
6938
|
// src/ui/settings-mutations.ts
|
|
6157
|
-
var
|
|
6158
|
-
"scoutEnabled",
|
|
6159
|
-
"showUsageDetails",
|
|
6160
|
-
"showUsageFooter",
|
|
6161
|
-
"planGate",
|
|
6162
|
-
"failureGate",
|
|
6163
|
-
"completionGate",
|
|
6164
|
-
"collapseResponses",
|
|
6165
|
-
"blockOnBlocked",
|
|
6939
|
+
var BOOLEAN_SETTING_FIELDS = [
|
|
6166
6940
|
"autoLoopGate",
|
|
6167
|
-
"
|
|
6941
|
+
"blockOnBlocked",
|
|
6942
|
+
"collapseResponses",
|
|
6943
|
+
"completionGate",
|
|
6944
|
+
"failureGate",
|
|
6168
6945
|
"herdrIntegration",
|
|
6946
|
+
"outcomeLogging",
|
|
6947
|
+
"planGate",
|
|
6169
6948
|
"redactSecrets",
|
|
6949
|
+
"scoutEnabled",
|
|
6950
|
+
"sessionSummary",
|
|
6951
|
+
"showUsageDetails",
|
|
6952
|
+
"showUsageFooter",
|
|
6170
6953
|
"trackedFileContent",
|
|
6171
|
-
"untrackedContent"
|
|
6172
|
-
|
|
6173
|
-
|
|
6174
|
-
var
|
|
6954
|
+
"untrackedContent"
|
|
6955
|
+
];
|
|
6956
|
+
var BOOLEAN_SETTING_IDS = new Set(BOOLEAN_SETTING_FIELDS);
|
|
6957
|
+
var parseModelWhitelist = (value) => [
|
|
6958
|
+
...new Set(value.split(",").map((model) => model.trim()).filter(Boolean))
|
|
6959
|
+
];
|
|
6960
|
+
var applyCoreMutation = (settings, id, value, presets) => {
|
|
6175
6961
|
switch (id) {
|
|
6176
|
-
case "context":
|
|
6962
|
+
case "context": {
|
|
6177
6963
|
settings.contextMaxChars = presets.find((preset) => preset.label === value)?.value ?? settings.contextMaxChars;
|
|
6178
|
-
|
|
6179
|
-
|
|
6964
|
+
return true;
|
|
6965
|
+
}
|
|
6966
|
+
case "simpleMode": {
|
|
6180
6967
|
settings.simpleMode = value === "On";
|
|
6181
|
-
|
|
6182
|
-
|
|
6968
|
+
return true;
|
|
6969
|
+
}
|
|
6970
|
+
case "alwaysOn": {
|
|
6183
6971
|
settings.alwaysOn = value === "On";
|
|
6184
|
-
|
|
6185
|
-
|
|
6972
|
+
return true;
|
|
6973
|
+
}
|
|
6974
|
+
case "effort": {
|
|
6186
6975
|
settings.effort = value;
|
|
6187
|
-
|
|
6188
|
-
|
|
6976
|
+
return true;
|
|
6977
|
+
}
|
|
6978
|
+
case "customRule": {
|
|
6189
6979
|
settings.customRule = value.trim() || undefined;
|
|
6190
|
-
|
|
6191
|
-
|
|
6980
|
+
return true;
|
|
6981
|
+
}
|
|
6982
|
+
case "toolPolicies": {
|
|
6192
6983
|
settings.toolPolicies = JSON.parse(value);
|
|
6193
|
-
|
|
6194
|
-
|
|
6984
|
+
return true;
|
|
6985
|
+
}
|
|
6986
|
+
case "loopThreshold": {
|
|
6195
6987
|
settings.loopThreshold = Number(value.replace("After ", "").replace(" repeats", ""));
|
|
6196
|
-
|
|
6197
|
-
|
|
6988
|
+
return true;
|
|
6989
|
+
}
|
|
6990
|
+
case "maxCallsPerSession": {
|
|
6198
6991
|
settings.maxCallsPerSession = value === "∞" ? undefined : Number(value);
|
|
6199
|
-
|
|
6200
|
-
|
|
6992
|
+
return true;
|
|
6993
|
+
}
|
|
6994
|
+
case "modelWhitelist": {
|
|
6995
|
+
settings.modelWhitelist = parseModelWhitelist(value);
|
|
6996
|
+
return true;
|
|
6997
|
+
}
|
|
6998
|
+
case "failureMode": {
|
|
6201
6999
|
settings.failureMode = value;
|
|
6202
|
-
|
|
6203
|
-
|
|
7000
|
+
return true;
|
|
7001
|
+
}
|
|
7002
|
+
case "gitContext": {
|
|
6204
7003
|
settings.gitContext = value;
|
|
6205
|
-
|
|
6206
|
-
|
|
7004
|
+
return true;
|
|
7005
|
+
}
|
|
7006
|
+
default: {
|
|
7007
|
+
return false;
|
|
7008
|
+
}
|
|
7009
|
+
}
|
|
7010
|
+
};
|
|
7011
|
+
var applyNumericMutation = (settings, id, value) => {
|
|
7012
|
+
switch (id) {
|
|
7013
|
+
case "toolResultMaxLines": {
|
|
6207
7014
|
settings.toolResultMaxLines = Number(value);
|
|
6208
|
-
|
|
6209
|
-
|
|
7015
|
+
return true;
|
|
7016
|
+
}
|
|
7017
|
+
case "toolResultMaxBytes": {
|
|
6210
7018
|
settings.toolResultMaxBytes = Number(value);
|
|
6211
|
-
|
|
6212
|
-
|
|
7019
|
+
return true;
|
|
7020
|
+
}
|
|
7021
|
+
case "gitContextMaxChars": {
|
|
6213
7022
|
settings.gitContextMaxChars = Number(value);
|
|
6214
|
-
|
|
6215
|
-
|
|
7023
|
+
return true;
|
|
7024
|
+
}
|
|
7025
|
+
default: {
|
|
7026
|
+
return false;
|
|
7027
|
+
}
|
|
7028
|
+
}
|
|
7029
|
+
};
|
|
7030
|
+
var applyJevMutation = (settings, id, value) => {
|
|
7031
|
+
switch (id) {
|
|
7032
|
+
case "jevFilter": {
|
|
6216
7033
|
settings.jevFilterEnabled = value === "On";
|
|
6217
|
-
|
|
6218
|
-
|
|
7034
|
+
return true;
|
|
7035
|
+
}
|
|
7036
|
+
case "jevFilterSkipConfidence": {
|
|
6219
7037
|
settings.jevFilterSkipConfidence = Number(value);
|
|
6220
|
-
|
|
6221
|
-
|
|
7038
|
+
return true;
|
|
7039
|
+
}
|
|
7040
|
+
case "jevFilterNoulMargin": {
|
|
6222
7041
|
settings.jevFilterNoulMargin = Number(value);
|
|
6223
|
-
|
|
6224
|
-
|
|
7042
|
+
return true;
|
|
7043
|
+
}
|
|
7044
|
+
case "jevFilterOverrideWindow": {
|
|
6225
7045
|
settings.jevFilterOverrideWindow = Number(value.replace(" turns", ""));
|
|
6226
|
-
|
|
6227
|
-
|
|
6228
|
-
|
|
6229
|
-
|
|
6230
|
-
|
|
7046
|
+
return true;
|
|
7047
|
+
}
|
|
7048
|
+
case "jevTurnGateEveryTurns": {
|
|
7049
|
+
settings.jevTurnGateEveryTurns = value === "Off" ? 0 : Number(value.replaceAll(/[^0-9]/gu, ""));
|
|
7050
|
+
return true;
|
|
7051
|
+
}
|
|
7052
|
+
case "jevTurnGateNoulThreshold": {
|
|
6231
7053
|
settings.jevTurnGateNoulThreshold = Number(value);
|
|
6232
|
-
|
|
6233
|
-
|
|
7054
|
+
return true;
|
|
7055
|
+
}
|
|
7056
|
+
case "jevModel": {
|
|
6234
7057
|
settings.jevModel = value.trim() || DEFAULT_JEV_MODEL;
|
|
6235
|
-
|
|
6236
|
-
|
|
7058
|
+
return true;
|
|
7059
|
+
}
|
|
7060
|
+
case "jevTimeoutMs": {
|
|
6237
7061
|
settings.jevTimeoutMs = Number(value);
|
|
6238
|
-
|
|
6239
|
-
|
|
7062
|
+
return true;
|
|
7063
|
+
}
|
|
7064
|
+
case "jevDigestMaxChars": {
|
|
6240
7065
|
settings.jevDigestMaxChars = Number(value);
|
|
6241
|
-
|
|
6242
|
-
|
|
7066
|
+
return true;
|
|
7067
|
+
}
|
|
7068
|
+
case "jevPricePerMtok": {
|
|
6243
7069
|
settings.jevPricePerMtok = Number(value);
|
|
6244
|
-
|
|
6245
|
-
|
|
7070
|
+
return true;
|
|
7071
|
+
}
|
|
7072
|
+
case "jevTransport": {
|
|
6246
7073
|
settings.jevTransport = value;
|
|
6247
|
-
|
|
6248
|
-
|
|
6249
|
-
|
|
6250
|
-
|
|
6251
|
-
|
|
6252
|
-
|
|
7074
|
+
return true;
|
|
7075
|
+
}
|
|
7076
|
+
default: {
|
|
7077
|
+
return false;
|
|
7078
|
+
}
|
|
7079
|
+
}
|
|
7080
|
+
};
|
|
7081
|
+
var mutateAdvisorSettings = (settings, id, value, presets) => {
|
|
7082
|
+
if (applyCoreMutation(settings, id, value, presets)) {
|
|
7083
|
+
return;
|
|
7084
|
+
}
|
|
7085
|
+
if (applyNumericMutation(settings, id, value)) {
|
|
7086
|
+
return;
|
|
7087
|
+
}
|
|
7088
|
+
if (applyJevMutation(settings, id, value)) {
|
|
7089
|
+
return;
|
|
7090
|
+
}
|
|
7091
|
+
if (BOOLEAN_SETTING_IDS.has(id)) {
|
|
7092
|
+
settings[id] = value === "On";
|
|
6253
7093
|
}
|
|
6254
7094
|
};
|
|
6255
7095
|
|
|
@@ -6280,7 +7120,7 @@ class AdvisorSettingsSelector {
|
|
|
6280
7120
|
label: String(configuredContext),
|
|
6281
7121
|
value: configuredContext
|
|
6282
7122
|
}
|
|
6283
|
-
].
|
|
7123
|
+
].toSorted((a, b) => a.value - b.value);
|
|
6284
7124
|
if (this.settings.simpleMode) {
|
|
6285
7125
|
this.startSimpleModeGradient();
|
|
6286
7126
|
}
|
|
@@ -6311,8 +7151,10 @@ class AdvisorSettingsSelector {
|
|
|
6311
7151
|
}
|
|
6312
7152
|
return defaultLabel(text, selected);
|
|
6313
7153
|
};
|
|
7154
|
+
const modelWhitelist = advisorModelWhitelistItem(this.settings, this.options.modelRefs, this.options.keybindings, this.options.theme, this.options.tui);
|
|
6314
7155
|
const items = createSettingsItems({
|
|
6315
7156
|
effortLevels: this.options.effortLevels,
|
|
7157
|
+
modelWhitelist,
|
|
6316
7158
|
presets: this.presets,
|
|
6317
7159
|
settings: this.settings,
|
|
6318
7160
|
theme: this.options.theme,
|
|
@@ -6352,16 +7194,16 @@ class AdvisorSettingsSelector {
|
|
|
6352
7194
|
(this.options.onChange ?? this.options.onSave)?.({
|
|
6353
7195
|
...this.settings,
|
|
6354
7196
|
showUsageDetails: this.settings.showUsageDetails ?? true,
|
|
6355
|
-
toolPolicies: { ...this.settings.toolPolicies
|
|
7197
|
+
toolPolicies: { ...this.settings.toolPolicies }
|
|
6356
7198
|
});
|
|
6357
|
-
if (id === "context" || id === "simpleMode" || id === "customRule" || id === "toolPolicies") {
|
|
7199
|
+
if (id === "context" || id === "simpleMode" || id === "customRule" || id === "modelWhitelist" || id === "toolPolicies") {
|
|
6358
7200
|
this.settingsList = this.createSettingsList(id);
|
|
6359
7201
|
}
|
|
6360
7202
|
}
|
|
6361
7203
|
}
|
|
6362
7204
|
|
|
6363
7205
|
// src/commands/settings-persistence.ts
|
|
6364
|
-
var
|
|
7206
|
+
var applySessionSettings = (settings) => {
|
|
6365
7207
|
setAdvisorEffortRef(settings.effort === "Default (Model Default)" ? undefined : settings.effort);
|
|
6366
7208
|
setContextMaxCharsRef(settings.contextMaxChars);
|
|
6367
7209
|
setAdvisorPlanGateRef(settings.planGate);
|
|
@@ -6373,6 +7215,7 @@ var applyAdvisorSettings = (settings) => {
|
|
|
6373
7215
|
setAdvisorAutoLoopGateRef(settings.autoLoopGate ?? true);
|
|
6374
7216
|
setAdvisorLoopThresholdRef(settings.loopThreshold ?? 3);
|
|
6375
7217
|
setAdvisorMaxCallsPerSessionRef(settings.maxCallsPerSession);
|
|
7218
|
+
setAdvisorModelWhitelistRef(settings.modelWhitelist ?? []);
|
|
6376
7219
|
setAdvisorSessionSummaryRef(settings.sessionSummary ?? false);
|
|
6377
7220
|
setAdvisorScoutEnabledRef(settings.scoutEnabled ?? false);
|
|
6378
7221
|
setShowUsageDetailsRef(settings.showUsageDetails ?? true);
|
|
@@ -6381,6 +7224,8 @@ var applyAdvisorSettings = (settings) => {
|
|
|
6381
7224
|
setAlwaysOnRef(settings.alwaysOn ?? false);
|
|
6382
7225
|
setAdvisorFailureModeRef(settings.failureMode ?? "block-session");
|
|
6383
7226
|
setAdvisorHerdrIntegrationRef(settings.herdrIntegration ?? true);
|
|
7227
|
+
};
|
|
7228
|
+
var applyJevSettings = (settings) => {
|
|
6384
7229
|
setAdvisorJevFilterEnabledRef(settings.jevFilterEnabled ?? false);
|
|
6385
7230
|
setAdvisorJevFilterSkipConfidenceRef(settings.jevFilterSkipConfidence ?? DEFAULT_JEV_FILTER_SKIP_CONFIDENCE);
|
|
6386
7231
|
setAdvisorJevFilterNoulMarginRef(settings.jevFilterNoulMargin ?? DEFAULT_JEV_FILTER_NOUL_MARGIN);
|
|
@@ -6392,6 +7237,8 @@ var applyAdvisorSettings = (settings) => {
|
|
|
6392
7237
|
setAdvisorJevTransportRef(settings.jevTransport ?? DEFAULT_JEV_TRANSPORT);
|
|
6393
7238
|
setAdvisorJevTurnGateEveryTurnsRef(settings.jevTurnGateEveryTurns ?? DEFAULT_JEV_TURN_GATE_EVERY_TURNS);
|
|
6394
7239
|
setAdvisorJevTurnGateNoulThresholdRef(settings.jevTurnGateNoulThreshold ?? DEFAULT_JEV_TURN_GATE_NOUL_THRESHOLD);
|
|
7240
|
+
};
|
|
7241
|
+
var applyDisclosureSettings = (settings) => {
|
|
6395
7242
|
setAdvisorToolResultMaxLinesRef(settings.toolResultMaxLines ?? 2000);
|
|
6396
7243
|
setAdvisorToolResultMaxBytesRef(settings.toolResultMaxBytes ?? 50 * 1024);
|
|
6397
7244
|
setAdvisorRedactSecretsRef(settings.redactSecrets ?? false);
|
|
@@ -6402,6 +7249,11 @@ var applyAdvisorSettings = (settings) => {
|
|
|
6402
7249
|
setAdvisorUntrackedContentRef(settings.untrackedContent ?? false);
|
|
6403
7250
|
setAdvisorOutcomeLoggingRef(settings.outcomeLogging ?? false);
|
|
6404
7251
|
};
|
|
7252
|
+
var applyAdvisorSettings = (settings) => {
|
|
7253
|
+
applySessionSettings(settings);
|
|
7254
|
+
applyJevSettings(settings);
|
|
7255
|
+
applyDisclosureSettings(settings);
|
|
7256
|
+
};
|
|
6405
7257
|
var saveAdvisorSettings = (ctx, settings) => {
|
|
6406
7258
|
applyAdvisorSettings(settings);
|
|
6407
7259
|
const persisted = getPersistedModelRefs();
|
|
@@ -6421,10 +7273,12 @@ var registerSettingsCommands = (runtime) => {
|
|
|
6421
7273
|
return;
|
|
6422
7274
|
}
|
|
6423
7275
|
const initial = getAdvisorSettings();
|
|
6424
|
-
await ctx.ui.custom((tui, theme,
|
|
7276
|
+
await ctx.ui.custom((tui, theme, keybindings, done) => new AdvisorSettingsSelector({
|
|
6425
7277
|
effortLevels: EFFORT_LEVELS,
|
|
6426
7278
|
initial,
|
|
6427
|
-
|
|
7279
|
+
keybindings,
|
|
7280
|
+
modelRefs: getConfiguredModelRefs(ctx),
|
|
7281
|
+
onCancel: () => done(undefined),
|
|
6428
7282
|
onChange: (settings) => {
|
|
6429
7283
|
try {
|
|
6430
7284
|
saveAdvisorSettings(ctx, settings);
|
|
@@ -6484,6 +7338,7 @@ import {
|
|
|
6484
7338
|
writeFile
|
|
6485
7339
|
} from "node:fs/promises";
|
|
6486
7340
|
import { join as join5 } from "node:path";
|
|
7341
|
+
import { setTimeout as sleep2 } from "node:timers/promises";
|
|
6487
7342
|
import { getAgentDir as getAgentDir4 } from "@earendil-works/pi-coding-agent";
|
|
6488
7343
|
var ADOPTIONS = [
|
|
6489
7344
|
"followed",
|
|
@@ -6499,6 +7354,7 @@ var VALIDATIONS = [
|
|
|
6499
7354
|
var MAX_LOG_BYTES = 1024 * 1024;
|
|
6500
7355
|
var statePath = () => join5(getAgentDir4(), "advisor-outcomes-salt");
|
|
6501
7356
|
var outcomeLogPath = () => join5(getAgentDir4(), "advisor-outcomes.jsonl");
|
|
7357
|
+
var isErrnoException = (error) => error instanceof Error && ("code" in error);
|
|
6502
7358
|
var salt = async () => {
|
|
6503
7359
|
const path = statePath();
|
|
6504
7360
|
await mkdir(getAgentDir4(), { mode: 448, recursive: true });
|
|
@@ -6510,7 +7366,7 @@ var salt = async () => {
|
|
|
6510
7366
|
}
|
|
6511
7367
|
await unlink(path);
|
|
6512
7368
|
} catch (error) {
|
|
6513
|
-
if (error.code !== "ENOENT") {
|
|
7369
|
+
if (!isErrnoException(error) || error.code !== "ENOENT") {
|
|
6514
7370
|
throw error;
|
|
6515
7371
|
}
|
|
6516
7372
|
}
|
|
@@ -6521,7 +7377,7 @@ var salt = async () => {
|
|
|
6521
7377
|
await link(temporary, path);
|
|
6522
7378
|
return value;
|
|
6523
7379
|
} catch (error) {
|
|
6524
|
-
if (error.code !== "EEXIST") {
|
|
7380
|
+
if (!isErrnoException(error) || error.code !== "EEXIST") {
|
|
6525
7381
|
throw error;
|
|
6526
7382
|
}
|
|
6527
7383
|
} finally {
|
|
@@ -6553,7 +7409,7 @@ var withOutcomeLock = async (run) => {
|
|
|
6553
7409
|
}
|
|
6554
7410
|
}
|
|
6555
7411
|
} catch (error) {
|
|
6556
|
-
if (error.code !== "EEXIST") {
|
|
7412
|
+
if (!isErrnoException(error) || error.code !== "EEXIST") {
|
|
6557
7413
|
throw error;
|
|
6558
7414
|
}
|
|
6559
7415
|
const observed = await stat(lockPath).catch(() => {
|
|
@@ -6570,7 +7426,7 @@ var withOutcomeLock = async (run) => {
|
|
|
6570
7426
|
}
|
|
6571
7427
|
continue;
|
|
6572
7428
|
}
|
|
6573
|
-
await
|
|
7429
|
+
await sleep2(5);
|
|
6574
7430
|
}
|
|
6575
7431
|
}
|
|
6576
7432
|
throw new Error("Timed out waiting to append an Advisor outcome.");
|
|
@@ -6596,11 +7452,8 @@ var appendOutcome = async (record) => {
|
|
|
6596
7452
|
}
|
|
6597
7453
|
throw error;
|
|
6598
7454
|
});
|
|
6599
|
-
|
|
6600
|
-
|
|
6601
|
-
} else {
|
|
6602
|
-
await appendFile(path, line, { encoding: "utf8", mode: 384 });
|
|
6603
|
-
}
|
|
7455
|
+
const overflow = currentBytes + Buffer.byteLength(line) > MAX_LOG_BYTES;
|
|
7456
|
+
await (overflow ? writeFile(path, line, { encoding: "utf-8", mode: 384 }) : appendFile(path, line, { encoding: "utf-8", mode: 384 }));
|
|
6604
7457
|
await chmod(path, 384);
|
|
6605
7458
|
return next;
|
|
6606
7459
|
});
|
|
@@ -6698,7 +7551,7 @@ var handleJevTurnEnd = async (registration, ctx) => {
|
|
|
6698
7551
|
if (interval <= 0 || session.turnsSinceConsultation <= 0 || session.turnsSinceConsultation % interval !== 0) {
|
|
6699
7552
|
return;
|
|
6700
7553
|
}
|
|
6701
|
-
if (isSimpleMode() || session.blocked || !registration.activeTools().includes("ask_advisor") || !session.canConsult(getAdvisorMaxCallsPerSession())) {
|
|
7554
|
+
if (isSimpleMode() || session.blocked || !registration.activeTools().includes("ask_advisor") || !advisorModelIsAllowed(ctx) || !session.canConsult(getAdvisorMaxCallsPerSession())) {
|
|
6702
7555
|
return;
|
|
6703
7556
|
}
|
|
6704
7557
|
const consult = registration.deps?.consult ?? registration.consult;
|
|
@@ -6758,7 +7611,7 @@ var handleJevTurnEnd = async (registration, ctx) => {
|
|
|
6758
7611
|
}
|
|
6759
7612
|
} catch (error) {
|
|
6760
7613
|
session.recordJevGateFailure();
|
|
6761
|
-
if (error instanceof
|
|
7614
|
+
if (error instanceof JevFailureError) {
|
|
6762
7615
|
notifyFailureOnce(ctx, error.category, error.message);
|
|
6763
7616
|
} else if (!ctx.signal?.aborted) {
|
|
6764
7617
|
notifyFailureOnce(ctx, "error", error instanceof Error ? error.message : String(error));
|
|
@@ -6773,9 +7626,7 @@ var registerJevTurnGate = (on, registration) => {
|
|
|
6773
7626
|
import { Type } from "typebox";
|
|
6774
7627
|
|
|
6775
7628
|
// src/tools/render-advisor-result.ts
|
|
6776
|
-
import {
|
|
6777
|
-
getMarkdownTheme as getMarkdownTheme4
|
|
6778
|
-
} from "@earendil-works/pi-coding-agent";
|
|
7629
|
+
import { getMarkdownTheme as getMarkdownTheme4 } from "@earendil-works/pi-coding-agent";
|
|
6779
7630
|
import { Box as Box3, Markdown as Markdown4, Spacer, Text as Text5 } from "@earendil-works/pi-tui";
|
|
6780
7631
|
var advisorResultDetails = (result) => result.details;
|
|
6781
7632
|
var syncRenderPhase = (context, phase) => {
|
|
@@ -6832,6 +7683,24 @@ var renderPartialAdvisorResult = (box, result, expanded, theme, context) => {
|
|
|
6832
7683
|
box.addChild(new Markdown4(adviceForDisplay(details.text, expanded), 0, 0, getMarkdownTheme4()));
|
|
6833
7684
|
}
|
|
6834
7685
|
};
|
|
7686
|
+
var thinkingPreview = (details) => details?.thinking?.trim() ? `${details.thinking.slice(0, 300)}${details.thinking.length > 300 ? "…" : ""}` : "";
|
|
7687
|
+
var finalResultLines = (details, advice, theme) => {
|
|
7688
|
+
const lines = [renderAdvisorResponseHeader(hasSoundVerdict(advice), theme)];
|
|
7689
|
+
if (details?.advisor) {
|
|
7690
|
+
lines.push(theme.fg("dim", ` ${details.advisor}`));
|
|
7691
|
+
}
|
|
7692
|
+
if (getAdvisorSettings().showUsageDetails) {
|
|
7693
|
+
const usage = formatAdvisorUsage(details?.usage);
|
|
7694
|
+
if (usage) {
|
|
7695
|
+
lines.push(theme.fg("dim", ` Usage: ${usage}`));
|
|
7696
|
+
}
|
|
7697
|
+
}
|
|
7698
|
+
const attachments = attachmentLabels(details);
|
|
7699
|
+
if (attachments.length) {
|
|
7700
|
+
lines.push(theme.fg("dim", ` ${attachments.join(" · ")}`));
|
|
7701
|
+
}
|
|
7702
|
+
return lines;
|
|
7703
|
+
};
|
|
6835
7704
|
var renderFinalAdvisorResult = (box, result, expanded, theme, context) => {
|
|
6836
7705
|
syncRenderPhase(context, "final");
|
|
6837
7706
|
if (context.state.timerId) {
|
|
@@ -6854,21 +7723,8 @@ var renderFinalAdvisorResult = (box, result, expanded, theme, context) => {
|
|
|
6854
7723
|
return;
|
|
6855
7724
|
}
|
|
6856
7725
|
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 ? "…" : ""}` : "";
|
|
7726
|
+
const thinking = thinkingPreview(details);
|
|
7727
|
+
const lines = finalResultLines(details, advice, theme);
|
|
6872
7728
|
const displayAdvice = advice || "(Advisor returned no advice.)";
|
|
6873
7729
|
box.addChild(new Text5(lines.join(`
|
|
6874
7730
|
`), 0, 0));
|
|
@@ -6891,6 +7747,12 @@ var renderAdvisorResult = (result, { isPartial, expanded }, theme, context) => {
|
|
|
6891
7747
|
};
|
|
6892
7748
|
|
|
6893
7749
|
// src/tools/register-ask-advisor.ts
|
|
7750
|
+
var assertAdvisorModelAccess = (ctx) => {
|
|
7751
|
+
const accessReason = advisorModelAccessReason(ctx);
|
|
7752
|
+
if (accessReason) {
|
|
7753
|
+
throw new Error(accessReason);
|
|
7754
|
+
}
|
|
7755
|
+
};
|
|
6894
7756
|
var claimTrackedHandoff = (session, includeTrackedFiles) => {
|
|
6895
7757
|
if (!includeTrackedFiles?.length) {
|
|
6896
7758
|
return;
|
|
@@ -6913,6 +7775,7 @@ var registerAskAdvisorTool = ({
|
|
|
6913
7775
|
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
7776
|
async execute(_id, params, signal, onUpdate, ctx) {
|
|
6915
7777
|
reservedCalls.delete(_id);
|
|
7778
|
+
assertAdvisorModelAccess(ctx);
|
|
6916
7779
|
if (!(isSimpleMode() || session.canConsult(getAdvisorMaxCallsPerSession()))) {
|
|
6917
7780
|
throw new Error("Advisor call budget exhausted for this session.");
|
|
6918
7781
|
}
|
|
@@ -6985,7 +7848,22 @@ var registerAskAdvisorTool = ({
|
|
|
6985
7848
|
const usage = snapshotAdvisorUsage(result.usage);
|
|
6986
7849
|
const piUsage = advisorUsageForPi(result.usage);
|
|
6987
7850
|
updateAdvisorUsageStatus(ctx, session);
|
|
6988
|
-
|
|
7851
|
+
const details = {
|
|
7852
|
+
adviceId: result.adviceId,
|
|
7853
|
+
advisor: result.model,
|
|
7854
|
+
draftBytes: result.draftBytes,
|
|
7855
|
+
preferenceBytes: result.preferenceBytes,
|
|
7856
|
+
question: resolveAdvisorRequest(params.question),
|
|
7857
|
+
scout: scoutDetails,
|
|
7858
|
+
text: result.markdown,
|
|
7859
|
+
thinking: result.thinkingText,
|
|
7860
|
+
trackedBytes: result.trackedBytes,
|
|
7861
|
+
untrackedBytes: result.untrackedBytes
|
|
7862
|
+
};
|
|
7863
|
+
if (usage) {
|
|
7864
|
+
details.usage = usage;
|
|
7865
|
+
}
|
|
7866
|
+
const response = {
|
|
6989
7867
|
content: [
|
|
6990
7868
|
{
|
|
6991
7869
|
text: `Advisor (${result.model})
|
|
@@ -6994,21 +7872,12 @@ ${result.markdown}`,
|
|
|
6994
7872
|
type: "text"
|
|
6995
7873
|
}
|
|
6996
7874
|
],
|
|
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 } : {}
|
|
7875
|
+
details
|
|
7011
7876
|
};
|
|
7877
|
+
if (piUsage) {
|
|
7878
|
+
response.usage = piUsage;
|
|
7879
|
+
}
|
|
7880
|
+
return response;
|
|
7012
7881
|
} catch (error) {
|
|
7013
7882
|
coalescedUpdate.flush();
|
|
7014
7883
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -7076,27 +7945,31 @@ var sendAutomaticGateCall = (pi, event) => {
|
|
|
7076
7945
|
}, { deliverAs: "steer" });
|
|
7077
7946
|
};
|
|
7078
7947
|
var sendAutomaticGateFailure = (pi, markdown, usage) => {
|
|
7079
|
-
const
|
|
7948
|
+
const details = { text: markdown };
|
|
7949
|
+
if (usage) {
|
|
7950
|
+
details.usage = usage;
|
|
7951
|
+
}
|
|
7080
7952
|
pi.sendMessage({
|
|
7081
7953
|
content: markdown,
|
|
7082
7954
|
customType: "advisor-loop-result",
|
|
7083
|
-
details
|
|
7084
|
-
text: markdown,
|
|
7085
|
-
...normalizedUsage ? { usage: normalizedUsage } : {}
|
|
7086
|
-
},
|
|
7955
|
+
details,
|
|
7087
7956
|
display: true
|
|
7088
7957
|
}, { deliverAs: "steer" });
|
|
7089
7958
|
};
|
|
7090
7959
|
var sendAutomaticGateResult = (pi, result) => {
|
|
7960
|
+
const details = {
|
|
7961
|
+
advisor: result.model,
|
|
7962
|
+
decision: result.decision,
|
|
7963
|
+
text: result.markdown
|
|
7964
|
+
};
|
|
7965
|
+
const normalizedUsage = snapshotAdvisorUsage(result.usage);
|
|
7966
|
+
if (normalizedUsage) {
|
|
7967
|
+
details.usage = normalizedUsage;
|
|
7968
|
+
}
|
|
7091
7969
|
pi.sendMessage({
|
|
7092
7970
|
content: adviceForGateText(result),
|
|
7093
7971
|
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
|
-
},
|
|
7972
|
+
details,
|
|
7100
7973
|
display: true
|
|
7101
7974
|
}, { deliverAs: "steer" });
|
|
7102
7975
|
};
|
|
@@ -7112,7 +7985,7 @@ var applyGateDecision = (pi, ctx, session, result, reason, failureMode) => {
|
|
|
7112
7985
|
});
|
|
7113
7986
|
updateAdvisorUsageStatus(ctx, session);
|
|
7114
7987
|
const failure = failureEffect(result.category, result.message, ctx, session, failureMode);
|
|
7115
|
-
sendAutomaticGateFailure(pi, `**Advisor gate failure (${result.category}):** ${result.message}`, result.usage);
|
|
7988
|
+
sendAutomaticGateFailure(pi, `**Advisor gate failure (${result.category}):** ${result.message}`, snapshotAdvisorUsage(result.usage));
|
|
7116
7989
|
return failure.block ? { block: true, reason: `${reason}
|
|
7117
7990
|
${failure.reason}` } : undefined;
|
|
7118
7991
|
}
|
|
@@ -7139,7 +8012,7 @@ ${failure.reason}` } : undefined;
|
|
|
7139
8012
|
return { block: true, reason: gateReason };
|
|
7140
8013
|
};
|
|
7141
8014
|
var handleAutomaticGate = async (pi, event, ctx, session, runGate, scoutStatus) => {
|
|
7142
|
-
if (isSimpleMode() || event.toolName === "ask_advisor" || !advisorAutoLoopGateRef || !session.recordToolCall(event.toolName, event.input, advisorLoopThresholdRef)) {
|
|
8015
|
+
if (isSimpleMode() || event.toolName === "ask_advisor" || !advisorModelIsAllowed(ctx) || !advisorAutoLoopGateRef || !session.recordToolCall(event.toolName, event.input, advisorLoopThresholdRef)) {
|
|
7143
8016
|
return;
|
|
7144
8017
|
}
|
|
7145
8018
|
const reason = `Advisor loop gate: normalized signature for ${event.toolName} repeated ${advisorLoopThresholdRef} times without a materially different tool action.`;
|
|
@@ -7181,6 +8054,16 @@ var handleAutomaticGate = async (pi, event, ctx, session, runGate, scoutStatus)
|
|
|
7181
8054
|
};
|
|
7182
8055
|
|
|
7183
8056
|
// src/tools/register-lifecycle.ts
|
|
8057
|
+
var modelAccessBlock = (toolName, ctx) => {
|
|
8058
|
+
const access = advisorModelAccess(ctx);
|
|
8059
|
+
if (access.allowed || toolName !== "ask_advisor") {
|
|
8060
|
+
return;
|
|
8061
|
+
}
|
|
8062
|
+
return {
|
|
8063
|
+
block: true,
|
|
8064
|
+
reason: access.reason
|
|
8065
|
+
};
|
|
8066
|
+
};
|
|
7184
8067
|
var registerToolLifecycle = ({
|
|
7185
8068
|
pi,
|
|
7186
8069
|
reservedCalls,
|
|
@@ -7218,6 +8101,9 @@ var registerToolLifecycle = ({
|
|
|
7218
8101
|
return;
|
|
7219
8102
|
}
|
|
7220
8103
|
loadConfig(ctx);
|
|
8104
|
+
if (!advisorModelAccess(ctx).allowed) {
|
|
8105
|
+
return;
|
|
8106
|
+
}
|
|
7221
8107
|
const guidelines = advisorInvocationGuidelines();
|
|
7222
8108
|
const budget = isSimpleMode() ? undefined : session.remainingCalls(getAdvisorMaxCallsPerSession());
|
|
7223
8109
|
if (budget !== undefined) {
|
|
@@ -7245,6 +8131,10 @@ ${guidelines.map((rule) => `- ${rule}`).join(`
|
|
|
7245
8131
|
if (!loadConfigOrSkipGating(ctx)) {
|
|
7246
8132
|
return;
|
|
7247
8133
|
}
|
|
8134
|
+
const accessBlock = modelAccessBlock(event.toolName, ctx);
|
|
8135
|
+
if (accessBlock) {
|
|
8136
|
+
return accessBlock;
|
|
8137
|
+
}
|
|
7248
8138
|
const reservation = reserveAdvisorCall(event, ctx, session, reservedCalls);
|
|
7249
8139
|
if (event.toolName === "ask_advisor") {
|
|
7250
8140
|
return reservation;
|
|
@@ -7338,65 +8228,59 @@ var registerOutcomeTool = ({
|
|
|
7338
8228
|
};
|
|
7339
8229
|
|
|
7340
8230
|
// src/tools/register-renderers.ts
|
|
7341
|
-
import {
|
|
7342
|
-
getMarkdownTheme as getMarkdownTheme5
|
|
7343
|
-
} from "@earendil-works/pi-coding-agent";
|
|
8231
|
+
import { getMarkdownTheme as getMarkdownTheme5 } from "@earendil-works/pi-coding-agent";
|
|
7344
8232
|
import { Box as Box4, Markdown as Markdown5, Text as Text7 } from "@earendil-works/pi-tui";
|
|
8233
|
+
var addUsageLine = (box, details, theme) => {
|
|
8234
|
+
if (!getAdvisorSettings().showUsageDetails) {
|
|
8235
|
+
return;
|
|
8236
|
+
}
|
|
8237
|
+
const usageText = formatAdvisorUsage(details?.usage);
|
|
8238
|
+
if (usageText) {
|
|
8239
|
+
box.addChild(new Text7(theme.fg("dim", ` Usage: ${usageText}`), 0, 0));
|
|
8240
|
+
}
|
|
8241
|
+
};
|
|
8242
|
+
var callQuestionRenderer = (message, _options, theme) => renderAdvisorCallBox(message.details?.question, theme);
|
|
8243
|
+
var scoutResultRenderer = (entry, { expanded }, theme) => {
|
|
8244
|
+
const box = new Box4(1, 1, (text) => theme.bg("customMessageBg", text));
|
|
8245
|
+
renderScoutDetails(box, entry.data, Boolean(expanded), theme);
|
|
8246
|
+
return box;
|
|
8247
|
+
};
|
|
8248
|
+
var turnGateResultRenderer = (message, { expanded }, theme) => {
|
|
8249
|
+
const { details } = message;
|
|
8250
|
+
const box = new Box4(1, 1, (text) => theme.bg("customMessageBg", text));
|
|
8251
|
+
box.addChild(new Text7(theme.fg("warning", theme.bold("◆ ADVISOR · TURN REVIEW")), 0, 0));
|
|
8252
|
+
if (details?.advisor) {
|
|
8253
|
+
box.addChild(new Text7(theme.fg("dim", ` ${details.advisor}`), 0, 0));
|
|
8254
|
+
}
|
|
8255
|
+
addUsageLine(box, details, theme);
|
|
8256
|
+
if (details?.text) {
|
|
8257
|
+
box.addChild(new Markdown5(adviceForDisplay(details.text, Boolean(expanded)), 0, 0, getMarkdownTheme5()));
|
|
8258
|
+
} else {
|
|
8259
|
+
box.addChild(new Text7(theme.fg("error", isString(message.content) ? message.content : "Advisor turn review failed."), 0, 0));
|
|
8260
|
+
}
|
|
8261
|
+
return box;
|
|
8262
|
+
};
|
|
8263
|
+
var loopResultRenderer = (message, { expanded }, theme) => {
|
|
8264
|
+
const { details } = message;
|
|
8265
|
+
const box = new Box4(1, 1, (text) => theme.bg("customMessageBg", text));
|
|
8266
|
+
box.addChild(new Text7(theme.fg("warning", theme.bold(`◆ ADVISOR GATE: ${details?.decision ?? "failure"}`)), 0, 0));
|
|
8267
|
+
if (details?.advisor) {
|
|
8268
|
+
box.addChild(new Text7(theme.fg("dim", ` ${details.advisor}`), 0, 0));
|
|
8269
|
+
}
|
|
8270
|
+
addUsageLine(box, details, theme);
|
|
8271
|
+
if (details?.text) {
|
|
8272
|
+
box.addChild(new Markdown5(adviceForDisplay(details.text, Boolean(expanded)), 0, 0, getMarkdownTheme5()));
|
|
8273
|
+
} else {
|
|
8274
|
+
box.addChild(new Text7(theme.fg("error", isString(message.content) ? message.content : "Advisor gate failed."), 0, 0));
|
|
8275
|
+
}
|
|
8276
|
+
return box;
|
|
8277
|
+
};
|
|
7345
8278
|
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
|
-
});
|
|
8279
|
+
pi.registerEntryRenderer?.("advisor-scout-result", scoutResultRenderer);
|
|
8280
|
+
pi.registerMessageRenderer?.("advisor-turn-gate-call", callQuestionRenderer);
|
|
8281
|
+
pi.registerMessageRenderer?.("advisor-turn-gate-result", turnGateResultRenderer);
|
|
8282
|
+
pi.registerMessageRenderer?.("advisor-loop-call", callQuestionRenderer);
|
|
8283
|
+
pi.registerMessageRenderer?.("advisor-loop-result", loopResultRenderer);
|
|
7400
8284
|
};
|
|
7401
8285
|
|
|
7402
8286
|
// src/tools/registration.ts
|
|
@@ -7415,20 +8299,23 @@ var registerAdvisorTool = (pi, session = advisorSessionState, dependencies = {})
|
|
|
7415
8299
|
registerToolLifecycle(registration);
|
|
7416
8300
|
registerAskAdvisorTool(registration);
|
|
7417
8301
|
registerOutcomeTool(registration);
|
|
7418
|
-
|
|
8302
|
+
const turnGate = {
|
|
7419
8303
|
activeTools: () => pi.getActiveTools(),
|
|
7420
8304
|
consult: registration.consult,
|
|
7421
|
-
...dependencies.turnGateDeps ? { deps: dependencies.turnGateDeps } : {},
|
|
7422
8305
|
send: (message) => pi.sendMessage(message, { deliverAs: "steer" }),
|
|
7423
8306
|
session
|
|
7424
|
-
}
|
|
8307
|
+
};
|
|
8308
|
+
if (dependencies.turnGateDeps) {
|
|
8309
|
+
turnGate.deps = dependencies.turnGateDeps;
|
|
8310
|
+
}
|
|
8311
|
+
registerJevTurnGate((event, handler) => pi.on(event, handler), turnGate);
|
|
7425
8312
|
};
|
|
7426
8313
|
|
|
7427
8314
|
// extensions/index.ts
|
|
7428
8315
|
var consultAdvisor2 = (...args) => consultAdvisor(...args);
|
|
7429
8316
|
var parseAutomaticDecision2 = (...args) => parseAutomaticDecision(...args);
|
|
7430
8317
|
var runAdvisorGate2 = (...args) => runAdvisorGate(...args);
|
|
7431
|
-
function
|
|
8318
|
+
function registerPiAdvisor(pi) {
|
|
7432
8319
|
const sessionState = new AdvisorSessionState;
|
|
7433
8320
|
const scoutStatus = new ScoutStatusManager;
|
|
7434
8321
|
setHerdrBlockedEmitter((active, label) => pi.events.emit("herdr:blocked", { active, label }));
|
|
@@ -7441,8 +8328,8 @@ function extensions_default(pi) {
|
|
|
7441
8328
|
});
|
|
7442
8329
|
}
|
|
7443
8330
|
export {
|
|
7444
|
-
|
|
7445
|
-
extensions_default as default,
|
|
8331
|
+
runAdvisorGate2 as runAdvisorGate,
|
|
7446
8332
|
parseAutomaticDecision2 as parseAutomaticDecision,
|
|
7447
|
-
|
|
8333
|
+
registerPiAdvisor as default,
|
|
8334
|
+
consultAdvisor2 as consultAdvisor
|
|
7448
8335
|
};
|