@leejungkiin/awkit 1.7.4 → 1.7.5
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 +42 -5
- package/bin/awk.js +579 -15
- package/core/GEMINI.md +4 -0
- package/core/GEMINI.md.bak +4 -0
- package/core/GreetingEngine/index.js +225 -0
- package/core/GreetingEngine/locales/en.json +6 -0
- package/core/GreetingEngine/locales/ja.json +6 -0
- package/core/GreetingEngine/locales/vi.json +6 -0
- package/core/GreetingEngine/test.js +37 -0
- package/docs/PRD.md +57 -0
- package/docs/brainstorm_multi_agent_cli.md +211 -0
- package/docs/history/PRD.v1.md +55 -0
- package/docs/history/PRD.v2.md +57 -0
- package/docs/history/memoizedfibonacci_spec.v1.md +127 -0
- package/docs/specs/greetingapp_spec.md +97 -0
- package/docs/specs/memoizedfibonacci_spec.md +211 -0
- package/package.json +1 -1
- package/scripts/multi-model-pipeline.js +292 -50
- package/scripts/qwen-exec.js +147 -0
- package/skills/claude-planner/SKILL.md +24 -0
- package/skills/codex-conductor/SKILL.md +25 -1
- package/skills/qwen-conductor/SKILL.md +100 -0
- package/workflows/multi-agent-pipeline.md +52 -0
|
@@ -3,11 +3,12 @@
|
|
|
3
3
|
* Orchestrates tasks across Gemini Flash Medium, agy (Claude Opus), and Codex.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
const { execSync } = require('child_process');
|
|
6
|
+
const { execSync, spawnSync } = require('child_process');
|
|
7
7
|
const fs = require('fs');
|
|
8
8
|
const path = require('path');
|
|
9
|
+
const os = require('os');
|
|
9
10
|
|
|
10
|
-
const AGY_PATH = '
|
|
11
|
+
const AGY_PATH = path.join(os.homedir(), '.local', 'bin', 'agy');
|
|
11
12
|
|
|
12
13
|
const C = {
|
|
13
14
|
reset: '\x1b[0m',
|
|
@@ -25,6 +26,15 @@ const warn = (msg) => log(`${C.yellow}⚠${C.reset} ${msg}`);
|
|
|
25
26
|
const err = (msg) => log(`${C.red}✖${C.reset} ${msg}`);
|
|
26
27
|
const info = (msg) => log(`${C.cyan}ℹ${C.reset} ${msg}`);
|
|
27
28
|
|
|
29
|
+
// Load configurations from .project-identity
|
|
30
|
+
let config = {};
|
|
31
|
+
try {
|
|
32
|
+
const configPath = path.join(__dirname, '..', '.project-identity');
|
|
33
|
+
if (fs.existsSync(configPath)) {
|
|
34
|
+
config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
35
|
+
}
|
|
36
|
+
} catch (_) {}
|
|
37
|
+
|
|
28
38
|
function findAgy() {
|
|
29
39
|
if (fs.existsSync(AGY_PATH)) {
|
|
30
40
|
return AGY_PATH;
|
|
@@ -36,9 +46,135 @@ function findAgy() {
|
|
|
36
46
|
}
|
|
37
47
|
}
|
|
38
48
|
|
|
49
|
+
function findClaudePath() {
|
|
50
|
+
if (config.automation?.multiAgent?.runners?.claude) {
|
|
51
|
+
const p = config.automation.multiAgent.runners.claude;
|
|
52
|
+
if (fs.existsSync(p)) return p;
|
|
53
|
+
}
|
|
54
|
+
const homeClaude = path.join(os.homedir(), '.claude', 'local', 'claude');
|
|
55
|
+
if (fs.existsSync(homeClaude)) return homeClaude;
|
|
56
|
+
|
|
57
|
+
try {
|
|
58
|
+
const shell = process.env.SHELL || '/bin/zsh';
|
|
59
|
+
const shellCmd = shell.endsWith('zsh') ? 'zsh -lc "which claude"' : 'bash -lc "which claude"';
|
|
60
|
+
const detected = execSync(shellCmd, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim();
|
|
61
|
+
if (detected && fs.existsSync(detected)) return detected;
|
|
62
|
+
} catch (_) {}
|
|
63
|
+
|
|
64
|
+
return 'claude';
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function findCodexPath() {
|
|
68
|
+
if (config.automation?.multiAgent?.runners?.codex) {
|
|
69
|
+
const p = config.automation.multiAgent.runners.codex;
|
|
70
|
+
if (fs.existsSync(p)) return p;
|
|
71
|
+
}
|
|
72
|
+
try {
|
|
73
|
+
const shell = process.env.SHELL || '/bin/zsh';
|
|
74
|
+
const shellCmd = shell.endsWith('zsh') ? 'zsh -lc "which codex"' : 'bash -lc "which codex"';
|
|
75
|
+
const detected = execSync(shellCmd, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim();
|
|
76
|
+
if (detected && fs.existsSync(detected)) return detected;
|
|
77
|
+
} catch (_) {}
|
|
78
|
+
return 'codex';
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function findQwenPath() {
|
|
82
|
+
if (config.automation?.multiAgent?.runners?.qwen) {
|
|
83
|
+
const p = config.automation.multiAgent.runners.qwen;
|
|
84
|
+
if (fs.existsSync(p)) return p;
|
|
85
|
+
}
|
|
86
|
+
try {
|
|
87
|
+
const shell = process.env.SHELL || '/bin/zsh';
|
|
88
|
+
const shellCmd = shell.endsWith('zsh') ? 'zsh -lc "which qwen"' : 'bash -lc "which qwen"';
|
|
89
|
+
const detected = execSync(shellCmd, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim();
|
|
90
|
+
if (detected && fs.existsSync(detected)) return detected;
|
|
91
|
+
} catch (_) {}
|
|
92
|
+
return 'qwen';
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function readGlobalConfig() {
|
|
96
|
+
const defaultConf = {
|
|
97
|
+
audioAlerts: true,
|
|
98
|
+
runners: {
|
|
99
|
+
claude: true,
|
|
100
|
+
codex: true,
|
|
101
|
+
qwen: true
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
try {
|
|
105
|
+
const globalConfigPath = path.join(os.homedir(), '.awkit_config.json');
|
|
106
|
+
if (fs.existsSync(globalConfigPath)) {
|
|
107
|
+
const parsed = JSON.parse(fs.readFileSync(globalConfigPath, 'utf8'));
|
|
108
|
+
return {
|
|
109
|
+
...defaultConf,
|
|
110
|
+
...parsed,
|
|
111
|
+
runners: {
|
|
112
|
+
...defaultConf.runners,
|
|
113
|
+
...(parsed.runners || {})
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
} catch (_) {}
|
|
118
|
+
return defaultConf;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function isRunnerEnabled(runnerName) {
|
|
122
|
+
const localVal = config.automation?.multiAgent?.runnersEnabled?.[runnerName];
|
|
123
|
+
if (localVal !== undefined) return localVal;
|
|
124
|
+
|
|
125
|
+
const globalConfig = readGlobalConfig();
|
|
126
|
+
const globalVal = globalConfig.runners?.[runnerName];
|
|
127
|
+
if (globalVal !== undefined) return globalVal;
|
|
128
|
+
|
|
129
|
+
return true;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function isRunnerAvailable(runnerName) {
|
|
133
|
+
if (!isRunnerEnabled(runnerName)) return false;
|
|
134
|
+
let p;
|
|
135
|
+
if (runnerName === 'claude') p = findClaudePath();
|
|
136
|
+
else if (runnerName === 'codex') p = findCodexPath();
|
|
137
|
+
else if (runnerName === 'qwen') p = findQwenPath();
|
|
138
|
+
else return false;
|
|
139
|
+
|
|
140
|
+
if (fs.existsSync(p)) return true;
|
|
141
|
+
try {
|
|
142
|
+
execSync(`which ${p} || command -v ${p}`, { stdio: 'ignore' });
|
|
143
|
+
return true;
|
|
144
|
+
} catch (_) {
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
function playAudioAlert(message = "Quy trình đã hoàn thành") {
|
|
151
|
+
// 1. Read global configuration
|
|
152
|
+
let globalAudio = true;
|
|
153
|
+
try {
|
|
154
|
+
const globalConfigPath = path.join(os.homedir(), '.awkit_config.json');
|
|
155
|
+
if (fs.existsSync(globalConfigPath)) {
|
|
156
|
+
const globalConfig = JSON.parse(fs.readFileSync(globalConfigPath, 'utf8'));
|
|
157
|
+
if (globalConfig.audioAlerts !== undefined) {
|
|
158
|
+
globalAudio = globalConfig.audioAlerts;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
} catch (_) {}
|
|
162
|
+
|
|
163
|
+
// 2. Read local project identity config
|
|
164
|
+
let localAudio = config.automation?.multiAgent?.audioAlerts;
|
|
165
|
+
|
|
166
|
+
// Local configuration takes priority. If not defined, fallback to global config.
|
|
167
|
+
const audioEnabled = localAudio !== undefined ? localAudio : globalAudio;
|
|
168
|
+
|
|
169
|
+
if (audioEnabled) {
|
|
170
|
+
try {
|
|
171
|
+
execSync(`afplay /System/Library/Sounds/Glass.aiff && say "${message}"`, { stdio: 'ignore' });
|
|
172
|
+
} catch (_) {}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
39
176
|
function runPlanStep(featureName) {
|
|
40
|
-
|
|
41
|
-
info(`Khởi chạy Lập kế hoạch Kiến trúc (Opus 4.6 via agy CLI)...`);
|
|
177
|
+
info(`Khởi chạy Lập kế hoạch Kiến trúc...`);
|
|
42
178
|
|
|
43
179
|
// Ensure docs and docs/specs directories exist
|
|
44
180
|
fs.mkdirSync(path.join(process.cwd(), 'docs', 'specs'), { recursive: true });
|
|
@@ -46,72 +182,178 @@ function runPlanStep(featureName) {
|
|
|
46
182
|
const specFile = `docs/specs/${featureName.toLowerCase().replace(/\s+/g, '_')}_spec.md`;
|
|
47
183
|
const prompt = `Thực hiện lập kế hoạch chi tiết cho tính năng '${featureName}'. Tạo PRD tại docs/PRD.md và Spec chi tiết tại ${specFile}. Hãy vẽ kèm sơ đồ Mermaid thể hiện luồng xử lý. Hãy phản hồi ngắn gọn bằng JSON tóm tắt các files đã tạo và mô tả kế hoạch.`;
|
|
48
184
|
|
|
49
|
-
|
|
185
|
+
let planSuccess = false;
|
|
50
186
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
187
|
+
if (isRunnerAvailable('claude')) {
|
|
188
|
+
const claudePath = findClaudePath();
|
|
189
|
+
try {
|
|
190
|
+
info(`Calling Claude CLI (${claudePath}) for Architect Planning...`);
|
|
191
|
+
log(`$ ${claudePath} -p "..." --print --no-session-persistence --permission-mode bypassPermissions`);
|
|
192
|
+
const result = spawnSync(claudePath, [
|
|
193
|
+
'-p', prompt,
|
|
194
|
+
'--print',
|
|
195
|
+
'--no-session-persistence',
|
|
196
|
+
'--permission-mode', 'bypassPermissions'
|
|
197
|
+
], { encoding: 'utf8', timeout: 30000 }); // 30s timeout for Claude
|
|
198
|
+
|
|
199
|
+
if (result.status === 0) {
|
|
200
|
+
log(result.stdout);
|
|
201
|
+
planSuccess = true;
|
|
202
|
+
} else {
|
|
203
|
+
warn(`Claude planning failed: ${result.stderr || result.stdout}`);
|
|
204
|
+
}
|
|
205
|
+
} catch (e) {
|
|
206
|
+
warn(`Claude planning failed or timed out: ${e.message}. Falling back to agy (Gemini)...`);
|
|
207
|
+
}
|
|
208
|
+
} else {
|
|
209
|
+
info(`Claude CLI is disabled or not available. Falling back directly to agy (Gemini)...`);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (!planSuccess) {
|
|
213
|
+
const agyBin = findAgy();
|
|
214
|
+
try {
|
|
215
|
+
log(`$ ${agyBin} --model gemini-3.5-flash --print "..."`);
|
|
216
|
+
const result = spawnSync(agyBin, [
|
|
217
|
+
'--model', 'gemini-3.5-flash',
|
|
218
|
+
'--print', prompt
|
|
219
|
+
], { encoding: 'utf8', timeout: 60000 });
|
|
220
|
+
|
|
221
|
+
if (result.status === 0) {
|
|
222
|
+
log(result.stdout);
|
|
223
|
+
} else {
|
|
224
|
+
err(`Lỗi lập kế hoạch: ${result.stderr || result.stdout}`);
|
|
225
|
+
process.exit(1);
|
|
226
|
+
}
|
|
227
|
+
} catch (e) {
|
|
228
|
+
err(`Lỗi lập kế hoạch: ${e.message}`);
|
|
229
|
+
process.exit(1);
|
|
230
|
+
}
|
|
61
231
|
}
|
|
232
|
+
|
|
233
|
+
ok(`Hoàn thành lập kế hoạch kiến trúc.`);
|
|
234
|
+
ok(`PRD đã lưu tại docs/PRD.md`);
|
|
235
|
+
ok(`Spec đã lưu tại ${specFile}`);
|
|
236
|
+
playAudioAlert("Kế hoạch kiến trúc đã hoàn thành");
|
|
62
237
|
}
|
|
63
238
|
|
|
64
239
|
function runUiStep(featureName) {
|
|
65
|
-
info(`Khởi chạy Thiết kế Shell & GUI Assets (Codex CLI)...`);
|
|
66
|
-
|
|
67
240
|
// Ensure assets/ui directory exists
|
|
68
241
|
fs.mkdirSync(path.join(process.cwd(), 'assets', 'ui'), { recursive: true });
|
|
69
242
|
|
|
70
243
|
// Step A: Prompt to generate layout demo preview HTML/CSS or mockup code
|
|
71
244
|
const promptPreview = `Tạo UI Demo Preview dưới dạng HTML/CSS tĩnh hoặc SwiftUI shell cho tính năng '${featureName}' dựa trên tài liệu docs/PRD.md và spec hiện có. Lưu tệp demo vào assets/ui/demo_preview.html.`;
|
|
72
|
-
const cmdPreview = `codex exec "${promptPreview.replace(/"/g, '\\"')}" --sandbox read-only < /dev/null`;
|
|
73
245
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
246
|
+
if (isRunnerAvailable('codex')) {
|
|
247
|
+
info(`Khởi chạy Thiết kế Shell & GUI Assets (Codex CLI)...`);
|
|
248
|
+
const cmdPreview = `codex exec "${promptPreview.replace(/"/g, '\\"')}" -s read-only --skip-git-repo-check < /dev/null`;
|
|
249
|
+
try {
|
|
250
|
+
log(`${C.gray}$ ${cmdPreview}${C.reset}`);
|
|
251
|
+
const outPreview = execSync(cmdPreview, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'inherit'] });
|
|
252
|
+
log(outPreview);
|
|
253
|
+
ok(`Đã sinh UI Demo Preview tại assets/ui/demo_preview.html`);
|
|
254
|
+
} catch (e) {
|
|
255
|
+
err(`Lỗi sinh UI Demo Preview: ${e.message}`);
|
|
256
|
+
process.exit(1);
|
|
257
|
+
}
|
|
83
258
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
259
|
+
// Step B: Prompt to generate spritesheet/image assets
|
|
260
|
+
const promptAssets = `Tạo GUI assets (nút bấm, icons, pet companion, egg) dưới dạng spritesheet hoặc dạng assets lẻ dựa trên thiết kế của UI Demo Preview. Lưu tệp assets vào thư mục assets/ui/.`;
|
|
261
|
+
const cmdAssets = `codex exec "${promptAssets.replace(/"/g, '\\"')}" -s read-only --skip-git-repo-check < /dev/null`;
|
|
262
|
+
try {
|
|
263
|
+
log(`${C.gray}$ ${cmdAssets}${C.reset}`);
|
|
264
|
+
const outAssets = execSync(cmdAssets, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'inherit'] });
|
|
265
|
+
log(outAssets);
|
|
266
|
+
ok(`Đã sinh GUI assets thành công vào assets/ui/`);
|
|
267
|
+
} catch (e) {
|
|
268
|
+
err(`Lỗi sinh GUI assets: ${e.message}`);
|
|
269
|
+
process.exit(1);
|
|
270
|
+
}
|
|
271
|
+
} else {
|
|
272
|
+
info(`Codex CLI is disabled or not available. Falling back to agy (Gemini)...`);
|
|
273
|
+
const agyBin = findAgy();
|
|
274
|
+
try {
|
|
275
|
+
log(`$ ${agyBin} --model gemini-3.5-flash --print "..."`);
|
|
276
|
+
const outPreview = spawnSync(agyBin, [
|
|
277
|
+
'--model', 'gemini-3.5-flash',
|
|
278
|
+
'--print', promptPreview
|
|
279
|
+
], { encoding: 'utf8', timeout: 60000 });
|
|
87
280
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
281
|
+
if (outPreview.status === 0) {
|
|
282
|
+
let code = outPreview.stdout;
|
|
283
|
+
const match = code.match(/```html\s*([\s\S]*?)```/i) || code.match(/```xml\s*([\s\S]*?)```/i);
|
|
284
|
+
if (match) {
|
|
285
|
+
code = match[1];
|
|
286
|
+
}
|
|
287
|
+
fs.writeFileSync(path.join(process.cwd(), 'assets', 'ui', 'demo_preview.html'), code, 'utf8');
|
|
288
|
+
ok(`Đã sinh UI Demo Preview tại assets/ui/demo_preview.html`);
|
|
289
|
+
} else {
|
|
290
|
+
err(`Gemini UI Design failed: ${outPreview.stderr || outPreview.stdout}`);
|
|
291
|
+
process.exit(1);
|
|
292
|
+
}
|
|
293
|
+
} catch (e) {
|
|
294
|
+
err(`Lỗi sinh UI Demo Preview qua Gemini: ${e.message}`);
|
|
295
|
+
process.exit(1);
|
|
296
|
+
}
|
|
96
297
|
}
|
|
298
|
+
|
|
299
|
+
playAudioAlert("Thiết kế giao diện đã hoàn thành");
|
|
97
300
|
}
|
|
98
301
|
|
|
99
302
|
function runCodeStep(featureName) {
|
|
100
|
-
const
|
|
101
|
-
info(`Khởi chạy Thực thi Viết Code (Gemini via agy CLI)...`);
|
|
303
|
+
const isCostOptimized = config.automation?.multiAgent?.routingMode === 'cost-optimized';
|
|
102
304
|
|
|
103
|
-
|
|
104
|
-
|
|
305
|
+
if (isCostOptimized && isRunnerAvailable('qwen')) {
|
|
306
|
+
info(`Khởi chạy Thực thi Viết Code (Qwen Coder CLI)...`);
|
|
307
|
+
const promptCode = `Hãy thực hiện viết code cho tính năng '${featureName}' tuân thủ kế hoạch chi tiết trong implementation_plan.md và các spec giao diện trong docs/specs/. Sau đó tự động gọi critic subagent để review code.`;
|
|
308
|
+
|
|
309
|
+
try {
|
|
310
|
+
const qwenScript = path.join(__dirname, 'qwen-exec.js');
|
|
311
|
+
log(`$ node ${qwenScript} --prompt "..."`);
|
|
312
|
+
const result = spawnSync('node', [
|
|
313
|
+
qwenScript,
|
|
314
|
+
'--prompt', promptCode
|
|
315
|
+
], { encoding: 'utf8', timeout: 180000 });
|
|
105
316
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
317
|
+
if (result.status === 0) {
|
|
318
|
+
log(result.stdout);
|
|
319
|
+
ok(`Đã viết code và hoàn tất quá trình review từ critic subagent.`);
|
|
320
|
+
} else {
|
|
321
|
+
err(`Lỗi thực thi viết code Qwen: ${result.stderr || result.stdout}`);
|
|
322
|
+
process.exit(1);
|
|
323
|
+
}
|
|
324
|
+
} catch (e) {
|
|
325
|
+
err(`Lỗi thực thi viết code Qwen: ${e.message}`);
|
|
326
|
+
process.exit(1);
|
|
327
|
+
}
|
|
328
|
+
} else {
|
|
329
|
+
const agyBin = findAgy();
|
|
330
|
+
if (isCostOptimized && !isRunnerAvailable('qwen')) {
|
|
331
|
+
info(`Qwen Coder CLI is disabled or not available. Falling back to agy (Gemini)...`);
|
|
332
|
+
}
|
|
333
|
+
info(`Khởi chạy Thực thi Viết Code (Gemini via agy CLI)...`);
|
|
334
|
+
const promptCode = `Hãy thực hiện viết code cho tính năng '${featureName}' tuân thủ kế hoạch chi tiết trong implementation_plan.md và các spec giao diện trong docs/specs/. Sau đó tự động gọi critic subagent để review code.`;
|
|
335
|
+
|
|
336
|
+
try {
|
|
337
|
+
log(`$ ${agyBin} --model gemini-3.5-flash --print "..."`);
|
|
338
|
+
const result = spawnSync(agyBin, [
|
|
339
|
+
'--model', 'gemini-3.5-flash',
|
|
340
|
+
'--print', promptCode
|
|
341
|
+
], { encoding: 'utf8', timeout: 180000 });
|
|
342
|
+
|
|
343
|
+
if (result.status === 0) {
|
|
344
|
+
log(result.stdout);
|
|
345
|
+
ok(`Đã viết code và hoàn tất quá trình review từ critic subagent.`);
|
|
346
|
+
} else {
|
|
347
|
+
err(`Lỗi thực thi viết code: ${result.stderr || result.stdout}`);
|
|
348
|
+
process.exit(1);
|
|
349
|
+
}
|
|
350
|
+
} catch (e) {
|
|
351
|
+
err(`Lỗi thực thi viết code: ${e.message}`);
|
|
352
|
+
process.exit(1);
|
|
353
|
+
}
|
|
114
354
|
}
|
|
355
|
+
|
|
356
|
+
playAudioAlert("Viết mã nguồn đã hoàn thành");
|
|
115
357
|
}
|
|
116
358
|
|
|
117
359
|
function cmdPipeline(args) {
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Wrapper script to run Qwen CLI non-interactively within AWKit.
|
|
5
|
+
* Handles temporary files, sandbox options, and fallback protocols.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const fs = require('fs');
|
|
9
|
+
const path = require('path');
|
|
10
|
+
const os = require('os');
|
|
11
|
+
const { execSync } = require('child_process');
|
|
12
|
+
|
|
13
|
+
const args = process.argv.slice(2);
|
|
14
|
+
let prompt = '';
|
|
15
|
+
let promptFile = '';
|
|
16
|
+
let outputFile = '';
|
|
17
|
+
let sandbox = 'read-only';
|
|
18
|
+
|
|
19
|
+
for (let i = 0; i < args.length; i++) {
|
|
20
|
+
if (args[i] === '--prompt' && args[i + 1]) {
|
|
21
|
+
prompt = args[i + 1];
|
|
22
|
+
i++;
|
|
23
|
+
} else if (args[i] === '--prompt-file' && args[i + 1]) {
|
|
24
|
+
promptFile = args[i + 1];
|
|
25
|
+
i++;
|
|
26
|
+
} else if (args[i] === '--output' && args[i + 1]) {
|
|
27
|
+
outputFile = args[i + 1];
|
|
28
|
+
i++;
|
|
29
|
+
} else if (args[i] === '--sandbox' && args[i + 1]) {
|
|
30
|
+
sandbox = args[i + 1];
|
|
31
|
+
i++;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Read prompt from file if specified
|
|
36
|
+
if (promptFile && fs.existsSync(promptFile)) {
|
|
37
|
+
prompt = fs.readFileSync(promptFile, 'utf8');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (!prompt) {
|
|
41
|
+
console.error('Error: Prompt is required. Use --prompt or --prompt-file');
|
|
42
|
+
process.exit(1);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Load configurations from .project-identity
|
|
46
|
+
let routingMode = 'cost-optimized';
|
|
47
|
+
let config = {};
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
const identityPath = path.join(__dirname, '..', '.project-identity');
|
|
51
|
+
if (fs.existsSync(identityPath)) {
|
|
52
|
+
config = JSON.parse(fs.readFileSync(identityPath, 'utf8'));
|
|
53
|
+
if (config.automation?.multiAgent?.routingMode) {
|
|
54
|
+
routingMode = config.automation.multiAgent.routingMode;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
} catch (e) {
|
|
58
|
+
console.warn('⚠️ Could not load config from .project-identity, using defaults.');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Dynamic Qwen CLI Path Detection
|
|
62
|
+
function findQwenPath() {
|
|
63
|
+
// 1. Check config path
|
|
64
|
+
if (config.automation?.multiAgent?.runners?.qwen) {
|
|
65
|
+
const p = config.automation.multiAgent.runners.qwen;
|
|
66
|
+
if (fs.existsSync(p)) return p;
|
|
67
|
+
}
|
|
68
|
+
// 2. Try interactive shell which (resolves aliases/nvm)
|
|
69
|
+
try {
|
|
70
|
+
const shell = process.env.SHELL || '/bin/zsh';
|
|
71
|
+
const shellCmd = shell.endsWith('zsh') ? 'zsh -lc "which qwen"' : 'bash -lc "which qwen"';
|
|
72
|
+
const detected = execSync(shellCmd, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim();
|
|
73
|
+
if (detected && fs.existsSync(detected)) return detected;
|
|
74
|
+
} catch (_) {}
|
|
75
|
+
// 3. Basic command check
|
|
76
|
+
try {
|
|
77
|
+
const detected = execSync('which qwen || command -v qwen', { encoding: 'utf8' }).trim();
|
|
78
|
+
if (detected) return detected;
|
|
79
|
+
} catch (_) {}
|
|
80
|
+
|
|
81
|
+
return 'qwen';
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const qwenPath = findQwenPath();
|
|
85
|
+
|
|
86
|
+
// Verify Qwen CLI existence
|
|
87
|
+
let qwenAvailable = false;
|
|
88
|
+
try {
|
|
89
|
+
execSync(`[ -f "${qwenPath}" ] || command -v qwen`, { stdio: 'ignore' });
|
|
90
|
+
qwenAvailable = true;
|
|
91
|
+
} catch (e) {
|
|
92
|
+
console.warn(`⚠️ Qwen CLI not found at ${qwenPath}`);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Temporary file to pipe inputs securely
|
|
96
|
+
const tempInputFile = path.join(__dirname, `temp_qwen_input_${Date.now()}.txt`);
|
|
97
|
+
fs.writeFileSync(tempInputFile, prompt, 'utf8');
|
|
98
|
+
|
|
99
|
+
let success = false;
|
|
100
|
+
let finalOutput = '';
|
|
101
|
+
|
|
102
|
+
if (qwenAvailable) {
|
|
103
|
+
console.log('🤖 Running code generation via Qwen CLI...');
|
|
104
|
+
try {
|
|
105
|
+
const cmd = `"${qwenPath}" --max-session-turns 1 -s ${sandbox} < "${tempInputFile}"`;
|
|
106
|
+
finalOutput = execSync(cmd, { encoding: 'utf8', timeout: 120000 });
|
|
107
|
+
success = true;
|
|
108
|
+
} catch (e) {
|
|
109
|
+
console.warn('⚠️ Qwen execution failed. Fallback triggered.');
|
|
110
|
+
console.error(e.message);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Fallback to Gemini CLI (agy) if Qwen failed or unavailable
|
|
115
|
+
if (!success) {
|
|
116
|
+
console.log('📡 Falling back to Gemini CLI (agy)...');
|
|
117
|
+
try {
|
|
118
|
+
const agyBin = 'agy';
|
|
119
|
+
const cmd = `"${agyBin}" --model gemini-3.5-flash --print "${prompt.replace(/"/g, '\\"')}" < /dev/null`;
|
|
120
|
+
finalOutput = execSync(cmd, { encoding: 'utf8', timeout: 60000 });
|
|
121
|
+
success = true;
|
|
122
|
+
} catch (e) {
|
|
123
|
+
console.error('❌ Fallback to Gemini CLI also failed:', e.message);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Clean up temporary file
|
|
128
|
+
try {
|
|
129
|
+
if (fs.existsSync(tempInputFile)) {
|
|
130
|
+
fs.unlinkSync(tempInputFile);
|
|
131
|
+
}
|
|
132
|
+
} catch (_) {}
|
|
133
|
+
|
|
134
|
+
// Output results
|
|
135
|
+
if (success) {
|
|
136
|
+
if (outputFile) {
|
|
137
|
+
fs.mkdirSync(path.dirname(outputFile), { recursive: true });
|
|
138
|
+
fs.writeFileSync(outputFile, finalOutput, 'utf8');
|
|
139
|
+
console.log(`✅ Output written to ${outputFile}`);
|
|
140
|
+
} else {
|
|
141
|
+
process.stdout.write(finalOutput);
|
|
142
|
+
}
|
|
143
|
+
process.exit(0);
|
|
144
|
+
} else {
|
|
145
|
+
console.error('❌ Failed to execute code step with both Qwen and fallback Gemini.');
|
|
146
|
+
process.exit(1);
|
|
147
|
+
}
|
|
@@ -45,3 +45,27 @@ When writing `brain/<projectId>/plan_prompt.md`, ensure it contains:
|
|
|
45
45
|
4. NeuralMemory relevant constraints.
|
|
46
46
|
5. Symphony tasks.
|
|
47
47
|
6. The target format instructions for `implementation_plan.md`.
|
|
48
|
+
|
|
49
|
+
## ⚙️ Configuration & Control Techniques
|
|
50
|
+
|
|
51
|
+
### 1. Check Runner Status
|
|
52
|
+
Verify if the Claude planner runner is enabled globally:
|
|
53
|
+
```bash
|
|
54
|
+
awkit config claude
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### 2. Enable/Disable Runner
|
|
58
|
+
To temporarily disable Claude planner (forcing fallback to Gemini standard models) or enable it:
|
|
59
|
+
```bash
|
|
60
|
+
awkit config claude off
|
|
61
|
+
awkit config claude on
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### 3. Check All Runners
|
|
65
|
+
```bash
|
|
66
|
+
awkit config runners
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### 4. Active Routing & Fallback
|
|
70
|
+
If `claude` is disabled or not installed on the system, the pipeline automatically routes Gate 2 architect planning to **Gemini Flash** via `agy` CLI using `gemini-3.5-flash`.
|
|
71
|
+
|
|
@@ -130,6 +130,30 @@ Parallel: gemini-conductor (different role, can coexist)
|
|
|
130
130
|
Independent of: NeuralMemory
|
|
131
131
|
```
|
|
132
132
|
|
|
133
|
+
## ⚙️ Configuration & Control Techniques
|
|
134
|
+
|
|
135
|
+
### 1. Check Runner Status
|
|
136
|
+
Verify if the Codex runner is enabled globally:
|
|
137
|
+
```bash
|
|
138
|
+
awkit config codex
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### 2. Enable/Disable Runner
|
|
142
|
+
To temporarily disable Codex runner (forcing fallback to Gemini standard models) or enable it:
|
|
143
|
+
```bash
|
|
144
|
+
awkit config codex off
|
|
145
|
+
awkit config codex on
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### 3. Check All Runners
|
|
149
|
+
```bash
|
|
150
|
+
awkit config runners
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### 4. Active Routing & Fallback
|
|
154
|
+
If `codex` is disabled or unavailable on the system, the pipeline automatically routes UI design tasks (Gate 2.5 shell design) to **Gemini Flash** via `agy` CLI using `gemini-3.5-flash` and performs automatic HTML code extraction to save code to `assets/ui/demo_preview.html`.
|
|
155
|
+
|
|
133
156
|
---
|
|
134
157
|
|
|
135
|
-
*codex-conductor v1.
|
|
158
|
+
*codex-conductor v1.2 — Modular Router Architecture*
|
|
159
|
+
|