pi-ultracode 0.4.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/mode.ts CHANGED
@@ -1,45 +1,37 @@
1
1
  /**
2
2
  * Ultracode mode controller.
3
3
  *
4
- * Ultracode is a session-scoped effort mode. While on, it:
5
- * - raises the thinking level to the model's maximum (remembering the previous level),
4
+ * Ultracode is a session-scoped semantic analysis-depth mode. While active, it:
5
+ * - leaves the parent session's thinking level under user control,
6
6
  * - keeps the `workflow` tool active,
7
- * - injects a standing "author and run a workflow by default" system block on
8
- * every turn,
9
- * - persists its on/off state in session custom entries so it survives
10
- * reload, resume, fork, and compaction.
7
+ * - injects the configured auto/focused/standard/deep policy on every turn,
8
+ * - persists branch-local mode state across reload, resume, fork, and compaction.
11
9
  */
12
10
 
13
11
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
14
- import { ULTRACODE_ACTIVE_REMINDER, ULTRACODE_TAGLINE, ultracodeSystemBlock } from "./prompts.ts";
15
12
  import {
16
- LEGACY_ULTRACODE_THINKING_LEVEL,
17
- ULTRACODE_THINKING_LEVEL,
18
- isThinkingLevel,
19
- type ThinkingLevel,
20
- } from "./thinking.ts";
13
+ isActiveUltracodeMode,
14
+ type ActiveUltracodeMode,
15
+ type UltracodeModeName,
16
+ } from "./depth.ts";
17
+ import { ULTRACODE_ACTIVE_REMINDER, ULTRACODE_TAGLINE, ultracodeSystemBlock } from "./prompts.ts";
18
+ import type { ThinkingLevel } from "./thinking.ts";
21
19
 
22
20
  export type { ThinkingLevel } from "./thinking.ts";
23
21
 
24
22
  export const MODE_ENTRY_TYPE = "ultracode-mode";
25
23
 
26
24
  interface PersistedModeState {
27
- enabled: boolean;
28
- previousThinking?: ThinkingLevel;
29
- /** `null` records that the setting was originally absent (Pi defaults to medium). */
30
- previousDefaultThinking?: ThinkingLevel | null;
31
- /** Deferred when the selected model cannot represent the pre-mode level. */
32
- pendingPreviousThinking?: ThinkingLevel;
25
+ mode: UltracodeModeName;
33
26
  }
34
27
 
28
+ /** @deprecated Ultracode no longer reads or writes the parent's effort preference. */
35
29
  export interface ThinkingPreferenceSnapshot {
36
- /** Raw global value; undefined means Pi's implicit medium default. */
37
30
  global: ThinkingLevel | undefined;
38
- /** Global + project merged value used by Pi for model switches. */
39
31
  effective: ThinkingLevel | undefined;
40
32
  }
41
33
 
