@yycholla/pi-dynamic-workflows 3.3.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.
Files changed (179) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +325 -0
  3. package/assets/readme/hero.png +0 -0
  4. package/assets/readme/hero.svg +70 -0
  5. package/assets/readme/package-cover.png +0 -0
  6. package/assets/readme/package-cover.svg +66 -0
  7. package/assets/readme/workflow.png +0 -0
  8. package/assets/readme/workflow.svg +77 -0
  9. package/dist/accept-workflow-guidance.d.ts +14 -0
  10. package/dist/accept-workflow-guidance.js +53 -0
  11. package/dist/adversarial-review.d.ts +30 -0
  12. package/dist/adversarial-review.js +107 -0
  13. package/dist/agent-history.d.ts +20 -0
  14. package/dist/agent-history.js +122 -0
  15. package/dist/agent-registry.d.ts +83 -0
  16. package/dist/agent-registry.js +190 -0
  17. package/dist/agent.d.ts +364 -0
  18. package/dist/agent.js +714 -0
  19. package/dist/builtin-commands.d.ts +19 -0
  20. package/dist/builtin-commands.js +251 -0
  21. package/dist/builtin-workflows.d.ts +45 -0
  22. package/dist/builtin-workflows.js +121 -0
  23. package/dist/code-review.d.ts +26 -0
  24. package/dist/code-review.js +181 -0
  25. package/dist/config.d.ts +37 -0
  26. package/dist/config.js +44 -0
  27. package/dist/deep-research.d.ts +30 -0
  28. package/dist/deep-research.js +124 -0
  29. package/dist/display.d.ts +134 -0
  30. package/dist/display.js +248 -0
  31. package/dist/effort-command.d.ts +28 -0
  32. package/dist/effort-command.js +68 -0
  33. package/dist/enums.d.ts +69 -0
  34. package/dist/enums.js +78 -0
  35. package/dist/errors.d.ts +113 -0
  36. package/dist/errors.js +140 -0
  37. package/dist/extension-reload.d.ts +37 -0
  38. package/dist/extension-reload.js +78 -0
  39. package/dist/fs-persistence.d.ts +63 -0
  40. package/dist/fs-persistence.js +102 -0
  41. package/dist/index.d.ts +57 -0
  42. package/dist/index.js +35 -0
  43. package/dist/logger.d.ts +21 -0
  44. package/dist/logger.js +66 -0
  45. package/dist/model-routing.d.ts +30 -0
  46. package/dist/model-routing.js +50 -0
  47. package/dist/model-spec.d.ts +29 -0
  48. package/dist/model-spec.js +252 -0
  49. package/dist/model-tier-config.d.ts +133 -0
  50. package/dist/model-tier-config.js +249 -0
  51. package/dist/run-persistence.d.ts +180 -0
  52. package/dist/run-persistence.js +294 -0
  53. package/dist/saved-commands.d.ts +28 -0
  54. package/dist/saved-commands.js +100 -0
  55. package/dist/shared-store.d.ts +98 -0
  56. package/dist/shared-store.js +212 -0
  57. package/dist/structured-output.d.ts +19 -0
  58. package/dist/structured-output.js +30 -0
  59. package/dist/task-panel.d.ts +61 -0
  60. package/dist/task-panel.js +422 -0
  61. package/dist/usage-limit-scheduler.d.ts +145 -0
  62. package/dist/usage-limit-scheduler.js +368 -0
  63. package/dist/web-tools.d.ts +20 -0
  64. package/dist/web-tools.js +120 -0
  65. package/dist/workflow-authoring-coverage.d.ts +70 -0
  66. package/dist/workflow-authoring-coverage.js +421 -0
  67. package/dist/workflow-authoring-reference.d.ts +20 -0
  68. package/dist/workflow-authoring-reference.js +156 -0
  69. package/dist/workflow-capability-contract.d.ts +131 -0
  70. package/dist/workflow-capability-contract.js +604 -0
  71. package/dist/workflow-commands.d.ts +18 -0
  72. package/dist/workflow-commands.js +260 -0
  73. package/dist/workflow-comprehension.d.ts +133 -0
  74. package/dist/workflow-comprehension.js +1321 -0
  75. package/dist/workflow-context-measurement.d.ts +72 -0
  76. package/dist/workflow-context-measurement.js +213 -0
  77. package/dist/workflow-control-tool.d.ts +30 -0
  78. package/dist/workflow-control-tool.js +176 -0
  79. package/dist/workflow-delivery-choice.d.ts +20 -0
  80. package/dist/workflow-delivery-choice.js +48 -0
  81. package/dist/workflow-editor.d.ts +93 -0
  82. package/dist/workflow-editor.js +363 -0
  83. package/dist/workflow-manager.d.ts +492 -0
  84. package/dist/workflow-manager.js +1124 -0
  85. package/dist/workflow-paths.d.ts +22 -0
  86. package/dist/workflow-paths.js +46 -0
  87. package/dist/workflow-release-gate.d.ts +39 -0
  88. package/dist/workflow-release-gate.js +309 -0
  89. package/dist/workflow-saved.d.ts +38 -0
  90. package/dist/workflow-saved.js +126 -0
  91. package/dist/workflow-settings.d.ts +70 -0
  92. package/dist/workflow-settings.js +131 -0
  93. package/dist/workflow-tool.d.ts +71 -0
  94. package/dist/workflow-tool.js +367 -0
  95. package/dist/workflow-ui.d.ts +182 -0
  96. package/dist/workflow-ui.js +1587 -0
  97. package/dist/workflow.d.ts +333 -0
  98. package/dist/workflow.js +1151 -0
  99. package/dist/workflows-models-command.d.ts +31 -0
  100. package/dist/workflows-models-command.js +156 -0
  101. package/dist/worktree.d.ts +25 -0
  102. package/dist/worktree.js +61 -0
  103. package/extensions/workflow.ts +151 -0
  104. package/package.json +104 -0
  105. package/skills/workflow-authoring/SKILL.md +30 -0
  106. package/skills/workflow-authoring/examples/adversarial-verification.js +63 -0
  107. package/skills/workflow-authoring/examples/bounded-semantic-retry.js +56 -0
  108. package/skills/workflow-authoring/examples/classify-and-act.js +67 -0
  109. package/skills/workflow-authoring/examples/fan-out-and-synthesize.js +47 -0
  110. package/skills/workflow-authoring/examples/generate-and-filter.js +68 -0
  111. package/skills/workflow-authoring/examples/loop-until-done.js +68 -0
  112. package/skills/workflow-authoring/examples/phased-budgets.js +75 -0
  113. package/skills/workflow-authoring/examples/saved-nested-workflows.js +44 -0
  114. package/skills/workflow-authoring/examples/structured-output.js +37 -0
  115. package/skills/workflow-authoring/examples/tournament.js +81 -0
  116. package/skills/workflow-authoring/examples/validated-gate.js +63 -0
  117. package/skills/workflow-authoring/references/capabilities.md +41 -0
  118. package/skills/workflow-authoring/references/capability-details.md +357 -0
  119. package/skills/workflow-authoring/references/common-helpers.md +4 -0
  120. package/skills/workflow-authoring/references/debugging.md +27 -0
  121. package/skills/workflow-authoring/references/focused-recipes.md +11 -0
  122. package/skills/workflow-authoring/references/helpers.md +9 -0
  123. package/skills/workflow-authoring/references/lifecycle.md +33 -0
  124. package/skills/workflow-authoring/references/pattern-selection.md +15 -0
  125. package/skills/workflow-authoring/references/quality-helpers.md +8 -0
  126. package/skills/workflow-authoring/references/registry-ownership.md +15 -0
  127. package/skills/workflow-authoring/references/retry-helper.md +5 -0
  128. package/skills/workflow-authoring/references/review.md +43 -0
  129. package/skills/workflow-authoring/references/runtime.md +27 -0
  130. package/skills/workflow-authoring/references/specialized-helpers.md +19 -0
  131. package/skills/workflow-authoring/references/versions.md +13 -0
  132. package/skills/workflow-patterns/SKILL.md +51 -0
  133. package/src/accept-workflow-guidance.ts +71 -0
  134. package/src/adversarial-review.ts +120 -0
  135. package/src/agent-history.ts +157 -0
  136. package/src/agent-registry.ts +221 -0
  137. package/src/agent.ts +929 -0
  138. package/src/builtin-commands.ts +286 -0
  139. package/src/builtin-workflows.ts +155 -0
  140. package/src/code-review.ts +183 -0
  141. package/src/config.ts +55 -0
  142. package/src/deep-research.ts +135 -0
  143. package/src/display.ts +367 -0
  144. package/src/effort-command.ts +87 -0
  145. package/src/enums.ts +77 -0
  146. package/src/errors.ts +199 -0
  147. package/src/extension-reload.ts +100 -0
  148. package/src/fs-persistence.ts +124 -0
  149. package/src/index.ts +176 -0
  150. package/src/logger.ts +88 -0
  151. package/src/model-routing.ts +73 -0
  152. package/src/model-spec.ts +309 -0
  153. package/src/model-tier-config.ts +296 -0
  154. package/src/run-persistence.ts +484 -0
  155. package/src/saved-commands.ts +115 -0
  156. package/src/shared-store.ts +228 -0
  157. package/src/structured-output.ts +47 -0
  158. package/src/task-panel.ts +490 -0
  159. package/src/usage-limit-scheduler.ts +432 -0
  160. package/src/web-tools.ts +124 -0
  161. package/src/workflow-authoring-coverage.ts +486 -0
  162. package/src/workflow-authoring-reference.ts +186 -0
  163. package/src/workflow-capability-contract.ts +806 -0
  164. package/src/workflow-commands.ts +287 -0
  165. package/src/workflow-comprehension.ts +1673 -0
  166. package/src/workflow-context-measurement.ts +262 -0
  167. package/src/workflow-control-tool.ts +238 -0
  168. package/src/workflow-delivery-choice.ts +69 -0
  169. package/src/workflow-editor.ts +444 -0
  170. package/src/workflow-manager.ts +1405 -0
  171. package/src/workflow-paths.ts +63 -0
  172. package/src/workflow-release-gate.ts +529 -0
  173. package/src/workflow-saved.ts +180 -0
  174. package/src/workflow-settings.ts +194 -0
  175. package/src/workflow-tool.ts +464 -0
  176. package/src/workflow-ui.ts +1789 -0
  177. package/src/workflow.ts +1615 -0
  178. package/src/workflows-models-command.ts +211 -0
  179. package/src/worktree.ts +76 -0
