pi-ultracode 0.5.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/README.md +5 -5
- package/README.zh-CN.md +5 -5
- package/extensions/ultracode.ts +12 -77
- package/package.json +1 -1
- package/src/commands.ts +10 -8
- package/src/depth.ts +4 -5
- package/src/mode.ts +43 -393
- package/src/prompts.ts +3 -2
- package/src/thinking.ts +2 -2
- package/src/workflow/runtime.ts +3 -3
- package/src/workflow/tool.ts +5 -5
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
|
|
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
|
|
67
|
-
| `/ultracode off` | Disable it
|
|
68
|
-
| `/ultracode status` | Show the configured mode
|
|
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
|
-
|
|
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,9 +63,9 @@ Pi 会自行判断工作流是否有帮助。小任务仍可继续使用普通
|
|
|
63
63
|
| `/ultracode auto` | 使用自适应语义深度路由 |
|
|
64
64
|
| `/ultracode focused` | 固定使用轻量、聚焦的分析策略 |
|
|
65
65
|
| `/ultracode standard` | 固定使用平衡策略和条件验证 |
|
|
66
|
-
| `/ultracode deep` |
|
|
67
|
-
| `/ultracode off` |
|
|
68
|
-
| `/ultracode status` |
|
|
66
|
+
| `/ultracode deep` | 固定使用高保障深度验证 |
|
|
67
|
+
| `/ultracode off` | 关闭且不改变父代理 effort |
|
|
68
|
+
| `/ultracode status` | 查看配置的语义深度模式 |
|
|
69
69
|
| `/workflows` 或 `F6` | 打开工作流浏览器 |
|
|
70
70
|
| `/workflows <runId>` | 打开指定运行 |
|
|
71
71
|
| `/workflows abort` | 中止活动运行 |
|
|
@@ -83,7 +83,7 @@ Pi 会自行判断工作流是否有帮助。小任务仍可继续使用普通
|
|
|
83
83
|
|
|
84
84
|
当关键结论已有直接证据、没有实质冲突或未解决的高风险问题,且下一轮只会重复已知证据时停止。墙钟时间、deadline 和 duration limit 不得用于选择或停止分析深度。`maxAgents` 与 `reserveAgents` 继续作为结构性准入限制。
|
|
85
85
|
|
|
86
|
-
|
|
86
|
+
Ultracode 不会修改父会话的 effort。对于 workflow 子代理,父代理会按具体任务通过模型后缀选择 effort:边界明确的搜索或汇总通常使用 `:medium`,实质分析或实现使用 `:high`,只有深度调查或决定性的高风险验证才使用 `:max`。Workflow UI 会展示每个子代理经模型裁剪后实际生效的 effort;未指定后缀时,子会话使用正常的用户/模型配置。skeptic 和独立 synthesis agent 都不是默认步骤。
|
|
87
87
|
|
|
88
88
|
## 工作流示例
|
|
89
89
|
|
package/extensions/ultracode.ts
CHANGED
|
@@ -5,15 +5,9 @@
|
|
|
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";
|
|
18
12
|
import { WorkflowRegistry } from "../src/workflow/registry.ts";
|
|
19
13
|
|
|
@@ -23,7 +17,7 @@ export interface ThinkingPreferenceContext {
|
|
|
23
17
|
}
|
|
24
18
|
|
|
25
19
|
export interface UltracodeExtensionDeps extends Partial<WorkflowToolDeps> {
|
|
26
|
-
/**
|
|
20
|
+
/** @deprecated Parent effort is user-owned; retained for source compatibility. */
|
|
27
21
|
createThinkingPreferenceStore?: (
|
|
28
22
|
context: ThinkingPreferenceContext,
|
|
29
23
|
) => ThinkingPreferenceStore | undefined;
|
|
@@ -31,15 +25,13 @@ export interface UltracodeExtensionDeps extends Partial<WorkflowToolDeps> {
|
|
|
31
25
|
|
|
32
26
|
export default function extension(pi: ExtensionAPI, extraDeps: UltracodeExtensionDeps = {}): void {
|
|
33
27
|
const mode = new UltracodeMode("workflow");
|
|
34
|
-
mode.setRuntimeSupportsMaxThinking(piVersionSupportsMaxThinking(PI_VERSION));
|
|
35
28
|
const {
|
|
36
|
-
createThinkingPreferenceStore
|
|
29
|
+
createThinkingPreferenceStore: _unusedThinkingPreferenceStore,
|
|
37
30
|
...workflowDeps
|
|
38
31
|
} = extraDeps;
|
|
39
32
|
|
|
40
33
|
const registry = workflowDeps.registry ?? new WorkflowRegistry();
|
|
41
34
|
const workflowTool = createWorkflowTool({
|
|
42
|
-
getThinkingLevel: () => mode.getSubagentThinkingLevel(),
|
|
43
35
|
...workflowDeps,
|
|
44
36
|
registry,
|
|
45
37
|
isExecutionAllowed: () => mode.isEnforcing(),
|
|
@@ -72,16 +64,6 @@ export default function extension(pi: ExtensionAPI, extraDeps: UltracodeExtensio
|
|
|
72
64
|
});
|
|
73
65
|
|
|
74
66
|
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
67
|
// Restore persisted mode state across reload / resume / fork.
|
|
86
68
|
try {
|
|
87
69
|
// Mode entries are branch-local; discarded future branches must not win.
|
|
@@ -94,81 +76,34 @@ export default function extension(pi: ExtensionAPI, extraDeps: UltracodeExtensio
|
|
|
94
76
|
}
|
|
95
77
|
// Registration makes extension tools discoverable; activation remains opt-in.
|
|
96
78
|
mode.syncWorkflowTool(pi);
|
|
97
|
-
await mode.flushThinkingPreference();
|
|
98
79
|
if (ctx.hasUI) {
|
|
99
|
-
ctx.ui.setStatus(
|
|
80
|
+
ctx.ui.setStatus(
|
|
81
|
+
"ultracode",
|
|
82
|
+
mode.isEnabled() ? mode.statusLine((label) => ctx.ui.theme.fg("accent", label)) : undefined,
|
|
83
|
+
);
|
|
100
84
|
}
|
|
101
85
|
});
|
|
102
86
|
|
|
103
87
|
pi.on("session_tree", async (_event, ctx) => {
|
|
104
|
-
await mode.flushThinkingPreference();
|
|
105
88
|
if (mode.isSuspended()) return;
|
|
106
|
-
mode.setCurrentModelSupportsThinking(ctx.model ? Boolean(ctx.model.reasoning) : undefined);
|
|
107
89
|
mode.restore(pi, ctx.sessionManager.getBranch() as any);
|
|
108
|
-
await mode.flushThinkingPreference();
|
|
109
90
|
if (ctx.hasUI) {
|
|
110
|
-
ctx.ui.setStatus(
|
|
91
|
+
ctx.ui.setStatus(
|
|
92
|
+
"ultracode",
|
|
93
|
+
mode.isEnabled() ? mode.statusLine((label) => ctx.ui.theme.fg("accent", label)) : undefined,
|
|
94
|
+
);
|
|
111
95
|
}
|
|
112
96
|
});
|
|
113
97
|
|
|
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
98
|
pi.on("session_shutdown", async () => {
|
|
128
|
-
// Quiesce first so late model/effort events cannot undo restoration. The
|
|
129
99
|
// The persisted configured mode remains active for reload/resume/fork replacements.
|
|
130
100
|
mode.suspend(pi);
|
|
131
|
-
await mode.flushThinkingPreference();
|
|
132
101
|
});
|
|
133
102
|
|
|
134
103
|
pi.on("before_agent_start", async (event) => {
|
|
135
|
-
// Reconcile
|
|
104
|
+
// Reconcile tool availability and append the standing policy on every
|
|
136
105
|
// enforcing turn, even when another active-tool writer caused drift.
|
|
137
106
|
mode.syncWorkflowTool(pi);
|
|
138
|
-
mode.reapplyConfiguredThinking(pi);
|
|
139
|
-
await mode.flushThinkingPreference();
|
|
140
107
|
return mode.beforeAgentStart(event);
|
|
141
108
|
});
|
|
142
109
|
}
|
|
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.
|
|
3
|
+
"version": "0.6.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
|
@@ -18,21 +18,22 @@ export function registerCommands(pi: ExtensionAPI, mode: UltracodeMode, registry
|
|
|
18
18
|
.map((value) => ({ value, label: value }));
|
|
19
19
|
},
|
|
20
20
|
handler: async (args: string, ctx) => {
|
|
21
|
-
mode.setCurrentModelSupportsThinking(ctx.model ? Boolean(ctx.model.reasoning) : undefined);
|
|
22
21
|
const parts = args.trim().split(/\s+/).filter(Boolean);
|
|
23
22
|
const sub = (parts[0] ?? "").toLowerCase();
|
|
24
23
|
|
|
25
24
|
// Bare `/ultracode` enables auto from off and disables any active mode.
|
|
26
25
|
if (sub === "") {
|
|
27
26
|
const nowOn = mode.toggle(pi);
|
|
28
|
-
await mode.flushThinkingPreference();
|
|
29
27
|
ctx.ui.notify(
|
|
30
28
|
nowOn
|
|
31
29
|
? `Ultracode auto — ${mode.statusLine()}`
|
|
32
|
-
: "Ultracode off —
|
|
30
|
+
: "Ultracode off — workflow tool disabled; parent effort unchanged.",
|
|
33
31
|
"info",
|
|
34
32
|
);
|
|
35
|
-
ctx.ui.setStatus(
|
|
33
|
+
ctx.ui.setStatus(
|
|
34
|
+
"ultracode",
|
|
35
|
+
nowOn ? mode.statusLine((label) => ctx.ui.theme.fg("accent", label)) : undefined,
|
|
36
|
+
);
|
|
36
37
|
return;
|
|
37
38
|
}
|
|
38
39
|
|
|
@@ -48,8 +49,7 @@ export function registerCommands(pi: ExtensionAPI, mode: UltracodeMode, registry
|
|
|
48
49
|
|
|
49
50
|
if (sub === "off") {
|
|
50
51
|
mode.disable(pi);
|
|
51
|
-
|
|
52
|
-
ctx.ui.notify("Ultracode off — thinking restored and workflow tool disabled.", "info");
|
|
52
|
+
ctx.ui.notify("Ultracode off — workflow tool disabled; parent effort unchanged.", "info");
|
|
53
53
|
ctx.ui.setStatus("ultracode", undefined);
|
|
54
54
|
return;
|
|
55
55
|
}
|
|
@@ -60,9 +60,11 @@ export function registerCommands(pi: ExtensionAPI, mode: UltracodeMode, registry
|
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
mode.enable(pi, sub);
|
|
63
|
-
await mode.flushThinkingPreference();
|
|
64
63
|
ctx.ui.notify(`Ultracode ${sub} — ${mode.statusLine()}`, "info");
|
|
65
|
-
ctx.ui.setStatus(
|
|
64
|
+
ctx.ui.setStatus(
|
|
65
|
+
"ultracode",
|
|
66
|
+
mode.statusLine((label) => ctx.ui.theme.fg("accent", label)),
|
|
67
|
+
);
|
|
66
68
|
},
|
|
67
69
|
});
|
|
68
70
|
|
package/src/depth.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/** Ultracode's configured analysis modes
|
|
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
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
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
|
-
* -
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
79
|
-
bindThinkingPreferenceStore(
|
|
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
|
-
/**
|
|
85
|
-
setCurrentModelSupportsThinking(
|
|
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
|
-
/**
|
|
90
|
-
setRuntimeSupportsMaxThinking(
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
76
|
+
/** @deprecated Ultracode no longer applies a parent thinking level. */
|
|
118
77
|
getAppliedThinking(): ThinkingLevel | undefined {
|
|
119
|
-
return
|
|
78
|
+
return undefined;
|
|
120
79
|
}
|
|
121
80
|
|
|
122
81
|
/**
|
|
123
|
-
*
|
|
124
|
-
*
|
|
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
|
|
87
|
+
return undefined;
|
|
128
88
|
}
|
|
129
89
|
|
|
130
|
-
/**
|
|
131
|
-
reapplyConfiguredThinking(
|
|
132
|
-
|
|
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
|
|
138
|
-
reapplyMaximumThinking(
|
|
139
|
-
return
|
|
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
|
-
|
|
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
|
-
/**
|
|
163
|
-
restorePreviousThinking(
|
|
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
|
-
/**
|
|
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
|
-
|
|
180
|
-
|
|
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,30 @@ export class UltracodeMode {
|
|
|
213
139
|
}
|
|
214
140
|
|
|
215
141
|
/**
|
|
216
|
-
* Enable or switch modes
|
|
217
|
-
*
|
|
218
|
-
*
|
|
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
|
|
153
|
+
/** Turn Ultracode off without changing the parent thinking level. */
|
|
249
154
|
disable(pi: ExtensionAPI): void {
|
|
250
155
|
if (!this.isEnabled()) {
|
|
251
156
|
this.syncWorkflowTool(pi);
|
|
252
157
|
return;
|
|
253
158
|
}
|
|
254
|
-
const previous = this.previousThinking;
|
|
255
|
-
this.restorePreviousThinking(pi);
|
|
256
|
-
this.pendingPreviousThinking = previous && !this.pendingRestoreSucceeded(previous)
|
|
257
|
-
? previous
|
|
258
|
-
: undefined;
|
|
259
|
-
this.pendingClearGeneration++;
|
|
260
159
|
this.mode = "off";
|
|
261
160
|
this.suspended = false;
|
|
262
161
|
this.syncWorkflowTool(pi);
|
|
263
162
|
this.persist(pi);
|
|
264
163
|
}
|
|
265
164
|
|
|
266
|
-
|
|
267
|
-
/** Restore mode state from the active session branch. */
|
|
165
|
+
/** Restore mode state from the active session branch without touching effort. */
|
|
268
166
|
restore(
|
|
269
167
|
pi: ExtensionAPI,
|
|
270
168
|
entries: Array<{
|
|
@@ -274,120 +172,16 @@ export class UltracodeMode {
|
|
|
274
172
|
thinkingLevel?: unknown;
|
|
275
173
|
}>,
|
|
276
174
|
): void {
|
|
277
|
-
const wasEnforcing = this.isEnforcing();
|
|
278
|
-
this.pendingClearGeneration++;
|
|
279
175
|
let latestData: unknown;
|
|
280
|
-
let branchThinking: ThinkingLevel | undefined;
|
|
281
|
-
let thinkingAfterLatestMode: ThinkingLevel | undefined;
|
|
282
176
|
for (const entry of entries) {
|
|
283
177
|
if (entry.type === "custom" && entry.customType === MODE_ENTRY_TYPE && entry.data) {
|
|
284
178
|
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
179
|
}
|
|
290
180
|
}
|
|
291
181
|
|
|
292
|
-
|
|
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
|
-
|
|
182
|
+
this.mode = parsePersistedModeState(latestData)?.mode ?? "off";
|
|
324
183
|
this.suspended = false;
|
|
325
|
-
this.mode = latest.mode;
|
|
326
184
|
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
|
-
}
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
}
|
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
|
-
"-
|
|
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
|
|
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
|
-
/**
|
|
7
|
+
/** Strongest child-agent effort request supported by current Pi releases. */
|
|
8
8
|
export const ULTRACODE_THINKING_LEVEL: ThinkingLevel = "max";
|
|
9
9
|
|
|
10
|
-
/**
|
|
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);
|
package/src/workflow/runtime.ts
CHANGED
|
@@ -669,7 +669,7 @@ class Runtime implements ScriptExecutorHost {
|
|
|
669
669
|
prompt,
|
|
670
670
|
cached: true,
|
|
671
671
|
modelPattern: opts.model,
|
|
672
|
-
requestedEffort:
|
|
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:
|
|
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:
|
|
788
|
+
requestedEffort: selection.thinkingLevel,
|
|
789
789
|
modelId: result.modelId,
|
|
790
790
|
effort: result.effort,
|
|
791
791
|
agentType: opts.agentType,
|
package/src/workflow/tool.ts
CHANGED
|
@@ -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
|
-
/**
|
|
97
|
-
* Each child session clamps it independently
|
|
98
|
-
*
|
|
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
|
|
167
|
-
//
|
|
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;
|