pi-ui-extend 1.0.11 → 1.0.13
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/dist/app/runtime.d.ts +8 -1
- package/dist/app/runtime.js +39 -0
- package/external/pi-tools-suite/src/coding-discipline/index.ts +106 -9
- package/external/pi-tools-suite/src/todo/index.ts +72 -20
- package/external/pi-tools-suite/src/todo/todo.ts +2 -1
- package/external/pi-tools-suite/src/todo/tool/types.ts +19 -2
- package/package.json +1 -1
package/dist/app/runtime.d.ts
CHANGED
|
@@ -46,6 +46,13 @@ type RuntimeSessionManagerModelState = Pick<SessionManager, "getEntries" | "getB
|
|
|
46
46
|
export declare function resolvePixRuntimeModelRef(options: Pick<AppOptions, "modelRef">, sessionManager: RuntimeSessionManagerModelState, config?: PixConfig): string | undefined;
|
|
47
47
|
export declare function resolvePixRuntimeInitialThinkingLevel(options: Pick<AppOptions, "modelRef">, sessionManager: RuntimeSessionManagerModelState, config: PixConfig): ThinkingLevel | undefined;
|
|
48
48
|
export declare function resolveSessionModelRefFromTail(entries: readonly SessionEntry[]): string | undefined;
|
|
49
|
-
|
|
49
|
+
/**
|
|
50
|
+
* pi-ai 0.84.2 predates GLM-5.3's final thinking contract. The released model
|
|
51
|
+
* is always-thinking and accepts exactly low/high/max via reasoning_effort.
|
|
52
|
+
* Patch the mutable catalog model before AgentSession clamps the selected level.
|
|
53
|
+
*/
|
|
54
|
+
export declare function patchGlm53ThinkingMetadata(model: unknown): boolean;
|
|
55
|
+
export declare function patchPixModelRuntimeCompatibility(modelRuntime: Pick<AgentSessionServices["modelRuntime"], "getModels">): number;
|
|
56
|
+
export declare function refreshPixModelRuntimeForStartup(modelRuntime: Pick<AgentSessionServices["modelRuntime"], "refresh" | "getModels">): Promise<void>;
|
|
50
57
|
export declare function createPixRuntime(options: AppOptions, runtimeOptions?: CreatePixRuntimeOptions): Promise<AgentSessionRuntime>;
|
|
51
58
|
export {};
|
package/dist/app/runtime.js
CHANGED
|
@@ -235,10 +235,49 @@ export function resolveSessionModelRefFromTail(entries) {
|
|
|
235
235
|
return undefined;
|
|
236
236
|
return thinkingLevel ? `${modelRef}:${thinkingLevel}` : modelRef;
|
|
237
237
|
}
|
|
238
|
+
const GLM_53_THINKING_LEVEL_MAP = {
|
|
239
|
+
off: null,
|
|
240
|
+
minimal: null,
|
|
241
|
+
low: "low",
|
|
242
|
+
medium: null,
|
|
243
|
+
high: "high",
|
|
244
|
+
xhigh: null,
|
|
245
|
+
max: "max",
|
|
246
|
+
};
|
|
247
|
+
/**
|
|
248
|
+
* pi-ai 0.84.2 predates GLM-5.3's final thinking contract. The released model
|
|
249
|
+
* is always-thinking and accepts exactly low/high/max via reasoning_effort.
|
|
250
|
+
* Patch the mutable catalog model before AgentSession clamps the selected level.
|
|
251
|
+
*/
|
|
252
|
+
export function patchGlm53ThinkingMetadata(model) {
|
|
253
|
+
if (!model || typeof model !== "object" || Array.isArray(model))
|
|
254
|
+
return false;
|
|
255
|
+
const candidate = model;
|
|
256
|
+
if (candidate.id !== "glm-5.3")
|
|
257
|
+
return false;
|
|
258
|
+
if (!candidate.compat || typeof candidate.compat !== "object" || Array.isArray(candidate.compat))
|
|
259
|
+
return false;
|
|
260
|
+
const compat = candidate.compat;
|
|
261
|
+
if (compat.thinkingFormat !== "zai")
|
|
262
|
+
return false;
|
|
263
|
+
candidate.reasoning = true;
|
|
264
|
+
candidate.thinkingLevelMap = { ...GLM_53_THINKING_LEVEL_MAP };
|
|
265
|
+
candidate.compat = { ...compat, supportsReasoningEffort: true };
|
|
266
|
+
return true;
|
|
267
|
+
}
|
|
268
|
+
export function patchPixModelRuntimeCompatibility(modelRuntime) {
|
|
269
|
+
let patched = 0;
|
|
270
|
+
for (const model of modelRuntime.getModels()) {
|
|
271
|
+
if (patchGlm53ThinkingMetadata(model))
|
|
272
|
+
patched += 1;
|
|
273
|
+
}
|
|
274
|
+
return patched;
|
|
275
|
+
}
|
|
238
276
|
export async function refreshPixModelRuntimeForStartup(modelRuntime) {
|
|
239
277
|
// Startup only needs the locally configured model catalog. Remote catalog
|
|
240
278
|
// refreshes belong to explicit model-management flows and must not block boot.
|
|
241
279
|
await modelRuntime.refresh({ allowNetwork: false });
|
|
280
|
+
patchPixModelRuntimeCompatibility(modelRuntime);
|
|
242
281
|
}
|
|
243
282
|
export async function createPixRuntime(options, runtimeOptions = {}) {
|
|
244
283
|
const agentDir = getAgentDir();
|
|
@@ -41,12 +41,24 @@ const DEFAULT_LOOKUP_MAX_IMAGES = 6;
|
|
|
41
41
|
const DEFAULT_LOOKUP_MAX_TOKENS = 1_600;
|
|
42
42
|
const DEFAULT_LOOKUP_TIMEOUT_MS = 120_000;
|
|
43
43
|
const MAX_IMAGE_BYTES = 16 * 1024 * 1024;
|
|
44
|
-
|
|
45
|
-
|
|
44
|
+
// Keep recovery nudges sparse: newer GLM coding models are substantially better at
|
|
45
|
+
// long-horizon tool use, and repeated developer reminders can become prompt noise.
|
|
46
|
+
const SILENCE_REMINDER_MIN_VIOLATION_GAP = 5;
|
|
47
|
+
const SILENCE_REMINDER_MIN_MESSAGE_GAP = 20;
|
|
46
48
|
// When the visible history shrinks by more than this many messages (compaction or
|
|
47
49
|
// truncation), the chatter baseline is reset so it isn't measured against a stale peak.
|
|
48
50
|
const SILENCE_REMINDER_COMPACTION_MARGIN = 8;
|
|
49
51
|
const LOOKUP_TOOL_NAME = "lookup";
|
|
52
|
+
const GLM_53_THINKING_LEVEL_MAP = {
|
|
53
|
+
off: null,
|
|
54
|
+
minimal: null,
|
|
55
|
+
low: "low",
|
|
56
|
+
medium: null,
|
|
57
|
+
high: "high",
|
|
58
|
+
xhigh: null,
|
|
59
|
+
max: "max",
|
|
60
|
+
} as const;
|
|
61
|
+
const GLM_53_THINKING_LEVELS = ["low", "high", "max"] as const;
|
|
50
62
|
|
|
51
63
|
const LOOKUP_TOOL_PARAMS = Type.Object(
|
|
52
64
|
{
|
|
@@ -89,7 +101,9 @@ function buildDisciplineLines(strictness: CodingDisciplineStrictness): readonly
|
|
|
89
101
|
isStrict ? "- WORKING: exactly one next tool call with empty text;" : "- WORKING: one or more independent tool calls with empty text;",
|
|
90
102
|
"- FINAL: one final answer after completion or practical verification;",
|
|
91
103
|
"- BLOCKED: one concise question only when no safe/useful tool action can continue.",
|
|
92
|
-
|
|
104
|
+
isStrict
|
|
105
|
+
? "No transition permits commentary between tool calls."
|
|
106
|
+
: "When a thinking/reasoning channel is available, do not duplicate that reasoning as visible commentary between tool calls.",
|
|
93
107
|
"",
|
|
94
108
|
"PRIORITY: this overrides default assistant friendliness and conversational behavior.",
|
|
95
109
|
"",
|
|
@@ -107,7 +121,7 @@ function buildDisciplineLines(strictness: CodingDisciplineStrictness): readonly
|
|
|
107
121
|
const LOOKUP_DISCIPLINE_LINES = [
|
|
108
122
|
"",
|
|
109
123
|
"Visual lookup discipline:",
|
|
110
|
-
"
|
|
124
|
+
"Treat the current GLM coding endpoint as text-only; do not assume it can inspect images/screenshots directly.",
|
|
111
125
|
"When the user refers to an image, screenshot, UI visual bug, layout problem, arrow, annotation, highlight, visible text, chart, diagram, or any visual evidence, call the `lookup` tool before making visual claims.",
|
|
112
126
|
"Use a focused lookup query that includes the user's visual concern and asks the helper to pay attention to arrows, annotations, highlights, visible UI state, text, spacing/layout, and suspected bugs.",
|
|
113
127
|
"Treat lookup output as visual evidence; do not invent visual details beyond it.",
|
|
@@ -141,7 +155,8 @@ const DISCIPLINE_PROMPT_BLOCK_PATTERN = new RegExp(
|
|
|
141
155
|
* Strips pi's built-in "Pi documentation" reference block from the system prompt.
|
|
142
156
|
* That block (≈10 lines listing docs/examples paths and "when asked about X" routing)
|
|
143
157
|
* is useless dead weight for non-pi work and dilutes attention to the trailing
|
|
144
|
-
* <available_skills> section
|
|
158
|
+
* <available_skills> section and wastes attention that should stay on the task and
|
|
159
|
+
* the extension-provided agent contract.
|
|
145
160
|
* Anchored on the fixed header/footer strings from buildSystemPrompt() so it only
|
|
146
161
|
* ever matches pi's own block regardless of resolved doc/example paths.
|
|
147
162
|
*/
|
|
@@ -151,7 +166,7 @@ const PI_DOCS_BLOCK_PATTERN = new RegExp(
|
|
|
151
166
|
);
|
|
152
167
|
|
|
153
168
|
const LOOKUP_SYSTEM_PROMPT = [
|
|
154
|
-
"You are a vision-capable lookup helper for a
|
|
169
|
+
"You are a vision-capable lookup helper for a text-only GLM coding agent.",
|
|
155
170
|
"Inspect the provided screenshots/images and answer the parent agent's focused question using concrete visual evidence.",
|
|
156
171
|
"Pay special attention to arrows, annotations, highlights, cursor position, visible UI state, text, layout, spacing, colors, overlays, visual bugs, and error messages.",
|
|
157
172
|
"Use recent session context only to understand what the user is asking; do not perform code changes.",
|
|
@@ -198,13 +213,17 @@ export default function codingDiscipline(pi: ExtensionAPI) {
|
|
|
198
213
|
maybeRegisterLookupTool(process.cwd());
|
|
199
214
|
|
|
200
215
|
pi.on("session_start", async (_event: unknown, ctx: unknown) => {
|
|
216
|
+
patchGlm53ThinkingModels(ctx);
|
|
201
217
|
selectedModelRef = modelRefFromContext(ctx);
|
|
218
|
+
normalizeCurrentGlm53ThinkingLevel(pi, selectedModelRef);
|
|
202
219
|
maybeRegisterLookupTool(contextCwd(ctx));
|
|
203
220
|
syncLookupToolAvailability(selectedModelRef, contextCwd(ctx));
|
|
204
221
|
});
|
|
205
222
|
|
|
206
223
|
pi.on("model_select", async (event: { model?: unknown }, ctx: unknown) => {
|
|
224
|
+
patchGlm53ThinkingModels(ctx, event.model);
|
|
207
225
|
selectedModelRef = modelRefFromModel(event.model) ?? modelRefFromContext(ctx);
|
|
226
|
+
normalizeCurrentGlm53ThinkingLevel(pi, selectedModelRef);
|
|
208
227
|
maybeRegisterLookupTool(contextCwd(ctx));
|
|
209
228
|
syncLookupToolAvailability(selectedModelRef, contextCwd(ctx));
|
|
210
229
|
});
|
|
@@ -217,10 +236,11 @@ export default function codingDiscipline(pi: ExtensionAPI) {
|
|
|
217
236
|
lookupEnabled: Boolean(lookupModelFromConfig(cwd)),
|
|
218
237
|
strictness: codingDisciplineStrictnessFromConfig(cwd),
|
|
219
238
|
});
|
|
239
|
+
const corrected = applyGlm53ThinkingToPayload(injected, modelRef, ctx, pi);
|
|
220
240
|
if (process.env.PI_DEBUG_PROMPT === "1") {
|
|
221
|
-
logFinalPrompt(
|
|
241
|
+
logFinalPrompt(corrected, modelRef, contextCwd(ctx) ?? process.cwd());
|
|
222
242
|
}
|
|
223
|
-
return
|
|
243
|
+
return corrected;
|
|
224
244
|
});
|
|
225
245
|
|
|
226
246
|
pi.on("before_agent_start", async (event: { systemPromptOptions?: unknown; systemPrompt?: string }, ctx: unknown) => {
|
|
@@ -345,6 +365,83 @@ export function isGlmModel(modelRef: string | undefined): boolean {
|
|
|
345
365
|
return /(?:^|[/:_.-])glm(?:$|[/:_.-]|\d)/i.test(modelRef);
|
|
346
366
|
}
|
|
347
367
|
|
|
368
|
+
function isGlm53ModelRef(modelRef: string | undefined): boolean {
|
|
369
|
+
if (!modelRef) return false;
|
|
370
|
+
return /(?:^|\/)glm-5\.3(?:$|:)/i.test(modelRef) || /^glm-5\.3(?:$|:)/i.test(modelRef);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
function patchGlm53ThinkingModel(model: unknown): boolean {
|
|
374
|
+
if (!isRecord(model)) return false;
|
|
375
|
+
const id = typeof model.id === "string"
|
|
376
|
+
? model.id
|
|
377
|
+
: typeof model.modelId === "string"
|
|
378
|
+
? model.modelId
|
|
379
|
+
: undefined;
|
|
380
|
+
if (id !== "glm-5.3") return false;
|
|
381
|
+
const provider = typeof model.provider === "string" ? model.provider : undefined;
|
|
382
|
+
const compat = isRecord(model.compat) ? model.compat : {};
|
|
383
|
+
if (provider !== "zai" && provider !== "zai-coding-cn" && compat.thinkingFormat !== "zai") return false;
|
|
384
|
+
|
|
385
|
+
model.reasoning = true;
|
|
386
|
+
model.thinkingLevelMap = { ...GLM_53_THINKING_LEVEL_MAP };
|
|
387
|
+
model.compat = { ...compat, supportsReasoningEffort: true };
|
|
388
|
+
return true;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
function patchGlm53ThinkingModels(ctx: unknown, selectedModel?: unknown): void {
|
|
392
|
+
patchGlm53ThinkingModel(selectedModel);
|
|
393
|
+
if (!isRecord(ctx)) return;
|
|
394
|
+
patchGlm53ThinkingModel(ctx.model);
|
|
395
|
+
|
|
396
|
+
if (Array.isArray(ctx.scopedModels)) {
|
|
397
|
+
for (const entry of ctx.scopedModels) {
|
|
398
|
+
if (isRecord(entry)) patchGlm53ThinkingModel(entry.model);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
const registry = ctx.modelRegistry;
|
|
403
|
+
if (!isRecord(registry) || typeof registry.getAll !== "function") return;
|
|
404
|
+
try {
|
|
405
|
+
const models = registry.getAll();
|
|
406
|
+
if (Array.isArray(models)) {
|
|
407
|
+
for (const model of models) patchGlm53ThinkingModel(model);
|
|
408
|
+
}
|
|
409
|
+
} catch {
|
|
410
|
+
// Compatibility patching must never break session startup.
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
function normalizeGlm53ThinkingLevel(level: unknown): (typeof GLM_53_THINKING_LEVELS)[number] {
|
|
415
|
+
if (level === "low" || level === "high" || level === "max") return level;
|
|
416
|
+
if (level === "off" || level === "minimal") return "low";
|
|
417
|
+
if (level === "medium") return "high";
|
|
418
|
+
return "max";
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
function normalizeCurrentGlm53ThinkingLevel(pi: ExtensionAPI, modelRef: string | undefined): void {
|
|
422
|
+
if (!isGlm53ModelRef(modelRef)) return;
|
|
423
|
+
const getter = (pi as { getThinkingLevel?: () => unknown }).getThinkingLevel;
|
|
424
|
+
const setter = (pi as { setThinkingLevel?: (level: string) => void }).setThinkingLevel;
|
|
425
|
+
if (!getter || !setter) return;
|
|
426
|
+
const current = getter.call(pi);
|
|
427
|
+
const normalized = normalizeGlm53ThinkingLevel(current);
|
|
428
|
+
if (current !== normalized) setter.call(pi, normalized);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
function applyGlm53ThinkingToPayload(payload: unknown, modelRef: string | undefined, ctx: unknown, pi: ExtensionAPI): unknown {
|
|
432
|
+
if (!isGlm53ModelRef(modelRef) || !isRecord(payload)) return payload;
|
|
433
|
+
const getter = (pi as { getThinkingLevel?: () => unknown }).getThinkingLevel;
|
|
434
|
+
const runtimeLevel = getter ? getter.call(pi) : undefined;
|
|
435
|
+
const contextLevel = isRecord(ctx) ? ctx.thinkingLevel : undefined;
|
|
436
|
+
const effort = normalizeGlm53ThinkingLevel(runtimeLevel ?? contextLevel);
|
|
437
|
+
const existingThinking = isRecord(payload.thinking) ? payload.thinking : {};
|
|
438
|
+
return {
|
|
439
|
+
...payload,
|
|
440
|
+
thinking: { ...existingThinking, type: "enabled", clear_thinking: false },
|
|
441
|
+
reasoning_effort: effort,
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
|
|
348
445
|
export function injectCodingDisciplineIntoPayload(payload: unknown, options: DisciplinePromptOptions = {}): unknown {
|
|
349
446
|
if (!isRecord(payload)) return payload;
|
|
350
447
|
|
|
@@ -676,7 +773,7 @@ function codingDisciplineStrictnessFromConfig(cwd?: string): CodingDisciplineStr
|
|
|
676
773
|
|
|
677
774
|
function buildLookupPrompt(params: LookupParams, recentContext: string, imageCount: number, warnings: string[]): string {
|
|
678
775
|
return [
|
|
679
|
-
"Lookup request from a
|
|
776
|
+
"Lookup request from a text-only GLM parent model.",
|
|
680
777
|
"",
|
|
681
778
|
"Focused question:",
|
|
682
779
|
params.query.trim(),
|
|
@@ -8,7 +8,13 @@ import { ACTIVE_STATUSES, isTaskBlocked, selectVisibleTasks } from "./state/sele
|
|
|
8
8
|
import { applyTaskMutation } from "./state/state-reducer.js";
|
|
9
9
|
import { getState, replaceState } from "./state/store.js";
|
|
10
10
|
import { activateTodoStateScope, DEFAULT_PROMPT_GUIDELINES, DEFAULT_PROMPT_SNIPPET, publishTodoState, registerTodosCommand, registerTodoTool } from "./todo.js";
|
|
11
|
-
import
|
|
11
|
+
import {
|
|
12
|
+
TODO_THINKING_LEVEL_VALUES,
|
|
13
|
+
todoParamsSchemaForThinkingLevels,
|
|
14
|
+
type Task,
|
|
15
|
+
type TaskMutationParams,
|
|
16
|
+
type TodoThinkingLevel,
|
|
17
|
+
} from "./tool/types.js";
|
|
12
18
|
|
|
13
19
|
type AgentMessageLike = { role?: unknown; stopReason?: unknown; content?: unknown };
|
|
14
20
|
|
|
@@ -17,39 +23,73 @@ const TODO_NUDGE_INITIAL_DELAY_MS = 0;
|
|
|
17
23
|
const TODO_NUDGE_IDLE_RETRY_DELAY_MS = 100;
|
|
18
24
|
const TODO_NUDGE_MAX_IDLE_ATTEMPTS = 40;
|
|
19
25
|
const ASK_USER_TOOL_NAMES = new Set(["ask_user", "ask_user_question", "question"]);
|
|
20
|
-
const TODO_THINKING_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh", "max"] as const;
|
|
21
26
|
const TODO_THINKING_RESTORE_METADATA_KEY = "__piTodoRestoreThinking";
|
|
22
27
|
|
|
23
28
|
function isStaleExtensionContextError(error: unknown): boolean {
|
|
24
29
|
return error instanceof Error && /ctx is stale|stale ctx|stale after session replacement|stale after.*reload/i.test(error.message);
|
|
25
30
|
}
|
|
26
31
|
|
|
27
|
-
type
|
|
28
|
-
|
|
32
|
+
type ModelLike = {
|
|
33
|
+
provider?: string;
|
|
34
|
+
id?: string;
|
|
35
|
+
modelId?: string;
|
|
36
|
+
reasoning?: boolean;
|
|
37
|
+
thinkingLevelMap?: Partial<Record<TodoThinkingLevel, unknown | null>>;
|
|
38
|
+
compat?: { thinkingFormat?: unknown };
|
|
39
|
+
};
|
|
29
40
|
|
|
30
41
|
function isTodoThinkingLevel(value: unknown): value is TodoThinkingLevel {
|
|
31
|
-
return
|
|
42
|
+
return TODO_THINKING_LEVEL_VALUES.includes(value as TodoThinkingLevel);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function isGlm53TodoModel(model: ModelLike | undefined): boolean {
|
|
46
|
+
if (!model) return false;
|
|
47
|
+
const id = model.modelId ?? model.id;
|
|
48
|
+
if (id !== "glm-5.3") return false;
|
|
49
|
+
return model.provider === "zai" || model.provider === "zai-coding-cn" || model.compat?.thinkingFormat === "zai";
|
|
32
50
|
}
|
|
33
51
|
|
|
34
52
|
function getAvailableTodoThinkingLevels(model: unknown): TodoThinkingLevel[] {
|
|
35
53
|
const m = model as ModelLike | undefined;
|
|
36
54
|
if (!m?.reasoning) return ["off"];
|
|
55
|
+
if (isGlm53TodoModel(m)) return ["low", "high", "max"];
|
|
37
56
|
const map = m.thinkingLevelMap;
|
|
38
|
-
return
|
|
57
|
+
return TODO_THINKING_LEVEL_VALUES.filter((level) => {
|
|
58
|
+
const mapped = map?.[level];
|
|
59
|
+
if (mapped === null) return false;
|
|
60
|
+
if (level === "xhigh" || level === "max") return mapped !== undefined;
|
|
61
|
+
return true;
|
|
62
|
+
});
|
|
39
63
|
}
|
|
40
64
|
|
|
41
65
|
function buildThinkingPromptParts(model: unknown): { promptSnippet?: string; promptGuidelines?: string[] } {
|
|
42
66
|
const levels = getAvailableTodoThinkingLevels(model);
|
|
43
67
|
if (levels.length <= 1) return {};
|
|
68
|
+
const lowEffortWording = levels.includes("off") ? "lower/off" : "lower";
|
|
44
69
|
return {
|
|
45
70
|
promptSnippet: `${DEFAULT_PROMPT_SNIPPET} Set per-item thinking: ${levels.join("|")}.`.trim(),
|
|
46
71
|
promptGuidelines: [
|
|
47
72
|
...DEFAULT_PROMPT_GUIDELINES,
|
|
48
|
-
`If todoThinking is enabled, set \`thinking\` on every planned task during create/batch_create (or update); choose from ${levels.join(", ")}. Use higher thinking for investigation, hard debugging, risky edits, or review; use
|
|
73
|
+
`If todoThinking is enabled, set \`thinking\` on every planned task during create/batch_create (or update); choose from ${levels.join(", ")}. Use higher thinking for investigation, hard debugging, risky edits, or review; use ${lowEffortWording} for mechanical steps and the final report. Never leave it unset in a non-trivial plan.`,
|
|
49
74
|
],
|
|
50
75
|
};
|
|
51
76
|
}
|
|
52
77
|
|
|
78
|
+
function normalizeTodoThinkingLevelForModel(model: unknown, level: TodoThinkingLevel): TodoThinkingLevel {
|
|
79
|
+
const available = getAvailableTodoThinkingLevels(model);
|
|
80
|
+
if (available.includes(level)) return level;
|
|
81
|
+
const requestedIndex = TODO_THINKING_LEVEL_VALUES.indexOf(level);
|
|
82
|
+
for (let index = requestedIndex; index < TODO_THINKING_LEVEL_VALUES.length; index += 1) {
|
|
83
|
+
const candidate = TODO_THINKING_LEVEL_VALUES[index];
|
|
84
|
+
if (candidate && available.includes(candidate)) return candidate;
|
|
85
|
+
}
|
|
86
|
+
for (let index = requestedIndex - 1; index >= 0; index -= 1) {
|
|
87
|
+
const candidate = TODO_THINKING_LEVEL_VALUES[index];
|
|
88
|
+
if (candidate && available.includes(candidate)) return candidate;
|
|
89
|
+
}
|
|
90
|
+
return available[0] ?? "off";
|
|
91
|
+
}
|
|
92
|
+
|
|
53
93
|
function isAskUserToolName(toolName: string): boolean {
|
|
54
94
|
return ASK_USER_TOOL_NAMES.has(toolName);
|
|
55
95
|
}
|
|
@@ -140,13 +180,15 @@ export default function (pi: ExtensionAPI) {
|
|
|
140
180
|
let settledNudgeEligible = false;
|
|
141
181
|
|
|
142
182
|
function registerTodoToolWithCurrentPrompt(): void {
|
|
183
|
+
const availableThinkingLevels = todoThinkingEnabled ? getAvailableTodoThinkingLevels(currentModel) : undefined;
|
|
143
184
|
const thinkingPrompt = todoThinkingEnabled ? buildThinkingPromptParts(currentModel) : {};
|
|
144
185
|
registerTodoTool(pi, {
|
|
145
186
|
...thinkingPrompt,
|
|
187
|
+
...(availableThinkingLevels ? { parameters: todoParamsSchemaForThinkingLevels(availableThinkingLevels) } : {}),
|
|
146
188
|
prepareMutation: (state, _ctx, info) => {
|
|
147
189
|
if (!todoThinkingEnabled) return info.params;
|
|
148
|
-
if (info.action === "update") return prepareTodoThinkingMutation(state, info.params);
|
|
149
|
-
if (info.action === "batch_update") {
|
|
190
|
+
if (info.action === "create" || info.action === "update") return prepareTodoThinkingMutation(state, info.params);
|
|
191
|
+
if (info.action === "batch_create" || info.action === "batch_update") {
|
|
150
192
|
return {
|
|
151
193
|
...info.params,
|
|
152
194
|
items: (info.params.items ?? []).map((item) => prepareTodoThinkingMutation(state, item)),
|
|
@@ -194,20 +236,29 @@ export default function (pi: ExtensionAPI) {
|
|
|
194
236
|
}
|
|
195
237
|
|
|
196
238
|
function prepareTodoThinkingMutation(state: ReturnType<typeof getState>, params: TaskMutationParams): TaskMutationParams {
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
239
|
+
let nextParams = params;
|
|
240
|
+
if (params.thinking !== undefined) {
|
|
241
|
+
const normalized = normalizeTodoThinkingLevelForModel(currentModel, params.thinking);
|
|
242
|
+
if (normalized !== params.thinking) nextParams = { ...nextParams, thinking: normalized };
|
|
243
|
+
}
|
|
244
|
+
if (nextParams.id === undefined) return nextParams;
|
|
245
|
+
const current = state.tasks.find((task) => task.id === nextParams.id);
|
|
246
|
+
if (!current) return nextParams;
|
|
247
|
+
const nextStatus = nextParams.status ?? current.status;
|
|
248
|
+
if (nextStatus === "in_progress" && nextParams.thinking === undefined && current.thinking !== undefined) {
|
|
249
|
+
const normalized = normalizeTodoThinkingLevelForModel(currentModel, current.thinking);
|
|
250
|
+
if (normalized !== current.thinking) nextParams = { ...nextParams, thinking: normalized };
|
|
251
|
+
}
|
|
252
|
+
const nextThinking = nextParams.thinking ?? current.thinking;
|
|
202
253
|
const shouldCapturePreviousThinking =
|
|
203
|
-
nextStatus === "in_progress" && nextThinking !== undefined && (current.status !== "in_progress" ||
|
|
204
|
-
if (!shouldCapturePreviousThinking) return
|
|
254
|
+
nextStatus === "in_progress" && nextThinking !== undefined && (current.status !== "in_progress" || nextParams.thinking !== undefined);
|
|
255
|
+
if (!shouldCapturePreviousThinking) return nextParams;
|
|
205
256
|
const currentThinking = getCurrentThinkingLevel();
|
|
206
|
-
if (!currentThinking) return
|
|
257
|
+
if (!currentThinking) return nextParams;
|
|
207
258
|
return {
|
|
208
|
-
...
|
|
259
|
+
...nextParams,
|
|
209
260
|
metadata: {
|
|
210
|
-
...(
|
|
261
|
+
...(nextParams.metadata ?? {}),
|
|
211
262
|
[TODO_THINKING_RESTORE_METADATA_KEY]: currentThinking,
|
|
212
263
|
},
|
|
213
264
|
};
|
|
@@ -233,7 +284,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
233
284
|
const previous = getRememberedThinking(taskId, state);
|
|
234
285
|
if (!previous) return;
|
|
235
286
|
rememberedThinkingByTaskId.delete(taskId);
|
|
236
|
-
|
|
287
|
+
const restored = normalizeTodoThinkingLevelForModel(currentModel, previous);
|
|
288
|
+
if (getCurrentThinkingLevel() !== restored) setTodoThinkingLevel(restored);
|
|
237
289
|
}
|
|
238
290
|
|
|
239
291
|
function restoreInactiveTodoThinking(state: ReturnType<typeof getState>): void {
|
|
@@ -91,6 +91,7 @@ interface TodoToolHooks {
|
|
|
91
91
|
interface TodoToolRegistrationOptions extends TodoToolHooks {
|
|
92
92
|
promptSnippet?: string;
|
|
93
93
|
promptGuidelines?: string[];
|
|
94
|
+
parameters?: typeof TodoParamsSchema;
|
|
94
95
|
}
|
|
95
96
|
|
|
96
97
|
type TodoStateEventContext = { sessionManager?: { getSessionFile?: () => unknown; getSessionId?: () => unknown } };
|
|
@@ -391,7 +392,7 @@ export function registerTodoTool(pi: ExtensionAPI, hooks: TodoToolRegistrationOp
|
|
|
391
392
|
label: TOOL_LABEL,
|
|
392
393
|
promptSnippet: hooks.promptSnippet ?? DEFAULT_PROMPT_SNIPPET,
|
|
393
394
|
promptGuidelines: hooks.promptGuidelines ?? DEFAULT_PROMPT_GUIDELINES,
|
|
394
|
-
parameters: TodoParamsSchema,
|
|
395
|
+
parameters: hooks.parameters ?? TodoParamsSchema,
|
|
395
396
|
|
|
396
397
|
async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
|
|
397
398
|
activateTodoStateScope(_ctx);
|
|
@@ -24,7 +24,8 @@ export const MSG_NO_TODOS = "No todos yet. Ask the agent to add some!";
|
|
|
24
24
|
// ---------------------------------------------------------------------------
|
|
25
25
|
|
|
26
26
|
export type TaskStatus = "pending" | "in_progress" | "deferred" | "completed" | "deleted";
|
|
27
|
-
export
|
|
27
|
+
export const TODO_THINKING_LEVEL_VALUES = ["off", "minimal", "low", "medium", "high", "xhigh", "max"] as const;
|
|
28
|
+
export type TodoThinkingLevel = (typeof TODO_THINKING_LEVEL_VALUES)[number];
|
|
28
29
|
|
|
29
30
|
export type TaskAction = "create" | "update" | "batch_create" | "batch_update" | "list" | "get" | "delete" | "clear" | "export" | "import";
|
|
30
31
|
|
|
@@ -104,7 +105,7 @@ export const TodoParamsSchema = Type.Object({
|
|
|
104
105
|
}),
|
|
105
106
|
),
|
|
106
107
|
thinking: Type.Optional(
|
|
107
|
-
StringEnum(
|
|
108
|
+
StringEnum(TODO_THINKING_LEVEL_VALUES, {
|
|
108
109
|
description: "Per-task thinking level used when todoThinking is enabled and this task is in_progress",
|
|
109
110
|
}),
|
|
110
111
|
),
|
|
@@ -164,4 +165,20 @@ export const TodoParamsSchema = Type.Object({
|
|
|
164
165
|
replace: Type.Optional(Type.Boolean({ description: "For import/create/batch_create, replace existing tasks instead of appending. Use batch_create with replace:true when starting a new plan that supersedes old unfinished todos. Default: false." })),
|
|
165
166
|
});
|
|
166
167
|
|
|
168
|
+
export function todoParamsSchemaForThinkingLevels(levels: readonly TodoThinkingLevel[]): typeof TodoParamsSchema {
|
|
169
|
+
const supported = TODO_THINKING_LEVEL_VALUES.filter((level) => levels.includes(level));
|
|
170
|
+
const effective = supported.length > 0 ? supported : ["off"];
|
|
171
|
+
return {
|
|
172
|
+
...TodoParamsSchema,
|
|
173
|
+
properties: {
|
|
174
|
+
...TodoParamsSchema.properties,
|
|
175
|
+
thinking: Type.Optional(
|
|
176
|
+
StringEnum(effective as unknown as typeof TODO_THINKING_LEVEL_VALUES, {
|
|
177
|
+
description: "Per-task thinking level used when todoThinking is enabled and this task is in_progress",
|
|
178
|
+
}),
|
|
179
|
+
),
|
|
180
|
+
},
|
|
181
|
+
} as typeof TodoParamsSchema;
|
|
182
|
+
}
|
|
183
|
+
|
|
167
184
|
export type TodoParams = Static<typeof TodoParamsSchema>;
|
package/package.json
CHANGED