@@ -0,0 +1,211 @@
1
+ /**
2
+ * `/workflows-models` command handler.
3
+ *
4
+ * Uses Pi's built-in `ctx.ui.select()`, `ctx.ui.confirm()`, and `ctx.ui.notify()`
5
+ * to let users view and manage model tier configuration for workflows.
6
+ *
7
+ * Model selection draws from the host session's shared model registry so users
8
+ * see every provider Pi can reach, including extension-registered providers such
9
+ * as `ollama-cloud`.
10
+ *
11
+ * Each tier holds exactly one model spec string. The string may include Pi
12
+ * CLI-style thinking suffixes, e.g. `openai-codex/gpt-5.5:xhigh`.
13
+ * When editing a tier, users pick a model, then an optional thinking level.
14
+ */
15
+
16
+ import type { ExtensionAPI, ExtensionCommandContext, Theme } from "@earendil-works/pi-coding-agent";
17
+ import {
18
+ Container,
19
+ type SelectItem,
20
+ SelectList,
21
+ type SelectListTheme,
22
+ Spacer,
23
+ Text,
24
+ type TUI,
25
+ } from "@earendil-works/pi-tui";
26
+ import { listAvailableModelSpecs, listAvailableModels } from "./agent.js";
27
+ import {
28
+ formatModelSpecWithThinking,
29
+ type ModelThinkingLevel,
30
+ splitModelSpecThinking,
31
+ THINKING_LEVELS,
32
+ } from "./model-spec.js";
33
+ import {
34
+ buildDefaultTierConfig,
35
+ loadModelTierConfig,
36
+ saveModelTierConfig,
37
+ sortedTierNames,
38
+ } from "./model-tier-config.js";
39
+
40
+ /**
41
+ * Register the `/workflows-models` command with Pi.
42
+ */
43
+ export function registerWorkflowModelsCommand(pi: ExtensionAPI): void {
44
+ pi.registerCommand("workflows-models", {
45
+ description: "View and edit model tiers used by workflows (small/medium/big)",
46
+ handler: async (_args, ctx) => {
47
+ await ctx.waitForIdle();
48
+
49
+ // Load the saved config, or build an in-memory default spread across the
50
+ // available models. If the model registry is empty, fall back to the
51
+ // current Pi model so the tiers are still usable. Pass the host session's
52
+ // registry explicitly: since pi 0.80.8 the no-registry fallback inside
53
+ // listAvailableModels() initializes asynchronously and reports [] on the
54
+ // first call, which would rank defaults from an empty model list.
55
+ const currentModel = ctx.model ? `${ctx.model.provider}/${ctx.model.id}` : undefined;
56
+ let config =
57
+ loadModelTierConfig() ?? buildDefaultTierConfig(currentModel, listAvailableModels(ctx.modelRegistry));
58
+ let dirty = false;
59
+
60
+ const ensureFresh = (cfg: typeof config) => {
61
+ config = cfg;
62
+ dirty = true;
63
+ };
64
+
65
+ // eslint-disable-next-line no-constant-condition
66
+ while (true) {
67
+ const tiers = sortedTierNames(config);
68
+ const menuOptions: string[] = [];
69
+
70
+ menuOptions.push("─".repeat(30));
71
+ for (const name of tiers) {
72
+ const model = config.tiers[name];
73
+ menuOptions.push(`${name} tier → ${model}`);
74
+ }
75
+ menuOptions.push("─".repeat(30));
76
+
77
+ menuOptions.push("Reset to defaults");
78
+ menuOptions.push(dirty ? "Save and exit" : "Exit");
79
+
80
+ const choice = await ctx.ui.select("Model tier configuration", menuOptions);
81
+
82
+ if (!choice) break;
83
+
84
+ // Handle "<tier> → [model]" selections
85
+ for (const name of tiers) {
86
+ if (choice.startsWith(`${name} tier →`)) {
87
+ const updatedTiers = await editSingleTier(ctx, config.tiers, name);
88
+ if (updatedTiers !== null) {
89
+ ensureFresh({ ...config, tiers: updatedTiers });
90
+ }
91
+ break;
92
+ }
93
+ }
94
+
95
+ if (choice === "Reset to defaults") {
96
+ const confirmed = await ctx.ui.confirm(
97
+ "Reset model tiers",
98
+ "This will reset tiers from your available model list. Continue?",
99
+ );
100
+ if (confirmed) {
101
+ ensureFresh(buildDefaultTierConfig(currentModel, listAvailableModels(ctx.modelRegistry)));
102
+ ctx.ui.notify("Tiers reset to defaults. Use 'Save and exit' to persist.", "info");
103
+ }
104
+ }
105
+
106
+ if (choice === "Save and exit" || choice === "Exit") {
107
+ if (choice === "Save and exit") {
108
+ saveModelTierConfig(config);
109
+ ctx.ui.notify("Model tiers saved.", "info");
110
+ }
111
+ break;
112
+ }
113
+ }
114
+ },
115
+ });
116
+ }
117
+
118
+ const DEFAULT_THINKING_CHOICE = "Default thinking (session setting)";
119
+ const THINKING_CHOICES = [DEFAULT_THINKING_CHOICE, ...THINKING_LEVELS] as const;
120
+
121
+ function fromThinkingChoice(choice: string | undefined): ModelThinkingLevel | undefined {
122
+ return THINKING_LEVELS.find((level) => level === choice);
123
+ }
124
+
125
+ /**
126
+ * Interactive editor for a single tier — scrollable model picker plus optional
127
+ * thinking-level picker.
128
+ *
129
+ * Uses `ctx.ui.custom()` with Pi TUI's `SelectList` for proper scrollable list
130
+ * with limited visible rows (like `/advisor`). The currently selected base
131
+ * model is shown in the dialog title. After choosing the model, users can set
132
+ * a Pi CLI-style thinking suffix or keep the session default.
133
+ *
134
+ * Returns the updated tiers object, or null if nothing changed.
135
+ */
136
+ export async function editSingleTier(
137
+ ctx: ExtensionCommandContext,
138
+ tiers: Record<string, string>,
139
+ tierName: string,
140
+ ): Promise<Record<string, string> | null> {
141
+ const available = listAvailableModelSpecs(ctx.modelRegistry);
142
+ const knownSpecs = available.length > 0 ? available : undefined;
143
+ const current = tiers[tierName];
144
+ const currentParts = splitModelSpecThinking(current, knownSpecs);
145
+
146
+ // Build SelectItems: all available models as scrollable list
147
+ const items: SelectItem[] = available.map((m) => ({ value: m, label: m }));
148
+
149
+ const selectedModel = await ctx.ui.custom<string | null>((tui: TUI, theme: Theme, _keybindings, done) => {
150
+ const container = new Container();
151
+
152
+ // Title showing current model
153
+ const titleText = current
154
+ ? `Pick a model for "${tierName}" (current: ${current})`
155
+ : `Pick a model for "${tierName}"`;
156
+ container.addChild(new Text(theme.fg("accent", titleText), 1, 0));
157
+ container.addChild(new Spacer(1));
158
+
159
+ // SelectList theme
160
+ const selectTheme: SelectListTheme = {
161
+ selectedPrefix: (t: string) => theme.bg("selectedBg", theme.fg("accent", t)),
162
+ selectedText: (t: string) => theme.bg("selectedBg", theme.bold(t)),
163
+ description: (t: string) => theme.fg("muted", t),
164
+ scrollInfo: (t: string) => theme.fg("dim", t),
165
+ noMatch: (t: string) => theme.fg("warning", t),
166
+ };
167
+
168
+ const selectList = new SelectList(items, 12, selectTheme);
169
+
170
+ // Preselect the current base model even when the stored tier has :thinking.
171
+ if (currentParts.modelSpec) {
172
+ const idx = items.findIndex((i) => i.value === currentParts.modelSpec);
173
+ if (idx >= 0) selectList.setSelectedIndex(idx);
174
+ }
175
+
176
+ // Wire up callbacks
177
+ selectList.onSelect = (item) => done(item.value);
178
+ selectList.onCancel = () => done(null);
179
+
180
+ container.addChild(selectList);
181
+ container.addChild(new Spacer(1));
182
+ container.addChild(
183
+ new Text(theme.fg("dim", "↑↓ navigate enter select esc cancel · thinking is chosen next"), 1, 0),
184
+ );
185
+
186
+ return {
187
+ render: (w: number) => container.render(w),
188
+ invalidate: () => container.invalidate(),
189
+ handleInput: (data: string) => {
190
+ selectList.handleInput(data);
191
+ tui.requestRender();
192
+ },
193
+ };
194
+ });
195
+
196
+ if (!selectedModel) return null;
197
+
198
+ const currentThinkingLabel = currentParts.thinkingLevel ?? DEFAULT_THINKING_CHOICE;
199
+ const thinkingChoice = await ctx.ui.select(
200
+ `Thinking for "${tierName}" tier (current: ${currentThinkingLabel})`,
201
+ THINKING_CHOICES.map((choice) => String(choice)),
202
+ );
203
+ if (!thinkingChoice) return null;
204
+
205
+ const thinkingLevel = fromThinkingChoice(thinkingChoice);
206
+ const result = formatModelSpecWithThinking(selectedModel, thinkingLevel);
207
+ if (result === current) return null;
208
+
209
+ ctx.ui.notify(`"${tierName}" tier → ${result}`, "info");
210
+ return { ...tiers, [tierName]: result };
211
+ }
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Per-agent git worktree isolation. When an agent requests `isolation: "worktree"`,
3
+ * it runs in a throwaway worktree on its own branch so parallel agents can edit the
4
+ * same files without conflict. Results are NOT auto-merged — the path is surfaced for
5
+ * the caller to inspect. Falls back to a logged no-op when isolation isn't possible.
6
+ */
7
+
8
+ import { execFile } from "node:child_process";
9
+ import { join } from "node:path";
10
+ import { promisify } from "node:util";
11
+
12
+ const exec = promisify(execFile);
13
+
14
+ export interface Worktree {
15
+ /** True when a real worktree was created; false means "ran in the shared tree". */
16
+ isolated: boolean;
17
+ /** cwd the agent should run in (worktree path when isolated, else the base cwd). */
18
+ cwd: string;
19
+ branch?: string;
20
+ /** Repo root the worktree was added to (for teardown). */
21
+ repoRoot?: string;
22
+ /** Why isolation was skipped, when isolated === false. */
23
+ reason?: string;
24
+ }
25
+
26
+ function slug(name: string): string {
27
+ return (
28
+ name
29
+ .toLowerCase()
30
+ .replace(/[^a-z0-9]+/g, "-")
31
+ .replace(/^-+|-+$/g, "")
32
+ .slice(0, 32) || "agent"
33
+ );
34
+ }
35
+
36
+ /**
37
+ * Create an isolated worktree under `<repoRoot>/.pi/worktrees/<name>` on branch
38
+ * `pi/wf/<name>`. The `name` must be deterministic (derived from runId + call index,
39
+ * never wall-clock) so resume keys stay stable. Returns a no-op Worktree on any failure.
40
+ */
41
+ export async function createWorktree(baseCwd: string, name: string): Promise<Worktree> {
42
+ const id = slug(name);
43
+ let repoRoot: string;
44
+ try {
45
+ const { stdout } = await exec("git", ["-C", baseCwd, "rev-parse", "--show-toplevel"]);
46
+ repoRoot = stdout.trim();
47
+ } catch {
48
+ return { isolated: false, cwd: baseCwd, reason: "not a git repository" };
49
+ }
50
+
51
+ const path = join(repoRoot, ".pi", "worktrees", id);
52
+ const branch = `pi/wf/${id}`;
53
+ try {
54
+ await exec("git", ["-C", repoRoot, "worktree", "add", "-b", branch, path, "HEAD"]);
55
+ return { isolated: true, cwd: path, branch, repoRoot };
56
+ } catch (error) {
57
+ return { isolated: false, cwd: baseCwd, reason: error instanceof Error ? error.message : String(error) };
58
+ }
59
+ }
60
+
61
+ /** Remove a worktree and its branch. Best-effort; safe to call on a no-op Worktree. */
62
+ export async function removeWorktree(wt: Worktree): Promise<void> {
63
+ if (!wt.isolated || !wt.repoRoot) return;
64
+ try {
65
+ await exec("git", ["-C", wt.repoRoot, "worktree", "remove", "--force", wt.cwd]);
66
+ } catch {
67
+ // already gone / locked — fall through
68
+ }
69
+ if (wt.branch) {
70
+ try {
71
+ await exec("git", ["-C", wt.repoRoot, "branch", "-D", wt.branch]);
72
+ } catch {
73
+ // branch already deleted
74
+ }
75
+ }
76
+ }