pi-advisor-flow 0.5.6 → 0.7.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 +29 -0
- package/README.md +1 -0
- package/dist/index.js +3160 -1255
- package/extensions/index.ts +9 -9
- package/package.json +12 -8
- 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 +24 -10
- 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 +52 -4
- package/src/commands/types.ts +6 -13
- package/src/commands.ts +2 -2
- package/src/config/args.ts +3 -3
- package/src/config/defaults.ts +78 -3
- package/src/config/schema.ts +434 -0
- package/src/config/state.ts +71 -2
- package/src/config/storage.ts +27 -119
- package/src/config/types.ts +30 -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/jev/client.ts +348 -0
- package/src/jev/key-store.ts +252 -0
- package/src/jev/ledger.ts +234 -0
- package/src/jev/questions.ts +119 -0
- package/src/jev/state.ts +47 -0
- package/src/jev/transport.ts +58 -0
- package/src/model-stream.ts +5 -8
- package/src/outcomes.ts +3 -2
- 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 +215 -78
- 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/jev-filter.ts +202 -0
- package/src/tools/jev-turn-gate.ts +183 -0
- package/src/tools/loop-gate.ts +71 -61
- package/src/tools/outage-notifier.ts +30 -0
- package/src/tools/prompts.ts +4 -7
- package/src/tools/register-ask-advisor.ts +46 -15
- package/src/tools/register-lifecycle.ts +7 -7
- package/src/tools/register-outcome.ts +5 -5
- package/src/tools/register-renderers.ts +63 -5
- package/src/tools/registration.ts +27 -10
- package/src/tools/render-advisor-result.ts +54 -22
- 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 +17 -7
- package/src/tools.ts +9 -9
- package/src/ui/jev-setup-submenu.ts +344 -0
- package/src/ui/manual-dialog-render.ts +2 -2
- package/src/ui/manual-dialog.ts +4 -9
- package/src/ui/masked-input.ts +68 -0
- package/src/ui/model-selector.ts +1 -1
- package/src/ui/settings-formatting.ts +2 -2
- package/src/ui/settings-items.ts +166 -5
- package/src/ui/settings-mutations.ts +37 -2
- package/src/ui/settings-selector.ts +5 -5
- package/src/ui/text-setting-submenu.ts +1 -1
- package/src/ui/types.ts +13 -2
- package/src/ui.ts +4 -4
- package/src/usage.ts +4 -7
package/src/session-state.ts
CHANGED
|
@@ -1,19 +1,20 @@
|
|
|
1
|
+
import { AdvisorJevLedgerState, type AdvisorJevUsage } from "./jev/ledger.ts";
|
|
1
2
|
import {
|
|
2
3
|
type AdvisorUsageTotals,
|
|
3
4
|
addAdvisorUsage,
|
|
4
5
|
emptyAdvisorUsageTotals,
|
|
5
6
|
formatAdvisorUsageStatus,
|
|
6
7
|
formatAdvisorUsageTotals,
|
|
7
|
-
} from "./usage.
|
|
8
|
+
} from "./usage.ts";
|
|
8
9
|
|
|
9
10
|
export type GateDecision = "proceed" | "revise" | "blocked";
|
|
10
|
-
export type ConsultationTrigger = "manual" | "executor-requested";
|
|
11
|
+
export type ConsultationTrigger = "manual" | "executor-requested" | "turn-gate";
|
|
11
12
|
export type GateTrigger =
|
|
12
13
|
| "repeated-tool-call"
|
|
13
14
|
| "completion-review"
|
|
14
15
|
| "custom-rule";
|
|
15
|
-
|
|
16
|
-
|
|
16
|
+
type AdvisorTrigger = ConsultationTrigger | GateTrigger;
|
|
17
|
+
type ExecutionEffect = "continued" | "tool-blocked" | "session-blocked";
|
|
17
18
|
|
|
18
19
|
export interface AdvisorInvocationRecord {
|
|
19
20
|
cost?: number;
|
|
@@ -113,46 +114,77 @@ export const normalizeToolInput = (
|
|
|
113
114
|
export const normalizedToolSignature = (toolName: string, input: unknown) =>
|
|
114
115
|
`${toolName}:${JSON.stringify(normalizeToolInput(toolName, input))}`;
|
|
115
116
|
|
|
117
|
+
interface RepetitionState {
|
|
118
|
+
count: number;
|
|
119
|
+
interventions: number;
|
|
120
|
+
previousSignature?: string;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
interface AdviceLedger {
|
|
124
|
+
draftConsultations: number;
|
|
125
|
+
issued: Map<
|
|
126
|
+
string,
|
|
127
|
+
{
|
|
128
|
+
advice: string;
|
|
129
|
+
normalizedQuestion?: string;
|
|
130
|
+
trigger: ConsultationTrigger;
|
|
131
|
+
}
|
|
132
|
+
>;
|
|
133
|
+
lastAdvice?: string;
|
|
134
|
+
outcomes: number;
|
|
135
|
+
pending: Set<string>;
|
|
136
|
+
reported: Set<string>;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
interface UsageState {
|
|
140
|
+
invocations: AdvisorInvocationRecord[];
|
|
141
|
+
totals: AdvisorUsageTotals;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const freshRepetition = (): RepetitionState => ({
|
|
145
|
+
count: 0,
|
|
146
|
+
interventions: 0,
|
|
147
|
+
});
|
|
148
|
+
const freshAdviceLedger = (): AdviceLedger => ({
|
|
149
|
+
draftConsultations: 0,
|
|
150
|
+
issued: new Map(),
|
|
151
|
+
outcomes: 0,
|
|
152
|
+
pending: new Set(),
|
|
153
|
+
reported: new Set(),
|
|
154
|
+
});
|
|
155
|
+
const freshUsage = (): UsageState => ({
|
|
156
|
+
invocations: [],
|
|
157
|
+
totals: emptyAdvisorUsageTotals(),
|
|
158
|
+
});
|
|
159
|
+
|
|
116
160
|
export class AdvisorSessionState {
|
|
117
|
-
#
|
|
118
|
-
#repetitions = 0;
|
|
161
|
+
#repetition = freshRepetition();
|
|
119
162
|
#blockedReason?: string;
|
|
120
|
-
#
|
|
121
|
-
#
|
|
163
|
+
#ledger = freshAdviceLedger();
|
|
164
|
+
#usage = freshUsage();
|
|
122
165
|
#consumedCalls = 0;
|
|
123
|
-
readonly #
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
>();
|
|
127
|
-
readonly #pendingAdvice = new Set<string>();
|
|
128
|
-
readonly #reportedAdvice = new Set<string>();
|
|
129
|
-
#draftConsultations = 0;
|
|
130
|
-
#outcomes = 0;
|
|
131
|
-
#lastAdvice?: string;
|
|
132
|
-
#usage = emptyAdvisorUsageTotals();
|
|
166
|
+
readonly #jev = new AdvisorJevLedgerState();
|
|
167
|
+
#sessionTurnOrdinal = 0;
|
|
168
|
+
#turnsSinceConsultation = 0;
|
|
133
169
|
|
|
134
170
|
resetTask() {
|
|
135
|
-
this.#
|
|
136
|
-
this.#repetitions = 0;
|
|
171
|
+
this.#repetition = freshRepetition();
|
|
137
172
|
this.#blockedReason = undefined;
|
|
138
|
-
this.#
|
|
139
|
-
this.#
|
|
173
|
+
this.#ledger = freshAdviceLedger();
|
|
174
|
+
this.#usage = freshUsage();
|
|
140
175
|
this.#consumedCalls = 0;
|
|
141
|
-
this.#
|
|
142
|
-
this.#
|
|
143
|
-
this.#
|
|
144
|
-
this.#draftConsultations = 0;
|
|
145
|
-
this.#outcomes = 0;
|
|
146
|
-
this.#lastAdvice = undefined;
|
|
147
|
-
this.#usage = emptyAdvisorUsageTotals();
|
|
176
|
+
this.#jev.reset();
|
|
177
|
+
this.#sessionTurnOrdinal = 0;
|
|
178
|
+
this.#turnsSinceConsultation = 0;
|
|
148
179
|
}
|
|
149
180
|
|
|
150
181
|
clearBlocked() {
|
|
151
182
|
this.#blockedReason = undefined;
|
|
152
183
|
}
|
|
153
184
|
resetRepetition() {
|
|
154
|
-
this
|
|
155
|
-
this.#
|
|
185
|
+
// Cumulative interventions feed the session summary and must survive this reset.
|
|
186
|
+
this.#repetition.count = 0;
|
|
187
|
+
this.#repetition.previousSignature = undefined;
|
|
156
188
|
}
|
|
157
189
|
get blocked() {
|
|
158
190
|
return this.#blockedReason !== undefined;
|
|
@@ -169,13 +201,15 @@ export class AdvisorSessionState {
|
|
|
169
201
|
return false;
|
|
170
202
|
}
|
|
171
203
|
const signature = normalizedToolSignature(toolName, input);
|
|
172
|
-
this.#
|
|
173
|
-
signature === this.#previousSignature
|
|
174
|
-
|
|
175
|
-
|
|
204
|
+
this.#repetition.count =
|
|
205
|
+
signature === this.#repetition.previousSignature
|
|
206
|
+
? this.#repetition.count + 1
|
|
207
|
+
: 1;
|
|
208
|
+
this.#repetition.previousSignature = signature;
|
|
209
|
+
if (this.#repetition.count < threshold) {
|
|
176
210
|
return false;
|
|
177
211
|
}
|
|
178
|
-
this.#
|
|
212
|
+
this.#repetition.interventions += 1;
|
|
179
213
|
return true;
|
|
180
214
|
}
|
|
181
215
|
|
|
@@ -194,34 +228,75 @@ export class AdvisorSessionState {
|
|
|
194
228
|
return this.#consumedCalls;
|
|
195
229
|
}
|
|
196
230
|
|
|
231
|
+
/** Counts one completed executor turn on both turn counters. The ordinal
|
|
232
|
+
* feeds the filter override window and summary positions and is never reset
|
|
233
|
+
* by anything; turnsSinceConsultation resets on every consultation. */
|
|
234
|
+
recordCompletedTurn() {
|
|
235
|
+
this.#sessionTurnOrdinal += 1;
|
|
236
|
+
this.#turnsSinceConsultation += 1;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
resetTurnsSinceConsultation() {
|
|
240
|
+
this.#turnsSinceConsultation = 0;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
get sessionTurnOrdinal() {
|
|
244
|
+
return this.#sessionTurnOrdinal;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
get turnsSinceConsultation() {
|
|
248
|
+
return this.#turnsSinceConsultation;
|
|
249
|
+
}
|
|
250
|
+
|
|
197
251
|
/** Returns a copy of cumulative direct Advisor usage for this session. */
|
|
198
252
|
get usageTotals(): AdvisorUsageTotals {
|
|
199
|
-
return { ...this.#usage };
|
|
253
|
+
return { ...this.#usage.totals };
|
|
200
254
|
}
|
|
201
255
|
|
|
202
256
|
/** Returns the footer-ready direct Advisor usage status for this session. */
|
|
203
257
|
usageStatus() {
|
|
204
|
-
return formatAdvisorUsageStatus(this.#usage);
|
|
258
|
+
return formatAdvisorUsageStatus(this.#usage.totals);
|
|
205
259
|
}
|
|
206
260
|
|
|
207
261
|
recordInvocation(record: AdvisorInvocationRecord) {
|
|
208
|
-
this.#invocations.push(record);
|
|
209
|
-
addAdvisorUsage(this.#usage, record.usage);
|
|
262
|
+
this.#usage.invocations.push(record);
|
|
263
|
+
addAdvisorUsage(this.#usage.totals, record.usage);
|
|
210
264
|
}
|
|
211
265
|
issueAdvice(
|
|
212
266
|
id: string,
|
|
213
267
|
advice: string,
|
|
214
268
|
trigger: ConsultationTrigger,
|
|
215
|
-
draft = false
|
|
269
|
+
draft = false,
|
|
270
|
+
normalizedQuestion?: string
|
|
216
271
|
) {
|
|
217
|
-
this.#
|
|
218
|
-
|
|
272
|
+
this.#ledger.issued.set(id, {
|
|
273
|
+
advice,
|
|
274
|
+
...(normalizedQuestion ? { normalizedQuestion } : {}),
|
|
275
|
+
trigger,
|
|
276
|
+
});
|
|
277
|
+
this.#ledger.lastAdvice = advice;
|
|
219
278
|
if (draft) {
|
|
220
|
-
this.#draftConsultations += 1;
|
|
279
|
+
this.#ledger.draftConsultations += 1;
|
|
221
280
|
}
|
|
222
281
|
}
|
|
282
|
+
|
|
283
|
+
/** Returns earlier advice for an exactly-matching normalized question. */
|
|
284
|
+
reattachedAdviceFor(
|
|
285
|
+
normalizedQuestion: string | undefined
|
|
286
|
+
): string | undefined {
|
|
287
|
+
if (!normalizedQuestion) {
|
|
288
|
+
return undefined;
|
|
289
|
+
}
|
|
290
|
+
for (const entry of this.#ledger.issued.values()) {
|
|
291
|
+
if (entry.normalizedQuestion === normalizedQuestion) {
|
|
292
|
+
return entry.advice;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
return undefined;
|
|
296
|
+
}
|
|
223
297
|
claimTrackedFiles(paths: string[]) {
|
|
224
|
-
|
|
298
|
+
const advice = this.#ledger.lastAdvice;
|
|
299
|
+
if (!advice || paths.length === 0) {
|
|
225
300
|
return false;
|
|
226
301
|
}
|
|
227
302
|
const mentioned = paths.every((path) => {
|
|
@@ -230,38 +305,38 @@ export class AdvisorSessionState {
|
|
|
230
305
|
"(^|[\\s\\\"'`()\\[])" +
|
|
231
306
|
escaped +
|
|
232
307
|
"(?=$|[\\s\\\"'`),;:!?\\]]|\\.(?=\\s|$))";
|
|
233
|
-
return new RegExp(boundary).test(
|
|
308
|
+
return new RegExp(boundary).test(advice);
|
|
234
309
|
});
|
|
235
310
|
if (!mentioned) {
|
|
236
311
|
return false;
|
|
237
312
|
}
|
|
238
|
-
this.#lastAdvice = undefined;
|
|
313
|
+
this.#ledger.lastAdvice = undefined;
|
|
239
314
|
return true;
|
|
240
315
|
}
|
|
241
316
|
|
|
242
317
|
reserveAdvice(id: string) {
|
|
243
|
-
if (this.#
|
|
318
|
+
if (this.#ledger.reported.has(id) || this.#ledger.pending.has(id)) {
|
|
244
319
|
return;
|
|
245
320
|
}
|
|
246
|
-
const advice = this.#
|
|
321
|
+
const advice = this.#ledger.issued.get(id);
|
|
247
322
|
if (!advice) {
|
|
248
323
|
return;
|
|
249
324
|
}
|
|
250
|
-
this.#
|
|
325
|
+
this.#ledger.pending.add(id);
|
|
251
326
|
return advice;
|
|
252
327
|
}
|
|
253
328
|
|
|
254
329
|
commitAdvice(id: string) {
|
|
255
|
-
if (!this.#
|
|
330
|
+
if (!this.#ledger.pending.delete(id)) {
|
|
256
331
|
return false;
|
|
257
332
|
}
|
|
258
|
-
this.#
|
|
259
|
-
this.#outcomes += 1;
|
|
333
|
+
this.#ledger.reported.add(id);
|
|
334
|
+
this.#ledger.outcomes += 1;
|
|
260
335
|
return true;
|
|
261
336
|
}
|
|
262
337
|
|
|
263
338
|
releaseAdvice(id: string) {
|
|
264
|
-
this.#
|
|
339
|
+
this.#ledger.pending.delete(id);
|
|
265
340
|
}
|
|
266
341
|
|
|
267
342
|
/** Compatibility helper for synchronous callers that can commit immediately. */
|
|
@@ -274,17 +349,11 @@ export class AdvisorSessionState {
|
|
|
274
349
|
return advice;
|
|
275
350
|
}
|
|
276
351
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
}
|
|
281
|
-
const markdown = this.#invocations.filter(
|
|
282
|
-
(item) => item.kind === "markdown"
|
|
352
|
+
#decisionsLine() {
|
|
353
|
+
const gates = this.#usage.invocations.filter(
|
|
354
|
+
(item) => item.kind === "gate"
|
|
283
355
|
);
|
|
284
|
-
|
|
285
|
-
const countTrigger = (trigger: AdvisorTrigger) =>
|
|
286
|
-
this.#invocations.filter((item) => item.trigger === trigger).length;
|
|
287
|
-
const decisions =
|
|
356
|
+
return (
|
|
288
357
|
(["proceed", "revise", "blocked"] as GateDecision[])
|
|
289
358
|
.map(
|
|
290
359
|
(decision) =>
|
|
@@ -295,34 +364,102 @@ export class AdvisorSessionState {
|
|
|
295
364
|
)
|
|
296
365
|
.filter(([, count]) => count > 0)
|
|
297
366
|
.map(([decision, count]) => `${count} ${decision}`)
|
|
298
|
-
.join(", ") || "none"
|
|
367
|
+
.join(", ") || "none"
|
|
368
|
+
);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
#countTrigger(trigger: AdvisorTrigger) {
|
|
372
|
+
return this.#usage.invocations.filter((item) => item.trigger === trigger)
|
|
373
|
+
.length;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
#triggersLine() {
|
|
377
|
+
return (
|
|
378
|
+
[
|
|
379
|
+
"manual",
|
|
380
|
+
"executor-requested",
|
|
381
|
+
"turn-gate",
|
|
382
|
+
"repeated-tool-call",
|
|
383
|
+
"completion-review",
|
|
384
|
+
"custom-rule",
|
|
385
|
+
]
|
|
386
|
+
.filter((trigger) => this.#countTrigger(trigger as AdvisorTrigger) > 0)
|
|
387
|
+
.join(", ") || "none"
|
|
388
|
+
);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
recordJevFilterAllowed() {
|
|
392
|
+
this.#jev.recordFilterAllowed();
|
|
393
|
+
}
|
|
394
|
+
recordJevFilterSkipped(repeat: boolean, normalizedQuestion?: string) {
|
|
395
|
+
this.#jev.recordFilterSkipped(
|
|
396
|
+
repeat,
|
|
397
|
+
normalizedQuestion,
|
|
398
|
+
this.#sessionTurnOrdinal
|
|
399
|
+
);
|
|
400
|
+
}
|
|
401
|
+
get lastJevSkip() {
|
|
402
|
+
return this.#jev.lastSkip;
|
|
403
|
+
}
|
|
404
|
+
recordJevFilterOverride() {
|
|
405
|
+
this.#jev.recordFilterOverride();
|
|
406
|
+
}
|
|
407
|
+
recordJevFilterFailure() {
|
|
408
|
+
this.#jev.recordFilterFailure();
|
|
409
|
+
}
|
|
410
|
+
recordJevFilterUsage(usage: AdvisorJevUsage) {
|
|
411
|
+
this.#jev.recordFilterUsage(usage);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
recordJevGateCheck(usage?: AdvisorJevUsage) {
|
|
415
|
+
this.#jev.recordGateCheck(usage);
|
|
416
|
+
}
|
|
417
|
+
recordJevGateConsultation() {
|
|
418
|
+
this.#jev.recordGateConsultation();
|
|
419
|
+
}
|
|
420
|
+
recordJevGateFailure() {
|
|
421
|
+
this.#jev.recordGateFailure();
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
summary(limit: number | undefined) {
|
|
425
|
+
const { invocations, totals } = this.#usage;
|
|
426
|
+
const jevLines = this.#jev.summaryLines(invocations);
|
|
427
|
+
if (
|
|
428
|
+
invocations.length === 0 &&
|
|
429
|
+
this.#repetition.interventions === 0 &&
|
|
430
|
+
jevLines.length === 0
|
|
431
|
+
) {
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
const markdown = invocations.filter((item) => item.kind === "markdown");
|
|
435
|
+
const gates = invocations.filter((item) => item.kind === "gate");
|
|
299
436
|
const effects = (effect: ExecutionEffect) =>
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
const failures = this.#invocations
|
|
437
|
+
invocations.filter((item) => item.executionEffect === effect).length;
|
|
438
|
+
const failures = invocations
|
|
303
439
|
.filter((item) => item.failure)
|
|
304
440
|
.map((item) => item.failure);
|
|
305
441
|
const models =
|
|
306
|
-
[
|
|
307
|
-
|
|
308
|
-
|
|
442
|
+
[...new Set(invocations.map((item) => item.model).filter(Boolean))].join(
|
|
443
|
+
", "
|
|
444
|
+
) || "unknown";
|
|
309
445
|
const budget =
|
|
310
446
|
limit === undefined
|
|
311
447
|
? `${this.#consumedCalls} used; unlimited remaining`
|
|
312
448
|
: `${this.#consumedCalls} / ${limit} used; ${Math.max(0, limit - this.#consumedCalls)} remaining`;
|
|
313
449
|
return [
|
|
314
450
|
"[Session Advisor Summary]",
|
|
315
|
-
`Consultations: ${markdown.length} Markdown (${countTrigger("manual")} manual, ${countTrigger("executor-requested")} executor-requested), automatic gates: ${gates.length}`,
|
|
316
|
-
`Triggers: ${
|
|
451
|
+
`Consultations: ${markdown.length} Markdown (${this.#countTrigger("manual")} manual, ${this.#countTrigger("executor-requested")} executor-requested), automatic gates: ${gates.length}`,
|
|
452
|
+
`Triggers: ${this.#triggersLine()}`,
|
|
317
453
|
`Models: ${models}`,
|
|
318
454
|
`Budget: ${budget}`,
|
|
319
|
-
`Usage: ${formatAdvisorUsageTotals(
|
|
320
|
-
`Markdown advice: ${markdown.length} responses (${this.#draftConsultations} with drafts)`,
|
|
321
|
-
`Outcome reports: ${this.#outcomes}`,
|
|
322
|
-
`Gate decisions: ${
|
|
323
|
-
`Loop matching: normalized tool signatures; ${this.#
|
|
455
|
+
`Usage: ${formatAdvisorUsageTotals(totals)}`,
|
|
456
|
+
`Markdown advice: ${markdown.length} responses (${this.#ledger.draftConsultations} with drafts)`,
|
|
457
|
+
`Outcome reports: ${this.#ledger.outcomes}`,
|
|
458
|
+
`Gate decisions: ${this.#decisionsLine()}`,
|
|
459
|
+
`Loop matching: normalized tool signatures; ${this.#repetition.interventions} gate intervention${this.#repetition.interventions === 1 ? "" : "s"}`,
|
|
324
460
|
`Execution effects: ${effects("tool-blocked")} tool blocked, ${effects("session-blocked")} sessions blocked, ${effects("continued")} continued`,
|
|
325
461
|
`Failures: ${failures.length ? failures.join(", ") : "none"}`,
|
|
462
|
+
...jevLines,
|
|
326
463
|
].join("\n");
|
|
327
464
|
}
|
|
328
465
|
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DEFAULT_ADVISOR_TOOL_RESULT_MAX_BYTES,
|
|
3
|
+
DEFAULT_ADVISOR_TOOL_RESULT_MAX_LINES,
|
|
4
|
+
} from "./config/types.ts";
|
|
5
|
+
import { byteLength, capUtf8Bytes } from "./content-utils.ts";
|
|
6
|
+
|
|
7
|
+
export interface ToolResultTruncation {
|
|
8
|
+
content: string;
|
|
9
|
+
omittedLines: number;
|
|
10
|
+
totalBytes: number;
|
|
11
|
+
totalLines: number;
|
|
12
|
+
truncated: boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const OMITTED_MARKER = "[... omitted tool-result section ...]";
|
|
16
|
+
|
|
17
|
+
export const capToolResult = (
|
|
18
|
+
value: string,
|
|
19
|
+
maxLines = DEFAULT_ADVISOR_TOOL_RESULT_MAX_LINES,
|
|
20
|
+
maxBytes = DEFAULT_ADVISOR_TOOL_RESULT_MAX_BYTES
|
|
21
|
+
): ToolResultTruncation => {
|
|
22
|
+
const lines = value.split("\n");
|
|
23
|
+
const totalLines = lines.length;
|
|
24
|
+
const totalBytes = byteLength(value);
|
|
25
|
+
if ((maxLines === 0 || maxBytes === 0) && value.length > 0) {
|
|
26
|
+
return {
|
|
27
|
+
content: "[Tool result omitted: configured limit is zero]",
|
|
28
|
+
omittedLines: totalLines,
|
|
29
|
+
totalBytes,
|
|
30
|
+
totalLines,
|
|
31
|
+
truncated: true,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
if (totalLines <= maxLines && totalBytes <= maxBytes) {
|
|
35
|
+
return {
|
|
36
|
+
content: value,
|
|
37
|
+
omittedLines: 0,
|
|
38
|
+
totalBytes,
|
|
39
|
+
totalLines,
|
|
40
|
+
truncated: false,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const markerBytes = byteLength(OMITTED_MARKER);
|
|
45
|
+
if (maxBytes < markerBytes || maxLines === 1) {
|
|
46
|
+
return {
|
|
47
|
+
content: capUtf8Bytes(OMITTED_MARKER, maxBytes),
|
|
48
|
+
omittedLines: totalLines,
|
|
49
|
+
totalBytes,
|
|
50
|
+
totalLines,
|
|
51
|
+
truncated: true,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
const headCount = Math.floor((maxLines - 1) / 2);
|
|
55
|
+
const tailCount = maxLines - 1 - headCount;
|
|
56
|
+
const collect = (
|
|
57
|
+
candidates: string[],
|
|
58
|
+
maxEntries: number,
|
|
59
|
+
maxContentBytes: number
|
|
60
|
+
) => {
|
|
61
|
+
const selected: string[] = [];
|
|
62
|
+
let used = 0;
|
|
63
|
+
for (const line of candidates.slice(0, maxEntries)) {
|
|
64
|
+
const next = used + byteLength(line) + (selected.length ? 1 : 0);
|
|
65
|
+
if (next > maxContentBytes) {
|
|
66
|
+
break;
|
|
67
|
+
}
|
|
68
|
+
selected.push(line);
|
|
69
|
+
used = next;
|
|
70
|
+
}
|
|
71
|
+
return selected;
|
|
72
|
+
};
|
|
73
|
+
const availableBytes = maxBytes - markerBytes - 2;
|
|
74
|
+
const head = collect(lines, headCount, Math.floor(availableBytes / 2));
|
|
75
|
+
const tail = collect(
|
|
76
|
+
lines.slice(Math.max(head.length, lines.length - tailCount)),
|
|
77
|
+
tailCount,
|
|
78
|
+
availableBytes - byteLength(head.join("\n"))
|
|
79
|
+
);
|
|
80
|
+
const content = [...head, OMITTED_MARKER, ...tail].join("\n");
|
|
81
|
+
return {
|
|
82
|
+
content,
|
|
83
|
+
omittedLines: Math.max(0, totalLines - head.length - tail.length),
|
|
84
|
+
totalBytes,
|
|
85
|
+
totalLines,
|
|
86
|
+
truncated: true,
|
|
87
|
+
};
|
|
88
|
+
};
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import {
|
|
3
|
+
readTrackedFiles,
|
|
4
|
+
readUntrackedFiles,
|
|
5
|
+
type UntrackedAttachment,
|
|
6
|
+
} from "../attachments.ts";
|
|
7
|
+
import {
|
|
8
|
+
advisorGitContextMaxCharsRef,
|
|
9
|
+
advisorGitContextRef,
|
|
10
|
+
advisorRedactSecretsRef,
|
|
11
|
+
advisorScoutEnabledRef,
|
|
12
|
+
advisorTrackedFileContentRef,
|
|
13
|
+
advisorUntrackedContentRef,
|
|
14
|
+
contextMaxCharsRef,
|
|
15
|
+
} from "../config/state.ts";
|
|
16
|
+
import {
|
|
17
|
+
clampGitContextLevel,
|
|
18
|
+
collectGitContext,
|
|
19
|
+
type GitContextLevel,
|
|
20
|
+
} from "../git.ts";
|
|
21
|
+
import { readProjectPreferences } from "../preferences.ts";
|
|
22
|
+
import { redactAndCapText, redactSecrets } from "../redaction.ts";
|
|
23
|
+
import {
|
|
24
|
+
runAdvisorScout,
|
|
25
|
+
type ScoutLifecycleEvent,
|
|
26
|
+
type ScoutOutcome,
|
|
27
|
+
} from "../scout.ts";
|
|
28
|
+
import { curateAdvisorConversation } from "../scout-curation.ts";
|
|
29
|
+
import {
|
|
30
|
+
advisorGitContextBudget,
|
|
31
|
+
advisorRepositoryContext,
|
|
32
|
+
advisorRequestConversation,
|
|
33
|
+
} from "./prompts.ts";
|
|
34
|
+
|
|
35
|
+
/** Maximum bytes for project preferences and the redacted draft. */
|
|
36
|
+
const ATTACHMENT_TEXT_MAX_BYTES = 8 * 1024;
|
|
37
|
+
/** Combined budget for untracked plus tracked file attachments. */
|
|
38
|
+
const ATTACHMENTS_TOTAL_MAX_BYTES = 24 * 1024;
|
|
39
|
+
|
|
40
|
+
export interface ConsultationContextOptions {
|
|
41
|
+
ctx: ExtensionContext;
|
|
42
|
+
currentInvocationId?: string;
|
|
43
|
+
draft?: string;
|
|
44
|
+
gitContext?: GitContextLevel;
|
|
45
|
+
includeTracked?: string[];
|
|
46
|
+
includeUntracked?: string[];
|
|
47
|
+
onScout?: (event: ScoutLifecycleEvent) => void;
|
|
48
|
+
signal?: AbortSignal;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface ConsultationContext {
|
|
52
|
+
/** Disclosed repository changes header. */
|
|
53
|
+
changeText: string;
|
|
54
|
+
/** Curated (or legacy) conversation body. */
|
|
55
|
+
conversation: string;
|
|
56
|
+
/** Redacted draft text, if a draft was supplied. */
|
|
57
|
+
draftText?: string;
|
|
58
|
+
/** Redacted project preferences, if present. */
|
|
59
|
+
preferences?: { bytes: number; text: string };
|
|
60
|
+
/** Non-cancelling Scout outcome, when Scout ran. */
|
|
61
|
+
scout?: Exclude<ScoutOutcome, { cancelled: true }>;
|
|
62
|
+
/** Redacted tracked-file attachments. */
|
|
63
|
+
tracked: UntrackedAttachment[];
|
|
64
|
+
/** Redacted untracked-file attachments. */
|
|
65
|
+
untracked: UntrackedAttachment[];
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Assembles every context region the Advisor request embeds: repository
|
|
69
|
+
* changes (spending the shared budget first), the curated conversation,
|
|
70
|
+
* project preferences, and consented file attachments. */
|
|
71
|
+
export const assembleConsultationContext = async (
|
|
72
|
+
options: ConsultationContextOptions
|
|
73
|
+
): Promise<ConsultationContext> => {
|
|
74
|
+
const { ctx } = options;
|
|
75
|
+
|
|
76
|
+
// The user setting is the ceiling; the Executor may only narrow it.
|
|
77
|
+
const allowed = advisorGitContextRef;
|
|
78
|
+
const level = clampGitContextLevel(options.gitContext ?? allowed, allowed);
|
|
79
|
+
const gitBudget = advisorGitContextBudget(
|
|
80
|
+
contextMaxCharsRef,
|
|
81
|
+
advisorGitContextMaxCharsRef
|
|
82
|
+
);
|
|
83
|
+
const changes = collectGitContext(
|
|
84
|
+
ctx.cwd,
|
|
85
|
+
level,
|
|
86
|
+
gitBudget,
|
|
87
|
+
advisorRedactSecretsRef ? redactSecrets : undefined
|
|
88
|
+
);
|
|
89
|
+
// The disclosure warning is control metadata, not repository payload. Keep it
|
|
90
|
+
// outside the zero-byte Git budget so disabling disclosure cannot erase it.
|
|
91
|
+
const changeText = advisorRepositoryContext(
|
|
92
|
+
changes,
|
|
93
|
+
options.gitContext ?? allowed,
|
|
94
|
+
level,
|
|
95
|
+
gitBudget
|
|
96
|
+
);
|
|
97
|
+
// Repository context spends part of the shared budget, so a large patch
|
|
98
|
+
// cannot silently push the conversation past the model's context window.
|
|
99
|
+
const conversationBudget = Math.max(
|
|
100
|
+
0,
|
|
101
|
+
contextMaxCharsRef - changeText.length
|
|
102
|
+
);
|
|
103
|
+
const legacyConversation = advisorRequestConversation(
|
|
104
|
+
ctx,
|
|
105
|
+
conversationBudget
|
|
106
|
+
);
|
|
107
|
+
const curated = await curateAdvisorConversation(
|
|
108
|
+
ctx,
|
|
109
|
+
legacyConversation,
|
|
110
|
+
options.signal,
|
|
111
|
+
options.onScout,
|
|
112
|
+
advisorScoutEnabledRef,
|
|
113
|
+
runAdvisorScout,
|
|
114
|
+
options.currentInvocationId,
|
|
115
|
+
conversationBudget
|
|
116
|
+
);
|
|
117
|
+
const preferences = await readProjectPreferences(
|
|
118
|
+
ctx,
|
|
119
|
+
ATTACHMENT_TEXT_MAX_BYTES,
|
|
120
|
+
advisorRedactSecretsRef
|
|
121
|
+
);
|
|
122
|
+
const draftText = options.draft
|
|
123
|
+
? redactAndCapText(
|
|
124
|
+
options.draft,
|
|
125
|
+
ATTACHMENT_TEXT_MAX_BYTES,
|
|
126
|
+
advisorRedactSecretsRef
|
|
127
|
+
)
|
|
128
|
+
: undefined;
|
|
129
|
+
const untracked = await readUntrackedFiles(
|
|
130
|
+
ctx.cwd,
|
|
131
|
+
options.includeUntracked ?? [],
|
|
132
|
+
advisorUntrackedContentRef,
|
|
133
|
+
advisorRedactSecretsRef
|
|
134
|
+
);
|
|
135
|
+
const tracked = await readTrackedFiles(
|
|
136
|
+
ctx.cwd,
|
|
137
|
+
options.includeTracked ?? [],
|
|
138
|
+
advisorTrackedFileContentRef,
|
|
139
|
+
advisorRedactSecretsRef,
|
|
140
|
+
Math.max(
|
|
141
|
+
0,
|
|
142
|
+
ATTACHMENTS_TOTAL_MAX_BYTES -
|
|
143
|
+
untracked.reduce((sum, item) => sum + item.bytes, 0)
|
|
144
|
+
)
|
|
145
|
+
);
|
|
146
|
+
return {
|
|
147
|
+
changeText,
|
|
148
|
+
conversation: curated.conversation,
|
|
149
|
+
draftText,
|
|
150
|
+
preferences,
|
|
151
|
+
scout: curated.scout,
|
|
152
|
+
tracked,
|
|
153
|
+
untracked,
|
|
154
|
+
};
|
|
155
|
+
};
|