pi-ultracode 0.1.1 → 0.1.3

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
@@ -2,7 +2,7 @@
2
2
 
3
3
  **Ultracode** 是 [Pi](https://github.com/badlogic/pi-mono) 的“全力执行”模式。它把 Pi 的 Agentic Loop 从单线程编码助手升级为能够运行子代理团队的编排器。
4
4
 
5
- 启用 Ultracode 后,主代理会获得一条 `workflow` 工具,以及一段要求它主动使用该工具处理复杂任务的系统提示词。每个子代理都运行在独立的内存 Pi 会话中,拥有标准编码工具、独立上下文,并可选择结构化输出、模型覆盖、Agent Type 和隔离的 git worktree。
5
+ 启用 Ultracode 后,主代理会获得一条 `workflow` 工具,以及一段要求它主动使用该工具处理复杂任务的系统提示词。扩展加载时会先注册该工具,并在模型请求前的 input 与 `before_agent_start` 边界按模式校准 active tools;未启用或已暂停时,残留调用会被拒绝。每个子代理都运行在独立的内存 Pi 会话中,拥有标准编码工具、独立上下文,并可选择结构化输出、模型覆盖、Agent Type 和隔离的 git worktree。
6
6
 
7
7
  Ultracode 保留 Pi 自身的核心循环、内存模型、工具系统、扩展 API 与会话格式。它是在 Pi 之上增加编排能力,而不是替换 Pi。
8
8
 
@@ -10,7 +10,7 @@ Ultracode 保留 Pi 自身的核心循环、内存模型、工具系统、扩展
10
10
 
11
11
  | 能力 | 说明 |
12
12
  | --- | --- |
13
- | `workflow` 工具 | 运行确定性的 JavaScript 编排脚本,并调用 `agent()`、`parallel()`、`pipeline()` 和 `workflow()`。 |
13
+ | `workflow` 工具 | 模型请求前按 Ultracode 状态校准;运行确定性的 JavaScript 编排脚本,并调用 `agent()`、`parallel()`、`pipeline()` 和 `workflow()`。 |
14
14
  | 独立子代理 | 每次 `agent()` 调用都会创建一条带标准编码工具的新 Pi 会话。 |
15
15
  | 并行执行 | `parallel()` 并发运行任务,且保留输入顺序。 |
16
16
  | 流水线 | `pipeline()` 让任务独立流经多个阶段,无需全局屏障。 |
@@ -18,7 +18,7 @@ Ultracode 保留 Pi 自身的核心循环、内存模型、工具系统、扩展
18
18
  | Agent Type | 通过 `.pi/ultracode/agents/*.md` 定义角色提示词、工具白名单、模型和思考强度。 |
19
19
  | 模型覆盖 | 每次 `agent()` 调用都可指定模型模式,例如 `anthropic/claude-sonnet-4`。 |
20
20
  | Worktree 隔离 | 让写入型并行代理在临时 git worktree 中运行,并把改动安全合并回共享工作树。 |
21
- | 进度面板 | 实时状态、活动计数、token 与成本统计、阶段信息,以及可选的 TUI 面板。 |
21
+ | 进度与详情 | 实时状态、实际模型/effort、turn/工具/token 统计,以及可流式展开的 TUI 任务详情浮层。 |
22
22
  | Ultracode 模式 | `/ultracode on` 启用主动编排提示词,并请求当前模型支持的最高思考强度。 |
23
23
  | 配额执行 | 可选 token 预算、代理数量限制、并发限制、脚本超时与嵌套深度限制。 |
24
24
 
@@ -82,17 +82,19 @@ pi --ultracode
82
82
  工作流运行时会内联显示实时进度,例如:
83
83
 
84
84
  ```text
85
- ◆ ▶ audit_repo (4/7 done, 2 running) · 3 cached · 41.2k/500k tok
85
+ ◆ ▶ audit_repo (4/7 done, 2 running) · 3 cached · 203k token (141k new, 62k replayed) · 41.2k/500k out
86
86
  ✓ Survey 1/1
87
87
  #1 ✓ repo inventory
88
+ gpt-5.6-sol • max · 15 turns · 42 tool uses · 141k token
88
89
  ▶ Review 3/4 · 1 running
89
90
  #2 ✓ auth module
90
91
  #3 ✓ db layer
91
92
  #4 ● payments module
93
+ claude-sonnet-4 • high · 6 turns · 18 tool uses · 52.3k+ token · partial
92
94
  ▶ Verify 0/2 · 2 running
93
95
  ```
94
96
 
95
- `Esc` 可取消;运行中的子代理会被终止并显示为 skipped。
97
+ 紧凑 token 数始终是 input + output;cache read/write 与成本只出现在任务详情中。主对话会折叠较早任务并明确显示省略数量,按 `Ctrl+O` 可展开全部,或用 `F6` 打开详情。按 `Esc` 可取消;运行中的子代理会被终止并显示为 cancelled,同时保留已有输出和部分用量。
96
98
 
97
99
  ## 命令
98
100
 
@@ -100,17 +102,25 @@ pi --ultracode
100
102
  /ultracode # toggle on/off
101
103
  /ultracode on # enable orchestration mode
102
104
  /ultracode on 500k # enable it with an output-token budget
103
- /ultracode off # disable it and restore the previous thinking level
105
+ /ultracode off # disable the tool and restore the previous thinking level
104
106
  /ultracode status # show status and the configured budget
105
107
  /ultracode budget 500k # set a token budget
106
108
  /ultracode budget off # remove the budget
107
- /workflows # toggle the workflow run panel
108
- /workflows <runId> # show one run's detail
109
- /workflows clear # hide the panel
110
- /workflows abort # abort active runs and hide the panel
109
+ /workflows # open the interactive workflow detail overlay
110
+ /workflows <runId> # open one run directly (prefix accepted)
111
+ /workflows abort # abort active runs
112
+ F6 # open the workflow detail overlay
111
113
  ```
112
114
 
113
- `/workflows` 面板是执行命令时生成的静态快照;再次运行该命令可刷新,`/workflows clear` 只隐藏面板,不删除 registry 记录。工作流执行期间的实时进度显示在工具结果中。为缩小隐私边界,进度只包含结构化生命周期状态和安全摘要,不捕获、持久化或展示子代理的原始流式响应正文。
115
+ `/workflows` `F6` 打开实时浮层。只有一个活动运行时会直接进入;多个活动运行会先显示选择器;没有活动运行时会打开最近完成的运行。宽终端使用任务/详情双栏,窄终端在列表与详情间切换。键盘操作:
116
+
117
+ - `↑` / `↓` 选择运行或任务;`Enter` 打开;`Tab` 切换双栏焦点;`Esc` 返回或关闭;
118
+ - `PageUp` / `PageDown` 滚动;`End` 恢复尾随;
119
+ - `/` 搜索任务,`r` 只看运行中的任务,`a` 恢复全部,`p` 展开或收起任务 prompt。
120
+
121
+ 每次 `agent()` 调用都是一个可独立展开的任务。详情按时间交错显示 turn、assistant 正文、工具调用、重试、上下文压缩、错误与 thinking 持续时间;thinking 正文不会被捕获。正文 delta 只保存在有界内存中并以不超过每 100ms 一次的频率刷新,消息完成后改用 Markdown 渲染。
122
+
123
+ 所有可视化和落盘内容都会移除终端控制序列并脱敏凭据。内存上限为每任务 1MiB / 5000 行、每工作流 32MiB;最终 transcript 上限为每任务 10MB(约 1MB 头部 + 9MB 尾部)、每工作流 128MB。最终清理后的 timeline 与 manifest 保存在 session 的 `ultracode-runs/` 目录,因而 `/reload` 后仍可浏览;不会复制系统 prompt、注入 skill、AGENTS 内容或父会话上下文。
114
124
 
115
125
  状态行会显示实际生效的思考强度,例如:
116
126
 
@@ -133,7 +143,7 @@ Ultracode 请求的是**当前模型支持的最高强度**,不是固定标签
133
143
 
134
144
  ## 工作原理
135
145
 
136
- 扩展注册单个 `workflow` 工具。工具输入是 JavaScript 源码,其中必须包含纯字面量 `meta` 导出:
146
+ 扩展注册单个 `workflow` 工具,并在 input preflight 与 `before_agent_start` 边界按 Ultracode 状态校准 active tools,同时保留其他 active tools。关闭或暂停时,`tool_call` 与实际执行入口会双重 fail-closed 拒绝残留调用。工具输入是 JavaScript 源码,其中必须包含纯字面量 `meta` 导出:
137
147
 
138
148
  ```js
139
149
  export const meta = {
@@ -410,11 +420,12 @@ while (budget.total && budget.remaining() > 50_000) {
410
420
 
411
421
  Ultracode 模式状态通过自定义 session entry 持久化。恢复、reload、fork 和 `/tree` 导航都会按当前 branch 重新读取状态;被丢弃分支中的 entry 不会错误启用模式。旧版 Pi 或旧模型会把 `max` 兼容回退为 `xhigh`,而不会把未知值静默变成 `off`。
412
422
 
413
- 扩展清理只管理自己的 widget、status 与运行状态,不会调用 Pi 的全局 `ui.clear()`,因此不会清除其他扩展的 UI。
423
+ 扩展清理只管理自己的详情浮层、status 与运行状态,不会调用 Pi 的全局 `ui.clear()`,因此不会清除其他扩展的 UI。
414
424
 
415
425
  ## 开发
416
426
 
417
427
  ```bash
428
+ npm ci
418
429
  npm run typecheck
419
430
  npm test
420
431
  npm pack --dry-run
@@ -429,7 +440,32 @@ npm pack --dry-run
429
440
  - `test/`:单元、扩展和回归测试;
430
441
  - `docs/parity.md`:与原生 Pi 能力边界的说明。
431
442
 
432
- 直接运行 Node 测试时没有 Pi 的模块加载器,需要把真实的 `acorn` 与 Pi 提供的 peer 包链接到 `node_modules/`;`npm run typecheck` 还要求本地可用的 TypeScript。通过 `pi install` 加载扩展时,Pi 会提供这些 peer 依赖。
443
+ 开发依赖会安装 TypeScript 和用于测试的 Pi peer 包;通过 `pi install` 加载正式包时,Pi 仍会提供这些 peer 依赖。
444
+
445
+ ## 发布
446
+
447
+ npm 发布由 [`.github/workflows/publish-npm.yml`](.github/workflows/publish-npm.yml) 完成,使用 npm Trusted Publishing(OIDC),无需保存长期 `NPM_TOKEN`。
448
+
449
+ 首次使用前,在 npm 的 `pi-ultracode` 包设置中添加 GitHub Actions Trusted Publisher:
450
+
451
+ - Organization or user:`ekil1100`
452
+ - Repository:`pi-ultracode`
453
+ - Workflow filename:`publish-npm.yml`
454
+ - Environment name:`npm`
455
+ - Allowed action:`npm publish`
456
+
457
+ 同时在 GitHub 仓库中创建名为 `npm` 的 Environment;可以按需增加审批人。正常发布无需打开 Actions 页面:
458
+
459
+ ```bash
460
+ npm version patch # or: minor / major / 0.2.0-beta.1
461
+ git push origin main --follow-tags
462
+ ```
463
+
464
+ `npm version` 会同步更新 `package.json` 和 `package-lock.json`,创建版本提交与 `v<version>` Tag;推送 Tag 后 CI 自动发布。稳定版本发布到 `latest`,预发布版本发布到 `next`。
465
+
466
+ 需要重试或指定其他 npm dist-tag 时,也可以在 Actions 中手动运行工作流;填写完全匹配 `package.json` 的版本,且只允许从 `main` 运行。
467
+
468
+ 工作流会校验 Tag 与包版本完全一致、确认版本尚未发布,并在正式发布前执行干净安装、测试和类型检查。
433
469
 
434
470
  ## 设计原则
435
471
 
@@ -42,6 +42,7 @@ export default function extension(pi: ExtensionAPI, extraDeps: UltracodeExtensio
42
42
  getDefaultBudget: () => mode.getBudget(),
43
43
  getThinkingLevel: () => mode.getSubagentThinkingLevel(),
44
44
  ...workflowDeps,
45
+ isExecutionAllowed: () => mode.isEnforcing(),
45
46
  });
46
47
  pi.registerTool(workflowTool);
47
48
 
@@ -53,6 +54,23 @@ export default function extension(pi: ExtensionAPI, extraDeps: UltracodeExtensio
53
54
  description: "Start the session in ultracode mode (max thinking + default workflow orchestration).",
54
55
  });
55
56
 
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
+
56
74
  pi.on("session_start", async (_event, ctx) => {
57
75
  await mode.flushThinkingPreference();
58
76
  mode.setCurrentModelSupportsThinking(ctx.model ? Boolean(ctx.model.reasoning) : undefined);
@@ -74,13 +92,8 @@ export default function extension(pi: ExtensionAPI, extraDeps: UltracodeExtensio
74
92
  if (!mode.isEnabled() && pi.getFlag?.("ultracode") === true) {
75
93
  mode.enable(pi);
76
94
  }
77
- // Always keep the workflow tool available so the model can use it on request.
78
- try {
79
- const active = pi.getActiveTools();
80
- if (!active.includes(workflowTool.name)) pi.setActiveTools([...active, workflowTool.name]);
81
- } catch {
82
- // ignore
83
- }
95
+ // Registration makes extension tools discoverable; activation remains opt-in.
96
+ mode.syncWorkflowTool(pi);
84
97
  await mode.flushThinkingPreference();
85
98
  if (ctx.hasUI) {
86
99
  ctx.ui.setStatus("ultracode", mode.isEnabled() ? mode.statusLine() : undefined);
@@ -119,7 +132,9 @@ export default function extension(pi: ExtensionAPI, extraDeps: UltracodeExtensio
119
132
  });
120
133
 
121
134
  pi.on("before_agent_start", async (event) => {
122
- // Final synchronous effort barrier before Pi starts the provider request.
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);
123
138
  mode.reapplyMaximumThinking(pi);
124
139
  await mode.flushThinkingPreference();
125
140
  return mode.beforeAgentStart(event);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-ultracode",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
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.83.0",
57
+ "@earendil-works/pi-ai": "^0.83.0",
58
+ "@earendil-works/pi-coding-agent": "^0.83.0",
59
+ "@earendil-works/pi-tui": "^0.83.0",
60
+ "@types/node": "^24.12.4",
61
+ "typebox": "1.3.7",
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,8 +5,8 @@
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";
9
- import { truncateDisplay } from "./workflow/display-text.ts";
8
+ import { workflowRunsDir } from "./workflow/tool.ts";
9
+ import { openWorkflowOverlay } from "./workflow/workflow-overlay.ts";
10
10
 
11
11
  export function registerCommands(pi: ExtensionAPI, mode: UltracodeMode): void {
12
12
  pi.registerCommand("ultracode", {
@@ -25,7 +25,10 @@ export function registerCommands(pi: ExtensionAPI, mode: UltracodeMode): void {
25
25
  if (sub === "") {
26
26
  const nowOn = mode.toggle(pi);
27
27
  await mode.flushThinkingPreference();
28
- ctx.ui.notify(nowOn ? `Ultracode on — ${mode.statusLine()}` : "Ultracode off — thinking restored.", "info");
28
+ ctx.ui.notify(
29
+ nowOn ? `Ultracode on — ${mode.statusLine()}` : "Ultracode off — thinking restored and workflow tool disabled.",
30
+ "info",
31
+ );
29
32
  ctx.ui.setStatus("ultracode", nowOn ? mode.statusLine() : undefined);
30
33
  return;
31
34
  }
@@ -38,7 +41,7 @@ export function registerCommands(pi: ExtensionAPI, mode: UltracodeMode): void {
38
41
  if (sub === "off") {
39
42
  mode.disable(pi);
40
43
  await mode.flushThinkingPreference();
41
- ctx.ui.notify("Ultracode off — thinking restored, workflow orchestration is opt-in again.", "info");
44
+ ctx.ui.notify("Ultracode off — thinking restored and workflow tool disabled.", "info");
42
45
  ctx.ui.setStatus("ultracode", undefined);
43
46
  return;
44
47
  }
@@ -71,96 +74,36 @@ export function registerCommands(pi: ExtensionAPI, mode: UltracodeMode): void {
71
74
  },
72
75
  });
73
76
 
74
- // Tracks whether the run panel is currently shown, so bare /workflows toggles it.
75
- 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
+ };
76
82
 
77
83
  pi.registerCommand("workflows", {
78
- description: "Toggle the workflow-run panel. Usage: /workflows [runId | clear | abort]",
84
+ description: "Open the interactive workflow/task detail overlay. Usage: /workflows [runId | abort]",
79
85
  getArgumentCompletions(prefix: string) {
80
- return ["clear", "abort"]
81
- .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))
82
92
  .map((value) => ({ value, label: value }));
83
93
  },
84
94
  handler: async (args: string, ctx) => {
85
- const registry = getRegistry();
86
95
  const arg = args.trim();
87
- const sub = arg.toLowerCase();
88
-
89
- const hide = () => {
90
- ctx.ui.setWidget("ultracode-workflows", undefined);
91
- panelVisible = false;
92
- };
93
- const show = (lines: string[]) => {
94
- ctx.ui.setWidget("ultracode-workflows", lines);
95
- panelVisible = true;
96
- };
97
-
98
- if (sub === "clear" || sub === "hide" || sub === "off") {
99
- hide();
100
- ctx.ui.notify("Workflow panel hidden.", "info");
101
- return;
102
- }
103
-
104
- if (sub === "abort") {
105
- registry.abortAll();
106
- hide();
107
- ctx.ui.notify("Requested abort of all active workflow runs; panel hidden.", "info");
108
- return;
109
- }
110
-
111
- const runs = registry.list();
112
-
113
- // Explicit run id -> show that run's detail.
114
- if (arg) {
115
- const handle = registry.get(arg) ?? runs.find((r) => r.snapshot.runId?.startsWith(arg));
116
- if (!handle) {
117
- ctx.ui.notify(`No workflow run matching "${truncateDisplay(arg, 80)}". /workflows to list, /workflows clear to hide.`, "warning");
118
- return;
119
- }
120
- show(renderWorkflowLines(handle.snapshot, { maxAgents: 12, maxLogs: 6, showResultPreviews: true }));
121
- ctx.ui.notify(`Showing ${truncateDisplay(handle.snapshot.runId ?? "run", 128)}. /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");
122
99
  return;
123
100
  }
124
-
125
- // Bare /workflows toggles the panel off if it's already up.
126
- if (panelVisible) {
127
- hide();
128
- ctx.ui.notify("Workflow panel hidden.", "info");
129
- return;
130
- }
131
-
132
- if (runs.length === 0) {
133
- hide();
134
- ctx.ui.notify("No workflow runs in this session yet.", "info");
135
- return;
136
- }
137
-
138
- const summary = runs.map((handle) => {
139
- const s = handle.snapshot;
140
- return `${statusGlyph(s.status)} ${truncateDisplay(s.runId ?? "run", 128)} ${truncateDisplay(s.name, 80)} ${s.doneCount}/${s.agentCount}${
141
- s.runningCount ? ` (${s.runningCount} running)` : ""
142
- }`;
143
- });
144
- show(["◆ Ultracode workflow runs · /workflows clear to hide", ...summary.map((l) => ` ${l}`)]);
145
- ctx.ui.notify(
146
- `${runs.length} run(s), ${registry.active().length} active. /workflows again (or /workflows clear) to hide.`,
147
- "info",
148
- );
101
+ await openWorkflows(ctx, arg || undefined);
149
102
  },
150
103
  });
