@quintinshaw/pi-dynamic-workflows 1.0.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 +159 -0
- package/dist/adversarial-review.d.ts +20 -0
- package/dist/adversarial-review.js +87 -0
- package/dist/agent.d.ts +29 -0
- package/dist/agent.js +90 -0
- package/dist/auto-workflow.d.ts +26 -0
- package/dist/auto-workflow.js +121 -0
- package/dist/config.d.ts +17 -0
- package/dist/config.js +17 -0
- package/dist/deep-research.d.ts +22 -0
- package/dist/deep-research.js +110 -0
- package/dist/display.d.ts +62 -0
- package/dist/display.js +163 -0
- package/dist/errors.d.ts +41 -0
- package/dist/errors.js +63 -0
- package/dist/index.d.ts +28 -0
- package/dist/index.js +15 -0
- package/dist/logger.d.ts +21 -0
- package/dist/logger.js +67 -0
- package/dist/model-routing.d.ts +33 -0
- package/dist/model-routing.js +57 -0
- package/dist/run-persistence.d.ts +53 -0
- package/dist/run-persistence.js +78 -0
- package/dist/structured-output.d.ts +19 -0
- package/dist/structured-output.js +30 -0
- package/dist/workflow-manager.d.ts +74 -0
- package/dist/workflow-manager.js +241 -0
- package/dist/workflow-saved.d.ts +35 -0
- package/dist/workflow-saved.js +91 -0
- package/dist/workflow-tool.d.ts +22 -0
- package/dist/workflow-tool.js +216 -0
- package/dist/workflow.d.ts +75 -0
- package/dist/workflow.js +364 -0
- package/extensions/workflow.ts +14 -0
- package/package.json +70 -0
- package/src/adversarial-review.ts +107 -0
- package/src/agent.ts +135 -0
- package/src/auto-workflow.ts +146 -0
- package/src/config.ts +24 -0
- package/src/deep-research.ts +128 -0
- package/src/display.ts +236 -0
- package/src/errors.ts +85 -0
- package/src/index.ts +55 -0
- package/src/logger.ts +89 -0
- package/src/model-routing.ts +80 -0
- package/src/run-persistence.ts +132 -0
- package/src/structured-output.ts +47 -0
- package/src/workflow-manager.ts +294 -0
- package/src/workflow-saved.ts +131 -0
- package/src/workflow-tool.ts +254 -0
- package/src/workflow.ts +492 -0
package/package.json
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@quintinshaw/pi-dynamic-workflows",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Claude-Code-style dynamic workflow orchestration for Pi.",
|
|
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
|
+
"src/",
|
|
21
|
+
"README.md"
|
|
22
|
+
],
|
|
23
|
+
"scripts": {
|
|
24
|
+
"test": "npm run check && npm run build && npm run test:unit",
|
|
25
|
+
"test:unit": "tsx --test tests/**/*.test.ts",
|
|
26
|
+
"check": "biome check .",
|
|
27
|
+
"format": "biome format --write .",
|
|
28
|
+
"lint": "biome lint .",
|
|
29
|
+
"build": "tsc",
|
|
30
|
+
"dev": "tsx src/index.ts",
|
|
31
|
+
"prepublishOnly": "npm run build"
|
|
32
|
+
},
|
|
33
|
+
"keywords": [
|
|
34
|
+
"pi-package",
|
|
35
|
+
"pi",
|
|
36
|
+
"workflow",
|
|
37
|
+
"agents"
|
|
38
|
+
],
|
|
39
|
+
"pi": {
|
|
40
|
+
"extensions": [
|
|
41
|
+
"extensions/workflow.ts"
|
|
42
|
+
]
|
|
43
|
+
},
|
|
44
|
+
"repository": {
|
|
45
|
+
"type": "git",
|
|
46
|
+
"url": "git+https://github.com/QuintinShaw/pi-dynamic-workflows.git"
|
|
47
|
+
},
|
|
48
|
+
"author": "QuintinShaw",
|
|
49
|
+
"contributors": [
|
|
50
|
+
"michaelliv (original author)"
|
|
51
|
+
],
|
|
52
|
+
"license": "MIT",
|
|
53
|
+
"dependencies": {
|
|
54
|
+
"acorn": "^8.16.0"
|
|
55
|
+
},
|
|
56
|
+
"peerDependencies": {
|
|
57
|
+
"@earendil-works/pi-coding-agent": "*",
|
|
58
|
+
"@earendil-works/pi-tui": "*",
|
|
59
|
+
"typebox": "*"
|
|
60
|
+
},
|
|
61
|
+
"devDependencies": {
|
|
62
|
+
"@biomejs/biome": "2.4.16",
|
|
63
|
+
"@earendil-works/pi-ai": "latest",
|
|
64
|
+
"@earendil-works/pi-coding-agent": "latest",
|
|
65
|
+
"@earendil-works/pi-tui": "latest",
|
|
66
|
+
"tsx": "latest",
|
|
67
|
+
"typebox": "latest",
|
|
68
|
+
"typescript": "latest"
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adversarial review mode for workflows.
|
|
3
|
+
* Agents cross-check each other's findings for higher quality results.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export interface AdversarialReviewConfig {
|
|
7
|
+
/** Number of independent reviewers per finding. */
|
|
8
|
+
reviewerCount: number;
|
|
9
|
+
/** Whether to filter out findings that don't survive cross-checking. */
|
|
10
|
+
filterContested: boolean;
|
|
11
|
+
/** Minimum agreement threshold (0-1). */
|
|
12
|
+
agreementThreshold: number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const DEFAULT_CONFIG: AdversarialReviewConfig = {
|
|
16
|
+
reviewerCount: 2,
|
|
17
|
+
filterContested: true,
|
|
18
|
+
agreementThreshold: 0.5,
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Generate an adversarial review workflow script.
|
|
23
|
+
*/
|
|
24
|
+
export function generateAdversarialReviewWorkflow(
|
|
25
|
+
taskDescription: string,
|
|
26
|
+
config: Partial<AdversarialReviewConfig> = {},
|
|
27
|
+
): string {
|
|
28
|
+
const cfg = { ...DEFAULT_CONFIG, ...config };
|
|
29
|
+
|
|
30
|
+
return `export const meta = {
|
|
31
|
+
name: 'adversarial_review',
|
|
32
|
+
description: 'Adversarial review with ${cfg.reviewerCount} independent reviewers',
|
|
33
|
+
phases: [
|
|
34
|
+
{ title: 'Initial Investigation' },
|
|
35
|
+
{ title: 'Independent Review' },
|
|
36
|
+
{ title: 'Cross-Check' },
|
|
37
|
+
{ title: 'Consensus' },
|
|
38
|
+
],
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
phase('Initial Investigation');
|
|
42
|
+
const findings = await agent(
|
|
43
|
+
'Investigate and document findings for: ${taskDescription.replace(/'/g, "\\'").slice(0, 80)}',
|
|
44
|
+
{ label: 'investigator' }
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
phase('Independent Review');
|
|
48
|
+
const reviews = await parallel(Array.from({ length: ${cfg.reviewerCount} }, (_, i) => () =>
|
|
49
|
+
agent(
|
|
50
|
+
'Independently review these findings. Agree or disagree with each point, and explain why:\\n\\n' + findings,
|
|
51
|
+
{ label: 'reviewer-' + (i + 1) }
|
|
52
|
+
)
|
|
53
|
+
));
|
|
54
|
+
|
|
55
|
+
phase('Cross-Check');
|
|
56
|
+
const crossCheck = await agent(
|
|
57
|
+
'Compare these independent reviews and identify points of agreement and disagreement:\\n' +
|
|
58
|
+
'Reviews: ' + JSON.stringify(reviews) + '\\n' +
|
|
59
|
+
'Original findings: ' + findings,
|
|
60
|
+
{ label: 'cross-checker' }
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
phase('Consensus');
|
|
64
|
+
const consensus = await agent(
|
|
65
|
+
'Based on the cross-check, produce a final verified report. Only include findings that survived independent review:\\n' + crossCheck,
|
|
66
|
+
{ label: 'consensus-builder' }
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
return { findings, reviews, crossCheck, consensus };`;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Generate a multi-perspective analysis workflow.
|
|
74
|
+
*/
|
|
75
|
+
export function generateMultiPerspectiveWorkflow(topic: string, perspectives: string[]): string {
|
|
76
|
+
const perspectiveAgents = perspectives
|
|
77
|
+
.map(
|
|
78
|
+
(p, _i) =>
|
|
79
|
+
` () => agent('Analyze from ${p} perspective: ' + topic, { label: '${p.toLowerCase().replace(/\\s+/g, "-")}' }),`,
|
|
80
|
+
)
|
|
81
|
+
.join("\n");
|
|
82
|
+
|
|
83
|
+
return `export const meta = {
|
|
84
|
+
name: 'multi_perspective_analysis',
|
|
85
|
+
description: 'Analyze from ${perspectives.length} different perspectives',
|
|
86
|
+
phases: [
|
|
87
|
+
{ title: 'Perspective Analysis' },
|
|
88
|
+
{ title: 'Synthesis' },
|
|
89
|
+
],
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
phase('Perspective Analysis');
|
|
93
|
+
const topic = '${topic.replace(/'/g, "\\'")}';
|
|
94
|
+
const analyses = await parallel([
|
|
95
|
+
${perspectiveAgents}
|
|
96
|
+
]);
|
|
97
|
+
|
|
98
|
+
phase('Synthesis');
|
|
99
|
+
const synthesis = await agent(
|
|
100
|
+
'Synthesize these different perspectives into a balanced analysis:\\n' +
|
|
101
|
+
'Analyses: ' + JSON.stringify(analyses) + '\\n' +
|
|
102
|
+
'Topic: ' + topic,
|
|
103
|
+
{ label: 'synthesizer' }
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
return { analyses, synthesis };`;
|
|
107
|
+
}
|
package/src/agent.ts
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import type { AssistantMessage, TextContent } from "@earendil-works/pi-ai";
|
|
2
|
+
import {
|
|
3
|
+
type CreateAgentSessionOptions,
|
|
4
|
+
createAgentSession,
|
|
5
|
+
createCodingTools,
|
|
6
|
+
getAgentDir,
|
|
7
|
+
SessionManager,
|
|
8
|
+
SettingsManager,
|
|
9
|
+
type ToolDefinition,
|
|
10
|
+
} from "@earendil-works/pi-coding-agent";
|
|
11
|
+
import type { Static, TSchema } from "typebox";
|
|
12
|
+
import { createStructuredOutputTool, type StructuredOutputCapture } from "./structured-output.js";
|
|
13
|
+
|
|
14
|
+
export interface WorkflowAgentOptions {
|
|
15
|
+
cwd?: string;
|
|
16
|
+
/** Extra tools available to the subagent in addition to the structured output tool. */
|
|
17
|
+
tools?: ToolDefinition[];
|
|
18
|
+
/** Override any createAgentSession option (model, authStorage, resourceLoader, etc.). */
|
|
19
|
+
session?: Partial<CreateAgentSessionOptions>;
|
|
20
|
+
/** Extra system guidance prepended to every subagent task. */
|
|
21
|
+
instructions?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface AgentRunOptions<TSchemaDef extends TSchema | undefined = undefined> {
|
|
25
|
+
label?: string;
|
|
26
|
+
schema?: TSchemaDef;
|
|
27
|
+
tools?: ToolDefinition[];
|
|
28
|
+
instructions?: string;
|
|
29
|
+
signal?: AbortSignal;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export type AgentRunResult<TSchemaDef extends TSchema | undefined> = TSchemaDef extends TSchema
|
|
33
|
+
? Static<TSchemaDef>
|
|
34
|
+
: string;
|
|
35
|
+
|
|
36
|
+
export class WorkflowAgent {
|
|
37
|
+
private readonly cwd: string;
|
|
38
|
+
private readonly baseTools: ToolDefinition[];
|
|
39
|
+
private readonly sessionOptions: Partial<CreateAgentSessionOptions>;
|
|
40
|
+
private readonly instructions?: string;
|
|
41
|
+
|
|
42
|
+
constructor(options: WorkflowAgentOptions = {}) {
|
|
43
|
+
this.cwd = options.cwd ?? process.cwd();
|
|
44
|
+
this.baseTools = options.tools ?? createCodingTools(this.cwd);
|
|
45
|
+
this.sessionOptions = options.session ?? {};
|
|
46
|
+
this.instructions = options.instructions;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async run<TSchemaDef extends TSchema | undefined = undefined>(
|
|
50
|
+
prompt: string,
|
|
51
|
+
options: AgentRunOptions<TSchemaDef> = {},
|
|
52
|
+
): Promise<AgentRunResult<TSchemaDef>> {
|
|
53
|
+
const capture: StructuredOutputCapture<any> = { called: false, value: undefined };
|
|
54
|
+
const customTools: ToolDefinition[] = [...this.baseTools, ...(options.tools ?? [])];
|
|
55
|
+
|
|
56
|
+
if (options.schema) {
|
|
57
|
+
customTools.push(createStructuredOutputTool({ schema: options.schema, capture }) as unknown as ToolDefinition);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const agentDir = getAgentDir();
|
|
61
|
+
const { session } = await createAgentSession({
|
|
62
|
+
cwd: this.cwd,
|
|
63
|
+
agentDir,
|
|
64
|
+
sessionManager: SessionManager.inMemory(),
|
|
65
|
+
// Use real SettingsManager to inherit user's default provider/model settings.
|
|
66
|
+
// SettingsManager.inMemory() doesn't load ~/.pi/settings.json, so subagents
|
|
67
|
+
// would fall back to the first available model (e.g. openai-codex) which may
|
|
68
|
+
// not have valid auth, causing silent empty responses.
|
|
69
|
+
settingsManager: SettingsManager.create(this.cwd, agentDir),
|
|
70
|
+
customTools,
|
|
71
|
+
...this.sessionOptions,
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
let removeAbortListener: (() => void) | undefined;
|
|
75
|
+
try {
|
|
76
|
+
if (options.signal?.aborted) throw new Error("Subagent was aborted");
|
|
77
|
+
if (options.signal) {
|
|
78
|
+
const onAbort = () => void session.abort();
|
|
79
|
+
options.signal.addEventListener("abort", onAbort, { once: true });
|
|
80
|
+
removeAbortListener = () => options.signal?.removeEventListener("abort", onAbort);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
await session.prompt(this.buildPrompt(prompt, options as AgentRunOptions<any>, Boolean(options.schema)));
|
|
84
|
+
if (options.signal?.aborted) throw new Error("Subagent was aborted");
|
|
85
|
+
|
|
86
|
+
if (options.schema) {
|
|
87
|
+
if (!capture.called) {
|
|
88
|
+
throw new Error("Subagent finished without calling structured_output");
|
|
89
|
+
}
|
|
90
|
+
return capture.value as AgentRunResult<TSchemaDef>;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return this.lastAssistantText(session.messages) as AgentRunResult<TSchemaDef>;
|
|
94
|
+
} finally {
|
|
95
|
+
removeAbortListener?.();
|
|
96
|
+
session.dispose();
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
private buildPrompt(prompt: string, options: AgentRunOptions<any>, structured: boolean): string {
|
|
101
|
+
const parts = [
|
|
102
|
+
this.instructions,
|
|
103
|
+
options.instructions,
|
|
104
|
+
options.label ? `Task label: ${options.label}` : undefined,
|
|
105
|
+
prompt,
|
|
106
|
+
].filter(Boolean);
|
|
107
|
+
|
|
108
|
+
if (structured) {
|
|
109
|
+
parts.push(
|
|
110
|
+
[
|
|
111
|
+
"Final output contract:",
|
|
112
|
+
"- Your final action MUST be a structured_output tool call.",
|
|
113
|
+
"- The structured_output arguments are the return value of this subagent.",
|
|
114
|
+
"- Do not emit a prose final answer instead of structured_output.",
|
|
115
|
+
"- If you need to inspect files or run commands first, do so, then call structured_output exactly once.",
|
|
116
|
+
].join("\n"),
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return parts.join("\n\n");
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
private lastAssistantText(messages: unknown[]): string {
|
|
124
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
125
|
+
const message = messages[i] as Partial<AssistantMessage> | undefined;
|
|
126
|
+
if (message?.role !== "assistant" || !Array.isArray(message.content)) continue;
|
|
127
|
+
const text = message.content
|
|
128
|
+
.filter((part): part is TextContent => part.type === "text")
|
|
129
|
+
.map((part) => part.text)
|
|
130
|
+
.join("");
|
|
131
|
+
if (text.trim()) return text;
|
|
132
|
+
}
|
|
133
|
+
return "";
|
|
134
|
+
}
|
|
135
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auto-workflow mode (ultracode equivalent).
|
|
3
|
+
* Automatically decides when to use workflows based on task complexity.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export interface AutoWorkflowConfig {
|
|
7
|
+
/** Enable auto-workflow mode. */
|
|
8
|
+
enabled: boolean;
|
|
9
|
+
/** Minimum number of subtasks to trigger a workflow. */
|
|
10
|
+
minSubtasks: number;
|
|
11
|
+
/** Keywords that suggest workflow usage. */
|
|
12
|
+
triggerKeywords: string[];
|
|
13
|
+
/** Maximum complexity score before auto-triggering. */
|
|
14
|
+
complexityThreshold: number;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const DEFAULT_CONFIG: AutoWorkflowConfig = {
|
|
18
|
+
enabled: false,
|
|
19
|
+
minSubtasks: 3,
|
|
20
|
+
triggerKeywords: [
|
|
21
|
+
"workflow",
|
|
22
|
+
"parallel",
|
|
23
|
+
"fan-out",
|
|
24
|
+
"audit",
|
|
25
|
+
"migrate",
|
|
26
|
+
"review",
|
|
27
|
+
"research",
|
|
28
|
+
"analyze all",
|
|
29
|
+
"check every",
|
|
30
|
+
"sweep",
|
|
31
|
+
"batch",
|
|
32
|
+
"bulk",
|
|
33
|
+
],
|
|
34
|
+
complexityThreshold: 7,
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Analyze a task description and determine if it should use a workflow.
|
|
39
|
+
*/
|
|
40
|
+
export function shouldUseWorkflow(
|
|
41
|
+
taskDescription: string,
|
|
42
|
+
config: Partial<AutoWorkflowConfig> = { enabled: true },
|
|
43
|
+
): { useWorkflow: boolean; confidence: number; reason: string } {
|
|
44
|
+
const cfg = { ...DEFAULT_CONFIG, ...config };
|
|
45
|
+
|
|
46
|
+
if (!cfg.enabled) {
|
|
47
|
+
return { useWorkflow: false, confidence: 0, reason: "Auto-workflow disabled" };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const lower = taskDescription.toLowerCase();
|
|
51
|
+
|
|
52
|
+
// Check for explicit workflow keywords
|
|
53
|
+
const keywordMatches = cfg.triggerKeywords.filter((kw) => lower.includes(kw));
|
|
54
|
+
if (keywordMatches.length > 0) {
|
|
55
|
+
return {
|
|
56
|
+
useWorkflow: true,
|
|
57
|
+
confidence: Math.min(0.5 + keywordMatches.length * 0.15, 1),
|
|
58
|
+
reason: `Matched keywords: ${keywordMatches.join(", ")}`,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Analyze complexity indicators
|
|
63
|
+
const complexityIndicators = [
|
|
64
|
+
{ pattern: /\b(all|every|each|entire|whole)\b/i, weight: 2 },
|
|
65
|
+
{ pattern: /\b(files?|directories|folders?|modules?|components?|endpoints?)\b/i, weight: 1.5 },
|
|
66
|
+
{ pattern: /\b(parallel|concurrent|simultaneously)\b/i, weight: 2 },
|
|
67
|
+
{ pattern: /\b(review|audit|check|verify|validate)\b/i, weight: 1 },
|
|
68
|
+
{ pattern: /\b(migrate|refactor|update|modify|change)\b/i, weight: 1.5 },
|
|
69
|
+
{ pattern: /\b(research|investigate|analyze|compare)\b/i, weight: 1 },
|
|
70
|
+
{ pattern: /\d+\s*(files?|items?|tasks?|components?)/i, weight: 2 },
|
|
71
|
+
{ pattern: /\b(across|throughout|cross-cutting)\b/i, weight: 1.5 },
|
|
72
|
+
];
|
|
73
|
+
|
|
74
|
+
let complexityScore = 0;
|
|
75
|
+
for (const indicator of complexityIndicators) {
|
|
76
|
+
if (indicator.pattern.test(taskDescription)) {
|
|
77
|
+
complexityScore += indicator.weight;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Estimate subtask count
|
|
82
|
+
const subtaskIndicators = [
|
|
83
|
+
/\bfirst\b/gi,
|
|
84
|
+
/\bthen\b/gi,
|
|
85
|
+
/\bfinally\b/gi,
|
|
86
|
+
/\bafter\b/gi,
|
|
87
|
+
/\bnext\b/gi,
|
|
88
|
+
/\balso\b/gi,
|
|
89
|
+
/\bstep \d/gi,
|
|
90
|
+
];
|
|
91
|
+
|
|
92
|
+
let estimatedSubtasks = 1;
|
|
93
|
+
for (const pattern of subtaskIndicators) {
|
|
94
|
+
const matches = taskDescription.match(pattern);
|
|
95
|
+
if (matches) estimatedSubtasks += matches.length;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (complexityScore >= cfg.complexityThreshold || estimatedSubtasks >= cfg.minSubtasks) {
|
|
99
|
+
return {
|
|
100
|
+
useWorkflow: true,
|
|
101
|
+
confidence: Math.min(complexityScore / 10, 1),
|
|
102
|
+
reason: `Complexity score: ${complexityScore}, estimated subtasks: ${estimatedSubtasks}`,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return {
|
|
107
|
+
useWorkflow: false,
|
|
108
|
+
confidence: 0.3,
|
|
109
|
+
reason: `Below threshold (complexity: ${complexityScore}, subtasks: ${estimatedSubtasks})`,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Generate a workflow script suggestion from a task description.
|
|
115
|
+
*/
|
|
116
|
+
export function suggestWorkflowScript(taskDescription: string): string {
|
|
117
|
+
return `export const meta = {
|
|
118
|
+
name: 'auto_generated',
|
|
119
|
+
description: '${taskDescription.replace(/'/g, "\\'").slice(0, 100)}',
|
|
120
|
+
phases: [
|
|
121
|
+
{ title: 'Analyze' },
|
|
122
|
+
{ title: 'Execute' },
|
|
123
|
+
{ title: 'Verify' },
|
|
124
|
+
],
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
phase('Analyze');
|
|
128
|
+
const analysis = await agent(
|
|
129
|
+
'Analyze this task and break it into subtasks: ${taskDescription.replace(/'/g, "\\'").slice(0, 80)}',
|
|
130
|
+
{ label: 'task analysis' }
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
phase('Execute');
|
|
134
|
+
const results = await parallel([
|
|
135
|
+
() => agent('Execute subtask 1 based on: ' + analysis, { label: 'subtask-1' }),
|
|
136
|
+
// Add more subtasks as needed
|
|
137
|
+
]);
|
|
138
|
+
|
|
139
|
+
phase('Verify');
|
|
140
|
+
const verification = await agent(
|
|
141
|
+
'Verify these results are correct: ' + JSON.stringify(results),
|
|
142
|
+
{ label: 'verification' }
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
return { analysis, results, verification };`;
|
|
146
|
+
}
|
package/src/config.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration constants for pi-dynamic-workflows.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/** Maximum number of agents allowed per workflow run. */
|
|
6
|
+
export const MAX_AGENTS_PER_RUN = 1000;
|
|
7
|
+
|
|
8
|
+
/** Default timeout for a single agent in milliseconds (5 minutes). */
|
|
9
|
+
export const DEFAULT_AGENT_TIMEOUT_MS = 5 * 60 * 1000;
|
|
10
|
+
|
|
11
|
+
/** Maximum concurrent agents (matches Claude Code limit). */
|
|
12
|
+
export const MAX_CONCURRENCY = 16;
|
|
13
|
+
|
|
14
|
+
/** Default token budget if none specified. */
|
|
15
|
+
export const DEFAULT_TOKEN_BUDGET = null;
|
|
16
|
+
|
|
17
|
+
/** Directory for persisting workflow run state. */
|
|
18
|
+
export const WORKFLOW_RUNS_DIR = ".pi/workflows/runs";
|
|
19
|
+
|
|
20
|
+
/** Directory for saved workflow commands. */
|
|
21
|
+
export const WORKFLOW_SAVED_DIR = ".pi/workflows/saved";
|
|
22
|
+
|
|
23
|
+
/** User-level saved workflows directory. */
|
|
24
|
+
export const USER_WORKFLOW_SAVED_DIR = "~/.pi/workflows/saved";
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deep research workflow.
|
|
3
|
+
* Built-in workflow for comprehensive research across multiple sources.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export interface DeepResearchConfig {
|
|
7
|
+
/** Number of search angles to explore. */
|
|
8
|
+
searchAngles: number;
|
|
9
|
+
/** Number of sources to fetch per angle. */
|
|
10
|
+
sourcesPerAngle: number;
|
|
11
|
+
/** Whether to cross-check claims across sources. */
|
|
12
|
+
crossCheck: boolean;
|
|
13
|
+
/** Maximum number of agents to use. */
|
|
14
|
+
maxAgents: number;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const DEFAULT_CONFIG: DeepResearchConfig = {
|
|
18
|
+
searchAngles: 4,
|
|
19
|
+
sourcesPerAngle: 3,
|
|
20
|
+
crossCheck: true,
|
|
21
|
+
maxAgents: 20,
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Generate a deep research workflow script.
|
|
26
|
+
*/
|
|
27
|
+
export function generateDeepResearchWorkflow(question: string, config: Partial<DeepResearchConfig> = {}): string {
|
|
28
|
+
const cfg = { ...DEFAULT_CONFIG, ...config };
|
|
29
|
+
const escapedQuestion = question.replace(/'/g, "\\'").slice(0, 80);
|
|
30
|
+
|
|
31
|
+
const crossCheckPhase = cfg.crossCheck
|
|
32
|
+
? `phase('Cross-Check');
|
|
33
|
+
const crossCheck = await agent(
|
|
34
|
+
'Cross-check these research findings. Identify claims that are supported by multiple sources vs. claims that appear in only one source:\\n' +
|
|
35
|
+
'Sources: ' + JSON.stringify(sources),
|
|
36
|
+
{ label: 'cross-checker' }
|
|
37
|
+
);`
|
|
38
|
+
: "";
|
|
39
|
+
|
|
40
|
+
const crossCheckRef = cfg.crossCheck ? "'Cross-check: ' + crossCheck + '\\n' + " : "";
|
|
41
|
+
const crossCheckReturn = cfg.crossCheck ? "crossCheck, " : "";
|
|
42
|
+
|
|
43
|
+
return `export const meta = {
|
|
44
|
+
name: 'deep_research',
|
|
45
|
+
description: 'Deep research: ${escapedQuestion}',
|
|
46
|
+
phases: [
|
|
47
|
+
{ title: 'Search Planning' },
|
|
48
|
+
{ title: 'Source Gathering' },
|
|
49
|
+
{ title: 'Cross-Check' },
|
|
50
|
+
{ title: 'Report' },
|
|
51
|
+
],
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
phase('Search Planning');
|
|
55
|
+
const question = '${escapedQuestion}';
|
|
56
|
+
const searchPlan = await agent(
|
|
57
|
+
'Plan ${cfg.searchAngles} different search angles to research this question comprehensively: ' + question,
|
|
58
|
+
{ label: 'search-planner' }
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
phase('Source Gathering');
|
|
62
|
+
const sources = await parallel(Array.from({ length: ${cfg.searchAngles} }, (_, i) => () =>
|
|
63
|
+
agent(
|
|
64
|
+
'Research angle ' + (i + 1) + ' for this question: ' + question + '\\n\\nPlan: ' + searchPlan + '\\n\\nFind and summarize ${cfg.sourcesPerAngle} relevant sources.',
|
|
65
|
+
{ label: 'researcher-' + (i + 1) }
|
|
66
|
+
)
|
|
67
|
+
));
|
|
68
|
+
|
|
69
|
+
${crossCheckPhase}
|
|
70
|
+
|
|
71
|
+
phase('Report');
|
|
72
|
+
const report = await agent(
|
|
73
|
+
'Synthesize a comprehensive research report from these findings:\\n' +
|
|
74
|
+
'Question: ' + question + '\\n' +
|
|
75
|
+
'Sources: ' + JSON.stringify(sources) + '\\n' +
|
|
76
|
+
${crossCheckRef}'\\n\\nProduce a well-structured report with citations and confidence levels.',
|
|
77
|
+
{ label: 'report-writer' }
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
return { searchPlan, sources, ${crossCheckReturn}report };`;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Generate a codebase audit workflow.
|
|
85
|
+
*/
|
|
86
|
+
export function generateCodebaseAuditWorkflow(scope: string, checks: string[]): string {
|
|
87
|
+
const escapedScope = scope.replace(/'/g, "\\'").slice(0, 60);
|
|
88
|
+
const checkAgents = checks
|
|
89
|
+
.map((check) => {
|
|
90
|
+
const label = check
|
|
91
|
+
.toLowerCase()
|
|
92
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
93
|
+
.slice(0, 20);
|
|
94
|
+
return ` () => agent('Audit ${check} across: ' + scope, { label: '${label}' }),`;
|
|
95
|
+
})
|
|
96
|
+
.join("\n");
|
|
97
|
+
|
|
98
|
+
return `export const meta = {
|
|
99
|
+
name: 'codebase_audit',
|
|
100
|
+
description: 'Codebase audit: ${escapedScope}',
|
|
101
|
+
phases: [
|
|
102
|
+
{ title: 'Individual Checks' },
|
|
103
|
+
{ title: 'Cross-Validation' },
|
|
104
|
+
{ title: 'Report' },
|
|
105
|
+
],
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
phase('Individual Checks');
|
|
109
|
+
const scope = '${escapedScope}';
|
|
110
|
+
const findings = await parallel([
|
|
111
|
+
${checkAgents}
|
|
112
|
+
]);
|
|
113
|
+
|
|
114
|
+
phase('Cross-Validation');
|
|
115
|
+
const validated = await agent(
|
|
116
|
+
'Cross-validate these audit findings. Remove false positives and confirm real issues:\\n' +
|
|
117
|
+
JSON.stringify(findings),
|
|
118
|
+
{ label: 'validator' }
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
phase('Report');
|
|
122
|
+
const report = await agent(
|
|
123
|
+
'Generate a prioritized audit report with actionable recommendations:\\n' + validated,
|
|
124
|
+
{ label: 'report-writer' }
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
return { findings, validated, report };`;
|
|
128
|
+
}
|