@triflux/remote 10.1.0 → 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.
Files changed (162) hide show
  1. package/hub/pipe.mjs +239 -121
  2. package/hub/public/dashboard.html +61 -0
  3. package/hub/server.mjs +2179 -709
  4. package/hub/store-adapter.mjs +59 -26
  5. package/hub/store.mjs +356 -153
  6. package/hub/team/agent-map.json +26 -7
  7. package/hub/team/ansi.mjs +245 -146
  8. package/hub/team/backend.mjs +78 -17
  9. package/hub/team/build-worker-prompt.mjs +73 -0
  10. package/hub/team/check-mcp-hub.mjs +49 -0
  11. package/hub/team/claude-daemon-control.mjs +1130 -0
  12. package/hub/team/claude-native-bridge.mjs +1011 -0
  13. package/hub/team/claude-session-projection.mjs +74 -0
  14. package/hub/team/cli/commands/attach.mjs +57 -37
  15. package/hub/team/cli/commands/control.mjs +29 -8
  16. package/hub/team/cli/commands/debug.mjs +95 -74
  17. package/hub/team/cli/commands/focus.mjs +80 -53
  18. package/hub/team/cli/commands/interrupt.mjs +18 -6
  19. package/hub/team/cli/commands/kill.mjs +16 -5
  20. package/hub/team/cli/commands/list.mjs +31 -24
  21. package/hub/team/cli/commands/send.mjs +19 -6
  22. package/hub/team/cli/commands/start/index.mjs +151 -33
  23. package/hub/team/cli/commands/start/parse-args.mjs +97 -13
  24. package/hub/team/cli/commands/start/start-headless.mjs +155 -37
  25. package/hub/team/cli/commands/start/start-in-process.mjs +50 -40
  26. package/hub/team/cli/commands/start/start-mux.mjs +122 -73
  27. package/hub/team/cli/commands/start/start-wt.mjs +86 -69
  28. package/hub/team/cli/commands/status.mjs +43 -14
  29. package/hub/team/cli/commands/stop.mjs +11 -4
  30. package/hub/team/cli/commands/task.mjs +8 -3
  31. package/hub/team/cli/commands/tasks.mjs +13 -13
  32. package/hub/team/cli/help.mjs +1 -1
  33. package/hub/team/cli/index.mjs +2 -2
  34. package/hub/team/cli/manifest.mjs +38 -8
  35. package/hub/team/cli/render.mjs +52 -30
  36. package/hub/team/cli/services/attach-fallback.mjs +74 -54
  37. package/hub/team/cli/services/hub-client.mjs +44 -16
  38. package/hub/team/cli/services/member-selector.mjs +37 -30
  39. package/hub/team/cli/services/native-control.mjs +144 -116
  40. package/hub/team/cli/services/runtime-mode.mjs +61 -4
  41. package/hub/team/cli/services/state-store.mjs +66 -9
  42. package/hub/team/cli/services/task-model.mjs +40 -30
  43. package/hub/team/codex-review.mjs +521 -0
  44. package/hub/team/conductor-mesh-bridge.mjs +25 -24
  45. package/hub/team/conductor-registry.mjs +121 -0
  46. package/hub/team/conductor.mjs +1379 -671
  47. package/hub/team/consensus-meta.mjs +176 -0
  48. package/hub/team/dashboard-anchor.mjs +4 -5
  49. package/hub/team/dashboard-layout.mjs +3 -1
  50. package/hub/team/dashboard-open.mjs +20 -118
  51. package/hub/team/dashboard.mjs +76 -28
  52. package/hub/team/event-log.mjs +18 -10
  53. package/hub/team/execution-mode.mjs +256 -0
  54. package/hub/team/extract-completion-payload.mjs +110 -0
  55. package/hub/team/git-preflight.mjs +286 -0
  56. package/hub/team/handoff.mjs +49 -18
  57. package/hub/team/headless-bridge-session.mjs +17 -0
  58. package/hub/team/headless.mjs +2213 -1156
  59. package/hub/team/health-probe.mjs +232 -69
  60. package/hub/team/interactive-native-launcher.mjs +106 -0
  61. package/hub/team/interactive-native-worker.mjs +80 -0
  62. package/hub/team/interactive-tui-transport.mjs +465 -0
  63. package/hub/team/launcher-template.mjs +49 -17
  64. package/hub/team/lead-control.mjs +91 -4
  65. package/hub/team/mcp-selector.mjs +145 -0
  66. package/hub/team/native-supervisor.mjs +90 -26
  67. package/hub/team/native.mjs +120 -42
  68. package/hub/team/nativeProxy.mjs +184 -113
  69. package/hub/team/notify.mjs +147 -78
  70. package/hub/team/orchestrator.mjs +173 -161
  71. package/hub/team/pane.mjs +43 -20
  72. package/hub/team/process-cleanup.mjs +69 -25
  73. package/hub/team/psmux.mjs +1022 -281
  74. package/hub/team/recovery-store.mjs +149 -0
  75. package/hub/team/remote-probe.mjs +68 -52
  76. package/hub/team/remote-session.mjs +131 -59
  77. package/hub/team/remote-watcher.mjs +61 -33
  78. package/hub/team/retry-state-machine.mjs +336 -0
  79. package/hub/team/routing.mjs +51 -25
  80. package/hub/team/runtime-strategy.mjs +200 -0
  81. package/hub/team/schemas/worker-completion.json +39 -0
  82. package/hub/team/sentinel-capture.mjs +127 -0
  83. package/hub/team/session-sync.mjs +153 -6
  84. package/hub/team/session.mjs +574 -611
  85. package/hub/team/shared.mjs +13 -13
  86. package/hub/team/staleState.mjs +72 -30
  87. package/hub/team/swarm-cli.mjs +390 -0
  88. package/hub/team/swarm-hypervisor.mjs +1544 -193
  89. package/hub/team/swarm-intent.mjs +216 -0
  90. package/hub/team/swarm-locks.mjs +140 -25
  91. package/hub/team/swarm-planner.mjs +121 -21
  92. package/hub/team/swarm-preflight.mjs +319 -0
  93. package/hub/team/swarm-reconciler.mjs +128 -23
  94. package/hub/team/synapse-cli.mjs +458 -0
  95. package/hub/team/synapse-http.mjs +60 -0
  96. package/hub/team/synapse-registry.mjs +329 -0
  97. package/hub/team/terminal-opener.mjs +216 -0
  98. package/hub/team/tui-core.mjs +292 -0
  99. package/hub/team/tui-lite.mjs +237 -148
  100. package/hub/team/tui-remote-adapter.mjs +18 -34
  101. package/hub/team/tui-synapse.mjs +223 -0
  102. package/hub/team/tui-viewer.mjs +108 -48
  103. package/hub/team/tui-widgets.mjs +262 -0
  104. package/hub/team/tui.mjs +931 -439
  105. package/hub/team/worker-completion-validator.mjs +84 -0
  106. package/hub/team/worker-sandbox.mjs +97 -0
  107. package/hub/team/worker-signal.mjs +263 -0
  108. package/hub/team/worker-signal.types.d.ts +52 -0
  109. package/hub/team/worktree-lifecycle.mjs +517 -63
  110. package/hub/team/wt-manager.mjs +297 -26
  111. package/hub/tools.mjs +477 -252
  112. package/hub/tray.mjs +411 -368
  113. package/hub/workers/antigravity-route-worker.mjs +169 -0
  114. package/hub/workers/claude-worker.mjs +162 -118
  115. package/hub/workers/codex-app-server-worker.mjs +1015 -0
  116. package/hub/workers/codex-mcp.mjs +724 -507
  117. package/hub/workers/delegator-mcp.mjs +570 -499
  118. package/hub/workers/factory.mjs +103 -21
  119. package/hub/workers/gemini-worker.mjs +126 -284
  120. package/hub/workers/interface.mjs +24 -2
  121. package/hub/workers/lib/jsonrpc-stdio.mjs +463 -0
  122. package/hub/workers/worker-utils.mjs +23 -17
  123. package/package.json +4 -3
  124. package/scripts/lib/agent-json.mjs +27 -0
  125. package/scripts/lib/async.mjs +174 -0
  126. package/scripts/lib/claudemd-scanner.mjs +223 -0
  127. package/scripts/lib/cli-agy.mjs +62 -0
  128. package/scripts/lib/cli-claude.mjs +78 -0
  129. package/scripts/lib/cli-codex.mjs +199 -0
  130. package/scripts/lib/cli-gemini.mjs +37 -0
  131. package/scripts/lib/codex-recovery.sh +50 -0
  132. package/scripts/lib/context.mjs +67 -0
  133. package/scripts/lib/cross-review-utils.mjs +59 -0
  134. package/scripts/lib/doctor-env-checks.mjs +121 -0
  135. package/scripts/lib/dynamic-route-cli.mjs +107 -0
  136. package/scripts/lib/env-probe.mjs +269 -0
  137. package/scripts/lib/env.mjs +14 -0
  138. package/scripts/lib/gemini-profiles.mjs +119 -0
  139. package/scripts/lib/handoff.mjs +187 -0
  140. package/scripts/lib/hook-utils.mjs +16 -0
  141. package/scripts/lib/hub.mjs +48 -0
  142. package/scripts/lib/keyword-rules.mjs +195 -0
  143. package/scripts/lib/logger.mjs +105 -0
  144. package/scripts/lib/mcp-filter.mjs +883 -0
  145. package/scripts/lib/mcp-gateway-health-check.mjs +159 -0
  146. package/scripts/lib/mcp-gateway-servers.mjs +70 -0
  147. package/scripts/lib/mcp-gateway-wrapper-check.mjs +35 -0
  148. package/scripts/lib/mcp-guard-engine.mjs +2120 -0
  149. package/scripts/lib/mcp-health.mjs +773 -0
  150. package/scripts/lib/mcp-manifest.mjs +89 -0
  151. package/scripts/lib/mcp-server-catalog.mjs +355 -0
  152. package/scripts/lib/pid.mjs +63 -0
  153. package/scripts/lib/process-utils.mjs +26 -0
  154. package/scripts/lib/psmux-info.mjs +224 -0
  155. package/scripts/lib/quota.mjs +47 -0
  156. package/scripts/lib/remote-spawn-transfer.mjs +226 -0
  157. package/scripts/lib/skill-state.mjs +220 -0
  158. package/scripts/lib/skill-template.mjs +375 -0
  159. package/scripts/lib/team.mjs +75 -0
  160. package/scripts/lib/timeout.mjs +66 -0
  161. package/scripts/lib/tmp.mjs +26 -0
  162. package/scripts/lib/toml.mjs +113 -0
