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 +50 -14
- package/extensions/ultracode.ts +23 -8
- package/package.json +22 -1
- package/src/commands.ts +27 -84
- package/src/index.ts +20 -0
- package/src/mode.ts +32 -6
- package/src/prompts.ts +1 -1
- package/src/workflow/agent-runner.ts +526 -39
- package/src/workflow/display-text.ts +17 -6
- package/src/workflow/display.ts +104 -20
- package/src/workflow/journal.ts +0 -0
- package/src/workflow/registry.ts +106 -10
- package/src/workflow/run-details.ts +1276 -0
- package/src/workflow/runtime.ts +175 -26
- package/src/workflow/tool.ts +180 -14
- package/src/workflow/workflow-overlay.ts +719 -0
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`
|
|
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` 工具 |
|
|
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
|
-
|
|
|
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
|
|
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
|
-
|
|
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
|
|
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 #
|
|
108
|
-
/workflows <runId> #
|
|
109
|
-
/workflows
|
|
110
|
-
|
|
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`
|
|
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`
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
package/extensions/ultracode.ts
CHANGED
|
@@ -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
|
-
//
|
|
78
|
-
|
|
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
|
-
//
|
|
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.
|
|
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 {
|
|
9
|
-
import {
|
|
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(
|
|
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
|
|
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
|
-
|
|
75
|
-
|
|
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: "
|
|
84
|
+
description: "Open the interactive workflow/task detail overlay. Usage: /workflows [runId | abort]",
|
|
79
85
|
getArgumentCompletions(prefix: string) {
|
|
80
|
-
|
|
81
|
-
|
|
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
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
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)
|
|
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.
|
|
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)
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
-
"
|
|
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.",
|