pi-advisor-flow 0.5.5 → 0.6.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 +28 -1
- package/README.md +1 -1
- package/dist/index.js +5786 -0
- package/extensions/index.ts +9 -9
- package/package.json +16 -10
- package/src/{untracked.ts → attachments.ts} +2 -5
- package/src/commands/activation-preparation.ts +6 -6
- package/src/commands/activation.ts +7 -7
- package/src/commands/lifecycle.ts +5 -5
- package/src/commands/manual-command.ts +13 -9
- package/src/commands/manual-consultation.ts +8 -8
- package/src/commands/manual-progress.ts +3 -3
- package/src/commands/model-commands.ts +6 -6
- package/src/commands/model-options.ts +4 -4
- package/src/commands/model-picker.ts +2 -2
- package/src/commands/registration.ts +8 -8
- package/src/commands/renderers.ts +5 -5
- package/src/commands/runtime.ts +8 -8
- package/src/commands/settings-commands.ts +8 -8
- package/src/commands/settings-persistence.ts +4 -4
- package/src/commands/types.ts +5 -13
- package/src/commands.ts +2 -2
- package/src/config/args.ts +3 -3
- package/src/config/defaults.ts +3 -3
- package/src/config/schema.ts +322 -0
- package/src/config/state.ts +2 -2
- package/src/config/storage.ts +113 -103
- package/src/config/types.ts +1 -1
- package/src/config/validation.ts +61 -181
- package/src/config.ts +7 -7
- package/src/content-utils.ts +30 -0
- package/src/conversation.ts +13 -168
- package/src/git.ts +4 -18
- package/src/herdr.ts +6 -8
- package/src/model-stream.ts +5 -8
- package/src/outcomes.ts +2 -5
- package/src/pi-settings.ts +59 -0
- package/src/preferences.ts +2 -2
- package/src/redaction.ts +50 -0
- package/src/scout-context.ts +121 -485
- package/src/scout-curation.ts +84 -0
- package/src/scout-groups.ts +485 -0
- package/src/scout-protocol.ts +122 -0
- package/src/scout-reconstruct.ts +44 -0
- package/src/scout-types.ts +66 -0
- package/src/scout.ts +53 -63
- package/src/session-state.ts +119 -76
- package/src/tool-result-cap.ts +88 -0
- package/src/tools/consult-context.ts +155 -0
- package/src/tools/consultation.ts +71 -220
- package/src/tools/gate-policy.ts +5 -5
- package/src/tools/gate-protocol.ts +2 -2
- package/src/tools/loop-gate.ts +70 -61
- package/src/tools/prompts.ts +4 -7
- package/src/tools/register-ask-advisor.ts +9 -14
- package/src/tools/register-lifecycle.ts +7 -7
- package/src/tools/register-outcome.ts +5 -5
- package/src/tools/register-renderers.ts +5 -5
- package/src/tools/registration.ts +10 -10
- package/src/tools/render-advisor-result.ts +11 -8
- package/src/tools/render-common.ts +25 -13
- package/src/tools/scout-status.ts +5 -5
- package/src/tools/session.ts +1 -1
- package/src/tools/types.ts +7 -7
- package/src/tools.ts +9 -9
- package/src/ui/manual-dialog-render.ts +10 -3
- package/src/ui/manual-dialog.ts +4 -9
- package/src/ui/model-selector.ts +7 -6
- package/src/ui/settings-formatting.ts +2 -2
- package/src/ui/settings-items.ts +4 -4
- package/src/ui/settings-mutations.ts +2 -2
- package/src/ui/settings-selector.ts +5 -5
- package/src/ui/text-setting-submenu.ts +6 -1
- package/src/ui/types.ts +2 -2
- package/src/ui.ts +4 -4
- package/src/usage.ts +1 -6
package/src/scout-context.ts
CHANGED
|
@@ -1,438 +1,110 @@
|
|
|
1
|
-
import { createHash } from "node:crypto";
|
|
2
1
|
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
3
2
|
import {
|
|
4
3
|
advisorRedactSecretsRef,
|
|
5
4
|
advisorToolPoliciesRef,
|
|
6
5
|
advisorToolResultMaxBytesRef,
|
|
7
6
|
advisorToolResultMaxLinesRef,
|
|
8
|
-
} from "./config/state.
|
|
9
|
-
import
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export interface ScoutManifest {
|
|
41
|
-
availableBytes: number;
|
|
42
|
-
availableCount: number;
|
|
43
|
-
groups: ScoutContextGroup[];
|
|
44
|
-
omittedBytes: number;
|
|
45
|
-
omittedCount: number;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export type ScoutManifestResult =
|
|
49
|
-
| { ok: true; manifest: ScoutManifest }
|
|
50
|
-
| {
|
|
51
|
-
ok: false;
|
|
52
|
-
reason: "invalid-protocol" | "required-group-overflow";
|
|
53
|
-
message: string;
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
const isRecord = (value: unknown): value is RecordValue =>
|
|
57
|
-
Boolean(value) && typeof value === "object";
|
|
58
|
-
const byteLength = (value: string) => Buffer.byteLength(value, "utf8");
|
|
59
|
-
const SPEAKER_PREFIX = /^(User|Executor):\s*/;
|
|
60
|
-
const contentParts = (content: unknown): unknown[] =>
|
|
61
|
-
Array.isArray(content) ? content : [];
|
|
62
|
-
const toolCalls = (message: RecordValue) =>
|
|
63
|
-
contentParts(message.content).filter(
|
|
64
|
-
(part): part is RecordValue => isRecord(part) && part.type === "toolCall"
|
|
65
|
-
);
|
|
66
|
-
const toolCallId = (part: RecordValue) =>
|
|
67
|
-
typeof part.id === "string" ? part.id : undefined;
|
|
68
|
-
|
|
69
|
-
const boundedLabel = (value: string) =>
|
|
70
|
-
[...value.replace(/\s+/g, " ").trim()]
|
|
71
|
-
.slice(0, SCOUT_LABEL_MAX_CHARS)
|
|
72
|
-
.join("");
|
|
73
|
-
|
|
74
|
-
const labelFor = (kind: ScoutGroupKind, content: string) => {
|
|
75
|
-
const preview = boundedLabel(content.replace(SPEAKER_PREFIX, ""));
|
|
76
|
-
const prefix: Record<ScoutGroupKind, string> = {
|
|
77
|
-
assistant: "Executor",
|
|
78
|
-
compaction: "Compaction summary",
|
|
79
|
-
"pending-invocation": "Current Advisor invocation",
|
|
80
|
-
"tool-exchange": "Tool exchange",
|
|
81
|
-
user: "User request",
|
|
82
|
-
};
|
|
83
|
-
return boundedLabel(`${prefix[kind]}: ${preview || "(no text)"}`);
|
|
84
|
-
};
|
|
85
|
-
|
|
86
|
-
const stableId = (
|
|
87
|
-
index: number,
|
|
88
|
-
entryIds: string[],
|
|
89
|
-
kind: ScoutGroupKind,
|
|
90
|
-
content: string
|
|
91
|
-
) =>
|
|
92
|
-
`g_${createHash("sha256")
|
|
93
|
-
.update(JSON.stringify([index, entryIds, kind, content]))
|
|
94
|
-
.digest("hex")
|
|
95
|
-
.slice(0, 16)}`;
|
|
96
|
-
|
|
97
|
-
const groupWireBytes = (group: ScoutContextGroup) =>
|
|
98
|
-
byteLength(
|
|
99
|
-
JSON.stringify({
|
|
100
|
-
bytes: group.bytes,
|
|
101
|
-
content: group.content,
|
|
102
|
-
id: group.id,
|
|
103
|
-
kind: group.kind,
|
|
104
|
-
label: group.label,
|
|
105
|
-
required: group.required,
|
|
106
|
-
})
|
|
107
|
-
);
|
|
108
|
-
|
|
109
|
-
const createGroup = (
|
|
110
|
-
originalIndex: number,
|
|
111
|
-
entryIds: string[],
|
|
112
|
-
kind: ScoutGroupKind,
|
|
113
|
-
content: string,
|
|
114
|
-
required: boolean
|
|
115
|
-
): ScoutContextGroup => ({
|
|
116
|
-
bytes: byteLength(content),
|
|
117
|
-
content,
|
|
118
|
-
id: stableId(originalIndex, entryIds, kind, content),
|
|
119
|
-
kind,
|
|
120
|
-
label: labelFor(kind, content),
|
|
121
|
-
originalIndex,
|
|
122
|
-
required,
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
const pendingAdvisorArguments = (value: unknown): RecordValue => {
|
|
126
|
-
if (!isRecord(value)) {
|
|
127
|
-
return {};
|
|
128
|
-
}
|
|
129
|
-
const allowed: RecordValue = {};
|
|
130
|
-
for (const key of ["gitContext", "question"]) {
|
|
131
|
-
if (key in value) {
|
|
132
|
-
allowed[key] = value[key];
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
return allowed;
|
|
136
|
-
};
|
|
137
|
-
|
|
138
|
-
const pendingInvocationDisclosure = (
|
|
139
|
-
entry: RecordValue,
|
|
140
|
-
invocationId: string,
|
|
141
|
-
toolResultMaxLines: number,
|
|
142
|
-
toolResultMaxBytes: number,
|
|
143
|
-
policies: AdvisorToolPolicies,
|
|
144
|
-
redact: boolean,
|
|
145
|
-
disclosed: string
|
|
146
|
-
) => {
|
|
147
|
-
if (!isRecord(entry.message)) {
|
|
148
|
-
return disclosed;
|
|
149
|
-
}
|
|
150
|
-
const content = contentParts(entry.message.content).map((part) => {
|
|
151
|
-
if (
|
|
152
|
-
!isRecord(part) ||
|
|
153
|
-
part.type !== "toolCall" ||
|
|
154
|
-
toolCallId(part) !== invocationId ||
|
|
155
|
-
part.name !== "ask_advisor"
|
|
156
|
-
) {
|
|
157
|
-
return part;
|
|
158
|
-
}
|
|
159
|
-
return { ...part, arguments: pendingAdvisorArguments(part.arguments) };
|
|
160
|
-
});
|
|
161
|
-
return conversationEntry(
|
|
162
|
-
{ ...entry, message: { ...entry.message, content } },
|
|
163
|
-
toolResultMaxLines,
|
|
164
|
-
toolResultMaxBytes,
|
|
165
|
-
policies,
|
|
166
|
-
redact
|
|
167
|
-
);
|
|
168
|
-
};
|
|
169
|
-
|
|
170
|
-
export interface BuildScoutManifestOptions {
|
|
171
|
-
currentInvocationId?: string;
|
|
172
|
-
/** @deprecated Use maxManifestBytes for the Scout transport budget. */
|
|
173
|
-
maxBytes?: number;
|
|
174
|
-
/** Maximum reconstructed Advisor conversation characters. */
|
|
7
|
+
} from "./config/state.ts";
|
|
8
|
+
import {
|
|
9
|
+
buildGroups,
|
|
10
|
+
type DisclosureCaps,
|
|
11
|
+
type GroupPass,
|
|
12
|
+
groupWireBytes,
|
|
13
|
+
} from "./scout-groups.ts";
|
|
14
|
+
import { indexToolCalls } from "./scout-protocol.ts";
|
|
15
|
+
import {
|
|
16
|
+
type BuildScoutManifestOptions,
|
|
17
|
+
SCOUT_GROUP_MAX_BYTES,
|
|
18
|
+
SCOUT_MANIFEST_MAX_BYTES,
|
|
19
|
+
SCOUT_MANIFEST_MAX_GROUPS,
|
|
20
|
+
type ScoutContextGroup,
|
|
21
|
+
type ScoutManifestResult,
|
|
22
|
+
} from "./scout-types.ts";
|
|
23
|
+
|
|
24
|
+
// biome-ignore lint/performance/noBarrelFile: re-export preserves scout-context's historical public import surface.
|
|
25
|
+
export { reconstructScoutConversation } from "./scout-reconstruct.ts";
|
|
26
|
+
export type {
|
|
27
|
+
BuildScoutManifestOptions,
|
|
28
|
+
ScoutManifest,
|
|
29
|
+
ScoutManifestResult,
|
|
30
|
+
} from "./scout-types.ts";
|
|
31
|
+
|
|
32
|
+
export { SCOUT_MANIFEST_MAX_BYTES } from "./scout-types.ts";
|
|
33
|
+
|
|
34
|
+
const resolveCaps = (
|
|
35
|
+
options: BuildScoutManifestOptions
|
|
36
|
+
): DisclosureCaps & {
|
|
175
37
|
maxConversationChars?: number;
|
|
176
|
-
maxGroupBytes
|
|
177
|
-
maxGroups
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
38
|
+
maxGroupBytes: number;
|
|
39
|
+
maxGroups: number;
|
|
40
|
+
maxManifestBytes: number;
|
|
41
|
+
} => ({
|
|
42
|
+
currentInvocationId: options.currentInvocationId,
|
|
43
|
+
maxConversationChars: options.maxConversationChars,
|
|
44
|
+
maxGroupBytes: options.maxGroupBytes ?? SCOUT_GROUP_MAX_BYTES,
|
|
45
|
+
maxGroups: options.maxGroups ?? SCOUT_MANIFEST_MAX_GROUPS,
|
|
46
|
+
maxManifestBytes:
|
|
47
|
+
options.maxManifestBytes ?? options.maxBytes ?? SCOUT_MANIFEST_MAX_BYTES,
|
|
48
|
+
policies: options.policies ?? advisorToolPoliciesRef,
|
|
49
|
+
redact: options.redact ?? advisorRedactSecretsRef,
|
|
50
|
+
toolResultMaxBytes:
|
|
51
|
+
options.toolResultMaxBytes ?? advisorToolResultMaxBytesRef,
|
|
52
|
+
toolResultMaxLines:
|
|
53
|
+
options.toolResultMaxLines ?? advisorToolResultMaxLinesRef,
|
|
54
|
+
});
|
|
185
55
|
|
|
186
56
|
/** Builds disclosed, indivisible history groups from Pi's compaction-aware branch. */
|
|
187
57
|
export const buildScoutManifest = (
|
|
188
58
|
ctx: ExtensionContext,
|
|
189
59
|
options: BuildScoutManifestOptions = {}
|
|
190
|
-
// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: protocol validation keeps fail-open branches visible in one deterministic pass.
|
|
191
60
|
): ScoutManifestResult => {
|
|
192
61
|
const entries = ctx.sessionManager.buildContextEntries();
|
|
193
|
-
const
|
|
194
|
-
const
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
const toolResultMaxBytes =
|
|
198
|
-
options.toolResultMaxBytes ?? advisorToolResultMaxBytesRef;
|
|
199
|
-
const {
|
|
200
|
-
maxBytes,
|
|
201
|
-
maxConversationChars,
|
|
202
|
-
maxGroupBytes = SCOUT_GROUP_MAX_BYTES,
|
|
203
|
-
maxGroups = SCOUT_MANIFEST_MAX_GROUPS,
|
|
204
|
-
maxManifestBytes = maxBytes ?? SCOUT_MANIFEST_MAX_BYTES,
|
|
205
|
-
} = options;
|
|
206
|
-
|
|
207
|
-
let latestUserIndex = -1;
|
|
208
|
-
const callOwners = new Map<string, { index: number; name: string }>();
|
|
209
|
-
const resultsByCall = new Map<
|
|
210
|
-
string,
|
|
211
|
-
Array<{ entry: RecordValue; index: number }>
|
|
212
|
-
>();
|
|
213
|
-
for (let index = 0; index < entries.length; index += 1) {
|
|
214
|
-
const entry = entries[index] as unknown as RecordValue;
|
|
215
|
-
if (entry.type !== "message" || !isRecord(entry.message)) {
|
|
216
|
-
continue;
|
|
217
|
-
}
|
|
218
|
-
if (entry.message.role === "user" && textFrom(entry.message.content)) {
|
|
219
|
-
latestUserIndex = index;
|
|
220
|
-
}
|
|
221
|
-
if (entry.message.role === "assistant") {
|
|
222
|
-
for (const call of toolCalls(entry.message)) {
|
|
223
|
-
const id = toolCallId(call);
|
|
224
|
-
if (!id || callOwners.has(id)) {
|
|
225
|
-
return {
|
|
226
|
-
message: `Assistant tool calls at context entry ${index} have missing or duplicate IDs.`,
|
|
227
|
-
ok: false,
|
|
228
|
-
reason: "invalid-protocol",
|
|
229
|
-
};
|
|
230
|
-
}
|
|
231
|
-
callOwners.set(id, {
|
|
232
|
-
index,
|
|
233
|
-
name: typeof call.name === "string" ? call.name : "unknown",
|
|
234
|
-
});
|
|
235
|
-
}
|
|
236
|
-
} else if (entry.message.role === "toolResult") {
|
|
237
|
-
const id = entry.message.toolCallId;
|
|
238
|
-
if (typeof id !== "string") {
|
|
239
|
-
return {
|
|
240
|
-
message: `Tool result at context entry ${index} has no tool-call ID.`,
|
|
241
|
-
ok: false,
|
|
242
|
-
reason: "invalid-protocol",
|
|
243
|
-
};
|
|
244
|
-
}
|
|
245
|
-
const results = resultsByCall.get(id) ?? [];
|
|
246
|
-
results.push({ entry, index });
|
|
247
|
-
resultsByCall.set(id, results);
|
|
248
|
-
}
|
|
62
|
+
const caps = resolveCaps(options);
|
|
63
|
+
const indexed = indexToolCalls(entries);
|
|
64
|
+
if (!indexed.ok) {
|
|
65
|
+
return indexed;
|
|
249
66
|
}
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
message: `Tool call ${id} has duplicate result messages.`,
|
|
254
|
-
ok: false,
|
|
255
|
-
reason: "invalid-protocol",
|
|
256
|
-
};
|
|
257
|
-
}
|
|
67
|
+
const built = buildGroups(entries, indexed.index, caps);
|
|
68
|
+
if (!built.ok) {
|
|
69
|
+
return built;
|
|
258
70
|
}
|
|
71
|
+
return fitToBudget(built, caps);
|
|
72
|
+
};
|
|
259
73
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
entry,
|
|
268
|
-
toolResultMaxLines,
|
|
269
|
-
toolResultMaxBytes,
|
|
270
|
-
policies,
|
|
271
|
-
redact
|
|
272
|
-
);
|
|
273
|
-
if (!disclosed) {
|
|
274
|
-
continue;
|
|
275
|
-
}
|
|
276
|
-
const entryId = typeof entry.id === "string" ? entry.id : String(index);
|
|
277
|
-
if (entry.type !== "message" || !isRecord(entry.message)) {
|
|
278
|
-
groups.push(
|
|
279
|
-
createGroup(index, [entryId], "compaction", disclosed, false)
|
|
280
|
-
);
|
|
281
|
-
continue;
|
|
282
|
-
}
|
|
283
|
-
const { message } = entry;
|
|
284
|
-
if (message.role === "user") {
|
|
285
|
-
groups.push(
|
|
286
|
-
createGroup(
|
|
287
|
-
index,
|
|
288
|
-
[entryId],
|
|
289
|
-
"user",
|
|
290
|
-
disclosed,
|
|
291
|
-
index === latestUserIndex
|
|
292
|
-
)
|
|
293
|
-
);
|
|
294
|
-
continue;
|
|
295
|
-
}
|
|
296
|
-
if (message.role === "toolResult") {
|
|
297
|
-
if (consumedResultIndexes.has(index)) {
|
|
298
|
-
continue;
|
|
299
|
-
}
|
|
300
|
-
const resultId = message.toolCallId;
|
|
301
|
-
const owner =
|
|
302
|
-
typeof resultId === "string" ? callOwners.get(resultId) : undefined;
|
|
303
|
-
if (owner) {
|
|
304
|
-
return {
|
|
305
|
-
message: `Tool result at context entry ${index} precedes or conflicts with its retained call.`,
|
|
306
|
-
ok: false,
|
|
307
|
-
reason: "invalid-protocol",
|
|
308
|
-
};
|
|
309
|
-
}
|
|
310
|
-
// Compaction or interrupted history can retain a result without its call.
|
|
311
|
-
// It is unavailable optional evidence and is never offered to Scout.
|
|
312
|
-
protocolOmittedCount += 1;
|
|
313
|
-
protocolOmittedBytes += byteLength(disclosed);
|
|
314
|
-
continue;
|
|
315
|
-
}
|
|
316
|
-
if (message.role !== "assistant") {
|
|
317
|
-
continue;
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
const calls = toolCalls(message);
|
|
321
|
-
if (calls.length === 0) {
|
|
322
|
-
groups.push(createGroup(index, [entryId], "assistant", disclosed, false));
|
|
323
|
-
continue;
|
|
324
|
-
}
|
|
325
|
-
const callIds = calls.map(toolCallId) as string[];
|
|
326
|
-
const immediate = entries[index + 1] as unknown as RecordValue | undefined;
|
|
327
|
-
if (
|
|
328
|
-
immediate?.type === "message" &&
|
|
329
|
-
isRecord(immediate.message) &&
|
|
330
|
-
immediate.message.role === "toolResult" &&
|
|
331
|
-
typeof immediate.message.toolCallId === "string" &&
|
|
332
|
-
!callIds.includes(immediate.message.toolCallId) &&
|
|
333
|
-
!callOwners.has(immediate.message.toolCallId)
|
|
334
|
-
) {
|
|
335
|
-
return {
|
|
336
|
-
message: `Tool result at context entry ${index + 1} does not match its adjacent assistant group.`,
|
|
337
|
-
ok: false,
|
|
338
|
-
reason: "invalid-protocol",
|
|
339
|
-
};
|
|
340
|
-
}
|
|
341
|
-
const missing = new Set<string>();
|
|
342
|
-
const resultParts: string[] = [];
|
|
343
|
-
const resultEntryIds: string[] = [];
|
|
344
|
-
for (const [callIndex, callId] of callIds.entries()) {
|
|
345
|
-
const resultMatch = resultsByCall.get(callId)?.[0];
|
|
346
|
-
if (!resultMatch) {
|
|
347
|
-
missing.add(callId);
|
|
348
|
-
continue;
|
|
349
|
-
}
|
|
350
|
-
if (resultMatch.index <= index) {
|
|
351
|
-
return {
|
|
352
|
-
message: `Tool result at context entry ${resultMatch.index} precedes its assistant call.`,
|
|
353
|
-
ok: false,
|
|
354
|
-
reason: "invalid-protocol",
|
|
355
|
-
};
|
|
356
|
-
}
|
|
357
|
-
const resultMessage = resultMatch.entry.message;
|
|
358
|
-
const expectedName =
|
|
359
|
-
typeof calls[callIndex].name === "string"
|
|
360
|
-
? calls[callIndex].name
|
|
361
|
-
: "unknown";
|
|
362
|
-
if (!isRecord(resultMessage) || resultMessage.toolName !== expectedName) {
|
|
363
|
-
return {
|
|
364
|
-
message: `Tool result at context entry ${resultMatch.index} conflicts with call ${callId}.`,
|
|
365
|
-
ok: false,
|
|
366
|
-
reason: "invalid-protocol",
|
|
367
|
-
};
|
|
368
|
-
}
|
|
369
|
-
consumedResultIndexes.add(resultMatch.index);
|
|
370
|
-
const resultText = conversationEntry(
|
|
371
|
-
resultMatch.entry,
|
|
372
|
-
toolResultMaxLines,
|
|
373
|
-
toolResultMaxBytes,
|
|
374
|
-
policies,
|
|
375
|
-
redact
|
|
376
|
-
);
|
|
377
|
-
if (resultText) {
|
|
378
|
-
resultParts.push(resultText);
|
|
379
|
-
}
|
|
380
|
-
resultEntryIds.push(
|
|
381
|
-
typeof resultMatch.entry.id === "string"
|
|
382
|
-
? resultMatch.entry.id
|
|
383
|
-
: String(resultMatch.index)
|
|
384
|
-
);
|
|
385
|
-
}
|
|
386
|
-
if (missing.size > 0) {
|
|
387
|
-
const { currentInvocationId } = options;
|
|
388
|
-
const pendingCurrentInvocation =
|
|
389
|
-
currentInvocationId !== undefined &&
|
|
390
|
-
missing.size === 1 &&
|
|
391
|
-
missing.has(currentInvocationId) &&
|
|
392
|
-
callIds.includes(currentInvocationId);
|
|
393
|
-
if (!pendingCurrentInvocation) {
|
|
394
|
-
protocolOmittedCount += 1;
|
|
395
|
-
protocolOmittedBytes += byteLength(
|
|
396
|
-
[disclosed, ...resultParts].join("\n\n")
|
|
397
|
-
);
|
|
398
|
-
continue;
|
|
399
|
-
}
|
|
400
|
-
const pendingDisclosed = pendingInvocationDisclosure(
|
|
401
|
-
entry,
|
|
402
|
-
currentInvocationId,
|
|
403
|
-
toolResultMaxLines,
|
|
404
|
-
toolResultMaxBytes,
|
|
405
|
-
policies,
|
|
406
|
-
redact,
|
|
407
|
-
disclosed
|
|
408
|
-
);
|
|
409
|
-
groups.push(
|
|
410
|
-
createGroup(
|
|
411
|
-
index,
|
|
412
|
-
[entryId, ...resultEntryIds],
|
|
413
|
-
"pending-invocation",
|
|
414
|
-
[pendingDisclosed ?? disclosed, ...resultParts].join("\n\n"),
|
|
415
|
-
true
|
|
416
|
-
)
|
|
417
|
-
);
|
|
418
|
-
continue;
|
|
419
|
-
}
|
|
420
|
-
groups.push(
|
|
421
|
-
createGroup(
|
|
422
|
-
index,
|
|
423
|
-
[entryId, ...resultEntryIds],
|
|
424
|
-
"tool-exchange",
|
|
425
|
-
[disclosed, ...resultParts].join("\n\n"),
|
|
426
|
-
false
|
|
427
|
-
)
|
|
428
|
-
);
|
|
74
|
+
const fits = (
|
|
75
|
+
selected: ScoutContextGroup[],
|
|
76
|
+
caps: {
|
|
77
|
+
maxConversationChars?: number;
|
|
78
|
+
maxGroupBytes: number;
|
|
79
|
+
maxGroups: number;
|
|
80
|
+
maxManifestBytes: number;
|
|
429
81
|
}
|
|
430
|
-
|
|
82
|
+
) =>
|
|
83
|
+
selected.length <= caps.maxGroups &&
|
|
84
|
+
selected.reduce((sum, group) => sum + groupWireBytes(group), 0) <=
|
|
85
|
+
caps.maxManifestBytes &&
|
|
86
|
+
(caps.maxConversationChars === undefined ||
|
|
87
|
+
contentChars(selected) <= caps.maxConversationChars);
|
|
88
|
+
|
|
89
|
+
const contentChars = (items: ScoutContextGroup[]) =>
|
|
90
|
+
items.reduce((sum, group) => sum + group.content.length, 0) +
|
|
91
|
+
Math.max(0, items.length - 1) * 2;
|
|
92
|
+
|
|
93
|
+
const fitToBudget = (
|
|
94
|
+
built: GroupPass,
|
|
95
|
+
caps: {
|
|
96
|
+
maxConversationChars?: number;
|
|
97
|
+
maxGroupBytes: number;
|
|
98
|
+
maxGroups: number;
|
|
99
|
+
maxManifestBytes: number;
|
|
100
|
+
}
|
|
101
|
+
): ScoutManifestResult => {
|
|
102
|
+
const { groups, protocolOmittedBytes, protocolOmittedCount } = built;
|
|
431
103
|
const availableCount = groups.length + protocolOmittedCount;
|
|
432
104
|
const availableBytes =
|
|
433
105
|
groups.reduce((sum, group) => sum + groupWireBytes(group), 0) +
|
|
434
106
|
protocolOmittedBytes;
|
|
435
|
-
if (maxManifestBytes <= 0) {
|
|
107
|
+
if (caps.maxManifestBytes <= 0) {
|
|
436
108
|
return {
|
|
437
109
|
manifest: {
|
|
438
110
|
availableBytes: 0,
|
|
@@ -445,43 +117,14 @@ export const buildScoutManifest = (
|
|
|
445
117
|
};
|
|
446
118
|
}
|
|
447
119
|
const required = groups.filter((group) => group.required);
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
required.reduce((sum, group) => sum + groupWireBytes(group), 0) >
|
|
452
|
-
maxManifestBytes
|
|
453
|
-
) {
|
|
454
|
-
return {
|
|
455
|
-
message:
|
|
456
|
-
"Required Scout context exceeds the Scout manifest transport limit.",
|
|
457
|
-
ok: false,
|
|
458
|
-
reason: "required-group-overflow",
|
|
459
|
-
};
|
|
460
|
-
}
|
|
461
|
-
const contentChars = (items: ScoutContextGroup[]) =>
|
|
462
|
-
items.reduce((sum, group) => sum + group.content.length, 0) +
|
|
463
|
-
Math.max(0, items.length - 1) * 2;
|
|
464
|
-
if (
|
|
465
|
-
maxConversationChars !== undefined &&
|
|
466
|
-
contentChars(required) > maxConversationChars
|
|
467
|
-
) {
|
|
468
|
-
return {
|
|
469
|
-
message:
|
|
470
|
-
"Required Scout context exceeds the Advisor conversation budget.",
|
|
471
|
-
ok: false,
|
|
472
|
-
reason: "required-group-overflow",
|
|
473
|
-
};
|
|
120
|
+
const overflow = requiredOverflow(required, caps);
|
|
121
|
+
if (overflow) {
|
|
122
|
+
return overflow;
|
|
474
123
|
}
|
|
475
124
|
const selected = groups.filter(
|
|
476
|
-
(group) => group.required || group.bytes <= maxGroupBytes
|
|
125
|
+
(group) => group.required || group.bytes <= caps.maxGroupBytes
|
|
477
126
|
);
|
|
478
|
-
|
|
479
|
-
selected.length <= maxGroups &&
|
|
480
|
-
selected.reduce((sum, group) => sum + groupWireBytes(group), 0) <=
|
|
481
|
-
maxManifestBytes &&
|
|
482
|
-
(maxConversationChars === undefined ||
|
|
483
|
-
contentChars(selected) <= maxConversationChars);
|
|
484
|
-
while (!fits()) {
|
|
127
|
+
while (!fits(selected, caps)) {
|
|
485
128
|
const optionalIndex = selected.findIndex((group) => !group.required);
|
|
486
129
|
if (optionalIndex < 0) {
|
|
487
130
|
return {
|
|
@@ -508,45 +151,38 @@ export const buildScoutManifest = (
|
|
|
508
151
|
};
|
|
509
152
|
};
|
|
510
153
|
|
|
511
|
-
const
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
}
|
|
519
|
-
return result;
|
|
520
|
-
};
|
|
521
|
-
|
|
522
|
-
export const reconstructScoutConversation = (
|
|
523
|
-
manifest: ScoutManifest,
|
|
524
|
-
selectedIds: string[],
|
|
525
|
-
synthesis?: string,
|
|
526
|
-
maxChars = Number.MAX_SAFE_INTEGER
|
|
527
|
-
): string => {
|
|
528
|
-
const selected = new Set(selectedIds);
|
|
529
|
-
const evidence = manifest.groups
|
|
530
|
-
.filter((group) => group.required || selected.has(group.id))
|
|
531
|
-
.sort((left, right) => left.originalIndex - right.originalIndex)
|
|
532
|
-
.map((group) => group.content);
|
|
533
|
-
const evidenceText = evidence.join("\n\n");
|
|
534
|
-
if (maxChars <= 0) {
|
|
535
|
-
return "";
|
|
154
|
+
const requiredOverflow = (
|
|
155
|
+
required: ScoutContextGroup[],
|
|
156
|
+
caps: {
|
|
157
|
+
maxConversationChars?: number;
|
|
158
|
+
maxGroupBytes: number;
|
|
159
|
+
maxGroups: number;
|
|
160
|
+
maxManifestBytes: number;
|
|
536
161
|
}
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
return
|
|
162
|
+
): ScoutManifestResult | undefined => {
|
|
163
|
+
if (
|
|
164
|
+
required.some((group) => group.bytes > caps.maxGroupBytes) ||
|
|
165
|
+
required.length > caps.maxGroups ||
|
|
166
|
+
required.reduce((sum, group) => sum + groupWireBytes(group), 0) >
|
|
167
|
+
caps.maxManifestBytes
|
|
168
|
+
) {
|
|
169
|
+
return {
|
|
170
|
+
message:
|
|
171
|
+
"Required Scout context exceeds the Scout manifest transport limit.",
|
|
172
|
+
ok: false,
|
|
173
|
+
reason: "required-group-overflow",
|
|
174
|
+
};
|
|
545
175
|
}
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
176
|
+
if (
|
|
177
|
+
caps.maxConversationChars !== undefined &&
|
|
178
|
+
contentChars(required) > caps.maxConversationChars
|
|
179
|
+
) {
|
|
180
|
+
return {
|
|
181
|
+
message:
|
|
182
|
+
"Required Scout context exceeds the Advisor conversation budget.",
|
|
183
|
+
ok: false,
|
|
184
|
+
reason: "required-group-overflow",
|
|
185
|
+
};
|
|
550
186
|
}
|
|
551
|
-
return
|
|
187
|
+
return undefined;
|
|
552
188
|
};
|