151
- }
152
104
 
153
- function statusGlyph(status: string): string {
154
- switch (status) {
155
- case "completed":
156
- return "✓";
157
- case "running":
158
- return "▶";
159
- case "aborted":
160
- return "■";
161
- case "failed":
162
- return "✗";
163
- default:
164
- return "·";
165
- }
105
+ pi.registerShortcut("f6", {
106
+ description: "Open workflow task details",
107
+ handler: async (ctx) => openWorkflows(ctx),
108
+ });
166
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,
package/src/mode.ts CHANGED
@@ -154,9 +154,13 @@ export class UltracodeMode {
154
154
 
155
155
  /** Stop enforcing synchronously, then restore effort before session teardown. */
156
156
  suspend(pi: ExtensionAPI): void {
157
- if (this.suspended) return;
157
+ if (this.suspended) {
158
+ this.syncWorkflowTool(pi);
159
+ return;
160
+ }
158
161
  this.suspended = true;
159
162
  this.restorePreviousThinking(pi);
163
+ this.syncWorkflowTool(pi);
160
164
  }
161
165
 
162
166
  /**
@@ -191,6 +195,12 @@ export class UltracodeMode {
191
195
  return this.budgetTotal;
192
196
  }
193
197
 
198
+ /** Keep tool availability aligned with the current mode state. */
199
+ syncWorkflowTool(pi: ExtensionAPI): void {
200
+ if (this.isEnforcing()) this.activateWorkflowTool(pi);
201
+ else this.deactivateWorkflowTool(pi);
202
+ }
203
+
194
204
  tagline(): string {
195
205
  return ULTRACODE_TAGLINE;
196
206
  }
@@ -221,13 +231,16 @@ export class UltracodeMode {
221
231
  this.enabled = true;
222
232
  }
223
233
  this.applyUltracodeThinking(pi);
224
- this.activateWorkflowTool(pi);
234
+ this.syncWorkflowTool(pi);
225
235
  this.persist(pi);
226
236
  }
227
237
 
228
238
  /** Turn ultracode off, restoring the previous thinking level. */
229
239
  disable(pi: ExtensionAPI): void {
230
- if (!this.enabled) return;
240
+ if (!this.enabled) {
241
+ this.syncWorkflowTool(pi);
242
+ return;
243
+ }
231
244
  const previous = this.previousThinking;
232
245
  this.restorePreviousThinking(pi);
233
246
  this.pendingPreviousThinking = previous && !this.pendingRestoreSucceeded(previous)
@@ -236,6 +249,7 @@ export class UltracodeMode {
236
249
  this.pendingClearGeneration++;
237
250
  this.enabled = false;
238
251
  this.suspended = false;
252
+ this.syncWorkflowTool(pi);
239
253
  this.persist(pi);
240
254
  }
241
255
 
@@ -290,6 +304,7 @@ export class UltracodeMode {
290
304
  this.previousDefaultThinking = globalPreference;
291
305
  this.legacyDefaultMigrationPending = false;
292
306
  this.appliedThinking = current;
307
+ this.syncWorkflowTool(pi);
293
308
  if (target && current !== target) this.applyCompatibleThinking(pi, target);
294
309
  if (target && !this.pendingRestoreSucceeded(target)) {
295
310
  this.pendingPreviousThinking = target;
@@ -304,6 +319,7 @@ export class UltracodeMode {
304
319
  this.suspended = false;
305
320
  this.enabled = latest.enabled;
306
321
  this.budgetTotal = latest.budgetTotal;
322
+ this.syncWorkflowTool(pi);
307
323
  const maxIsUnknownToRuntime = !this.runtimeSupportsMaxThinking
308
324
  && preference.effective === ULTRACODE_THINKING_LEVEL;
309
325
  this.previousThinking = latest.previousThinking
@@ -330,7 +346,6 @@ export class UltracodeMode {
330
346
  this.pendingPreviousThinking = this.enabled ? undefined : latest.pendingPreviousThinking;
331
347
  if (this.enabled) {
332
348
  this.applyUltracodeThinking(pi);
333
- this.activateWorkflowTool(pi);
334
349
  if (migratesLegacyDefault) {
335
350
  this.queueDefaultThinkingRestore(() => {
336
351
  this.legacyDefaultMigrationPending = false;
@@ -430,7 +445,7 @@ export class UltracodeMode {
430
445
  }
431
446
  }
432
447
 
433
- private isEnforcing(): boolean {
448
+ isEnforcing(): boolean {
434
449
  return this.enabled && !this.suspended;
435
450
  }
436
451
 
@@ -509,7 +524,18 @@ export class UltracodeMode {
509
524
  pi.setActiveTools([...active, this.workflowToolName]);
510
525
  }
511
526
  } catch {
512
- // ignore
527
+ // Mode enforcement remains usable when tool selection is unavailable.
528
+ }
529
+ }
530
+
531
+ private deactivateWorkflowTool(pi: ExtensionAPI): void {
532
+ try {
533
+ const active = pi.getActiveTools();
534
+ if (active.includes(this.workflowToolName)) {
535
+ pi.setActiveTools(active.filter((name) => name !== this.workflowToolName));
536
+ }
537
+ } catch {
538
+ // The execution guards still fail closed when selection cannot be updated.
513
539
  }
514
540
  }
515
541
 
package/src/prompts.ts CHANGED
@@ -59,7 +59,7 @@ export const WORKFLOW_PROMPT_SNIPPET =
59
59
 
60
60
  /** Guideline bullets appended to the Guidelines section when the tool is active. */
61
61
  export const WORKFLOW_GUIDELINES: string[] = [
62
- "Use the workflow tool to decompose-and-cover in parallel, to gather independent perspectives that adversarially verify each other, or to take on scale a single context can't hold. Outside ultracode mode, reserve it for explicit fan-out / multi-agent requests.",
62
+ "While Ultracode is enabled, use the workflow tool to decompose-and-cover in parallel, gather independent perspectives that adversarially verify each other, or take on scale one context can't hold.",
63
63
  "For the workflow tool, pass one raw JavaScript string in the required `script` parameter; no Markdown fences, no prose around the script.",
64
64
  "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.",
65
65
  "For the workflow tool, write plain JavaScript after the meta export. No TypeScript syntax, imports, require(), fs, network, Date.now(), Math.random(), or new Date() (they break determinism and resume). Stamp timestamps after the workflow returns; vary randomness by agent index.",