@@ -1,21 +1,103 @@
1
- // hub/workers/factory.mjs — Worker 생성 팩토리
2
-
3
- import { GeminiWorker } from './gemini-worker.mjs';
4
- import { ClaudeWorker } from './claude-worker.mjs';
5
- import { CodexMcpWorker } from './codex-mcp.mjs';
6
- import { DelegatorMcpWorker } from './delegator-mcp.mjs';
7
-
8
- export function createWorker(type, opts = {}) {
9
- switch (type) {
10
- case 'gemini':
11
- return new GeminiWorker(opts);
12
- case 'claude':
13
- return new ClaudeWorker(opts);
14
- case 'codex':
15
- return new CodexMcpWorker(opts);
16
- case 'delegator':
17
- return new DelegatorMcpWorker(opts);
18
- default:
19
- throw new Error(`Unknown worker type: ${type}`);
20
- }
21
- }
1
+ // hub/workers/factory.mjs — Worker 생성 팩토리
2
+ //
3
+ // Supported worker types:
4
+ // - 'gemini' → AntigravityRouteWorker (deprecated alias)
5
+ // - 'antigravity' → AntigravityRouteWorker
6
+ // - 'claude' → ClaudeWorker
7
+ // - 'codex' → CodexMcpWorker (default) or CodexAppServerWorker when
8
+ // opts.transport === 'app-server'
9
+ // - 'codex-app-server' → CodexAppServerWorker (explicit alias)
10
+ // - 'delegator' → DelegatorMcpWorker
11
+ //
12
+ // For the codex app-server transport the factory injects a default
13
+ // `publishCallback` that forwards each envelope to `/bridge/publish` via the
14
+ // existing `requestJson` helper. Callers can override by passing their own
15
+ // `publishCallback` or swap the transport with `requestJsonFn`.
16
+
17
+ import { ClaudeWorker } from "./claude-worker.mjs";
18
+ import { AntigravityRouteWorker } from "./antigravity-route-worker.mjs";
19
+ import { CodexAppServerWorker } from "./codex-app-server-worker.mjs";
20
+
21
+ /**
22
+ * Build a best-effort publishCallback that posts the envelope to
23
+ * `/bridge/publish`. Failures are swallowed so publish pressure never crashes
24
+ * a running worker turn.
25
+ * @param {(path: string, opts?: object) => Promise<unknown>} [requestJsonFn]
26
+ * @returns {(publishMessage: object) => Promise<void>}
27
+ */
28
+ function defaultPublishCallback(requestJsonFn = null) {
29
+ return async (publishMessage) => {
30
+ try {
31
+ const publishRequestJson =
32
+ requestJsonFn || (await import("@triflux/core/hub/bridge.mjs")).requestJson;
33
+ await publishRequestJson("/bridge/publish", { body: publishMessage });
34
+ } catch {
35
+ // best-effort; publish failures must not crash the worker
36
+ }
37
+ };
38
+ }
39
+
40
+ /**
41
+ * Construct a CodexAppServerWorker with a default publishCallback wired to
42
+ * `requestJson('/bridge/publish', ...)` unless the caller supplied one.
43
+ *
44
+ * Issue #95 P1 #4 validation: the app-server transport does not yet implement
45
+ * the server-initiated approval / `tool/requestUserInput` round-trip. Any
46
+ * `approvalPolicy !== 'never'` would cause a codex turn to hang waiting for
47
+ * an approval response the worker cannot produce. Reject such configs at
48
+ * factory time with a clear, actionable message.
49
+ *
50
+ * @param {object} [opts]
51
+ */
52
+ async function createCodexWorker(opts = {}) {
53
+ const { transport, requestJsonFn, publishCallback, ...rest } = opts;
54
+
55
+ if (transport === "app-server") {
56
+ const policy = rest.approvalPolicy;
57
+ if (policy !== undefined && policy !== null && policy !== "never") {
58
+ throw new Error(
59
+ `codex app-server transport currently requires approvalPolicy='never' (got '${policy}'). ` +
60
+ "The server-initiated approval / tool/requestUserInput round-trip is not yet implemented. " +
61
+ "Set approvalPolicy='never' or use transport='mcp'. " +
62
+ "Tracked in follow-up issue.",
63
+ );
64
+ }
65
+ return new CodexAppServerWorker({
66
+ ...rest,
67
+ publishCallback:
68
+ typeof publishCallback === "function"
69
+ ? publishCallback
70
+ : defaultPublishCallback(requestJsonFn),
71
+ });
72
+ }
73
+
74
+ // Default (and transport === 'mcp'): CodexMcpWorker with zero new deps.
75
+ const { CodexMcpWorker } = await import("./codex-mcp.mjs");
76
+ return new CodexMcpWorker(rest);
77
+ }
78
+
79
+ /**
80
+ * @param {'gemini'|'antigravity'|'agy'|'claude'|'codex'|'codex-app-server'|'delegator'} type
81
+ * @param {object} [opts]
82
+ * @returns {Promise<import('./interface.mjs').IWorker>}
83
+ */
84
+ export async function createWorker(type, opts = {}) {
85
+ switch (type) {
86
+ case "gemini":
87
+ case "antigravity":
88
+ case "agy":
89
+ return new AntigravityRouteWorker(opts);
90
+ case "claude":
91
+ return new ClaudeWorker(opts);
92
+ case "codex":
93
+ return createCodexWorker(opts);
94
+ case "codex-app-server":
95
+ return createCodexWorker({ ...opts, transport: "app-server" });
96
+ case "delegator": {
97
+ const { DelegatorMcpWorker } = await import("./delegator-mcp.mjs");
98
+ return new DelegatorMcpWorker(opts);
99
+ }
100
+ default:
101
+ throw new Error(`Unknown worker type: ${type}`);
102
+ }
103
+ }
@@ -1,62 +1,60 @@
1
- // hub/workers/gemini-worker.mjs — Gemini headless subprocess 래퍼
2
- // ADR-006: --output-format stream-json 기반 단발 실행 워커.
1
+ // hub/workers/gemini-worker.mjs
2
+ // Deprecated compatibility surface. Direct Gemini CLI execution is removed;
3
+ // old imports now route through Antigravity.
3
4
 
