@maplezzk/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 ADDED
@@ -0,0 +1,58 @@
1
+ # pi-dynamic-workflows
2
+
3
+ Claude-Code-style dynamic workflow orchestration for Pi.
4
+
5
+ > **Fork notice:** This package is a fork of [michaelliv/pi-dynamic-workflows](https://github.com/Michaelliv/pi-dynamic-workflows) (MIT license). Full credit to the original author **michaelliv** for the design and implementation. Changes in this fork: monorepo integration, i18n support, and config migration from environment variables to a slash command.
6
+
7
+ ## Features
8
+
9
+ - Define multi-agent workflows as plain JavaScript scripts with `meta`, `phase()`, `agent()`, `parallel()`, and `pipeline()` primitives
10
+ - Static validation of workflow scripts via acorn AST parsing
11
+ - Optional subagent backend via `pi-interactive-subagents` for real tool access per agent
12
+ - Async background execution mode with live status widget
13
+ - Configurable via `/workflow-config` slash command (persisted to JSON)
14
+
15
+ ## Installation
16
+
17
+ ```bash
18
+ pi install @maplezzk/pi-dynamic-workflows
19
+ ```
20
+
21
+ ## Configuration
22
+
23
+ Run `/workflow-config` to interactively configure:
24
+
25
+ - **Execution backend**: `workflow` (built-in in-process agent) or `subagent` (requires `pi-interactive-subagents`)
26
+ - **Async mode**: run workflows in the background with a live status widget
27
+
28
+ Config is persisted to `~/.pi/agent/extensions/pi-dynamic-workflows/config.json`.
29
+
30
+ Environment variables are supported as fallback only:
31
+
32
+ | Variable | Values | Effect |
33
+ |---|---|---|
34
+ | `PI_WORKFLOW_BACKEND` | `subagent` | Use subagent backend (fallback) |
35
+ | `PI_WORKFLOW_ASYNC` | `true` | Enable async mode (fallback) |
36
+
37
+ JSON config takes priority over environment variables.
38
+
39
+ ## Usage
40
+
41
+ ```js
42
+ // In a workflow script passed to the workflow tool:
43
+ export const meta = {
44
+ name: 'my_workflow',
45
+ description: 'Does something useful',
46
+ phases: [{ title: 'Phase 1' }]
47
+ };
48
+
49
+ phase('Phase 1');
50
+ const result = await agent('Analyze the codebase', {
51
+ label: 'code analysis',
52
+ schema: { type: 'object', properties: { summary: { type: 'string' } }, required: ['summary'] }
53
+ });
54
+ ```
55
+
56
+ ## License
57
+
58
+ MIT — see original repository for details.
@@ -0,0 +1,58 @@
1
+ # pi-dynamic-workflows
2
+
3
+ 为 Pi 提供 Claude-Code 风格的动态 workflow 编排能力。
4
+
5
+ > **Fork 说明:** 本包 fork 自 [michaelliv/pi-dynamic-workflows](https://github.com/Michaelliv/pi-dynamic-workflows)(MIT 协议)。设计与实现的版权归原作者 **michaelliv** 所有。本 fork 的变更:monorepo 集成、i18n 支持、配置方式从环境变量迁移为斜杠命令。
6
+
7
+ ## 功能
8
+
9
+ - 用纯 JavaScript 脚本定义多 agent 工作流,支持 `meta`、`phase()`、`agent()`、`parallel()`、`pipeline()` 原语
10
+ - 通过 acorn AST 解析对 workflow 脚本进行静态校验
11
+ - 可选 subagent 后端(依赖 `pi-interactive-subagents`),每个 agent 拥有真实工具访问
12
+ - 异步后台执行模式,带实时状态 widget
13
+ - 通过 `/workflow-config` 斜杠命令配置(持久化到 JSON)
14
+
15
+ ## 安装
16
+
17
+ ```bash
18
+ pi install @maplezzk/pi-dynamic-workflows
19
+ ```
20
+
21
+ ## 配置
22
+
23
+ 运行 `/workflow-config` 进行交互式配置:
24
+
25
+ - **执行后端**:`workflow`(内置进程内 agent)或 `subagent`(需安装 `pi-interactive-subagents`)
26
+ - **异步模式**:后台运行 workflow,带实时状态 widget
27
+
28
+ 配置持久化到 `~/.pi/agent/extensions/pi-dynamic-workflows/config.json`。
29
+
30
+ 环境变量仅作兜底支持:
31
+
32
+ | 变量 | 值 | 效果 |
33
+ |---|---|---|
34
+ | `PI_WORKFLOW_BACKEND` | `subagent` | 使用 subagent 后端(兜底) |
35
+ | `PI_WORKFLOW_ASYNC` | `true` | 启用异步模式(兜底) |
36
+
37
+ JSON 配置优先级高于环境变量。
38
+
39
+ ## 用法
40
+
41
+ ```js
42
+ // 传给 workflow 工具的脚本:
43
+ export const meta = {
44
+ name: 'my_workflow',
45
+ description: '完成某件有用的事',
46
+ phases: [{ title: '阶段 1' }]
47
+ };
48
+
49
+ phase('阶段 1');
50
+ const result = await agent('分析代码库', {
51
+ label: '代码分析',
52
+ schema: { type: 'object', properties: { summary: { type: 'string' } }, required: ['summary'] }
53
+ });
54
+ ```
55
+
56
+ ## 许可证
57
+
58
+ MIT — 详见原始仓库。
package/index.ts ADDED
@@ -0,0 +1,2 @@
1
+ export { default } from "./src/extension.ts";
2
+ export * from "./src/index.ts";
@@ -0,0 +1,78 @@
1
+ {
2
+ "commandDescription": {
3
+ "zh-CN": "配置 workflow 执行后端与异步模式",
4
+ "en-US": "Configure workflow execution backend and async mode"
5
+ },
6
+ "configTitle": {
7
+ "zh-CN": "Workflow 配置",
8
+ "en-US": "Workflow Config"
9
+ },
10
+ "exit": {
11
+ "zh-CN": "退出",
12
+ "en-US": "Exit"
13
+ },
14
+ "on": {
15
+ "zh-CN": "开启",
16
+ "en-US": "on"
17
+ },
18
+ "off": {
19
+ "zh-CN": "关闭",
20
+ "en-US": "off"
21
+ },
22
+ "toggleBackend": {
23
+ "zh-CN": "切换执行后端(当前:{value})",
24
+ "en-US": "Toggle execution backend (current: {value})"
25
+ },
26
+ "toggleAsync": {
27
+ "zh-CN": "切换异步模式(当前:{value})",
28
+ "en-US": "Toggle async mode (current: {value})"
29
+ },
30
+ "savedBackend": {
31
+ "zh-CN": "执行后端已切换为:{value}",
32
+ "en-US": "Execution backend switched to: {value}"
33
+ },
34
+ "savedAsync": {
35
+ "zh-CN": "异步模式已切换为:{value}",
36
+ "en-US": "Async mode switched to: {value}"
37
+ },
38
+ "reloadHint": {
39
+ "zh-CN": "需 /reload 生效",
40
+ "en-US": "Requires /reload to take effect"
41
+ },
42
+ "cancelToolDescription": {
43
+ "zh-CN": "取消正在后台运行的 workflow",
44
+ "en-US": "Cancel a running background workflow"
45
+ },
46
+ "cancelSent": {
47
+ "zh-CN": "Workflow {name} 取消请求已发送",
48
+ "en-US": "Cancel request sent for Workflow {name}"
49
+ },
50
+ "noneRunning": {
51
+ "zh-CN": "当前没有正在运行的 workflow",
52
+ "en-US": "No workflow is currently running"
53
+ },
54
+ "alreadyRunning": {
55
+ "zh-CN": "已有 workflow 在运行: {name}",
56
+ "en-US": "A workflow is already running: {name}"
57
+ },
58
+ "resultWritten": {
59
+ "zh-CN": "Workflow {name} 已完成({count} 个 agent,耗时 {ms}ms)。\n结果已写入: {path}",
60
+ "en-US": "Workflow {name} completed with {count} agent(s) in {ms}ms.\nResult written to: {path}"
61
+ },
62
+ "cancelled": {
63
+ "zh-CN": "Workflow {name} 已取消",
64
+ "en-US": "Workflow {name} cancelled"
65
+ },
66
+ "started": {
67
+ "zh-CN": "Workflow {name} 已启动,后台执行中...",
68
+ "en-US": "Workflow {name} started, running in background..."
69
+ },
70
+ "totalResult": {
71
+ "zh-CN": "📄 总结果:{path}",
72
+ "en-US": "📄 Result: {path}"
73
+ },
74
+ "subagentRequired": {
75
+ "zh-CN": "PI_WORKFLOW_BACKEND=subagent 需要 pi-interactive-subagents 扩展。\n安装: pi install pi-interactive-subagents",
76
+ "en-US": "PI_WORKFLOW_BACKEND=subagent requires the pi-interactive-subagents extension.\nInstall: pi install pi-interactive-subagents"
77
+ }
78
+ }
package/package.json ADDED
@@ -0,0 +1,76 @@
1
+ {
2
+ "name": "@maplezzk/pi-dynamic-workflows",
3
+ "version": "1.0.0",
4
+ "description": "Claude-Code-style dynamic workflow orchestration for Pi. Fork of michaelliv/pi-dynamic-workflows.",
5
+ "type": "module",
6
+ "main": "./index.ts",
7
+ "exports": {
8
+ ".": "./index.ts"
9
+ },
10
+ "files": [
11
+ "index.ts",
12
+ "src",
13
+ "locales",
14
+ "types",
15
+ "README.md",
16
+ "README.zh-CN.md"
17
+ ],
18
+ "scripts": {
19
+ "test": "tsx --test tests/*.test.ts",
20
+ "typecheck": "tsc --noEmit --pretty false",
21
+ "build": "npm run typecheck",
22
+ "check": "npm run typecheck && npm test && npm pack --dry-run --json > /dev/null"
23
+ },
24
+ "pi": {
25
+ "extensions": [
26
+ "./index.ts"
27
+ ]
28
+ },
29
+ "engines": {
30
+ "node": ">=22"
31
+ },
32
+ "license": "MIT",
33
+ "author": "michaelliv",
34
+ "contributors": [
35
+ "maplezzk"
36
+ ],
37
+ "homepage": "https://github.com/maplezzk/pi-extensions/tree/main/packages/pi-dynamic-workflows",
38
+ "bugs": "https://github.com/maplezzk/pi-extensions/issues",
39
+ "repository": {
40
+ "type": "git",
41
+ "url": "git+https://github.com/maplezzk/pi-extensions.git",
42
+ "directory": "packages/pi-dynamic-workflows"
43
+ },
44
+ "publishConfig": {
45
+ "access": "public",
46
+ "registry": "https://registry.npmjs.org"
47
+ },
48
+ "keywords": [
49
+ "pi-package",
50
+ "pi",
51
+ "pi-extension",
52
+ "coding-agent",
53
+ "workflow",
54
+ "agents"
55
+ ],
56
+ "dependencies": {
57
+ "acorn": "^8.16.0"
58
+ },
59
+ "peerDependencies": {
60
+ "@earendil-works/pi-ai": ">=0.80.0 <0.81.0",
61
+ "@earendil-works/pi-coding-agent": ">=0.80.0 <0.81.0",
62
+ "@earendil-works/pi-tui": ">=0.80.0 <0.81.0",
63
+ "pi-extensions-i18n": "^0.3.0",
64
+ "typebox": "*"
65
+ },
66
+ "devDependencies": {
67
+ "@earendil-works/pi-ai": "0.80.10",
68
+ "@earendil-works/pi-coding-agent": "0.80.10",
69
+ "@earendil-works/pi-tui": "0.80.10",
70
+ "@types/node": "24.12.4",
71
+ "pi-extensions-i18n": "^0.3.0",
72
+ "tsx": "4.23.1",
73
+ "typebox": "latest",
74
+ "typescript": "5.9.3"
75
+ }
76
+ }
package/src/agent.ts ADDED
@@ -0,0 +1,131 @@
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.ts";
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(this.cwd),
65
+ settingsManager: SettingsManager.create(this.cwd, agentDir),
66
+ customTools,
67
+ ...this.sessionOptions,
68
+ });
69
+
70
+ let removeAbortListener: (() => void) | undefined;
71
+ try {
72
+ if (options.signal?.aborted) throw new Error("Subagent was aborted");
73
+ if (options.signal) {
74
+ const onAbort = () => void session.abort();
75
+ options.signal.addEventListener("abort", onAbort, { once: true });
76
+ removeAbortListener = () => options.signal?.removeEventListener("abort", onAbort);
77
+ }
78
+
79
+ await session.prompt(this.buildPrompt(prompt, options as AgentRunOptions<any>, Boolean(options.schema)));
80
+ if (options.signal?.aborted) throw new Error("Subagent was aborted");
81
+
82
+ if (options.schema) {
83
+ if (!capture.called) {
84
+ throw new Error("Subagent finished without calling structured_output");
85
+ }
86
+ return capture.value as AgentRunResult<TSchemaDef>;
87
+ }
88
+
89
+ return this.lastAssistantText(session.messages) as AgentRunResult<TSchemaDef>;
90
+ } finally {
91
+ removeAbortListener?.();
92
+ session.dispose();
93
+ }
94
+ }
95
+
96
+ private buildPrompt(prompt: string, options: AgentRunOptions<any>, structured: boolean): string {
97
+ const parts = [
98
+ this.instructions,
99
+ options.instructions,
100
+ options.label ? `Task label: ${options.label}` : undefined,
101
+ prompt,
102
+ ].filter(Boolean);
103
+
104
+ if (structured) {
105
+ parts.push(
106
+ [
107
+ "Final output contract:",
108
+ "- Your final action MUST be a structured_output tool call.",
109
+ "- The structured_output arguments are the return value of this subagent.",
110
+ "- Do not emit a prose final answer instead of structured_output.",
111
+ "- If you need to inspect files or run commands first, do so, then call structured_output exactly once.",
112
+ ].join("\n"),
113
+ );
114
+ }
115
+
116
+ return parts.join("\n\n");
117
+ }
118
+
119
+ private lastAssistantText(messages: unknown[]): string {
120
+ for (let i = messages.length - 1; i >= 0; i--) {
121
+ const message = messages[i] as Partial<AssistantMessage> | undefined;
122
+ if (message?.role !== "assistant" || !Array.isArray(message.content)) continue;
123
+ const text = message.content
124
+ .filter((part): part is TextContent => part.type === "text")
125
+ .map((part) => part.text)
126
+ .join("");
127
+ if (text.trim()) return text;
128
+ }
129
+ return "";
130
+ }
131
+ }
package/src/config.ts ADDED
@@ -0,0 +1,55 @@
1
+ import { getAgentDir } from "@earendil-works/pi-coding-agent";
2
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
3
+ import { dirname, join } from "node:path";
4
+
5
+ /**
6
+ * Workflow 运行时配置。
7
+ *
8
+ * 优先级:JSON 配置文件(由 /workflow-config 斜杠命令写入)> 环境变量兜底 > 默认值。
9
+ * 环境变量仅作为兜底支持(PI_WORKFLOW_BACKEND / PI_WORKFLOW_ASYNC)。
10
+ */
11
+ export type WorkflowBackend = "workflow" | "subagent";
12
+
13
+ export interface WorkflowConfig {
14
+ /** 执行后端:内置 workflow agent 或 pi-interactive-subagents 子会话。 */
15
+ backend: WorkflowBackend;
16
+ /** 是否以后台异步模式运行 workflow(注册 workflow_cancel 工具)。 */
17
+ async: boolean;
18
+ }
19
+
20
+ const DEFAULTS: WorkflowConfig = { backend: "workflow", async: false };
21
+
22
+ /** 配置文件路径:<agentDir>/extensions/pi-dynamic-workflows/config.json */
23
+ export function configPath(): string {
24
+ return join(getAgentDir(), "extensions", "pi-dynamic-workflows", "config.json");
25
+ }
26
+
27
+ function fromEnv(): WorkflowConfig {
28
+ const config = { ...DEFAULTS };
29
+ if (process.env.PI_WORKFLOW_BACKEND === "subagent") config.backend = "subagent";
30
+ if (process.env.PI_WORKFLOW_ASYNC === "true") config.async = true;
31
+ return config;
32
+ }
33
+
34
+ /** 读取配置:JSON 文件覆盖环境变量兜底值。文件缺失或损坏时静默回退到环境变量/默认值。 */
35
+ export function loadConfig(): WorkflowConfig {
36
+ const config = fromEnv();
37
+ try {
38
+ if (!existsSync(configPath())) return config;
39
+ const parsed = JSON.parse(readFileSync(configPath(), "utf-8")) as Partial<WorkflowConfig>;
40
+ if (parsed.backend === "workflow" || parsed.backend === "subagent") config.backend = parsed.backend;
41
+ if (typeof parsed.async === "boolean") config.async = parsed.async;
42
+ } catch {
43
+ // 配置损坏:保留环境变量兜底值,不抛出。
44
+ }
45
+ return config;
46
+ }
47
+
48
+ /** 合并写入配置,返回写入后的完整配置。 */
49
+ export function saveConfig(partial: Partial<WorkflowConfig>): WorkflowConfig {
50
+ const merged: WorkflowConfig = { ...loadConfig(), ...partial };
51
+ const path = configPath();
52
+ mkdirSync(dirname(path), { recursive: true });
53
+ writeFileSync(path, `${JSON.stringify(merged, null, 2)}\n`, "utf-8");
54
+ return merged;
55
+ }