pi-ultracode 0.6.0 → 0.7.1

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.zh-CN.md CHANGED
@@ -66,10 +66,15 @@ Pi 会自行判断工作流是否有帮助。小任务仍可继续使用普通
66
66
  | `/ultracode deep` | 固定使用高保障深度验证 |
67
67
  | `/ultracode off` | 关闭且不改变父代理 effort |
68
68
  | `/ultracode status` | 查看配置的语义深度模式 |
69
+ | `/ultracode default on` | 全局默认开启,并立即开启当前会话;已开启时保留当前深度 |
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` 保存全局启动默认值,并立即将关闭的当前会话切换为 `auto`;当前已开启时保留所选深度。`/ultracode default off` 只取消默认开启,不关闭当前会话;关闭当前会话请用 `/ultracode off`。这些操作均不改变 effort。设置保存在 `~/.pi/agent/ultracode.json`(遵循 `PI_CODING_AGENT_DIR`),跨项目和重启生效;未设置时默认关闭。启动时,只有当前分支没有保存 Ultracode 模式才应用默认值,已有的 `off` 或深度选择在 reload、resume、fork 后继续保留。显式 `pi --ultracode` 仍可启用 `auto`。
77
+
73
78
  按 `Esc` 可取消正在运行的工作流。在 Pi 的 fullscreen TUI 中,请在工作流详情内使用 `Ctrl+PageUp`、`Ctrl+PageDown` 和 `Ctrl+End`。
74
79
 
75
80
  ## 分析深度
@@ -9,6 +9,7 @@ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
9
9
  import { createWorkflowTool, type WorkflowToolDeps } from "../src/workflow/tool.ts";
10
10
  import { UltracodeMode, type ThinkingPreferenceStore } from "../src/mode.ts";
11
11
  import { registerCommands } from "../src/commands.ts";
12
+ import { UltracodePreferences, type UltracodePreferenceStore } from "../src/preferences.ts";
12
13
  import { WorkflowRegistry } from "../src/workflow/registry.ts";
13
14
 
14
15
  export interface ThinkingPreferenceContext {
@@ -17,6 +18,7 @@ export interface ThinkingPreferenceContext {
17
18
  }
18
19
 
19
20
  export interface UltracodeExtensionDeps extends Partial<WorkflowToolDeps> {
21
+ preferences?: UltracodePreferenceStore;
20
22
  /** @deprecated Parent effort is user-owned; retained for source compatibility. */
21
23
  createThinkingPreferenceStore?: (
22
24
  context: ThinkingPreferenceContext,
@@ -27,6 +29,7 @@ export default function extension(pi: ExtensionAPI, extraDeps: UltracodeExtensio
27
29
  const mode = new UltracodeMode("workflow");
28
30
  const {
29
31
  createThinkingPreferenceStore: _unusedThinkingPreferenceStore,
32
+ preferences = new UltracodePreferences(),
30
33
  ...workflowDeps
31
34
  } = extraDeps;
32
35
 
@@ -38,7 +41,7 @@ export default function extension(pi: ExtensionAPI, extraDeps: UltracodeExtensio
38
41
  });
39
42
  pi.registerTool(workflowTool);
40
43
 
41
- registerCommands(pi, mode, registry);
44
+ registerCommands(pi, mode, registry, preferences);
42
45
 
43
46
  // Opt-in via CLI flag: `pi --ultracode`.
44
47
  pi.registerFlag("ultracode", {
@@ -65,16 +68,23 @@ export default function extension(pi: ExtensionAPI, extraDeps: UltracodeExtensio
65
68
 
66
69
  pi.on("session_start", async (_event, ctx) => {
67
70
  // Restore persisted mode state across reload / resume / fork.
71
+ let hasSavedMode = true;
68
72
  try {
69
73
  // Mode entries are branch-local; discarded future branches must not win.
70
- mode.restore(pi, ctx.sessionManager.getBranch() as any);
74
+ hasSavedMode = mode.restore(pi, ctx.sessionManager.getBranch() as any);
71
75
  } catch {
72
- // ignore
76
+ // Do not apply a startup default when branch restoration failed.
73
77
  }
74
78
  if (!mode.isEnabled() && pi.getFlag?.("ultracode") === true) {
75
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
+ }
76
86
  }
77
- // Registration makes extension tools discoverable; activation remains opt-in.
87
+ // Registration makes tools discoverable; activation follows the chosen mode.
78
88
  mode.syncWorkflowTool(pi);
79
89
  if (ctx.hasUI) {
80
90
  ctx.ui.setStatus(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-ultracode",
3
- "version": "0.6.0",
3
+ "version": "0.7.1",
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,15 +5,21 @@
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
  },
@@ -37,6 +43,30 @@ export function registerCommands(pi: ExtensionAPI, mode: UltracodeMode, registry
37
43
  return;
38
44
  }
39
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
+ if (value === "on") {
56
+ if (!mode.isEnabled()) mode.enable(pi, "auto");
57
+ ctx.ui.setStatus("ultracode", mode.statusLine((label) => ctx.ui.theme.fg("accent", label)));
58
+ }
59
+ const sessionStatus = value === "on" ? `current session: ${mode.getMode()}` : "current session unchanged";
60
+ ctx.ui.notify(
61
+ `Ultracode default ${enabled ? "on (auto)" : "off"} — global startup preference; ${sessionStatus}.`,
62
+ "info",
63
+ );
64
+ } catch (error) {
65
+ ctx.ui.notify(`Failed to ${value === undefined ? "read" : "save"} Ultracode default: ${String(error)}`, "error");
66
+ }
67
+ return;
68
+ }
69
+
40
70
  if (parts.length > 1) {
41
71
  ctx.ui.notify(ultracodeUsage(), "error");
42
72
  return;
@@ -102,5 +132,5 @@ export function registerCommands(pi: ExtensionAPI, mode: UltracodeMode, registry
102
132
  }
103
133
 
104
134
  function ultracodeUsage(): string {
105
- return "Usage: /ultracode [auto|focused|standard|deep|off|status]";
135
+ return "Usage: /ultracode [auto|focused|standard|deep|off|status] or /ultracode default [on|off]";
106
136
  }
package/src/mode.ts CHANGED
@@ -152,17 +152,15 @@ export class UltracodeMode {
152
152
 
153
153
  /** Turn Ultracode off without changing the parent thinking level. */
154
154
  disable(pi: ExtensionAPI): void {
155
- if (!this.isEnabled()) {
156
- this.syncWorkflowTool(pi);
157
- return;
158
- }
155
+ // Persist an explicit off even when already disabled, so startup defaults
156
+ // cannot override this session's choice on reload or resume.
159
157
  this.mode = "off";
160
158
  this.suspended = false;
161
159
  this.syncWorkflowTool(pi);
162
160
  this.persist(pi);
163
161
  }
164
162
 
165
- /** Restore mode state from the active session branch without touching effort. */
163
+ /** Restore branch-local state; return whether a saved mode was found. */
166
164
  restore(
167
165
  pi: ExtensionAPI,
168
166
  entries: Array<{
@@ -171,7 +169,7 @@ export class UltracodeMode {
171
169
  data?: unknown;
172
170
  thinkingLevel?: unknown;
173
171
  }>,
174
- ): void {
172
+ ): boolean {
175
173
  let latestData: unknown;
176
174
  for (const entry of entries) {
177
175
  if (entry.type === "custom" && entry.customType === MODE_ENTRY_TYPE && entry.data) {
@@ -179,9 +177,11 @@ export class UltracodeMode {
179
177
  }
180
178
  }
181
179
 
182
- this.mode = parsePersistedModeState(latestData)?.mode ?? "off";
180
+ const state = parsePersistedModeState(latestData);
181
+ this.mode = state?.mode ?? "off";
183
182
  this.suspended = false;
184
183
  this.syncWorkflowTool(pi);
184
+ return state !== undefined;
185
185
  }
186
186
 
187
187
  /** Append the configured semantic-depth policy to the turn's system prompt. */
@@ -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
+ }