pi-ultracode 0.5.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -33,7 +33,7 @@ Current releases are tested with Pi 0.84.
33
33
 
34
34
  ## Core features
35
35
 
36
- - **Adaptive semantic depth** — choose `auto`, `focused`, `standard`, or `deep`; `auto` selects the smallest evidence-sufficient depth and `off` restores the previous thinking level.
36
+ - **Adaptive semantic depth** — choose `auto`, `focused`, `standard`, or `deep`; `auto` selects the smallest evidence-sufficient depth while the parent effort remains under user control.
37
37
  - **Explicit workflows** — compose work with `agent()`, `parallel()`, `pipeline()`, and nested `workflow()` calls.
38
38
  - **Independent subagents** — each agent gets its own Pi session, context, tools, model selection, and optional role.
39
39
  - **Parallel worktree isolation** — writing agents can work in temporary git worktrees before their patches are integrated.
@@ -63,9 +63,9 @@ Useful commands:
63
63
  | `/ultracode auto` | Select adaptive semantic-depth routing |
64
64
  | `/ultracode focused` | Fix the lightweight, narrowly scoped policy |
65
65
  | `/ultracode standard` | Fix the balanced policy with conditional verification |
66
- | `/ultracode deep` | Fix the high-assurance policy with deep verification and max effort |
67
- | `/ultracode off` | Disable it and restore the previous thinking level |
68
- | `/ultracode status` | Show the configured mode and effective thinking level |
66
+ | `/ultracode deep` | Fix the high-assurance policy with deep verification |
67
+ | `/ultracode off` | Disable it without changing the parent effort |
68
+ | `/ultracode status` | Show the configured semantic-depth mode |
69
69
  | `/workflows` or `F6` | Open the workflow browser |
70
70
  | `/workflows <runId>` | Open a specific run |
71
71
  | `/workflows abort` | Abort active runs |
@@ -83,7 +83,7 @@ Depth is semantic, not time-based:
83
83
 
84
84
  Research stops when key claims have direct evidence, no material conflict or unresolved high-risk question remains, and another round would repeat known evidence. Wall-clock time, deadlines, and duration limits are never used to choose or stop analysis depth. `maxAgents` and `reserveAgents` remain structural admission limits.
85
85
 
86
- Focused defaults to medium effort, auto and standard to high, and deep to max; individual workflow agents can still override effort with a model suffix. A separate skeptic or synthesis agent is not automatic.
86
+ Ultracode never changes the parent session's effort. For workflow children, the parent selects effort per assigned task with a model suffix: typically `:medium` for bounded discovery or synthesis, `:high` for substantive analysis or implementation, and `:max` only for deep or decisive high-risk verification. The workflow UI reports each child's actual model-clamped effort. If no suffix is supplied, the child uses its normal user/model configuration. A separate skeptic or synthesis agent is not automatic.
87
87
 
88
88
  ## Workflow example
89
89
 
package/README.zh-CN.md CHANGED
@@ -33,7 +33,7 @@ pi --ultracode
33
33
 
34
34
  ## 核心特性
35
35
 
36
- - **自适应语义深度**:可选择 `auto`、`focused`、`standard` 或 `deep`;`auto` 使用证据充分的最小深度,关闭时恢复之前的思考强度。
36
+ - **自适应语义深度**:可选择 `auto`、`focused`、`standard` 或 `deep`;`auto` 使用证据充分的最小深度,父代理 effort 始终由用户控制。
37
37
  - **显式工作流**:通过 `agent()`、`parallel()`、`pipeline()` 和嵌套 `workflow()` 组合任务。
38
38
  - **独立子代理**:每个代理拥有独立的 Pi 会话、上下文、工具、模型选择和可选角色。
39
39
  - **并行 worktree 隔离**:写入型代理可在临时 git worktree 中工作,再集成补丁。
@@ -63,13 +63,18 @@ Pi 会自行判断工作流是否有帮助。小任务仍可继续使用普通
63
63
  | `/ultracode auto` | 使用自适应语义深度路由 |
64
64
  | `/ultracode focused` | 固定使用轻量、聚焦的分析策略 |
65
65
  | `/ultracode standard` | 固定使用平衡策略和条件验证 |
66
- | `/ultracode deep` | 固定使用高保障深度验证和 max effort |
67
- | `/ultracode off` | 关闭并恢复之前的思考强度 |
68
- | `/ultracode status` | 查看配置模式和实际思考强度 |
66
+ | `/ultracode deep` | 固定使用高保障深度验证 |
67
+ | `/ultracode off` | 关闭且不改变父代理 effort |
68
+ | `/ultracode status` | 查看配置的语义深度模式 |
69
+ | `/ultracode default on` | 全局默认开启,新会话使用 `auto` |
70
+ | `/ultracode default off` | 取消默认开启 |
71
+ | `/ultracode default` | 查看全局启动默认值 |
69
72
  | `/workflows` 或 `F6` | 打开工作流浏览器 |
70
73
  | `/workflows <runId>` | 打开指定运行 |
71
74
  | `/workflows abort` | 中止活动运行 |
72
75
 
76
+ `/ultracode default on|off` 只修改启动默认值,不切换当前会话的模式,也不改变 effort。设置保存在 `~/.pi/agent/ultracode.json`(遵循 `PI_CODING_AGENT_DIR`),跨项目和重启生效;未设置时默认关闭。启动时,只有当前分支没有保存 Ultracode 模式才应用默认值,已有的 `off` 或深度选择在 reload、resume、fork 后继续保留。显式 `pi --ultracode` 仍可启用 `auto`。如需立即开启当前会话,执行 `/ultracode auto`。
77
+
73
78
  按 `Esc` 可取消正在运行的工作流。在 Pi 的 fullscreen TUI 中,请在工作流详情内使用 `Ctrl+PageUp`、`Ctrl+PageDown` 和 `Ctrl+End`。
74
79
 
75
80
  ## 分析深度
@@ -83,7 +88,7 @@ Pi 会自行判断工作流是否有帮助。小任务仍可继续使用普通
83
88
 
84
89
  当关键结论已有直接证据、没有实质冲突或未解决的高风险问题,且下一轮只会重复已知证据时停止。墙钟时间、deadline 和 duration limit 不得用于选择或停止分析深度。`maxAgents` 与 `reserveAgents` 继续作为结构性准入限制。
85
90
 
86
- Focused 默认使用 medium effort,auto standard 默认使用 high,deep 使用 max;单个 workflow agent 仍可通过模型后缀覆盖 effortskeptic 和独立 synthesis agent 都不是默认步骤。
91
+ Ultracode 不会修改父会话的 effort。对于 workflow 子代理,父代理会按具体任务通过模型后缀选择 effort:边界明确的搜索或汇总通常使用 `:medium`,实质分析或实现使用 `:high`,只有深度调查或决定性的高风险验证才使用 `:max`。Workflow UI 会展示每个子代理经模型裁剪后实际生效的 effort;未指定后缀时,子会话使用正常的用户/模型配置。skeptic 和独立 synthesis agent 都不是默认步骤。
87
92
 
88
93
  ## 工作流示例
89
94
 
@@ -5,16 +5,11 @@
5
5
  * workflow orchestration, and the `/ultracode` / `/workflows` commands.
6
6
  */
7
7
 
8
- import {
9
- getAgentDir,
10
- SettingsManager,
11
- VERSION as PI_VERSION,
12
- type ExtensionAPI,
13
- } from "@earendil-works/pi-coding-agent";
8
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
14
9
  import { createWorkflowTool, type WorkflowToolDeps } from "../src/workflow/tool.ts";
15
10
  import { UltracodeMode, type ThinkingPreferenceStore } from "../src/mode.ts";
