@geekbeer/minion 3.5.35 → 3.6.2
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/step-poller.js +7 -3
- package/core/lib/template-expander.js +85 -0
- package/core/stores/variable-store.js +16 -14
- package/docs/api-reference.md +14 -5
- package/docs/task-guides.md +14 -3
- package/linux/routine-runner.js +18 -1
- package/linux/server.js +3 -2
- package/linux/workflow-runner.js +18 -1
- package/package.json +1 -1
- package/rules/core.md +4 -2
- package/win/minion-cli.ps1 +86 -18
- package/win/routine-runner.js +18 -1
- package/win/server.js +3 -2
- package/win/workflow-runner.js +18 -1
package/core/lib/step-poller.js
CHANGED
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
|
|
21
21
|
const { config, isHqConfigured } = require('../config')
|
|
22
22
|
const api = require('../api')
|
|
23
|
+
const variableStore = require('../stores/variable-store')
|
|
23
24
|
|
|
24
25
|
// Polling interval: 30 seconds (matches heartbeat frequency)
|
|
25
26
|
const POLL_INTERVAL_MS = 30_000
|
|
@@ -129,11 +130,14 @@ async function executeStep(step) {
|
|
|
129
130
|
}
|
|
130
131
|
|
|
131
132
|
// 2. Fetch the skill from HQ to ensure it's deployed locally
|
|
132
|
-
//
|
|
133
|
+
// Merge minion variables into template_vars so HQ expands {{VAR_NAME}} in SKILL.md.
|
|
134
|
+
// Merge order: minion vars < project vars < workflow vars (template_vars already merged by HQ)
|
|
133
135
|
if (skill_name) {
|
|
134
136
|
try {
|
|
135
|
-
const
|
|
136
|
-
|
|
137
|
+
const minionVars = variableStore.getAll('variables')
|
|
138
|
+
const mergedVars = { ...minionVars, ...(template_vars || {}) }
|
|
139
|
+
const varsParam = Object.keys(mergedVars).length > 0
|
|
140
|
+
? `?vars=${Buffer.from(JSON.stringify(mergedVars)).toString('base64')}`
|
|
137
141
|
: ''
|
|
138
142
|
const fetchUrl = `http://localhost:${config.AGENT_PORT || 8080}/api/skills/fetch/${encodeURIComponent(skill_name)}${varsParam}`
|
|
139
143
|
const fetchResp = await fetch(fetchUrl, {
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Template Expander
|
|
3
|
+
*
|
|
4
|
+
* Expands {{VAR_NAME}} placeholders in SKILL.md files using minion variables.
|
|
5
|
+
* Same syntax as project/workflow variable expansion done on HQ side.
|
|
6
|
+
*
|
|
7
|
+
* Design:
|
|
8
|
+
* - Variables (non-sensitive config) use {{VAR}} template expansion in skill content
|
|
9
|
+
* - Secrets (sensitive credentials) remain as environment variables ($ENV_VAR)
|
|
10
|
+
* - Expand at execution time (not write time) so variable changes take effect immediately
|
|
11
|
+
* - Save/restore original SKILL.md to avoid persisting expanded content
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const fs = require('fs').promises
|
|
15
|
+
const path = require('path')
|
|
16
|
+
const { config } = require('../config')
|
|
17
|
+
const variableStore = require('../stores/variable-store')
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Expand {{VAR_NAME}} template placeholders in content string.
|
|
21
|
+
* Unmatched placeholders are left as-is (same behavior as HQ-side expansion).
|
|
22
|
+
*
|
|
23
|
+
* @param {string} content - Skill content with {{VAR}} placeholders
|
|
24
|
+
* @param {Record<string, string>} vars - Key-value pairs for substitution
|
|
25
|
+
* @returns {string} Content with matched placeholders replaced
|
|
26
|
+
*/
|
|
27
|
+
function expandTemplateVars(content, vars) {
|
|
28
|
+
return content.replace(/\{\{(\w+)\}\}/g, (match, key) =>
|
|
29
|
+
vars[key] !== undefined ? vars[key] : match
|
|
30
|
+
)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Expand templates in SKILL.md files for the given skill names.
|
|
35
|
+
* Reads each SKILL.md, expands {{VAR}} with minion variables, writes back.
|
|
36
|
+
* Returns the original contents for restoration after execution.
|
|
37
|
+
*
|
|
38
|
+
* @param {string[]} skillNames - Skill slugs to expand
|
|
39
|
+
* @returns {Promise<Map<string, string>>} Map of skillName -> original content (for restoration)
|
|
40
|
+
*/
|
|
41
|
+
async function expandSkillTemplates(skillNames) {
|
|
42
|
+
const minionVars = variableStore.getAll('variables')
|
|
43
|
+
|
|
44
|
+
// If no variables defined, skip
|
|
45
|
+
if (Object.keys(minionVars).length === 0) return new Map()
|
|
46
|
+
|
|
47
|
+
const originals = new Map()
|
|
48
|
+
const skillsDir = path.join(config.HOME_DIR, '.claude', 'skills')
|
|
49
|
+
|
|
50
|
+
for (const name of skillNames) {
|
|
51
|
+
const skillMdPath = path.join(skillsDir, name, 'SKILL.md')
|
|
52
|
+
try {
|
|
53
|
+
const original = await fs.readFile(skillMdPath, 'utf-8')
|
|
54
|
+
const expanded = expandTemplateVars(original, minionVars)
|
|
55
|
+
if (expanded !== original) {
|
|
56
|
+
originals.set(name, original)
|
|
57
|
+
await fs.writeFile(skillMdPath, expanded, 'utf-8')
|
|
58
|
+
console.log(`[TemplateExpander] Expanded {{VAR}} in skill: ${name}`)
|
|
59
|
+
}
|
|
60
|
+
} catch (err) {
|
|
61
|
+
console.error(`[TemplateExpander] Failed to expand ${name}: ${err.message}`)
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return originals
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Restore original SKILL.md contents after execution.
|
|
70
|
+
*
|
|
71
|
+
* @param {Map<string, string>} originals - Map from expandSkillTemplates
|
|
72
|
+
*/
|
|
73
|
+
async function restoreSkillTemplates(originals) {
|
|
74
|
+
const skillsDir = path.join(config.HOME_DIR, '.claude', 'skills')
|
|
75
|
+
for (const [name, content] of originals) {
|
|
76
|
+
const skillMdPath = path.join(skillsDir, name, 'SKILL.md')
|
|
77
|
+
try {
|
|
78
|
+
await fs.writeFile(skillMdPath, content, 'utf-8')
|
|
79
|
+
} catch (err) {
|
|
80
|
+
console.error(`[TemplateExpander] Failed to restore ${name}: ${err.message}`)
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
module.exports = { expandTemplateVars, expandSkillTemplates, restoreSkillTemplates }
|
|
@@ -89,6 +89,8 @@ function get(type, key) {
|
|
|
89
89
|
|
|
90
90
|
/**
|
|
91
91
|
* Set a key-value pair (creates or updates).
|
|
92
|
+
* Only secrets are synced to process.env (for child process inheritance).
|
|
93
|
+
* Variables use {{VAR}} template expansion in skill content instead.
|
|
92
94
|
* @param {'secrets' | 'variables'} type
|
|
93
95
|
* @param {string} key
|
|
94
96
|
* @param {string} value
|
|
@@ -98,8 +100,10 @@ function set(type, key, value) {
|
|
|
98
100
|
const data = parseEnvFile(filePath)
|
|
99
101
|
data[key] = value
|
|
100
102
|
writeEnvFile(filePath, data)
|
|
101
|
-
//
|
|
102
|
-
|
|
103
|
+
// Only sync secrets to process.env; variables use template expansion instead
|
|
104
|
+
if (type === 'secrets') {
|
|
105
|
+
process.env[key] = value
|
|
106
|
+
}
|
|
103
107
|
console.log(`[VariableStore] Set ${type} key: ${key}`)
|
|
104
108
|
}
|
|
105
109
|
|
|
@@ -115,8 +119,10 @@ function remove(type, key) {
|
|
|
115
119
|
if (!(key in data)) return false
|
|
116
120
|
delete data[key]
|
|
117
121
|
writeEnvFile(filePath, data)
|
|
118
|
-
//
|
|
119
|
-
|
|
122
|
+
// Only sync secrets to process.env; variables use template expansion instead
|
|
123
|
+
if (type === 'secrets') {
|
|
124
|
+
delete process.env[key]
|
|
125
|
+
}
|
|
120
126
|
console.log(`[VariableStore] Removed ${type} key: ${key}`)
|
|
121
127
|
return true
|
|
122
128
|
}
|
|
@@ -131,18 +137,14 @@ function listKeys(type) {
|
|
|
131
137
|
}
|
|
132
138
|
|
|
133
139
|
/**
|
|
134
|
-
* Build
|
|
135
|
-
*
|
|
136
|
-
*
|
|
140
|
+
* Build environment object from minion secrets only.
|
|
141
|
+
* Variables are no longer injected as env vars; they use {{VAR}} template
|
|
142
|
+
* expansion in skill content (same mechanism as project/workflow variables).
|
|
137
143
|
*
|
|
138
|
-
* @
|
|
139
|
-
* @returns {Record<string, string>} Merged key-value pairs
|
|
144
|
+
* @returns {Record<string, string>} Secret key-value pairs for process.env
|
|
140
145
|
*/
|
|
141
|
-
function buildEnv(
|
|
142
|
-
|
|
143
|
-
const secrets = getAll('secrets')
|
|
144
|
-
// Merge order: variables < secrets < extraVars (later wins)
|
|
145
|
-
return { ...variables, ...secrets, ...extraVars }
|
|
146
|
+
function buildEnv() {
|
|
147
|
+
return getAll('secrets')
|
|
146
148
|
}
|
|
147
149
|
|
|
148
150
|
module.exports = {
|
package/docs/api-reference.md
CHANGED
|
@@ -544,11 +544,20 @@ Response:
|
|
|
544
544
|
}
|
|
545
545
|
```
|
|
546
546
|
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
547
|
+
ワークフロー変数はプロジェクト変数を上書きする(同名キーの場合)。
|
|
548
|
+
|
|
549
|
+
#### 変数とシークレットの違い
|
|
550
|
+
|
|
551
|
+
**変数**(ミニオン変数・プロジェクト変数・ワークフロー変数)はスキル本文の `{{VAR_NAME}}` テンプレートとして実行時に展開される。スキル作成時にパラメータ化したい値は `{{変数名}}` で記述する。
|
|
552
|
+
|
|
553
|
+
**シークレット**(ミニオンシークレット)は環境変数 `$SECRET_NAME` としてプロセスに注入される。APIキーやパスワード等の機密情報に使用する。テンプレート展開は行われない。
|
|
554
|
+
|
|
555
|
+
#### テンプレート変数の展開優先順位
|
|
556
|
+
|
|
557
|
+
同名の変数が複数のスコープで定義されている場合、以下の順序で上書きされる:
|
|
558
|
+
1. ミニオン変数(最低優先)
|
|
559
|
+
2. プロジェクト変数
|
|
560
|
+
3. ワークフロー変数(最優先)
|
|
552
561
|
|
|
553
562
|
### Workflows (project-scoped, versioned)
|
|
554
563
|
|
package/docs/task-guides.md
CHANGED
|
@@ -49,7 +49,7 @@ Use {{PROJECT_VAR}} to reference project/workflow variables.
|
|
|
49
49
|
|
|
50
50
|
### テンプレート変数
|
|
51
51
|
|
|
52
|
-
スキル本文中で `{{VAR_NAME}}`
|
|
52
|
+
スキル本文中で `{{VAR_NAME}}` と記述すると、実行時に変数の値で自動的に置換される。スキルを再利用しつつ、スコープごとに異なるパラメータを渡したい場合に使う。
|
|
53
53
|
|
|
54
54
|
```markdown
|
|
55
55
|
---
|
|
@@ -62,8 +62,19 @@ description: サイトをデプロイする
|
|
|
62
62
|
|
|
63
63
|
- 変数名は英数字とアンダースコアのみ(`\w+`)
|
|
64
64
|
- 未定義の変数は `{{VAR_NAME}}` のまま残る(エラーにはならない)
|
|
65
|
-
-
|
|
66
|
-
-
|
|
65
|
+
- 展開優先順位: ミニオン変数 < プロジェクト変数 < ワークフロー変数(後者が上書き)
|
|
66
|
+
- ルーティン実行時もミニオン変数による `{{VAR}}` 展開が行われる
|
|
67
|
+
|
|
68
|
+
### 変数とシークレットの使い分け
|
|
69
|
+
|
|
70
|
+
| 種別 | 構文 | 用途 | 例 |
|
|
71
|
+
|------|------|------|-----|
|
|
72
|
+
| 変数 | `{{VAR_NAME}}` | 設定・パラメータ(非機密) | デプロイ先、サイトURL、プロジェクト名 |
|
|
73
|
+
| シークレット | `$SECRET_NAME`(環境変数) | 機密情報 | APIキー、パスワード、トークン |
|
|
74
|
+
|
|
75
|
+
- **変数**はスキル本文のテンプレートとして展開される。全スコープ(ミニオン・プロジェクト・ワークフロー)で同じ `{{VAR}}` 構文を使用する
|
|
76
|
+
- **シークレット**は環境変数としてプロセスに注入される。テンプレート展開は行われない
|
|
77
|
+
- デイリーログやメモリーから変数・シークレットの値を推測して使用しないこと
|
|
67
78
|
|
|
68
79
|
### 2. HQ に反映する
|
|
69
80
|
|
package/linux/routine-runner.js
CHANGED
|
@@ -22,6 +22,7 @@ const executionStore = require('../core/stores/execution-store')
|
|
|
22
22
|
const routineStore = require('../core/stores/routine-store')
|
|
23
23
|
const logManager = require('../core/lib/log-manager')
|
|
24
24
|
const runningTasks = require('../core/lib/running-tasks')
|
|
25
|
+
const { expandSkillTemplates, restoreSkillTemplates } = require('../core/lib/template-expander')
|
|
25
26
|
|
|
26
27
|
// Active cron jobs keyed by routine ID
|
|
27
28
|
const activeJobs = new Map()
|
|
@@ -83,6 +84,17 @@ async function executeRoutineSession(routine, executionId, skillNames) {
|
|
|
83
84
|
console.log(`[RoutineRunner] tmux session: ${sessionName}`)
|
|
84
85
|
console.log(`[RoutineRunner] Log file: ${logFile}`)
|
|
85
86
|
|
|
87
|
+
// Expand {{VAR}} templates in SKILL.md files with minion variables
|
|
88
|
+
let expandedOriginals = new Map()
|
|
89
|
+
try {
|
|
90
|
+
expandedOriginals = await expandSkillTemplates(skillNames)
|
|
91
|
+
if (expandedOriginals.size > 0) {
|
|
92
|
+
console.log(`[RoutineRunner] Expanded templates in ${expandedOriginals.size} skill(s)`)
|
|
93
|
+
}
|
|
94
|
+
} catch (err) {
|
|
95
|
+
console.error(`[RoutineRunner] Template expansion failed: ${err.message}`)
|
|
96
|
+
}
|
|
97
|
+
|
|
86
98
|
try {
|
|
87
99
|
// Ensure log directory exists and prune old logs
|
|
88
100
|
await logManager.ensureLogDir()
|
|
@@ -102,7 +114,7 @@ async function executeRoutineSession(routine, executionId, skillNames) {
|
|
|
102
114
|
const llmCommand = config.LLM_COMMAND.replace(/\{prompt\}/g, escapedPrompt)
|
|
103
115
|
|
|
104
116
|
// Create tmux session with the LLM command.
|
|
105
|
-
// PATH, HOME, DISPLAY, and minion
|
|
117
|
+
// PATH, HOME, DISPLAY, and minion secrets are already set in
|
|
106
118
|
// process.env at server startup, so child processes inherit them automatically.
|
|
107
119
|
// Per-execution identifiers are passed via -e flags for the session environment.
|
|
108
120
|
const tmuxCommand = [
|
|
@@ -173,6 +185,11 @@ async function executeRoutineSession(routine, executionId, skillNames) {
|
|
|
173
185
|
} catch (error) {
|
|
174
186
|
console.error(`[RoutineRunner] Routine ${routine.name} failed: ${error.message}`)
|
|
175
187
|
return { success: false, error: error.message, sessionName }
|
|
188
|
+
} finally {
|
|
189
|
+
// Restore original SKILL.md files after execution
|
|
190
|
+
if (expandedOriginals.size > 0) {
|
|
191
|
+
await restoreSkillTemplates(expandedOriginals)
|
|
192
|
+
}
|
|
176
193
|
}
|
|
177
194
|
}
|
|
178
195
|
|
package/linux/server.js
CHANGED
|
@@ -302,13 +302,14 @@ async function start() {
|
|
|
302
302
|
process.env.HOME = config.HOME_DIR
|
|
303
303
|
process.env.DISPLAY = process.env.DISPLAY || ':99'
|
|
304
304
|
|
|
305
|
-
// Load minion
|
|
305
|
+
// Load minion secrets into process.env for child process inheritance.
|
|
306
|
+
// Variables are NOT loaded here — they use {{VAR}} template expansion in skill content.
|
|
306
307
|
const variableStore = require('../core/stores/variable-store')
|
|
307
308
|
const minionEnv = variableStore.buildEnv()
|
|
308
309
|
for (const [key, value] of Object.entries(minionEnv)) {
|
|
309
310
|
if (!(key in process.env)) process.env[key] = value
|
|
310
311
|
}
|
|
311
|
-
console.log(`[Server] Loaded ${Object.keys(minionEnv).length} minion
|
|
312
|
+
console.log(`[Server] Loaded ${Object.keys(minionEnv).length} minion secrets into process.env`)
|
|
312
313
|
|
|
313
314
|
// Sync bundled assets
|
|
314
315
|
syncBundledRules()
|
package/linux/workflow-runner.js
CHANGED
|
@@ -21,6 +21,7 @@ const executionStore = require('../core/stores/execution-store')
|
|
|
21
21
|
const workflowStore = require('../core/stores/workflow-store')
|
|
22
22
|
const logManager = require('../core/lib/log-manager')
|
|
23
23
|
const runningTasks = require('../core/lib/running-tasks')
|
|
24
|
+
const { expandSkillTemplates, restoreSkillTemplates } = require('../core/lib/template-expander')
|
|
24
25
|
|
|
25
26
|
// Active cron jobs keyed by workflow ID
|
|
26
27
|
const activeJobs = new Map()
|
|
@@ -87,6 +88,17 @@ async function executeWorkflowSession(workflow, executionId, skillNames, options
|
|
|
87
88
|
console.log(`[WorkflowRunner] Log file: ${logFile}`)
|
|
88
89
|
console.log(`[WorkflowRunner] HOME: ${homeDir}`)
|
|
89
90
|
|
|
91
|
+
// Expand {{VAR}} templates in SKILL.md files with minion variables
|
|
92
|
+
let expandedOriginals = new Map()
|
|
93
|
+
try {
|
|
94
|
+
expandedOriginals = await expandSkillTemplates(skillNames)
|
|
95
|
+
if (expandedOriginals.size > 0) {
|
|
96
|
+
console.log(`[WorkflowRunner] Expanded templates in ${expandedOriginals.size} skill(s)`)
|
|
97
|
+
}
|
|
98
|
+
} catch (err) {
|
|
99
|
+
console.error(`[WorkflowRunner] Template expansion failed: ${err.message}`)
|
|
100
|
+
}
|
|
101
|
+
|
|
90
102
|
try {
|
|
91
103
|
// Ensure log directory exists and prune old logs
|
|
92
104
|
await logManager.ensureLogDir()
|
|
@@ -106,7 +118,7 @@ async function executeWorkflowSession(workflow, executionId, skillNames, options
|
|
|
106
118
|
const llmCommand = config.LLM_COMMAND.replace(/\{prompt\}/g, escapedPrompt)
|
|
107
119
|
|
|
108
120
|
// Create tmux session with the LLM command.
|
|
109
|
-
// PATH, HOME, DISPLAY, and minion
|
|
121
|
+
// PATH, HOME, DISPLAY, and minion secrets are already set in
|
|
110
122
|
// process.env at server startup, so child processes inherit them automatically.
|
|
111
123
|
const tmuxCommand = [
|
|
112
124
|
'tmux new-session -d',
|
|
@@ -173,6 +185,11 @@ async function executeWorkflowSession(workflow, executionId, skillNames, options
|
|
|
173
185
|
} catch (error) {
|
|
174
186
|
console.error(`[WorkflowRunner] Workflow ${workflow.name} failed: ${error.message}`)
|
|
175
187
|
return { success: false, error: error.message, sessionName }
|
|
188
|
+
} finally {
|
|
189
|
+
// Restore original SKILL.md files after execution
|
|
190
|
+
if (expandedOriginals.size > 0) {
|
|
191
|
+
await restoreSkillTemplates(expandedOriginals)
|
|
192
|
+
}
|
|
176
193
|
}
|
|
177
194
|
}
|
|
178
195
|
|
package/package.json
CHANGED
package/rules/core.md
CHANGED
|
@@ -133,9 +133,11 @@ Routine 実行中は以下もtmuxセッション環境で利用可能:
|
|
|
133
133
|
- `MINION_ROUTINE_ID` — ルーティンUUID
|
|
134
134
|
- `MINION_ROUTINE_NAME` — ルーティン名
|
|
135
135
|
|
|
136
|
-
|
|
136
|
+
**変数**(ミニオン変数・プロジェクト変数・ワークフロー変数)はスキル本文の `{{VAR_NAME}}` テンプレートとして実行時に展開される。スキル作成時にパラメータ化したい値は `{{変数名}}` で記述すること。展開優先順位: ミニオン変数 < プロジェクト変数 < ワークフロー変数(後者が優先)。
|
|
137
137
|
|
|
138
|
-
|
|
138
|
+
**シークレット**(ミニオンシークレット)はサーバー起動時に `process.env` にロードされ、全子プロセスで環境変数 `$SECRET_NAME` として利用可能。APIキーやパスワード等の機密情報に使用する。シークレットは `{{VAR}}` テンプレートでは展開されない。
|
|
139
|
+
|
|
140
|
+
デイリーログやメモリーから変数・シークレットの値を推測して使用してはならない。変数は `{{VAR_NAME}}` テンプレートとして定義し、シークレットは環境変数として参照すること。
|
|
139
141
|
|
|
140
142
|
## Skills Directory
|
|
141
143
|
|
package/win/minion-cli.ps1
CHANGED
|
@@ -451,7 +451,7 @@ function Restart-MinionService {
|
|
|
451
451
|
# ============================================================
|
|
452
452
|
|
|
453
453
|
function Invoke-Setup {
|
|
454
|
-
$totalSteps =
|
|
454
|
+
$totalSteps = 12
|
|
455
455
|
|
|
456
456
|
# Minionization warning
|
|
457
457
|
Write-Host ""
|
|
@@ -877,8 +877,52 @@ function Invoke-Setup {
|
|
|
877
877
|
Write-Warn "websockify not available, VNC WebSocket proxy will not be registered"
|
|
878
878
|
}
|
|
879
879
|
|
|
880
|
+
# Step 10: Download and register cloudflared
|
|
881
|
+
Write-Step 10 $totalSteps "Setting up Cloudflare Tunnel (cloudflared)..."
|
|
882
|
+
$cfExe = $null
|
|
883
|
+
if (Get-Command cloudflared -ErrorAction SilentlyContinue) {
|
|
884
|
+
$cfExe = (Get-Command cloudflared).Source
|
|
885
|
+
Write-Detail "cloudflared already installed: $cfExe"
|
|
886
|
+
} else {
|
|
887
|
+
Write-Host " Downloading cloudflared..."
|
|
888
|
+
try {
|
|
889
|
+
$cfUrl = 'https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-windows-amd64.exe'
|
|
890
|
+
$cfPath = Join-Path $DataDir 'cloudflared.exe'
|
|
891
|
+
Invoke-WebRequest -Uri $cfUrl -OutFile $cfPath -UseBasicParsing
|
|
892
|
+
$cfExe = $cfPath
|
|
893
|
+
Write-Detail "cloudflared downloaded to $cfExe"
|
|
894
|
+
}
|
|
895
|
+
catch {
|
|
896
|
+
Write-Warn "Failed to download cloudflared: $_"
|
|
897
|
+
Write-Host " You can install manually or re-run setup later." -ForegroundColor Gray
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
if ($cfExe) {
|
|
901
|
+
# Register cloudflared as NSSM service (config will be set by 'configure --setup-tunnel')
|
|
902
|
+
Invoke-Nssm stop minion-cloudflared
|
|
903
|
+
Invoke-Nssm remove minion-cloudflared confirm
|
|
904
|
+
$cfConfigDir = Join-Path $TargetUserProfile '.cloudflared'
|
|
905
|
+
$cfConfigPath = Join-Path $cfConfigDir 'config.yml'
|
|
906
|
+
Invoke-Nssm install minion-cloudflared $cfExe "tunnel run --config `"$cfConfigPath`""
|
|
907
|
+
Invoke-Nssm set minion-cloudflared Start SERVICE_DEMAND_START
|
|
908
|
+
Invoke-Nssm set minion-cloudflared DisplayName "Minion Cloudflared"
|
|
909
|
+
Invoke-Nssm set minion-cloudflared Description "Cloudflare Tunnel for Minion"
|
|
910
|
+
Invoke-Nssm set minion-cloudflared AppRestartDelay 5000
|
|
911
|
+
Invoke-Nssm set minion-cloudflared AppStdout (Join-Path $LogDir 'cloudflared-stdout.log')
|
|
912
|
+
Invoke-Nssm set minion-cloudflared AppStderr (Join-Path $LogDir 'cloudflared-stderr.log')
|
|
913
|
+
Invoke-Nssm set minion-cloudflared AppStdoutCreationDisposition 4
|
|
914
|
+
Invoke-Nssm set minion-cloudflared AppStderrCreationDisposition 4
|
|
915
|
+
Invoke-Nssm set minion-cloudflared AppRotateFiles 1
|
|
916
|
+
Invoke-Nssm set minion-cloudflared AppRotateBytes 10485760
|
|
917
|
+
Grant-ServiceControlToUser 'minion-cloudflared' $setupUserSid
|
|
918
|
+
if ($targetUserSid -and $targetUserSid -ne $setupUserSid) {
|
|
919
|
+
Grant-ServiceControlToUser 'minion-cloudflared' $targetUserSid
|
|
920
|
+
}
|
|
921
|
+
Write-Detail "minion-cloudflared service registered (starts via 'configure --setup-tunnel')"
|
|
922
|
+
}
|
|
923
|
+
|
|
880
924
|
# Step 11: Disable screensaver, lock screen, and sleep
|
|
881
|
-
Write-Step
|
|
925
|
+
Write-Step 11 $totalSteps "Disabling screensaver, lock screen, and sleep..."
|
|
882
926
|
Set-ItemProperty -Path 'HKCU:\Control Panel\Desktop' -Name ScreenSaveActive -Value '0'
|
|
883
927
|
Set-ItemProperty -Path 'HKCU:\Control Panel\Desktop' -Name ScreenSaveTimeOut -Value '0'
|
|
884
928
|
Set-ItemProperty -Path 'HKCU:\Control Panel\Desktop' -Name SCRNSAVE.EXE -Value ''
|
|
@@ -892,7 +936,7 @@ function Invoke-Setup {
|
|
|
892
936
|
Write-Detail "Sleep and monitor timeout disabled"
|
|
893
937
|
|
|
894
938
|
# Configure firewall rules
|
|
895
|
-
Write-Step
|
|
939
|
+
Write-Step 12 $totalSteps "Configuring firewall rules..."
|
|
896
940
|
$fwRules = @(
|
|
897
941
|
@{ Name = 'Minion Agent'; Port = 8080 },
|
|
898
942
|
@{ Name = 'Minion Terminal'; Port = 7681 },
|
|
@@ -985,6 +1029,7 @@ function Invoke-Setup {
|
|
|
985
1029
|
Write-Host "Services registered (not yet started):"
|
|
986
1030
|
Write-Host " minion-agent - AI Agent (port 8080)"
|
|
987
1031
|
Write-Host " minion-websockify - WebSocket proxy (port 6080)"
|
|
1032
|
+
Write-Host " minion-cloudflared - Cloudflare Tunnel (starts via configure --setup-tunnel)"
|
|
988
1033
|
Write-Host " MinionVNC (task) - TightVNC (port 5900, starts at logon)"
|
|
989
1034
|
Write-Host ""
|
|
990
1035
|
Write-Host "Next step: Connect to HQ (run as regular user):" -ForegroundColor Yellow
|
|
@@ -1303,21 +1348,44 @@ function Invoke-Configure {
|
|
|
1303
1348
|
[System.IO.File]::WriteAllText((Join-Path $cfConfigDir 'config.yml'), $tunnelData.config_yml, [System.Text.UTF8Encoding]::new($false))
|
|
1304
1349
|
Write-Detail "Tunnel config saved"
|
|
1305
1350
|
|
|
1306
|
-
#
|
|
1307
|
-
$
|
|
1308
|
-
if (
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
#
|
|
1315
|
-
$
|
|
1316
|
-
if (-
|
|
1317
|
-
|
|
1318
|
-
}
|
|
1319
|
-
|
|
1320
|
-
|
|
1351
|
+
# Start cloudflared service (registered during setup)
|
|
1352
|
+
$cfState = Get-ServiceState 'minion-cloudflared'
|
|
1353
|
+
if ($cfState) {
|
|
1354
|
+
# Enable auto-start now that tunnel config is in place
|
|
1355
|
+
Invoke-Nssm set minion-cloudflared Start SERVICE_AUTO_START 2>&1 | Out-Null
|
|
1356
|
+
sc.exe start minion-cloudflared 2>&1 | Out-Null
|
|
1357
|
+
Write-Detail "minion-cloudflared started"
|
|
1358
|
+
} else {
|
|
1359
|
+
# Fallback: service not registered (e.g. setup was run before v3.6.2)
|
|
1360
|
+
$cfExe = $null
|
|
1361
|
+
if (Get-Command cloudflared -ErrorAction SilentlyContinue) {
|
|
1362
|
+
$cfExe = (Get-Command cloudflared).Source
|
|
1363
|
+
} elseif (Test-Path (Join-Path $DataDir 'cloudflared.exe')) {
|
|
1364
|
+
$cfExe = Join-Path $DataDir 'cloudflared.exe'
|
|
1365
|
+
}
|
|
1366
|
+
if ($cfExe) {
|
|
1367
|
+
$isAdmin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
|
|
1368
|
+
if ($isAdmin) {
|
|
1369
|
+
$cfConfigPath = Join-Path $cfConfigDir 'config.yml'
|
|
1370
|
+
Invoke-Nssm install minion-cloudflared $cfExe "tunnel run --config `"$cfConfigPath`""
|
|
1371
|
+
Invoke-Nssm set minion-cloudflared Start SERVICE_AUTO_START
|
|
1372
|
+
Invoke-Nssm set minion-cloudflared DisplayName "Minion Cloudflared"
|
|
1373
|
+
Invoke-Nssm set minion-cloudflared Description "Cloudflare Tunnel for Minion"
|
|
1374
|
+
Invoke-Nssm set minion-cloudflared AppRestartDelay 5000
|
|
1375
|
+
Invoke-Nssm set minion-cloudflared AppStdout (Join-Path $LogDir 'cloudflared-stdout.log')
|
|
1376
|
+
Invoke-Nssm set minion-cloudflared AppStderr (Join-Path $LogDir 'cloudflared-stderr.log')
|
|
1377
|
+
Invoke-Nssm set minion-cloudflared AppStdoutCreationDisposition 4
|
|
1378
|
+
Invoke-Nssm set minion-cloudflared AppStderrCreationDisposition 4
|
|
1379
|
+
Invoke-Nssm set minion-cloudflared AppRotateFiles 1
|
|
1380
|
+
Invoke-Nssm set minion-cloudflared AppRotateBytes 10485760
|
|
1381
|
+
$setupUserSid = Get-SetupUserSid
|
|
1382
|
+
Grant-ServiceControlToUser 'minion-cloudflared' $setupUserSid
|
|
1383
|
+
Write-Detail "minion-cloudflared service registered (fallback)"
|
|
1384
|
+
sc.exe start minion-cloudflared 2>&1 | Out-Null
|
|
1385
|
+
Write-Detail "minion-cloudflared started"
|
|
1386
|
+
} else {
|
|
1387
|
+
Write-Warn "minion-cloudflared service not registered. Run 'setup' as admin to register, or run 'configure --setup-tunnel' as admin."
|
|
1388
|
+
}
|
|
1321
1389
|
}
|
|
1322
1390
|
}
|
|
1323
1391
|
}
|
package/win/routine-runner.js
CHANGED
|
@@ -17,6 +17,7 @@ const executionStore = require('../core/stores/execution-store')
|
|
|
17
17
|
const routineStore = require('../core/stores/routine-store')
|
|
18
18
|
const logManager = require('../core/lib/log-manager')
|
|
19
19
|
const { activeSessions } = require('./workflow-runner')
|
|
20
|
+
const { expandSkillTemplates, restoreSkillTemplates } = require('../core/lib/template-expander')
|
|
20
21
|
|
|
21
22
|
const activeJobs = new Map()
|
|
22
23
|
const runningExecutions = new Map()
|
|
@@ -60,6 +61,17 @@ async function executeRoutineSession(routine, executionId, skillNames) {
|
|
|
60
61
|
console.log(`[RoutineRunner] Session: ${sessionName}`)
|
|
61
62
|
console.log(`[RoutineRunner] Log file: ${logFile}`)
|
|
62
63
|
|
|
64
|
+
// Expand {{VAR}} templates in SKILL.md files with minion variables
|
|
65
|
+
let expandedOriginals = new Map()
|
|
66
|
+
try {
|
|
67
|
+
expandedOriginals = await expandSkillTemplates(skillNames)
|
|
68
|
+
if (expandedOriginals.size > 0) {
|
|
69
|
+
console.log(`[RoutineRunner] Expanded templates in ${expandedOriginals.size} skill(s)`)
|
|
70
|
+
}
|
|
71
|
+
} catch (err) {
|
|
72
|
+
console.error(`[RoutineRunner] Template expansion failed: ${err.message}`)
|
|
73
|
+
}
|
|
74
|
+
|
|
63
75
|
try {
|
|
64
76
|
await logManager.ensureLogDir()
|
|
65
77
|
await logManager.pruneOldLogs()
|
|
@@ -76,7 +88,7 @@ async function executeRoutineSession(routine, executionId, skillNames) {
|
|
|
76
88
|
const escapedPrompt = prompt.replace(/'/g, "''")
|
|
77
89
|
const llmCommand = config.LLM_COMMAND.replace(/\{prompt\}/g, escapedPrompt)
|
|
78
90
|
|
|
79
|
-
// PATH, HOME, USERPROFILE, and minion
|
|
91
|
+
// PATH, HOME, USERPROFILE, and minion secrets are already set in
|
|
80
92
|
// process.env at server startup. Per-execution identifiers are added here.
|
|
81
93
|
const env = {
|
|
82
94
|
...process.env,
|
|
@@ -147,6 +159,11 @@ async function executeRoutineSession(routine, executionId, skillNames) {
|
|
|
147
159
|
} catch (error) {
|
|
148
160
|
console.error(`[RoutineRunner] Routine ${routine.name} failed: ${error.message}`)
|
|
149
161
|
return { success: false, error: error.message, sessionName }
|
|
162
|
+
} finally {
|
|
163
|
+
// Restore original SKILL.md files after execution
|
|
164
|
+
if (expandedOriginals.size > 0) {
|
|
165
|
+
await restoreSkillTemplates(expandedOriginals)
|
|
166
|
+
}
|
|
150
167
|
}
|
|
151
168
|
}
|
|
152
169
|
|
package/win/server.js
CHANGED
|
@@ -249,13 +249,14 @@ async function start() {
|
|
|
249
249
|
process.env.HOME = config.HOME_DIR
|
|
250
250
|
process.env.USERPROFILE = config.HOME_DIR
|
|
251
251
|
|
|
252
|
-
// Load minion
|
|
252
|
+
// Load minion secrets into process.env for child process inheritance.
|
|
253
|
+
// Variables are NOT loaded here — they use {{VAR}} template expansion in skill content.
|
|
253
254
|
const variableStore = require('../core/stores/variable-store')
|
|
254
255
|
const minionEnv = variableStore.buildEnv()
|
|
255
256
|
for (const [key, value] of Object.entries(minionEnv)) {
|
|
256
257
|
if (!(key in process.env)) process.env[key] = value
|
|
257
258
|
}
|
|
258
|
-
console.log(`[Server] Loaded ${Object.keys(minionEnv).length} minion
|
|
259
|
+
console.log(`[Server] Loaded ${Object.keys(minionEnv).length} minion secrets into process.env`)
|
|
259
260
|
|
|
260
261
|
// Sync bundled assets
|
|
261
262
|
syncBundledRules()
|
package/win/workflow-runner.js
CHANGED
|
@@ -23,6 +23,7 @@ const { config } = require('../core/config')
|
|
|
23
23
|
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
|
+
const { expandSkillTemplates, restoreSkillTemplates } = require('../core/lib/template-expander')
|
|
26
27
|
|
|
27
28
|
// Active cron jobs keyed by workflow ID
|
|
28
29
|
const activeJobs = new Map()
|
|
@@ -92,6 +93,17 @@ async function executeWorkflowSession(workflow, executionId, skillNames, options
|
|
|
92
93
|
console.log(`[WorkflowRunner] Log file: ${logFile}`)
|
|
93
94
|
console.log(`[WorkflowRunner] HOME: ${homeDir}`)
|
|
94
95
|
|
|
96
|
+
// Expand {{VAR}} templates in SKILL.md files with minion variables
|
|
97
|
+
let expandedOriginals = new Map()
|
|
98
|
+
try {
|
|
99
|
+
expandedOriginals = await expandSkillTemplates(skillNames)
|
|
100
|
+
if (expandedOriginals.size > 0) {
|
|
101
|
+
console.log(`[WorkflowRunner] Expanded templates in ${expandedOriginals.size} skill(s)`)
|
|
102
|
+
}
|
|
103
|
+
} catch (err) {
|
|
104
|
+
console.error(`[WorkflowRunner] Template expansion failed: ${err.message}`)
|
|
105
|
+
}
|
|
106
|
+
|
|
95
107
|
try {
|
|
96
108
|
await logManager.ensureLogDir()
|
|
97
109
|
await logManager.pruneOldLogs()
|
|
@@ -110,7 +122,7 @@ async function executeWorkflowSession(workflow, executionId, skillNames, options
|
|
|
110
122
|
const escapedPrompt = prompt.replace(/'/g, "''")
|
|
111
123
|
const llmCommand = config.LLM_COMMAND.replace(/\{prompt\}/g, escapedPrompt)
|
|
112
124
|
|
|
113
|
-
// PATH, HOME, USERPROFILE, and minion
|
|
125
|
+
// PATH, HOME, USERPROFILE, and minion secrets are already set in
|
|
114
126
|
// process.env at server startup, so child processes inherit them automatically.
|
|
115
127
|
|
|
116
128
|
// Open log file for streaming writes
|
|
@@ -192,6 +204,11 @@ async function executeWorkflowSession(workflow, executionId, skillNames, options
|
|
|
192
204
|
} catch (error) {
|
|
193
205
|
console.error(`[WorkflowRunner] Workflow ${workflow.name} failed: ${error.message}`)
|
|
194
206
|
return { success: false, error: error.message, sessionName }
|
|
207
|
+
} finally {
|
|
208
|
+
// Restore original SKILL.md files after execution
|
|
209
|
+
if (expandedOriginals.size > 0) {
|
|
210
|
+
await restoreSkillTemplates(expandedOriginals)
|
|
211
|
+
}
|
|
195
212
|
}
|
|
196
213
|
}
|
|
197
214
|
|