triflux 3.2.0-dev.7 → 3.2.0-dev.9
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/bin/triflux.mjs +557 -251
- package/hooks/keyword-rules.json +16 -0
- package/hub/bridge.mjs +410 -318
- package/hub/hitl.mjs +45 -31
- package/hub/pipe.mjs +457 -0
- package/hub/router.mjs +422 -161
- package/hub/server.mjs +429 -424
- package/hub/store.mjs +388 -314
- package/hub/team/cli-team-common.mjs +348 -0
- package/hub/team/cli-team-control.mjs +393 -0
- package/hub/team/cli-team-start.mjs +512 -0
- package/hub/team/cli-team-status.mjs +269 -0
- package/hub/team/cli.mjs +59 -1459
- package/hub/team/dashboard.mjs +1 -9
- package/hub/team/native.mjs +12 -80
- package/hub/team/nativeProxy.mjs +121 -47
- package/hub/team/pane.mjs +66 -43
- package/hub/team/psmux.mjs +297 -0
- package/hub/team/session.mjs +354 -291
- package/hub/team/shared.mjs +13 -0
- package/hub/team/staleState.mjs +299 -0
- package/hub/tools.mjs +41 -52
- package/hub/workers/claude-worker.mjs +446 -0
- package/hub/workers/codex-mcp.mjs +414 -0
- package/hub/workers/factory.mjs +18 -0
- package/hub/workers/gemini-worker.mjs +349 -0
- package/hub/workers/interface.mjs +41 -0
- package/hud/hud-qos-status.mjs +4 -2
- package/package.json +4 -1
- package/scripts/keyword-detector.mjs +15 -0
- package/scripts/lib/keyword-rules.mjs +4 -1
- package/scripts/psmux-steering-prototype.sh +368 -0
- package/scripts/setup.mjs +128 -70
- package/scripts/tfx-route-worker.mjs +161 -0
- package/scripts/tfx-route.sh +415 -80
- package/skills/tfx-auto/SKILL.md +90 -564
- package/skills/tfx-auto-codex/SKILL.md +1 -3
- package/skills/tfx-codex/SKILL.md +1 -4
- package/skills/tfx-doctor/SKILL.md +1 -0
- package/skills/tfx-gemini/SKILL.md +1 -4
- package/skills/tfx-setup/SKILL.md +1 -4
- package/skills/tfx-team/SKILL.md +53 -62
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
// hub/workers/gemini-worker.mjs — Gemini headless subprocess 래퍼
|
|
2
|
+
// ADR-006: --output-format stream-json 기반 단발 실행 워커.
|
|
3
|
+
|
|
4
|
+
import { spawn } from 'node:child_process';
|
|
5
|
+
import readline from 'node:readline';
|
|
6
|
+
|
|
7
|
+
const DEFAULT_TIMEOUT_MS = 15 * 60 * 1000;
|
|
8
|
+
const DEFAULT_KILL_GRACE_MS = 1000;
|
|
9
|
+
|
|
10
|
+
function toStringList(value) {
|
|
11
|
+
if (!Array.isArray(value)) return [];
|
|
12
|
+
return value
|
|
13
|
+
.map((item) => String(item ?? '').trim())
|
|
14
|
+
.filter(Boolean);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function safeJsonParse(line) {
|
|
18
|
+
try {
|
|
19
|
+
return JSON.parse(line);
|
|
20
|
+
} catch {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function appendTextFragments(value, parts) {
|
|
26
|
+
if (value == null) return;
|
|
27
|
+
if (typeof value === 'string') {
|
|
28
|
+
const trimmed = value.trim();
|
|
29
|
+
if (trimmed) parts.push(trimmed);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
if (Array.isArray(value)) {
|
|
33
|
+
for (const item of value) appendTextFragments(item, parts);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
if (typeof value === 'object') {
|
|
37
|
+
if (typeof value.text === 'string') appendTextFragments(value.text, parts);
|
|
38
|
+
if (typeof value.response === 'string') appendTextFragments(value.response, parts);
|
|
39
|
+
if (typeof value.result === 'string') appendTextFragments(value.result, parts);
|
|
40
|
+
if (typeof value.content === 'string' || Array.isArray(value.content) || value.content) {
|
|
41
|
+
appendTextFragments(value.content, parts);
|
|
42
|
+
}
|
|
43
|
+
if (value.message) appendTextFragments(value.message, parts);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function extractText(event) {
|
|
48
|
+
const parts = [];
|
|
49
|
+
appendTextFragments(event, parts);
|
|
50
|
+
return parts.join('\n').trim();
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function findLastEvent(events, predicate) {
|
|
54
|
+
for (let index = events.length - 1; index >= 0; index -= 1) {
|
|
55
|
+
if (predicate(events[index])) return events[index];
|
|
56
|
+
}
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function buildGeminiArgs(options) {
|
|
61
|
+
const args = [];
|
|
62
|
+
|
|
63
|
+
if (options.model) {
|
|
64
|
+
args.push('--model', options.model);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (options.approvalMode) {
|
|
68
|
+
args.push('--approval-mode', options.approvalMode);
|
|
69
|
+
} else if (options.yolo !== false) {
|
|
70
|
+
args.push('--yolo');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const allowedMcpServers = toStringList(options.allowedMcpServerNames);
|
|
74
|
+
if (allowedMcpServers.length) {
|
|
75
|
+
args.push('--allowed-mcp-server-names', ...allowedMcpServers);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const extraArgs = toStringList(options.extraArgs);
|
|
79
|
+
if (extraArgs.length) args.push(...extraArgs);
|
|
80
|
+
|
|
81
|
+
args.push('--prompt', options.promptArgument ?? '');
|
|
82
|
+
args.push('--output-format', 'stream-json');
|
|
83
|
+
|
|
84
|
+
return args;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function createWorkerError(message, details = {}) {
|
|
88
|
+
const error = new Error(message);
|
|
89
|
+
Object.assign(error, details);
|
|
90
|
+
return error;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Gemini stream-json 래퍼
|
|
95
|
+
*/
|
|
96
|
+
export class GeminiWorker {
|
|
97
|
+
type = 'gemini';
|
|
98
|
+
|
|
99
|
+
constructor(options = {}) {
|
|
100
|
+
this.command = options.command || 'gemini';
|
|
101
|
+
this.commandArgs = toStringList(options.commandArgs || options.args);
|
|
102
|
+
this.cwd = options.cwd || process.cwd();
|
|
103
|
+
this.env = { ...process.env, ...(options.env || {}) };
|
|
104
|
+
this.model = options.model || null;
|
|
105
|
+
this.approvalMode = options.approvalMode || null;
|
|
106
|
+
this.yolo = options.yolo !== false;
|
|
107
|
+
this.allowedMcpServerNames = toStringList(options.allowedMcpServerNames);
|
|
108
|
+
this.extraArgs = toStringList(options.extraArgs);
|
|
109
|
+
this.timeoutMs = Number(options.timeoutMs) > 0 ? Number(options.timeoutMs) : DEFAULT_TIMEOUT_MS;
|
|
110
|
+
this.killGraceMs = Number(options.killGraceMs) > 0 ? Number(options.killGraceMs) : DEFAULT_KILL_GRACE_MS;
|
|
111
|
+
this.onEvent = typeof options.onEvent === 'function' ? options.onEvent : null;
|
|
112
|
+
|
|
113
|
+
this.state = 'idle';
|
|
114
|
+
this.child = null;
|
|
115
|
+
this.lastRun = null;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
getStatus() {
|
|
119
|
+
return {
|
|
120
|
+
type: 'gemini',
|
|
121
|
+
state: this.state,
|
|
122
|
+
pid: this.child?.pid || null,
|
|
123
|
+
last_run_at_ms: this.lastRun?.finishedAtMs || null,
|
|
124
|
+
last_exit_code: this.lastRun?.exitCode ?? null,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
async start() {
|
|
129
|
+
if (this.state === 'stopped') {
|
|
130
|
+
this.state = 'idle';
|
|
131
|
+
}
|
|
132
|
+
return this.getStatus();
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
async stop() {
|
|
136
|
+
if (!this.child) {
|
|
137
|
+
this.state = 'stopped';
|
|
138
|
+
return this.getStatus();
|
|
139
|
+
}
|
|
140
|
+
const child = this.child;
|
|
141
|
+
this._terminateChild(child);
|
|
142
|
+
await new Promise((resolve) => {
|
|
143
|
+
child.once('close', resolve);
|
|
144
|
+
setTimeout(resolve, this.killGraceMs + 50).unref?.();
|
|
145
|
+
});
|
|
146
|
+
this.child = null;
|
|
147
|
+
this.state = 'stopped';
|
|
148
|
+
return this.getStatus();
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
async restart() {
|
|
152
|
+
await this.stop();
|
|
153
|
+
this.state = 'idle';
|
|
154
|
+
return this.getStatus();
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
_terminateChild(child) {
|
|
158
|
+
if (!child || child.exitCode !== null || child.killed) return;
|
|
159
|
+
try { child.stdin.end(); } catch {}
|
|
160
|
+
try { child.kill(); } catch {}
|
|
161
|
+
|
|
162
|
+
const timer = setTimeout(() => {
|
|
163
|
+
if (child.exitCode === null) {
|
|
164
|
+
try { child.kill('SIGKILL'); } catch {}
|
|
165
|
+
}
|
|
166
|
+
}, this.killGraceMs);
|
|
167
|
+
timer.unref?.();
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
async run(prompt, options = {}) {
|
|
171
|
+
if (this.child) {
|
|
172
|
+
throw createWorkerError('GeminiWorker is already running', { code: 'WORKER_BUSY' });
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
await this.start();
|
|
176
|
+
|
|
177
|
+
const timeoutMs = Number(options.timeoutMs) > 0 ? Number(options.timeoutMs) : this.timeoutMs;
|
|
178
|
+
const startedAtMs = Date.now();
|
|
179
|
+
const args = [
|
|
180
|
+
...this.commandArgs,
|
|
181
|
+
...buildGeminiArgs({
|
|
182
|
+
model: options.model || this.model,
|
|
183
|
+
approvalMode: options.approvalMode || this.approvalMode,
|
|
184
|
+
yolo: options.yolo ?? this.yolo,
|
|
185
|
+
allowedMcpServerNames: options.allowedMcpServerNames || this.allowedMcpServerNames,
|
|
186
|
+
extraArgs: options.extraArgs || this.extraArgs,
|
|
187
|
+
promptArgument: options.promptArgument ?? '',
|
|
188
|
+
}),
|
|
189
|
+
];
|
|
190
|
+
|
|
191
|
+
const child = spawn(this.command, args, {
|
|
192
|
+
cwd: options.cwd || this.cwd,
|
|
193
|
+
env: { ...this.env, ...(options.env || {}) },
|
|
194
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
195
|
+
windowsHide: true,
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
this.child = child;
|
|
199
|
+
this.state = 'running';
|
|
200
|
+
|
|
201
|
+
const events = [];
|
|
202
|
+
const stdoutLines = [];
|
|
203
|
+
const stderrLines = [];
|
|
204
|
+
let lastErrorEvent = null;
|
|
205
|
+
let timedOut = false;
|
|
206
|
+
let exitCode = null;
|
|
207
|
+
let exitSignal = null;
|
|
208
|
+
|
|
209
|
+
const stdoutReader = readline.createInterface({
|
|
210
|
+
input: child.stdout,
|
|
211
|
+
crlfDelay: Infinity,
|
|
212
|
+
});
|
|
213
|
+
const stderrReader = readline.createInterface({
|
|
214
|
+
input: child.stderr,
|
|
215
|
+
crlfDelay: Infinity,
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
stdoutReader.on('line', (line) => {
|
|
219
|
+
if (!line) return;
|
|
220
|
+
const event = safeJsonParse(line);
|
|
221
|
+
if (!event) {
|
|
222
|
+
stdoutLines.push(line);
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
events.push(event);
|
|
227
|
+
if (event.type === 'error') lastErrorEvent = event;
|
|
228
|
+
if (this.onEvent) {
|
|
229
|
+
try { this.onEvent(event); } catch {}
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
stderrReader.on('line', (line) => {
|
|
234
|
+
if (!line) return;
|
|
235
|
+
stderrLines.push(line);
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
const closePromise = new Promise((resolve, reject) => {
|
|
239
|
+
child.once('error', reject);
|
|
240
|
+
child.once('close', (code, signal) => {
|
|
241
|
+
exitCode = code;
|
|
242
|
+
exitSignal = signal;
|
|
243
|
+
resolve();
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
const timeout = setTimeout(() => {
|
|
248
|
+
timedOut = true;
|
|
249
|
+
this._terminateChild(child);
|
|
250
|
+
}, timeoutMs);
|
|
251
|
+
timeout.unref?.();
|
|
252
|
+
|
|
253
|
+
child.stdin.on('error', () => {});
|
|
254
|
+
child.stdin.end(String(prompt ?? ''));
|
|
255
|
+
|
|
256
|
+
try {
|
|
257
|
+
await closePromise;
|
|
258
|
+
} finally {
|
|
259
|
+
clearTimeout(timeout);
|
|
260
|
+
stdoutReader.close();
|
|
261
|
+
stderrReader.close();
|
|
262
|
+
if (this.child === child) {
|
|
263
|
+
this.child = null;
|
|
264
|
+
}
|
|
265
|
+
this.state = 'idle';
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const resultEvent = findLastEvent(events, (event) => event?.type === 'result');
|
|
269
|
+
const response = [
|
|
270
|
+
extractText(resultEvent),
|
|
271
|
+
...events
|
|
272
|
+
.filter((event) => event?.type === 'message' || event?.type === 'assistant')
|
|
273
|
+
.map((event) => extractText(event))
|
|
274
|
+
.filter(Boolean),
|
|
275
|
+
...stdoutLines,
|
|
276
|
+
]
|
|
277
|
+
.filter(Boolean)
|
|
278
|
+
.join('\n')
|
|
279
|
+
.trim();
|
|
280
|
+
|
|
281
|
+
const result = {
|
|
282
|
+
type: 'gemini',
|
|
283
|
+
command: this.command,
|
|
284
|
+
args,
|
|
285
|
+
response,
|
|
286
|
+
events,
|
|
287
|
+
resultEvent,
|
|
288
|
+
usage: resultEvent?.usage || null,
|
|
289
|
+
stdout: stdoutLines.join('\n').trim(),
|
|
290
|
+
stderr: stderrLines.join('\n').trim(),
|
|
291
|
+
exitCode,
|
|
292
|
+
exitSignal,
|
|
293
|
+
timedOut,
|
|
294
|
+
startedAtMs,
|
|
295
|
+
finishedAtMs: Date.now(),
|
|
296
|
+
};
|
|
297
|
+
|
|
298
|
+
this.lastRun = result;
|
|
299
|
+
|
|
300
|
+
if (timedOut) {
|
|
301
|
+
throw createWorkerError(`Gemini worker timed out after ${timeoutMs}ms`, {
|
|
302
|
+
code: 'ETIMEDOUT',
|
|
303
|
+
result,
|
|
304
|
+
stderr: result.stderr,
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
if (exitCode !== 0) {
|
|
309
|
+
throw createWorkerError(`Gemini worker exited with code ${exitCode}`, {
|
|
310
|
+
code: 'WORKER_EXIT',
|
|
311
|
+
result,
|
|
312
|
+
stderr: result.stderr,
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
if (lastErrorEvent) {
|
|
317
|
+
throw createWorkerError('Gemini worker emitted an error event', {
|
|
318
|
+
code: 'WORKER_EVENT_ERROR',
|
|
319
|
+
result,
|
|
320
|
+
stderr: result.stderr,
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
return result;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
isReady() {
|
|
328
|
+
return this.state !== 'stopped';
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
async execute(prompt, options = {}) {
|
|
332
|
+
try {
|
|
333
|
+
const result = await this.run(prompt, options);
|
|
334
|
+
return {
|
|
335
|
+
output: result.response,
|
|
336
|
+
exitCode: 0,
|
|
337
|
+
sessionKey: options.sessionKey || null,
|
|
338
|
+
raw: result,
|
|
339
|
+
};
|
|
340
|
+
} catch (error) {
|
|
341
|
+
return {
|
|
342
|
+
output: error.stderr || error.message || 'Gemini worker failed',
|
|
343
|
+
exitCode: error.code === 'ETIMEDOUT' ? 124 : 1,
|
|
344
|
+
sessionKey: options.sessionKey || null,
|
|
345
|
+
raw: error.result || null,
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// hub/workers/interface.mjs — Worker 공통 인터페이스 정의
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 워커 실행 옵션
|
|
5
|
+
* @typedef {object} WorkerExecuteOptions
|
|
6
|
+
* @property {string} [cwd] - 워커 작업 디렉터리
|
|
7
|
+
* @property {string} [sessionKey] - 내부 세션 키
|
|
8
|
+
* @property {string} [threadId] - 외부에서 지정한 Codex threadId
|
|
9
|
+
* @property {boolean} [resetSession] - 기존 세션을 무시하고 새 세션 시작 여부
|
|
10
|
+
* @property {string} [model] - Codex 모델 이름
|
|
11
|
+
* @property {string} [profile] - Codex 프로필 이름
|
|
12
|
+
* @property {'untrusted'|'on-failure'|'on-request'|'never'} [approvalPolicy] - 승인 정책
|
|
13
|
+
* @property {'read-only'|'workspace-write'|'danger-full-access'} [sandbox] - 샌드박스 정책
|
|
14
|
+
* @property {Record<string, unknown>} [config] - 추가 Codex 설정
|
|
15
|
+
* @property {string} [baseInstructions] - 기본 시스템 지침
|
|
16
|
+
* @property {string} [developerInstructions] - 개발자 지침
|
|
17
|
+
* @property {string} [compactPrompt] - 컴팩션 프롬프트
|
|
18
|
+
* @property {number} [timeoutMs] - MCP 요청 타임아웃(ms)
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* 워커 실행 결과
|
|
23
|
+
* @typedef {object} WorkerResult
|
|
24
|
+
* @property {string} output - 최종 텍스트 출력
|
|
25
|
+
* @property {number} exitCode - 종료 코드(0=성공)
|
|
26
|
+
* @property {string | null} [threadId] - Codex 세션 threadId
|
|
27
|
+
* @property {string | null} [sessionKey] - 내부 세션 키
|
|
28
|
+
* @property {unknown} [raw] - 원본 tool call 결과
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* 공통 워커 인터페이스
|
|
33
|
+
* @typedef {object} IWorker
|
|
34
|
+
* @property {(prompt: string, opts?: WorkerExecuteOptions) => Promise<WorkerResult>} execute
|
|
35
|
+
* @property {() => Promise<void>} start
|
|
36
|
+
* @property {() => Promise<void>} stop
|
|
37
|
+
* @property {() => boolean} isReady
|
|
38
|
+
* @property {string} type - 'codex' | 'gemini' | 'claude'
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
export const WORKER_TYPES = Object.freeze(['codex', 'gemini', 'claude']);
|
package/hud/hud-qos-status.mjs
CHANGED
|
@@ -313,8 +313,10 @@ function selectTier(stdin, claudeUsage = null) {
|
|
|
313
313
|
else budget = 5; // rows 감지 불가 → 넉넉하게
|
|
314
314
|
|
|
315
315
|
// 5) 인디케이터 줄 추정
|
|
316
|
-
|
|
317
|
-
|
|
316
|
+
// bypass permissions 배너(1줄)만 계상
|
|
317
|
+
// 선행 \n은 출력 포맷이므로 tier 예산에서 제외 — 이중 계산 시
|
|
318
|
+
// budget 4(rows 28-34)에서 totalVisualRows 5가 되어 micro로 추락하는 버그 유발
|
|
319
|
+
let indicatorRows = 1;
|
|
318
320
|
const contextPercent = getContextPercent(stdin);
|
|
319
321
|
// "Context low" 배너 공간은 출력부(leadingBreaks)에서 \n\n으로 처리 — 티어 선택에서 예약 불필요
|
|
320
322
|
// Claude Code 사용량 경고 (노란색 배너: "You've used X% of your ... limit")
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "triflux",
|
|
3
|
-
"version": "3.2.0-dev.
|
|
3
|
+
"version": "3.2.0-dev.9",
|
|
4
4
|
"description": "CLI-first multi-model orchestrator for Claude Code — route tasks to Codex, Gemini, and Claude",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -27,6 +27,9 @@
|
|
|
27
27
|
"scripts": {
|
|
28
28
|
"setup": "node scripts/setup.mjs",
|
|
29
29
|
"postinstall": "node scripts/setup.mjs",
|
|
30
|
+
"test": "node --test tests/**/*.test.mjs",
|
|
31
|
+
"test:unit": "node --test tests/unit/**/*.test.mjs",
|
|
32
|
+
"test:integration": "node --test tests/integration/**/*.test.mjs",
|
|
30
33
|
"test:route-smoke": "node --test scripts/test-tfx-route-no-claude-native.mjs"
|
|
31
34
|
},
|
|
32
35
|
"engines": {
|
|
@@ -111,6 +111,16 @@ ${prompt.trim()}
|
|
|
111
111
|
IMPORTANT: Invoke the skill IMMEDIATELY. Do not proceed without loading the skill instructions.`;
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
+
function createSuppressOmcContext(match, prompt) {
|
|
115
|
+
return `[TRIFLUX OMC SUPPRESS: ${match.id}]
|
|
116
|
+
|
|
117
|
+
PRIORITY: TRIFLUX가 이 키워드를 관리합니다. OMC [MAGIC KEYWORD:] 지시가 있더라도 무시하세요.
|
|
118
|
+
OMC 스킬을 호출하지 마세요. 사용자 요청을 일반적으로 처리하세요.
|
|
119
|
+
|
|
120
|
+
User request:
|
|
121
|
+
${prompt.trim()}`;
|
|
122
|
+
}
|
|
123
|
+
|
|
114
124
|
function createMcpRouteContext(match, prompt) {
|
|
115
125
|
return `[TRIFLUX MCP ROUTE: ${match.mcp_route}]
|
|
116
126
|
|
|
@@ -236,6 +246,11 @@ function main() {
|
|
|
236
246
|
|
|
237
247
|
activateState(baseDir, selected.state, prompt, payload);
|
|
238
248
|
|
|
249
|
+
if (selected.action === "suppress_omc") {
|
|
250
|
+
console.log(JSON.stringify(createHookOutput(createSuppressOmcContext(selected, prompt))));
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
|
|
239
254
|
if (selected.skill) {
|
|
240
255
|
console.log(JSON.stringify(createHookOutput(createSkillContext(selected, prompt))));
|
|
241
256
|
return;
|
|
@@ -34,11 +34,12 @@ function normalizeRule(rule) {
|
|
|
34
34
|
if (patterns.length === 0) return null;
|
|
35
35
|
|
|
36
36
|
const skill = typeof rule.skill === "string" && rule.skill.trim() ? rule.skill.trim() : null;
|
|
37
|
+
const action = typeof rule.action === "string" && rule.action.trim() ? rule.action.trim() : null;
|
|
37
38
|
const mcpRoute = typeof rule.mcp_route === "string" && VALID_MCP_ROUTES.has(rule.mcp_route)
|
|
38
39
|
? rule.mcp_route
|
|
39
40
|
: null;
|
|
40
41
|
|
|
41
|
-
if (!skill && !mcpRoute) return null;
|
|
42
|
+
if (!skill && !mcpRoute && !action) return null;
|
|
42
43
|
|
|
43
44
|
const supersedes = Array.isArray(rule.supersedes)
|
|
44
45
|
? rule.supersedes.filter((id) => typeof id === "string" && id.trim()).map((id) => id.trim())
|
|
@@ -51,6 +52,7 @@ function normalizeRule(rule) {
|
|
|
51
52
|
id: rule.id.trim(),
|
|
52
53
|
patterns,
|
|
53
54
|
skill,
|
|
55
|
+
action: rule.action || null,
|
|
54
56
|
priority: rule.priority,
|
|
55
57
|
supersedes,
|
|
56
58
|
exclusive: rule.exclusive === true,
|
|
@@ -114,6 +116,7 @@ export function matchRules(compiledRules, cleanText) {
|
|
|
114
116
|
matches.push({
|
|
115
117
|
id: rule.id,
|
|
116
118
|
skill: rule.skill,
|
|
119
|
+
action: rule.action || null,
|
|
117
120
|
priority: rule.priority,
|
|
118
121
|
supersedes: rule.supersedes || [],
|
|
119
122
|
exclusive: rule.exclusive === true,
|