42
- /** Adapter for preserving Pi's raw global effort preference while this mode is active. */
34
+ /** @deprecated Ultracode no longer reads or writes the parent's effort preference. */
43
35
  export interface ThinkingPreferenceStore {
44
36
  getThinkingPreference(): ThinkingPreferenceSnapshot;
45
37
  setDefaultThinkingLevel(level: ThinkingLevel | undefined): void | Promise<void>;
@@ -47,149 +39,95 @@ export interface ThinkingPreferenceStore {
47
39
  }
48
40
 
49
41
  export class UltracodeMode {
50
- private enabled = false;
42
+ private mode: UltracodeModeName = "off";
51
43
  private suspended = false;
52
- private previousThinking: ThinkingLevel | undefined;
53
- private previousDefaultThinking: ThinkingLevel | null | undefined;
54
- /** Restore a level later if the current non-reasoning model clamps it to off. */
55
- private pendingPreviousThinking: ThinkingLevel | undefined;
56
- /** The level Pi actually applied after clamping the maximum request. */
57
- private appliedThinking: ThinkingLevel | undefined;
58
- /** Prevent mode-owned thinking changes from being mistaken for manual overrides. */
59
- private applyingThinking = false;
60
- private thinkingPreferenceStore: ThinkingPreferenceStore | undefined;
61
- private preferenceWriteQueue: Promise<void> = Promise.resolve();
62
- private preferenceWriteGeneration = 0;
63
- private legacyDefaultMigrationPending = false;
64
- private pendingClearGeneration = 0;
65
- private currentModelSupportsThinking: boolean | undefined;
66
- private runtimeSupportsMaxThinking = true;
67
44
  private readonly workflowToolName: string;
68
45
 
69
46
  constructor(workflowToolName: string) {
70
47
  this.workflowToolName = workflowToolName;
71
48
  }
72
49
 
73
- /** Bind the settings adapter once the session cwd is known. */
74
- bindThinkingPreferenceStore(store: ThinkingPreferenceStore | undefined): void {
75
- this.preferenceWriteGeneration++;
76
- this.thinkingPreferenceStore = store;
77
- }
50
+ /** @deprecated Parent effort is user-owned; retained as a compatibility no-op. */
51
+ bindThinkingPreferenceStore(_store: ThinkingPreferenceStore | undefined): void {}
78
52
 
79
- /** Track capability explicitly; `off` alone cannot distinguish clamp from intent. */
80
- setCurrentModelSupportsThinking(supportsThinking: boolean | undefined): void {
81
- this.currentModelSupportsThinking = supportsThinking;
82
- }
53
+ /** @deprecated Parent effort is user-owned; retained as a compatibility no-op. */
54
+ setCurrentModelSupportsThinking(_supportsThinking: boolean | undefined): void {}
83
55
 
84
- /** Configure migration behavior for Pi versions released before `max`. */
85
- setRuntimeSupportsMaxThinking(supportsMaxThinking: boolean): void {
86
- this.runtimeSupportsMaxThinking = supportsMaxThinking;
87
- }
56
+ /** @deprecated Parent effort is user-owned; retained as a compatibility no-op. */
57
+ setRuntimeSupportsMaxThinking(_supportsMaxThinking: boolean): void {}
88
58
 
89
- /** Wait for queued preference restoration before teardown or command completion. */
90
- async flushThinkingPreference(): Promise<void> {
91
- try {
92
- await this.preferenceWriteQueue;
93
- } catch {
94
- // Pi owns settings error reporting; effort enforcement must remain usable.
95
- }
96
- }
59
+ /** @deprecated Parent effort is user-owned; retained as a compatibility no-op. */
60
+ async flushThinkingPreference(): Promise<void> {}
97
61
 
98
- /** Enable if off, disable if on. Returns the new enabled state. */
62
+ /** Enable auto if off, otherwise disable. Returns the new enabled state. */
99
63
  toggle(pi: ExtensionAPI): boolean {
100
- if (this.enabled) {
64
+ if (this.isEnabled()) {
101
65
  this.disable(pi);
102
66
  return false;
103
67
  }
104
- this.enable(pi);
68
+ this.enable(pi, "auto");
105
69
  return true;
106
70
  }
107
71
 
108
- /** The thinking level Pi actually applied (`max`, or the model/runtime fallback). */
72
+ getMode(): UltracodeModeName {
73
+ return this.mode;
74
+ }
75
+
76
+ /** @deprecated Ultracode no longer applies a parent thinking level. */
109
77
  getAppliedThinking(): ThinkingLevel | undefined {
110
- return this.appliedThinking;
78
+ return undefined;
111
79
  }
112
80
 
113
81
  /**
114
- * Return the raw maximum request, not the parent's applied value, so every
115
- * workflow subagent is clamped independently against its own model.
82
+ * @deprecated Workflow agents now select effort per call. An omitted suffix
83
+ * falls back to the child Pi session's normal user/model configuration rather
84
+ * than a mode-owned default.
116
85
  */
117
86
  getSubagentThinkingLevel(): ThinkingLevel | undefined {
118
- return this.isEnforcing() ? ULTRACODE_THINKING_LEVEL : undefined;
87
+ return undefined;
119
88
  }
120
89
 
121
- /** Reassert the maximum before a turn or after a model change. */
122
- reapplyMaximumThinking(pi: ExtensionAPI): boolean {
123
- if (!this.isEnforcing()) return false;
124
- this.applyUltracodeThinking(pi);
125
- return true;
90
+ /** @deprecated Parent effort is user-owned; retained as a compatibility no-op. */
91
+ reapplyConfiguredThinking(_pi: ExtensionAPI): boolean {
92
+ return false;
126
93
  }
127
94
 
128
- /**
129
- * Handle model switches both while active and after a clamped restoration.
130
- * Returns true when Ultracode remains active and the UI should be refreshed.
131
- */
132
- handleModelSelect(pi: ExtensionAPI): boolean {
133
- if (this.suspended) return false;
134
- this.pendingClearGeneration++;
135
- if (this.enabled) {
136
- this.applyUltracodeThinking(pi);
137
- return true;
138
- }
139
- if (this.pendingPreviousThinking) {
140
- const pending = this.pendingPreviousThinking;
141
- this.applyCompatibleThinking(pi, pending);
142
- if (this.pendingRestoreSucceeded(pending)) this.pendingPreviousThinking = undefined;
143
- this.persist(pi);
144
- }
95
+ /** @deprecated Parent effort is user-owned; retained as a compatibility no-op. */
96
+ reapplyMaximumThinking(_pi: ExtensionAPI): boolean {
145
97
  return false;
146
98
  }
147
99
 
148
- /** Restore the pre-mode effective effort without changing persisted mode state. */
149
- restorePreviousThinking(pi: ExtensionAPI): void {
150
- if (this.enabled && this.previousThinking) this.applyCompatibleThinking(pi, this.previousThinking);
100
+ /** @deprecated Model changes do not alter Ultracode mode or parent effort. */
101
+ handleModelSelect(_pi: ExtensionAPI): boolean {
102
+ return false;
151
103
  }
152
104
 
153
- /** Stop enforcing synchronously, then restore effort before session teardown. */
105
+ /** @deprecated Parent effort is never restored because Ultracode never changes it. */
106
+ restorePreviousThinking(_pi: ExtensionAPI): void {}
107
+
108
+ /** Quiesce tool and prompt enforcement before session teardown. */
154
109
  suspend(pi: ExtensionAPI): void {
155
110
  if (this.suspended) {
156
111
  this.syncWorkflowTool(pi);
157
112
  return;
158
113
  }
159
114
  this.suspended = true;
160
- this.restorePreviousThinking(pi);
161
115
  this.syncWorkflowTool(pi);
162
116
  }
163
117
 
164
- /**
165
- * Enforce the mode after an external thinking-level selection. Stale events
166
- * and events emitted by this mode are ignored to avoid recursive updates.
167
- * Returns true when a manual selection was overridden.
168
- */
169
- handleThinkingLevelSelect(pi: ExtensionAPI, level: ThinkingLevel): boolean {
170
- if (this.suspended || this.applyingThinking) return false;
171
- const current = safeGetThinking(pi);
172
- if (!current || current !== level) return false;
173
- if (!this.enabled) {
174
- // Pi emits the same event for a user selection and an automatic model
175
- // re-clamp. Defer clearing until model_select has had a chance to consume it.
176
- if (this.pendingPreviousThinking) this.deferPendingClear(pi, level);
177
- return false;
178
- }
179
- if (current === this.appliedThinking) return false;
180
- this.applyUltracodeThinking(pi);
181
- return true;
118
+ /** @deprecated User-selected parent effort is accepted without interception. */
119
+ handleThinkingLevelSelect(_pi: ExtensionAPI, _level: ThinkingLevel): boolean {
120
+ return false;
182
121
  }
183
122
 
184
123
  isEnabled(): boolean {
185
- return this.enabled;
124
+ return this.mode !== "off";
186
125
  }
187
126
 
188
127
  isSuspended(): boolean {
189
128
  return this.suspended;
190
129
  }
191
130
 
192
-
193
131
  /** Keep tool availability aligned with the current mode state. */
194
132
  syncWorkflowTool(pi: ExtensionAPI): void {
195
133
  if (this.isEnforcing()) this.activateWorkflowTool(pi);
@@ -200,55 +138,31 @@ export class UltracodeMode {
200
138
  return ULTRACODE_TAGLINE;
201
139
  }
202
140
 
203
- /** Turn ultracode on. Idempotent. */
204
- enable(pi: ExtensionAPI): void {
141
+ /**
142
+ * Enable or switch modes. The no-argument form retains the pre-0.5
143
+ * programmatic deep behavior; user commands pass an explicit mode and bare
144
+ * `/ultracode` uses toggle() to enter auto.
145
+ */
146
+ enable(pi: ExtensionAPI, mode: ActiveUltracodeMode = "deep"): void {
205
147
  this.suspended = false;
206
- this.pendingPreviousThinking = undefined;
207
- this.pendingClearGeneration++;
208
- if (!this.enabled) {
209
- const current = safeGetThinking(pi);
210
- const preference = this.captureThinkingPreference();
211
- const effectivePreference = this.runtimeCompatibleThinking(preference.effective) as
212
- | ThinkingLevel
213
- | undefined;
214
- const maxIsUnknownToRuntime = !this.runtimeSupportsMaxThinking
215
- && preference.effective === ULTRACODE_THINKING_LEVEL;
216
- // A non-reasoning model exposes only `off`; preserve the merged default
217
- // that Pi would use when the user later selects a reasoning model. A
218
- // pre-max Pi also reports a persisted max preference as off.
219
- this.previousThinking = current === "off"
220
- && (this.currentModelSupportsThinking !== true || maxIsUnknownToRuntime)
221
- ? effectivePreference ?? current
222
- : current;
223
- this.previousDefaultThinking = this.runtimeCompatibleThinking(preference.global);
224
- this.legacyDefaultMigrationPending = false;
225
- this.enabled = true;
226
- }
227
- this.applyUltracodeThinking(pi);
148
+ this.mode = mode;
228
149
  this.syncWorkflowTool(pi);
229
150
  this.persist(pi);
230
151
  }
231
152
 
232
- /** Turn ultracode off, restoring the previous thinking level. */
153
+ /** Turn Ultracode off without changing the parent thinking level. */
233
154
  disable(pi: ExtensionAPI): void {
234
- if (!this.enabled) {
155
+ if (!this.isEnabled()) {
235
156
  this.syncWorkflowTool(pi);
236
157
  return;
237
158
  }
238
- const previous = this.previousThinking;
239
- this.restorePreviousThinking(pi);
240
- this.pendingPreviousThinking = previous && !this.pendingRestoreSucceeded(previous)
241
- ? previous
242
- : undefined;
243
- this.pendingClearGeneration++;
244
- this.enabled = false;
159
+ this.mode = "off";
245
160
  this.suspended = false;
246
161
  this.syncWorkflowTool(pi);
247
162
  this.persist(pi);
248
163
  }
249
164
 
250
-
251
- /** Restore mode state from the active session branch. */
165
+ /** Restore mode state from the active session branch without touching effort. */
252
166
  restore(
253
167
  pi: ExtensionAPI,
254
168
  entries: Array<{
@@ -258,250 +172,31 @@ export class UltracodeMode {
258
172
  thinkingLevel?: unknown;
259
173
  }>,
260
174
  ): void {
261
- const wasEnforcing = this.isEnforcing();
262
- this.pendingClearGeneration++;
263
175
  let latestData: unknown;
264
- let branchThinking: ThinkingLevel | undefined;
265
- let thinkingAfterLatestMode: ThinkingLevel | undefined;
266
176
  for (const entry of entries) {
267
177
  if (entry.type === "custom" && entry.customType === MODE_ENTRY_TYPE && entry.data) {
268
178
  latestData = entry.data;
269
- thinkingAfterLatestMode = undefined;
270
- } else if (entry.type === "thinking_level_change" && isThinkingLevel(entry.thinkingLevel)) {
271
- branchThinking = entry.thinkingLevel;
272
- if (latestData !== undefined) thinkingAfterLatestMode = entry.thinkingLevel;
273
179
  }
274
180
  }
275
181
 
276
- const current = safeGetThinking(pi);
277
- const preference = this.captureThinkingPreference();
278
- const effectivePreference = this.runtimeCompatibleThinking(preference.effective) as
279
- | ThinkingLevel
280
- | undefined;
281
- const globalPreference = this.runtimeCompatibleThinking(preference.global);
282
- const latest = parsePersistedModeState(latestData);
283
- if (!latest) {
284
- const target = this.runtimeCompatibleThinking(
285
- branchThinking
286
- ?? (wasEnforcing ? this.previousThinking : undefined)
287
- ?? effectivePreference,
288
- ) as ThinkingLevel | undefined;
289
- this.enabled = false;
290
- this.suspended = false;
291
- this.pendingPreviousThinking = undefined;
292
- this.previousThinking = target;
293
- this.previousDefaultThinking = globalPreference;
294
- this.legacyDefaultMigrationPending = false;
295
- this.appliedThinking = current;
296
- this.syncWorkflowTool(pi);
297
- if (target && current !== target) this.applyCompatibleThinking(pi, target);
298
- if (target && !this.pendingRestoreSucceeded(target)) {
299
- this.pendingPreviousThinking = target;
300
- this.persist(pi);
301
- } else {
302
- this.previousThinking = undefined;
303
- this.previousDefaultThinking = undefined;
304
- }
305
- return;
306
- }
307
-
182
+ this.mode = parsePersistedModeState(latestData)?.mode ?? "off";
308
183
  this.suspended = false;
309
- this.enabled = latest.enabled;
310
184
  this.syncWorkflowTool(pi);
311
- const maxIsUnknownToRuntime = !this.runtimeSupportsMaxThinking
312
- && preference.effective === ULTRACODE_THINKING_LEVEL;
313
- this.previousThinking = latest.previousThinking
314
- ?? (current === "off"
315
- && (this.currentModelSupportsThinking !== true || maxIsUnknownToRuntime)
316
- ? effectivePreference ?? current
317
- : current);
318
-
319
- // Pre-preference-store releases persisted only previousThinking while their
320
- // active xhigh request polluted Pi's global default. Recover that baseline
321
- // once instead of treating the known Ultracode value as a user preference.
322
- const migratesLegacyDefault = latest.enabled
323
- && latest.previousDefaultThinking === undefined
324
- && latest.previousThinking !== undefined
325
- && (preference.global === LEGACY_ULTRACODE_THINKING_LEVEL
326
- || preference.global === ULTRACODE_THINKING_LEVEL);
327
- this.previousDefaultThinking = migratesLegacyDefault
328
- ? this.runtimeCompatibleThinking(latest.previousThinking)
329
- : preference.global !== undefined
330
- ? globalPreference
331
- : this.runtimeCompatibleThinking(latest.previousDefaultThinking);
332
- this.legacyDefaultMigrationPending = migratesLegacyDefault;
333
-
334
- this.pendingPreviousThinking = this.enabled ? undefined : latest.pendingPreviousThinking;
335
- if (this.enabled) {
336
- this.applyUltracodeThinking(pi);
337
- if (migratesLegacyDefault) {
338
- this.queueDefaultThinkingRestore(() => {
339
- this.legacyDefaultMigrationPending = false;
340
- this.persist(pi);
341
- });
342
- }
343
- return;
344
- }
345
-
346
- if (this.pendingPreviousThinking) {
347
- if (thinkingAfterLatestMode !== undefined) {
348
- // A thinking entry after the disabled mode snapshot is an explicit user
349
- // choice and supersedes the older deferred restoration.
350
- const target = this.runtimeCompatibleThinking(thinkingAfterLatestMode) as ThinkingLevel;
351
- this.pendingPreviousThinking = undefined;
352
- this.previousThinking = target;
353
- this.appliedThinking = current;
354
- if (current !== target) this.applyCompatibleThinking(pi, target);
355
- if (!this.pendingRestoreSucceeded(target)) this.pendingPreviousThinking = target;
356
- this.persist(pi);
357
- } else if (this.currentModelSupportsThinking === true) {
358
- const pending = this.pendingPreviousThinking;
359
- this.applyCompatibleThinking(pi, pending);
360
- if (this.pendingRestoreSucceeded(pending)) this.pendingPreviousThinking = undefined;
361
- this.persist(pi);
362
- }
363
- return;
364
- }
365
-
366
- const target = this.runtimeCompatibleThinking(branchThinking ?? latest.previousThinking) as
367
- | ThinkingLevel
368
- | undefined;
369
- this.appliedThinking = current;
370
- if (target && current !== target) this.applyCompatibleThinking(pi, target);
371
- if (target && !this.pendingRestoreSucceeded(target)) {
372
- this.pendingPreviousThinking = target;
373
- this.persist(pi);
374
- }
375
185
  }
376
186
 
377
- /**
378
- * Build the before_agent_start result: appends the ultracode system block to the
379
- * turn's system prompt when enabled.
380
- */
187
+ /** Append the configured semantic-depth policy to the turn's system prompt. */
381
188
  beforeAgentStart(event: { systemPrompt: string }): { systemPrompt: string } | undefined {
382
- if (!this.isEnforcing()) return undefined;
383
- const block = ultracodeSystemBlock();
189
+ if (!this.isEnforcing() || !isActiveUltracodeMode(this.mode)) return undefined;
190
+ const block = ultracodeSystemBlock(this.mode);
384
191
  return { systemPrompt: `${event.systemPrompt}\n\n${block}\n\n${ULTRACODE_ACTIVE_REMINDER}` };
385
192
  }
386
193
 
387
- statusLine(): string {
388
- if (!this.enabled) return `ultracode: off`;
389
- const parts = ["ultracode: on"];
390
- // Show the level that actually applied, including compatibility/model fallback.
391
- if (this.appliedThinking) parts.push(this.appliedThinking);
392
- return parts.join(" · ");
393
- }
394
-
395
- private applyUltracodeThinking(pi: ExtensionAPI): void {
396
- const writeGeneration = this.preferenceWriteGeneration;
397
- this.applyCompatibleThinking(pi, ULTRACODE_THINKING_LEVEL);
398
- // Pi normally skips persistence when the effective level is unchanged, but
399
- // the extension API does not promise that. Defensively restore the raw
400
- // baseline even after a stable max -> max request.
401
- if (writeGeneration === this.preferenceWriteGeneration) {
402
- this.queueDefaultThinkingRestore();
403
- }
404
- }
405
-
406
- /** Apply max compatibly and return the concrete level accepted as success. */
407
- private applyCompatibleThinking(pi: ExtensionAPI, level: ThinkingLevel): ThinkingLevel {
408
- this.applyThinking(pi, level);
409
- if (level === ULTRACODE_THINKING_LEVEL && this.appliedThinking !== level) {
410
- // Modern Pi clamps max per model. Pre-max Pi may treat it as unknown, so
411
- // retry xhigh for both activation and restoration of persisted max state.
412
- this.applyThinking(pi, LEGACY_ULTRACODE_THINKING_LEVEL);
413
- return LEGACY_ULTRACODE_THINKING_LEVEL;
414
- }
415
- return level;
416
- }
417
-
418
- private applyThinking(pi: ExtensionAPI, level: ThinkingLevel): void {
419
- const wasApplying = this.applyingThinking;
420
- const before = safeGetThinking(pi);
421
- this.applyingThinking = true;
422
- try {
423
- // Pi clamps the request and persists the effective level. Preference
424
- // restoration is queued after Pi's own SettingsManager chain drains.
425
- pi.setThinkingLevel(level as any);
426
- this.appliedThinking = safeGetThinking(pi) ?? level;
427
- } catch {
428
- this.appliedThinking = safeGetThinking(pi);
429
- } finally {
430
- if (this.appliedThinking !== before) this.queueDefaultThinkingRestore();
431
- this.applyingThinking = wasApplying;
432
- }
194
+ statusLine(styleLabel: (label: string) => string = (label) => label): string {
195
+ return [styleLabel("ultracode"), this.mode].join(" · ");
433
196
  }
434
197
 
435
198
  isEnforcing(): boolean {
436
- return this.enabled && !this.suspended;
437
- }
438
-
439
- private pendingRestoreSucceeded(pending: ThinkingLevel): boolean {
440
- if (this.appliedThinking === pending) return true;
441
- return pending === ULTRACODE_THINKING_LEVEL
442
- && !this.runtimeSupportsMaxThinking
443
- && this.appliedThinking !== undefined
444
- && this.appliedThinking !== "off";
445
- }
446
-
447
- private runtimeCompatibleThinking(
448
- level: ThinkingLevel | null | undefined,
449
- ): ThinkingLevel | null | undefined {
450
- return level === ULTRACODE_THINKING_LEVEL && !this.runtimeSupportsMaxThinking
451
- ? LEGACY_ULTRACODE_THINKING_LEVEL
452
- : level;
453
- }
454
-
455
- private captureThinkingPreference(): {
456
- global: ThinkingLevel | null | undefined;
457
- effective: ThinkingLevel | undefined;
458
- } {
459
- if (!this.thinkingPreferenceStore) return { global: undefined, effective: undefined };
460
- try {
461
- const preference = this.thinkingPreferenceStore.getThinkingPreference();
462
- return {
463
- global: isThinkingLevel(preference.global) ? preference.global : null,
464
- effective: isThinkingLevel(preference.effective) ? preference.effective : undefined,
465
- };
466
- } catch {
467
- return { global: undefined, effective: undefined };
468
- }
469
- }
470
-
471
- private queueDefaultThinkingRestore(onSuccess?: () => void): void {
472
- const store = this.thinkingPreferenceStore;
473
- const baseline = this.runtimeCompatibleThinking(this.previousDefaultThinking);
474
- if (!store || baseline === undefined) return;
475
- const generation = ++this.preferenceWriteGeneration;
476
- this.preferenceWriteQueue = this.preferenceWriteQueue
477
- .catch(() => {})
478
- .then(waitForSettingsWrites)
479
- .then(async () => {
480
- if (generation !== this.preferenceWriteGeneration) return;
481
- const currentGlobal = this.captureThinkingPreference().global;
482
- if (currentGlobal !== baseline) {
483
- await store.setDefaultThinkingLevel(baseline ?? undefined);
484
- await store.flush?.();
485
- }
486
- if (generation === this.preferenceWriteGeneration) onSuccess?.();
487
- })
488
- .catch(() => {
489
- // Keep the session usable even when settings cannot be restored.
490
- });
491
- }
492
-
493
- private deferPendingClear(pi: ExtensionAPI, level: ThinkingLevel): void {
494
- const generation = ++this.pendingClearGeneration;
495
- setImmediate(() => {
496
- if (
497
- generation !== this.pendingClearGeneration
498
- || this.enabled
499
- || this.suspended
500
- || safeGetThinking(pi) !== level
501
- ) return;
502
- this.pendingPreviousThinking = undefined;
503
- this.persist(pi);
504
- });
199
+ return this.isEnabled() && !this.suspended;
505
200
  }
506
201
 
507
202
  private activateWorkflowTool(pi: ExtensionAPI): void {
@@ -511,7 +206,7 @@ export class UltracodeMode {
511
206
  pi.setActiveTools([...active, this.workflowToolName]);
512
207
  }
513
208
  } catch {
514
- // Mode enforcement remains usable when tool selection is unavailable.
209
+ // Prompt policy remains usable when tool selection is unavailable.
515
210
  }
516
211
  }
517
212
 
@@ -527,14 +222,7 @@ export class UltracodeMode {
527
222
  }
528
223
 
529
224
  private persist(pi: ExtensionAPI): void {
530
- const state: PersistedModeState = {
531
- enabled: this.enabled,
532
- previousThinking: this.previousThinking,
533
- previousDefaultThinking: this.legacyDefaultMigrationPending
534
- ? undefined
535
- : this.previousDefaultThinking,
536
- pendingPreviousThinking: this.pendingPreviousThinking,
537
- };
225
+ const state: PersistedModeState = { mode: this.mode };
538
226
  try {
539
227
  pi.appendEntry(MODE_ENTRY_TYPE, state);
540
228
  } catch {
@@ -546,28 +234,14 @@ export class UltracodeMode {
546
234
  function parsePersistedModeState(data: unknown): PersistedModeState | undefined {
547
235
  if (!data || typeof data !== "object") return undefined;
548
236
  const value = data as Record<string, unknown>;
549
- const previousDefault = value.previousDefaultThinking;
550
- return {
551
- enabled: value.enabled === true,
552
- previousThinking: isThinkingLevel(value.previousThinking) ? value.previousThinking : undefined,
553
- previousDefaultThinking: previousDefault === null || isThinkingLevel(previousDefault)
554
- ? previousDefault
555
- : undefined,
556
- pendingPreviousThinking: isThinkingLevel(value.pendingPreviousThinking)
557
- ? value.pendingPreviousThinking
558
- : undefined,
559
- };
560
- }
561
-
562
- function waitForSettingsWrites(): Promise<void> {
563
- return new Promise((resolve) => setImmediate(resolve));
564
- }
565
-
566
- function safeGetThinking(pi: ExtensionAPI): ThinkingLevel | undefined {
567
- try {
568
- const level = pi.getThinkingLevel();
569
- return isThinkingLevel(level) ? level : undefined;
570
- } catch {
571
- return undefined;
572
- }
237
+ // Sessions written before semantic-depth modes stored only enabled:boolean.
238
+ // Preserve their behavior by migrating enabled:true to the old deep mode.
239
+ const mode: UltracodeModeName = isActiveUltracodeMode(value.mode)
240
+ ? value.mode
241
+ : value.mode === "off"
242
+ ? "off"
243
+ : value.enabled === true
244
+ ? "deep"
245
+ : "off";
246
+ return { mode };
573
247
  }