lilflow 0.1.0 → 0.2.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 +178 -51
- package/package.json +4 -3
- package/plugin/plugin.json +7 -0
- package/plugin/skills/lilflow-workflow-driver/SKILL.md +110 -0
- package/src/agents/index.js +41 -1
- package/src/agents/output-file.js +204 -0
- package/src/cli.js +17 -9
- package/src/run-workflow.js +202 -1
- package/src/session-bridge.js +644 -0
- package/src/session-prompt.js +59 -0
- package/src/session-runner.js +150 -0
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { resolveAgentBinary } from "./agents/index.js";
|
|
5
|
+
import { buildSessionSystemPrompt } from "./session-prompt.js";
|
|
6
|
+
|
|
7
|
+
const HERE = path.dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Path to the bundled plugin that exposes the lilflow skill. The same layout
|
|
11
|
+
* works for both providers — Claude Code loads it natively, and OpenCode
|
|
12
|
+
* picks it up through the oh-my-opencode Claude Code compatibility layer.
|
|
13
|
+
*
|
|
14
|
+
* @returns {string} Absolute directory path.
|
|
15
|
+
*/
|
|
16
|
+
export function getBundledPluginPath() {
|
|
17
|
+
return path.resolve(HERE, "..", "plugin");
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Execute a workflow in session mode. Spawns one long-lived agent process,
|
|
22
|
+
* injects the workflow-aware system prompt, and lets the agent drive the bridge.
|
|
23
|
+
*
|
|
24
|
+
* @param {object} options - Runtime options.
|
|
25
|
+
* @param {object} options.workflow - Loaded workflow with `mode: "session"`.
|
|
26
|
+
* @param {string} options.workflowPath - Absolute workflow path.
|
|
27
|
+
* @param {string} options.cwd - Working directory.
|
|
28
|
+
* @param {object} options.env - Process environment.
|
|
29
|
+
* @param {(message: string) => void} options.stdout - Output writer.
|
|
30
|
+
* @param {(message: string) => void} options.stderr - Error writer.
|
|
31
|
+
* @param {object} options.flowConfig - Resolved flow config.
|
|
32
|
+
* @param {{runId: string}} options.eventLogger - Already-initialized logger.
|
|
33
|
+
* @param {typeof spawn} [options.spawnProcess=spawn] - Injection for tests.
|
|
34
|
+
* @returns {Promise<{exitCode: number, runId: string}>} Session result.
|
|
35
|
+
*/
|
|
36
|
+
export async function executeSessionWorkflow(options) {
|
|
37
|
+
const {
|
|
38
|
+
workflow,
|
|
39
|
+
workflowPath,
|
|
40
|
+
cwd,
|
|
41
|
+
env,
|
|
42
|
+
stdout,
|
|
43
|
+
stderr,
|
|
44
|
+
flowConfig,
|
|
45
|
+
eventLogger,
|
|
46
|
+
spawnProcess = spawn
|
|
47
|
+
} = options;
|
|
48
|
+
|
|
49
|
+
if (workflow.mode !== "session") {
|
|
50
|
+
throw new Error("executeSessionWorkflow requires workflow.mode === 'session'.");
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (!workflow.session || (workflow.session.provider !== "claude-code" && workflow.session.provider !== "opencode")) {
|
|
54
|
+
throw new Error("Session mode supports provider 'claude-code' or 'opencode'.");
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const runId = eventLogger.runId;
|
|
58
|
+
const systemPrompt = buildSessionSystemPrompt(workflow, runId);
|
|
59
|
+
const pluginPath = getBundledPluginPath();
|
|
60
|
+
|
|
61
|
+
const childEnv = {
|
|
62
|
+
...env,
|
|
63
|
+
FLOW_RUN_ID: runId,
|
|
64
|
+
FLOW_WORKFLOW_PATH: workflowPath,
|
|
65
|
+
// Both providers recognize CLAUDE_PLUGINS_PATH when oh-my-opencode loads
|
|
66
|
+
// Claude Code plugins.
|
|
67
|
+
CLAUDE_PLUGINS_PATH: [pluginPath, env.CLAUDE_PLUGINS_PATH].filter(Boolean).join(path.delimiter)
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const provider = workflow.session.provider;
|
|
71
|
+
const { binary, args, interactive } = provider === "claude-code"
|
|
72
|
+
? await buildClaudeArgs(workflow, systemPrompt, flowConfig, env)
|
|
73
|
+
: await buildOpencodeArgs(workflow, systemPrompt, flowConfig, env);
|
|
74
|
+
|
|
75
|
+
stdout(`Starting session-mode workflow '${workflow.name}' (run ${runId}, provider ${provider}).`);
|
|
76
|
+
|
|
77
|
+
if (provider === "opencode" && interactive) {
|
|
78
|
+
// OpenCode does not expose a --append-system-prompt flag, so we surface the
|
|
79
|
+
// system prompt to the user before the TUI takes over. The user pastes it
|
|
80
|
+
// (or the skill picks it up from FLOW_RUN_ID and re-fetches workflow state).
|
|
81
|
+
stdout("");
|
|
82
|
+
stdout("=== Workflow system prompt (paste into opencode if not auto-loaded) ===");
|
|
83
|
+
stdout(systemPrompt);
|
|
84
|
+
stdout("=== End system prompt ===");
|
|
85
|
+
stdout("");
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return new Promise((resolve, reject) => {
|
|
89
|
+
const child = spawnProcess(binary, args, {
|
|
90
|
+
cwd,
|
|
91
|
+
env: childEnv,
|
|
92
|
+
stdio: "inherit"
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
child.on("error", (error) => {
|
|
96
|
+
reject(new Error(`Failed to start ${provider} agent: ${error.message}`));
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
child.on("close", (code, signal) => {
|
|
100
|
+
if (signal) {
|
|
101
|
+
stderr(`${provider} agent exited due to signal ${signal}`);
|
|
102
|
+
}
|
|
103
|
+
resolve({ exitCode: code ?? 1, runId });
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Build claude CLI invocation arguments.
|
|
110
|
+
*/
|
|
111
|
+
async function buildClaudeArgs(workflow, systemPrompt, flowConfig, env) {
|
|
112
|
+
const binary = await resolveAgentBinary("claude-code", flowConfig, env);
|
|
113
|
+
const args = [];
|
|
114
|
+
|
|
115
|
+
if (workflow.session.model) {
|
|
116
|
+
args.push("--model", workflow.session.model);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (Array.isArray(workflow.session.allowTools) && workflow.session.allowTools.length > 0) {
|
|
120
|
+
args.push("--allowedTools", ...workflow.session.allowTools);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
args.push("--append-system-prompt", systemPrompt);
|
|
124
|
+
args.push("--dangerously-skip-permissions");
|
|
125
|
+
|
|
126
|
+
return { binary, args, interactive: true };
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Build opencode CLI invocation arguments. Opencode has no
|
|
131
|
+
* `--append-system-prompt` equivalent — the system prompt is surfaced to the
|
|
132
|
+
* user above the TUI and the skill (loaded via oh-my-opencode's Claude Code
|
|
133
|
+
* compatibility layer) picks up the rest from FLOW_RUN_ID.
|
|
134
|
+
*/
|
|
135
|
+
async function buildOpencodeArgs(workflow, _systemPrompt, flowConfig, env) {
|
|
136
|
+
const binary = await resolveAgentBinary("opencode", flowConfig, env);
|
|
137
|
+
const args = [];
|
|
138
|
+
|
|
139
|
+
if (workflow.session.model) {
|
|
140
|
+
args.push("--model", workflow.session.model);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (Array.isArray(workflow.session.plugins)) {
|
|
144
|
+
for (const plugin of workflow.session.plugins) {
|
|
145
|
+
args.push("--plugin", plugin);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return { binary, args, interactive: true };
|
|
150
|
+
}
|