pi-ultracode 0.1.0 → 0.1.2

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/src/mode.ts CHANGED
@@ -2,7 +2,7 @@
2
2
  * Ultracode mode controller.
3
3
  *
4
4
  * Ultracode is a session-scoped effort mode. While on, it:
5
- * - raises the thinking level to xhigh (remembering the previous level),
5
+ * - raises the thinking level to the model's maximum (remembering the previous level),
6
6
  * - keeps the `workflow` tool active,
7
7
  * - injects a standing "author and run a workflow by default" system block on
8
8
  * every turn, plus an optional token budget,
@@ -12,8 +12,14 @@
12
12
 
13
13
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
14
14
  import { ULTRACODE_ACTIVE_REMINDER, ULTRACODE_TAGLINE, ultracodeSystemBlock } from "./prompts.ts";
15
+ import {
16
+ LEGACY_ULTRACODE_THINKING_LEVEL,
17
+ ULTRACODE_THINKING_LEVEL,
18
+ isThinkingLevel,
19
+ type ThinkingLevel,
20
+ } from "./thinking.ts";
15
21
 
16
- export type ThinkingLevel = "off" | "minimal" | "low" | "medium" | "high" | "xhigh";
22
+ export type { ThinkingLevel } from "./thinking.ts";
17
23
 
18
24
  export const MODE_ENTRY_TYPE = "ultracode-mode";
19
25
 
@@ -21,20 +27,76 @@ interface PersistedModeState {
21
27
  enabled: boolean;
22
28
  budgetTotal: number | null;
23
29
  previousThinking?: ThinkingLevel;
30
+ /** `null` records that the setting was originally absent (Pi defaults to medium). */
31
+ previousDefaultThinking?: ThinkingLevel | null;
32
+ /** Deferred when the selected model cannot represent the pre-mode level. */
33
+ pendingPreviousThinking?: ThinkingLevel;
34
+ }
35
+
36
+ export interface ThinkingPreferenceSnapshot {
37
+ /** Raw global value; undefined means Pi's implicit medium default. */
38
+ global: ThinkingLevel | undefined;
39
+ /** Global + project merged value used by Pi for model switches. */
40
+ effective: ThinkingLevel | undefined;
41
+ }
42
+
43
+ /** Adapter for preserving Pi's raw global effort preference while this mode is active. */
44
+ export interface ThinkingPreferenceStore {
45
+ getThinkingPreference(): ThinkingPreferenceSnapshot;
46
+ setDefaultThinkingLevel(level: ThinkingLevel | undefined): void | Promise<void>;
47
+ flush?(): Promise<void>;
24
48
  }
25
49
 