16
- import { isThinkingLevel, piVersionSupportsMaxThinking } from "../src/thinking.ts";
17
11
  import { registerCommands } from "../src/commands.ts";
12
+ import { UltracodePreferences, type UltracodePreferenceStore } from "../src/preferences.ts";
18
13
  import { WorkflowRegistry } from "../src/workflow/registry.ts";
19
14
 
20
15
  export interface ThinkingPreferenceContext {
@@ -23,7 +18,8 @@ export interface ThinkingPreferenceContext {
23
18
  }
24
19
 
25
20
  export interface UltracodeExtensionDeps extends Partial<WorkflowToolDeps> {
26
- /** SDK-host seam when the active profile differs from Pi's ambient agent dir. */
21
+ preferences?: UltracodePreferenceStore;
22
+ /** @deprecated Parent effort is user-owned; retained for source compatibility. */
27
23
  createThinkingPreferenceStore?: (
28
24
  context: ThinkingPreferenceContext,
29
25
  ) => ThinkingPreferenceStore | undefined;
@@ -31,22 +27,21 @@ export interface UltracodeExtensionDeps extends Partial<WorkflowToolDeps> {
31
27
 
32
28
  export default function extension(pi: ExtensionAPI, extraDeps: UltracodeExtensionDeps = {}): void {
33
29
  const mode = new UltracodeMode("workflow");
34
- mode.setRuntimeSupportsMaxThinking(piVersionSupportsMaxThinking(PI_VERSION));
35
30
  const {
36
- createThinkingPreferenceStore = createPiThinkingPreferenceStore,
31
+ createThinkingPreferenceStore: _unusedThinkingPreferenceStore,
32
+ preferences = new UltracodePreferences(),
37
33
  ...workflowDeps
38
34
  } = extraDeps;
39
35
 
40
36
  const registry = workflowDeps.registry ?? new WorkflowRegistry();
41
37
  const workflowTool = createWorkflowTool({
42
- getThinkingLevel: () => mode.getSubagentThinkingLevel(),
43
38
  ...workflowDeps,
44
39
  registry,
45
40
  isExecutionAllowed: () => mode.isEnforcing(),
46
41
  });
47
42
  pi.registerTool(workflowTool);
48
43
 
49
- registerCommands(pi, mode, registry);
44
+ registerCommands(pi, mode, registry, preferences);
50
45
 
51
46
  // Opt-in via CLI flag: `pi --ultracode`.
52
47
  pi.registerFlag("ultracode", {
@@ -72,103 +67,53 @@ export default function extension(pi: ExtensionAPI, extraDeps: UltracodeExtensio
72
67
  });
73
68
 
74
69
  pi.on("session_start", async (_event, ctx) => {
75
- await mode.flushThinkingPreference();
76
- mode.setCurrentModelSupportsThinking(ctx.model ? Boolean(ctx.model.reasoning) : undefined);
77
- try {
78
- mode.bindThinkingPreferenceStore(createThinkingPreferenceStore({
79
- cwd: ctx.cwd,
80
- projectTrusted: ctx.isProjectTrusted(),
81
- }));
82
- } catch {
83
- mode.bindThinkingPreferenceStore(undefined);
84
- }
85
70
  // Restore persisted mode state across reload / resume / fork.
71
+ let hasSavedMode = true;
86
72
  try {
87
73
  // Mode entries are branch-local; discarded future branches must not win.
88
- mode.restore(pi, ctx.sessionManager.getBranch() as any);
74
+ hasSavedMode = mode.restore(pi, ctx.sessionManager.getBranch() as any);
89
75
  } catch {
90
- // ignore
76
+ // Do not apply a startup default when branch restoration failed.
91
77
  }
92
78
  if (!mode.isEnabled() && pi.getFlag?.("ultracode") === true) {
93
79
  mode.enable(pi, "auto");
80
+ } else if (!hasSavedMode && !mode.isEnabled()) {
81
+ try {
82
+ if (preferences.getDefaultEnabled()) mode.enable(pi, "auto");
83
+ } catch (error) {
84
+ ctx.ui.notify(`Failed to read Ultracode default: ${String(error)}`, "warning");
85
+ }
94
86
  }
95
- // Registration makes extension tools discoverable; activation remains opt-in.
87
+ // Registration makes tools discoverable; activation follows the chosen mode.
96
88
  mode.syncWorkflowTool(pi);
97
- await mode.flushThinkingPreference();
98
89
  if (ctx.hasUI) {
99
- ctx.ui.setStatus("ultracode", mode.isEnabled() ? mode.statusLine() : undefined);
90
+ ctx.ui.setStatus(
91
+ "ultracode",
92
+ mode.isEnabled() ? mode.statusLine((label) => ctx.ui.theme.fg("accent", label)) : undefined,
93
+ );
100
94
  }
101
95
  });
102
96
 
103
97
  pi.on("session_tree", async (_event, ctx) => {
104
- await mode.flushThinkingPreference();
105
98
  if (mode.isSuspended()) return;
106
- mode.setCurrentModelSupportsThinking(ctx.model ? Boolean(ctx.model.reasoning) : undefined);
107
99
  mode.restore(pi, ctx.sessionManager.getBranch() as any);
108
- await mode.flushThinkingPreference();
109
100
  if (ctx.hasUI) {
110
- ctx.ui.setStatus("ultracode", mode.isEnabled() ? mode.statusLine() : undefined);
101
+ ctx.ui.setStatus(
102
+ "ultracode",
103
+ mode.isEnabled() ? mode.statusLine((label) => ctx.ui.theme.fg("accent", label)) : undefined,
104
+ );
111
105
  }
112
106
  });
113
107
 
114
- pi.on("model_select", async (event, ctx) => {
115
- mode.setCurrentModelSupportsThinking(Boolean(event.model.reasoning));
116
- const refreshStatus = mode.handleModelSelect(pi);
117
- await mode.flushThinkingPreference();
118
- if (refreshStatus && ctx.hasUI) ctx.ui.setStatus("ultracode", mode.statusLine());
119
- });
120
-
121
- pi.on("thinking_level_select", async (event, ctx) => {
122
- if (!mode.handleThinkingLevelSelect(pi, event.level)) return;
123
- await mode.flushThinkingPreference();
124
- if (ctx.hasUI) ctx.ui.setStatus("ultracode", mode.statusLine());
125
- });
126
-
127
108
  pi.on("session_shutdown", async () => {
128
- // Quiesce first so late model/effort events cannot undo restoration. The
129
109
  // The persisted configured mode remains active for reload/resume/fork replacements.
130
110
  mode.suspend(pi);
131
- await mode.flushThinkingPreference();
132
111
  });
133
112
 
134
113
  pi.on("before_agent_start", async (event) => {
135
- // Reconcile the tool schema and always append the standing block on an
114
+ // Reconcile tool availability and append the standing policy on every
136
115
  // enforcing turn, even when another active-tool writer caused drift.
137
116
  mode.syncWorkflowTool(pi);
138
- mode.reapplyConfiguredThinking(pi);
139
- await mode.flushThinkingPreference();
140
117
  return mode.beforeAgentStart(event);
141
118
  });
142
119
  }
143
-
144
- function createPiThinkingPreferenceStore(
145
- context: ThinkingPreferenceContext,
146
- ): ThinkingPreferenceStore {
147
- const createSettings = () => SettingsManager.create(
148
- context.cwd,
149
- getAgentDir(),
150
- { projectTrusted: context.projectTrusted },
151
- );
152
- return {
153
- getThinkingPreference() {
154
- // Use a fresh manager so a selection made after session_start is visible;
155
- // SettingsManager instances intentionally keep their own cached snapshot.
156
- const settings = createSettings();
157
- const global = settings.getGlobalSettings().defaultThinkingLevel;
158
- const effective = settings.getDefaultThinkingLevel();
159
- return {
160
- global: isThinkingLevel(global) ? global : undefined,
161
- effective: isThinkingLevel(effective) ? effective : "medium",
162
- };
163
- },
164
- async setDefaultThinkingLevel(level) {
165
- // Use a fresh queue after the mode's macrotask barrier. This serializes
166
- // behind Pi's already-enqueued writes instead of racing a long-lived peer.
167
- const settings = createSettings();
168
- // An absent setting is semantically Pi's `medium` default. SettingsManager
169
- // has no unset operation, so restore that equivalent value explicitly.
170
- settings.setDefaultThinkingLevel((level ?? "medium") as any);
171
- await settings.flush();
172
- },
173
- };
174
- }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-ultracode",
3
- "version": "0.5.0",
3
+ "version": "0.7.0",
4
4
  "description": "Adaptive semantic-depth workflow orchestration for Pi, with focused, standard, and deep modes, isolated subagents, worktree delivery, structured output, and durable resume.",
5
5
  "type": "module",
6
6
  "keywords": [
package/src/commands.ts CHANGED
@@ -5,34 +5,60 @@
5
5
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
6
6
  import { ACTIVE_ULTRACODE_MODES, isActiveUltracodeMode } from "./depth.ts";
7
7
  import type { UltracodeMode } from "./mode.ts";
8
+ import type { UltracodePreferenceStore } from "./preferences.ts";
8
9
  import type { WorkflowRegistry } from "./workflow/registry.ts";
9
10
  import { workflowRunsDir } from "./workflow/tool.ts";
10
11
  import { openWorkflowOverlay } from "./workflow/workflow-overlay.ts";
11
12
 
12
- export function registerCommands(pi: ExtensionAPI, mode: UltracodeMode, registry: WorkflowRegistry): void {
13
+ export function registerCommands(
14
+ pi: ExtensionAPI,
15
+ mode: UltracodeMode,
16
+ registry: WorkflowRegistry,
17
+ preferences: UltracodePreferenceStore,
18
+ ): void {
13
19
  pi.registerCommand("ultracode", {
14
- description: "Toggle adaptive Ultracode, or select a fixed semantic depth: auto|focused|standard|deep|off|status",
20
+ description: "Toggle Ultracode, select auto|focused|standard|deep|off|status, or set default on|off",
15
21
  getArgumentCompletions(prefix: string) {
16
- return [...ACTIVE_ULTRACODE_MODES, "off", "status"]
22
+ return [...ACTIVE_ULTRACODE_MODES, "off", "status", "default", "default on", "default off"]
17
23
  .filter((value) => value.startsWith(prefix))
18
24
  .map((value) => ({ value, label: value }));
19
25
  },
20
26
  handler: async (args: string, ctx) => {
21
- mode.setCurrentModelSupportsThinking(ctx.model ? Boolean(ctx.model.reasoning) : undefined);
22
27
  const parts = args.trim().split(/\s+/).filter(Boolean);
23
28
  const sub = (parts[0] ?? "").toLowerCase();
24
29
 
25
30
  // Bare `/ultracode` enables auto from off and disables any active mode.
26
31
  if (sub === "") {
27
32
  const nowOn = mode.toggle(pi);
28
- await mode.flushThinkingPreference();
29
33
  ctx.ui.notify(
30
34
  nowOn
31
35
  ? `Ultracode auto — ${mode.statusLine()}`
32
- : "Ultracode off — thinking restored and workflow tool disabled.",
36
+ : "Ultracode off — workflow tool disabled; parent effort unchanged.",
33
37
  "info",
34
38
  );
35
- ctx.ui.setStatus("ultracode", nowOn ? mode.statusLine() : undefined);
39
+ ctx.ui.setStatus(
40
+ "ultracode",
41
+ nowOn ? mode.statusLine((label) => ctx.ui.theme.fg("accent", label)) : undefined,
42
+ );
43
+ return;
44
+ }
45
+
46
+ if (sub === "default") {
47
+ const value = parts[1]?.toLowerCase();
48
+ if (parts.length > 2 || (value !== undefined && value !== "on" && value !== "off")) {
49
+ ctx.ui.notify("Usage: /ultracode default [on|off]", "error");
50
+ return;
51
+ }
52
+ try {
53
+ if (value !== undefined) preferences.setDefaultEnabled(value === "on");
54
+ const enabled = preferences.getDefaultEnabled();
55
+ ctx.ui.notify(
56
+ `Ultracode default ${enabled ? "on (auto)" : "off"} — global startup preference; current session unchanged.`,
57
+ "info",
58
+ );
59
+ } catch (error) {
60
+ ctx.ui.notify(`Failed to ${value === undefined ? "read" : "save"} Ultracode default: ${String(error)}`, "error");
61
+ }
36
62
  return;
37
63
  }
38
64
 
@@ -48,8 +74,7 @@ export function registerCommands(pi: ExtensionAPI, mode: UltracodeMode, registry
48
74
 
49
75
  if (sub === "off") {
50
76
  mode.disable(pi);
51
- await mode.flushThinkingPreference();
52
- ctx.ui.notify("Ultracode off — thinking restored and workflow tool disabled.", "info");
77
+ ctx.ui.notify("Ultracode off — workflow tool disabled; parent effort unchanged.", "info");
53
78
  ctx.ui.setStatus("ultracode", undefined);
54
79
  return;
55
80
  }
@@ -60,9 +85,11 @@ export function registerCommands(pi: ExtensionAPI, mode: UltracodeMode, registry
60
85
  }
61
86
 
62
87
  mode.enable(pi, sub);
63
- await mode.flushThinkingPreference();
64
88
  ctx.ui.notify(`Ultracode ${sub} — ${mode.statusLine()}`, "info");
65
- ctx.ui.setStatus("ultracode", mode.statusLine());
89
+ ctx.ui.setStatus(
90
+ "ultracode",
91
+ mode.statusLine((label) => ctx.ui.theme.fg("accent", label)),
92
+ );
66
93
  },
67
94
  });
68
95
 
@@ -100,5 +127,5 @@ export function registerCommands(pi: ExtensionAPI, mode: UltracodeMode, registry
100
127
  }
101
128
 
102
129
  function ultracodeUsage(): string {
103
- return "Usage: /ultracode [auto|focused|standard|deep|off|status]";
130
+ return "Usage: /ultracode [auto|focused|standard|deep|off|status] or /ultracode default [on|off]";
104
131
  }
package/src/depth.ts CHANGED
@@ -1,4 +1,4 @@
1
- /** Ultracode's configured analysis modes and their default reasoning effort. */
1
+ /** Ultracode's configured semantic analysis-depth modes. */
2
2
 
3
3
  import type { ThinkingLevel } from "./thinking.ts";
4
4
 
@@ -18,10 +18,9 @@ export function isUltracodeModeName(value: unknown): value is UltracodeModeName
18
18
  }
19
19
 
20
20
  /**
21
- * Default effort for the parent turn and for child agents that do not override
22
- * it explicitly. Auto starts from the balanced standard effort; its prompt
23
- * policy can route individual workflow agents lower or higher as evidence
24
- * requires. Pi still clamps each request to the selected model's capabilities.
21
+ * @deprecated Legacy mode-to-effort mapping retained for API compatibility.
22
+ * Ultracode no longer applies it to the parent or as a workflow child default;
23
+ * each child selects effort per task and Pi clamps it to model capabilities.
25
24
  */
26
25
  export function thinkingLevelForMode(mode: UltracodeModeName): ThinkingLevel | undefined {
27
26
  switch (mode) {
package/src/mode.ts CHANGED
@@ -2,8 +2,7 @@
2
2
  * Ultracode mode controller.
3
3
  *
4
4
  * Ultracode is a session-scoped semantic analysis-depth mode. While active, it:
5
- * - applies the configured mode's default thinking level while preserving the
6
- * user's previous level,
5
+ * - leaves the parent session's thinking level under user control,
7
6
  * - keeps the `workflow` tool active,
8
7
  * - injects the configured auto/focused/standard/deep policy on every turn,
9
8
  * - persists branch-local mode state across reload, resume, fork, and compaction.
@@ -12,17 +11,11 @@
12
11
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
13
12
  import {
14
13
  isActiveUltracodeMode,
15
- thinkingLevelForMode,
16
14
  type ActiveUltracodeMode,
17
15
  type UltracodeModeName,
18
16
  } from "./depth.ts";
19
17
  import { ULTRACODE_ACTIVE_REMINDER, ULTRACODE_TAGLINE, ultracodeSystemBlock } from "./prompts.ts";
20
- import {
21
- LEGACY_ULTRACODE_THINKING_LEVEL,
22
- ULTRACODE_THINKING_LEVEL,
23
- isThinkingLevel,
24
- type ThinkingLevel,
25
- } from "./thinking.ts";
18
+ import type { ThinkingLevel } from "./thinking.ts";
26
19
 
27
20
  export type { ThinkingLevel } from "./thinking.ts";
28
21
 
@@ -30,21 +23,15 @@ export const MODE_ENTRY_TYPE = "ultracode-mode";
30
23
 
31
24
  interface PersistedModeState {
32
25
  mode: UltracodeModeName;
33
- previousThinking?: ThinkingLevel;
34
- /** `null` records that the setting was originally absent (Pi defaults to medium). */
35
- previousDefaultThinking?: ThinkingLevel | null;
36
- /** Deferred when the selected model cannot represent the pre-mode level. */
37
- pendingPreviousThinking?: ThinkingLevel;
38
26
  }
39
27
 
28
+ /** @deprecated Ultracode no longer reads or writes the parent's effort preference. */
40
29
  export interface ThinkingPreferenceSnapshot {
41
- /** Raw global value; undefined means Pi's implicit medium default. */
42
30
  global: ThinkingLevel | undefined;
43
- /** Global + project merged value used by Pi for model switches. */
44
31
  effective: ThinkingLevel | undefined;
45
32
  }
46
33
 
47
- /** 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. */
48
35
  export interface ThinkingPreferenceStore {
49
36
  getThinkingPreference(): ThinkingPreferenceSnapshot;
50
37
  setDefaultThinkingLevel(level: ThinkingLevel | undefined): void | Promise<void>;
@@ -54,51 +41,23 @@ export interface ThinkingPreferenceStore {
54
41
  export class UltracodeMode {
55
42
  private mode: UltracodeModeName = "off";
56
43
  private suspended = false;
57
- private previousThinking: ThinkingLevel | undefined;
58
- private previousDefaultThinking: ThinkingLevel | null | undefined;
59
- /** Restore a level later if the current non-reasoning model clamps it to off. */
60
- private pendingPreviousThinking: ThinkingLevel | undefined;
61
- /** The level Pi actually applied after clamping the configured request. */
62
- private appliedThinking: ThinkingLevel | undefined;
63
- /** Prevent mode-owned thinking changes from being mistaken for manual overrides. */
64
- private applyingThinking = false;
65
- private thinkingPreferenceStore: ThinkingPreferenceStore | undefined;
66
- private preferenceWriteQueue: Promise<void> = Promise.resolve();
67
- private preferenceWriteGeneration = 0;
68
- private legacyDefaultMigrationPending = false;
69
- private pendingClearGeneration = 0;
70
- private currentModelSupportsThinking: boolean | undefined;
71
- private runtimeSupportsMaxThinking = true;
72
44
  private readonly workflowToolName: string;
73
45
 
74
46
  constructor(workflowToolName: string) {
75
47
  this.workflowToolName = workflowToolName;
76
48
  }
77
49
 
78
- /** Bind the settings adapter once the session cwd is known. */
79
- bindThinkingPreferenceStore(store: ThinkingPreferenceStore | undefined): void {
80
- this.preferenceWriteGeneration++;
81
- this.thinkingPreferenceStore = store;
82
- }
50
+ /** @deprecated Parent effort is user-owned; retained as a compatibility no-op. */
51
+ bindThinkingPreferenceStore(_store: ThinkingPreferenceStore | undefined): void {}
83
52
 
84
- /** Track capability explicitly; `off` alone cannot distinguish clamp from intent. */
85
- setCurrentModelSupportsThinking(supportsThinking: boolean | undefined): void {
86
- this.currentModelSupportsThinking = supportsThinking;
87
- }
53
+ /** @deprecated Parent effort is user-owned; retained as a compatibility no-op. */
54
+ setCurrentModelSupportsThinking(_supportsThinking: boolean | undefined): void {}
88
55
 
89
- /** Configure migration behavior for Pi versions released before `max`. */
90
- setRuntimeSupportsMaxThinking(supportsMaxThinking: boolean): void {
91
- this.runtimeSupportsMaxThinking = supportsMaxThinking;
92
- }
56
+ /** @deprecated Parent effort is user-owned; retained as a compatibility no-op. */
57
+ setRuntimeSupportsMaxThinking(_supportsMaxThinking: boolean): void {}
93
58
 
94
- /** Wait for queued preference restoration before teardown or command completion. */
95
- async flushThinkingPreference(): Promise<void> {
96
- try {
97
- await this.preferenceWriteQueue;
98
- } catch {
99
- // Pi owns settings error reporting; effort enforcement must remain usable.
100
- }
101
- }
59
+ /** @deprecated Parent effort is user-owned; retained as a compatibility no-op. */
60
+ async flushThinkingPreference(): Promise<void> {}
102
61
 
103
62
  /** Enable auto if off, otherwise disable. Returns the new enabled state. */
104
63
  toggle(pi: ExtensionAPI): boolean {
@@ -114,84 +73,51 @@ export class UltracodeMode {
114
73
  return this.mode;
115
74
  }
116
75
 
117
- /** The thinking level Pi actually applied after model/runtime clamping. */
76
+ /** @deprecated Ultracode no longer applies a parent thinking level. */
118
77
  getAppliedThinking(): ThinkingLevel | undefined {
119
- return this.appliedThinking;
78
+ return undefined;
120
79
  }
121
80
 
122
81
  /**
123
- * Return the configured raw effort request so every workflow subagent is
124
- * 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.
125
85
  */
126
86
  getSubagentThinkingLevel(): ThinkingLevel | undefined {
127
- return this.isEnforcing() ? thinkingLevelForMode(this.mode) : undefined;
87
+ return undefined;
128
88
  }
129
89
 
130
- /** Reassert the configured mode effort before a turn or after a model change. */
131
- reapplyConfiguredThinking(pi: ExtensionAPI): boolean {
132
- if (!this.isEnforcing()) return false;
133
- this.applyConfiguredThinking(pi);
134
- return true;
90
+ /** @deprecated Parent effort is user-owned; retained as a compatibility no-op. */
91
+ reapplyConfiguredThinking(_pi: ExtensionAPI): boolean {
92
+ return false;
135
93
  }
136
94
 
137
- /** @deprecated Use reapplyConfiguredThinking(). */
138
- reapplyMaximumThinking(pi: ExtensionAPI): boolean {
139
- return this.reapplyConfiguredThinking(pi);
95
+ /** @deprecated Parent effort is user-owned; retained as a compatibility no-op. */
96
+ reapplyMaximumThinking(_pi: ExtensionAPI): boolean {
97
+ return false;
140
98
  }
141
99
 
142
- /**
143
- * Handle model switches both while active and after a clamped restoration.
144
- * Returns true when Ultracode remains active and the UI should be refreshed.
145
- */
146
- handleModelSelect(pi: ExtensionAPI): boolean {
147
- if (this.suspended) return false;
148
- this.pendingClearGeneration++;
149
- if (this.isEnabled()) {
150
- this.applyConfiguredThinking(pi);
151
- return true;
152
- }
153
- if (this.pendingPreviousThinking) {
154
- const pending = this.pendingPreviousThinking;
155
- this.applyCompatibleThinking(pi, pending);
156
- if (this.pendingRestoreSucceeded(pending)) this.pendingPreviousThinking = undefined;
157
- this.persist(pi);
158
- }
100
+ /** @deprecated Model changes do not alter Ultracode mode or parent effort. */
101
+ handleModelSelect(_pi: ExtensionAPI): boolean {
159
102
  return false;
160
103
  }
161
104
 
162
- /** Restore the pre-mode effective effort without changing persisted mode state. */
163
- restorePreviousThinking(pi: ExtensionAPI): void {
164
- if (this.isEnabled() && this.previousThinking) this.applyCompatibleThinking(pi, this.previousThinking);
165
- }
105
+ /** @deprecated Parent effort is never restored because Ultracode never changes it. */
106
+ restorePreviousThinking(_pi: ExtensionAPI): void {}
166
107
 
167
- /** Stop enforcing synchronously, then restore effort before session teardown. */
108
+ /** Quiesce tool and prompt enforcement before session teardown. */
168
109
  suspend(pi: ExtensionAPI): void {
169
110
  if (this.suspended) {
170
111
  this.syncWorkflowTool(pi);
171
112
  return;
172
113
  }
173
114
  this.suspended = true;
174
- this.restorePreviousThinking(pi);
175
115
  this.syncWorkflowTool(pi);
176
116
  }
177
117
 
178
- /**
179
- * Enforce the configured effort after an external thinking-level selection.
180
- * Stale events and events emitted by this mode are ignored to avoid recursion.
181
- */
182
- handleThinkingLevelSelect(pi: ExtensionAPI, level: ThinkingLevel): boolean {
183
- if (this.suspended || this.applyingThinking) return false;
184
- const current = safeGetThinking(pi);
185
- if (!current || current !== level) return false;
186
- if (!this.isEnabled()) {
187
- // Pi emits the same event for a user selection and an automatic model
188
- // re-clamp. Defer clearing until model_select has had a chance to consume it.
189
- if (this.pendingPreviousThinking) this.deferPendingClear(pi, level);
190
- return false;
191
- }
192
- if (current === this.appliedThinking) return false;
193
- this.applyConfiguredThinking(pi);
194
- return true;
118
+ /** @deprecated User-selected parent effort is accepted without interception. */
119
+ handleThinkingLevelSelect(_pi: ExtensionAPI, _level: ThinkingLevel): boolean {
120
+ return false;
195
121
  }
196
122
 
197
123
  isEnabled(): boolean {
@@ -213,58 +139,28 @@ export class UltracodeMode {
213
139
  }
214
140
 
215
141
  /**
216
- * Enable or switch modes without replacing the saved baseline. The no-argument
217
- * form retains the pre-0.5 programmatic deep behavior; user commands pass an
218
- * explicit mode and bare `/ultracode` uses toggle() to enter auto.
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.
219
145
  */
220
146
  enable(pi: ExtensionAPI, mode: ActiveUltracodeMode = "deep"): void {
221
147
  this.suspended = false;
222
- this.pendingPreviousThinking = undefined;
223
- this.pendingClearGeneration++;
224
- if (!this.isEnabled()) {
225
- const current = safeGetThinking(pi);
226
- const preference = this.captureThinkingPreference();
227
- const effectivePreference = this.runtimeCompatibleThinking(preference.effective) as
228
- | ThinkingLevel
229
- | undefined;
230
- const maxIsUnknownToRuntime = !this.runtimeSupportsMaxThinking
231
- && preference.effective === ULTRACODE_THINKING_LEVEL;
232
- // A non-reasoning model exposes only `off`; preserve the merged default
233
- // that Pi would use when the user later selects a reasoning model. A
234
- // pre-max Pi also reports a persisted max preference as off.
235
- this.previousThinking = current === "off"
236
- && (this.currentModelSupportsThinking !== true || maxIsUnknownToRuntime)
237
- ? effectivePreference ?? current
238
- : current;
239
- this.previousDefaultThinking = this.runtimeCompatibleThinking(preference.global);
240
- this.legacyDefaultMigrationPending = false;
241
- }
242
148
  this.mode = mode;
243
- this.applyConfiguredThinking(pi);
244
149
  this.syncWorkflowTool(pi);
245
150
  this.persist(pi);
246
151
  }
247
152
 
248
- /** Turn Ultracode off, restoring the pre-mode thinking level. */
153
+ /** Turn Ultracode off without changing the parent thinking level. */
249
154
  disable(pi: ExtensionAPI): void {
250
- if (!this.isEnabled()) {
251
- this.syncWorkflowTool(pi);
252
- return;
253
- }
254
- const previous = this.previousThinking;
255
- this.restorePreviousThinking(pi);
256
- this.pendingPreviousThinking = previous && !this.pendingRestoreSucceeded(previous)
257
- ? previous
258
- : undefined;
259
- this.pendingClearGeneration++;
155
+ // Persist an explicit off even when already disabled, so startup defaults
156
+ // cannot override this session's choice on reload or resume.
260
157
  this.mode = "off";
261
158
  this.suspended = false;
262
159
  this.syncWorkflowTool(pi);
263
160
  this.persist(pi);
264
161
  }
265
162
 
266
-
267
- /** Restore mode state from the active session branch. */
163
+ /** Restore branch-local state; return whether a saved mode was found. */
268
164
  restore(
269
165
  pi: ExtensionAPI,
270
166
  entries: Array<{
@@ -273,121 +169,19 @@ export class UltracodeMode {
273
169
  data?: unknown;
274
170
  thinkingLevel?: unknown;
275
171
  }>,
276
- ): void {
277
- const wasEnforcing = this.isEnforcing();
278
- this.pendingClearGeneration++;
172
+ ): boolean {
279
173
  let latestData: unknown;
280
- let branchThinking: ThinkingLevel | undefined;
281
- let thinkingAfterLatestMode: ThinkingLevel | undefined;
282
174
  for (const entry of entries) {
283
175
  if (entry.type === "custom" && entry.customType === MODE_ENTRY_TYPE && entry.data) {
284
176
  latestData = entry.data;
285
- thinkingAfterLatestMode = undefined;
286
- } else if (entry.type === "thinking_level_change" && isThinkingLevel(entry.thinkingLevel)) {
287
- branchThinking = entry.thinkingLevel;
288
- if (latestData !== undefined) thinkingAfterLatestMode = entry.thinkingLevel;
289
177
  }
290
178
  }
291
179
 
292
- const current = safeGetThinking(pi);
293
- const preference = this.captureThinkingPreference();
294
- const effectivePreference = this.runtimeCompatibleThinking(preference.effective) as
295
- | ThinkingLevel
296
- | undefined;
297
- const globalPreference = this.runtimeCompatibleThinking(preference.global);
298
- const latest = parsePersistedModeState(latestData);
299
- if (!latest) {
300
- const target = this.runtimeCompatibleThinking(
301
- branchThinking
302
- ?? (wasEnforcing ? this.previousThinking : undefined)
303
- ?? effectivePreference,
304
- ) as ThinkingLevel | undefined;
305
- this.mode = "off";
306
- this.suspended = false;
307
- this.pendingPreviousThinking = undefined;
308
- this.previousThinking = target;
309
- this.previousDefaultThinking = globalPreference;
310
- this.legacyDefaultMigrationPending = false;
311
- this.appliedThinking = current;
312
- this.syncWorkflowTool(pi);
313
- if (target && current !== target) this.applyCompatibleThinking(pi, target);
314
- if (target && !this.pendingRestoreSucceeded(target)) {
315
- this.pendingPreviousThinking = target;
316
- this.persist(pi);
317
- } else {
318
- this.previousThinking = undefined;
319
- this.previousDefaultThinking = undefined;
320
- }
321
- return;
322
- }
323
-
180
+ const state = parsePersistedModeState(latestData);
181
+ this.mode = state?.mode ?? "off";
324
182
  this.suspended = false;
325
- this.mode = latest.mode;
326
183
  this.syncWorkflowTool(pi);
327
- const maxIsUnknownToRuntime = !this.runtimeSupportsMaxThinking
328
- && preference.effective === ULTRACODE_THINKING_LEVEL;
329
- this.previousThinking = latest.previousThinking
330
- ?? (current === "off"
331
- && (this.currentModelSupportsThinking !== true || maxIsUnknownToRuntime)
332
- ? effectivePreference ?? current
333
- : current);
334
-
335
- // Pre-preference-store releases persisted only previousThinking while their
336
- // active xhigh request polluted Pi's global default. Recover that baseline
337
- // once instead of treating the known Ultracode value as a user preference.
338
- const migratesLegacyDefault = latest.mode !== "off"
339
- && latest.previousDefaultThinking === undefined
340
- && latest.previousThinking !== undefined
341
- && (preference.global === LEGACY_ULTRACODE_THINKING_LEVEL
342
- || preference.global === ULTRACODE_THINKING_LEVEL);
343
- this.previousDefaultThinking = migratesLegacyDefault
344
- ? this.runtimeCompatibleThinking(latest.previousThinking)
345
- : preference.global !== undefined
346
- ? globalPreference
347
- : this.runtimeCompatibleThinking(latest.previousDefaultThinking);
348
- this.legacyDefaultMigrationPending = migratesLegacyDefault;
349
-
350
- this.pendingPreviousThinking = this.isEnabled() ? undefined : latest.pendingPreviousThinking;
351
- if (this.isEnabled()) {
352
- this.applyConfiguredThinking(pi);
353
- if (migratesLegacyDefault) {
354
- this.queueDefaultThinkingRestore(() => {
355
- this.legacyDefaultMigrationPending = false;
356
- this.persist(pi);
357
- });
358
- }
359
- return;
360
- }
361
-
362
- if (this.pendingPreviousThinking) {
363
- if (thinkingAfterLatestMode !== undefined) {
364
- // A thinking entry after the disabled mode snapshot is an explicit user
365
- // choice and supersedes the older deferred restoration.
366
- const target = this.runtimeCompatibleThinking(thinkingAfterLatestMode) as ThinkingLevel;
367
- this.pendingPreviousThinking = undefined;
368
- this.previousThinking = target;
369
- this.appliedThinking = current;
370
- if (current !== target) this.applyCompatibleThinking(pi, target);
371
- if (!this.pendingRestoreSucceeded(target)) this.pendingPreviousThinking = target;
372
- this.persist(pi);
373
- } else if (this.currentModelSupportsThinking === true) {
374
- const pending = this.pendingPreviousThinking;
375
- this.applyCompatibleThinking(pi, pending);
376
- if (this.pendingRestoreSucceeded(pending)) this.pendingPreviousThinking = undefined;
377
- this.persist(pi);
378
- }
379
- return;
380
- }
381
-
382
- const target = this.runtimeCompatibleThinking(branchThinking ?? latest.previousThinking) as
383
- | ThinkingLevel
384
- | undefined;
385
- this.appliedThinking = current;
386
- if (target && current !== target) this.applyCompatibleThinking(pi, target);
387
- if (target && !this.pendingRestoreSucceeded(target)) {
388
- this.pendingPreviousThinking = target;
389
- this.persist(pi);
390
- }
184
+ return state !== undefined;
391
185
  }
392
186
 
393
187
  /** Append the configured semantic-depth policy to the turn's system prompt. */
@@ -397,128 +191,14 @@ export class UltracodeMode {
397
191
  return { systemPrompt: `${event.systemPrompt}\n\n${block}\n\n${ULTRACODE_ACTIVE_REMINDER}` };
398
192
  }
399
193
 
400
- statusLine(): string {
401
- if (!this.isEnabled()) return "ultracode: off";
402
- const parts = [`ultracode: ${this.mode}`];
403
- // Show the level that actually applied, including compatibility/model fallback.
404
- if (this.appliedThinking) parts.push(this.appliedThinking);
405
- return parts.join(" · ");
406
- }
407
-
408
- private applyConfiguredThinking(pi: ExtensionAPI): void {
409
- const target = thinkingLevelForMode(this.mode);
410
- if (!target) return;
411
- const writeGeneration = this.preferenceWriteGeneration;
412
- this.applyCompatibleThinking(pi, target);
413
- // Pi normally skips persistence when the effective level is unchanged, but
414
- // the extension API does not promise that. Defensively restore the raw
415
- // baseline even after a stable mode-owned request.
416
- if (writeGeneration === this.preferenceWriteGeneration) {
417
- this.queueDefaultThinkingRestore();
418
- }
419
- }
420
-
421
- /** Apply max compatibly and return the concrete level accepted as success. */
422
- private applyCompatibleThinking(pi: ExtensionAPI, level: ThinkingLevel): ThinkingLevel {
423
- this.applyThinking(pi, level);
424
- if (level === ULTRACODE_THINKING_LEVEL && this.appliedThinking !== level) {
425
- // Modern Pi clamps max per model. Pre-max Pi may treat it as unknown, so
426
- // retry xhigh for both activation and restoration of persisted max state.
427
- this.applyThinking(pi, LEGACY_ULTRACODE_THINKING_LEVEL);
428
- return LEGACY_ULTRACODE_THINKING_LEVEL;
429
- }
430
- return level;
431
- }
432
-
433
- private applyThinking(pi: ExtensionAPI, level: ThinkingLevel): void {
434
- const wasApplying = this.applyingThinking;
435
- const before = safeGetThinking(pi);
436
- this.applyingThinking = true;
437
- try {
438
- // Pi clamps the request and persists the effective level. Preference
439
- // restoration is queued after Pi's own SettingsManager chain drains.
440
- pi.setThinkingLevel(level as any);
441
- this.appliedThinking = safeGetThinking(pi) ?? level;
442
- } catch {
443
- this.appliedThinking = safeGetThinking(pi);
444
- } finally {
445
- if (this.appliedThinking !== before) this.queueDefaultThinkingRestore();
446
- this.applyingThinking = wasApplying;
447
- }
194
+ statusLine(styleLabel: (label: string) => string = (label) => label): string {
195
+ return [styleLabel("ultracode"), this.mode].join(" · ");
448
196
  }
449
197
 
450
198
  isEnforcing(): boolean {
451
199
  return this.isEnabled() && !this.suspended;
452
200
  }
453
201
 
454
- private pendingRestoreSucceeded(pending: ThinkingLevel): boolean {
455
- if (this.appliedThinking === pending) return true;
456
- return pending === ULTRACODE_THINKING_LEVEL
457
- && !this.runtimeSupportsMaxThinking
458
- && this.appliedThinking !== undefined
459
- && this.appliedThinking !== "off";
460
- }
461
-
462
- private runtimeCompatibleThinking(
463
- level: ThinkingLevel | null | undefined,
464
- ): ThinkingLevel | null | undefined {
465
- return level === ULTRACODE_THINKING_LEVEL && !this.runtimeSupportsMaxThinking
466
- ? LEGACY_ULTRACODE_THINKING_LEVEL
467
- : level;
468
- }
469
-
470
- private captureThinkingPreference(): {
471
- global: ThinkingLevel | null | undefined;
472
- effective: ThinkingLevel | undefined;
473
- } {
474
- if (!this.thinkingPreferenceStore) return { global: undefined, effective: undefined };
475
- try {
476
- const preference = this.thinkingPreferenceStore.getThinkingPreference();
477
- return {
478
- global: isThinkingLevel(preference.global) ? preference.global : null,
479
- effective: isThinkingLevel(preference.effective) ? preference.effective : undefined,
480
- };
481
- } catch {
482
- return { global: undefined, effective: undefined };
483
- }
484
- }
485
-
486
- private queueDefaultThinkingRestore(onSuccess?: () => void): void {
487
- const store = this.thinkingPreferenceStore;
488
- const baseline = this.runtimeCompatibleThinking(this.previousDefaultThinking);
489
- if (!store || baseline === undefined) return;
490
- const generation = ++this.preferenceWriteGeneration;
491
- this.preferenceWriteQueue = this.preferenceWriteQueue
492
- .catch(() => {})
493
- .then(waitForSettingsWrites)
494
- .then(async () => {
495
- if (generation !== this.preferenceWriteGeneration) return;
496
- const currentGlobal = this.captureThinkingPreference().global;
497
- if (currentGlobal !== baseline) {
498
- await store.setDefaultThinkingLevel(baseline ?? undefined);
499
- await store.flush?.();
500
- }
501
- if (generation === this.preferenceWriteGeneration) onSuccess?.();
502
- })
503
- .catch(() => {
504
- // Keep the session usable even when settings cannot be restored.
505
- });
506
- }
507
-
508
- private deferPendingClear(pi: ExtensionAPI, level: ThinkingLevel): void {
509
- const generation = ++this.pendingClearGeneration;
510
- setImmediate(() => {
511
- if (
512
- generation !== this.pendingClearGeneration
513
- || this.isEnabled()
514
- || this.suspended
515
- || safeGetThinking(pi) !== level
516
- ) return;
517
- this.pendingPreviousThinking = undefined;
518
- this.persist(pi);
519
- });
520
- }
521
-
522
202
  private activateWorkflowTool(pi: ExtensionAPI): void {
523
203
  try {
524
204
  const active = pi.getActiveTools();
@@ -526,7 +206,7 @@ export class UltracodeMode {
526
206
  pi.setActiveTools([...active, this.workflowToolName]);
527
207
  }
528
208
  } catch {
529
- // Mode enforcement remains usable when tool selection is unavailable.
209
+ // Prompt policy remains usable when tool selection is unavailable.
530
210
  }
531
211
  }
532
212
 
@@ -542,14 +222,7 @@ export class UltracodeMode {
542
222
  }
543
223
 
544
224
  private persist(pi: ExtensionAPI): void {
545
- const state: PersistedModeState = {
546
- mode: this.mode,
547
- previousThinking: this.previousThinking,
548
- previousDefaultThinking: this.legacyDefaultMigrationPending
549
- ? undefined
550
- : this.previousDefaultThinking,
551
- pendingPreviousThinking: this.pendingPreviousThinking,
552
- };
225
+ const state: PersistedModeState = { mode: this.mode };
553
226
  try {
554
227
  pi.appendEntry(MODE_ENTRY_TYPE, state);
555
228
  } catch {
@@ -561,7 +234,6 @@ export class UltracodeMode {
561
234
  function parsePersistedModeState(data: unknown): PersistedModeState | undefined {
562
235
  if (!data || typeof data !== "object") return undefined;
563
236
  const value = data as Record<string, unknown>;
564
- const previousDefault = value.previousDefaultThinking;
565
237
  // Sessions written before semantic-depth modes stored only enabled:boolean.
566
238
  // Preserve their behavior by migrating enabled:true to the old deep mode.
567
239
  const mode: UltracodeModeName = isActiveUltracodeMode(value.mode)
@@ -571,27 +243,5 @@ function parsePersistedModeState(data: unknown): PersistedModeState | undefined
571
243
  : value.enabled === true
572
244
  ? "deep"
573
245
  : "off";
574
- return {
575
- mode,
576
- previousThinking: isThinkingLevel(value.previousThinking) ? value.previousThinking : undefined,
577
- previousDefaultThinking: previousDefault === null || isThinkingLevel(previousDefault)
578
- ? previousDefault
579
- : undefined,
580
- pendingPreviousThinking: isThinkingLevel(value.pendingPreviousThinking)
581
- ? value.pendingPreviousThinking
582
- : undefined,
583
- };
584
- }
585
-
586
- function waitForSettingsWrites(): Promise<void> {
587
- return new Promise((resolve) => setImmediate(resolve));
588
- }
589
-
590
- function safeGetThinking(pi: ExtensionAPI): ThinkingLevel | undefined {
591
- try {
592
- const level = pi.getThinkingLevel();
593
- return isThinkingLevel(level) ? level : undefined;
594
- } catch {
595
- return undefined;
596
- }
246
+ return { mode };
597
247
  }
@@ -0,0 +1,50 @@
1
+ /** Global Ultracode startup preferences, separate from Pi's own settings. */
2
+ import { randomUUID } from "node:crypto";
3
+ import { mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
4
+ import { dirname, join } from "node:path";
5
+ import { getAgentDir } from "@earendil-works/pi-coding-agent";
6
+
7
+ export interface UltracodePreferenceStore {
8
+ getDefaultEnabled(): boolean;
9
+ setDefaultEnabled(enabled: boolean): void;
10
+ }
11
+
12
+ export class UltracodePreferences implements UltracodePreferenceStore {
13
+ private readonly path: string;
14
+
15
+ constructor(path = join(getAgentDir(), "ultracode.json")) {
16
+ this.path = path;
17
+ }
18
+
19
+ getDefaultEnabled(): boolean {
20
+ return this.read().defaultEnabled === true;
21
+ }
22
+
23
+ setDefaultEnabled(enabled: boolean): void {
24
+ const settings = { ...this.read(), defaultEnabled: enabled };
25
+ mkdirSync(dirname(this.path), { recursive: true });
26
+ const temporaryPath = `${this.path}.${randomUUID()}.tmp`;
27
+ try {
28
+ writeFileSync(temporaryPath, `${JSON.stringify(settings, null, 2)}\n`, { flag: "wx", mode: 0o600 });
29
+ renameSync(temporaryPath, this.path);
30
+ } finally {
31
+ rmSync(temporaryPath, { force: true });
32
+ }
33
+ }
34
+
35
+ private read(): Record<string, unknown> {
36
+ let content: string;
37
+ try {
38
+ content = readFileSync(this.path, "utf8");
39
+ } catch (error) {
40
+ if ((error as NodeJS.ErrnoException).code === "ENOENT") return {};
41
+ throw error;
42
+ }
43
+ const value: unknown = JSON.parse(content);
44
+ if (!value || typeof value !== "object" || Array.isArray(value)
45
+ || ("defaultEnabled" in value && typeof value.defaultEnabled !== "boolean")) {
46
+ throw new Error(`Invalid Ultracode preferences in ${this.path}: expected an object with a boolean defaultEnabled.`);
47
+ }
48
+ return value as Record<string, unknown>;
49
+ }
50
+ }
package/src/prompts.ts CHANGED
@@ -17,6 +17,7 @@ export function ultracodeSystemBlock(mode: ActiveUltracodeMode = "deep"): string
17
17
  "Analysis depth is a semantic quality decision, never a wall-clock decision. Do not use elapsed time, deadlines, or duration limits to choose, lower, or stop analysis depth.",
18
18
  "Use the smallest depth that can establish a correct answer. Depth is controlled by research rounds, independent perspectives, verification strength, evidence requirements, skeptic count, and per-agent reasoning effort.",
19
19
  "Existing maxAgents/reserveAgents limits are structural admission bounds, not evidence that every available slot should be used.",
20
+ "Ultracode does not change the parent session's effort; that remains under user control.",
20
21
  "",
21
22
  ...modeInstructions(mode),
22
23
  "",
@@ -31,7 +32,7 @@ export function ultracodeSystemBlock(mode: ActiveUltracodeMode = "deep"): string
31
32
  "- Use a workflow only when independent decomposition, verification, isolation, or context scale provides real value. Otherwise solve directly in the parent session.",
32
33
  "- Make skeptics conditional: verify high-risk, conflicting, weakly evidenced, or low-confidence claims instead of automatically verifying every branch.",
33
34
  "- Avoid a separate synthesis agent when deterministic merging or parent synthesis is enough. Use an adjudicator only when a material conflict remains.",
34
- "- Match effort to the stage with per-call model suffixes when useful: focused discovery/synthesis may use :medium, standard analysis :high, and :max is reserved for deep or decisive high-risk verification.",
35
+ "- Select each workflow agent's effort from its task: use a per-call model suffix such as :medium for bounded discovery/synthesis, :high for substantive analysis or implementation, and :max only for deep or decisive high-risk verification. If omitted, the child session uses its normal user/model configuration.",
35
36
  "- When a workflow runs, log `analysis-depth: <level> — <reason>` before launching agents, `analysis-escalation: ...` for each semantic escalation, and `analysis-stop: ...` for the final evidence-based stop reason. Never use time as an escalation or stop reason.",
36
37
  "</ultracode>",
37
38
  ].join("\n");
@@ -86,7 +87,7 @@ export const WORKFLOW_GUIDELINES: string[] = [
86
87
  "For an auto-depth workflow, log `analysis-depth: <level> — <reason>` before launching agents, `analysis-escalation: ...` when evidence requires more depth, and `analysis-stop: ...` when evidence is sufficient. Elapsed time must never determine depth.",
87
88
  "For workflow verification, run skeptics only for high-risk, conflicting, low-confidence, or weakly evidenced claims. Do not automatically attach a skeptic to every branch.",
88
89
  "For workflow synthesis, prefer structured results plus deterministic or parent-session merging. Start a synthesis/adjudication agent only for a material unresolved conflict, and normally give pure synthesis lower effort and no broad source-search mandate.",
89
- "For workflow agent effort, use a model suffix such as `:medium`, `:high`, or `:max` when the stage should differ from the mode default; reserve max for deep investigation or decisive high-risk verification.",
90
+ "For every substantive workflow agent, select effort from the assigned task with a model suffix such as `:medium`, `:high`, or `:max`: use medium for bounded discovery/synthesis, high for substantive analysis or implementation, and reserve max for deep or decisive high-risk verification. Ultracode never changes the parent session's effort; an omitted child suffix uses the child session's normal user/model configuration.",
90
91
  "For the workflow tool, provide workflow source with `script`, `scriptPath`, or `name`. Inline `script` must be one raw JavaScript string: no Markdown fences, no prose around the script.",
91
92
  "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.",
92
93
  "For the workflow tool, write plain JavaScript after the meta export. No TypeScript syntax, imports, require(), fs, network, Date/Intl/Temporal, Math.random(), binary memory constructors, WebAssembly, or dynamic method calls such as value[key](...) (they bypass deterministic checks or resource bounds). Stamp timestamps after the workflow returns; vary randomness by agent index.",
package/src/thinking.ts CHANGED
@@ -4,10 +4,10 @@ export const THINKING_LEVELS = ["off", "minimal", "low", "medium", "high", "xhig
4
4
 
5
5
  export type ThinkingLevel = (typeof THINKING_LEVELS)[number];
6
6
 
7
- /** Deep mode asks Pi for the strongest effort the selected model supports. */
7
+ /** Strongest child-agent effort request supported by current Pi releases. */
8
8
  export const ULTRACODE_THINKING_LEVEL: ThinkingLevel = "max";
9
9
 
10
- /** Compatibility retry for Pi versions released before the `max` level existed. */
10
+ /** Child-session compatibility retry for Pi versions released before `max`. */
11
11
  export const LEGACY_ULTRACODE_THINKING_LEVEL: ThinkingLevel = "xhigh";
12
12
 
13
13
  const THINKING_LEVEL_SET = new Set<string>(THINKING_LEVELS);
@@ -669,7 +669,7 @@ class Runtime implements ScriptExecutorHost {
669
669
  prompt,
670
670
  cached: true,
671
671
  modelPattern: opts.model,
672
- requestedEffort: this.options.thinkingLevel,
672
+ requestedEffort: selection.thinkingLevel,
673
673
  agentType: opts.agentType,
674
674
  isolation: opts.isolation,
675
675
  structuredOutput: opts.schema != null,
@@ -712,7 +712,7 @@ class Runtime implements ScriptExecutorHost {
712
712
  prompt,
713
713
  cached: false,
714
714
  modelPattern: opts.model,
715
- requestedEffort: this.options.thinkingLevel,
715
+ requestedEffort: selection.thinkingLevel,
716
716
  agentType: opts.agentType,
717
717
  isolation: opts.isolation,
718
718
  structuredOutput: opts.schema != null,
@@ -785,7 +785,7 @@ class Runtime implements ScriptExecutorHost {
785
785
  retries: usage.retries,
786
786
  compactions: usage.compactions,
787
787
  requestedModelId: opts.model,
788
- requestedEffort: this.options.thinkingLevel,
788
+ requestedEffort: selection.thinkingLevel,
789
789
  modelId: result.modelId,
790
790
  effort: result.effort,
791
791
  agentType: opts.agentType,
@@ -93,9 +93,9 @@ export interface WorkflowToolDeps {
93
93
  registry?: WorkflowRegistry;
94
94
  /** Canonical runtime supplied by an SDK host; shared by all child sessions. */
95
95
  modelRuntime?: ModelRuntimeLike;
96
- /** The configured Ultracode mode's raw default effort for workflow subagents.
97
- * Each child session clamps it independently; undefined when off. A per-call
98
- * `model: "X:level"` suffix or agentType `thinking:` override still wins. */
96
+ /** Optional host-provided fallback effort for workflow subagents.
97
+ * Each child session clamps it independently. A per-call `model: "X:level"`
98
+ * suffix or agentType `thinking:` override still wins. */
99
99
  getThinkingLevel?: () => ThinkingLevel | undefined;
100
100
  /** Optional execution gate for mode-scoped registrations. Omit for standalone use. */
101
101
  isExecutionAllowed?: () => boolean;
@@ -163,8 +163,8 @@ export function createWorkflowTool(deps: WorkflowToolDeps = {}): ToolDefinition<
163
163
  if (resuming && !RunJournal.exists(runsDir, runId)) {
164
164
  throw new Error(`workflow: resumeFromRunId ${runId} was not found in this session`);
165
165
  }
166
- // Forward the mode's raw effort request so each subagent session clamps it
167
- // against that subagent's model. Undefined when Ultracode is off.
166
+ // Forward an optional host fallback. Per-call and agent-role effort
167
+ // selection is resolved later for each child and clamped by its model.
168
168
  const thinkingLevel = deps.getThinkingLevel?.();
169
169
  if (controller.signal.aborted) throw new Error("Workflow was aborted before it started");
170
170
  const run = deps.runWorkflowFn ?? runWorkflow;