advisor-pi 1.0.3 → 1.2.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/README.md +39 -2
- package/dist/config-ui.d.ts +12 -0
- package/dist/config-ui.d.ts.map +1 -0
- package/dist/config-ui.js +26 -0
- package/dist/config-ui.js.map +1 -0
- package/dist/index.d.ts +52 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +303 -53
- package/dist/index.js.map +1 -1
- package/dist/preferences.d.ts +40 -0
- package/dist/preferences.d.ts.map +1 -0
- package/dist/preferences.js +198 -0
- package/dist/preferences.js.map +1 -0
- package/package.json +2 -1
- package/src/config-ui.ts +40 -0
- package/src/index.ts +334 -52
- package/src/preferences.ts +228 -0
package/dist/index.js
CHANGED
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
import { completeSimple, StringEnum } from "@earendil-works/pi-ai";
|
|
2
2
|
import { buildSessionContext, convertToLlm, serializeConversation, } from "@earendil-works/pi-coding-agent";
|
|
3
3
|
import { Type } from "typebox";
|
|
4
|
-
|
|
4
|
+
import { loadPreferences, savePreferencesPatch } from "./preferences.js";
|
|
5
|
+
import { buildMenuRows, rowForLabel } from "./config-ui.js";
|
|
6
|
+
export { loadPreferences, preferencesPath, savePreferences, savePreferencesPatch } from "./preferences.js";
|
|
7
|
+
export const STATE_ENTRY = "advisor-pi-state";
|
|
5
8
|
const TOOL_NAME = "advisor";
|
|
6
|
-
const DEFAULT_ADVISOR_MODEL = "openai-codex/gpt-5.6-sol";
|
|
7
|
-
const LEGACY_DEFAULT_ADVISOR_MODEL = "openai-codex/gpt-5.5";
|
|
9
|
+
export const DEFAULT_ADVISOR_MODEL = "openai-codex/gpt-5.6-sol";
|
|
10
|
+
export const LEGACY_DEFAULT_ADVISOR_MODEL = "openai-codex/gpt-5.5";
|
|
8
11
|
const DEFAULT_ADVISOR_THINKING_LEVEL = "high";
|
|
9
12
|
const DEFAULT_MAX_USES = 5;
|
|
10
13
|
const DEFAULT_CACHE_RETENTION = "short";
|
|
11
14
|
const DEFAULT_TIMEOUT_MS = 600_000;
|
|
12
15
|
const DEFAULT_MAX_TOKENS = 4_000;
|
|
16
|
+
export const DEFAULT_MAX_TRANSCRIPT_CHARS = 20_000;
|
|
13
17
|
const advisorToolSchema = Type.Object({
|
|
14
18
|
question: Type.String({
|
|
15
19
|
description: "Strategic question for the advisor. Ask for planning, risk analysis, course correction, or review guidance.",
|
|
@@ -22,8 +26,11 @@ const advisorToolSchema = Type.Object({
|
|
|
22
26
|
description: "Optional extra context that is not obvious from the transcript, such as constraints, current plan, or failed attempts.",
|
|
23
27
|
})),
|
|
24
28
|
});
|
|
29
|
+
function isTuiContext(ctx) {
|
|
30
|
+
return ctx.mode === "tui" && ctx.hasUI;
|
|
31
|
+
}
|
|
25
32
|
export default function advisorPiExtension(pi) {
|
|
26
|
-
let config = defaultConfig();
|
|
33
|
+
let config = normalizeConfig(loadPreferences(), defaultConfig(), undefined, { migrateLegacyDefault: false });
|
|
27
34
|
let useCount = 0;
|
|
28
35
|
pi.registerFlag("advisor-model", {
|
|
29
36
|
description: "Advisor model as provider/model, e.g. openai-codex/gpt-5.6-sol",
|
|
@@ -41,10 +48,13 @@ export default function advisorPiExtension(pi) {
|
|
|
41
48
|
description: "Advisor prompt-cache preference: none, short, or long",
|
|
42
49
|
type: "string",
|
|
43
50
|
});
|
|
51
|
+
pi.registerFlag("advisor-max-transcript-chars", {
|
|
52
|
+
description: "Maximum conversation transcript characters sent per advisor call (keeps most recent)",
|
|
53
|
+
type: "string",
|
|
54
|
+
});
|
|
44
55
|
pi.registerFlag("advisor-enabled", {
|
|
45
56
|
description: "Enable the advisor tool on startup",
|
|
46
57
|
type: "boolean",
|
|
47
|
-
default: true,
|
|
48
58
|
});
|
|
49
59
|
pi.registerTool({
|
|
50
60
|
name: TOOL_NAME,
|
|
@@ -59,7 +69,8 @@ export default function advisorPiExtension(pi) {
|
|
|
59
69
|
parameters: advisorToolSchema,
|
|
60
70
|
executionMode: "sequential",
|
|
61
71
|
async execute(_toolCallId, params, signal, onUpdate, ctx) {
|
|
62
|
-
|
|
72
|
+
// Branch state is replayed at session_start/session_tree; refreshing here
|
|
73
|
+
// would reapply startup flags over explicit in-session command edits.
|
|
63
74
|
if (!config.enabled) {
|
|
64
75
|
return advisorDisabledResult(config, useCount, params);
|
|
65
76
|
}
|
|
@@ -83,7 +94,7 @@ export default function advisorPiExtension(pi) {
|
|
|
83
94
|
if (!auth.ok)
|
|
84
95
|
throw new Error(auth.error);
|
|
85
96
|
const startedAt = Date.now();
|
|
86
|
-
const conversationText = serializeCurrentConversation(ctx);
|
|
97
|
+
const conversationText = truncateTranscript(serializeCurrentConversation(ctx), config.maxTranscriptChars);
|
|
87
98
|
const userMessage = {
|
|
88
99
|
role: "user",
|
|
89
100
|
content: [
|
|
@@ -145,15 +156,16 @@ export default function advisorPiExtension(pi) {
|
|
|
145
156
|
},
|
|
146
157
|
});
|
|
147
158
|
pi.registerCommand("advisor-pi", {
|
|
148
|
-
description: "Configure advisor-pi: status, enable, disable, model, thinking, max-uses, cache, reset",
|
|
159
|
+
description: "Configure advisor-pi: status, enable, disable, model, thinking, max-uses, max-transcript-chars, cache, reset",
|
|
149
160
|
handler: async (args, ctx) => {
|
|
150
|
-
const
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
if (
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
161
|
+
const trimmed = args.trim();
|
|
162
|
+
// Bare /advisor-pi is interactive only in the terminal TUI. Other modes
|
|
163
|
+
// keep the existing status response for scripts and print output.
|
|
164
|
+
if (!trimmed && isTuiContext(ctx)) {
|
|
165
|
+
await openConfigPanel(ctx);
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
await applyCommand(trimmed, ctx);
|
|
157
169
|
},
|
|
158
170
|
});
|
|
159
171
|
pi.on("session_start", async (_event, ctx) => {
|
|
@@ -173,15 +185,15 @@ export default function advisorPiExtension(pi) {
|
|
|
173
185
|
persistState(pi, config, useCount);
|
|
174
186
|
});
|
|
175
187
|
function refreshStateFromBranch(ctx) {
|
|
176
|
-
config = defaultConfig();
|
|
188
|
+
config = normalizeConfig(loadPreferences(), defaultConfig(), ctx.modelRegistry, { migrateLegacyDefault: false });
|
|
177
189
|
useCount = 0;
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
190
|
+
restoreStateFromSession(ctx, ctx.modelRegistry);
|
|
191
|
+
applyStartupFlags(pi, ctx.modelRegistry);
|
|
192
|
+
ensureAdvisorModelResolves(ctx);
|
|
181
193
|
syncActiveTool(pi);
|
|
182
194
|
updateStatus(ctx);
|
|
183
195
|
}
|
|
184
|
-
function restoreStateFromSession(ctx) {
|
|
196
|
+
function restoreStateFromSession(ctx, registry) {
|
|
185
197
|
let restored = false;
|
|
186
198
|
const branch = ctx.sessionManager.getBranch();
|
|
187
199
|
for (const entry of branch) {
|
|
@@ -189,7 +201,7 @@ export default function advisorPiExtension(pi) {
|
|
|
189
201
|
restored = true;
|
|
190
202
|
const data = entry.data;
|
|
191
203
|
if (data?.config)
|
|
192
|
-
config = normalizeConfig(data.config, config);
|
|
204
|
+
config = normalizeConfig(data.config, config, registry);
|
|
193
205
|
if (typeof data?.useCount === "number")
|
|
194
206
|
useCount = Math.max(0, data.useCount);
|
|
195
207
|
}
|
|
@@ -197,7 +209,7 @@ export default function advisorPiExtension(pi) {
|
|
|
197
209
|
restored = true;
|
|
198
210
|
const details = entry.message.details;
|
|
199
211
|
if (details?.state?.config)
|
|
200
|
-
config = normalizeConfig(details.state.config, config);
|
|
212
|
+
config = normalizeConfig(details.state.config, config, registry);
|
|
201
213
|
if (typeof details?.state?.useCount === "number")
|
|
202
214
|
useCount = Math.max(useCount, details.state.useCount);
|
|
203
215
|
else if (details?.advisor?.useCount)
|
|
@@ -206,36 +218,65 @@ export default function advisorPiExtension(pi) {
|
|
|
206
218
|
}
|
|
207
219
|
return restored;
|
|
208
220
|
}
|
|
209
|
-
function applyStartupFlags(api) {
|
|
210
|
-
const enabledFlag = api
|
|
221
|
+
function applyStartupFlags(api, registry) {
|
|
222
|
+
const enabledFlag = flagValue(api, "advisor-enabled");
|
|
211
223
|
if (typeof enabledFlag === "boolean")
|
|
212
224
|
config.enabled = enabledFlag;
|
|
213
|
-
const modelFlag = api
|
|
225
|
+
const modelFlag = flagValue(api, "advisor-model");
|
|
214
226
|
if (typeof modelFlag === "string" && modelFlag.trim()) {
|
|
215
227
|
const parsed = parseModelSpec(modelFlag.trim());
|
|
216
|
-
if (parsed) {
|
|
228
|
+
if (parsed && (!registry || modelResolves(registry, parsed.provider, parsed.modelId))) {
|
|
217
229
|
config.provider = parsed.provider;
|
|
218
230
|
config.modelId = parsed.modelId;
|
|
219
231
|
}
|
|
220
232
|
}
|
|
221
|
-
const thinkingFlag = api
|
|
233
|
+
const thinkingFlag = flagValue(api, "advisor-thinking");
|
|
222
234
|
if (typeof thinkingFlag === "string") {
|
|
223
235
|
const thinkingLevel = parseThinkingLevel(thinkingFlag);
|
|
224
236
|
if (thinkingLevel)
|
|
225
237
|
config.thinkingLevel = thinkingLevel;
|
|
226
238
|
}
|
|
227
|
-
const maxUsesFlag = api
|
|
239
|
+
const maxUsesFlag = flagValue(api, "advisor-max-uses");
|
|
228
240
|
if (typeof maxUsesFlag === "string") {
|
|
229
241
|
const maxUses = parsePositiveInt(maxUsesFlag);
|
|
230
242
|
if (maxUses !== undefined)
|
|
231
243
|
config.maxUses = maxUses;
|
|
232
244
|
}
|
|
233
|
-
const cacheFlag = api
|
|
245
|
+
const cacheFlag = flagValue(api, "advisor-cache");
|
|
234
246
|
if (typeof cacheFlag === "string") {
|
|
235
247
|
const cacheRetention = parseCacheRetention(cacheFlag);
|
|
236
248
|
if (cacheRetention)
|
|
237
249
|
config.cacheRetention = cacheRetention;
|
|
238
250
|
}
|
|
251
|
+
const maxTranscriptCharsFlag = flagValue(api, "advisor-max-transcript-chars");
|
|
252
|
+
if (typeof maxTranscriptCharsFlag === "string") {
|
|
253
|
+
const maxTranscriptChars = parsePositiveInt(maxTranscriptCharsFlag);
|
|
254
|
+
if (maxTranscriptChars !== undefined)
|
|
255
|
+
config.maxTranscriptChars = maxTranscriptChars;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
function ensureAdvisorModelResolves(ctx) {
|
|
259
|
+
const registry = ctx.modelRegistry;
|
|
260
|
+
if (!registry || modelResolves(registry, config.provider, config.modelId))
|
|
261
|
+
return;
|
|
262
|
+
const previous = `${config.provider}/${config.modelId}`;
|
|
263
|
+
const candidates = [parseModelSpec(DEFAULT_ADVISOR_MODEL), parseModelSpec(LEGACY_DEFAULT_ADVISOR_MODEL)].filter((candidate) => candidate !== undefined);
|
|
264
|
+
const working = resolveAdvisorModel(registry, candidates);
|
|
265
|
+
if (!working)
|
|
266
|
+
return;
|
|
267
|
+
config.provider = working.provider;
|
|
268
|
+
config.modelId = working.modelId;
|
|
269
|
+
persistState(pi, config, useCount);
|
|
270
|
+
if (ctx.hasUI) {
|
|
271
|
+
ctx.ui.notify(`Advisor model ${previous} not found. Falling back to ${working.provider}/${working.modelId}.`, "warning");
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
async function persistPreferencesForEdit(ctx, patch) {
|
|
275
|
+
if (await savePreferencesPatch(patch))
|
|
276
|
+
return;
|
|
277
|
+
if (ctx.hasUI) {
|
|
278
|
+
ctx.ui.notify("Could not save advisor-pi preferences; this change applies to the current session only.", "warning");
|
|
279
|
+
}
|
|
239
280
|
}
|
|
240
281
|
function handleCommand(args, ctx) {
|
|
241
282
|
if (!args || args === "status") {
|
|
@@ -251,10 +292,22 @@ export default function advisorPiExtension(pi) {
|
|
|
251
292
|
switch (command) {
|
|
252
293
|
case "enable":
|
|
253
294
|
config.enabled = true;
|
|
254
|
-
return {
|
|
295
|
+
return {
|
|
296
|
+
message: "advisor-pi enabled",
|
|
297
|
+
level: "info",
|
|
298
|
+
persist: true,
|
|
299
|
+
preferencePatch: { enabled: true },
|
|
300
|
+
updateToolState: true,
|
|
301
|
+
};
|
|
255
302
|
case "disable":
|
|
256
303
|
config.enabled = false;
|
|
257
|
-
return {
|
|
304
|
+
return {
|
|
305
|
+
message: "advisor-pi disabled",
|
|
306
|
+
level: "info",
|
|
307
|
+
persist: true,
|
|
308
|
+
preferencePatch: { enabled: false },
|
|
309
|
+
updateToolState: true,
|
|
310
|
+
};
|
|
258
311
|
case "reset":
|
|
259
312
|
useCount = 0;
|
|
260
313
|
return { message: "advisor-pi use count reset", level: "info", persist: true, updateToolState: false };
|
|
@@ -283,6 +336,7 @@ export default function advisorPiExtension(pi) {
|
|
|
283
336
|
message: `advisor-pi model set to ${config.provider}/${config.modelId}`,
|
|
284
337
|
level: "info",
|
|
285
338
|
persist: true,
|
|
339
|
+
preferencePatch: { provider: config.provider, modelId: config.modelId },
|
|
286
340
|
updateToolState: false,
|
|
287
341
|
};
|
|
288
342
|
}
|
|
@@ -301,6 +355,7 @@ export default function advisorPiExtension(pi) {
|
|
|
301
355
|
message: `advisor-pi thinking level set to ${thinkingLevel}`,
|
|
302
356
|
level: "info",
|
|
303
357
|
persist: true,
|
|
358
|
+
preferencePatch: { thinkingLevel },
|
|
304
359
|
updateToolState: false,
|
|
305
360
|
};
|
|
306
361
|
}
|
|
@@ -319,6 +374,7 @@ export default function advisorPiExtension(pi) {
|
|
|
319
374
|
message: `advisor-pi max uses set to ${maxUses}`,
|
|
320
375
|
level: "info",
|
|
321
376
|
persist: true,
|
|
377
|
+
preferencePatch: { maxUses },
|
|
322
378
|
updateToolState: false,
|
|
323
379
|
};
|
|
324
380
|
}
|
|
@@ -337,18 +393,143 @@ export default function advisorPiExtension(pi) {
|
|
|
337
393
|
message: `advisor-pi cache set to ${cacheRetention}`,
|
|
338
394
|
level: "info",
|
|
339
395
|
persist: true,
|
|
396
|
+
preferencePatch: { cacheRetention },
|
|
397
|
+
updateToolState: false,
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
case "max-transcript-chars": {
|
|
401
|
+
const maxTranscriptChars = parsePositiveInt(value);
|
|
402
|
+
if (maxTranscriptChars === undefined) {
|
|
403
|
+
return {
|
|
404
|
+
message: "Usage: /advisor-pi max-transcript-chars <positive-number>",
|
|
405
|
+
level: "error",
|
|
406
|
+
persist: false,
|
|
407
|
+
updateToolState: false,
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
config.maxTranscriptChars = maxTranscriptChars;
|
|
411
|
+
return {
|
|
412
|
+
message: `advisor-pi max transcript chars set to ${maxTranscriptChars}`,
|
|
413
|
+
level: "info",
|
|
414
|
+
persist: true,
|
|
415
|
+
preferencePatch: { maxTranscriptChars },
|
|
340
416
|
updateToolState: false,
|
|
341
417
|
};
|
|
342
418
|
}
|
|
343
419
|
default:
|
|
344
420
|
return {
|
|
345
|
-
message: "Usage: /advisor-pi [status|enable|disable|model <provider>/<model>|thinking <minimal|low|medium|high|xhigh|max>|max-uses <n>|cache <none|short|long>|reset]",
|
|
421
|
+
message: "Usage: /advisor-pi [status|enable|disable|model <provider>/<model>|thinking <minimal|low|medium|high|xhigh|max>|max-uses <n>|max-transcript-chars <n>|cache <none|short|long>|reset]",
|
|
346
422
|
level: "error",
|
|
347
423
|
persist: false,
|
|
348
424
|
updateToolState: false,
|
|
349
425
|
};
|
|
350
426
|
}
|
|
351
427
|
}
|
|
428
|
+
async function applyCommand(args, ctx, options = {}) {
|
|
429
|
+
const result = handleCommand(args, ctx);
|
|
430
|
+
if (result.persist) {
|
|
431
|
+
persistState(pi, config, useCount);
|
|
432
|
+
if (result.preferencePatch)
|
|
433
|
+
await persistPreferencesForEdit(ctx, result.preferencePatch);
|
|
434
|
+
}
|
|
435
|
+
if (result.updateToolState)
|
|
436
|
+
syncActiveTool(pi);
|
|
437
|
+
updateStatus(ctx);
|
|
438
|
+
const message = options.redactModelErrors && result.level === "error" ? "Advisor model not found" : result.message;
|
|
439
|
+
ctx.ui.notify(message, result.level);
|
|
440
|
+
}
|
|
441
|
+
async function openConfigPanel(ctx) {
|
|
442
|
+
for (;;) {
|
|
443
|
+
const rows = buildMenuRows(config, useCount);
|
|
444
|
+
const choice = await ctx.ui.select("advisor-pi settings", rows.map((row) => row.label));
|
|
445
|
+
const row = rowForLabel(rows, choice);
|
|
446
|
+
// Escape returns undefined, which closes the panel without changing state.
|
|
447
|
+
if (!row || row.key === "close")
|
|
448
|
+
return;
|
|
449
|
+
await applyMenuChoice(row.key, ctx);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
async function applyMenuChoice(key, ctx) {
|
|
453
|
+
switch (key) {
|
|
454
|
+
case "enabled": {
|
|
455
|
+
const choice = await ctx.ui.select("Advisor enabled", ["enabled", "disabled"]);
|
|
456
|
+
if (choice !== "enabled" && choice !== "disabled")
|
|
457
|
+
return;
|
|
458
|
+
const enabled = choice === "enabled";
|
|
459
|
+
if (enabled === config.enabled)
|
|
460
|
+
return;
|
|
461
|
+
const confirmed = await ctx.ui.confirm(enabled ? "Enable advisor-pi?" : "Disable advisor-pi?", enabled
|
|
462
|
+
? "The advisor tool will be available to the executor."
|
|
463
|
+
: "The advisor tool will be removed from the active tool list.");
|
|
464
|
+
if (!confirmed)
|
|
465
|
+
return;
|
|
466
|
+
await applyCommand(enabled ? "enable" : "disable", ctx);
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
469
|
+
case "model": {
|
|
470
|
+
const typed = await ctx.ui.input("Advisor model", "provider/model");
|
|
471
|
+
if (typed === undefined)
|
|
472
|
+
return;
|
|
473
|
+
const parsed = parseModelSpec(typed);
|
|
474
|
+
if (!parsed) {
|
|
475
|
+
ctx.ui.notify("Expected provider/model", "error");
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
478
|
+
let model;
|
|
479
|
+
try {
|
|
480
|
+
model = ctx.modelRegistry.find(parsed.provider, parsed.modelId);
|
|
481
|
+
}
|
|
482
|
+
catch {
|
|
483
|
+
model = undefined;
|
|
484
|
+
}
|
|
485
|
+
if (!model) {
|
|
486
|
+
ctx.ui.notify("Advisor model not found", "error");
|
|
487
|
+
return;
|
|
488
|
+
}
|
|
489
|
+
await applyCommand(`model ${parsed.provider}/${parsed.modelId}`, ctx, { redactModelErrors: true });
|
|
490
|
+
return;
|
|
491
|
+
}
|
|
492
|
+
case "thinking": {
|
|
493
|
+
const choice = await ctx.ui.select("Advisor thinking", ["minimal", "low", "medium", "high", "xhigh", "max"]);
|
|
494
|
+
if (!parseThinkingLevel(choice))
|
|
495
|
+
return;
|
|
496
|
+
await applyCommand(`thinking ${choice}`, ctx);
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
case "max-uses": {
|
|
500
|
+
const value = await ctx.ui.input("Maximum advisor uses", String(config.maxUses));
|
|
501
|
+
if (value === undefined)
|
|
502
|
+
return;
|
|
503
|
+
await applyCommand(`max-uses ${value}`, ctx);
|
|
504
|
+
return;
|
|
505
|
+
}
|
|
506
|
+
case "max-transcript-chars": {
|
|
507
|
+
const value = await ctx.ui.input("Maximum transcript characters", String(config.maxTranscriptChars));
|
|
508
|
+
if (value === undefined)
|
|
509
|
+
return;
|
|
510
|
+
await applyCommand(`max-transcript-chars ${value}`, ctx);
|
|
511
|
+
return;
|
|
512
|
+
}
|
|
513
|
+
case "cache": {
|
|
514
|
+
const choice = await ctx.ui.select("Advisor cache retention", ["none", "short", "long"]);
|
|
515
|
+
if (!parseCacheRetention(choice))
|
|
516
|
+
return;
|
|
517
|
+
await applyCommand(`cache ${choice}`, ctx);
|
|
518
|
+
return;
|
|
519
|
+
}
|
|
520
|
+
case "reset": {
|
|
521
|
+
if (useCount <= 0)
|
|
522
|
+
return;
|
|
523
|
+
const confirmed = await ctx.ui.confirm("Reset advisor use count?", `This clears ${useCount} advisor use${useCount === 1 ? "" : "s"} for the current session branch.`);
|
|
524
|
+
if (!confirmed)
|
|
525
|
+
return;
|
|
526
|
+
await applyCommand("reset", ctx);
|
|
527
|
+
return;
|
|
528
|
+
}
|
|
529
|
+
case "close":
|
|
530
|
+
return;
|
|
531
|
+
}
|
|
532
|
+
}
|
|
352
533
|
function syncActiveTool(api) {
|
|
353
534
|
const activeTools = api.getActiveTools();
|
|
354
535
|
const hasAdvisor = activeTools.includes(TOOL_NAME);
|
|
@@ -378,6 +559,7 @@ export default function advisorPiExtension(pi) {
|
|
|
378
559
|
`model: ${config.provider}/${config.modelId} (${availability})`,
|
|
379
560
|
`thinking: ${config.thinkingLevel}`,
|
|
380
561
|
`uses: ${useCount}/${config.maxUses}`,
|
|
562
|
+
`transcript: max ${config.maxTranscriptChars} chars`,
|
|
381
563
|
`cache: ${config.cacheRetention}`,
|
|
382
564
|
].join(" • ");
|
|
383
565
|
}
|
|
@@ -393,7 +575,7 @@ Your role is strategic guidance only. You do not have tools and you do not make
|
|
|
393
575
|
- If the task is unsafe, impossible, or underspecified, say what must be clarified.
|
|
394
576
|
|
|
395
577
|
Return guidance in short sections with bullets when useful.`;
|
|
396
|
-
function defaultConfig() {
|
|
578
|
+
export function defaultConfig() {
|
|
397
579
|
const parsed = parseModelSpec(DEFAULT_ADVISOR_MODEL) ?? { provider: "openai-codex", modelId: "gpt-5.6-sol" };
|
|
398
580
|
return {
|
|
399
581
|
enabled: true,
|
|
@@ -404,6 +586,7 @@ function defaultConfig() {
|
|
|
404
586
|
cacheRetention: DEFAULT_CACHE_RETENTION,
|
|
405
587
|
maxTokens: DEFAULT_MAX_TOKENS,
|
|
406
588
|
timeoutMs: DEFAULT_TIMEOUT_MS,
|
|
589
|
+
maxTranscriptChars: DEFAULT_MAX_TRANSCRIPT_CHARS,
|
|
407
590
|
};
|
|
408
591
|
}
|
|
409
592
|
function buildExecutorGuidance(config, useCount) {
|
|
@@ -422,6 +605,14 @@ function serializeCurrentConversation(ctx) {
|
|
|
422
605
|
const llmMessages = convertToLlm(sessionContext.messages);
|
|
423
606
|
return serializeConversation(llmMessages);
|
|
424
607
|
}
|
|
608
|
+
export function truncateTranscript(text, maxChars) {
|
|
609
|
+
if (maxChars <= 0)
|
|
610
|
+
return text;
|
|
611
|
+
if (text.length <= maxChars)
|
|
612
|
+
return text;
|
|
613
|
+
const omitted = text.length - maxChars;
|
|
614
|
+
return `[... earlier conversation truncated: showing the most recent ${maxChars} of ${text.length} characters (${omitted} omitted)]\n\n${text.slice(text.length - maxChars)}`;
|
|
615
|
+
}
|
|
425
616
|
function buildAdvisorUserPrompt(params, conversationText) {
|
|
426
617
|
return [
|
|
427
618
|
"## Executor Question",
|
|
@@ -480,7 +671,7 @@ function makeSkippedDetails(config, useCount, params) {
|
|
|
480
671
|
state: makeStateEntry(config, useCount),
|
|
481
672
|
};
|
|
482
673
|
}
|
|
483
|
-
function makeStateEntry(config, useCount) {
|
|
674
|
+
export function makeStateEntry(config, useCount) {
|
|
484
675
|
return {
|
|
485
676
|
version: 1,
|
|
486
677
|
config: { ...config },
|
|
@@ -488,42 +679,101 @@ function makeStateEntry(config, useCount) {
|
|
|
488
679
|
updatedAt: new Date().toISOString(),
|
|
489
680
|
};
|
|
490
681
|
}
|
|
491
|
-
function persistState(pi, config, useCount) {
|
|
682
|
+
export function persistState(pi, config, useCount) {
|
|
492
683
|
pi.appendEntry(STATE_ENTRY, makeStateEntry(config, useCount));
|
|
493
684
|
}
|
|
494
|
-
function normalizeConfig(input, fallback) {
|
|
685
|
+
export function normalizeConfig(input, fallback, registry, options = {}) {
|
|
495
686
|
const normalized = {
|
|
496
687
|
enabled: typeof input.enabled === "boolean" ? input.enabled : fallback.enabled,
|
|
497
|
-
provider: typeof input.provider === "string" && input.provider ? input.provider : fallback.provider,
|
|
498
|
-
modelId: typeof input.modelId === "string" && input.modelId ? input.modelId : fallback.modelId,
|
|
688
|
+
provider: typeof input.provider === "string" && input.provider.trim() ? input.provider.trim() : fallback.provider,
|
|
689
|
+
modelId: typeof input.modelId === "string" && input.modelId.trim() ? input.modelId.trim() : fallback.modelId,
|
|
499
690
|
thinkingLevel: parseThinkingLevel(input.thinkingLevel) ?? fallback.thinkingLevel,
|
|
500
|
-
maxUses: typeof input.maxUses === "number" && input.maxUses
|
|
691
|
+
maxUses: typeof input.maxUses === "number" && Number.isFinite(input.maxUses) && Math.floor(input.maxUses) > 0
|
|
692
|
+
? Math.floor(input.maxUses)
|
|
693
|
+
: fallback.maxUses,
|
|
501
694
|
cacheRetention: parseCacheRetention(input.cacheRetention) ?? fallback.cacheRetention,
|
|
502
|
-
maxTokens: typeof input.maxTokens === "number" && input.maxTokens
|
|
503
|
-
|
|
695
|
+
maxTokens: typeof input.maxTokens === "number" && Number.isFinite(input.maxTokens) && Math.floor(input.maxTokens) > 0
|
|
696
|
+
? Math.floor(input.maxTokens)
|
|
697
|
+
: fallback.maxTokens,
|
|
698
|
+
timeoutMs: typeof input.timeoutMs === "number" && Number.isFinite(input.timeoutMs) && Math.floor(input.timeoutMs) > 0
|
|
699
|
+
? Math.floor(input.timeoutMs)
|
|
700
|
+
: fallback.timeoutMs,
|
|
701
|
+
maxTranscriptChars: typeof input.maxTranscriptChars === "number" && Number.isFinite(input.maxTranscriptChars) && Math.floor(input.maxTranscriptChars) > 0
|
|
702
|
+
? Math.floor(input.maxTranscriptChars)
|
|
703
|
+
: fallback.maxTranscriptChars,
|
|
504
704
|
};
|
|
505
705
|
const legacyDefault = parseModelSpec(LEGACY_DEFAULT_ADVISOR_MODEL);
|
|
506
|
-
if (
|
|
706
|
+
if (options.migrateLegacyDefault !== false &&
|
|
707
|
+
input.thinkingLevel === undefined &&
|
|
507
708
|
legacyDefault &&
|
|
508
709
|
normalized.provider === legacyDefault.provider &&
|
|
509
710
|
normalized.modelId === legacyDefault.modelId) {
|
|
510
|
-
|
|
511
|
-
|
|
711
|
+
// Only rewrite the stored legacy default when the migration target resolves.
|
|
712
|
+
// Otherwise a resolving stored model would be replaced by a non-resolving default.
|
|
713
|
+
if (!registry || modelResolves(registry, fallback.provider, fallback.modelId)) {
|
|
714
|
+
normalized.provider = fallback.provider;
|
|
715
|
+
normalized.modelId = fallback.modelId;
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
if (registry && typeof input.provider === "string" && input.provider && typeof input.modelId === "string" && input.modelId) {
|
|
719
|
+
// Never replace a resolving stored model with a non-resolving one.
|
|
720
|
+
if (modelResolves(registry, input.provider, input.modelId) && !modelResolves(registry, normalized.provider, normalized.modelId)) {
|
|
721
|
+
normalized.provider = input.provider;
|
|
722
|
+
normalized.modelId = input.modelId;
|
|
723
|
+
}
|
|
512
724
|
}
|
|
513
725
|
return normalized;
|
|
514
726
|
}
|
|
515
|
-
function
|
|
516
|
-
|
|
517
|
-
|
|
727
|
+
export function modelResolves(registry, provider, modelId) {
|
|
728
|
+
if (!registry)
|
|
729
|
+
return false;
|
|
730
|
+
try {
|
|
731
|
+
const found = registry.find(provider, modelId);
|
|
732
|
+
return found !== undefined && found !== null;
|
|
733
|
+
}
|
|
734
|
+
catch {
|
|
735
|
+
return false;
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
export function resolveAdvisorModel(registry, candidates) {
|
|
739
|
+
if (!registry)
|
|
518
740
|
return undefined;
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
}
|
|
741
|
+
for (const candidate of candidates) {
|
|
742
|
+
if (modelResolves(registry, candidate.provider, candidate.modelId))
|
|
743
|
+
return candidate;
|
|
744
|
+
}
|
|
745
|
+
try {
|
|
746
|
+
const available = registry.getAvailable?.();
|
|
747
|
+
const first = Array.isArray(available) ? available[0] : undefined;
|
|
748
|
+
if (first && typeof first.provider === "string" && typeof first.id === "string" && first.provider && first.id) {
|
|
749
|
+
return { provider: first.provider, modelId: first.id };
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
catch {
|
|
753
|
+
// Ignore registry listing failures; callers keep the current config.
|
|
754
|
+
}
|
|
755
|
+
return undefined;
|
|
756
|
+
}
|
|
757
|
+
export function parseModelSpec(value) {
|
|
758
|
+
const trimmed = value.trim();
|
|
759
|
+
const slash = trimmed.indexOf("/");
|
|
760
|
+
if (slash <= 0 || slash === trimmed.length - 1)
|
|
761
|
+
return undefined;
|
|
762
|
+
const provider = trimmed.slice(0, slash).trim();
|
|
763
|
+
const modelId = trimmed.slice(slash + 1).trim();
|
|
764
|
+
if (!provider || !modelId)
|
|
765
|
+
return undefined;
|
|
766
|
+
return { provider, modelId };
|
|
767
|
+
}
|
|
768
|
+
function flagValue(api, name) {
|
|
769
|
+
return typeof api.getFlag === "function" ? api.getFlag(name) : undefined;
|
|
523
770
|
}
|
|
524
771
|
function parsePositiveInt(value) {
|
|
525
|
-
const
|
|
526
|
-
if (
|
|
772
|
+
const trimmed = value.trim();
|
|
773
|
+
if (!/^\d+$/.test(trimmed))
|
|
774
|
+
return undefined;
|
|
775
|
+
const parsed = Number(trimmed);
|
|
776
|
+
if (!Number.isSafeInteger(parsed) || parsed <= 0)
|
|
527
777
|
return undefined;
|
|
528
778
|
return parsed;
|
|
529
779
|
}
|