@triflux/core 10.25.1 → 10.26.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.
@@ -64,7 +64,7 @@
64
64
  "enabled": true,
65
65
  "timeout": 3,
66
66
  "blocking": false,
67
- "description": "psmux headless 가드"
67
+ "description": "primary multiplexer headless 가드"
68
68
  },
69
69
  {
70
70
  "id": "omc-tfx-gate-activate",
@@ -347,7 +347,7 @@
347
347
  "supersedes": [],
348
348
  "exclusive": false,
349
349
  "state": null,
350
- "mcp_route": "gemini"
350
+ "mcp_route": "antigravity"
351
351
  },
352
352
  {
353
353
  "id": "chrome-route",
@@ -366,7 +366,7 @@
366
366
  "supersedes": [],
367
367
  "exclusive": false,
368
368
  "state": null,
369
- "mcp_route": "gemini"
369
+ "mcp_route": "antigravity"
370
370
  },
371
371
  {
372
372
  "id": "jira-route",
@@ -404,7 +404,7 @@
404
404
  "supersedes": [],
405
405
  "exclusive": false,
406
406
  "state": null,
407
- "mcp_route": "gemini"
407
+ "mcp_route": "antigravity"
408
408
  },
409
409
  {
410
410
  "id": "calendar-route",
@@ -423,7 +423,7 @@
423
423
  "supersedes": [],
424
424
  "exclusive": false,
425
425
  "state": null,
426
- "mcp_route": "gemini"
426
+ "mcp_route": "antigravity"
427
427
  },
428
428
  {
429
429
  "id": "playwright-route",
@@ -438,7 +438,7 @@
438
438
  "supersedes": [],
439
439
  "exclusive": false,
440
440
  "state": null,
441
- "mcp_route": "gemini"
441
+ "mcp_route": "antigravity"
442
442
  },
443
443
  {
444
444
  "id": "canva-route",
@@ -453,7 +453,7 @@
453
453
  "supersedes": [],
454
454
  "exclusive": false,
455
455
  "state": null,
456
- "mcp_route": "gemini"
456
+ "mcp_route": "antigravity"
457
457
  },
458
458
  {
459
459
  "id": "confluence-route",
@@ -472,7 +472,7 @@
472
472
  "supersedes": [],
473
473
  "exclusive": false,
474
474
  "state": null,
475
- "mcp_route": "gemini"
475
+ "mcp_route": "antigravity"
476
476
  },
477
477
  {
478
478
  "id": "slack-route",
@@ -491,7 +491,7 @@
491
491
  "supersedes": [],
492
492
  "exclusive": false,
493
493
  "state": null,
494
- "mcp_route": "gemini"
494
+ "mcp_route": "antigravity"
495
495
  },
