pi-ultracode 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,23 +2,47 @@
2
2
  * pi-ultracode extension entrypoint.
3
3
  *
4
4
  * Wires together the three pillars of Claude-Code-style "ultracode":
5
- * 1. The ultracode effort mode (xhigh thinking + standing workflow opt-in).
5
+ * 1. The ultracode effort mode (max thinking + standing workflow opt-in).
6
6
  * 2. The full `workflow` orchestration tool.
7
7
  * 3. The `/ultracode` and `/workflows` commands.
8
8
  */
9
9
 
10
- import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
10
+ import {
11
+ getAgentDir,
12
+ SettingsManager,
13
+ VERSION as PI_VERSION,
14
+ type ExtensionAPI,
15
+ } from "@earendil-works/pi-coding-agent";
11
16
  import { createWorkflowTool, type WorkflowToolDeps } from "../src/workflow/tool.ts";
12
- import { UltracodeMode } from "../src/mode.ts";
17
+ import { UltracodeMode, type ThinkingPreferenceStore } from "../src/mode.ts";
18
+ import { isThinkingLevel, piVersionSupportsMaxThinking } from "../src/thinking.ts";
13
19
  import { registerCommands } from "../src/commands.ts";
14
20
 
15
- export default function extension(pi: ExtensionAPI, extraDeps: Partial<WorkflowToolDeps> = {}): void {
21
+ export interface ThinkingPreferenceContext {
22
+ cwd: string;
23
+ projectTrusted: boolean;
24
+ }
25
+
26
+ export interface UltracodeExtensionDeps extends Partial<WorkflowToolDeps> {
27
+ /** SDK-host seam when the active profile differs from Pi's ambient agent dir. */
28
+ createThinkingPreferenceStore?: (
29
+ context: ThinkingPreferenceContext,
30
+ ) => ThinkingPreferenceStore | undefined;
31
+ }
32
+
33
+ export default function extension(pi: ExtensionAPI, extraDeps: UltracodeExtensionDeps = {}): void {
16
34
  const mode = new UltracodeMode("workflow");
35
+ mode.setRuntimeSupportsMaxThinking(piVersionSupportsMaxThinking(PI_VERSION));
36
+ const {
37
+ createThinkingPreferenceStore = createPiThinkingPreferenceStore,
38
+ ...workflowDeps
39
+ } = extraDeps;
17
40
 
18
41
  const workflowTool = createWorkflowTool({
19
42
  getDefaultBudget: () => mode.getBudget(),
20
43
  getThinkingLevel: () => mode.getSubagentThinkingLevel(),
21
- ...extraDeps,
44
+ ...workflowDeps,
45
+ isExecutionAllowed: () => mode.isEnforcing(),
22
46
  });
23
47
  pi.registerTool(workflowTool);
24
48
 
@@ -27,30 +51,124 @@ export default function extension(pi: ExtensionAPI, extraDeps: Partial<WorkflowT
27
51
  // Opt-in via CLI flag: `pi --ultracode`.
28
52
  pi.registerFlag("ultracode", {
29
53
  type: "boolean",
30
- description: "Start the session in ultracode mode (xhigh thinking + default workflow orchestration).",
54
+ description: "Start the session in ultracode mode (max thinking + default workflow orchestration).",
31
55
  });
32
56
 
33
- pi.on("session_start", (_event, ctx) => {
57
+ // SDK-created sessions can prompt without emitting session_start. Sync during
58
+ // input preflight so before_agent_start receives Pi's rebuilt base prompt.
59
+ pi.on("input", () => {
60
+ mode.syncWorkflowTool(pi);
61
+ });
62
+
63
+ // Fail closed if another active-tool writer re-exposes workflow while the
64
+ // mode is off or quiescing.
65
+ pi.on("tool_call", (event) => {
66
+ if (event.toolName === workflowTool.name && !mode.isEnforcing()) {
67
+ return {
68
+ block: true,
69
+ reason: "The workflow tool is disabled. Run /ultracode on before using it.",
70
+ };
71
+ }
72
+ });
73
+
74
+ 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
+ }
34
85
  // Restore persisted mode state across reload / resume / fork.
35
86
  try {
36
- mode.restore(pi, ctx.sessionManager.getEntries() as any);
87
+ // Mode entries are branch-local; discarded future branches must not win.
88
+ mode.restore(pi, ctx.sessionManager.getBranch() as any);
37
89
  } catch {
38
90
  // ignore
39
91
  }
40
92
  if (!mode.isEnabled() && pi.getFlag?.("ultracode") === true) {
41
93
  mode.enable(pi);
42
94
  }
43
- // Always keep the workflow tool available so the model can use it on request.
44
- try {
45
- const active = pi.getActiveTools();
46
- if (!active.includes(workflowTool.name)) pi.setActiveTools([...active, workflowTool.name]);
47
- } catch {
48
- // ignore
95
+ // Registration makes extension tools discoverable; activation remains opt-in.
96
+ mode.syncWorkflowTool(pi);
97
+ await mode.flushThinkingPreference();
98
+ if (ctx.hasUI) {
99
+ ctx.ui.setStatus("ultracode", mode.isEnabled() ? mode.statusLine() : undefined);
49
100
  }
50
- if (ctx.hasUI && mode.isEnabled()) ctx.ui.setStatus("ultracode", mode.statusLine());
51
101
  });
52
102
 
53
- pi.on("before_agent_start", (event) => {
103
+ pi.on("session_tree", async (_event, ctx) => {
104
+ await mode.flushThinkingPreference();
105
+ if (mode.isSuspended()) return;
106
+ mode.setCurrentModelSupportsThinking(ctx.model ? Boolean(ctx.model.reasoning) : undefined);
107
+ mode.restore(pi, ctx.sessionManager.getBranch() as any);
108
+ await mode.flushThinkingPreference();
109
+ if (ctx.hasUI) {
110
+ ctx.ui.setStatus("ultracode", mode.isEnabled() ? mode.statusLine() : undefined);
111
+ }
112
+ });
113
+
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
+ pi.on("session_shutdown", async () => {
128
+ // Quiesce first so late model/effort events cannot undo restoration. The
129
+ // persisted enabled state remains on for reload/resume/fork replacements.
130
+ mode.suspend(pi);
131
+ await mode.flushThinkingPreference();
132
+ });
133
+
134
+ pi.on("before_agent_start", async (event) => {
135
+ // Reconcile the tool schema and always append the standing block on an
136
+ // enforcing turn, even when another active-tool writer caused drift.
137
+ mode.syncWorkflowTool(pi);
138
+ mode.reapplyMaximumThinking(pi);
139
+ await mode.flushThinkingPreference();
54
140
  return mode.beforeAgentStart(event);
55
141
  });
56
142
  }
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.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Claude-Code-style \"ultracode\" for Pi: an effort mode that defaults to deterministic multi-agent workflow orchestration, with worktree isolation, per-agent model overrides, custom agent types, nested workflows, token budgets, resumable runs, and a /workflows manager.",
5
5
  "type": "module",
6
6
  "keywords": [
@@ -16,6 +16,14 @@
16
16
  ],
17
17
  "author": "pi-ultracode",
18
18
  "license": "MIT",
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "git+https://github.com/ekil1100/pi-ultracode.git"
22
+ },
23
+ "homepage": "https://github.com/ekil1100/pi-ultracode#readme",
24
+ "bugs": {
25
+ "url": "https://github.com/ekil1100/pi-ultracode/issues"
26
+ },
19
27
  "files": [
20
28
  "extensions/",
21
29
  "src/",
@@ -23,6 +31,10 @@
23
31
  "examples/",
24
32
  "README.md"
25
33
  ],
34
+ "publishConfig": {
35
+ "access": "public",
36
+ "registry": "https://registry.npmjs.org/"
37
+ },
26
38
  "pi": {
27
39
  "extensions": [
28
40
  "./extensions/ultracode.ts"
@@ -40,6 +52,15 @@
40
52
  "dependencies": {
41
53
  "acorn": "^8.11.0"
42
54
  },
55
+ "devDependencies": {
56
+ "@earendil-works/pi-agent-core": "^0.80.10",
57
+ "@earendil-works/pi-ai": "^0.80.10",
58
+ "@earendil-works/pi-coding-agent": "^0.80.10",
59
+ "@earendil-works/pi-tui": "^0.80.10",
60
+ "@types/node": "^24.12.4",
61
+ "typebox": "1.1.38",
62
+ "typescript": "^5.9.3"
63
+ },
43
64
  "peerDependencies": {
44
65
  "@earendil-works/pi-agent-core": "*",
45
66
  "@earendil-works/pi-ai": "*",
package/src/commands.ts CHANGED
@@ -5,24 +5,30 @@
5
5
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
6
6
  import { parseBudget, type UltracodeMode } from "./mode.ts";
7
7
  import { getRegistry } from "./workflow/registry.ts";
8
- import { renderWorkflowLines } from "./workflow/display.ts";
8
+ import { workflowRunsDir } from "./workflow/tool.ts";
9
+ import { openWorkflowOverlay } from "./workflow/workflow-overlay.ts";
9
10
 
10
11
  export function registerCommands(pi: ExtensionAPI, mode: UltracodeMode): void {
11
12
  pi.registerCommand("ultracode", {
12
- description: "Toggle ultracode mode (xhigh thinking + default workflow orchestration). Bare /ultracode toggles; subcommands: on|off|status|budget <n>",
13
+ description: "Toggle ultracode mode (max thinking + default workflow orchestration). Bare /ultracode toggles; subcommands: on|off|status|budget <n>",
13
14
  getArgumentCompletions(prefix: string) {
14
15
  return ["on", "off", "status", "budget"]
15
16
  .filter((s) => s.startsWith(prefix))
16
17
  .map((value) => ({ value, label: value }));
17
18
  },
18
19
  handler: async (args: string, ctx) => {
20
+ mode.setCurrentModelSupportsThinking(ctx.model ? Boolean(ctx.model.reasoning) : undefined);
19
21
  const parts = args.trim().split(/\s+/).filter(Boolean);
20
22
  const sub = (parts[0] ?? "").toLowerCase();
21
23
 
22
24
  // Bare `/ultracode` is a toggle.
23
25
  if (sub === "") {
24
26
  const nowOn = mode.toggle(pi);
25
- ctx.ui.notify(nowOn ? `Ultracode on — ${mode.statusLine()}` : "Ultracode off — thinking restored.", "info");
27
+ await mode.flushThinkingPreference();
28
+ ctx.ui.notify(
29
+ nowOn ? `Ultracode on — ${mode.statusLine()}` : "Ultracode off — thinking restored and workflow tool disabled.",
30
+ "info",
31
+ );
26
32
  ctx.ui.setStatus("ultracode", nowOn ? mode.statusLine() : undefined);
27
33
  return;
28
34
  }
@@ -34,7 +40,8 @@ export function registerCommands(pi: ExtensionAPI, mode: UltracodeMode): void {
34
40
 
35
41
  if (sub === "off") {
36
42
  mode.disable(pi);
37
- ctx.ui.notify("Ultracode off — thinking restored, workflow orchestration is opt-in again.", "info");
43
+ await mode.flushThinkingPreference();
44
+ ctx.ui.notify("Ultracode off — thinking restored and workflow tool disabled.", "info");
38
45
  ctx.ui.setStatus("ultracode", undefined);
39
46
  return;
40
47
  }
@@ -58,6 +65,7 @@ export function registerCommands(pi: ExtensionAPI, mode: UltracodeMode): void {
58
65
  if (parsed) budget = parsed;
59
66
  }
60
67
  mode.enable(pi, budget !== undefined ? { budget } : {});
68
+ await mode.flushThinkingPreference();
61
69
  ctx.ui.notify(
62
70
  `Ultracode on — ${mode.statusLine()}${budget ? ` (budget ~${budget} tokens)` : ""}`,
63
71
  "info",
@@ -66,96 +74,36 @@ export function registerCommands(pi: ExtensionAPI, mode: UltracodeMode): void {
66
74
  },
67
75
  });
68
76
 
69
- // Tracks whether the run panel is currently shown, so bare /workflows toggles it.
70
- let panelVisible = false;
77
+ const openWorkflows = async (ctx: any, runId?: string) => {
78
+ const registry = getRegistry();
79
+ registry.restoreRuns(workflowRunsDir(ctx));
80
+ await openWorkflowOverlay(ctx, registry, runId);
81
+ };
71
82
 
72
83
  pi.registerCommand("workflows", {
73
- description: "Toggle the workflow-run panel. Usage: /workflows [runId | clear | abort]",
84
+ description: "Open the interactive workflow/task detail overlay. Usage: /workflows [runId | abort]",
74
85
  getArgumentCompletions(prefix: string) {
75
- return ["clear", "abort"]
76
- .filter((s) => s.startsWith(prefix))
86
+ const values = [
87
+ "abort",
88
+ ...getRegistry().list().map((handle) => handle.snapshot.runId).filter((runId): runId is string => Boolean(runId)),
89
+ ];
90
+ return [...new Set(values)]
91
+ .filter((value) => value.startsWith(prefix))
77
92
  .map((value) => ({ value, label: value }));
78
93
  },
79
94
  handler: async (args: string, ctx) => {
80
- const registry = getRegistry();
81
95
  const arg = args.trim();
82
- const sub = arg.toLowerCase();
83
-
84
- const hide = () => {
85
- ctx.ui.setWidget("ultracode-workflows", undefined);
86
- panelVisible = false;
87
- };
88
- const show = (lines: string[]) => {
89
- ctx.ui.setWidget("ultracode-workflows", lines);
90
- panelVisible = true;
91
- };
92
-
93
- if (sub === "clear" || sub === "hide" || sub === "off") {
94
- hide();
95
- ctx.ui.notify("Workflow panel hidden.", "info");
96
- return;
97
- }
98
-
99
- if (sub === "abort") {
100
- registry.abortAll();
101
- hide();
102
- ctx.ui.notify("Requested abort of all active workflow runs; panel hidden.", "info");
103
- return;
104
- }
105
-
106
- const runs = registry.list();
107
-
108
- // Explicit run id -> show that run's detail.
109
- if (arg) {
110
- const handle = registry.get(arg) ?? runs.find((r) => r.snapshot.runId?.startsWith(arg));
111
- if (!handle) {
112
- ctx.ui.notify(`No workflow run matching "${arg}". /workflows to list, /workflows clear to hide.`, "warning");
113
- return;
114
- }
115
- show(renderWorkflowLines(handle.snapshot, { maxAgents: 12, maxLogs: 6, showResultPreviews: true, showStream: true }));
116
- ctx.ui.notify(`Showing ${handle.snapshot.runId ?? "run"}. /workflows clear to hide.`, "info");
96
+ if (arg.toLowerCase() === "abort") {
97
+ getRegistry().abortAll();
98
+ ctx.ui.notify("Requested abort of all active workflow runs.", "warning");
117
99
  return;
118
100
  }
119
-
120
- // Bare /workflows toggles the panel off if it's already up.
121
- if (panelVisible) {
122
- hide();
123
- ctx.ui.notify("Workflow panel hidden.", "info");
124
- return;
125
- }
126
-
127
- if (runs.length === 0) {
128
- hide();
129
- ctx.ui.notify("No workflow runs in this session yet.", "info");
130
- return;
131
- }
132
-
133
- const summary = runs.map((handle) => {
134
- const s = handle.snapshot;
135
- return `${statusGlyph(s.status)} ${s.runId} ${s.name} ${s.doneCount}/${s.agentCount}${
136
- s.runningCount ? ` (${s.runningCount} running)` : ""
137
- }`;
138
- });
139
- show(["◆ Ultracode workflow runs · /workflows clear to hide", ...summary.map((l) => ` ${l}`)]);
140
- ctx.ui.notify(
141
- `${runs.length} run(s), ${registry.active().length} active. /workflows again (or /workflows clear) to hide.`,
142
- "info",
143
- );
101
+ await openWorkflows(ctx, arg || undefined);
144
102
  },
145
103
  });
146
- }
147
104
 
148
- function statusGlyph(status: string): string {
149
- switch (status) {
150
- case "completed":
151
- return "✓";
152
- case "running":
153
- return "▶";
154
- case "aborted":
155
- return "■";
156
- case "failed":
157
- return "✗";
158
- default:
159
- return "·";
160
- }
105
+ pi.registerShortcut("f6", {
106
+ description: "Open workflow task details",
107
+ handler: async (ctx) => openWorkflows(ctx),
108
+ });
161
109
  }
package/src/index.ts CHANGED
@@ -22,9 +22,15 @@ export {
22
22
  matchModelIn,
23
23
  type AgentRunCall,
24
24
  type AgentRunResult,
25
+ type AgentTelemetryEvent,
26
+ type AgentTurnUsage,
27
+ type AgentUsage,
25
28
  type ThinkingLevel,
26
29
  type ModelLike,
27
30
  type ModelRegistryLike,
31
+ type ModelRuntimeCreateOptions,
32
+ type ModelRuntimeFactory,
33
+ type ModelRuntimeLike,
28
34
  } from "./workflow/agent-runner.ts";
29
35
  export { createStructuredOutputTool, type StructuredOutputCapture } from "./workflow/structured-output.ts";
30
36
  export {
@@ -42,8 +48,22 @@ export {
42
48
  renderWorkflowLines,
43
49
  renderWorkflowText,
44
50
  preview,
51
+ type WorkflowAgentSnapshot,
52
+ type WorkflowAgentUsageSnapshot,
45
53
  type WorkflowSnapshot,
46
54
  } from "./workflow/display.ts";
55
+ export {
56
+ WorkflowRunDetails,
57
+ MAX_LIVE_TASK_BYTES,
58
+ MAX_LIVE_TASK_LINES,
59
+ MAX_LIVE_RUN_BYTES,
60
+ MAX_TASK_TRANSCRIPT_BYTES,
61
+ MAX_RUN_TRANSCRIPT_BYTES,
62
+ type WorkflowTaskDetail,
63
+ type WorkflowTaskSummary,
64
+ type WorkflowTaskUsage,
65
+ type WorkflowTimelineEvent,
66
+ } from "./workflow/run-details.ts";
47
67
  export {
48
68
  createWorktree,
49
69
  captureWorktreeDiff,