pi-advisor-flow 0.8.3-dev.3 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -1
- package/README.md +6 -5
- package/dist/index.js +631 -76
- package/package.json +1 -1
- package/src/attachments.ts +21 -5
- package/src/commands/activation.ts +6 -1
- package/src/commands/lifecycle.ts +6 -0
- package/src/commands/manual-command.ts +13 -1
- package/src/commands/model-commands.ts +1 -0
- package/src/commands/runtime.ts +29 -0
- package/src/commands/settings-commands.ts +2 -0
- package/src/commands/settings-persistence.ts +2 -0
- package/src/commands/types.ts +5 -0
- package/src/config/defaults.ts +7 -0
- package/src/config/schema.ts +7 -0
- package/src/config/state.ts +5 -0
- package/src/config/types.ts +1 -0
- package/src/conversation.ts +29 -7
- package/src/image-attachments.ts +118 -0
- package/src/image-validation.ts +288 -0
- package/src/images.ts +151 -0
- package/src/scout-context.ts +1 -0
- package/src/scout-curation.ts +11 -4
- package/src/scout-groups.ts +13 -3
- package/src/scout-types.ts +2 -0
- package/src/tools/consult-context.ts +78 -2
- package/src/tools/consultation.ts +36 -4
- package/src/tools/jev-turn-gate.ts +4 -1
- package/src/tools/model-access.ts +21 -2
- package/src/tools/prompts.ts +14 -2
- package/src/tools/register-ask-advisor.ts +28 -1
- package/src/tools/register-lifecycle.ts +8 -2
- package/src/tools/render-advisor-result.ts +24 -8
- package/src/tools/types.ts +9 -0
- package/src/ui/settings-items.ts +17 -1
- package/src/ui/settings-mutations.ts +1 -0
- package/src/ui/types.ts +1 -0
package/dist/index.js
CHANGED
|
@@ -52,6 +52,7 @@ var advisorPlanGateRef = true;
|
|
|
52
52
|
var advisorFailureGateRef = true;
|
|
53
53
|
var advisorCompletionGateRef = true;
|
|
54
54
|
var advisorCustomInvocationRef;
|
|
55
|
+
var advisorDisableSameModelRef = true;
|
|
55
56
|
var advisorCollapseResponsesRef = false;
|
|
56
57
|
var advisorBlockOnBlockedRef = true;
|
|
57
58
|
var advisorAutoLoopGateRef = true;
|
|
@@ -122,6 +123,9 @@ var setAdvisorCompletionGateRef = (enabled) => {
|
|
|
122
123
|
var setAdvisorCustomInvocationRef = (rule) => {
|
|
123
124
|
advisorCustomInvocationRef = rule?.trim() || undefined;
|
|
124
125
|
};
|
|
126
|
+
var setAdvisorDisableSameModelRef = (enabled) => {
|
|
127
|
+
advisorDisableSameModelRef = enabled;
|
|
128
|
+
};
|
|
125
129
|
var setAdvisorCollapseResponsesRef = (enabled) => {
|
|
126
130
|
advisorCollapseResponsesRef = enabled;
|
|
127
131
|
};
|
|
@@ -238,6 +242,7 @@ var getAdvisorSettings = () => ({
|
|
|
238
242
|
completionGate: advisorCompletionGateRef,
|
|
239
243
|
contextMaxChars: contextMaxCharsRef,
|
|
240
244
|
customRule: advisorCustomInvocationRef,
|
|
245
|
+
disableSameModel: advisorDisableSameModelRef,
|
|
241
246
|
effort: advisorEffortRef,
|
|
242
247
|
failureGate: advisorFailureGateRef,
|
|
243
248
|
failureMode: advisorFailureModeRef,
|
|
@@ -487,6 +492,12 @@ var CONFIG_SCHEMA = {
|
|
|
487
492
|
persisted: true,
|
|
488
493
|
type: "string"
|
|
489
494
|
},
|
|
495
|
+
advisorDisableSameModel: {
|
|
496
|
+
accepted: "true or false",
|
|
497
|
+
current: () => advisorDisableSameModelRef,
|
|
498
|
+
persisted: true,
|
|
499
|
+
type: "boolean"
|
|
500
|
+
},
|
|
490
501
|
advisorEffort: {
|
|
491
502
|
accepted: "a string",
|
|
492
503
|
current: () => advisorEffortRef,
|
|
@@ -852,6 +863,7 @@ var resetDefaults = () => {
|
|
|
852
863
|
setAdvisorFailureGateRef(true);
|
|
853
864
|
setAdvisorCompletionGateRef(true);
|
|
854
865
|
setAdvisorCustomInvocationRef(undefined);
|
|
866
|
+
setAdvisorDisableSameModelRef(true);
|
|
855
867
|
setAdvisorCollapseResponsesRef(false);
|
|
856
868
|
setAdvisorBlockOnBlockedRef(true);
|
|
857
869
|
setAdvisorAutoLoopGateRef(true);
|
|
@@ -909,6 +921,7 @@ var applyConfig = (config) => {
|
|
|
909
921
|
applyOptionalConfig(config, "advisorFailureGate", setAdvisorFailureGateRef);
|
|
910
922
|
applyOptionalConfig(config, "advisorCompletionGate", setAdvisorCompletionGateRef);
|
|
911
923
|
applyOptionalConfig(config, "advisorCustomInvocation", setAdvisorCustomInvocationRef);
|
|
924
|
+
applyOptionalConfig(config, "advisorDisableSameModel", setAdvisorDisableSameModelRef);
|
|
912
925
|
applyOptionalConfig(config, "advisorCollapseResponses", setAdvisorCollapseResponsesRef);
|
|
913
926
|
applyOptionalConfig(config, "advisorBlockOnBlocked", setAdvisorBlockOnBlockedRef);
|
|
914
927
|
applyOptionalConfig(config, "advisorAutoLoopGate", setAdvisorAutoLoopGateRef);
|
|
@@ -1070,6 +1083,43 @@ var saveGlobalOutcomeLogging = (enabled) => {
|
|
|
1070
1083
|
return path;
|
|
1071
1084
|
};
|
|
1072
1085
|
|
|
1086
|
+
// src/tools/model-access.ts
|
|
1087
|
+
var currentModelRef = (ctx) => {
|
|
1088
|
+
const { model } = ctx;
|
|
1089
|
+
return model ? `${model.provider}/${model.id}` : undefined;
|
|
1090
|
+
};
|
|
1091
|
+
var advisorModelAccess = (ctx) => {
|
|
1092
|
+
const modelRef = currentModelRef(ctx);
|
|
1093
|
+
if (advisorModelWhitelistRef.length === 0) {
|
|
1094
|
+
return modelRef ? { allowed: true, modelRef } : { allowed: true };
|
|
1095
|
+
}
|
|
1096
|
+
if (modelRef && advisorModelWhitelistRef.includes(modelRef)) {
|
|
1097
|
+
return { allowed: true, modelRef };
|
|
1098
|
+
}
|
|
1099
|
+
const current = modelRef ?? "no current model";
|
|
1100
|
+
const denial = {
|
|
1101
|
+
allowed: false,
|
|
1102
|
+
reason: `Advisor calls are restricted to the configured model whitelist (${advisorModelWhitelistRef.join(", ")}). Current model: ${current}.`
|
|
1103
|
+
};
|
|
1104
|
+
if (modelRef) {
|
|
1105
|
+
denial.modelRef = modelRef;
|
|
1106
|
+
}
|
|
1107
|
+
return denial;
|
|
1108
|
+
};
|
|
1109
|
+
var sameModelAdvisorDisabled = (ctx, model = ctx.model) => {
|
|
1110
|
+
if (!(advisorDisableSameModelRef && advisorRef && model)) {
|
|
1111
|
+
return false;
|
|
1112
|
+
}
|
|
1113
|
+
const [provider, id] = splitRef(advisorRef);
|
|
1114
|
+
return provider === model.provider && id === model.id;
|
|
1115
|
+
};
|
|
1116
|
+
var sameModelAdvisorNotice = "Advisor disabled: executor and advisor are the same model.";
|
|
1117
|
+
var advisorModelIsAllowed = (ctx) => advisorModelAccess(ctx).allowed && !sameModelAdvisorDisabled(ctx);
|
|
1118
|
+
var advisorModelAccessReason = (ctx) => {
|
|
1119
|
+
const access = advisorModelAccess(ctx);
|
|
1120
|
+
return access.allowed ? undefined : access.reason;
|
|
1121
|
+
};
|
|
1122
|
+
|
|
1073
1123
|
// src/commands/model-options.ts
|
|
1074
1124
|
var DEFAULT_EFFORT_LEVEL = "Default (Model Default)";
|
|
1075
1125
|
var SELECTED_PREFIX = "✓ ";
|
|
@@ -1710,7 +1760,7 @@ var herdrAdvisorActivity = new HerdrAdvisorActivity(sendToHerdr, () => getAdviso
|
|
|
1710
1760
|
var herdrAdvisorBlock = new HerdrAdvisorBlock(sendToHerdr, () => getAdvisorSettings().herdrIntegration);
|
|
1711
1761
|
|
|
1712
1762
|
// src/tools/consultation.ts
|
|
1713
|
-
import { randomUUID } from "node:crypto";
|
|
1763
|
+
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
1714
1764
|
|
|
1715
1765
|
// src/model-stream.ts
|
|
1716
1766
|
import { stream } from "@earendil-works/pi-ai/compat";
|
|
@@ -1859,6 +1909,9 @@ var collectTextStream = async (resolved, options, streamModel = stream) => {
|
|
|
1859
1909
|
};
|
|
1860
1910
|
};
|
|
1861
1911
|
|
|
1912
|
+
// src/tools/consult-context.ts
|
|
1913
|
+
import { randomUUID } from "node:crypto";
|
|
1914
|
+
|
|
1862
1915
|
// src/attachments.ts
|
|
1863
1916
|
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
1864
1917
|
import { constants } from "node:fs";
|
|
@@ -1866,6 +1919,15 @@ import { lstat, open, realpath } from "node:fs/promises";
|
|
|
1866
1919
|
import { isAbsolute, relative, resolve } from "node:path";
|
|
1867
1920
|
var ADVISOR_FILE_MAX_BYTES = 8 * 1024;
|
|
1868
1921
|
var ADVISOR_FILES_TOTAL_MAX_BYTES = 24 * 1024;
|
|
1922
|
+
var imageExtensions = new Map([
|
|
1923
|
+
[".png", "image/png"],
|
|
1924
|
+
[".jpg", "image/jpeg"],
|
|
1925
|
+
[".jpeg", "image/jpeg"],
|
|
1926
|
+
[".gif", "image/gif"],
|
|
1927
|
+
[".webp", "image/webp"]
|
|
1928
|
+
]);
|
|
1929
|
+
var imageMime = (path) => imageExtensions.get(path.slice(path.lastIndexOf(".")).toLowerCase());
|
|
1930
|
+
var imageFileCandidate = (path) => Boolean(imageMime(path)) || /\.(?:avif|bmp|heic|svg|tiff?)$/iu.test(path);
|
|
1869
1931
|
var PATH_SEGMENTS = /[\\/]/u;
|
|
1870
1932
|
var within = (root, candidate) => {
|
|
1871
1933
|
const path = relative(root, candidate);
|
|
@@ -1967,7 +2029,7 @@ var readFiles = async (cwd, requested, enabled, redact, kind, totalLimit = ADVIS
|
|
|
1967
2029
|
let total = 0;
|
|
1968
2030
|
for (const name of requested) {
|
|
1969
2031
|
const normalizedName = normalizeRequestedPath(root, name);
|
|
1970
|
-
if (!normalizedName || unique.has(normalizedName)) {
|
|
2032
|
+
if (!normalizedName || unique.has(normalizedName) || imageFileCandidate(normalizedName)) {
|
|
1971
2033
|
continue;
|
|
1972
2034
|
}
|
|
1973
2035
|
unique.add(normalizedName);
|
|
@@ -1991,8 +2053,399 @@ var readFiles = async (cwd, requested, enabled, redact, kind, totalLimit = ADVIS
|
|
|
1991
2053
|
var readTrackedFiles = (cwd, requested, enabled, redact, totalLimit = ADVISOR_FILES_TOTAL_MAX_BYTES) => readFiles(cwd, requested, enabled, redact, "tracked", totalLimit);
|
|
1992
2054
|
var readUntrackedFiles = (cwd, requested, enabled, redact, totalLimit = ADVISOR_FILES_TOTAL_MAX_BYTES) => readFiles(cwd, requested, enabled, redact, "untracked", totalLimit);
|
|
1993
2055
|
|
|
1994
|
-
// src/
|
|
2056
|
+
// src/image-attachments.ts
|
|
2057
|
+
import { constants as constants2 } from "node:fs";
|
|
1995
2058
|
import { lstat as lstat2, open as open2, realpath as realpath2 } from "node:fs/promises";
|
|
2059
|
+
import { resolve as resolve2 } from "node:path";
|
|
2060
|
+
|
|
2061
|
+
// src/images.ts
|
|
2062
|
+
import { createHash } from "node:crypto";
|
|
2063
|
+
|
|
2064
|
+
// src/image-validation.ts
|
|
2065
|
+
import { inflateSync } from "node:zlib";
|
|
2066
|
+
var PNG_SIGNATURE = Buffer.from("89504e470d0a1a0a", "hex");
|
|
2067
|
+
var isPng = (bytes) => {
|
|
2068
|
+
if (bytes.length < 57 || !bytes.subarray(0, 8).equals(PNG_SIGNATURE)) {
|
|
2069
|
+
return false;
|
|
2070
|
+
}
|
|
2071
|
+
let offset = 8;
|
|
2072
|
+
let hasHeader = false;
|
|
2073
|
+
const compressed = [];
|
|
2074
|
+
while (offset + 12 <= bytes.length) {
|
|
2075
|
+
const length = bytes.readUInt32BE(offset);
|
|
2076
|
+
if (length > bytes.length - offset - 12) {
|
|
2077
|
+
return false;
|
|
2078
|
+
}
|
|
2079
|
+
const type = bytes.toString("ascii", offset + 4, offset + 8);
|
|
2080
|
+
const data = offset + 8;
|
|
2081
|
+
offset += length + 12;
|
|
2082
|
+
if (!hasHeader) {
|
|
2083
|
+
if (type !== "IHDR" || length !== 13 || bytes.readUInt32BE(data) === 0 || bytes.readUInt32BE(data + 4) === 0) {
|
|
2084
|
+
return false;
|
|
2085
|
+
}
|
|
2086
|
+
hasHeader = true;
|
|
2087
|
+
} else if (type === "IDAT") {
|
|
2088
|
+
compressed.push(bytes.subarray(data, data + length));
|
|
2089
|
+
} else if (type === "IEND") {
|
|
2090
|
+
if (length !== 0 || offset !== bytes.length || !compressed.length) {
|
|
2091
|
+
return false;
|
|
2092
|
+
}
|
|
2093
|
+
try {
|
|
2094
|
+
return inflateSync(Buffer.concat(compressed), {
|
|
2095
|
+
maxOutputLength: 32 * 1024 * 1024
|
|
2096
|
+
}).length > 0;
|
|
2097
|
+
} catch {
|
|
2098
|
+
return false;
|
|
2099
|
+
}
|
|
2100
|
+
} else if (type === "IHDR") {
|
|
2101
|
+
return false;
|
|
2102
|
+
}
|
|
2103
|
+
}
|
|
2104
|
+
return false;
|
|
2105
|
+
};
|
|
2106
|
+
var isFrameMarker = (marker) => [
|
|
2107
|
+
192,
|
|
2108
|
+
193,
|
|
2109
|
+
194,
|
|
2110
|
+
195,
|
|
2111
|
+
197,
|
|
2112
|
+
198,
|
|
2113
|
+
199,
|
|
2114
|
+
201,
|
|
2115
|
+
202,
|
|
2116
|
+
203,
|
|
2117
|
+
205,
|
|
2118
|
+
206,
|
|
2119
|
+
207
|
|
2120
|
+
].includes(marker);
|
|
2121
|
+
var nextScanMarker = (bytes, from) => {
|
|
2122
|
+
let offset = from;
|
|
2123
|
+
while (offset < bytes.length - 1) {
|
|
2124
|
+
if (bytes[offset] !== 255) {
|
|
2125
|
+
offset += 1;
|
|
2126
|
+
continue;
|
|
2127
|
+
}
|
|
2128
|
+
let next = offset + 1;
|
|
2129
|
+
while (bytes[next] === 255) {
|
|
2130
|
+
next += 1;
|
|
2131
|
+
}
|
|
2132
|
+
if (bytes[next] !== 0 && (bytes[next] < 208 || bytes[next] > 215)) {
|
|
2133
|
+
return offset;
|
|
2134
|
+
}
|
|
2135
|
+
offset = next + 1;
|
|
2136
|
+
}
|
|
2137
|
+
return bytes.length;
|
|
2138
|
+
};
|
|
2139
|
+
var readJpegSegment = (bytes, at) => {
|
|
2140
|
+
if (bytes[at] !== 255) {
|
|
2141
|
+
return;
|
|
2142
|
+
}
|
|
2143
|
+
let offset = at;
|
|
2144
|
+
while (bytes[offset] === 255) {
|
|
2145
|
+
offset += 1;
|
|
2146
|
+
}
|
|
2147
|
+
const marker = bytes[offset];
|
|
2148
|
+
offset += 1;
|
|
2149
|
+
if (marker === 217) {
|
|
2150
|
+
return { end: offset, frame: false, marker, size: 0 };
|
|
2151
|
+
}
|
|
2152
|
+
if (offset + 2 > bytes.length || marker === 0) {
|
|
2153
|
+
return;
|
|
2154
|
+
}
|
|
2155
|
+
const size = bytes.readUInt16BE(offset);
|
|
2156
|
+
if (size < 2 || offset + size > bytes.length) {
|
|
2157
|
+
return;
|
|
2158
|
+
}
|
|
2159
|
+
const frame = isFrameMarker(marker) && size >= 11 && bytes.readUInt16BE(offset + 3) > 0 && bytes.readUInt16BE(offset + 5) > 0;
|
|
2160
|
+
return { end: offset + size, frame, marker, size };
|
|
2161
|
+
};
|
|
2162
|
+
var hasJpegEnvelope = (bytes) => bytes.length >= 24 && bytes[0] === 255 && bytes[1] === 216 && bytes.at(-2) === 255 && bytes.at(-1) === 217;
|
|
2163
|
+
var isJpeg = (bytes) => {
|
|
2164
|
+
if (!hasJpegEnvelope(bytes)) {
|
|
2165
|
+
return false;
|
|
2166
|
+
}
|
|
2167
|
+
let offset = 2;
|
|
2168
|
+
let frame = false;
|
|
2169
|
+
let scan = false;
|
|
2170
|
+
let quantization = false;
|
|
2171
|
+
let entropyTable = false;
|
|
2172
|
+
while (offset < bytes.length) {
|
|
2173
|
+
const segment = readJpegSegment(bytes, offset);
|
|
2174
|
+
if (!segment) {
|
|
2175
|
+
return false;
|
|
2176
|
+
}
|
|
2177
|
+
const { end, marker, size } = segment;
|
|
2178
|
+
if (marker === 217) {
|
|
2179
|
+
return frame && scan && end === bytes.length;
|
|
2180
|
+
}
|
|
2181
|
+
frame ||= segment.frame;
|
|
2182
|
+
quantization ||= marker === 219;
|
|
2183
|
+
entropyTable ||= marker === 196 || marker === 204;
|
|
2184
|
+
offset = end;
|
|
2185
|
+
if (marker === 218) {
|
|
2186
|
+
if (!(frame && quantization && entropyTable && size >= 6)) {
|
|
2187
|
+
return false;
|
|
2188
|
+
}
|
|
2189
|
+
const next = nextScanMarker(bytes, offset);
|
|
2190
|
+
if (next <= offset) {
|
|
2191
|
+
return false;
|
|
2192
|
+
}
|
|
2193
|
+
scan = true;
|
|
2194
|
+
offset = next;
|
|
2195
|
+
}
|
|
2196
|
+
}
|
|
2197
|
+
return false;
|
|
2198
|
+
};
|
|
2199
|
+
var skipGifBlocks = (bytes, at) => {
|
|
2200
|
+
let offset = at;
|
|
2201
|
+
while (offset < bytes.length) {
|
|
2202
|
+
const length = bytes[offset];
|
|
2203
|
+
offset += 1;
|
|
2204
|
+
if (length === 0) {
|
|
2205
|
+
return offset;
|
|
2206
|
+
}
|
|
2207
|
+
if (offset + length > bytes.length) {
|
|
2208
|
+
return;
|
|
2209
|
+
}
|
|
2210
|
+
offset += length;
|
|
2211
|
+
}
|
|
2212
|
+
};
|
|
2213
|
+
var gifColorTableEnd = (bytes, at, packed) => at + ((packed & 128) === 0 ? 0 : 3 * 2 ** ((packed & 7) + 1));
|
|
2214
|
+
var gifFrameEnd = (bytes, at) => {
|
|
2215
|
+
if (at + 9 > bytes.length || bytes.readUInt16LE(at + 4) === 0 || bytes.readUInt16LE(at + 6) === 0) {
|
|
2216
|
+
return;
|
|
2217
|
+
}
|
|
2218
|
+
const data = gifColorTableEnd(bytes, at + 9, bytes[at + 8]);
|
|
2219
|
+
if (data + 2 >= bytes.length || bytes[data] < 2 || bytes[data] > 12 || bytes[data + 1] === 0) {
|
|
2220
|
+
return;
|
|
2221
|
+
}
|
|
2222
|
+
return skipGifBlocks(bytes, data + 1);
|
|
2223
|
+
};
|
|
2224
|
+
var isGif = (bytes) => {
|
|
2225
|
+
if (bytes.length < 20 || !["GIF87a", "GIF89a"].includes(bytes.toString("ascii", 0, 6)) || bytes.readUInt16LE(6) === 0 || bytes.readUInt16LE(8) === 0) {
|
|
2226
|
+
return false;
|
|
2227
|
+
}
|
|
2228
|
+
let offset = gifColorTableEnd(bytes, 13, bytes[10]);
|
|
2229
|
+
let frame = false;
|
|
2230
|
+
while (offset < bytes.length) {
|
|
2231
|
+
const block = bytes[offset];
|
|
2232
|
+
offset += 1;
|
|
2233
|
+
if (block === 59) {
|
|
2234
|
+
return frame && offset === bytes.length;
|
|
2235
|
+
}
|
|
2236
|
+
if (block === 33 && offset < bytes.length) {
|
|
2237
|
+
offset = skipGifBlocks(bytes, offset + 1) ?? bytes.length;
|
|
2238
|
+
} else if (block === 44) {
|
|
2239
|
+
const end = gifFrameEnd(bytes, offset);
|
|
2240
|
+
if (end === undefined) {
|
|
2241
|
+
return false;
|
|
2242
|
+
}
|
|
2243
|
+
frame = true;
|
|
2244
|
+
offset = end;
|
|
2245
|
+
} else {
|
|
2246
|
+
return false;
|
|
2247
|
+
}
|
|
2248
|
+
}
|
|
2249
|
+
return false;
|
|
2250
|
+
};
|
|
2251
|
+
var validWebpFrame = (bytes, type, data, length) => {
|
|
2252
|
+
if (type === "VP8 ") {
|
|
2253
|
+
return length >= 10 && bytes.toString("hex", data + 3, data + 6) === "9d012a" && (bytes.readUInt16LE(data + 6) & 16383) > 0 && (bytes.readUInt16LE(data + 8) & 16383) > 0;
|
|
2254
|
+
}
|
|
2255
|
+
if (type === "VP8L") {
|
|
2256
|
+
return length >= 5 && bytes[data] === 47;
|
|
2257
|
+
}
|
|
2258
|
+
return type === "ANMF" && length >= 16;
|
|
2259
|
+
};
|
|
2260
|
+
var isWebp = (bytes) => {
|
|
2261
|
+
if (bytes.length < 30 || bytes.toString("ascii", 0, 4) !== "RIFF" || bytes.toString("ascii", 8, 12) !== "WEBP" || bytes.readUInt32LE(4) !== bytes.length - 8) {
|
|
2262
|
+
return false;
|
|
2263
|
+
}
|
|
2264
|
+
let offset = 12;
|
|
2265
|
+
let frame = false;
|
|
2266
|
+
while (offset + 8 <= bytes.length) {
|
|
2267
|
+
const type = bytes.toString("ascii", offset, offset + 4);
|
|
2268
|
+
const length = bytes.readUInt32LE(offset + 4);
|
|
2269
|
+
const data = offset + 8;
|
|
2270
|
+
const end = data + length + length % 2;
|
|
2271
|
+
if (end > bytes.length) {
|
|
2272
|
+
return false;
|
|
2273
|
+
}
|
|
2274
|
+
frame ||= validWebpFrame(bytes, type, data, length);
|
|
2275
|
+
offset = end;
|
|
2276
|
+
}
|
|
2277
|
+
return frame && offset === bytes.length;
|
|
2278
|
+
};
|
|
2279
|
+
var detectImageFormat = (bytes) => {
|
|
2280
|
+
if (isPng(bytes)) {
|
|
2281
|
+
return "image/png";
|
|
2282
|
+
}
|
|
2283
|
+
if (isJpeg(bytes)) {
|
|
2284
|
+
return "image/jpeg";
|
|
2285
|
+
}
|
|
2286
|
+
if (isGif(bytes)) {
|
|
2287
|
+
return "image/gif";
|
|
2288
|
+
}
|
|
2289
|
+
if (isWebp(bytes)) {
|
|
2290
|
+
return "image/webp";
|
|
2291
|
+
}
|
|
2292
|
+
};
|
|
2293
|
+
|
|
2294
|
+
// src/images.ts
|
|
2295
|
+
var ADVISOR_IMAGE_MAX_BYTES = 4 * 1024 * 1024;
|
|
2296
|
+
var ADVISOR_IMAGES_TOTAL_MAX_BYTES = 8 * 1024 * 1024;
|
|
2297
|
+
var ADVISOR_IMAGES_MAX_COUNT = 4;
|
|
2298
|
+
var imageFromBytes = (bytes, mimeType) => {
|
|
2299
|
+
if (!bytes.length || bytes.length > ADVISOR_IMAGE_MAX_BYTES) {
|
|
2300
|
+
return;
|
|
2301
|
+
}
|
|
2302
|
+
const actual = detectImageFormat(bytes);
|
|
2303
|
+
if (!actual || mimeType && mimeType !== actual) {
|
|
2304
|
+
return;
|
|
2305
|
+
}
|
|
2306
|
+
return { data: bytes.toString("base64"), mimeType: actual, type: "image" };
|
|
2307
|
+
};
|
|
2308
|
+
var imageFromPart = (part) => {
|
|
2309
|
+
if (part.type !== "image" || !isString(part.data) || !isString(part.mimeType) || part.data.length > Math.ceil(ADVISOR_IMAGE_MAX_BYTES * 4 / 3) + 4 || !/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/u.test(part.data)) {
|
|
2310
|
+
return;
|
|
2311
|
+
}
|
|
2312
|
+
const bytes = Buffer.from(part.data, "base64");
|
|
2313
|
+
if (bytes.toString("base64") !== part.data) {
|
|
2314
|
+
return;
|
|
2315
|
+
}
|
|
2316
|
+
return imageFromBytes(bytes, part.mimeType);
|
|
2317
|
+
};
|
|
2318
|
+
var imageMarker = (part, nonce = "") => {
|
|
2319
|
+
if (part.type !== "image") {
|
|
2320
|
+
return;
|
|
2321
|
+
}
|
|
2322
|
+
const image = imageFromPart(part);
|
|
2323
|
+
if (!image) {
|
|
2324
|
+
return "[Image omitted: unsupported format, invalid data, or over 4 MiB; pixels not reviewed]";
|
|
2325
|
+
}
|
|
2326
|
+
const id = createHash("sha256").update(nonce).update(image.data).digest("hex").slice(0, 24);
|
|
2327
|
+
return `[Image ref=img_${id}; pixels reviewed only if attached below]`;
|
|
2328
|
+
};
|
|
2329
|
+
var eligibleImagePart = (part) => part.type === "image" && isString(part.data) && isString(part.mimeType);
|
|
2330
|
+
var entryImageCensus = (content, conversation, nonce, seen) => {
|
|
2331
|
+
let parts = 0;
|
|
2332
|
+
const selected = [];
|
|
2333
|
+
for (const part of contentParts(content)) {
|
|
2334
|
+
if (!isRecordOf(part)) {
|
|
2335
|
+
continue;
|
|
2336
|
+
}
|
|
2337
|
+
if (eligibleImagePart(part)) {
|
|
2338
|
+
parts += 1;
|
|
2339
|
+
}
|
|
2340
|
+
const marker = imageMarker(part, nonce);
|
|
2341
|
+
if (!marker || seen.has(marker) || !conversation.includes(marker)) {
|
|
2342
|
+
continue;
|
|
2343
|
+
}
|
|
2344
|
+
const image = imageFromPart(part);
|
|
2345
|
+
if (image) {
|
|
2346
|
+
seen.add(marker);
|
|
2347
|
+
selected.push({ image, marker });
|
|
2348
|
+
}
|
|
2349
|
+
}
|
|
2350
|
+
return { parts, selected };
|
|
2351
|
+
};
|
|
2352
|
+
var selectedConversationImages = (ctx, conversation, policies, selectedEntryIds, nonce) => {
|
|
2353
|
+
if (!conversation) {
|
|
2354
|
+
return { imagePartsSeen: 0, selected: [] };
|
|
2355
|
+
}
|
|
2356
|
+
const entries = selectedEntryIds ? ctx.sessionManager.buildContextEntries() : ctx.sessionManager.getBranch();
|
|
2357
|
+
const images = [];
|
|
2358
|
+
const seen = new Set;
|
|
2359
|
+
let imagePartsSeen = 0;
|
|
2360
|
+
for (const [index, entry] of entries.entries()) {
|
|
2361
|
+
if (entry.type !== "message" || selectedEntryIds && !selectedEntryIds.has(entry.id ?? String(index))) {
|
|
2362
|
+
continue;
|
|
2363
|
+
}
|
|
2364
|
+
const { message } = entry;
|
|
2365
|
+
if (message.role !== "user" && message.role !== "toolResult") {
|
|
2366
|
+
continue;
|
|
2367
|
+
}
|
|
2368
|
+
if (message.role === "toolResult" && (policies[message.toolName] ?? "full") !== "full") {
|
|
2369
|
+
continue;
|
|
2370
|
+
}
|
|
2371
|
+
const census = entryImageCensus(message.content, conversation, nonce, seen);
|
|
2372
|
+
imagePartsSeen += census.parts;
|
|
2373
|
+
images.push(...census.selected);
|
|
2374
|
+
}
|
|
2375
|
+
return { imagePartsSeen, selected: images };
|
|
2376
|
+
};
|
|
2377
|
+
|
|
2378
|
+
// src/image-attachments.ts
|
|
2379
|
+
var readImageAttachment = async (root, name, mimeType, remaining) => {
|
|
2380
|
+
const absolute = resolve2(root, name);
|
|
2381
|
+
if (!within(root, absolute)) {
|
|
2382
|
+
return;
|
|
2383
|
+
}
|
|
2384
|
+
const stats = await lstat2(absolute);
|
|
2385
|
+
if (stats.isSymbolicLink() || !stats.isFile()) {
|
|
2386
|
+
return;
|
|
2387
|
+
}
|
|
2388
|
+
const resolved = await realpath2(absolute);
|
|
2389
|
+
if (!within(root, resolved)) {
|
|
2390
|
+
return;
|
|
2391
|
+
}
|
|
2392
|
+
const flags = constants2.O_NOFOLLOW ? constants2.O_RDONLY + constants2.O_NOFOLLOW : constants2.O_RDONLY;
|
|
2393
|
+
const file = await open2(resolved, flags);
|
|
2394
|
+
try {
|
|
2395
|
+
const opened = await file.stat();
|
|
2396
|
+
if (!opened.isFile() || opened.size > ADVISOR_IMAGE_MAX_BYTES || opened.size > remaining) {
|
|
2397
|
+
return;
|
|
2398
|
+
}
|
|
2399
|
+
const buffer = Buffer.alloc(opened.size + 1);
|
|
2400
|
+
const { bytesRead } = await file.read(buffer, 0, buffer.length, 0);
|
|
2401
|
+
if (bytesRead !== opened.size) {
|
|
2402
|
+
return;
|
|
2403
|
+
}
|
|
2404
|
+
const image = imageFromBytes(buffer.subarray(0, bytesRead), mimeType);
|
|
2405
|
+
return image ? { bytes: bytesRead, image, path: name } : undefined;
|
|
2406
|
+
} finally {
|
|
2407
|
+
await file.close();
|
|
2408
|
+
}
|
|
2409
|
+
};
|
|
2410
|
+
var readImageFiles = async (cwd, requested, enabled, kind, remainingBytes, remainingCount) => {
|
|
2411
|
+
const names = Array.isArray(requested) ? requested.filter((path) => isString(path) && imageFileCandidate(path)) : [];
|
|
2412
|
+
if (!enabled || !names.length) {
|
|
2413
|
+
return { images: [], omitted: names.length };
|
|
2414
|
+
}
|
|
2415
|
+
const root = await repositoryRoot(cwd);
|
|
2416
|
+
if (!root) {
|
|
2417
|
+
return { images: [], omitted: names.length };
|
|
2418
|
+
}
|
|
2419
|
+
const seen = new Set;
|
|
2420
|
+
const images = [];
|
|
2421
|
+
let omitted = 0;
|
|
2422
|
+
let bytes = 0;
|
|
2423
|
+
for (const path of names) {
|
|
2424
|
+
const name = normalizeRequestedPath(root, path);
|
|
2425
|
+
if (name && seen.has(name)) {
|
|
2426
|
+
continue;
|
|
2427
|
+
}
|
|
2428
|
+
if (name) {
|
|
2429
|
+
seen.add(name);
|
|
2430
|
+
}
|
|
2431
|
+
try {
|
|
2432
|
+
const mime = name && imageMime(name);
|
|
2433
|
+
const image = name && mime && images.length < remainingCount && remainingBytes - bytes > 0 && isPermitted(root, name, kind) ? await readImageAttachment(root, name, mime, remainingBytes - bytes) : undefined;
|
|
2434
|
+
if (image) {
|
|
2435
|
+
images.push(image);
|
|
2436
|
+
bytes += image.bytes;
|
|
2437
|
+
} else {
|
|
2438
|
+
omitted += 1;
|
|
2439
|
+
}
|
|
2440
|
+
} catch {
|
|
2441
|
+
omitted += 1;
|
|
2442
|
+
}
|
|
2443
|
+
}
|
|
2444
|
+
return { images, omitted };
|
|
2445
|
+
};
|
|
2446
|
+
|
|
2447
|
+
// src/preferences.ts
|
|
2448
|
+
import { lstat as lstat3, open as open3, realpath as realpath3 } from "node:fs/promises";
|
|
1996
2449
|
import { join as join2, relative as relative2 } from "node:path";
|
|
1997
2450
|
var PREFERENCES_MAX_BYTES = 8 * 1024;
|
|
1998
2451
|
var PREFERENCES_FILENAME = ["advisor-preferences", "md"].join(".");
|
|
@@ -2005,17 +2458,17 @@ var readProjectPreferences = async (ctx, maxBytes = PREFERENCES_MAX_BYTES, redac
|
|
|
2005
2458
|
return;
|
|
2006
2459
|
}
|
|
2007
2460
|
try {
|
|
2008
|
-
const root = await
|
|
2461
|
+
const root = await realpath3(ctx.cwd);
|
|
2009
2462
|
const candidate = join2(ctx.cwd, ".pi", PREFERENCES_FILENAME);
|
|
2010
|
-
const stats = await
|
|
2463
|
+
const stats = await lstat3(candidate);
|
|
2011
2464
|
if (stats.isSymbolicLink() || !stats.isFile()) {
|
|
2012
2465
|
return;
|
|
2013
2466
|
}
|
|
2014
|
-
const resolved = await
|
|
2467
|
+
const resolved = await realpath3(candidate);
|
|
2015
2468
|
if (!inside(root, resolved)) {
|
|
2016
2469
|
return;
|
|
2017
2470
|
}
|
|
2018
|
-
const file = await
|
|
2471
|
+
const file = await open3(resolved, "r");
|
|
2019
2472
|
try {
|
|
2020
2473
|
const buffer = Buffer.alloc(maxBytes + 1);
|
|
2021
2474
|
const { bytesRead } = await file.read(buffer, 0, buffer.length, 0);
|
|
@@ -2029,7 +2482,7 @@ var readProjectPreferences = async (ctx, maxBytes = PREFERENCES_MAX_BYTES, redac
|
|
|
2029
2482
|
};
|
|
2030
2483
|
|
|
2031
2484
|
// src/scout-groups.ts
|
|
2032
|
-
import { createHash } from "node:crypto";
|
|
2485
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
2033
2486
|
|
|
2034
2487
|
// src/tool-result-cap.ts
|
|
2035
2488
|
var OMITTED_MARKER = "[... omitted tool-result section ...]";
|
|
@@ -2108,6 +2561,7 @@ var textFromPart = (part) => {
|
|
|
2108
2561
|
};
|
|
2109
2562
|
var textFrom = (content) => contentParts(content).map(textFromPart).join(`
|
|
2110
2563
|
`).trim();
|
|
2564
|
+
var imageNotes = (content, nonce) => contentParts(content).map((part) => isRecordOf(part) ? imageMarker(part, nonce) : undefined).filter(isString);
|
|
2111
2565
|
var assistantEntry = (message, policies, redact) => {
|
|
2112
2566
|
const parts = [];
|
|
2113
2567
|
const text = textFrom(message.content);
|
|
@@ -2134,11 +2588,13 @@ var assistantEntry = (message, policies, redact) => {
|
|
|
2134
2588
|
return parts.length > 0 ? `Executor: ${parts.join(`
|
|
2135
2589
|
`)}` : undefined;
|
|
2136
2590
|
};
|
|
2137
|
-
var toolResultEntry = (message, toolResultMaxLines, toolResultMaxBytes, policies, redact) => {
|
|
2591
|
+
var toolResultEntry = (message, toolResultMaxLines, toolResultMaxBytes, policies, redact, describeImages, imageNonce) => {
|
|
2138
2592
|
const status = message.isError ? "error" : "success";
|
|
2139
2593
|
const toolName = isString(message.toolName) ? message.toolName : "unknown";
|
|
2140
2594
|
const policy = policies[toolName] ?? "full";
|
|
2141
|
-
const
|
|
2595
|
+
const notes = describeImages ? imageNotes(message.content, imageNonce) : [];
|
|
2596
|
+
const source = [textFrom(message.content), ...notes].filter(Boolean).join(`
|
|
2597
|
+
`);
|
|
2142
2598
|
if (policy === "exclude") {
|
|
2143
2599
|
return `[Tool Result for ${toolName}] (excluded by Advisor tool policy)`;
|
|
2144
2600
|
}
|
|
@@ -2151,7 +2607,7 @@ var toolResultEntry = (message, toolResultMaxLines, toolResultMaxBytes, policies
|
|
|
2151
2607
|
return `[Tool Result for ${toolName}] (${message.isError ? "Error " : ""}output):
|
|
2152
2608
|
${capped.content}`;
|
|
2153
2609
|
};
|
|
2154
|
-
var conversationEntry = (entry, toolResultMaxLines, toolResultMaxBytes, policies, redact) => {
|
|
2610
|
+
var conversationEntry = (entry, toolResultMaxLines, toolResultMaxBytes, policies, redact, describeImages = false, imageNonce = "") => {
|
|
2155
2611
|
if (!isRecordOf(entry)) {
|
|
2156
2612
|
return;
|
|
2157
2613
|
}
|
|
@@ -2163,14 +2619,16 @@ var conversationEntry = (entry, toolResultMaxLines, toolResultMaxBytes, policies
|
|
|
2163
2619
|
}
|
|
2164
2620
|
const { message } = entry;
|
|
2165
2621
|
if (message.role === "user") {
|
|
2166
|
-
const
|
|
2622
|
+
const notes = describeImages ? imageNotes(message.content, imageNonce) : [];
|
|
2623
|
+
const text = [textFrom(message.content), ...notes].filter(Boolean).join(`
|
|
2624
|
+
`);
|
|
2167
2625
|
return text ? `User: ${redact ? redactSecrets(text) : text}` : undefined;
|
|
2168
2626
|
}
|
|
2169
2627
|
if (message.role === "assistant") {
|
|
2170
2628
|
return assistantEntry(message, policies, redact);
|
|
2171
2629
|
}
|
|
2172
2630
|
if (message.role === "toolResult" || message.role === "tool") {
|
|
2173
|
-
return toolResultEntry(message, toolResultMaxLines, toolResultMaxBytes, policies, redact);
|
|
2631
|
+
return toolResultEntry(message, toolResultMaxLines, toolResultMaxBytes, policies, redact, describeImages, imageNonce);
|
|
2174
2632
|
}
|
|
2175
2633
|
};
|
|
2176
2634
|
var omissionMarker = (omitted) => `[Older context omitted: ${omitted} complete entr${omitted === 1 ? "y" : "ies"}]`;
|
|
@@ -2208,11 +2666,11 @@ var selectRecentEntries = (entries, maxChars) => {
|
|
|
2208
2666
|
const prefix = `${marker}${separator}${newestTruncated}${separator}`;
|
|
2209
2667
|
return `${prefix}${entries.at(-1)?.slice(0, Math.max(0, maxChars - prefix.length)) ?? ""}`.slice(0, maxChars);
|
|
2210
2668
|
};
|
|
2211
|
-
var recentConversation = (ctx, maxChars = 15000, toolResultMaxLines = advisorToolResultMaxLinesRef, toolResultMaxBytes = advisorToolResultMaxBytesRef, policies = advisorToolPoliciesRef, redact = advisorRedactSecretsRef) => {
|
|
2669
|
+
var recentConversation = (ctx, maxChars = 15000, toolResultMaxLines = advisorToolResultMaxLinesRef, toolResultMaxBytes = advisorToolResultMaxBytesRef, policies = advisorToolPoliciesRef, redact = advisorRedactSecretsRef, describeImages = false, imageNonce = "") => {
|
|
2212
2670
|
if (maxChars === 0) {
|
|
2213
2671
|
return "";
|
|
2214
2672
|
}
|
|
2215
|
-
const entries = ctx.sessionManager.getBranch().map((entry) => conversationEntry(entry, toolResultMaxLines, toolResultMaxBytes, policies, redact)).filter((entry) => entry !== undefined);
|
|
2673
|
+
const entries = ctx.sessionManager.getBranch().map((entry) => conversationEntry(entry, toolResultMaxLines, toolResultMaxBytes, policies, redact, describeImages, imageNonce)).filter((entry) => entry !== undefined);
|
|
2216
2674
|
return selectRecentEntries(entries, maxChars);
|
|
2217
2675
|
};
|
|
2218
2676
|
|
|
@@ -2312,7 +2770,7 @@ var labelFor = (kind, content) => {
|
|
|
2312
2770
|
};
|
|
2313
2771
|
return boundedLabel(`${prefix[kind]}: ${preview || "(no text)"}`);
|
|
2314
2772
|
};
|
|
2315
|
-
var stableId = (index, entryIds, kind, content) => `g_${
|
|
2773
|
+
var stableId = (index, entryIds, kind, content) => `g_${createHash2("sha256").update(JSON.stringify([index, entryIds, kind, content])).digest("hex").slice(0, 16)}`;
|
|
2316
2774
|
var groupWire = (group) => ({
|
|
2317
2775
|
bytes: group.bytes,
|
|
2318
2776
|
content: group.content,
|
|
@@ -2325,6 +2783,7 @@ var groupWireBytes = (group) => byteLength(JSON.stringify(groupWire(group)));
|
|
|
2325
2783
|
var createGroup = (originalIndex, entryIds, kind, content, required) => ({
|
|
2326
2784
|
bytes: byteLength(content),
|
|
2327
2785
|
content,
|
|
2786
|
+
entryIds,
|
|
2328
2787
|
id: stableId(originalIndex, entryIds, kind, content),
|
|
2329
2788
|
kind,
|
|
2330
2789
|
label: labelFor(kind, content),
|
|
@@ -2341,7 +2800,7 @@ var pendingAdvisorArguments = (part) => {
|
|
|
2341
2800
|
}
|
|
2342
2801
|
return allowed;
|
|
2343
2802
|
};
|
|
2344
|
-
var pendingInvocationDisclosure = (entry, invocationId, toolResultMaxLines, toolResultMaxBytes, policies, redact, disclosed) => {
|
|
2803
|
+
var pendingInvocationDisclosure = (entry, invocationId, toolResultMaxLines, toolResultMaxBytes, policies, redact, imageNonce, disclosed) => {
|
|
2345
2804
|
if (!isRecord(entry.message)) {
|
|
2346
2805
|
return disclosed;
|
|
2347
2806
|
}
|
|
@@ -2352,7 +2811,7 @@ var pendingInvocationDisclosure = (entry, invocationId, toolResultMaxLines, tool
|
|
|
2352
2811
|
}
|
|
2353
2812
|
return { ...part, arguments: pendingAdvisorArguments(part) };
|
|
2354
2813
|
});
|
|
2355
|
-
return conversationEntry({ ...entry, message: { ...message, content } }, toolResultMaxLines, toolResultMaxBytes, policies, redact);
|
|
2814
|
+
return conversationEntry({ ...entry, message: { ...message, content } }, toolResultMaxLines, toolResultMaxBytes, policies, redact, true, imageNonce);
|
|
2356
2815
|
};
|
|
2357
2816
|
var adjacentResultMismatch = (immediate, index, callIds, callOwners) => {
|
|
2358
2817
|
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)) {
|
|
@@ -2377,7 +2836,7 @@ var collectResults = (calls, callIds, index, resultsByCall, consumedResultIndexe
|
|
|
2377
2836
|
return `Tool result at context entry ${resultMatch.index} conflicts with call ${callId}.`;
|
|
2378
2837
|
}
|
|
2379
2838
|
consumedResultIndexes.add(resultMatch.index);
|
|
2380
|
-
const resultText = conversationEntry(resultMatch.entry, caps.toolResultMaxLines, caps.toolResultMaxBytes, caps.policies, caps.redact);
|
|
2839
|
+
const resultText = conversationEntry(resultMatch.entry, caps.toolResultMaxLines, caps.toolResultMaxBytes, caps.policies, caps.redact, true, caps.imageNonce);
|
|
2381
2840
|
if (resultText) {
|
|
2382
2841
|
resultParts.push(resultText);
|
|
2383
2842
|
}
|
|
@@ -2396,7 +2855,7 @@ var missingOutcome = (entry, index, entryId, disclosed, callIds, missing, result
|
|
|
2396
2855
|
kind: "omitted"
|
|
2397
2856
|
};
|
|
2398
2857
|
}
|
|
2399
|
-
const pendingDisclosed = pendingInvocationDisclosure(entry, currentInvocationId, caps.toolResultMaxLines, caps.toolResultMaxBytes, caps.policies, caps.redact, disclosed);
|
|
2858
|
+
const pendingDisclosed = pendingInvocationDisclosure(entry, currentInvocationId, caps.toolResultMaxLines, caps.toolResultMaxBytes, caps.policies, caps.redact, caps.imageNonce, disclosed);
|
|
2400
2859
|
return {
|
|
2401
2860
|
group: createGroup(index, [entryId, ...resultEntryIds], "pending-invocation", [pendingDisclosed ?? disclosed, ...resultParts].join(`
|
|
2402
2861
|
|
|
@@ -2482,7 +2941,7 @@ var buildGroups = (entries, indexed, caps) => {
|
|
|
2482
2941
|
if (!isRecord(entry)) {
|
|
2483
2942
|
continue;
|
|
2484
2943
|
}
|
|
2485
|
-
const disclosed = conversationEntry(entry, caps.toolResultMaxLines, caps.toolResultMaxBytes, caps.policies, caps.redact);
|
|
2944
|
+
const disclosed = conversationEntry(entry, caps.toolResultMaxLines, caps.toolResultMaxBytes, caps.policies, caps.redact, true, caps.imageNonce);
|
|
2486
2945
|
if (!disclosed) {
|
|
2487
2946
|
continue;
|
|
2488
2947
|
}
|
|
@@ -2548,6 +3007,7 @@ ${synthesis.trim()}` : undefined;
|
|
|
2548
3007
|
// src/scout-context.ts
|
|
2549
3008
|
var resolveCaps = (options) => ({
|
|
2550
3009
|
currentInvocationId: options.currentInvocationId,
|
|
3010
|
+
imageNonce: options.imageNonce ?? "",
|
|
2551
3011
|
maxConversationChars: options.maxConversationChars,
|
|
2552
3012
|
maxGroupBytes: options.maxGroupBytes ?? SCOUT_GROUP_MAX_BYTES,
|
|
2553
3013
|
maxGroups: options.maxGroups ?? SCOUT_MANIFEST_MAX_GROUPS,
|
|
@@ -2994,7 +3454,7 @@ var runAdvisorScout = async (ctx, manifest, parentSignal, onEvent, timeoutMs = a
|
|
|
2994
3454
|
};
|
|
2995
3455
|
|
|
2996
3456
|
// src/scout-curation.ts
|
|
2997
|
-
var curateAdvisorConversation = async (ctx, legacyConversation, signal, onScout, enabled = advisorScoutEnabledRef, runScout = runAdvisorScout, currentInvocationId, maxChars) => {
|
|
3457
|
+
var curateAdvisorConversation = async (ctx, legacyConversation, signal, onScout, enabled = advisorScoutEnabledRef, runScout = runAdvisorScout, currentInvocationId, maxChars, imageNonce = "") => {
|
|
2998
3458
|
if (!enabled) {
|
|
2999
3459
|
return { conversation: legacyConversation };
|
|
3000
3460
|
}
|
|
@@ -3003,6 +3463,7 @@ var curateAdvisorConversation = async (ctx, legacyConversation, signal, onScout,
|
|
|
3003
3463
|
}
|
|
3004
3464
|
const built = buildScoutManifest(ctx, {
|
|
3005
3465
|
currentInvocationId,
|
|
3466
|
+
imageNonce,
|
|
3006
3467
|
maxConversationChars: maxChars,
|
|
3007
3468
|
maxManifestBytes: SCOUT_MANIFEST_MAX_BYTES
|
|
3008
3469
|
});
|
|
@@ -3027,11 +3488,13 @@ var curateAdvisorConversation = async (ctx, legacyConversation, signal, onScout,
|
|
|
3027
3488
|
if (!outcome.ok && outcome.cancelled) {
|
|
3028
3489
|
throw signal?.reason instanceof Error ? signal.reason : new Error("Advisor operation cancelled during Scout.");
|
|
3029
3490
|
}
|
|
3030
|
-
let conversation = legacyConversation;
|
|
3031
3491
|
if (outcome.ok) {
|
|
3032
|
-
|
|
3492
|
+
const selected = new Set(outcome.selection.selectedIds);
|
|
3493
|
+
const selectedEntryIds = built.manifest.groups.filter((group) => group.required || selected.has(group.id)).flatMap((group) => group.entryIds ?? []);
|
|
3494
|
+
const conversation = maxChars === undefined ? outcome.conversation : reconstructScoutConversation(built.manifest, outcome.selection.selectedIds, outcome.selection.synthesis, maxChars);
|
|
3495
|
+
return { conversation, scout: outcome, selectedEntryIds };
|
|
3033
3496
|
}
|
|
3034
|
-
return { conversation, scout: outcome };
|
|
3497
|
+
return { conversation: legacyConversation, scout: outcome };
|
|
3035
3498
|
};
|
|
3036
3499
|
|
|
3037
3500
|
// src/tools/prompts.ts
|
|
@@ -3097,7 +3560,7 @@ var advisorRepositoryContext = (result, requested, allowed, budget) => {
|
|
|
3097
3560
|
|
|
3098
3561
|
`);
|
|
3099
3562
|
};
|
|
3100
|
-
var advisorRequestConversation = (ctx, maxChars = contextMaxCharsRef) => recentConversation(ctx, maxChars);
|
|
3563
|
+
var advisorRequestConversation = (ctx, maxChars = contextMaxCharsRef, describeImages = false, imageNonce = "") => recentConversation(ctx, maxChars, undefined, undefined, undefined, undefined, describeImages, imageNonce);
|
|
3101
3564
|
var advisorInvocationGuidelines = () => {
|
|
3102
3565
|
if (isSimpleMode()) {
|
|
3103
3566
|
return [
|
|
@@ -3149,18 +3612,42 @@ var assembleConsultationContext = async (options) => {
|
|
|
3149
3612
|
const changes = collectGitContext(ctx.cwd, level, gitBudget, advisorRedactSecretsRef ? redactSecrets : undefined);
|
|
3150
3613
|
const changeText = advisorRepositoryContext(changes, options.gitContext ?? allowed, level, gitBudget);
|
|
3151
3614
|
const conversationBudget = Math.max(0, contextMaxCharsRef - changeText.length);
|
|
3152
|
-
const
|
|
3153
|
-
const
|
|
3615
|
+
const imageNonce = randomUUID();
|
|
3616
|
+
const legacyConversation = advisorRequestConversation(ctx, conversationBudget, true, imageNonce);
|
|
3617
|
+
const curated = await curateAdvisorConversation(ctx, legacyConversation, options.signal, options.onScout, advisorScoutEnabledRef, runAdvisorScout, options.currentInvocationId, conversationBudget, imageNonce);
|
|
3154
3618
|
const preferences = await readProjectPreferences(ctx, ATTACHMENT_TEXT_MAX_BYTES, advisorRedactSecretsRef);
|
|
3155
3619
|
const draftText = options.draft ? redactAndCapText(options.draft, ATTACHMENT_TEXT_MAX_BYTES, advisorRedactSecretsRef) : undefined;
|
|
3156
3620
|
const untracked = await readUntrackedFiles(ctx.cwd, options.includeUntracked ?? [], advisorUntrackedContentRef, advisorRedactSecretsRef);
|
|
3157
3621
|
const tracked = await readTrackedFiles(ctx.cwd, options.includeTracked ?? [], advisorTrackedFileContentRef, advisorRedactSecretsRef, Math.max(0, ATTACHMENTS_TOTAL_MAX_BYTES - untracked.reduce((sum, item) => sum + item.bytes, 0)));
|
|
3622
|
+
const supportsImages = options.supportsImages ?? false;
|
|
3623
|
+
const untrackedImages = await readImageFiles(ctx.cwd, options.includeUntracked ?? [], supportsImages && advisorUntrackedContentRef, "untracked", ADVISOR_IMAGES_TOTAL_MAX_BYTES, ADVISOR_IMAGES_MAX_COUNT);
|
|
3624
|
+
const trackedImages = await readImageFiles(ctx.cwd, options.includeTracked ?? [], supportsImages && advisorTrackedFileContentRef, "tracked", ADVISOR_IMAGES_TOTAL_MAX_BYTES - untrackedImages.images.reduce((sum, item) => sum + item.bytes, 0), ADVISOR_IMAGES_MAX_COUNT - untrackedImages.images.length);
|
|
3625
|
+
const images = [...untrackedImages.images, ...trackedImages.images].map((item) => ({
|
|
3626
|
+
image: item.image,
|
|
3627
|
+
label: `File ${JSON.stringify(item.path)}`
|
|
3628
|
+
}));
|
|
3629
|
+
let remainingBytes = ADVISOR_IMAGES_TOTAL_MAX_BYTES - [...untrackedImages.images, ...trackedImages.images].reduce((sum, item) => sum + item.bytes, 0);
|
|
3630
|
+
let imageOmissions = untrackedImages.omitted + trackedImages.omitted;
|
|
3631
|
+
const census = selectedConversationImages(ctx, curated.conversation, advisorToolPoliciesRef, curated.scout?.ok === true ? new Set(curated.selectedEntryIds) : undefined, imageNonce);
|
|
3632
|
+
for (const item of census.selected) {
|
|
3633
|
+
const bytes = Buffer.from(item.image.data, "base64").length;
|
|
3634
|
+
if (!supportsImages || images.length >= ADVISOR_IMAGES_MAX_COUNT || bytes > remainingBytes) {
|
|
3635
|
+
imageOmissions += 1;
|
|
3636
|
+
continue;
|
|
3637
|
+
}
|
|
3638
|
+
remainingBytes -= bytes;
|
|
3639
|
+
images.push({ image: item.image, label: item.marker });
|
|
3640
|
+
}
|
|
3158
3641
|
return {
|
|
3159
3642
|
changeText,
|
|
3160
3643
|
conversation: curated.conversation,
|
|
3161
3644
|
draftText,
|
|
3645
|
+
imageOmissions,
|
|
3646
|
+
imagePartsSeen: census.imagePartsSeen,
|
|
3647
|
+
images,
|
|
3162
3648
|
preferences,
|
|
3163
3649
|
scout: curated.scout,
|
|
3650
|
+
supportsImages,
|
|
3164
3651
|
tracked,
|
|
3165
3652
|
untracked
|
|
3166
3653
|
};
|
|
@@ -3263,35 +3750,6 @@ var adviceForGateText = (result) => `**Decision: ${result.decision}**
|
|
|
3263
3750
|
|
|
3264
3751
|
${result.markdown}`;
|
|
3265
3752
|
|
|
3266
|
-
// src/tools/model-access.ts
|
|
3267
|
-
var currentModelRef = (ctx) => {
|
|
3268
|
-
const { model } = ctx;
|
|
3269
|
-
return model ? `${model.provider}/${model.id}` : undefined;
|
|
3270
|
-
};
|
|
3271
|
-
var advisorModelAccess = (ctx) => {
|
|
3272
|
-
const modelRef = currentModelRef(ctx);
|
|
3273
|
-
if (advisorModelWhitelistRef.length === 0) {
|
|
3274
|
-
return modelRef ? { allowed: true, modelRef } : { allowed: true };
|
|
3275
|
-
}
|
|
3276
|
-
if (modelRef && advisorModelWhitelistRef.includes(modelRef)) {
|
|
3277
|
-
return { allowed: true, modelRef };
|
|
3278
|
-
}
|
|
3279
|
-
const current = modelRef ?? "no current model";
|
|
3280
|
-
const denial = {
|
|
3281
|
-
allowed: false,
|
|
3282
|
-
reason: `Advisor calls are restricted to the configured model whitelist (${advisorModelWhitelistRef.join(", ")}). Current model: ${current}.`
|
|
3283
|
-
};
|
|
3284
|
-
if (modelRef) {
|
|
3285
|
-
denial.modelRef = modelRef;
|
|
3286
|
-
}
|
|
3287
|
-
return denial;
|
|
3288
|
-
};
|
|
3289
|
-
var advisorModelIsAllowed = (ctx) => advisorModelAccess(ctx).allowed;
|
|
3290
|
-
var advisorModelAccessReason = (ctx) => {
|
|
3291
|
-
const access = advisorModelAccess(ctx);
|
|
3292
|
-
return access.allowed ? undefined : access.reason;
|
|
3293
|
-
};
|
|
3294
|
-
|
|
3295
3753
|
// src/tools/consultation.ts
|
|
3296
3754
|
class AdvisorNoAdviceError extends Error {
|
|
3297
3755
|
constructor() {
|
|
@@ -3305,20 +3763,38 @@ ${item.text}
|
|
|
3305
3763
|
var collectAdvisorResponse = async (options) => {
|
|
3306
3764
|
const { ctx, question, signal, systemPrompt } = options;
|
|
3307
3765
|
loadConfig(ctx);
|
|
3766
|
+
if (sameModelAdvisorDisabled(ctx)) {
|
|
3767
|
+
throw new Error(sameModelAdvisorNotice);
|
|
3768
|
+
}
|
|
3308
3769
|
const accessReason = advisorModelAccessReason(ctx);
|
|
3309
3770
|
if (accessReason) {
|
|
3310
3771
|
throw new Error(accessReason);
|
|
3311
3772
|
}
|
|
3312
3773
|
const resolved = await resolveConfiguredModel(ctx, advisorRef, "Advisor");
|
|
3313
|
-
const context = await assembleConsultationContext(
|
|
3774
|
+
const context = await assembleConsultationContext({
|
|
3775
|
+
...options,
|
|
3776
|
+
supportsImages: resolved.model.input.includes("image")
|
|
3777
|
+
});
|
|
3314
3778
|
const outboundQuestion = advisorRedactSecretsRef && question !== undefined ? redactSecrets(question) : question;
|
|
3779
|
+
const imageNotice = context.imageOmissions || context.images.length || /\[Image[ :]/u.test(context.conversation) ? `
|
|
3780
|
+
|
|
3781
|
+
Image disclosure: ${context.supportsImages ? `${context.images.length} image(s) attached below; ${context.imageOmissions} image(s) withheld by format, consent, or size/count limits` : "Advisor model does not support image input; no pixels were forwarded"}. Only the images explicitly attached below have pixels available. Other image markers, image paths, and text descriptions are not visual evidence.` : "";
|
|
3315
3782
|
const messages = [
|
|
3316
3783
|
{
|
|
3317
3784
|
content: [
|
|
3318
3785
|
{
|
|
3319
|
-
text: advisorMessageText(context.conversation, outboundQuestion, context.changeText, context.draftText, context.preferences?.text, context.untracked.map(fileTag), context.tracked.map(fileTag))
|
|
3786
|
+
text: `${advisorMessageText(context.conversation, outboundQuestion, context.changeText, context.draftText, context.preferences?.text, context.untracked.map(fileTag), context.tracked.map(fileTag))}${imageNotice}`,
|
|
3320
3787
|
type: "text"
|
|
3321
|
-
}
|
|
3788
|
+
},
|
|
3789
|
+
...context.images.flatMap(({ image, label }) => [
|
|
3790
|
+
{
|
|
3791
|
+
text: `
|
|
3792
|
+
|
|
3793
|
+
${escapeRepositoryText(label)}: attached image pixels (untrusted data).`,
|
|
3794
|
+
type: "text"
|
|
3795
|
+
},
|
|
3796
|
+
image
|
|
3797
|
+
])
|
|
3322
3798
|
],
|
|
3323
3799
|
role: "user",
|
|
3324
3800
|
timestamp: Date.now()
|
|
@@ -3337,6 +3813,10 @@ var collectAdvisorResponse = async (options) => {
|
|
|
3337
3813
|
}
|
|
3338
3814
|
const response = {
|
|
3339
3815
|
draftBytes: context.draftText ? Buffer.byteLength(context.draftText, "utf-8") : undefined,
|
|
3816
|
+
imageBytes: context.images.reduce((sum, item) => sum + Buffer.from(item.image.data, "base64").length, 0) || undefined,
|
|
3817
|
+
imageCount: context.images.length,
|
|
3818
|
+
imageOmissions: context.imageOmissions || undefined,
|
|
3819
|
+
imagePartsSeen: context.imagePartsSeen || undefined,
|
|
3340
3820
|
markdown,
|
|
3341
3821
|
model: advisorRef,
|
|
3342
3822
|
preferenceBytes: context.preferences?.bytes,
|
|
@@ -3364,7 +3844,7 @@ var consultAdvisor = async (ctx, question, signal, onChunk, trigger = "executor-
|
|
|
3364
3844
|
signal,
|
|
3365
3845
|
systemPrompt: ADVISOR_SYSTEM
|
|
3366
3846
|
});
|
|
3367
|
-
return { ...result, adviceId:
|
|
3847
|
+
return { ...result, adviceId: randomUUID2(), trigger };
|
|
3368
3848
|
};
|
|
3369
3849
|
var runAdvisorGate = async (ctx, question, trigger = "repeated-tool-call", signal, onChunk, onScout, currentInvocationId) => {
|
|
3370
3850
|
try {
|
|
@@ -4187,6 +4667,7 @@ class CommandRuntime {
|
|
|
4187
4667
|
manualProgressSequence = 0;
|
|
4188
4668
|
pendingExecutorModelRef;
|
|
4189
4669
|
suppressModelSelectionSync = false;
|
|
4670
|
+
lastSameModelDisabled;
|
|
4190
4671
|
constructor(pi, dependencies = {}) {
|
|
4191
4672
|
this.pi = pi;
|
|
4192
4673
|
this.advisorSessionState = dependencies.sessionState ?? advisorSessionState;
|
|
@@ -4197,6 +4678,21 @@ class CommandRuntime {
|
|
|
4197
4678
|
flowEnabled() {
|
|
4198
4679
|
return this.pi.getActiveTools().includes("ask_advisor");
|
|
4199
4680
|
}
|
|
4681
|
+
resetSameModelNotice() {
|
|
4682
|
+
this.lastSameModelDisabled = undefined;
|
|
4683
|
+
}
|
|
4684
|
+
updateSameModelNotice(ctx, model = ctx.model) {
|
|
4685
|
+
if (!this.flowEnabled()) {
|
|
4686
|
+
return;
|
|
4687
|
+
}
|
|
4688
|
+
const disabled = sameModelAdvisorDisabled(ctx, model);
|
|
4689
|
+
if (disabled && this.lastSameModelDisabled !== true) {
|
|
4690
|
+
notify(ctx, sameModelAdvisorNotice, "info");
|
|
4691
|
+
} else if (!disabled && this.lastSameModelDisabled === true) {
|
|
4692
|
+
notify(ctx, "Advisor re-enabled: executor and advisor models differ.", "info");
|
|
4693
|
+
}
|
|
4694
|
+
this.lastSameModelDisabled = disabled;
|
|
4695
|
+
}
|
|
4200
4696
|
nextManualProgressId() {
|
|
4201
4697
|
this.manualProgressSequence += 1;
|
|
4202
4698
|
return `manual-${this.manualProgressSequence}`;
|
|
@@ -4338,12 +4834,14 @@ var activateAdvisor = async (runtime, args, ctx, announce = true) => {
|
|
|
4338
4834
|
"record_advisor_outcome"
|
|
4339
4835
|
]);
|
|
4340
4836
|
}
|
|
4837
|
+
const activeModel = isMarkedSubagent() ? ctx.model : findConfiguredModel(ctx, executorRef);
|
|
4838
|
+
runtime.updateSameModelNotice(ctx, activeModel);
|
|
4341
4839
|
if (announce) {
|
|
4342
4840
|
const activeExecutorRef = effectiveExecutorRef(ctx);
|
|
4343
4841
|
const activeExecutorEffort = effectiveExecutorEffort(ctx);
|
|
4344
4842
|
notify(ctx, `${ADVISOR_ACTIVATION_EXPLANATION}
|
|
4345
4843
|
|
|
4346
|
-
Advisor flow ready — Executor: ${activeExecutorRef} (thinking: ${activeExecutorEffort || "default"}) · Advisor: ${advisorRef} (thinking: ${advisorEffortRef || "default"})`, "info");
|
|
4844
|
+
Advisor flow ${sameModelAdvisorDisabled(ctx, activeModel) ? "configured" : "ready"} — Executor: ${activeExecutorRef} (thinking: ${activeExecutorEffort || "default"}) · Advisor: ${advisorRef} (thinking: ${advisorEffortRef || "default"})`, "info");
|
|
4347
4845
|
}
|
|
4348
4846
|
};
|
|
4349
4847
|
|
|
@@ -4351,10 +4849,13 @@ Advisor flow ready — Executor: ${activeExecutorRef} (thinking: ${activeExecuto
|
|
|
4351
4849
|
var registerCommandLifecycle = (runtime, activateAdvisor) => {
|
|
4352
4850
|
runtime.pi.on("session_start", async (_event, ctx) => {
|
|
4353
4851
|
runtime.pendingExecutorModelRef = undefined;
|
|
4852
|
+
runtime.resetSameModelNotice();
|
|
4354
4853
|
try {
|
|
4355
4854
|
loadConfig(ctx);
|
|
4356
4855
|
if (alwaysOnRef) {
|
|
4357
4856
|
await activateAdvisor("", ctx, false);
|
|
4857
|
+
} else {
|
|
4858
|
+
runtime.updateSameModelNotice(ctx);
|
|
4358
4859
|
}
|
|
4359
4860
|
} catch (error) {
|
|
4360
4861
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -4362,6 +4863,9 @@ var registerCommandLifecycle = (runtime, activateAdvisor) => {
|
|
|
4362
4863
|
}
|
|
4363
4864
|
});
|
|
4364
4865
|
runtime.pi.on("model_select", (event, ctx) => {
|
|
4866
|
+
if (!runtime.suppressModelSelectionSync) {
|
|
4867
|
+
runtime.updateSameModelNotice(ctx, event.model);
|
|
4868
|
+
}
|
|
4365
4869
|
if (event.source !== "set" || runtime.suppressModelSelectionSync || isMarkedSubagent()) {
|
|
4366
4870
|
return;
|
|
4367
4871
|
}
|
|
@@ -5464,6 +5968,10 @@ var registerManualCommand = (runtime) => {
|
|
|
5464
5968
|
if (!loadCommandConfig(ctx)) {
|
|
5465
5969
|
return;
|
|
5466
5970
|
}
|
|
5971
|
+
if (sameModelAdvisorDisabled(ctx)) {
|
|
5972
|
+
notify(ctx, sameModelAdvisorNotice, "info");
|
|
5973
|
+
return;
|
|
5974
|
+
}
|
|
5467
5975
|
const accessReason = advisorModelAccessReason(ctx);
|
|
5468
5976
|
if (accessReason) {
|
|
5469
5977
|
notify(ctx, accessReason, "warning");
|
|
@@ -5507,6 +6015,10 @@ var registerManualCommand = (runtime) => {
|
|
|
5507
6015
|
} else {
|
|
5508
6016
|
question = resolveAdvisorRequest(args);
|
|
5509
6017
|
}
|
|
6018
|
+
if (sameModelAdvisorDisabled(ctx)) {
|
|
6019
|
+
notify(ctx, sameModelAdvisorNotice, "info");
|
|
6020
|
+
return;
|
|
6021
|
+
}
|
|
5510
6022
|
if (!isSimpleMode()) {
|
|
5511
6023
|
runtime.advisorSessionState.consumeCall();
|
|
5512
6024
|
}
|
|
@@ -5561,6 +6073,7 @@ var registerModelCommands = (runtime) => {
|
|
|
5561
6073
|
persistExecutor: true
|
|
5562
6074
|
});
|
|
5563
6075
|
runtime.pendingExecutorModelRef = undefined;
|
|
6076
|
+
runtime.updateSameModelNotice(ctx);
|
|
5564
6077
|
ctx.ui.notify(`Saved Executor + Advisor configurations to ${path}`, "info");
|
|
5565
6078
|
}
|
|
5566
6079
|
});
|
|
@@ -6320,7 +6833,7 @@ var createSettingsItems = ({
|
|
|
6320
6833
|
}
|
|
6321
6834
|
];
|
|
6322
6835
|
if (settings.simpleMode) {
|
|
6323
|
-
items.push(modelWhitelist);
|
|
6836
|
+
items.push(modelWhitelist, toggle("disableSameModel", "Disable same-model Advisor", "Skip advice when the active Executor and Advisor use the same provider/model; turn off to allow higher-effort same-model reviews.", settings.disableSameModel, true));
|
|
6324
6837
|
return items;
|
|
6325
6838
|
}
|
|
6326
6839
|
items.push({
|
|
@@ -6329,7 +6842,7 @@ var createSettingsItems = ({
|
|
|
6329
6842
|
id: "effort",
|
|
6330
6843
|
label: "Advisor reasoning",
|
|
6331
6844
|
values: withCurrentValue(currentEffort(settings.effort), effortLevels)
|
|
6332
|
-
}, modelWhitelist, toggle("scoutEnabled", "Experimental Advisor Scout", "Enable the experimental Scout before Advisor calls.", settings.scoutEnabled, false), scoutTimeoutItem(settings), 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), {
|
|
6845
|
+
}, modelWhitelist, toggle("disableSameModel", "Disable same-model Advisor", "Skip advice when the active Executor and Advisor use the same provider/model; turn off to allow higher-effort same-model reviews.", settings.disableSameModel, true), toggle("scoutEnabled", "Experimental Advisor Scout", "Enable the experimental Scout before Advisor calls.", settings.scoutEnabled, false), scoutTimeoutItem(settings), 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), {
|
|
6333
6846
|
currentValue: settings.customRule || "None",
|
|
6334
6847
|
description: "Add a rule that triggers Advisor involvement.",
|
|
6335
6848
|
id: "customRule",
|
|
@@ -6507,6 +7020,7 @@ var BOOLEAN_SETTING_FIELDS = [
|
|
|
6507
7020
|
"blockOnBlocked",
|
|
6508
7021
|
"collapseResponses",
|
|
6509
7022
|
"completionGate",
|
|
7023
|
+
"disableSameModel",
|
|
6510
7024
|
"failureGate",
|
|
6511
7025
|
"herdrIntegration",
|
|
6512
7026
|
"outcomeLogging",
|
|
@@ -6779,6 +7293,7 @@ var applySessionSettings = (settings) => {
|
|
|
6779
7293
|
setAdvisorPlanGateRef(settings.planGate);
|
|
6780
7294
|
setAdvisorFailureGateRef(settings.failureGate);
|
|
6781
7295
|
setAdvisorCompletionGateRef(settings.completionGate);
|
|
7296
|
+
setAdvisorDisableSameModelRef(settings.disableSameModel ?? true);
|
|
6782
7297
|
setAdvisorCollapseResponsesRef(settings.collapseResponses);
|
|
6783
7298
|
setAdvisorCustomInvocationRef(settings.customRule);
|
|
6784
7299
|
setAdvisorBlockOnBlockedRef(settings.blockOnBlocked ?? true);
|
|
@@ -6853,6 +7368,7 @@ var registerSettingsCommands = (runtime) => {
|
|
|
6853
7368
|
onChange: (settings) => {
|
|
6854
7369
|
try {
|
|
6855
7370
|
saveAdvisorSettings(ctx, settings);
|
|
7371
|
+
runtime.updateSameModelNotice(ctx);
|
|
6856
7372
|
runtime.updateAdvisorUsageStatus(ctx);
|
|
6857
7373
|
} catch (error) {
|
|
6858
7374
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -6869,6 +7385,7 @@ var registerSettingsCommands = (runtime) => {
|
|
|
6869
7385
|
description: "Disable on-demand Advisor calls; keep the current model",
|
|
6870
7386
|
handler: (_args, ctx) => {
|
|
6871
7387
|
runtime.pi.setActiveTools(runtime.pi.getActiveTools().filter((name) => name !== "ask_advisor" && name !== "record_advisor_outcome"));
|
|
7388
|
+
runtime.resetSameModelNotice();
|
|
6872
7389
|
const wasAlwaysOn = alwaysOnRef;
|
|
6873
7390
|
if (wasAlwaysOn) {
|
|
6874
7391
|
const persisted = getPersistedModelRefs();
|
|
@@ -6902,7 +7419,7 @@ import {
|
|
|
6902
7419
|
chmod,
|
|
6903
7420
|
link,
|
|
6904
7421
|
mkdir,
|
|
6905
|
-
open as
|
|
7422
|
+
open as open4,
|
|
6906
7423
|
readFile,
|
|
6907
7424
|
stat,
|
|
6908
7425
|
unlink,
|
|
@@ -6964,7 +7481,7 @@ var withOutcomeLock = async (run) => {
|
|
|
6964
7481
|
const lockPath = `${outcomeLogPath()}.lock`;
|
|
6965
7482
|
for (let attempt = 0;attempt < 200; attempt += 1) {
|
|
6966
7483
|
try {
|
|
6967
|
-
const lock = await
|
|
7484
|
+
const lock = await open4(lockPath, "wx", 384);
|
|
6968
7485
|
const identity = await lock.stat();
|
|
6969
7486
|
try {
|
|
6970
7487
|
return await run();
|
|
@@ -7147,7 +7664,7 @@ var handleJevTurnEnd = async (registration, ctx) => {
|
|
|
7147
7664
|
});
|
|
7148
7665
|
session.recordJevGateCheck(result.usage);
|
|
7149
7666
|
const shouldConsult = composeTurnGateVerdict(result.answers, advisorJevTurnGateNoulThresholdRef);
|
|
7150
|
-
if (!consumeTurnGateBudget(session, shouldConsult)) {
|
|
7667
|
+
if (!advisorModelIsAllowed(ctx) || !consumeTurnGateBudget(session, shouldConsult)) {
|
|
7151
7668
|
return;
|
|
7152
7669
|
}
|
|
7153
7670
|
const herdrActivity = registration.herdrActivity ?? herdrAdvisorActivity;
|
|
@@ -7214,17 +7731,30 @@ var syncRenderPhase = (context, phase) => {
|
|
|
7214
7731
|
}
|
|
7215
7732
|
context.state.phase = phase;
|
|
7216
7733
|
};
|
|
7734
|
+
var formatByteSize = (bytes) => {
|
|
7735
|
+
if (bytes >= 1024 ** 3) {
|
|
7736
|
+
return `${(bytes / 1024 ** 3).toFixed(1)} GiB`;
|
|
7737
|
+
}
|
|
7738
|
+
if (bytes >= 1024 ** 2) {
|
|
7739
|
+
return `${(bytes / 1024 ** 2).toFixed(1)} MiB`;
|
|
7740
|
+
}
|
|
7741
|
+
if (bytes >= 1024) {
|
|
7742
|
+
return `${Math.round(bytes / 1024)} KiB`;
|
|
7743
|
+
}
|
|
7744
|
+
return `${bytes} B`;
|
|
7745
|
+
};
|
|
7217
7746
|
var attachmentLabels = (details) => [
|
|
7218
|
-
details?.draftBytes ? `Draft attached · ${details.draftBytes}
|
|
7219
|
-
details?.preferenceBytes ? `Project preferences attached · ${details.preferenceBytes}
|
|
7220
|
-
details?.trackedBytes ? `Tracked files attached · ${details.trackedBytes}
|
|
7221
|
-
details?.untrackedBytes ? `Untracked files attached · ${details.untrackedBytes}
|
|
7747
|
+
details?.draftBytes ? `Draft attached · ${formatByteSize(details.draftBytes)}` : undefined,
|
|
7748
|
+
details?.preferenceBytes ? `Project preferences attached · ${formatByteSize(details.preferenceBytes)}` : undefined,
|
|
7749
|
+
details?.trackedBytes ? `Tracked files attached · ${formatByteSize(details.trackedBytes)}` : undefined,
|
|
7750
|
+
details?.untrackedBytes ? `Untracked files attached · ${formatByteSize(details.untrackedBytes)}` : undefined,
|
|
7751
|
+
details?.imageCount ? `${details.imageCount} image${details.imageCount === 1 ? "" : "s"} attached${details.imageBytes ? ` · ${formatByteSize(details.imageBytes)}` : ""}${details.imageOmissions ? ` · ${details.imageOmissions} withheld` : ""}` : undefined
|
|
7222
7752
|
].filter((label) => label !== undefined);
|
|
7223
|
-
var
|
|
7753
|
+
var renderSkipBox = (box, result, expanded, theme) => {
|
|
7224
7754
|
const details = advisorResultDetails(result);
|
|
7225
7755
|
const lines = [
|
|
7226
7756
|
theme.fg("dim", theme.bold("◆ ADVISOR · SKIPPED")),
|
|
7227
|
-
theme.fg("dim", ` ${details?.jev?.reason ?? ""}`)
|
|
7757
|
+
theme.fg("dim", ` ${details?.skipReason ?? details?.jev?.reason ?? ""}`)
|
|
7228
7758
|
];
|
|
7229
7759
|
box.addChild(new Text5(lines.join(`
|
|
7230
7760
|
`), 0, 0));
|
|
@@ -7286,8 +7816,8 @@ var renderFinalAdvisorResult = (box, result, expanded, theme, context) => {
|
|
|
7286
7816
|
context.state.timerId = undefined;
|
|
7287
7817
|
}
|
|
7288
7818
|
const details = advisorResultDetails(result);
|
|
7289
|
-
if (details?.jev?.skipped) {
|
|
7290
|
-
|
|
7819
|
+
if (details?.jev?.skipped || details?.skipReason) {
|
|
7820
|
+
renderSkipBox(box, result, expanded, theme);
|
|
7291
7821
|
return;
|
|
7292
7822
|
}
|
|
7293
7823
|
if (details?.scout) {
|
|
@@ -7353,7 +7883,22 @@ var registerAskAdvisorTool = ({
|
|
|
7353
7883
|
pi.registerTool({
|
|
7354
7884
|
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.",
|
|
7355
7885
|
async execute(_id, params, signal, onUpdate, ctx) {
|
|
7886
|
+
const skipSameModel = () => {
|
|
7887
|
+
session.releaseCall(_id);
|
|
7888
|
+
reservedCalls.delete(_id);
|
|
7889
|
+
return {
|
|
7890
|
+
content: [{ text: sameModelAdvisorNotice, type: "text" }],
|
|
7891
|
+
details: {
|
|
7892
|
+
skipReason: sameModelAdvisorNotice,
|
|
7893
|
+
text: sameModelAdvisorNotice
|
|
7894
|
+
}
|
|
7895
|
+
};
|
|
7896
|
+
};
|
|
7356
7897
|
try {
|
|
7898
|
+
loadConfig(ctx);
|
|
7899
|
+
if (sameModelAdvisorDisabled(ctx)) {
|
|
7900
|
+
return skipSameModel();
|
|
7901
|
+
}
|
|
7357
7902
|
assertAdvisorModelAccess(ctx);
|
|
7358
7903
|
} catch (error) {
|
|
7359
7904
|
session.releaseCall(_id);
|
|
@@ -7398,6 +7943,9 @@ var registerAskAdvisorTool = ({
|
|
|
7398
7943
|
}
|
|
7399
7944
|
};
|
|
7400
7945
|
}
|
|
7946
|
+
if (sameModelAdvisorDisabled(ctx)) {
|
|
7947
|
+
return skipSameModel();
|
|
7948
|
+
}
|
|
7401
7949
|
if (!simpleMode && !session.canConsult(getAdvisorMaxCallsPerSession(), _id)) {
|
|
7402
7950
|
throw new Error("Advisor call budget exhausted for this session.");
|
|
7403
7951
|
}
|
|
@@ -7460,6 +8008,10 @@ var registerAskAdvisorTool = ({
|
|
|
7460
8008
|
adviceId: result.adviceId,
|
|
7461
8009
|
advisor: result.model,
|
|
7462
8010
|
draftBytes: result.draftBytes,
|
|
8011
|
+
imageBytes: result.imageBytes,
|
|
8012
|
+
imageCount: result.imageCount,
|
|
8013
|
+
imageOmissions: result.imageOmissions,
|
|
8014
|
+
imagePartsSeen: result.imagePartsSeen,
|
|
7463
8015
|
preferenceBytes: result.preferenceBytes,
|
|
7464
8016
|
question: resolveAdvisorRequest(params.question),
|
|
7465
8017
|
scout: scoutDetails,
|
|
@@ -7713,7 +8265,7 @@ var registerToolLifecycle = ({
|
|
|
7713
8265
|
return;
|
|
7714
8266
|
}
|
|
7715
8267
|
loadConfig(ctx);
|
|
7716
|
-
if (!advisorModelAccess(ctx).allowed) {
|
|
8268
|
+
if (!advisorModelAccess(ctx).allowed || sameModelAdvisorDisabled(ctx)) {
|
|
7717
8269
|
return;
|
|
7718
8270
|
}
|
|
7719
8271
|
const guidelines = advisorInvocationGuidelines();
|
|
@@ -7743,6 +8295,9 @@ ${guidelines.map((rule) => `- ${rule}`).join(`
|
|
|
7743
8295
|
if (!loadConfigOrSkipGating(ctx)) {
|
|
7744
8296
|
return;
|
|
7745
8297
|
}
|
|
8298
|
+
if (sameModelAdvisorDisabled(ctx)) {
|
|
8299
|
+
return;
|
|
8300
|
+
}
|
|
7746
8301
|
const accessBlock = modelAccessBlock(event.toolName, ctx);
|
|
7747
8302
|
if (accessBlock) {
|
|
7748
8303
|
return accessBlock;
|