@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.
- package/LICENSE +22 -0
- package/README.md +325 -0
- package/assets/readme/hero.png +0 -0
- package/assets/readme/hero.svg +70 -0
- package/assets/readme/package-cover.png +0 -0
- package/assets/readme/package-cover.svg +66 -0
- package/assets/readme/workflow.png +0 -0
- package/assets/readme/workflow.svg +77 -0
- package/dist/accept-workflow-guidance.d.ts +14 -0
- package/dist/accept-workflow-guidance.js +53 -0
- package/dist/adversarial-review.d.ts +30 -0
- package/dist/adversarial-review.js +107 -0
- package/dist/agent-history.d.ts +20 -0
- package/dist/agent-history.js +122 -0
- package/dist/agent-registry.d.ts +83 -0
- package/dist/agent-registry.js +190 -0
- package/dist/agent.d.ts +364 -0
- package/dist/agent.js +714 -0
- package/dist/builtin-commands.d.ts +19 -0
- package/dist/builtin-commands.js +251 -0
- package/dist/builtin-workflows.d.ts +45 -0
- package/dist/builtin-workflows.js +121 -0
- package/dist/code-review.d.ts +26 -0
- package/dist/code-review.js +181 -0
- package/dist/config.d.ts +37 -0
- package/dist/config.js +44 -0
- package/dist/deep-research.d.ts +30 -0
- package/dist/deep-research.js +124 -0
- package/dist/display.d.ts +134 -0
- package/dist/display.js +248 -0
- package/dist/effort-command.d.ts +28 -0
- package/dist/effort-command.js +68 -0
- package/dist/enums.d.ts +69 -0
- package/dist/enums.js +78 -0
- package/dist/errors.d.ts +113 -0
- package/dist/errors.js +140 -0
- package/dist/extension-reload.d.ts +37 -0
- package/dist/extension-reload.js +78 -0
- package/dist/fs-persistence.d.ts +63 -0
- package/dist/fs-persistence.js +102 -0
- package/dist/index.d.ts +57 -0
- package/dist/index.js +35 -0
- package/dist/logger.d.ts +21 -0
- package/dist/logger.js +66 -0
- package/dist/model-routing.d.ts +30 -0
- package/dist/model-routing.js +50 -0
- package/dist/model-spec.d.ts +29 -0
- package/dist/model-spec.js +252 -0
- package/dist/model-tier-config.d.ts +133 -0
- package/dist/model-tier-config.js +249 -0
- package/dist/run-persistence.d.ts +180 -0
- package/dist/run-persistence.js +294 -0
- package/dist/saved-commands.d.ts +28 -0
- package/dist/saved-commands.js +100 -0
- package/dist/shared-store.d.ts +98 -0
- package/dist/shared-store.js +212 -0
- package/dist/structured-output.d.ts +19 -0
- package/dist/structured-output.js +30 -0
- package/dist/task-panel.d.ts +61 -0
- package/dist/task-panel.js +422 -0
- package/dist/usage-limit-scheduler.d.ts +145 -0
- package/dist/usage-limit-scheduler.js +368 -0
- package/dist/web-tools.d.ts +20 -0
- package/dist/web-tools.js +120 -0
- package/dist/workflow-authoring-coverage.d.ts +70 -0
- package/dist/workflow-authoring-coverage.js +421 -0
- package/dist/workflow-authoring-reference.d.ts +20 -0
- package/dist/workflow-authoring-reference.js +156 -0
- package/dist/workflow-capability-contract.d.ts +131 -0
- package/dist/workflow-capability-contract.js +604 -0
- package/dist/workflow-commands.d.ts +18 -0
- package/dist/workflow-commands.js +260 -0
- package/dist/workflow-comprehension.d.ts +133 -0
- package/dist/workflow-comprehension.js +1321 -0
- package/dist/workflow-context-measurement.d.ts +72 -0
- package/dist/workflow-context-measurement.js +213 -0
- package/dist/workflow-control-tool.d.ts +30 -0
- package/dist/workflow-control-tool.js +176 -0
- package/dist/workflow-delivery-choice.d.ts +20 -0
- package/dist/workflow-delivery-choice.js +48 -0
- package/dist/workflow-editor.d.ts +93 -0
- package/dist/workflow-editor.js +363 -0
- package/dist/workflow-manager.d.ts +492 -0
- package/dist/workflow-manager.js +1124 -0
- package/dist/workflow-paths.d.ts +22 -0
- package/dist/workflow-paths.js +46 -0
- package/dist/workflow-release-gate.d.ts +39 -0
- package/dist/workflow-release-gate.js +309 -0
- package/dist/workflow-saved.d.ts +38 -0
- package/dist/workflow-saved.js +126 -0
- package/dist/workflow-settings.d.ts +70 -0
- package/dist/workflow-settings.js +131 -0
- package/dist/workflow-tool.d.ts +71 -0
- package/dist/workflow-tool.js +367 -0
- package/dist/workflow-ui.d.ts +182 -0
- package/dist/workflow-ui.js +1587 -0
- package/dist/workflow.d.ts +333 -0
- package/dist/workflow.js +1151 -0
- package/dist/workflows-models-command.d.ts +31 -0
- package/dist/workflows-models-command.js +156 -0
- package/dist/worktree.d.ts +25 -0
- package/dist/worktree.js +61 -0
- package/extensions/workflow.ts +151 -0
- package/package.json +104 -0
- package/skills/workflow-authoring/SKILL.md +30 -0
- package/skills/workflow-authoring/examples/adversarial-verification.js +63 -0
- package/skills/workflow-authoring/examples/bounded-semantic-retry.js +56 -0
- package/skills/workflow-authoring/examples/classify-and-act.js +67 -0
- package/skills/workflow-authoring/examples/fan-out-and-synthesize.js +47 -0
- package/skills/workflow-authoring/examples/generate-and-filter.js +68 -0
- package/skills/workflow-authoring/examples/loop-until-done.js +68 -0
- package/skills/workflow-authoring/examples/phased-budgets.js +75 -0
- package/skills/workflow-authoring/examples/saved-nested-workflows.js +44 -0
- package/skills/workflow-authoring/examples/structured-output.js +37 -0
- package/skills/workflow-authoring/examples/tournament.js +81 -0
- package/skills/workflow-authoring/examples/validated-gate.js +63 -0
- package/skills/workflow-authoring/references/capabilities.md +41 -0
- package/skills/workflow-authoring/references/capability-details.md +357 -0
- package/skills/workflow-authoring/references/common-helpers.md +4 -0
- package/skills/workflow-authoring/references/debugging.md +27 -0
- package/skills/workflow-authoring/references/focused-recipes.md +11 -0
- package/skills/workflow-authoring/references/helpers.md +9 -0
- package/skills/workflow-authoring/references/lifecycle.md +33 -0
- package/skills/workflow-authoring/references/pattern-selection.md +15 -0
- package/skills/workflow-authoring/references/quality-helpers.md +8 -0
- package/skills/workflow-authoring/references/registry-ownership.md +15 -0
- package/skills/workflow-authoring/references/retry-helper.md +5 -0
- package/skills/workflow-authoring/references/review.md +43 -0
- package/skills/workflow-authoring/references/runtime.md +27 -0
- package/skills/workflow-authoring/references/specialized-helpers.md +19 -0
- package/skills/workflow-authoring/references/versions.md +13 -0
- package/skills/workflow-patterns/SKILL.md +51 -0
- package/src/accept-workflow-guidance.ts +71 -0
- package/src/adversarial-review.ts +120 -0
- package/src/agent-history.ts +157 -0
- package/src/agent-registry.ts +221 -0
- package/src/agent.ts +929 -0
- package/src/builtin-commands.ts +286 -0
- package/src/builtin-workflows.ts +155 -0
- package/src/code-review.ts +183 -0
- package/src/config.ts +55 -0
- package/src/deep-research.ts +135 -0
- package/src/display.ts +367 -0
- package/src/effort-command.ts +87 -0
- package/src/enums.ts +77 -0
- package/src/errors.ts +199 -0
- package/src/extension-reload.ts +100 -0
- package/src/fs-persistence.ts +124 -0
- package/src/index.ts +176 -0
- package/src/logger.ts +88 -0
- package/src/model-routing.ts +73 -0
- package/src/model-spec.ts +309 -0
- package/src/model-tier-config.ts +296 -0
- package/src/run-persistence.ts +484 -0
- package/src/saved-commands.ts +115 -0
- package/src/shared-store.ts +228 -0
- package/src/structured-output.ts +47 -0
- package/src/task-panel.ts +490 -0
- package/src/usage-limit-scheduler.ts +432 -0
- package/src/web-tools.ts +124 -0
- package/src/workflow-authoring-coverage.ts +486 -0
- package/src/workflow-authoring-reference.ts +186 -0
- package/src/workflow-capability-contract.ts +806 -0
- package/src/workflow-commands.ts +287 -0
- package/src/workflow-comprehension.ts +1673 -0
- package/src/workflow-context-measurement.ts +262 -0
- package/src/workflow-control-tool.ts +238 -0
- package/src/workflow-delivery-choice.ts +69 -0
- package/src/workflow-editor.ts +444 -0
- package/src/workflow-manager.ts +1405 -0
- package/src/workflow-paths.ts +63 -0
- package/src/workflow-release-gate.ts +529 -0
- package/src/workflow-saved.ts +180 -0
- package/src/workflow-settings.ts +194 -0
- package/src/workflow-tool.ts +464 -0
- package/src/workflow-ui.ts +1789 -0
- package/src/workflow.ts +1615 -0
- package/src/workflows-models-command.ts +211 -0
- package/src/worktree.ts +76 -0
|
@@ -0,0 +1,31 @@
|
|
|
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
|
+
import type { ExtensionAPI, ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
|
|
16
|
+
/**
|
|
17
|
+
* Register the `/workflows-models` command with Pi.
|
|
18
|
+
*/
|
|
19
|
+
export declare function registerWorkflowModelsCommand(pi: ExtensionAPI): void;
|
|
20
|
+
/**
|
|
21
|
+
* Interactive editor for a single tier — scrollable model picker plus optional
|
|
22
|
+
* thinking-level picker.
|
|
23
|
+
*
|
|
24
|
+
* Uses `ctx.ui.custom()` with Pi TUI's `SelectList` for proper scrollable list
|
|
25
|
+
* with limited visible rows (like `/advisor`). The currently selected base
|
|
26
|
+
* model is shown in the dialog title. After choosing the model, users can set
|
|
27
|
+
* a Pi CLI-style thinking suffix or keep the session default.
|
|
28
|
+
*
|
|
29
|
+
* Returns the updated tiers object, or null if nothing changed.
|
|
30
|
+
*/
|
|
31
|
+
export declare function editSingleTier(ctx: ExtensionCommandContext, tiers: Record<string, string>, tierName: string): Promise<Record<string, string> | null>;
|
|
@@ -0,0 +1,156 @@
|
|
|
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
|
+
import { Container, SelectList, Spacer, Text, } from "@earendil-works/pi-tui";
|
|
16
|
+
import { listAvailableModelSpecs, listAvailableModels } from "./agent.js";
|
|
17
|
+
import { formatModelSpecWithThinking, splitModelSpecThinking, THINKING_LEVELS, } from "./model-spec.js";
|
|
18
|
+
import { buildDefaultTierConfig, loadModelTierConfig, saveModelTierConfig, sortedTierNames, } from "./model-tier-config.js";
|
|
19
|
+
/**
|
|
20
|
+
* Register the `/workflows-models` command with Pi.
|
|
21
|
+
*/
|
|
22
|
+
export function registerWorkflowModelsCommand(pi) {
|
|
23
|
+
pi.registerCommand("workflows-models", {
|
|
24
|
+
description: "View and edit model tiers used by workflows (small/medium/big)",
|
|
25
|
+
handler: async (_args, ctx) => {
|
|
26
|
+
await ctx.waitForIdle();
|
|
27
|
+
// Load the saved config, or build an in-memory default spread across the
|
|
28
|
+
// available models. If the model registry is empty, fall back to the
|
|
29
|
+
// current Pi model so the tiers are still usable. Pass the host session's
|
|
30
|
+
// registry explicitly: since pi 0.80.8 the no-registry fallback inside
|
|
31
|
+
// listAvailableModels() initializes asynchronously and reports [] on the
|
|
32
|
+
// first call, which would rank defaults from an empty model list.
|
|
33
|
+
const currentModel = ctx.model ? `${ctx.model.provider}/${ctx.model.id}` : undefined;
|
|
34
|
+
let config = loadModelTierConfig() ?? buildDefaultTierConfig(currentModel, listAvailableModels(ctx.modelRegistry));
|
|
35
|
+
let dirty = false;
|
|
36
|
+
const ensureFresh = (cfg) => {
|
|
37
|
+
config = cfg;
|
|
38
|
+
dirty = true;
|
|
39
|
+
};
|
|
40
|
+
// eslint-disable-next-line no-constant-condition
|
|
41
|
+
while (true) {
|
|
42
|
+
const tiers = sortedTierNames(config);
|
|
43
|
+
const menuOptions = [];
|
|
44
|
+
menuOptions.push("─".repeat(30));
|
|
45
|
+
for (const name of tiers) {
|
|
46
|
+
const model = config.tiers[name];
|
|
47
|
+
menuOptions.push(`${name} tier → ${model}`);
|
|
48
|
+
}
|
|
49
|
+
menuOptions.push("─".repeat(30));
|
|
50
|
+
menuOptions.push("Reset to defaults");
|
|
51
|
+
menuOptions.push(dirty ? "Save and exit" : "Exit");
|
|
52
|
+
const choice = await ctx.ui.select("Model tier configuration", menuOptions);
|
|
53
|
+
if (!choice)
|
|
54
|
+
break;
|
|
55
|
+
// Handle "<tier> → [model]" selections
|
|
56
|
+
for (const name of tiers) {
|
|
57
|
+
if (choice.startsWith(`${name} tier →`)) {
|
|
58
|
+
const updatedTiers = await editSingleTier(ctx, config.tiers, name);
|
|
59
|
+
if (updatedTiers !== null) {
|
|
60
|
+
ensureFresh({ ...config, tiers: updatedTiers });
|
|
61
|
+
}
|
|
62
|
+
break;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
if (choice === "Reset to defaults") {
|
|
66
|
+
const confirmed = await ctx.ui.confirm("Reset model tiers", "This will reset tiers from your available model list. Continue?");
|
|
67
|
+
if (confirmed) {
|
|
68
|
+
ensureFresh(buildDefaultTierConfig(currentModel, listAvailableModels(ctx.modelRegistry)));
|
|
69
|
+
ctx.ui.notify("Tiers reset to defaults. Use 'Save and exit' to persist.", "info");
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
if (choice === "Save and exit" || choice === "Exit") {
|
|
73
|
+
if (choice === "Save and exit") {
|
|
74
|
+
saveModelTierConfig(config);
|
|
75
|
+
ctx.ui.notify("Model tiers saved.", "info");
|
|
76
|
+
}
|
|
77
|
+
break;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
const DEFAULT_THINKING_CHOICE = "Default thinking (session setting)";
|
|
84
|
+
const THINKING_CHOICES = [DEFAULT_THINKING_CHOICE, ...THINKING_LEVELS];
|
|
85
|
+
function fromThinkingChoice(choice) {
|
|
86
|
+
return THINKING_LEVELS.find((level) => level === choice);
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Interactive editor for a single tier — scrollable model picker plus optional
|
|
90
|
+
* thinking-level picker.
|
|
91
|
+
*
|
|
92
|
+
* Uses `ctx.ui.custom()` with Pi TUI's `SelectList` for proper scrollable list
|
|
93
|
+
* with limited visible rows (like `/advisor`). The currently selected base
|
|
94
|
+
* model is shown in the dialog title. After choosing the model, users can set
|
|
95
|
+
* a Pi CLI-style thinking suffix or keep the session default.
|
|
96
|
+
*
|
|
97
|
+
* Returns the updated tiers object, or null if nothing changed.
|
|
98
|
+
*/
|
|
99
|
+
export async function editSingleTier(ctx, tiers, tierName) {
|
|
100
|
+
const available = listAvailableModelSpecs(ctx.modelRegistry);
|
|
101
|
+
const knownSpecs = available.length > 0 ? available : undefined;
|
|
102
|
+
const current = tiers[tierName];
|
|
103
|
+
const currentParts = splitModelSpecThinking(current, knownSpecs);
|
|
104
|
+
// Build SelectItems: all available models as scrollable list
|
|
105
|
+
const items = available.map((m) => ({ value: m, label: m }));
|
|
106
|
+
const selectedModel = await ctx.ui.custom((tui, theme, _keybindings, done) => {
|
|
107
|
+
const container = new Container();
|
|
108
|
+
// Title showing current model
|
|
109
|
+
const titleText = current
|
|
110
|
+
? `Pick a model for "${tierName}" (current: ${current})`
|
|
111
|
+
: `Pick a model for "${tierName}"`;
|
|
112
|
+
container.addChild(new Text(theme.fg("accent", titleText), 1, 0));
|
|
113
|
+
container.addChild(new Spacer(1));
|
|
114
|
+
// SelectList theme
|
|
115
|
+
const selectTheme = {
|
|
116
|
+
selectedPrefix: (t) => theme.bg("selectedBg", theme.fg("accent", t)),
|
|
117
|
+
selectedText: (t) => theme.bg("selectedBg", theme.bold(t)),
|
|
118
|
+
description: (t) => theme.fg("muted", t),
|
|
119
|
+
scrollInfo: (t) => theme.fg("dim", t),
|
|
120
|
+
noMatch: (t) => theme.fg("warning", t),
|
|
121
|
+
};
|
|
122
|
+
const selectList = new SelectList(items, 12, selectTheme);
|
|
123
|
+
// Preselect the current base model even when the stored tier has :thinking.
|
|
124
|
+
if (currentParts.modelSpec) {
|
|
125
|
+
const idx = items.findIndex((i) => i.value === currentParts.modelSpec);
|
|
126
|
+
if (idx >= 0)
|
|
127
|
+
selectList.setSelectedIndex(idx);
|
|
128
|
+
}
|
|
129
|
+
// Wire up callbacks
|
|
130
|
+
selectList.onSelect = (item) => done(item.value);
|
|
131
|
+
selectList.onCancel = () => done(null);
|
|
132
|
+
container.addChild(selectList);
|
|
133
|
+
container.addChild(new Spacer(1));
|
|
134
|
+
container.addChild(new Text(theme.fg("dim", "↑↓ navigate enter select esc cancel · thinking is chosen next"), 1, 0));
|
|
135
|
+
return {
|
|
136
|
+
render: (w) => container.render(w),
|
|
137
|
+
invalidate: () => container.invalidate(),
|
|
138
|
+
handleInput: (data) => {
|
|
139
|
+
selectList.handleInput(data);
|
|
140
|
+
tui.requestRender();
|
|
141
|
+
},
|
|
142
|
+
};
|
|
143
|
+
});
|
|
144
|
+
if (!selectedModel)
|
|
145
|
+
return null;
|
|
146
|
+
const currentThinkingLabel = currentParts.thinkingLevel ?? DEFAULT_THINKING_CHOICE;
|
|
147
|
+
const thinkingChoice = await ctx.ui.select(`Thinking for "${tierName}" tier (current: ${currentThinkingLabel})`, THINKING_CHOICES.map((choice) => String(choice)));
|
|
148
|
+
if (!thinkingChoice)
|
|
149
|
+
return null;
|
|
150
|
+
const thinkingLevel = fromThinkingChoice(thinkingChoice);
|
|
151
|
+
const result = formatModelSpecWithThinking(selectedModel, thinkingLevel);
|
|
152
|
+
if (result === current)
|
|
153
|
+
return null;
|
|
154
|
+
ctx.ui.notify(`"${tierName}" tier → ${result}`, "info");
|
|
155
|
+
return { ...tiers, [tierName]: result };
|
|
156
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
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
|
+
export interface Worktree {
|
|
8
|
+
/** True when a real worktree was created; false means "ran in the shared tree". */
|
|
9
|
+
isolated: boolean;
|
|
10
|
+
/** cwd the agent should run in (worktree path when isolated, else the base cwd). */
|
|
11
|
+
cwd: string;
|
|
12
|
+
branch?: string;
|
|
13
|
+
/** Repo root the worktree was added to (for teardown). */
|
|
14
|
+
repoRoot?: string;
|
|
15
|
+
/** Why isolation was skipped, when isolated === false. */
|
|
16
|
+
reason?: string;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Create an isolated worktree under `<repoRoot>/.pi/worktrees/<name>` on branch
|
|
20
|
+
* `pi/wf/<name>`. The `name` must be deterministic (derived from runId + call index,
|
|
21
|
+
* never wall-clock) so resume keys stay stable. Returns a no-op Worktree on any failure.
|
|
22
|
+
*/
|
|
23
|
+
export declare function createWorktree(baseCwd: string, name: string): Promise<Worktree>;
|
|
24
|
+
/** Remove a worktree and its branch. Best-effort; safe to call on a no-op Worktree. */
|
|
25
|
+
export declare function removeWorktree(wt: Worktree): Promise<void>;
|
package/dist/worktree.js
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
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
|
+
import { execFile } from "node:child_process";
|
|
8
|
+
import { join } from "node:path";
|
|
9
|
+
import { promisify } from "node:util";
|
|
10
|
+
const exec = promisify(execFile);
|
|
11
|
+
function slug(name) {
|
|
12
|
+
return (name
|
|
13
|
+
.toLowerCase()
|
|
14
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
15
|
+
.replace(/^-+|-+$/g, "")
|
|
16
|
+
.slice(0, 32) || "agent");
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Create an isolated worktree under `<repoRoot>/.pi/worktrees/<name>` on branch
|
|
20
|
+
* `pi/wf/<name>`. The `name` must be deterministic (derived from runId + call index,
|
|
21
|
+
* never wall-clock) so resume keys stay stable. Returns a no-op Worktree on any failure.
|
|
22
|
+
*/
|
|
23
|
+
export async function createWorktree(baseCwd, name) {
|
|
24
|
+
const id = slug(name);
|
|
25
|
+
let repoRoot;
|
|
26
|
+
try {
|
|
27
|
+
const { stdout } = await exec("git", ["-C", baseCwd, "rev-parse", "--show-toplevel"]);
|
|
28
|
+
repoRoot = stdout.trim();
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return { isolated: false, cwd: baseCwd, reason: "not a git repository" };
|
|
32
|
+
}
|
|
33
|
+
const path = join(repoRoot, ".pi", "worktrees", id);
|
|
34
|
+
const branch = `pi/wf/${id}`;
|
|
35
|
+
try {
|
|
36
|
+
await exec("git", ["-C", repoRoot, "worktree", "add", "-b", branch, path, "HEAD"]);
|
|
37
|
+
return { isolated: true, cwd: path, branch, repoRoot };
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
return { isolated: false, cwd: baseCwd, reason: error instanceof Error ? error.message : String(error) };
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/** Remove a worktree and its branch. Best-effort; safe to call on a no-op Worktree. */
|
|
44
|
+
export async function removeWorktree(wt) {
|
|
45
|
+
if (!wt.isolated || !wt.repoRoot)
|
|
46
|
+
return;
|
|
47
|
+
try {
|
|
48
|
+
await exec("git", ["-C", wt.repoRoot, "worktree", "remove", "--force", wt.cwd]);
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
// already gone / locked — fall through
|
|
52
|
+
}
|
|
53
|
+
if (wt.branch) {
|
|
54
|
+
try {
|
|
55
|
+
await exec("git", ["-C", wt.repoRoot, "branch", "-D", wt.branch]);
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
// branch already deleted
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { createCodingTools, type ExtensionAPI, type ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import {
|
|
3
|
+
claimWorkflowRuntime,
|
|
4
|
+
discardWorkflowRuntime,
|
|
5
|
+
handoffWorkflowRuntime,
|
|
6
|
+
pauseVersionMismatchWorkflowRuntime,
|
|
7
|
+
WORKFLOW_EXTENSION_VERSION,
|
|
8
|
+
type WorkflowReloadRuntime,
|
|
9
|
+
} from "../src/extension-reload.js";
|
|
10
|
+
import {
|
|
11
|
+
createEffortState,
|
|
12
|
+
createWebTools,
|
|
13
|
+
createWorkflowControlTool,
|
|
14
|
+
createWorkflowStorage,
|
|
15
|
+
createWorkflowTool,
|
|
16
|
+
installResultDelivery,
|
|
17
|
+
installTaskPanel,
|
|
18
|
+
installWorkflowKeywordArming,
|
|
19
|
+
loadWorkflowSettings,
|
|
20
|
+
registerAllSavedWorkflows,
|
|
21
|
+
registerBuiltinWorkflows,
|
|
22
|
+
registerEffortCommand,
|
|
23
|
+
registerWorkflowCommands,
|
|
24
|
+
registerWorkflowModelsCommand,
|
|
25
|
+
saveWorkflowSettingsForCwd,
|
|
26
|
+
UsageLimitScheduler,
|
|
27
|
+
WorkflowManager,
|
|
28
|
+
} from "../src/index.js";
|
|
29
|
+
|
|
30
|
+
export default function extension(pi: ExtensionAPI) {
|
|
31
|
+
// Single manager shared by the workflow tool and /workflows command. Pi loads
|
|
32
|
+
// a fresh extension factory for /reload, so explicitly claim the old live
|
|
33
|
+
// manager when session_shutdown staged one; otherwise in-flight promises,
|
|
34
|
+
// controls, event delivery, and live UI updates would stay on an unreachable
|
|
35
|
+
// manager even though their persisted snapshots remained visible on disk.
|
|
36
|
+
const cwd = process.cwd();
|
|
37
|
+
const storage = createWorkflowStorage(cwd);
|
|
38
|
+
const settings = loadWorkflowSettings({ cwd });
|
|
39
|
+
const managerOptions = {
|
|
40
|
+
loadSavedWorkflow: (name: string) => storage.load(name)?.script,
|
|
41
|
+
// Named toolsets survive on the persisted run (the tag, not the functions),
|
|
42
|
+
// so a resumed run re-resolves the tools it started with — e.g. a paused
|
|
43
|
+
// /deep-research keeps web access instead of degrading to coding tools.
|
|
44
|
+
toolsets: {
|
|
45
|
+
"web-research": () => [...createCodingTools(cwd), ...createWebTools()],
|
|
46
|
+
},
|
|
47
|
+
// On top of the always-on workflow/workflow_control denial in subagents
|
|
48
|
+
// (#107), let users block additional recursive-orchestration tools.
|
|
49
|
+
excludeSubagentTools: settings.excludeSubagentTools,
|
|
50
|
+
defaultAgentTimeoutMs: settings.defaultAgentTimeoutMs ?? null,
|
|
51
|
+
defaultTokenBudget: settings.defaultTokenBudget ?? null,
|
|
52
|
+
concurrency: settings.defaultConcurrency,
|
|
53
|
+
defaultAgentRetries: settings.defaultAgentRetries,
|
|
54
|
+
persistAgentSessions: settings.persistAgentSessions,
|
|
55
|
+
};
|
|
56
|
+
const runtimeClaim = claimWorkflowRuntime(cwd);
|
|
57
|
+
const previousRuntime = runtimeClaim.compatible;
|
|
58
|
+
const pausedForVersionChange = runtimeClaim.versionMismatch
|
|
59
|
+
? pauseVersionMismatchWorkflowRuntime(runtimeClaim.versionMismatch)
|
|
60
|
+
: 0;
|
|
61
|
+
const manager = previousRuntime?.manager ?? new WorkflowManager({ cwd, ...managerOptions });
|
|
62
|
+
if (previousRuntime) manager.reconfigureAfterReload(managerOptions);
|
|
63
|
+
// /effort is independent of the manager implementation and can safely
|
|
64
|
+
// survive an extension-version fallback to a fresh manager.
|
|
65
|
+
const effort = (previousRuntime ?? runtimeClaim.versionMismatch)?.effort ?? createEffortState();
|
|
66
|
+
const runtime: WorkflowReloadRuntime = {
|
|
67
|
+
cwd,
|
|
68
|
+
extensionVersion: WORKFLOW_EXTENSION_VERSION,
|
|
69
|
+
manager,
|
|
70
|
+
effort,
|
|
71
|
+
};
|
|
72
|
+
// Refresh the delivery holder immediately after claiming the manager. On a
|
|
73
|
+
// reload handoff its listener survives, but Pi invalidates the old ExtensionAPI
|
|
74
|
+
// before loading this generation.
|
|
75
|
+
installResultDelivery(pi, manager, { loadSettings: () => loadWorkflowSettings({ cwd }) });
|
|
76
|
+
|
|
77
|
+
const workflowTool = createWorkflowTool({ cwd, manager, storage });
|
|
78
|
+
const workflowControlTool = createWorkflowControlTool({ manager });
|
|
79
|
+
pi.registerTool(workflowTool);
|
|
80
|
+
pi.registerTool(workflowControlTool);
|
|
81
|
+
// Auto-resume runs that paused on a provider usage limit once the quota is
|
|
82
|
+
// likely refilled. Standalone: only consumes the manager's public surface, so
|
|
83
|
+
// it stays decoupled from manager/persistence internals. Its constructor also
|
|
84
|
+
// re-arms any run that was already paused-on-usage_limit before this process
|
|
85
|
+
// started (cold start), so restarting pi doesn't strand a paused run.
|
|
86
|
+
const usageLimitScheduler = new UsageLimitScheduler(manager);
|
|
87
|
+
pi.on("session_shutdown", (event?: { reason?: string }) => {
|
|
88
|
+
usageLimitScheduler.dispose();
|
|
89
|
+
if (event?.reason === "reload") {
|
|
90
|
+
handoffWorkflowRuntime(runtime);
|
|
91
|
+
} else {
|
|
92
|
+
discardWorkflowRuntime(cwd, runtime);
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
// Standing /effort opt-in (off|high|ultra): auto-arms a workflow for substantive
|
|
96
|
+
// messages, like CC's ultracode. Shared with the editor's input hook below and
|
|
97
|
+
// with the explicit /workflows run <prompt> manual trigger. It is part of the
|
|
98
|
+
// reload handoff so /reload does not silently turn the selected effort off.
|
|
99
|
+
registerWorkflowCommands(pi, manager, { storage, cwd, effort });
|
|
100
|
+
registerWorkflowModelsCommand(pi);
|
|
101
|
+
registerBuiltinWorkflows(pi, { cwd, manager, storage });
|
|
102
|
+
registerAllSavedWorkflows(pi, cwd, storage, manager);
|
|
103
|
+
registerEffortCommand(pi, effort);
|
|
104
|
+
// "Workflows mode": type `workflow(s)` to arm a forced workflow at submit
|
|
105
|
+
// time. Installed once (guarded below) inside session_start alongside the
|
|
106
|
+
// other per-session installers.
|
|
107
|
+
let armingInstalled = false;
|
|
108
|
+
|
|
109
|
+
pi.on("session_start", (_event: unknown, ctx: ExtensionContext) => {
|
|
110
|
+
if (pausedForVersionChange > 0) {
|
|
111
|
+
ctx.ui.notify(
|
|
112
|
+
`Workflow extension updated during /reload; paused ${pausedForVersionChange} active workflow(s) for safe resume.`,
|
|
113
|
+
"warning",
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
// Tell the manager the session's main model so "explore" agents auto-tier
|
|
117
|
+
// down to a lighter same-family sibling (e.g. Claude → Haiku).
|
|
118
|
+
manager.setMainModel(ctx.model ? `${ctx.model.provider}/${ctx.model.id}` : undefined);
|
|
119
|
+
// Share the host session's model registry so tier/phase routing resolves
|
|
120
|
+
// extension-registered providers (e.g. ollama-cloud) consistently. Set it
|
|
121
|
+
// before activating the tool: the tool's promptGuidelines read the
|
|
122
|
+
// manager's registry lazily, so tool-registry refreshes from here on
|
|
123
|
+
// advertise the shared registry's models.
|
|
124
|
+
manager.setModelRegistry(ctx.modelRegistry);
|
|
125
|
+
const active = pi.getActiveTools();
|
|
126
|
+
const workflowTools = [workflowTool.name, workflowControlTool.name];
|
|
127
|
+
const missing = workflowTools.filter((name) => !active.includes(name));
|
|
128
|
+
if (missing.length) pi.setActiveTools([...active, ...missing]);
|
|
129
|
+
// Scope the /workflows history to this session: runs persist on disk across
|
|
130
|
+
// sessions, but the navigator/task panel show only the current session's runs.
|
|
131
|
+
// Switching back to a previous session re-shows that session's runs.
|
|
132
|
+
try {
|
|
133
|
+
manager.setSessionId(ctx.sessionManager?.getSessionId());
|
|
134
|
+
} catch {
|
|
135
|
+
// sessionManager may be unavailable in some contexts — fall back to global history.
|
|
136
|
+
}
|
|
137
|
+
// Live "workflows running" panel below the input (focus + enter to open).
|
|
138
|
+
// Pass a live settings loader so /workflows-progress (compact|detailed) takes
|
|
139
|
+
// effect without a restart.
|
|
140
|
+
installTaskPanel(pi, manager, ctx.ui, { storage, cwd, loadSettings: () => loadWorkflowSettings({ cwd }) });
|
|
141
|
+
if (!armingInstalled) {
|
|
142
|
+
installWorkflowKeywordArming(pi, effort, {
|
|
143
|
+
settingsStore: {
|
|
144
|
+
load: () => loadWorkflowSettings({ cwd }),
|
|
145
|
+
save: (nextSettings) => saveWorkflowSettingsForCwd(nextSettings, cwd),
|
|
146
|
+
},
|
|
147
|
+
});
|
|
148
|
+
armingInstalled = true;
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@yycholla/pi-dynamic-workflows",
|
|
3
|
+
"version": "3.3.0",
|
|
4
|
+
"description": "Fork of QuitinShaw/pi-dynamic-workflows improving pager and workflow persistance",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"publishConfig": {
|
|
9
|
+
"access": "public"
|
|
10
|
+
},
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"import": "./dist/index.js"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"dist/",
|
|
19
|
+
"extensions/",
|
|
20
|
+
"skills/",
|
|
21
|
+
"src/",
|
|
22
|
+
"assets/readme/",
|
|
23
|
+
"README.md"
|
|
24
|
+
],
|
|
25
|
+
"scripts": {
|
|
26
|
+
"test": "npm run check && npm run release:check",
|
|
27
|
+
"test:unit": "tsx --test tests/**/*.test.ts",
|
|
28
|
+
"release:check": "npm run build && npm run docs:check && npm run context:check && npm run test:unit && npm run release:verify",
|
|
29
|
+
"release:verify": "tsx scripts/check-workflow-release.ts",
|
|
30
|
+
"check": "biome check . && npm run check:scripts",
|
|
31
|
+
"check:scripts": "tsc -p tsconfig.scripts.json",
|
|
32
|
+
"format": "biome format --write .",
|
|
33
|
+
"lint": "biome lint .",
|
|
34
|
+
"build": "tsc",
|
|
35
|
+
"docs:generate": "tsx scripts/generate-workflow-capabilities.ts",
|
|
36
|
+
"docs:check": "tsx scripts/generate-workflow-capabilities.ts --check",
|
|
37
|
+
"context:generate": "tsx scripts/generate-workflow-context-measurement.ts",
|
|
38
|
+
"context:check": "tsx scripts/generate-workflow-context-measurement.ts --check",
|
|
39
|
+
"guidance:generate": "tsx scripts/generate-workflow-guidance-baseline.ts",
|
|
40
|
+
"guidance:check": "tsx scripts/generate-workflow-guidance-baseline.ts --check",
|
|
41
|
+
"guidance:accept": "tsx scripts/accept-workflow-guidance.ts",
|
|
42
|
+
"comprehension": "tsx scripts/run-workflow-comprehension.ts",
|
|
43
|
+
"delivery-choice": "tsx scripts/run-workflow-delivery-choice.ts",
|
|
44
|
+
"dev": "tsx src/index.ts",
|
|
45
|
+
"prepublishOnly": "npm run release:check"
|
|
46
|
+
},
|
|
47
|
+
"keywords": [
|
|
48
|
+
"pi-package",
|
|
49
|
+
"pi",
|
|
50
|
+
"pi-coding-agent",
|
|
51
|
+
"workflow",
|
|
52
|
+
"workflows",
|
|
53
|
+
"dynamic-workflows",
|
|
54
|
+
"orchestration",
|
|
55
|
+
"subagents",
|
|
56
|
+
"multi-agent",
|
|
57
|
+
"agents",
|
|
58
|
+
"ai-agents",
|
|
59
|
+
"parallel",
|
|
60
|
+
"fan-out",
|
|
61
|
+
"claude-code",
|
|
62
|
+
"deep-research",
|
|
63
|
+
"code-review",
|
|
64
|
+
"llm"
|
|
65
|
+
],
|
|
66
|
+
"pi": {
|
|
67
|
+
"extensions": [
|
|
68
|
+
"extensions/workflow.ts"
|
|
69
|
+
],
|
|
70
|
+
"skills": [
|
|
71
|
+
"skills/workflow-authoring",
|
|
72
|
+
"skills/workflow-patterns"
|
|
73
|
+
],
|
|
74
|
+
"image": "https://raw.githubusercontent.com/QuintinShaw/pi-dynamic-workflows/main/assets/readme/package-cover.png"
|
|
75
|
+
},
|
|
76
|
+
"repository": {
|
|
77
|
+
"type": "git",
|
|
78
|
+
"url": "git+https://github.com/yycholla/pi-dynamic-workflows.git"
|
|
79
|
+
},
|
|
80
|
+
"author": "yycholla",
|
|
81
|
+
"contributors": [
|
|
82
|
+
"michaelliv (original author)",
|
|
83
|
+
"QuintinShaw"
|
|
84
|
+
],
|
|
85
|
+
"license": "MIT",
|
|
86
|
+
"dependencies": {
|
|
87
|
+
"acorn": "^8.16.0"
|
|
88
|
+
},
|
|
89
|
+
"peerDependencies": {
|
|
90
|
+
"@earendil-works/pi-coding-agent": ">=0.80.8",
|
|
91
|
+
"@earendil-works/pi-tui": ">=0.80.6",
|
|
92
|
+
"typebox": "*"
|
|
93
|
+
},
|
|
94
|
+
"devDependencies": {
|
|
95
|
+
"@biomejs/biome": "2.4.16",
|
|
96
|
+
"@earendil-works/pi-ai": "latest",
|
|
97
|
+
"@earendil-works/pi-coding-agent": "latest",
|
|
98
|
+
"@earendil-works/pi-tui": "latest",
|
|
99
|
+
"fast-check": "^4.8.0",
|
|
100
|
+
"tsx": "latest",
|
|
101
|
+
"typebox": "latest",
|
|
102
|
+
"typescript": "latest"
|
|
103
|
+
}
|
|
104
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: workflow-authoring
|
|
3
|
+
description: Guidance for writing, editing, reviewing, and debugging JavaScript workflow code for pi-dynamic-workflows. Use when authoring or changing workflow scripts; not for merely running an existing workflow.
|
|
4
|
+
metadata:
|
|
5
|
+
version: "3.3.0"
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Workflow authoring
|
|
9
|
+
|
|
10
|
+
Load this skill when workflow JavaScript changes. Running an existing workflow needs no authoring reference.
|
|
11
|
+
|
|
12
|
+
## Choose a branch
|
|
13
|
+
|
|
14
|
+
Read only what the task needs:
|
|
15
|
+
|
|
16
|
+
- **Write or edit:** start with [runtime](references/runtime.md). Add [pattern selection](references/pattern-selection.md) for topology, [lifecycle](references/lifecycle.md) for limits or resume, and [focused recipes](references/focused-recipes.md) for the matching concern.
|
|
17
|
+
- **Helper task:** read [quality helpers](references/quality-helpers.md) only for `verify` or `judgePanel`, the [retry helper](references/retry-helper.md) only for `retry`, and [specialized helpers](references/specialized-helpers.md) only for `completenessCheck`, `loopUntilDry`, `gate`, or `checkpoint`.
|
|
18
|
+
- **Review:** use the [review checklist](references/review.md), plus only the matching [quality](references/quality-helpers.md) or [specialized](references/specialized-helpers.md) helper contracts.
|
|
19
|
+
- **Debug:** use the [debugging map](references/debugging.md).
|
|
20
|
+
- **Routing:** read [registry ownership](references/registry-ownership.md) before using `model`, `tier`, phase models, or `agentType`; use environment-specific names only when context supplies them.
|
|
21
|
+
- **Exact lookup or portability:** start with the generated [capability index](references/capabilities.md). Follow its exhaustive-facts pointer only for constraints or support boundaries. Use [versions](references/versions.md) when moving scripts between installations.
|
|
22
|
+
|
|
23
|
+
## Invariants
|
|
24
|
+
|
|
25
|
+
- Start with literal `export const meta = { name, description }`; declare phases as an array of used `{ title }` objects and enter each named phase.
|
|
26
|
+
- Call `agent()` at least once, give every call a short unique `label`, and return plain JSON data explicitly.
|
|
27
|
+
- Pair ordered results with stable work IDs before filtering. When one agent consumes another's selected result, include both its stable ID and actual data in the downstream prompt. Treat recoverable `null` as missing coverage and report it.
|
|
28
|
+
- Bound fan-out, loops, retries, agents, concurrency, time, and token spend to the task.
|
|
29
|
+
- Use `log()` for new code; `console` is compatibility-only.
|
|
30
|
+
- Write plain JavaScript without imports or filesystem modules. Pass nondeterminism through `args`; `Date.now()`, `Math.random()`, and no-argument `new Date()` are unavailable.
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
export const meta = {
|
|
2
|
+
name: "adversarial_verification",
|
|
3
|
+
description: "Produce claims and challenge them in separate skeptical contexts",
|
|
4
|
+
phases: [{ title: "Produce" }, { title: "Verify" }],
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
// ADAPT: validate and bound topics; define the evidence standard and schemas.
|
|
8
|
+
const topics =
|
|
9
|
+
args && Array.isArray(args.topics) ? args.topics : [{ id: "sample", topic: "Verify one sample claim" }];
|
|
10
|
+
const rubric = args && typeof args.rubric === "string" ? args.rubric : "Evidence is direct and sufficient";
|
|
11
|
+
const productionSchema = {
|
|
12
|
+
type: "object",
|
|
13
|
+
properties: {
|
|
14
|
+
claim: { type: "string" },
|
|
15
|
+
evidence: { type: "array", items: { type: "string" } },
|
|
16
|
+
},
|
|
17
|
+
required: ["claim", "evidence"],
|
|
18
|
+
};
|
|
19
|
+
const verdictSchema = {
|
|
20
|
+
type: "object",
|
|
21
|
+
properties: { upheld: { type: "boolean" }, reason: { type: "string" } },
|
|
22
|
+
required: ["upheld", "reason"],
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
phase("Produce");
|
|
26
|
+
const productions = await parallel(
|
|
27
|
+
topics.map((topic, index) => () =>
|
|
28
|
+
agent(`Develop a claim and cite its evidence.\n\n${JSON.stringify(topic)}`, {
|
|
29
|
+
label: `produce:${index}:${String(topic.id)}`,
|
|
30
|
+
schema: productionSchema,
|
|
31
|
+
}),
|
|
32
|
+
),
|
|
33
|
+
);
|
|
34
|
+
const producerFailures = topics.flatMap((topic, index) =>
|
|
35
|
+
productions[index] === null ? [String(topic.id)] : [],
|
|
36
|
+
);
|
|
37
|
+
const reviewable = topics.flatMap((topic, index) =>
|
|
38
|
+
productions[index] === null ? [] : [{ topic, production: productions[index] }],
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
phase("Verify");
|
|
42
|
+
// INVARIANT: each skeptic is a fresh agent call, never the producer reviewing itself.
|
|
43
|
+
const verdicts = await parallel(
|
|
44
|
+
reviewable.map(({ topic, production }, index) => () =>
|
|
45
|
+
agent(
|
|
46
|
+
`Act as a skeptic. Try to refute this production under the rubric.\nRubric: ${rubric}\nProduction: ${JSON.stringify(production)}`,
|
|
47
|
+
{ label: `skeptic:${index}:${String(topic.id)}`, schema: verdictSchema },
|
|
48
|
+
),
|
|
49
|
+
),
|
|
50
|
+
);
|
|
51
|
+
const skepticFailures = reviewable.flatMap(({ topic }, index) =>
|
|
52
|
+
verdicts[index] === null ? [String(topic.id)] : [],
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
reviewed: reviewable.flatMap(({ topic, production }, index) =>
|
|
57
|
+
verdicts[index] === null
|
|
58
|
+
? []
|
|
59
|
+
: [{ id: String(topic.id), production, verdict: verdicts[index] }],
|
|
60
|
+
),
|
|
61
|
+
failed: { producers: producerFailures, skeptics: skepticFailures },
|
|
62
|
+
complete: producerFailures.length === 0 && skepticFailures.length === 0,
|
|
63
|
+
};
|