4
- import { spawn } from 'node:child_process';
5
- import { isAbsolute } from 'node:path';
6
- import readline from 'node:readline';
5
+ import { existsSync } from "node:fs";
6
+ import { extname } from "node:path";
7
7
 
8
- import { extractText, terminateChild, withRetry } from './worker-utils.mjs';
9
-
10
- const DEFAULT_TIMEOUT_MS = 15 * 60 * 1000;
11
- const DEFAULT_KILL_GRACE_MS = 1000;
8
+ import { AntigravityRouteWorker } from "./antigravity-route-worker.mjs";
9
+ import { withRetry } from "./worker-utils.mjs";
12
10
 
13
11
  function toStringList(value) {
14
12
  if (!Array.isArray(value)) return [];
15
- return value
16
- .map((item) => String(item ?? '').trim())
17
- .filter(Boolean);
18
- }
19
-
20
- function safeJsonParse(line) {
21
- try {
22
- return JSON.parse(line);
23
- } catch {
24
- return null;
25
- }
13
+ return value.map((item) => String(item ?? "").trim()).filter(Boolean);
26
14
  }
27
15
 
28
- function findLastEvent(events, predicate) {
29
- for (let index = events.length - 1; index >= 0; index -= 1) {
30
- if (predicate(events[index])) return events[index];
31
- }
32
- return null;
16
+ /**
17
+ * Windows에서 cmd.exe에 전달할 인자를 안전하게 quoting한다.
18
+ */
19
+ function quoteWindowsCmdArg(value) {
20
+ const raw = String(value ?? "").replace(/[\r\n]/g, " ");
21
+ if (raw.length === 0) return '""';
22
+ if (!/[\s"()^&|<>%!]/.test(raw)) return raw;
23
+ const escaped = raw
24
+ .replace(/%/g, "%%")
25
+ .replace(/(\\*)"/g, '$1$1\\"')
26
+ .replace(/(\\+)$/g, "$1$1");
27
+ return `"${escaped}"`;
33
28
  }
34
29
 
35
- function buildGeminiArgs(options) {
36
- const args = [];
37
-
38
- if (options.model) {
39
- args.push('--model', options.model);
30
+ /**
31
+ * Windows npm shim(.cmd) spawn 문제를 해결한다.
32
+ */
33
+ function buildSpawnSpec(command, args) {
34
+ if (process.platform !== "win32") {
35
+ return { command, args, shell: false };
40
36
  }
41
37
 
42
- if (options.approvalMode) {
43
- args.push('--approval-mode', options.approvalMode);
44
- } else if (options.yolo !== false) {
45
- args.push('--yolo');
38
+ let resolved = command;
39
+ if (!extname(resolved)) {
40
+ for (const ext of [".exe", ".cmd", ".bat"]) {
41
+ if (existsSync(resolved + ext)) {
42
+ resolved = resolved + ext;
43
+ break;
44
+ }
45
+ }
46
46
  }
47
47
 
48
- const allowedMcpServers = toStringList(options.allowedMcpServerNames);
49
- if (allowedMcpServers.length) {
50
- args.push('--allowed-mcp-server-names', ...allowedMcpServers);
48
+ if (/\.(cmd|bat)$/i.test(resolved)) {
49
+ const line = [resolved, ...args].map(quoteWindowsCmdArg).join(" ");
50
+ return {
51
+ command: "cmd.exe",
52
+ args: ["/d", "/s", "/v:off", "/c", line],
53
+ shell: false,
54
+ };
51
55
  }
52
56
 
53
- const extraArgs = toStringList(options.extraArgs);
54
- if (extraArgs.length) args.push(...extraArgs);
55
-
56
- args.push('--prompt', options.promptArgument ?? '');
57
- args.push('--output-format', 'stream-json');
58
-
59
- return args;
57
+ return { command: resolved, args, shell: false };
60
58
  }
61
59
 
62
60
  function createWorkerError(message, details = {}) {
@@ -66,49 +64,54 @@ function createWorkerError(message, details = {}) {
66
64
  }
67
65
 
68
66
  function normalizeRetryOptions(retryOptions) {
69
- if (!retryOptions || typeof retryOptions !== 'object') {
67
+ if (!retryOptions || typeof retryOptions !== "object") {
70
68
  return Object.freeze({});
71
69
  }
72
70
  return Object.freeze({ ...retryOptions });
73
71
  }
74
72
 
75
- function isGeminiRetryable(error) {
76
- return error?.code === 'WORKER_EXIT'
77
- && error?.result?.exitCode !== 0
78
- && error?.result?.exitCode !== 2;
73
+ function isAntigravityRetryable(error) {
74
+ return (
75
+ error?.code === "WORKER_EXIT" &&
76
+ error?.result?.exitCode !== 0 &&
77
+ error?.result?.exitCode !== 2
78
+ );
79
79
  }
80
80
 
81
- function detectGeminiCategory(error) {
82
- const combined = `${error?.message || ''}\n${error?.stderr || ''}`.toLowerCase();
81
+ function detectAntigravityCategory(error) {
82
+ const combined =
83
+ `${error?.message || ""}\n${error?.stderr || ""}`.toLowerCase();
83
84
 
84
- if (/(unauthorized|forbidden|auth|login|token|credential|apikey|api key)/.test(combined)) {
85
- return 'auth';
86
- }
87
- if (error?.result?.exitCode === 2 || /expected stream-json|unknown option|invalid option|config/.test(combined)) {
88
- return 'config';
85
+ if (
86
+ /(unauthorized|forbidden|auth|login|token|credential|apikey|api key)/.test(
87
+ combined,
88
+ )
89
+ ) {
90
+ return "auth";
89
91
  }
90
- if (error?.code === 'WORKER_EVENT_ERROR') {
91
- return 'input';
92
+ if (
93
+ error?.result?.exitCode === 2 ||
94
+ /unknown option|invalid option|config|route script/.test(combined)
95
+ ) {
96
+ return "config";
92
97
  }
93
98
 
94
- return 'transient';
99
+ return "transient";
95
100
  }
96
101
 
97
- function buildGeminiErrorInfo(error, attempts) {
98
- const category = detectGeminiCategory(error);
99
- const retryable = isGeminiRetryable(error);
100
- let recovery = 'Retry the Gemini worker after correcting the reported issue.';
102
+ function buildAntigravityErrorInfo(error, attempts) {
103
+ const category = detectAntigravityCategory(error);
104
+ const retryable = isAntigravityRetryable(error);
105
+ let recovery = "Retry the Antigravity route worker after correcting the issue.";
101
106
 
102
- if (category === 'auth') {
103
- recovery = 'Refresh the Gemini authentication state and retry.';
104
- } else if (category === 'config') {
105
- recovery = 'Check the Gemini CLI flags and worker configuration.';
106
- } else if (category === 'input') {
107
- recovery = 'Check the Gemini request payload and streamed event format.';
107
+ if (category === "auth") {
108
+ recovery = "Refresh the Antigravity authentication state and retry.";
109
+ } else if (category === "config") {
110
+ recovery = "Check the Antigravity route worker configuration.";
108
111
  }
109
112
 
110
113
  return Object.freeze({
111
- code: error?.code || 'GEMINI_EXECUTION_ERROR',
114
+ code: error?.code || "ANTIGRAVITY_EXECUTION_ERROR",
112
115
  retryable,
113
116
  attempts,
114
117
  category,
@@ -116,244 +119,80 @@ function buildGeminiErrorInfo(error, attempts) {
116
119
  });
117
120
  }
118
121
 
119
- /**
120
- * Gemini stream-json 래퍼
121
- */
122
- export class GeminiWorker {
123
- type = 'gemini';
122
+ export class GeminiWorker extends AntigravityRouteWorker {
123
+ type = "antigravity";
124
124
 
125
125
  constructor(options = {}) {
126
- this.command = options.command || 'gemini';
127
- this.commandArgs = toStringList(options.commandArgs || options.args);
128
- this.cwd = options.cwd || process.cwd();
129
- this.env = { ...process.env, ...(options.env || {}) };
130
- this.model = options.model || null;
131
- this.approvalMode = options.approvalMode || null;
132
- this.yolo = options.yolo !== false;
133
- this.allowedMcpServerNames = toStringList(options.allowedMcpServerNames);
134
- this.extraArgs = toStringList(options.extraArgs);
135
- this.timeoutMs = Number(options.timeoutMs) > 0 ? Number(options.timeoutMs) : DEFAULT_TIMEOUT_MS;
136
- this.killGraceMs = Number(options.killGraceMs) > 0 ? Number(options.killGraceMs) : DEFAULT_KILL_GRACE_MS;
126
+ const { command, commandArgs, args, model, approvalMode, yolo, ...rest } =
127
+ options;
128
+ void commandArgs;
129
+ void args;
130
+ void model;
131
+ void approvalMode;
132
+ void yolo;
133
+ super(rest);
134
+ this.command = command || "agy";
137
135
  this.retryOptions = normalizeRetryOptions(options.retryOptions);
138
- this.onEvent = typeof options.onEvent === 'function' ? options.onEvent : null;
139
-
140
- this.state = 'idle';
141
- this.child = null;
142
136
  this.lastRun = null;
143
137
  }
144
138
 
145
139
  getStatus() {
146
140
  return {
147
- type: 'gemini',
148
- state: this.state,
149
- pid: this.child?.pid || null,
141
+ type: "antigravity",
142
+ state: this.children.size > 0 ? "running" : "idle",
143
+ pid: null,
150
144
  last_run_at_ms: this.lastRun?.finishedAtMs || null,
151
145
  last_exit_code: this.lastRun?.exitCode ?? null,
152
146
  };
153
147
  }
154
148
 
155
- async start() {
156
- if (this.state === 'stopped') {
157
- this.state = 'idle';
158
- }
159
- return this.getStatus();
160
- }
161
-
162
- async stop() {
163
- if (!this.child) {
164
- this.state = 'stopped';
165
- return this.getStatus();
166
- }
167
- const child = this.child;
168
- terminateChild(child, this.killGraceMs);
169
- await new Promise((resolve) => {
170
- child.once('close', resolve);
171
- setTimeout(resolve, this.killGraceMs + 50).unref?.();
172
- });
173
- this.child = null;
174
- this.state = 'stopped';
175
- return this.getStatus();
176
- }
177
-
178
- async restart() {
179
- await this.stop();
180
- this.state = 'idle';
181
- return this.getStatus();
182
- }
183
-
184
149
  async run(prompt, options = {}) {
185
- if (this.child) {
186
- throw createWorkerError('GeminiWorker is already running', { code: 'WORKER_BUSY' });
187
- }
188
-
189
- await this.start();
190
-
191
- const timeoutMs = Number(options.timeoutMs) > 0 ? Number(options.timeoutMs) : this.timeoutMs;
192
150
  const startedAtMs = Date.now();
193
- const args = [
194
- ...this.commandArgs,
195
- ...buildGeminiArgs({
196
- model: options.model || this.model,
197
- approvalMode: options.approvalMode || this.approvalMode,
198
- yolo: options.yolo ?? this.yolo,
199
- allowedMcpServerNames: options.allowedMcpServerNames || this.allowedMcpServerNames,
200
- extraArgs: options.extraArgs || this.extraArgs,
201
- promptArgument: options.promptArgument ?? '',
202
- }),
203
- ];
204
-
205
- const child = spawn(this.command, args, {
206
- cwd: options.cwd || this.cwd,
207
- env: { ...this.env, ...(options.env || {}) },
208
- stdio: ['pipe', 'pipe', 'pipe'],
209
- windowsHide: true,
210
- shell: process.platform === 'win32' && !isAbsolute(this.command),
211
- });
212
-
213
- this.child = child;
214
- this.state = 'running';
215
-
216
- const events = [];
217
- const stdoutLines = [];
218
- const stderrLines = [];
219
- let lastErrorEvent = null;
220
- let timedOut = false;
221
- let exitCode = null;
222
- let exitSignal = null;
223
-
224
- const stdoutReader = readline.createInterface({
225
- input: child.stdout,
226
- crlfDelay: Infinity,
227
- });
228
- const stderrReader = readline.createInterface({
229
- input: child.stderr,
230
- crlfDelay: Infinity,
231
- });
232
-
233
- stdoutReader.on('line', (line) => {
234
- if (!line) return;
235
- const event = safeJsonParse(line);
236
- if (!event) {
237
- stdoutLines.push(line);
238
- return;
239
- }
240
-
241
- events.push(event);
242
- if (event.type === 'error') lastErrorEvent = event;
243
- if (this.onEvent) {
244
- try { this.onEvent(event); } catch {}
245
- }
246
- });
247
-
248
- stderrReader.on('line', (line) => {
249
- if (!line) return;
250
- stderrLines.push(line);
251
- });
252
-
253
- const closePromise = new Promise((resolve, reject) => {
254
- child.once('error', reject);
255
- child.once('close', (code, signal) => {
256
- exitCode = code;
257
- exitSignal = signal;
258
- resolve();
259
- });
260
- });
261
-
262
- const timeout = setTimeout(() => {
263
- timedOut = true;
264
- terminateChild(child, this.killGraceMs);
265
- }, timeoutMs);
266
- timeout.unref?.();
267
-
268
- child.stdin.on('error', () => {});
269
- child.stdin.end(String(prompt ?? ''));
270
-
271
- try {
272
- await closePromise;
273
- } finally {
274
- clearTimeout(timeout);
275
- stdoutReader.close();
276
- stderrReader.close();
277
- if (this.child === child) {
278
- this.child = null;
279
- }
280
- this.state = 'idle';
281
- }
282
-
283
- const resultEvent = findLastEvent(events, (event) => event?.type === 'result');
284
- const response = [
285
- extractText(resultEvent),
286
- ...events
287
- .filter((event) => event?.type === 'message' || event?.type === 'assistant')
288
- .map((event) => extractText(event))
289
- .filter(Boolean),
290
- ...stdoutLines,
291
- ]
292
- .filter(Boolean)
293
- .join('\n')
294
- .trim();
295
-
296
- const result = {
297
- type: 'gemini',
151
+ const result = await super.run(prompt, options);
152
+ const normalized = {
153
+ type: "antigravity",
298
154
  command: this.command,
299
- args,
300
- response,
301
- events,
302
- resultEvent,
303
- usage: resultEvent?.usage || null,
304
- stdout: stdoutLines.join('\n').trim(),
305
- stderr: stderrLines.join('\n').trim(),
306
- exitCode,
307
- exitSignal,
308
- timedOut,
155
+ args: toStringList(options.extraArgs),
156
+ response: result.response || result.output || "",
157
+ output: result.output || result.response || "",
158
+ stderr: result.stderr || "",
159
+ exitCode: result.exitCode ?? 1,
160
+ exitSignal: null,
161
+ timedOut: result.exitCode === 124,
309
162
  startedAtMs,
310
163
  finishedAtMs: Date.now(),
164
+ raw: result.raw || result,
311
165
  };
312
-
313
- this.lastRun = result;
314
-
315
- if (timedOut) {
316
- throw createWorkerError(`Gemini worker timed out after ${timeoutMs}ms`, {
317
- code: 'ETIMEDOUT',
318
- result,
319
- stderr: result.stderr,
320
- });
166
+ this.lastRun = normalized;
167
+
168
+ if (normalized.exitCode !== 0) {
169
+ throw createWorkerError(
170
+ `Antigravity route worker exited with code ${normalized.exitCode}`,
171
+ {
172
+ code: "WORKER_EXIT",
173
+ result: normalized,
174
+ stderr: normalized.stderr,
175
+ },
176
+ );
321
177
  }
322
178
 
323
- if (exitCode !== 0) {
324
- throw createWorkerError(`Gemini worker exited with code ${exitCode}`, {
325
- code: 'WORKER_EXIT',
326
- result,
327
- stderr: result.stderr,
328
- });
329
- }
330
-
331
- if (lastErrorEvent) {
332
- throw createWorkerError('Gemini worker emitted an error event', {
333
- code: 'WORKER_EVENT_ERROR',
334
- result,
335
- stderr: result.stderr,
336
- });
337
- }
338
-
339
- return result;
340
- }
341
-
342
- isReady() {
343
- return this.state !== 'stopped';
179
+ return normalized;
344
180
  }
345
181
 
346
182
  async execute(prompt, options = {}) {
347
183
  let attempts = 0;
348
184
 
349
185
  try {
350
- const result = await withRetry(async () => {
351
- attempts += 1;
352
- return this.run(prompt, options);
353
- }, {
354
- ...this.retryOptions,
355
- shouldRetry: (error) => isGeminiRetryable(error),
356
- });
186
+ const result = await withRetry(
187
+ async () => {
188
+ attempts += 1;
189
+ return this.run(prompt, options);
190
+ },
191
+ {
192
+ ...this.retryOptions,
193
+ shouldRetry: (error) => isAntigravityRetryable(error),
194
+ },
195
+ );
357
196
 
358
197
  return {
359
198
  output: result.response,
@@ -363,12 +202,15 @@ export class GeminiWorker {
363
202
  };
364
203
  } catch (error) {
365
204
  return {
366
- output: error.stderr || error.message || 'Gemini worker failed',
367
- exitCode: error.code === 'ETIMEDOUT' ? 124 : 1,
205
+ output: error.stderr || error.message || "Antigravity worker failed",
206
+ exitCode: error.code === "ETIMEDOUT" ? 124 : 1,
368
207
  sessionKey: options.sessionKey || null,
369
- error: buildGeminiErrorInfo(error, attempts || 1),
208
+ error: buildAntigravityErrorInfo(error, attempts || 1),
370
209
  raw: error.result || null,
371
210
  };
372
211
  }
373
212
  }
374
213
  }
214
+
215
+ /** @visibleForTesting - 테스트 전용 export. 외부 소비 금지. */
216
+ export { buildSpawnSpec, quoteWindowsCmdArg };
@@ -16,6 +16,21 @@
16
16
  * @property {string} [developerInstructions] - 개발자 지침
17
17
  * @property {string} [compactPrompt] - 컴팩션 프롬프트
18
18
  * @property {number} [timeoutMs] - MCP 요청 타임아웃(ms)
19
+ * @property {'mcp'|'app-server'} [transport] - Codex 전송 계층 선택 (팩토리 전용)
20
+ */
21
+
22
+ /**
23
+ * Codex 워커 생성 옵션 (팩토리 레벨)
24
+ * @typedef {object} CodexWorkerFactoryOptions
25
+ * @property {'mcp'|'app-server'} [transport] - 'mcp' (기본) 또는 'app-server'
26
+ * @property {(publishMessage: object) => void|Promise<void>} [publishCallback] - app-server 전용 publish 후크
27
+ * @property {(path: string, opts?: object) => Promise<unknown>} [requestJsonFn] - 테스트/리팩터용 requestJson 주입
28
+ * @property {string} [command] - codex 실행 바이너리 경로
29
+ * @property {string[]} [args] - codex CLI 인자 배열
30
+ * @property {string} [cwd] - 워커 작업 디렉터리
31
+ * @property {Record<string, string>} [env] - 추가 환경 변수
32
+ * @property {{ name: string, version: string }} [clientInfo] - MCP/JSON-RPC clientInfo
33
+ * @property {number} [bootstrapTimeoutMs] - 핸드셰이크 타임아웃(ms)
19
34
  */
20
35
 
21
36
  /**
@@ -46,7 +61,14 @@
46
61
  * @property {() => Promise<void>} start
47
62
  * @property {() => Promise<void>} stop
48
63
  * @property {() => boolean} isReady
49
- * @property {string} type - 'codex' | 'gemini' | 'claude' | 'delegator'
64
+ * @property {'codex'|'codex-app-server'|'gemini'|'antigravity'|'claude'|'delegator'} type
50
65
  */
51
66
 
52
- export const WORKER_TYPES = Object.freeze(['codex', 'gemini', 'claude', 'delegator']);
67
+ export const WORKER_TYPES = Object.freeze([
68
+ "codex",
69
+ "codex-app-server",
70
+ "gemini",
71
+ "antigravity",
72
+ "claude",
73
+ "delegator",
74
+ ]);