496
496
  {
497
497
  "id": "github-route",
@@ -340,7 +340,14 @@ function main() {
340
340
  return hasSegmentInvocation(cmd, WT_DIRECT_PATTERNS);
341
341
  }
342
342
 
343
- if (isWtDirectInvocation(command)) {
343
+ // wt.exe는 Windows Terminal 전용. macOS/Linux에서는 PATH에 존재하지 않아
344
+ // 차단해도 dormant이며, 문서/로그 텍스트의 wt 단어가 false positive로 잡힐
345
+ // 위험만 있다. win32에서만 차단 적용.
346
+ // SAFETY_GUARD_FORCE_PLATFORM env로 테스트에서 platform 시뮬레이션 가능
347
+ // (mac CI에서 win32 차단 동작을 검증하기 위함).
348
+ const guardPlatform =
349
+ process.env.SAFETY_GUARD_FORCE_PLATFORM || process.platform;
350
+ if (guardPlatform === "win32" && isWtDirectInvocation(command)) {
344
351
  blockCommand(WT_DIRECT_BLOCK_MESSAGE, command);
345
352
  }
346
353
 
package/hub/bridge.mjs CHANGED
@@ -371,6 +371,7 @@ export function parseArgs(argv) {
371
371
  capabilities: { type: "string" },
372
372
  file: { type: "string" },
373
373
  payload: { type: "string" },
374
+ "payload-file": { type: "string" },
374
375
  topic: { type: "string" },
375
376
  trace: { type: "string" },
376
377
  correlation: { type: "string" },
@@ -448,6 +449,21 @@ export function parseJsonSafe(raw, fallback = null) {
448
449
  }
449
450
  }
450
451
 
452
+ export function readBridgePayload(args = {}, stdin) {
453
+ if (args["payload-file"]) {
454
+ const payloadFile = String(args["payload-file"]);
455
+ const raw =
456
+ payloadFile === "-"
457
+ ? stdin === undefined
458
+ ? readFileSync(0, "utf8")
459
+ : String(stdin)
460
+ : readFileSync(payloadFile, "utf8");
461
+ return JSON.parse(raw);
462
+ }
463
+ if (args.payload) return JSON.parse(args.payload);
464
+ return {};
465
+ }
466
+
451
467
  function normalizeBridgePayload(payload) {
452
468
  if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
453
469
  return {};
@@ -1145,6 +1161,185 @@ async function cmdHitlPending() {
1145
1161
  return emitJson(outcome?.result || unavailableResult());
1146
1162
  }
1147
1163
 
1164
+ async function loadDaemonControl() {
1165
+ return await import("./team/claude-daemon-control.mjs");
1166
+ }
1167
+
1168
+ function daemonErrorResult(error) {
1169
+ return {
1170
+ ok: false,
1171
+ error: error?.message || String(error),
1172
+ };
1173
+ }
1174
+
1175
+ function daemonAttachErrorResult(error) {
1176
+ return {
1177
+ ok: false,
1178
+ text: "",
1179
+ raw: "",
1180
+ responseRaw: "",
1181
+ matchedCompletion: false,
1182
+ timedOut: false,
1183
+ closed: false,
1184
+ inputSent: error?.inputSent === true,
1185
+ error: error?.message || String(error),
1186
+ };
1187
+ }
1188
+
1189
+ function daemonInterruptErrorResult(error) {
1190
+ return {
1191
+ ok: false,
1192
+ done: false,
1193
+ aborted: false,
1194
+ reason: "interrupt_failed",
1195
+ inputSent: error?.inputSent === true,
1196
+ error: error?.message || String(error),
1197
+ };
1198
+ }
1199
+
1200
+ function numericOption(value, fallback) {
1201
+ const parsed = Number(value);
1202
+ return Number.isFinite(parsed) ? parsed : fallback;
1203
+ }
1204
+
1205
+ async function cmdDaemonProbe(args) {
1206
+ try {
1207
+ const payload = readBridgePayload(args);
1208
+ const {
1209
+ deriveClaudeDaemonPaths,
1210
+ findDaemonJobBySessionId,
1211
+ findDaemonJobByShort,
1212
+ sendClaudeControlRequest,
1213
+ } = await loadDaemonControl();
1214
+ const daemonPaths = deriveClaudeDaemonPaths({
1215
+ configDir: payload.configDir,
1216
+ });
1217
+ const list = await sendClaudeControlRequest(
1218
+ daemonPaths.controlSock,
1219
+ { proto: 1, op: "list" },
1220
+ { timeoutMs: numericOption(payload.timeoutMs, 6000) },
1221
+ );
1222
+ const target = payload.sessionId
1223
+ ? findDaemonJobBySessionId(list, payload.sessionId)
1224
+ : payload.short
1225
+ ? findDaemonJobByShort(list, payload.short)
1226
+ : null;
1227
+
1228
+ return emitJson({
1229
+ ok: list?.ok !== false,
1230
+ controlSock: daemonPaths.controlSock,
1231
+ sessions: list?.jobs || list?.sessions || [],
1232
+ target: target || undefined,
1233
+ error: list?.ok === false ? list?.error : undefined,
1234
+ });
1235
+ } catch (error) {
1236
+ return emitJson(daemonErrorResult(error));
1237
+ }
1238
+ }
1239
+
1240
+ async function cmdDaemonAttach(args) {
1241
+ try {
1242
+ const payload = readBridgePayload(args);
1243
+ if (!payload.prompt) throw new Error("prompt is required");
1244
+
1245
+ const {
1246
+ attachClaudeDaemonSession,
1247
+ deriveClaudeDaemonPaths,
1248
+ findDaemonJobBySessionId,
1249
+ sendClaudeControlRequest,
1250
+ } = await loadDaemonControl();
1251
+ const daemonPaths = deriveClaudeDaemonPaths({
1252
+ configDir: payload.configDir,
1253
+ });
1254
+ let short = payload.short;
1255
+ if (!short && payload.sessionId) {
1256
+ const list = await sendClaudeControlRequest(
1257
+ daemonPaths.controlSock,
1258
+ { proto: 1, op: "list" },
1259
+ { timeoutMs: numericOption(payload.timeoutMs, 6000) },
1260
+ );
1261
+ const target = findDaemonJobBySessionId(list, payload.sessionId);
1262
+ short = target?.short;
1263
+ }
1264
+ if (!short) throw new Error("short or resolvable sessionId is required");
1265
+
1266
+ const result = await attachClaudeDaemonSession({
1267
+ controlSock: daemonPaths.controlSock,
1268
+ short,
1269
+ input: payload.prompt,
1270
+ cols: numericOption(payload.cols, undefined),
1271
+ rows: numericOption(payload.rows, undefined),
1272
+ timeoutMs: numericOption(payload.timeoutMs, 30_000),
1273
+ });
1274
+
1275
+ return emitJson({
1276
+ ok: result.matchedCompletion === true,
1277
+ text: result.text,
1278
+ raw: result.streamText,
1279
+ responseRaw: result.responseStreamText,
1280
+ matchedCompletion: result.matchedCompletion === true,
1281
+ timedOut: result.timedOut === true,
1282
+ closed: result.closed === true,
1283
+ inputSent: result.inputSent === true,
1284
+ error:
1285
+ result.handshake?.ok === false ? result.handshake?.error : undefined,
1286
+ });
1287
+ } catch (error) {
1288
+ return emitJson(daemonAttachErrorResult(error));
1289
+ }
1290
+ }
1291
+
1292
+ async function cmdDaemonInterrupt(args) {
1293
+ try {
1294
+ const payload = readBridgePayload(args);
1295
+
1296
+ const {
1297
+ deriveClaudeDaemonPaths,
1298
+ findDaemonJobBySessionId,
1299
+ interruptClaudeDaemonSession,
1300
+ sendClaudeControlRequest,
1301
+ } = await loadDaemonControl();
1302
+ const daemonPaths = deriveClaudeDaemonPaths({
1303
+ configDir: payload.configDir,
1304
+ });
1305
+ let short = payload.short;
1306
+ if (!short && payload.sessionId) {
1307
+ const list = await sendClaudeControlRequest(
1308
+ daemonPaths.controlSock,
1309
+ { proto: 1, op: "list" },
1310
+ { timeoutMs: numericOption(payload.timeoutMs, 6000) },
1311
+ );
1312
+ const target = findDaemonJobBySessionId(list, payload.sessionId);
1313
+ short = target?.short;
1314
+ }
1315
+ if (!short) throw new Error("short or resolvable sessionId is required");
1316
+
1317
+ const result = await interruptClaudeDaemonSession({
1318
+ controlSock: daemonPaths.controlSock,
1319
+ short,
1320
+ cols: numericOption(payload.cols, undefined),
1321
+ rows: numericOption(payload.rows, undefined),
1322
+ timeoutMs: numericOption(payload.timeoutMs, 5000),
1323
+ });
1324
+ const aborted = result.inputSent === true;
1325
+
1326
+ return emitJson({
1327
+ ok: aborted,
1328
+ done: false,
1329
+ aborted,
1330
+ reason: aborted ? "user_interrupt" : "interrupt_failed",
1331
+ raw: result.streamText,
1332
+ timedOut: result.timedOut === true,
1333
+ closed: result.closed === true,
1334
+ inputSent: result.inputSent === true,
1335
+ error:
1336
+ result.handshake?.ok === false ? result.handshake?.error : undefined,
1337
+ });
1338
+ } catch (error) {
1339
+ return emitJson(daemonInterruptErrorResult(error));
1340
+ }
1341
+ }
1342
+
1148
1343
  // ---------------------------------------------------------------------------
1149
1344
  // retry-run / retry-status — Phase 3 Step C2 bridge 서브커맨드.
1150
1345
  // retry-state-machine.mjs 를 multi-process safe 하게 외부 호출용 wrap.
@@ -1178,6 +1373,21 @@ function buildRetrySmFromArgs(args, snapshot) {
1178
1373
  return sm;
1179
1374
  }
1180
1375
 
1376
+ function buildRetryCliInvocation(cli) {
1377
+ if (!cli) return null;
1378
+
1379
+ const invocation = {
1380
+ cli: cli.cli,
1381
+ model: cli.model,
1382
+ argv: [],
1383
+ };
1384
+ if (cli.profile) invocation.profile = cli.profile;
1385
+ if (cli.cli === "codex" && cli.profile) {
1386
+ invocation.argv.push("--profile", cli.profile);
1387
+ }
1388
+ return invocation;
1389
+ }
1390
+
1181
1391
  async function cmdRetryRun(args) {
1182
1392
  const snapshotFile = args.snapshot || args["snapshot-file"];
1183
1393
  const event = args.event;
@@ -1216,12 +1426,14 @@ async function cmdRetryRun(args) {
1216
1426
 
1217
1427
  const terminal = ["DONE", "STUCK", "BUDGET_EXCEEDED"].includes(snap.current);
1218
1428
  const cli = snap.cliChain?.[snap.cliIndex] || null;
1429
+ const cliInvocation = buildRetryCliInvocation(cli);
1219
1430
  const out = {
1220
1431
  ok: true,
1221
1432
  current: snap.current,
1222
1433
  iterations: snap.iterations,
1223
1434
  cliIndex: snap.cliIndex,
1224
1435
  cli,
1436
+ cliInvocation,
1225
1437
  done: snap.current === "DONE",
1226
1438
  shouldStop: terminal,
1227
1439
  stuckCounter: snap.stuckCounter,
@@ -1245,6 +1457,7 @@ async function cmdRetryStatus(args) {
1245
1457
  }
1246
1458
  const terminal = ["DONE", "STUCK", "BUDGET_EXCEEDED"].includes(snap.current);
1247
1459
  const cli = snap.cliChain?.[snap.cliIndex] || null;
1460
+ const cliInvocation = buildRetryCliInvocation(cli);
1248
1461
  console.log(
1249
1462
  JSON.stringify({
1250
1463
  ok: true,
@@ -1254,6 +1467,7 @@ async function cmdRetryStatus(args) {
1254
1467
  maxIterations: snap.maxIterations,
1255
1468
  cliIndex: snap.cliIndex,
1256
1469
  cli,
1470
+ cliInvocation,
1257
1471
  mode: snap.mode,
1258
1472
  shouldStop: terminal,
1259
1473
  stuckCounter: snap.stuckCounter,
@@ -1322,13 +1536,19 @@ export async function main(argv = process.argv.slice(2)) {
1322
1536
  return await cmdHitlSubmit(args);
1323
1537
  case "hitl-pending":
1324
1538
  return await cmdHitlPending(args);
1539
+ case "daemon-probe":
1540
+ return await cmdDaemonProbe(args);
1541
+ case "daemon-attach":
1542
+ return await cmdDaemonAttach(args);
1543
+ case "daemon-interrupt":
1544
+ return await cmdDaemonInterrupt(args);
1325
1545
  case "retry-run":
1326
1546
  return await cmdRetryRun(args);
1327
1547
  case "retry-status":
1328
1548
  return await cmdRetryStatus(args);
1329
1549
  default:
1330
1550
  console.error(
1331
- "사용법: bridge.mjs <register|result|control|handoff|publish|send-input|context|deregister|assign-async|assign-result|assign-status|assign-retry|team-info|team-task-list|team-task-update|team-send-message|pipeline-state|pipeline-advance|pipeline-init|pipeline-list|ping|delegator-delegate|delegator-reply|delegator-status|hitl-request|hitl-submit|hitl-pending|retry-run|retry-status> [--옵션]",
1551
+ "사용법: bridge.mjs <register|result|control|handoff|publish|send-input|context|deregister|assign-async|assign-result|assign-status|assign-retry|team-info|team-task-list|team-task-update|team-send-message|pipeline-state|pipeline-advance|pipeline-init|pipeline-list|ping|delegator-delegate|delegator-reply|delegator-status|hitl-request|hitl-submit|hitl-pending|daemon-probe|daemon-attach|daemon-interrupt|retry-run|retry-status> [--옵션]",
1332
1552
  );
1333
1553
  process.exit(1);
1334
1554
  }
@@ -25,7 +25,12 @@ export const DELEGATOR_JOB_STATUSES = Object.freeze([
25
25
 
26
26
  export const DELEGATOR_MODES = Object.freeze(["sync", "async"]);
27
27
 
28
- export const DELEGATOR_PROVIDERS = Object.freeze(["auto", "codex", "gemini"]);
28
+ export const DELEGATOR_PROVIDERS = Object.freeze([
29
+ "auto",
30
+ "codex",
31
+ "antigravity",
32
+ "gemini",
33
+ ]);
29
34
 
30
35
  export const DELEGATOR_SCHEMA_URL = new URL(
31
36
  "./schema/delegator-tools.schema.json",
@@ -5,7 +5,7 @@
5
5
  "$defs": {
6
6
  "Provider": {
7
7
  "type": "string",
8
- "enum": ["auto", "codex", "gemini"]
8
+ "enum": ["auto", "codex", "antigravity", "gemini"]
9
9
  },
10
10
  "Mode": {
11
11
  "type": "string",
@@ -19,6 +19,11 @@ function assertKnown(enumValues, value, fieldName) {
19
19
  }
20
20
  }
21
21
 
22
+ function normalizeProvider(provider = "auto") {
23
+ const value = provider || "auto";
24
+ return value === "gemini" ? "antigravity" : value;
25
+ }
26
+
22
27
  export class DelegatorService {
23
28
  constructor({
24
29
  idFactory = randomUUID,
@@ -40,7 +45,7 @@ export class DelegatorService {
40
45
  const timestamp = this.now().toISOString();
41
46
  const jobId = input.job_id || this.idFactory();
42
47
  const mode = input.mode || "sync";
43
- const providerRequested = input.provider || "auto";
48
+ const providerRequested = normalizeProvider(input.provider);
44
49
 
45
50
  assertKnown(DELEGATOR_MODES, mode, "mode");
46
51
  assertKnown(DELEGATOR_PROVIDERS, providerRequested, "provider");
@@ -86,7 +91,7 @@ export class DelegatorService {
86
91
  _normalizeInput(input = {}) {
87
92
  return {
88
93
  prompt: input.prompt,
89
- provider: input.provider || "auto",
94
+ provider: normalizeProvider(input.provider),
90
95
  mode: input.mode || "sync",
91
96
  agent_type: input.agent_type || input.agentType || "executor",
92
97
  cwd: input.cwd || null,
@@ -1,5 +1,5 @@
1
- // hub/gemini-adapter.mjs — Gemini CLI 방어 계층
2
- // codex-adapter.mjs와 동일 패턴, cli-adapter-base 공통 인터페이스 사용
1
+ // hub/gemini-adapter.mjs — legacy Gemini adapter compatibility layer
2
+ // The "gemini" route is retained as an alias, but execution is Antigravity/agy.
3
3
 
4
4
  import { mkdirSync } from "node:fs";
5
5
  import { tmpdir } from "node:os";
@@ -8,11 +8,14 @@ import {
8
8
  executeWithCircuitBroker,
9
9
  normalizePathForShell,
10
10
  runProcess,
11
- shellQuote,
12
11
  } from "./cli-adapter-base.mjs";
13
12
  import { whichCommandAsync } from "./platform.mjs";
14
13
 
15
- // ── Gemini-specific stall inference ─────────────────────────────
14
+ function shellSingleQuote(value) {
15
+ return `'${String(value).replace(/'/g, "'\\''")}'`;
16
+ }
17
+
18
+ // ── Antigravity stall inference ─────────────────────────────────
16
19
 
17
20
  function inferStallMode(stdout, stderr) {
18
21
  const text = `${stdout}\n${stderr}`.toLowerCase();
@@ -30,12 +33,12 @@ function inferStallMode(stdout, stderr) {
30
33
  // ── Preflight ───────────────────────────────────────────────────
31
34
 
32
35
  async function runPreflight(opts = {}) {
33
- const geminiPath = await whichCommandAsync("gemini");
34
- if (!geminiPath) {
36
+ const agyPath = await whichCommandAsync("agy");
37
+ if (!agyPath) {
35
38
  return {
36
- geminiPath: null,
39
+ agyPath: null,
37
40
  warnings: [
38
- "Gemini CLI not found. Install Gemini and ensure `gemini` is available on PATH.",
41
+ "Antigravity CLI not found. Install Antigravity and ensure `agy` is available on PATH.",
39
42
  ],
40
43
  excludeMcpServers: [],
41
44
  ok: false,
@@ -48,40 +51,27 @@ async function runPreflight(opts = {}) {
48
51
  for (const name of Array.isArray(opts.mcpServers) ? opts.mcpServers : []) {
49
52
  const server = String(name ?? "").trim();
50
53
  if (!server) continue;
51
- // Gemini MCP health는 best-effort: 실행 시점에 --allowed-mcp-server-names로 필터링
52
- // 사전 probe는 수행하지 않음 (gemini가 자체적으로 graceful degrade)
54
+ // Antigravity MCP health는 best-effort: 실행 시점 probe는 수행하지 않음.
53
55
  }
54
56
 
55
- return { geminiPath, warnings, excludeMcpServers, ok: true };
57
+ return { agyPath, warnings, excludeMcpServers, ok: true };
56
58
  }
57
59
 
58
60
  // ── Command building ────────────────────────────────────────────
59
61
 
60
62
  function buildGeminiCommand(prompt, resultFile, opts = {}) {
61
- const parts = ["gemini"];
62
-
63
- if (opts.model) parts.push("--model", shellQuote(opts.model));
64
- parts.push("--yolo");
65
-
66
- const allowed = Array.isArray(opts.allowedMcpServers)
67
- ? opts.allowedMcpServers
68
- : [];
69
- const excluded = Array.isArray(opts.excludeMcpServers)
70
- ? opts.excludeMcpServers
71
- : [];
72
- const filtered = allowed.filter((name) => !excluded.includes(name));
73
- if (filtered.length) {
74
- parts.push(
75
- "--allowed-mcp-server-names",
76
- ...filtered.map((n) => shellQuote(n)),
77
- );
78
- }
79
-
80
- parts.push("--prompt", shellQuote(prompt));
81
- parts.push("--output-format", "text");
63
+ const parts = [
64
+ "printf",
65
+ "%s",
66
+ shellSingleQuote(prompt),
67
+ "|",
68
+ "agy",
69
+ "--print",
70
+ "--dangerously-skip-permissions",
71
+ ];
82
72
 
83
73
  if (resultFile) {
84
- return `${parts.join(" ")} > ${shellQuote(normalizePathForShell(resultFile))} 2>${shellQuote(normalizePathForShell(resultFile + ".err"))}`;
74
+ return `${parts.join(" ")} > ${shellSingleQuote(normalizePathForShell(resultFile))} 2>${shellSingleQuote(normalizePathForShell(resultFile + ".err"))}`;
85
75
  }
86
76
 
87
77
  return parts.join(" ");
@@ -114,11 +104,11 @@ export function buildExecArgs(opts = {}) {
114
104
  // ── Execution ───────────────────────────────────────────────────
115
105
 
116
106
  async function runGemini(prompt, workdir, preflight, attempt, lease) {
117
- const dir = join(tmpdir(), "triflux-gemini-exec");
107
+ const dir = join(tmpdir(), "triflux-antigravity-exec");
118
108
  mkdirSync(dir, { recursive: true });
119
109
  const resultFile = join(
120
110
  dir,
121
- `gemini-${Date.now()}-${Math.random().toString(36).slice(2)}.txt`,
111
+ `agy-${Date.now()}-${Math.random().toString(36).slice(2)}.txt`,
122
112
  );
123
113
  const command = buildGeminiCommand(prompt, resultFile, {
124
114
  model: attempt.model,
@@ -153,6 +143,8 @@ export async function getCircuitState() {
153
143
 
154
144
  export function execute(opts = {}) {
155
145
  return executeWithCircuitBroker({
146
+ // Account-broker still stores this compatibility bucket as "gemini";
147
+ // execution itself is routed to agy above.
156
148
  provider: "gemini",
157
149
  runFn: runGemini,
158
150
  preflightFn: (o) => runPreflight({ mcpServers: o.mcpServers }),
@@ -20,7 +20,7 @@ try {
20
20
  }
21
21
 
22
22
  /** 사용 가능한 CLI 액션 */
23
- const ACTIONS = ["codex", "gemini", "claude", "haiku", "sonnet"];
23
+ const ACTIONS = ["codex", "antigravity", "claude", "haiku", "sonnet"];
24
24
 
25
25
  /** 특성 벡터 키워드 (48차원, 에이전트 타입 기반) */
26
26
  const FEATURE_KEYWORDS = [
@@ -42,7 +42,7 @@ const FEATURE_KEYWORDS = [
42
42
  "optimize",
43
43
  "research",
44
44
  "evaluate",
45
- // 디자인/문서 (gemini 친화)
45
+ // 디자인/문서 (Antigravity 친화)
46
46
  "design",
47
47
  "ui",
48
48
  "ux",
@@ -74,7 +74,7 @@ const FEATURE_KEYWORDS = [
74
74
  "검토",
75
75
  "보안",
76
76
  "최적화",
77
- // 한국어 — 디자인/문서 (gemini 친화)
77
+ // 한국어 — 디자인/문서 (Antigravity 친화)
78
78
  "디자인",
79
79
  "문서화",
80
80
  // 한국어 — 간단/빠른 (haiku 친화)