pi-advisor-flow 0.8.3 → 0.9.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 +24 -0
- package/README.md +6 -5
- package/dist/index.js +685 -96
- package/package.json +14 -14
- 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 +44 -12
- package/src/commands/manual-consultation.ts +7 -3
- package/src/commands/model-commands.ts +1 -0
- package/src/commands/runtime.ts +36 -10
- 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 +33 -9
- package/src/tools/scout-status.ts +11 -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/src/ui-guard.ts +31 -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 {
|
|
@@ -3404,6 +3884,30 @@ var runAdvisorGate = async (ctx, question, trigger = "repeated-tool-call", signa
|
|
|
3404
3884
|
// src/tools/scout-status.ts
|
|
3405
3885
|
import { Text as Text2 } from "@earendil-works/pi-tui";
|
|
3406
3886
|
|
|
3887
|
+
// src/ui-guard.ts
|
|
3888
|
+
var isStaleContext = (error) => error instanceof Error && /stale/iu.test(error.message);
|
|
3889
|
+
var uiAvailable = (ctx) => {
|
|
3890
|
+
try {
|
|
3891
|
+
return ctx.hasUI;
|
|
3892
|
+
} catch (error) {
|
|
3893
|
+
if (isStaleContext(error)) {
|
|
3894
|
+
return false;
|
|
3895
|
+
}
|
|
3896
|
+
throw error;
|
|
3897
|
+
}
|
|
3898
|
+
};
|
|
3899
|
+
var uiAction = (ctx, action) => {
|
|
3900
|
+
try {
|
|
3901
|
+
if (ctx.hasUI) {
|
|
3902
|
+
action(ctx.ui);
|
|
3903
|
+
}
|
|
3904
|
+
} catch (error) {
|
|
3905
|
+
if (!isStaleContext(error)) {
|
|
3906
|
+
throw error;
|
|
3907
|
+
}
|
|
3908
|
+
}
|
|
3909
|
+
};
|
|
3910
|
+
|
|
3407
3911
|
// src/tools/render-common.ts
|
|
3408
3912
|
import { getMarkdownTheme } from "@earendil-works/pi-coding-agent";
|
|
3409
3913
|
import {
|
|
@@ -3591,14 +4095,14 @@ class ScoutStatusManager {
|
|
|
3591
4095
|
}
|
|
3592
4096
|
}
|
|
3593
4097
|
update(ctx, token, event) {
|
|
3594
|
-
if (this.#retired.has(token) || !ctx
|
|
4098
|
+
if (this.#retired.has(token) || !uiAvailable(ctx)) {
|
|
3595
4099
|
return;
|
|
3596
4100
|
}
|
|
3597
4101
|
this.#known.add(token);
|
|
3598
4102
|
if (event.type === "call" || event.type === "chunk") {
|
|
3599
4103
|
this.#active.add(token);
|
|
3600
4104
|
if (this.showStatus) {
|
|
3601
|
-
ctx
|
|
4105
|
+
uiAction(ctx, (ui) => ui.setStatus("advisor-scout", "Scout curating…"));
|
|
3602
4106
|
}
|
|
3603
4107
|
return;
|
|
3604
4108
|
}
|
|
@@ -3608,10 +4112,10 @@ class ScoutStatusManager {
|
|
|
3608
4112
|
this.#active.delete(token);
|
|
3609
4113
|
this.#known.delete(token);
|
|
3610
4114
|
this.#retired.add(token);
|
|
3611
|
-
if (!(ctx
|
|
4115
|
+
if (!(uiAvailable(ctx) && this.showStatus)) {
|
|
3612
4116
|
return;
|
|
3613
4117
|
}
|
|
3614
|
-
ctx
|
|
4118
|
+
uiAction(ctx, (ui) => ui.setStatus("advisor-scout", this.#active.size > 0 ? "Scout curating…" : undefined));
|
|
3615
4119
|
}
|
|
3616
4120
|
clear(ctx) {
|
|
3617
4121
|
for (const token of this.#known) {
|
|
@@ -3619,8 +4123,8 @@ class ScoutStatusManager {
|
|
|
3619
4123
|
}
|
|
3620
4124
|
this.#known.clear();
|
|
3621
4125
|
this.#active.clear();
|
|
3622
|
-
if (
|
|
3623
|
-
ctx
|
|
4126
|
+
if (this.showStatus) {
|
|
4127
|
+
uiAction(ctx, (ui) => ui.setStatus("advisor-scout", undefined));
|
|
3624
4128
|
}
|
|
3625
4129
|
}
|
|
3626
4130
|
}
|
|
@@ -4158,9 +4662,7 @@ var advisorSessionState = new AdvisorSessionState;
|
|
|
4158
4662
|
|
|
4159
4663
|
// src/commands/runtime.ts
|
|
4160
4664
|
var notify = (ctx, message, level) => {
|
|
4161
|
-
|
|
4162
|
-
ctx.ui.notify(message, level);
|
|
4163
|
-
}
|
|
4665
|
+
uiAction(ctx, (ui) => ui.notify(message, level));
|
|
4164
4666
|
};
|
|
4165
4667
|
var reportManualBudgetExhausted = (ctx) => {
|
|
4166
4668
|
const message = "Advisor call budget exhausted for this session.";
|
|
@@ -4168,9 +4670,7 @@ var reportManualBudgetExhausted = (ctx) => {
|
|
|
4168
4670
|
notifyHerdrAdvisorFailure("Advisor budget exhausted", message);
|
|
4169
4671
|
};
|
|
4170
4672
|
var requestManualRender = (ctx) => {
|
|
4171
|
-
|
|
4172
|
-
ctx.ui.setStatus("advisor-manual", undefined);
|
|
4173
|
-
}
|
|
4673
|
+
uiAction(ctx, (ui) => ui.setStatus("advisor-manual", undefined));
|
|
4174
4674
|
};
|
|
4175
4675
|
|
|
4176
4676
|
class CommandRuntime {
|
|
@@ -4187,6 +4687,7 @@ class CommandRuntime {
|
|
|
4187
4687
|
manualProgressSequence = 0;
|
|
4188
4688
|
pendingExecutorModelRef;
|
|
4189
4689
|
suppressModelSelectionSync = false;
|
|
4690
|
+
lastSameModelDisabled;
|
|
4190
4691
|
constructor(pi, dependencies = {}) {
|
|
4191
4692
|
this.pi = pi;
|
|
4192
4693
|
this.advisorSessionState = dependencies.sessionState ?? advisorSessionState;
|
|
@@ -4197,6 +4698,21 @@ class CommandRuntime {
|
|
|
4197
4698
|
flowEnabled() {
|
|
4198
4699
|
return this.pi.getActiveTools().includes("ask_advisor");
|
|
4199
4700
|
}
|
|
4701
|
+
resetSameModelNotice() {
|
|
4702
|
+
this.lastSameModelDisabled = undefined;
|
|
4703
|
+
}
|
|
4704
|
+
updateSameModelNotice(ctx, model = ctx.model) {
|
|
4705
|
+
if (!this.flowEnabled()) {
|
|
4706
|
+
return;
|
|
4707
|
+
}
|
|
4708
|
+
const disabled = sameModelAdvisorDisabled(ctx, model);
|
|
4709
|
+
if (disabled && this.lastSameModelDisabled !== true) {
|
|
4710
|
+
notify(ctx, sameModelAdvisorNotice, "info");
|
|
4711
|
+
} else if (!disabled && this.lastSameModelDisabled === true) {
|
|
4712
|
+
notify(ctx, "Advisor re-enabled: executor and advisor models differ.", "info");
|
|
4713
|
+
}
|
|
4714
|
+
this.lastSameModelDisabled = disabled;
|
|
4715
|
+
}
|
|
4200
4716
|
nextManualProgressId() {
|
|
4201
4717
|
this.manualProgressSequence += 1;
|
|
4202
4718
|
return `manual-${this.manualProgressSequence}`;
|
|
@@ -4210,9 +4726,7 @@ class CommandRuntime {
|
|
|
4210
4726
|
}
|
|
4211
4727
|
}
|
|
4212
4728
|
updateAdvisorUsageStatus(ctx) {
|
|
4213
|
-
|
|
4214
|
-
ctx.ui.setStatus("advisor-usage", getAdvisorSettings().showUsageFooter ? this.advisorSessionState.usageStatus() : undefined);
|
|
4215
|
-
}
|
|
4729
|
+
uiAction(ctx, (ui) => ui.setStatus("advisor-usage", getAdvisorSettings().showUsageFooter ? this.advisorSessionState.usageStatus() : undefined));
|
|
4216
4730
|
}
|
|
4217
4731
|
}
|
|
4218
4732
|
var createCommandRuntime = (pi, dependencies = {}) => new CommandRuntime(pi, dependencies);
|
|
@@ -4338,12 +4852,14 @@ var activateAdvisor = async (runtime, args, ctx, announce = true) => {
|
|
|
4338
4852
|
"record_advisor_outcome"
|
|
4339
4853
|
]);
|
|
4340
4854
|
}
|
|
4855
|
+
const activeModel = isMarkedSubagent() ? ctx.model : findConfiguredModel(ctx, executorRef);
|
|
4856
|
+
runtime.updateSameModelNotice(ctx, activeModel);
|
|
4341
4857
|
if (announce) {
|
|
4342
4858
|
const activeExecutorRef = effectiveExecutorRef(ctx);
|
|
4343
4859
|
const activeExecutorEffort = effectiveExecutorEffort(ctx);
|
|
4344
4860
|
notify(ctx, `${ADVISOR_ACTIVATION_EXPLANATION}
|
|
4345
4861
|
|
|
4346
|
-
Advisor flow ready — Executor: ${activeExecutorRef} (thinking: ${activeExecutorEffort || "default"}) · Advisor: ${advisorRef} (thinking: ${advisorEffortRef || "default"})`, "info");
|
|
4862
|
+
Advisor flow ${sameModelAdvisorDisabled(ctx, activeModel) ? "configured" : "ready"} — Executor: ${activeExecutorRef} (thinking: ${activeExecutorEffort || "default"}) · Advisor: ${advisorRef} (thinking: ${advisorEffortRef || "default"})`, "info");
|
|
4347
4863
|
}
|
|
4348
4864
|
};
|
|
4349
4865
|
|
|
@@ -4351,10 +4867,13 @@ Advisor flow ready — Executor: ${activeExecutorRef} (thinking: ${activeExecuto
|
|
|
4351
4867
|
var registerCommandLifecycle = (runtime, activateAdvisor) => {
|
|
4352
4868
|
runtime.pi.on("session_start", async (_event, ctx) => {
|
|
4353
4869
|
runtime.pendingExecutorModelRef = undefined;
|
|
4870
|
+
runtime.resetSameModelNotice();
|
|
4354
4871
|
try {
|
|
4355
4872
|
loadConfig(ctx);
|
|
4356
4873
|
if (alwaysOnRef) {
|
|
4357
4874
|
await activateAdvisor("", ctx, false);
|
|
4875
|
+
} else {
|
|
4876
|
+
runtime.updateSameModelNotice(ctx);
|
|
4358
4877
|
}
|
|
4359
4878
|
} catch (error) {
|
|
4360
4879
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -4362,6 +4881,9 @@ var registerCommandLifecycle = (runtime, activateAdvisor) => {
|
|
|
4362
4881
|
}
|
|
4363
4882
|
});
|
|
4364
4883
|
runtime.pi.on("model_select", (event, ctx) => {
|
|
4884
|
+
if (!runtime.suppressModelSelectionSync) {
|
|
4885
|
+
runtime.updateSameModelNotice(ctx, event.model);
|
|
4886
|
+
}
|
|
4365
4887
|
if (event.source !== "set" || runtime.suppressModelSelectionSync || isMarkedSubagent()) {
|
|
4366
4888
|
return;
|
|
4367
4889
|
}
|
|
@@ -5345,9 +5867,10 @@ var screeningSkipText = (outcome) => outcome.kind === "repeat" && outcome.reatta
|
|
|
5345
5867
|
|
|
5346
5868
|
// src/commands/manual-consultation.ts
|
|
5347
5869
|
var startManualConsultation = async (runtime, ctx, question, controller, scoutStatusToken, progress, gitContext) => {
|
|
5870
|
+
const isPrintMode = ctx.mode === "print";
|
|
5348
5871
|
progress.phase = "preparing";
|
|
5349
5872
|
runtime.requestManualRender(ctx);
|
|
5350
|
-
if (ctx
|
|
5873
|
+
if (uiAvailable(ctx)) {
|
|
5351
5874
|
const timer = setInterval(() => {
|
|
5352
5875
|
if (controller.signal.aborted || runtime.manualConsultations.get(controller) !== scoutStatusToken) {
|
|
5353
5876
|
clearInterval(timer);
|
|
@@ -5412,7 +5935,7 @@ ${markdown}`,
|
|
|
5412
5935
|
display: true
|
|
5413
5936
|
}, {
|
|
5414
5937
|
deliverAs: "steer",
|
|
5415
|
-
triggerTurn:
|
|
5938
|
+
triggerTurn: !isPrintMode
|
|
5416
5939
|
});
|
|
5417
5940
|
} catch (error) {
|
|
5418
5941
|
if (controller.signal.aborted) {
|
|
@@ -5437,7 +5960,7 @@ ${markdown}`,
|
|
|
5437
5960
|
text: `**Advisor consultation failed:** ${message}`
|
|
5438
5961
|
},
|
|
5439
5962
|
display: true
|
|
5440
|
-
}, { deliverAs: "steer", triggerTurn:
|
|
5963
|
+
}, { deliverAs: "steer", triggerTurn: !isPrintMode });
|
|
5441
5964
|
notify(ctx, `Advisor consultation failed: ${message}`, "error");
|
|
5442
5965
|
notifyHerdrAdvisorFailure("Advisor consultation failed", message);
|
|
5443
5966
|
} finally {
|
|
@@ -5464,6 +5987,10 @@ var registerManualCommand = (runtime) => {
|
|
|
5464
5987
|
if (!loadCommandConfig(ctx)) {
|
|
5465
5988
|
return;
|
|
5466
5989
|
}
|
|
5990
|
+
if (sameModelAdvisorDisabled(ctx)) {
|
|
5991
|
+
notify(ctx, sameModelAdvisorNotice, "info");
|
|
5992
|
+
return;
|
|
5993
|
+
}
|
|
5467
5994
|
const accessReason = advisorModelAccessReason(ctx);
|
|
5468
5995
|
if (accessReason) {
|
|
5469
5996
|
notify(ctx, accessReason, "warning");
|
|
@@ -5507,6 +6034,10 @@ var registerManualCommand = (runtime) => {
|
|
|
5507
6034
|
} else {
|
|
5508
6035
|
question = resolveAdvisorRequest(args);
|
|
5509
6036
|
}
|
|
6037
|
+
if (sameModelAdvisorDisabled(ctx)) {
|
|
6038
|
+
notify(ctx, sameModelAdvisorNotice, "info");
|
|
6039
|
+
return;
|
|
6040
|
+
}
|
|
5510
6041
|
if (!isSimpleMode()) {
|
|
5511
6042
|
runtime.advisorSessionState.consumeCall();
|
|
5512
6043
|
}
|
|
@@ -5523,7 +6054,15 @@ var registerManualCommand = (runtime) => {
|
|
|
5523
6054
|
runtime.scoutStatus.register(scoutStatusToken);
|
|
5524
6055
|
runtime.manualConsultations.set(controller, scoutStatusToken);
|
|
5525
6056
|
runtime.pi.appendEntry?.("advisor-manual-call", { progressId, question });
|
|
5526
|
-
|
|
6057
|
+
if (ctx.mode === "print") {
|
|
6058
|
+
await startManualConsultation(runtime, ctx, question, controller, scoutStatusToken, progress, gitContext);
|
|
6059
|
+
return;
|
|
6060
|
+
}
|
|
6061
|
+
(async () => {
|
|
6062
|
+
try {
|
|
6063
|
+
await startManualConsultation(runtime, ctx, question, controller, scoutStatusToken, progress, gitContext);
|
|
6064
|
+
} catch {}
|
|
6065
|
+
})();
|
|
5527
6066
|
}
|
|
5528
6067
|
});
|
|
5529
6068
|
};
|
|
@@ -5561,6 +6100,7 @@ var registerModelCommands = (runtime) => {
|
|
|
5561
6100
|
persistExecutor: true
|
|
5562
6101
|
});
|
|
5563
6102
|
runtime.pendingExecutorModelRef = undefined;
|
|
6103
|
+
runtime.updateSameModelNotice(ctx);
|
|
5564
6104
|
ctx.ui.notify(`Saved Executor + Advisor configurations to ${path}`, "info");
|
|
5565
6105
|
}
|
|
5566
6106
|
});
|
|
@@ -6320,7 +6860,7 @@ var createSettingsItems = ({
|
|
|
6320
6860
|
}
|
|
6321
6861
|
];
|
|
6322
6862
|
if (settings.simpleMode) {
|
|
6323
|
-
items.push(modelWhitelist);
|
|
6863
|
+
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
6864
|
return items;
|
|
6325
6865
|
}
|
|
6326
6866
|
items.push({
|
|
@@ -6329,7 +6869,7 @@ var createSettingsItems = ({
|
|
|
6329
6869
|
id: "effort",
|
|
6330
6870
|
label: "Advisor reasoning",
|
|
6331
6871
|
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), {
|
|
6872
|
+
}, 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
6873
|
currentValue: settings.customRule || "None",
|
|
6334
6874
|
description: "Add a rule that triggers Advisor involvement.",
|
|
6335
6875
|
id: "customRule",
|
|
@@ -6507,6 +7047,7 @@ var BOOLEAN_SETTING_FIELDS = [
|
|
|
6507
7047
|
"blockOnBlocked",
|
|
6508
7048
|
"collapseResponses",
|
|
6509
7049
|
"completionGate",
|
|
7050
|
+
"disableSameModel",
|
|
6510
7051
|
"failureGate",
|
|
6511
7052
|
"herdrIntegration",
|
|
6512
7053
|
"outcomeLogging",
|
|
@@ -6779,6 +7320,7 @@ var applySessionSettings = (settings) => {
|
|
|
6779
7320
|
setAdvisorPlanGateRef(settings.planGate);
|
|
6780
7321
|
setAdvisorFailureGateRef(settings.failureGate);
|
|
6781
7322
|
setAdvisorCompletionGateRef(settings.completionGate);
|
|
7323
|
+
setAdvisorDisableSameModelRef(settings.disableSameModel ?? true);
|
|
6782
7324
|
setAdvisorCollapseResponsesRef(settings.collapseResponses);
|
|
6783
7325
|
setAdvisorCustomInvocationRef(settings.customRule);
|
|
6784
7326
|
setAdvisorBlockOnBlockedRef(settings.blockOnBlocked ?? true);
|
|
@@ -6853,6 +7395,7 @@ var registerSettingsCommands = (runtime) => {
|
|
|
6853
7395
|
onChange: (settings) => {
|
|
6854
7396
|
try {
|
|
6855
7397
|
saveAdvisorSettings(ctx, settings);
|
|
7398
|
+
runtime.updateSameModelNotice(ctx);
|
|
6856
7399
|
runtime.updateAdvisorUsageStatus(ctx);
|
|
6857
7400
|
} catch (error) {
|
|
6858
7401
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -6869,6 +7412,7 @@ var registerSettingsCommands = (runtime) => {
|
|
|
6869
7412
|
description: "Disable on-demand Advisor calls; keep the current model",
|
|
6870
7413
|
handler: (_args, ctx) => {
|
|
6871
7414
|
runtime.pi.setActiveTools(runtime.pi.getActiveTools().filter((name) => name !== "ask_advisor" && name !== "record_advisor_outcome"));
|
|
7415
|
+
runtime.resetSameModelNotice();
|
|
6872
7416
|
const wasAlwaysOn = alwaysOnRef;
|
|
6873
7417
|
if (wasAlwaysOn) {
|
|
6874
7418
|
const persisted = getPersistedModelRefs();
|
|
@@ -6902,7 +7446,7 @@ import {
|
|
|
6902
7446
|
chmod,
|
|
6903
7447
|
link,
|
|
6904
7448
|
mkdir,
|
|
6905
|
-
open as
|
|
7449
|
+
open as open4,
|
|
6906
7450
|
readFile,
|
|
6907
7451
|
stat,
|
|
6908
7452
|
unlink,
|
|
@@ -6964,7 +7508,7 @@ var withOutcomeLock = async (run) => {
|
|
|
6964
7508
|
const lockPath = `${outcomeLogPath()}.lock`;
|
|
6965
7509
|
for (let attempt = 0;attempt < 200; attempt += 1) {
|
|
6966
7510
|
try {
|
|
6967
|
-
const lock = await
|
|
7511
|
+
const lock = await open4(lockPath, "wx", 384);
|
|
6968
7512
|
const identity = await lock.stat();
|
|
6969
7513
|
try {
|
|
6970
7514
|
return await run();
|
|
@@ -7147,7 +7691,7 @@ var handleJevTurnEnd = async (registration, ctx) => {
|
|
|
7147
7691
|
});
|
|
7148
7692
|
session.recordJevGateCheck(result.usage);
|
|
7149
7693
|
const shouldConsult = composeTurnGateVerdict(result.answers, advisorJevTurnGateNoulThresholdRef);
|
|
7150
|
-
if (!consumeTurnGateBudget(session, shouldConsult)) {
|
|
7694
|
+
if (!advisorModelIsAllowed(ctx) || !consumeTurnGateBudget(session, shouldConsult)) {
|
|
7151
7695
|
return;
|
|
7152
7696
|
}
|
|
7153
7697
|
const herdrActivity = registration.herdrActivity ?? herdrAdvisorActivity;
|
|
@@ -7214,17 +7758,30 @@ var syncRenderPhase = (context, phase) => {
|
|
|
7214
7758
|
}
|
|
7215
7759
|
context.state.phase = phase;
|
|
7216
7760
|
};
|
|
7761
|
+
var formatByteSize = (bytes) => {
|
|
7762
|
+
if (bytes >= 1024 ** 3) {
|
|
7763
|
+
return `${(bytes / 1024 ** 3).toFixed(1)} GiB`;
|
|
7764
|
+
}
|
|
7765
|
+
if (bytes >= 1024 ** 2) {
|
|
7766
|
+
return `${(bytes / 1024 ** 2).toFixed(1)} MiB`;
|
|
7767
|
+
}
|
|
7768
|
+
if (bytes >= 1024) {
|
|
7769
|
+
return `${Math.round(bytes / 1024)} KiB`;
|
|
7770
|
+
}
|
|
7771
|
+
return `${bytes} B`;
|
|
7772
|
+
};
|
|
7217
7773
|
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}
|
|
7774
|
+
details?.draftBytes ? `Draft attached · ${formatByteSize(details.draftBytes)}` : undefined,
|
|
7775
|
+
details?.preferenceBytes ? `Project preferences attached · ${formatByteSize(details.preferenceBytes)}` : undefined,
|
|
7776
|
+
details?.trackedBytes ? `Tracked files attached · ${formatByteSize(details.trackedBytes)}` : undefined,
|
|
7777
|
+
details?.untrackedBytes ? `Untracked files attached · ${formatByteSize(details.untrackedBytes)}` : undefined,
|
|
7778
|
+
details?.imageCount ? `${details.imageCount} image${details.imageCount === 1 ? "" : "s"} attached${details.imageBytes ? ` · ${formatByteSize(details.imageBytes)}` : ""}${details.imageOmissions ? ` · ${details.imageOmissions} withheld` : ""}` : undefined
|
|
7222
7779
|
].filter((label) => label !== undefined);
|
|
7223
|
-
var
|
|
7780
|
+
var renderSkipBox = (box, result, expanded, theme) => {
|
|
7224
7781
|
const details = advisorResultDetails(result);
|
|
7225
7782
|
const lines = [
|
|
7226
7783
|
theme.fg("dim", theme.bold("◆ ADVISOR · SKIPPED")),
|
|
7227
|
-
theme.fg("dim", ` ${details?.jev?.reason ?? ""}`)
|
|
7784
|
+
theme.fg("dim", ` ${details?.skipReason ?? details?.jev?.reason ?? ""}`)
|
|
7228
7785
|
];
|
|
7229
7786
|
box.addChild(new Text5(lines.join(`
|
|
7230
7787
|
`), 0, 0));
|
|
@@ -7239,7 +7796,14 @@ var renderPartialAdvisorResult = (box, result, expanded, theme, context) => {
|
|
|
7239
7796
|
const scoutActive = scout?.status === "calling" || scout?.status === "streaming";
|
|
7240
7797
|
syncRenderPhase(context, scoutActive ? "scout" : "advisor");
|
|
7241
7798
|
if (!context.state.timerId) {
|
|
7242
|
-
context.state.timerId = setInterval(() =>
|
|
7799
|
+
context.state.timerId = setInterval(() => {
|
|
7800
|
+
try {
|
|
7801
|
+
context.invalidate();
|
|
7802
|
+
} catch {
|
|
7803
|
+
clearInterval(context.state.timerId);
|
|
7804
|
+
context.state.timerId = undefined;
|
|
7805
|
+
}
|
|
7806
|
+
}, 80);
|
|
7243
7807
|
}
|
|
7244
7808
|
if (scout) {
|
|
7245
7809
|
renderScoutDetails(box, scout, expanded, theme);
|
|
@@ -7286,8 +7850,8 @@ var renderFinalAdvisorResult = (box, result, expanded, theme, context) => {
|
|
|
7286
7850
|
context.state.timerId = undefined;
|
|
7287
7851
|
}
|
|
7288
7852
|
const details = advisorResultDetails(result);
|
|
7289
|
-
if (details?.jev?.skipped) {
|
|
7290
|
-
|
|
7853
|
+
if (details?.jev?.skipped || details?.skipReason) {
|
|
7854
|
+
renderSkipBox(box, result, expanded, theme);
|
|
7291
7855
|
return;
|
|
7292
7856
|
}
|
|
7293
7857
|
if (details?.scout) {
|
|
@@ -7353,7 +7917,22 @@ var registerAskAdvisorTool = ({
|
|
|
7353
7917
|
pi.registerTool({
|
|
7354
7918
|
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
7919
|
async execute(_id, params, signal, onUpdate, ctx) {
|
|
7920
|
+
const skipSameModel = () => {
|
|
7921
|
+
session.releaseCall(_id);
|
|
7922
|
+
reservedCalls.delete(_id);
|
|
7923
|
+
return {
|
|
7924
|
+
content: [{ text: sameModelAdvisorNotice, type: "text" }],
|
|
7925
|
+
details: {
|
|
7926
|
+
skipReason: sameModelAdvisorNotice,
|
|
7927
|
+
text: sameModelAdvisorNotice
|
|
7928
|
+
}
|
|
7929
|
+
};
|
|
7930
|
+
};
|
|
7356
7931
|
try {
|
|
7932
|
+
loadConfig(ctx);
|
|
7933
|
+
if (sameModelAdvisorDisabled(ctx)) {
|
|
7934
|
+
return skipSameModel();
|
|
7935
|
+
}
|
|
7357
7936
|
assertAdvisorModelAccess(ctx);
|
|
7358
7937
|
} catch (error) {
|
|
7359
7938
|
session.releaseCall(_id);
|
|
@@ -7398,6 +7977,9 @@ var registerAskAdvisorTool = ({
|
|
|
7398
7977
|
}
|
|
7399
7978
|
};
|
|
7400
7979
|
}
|
|
7980
|
+
if (sameModelAdvisorDisabled(ctx)) {
|
|
7981
|
+
return skipSameModel();
|
|
7982
|
+
}
|
|
7401
7983
|
if (!simpleMode && !session.canConsult(getAdvisorMaxCallsPerSession(), _id)) {
|
|
7402
7984
|
throw new Error("Advisor call budget exhausted for this session.");
|
|
7403
7985
|
}
|
|
@@ -7460,6 +8042,10 @@ var registerAskAdvisorTool = ({
|
|
|
7460
8042
|
adviceId: result.adviceId,
|
|
7461
8043
|
advisor: result.model,
|
|
7462
8044
|
draftBytes: result.draftBytes,
|
|
8045
|
+
imageBytes: result.imageBytes,
|
|
8046
|
+
imageCount: result.imageCount,
|
|
8047
|
+
imageOmissions: result.imageOmissions,
|
|
8048
|
+
imagePartsSeen: result.imagePartsSeen,
|
|
7463
8049
|
preferenceBytes: result.preferenceBytes,
|
|
7464
8050
|
question: resolveAdvisorRequest(params.question),
|
|
7465
8051
|
scout: scoutDetails,
|
|
@@ -7713,7 +8299,7 @@ var registerToolLifecycle = ({
|
|
|
7713
8299
|
return;
|
|
7714
8300
|
}
|
|
7715
8301
|
loadConfig(ctx);
|
|
7716
|
-
if (!advisorModelAccess(ctx).allowed) {
|
|
8302
|
+
if (!advisorModelAccess(ctx).allowed || sameModelAdvisorDisabled(ctx)) {
|
|
7717
8303
|
return;
|
|
7718
8304
|
}
|
|
7719
8305
|
const guidelines = advisorInvocationGuidelines();
|
|
@@ -7743,6 +8329,9 @@ ${guidelines.map((rule) => `- ${rule}`).join(`
|
|
|
7743
8329
|
if (!loadConfigOrSkipGating(ctx)) {
|
|
7744
8330
|
return;
|
|
7745
8331
|
}
|
|
8332
|
+
if (sameModelAdvisorDisabled(ctx)) {
|
|
8333
|
+
return;
|
|
8334
|
+
}
|
|
7746
8335
|
const accessBlock = modelAccessBlock(event.toolName, ctx);
|
|
7747
8336
|
if (accessBlock) {
|
|
7748
8337
|
return accessBlock;
|