26
50
  export class UltracodeMode {
27
51
  private enabled = false;
52
+ private suspended = false;
28
53
  private budgetTotal: number | null = null;
29
54
  private previousThinking: ThinkingLevel | undefined;
30
- /** The level pi actually applied after clamping "xhigh" to the model's capability. */
55
+ private previousDefaultThinking: ThinkingLevel | null | undefined;
56
+ /** Restore a level later if the current non-reasoning model clamps it to off. */
57
+ private pendingPreviousThinking: ThinkingLevel | undefined;
58
+ /** The level Pi actually applied after clamping the maximum request. */
31
59
  private appliedThinking: ThinkingLevel | undefined;
60
+ /** Prevent mode-owned thinking changes from being mistaken for manual overrides. */
61
+ private applyingThinking = false;
62
+ private thinkingPreferenceStore: ThinkingPreferenceStore | undefined;
63
+ private preferenceWriteQueue: Promise<void> = Promise.resolve();
64
+ private preferenceWriteGeneration = 0;
65
+ private legacyDefaultMigrationPending = false;
66
+ private pendingClearGeneration = 0;
67
+ private currentModelSupportsThinking: boolean | undefined;
68
+ private runtimeSupportsMaxThinking = true;
32
69
  private readonly workflowToolName: string;
33
70
 
34
71
  constructor(workflowToolName: string) {
35
72
  this.workflowToolName = workflowToolName;
36
73
  }
37
74
 
75
+ /** Bind the settings adapter once the session cwd is known. */
76
+ bindThinkingPreferenceStore(store: ThinkingPreferenceStore | undefined): void {
77
+ this.preferenceWriteGeneration++;
78
+ this.thinkingPreferenceStore = store;
79
+ }
80
+
81
+ /** Track capability explicitly; `off` alone cannot distinguish clamp from intent. */
82
+ setCurrentModelSupportsThinking(supportsThinking: boolean | undefined): void {
83
+ this.currentModelSupportsThinking = supportsThinking;
84
+ }
85
+
86
+ /** Configure migration behavior for Pi versions released before `max`. */
87
+ setRuntimeSupportsMaxThinking(supportsMaxThinking: boolean): void {
88
+ this.runtimeSupportsMaxThinking = supportsMaxThinking;
89
+ }
90
+
91
+ /** Wait for queued preference restoration before teardown or command completion. */
92
+ async flushThinkingPreference(): Promise<void> {
93
+ try {
94
+ await this.preferenceWriteQueue;
95
+ } catch {
96
+ // Pi owns settings error reporting; effort enforcement must remain usable.
97
+ }
98
+ }
99
+
38
100
  /** Enable if off, disable if on. Returns the new enabled state. */
39
101
  toggle(pi: ExtensionAPI, opts: { budget?: number | null } = {}): boolean {
40
102
  if (this.enabled) {
@@ -45,29 +107,100 @@ export class UltracodeMode {
45
107
  return true;
46
108
  }
47
109
 
48
- /** The thinking level pi actually applied (xhigh, or clamped down for models that lack it). */
110
+ /** The thinking level Pi actually applied (`max`, or the model/runtime fallback). */
49
111
  getAppliedThinking(): ThinkingLevel | undefined {
50
112
  return this.appliedThinking;
51
113
  }
52
114
 
53
- /** The effort level to forward to workflow subagents: xhigh when ultracode is
54
- * on, so each subagent's own session clamps it to THAT subagent model's max
55
- * (mirroring the parent's "request xhigh, clamp per model" contract). This
56
- * keeps subagents at their respective model's max even when they run a
57
- * different model than the parent. Undefined when off, so subagents fall back
58
- * to the session default. */
115
+ /**
116
+ * Return the raw maximum request, not the parent's applied value, so every
117
+ * workflow subagent is clamped independently against its own model.
118
+ */
59
119
  getSubagentThinkingLevel(): ThinkingLevel | undefined {
60
- return this.enabled ? "xhigh" : undefined;
120
+ return this.isEnforcing() ? ULTRACODE_THINKING_LEVEL : undefined;
121
+ }
122
+
123
+ /** Reassert the maximum before a turn or after a model change. */
124
+ reapplyMaximumThinking(pi: ExtensionAPI): boolean {
125
+ if (!this.isEnforcing()) return false;
126
+ this.applyUltracodeThinking(pi);
127
+ return true;
128
+ }
129
+
130
+ /**
131
+ * Handle model switches both while active and after a clamped restoration.
132
+ * Returns true when Ultracode remains active and the UI should be refreshed.
133
+ */
134
+ handleModelSelect(pi: ExtensionAPI): boolean {
135
+ if (this.suspended) return false;
136
+ this.pendingClearGeneration++;
137
+ if (this.enabled) {
138
+ this.applyUltracodeThinking(pi);
139
+ return true;
140
+ }
141
+ if (this.pendingPreviousThinking) {
142
+ const pending = this.pendingPreviousThinking;
143
+ this.applyCompatibleThinking(pi, pending);
144
+ if (this.pendingRestoreSucceeded(pending)) this.pendingPreviousThinking = undefined;
145
+ this.persist(pi);
146
+ }
147
+ return false;
148
+ }
149
+
150
+ /** Restore the pre-mode effective effort without changing persisted mode state. */
151
+ restorePreviousThinking(pi: ExtensionAPI): void {
152
+ if (this.enabled && this.previousThinking) this.applyCompatibleThinking(pi, this.previousThinking);
153
+ }
154
+
155
+ /** Stop enforcing synchronously, then restore effort before session teardown. */
156
+ suspend(pi: ExtensionAPI): void {
157
+ if (this.suspended) {
158
+ this.syncWorkflowTool(pi);
159
+ return;
160
+ }
161
+ this.suspended = true;
162
+ this.restorePreviousThinking(pi);
163
+ this.syncWorkflowTool(pi);
164
+ }
165
+
166
+ /**
167
+ * Enforce the mode after an external thinking-level selection. Stale events
168
+ * and events emitted by this mode are ignored to avoid recursive updates.
169
+ * Returns true when a manual selection was overridden.
170
+ */
171
+ handleThinkingLevelSelect(pi: ExtensionAPI, level: ThinkingLevel): boolean {
172
+ if (this.suspended || this.applyingThinking) return false;
173
+ const current = safeGetThinking(pi);
174
+ if (!current || current !== level) return false;
175
+ if (!this.enabled) {
176
+ // Pi emits the same event for a user selection and an automatic model
177
+ // re-clamp. Defer clearing until model_select has had a chance to consume it.
178
+ if (this.pendingPreviousThinking) this.deferPendingClear(pi, level);
179
+ return false;
180
+ }
181
+ if (current === this.appliedThinking) return false;
182
+ this.applyUltracodeThinking(pi);
183
+ return true;
61
184
  }
62
185
 
63
186
  isEnabled(): boolean {
64
187
  return this.enabled;
65
188
  }
66
189
 
190
+ isSuspended(): boolean {
191
+ return this.suspended;
192
+ }
193
+
67
194
  getBudget(): number | null {
68
195
  return this.budgetTotal;
69
196
  }
70
197
 
198
+ /** Keep tool availability aligned with the current mode state. */
199
+ syncWorkflowTool(pi: ExtensionAPI): void {
200
+ if (this.isEnforcing()) this.activateWorkflowTool(pi);
201
+ else this.deactivateWorkflowTool(pi);
202
+ }
203
+
71
204
  tagline(): string {
72
205
  return ULTRACODE_TAGLINE;
73
206
  }
@@ -75,19 +208,48 @@ export class UltracodeMode {
75
208
  /** Turn ultracode on. Idempotent. */
76
209
  enable(pi: ExtensionAPI, opts: { budget?: number | null } = {}): void {
77
210
  if (opts.budget !== undefined) this.budgetTotal = opts.budget;
211
+ this.suspended = false;
212
+ this.pendingPreviousThinking = undefined;
213
+ this.pendingClearGeneration++;
78
214
  if (!this.enabled) {
79
- this.previousThinking = safeGetThinking(pi);
215
+ const current = safeGetThinking(pi);
216
+ const preference = this.captureThinkingPreference();
217
+ const effectivePreference = this.runtimeCompatibleThinking(preference.effective) as
218
+ | ThinkingLevel
219
+ | undefined;
220
+ const maxIsUnknownToRuntime = !this.runtimeSupportsMaxThinking
221
+ && preference.effective === ULTRACODE_THINKING_LEVEL;
222
+ // A non-reasoning model exposes only `off`; preserve the merged default
223
+ // that Pi would use when the user later selects a reasoning model. A
224
+ // pre-max Pi also reports a persisted max preference as off.
225
+ this.previousThinking = current === "off"
226
+ && (this.currentModelSupportsThinking !== true || maxIsUnknownToRuntime)
227
+ ? effectivePreference ?? current
228
+ : current;
229
+ this.previousDefaultThinking = this.runtimeCompatibleThinking(preference.global);
230
+ this.legacyDefaultMigrationPending = false;
80
231
  this.enabled = true;
81
232
  }
82
- this.applyThinking(pi, "xhigh");
83
- this.activateWorkflowTool(pi);
233
+ this.applyUltracodeThinking(pi);
234
+ this.syncWorkflowTool(pi);
84
235
  this.persist(pi);
85
236
  }
86
237
 
87
238
  /** Turn ultracode off, restoring the previous thinking level. */
88
239
  disable(pi: ExtensionAPI): void {
89
- if (this.enabled && this.previousThinking) this.applyThinking(pi, this.previousThinking);
240
+ if (!this.enabled) {
241
+ this.syncWorkflowTool(pi);
242
+ return;
243
+ }
244
+ const previous = this.previousThinking;
245
+ this.restorePreviousThinking(pi);
246
+ this.pendingPreviousThinking = previous && !this.pendingRestoreSucceeded(previous)
247
+ ? previous
248
+ : undefined;
249
+ this.pendingClearGeneration++;
90
250
  this.enabled = false;
251
+ this.suspended = false;
252
+ this.syncWorkflowTool(pi);
91
253
  this.persist(pi);
92
254
  }
93
255
 
@@ -96,21 +258,131 @@ export class UltracodeMode {
96
258
  this.persist(pi);
97
259
  }
98
260
 
99
- /** Restore mode state from session entries (called on session_start). */
100
- restore(pi: ExtensionAPI, entries: Array<{ type?: string; customType?: string; data?: unknown }>): void {
101
- let latest: PersistedModeState | undefined;
261
+ /** Restore mode state from the active session branch. */
262
+ restore(
263
+ pi: ExtensionAPI,
264
+ entries: Array<{
265
+ type?: string;
266
+ customType?: string;
267
+ data?: unknown;
268
+ thinkingLevel?: unknown;
269
+ }>,
270
+ ): void {
271
+ const wasEnforcing = this.isEnforcing();
272
+ this.pendingClearGeneration++;
273
+ let latestData: unknown;
274
+ let branchThinking: ThinkingLevel | undefined;
275
+ let thinkingAfterLatestMode: ThinkingLevel | undefined;
102
276
  for (const entry of entries) {
103
277
  if (entry.type === "custom" && entry.customType === MODE_ENTRY_TYPE && entry.data) {
104
- latest = entry.data as PersistedModeState;
278
+ latestData = entry.data;
279
+ thinkingAfterLatestMode = undefined;
280
+ } else if (entry.type === "thinking_level_change" && isThinkingLevel(entry.thinkingLevel)) {
281
+ branchThinking = entry.thinkingLevel;
282
+ if (latestData !== undefined) thinkingAfterLatestMode = entry.thinkingLevel;
105
283
  }
106
284
  }
107
- if (!latest) return;
108
- this.budgetTotal = latest.budgetTotal ?? null;
109
- this.previousThinking = latest.previousThinking;
110
- if (latest.enabled) {
111
- this.enabled = true;
112
- this.applyThinking(pi, "xhigh");
113
- this.activateWorkflowTool(pi);
285
+
286
+ const current = safeGetThinking(pi);
287
+ const preference = this.captureThinkingPreference();
288
+ const effectivePreference = this.runtimeCompatibleThinking(preference.effective) as
289
+ | ThinkingLevel
290
+ | undefined;
291
+ const globalPreference = this.runtimeCompatibleThinking(preference.global);
292
+ const latest = parsePersistedModeState(latestData);
293
+ if (!latest) {
294
+ const target = this.runtimeCompatibleThinking(
295
+ branchThinking
296
+ ?? (wasEnforcing ? this.previousThinking : undefined)
297
+ ?? effectivePreference,
298
+ ) as ThinkingLevel | undefined;
299
+ this.enabled = false;
300
+ this.suspended = false;
301
+ this.budgetTotal = null;
302
+ this.pendingPreviousThinking = undefined;
303
+ this.previousThinking = target;
304
+ this.previousDefaultThinking = globalPreference;
305
+ this.legacyDefaultMigrationPending = false;
306
+ this.appliedThinking = current;
307
+ this.syncWorkflowTool(pi);
308
+ if (target && current !== target) this.applyCompatibleThinking(pi, target);
309
+ if (target && !this.pendingRestoreSucceeded(target)) {
310
+ this.pendingPreviousThinking = target;
311
+ this.persist(pi);
312
+ } else {
313
+ this.previousThinking = undefined;
314
+ this.previousDefaultThinking = undefined;
315
+ }
316
+ return;
317
+ }
318
+
319
+ this.suspended = false;
320
+ this.enabled = latest.enabled;
321
+ this.budgetTotal = latest.budgetTotal;
322
+ this.syncWorkflowTool(pi);
323
+ const maxIsUnknownToRuntime = !this.runtimeSupportsMaxThinking
324
+ && preference.effective === ULTRACODE_THINKING_LEVEL;
325
+ this.previousThinking = latest.previousThinking
326
+ ?? (current === "off"
327
+ && (this.currentModelSupportsThinking !== true || maxIsUnknownToRuntime)
328
+ ? effectivePreference ?? current
329
+ : current);
330
+
331
+ // Pre-preference-store releases persisted only previousThinking while their
332
+ // active xhigh request polluted Pi's global default. Recover that baseline
333
+ // once instead of treating the known Ultracode value as a user preference.
334
+ const migratesLegacyDefault = latest.enabled
335
+ && latest.previousDefaultThinking === undefined
336
+ && latest.previousThinking !== undefined
337
+ && (preference.global === LEGACY_ULTRACODE_THINKING_LEVEL
338
+ || preference.global === ULTRACODE_THINKING_LEVEL);
339
+ this.previousDefaultThinking = migratesLegacyDefault
340
+ ? this.runtimeCompatibleThinking(latest.previousThinking)
341
+ : preference.global !== undefined
342
+ ? globalPreference
343
+ : this.runtimeCompatibleThinking(latest.previousDefaultThinking);
344
+ this.legacyDefaultMigrationPending = migratesLegacyDefault;
345
+
346
+ this.pendingPreviousThinking = this.enabled ? undefined : latest.pendingPreviousThinking;
347
+ if (this.enabled) {
348
+ this.applyUltracodeThinking(pi);
349
+ if (migratesLegacyDefault) {
350
+ this.queueDefaultThinkingRestore(() => {
351
+ this.legacyDefaultMigrationPending = false;
352
+ this.persist(pi);
353
+ });
354
+ }
355
+ return;
356
+ }
357
+
358
+ if (this.pendingPreviousThinking) {
359
+ if (thinkingAfterLatestMode !== undefined) {
360
+ // A thinking entry after the disabled mode snapshot is an explicit user
361
+ // choice and supersedes the older deferred restoration.
362
+ const target = this.runtimeCompatibleThinking(thinkingAfterLatestMode) as ThinkingLevel;
363
+ this.pendingPreviousThinking = undefined;
364
+ this.previousThinking = target;
365
+ this.appliedThinking = current;
366
+ if (current !== target) this.applyCompatibleThinking(pi, target);
367
+ if (!this.pendingRestoreSucceeded(target)) this.pendingPreviousThinking = target;
368
+ this.persist(pi);
369
+ } else if (this.currentModelSupportsThinking === true) {
370
+ const pending = this.pendingPreviousThinking;
371
+ this.applyCompatibleThinking(pi, pending);
372
+ if (this.pendingRestoreSucceeded(pending)) this.pendingPreviousThinking = undefined;
373
+ this.persist(pi);
374
+ }
375
+ return;
376
+ }
377
+
378
+ const target = this.runtimeCompatibleThinking(branchThinking ?? latest.previousThinking) as
379
+ | ThinkingLevel
380
+ | undefined;
381
+ this.appliedThinking = current;
382
+ if (target && current !== target) this.applyCompatibleThinking(pi, target);
383
+ if (target && !this.pendingRestoreSucceeded(target)) {
384
+ this.pendingPreviousThinking = target;
385
+ this.persist(pi);
114
386
  }
115
387
  }
116
388
 
@@ -119,7 +391,7 @@ export class UltracodeMode {
119
391
  * turn's system prompt when enabled.
120
392
  */
121
393
  beforeAgentStart(event: { systemPrompt: string }): { systemPrompt: string } | undefined {
122
- if (!this.enabled) return undefined;
394
+ if (!this.isEnforcing()) return undefined;
123
395
  const block = ultracodeSystemBlock({ budgetTotal: this.budgetTotal });
124
396
  return { systemPrompt: `${event.systemPrompt}\n\n${block}\n\n${ULTRACODE_ACTIVE_REMINDER}` };
125
397
  }
@@ -127,23 +399,124 @@ export class UltracodeMode {
127
399
  statusLine(): string {
128
400
  if (!this.enabled) return `ultracode: off`;
129
401
  const parts = ["ultracode: on"];
130
- // Show the level that actually applied (xhigh, or whatever the model clamped to).
131
- if (this.appliedThinking) parts.push(`thinking ${this.appliedThinking}`);
402
+ // Show the level that actually applied, including compatibility/model fallback.
403
+ if (this.appliedThinking) parts.push(this.appliedThinking);
132
404
  if (this.budgetTotal) parts.push(`budget ~${formatTokens(this.budgetTotal)}`);
133
405
  return parts.join(" · ");
134
406
  }
135
407
 
408
+ private applyUltracodeThinking(pi: ExtensionAPI): void {
409
+ const writeGeneration = this.preferenceWriteGeneration;
410
+ this.applyCompatibleThinking(pi, ULTRACODE_THINKING_LEVEL);
411
+ // Pi normally skips persistence when the effective level is unchanged, but
412
+ // the extension API does not promise that. Defensively restore the raw
413
+ // baseline even after a stable max -> max request.
414
+ if (writeGeneration === this.preferenceWriteGeneration) {
415
+ this.queueDefaultThinkingRestore();
416
+ }
417
+ }
418
+
419
+ /** Apply max compatibly and return the concrete level accepted as success. */
420
+ private applyCompatibleThinking(pi: ExtensionAPI, level: ThinkingLevel): ThinkingLevel {
421
+ this.applyThinking(pi, level);
422
+ if (level === ULTRACODE_THINKING_LEVEL && this.appliedThinking !== level) {
423
+ // Modern Pi clamps max per model. Pre-max Pi may treat it as unknown, so
424
+ // retry xhigh for both activation and restoration of persisted max state.
425
+ this.applyThinking(pi, LEGACY_ULTRACODE_THINKING_LEVEL);
426
+ return LEGACY_ULTRACODE_THINKING_LEVEL;
427
+ }
428
+ return level;
429
+ }
430
+
136
431
  private applyThinking(pi: ExtensionAPI, level: ThinkingLevel): void {
432
+ const wasApplying = this.applyingThinking;
433
+ const before = safeGetThinking(pi);
434
+ this.applyingThinking = true;
137
435
  try {
138
- // pi clamps the requested level down to the model's capability (e.g. "high",
139
- // or "off" for non-reasoning models); it never throws. Read back what stuck.
140
- pi.setThinkingLevel(level);
436
+ // Pi clamps the request and persists the effective level. Preference
437
+ // restoration is queued after Pi's own SettingsManager chain drains.
438
+ pi.setThinkingLevel(level as any);
141
439
  this.appliedThinking = safeGetThinking(pi) ?? level;
142
440
  } catch {
143
441
  this.appliedThinking = safeGetThinking(pi);
442
+ } finally {
443
+ if (this.appliedThinking !== before) this.queueDefaultThinkingRestore();
444
+ this.applyingThinking = wasApplying;
445
+ }
446
+ }
447
+
448
+ isEnforcing(): boolean {
449
+ return this.enabled && !this.suspended;
450
+ }
451
+
452
+ private pendingRestoreSucceeded(pending: ThinkingLevel): boolean {
453
+ if (this.appliedThinking === pending) return true;
454
+ return pending === ULTRACODE_THINKING_LEVEL
455
+ && !this.runtimeSupportsMaxThinking
456
+ && this.appliedThinking !== undefined
457
+ && this.appliedThinking !== "off";
458
+ }
459
+
460
+ private runtimeCompatibleThinking(
461
+ level: ThinkingLevel | null | undefined,
462
+ ): ThinkingLevel | null | undefined {
463
+ return level === ULTRACODE_THINKING_LEVEL && !this.runtimeSupportsMaxThinking
464
+ ? LEGACY_ULTRACODE_THINKING_LEVEL
465
+ : level;
466
+ }
467
+
468
+ private captureThinkingPreference(): {
469
+ global: ThinkingLevel | null | undefined;
470
+ effective: ThinkingLevel | undefined;
471
+ } {
472
+ if (!this.thinkingPreferenceStore) return { global: undefined, effective: undefined };
473
+ try {
474
+ const preference = this.thinkingPreferenceStore.getThinkingPreference();
475
+ return {
476
+ global: isThinkingLevel(preference.global) ? preference.global : null,
477
+ effective: isThinkingLevel(preference.effective) ? preference.effective : undefined,
478
+ };
479
+ } catch {
480
+ return { global: undefined, effective: undefined };
144
481
  }
145
482
  }
146
483
 
484
+ private queueDefaultThinkingRestore(onSuccess?: () => void): void {
485
+ const store = this.thinkingPreferenceStore;
486
+ const baseline = this.runtimeCompatibleThinking(this.previousDefaultThinking);
487
+ if (!store || baseline === undefined) return;
488
+ const generation = ++this.preferenceWriteGeneration;
489
+ this.preferenceWriteQueue = this.preferenceWriteQueue
490
+ .catch(() => {})
491
+ .then(waitForSettingsWrites)
492
+ .then(async () => {
493
+ if (generation !== this.preferenceWriteGeneration) return;
494
+ const currentGlobal = this.captureThinkingPreference().global;
495
+ if (currentGlobal !== baseline) {
496
+ await store.setDefaultThinkingLevel(baseline ?? undefined);
497
+ await store.flush?.();
498
+ }
499
+ if (generation === this.preferenceWriteGeneration) onSuccess?.();
500
+ })
501
+ .catch(() => {
502
+ // Keep the session usable even when settings cannot be restored.
503
+ });
504
+ }
505
+
506
+ private deferPendingClear(pi: ExtensionAPI, level: ThinkingLevel): void {
507
+ const generation = ++this.pendingClearGeneration;
508
+ setImmediate(() => {
509
+ if (
510
+ generation !== this.pendingClearGeneration
511
+ || this.enabled
512
+ || this.suspended
513
+ || safeGetThinking(pi) !== level
514
+ ) return;
515
+ this.pendingPreviousThinking = undefined;
516
+ this.persist(pi);
517
+ });
518
+ }
519
+
147
520
  private activateWorkflowTool(pi: ExtensionAPI): void {
148
521
  try {
149
522
  const active = pi.getActiveTools();
@@ -151,7 +524,18 @@ export class UltracodeMode {
151
524
  pi.setActiveTools([...active, this.workflowToolName]);
152
525
  }
153
526
  } catch {
154
- // ignore
527
+ // Mode enforcement remains usable when tool selection is unavailable.
528
+ }
529
+ }
530
+
531
+ private deactivateWorkflowTool(pi: ExtensionAPI): void {
532
+ try {
533
+ const active = pi.getActiveTools();
534
+ if (active.includes(this.workflowToolName)) {
535
+ pi.setActiveTools(active.filter((name) => name !== this.workflowToolName));
536
+ }
537
+ } catch {
538
+ // The execution guards still fail closed when selection cannot be updated.
155
539
  }
156
540
  }
157
541
 
@@ -160,6 +544,10 @@ export class UltracodeMode {
160
544
  enabled: this.enabled,
161
545
  budgetTotal: this.budgetTotal,
162
546
  previousThinking: this.previousThinking,
547
+ previousDefaultThinking: this.legacyDefaultMigrationPending
548
+ ? undefined
549
+ : this.previousDefaultThinking,
550
+ pendingPreviousThinking: this.pendingPreviousThinking,
163
551
  };
164
552
  try {
165
553
  pi.appendEntry(MODE_ENTRY_TYPE, state);
@@ -169,9 +557,32 @@ export class UltracodeMode {
169
557
  }
170
558
  }
171
559
 
560
+ function parsePersistedModeState(data: unknown): PersistedModeState | undefined {
561
+ if (!data || typeof data !== "object") return undefined;
562
+ const value = data as Record<string, unknown>;
563
+ const budget = value.budgetTotal;
564
+ const previousDefault = value.previousDefaultThinking;
565
+ return {
566
+ enabled: value.enabled === true,
567
+ budgetTotal: typeof budget === "number" && Number.isFinite(budget) && budget > 0 ? budget : null,
568
+ previousThinking: isThinkingLevel(value.previousThinking) ? value.previousThinking : undefined,
569
+ previousDefaultThinking: previousDefault === null || isThinkingLevel(previousDefault)
570
+ ? previousDefault
571
+ : undefined,
572
+ pendingPreviousThinking: isThinkingLevel(value.pendingPreviousThinking)
573
+ ? value.pendingPreviousThinking
574
+ : undefined,
575
+ };
576
+ }
577
+
578
+ function waitForSettingsWrites(): Promise<void> {
579
+ return new Promise((resolve) => setImmediate(resolve));
580
+ }
581
+
172
582
  function safeGetThinking(pi: ExtensionAPI): ThinkingLevel | undefined {
173
583
  try {
174
- return pi.getThinkingLevel() as ThinkingLevel;
584
+ const level = pi.getThinkingLevel();
585
+ return isThinkingLevel(level) ? level : undefined;
175
586
  } catch {
176
587
  return undefined;
177
588
  }
package/src/prompts.ts CHANGED
@@ -7,7 +7,7 @@
7
7
  */
8
8
 
9
9
  /** One-line description shown by `/ultracode status` and the footer. */
10
- export const ULTRACODE_TAGLINE = "xhigh thinking + dynamic workflow orchestration";
10
+ export const ULTRACODE_TAGLINE = "max thinking + dynamic workflow orchestration";
11
11
 
12
12
  /**
13
13
  * The standing system-prompt block injected on every turn while ultracode is on.
@@ -59,7 +59,7 @@ export const WORKFLOW_PROMPT_SNIPPET =
59
59
 
60
60
  /** Guideline bullets appended to the Guidelines section when the tool is active. */
61
61
  export const WORKFLOW_GUIDELINES: string[] = [
62
- "Use the workflow tool to decompose-and-cover in parallel, to gather independent perspectives that adversarially verify each other, or to take on scale a single context can't hold. Outside ultracode mode, reserve it for explicit fan-out / multi-agent requests.",
62
+ "While Ultracode is enabled, use the workflow tool to decompose-and-cover in parallel, gather independent perspectives that adversarially verify each other, or take on scale one context can't hold.",
63
63
  "For the workflow tool, pass one raw JavaScript string in the required `script` parameter; no Markdown fences, no prose around the script.",
64
64
  "For the workflow tool, the script's first statement must be `export const meta = { name: 'short_snake_case', description: 'non-empty human description' }`. meta must be a pure literal: no variables, function calls, spreads, or template interpolation. meta.phases is optional and should mirror your phase() titles.",
65
65
  "For the workflow tool, write plain JavaScript after the meta export. No TypeScript syntax, imports, require(), fs, network, Date.now(), Math.random(), or new Date() (they break determinism and resume). Stamp timestamps after the workflow returns; vary randomness by agent index.",
@@ -0,0 +1,27 @@
1
+ /** Shared thinking-level vocabulary for the mode, workflow agents, and config parser. */
2
+
3
+ export const THINKING_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh", "max"] as const;
4
+
5
+ export type ThinkingLevel = (typeof THINKING_LEVELS)[number];
6
+
7
+ /** Ultracode always asks Pi for the strongest effort the selected model supports. */
8
+ export const ULTRACODE_THINKING_LEVEL: ThinkingLevel = "max";
9
+
10
+ /** Compatibility retry for Pi versions released before the `max` level existed. */
11
+ export const LEGACY_ULTRACODE_THINKING_LEVEL: ThinkingLevel = "xhigh";
12
+
13
+ const THINKING_LEVEL_SET = new Set<string>(THINKING_LEVELS);
14
+
15
+ export function isThinkingLevel(value: unknown): value is ThinkingLevel {
16
+ return typeof value === "string" && THINKING_LEVEL_SET.has(value);
17
+ }
18
+
19
+ /** Whether this Pi runtime recognizes `max` as a first-class thinking level. */
20
+ export function piVersionSupportsMaxThinking(version: string): boolean {
21
+ const match = /^(\d+)\.(\d+)\.(\d+)/.exec(version);
22
+ if (!match) return true;
23
+ const major = Number(match[1]);
24
+ const minor = Number(match[2]);
25
+ const patch = Number(match[3]);
26
+ return major > 0 || minor > 80 || (minor === 80 && patch >= 6);
27
+ }