@geekbeer/minion 3.16.1 → 3.17.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.
@@ -98,13 +98,24 @@ async function pollOnce() {
98
98
  }
99
99
 
100
100
  /**
101
- * Execute a single pending DAG node:
101
+ * Execute a single pending DAG node.
102
+ * Routes to skill or transform execution based on node_type.
103
+ */
104
+ async function executeNode(node) {
105
+ if (node.node_type === 'transform') {
106
+ return executeTransformNode(node)
107
+ }
108
+ return executeSkillNode(node)
109
+ }
110
+
111
+ /**
112
+ * Execute a skill node:
102
113
  * 1. Claim the node
103
114
  * 2. Fetch the skill from HQ
104
115
  * 3. Run the skill locally (with input_data injected)
105
116
  * 4. Report completion with output_data
106
117
  */
107
- async function executeNode(node) {
118
+ async function executeSkillNode(node) {
108
119
  const {
109
120
  node_execution_id,
110
121
  execution_id,
@@ -119,7 +130,7 @@ async function executeNode(node) {
119
130
  } = node
120
131
 
121
132
  console.log(
122
- `[DagPoller] Executing node "${node_id}" of DAG "${dag_workflow_name}" ` +
133
+ `[DagPoller] Executing skill node "${node_id}" of DAG "${dag_workflow_name}" ` +
123
134
  `(skill: ${resolvedSkillName || skill_version_id}, scope: "${scope_path || 'root'}", role: ${assigned_role})`
124
135
  )
125
136
 
@@ -212,15 +223,147 @@ async function executeNode(node) {
212
223
  // This will be handled by dag-node-executor.js which watches the session.
213
224
 
214
225
  } catch (err) {
215
- console.error(`[DagPoller] Failed to execute node ${node_id}: ${err.message}`)
226
+ console.error(`[DagPoller] Failed to execute skill node ${node_id}: ${err.message}`)
227
+ try {
228
+ await reportNodeComplete(node_execution_id, 'failed', null, err.message)
229
+ } catch {
230
+ // best-effort
231
+ }
232
+ }
233
+ }
234
+
235
+ /**
236
+ * Execute a transform node:
237
+ * 1. Claim the node
238
+ * 2. Create an ephemeral skill from the transform_instruction
239
+ * 3. Run the ephemeral skill via existing /api/skills/run
240
+ * 4. Clean up ephemeral skill directory
241
+ */
242
+ async function executeTransformNode(node) {
243
+ const {
244
+ node_execution_id,
245
+ execution_id,
246
+ dag_workflow_name,
247
+ node_id,
248
+ scope_path,
249
+ assigned_role,
250
+ input_data,
251
+ transform_instruction,
252
+ } = node
253
+
254
+ console.log(
255
+ `[DagPoller] Executing transform node "${node_id}" of DAG "${dag_workflow_name}" ` +
256
+ `(scope: "${scope_path || 'root'}", role: ${assigned_role})`
257
+ )
258
+
259
+ const ephemeralName = `_dag_transform_${node_execution_id.substring(0, 8)}`
260
+ const fs = require('fs').promises
261
+ const path = require('path')
262
+ const skillDir = path.join(config.HOME_DIR || process.env.HOME, '.claude', 'skills', ephemeralName)
263
+
264
+ try {
265
+ // 1. Claim the node
266
+ try {
267
+ await dagRequest('/claim-node', {
268
+ method: 'POST',
269
+ body: JSON.stringify({ node_execution_id }),
270
+ })
271
+ } catch (claimErr) {
272
+ if (claimErr.statusCode === 409) {
273
+ console.log(`[DagPoller] Transform node ${node_id} already claimed, skipping`)
274
+ return
275
+ }
276
+ throw claimErr
277
+ }
278
+
279
+ // 2. Create ephemeral skill from transform_instruction
280
+ const skillContent = buildTransformSkillContent(transform_instruction, input_data)
281
+ await fs.mkdir(skillDir, { recursive: true })
282
+ await fs.writeFile(path.join(skillDir, 'SKILL.md'), skillContent, 'utf-8')
283
+
284
+ // 3. Run the ephemeral skill
285
+ const runPayload = {
286
+ skill_name: ephemeralName,
287
+ execution_id,
288
+ workflow_name: dag_workflow_name,
289
+ role: assigned_role,
290
+ dag_node_id: node_id,
291
+ dag_input_data: input_data,
292
+ dag_node_execution_id: node_execution_id,
293
+ }
294
+
295
+ const runUrl = `http://localhost:${config.AGENT_PORT || 8080}/api/skills/run`
296
+ const runResp = await fetch(runUrl, {
297
+ method: 'POST',
298
+ headers: {
299
+ 'Content-Type': 'application/json',
300
+ 'Authorization': `Bearer ${config.API_TOKEN}`,
301
+ },
302
+ body: JSON.stringify(runPayload),
303
+ })
304
+
305
+ if (!runResp.ok) {
306
+ const errData = await runResp.json().catch(() => ({}))
307
+ console.error(`[DagPoller] Transform run failed: ${errData.error || runResp.status}`)
308
+ await reportNodeComplete(
309
+ node_execution_id,
310
+ 'failed',
311
+ null,
312
+ `Failed to start transform: ${errData.error || 'unknown error'}`
313
+ )
314
+ return
315
+ }
316
+
317
+ const runData = await runResp.json()
318
+ console.log(
319
+ `[DagPoller] Transform started for node "${node_id}" ` +
320
+ `(session: ${runData.session_name}). Completion reported by post-execution hook.`
321
+ )
322
+
323
+ } catch (err) {
324
+ console.error(`[DagPoller] Failed to execute transform node ${node_id}: ${err.message}`)
216
325
  try {
217
326
  await reportNodeComplete(node_execution_id, 'failed', null, err.message)
218
327
  } catch {
219
328
  // best-effort
220
329
  }
330
+ } finally {
331
+ // 4. Clean up ephemeral skill directory
332
+ try {
333
+ await fs.rm(skillDir, { recursive: true, force: true })
334
+ } catch {
335
+ // best-effort cleanup
336
+ }
221
337
  }
222
338
  }
223
339
 
340
+ /**
341
+ * Build SKILL.md content for a transform node's ephemeral skill.
342
+ */
343
+ function buildTransformSkillContent(instruction, inputData) {
344
+ return [
345
+ '---',
346
+ 'name: dag-transform',
347
+ 'description: DAG Transform Node',
348
+ '---',
349
+ '',
350
+ 'You are a data transformation step in a DAG workflow.',
351
+ '',
352
+ '## Input Data',
353
+ '```json',
354
+ JSON.stringify(inputData, null, 2),
355
+ '```',
356
+ '',
357
+ '## Transform Instruction',
358
+ instruction,
359
+ '',
360
+ '## Task',
361
+ 'Apply the transform instruction to the input data above.',
362
+ 'Output the result as a JSON object in an "## Output Data" section with a json code block.',
363
+ 'Do NOT output anything other than the Output Data section.',
364
+ ].join('\n')
365
+ }
366
+
224
367
  /**
225
368
  * Resolve skill_version_id to skill name via HQ API.
226
369
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@geekbeer/minion",
3
- "version": "3.16.1",
3
+ "version": "3.17.0",
4
4
  "description": "AI Agent runtime for Minion - manages status and skill deployment on VPS",
5
5
  "main": "linux/server.js",
6
6
  "bin": {