@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.
- package/hub/pipe.mjs +239 -121
- package/hub/public/dashboard.html +61 -0
- package/hub/server.mjs +2179 -709
- package/hub/store-adapter.mjs +59 -26
- package/hub/store.mjs +356 -153
- package/hub/team/agent-map.json +26 -7
- package/hub/team/ansi.mjs +245 -146
- package/hub/team/backend.mjs +78 -17
- package/hub/team/build-worker-prompt.mjs +73 -0
- package/hub/team/check-mcp-hub.mjs +49 -0
- package/hub/team/claude-daemon-control.mjs +1130 -0
- package/hub/team/claude-native-bridge.mjs +1011 -0
- package/hub/team/claude-session-projection.mjs +74 -0
- package/hub/team/cli/commands/attach.mjs +57 -37
- package/hub/team/cli/commands/control.mjs +29 -8
- package/hub/team/cli/commands/debug.mjs +95 -74
- package/hub/team/cli/commands/focus.mjs +80 -53
- package/hub/team/cli/commands/interrupt.mjs +18 -6
- package/hub/team/cli/commands/kill.mjs +16 -5
- package/hub/team/cli/commands/list.mjs +31 -24
- package/hub/team/cli/commands/send.mjs +19 -6
- package/hub/team/cli/commands/start/index.mjs +151 -33
- package/hub/team/cli/commands/start/parse-args.mjs +97 -13
- package/hub/team/cli/commands/start/start-headless.mjs +155 -37
- package/hub/team/cli/commands/start/start-in-process.mjs +50 -40
- package/hub/team/cli/commands/start/start-mux.mjs +122 -73
- package/hub/team/cli/commands/start/start-wt.mjs +86 -69
- package/hub/team/cli/commands/status.mjs +43 -14
- package/hub/team/cli/commands/stop.mjs +11 -4
- package/hub/team/cli/commands/task.mjs +8 -3
- package/hub/team/cli/commands/tasks.mjs +13 -13
- package/hub/team/cli/help.mjs +1 -1
- package/hub/team/cli/index.mjs +2 -2
- package/hub/team/cli/manifest.mjs +38 -8
- package/hub/team/cli/render.mjs +52 -30
- package/hub/team/cli/services/attach-fallback.mjs +74 -54
- package/hub/team/cli/services/hub-client.mjs +44 -16
- package/hub/team/cli/services/member-selector.mjs +37 -30
- package/hub/team/cli/services/native-control.mjs +144 -116
- package/hub/team/cli/services/runtime-mode.mjs +61 -4
- package/hub/team/cli/services/state-store.mjs +66 -9
- package/hub/team/cli/services/task-model.mjs +40 -30
- package/hub/team/codex-review.mjs +521 -0
- package/hub/team/conductor-mesh-bridge.mjs +25 -24
- package/hub/team/conductor-registry.mjs +121 -0
- package/hub/team/conductor.mjs +1379 -671
- package/hub/team/consensus-meta.mjs +176 -0
- package/hub/team/dashboard-anchor.mjs +4 -5
- package/hub/team/dashboard-layout.mjs +3 -1
- package/hub/team/dashboard-open.mjs +20 -118
- package/hub/team/dashboard.mjs +76 -28
- package/hub/team/event-log.mjs +18 -10
- package/hub/team/execution-mode.mjs +256 -0
- package/hub/team/extract-completion-payload.mjs +110 -0
- package/hub/team/git-preflight.mjs +286 -0
- package/hub/team/handoff.mjs +49 -18
- package/hub/team/headless-bridge-session.mjs +17 -0
- package/hub/team/headless.mjs +2213 -1156
- package/hub/team/health-probe.mjs +232 -69
- package/hub/team/interactive-native-launcher.mjs +106 -0
- package/hub/team/interactive-native-worker.mjs +80 -0
- package/hub/team/interactive-tui-transport.mjs +465 -0
- package/hub/team/launcher-template.mjs +49 -17
- package/hub/team/lead-control.mjs +91 -4
- package/hub/team/mcp-selector.mjs +145 -0
- package/hub/team/native-supervisor.mjs +90 -26
- package/hub/team/native.mjs +120 -42
- package/hub/team/nativeProxy.mjs +184 -113
- package/hub/team/notify.mjs +147 -78
- package/hub/team/orchestrator.mjs +173 -161
- package/hub/team/pane.mjs +43 -20
- package/hub/team/process-cleanup.mjs +69 -25
- package/hub/team/psmux.mjs +1022 -281
- package/hub/team/recovery-store.mjs +149 -0
- package/hub/team/remote-probe.mjs +68 -52
- package/hub/team/remote-session.mjs +131 -59
- package/hub/team/remote-watcher.mjs +61 -33
- package/hub/team/retry-state-machine.mjs +336 -0
- package/hub/team/routing.mjs +51 -25
- package/hub/team/runtime-strategy.mjs +200 -0
- package/hub/team/schemas/worker-completion.json +39 -0
- package/hub/team/sentinel-capture.mjs +127 -0
- package/hub/team/session-sync.mjs +153 -6
- package/hub/team/session.mjs +574 -611
- package/hub/team/shared.mjs +13 -13
- package/hub/team/staleState.mjs +72 -30
- package/hub/team/swarm-cli.mjs +390 -0
- package/hub/team/swarm-hypervisor.mjs +1544 -193
- package/hub/team/swarm-intent.mjs +216 -0
- package/hub/team/swarm-locks.mjs +140 -25
- package/hub/team/swarm-planner.mjs +121 -21
- package/hub/team/swarm-preflight.mjs +319 -0
- package/hub/team/swarm-reconciler.mjs +128 -23
- package/hub/team/synapse-cli.mjs +458 -0
- package/hub/team/synapse-http.mjs +60 -0
- package/hub/team/synapse-registry.mjs +329 -0
- package/hub/team/terminal-opener.mjs +216 -0
- package/hub/team/tui-core.mjs +292 -0
- package/hub/team/tui-lite.mjs +237 -148
- package/hub/team/tui-remote-adapter.mjs +18 -34
- package/hub/team/tui-synapse.mjs +223 -0
- package/hub/team/tui-viewer.mjs +108 -48
- package/hub/team/tui-widgets.mjs +262 -0
- package/hub/team/tui.mjs +931 -439
- package/hub/team/worker-completion-validator.mjs +84 -0
- package/hub/team/worker-sandbox.mjs +97 -0
- package/hub/team/worker-signal.mjs +263 -0
- package/hub/team/worker-signal.types.d.ts +52 -0
- package/hub/team/worktree-lifecycle.mjs +517 -63
- package/hub/team/wt-manager.mjs +297 -26
- package/hub/tools.mjs +477 -252
- package/hub/tray.mjs +411 -368
- package/hub/workers/antigravity-route-worker.mjs +169 -0
- package/hub/workers/claude-worker.mjs +162 -118
- package/hub/workers/codex-app-server-worker.mjs +1015 -0
- package/hub/workers/codex-mcp.mjs +724 -507
- package/hub/workers/delegator-mcp.mjs +570 -499
- package/hub/workers/factory.mjs +103 -21
- package/hub/workers/gemini-worker.mjs +126 -284
- package/hub/workers/interface.mjs +24 -2
- package/hub/workers/lib/jsonrpc-stdio.mjs +463 -0
- package/hub/workers/worker-utils.mjs +23 -17
- package/package.json +4 -3
- package/scripts/lib/agent-json.mjs +27 -0
- package/scripts/lib/async.mjs +174 -0
- package/scripts/lib/claudemd-scanner.mjs +223 -0
- package/scripts/lib/cli-agy.mjs +62 -0
- package/scripts/lib/cli-claude.mjs +78 -0
- package/scripts/lib/cli-codex.mjs +199 -0
- package/scripts/lib/cli-gemini.mjs +37 -0
- package/scripts/lib/codex-recovery.sh +50 -0
- package/scripts/lib/context.mjs +67 -0
- package/scripts/lib/cross-review-utils.mjs +59 -0
- package/scripts/lib/doctor-env-checks.mjs +121 -0
- package/scripts/lib/dynamic-route-cli.mjs +107 -0
- package/scripts/lib/env-probe.mjs +269 -0
- package/scripts/lib/env.mjs +14 -0
- package/scripts/lib/gemini-profiles.mjs +119 -0
- package/scripts/lib/handoff.mjs +187 -0
- package/scripts/lib/hook-utils.mjs +16 -0
- package/scripts/lib/hub.mjs +48 -0
- package/scripts/lib/keyword-rules.mjs +195 -0
- package/scripts/lib/logger.mjs +105 -0
- package/scripts/lib/mcp-filter.mjs +883 -0
- package/scripts/lib/mcp-gateway-health-check.mjs +159 -0
- package/scripts/lib/mcp-gateway-servers.mjs +70 -0
- package/scripts/lib/mcp-gateway-wrapper-check.mjs +35 -0
- package/scripts/lib/mcp-guard-engine.mjs +2120 -0
- package/scripts/lib/mcp-health.mjs +773 -0
- package/scripts/lib/mcp-manifest.mjs +89 -0
- package/scripts/lib/mcp-server-catalog.mjs +355 -0
- package/scripts/lib/pid.mjs +63 -0
- package/scripts/lib/process-utils.mjs +26 -0
- package/scripts/lib/psmux-info.mjs +224 -0
- package/scripts/lib/quota.mjs +47 -0
- package/scripts/lib/remote-spawn-transfer.mjs +226 -0
- package/scripts/lib/skill-state.mjs +220 -0
- package/scripts/lib/skill-template.mjs +375 -0
- package/scripts/lib/team.mjs +75 -0
- package/scripts/lib/timeout.mjs +66 -0
- package/scripts/lib/tmp.mjs +26 -0
- package/scripts/lib/toml.mjs +113 -0
package/hub/workers/factory.mjs
CHANGED
|
@@ -1,21 +1,103 @@
|
|
|
1
|
-
// hub/workers/factory.mjs — Worker 생성 팩토리
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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
|
|
2
|
-
//
|
|
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 {
|
|
5
|
-
import {
|
|
6
|
-
import readline from 'node:readline';
|
|
5
|
+
import { existsSync } from "node:fs";
|
|
6
|
+
import { extname } from "node:path";
|
|
7
7
|
|
|
8
|
-
import {
|
|
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
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
-
|
|
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 !==
|
|
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
|
|
76
|
-
return
|
|
77
|
-
|
|
78
|
-
|
|
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
|
|
82
|
-
const combined =
|
|
81
|
+
function detectAntigravityCategory(error) {
|
|
82
|
+
const combined =
|
|
83
|
+
`${error?.message || ""}\n${error?.stderr || ""}`.toLowerCase();
|
|
83
84
|
|
|
84
|
-
if (
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
85
|
+
if (
|
|
86
|
+
/(unauthorized|forbidden|auth|login|token|credential|apikey|api key)/.test(
|
|
87
|
+
combined,
|
|
88
|
+
)
|
|
89
|
+
) {
|
|
90
|
+
return "auth";
|
|
89
91
|
}
|
|
90
|
-
if (
|
|
91
|
-
|
|
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
|
|
99
|
+
return "transient";
|
|
95
100
|
}
|
|
96
101
|
|
|
97
|
-
function
|
|
98
|
-
const category =
|
|
99
|
-
const retryable =
|
|
100
|
-
let recovery =
|
|
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 ===
|
|
103
|
-
recovery =
|
|
104
|
-
} else if (category ===
|
|
105
|
-
recovery =
|
|
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 ||
|
|
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
|
-
|
|
121
|
-
*/
|
|
122
|
-
export class GeminiWorker {
|
|
123
|
-
type = 'gemini';
|
|
122
|
+
export class GeminiWorker extends AntigravityRouteWorker {
|
|
123
|
+
type = "antigravity";
|
|
124
124
|
|
|
125
125
|
constructor(options = {}) {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
this.
|
|
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:
|
|
148
|
-
state: this.
|
|
149
|
-
pid:
|
|
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
|
|
194
|
-
|
|
195
|
-
|
|
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
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
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
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
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
|
-
|
|
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(
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
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 ||
|
|
367
|
-
exitCode: error.code ===
|
|
205
|
+
output: error.stderr || error.message || "Antigravity worker failed",
|
|
206
|
+
exitCode: error.code === "ETIMEDOUT" ? 124 : 1,
|
|
368
207
|
sessionKey: options.sessionKey || null,
|
|
369
|
-
error:
|
|
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 {
|
|
64
|
+
* @property {'codex'|'codex-app-server'|'gemini'|'antigravity'|'claude'|'delegator'} type
|
|
50
65
|
*/
|
|
51
66
|
|
|
52
|
-
export const WORKER_TYPES = Object.freeze([
|
|
67
|
+
export const WORKER_TYPES = Object.freeze([
|
|
68
|
+
"codex",
|
|
69
|
+
"codex-app-server",
|
|
70
|
+
"gemini",
|
|
71
|
+
"antigravity",
|
|
72
|
+
"claude",
|
|
73
|
+
"delegator",
|
|
74
|
+
]);
|