pi-ultracode 0.1.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 ADDED
@@ -0,0 +1,225 @@
1
+ # pi-ultracode
2
+
3
+ > Claude-Code-style **ultracode** for [Pi](https://github.com/earendil-works/pi).
4
+
5
+ `ultracode` is an effort mode that turns Pi into a relentless, multi-agent
6
+ orchestrator. While it's on, Pi raises its thinking to **xhigh** and treats
7
+ "author and run a workflow" as the **default** for every substantive task —
8
+ decomposing work, fanning it out across isolated subagents, and adversarially
9
+ verifying findings before committing to an answer. Token cost is not the
10
+ constraint; correctness and coverage are.
11
+
12
+ This single extension implements the full ultracode surface:
13
+
14
+ | Pillar | What you get |
15
+ | --- | --- |
16
+ | **Ultracode mode** | `/ultracode on` raises thinking to xhigh, keeps the `workflow` tool active, and injects a standing "orchestrate + verify by default" system block on every turn. Survives reload / resume / fork via session entries. Optional token budget. |
17
+ | **The `workflow` tool** | A deterministic JavaScript orchestrator: `agent()`, `parallel()`, `pipeline()`, `phase()`, `log()`, `workflow()` (nesting), `args`, `budget`. Plus per-agent **model overrides**, **custom agent types**, **git-worktree isolation**, a real **token budget**, **resumable runs**, and **script persistence**. |
18
+ | **`/workflows` manager** | List recent and in-flight runs with live progress; inspect or abort runs. |
19
+
20
+ Inspired by Anthropic's [dynamic workflows in Claude Code](https://claude.com/blog/introducing-dynamic-workflows-in-claude-code),
21
+ and a superset of the `pi-dynamic-workflows` prototype.
22
+
23
+ ## Install
24
+
25
+ ```bash
26
+ pi install npm:pi-ultracode # or: pi install /path/to/pi-ultracode
27
+ ```
28
+
29
+ > **Conflict note:** pi-ultracode registers a tool named `workflow`, the same name
30
+ > used by `pi-dynamic-workflows`. pi-ultracode is a strict superset, so remove the
31
+ > prototype first: `pi remove npm:pi-dynamic-workflows`.
32
+
33
+ Then in Pi: `/reload`.
34
+
35
+ ## Usage
36
+
37
+ ### Turn ultracode on
38
+
39
+ ```text
40
+ /ultracode # TOGGLE on/off
41
+ /ultracode on # xhigh thinking + default workflow orchestration
42
+ /ultracode on 500k # also cap the per-session output-token budget at ~500k
43
+ /ultracode budget 1m # change the budget later
44
+ /ultracode status # show current mode
45
+ /ultracode off # restore the previous thinking level
46
+ ```
47
+
48
+ > **Models without `xhigh`:** pi clamps the requested level down to the model's
49
+ > maximum (e.g. glm-5.2 tops out at `high`; non-reasoning models fall to `off`) —
50
+ > it never errors. `/ultracode status` shows the level that actually applied
51
+ > (e.g. `ultracode: on · thinking high`). The **workflow orchestration** half of
52
+ > ultracode doesn't depend on the thinking level at all, so it's unaffected by
53
+ > the model you run.
54
+
55
+ You can also start a session in ultracode mode with the CLI flag:
56
+
57
+ ```bash
58
+ pi --ultracode
59
+ ```
60
+
61
+ While on, just describe the task. Pi will write a workflow script and call the
62
+ `workflow` tool, with live progress shown inline:
63
+
64
+ ```text
65
+ ◆ ▶ audit_repo (4/7 done, 2 running) · 3 cached · 41.2k/500k tok
66
+ ✓ Survey 1/1
67
+ #1 ✓ repo inventory
68
+ ▶ Review 3/4 · 1 running
69
+ #2 ✓ auth module
70
+ #3 ✓ db layer
71
+ #4 ● payments module
72
+ ▶ Verify 0/2 · 2 running
73
+ ```
74
+
75
+ Press `Esc` to cancel; running subagents are aborted and surfaced as skipped.
76
+
77
+ ### Manage runs
78
+
79
+ ```text
80
+ /workflows # toggle the run panel above the editor (run again to hide)
81
+ /workflows wf_ab12-3 # show one run's detail
82
+ /workflows clear # hide the panel
83
+ /workflows abort # abort all active runs (and hide the panel)
84
+ ```
85
+
86
+ The panel is a static snapshot taken when you run the command, so re-run
87
+ `/workflows` to refresh it, or `/workflows clear` to dismiss it. Live progress
88
+ while a workflow runs is shown inline in the tool result, not in this panel.
89
+
90
+ ## Workflow script shape
91
+
92
+ A workflow is plain JavaScript whose first statement is a **pure literal**
93
+ `export const meta`:
94
+
95
+ ```js
96
+ export const meta = {
97
+ name: 'review_changes',
98
+ description: 'Review changed files across dimensions, verify each finding',
99
+ phases: [{ title: 'Review' }, { title: 'Verify' }],
100
+ }
101
+
102
+ const DIMENSIONS = [
103
+ { key: 'bugs', prompt: 'Find correctness bugs in the diff.' },
104
+ { key: 'perf', prompt: 'Find performance regressions in the diff.' },
105
+ ]
106
+
107
+ // pipeline: each dimension verifies as soon as its review completes (no barrier).
108
+ const results = await pipeline(
109
+ DIMENSIONS,
110
+ (d) => agent(d.prompt, { label: 'review:' + d.key, phase: 'Review', schema: FINDINGS }),
111
+ (review) => parallel((review.findings ?? []).map((f) => () =>
112
+ agent('Adversarially verify, default to refuted: ' + f.title,
113
+ { label: 'verify:' + f.file, phase: 'Verify', agentType: 'code-reviewer', schema: VERDICT })
114
+ .then((v) => ({ ...f, verdict: v }))
115
+ )),
116
+ )
117
+
118
+ const confirmed = results.flat().filter(Boolean).filter((f) => f.verdict?.isReal)
119
+ return { confirmed }
120
+
121
+ const FINDINGS = { type: 'object', properties: { findings: { type: 'array', items: { type: 'object' } } } }
122
+ const VERDICT = { type: 'object', properties: { isReal: { type: 'boolean' }, why: { type: 'string' } }, required: ['isReal'] }
123
+ ```
124
+
125
+ ### Globals
126
+
127
+ | Global | Description |
128
+ | --- | --- |
129
+ | `agent(prompt, opts)` | Spawn an isolated subagent. Returns its final text, or a validated object when `opts.schema` is set. |
130
+ | `parallel(thunks)` | Run `() => agent(...)` thunks concurrently. **A barrier.** Failures become `null`. |
131
+ | `pipeline(items, ...stages)` | Run each item through stages independently (no barrier). Stages get `(prev, original, index)`. **The default** for multi-stage work. |
132
+ | `workflow(nameOrRef, args)` | Run a saved workflow (by name) or `{ scriptPath }` inline, sharing the run's concurrency cap, agent counter, and budget. One level of nesting. |
133
+ | `phase(title)` | Mark the current progress group. |
134
+ | `log(message)` | Append a workflow-level log line. |
135
+ | `args`, `cwd` | The tool's `args` value; the working directory. |
136
+ | `budget` | `{ total, spent(), remaining() }` — real output-token budget. |
137
+
138
+ ### `agent()` options
139
+
140
+ | Option | Effect |
141
+ | --- | --- |
142
+ | `label` | Short (2-5 word) name shown in live progress. |
143
+ | `phase` | Assign this agent to a progress group explicitly (use inside `parallel`/`pipeline`). |
144
+ | `schema` | Plain JSON Schema; the subagent returns a validated object via a terminating `structured_output` tool. |
145
+ | `model` | Override the subagent model by pattern, e.g. `'sonnet'` or `'provider/id:high'`. |
146
+ | `agentType` | Use a custom role: built-ins `claude`, `general-purpose`, `Explore`, `Plan`, `code-reviewer`, or your own (below). |
147
+ | `isolation: 'worktree'` | Run the agent in a throwaway git worktree (for parallel file mutation). Changes are applied back to the working tree under a lock; an unchanged worktree is auto-removed. |
148
+
149
+ ### Determinism
150
+
151
+ Scripts run in a `vm` sandbox. `Date.now()`, `new Date()`, `Math.random()`,
152
+ `require`/`import`, `fs`, and network APIs are unavailable — this keeps `meta`
153
+ parseable and runs **reproducible and resumable**. Pass timestamps via `args`;
154
+ vary randomness by agent index.
155
+
156
+ ## Custom agent types
157
+
158
+ Drop a Markdown file with frontmatter under
159
+ `.pi/ultracode/agents/<name>.md` (project) or `~/.pi/ultracode/agents/<name>.md`
160
+ (user):
161
+
162
+ ```markdown
163
+ ---
164
+ name: security
165
+ description: Security-focused reviewer
166
+ tools: read, grep, find, bash
167
+ model: sonnet
168
+ thinking: high
169
+ ---
170
+ You are a security reviewer. Hunt for injection, authz gaps, and unsafe deserialization.
171
+ Cite exact file:line evidence and prefer false negatives over invented findings.
172
+ ```
173
+
174
+ Then `agent('Audit auth.ts', { agentType: 'security' })`.
175
+
176
+ ## Saved & resumable runs
177
+
178
+ Every run persists its script and a JSONL journal under
179
+ `<sessionDir>/ultracode-runs/<runId>.{workflow.js,jsonl}`. To resume after a
180
+ pause, kill, or script edit, call the tool again with `resumeFromRunId`: the
181
+ longest unchanged prefix of `agent()` calls returns cached results instantly;
182
+ the first changed/new call and everything after it run live.
183
+
184
+ Save reusable workflows under `.pi/ultracode/workflows/<name>.workflow.js` and
185
+ run them with the tool's `name` parameter or `workflow('<name>')`.
186
+
187
+ ## Library modules
188
+
189
+ | File | Purpose |
190
+ | --- | --- |
191
+ | `src/prompts.ts` | Ultracode system block + workflow tool guidelines. |
192
+ | `src/mode.ts` | The ultracode mode controller (toggle, thinking, persistence, injection). |
193
+ | `src/commands.ts` | `/ultracode` and `/workflows` commands. |
194
+ | `src/workflow/parser.ts` | AST-validated, deterministic script parser. |
195
+ | `src/workflow/runtime.ts` | The sandboxed runtime (agent/parallel/pipeline/phase/log/workflow/budget). |
196
+ | `src/workflow/agent-runner.ts` | In-memory subagent runner (model, agent type, worktree, usage). |
197
+ | `src/workflow/worktree.ts` | Git worktree isolation. |
198
+ | `src/workflow/agent-types.ts` | Custom agent-type discovery. |
199
+ | `src/workflow/journal.ts` | Run journal + resume. |
200
+ | `src/workflow/registry.ts` | In-process run registry for `/workflows`. |
201
+ | `src/workflow/display.ts` | Live progress snapshots and renderers. |
202
+ | `src/workflow/structured-output.ts` / `json-schema.ts` | Terminating structured output + JSON-Schema → TypeBox. |
203
+ | `src/workflow/tool.ts` | The Pi `workflow` tool. |
204
+ | `extensions/ultracode.ts` | Extension entrypoint. |
205
+
206
+ ## Development
207
+
208
+ ```bash
209
+ npm test # node --test over the parser, runtime, journal, agent types, mode, and extension
210
+ npm run typecheck # tsc --noEmit (requires a local TypeScript)
211
+ ```
212
+
213
+ The test suite injects a mock subagent runner, so it runs without a model. A
214
+ faithful end-to-end check (real subagents) just calls `runWorkflow` with the
215
+ default runner.
216
+
217
+ **Dependencies.** The only hard runtime dependency is `acorn`; Pi provides
218
+ `typebox` and `@earendil-works/*` to extensions at load time. When running the
219
+ node test runner directly (which has no Pi loader), link those peers into
220
+ `node_modules/` — e.g. symlink them from your Pi install — alongside the
221
+ real `acorn` that `pi install` / `npm install` provides.
222
+
223
+ ## License
224
+
225
+ MIT
@@ -0,0 +1,74 @@
1
+ /// <reference types="pi-ultracode/workflow" />
2
+ //
3
+ // Example saved workflow. Copy to `.pi/ultracode/workflows/` and run it with
4
+ // the `workflow` tool's `name` parameter, or call `workflow('loop-until-dry-bugs')`
5
+ // from another workflow.
6
+ //
7
+ // Pattern: loop-until-dry discovery + perspective-diverse adversarial verify.
8
+ // Keeps spawning finders until two consecutive rounds surface nothing new, then
9
+ // confirms each fresh bug with three distinct lenses.
10
+
11
+ export const meta = {
12
+ name: 'loop-until-dry-bugs',
13
+ description: 'Find bugs until the well runs dry, verifying each with three lenses',
14
+ phases: [{ title: 'Find' }, { title: 'Verify' }],
15
+ }
16
+
17
+ const FINDERS = [
18
+ 'Find correctness bugs by reading the changed files closely.',
19
+ 'Find bugs by tracing data flow across module boundaries.',
20
+ 'Find bugs by hunting error-handling and edge-case gaps.',
21
+ ]
22
+
23
+ const BUGS = {
24
+ type: 'object',
25
+ properties: {
26
+ bugs: {
27
+ type: 'array',
28
+ items: {
29
+ type: 'object',
30
+ properties: { file: { type: 'string' }, line: { type: 'number' }, desc: { type: 'string' } },
31
+ required: ['file', 'desc'],
32
+ },
33
+ },
34
+ },
35
+ required: ['bugs'],
36
+ }
37
+
38
+ const VERDICT = {
39
+ type: 'object',
40
+ properties: { real: { type: 'boolean' }, why: { type: 'string' } },
41
+ required: ['real'],
42
+ }
43
+
44
+ const seen = new Set()
45
+ const confirmed = []
46
+ let dry = 0
47
+
48
+ while (dry < 2) {
49
+ const rounds = await parallel(
50
+ FINDERS.map((prompt, i) => () => agent(prompt, { label: 'find ' + i, phase: 'Find', schema: BUGS })),
51
+ )
52
+ const found = rounds.filter(Boolean).flatMap((r) => r.bugs ?? [])
53
+ const fresh = found.filter((b) => !seen.has(b.file + ':' + b.desc))
54
+ if (fresh.length === 0) {
55
+ dry++
56
+ continue
57
+ }
58
+ dry = 0
59
+ for (const b of fresh) seen.add(b.file + ':' + b.desc)
60
+
61
+ const judged = await parallel(
62
+ fresh.map((b) => () =>
63
+ parallel(
64
+ ['correctness', 'security', 'does-it-reproduce'].map((lens) => () =>
65
+ agent('Judge via the ' + lens + ' lens — is this real? "' + b.desc + '" (' + b.file + '). Default to real:false if unsure.',
66
+ { label: 'verify ' + lens, phase: 'Verify', agentType: 'code-reviewer', schema: VERDICT }),
67
+ ),
68
+ ).then((votes) => ({ bug: b, real: votes.filter(Boolean).filter((v) => v.real).length >= 2 })),
69
+ ),
70
+ )
71
+ confirmed.push(...judged.filter((j) => j.real).map((j) => j.bug))
72
+ }
73
+
74
+ return { confirmed, totalSeen: seen.size }
@@ -0,0 +1,56 @@
1
+ /**
2
+ * pi-ultracode extension entrypoint.
3
+ *
4
+ * Wires together the three pillars of Claude-Code-style "ultracode":
5
+ * 1. The ultracode effort mode (xhigh thinking + standing workflow opt-in).
6
+ * 2. The full `workflow` orchestration tool.
7
+ * 3. The `/ultracode` and `/workflows` commands.
8
+ */
9
+
10
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
11
+ import { createWorkflowTool, type WorkflowToolDeps } from "../src/workflow/tool.ts";
12
+ import { UltracodeMode } from "../src/mode.ts";
13
+ import { registerCommands } from "../src/commands.ts";
14
+
15
+ export default function extension(pi: ExtensionAPI, extraDeps: Partial<WorkflowToolDeps> = {}): void {
16
+ const mode = new UltracodeMode("workflow");
17
+
18
+ const workflowTool = createWorkflowTool({
19
+ getDefaultBudget: () => mode.getBudget(),
20
+ getThinkingLevel: () => mode.getSubagentThinkingLevel(),
21
+ ...extraDeps,
22
+ });
23
+ pi.registerTool(workflowTool);
24
+
25
+ registerCommands(pi, mode);
26
+
27
+ // Opt-in via CLI flag: `pi --ultracode`.
28
+ pi.registerFlag("ultracode", {
29
+ type: "boolean",
30
+ description: "Start the session in ultracode mode (xhigh thinking + default workflow orchestration).",
31
+ });
32
+
33
+ pi.on("session_start", (_event, ctx) => {
34
+ // Restore persisted mode state across reload / resume / fork.
35
+ try {
36
+ mode.restore(pi, ctx.sessionManager.getEntries() as any);
37
+ } catch {
38
+ // ignore
39
+ }
40
+ if (!mode.isEnabled() && pi.getFlag?.("ultracode") === true) {
41
+ mode.enable(pi);
42
+ }
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
49
+ }
50
+ if (ctx.hasUI && mode.isEnabled()) ctx.ui.setStatus("ultracode", mode.statusLine());
51
+ });
52
+
53
+ pi.on("before_agent_start", (event) => {
54
+ return mode.beforeAgentStart(event);
55
+ });
56
+ }
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "pi-ultracode",
3
+ "version": "0.1.0",
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
+ "type": "module",
6
+ "keywords": [
7
+ "pi-package",
8
+ "pi",
9
+ "pi-coding-agent",
10
+ "ultracode",
11
+ "workflow",
12
+ "workflows",
13
+ "agents",
14
+ "subagents",
15
+ "orchestration"
16
+ ],
17
+ "author": "pi-ultracode",
18
+ "license": "MIT",
19
+ "files": [
20
+ "extensions/",
21
+ "src/",
22
+ "types/",
23
+ "examples/",
24
+ "README.md"
25
+ ],
26
+ "pi": {
27
+ "extensions": [
28
+ "./extensions/ultracode.ts"
29
+ ]
30
+ },
31
+ "exports": {
32
+ "./workflow": {
33
+ "types": "./types/workflow.d.ts"
34
+ }
35
+ },
36
+ "scripts": {
37
+ "test": "node --experimental-strip-types --test test/*.test.ts",
38
+ "typecheck": "tsc --noEmit"
39
+ },
40
+ "dependencies": {
41
+ "acorn": "^8.11.0"
42
+ },
43
+ "peerDependencies": {
44
+ "@earendil-works/pi-agent-core": "*",
45
+ "@earendil-works/pi-ai": "*",
46
+ "@earendil-works/pi-coding-agent": "*",
47
+ "@earendil-works/pi-tui": "*",
48
+ "typebox": "*"
49
+ },
50
+ "peerDependenciesMeta": {
51
+ "@earendil-works/pi-agent-core": { "optional": true },
52
+ "@earendil-works/pi-ai": { "optional": true },
53
+ "@earendil-works/pi-coding-agent": { "optional": true },
54
+ "@earendil-works/pi-tui": { "optional": true },
55
+ "typebox": { "optional": true }
56
+ }
57
+ }
@@ -0,0 +1,161 @@
1
+ /**
2
+ * Slash commands: `/ultracode` (mode toggle) and `/workflows` (run manager).
3
+ */
4
+
5
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
6
+ import { parseBudget, type UltracodeMode } from "./mode.ts";
7
+ import { getRegistry } from "./workflow/registry.ts";
8
+ import { renderWorkflowLines } from "./workflow/display.ts";
9
+
10
+ export function registerCommands(pi: ExtensionAPI, mode: UltracodeMode): void {
11
+ pi.registerCommand("ultracode", {
12
+ description: "Toggle ultracode mode (xhigh thinking + default workflow orchestration). Bare /ultracode toggles; subcommands: on|off|status|budget <n>",
13
+ getArgumentCompletions(prefix: string) {
14
+ return ["on", "off", "status", "budget"]
15
+ .filter((s) => s.startsWith(prefix))
16
+ .map((value) => ({ value, label: value }));
17
+ },
18
+ handler: async (args: string, ctx) => {
19
+ const parts = args.trim().split(/\s+/).filter(Boolean);
20
+ const sub = (parts[0] ?? "").toLowerCase();
21
+
22
+ // Bare `/ultracode` is a toggle.
23
+ if (sub === "") {
24
+ const nowOn = mode.toggle(pi);
25
+ ctx.ui.notify(nowOn ? `Ultracode on — ${mode.statusLine()}` : "Ultracode off — thinking restored.", "info");
26
+ ctx.ui.setStatus("ultracode", nowOn ? mode.statusLine() : undefined);
27
+ return;
28
+ }
29
+
30
+ if (sub === "status") {
31
+ ctx.ui.notify(mode.statusLine(), "info");
32
+ return;
33
+ }
34
+
35
+ if (sub === "off") {
36
+ mode.disable(pi);
37
+ ctx.ui.notify("Ultracode off — thinking restored, workflow orchestration is opt-in again.", "info");
38
+ ctx.ui.setStatus("ultracode", undefined);
39
+ return;
40
+ }
41
+
42
+ if (sub === "budget") {
43
+ const budget = parts[1] ? parseBudget(parts[1]) : null;
44
+ mode.setBudget(pi, budget);
45
+ ctx.ui.notify(
46
+ budget ? `Ultracode token budget set to ~${budget} output tokens.` : "Ultracode token budget cleared.",
47
+ "info",
48
+ );
49
+ ctx.ui.setStatus("ultracode", mode.statusLine());
50
+ return;
51
+ }
52
+
53
+ // "on", "on 500k", "500k", "+500k"
54
+ let budget: number | null | undefined;
55
+ const budgetToken = sub === "on" ? parts[1] : sub;
56
+ if (budgetToken) {
57
+ const parsed = parseBudget(budgetToken);
58
+ if (parsed) budget = parsed;
59
+ }
60
+ mode.enable(pi, budget !== undefined ? { budget } : {});
61
+ ctx.ui.notify(
62
+ `Ultracode on — ${mode.statusLine()}${budget ? ` (budget ~${budget} tokens)` : ""}`,
63
+ "info",
64
+ );
65
+ ctx.ui.setStatus("ultracode", mode.statusLine());
66
+ },
67
+ });
68
+
69
+ // Tracks whether the run panel is currently shown, so bare /workflows toggles it.
70
+ let panelVisible = false;
71
+
72
+ pi.registerCommand("workflows", {
73
+ description: "Toggle the workflow-run panel. Usage: /workflows [runId | clear | abort]",
74
+ getArgumentCompletions(prefix: string) {
75
+ return ["clear", "abort"]
76
+ .filter((s) => s.startsWith(prefix))
77
+ .map((value) => ({ value, label: value }));
78
+ },
79
+ handler: async (args: string, ctx) => {
80
+ const registry = getRegistry();
81
+ 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");
117
+ return;
118
+ }
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
+ );
144
+ },
145
+ });
146
+ }
147
+
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
+ }
161
+ }
package/src/index.ts ADDED
@@ -0,0 +1,55 @@
1
+ /** Public API of pi-ultracode, for reuse and testing. */
2
+
3
+ export { default } from "../extensions/ultracode.ts";
4
+ export { UltracodeMode, parseBudget, MODE_ENTRY_TYPE } from "./mode.ts";
5
+ export { registerCommands } from "./commands.ts";
6
+ export {
7
+ ULTRACODE_TAGLINE,
8
+ ULTRACODE_ACTIVE_REMINDER,
9
+ ultracodeSystemBlock,
10
+ WORKFLOW_GUIDELINES,
11
+ WORKFLOW_PROMPT_SNIPPET,
12
+ WORKFLOW_TOOL_DESCRIPTION,
13
+ } from "./prompts.ts";
14
+
15
+ export { createWorkflowTool, type WorkflowToolDeps } from "./workflow/tool.ts";
16
+ export { runWorkflow, type WorkflowRunOptions, type WorkflowRunResult } from "./workflow/runtime.ts";
17
+ export { parseWorkflowScript, normalizeScript, type WorkflowMeta, type WorkflowMetaPhase } from "./workflow/parser.ts";
18
+ export { jsonSchemaToTypeBox } from "./workflow/json-schema.ts";
19
+ export {
20
+ WorkflowAgentRunner,
21
+ resolveModelSelection,
22
+ matchModelIn,
23
+ type AgentRunCall,
24
+ type AgentRunResult,
25
+ type ThinkingLevel,
26
+ type ModelLike,
27
+ type ModelRegistryLike,
28
+ } from "./workflow/agent-runner.ts";
29
+ export { createStructuredOutputTool, type StructuredOutputCapture } from "./workflow/structured-output.ts";
30
+ export {
31
+ discoverAgentTypes,
32
+ resolveAgentType,
33
+ parseFrontmatter,
34
+ parseAgentTypeFile,
35
+ type AgentTypeDef,
36
+ } from "./workflow/agent-types.ts";
37
+ export { RunJournal, agentCallKey, hashString, stableStringify } from "./workflow/journal.ts";
38
+ export { getRegistry, WorkflowRegistry } from "./workflow/registry.ts";
39
+ export {
40
+ createSnapshot,
41
+ recompute,
42
+ renderWorkflowLines,
43
+ renderWorkflowText,
44
+ preview,
45
+ type WorkflowSnapshot,
46
+ } from "./workflow/display.ts";
47
+ export {
48
+ createWorktree,
49
+ captureWorktreeDiff,
50
+ removeWorktree,
51
+ applyPatch,
52
+ writeRescuePatch,
53
+ isGitRepo,
54
+ type Worktree,
55
+ } from "./workflow/worktree.ts";