@geekbeer/minion 3.16.1 → 3.22.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/core/lib/dag-step-poller.js +158 -6
- package/core/lib/llm-checker.js +4 -7
- package/core/lib/template-expander.js +21 -18
- package/core/llm-dispatch/mcp-server.js +185 -0
- package/core/llm-dispatch/session-pool.js +97 -0
- package/core/llm-plugins/claude/index.js +151 -0
- package/core/llm-plugins/claude/stream.js +166 -0
- package/core/llm-plugins/codex/index.js +161 -0
- package/core/llm-plugins/gemini/index.js +104 -0
- package/core/llm-plugins/lib/active.js +23 -0
- package/core/llm-plugins/lib/mcp-registration.js +132 -0
- package/core/llm-plugins/lib/skill-dirs.js +67 -0
- package/core/llm-plugins/lib/spawn-helper.js +88 -0
- package/core/llm-plugins/registry.js +168 -0
- package/core/llm-plugins/types.js +85 -0
- package/core/routes/llm.js +89 -0
- package/core/routes/skills.js +112 -57
- package/docs/api-reference.md +439 -0
- package/docs/task-guides.md +220 -0
- package/linux/bin/hq +168 -15
- package/linux/minion-cli.sh +14 -0
- package/linux/routes/chat.js +121 -2
- package/linux/routine-runner.js +22 -7
- package/linux/server.js +2 -0
- package/linux/workflow-runner.js +26 -8
- package/package.json +1 -1
- package/rules/core.md +13 -7
- package/win/bin/hq.ps1 +155 -27
- package/win/routes/chat.js +115 -2
- package/win/routine-runner.js +20 -6
- package/win/server.js +2 -0
- package/win/workflow-runner.js +20 -7
package/win/bin/hq.ps1
CHANGED
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
HQ API helper for minion chat context (Windows version)
|
|
5
5
|
|
|
6
6
|
.DESCRIPTION
|
|
7
|
-
Fetches
|
|
8
|
-
Used by Claude CLI during chat to retrieve information about
|
|
9
|
-
skills, workflows,
|
|
7
|
+
Fetches and manipulates resources on the HQ server API.
|
|
8
|
+
Used by Claude CLI during chat to retrieve / edit information about
|
|
9
|
+
skills, workflows, projects, and DAG workflows.
|
|
10
10
|
|
|
11
11
|
Environment variables (inherited from minion server):
|
|
12
12
|
HQ_URL - HQ server URL (e.g., https://minion-agent.com)
|
|
@@ -17,6 +17,11 @@
|
|
|
17
17
|
.\hq.ps1 fetch workflow <name>
|
|
18
18
|
.\hq.ps1 fetch project <id>
|
|
19
19
|
.\hq.ps1 fetch project-context <id>
|
|
20
|
+
.\hq.ps1 fetch dag-workflow <id>
|
|
21
|
+
.\hq.ps1 fetch dag-execution <id>
|
|
22
|
+
.\hq.ps1 create dag-workflow <body.json>
|
|
23
|
+
.\hq.ps1 put dag-workflow <id> <body.json>
|
|
24
|
+
.\hq.ps1 publish dag-workflow <id>
|
|
20
25
|
#>
|
|
21
26
|
|
|
22
27
|
param(
|
|
@@ -24,15 +29,17 @@ param(
|
|
|
24
29
|
[string]$Command,
|
|
25
30
|
|
|
26
31
|
[Parameter(Position = 1)]
|
|
27
|
-
[string]$
|
|
32
|
+
[string]$Arg1,
|
|
28
33
|
|
|
29
34
|
[Parameter(Position = 2)]
|
|
30
|
-
[string]$
|
|
35
|
+
[string]$Arg2,
|
|
36
|
+
|
|
37
|
+
[Parameter(Position = 3)]
|
|
38
|
+
[string]$Arg3
|
|
31
39
|
)
|
|
32
40
|
|
|
33
41
|
$ErrorActionPreference = 'Stop'
|
|
34
42
|
|
|
35
|
-
# Validate required environment variables
|
|
36
43
|
if (-not $env:HQ_URL) {
|
|
37
44
|
Write-Error "Error: HQ_URL is not set"
|
|
38
45
|
exit 1
|
|
@@ -47,10 +54,46 @@ $Headers = @{ 'Authorization' = "Bearer $($env:API_TOKEN)" }
|
|
|
47
54
|
|
|
48
55
|
function Invoke-HqApi {
|
|
49
56
|
param([string]$Url)
|
|
50
|
-
|
|
51
57
|
try {
|
|
52
58
|
$response = Invoke-RestMethod -Uri $Url -Headers $Headers -Method Get -ErrorAction Stop
|
|
53
|
-
$response | ConvertTo-Json -Depth
|
|
59
|
+
$response | ConvertTo-Json -Depth 20
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
$statusCode = $_.Exception.Response.StatusCode.value__
|
|
63
|
+
Write-Error "Error: HQ API returned HTTP $statusCode"
|
|
64
|
+
Write-Error $_.ErrorDetails.Message
|
|
65
|
+
exit 1
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function Assert-ValidJsonFile {
|
|
70
|
+
param([string]$Path)
|
|
71
|
+
if (-not (Test-Path -Path $Path -PathType Leaf)) {
|
|
72
|
+
Write-Error "Error: file not found: $Path"
|
|
73
|
+
exit 1
|
|
74
|
+
}
|
|
75
|
+
try {
|
|
76
|
+
$raw = Get-Content -Raw -Path $Path
|
|
77
|
+
$null = $raw | ConvertFrom-Json -ErrorAction Stop
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
Write-Error "Error: invalid JSON syntax in $Path"
|
|
81
|
+
Write-Error $_.Exception.Message
|
|
82
|
+
exit 1
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function Send-JsonRequest {
|
|
87
|
+
param(
|
|
88
|
+
[string]$Method,
|
|
89
|
+
[string]$Url,
|
|
90
|
+
[string]$Path
|
|
91
|
+
)
|
|
92
|
+
$raw = Get-Content -Raw -Path $Path
|
|
93
|
+
try {
|
|
94
|
+
$response = Invoke-RestMethod -Uri $Url -Headers $Headers -Method $Method `
|
|
95
|
+
-ContentType 'application/json' -Body $raw -ErrorAction Stop
|
|
96
|
+
$response | ConvertTo-Json -Depth 20
|
|
54
97
|
}
|
|
55
98
|
catch {
|
|
56
99
|
$statusCode = $_.Exception.Response.StatusCode.value__
|
|
@@ -60,49 +103,134 @@ function Invoke-HqApi {
|
|
|
60
103
|
}
|
|
61
104
|
}
|
|
62
105
|
|
|
106
|
+
function Send-EmptyPost {
|
|
107
|
+
param([string]$Url)
|
|
108
|
+
try {
|
|
109
|
+
$response = Invoke-RestMethod -Uri $Url -Headers $Headers -Method Post `
|
|
110
|
+
-ContentType 'application/json' -Body '' -ErrorAction Stop
|
|
111
|
+
$response | ConvertTo-Json -Depth 20
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
$statusCode = $_.Exception.Response.StatusCode.value__
|
|
115
|
+
Write-Error "Error: HQ API returned HTTP $statusCode"
|
|
116
|
+
Write-Error $_.ErrorDetails.Message
|
|
117
|
+
exit 1
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function Show-Usage {
|
|
122
|
+
Write-Host "HQ API helper for minion chat" -ForegroundColor Cyan
|
|
123
|
+
Write-Host ""
|
|
124
|
+
Write-Host "Usage:"
|
|
125
|
+
Write-Host " hq fetch skill <name> - Get skill details"
|
|
126
|
+
Write-Host " hq fetch workflow <name> - Get workflow details"
|
|
127
|
+
Write-Host " hq fetch project <id> - Get project info"
|
|
128
|
+
Write-Host " hq fetch project-context <id> - Get project context"
|
|
129
|
+
Write-Host " hq fetch dag-workflow <id> - Get DAG workflow details"
|
|
130
|
+
Write-Host " hq fetch dag-execution <id> - Get DAG execution details"
|
|
131
|
+
Write-Host " hq create dag-workflow <body.json> - Create a DAG workflow (PM only)"
|
|
132
|
+
Write-Host " hq put dag-workflow <id> <body.json> - Update DAG workflow draft (PM only)"
|
|
133
|
+
Write-Host " hq publish dag-workflow <id> - Publish the draft as a new version (PM only)"
|
|
134
|
+
}
|
|
135
|
+
|
|
63
136
|
switch ($Command) {
|
|
64
137
|
'fetch' {
|
|
138
|
+
$Resource = $Arg1
|
|
139
|
+
$Identifier = $Arg2
|
|
65
140
|
if (-not $Resource -or -not $Identifier) {
|
|
66
|
-
Write-Error "Usage: hq fetch {skill|workflow|project|project-context} <identifier>"
|
|
141
|
+
Write-Error "Usage: hq fetch {skill|workflow|project|project-context|dag-workflow|dag-execution} <identifier>"
|
|
67
142
|
exit 1
|
|
68
143
|
}
|
|
69
144
|
|
|
70
145
|
switch ($Resource) {
|
|
71
|
-
'skill'
|
|
72
|
-
|
|
73
|
-
}
|
|
74
|
-
'workflow' {
|
|
75
|
-
Invoke-HqApi "$BaseUrl/workflows/$Identifier"
|
|
76
|
-
}
|
|
146
|
+
'skill' { Invoke-HqApi "$BaseUrl/skills/$Identifier" }
|
|
147
|
+
'workflow' { Invoke-HqApi "$BaseUrl/workflows/$Identifier" }
|
|
77
148
|
'project' {
|
|
78
149
|
$response = Invoke-RestMethod -Uri "$BaseUrl/me/projects" -Headers $Headers -Method Get
|
|
79
150
|
$project = $response.projects | Where-Object { $_.id -eq $Identifier }
|
|
80
151
|
if ($project) {
|
|
81
|
-
$project | ConvertTo-Json -Depth
|
|
152
|
+
$project | ConvertTo-Json -Depth 20
|
|
82
153
|
}
|
|
83
154
|
else {
|
|
84
155
|
Write-Error "Project not found: $Identifier"
|
|
85
156
|
exit 1
|
|
86
157
|
}
|
|
87
158
|
}
|
|
88
|
-
'project-context' {
|
|
89
|
-
|
|
90
|
-
}
|
|
159
|
+
'project-context' { Invoke-HqApi "$BaseUrl/me/project/$Identifier/context" }
|
|
160
|
+
'dag-workflow' { Invoke-HqApi "$BaseUrl/dag-workflows/$Identifier" }
|
|
161
|
+
'dag-execution' { Invoke-HqApi "$BaseUrl/dag-executions/$Identifier" }
|
|
91
162
|
default {
|
|
92
163
|
Write-Error "Unknown resource: $Resource"
|
|
93
|
-
Write-Error "Usage: hq fetch {skill|workflow|project|project-context} <identifier>"
|
|
164
|
+
Write-Error "Usage: hq fetch {skill|workflow|project|project-context|dag-workflow|dag-execution} <identifier>"
|
|
165
|
+
exit 1
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
'create' {
|
|
171
|
+
$Resource = $Arg1
|
|
172
|
+
switch ($Resource) {
|
|
173
|
+
'dag-workflow' {
|
|
174
|
+
$BodyFile = $Arg2
|
|
175
|
+
if (-not $BodyFile) {
|
|
176
|
+
Write-Error "Usage: hq create dag-workflow <body.json>"
|
|
177
|
+
Write-Error " body.json must contain at least { project_id, name } and optionally { graph, content, change_summary }"
|
|
178
|
+
exit 1
|
|
179
|
+
}
|
|
180
|
+
Assert-ValidJsonFile -Path $BodyFile
|
|
181
|
+
Send-JsonRequest -Method 'Post' -Url "$BaseUrl/dag-workflows" -Path $BodyFile
|
|
182
|
+
}
|
|
183
|
+
default {
|
|
184
|
+
Write-Error "Unknown create resource: $Resource"
|
|
185
|
+
Write-Error "Usage: hq create dag-workflow <body.json>"
|
|
186
|
+
exit 1
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
'put' {
|
|
192
|
+
$Resource = $Arg1
|
|
193
|
+
switch ($Resource) {
|
|
194
|
+
'dag-workflow' {
|
|
195
|
+
$Id = $Arg2
|
|
196
|
+
$BodyFile = $Arg3
|
|
197
|
+
if (-not $Id -or -not $BodyFile) {
|
|
198
|
+
Write-Error "Usage: hq put dag-workflow <id> <body.json>"
|
|
199
|
+
Write-Error " body.json may contain { graph, content, change_summary, name, is_active, maturity }"
|
|
200
|
+
exit 1
|
|
201
|
+
}
|
|
202
|
+
Assert-ValidJsonFile -Path $BodyFile
|
|
203
|
+
Send-JsonRequest -Method 'Put' -Url "$BaseUrl/dag-workflows/$Id" -Path $BodyFile
|
|
204
|
+
}
|
|
205
|
+
default {
|
|
206
|
+
Write-Error "Unknown put resource: $Resource"
|
|
207
|
+
Write-Error "Usage: hq put dag-workflow <id> <body.json>"
|
|
94
208
|
exit 1
|
|
95
209
|
}
|
|
96
210
|
}
|
|
97
211
|
}
|
|
212
|
+
|
|
213
|
+
'publish' {
|
|
214
|
+
$Resource = $Arg1
|
|
215
|
+
switch ($Resource) {
|
|
216
|
+
'dag-workflow' {
|
|
217
|
+
$Id = $Arg2
|
|
218
|
+
if (-not $Id) {
|
|
219
|
+
Write-Error "Usage: hq publish dag-workflow <id>"
|
|
220
|
+
exit 1
|
|
221
|
+
}
|
|
222
|
+
Send-EmptyPost -Url "$BaseUrl/dag-workflows/$Id/publish"
|
|
223
|
+
}
|
|
224
|
+
default {
|
|
225
|
+
Write-Error "Unknown publish resource: $Resource"
|
|
226
|
+
Write-Error "Usage: hq publish dag-workflow <id>"
|
|
227
|
+
exit 1
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
98
232
|
default {
|
|
99
|
-
|
|
100
|
-
Write-Host ""
|
|
101
|
-
Write-Host "Usage:"
|
|
102
|
-
Write-Host " hq fetch skill <name> - Get skill details"
|
|
103
|
-
Write-Host " hq fetch workflow <name> - Get workflow details"
|
|
104
|
-
Write-Host " hq fetch project <id> - Get project info"
|
|
105
|
-
Write-Host " hq fetch project-context <id> - Get project context"
|
|
233
|
+
Show-Usage
|
|
106
234
|
exit 1
|
|
107
235
|
}
|
|
108
236
|
}
|
package/win/routes/chat.js
CHANGED
|
@@ -21,6 +21,7 @@ const { config } = require('../../core/config')
|
|
|
21
21
|
const chatStore = require('../../core/stores/chat-store')
|
|
22
22
|
const { DATA_DIR } = require('../../core/lib/platform')
|
|
23
23
|
const { runEndOfDay } = require('../../core/lib/end-of-day')
|
|
24
|
+
const { getActivePrimary } = require('../../core/llm-plugins/lib/active')
|
|
24
25
|
|
|
25
26
|
let activeChatChild = null
|
|
26
27
|
let wslModeActive = false
|
|
@@ -398,6 +399,45 @@ async function buildContextPrefix(message, context, sessionId) {
|
|
|
398
399
|
)
|
|
399
400
|
}
|
|
400
401
|
break
|
|
402
|
+
case 'dag-workflow':
|
|
403
|
+
if (context.projectId && context.dagWorkflowId) {
|
|
404
|
+
parts.push(
|
|
405
|
+
`ユーザーはHQダッシュボードで DAG ワークフロー (ID: ${context.dagWorkflowId}) のエディタ/詳細を閲覧しています。`,
|
|
406
|
+
`DAG ワークフローはノード/エッジ形式でスキル間の依存関係を表現し、fan-out / join / conditional / transform / review をサポートします。`,
|
|
407
|
+
`DAG ワークフロー情報を取得するには以下を実行してください:`,
|
|
408
|
+
` hq fetch dag-workflow ${context.dagWorkflowId}`,
|
|
409
|
+
`プロジェクトコンテキスト:`,
|
|
410
|
+
` hq fetch project-context ${context.projectId}`,
|
|
411
|
+
`PMロールの場合、graph JSON を直接編集できます:`,
|
|
412
|
+
` hq put dag-workflow ${context.dagWorkflowId} <body.json> # ドラフト更新 (構造チェックのみ)`,
|
|
413
|
+
` hq publish dag-workflow ${context.dagWorkflowId} # ドラフトを新バージョンとして公開 (フル検証)`,
|
|
414
|
+
`新規作成は: hq create dag-workflow <body.json>`,
|
|
415
|
+
`DAG の構造(nodes/edges/node types/scope_path 等)や実行フローの詳細は ~/.minion/docs/api-reference.md の「DAG Workflows」セクション、および ~/.minion/docs/task-guides.md の「DAG ワークフロー」セクションを参照してください。`,
|
|
416
|
+
`取得した内容をもとに回答してください。`
|
|
417
|
+
)
|
|
418
|
+
}
|
|
419
|
+
break
|
|
420
|
+
case 'dag-execution':
|
|
421
|
+
if (context.projectId && context.dagExecutionId) {
|
|
422
|
+
parts.push(
|
|
423
|
+
`ユーザーはHQダッシュボードで DAG 実行 (ID: ${context.dagExecutionId}) の詳細を閲覧しています。`,
|
|
424
|
+
`DAG 実行の詳細(graph_snapshot + 各 node_executions の状態)を取得するには以下を実行してください:`,
|
|
425
|
+
` hq fetch dag-execution ${context.dagExecutionId}`
|
|
426
|
+
)
|
|
427
|
+
if (context.dagWorkflowId) {
|
|
428
|
+
parts.push(
|
|
429
|
+
`対応するDAGワークフロー定義:`,
|
|
430
|
+
` hq fetch dag-workflow ${context.dagWorkflowId}`
|
|
431
|
+
)
|
|
432
|
+
}
|
|
433
|
+
parts.push(
|
|
434
|
+
`プロジェクトコンテキスト:`,
|
|
435
|
+
` hq fetch project-context ${context.projectId}`,
|
|
436
|
+
`ノード状態の意味(pending/waiting/running/completed/failed/skipped, review_status, scope_path 等)は ~/.minion/docs/api-reference.md の「DAG Workflows」セクションを参照してください。`,
|
|
437
|
+
`取得した内容をもとに回答してください。`
|
|
438
|
+
)
|
|
439
|
+
}
|
|
440
|
+
break
|
|
401
441
|
}
|
|
402
442
|
}
|
|
403
443
|
if (parts.length > 0) return `${parts.join('\n')}\n\n${message}`
|
|
@@ -416,7 +456,70 @@ function getLlmBinary() {
|
|
|
416
456
|
* Tracks block types to correctly forward tool_use vs text events
|
|
417
457
|
* and counts turns for session management.
|
|
418
458
|
*/
|
|
419
|
-
function streamLlmResponse(res, prompt, sessionId, workspaceId, originalMessage) {
|
|
459
|
+
async function streamLlmResponse(res, prompt, sessionId, workspaceId, originalMessage) {
|
|
460
|
+
const primary = getActivePrimary()
|
|
461
|
+
if (primary) {
|
|
462
|
+
return streamViaPlugin(primary, res, prompt, sessionId, workspaceId, originalMessage)
|
|
463
|
+
}
|
|
464
|
+
return streamViaLegacyLlmCommand(res, prompt, sessionId, workspaceId, originalMessage)
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
async function streamViaPlugin(plugin, res, prompt, sessionId, workspaceId, originalMessage) {
|
|
468
|
+
const input = { prompt }
|
|
469
|
+
const activeRef = { current: null }
|
|
470
|
+
|
|
471
|
+
let fullResponse = ''
|
|
472
|
+
let resolvedSessionId = sessionId || null
|
|
473
|
+
let turnCount = 0
|
|
474
|
+
|
|
475
|
+
const emit = event => {
|
|
476
|
+
if (event.type === 'session') {
|
|
477
|
+
resolvedSessionId = event.sessionId
|
|
478
|
+
} else if (event.type === 'delta') {
|
|
479
|
+
fullResponse += event.content
|
|
480
|
+
res.write(`data: ${JSON.stringify({ type: 'delta', content: event.content })}\n\n`)
|
|
481
|
+
} else if (event.type === 'text') {
|
|
482
|
+
fullResponse += event.content
|
|
483
|
+
res.write(`data: ${JSON.stringify({ type: 'text', content: event.content })}\n\n`)
|
|
484
|
+
turnCount++
|
|
485
|
+
} else {
|
|
486
|
+
res.write(`data: ${JSON.stringify(event)}\n\n`)
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
res.on('close', () => { activeRef.current?.kill?.('SIGTERM') })
|
|
491
|
+
|
|
492
|
+
let output
|
|
493
|
+
if (plugin.capabilities.streaming && typeof plugin.stream === 'function') {
|
|
494
|
+
output = await plugin.stream(input, emit, { resumeSessionId: sessionId, activeChildRef: activeRef })
|
|
495
|
+
} else {
|
|
496
|
+
output = await plugin.invoke(input)
|
|
497
|
+
if (output.text) {
|
|
498
|
+
fullResponse = output.text
|
|
499
|
+
res.write(`data: ${JSON.stringify({ type: 'text', content: output.text })}\n\n`)
|
|
500
|
+
turnCount = 1
|
|
501
|
+
}
|
|
502
|
+
if (output.error) {
|
|
503
|
+
res.write(`data: ${JSON.stringify({ type: 'error', error: output.error.message })}\n\n`)
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
resolvedSessionId = output?.metadata?.sessionId || resolvedSessionId
|
|
507
|
+
|
|
508
|
+
if (resolvedSessionId) {
|
|
509
|
+
if (!sessionId) {
|
|
510
|
+
await chatStore.addMessage(resolvedSessionId, { role: 'user', content: originalMessage || prompt }, undefined, workspaceId)
|
|
511
|
+
}
|
|
512
|
+
if (fullResponse) {
|
|
513
|
+
await chatStore.addMessage(resolvedSessionId, { role: 'assistant', content: fullResponse }, turnCount, workspaceId)
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
const session = await chatStore.load(workspaceId)
|
|
518
|
+
const totalTurnCount = session?.turn_count || turnCount
|
|
519
|
+
res.write(`data: ${JSON.stringify({ type: 'done', session_id: resolvedSessionId, turn_count: totalTurnCount })}\n\n`)
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
function streamViaLegacyLlmCommand(res, prompt, sessionId, workspaceId, originalMessage) {
|
|
420
523
|
return new Promise((resolve, reject) => {
|
|
421
524
|
const binaryName = getLlmBinary()
|
|
422
525
|
if (!binaryName) {
|
|
@@ -597,7 +700,17 @@ function streamLlmResponse(res, prompt, sessionId, workspaceId, originalMessage)
|
|
|
597
700
|
/**
|
|
598
701
|
* Run a quick non-streaming LLM call (for summarization etc.)
|
|
599
702
|
*/
|
|
600
|
-
function runQuickLlmCall(prompt) {
|
|
703
|
+
async function runQuickLlmCall(prompt) {
|
|
704
|
+
const primary = getActivePrimary()
|
|
705
|
+
if (primary) {
|
|
706
|
+
const output = await primary.invoke({ prompt, model: primary.name === 'claude' ? 'haiku' : undefined, timeoutMs: 30000 })
|
|
707
|
+
if (output.error) throw new Error(output.error.message)
|
|
708
|
+
return output.text || ''
|
|
709
|
+
}
|
|
710
|
+
return runQuickLlmCallLegacy(prompt)
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
function runQuickLlmCallLegacy(prompt) {
|
|
601
714
|
return new Promise((resolve, reject) => {
|
|
602
715
|
const binaryName = getLlmBinary()
|
|
603
716
|
if (!binaryName) {
|
package/win/routine-runner.js
CHANGED
|
@@ -18,6 +18,8 @@ const routineStore = require('../core/stores/routine-store')
|
|
|
18
18
|
const logManager = require('../core/lib/log-manager')
|
|
19
19
|
const { activeSessions } = require('./workflow-runner')
|
|
20
20
|
const { expandSkillTemplates, restoreSkillTemplates } = require('../core/lib/template-expander')
|
|
21
|
+
const { getActivePrimary } = require('../core/llm-plugins/lib/active')
|
|
22
|
+
const os = require('os')
|
|
21
23
|
|
|
22
24
|
const activeJobs = new Map()
|
|
23
25
|
const runningExecutions = new Map()
|
|
@@ -48,11 +50,16 @@ async function executeRoutineSession(routine, executionId, skillNames) {
|
|
|
48
50
|
const homeDir = config.HOME_DIR
|
|
49
51
|
const sessionName = generateSessionName(routine.id, executionId)
|
|
50
52
|
|
|
51
|
-
const
|
|
53
|
+
const primary = getActivePrimary()
|
|
54
|
+
const formatSkill = primary && typeof primary.formatSkillInvocation === 'function'
|
|
55
|
+
? primary.formatSkillInvocation.bind(primary)
|
|
56
|
+
: (name) => `/${name}`
|
|
57
|
+
const skillCommands = skillNames.map(name => formatSkill(name)).join(', then ')
|
|
52
58
|
const contextPrefix = routine.context
|
|
53
59
|
? `## Context\n\n${routine.context}\n\n---\n\n`
|
|
54
60
|
: ''
|
|
55
|
-
const
|
|
61
|
+
const reportSkill = formatSkill('execution-report')
|
|
62
|
+
const prompt = `${contextPrefix}Run the following skills in order: ${skillCommands}. After completing all skills, run ${reportSkill} to report the results.`
|
|
56
63
|
|
|
57
64
|
const logFile = logManager.getLogPath(executionId)
|
|
58
65
|
|
|
@@ -82,11 +89,18 @@ async function executeRoutineSession(routine, executionId, skillNames) {
|
|
|
82
89
|
activeSessions.delete(sessionName)
|
|
83
90
|
}
|
|
84
91
|
|
|
85
|
-
|
|
86
|
-
|
|
92
|
+
let llmCommand
|
|
93
|
+
const primary = getActivePrimary()
|
|
94
|
+
if (primary && typeof primary.buildShellInvocation === 'function') {
|
|
95
|
+
const promptFile = path.join(os.tmpdir(), `minion-routine-prompt-${sessionName}.txt`)
|
|
96
|
+
await fs.writeFile(promptFile, prompt, 'utf-8')
|
|
97
|
+
llmCommand = primary.buildShellInvocation({ promptFile })
|
|
98
|
+
} else if (config.LLM_COMMAND) {
|
|
99
|
+
const escapedPrompt = prompt.replace(/'/g, "''")
|
|
100
|
+
llmCommand = config.LLM_COMMAND.replace(/\{prompt\}/g, escapedPrompt)
|
|
101
|
+
} else {
|
|
102
|
+
throw new Error('No LLM configured. Set a Primary plugin via /api/llm/config or LLM_COMMAND in minion.env')
|
|
87
103
|
}
|
|
88
|
-
const escapedPrompt = prompt.replace(/'/g, "''")
|
|
89
|
-
const llmCommand = config.LLM_COMMAND.replace(/\{prompt\}/g, escapedPrompt)
|
|
90
104
|
|
|
91
105
|
// PATH, HOME, USERPROFILE, and minion secrets are already set in
|
|
92
106
|
// process.env at server startup. Per-execution identifiers are added here.
|
package/win/server.js
CHANGED
|
@@ -62,6 +62,7 @@ const { threadRoutes } = require('../core/routes/threads')
|
|
|
62
62
|
const { todoRoutes } = require('../core/routes/todos')
|
|
63
63
|
const { emailRoutes } = require('../core/routes/emails')
|
|
64
64
|
const { daemonRoutes } = require('../core/routes/daemons')
|
|
65
|
+
const { llmRoutes } = require('../core/routes/llm')
|
|
65
66
|
|
|
66
67
|
// Validate configuration
|
|
67
68
|
validate()
|
|
@@ -224,6 +225,7 @@ async function registerRoutes(app) {
|
|
|
224
225
|
await app.register(todoRoutes)
|
|
225
226
|
await app.register(emailRoutes)
|
|
226
227
|
await app.register(daemonRoutes, { heartbeatStatus: () => ({ running: !!heartbeatTimer, last_beat_at: lastBeatAt }) })
|
|
228
|
+
await app.register(llmRoutes)
|
|
227
229
|
|
|
228
230
|
// Shutdown endpoint — allows detached restart/update scripts to trigger
|
|
229
231
|
// graceful shutdown (offline heartbeat) before force-killing the process.
|
package/win/workflow-runner.js
CHANGED
|
@@ -24,6 +24,8 @@ const executionStore = require('../core/stores/execution-store')
|
|
|
24
24
|
const workflowStore = require('../core/stores/workflow-store')
|
|
25
25
|
const logManager = require('../core/lib/log-manager')
|
|
26
26
|
const { expandSkillTemplates, restoreSkillTemplates } = require('../core/lib/template-expander')
|
|
27
|
+
const { getActivePrimary } = require('../core/llm-plugins/lib/active')
|
|
28
|
+
const os = require('os')
|
|
27
29
|
|
|
28
30
|
// Active cron jobs keyed by workflow ID
|
|
29
31
|
const activeJobs = new Map()
|
|
@@ -73,10 +75,14 @@ async function executeWorkflowSession(workflow, executionId, skillNames, options
|
|
|
73
75
|
const sessionName = generateSessionName(workflow.id, executionId)
|
|
74
76
|
|
|
75
77
|
// Build prompt
|
|
76
|
-
const
|
|
78
|
+
const primary = getActivePrimary()
|
|
79
|
+
const formatSkill = primary && typeof primary.formatSkillInvocation === 'function'
|
|
80
|
+
? primary.formatSkillInvocation.bind(primary)
|
|
81
|
+
: (name) => `/${name}`
|
|
82
|
+
const skillCommands = skillNames.map(name => formatSkill(name)).join(', then ')
|
|
77
83
|
|
|
78
84
|
const rolePrefix = options.role
|
|
79
|
-
? `You are acting as the
|
|
85
|
+
? `You are acting as the ${options.role} role in this session. Read ~/.minion/roles/${options.role}.md for your role guidelines before proceeding.\n\n`
|
|
80
86
|
: ''
|
|
81
87
|
|
|
82
88
|
const revisionContext = options.revisionFeedback
|
|
@@ -115,12 +121,19 @@ async function executeWorkflowSession(workflow, executionId, skillNames, options
|
|
|
115
121
|
activeSessions.delete(sessionName)
|
|
116
122
|
}
|
|
117
123
|
|
|
118
|
-
// Build the LLM command
|
|
119
|
-
|
|
120
|
-
|
|
124
|
+
// Build the LLM command (prefer plugin system, fall back to LLM_COMMAND)
|
|
125
|
+
let llmCommand
|
|
126
|
+
const primary = getActivePrimary()
|
|
127
|
+
if (primary && typeof primary.buildShellInvocation === 'function') {
|
|
128
|
+
const promptFile = path.join(os.tmpdir(), `minion-workflow-prompt-${sessionName}.txt`)
|
|
129
|
+
await fs.writeFile(promptFile, prompt, 'utf-8')
|
|
130
|
+
llmCommand = primary.buildShellInvocation({ promptFile })
|
|
131
|
+
} else if (config.LLM_COMMAND) {
|
|
132
|
+
const escapedPrompt = prompt.replace(/'/g, "''")
|
|
133
|
+
llmCommand = config.LLM_COMMAND.replace(/\{prompt\}/g, escapedPrompt)
|
|
134
|
+
} else {
|
|
135
|
+
throw new Error('No LLM configured. Set a Primary plugin via /api/llm/config or LLM_COMMAND in minion.env')
|
|
121
136
|
}
|
|
122
|
-
const escapedPrompt = prompt.replace(/'/g, "''")
|
|
123
|
-
const llmCommand = config.LLM_COMMAND.replace(/\{prompt\}/g, escapedPrompt)
|
|
124
137
|
|
|
125
138
|
// PATH, HOME, USERPROFILE, and minion secrets are already set in
|
|
126
139
|
// process.env at server startup, so child processes inherit them automatically.
|