@triflux/remote 10.50.2 → 10.52.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 (134) hide show
  1. package/cto/brief.mjs +0 -20
  2. package/cto/collect.mjs +1 -200
  3. package/cto/current.schema.json +2 -23
  4. package/cto/hygiene.mjs +2 -61
  5. package/cto/lake-root.mjs +0 -56
  6. package/cto/status.mjs +5 -187
  7. package/hub/index.mjs +0 -17
  8. package/hub/team/agent-map.json +0 -1
  9. package/hub/team/agents-row-attach.py +120 -0
  10. package/hub/team/agents-row.mjs +200 -0
  11. package/hub/team/ansi.mjs +1 -35
  12. package/hub/team/backend.mjs +9 -145
  13. package/hub/team/claude-daemon-control.mjs +1 -2
  14. package/hub/team/claude-session-projection.mjs +5 -73
  15. package/hub/team/cli/commands/attach.mjs +1 -17
  16. package/hub/team/cli/commands/control.mjs +12 -38
  17. package/hub/team/cli/commands/debug.mjs +2 -39
  18. package/hub/team/cli/commands/focus.mjs +5 -31
  19. package/hub/team/cli/commands/interrupt.mjs +6 -25
  20. package/hub/team/cli/commands/kill.mjs +1 -30
  21. package/hub/team/cli/commands/list.mjs +0 -16
  22. package/hub/team/cli/commands/send.mjs +6 -26
  23. package/hub/team/cli/commands/start/index.mjs +42 -133
  24. package/hub/team/cli/commands/start/parse-args.mjs +15 -40
  25. package/hub/team/cli/commands/start/start-headless.mjs +138 -105
  26. package/hub/team/cli/commands/start/start-mux.mjs +2 -7
  27. package/hub/team/cli/commands/status.mjs +2 -57
  28. package/hub/team/cli/commands/stop.mjs +14 -19
  29. package/hub/team/cli/help.mjs +4 -5
  30. package/hub/team/cli/manifest.mjs +5 -1
  31. package/hub/team/cli/render.mjs +0 -12
  32. package/hub/team/cli/services/runtime-mode.mjs +27 -98
  33. package/hub/team/cli/services/state-store.mjs +5 -20
  34. package/hub/team/handoff.mjs +3 -34
  35. package/hub/team/headless.mjs +108 -1296
  36. package/hub/team/orchestrator.mjs +4 -30
  37. package/hub/team/pane.mjs +6 -28
  38. package/hub/team/psmux.mjs +3 -34
  39. package/hub/team/remote-session.mjs +1 -2
  40. package/hub/team/session-context.mjs +1 -1
  41. package/hub/team/session.mjs +7 -91
  42. package/hub/team/tui-core.mjs +1 -1
  43. package/hub/team/tui-viewer.mjs +11 -58
  44. package/hub/team/tui.mjs +8 -306
  45. package/hub/team/uds-orchestrator.mjs +2 -580
  46. package/hub/team/wt-manager.mjs +2 -2
  47. package/hub/workers/factory.mjs +6 -84
  48. package/hub/workers/interface.mjs +1 -7
  49. package/hub/workers/lib/jsonrpc-core.mjs +11 -0
  50. package/hub/workers/lib/jsonrpc-ws-uds.mjs +5 -8
  51. package/package.json +3 -8
  52. package/scripts/lib/agent-route-policy.mjs +143 -62
  53. package/scripts/lib/cli-claude.mjs +0 -14
  54. package/scripts/lib/env-probe.mjs +2 -131
  55. package/scripts/lib/gemini-profiles.mjs +2 -69
  56. package/scripts/lib/legacy-mcp-cleanup.mjs +283 -0
  57. package/scripts/lib/machine-profile.mjs +3 -17
  58. package/scripts/lib/mcp-filter.mjs +109 -53
  59. package/scripts/lib/mcp-guard-engine.mjs +40 -98
  60. package/scripts/lib/mcp-health.mjs +2 -2
  61. package/scripts/lib/mcp-server-catalog.mjs +2 -2
  62. package/scripts/lib/psmux-info.mjs +11 -59
  63. package/scripts/lib/skill-template.mjs +0 -245
  64. package/hub/hub-lifecycle.mjs +0 -248
  65. package/hub/pipe.mjs +0 -825
  66. package/hub/promote-penalties.mjs +0 -138
  67. package/hub/public/dashboard.html +0 -416
  68. package/hub/schema.sql +0 -209
  69. package/hub/server.mjs +0 -3048
  70. package/hub/store-adapter.mjs +0 -182
  71. package/hub/store.mjs +0 -1736
  72. package/hub/team/build-worker-prompt.mjs +0 -92
  73. package/hub/team/check-mcp-hub.mjs +0 -49
  74. package/hub/team/claude-native-bridge.mjs +0 -987
  75. package/hub/team/cli/commands/start/start-in-process.mjs +0 -50
  76. package/hub/team/cli/commands/start/start-wt.mjs +0 -86
  77. package/hub/team/cli/services/hub-client.mjs +0 -264
  78. package/hub/team/cli/services/native-control.mjs +0 -145
  79. package/hub/team/codex-review.mjs +0 -520
  80. package/hub/team/conductor-mesh-bridge.mjs +0 -122
  81. package/hub/team/conductor-registry.mjs +0 -121
  82. package/hub/team/conductor.mjs +0 -1529
  83. package/hub/team/consensus-meta.mjs +0 -176
  84. package/hub/team/cto-auto-collect.mjs +0 -115
  85. package/hub/team/cto-pull.mjs +0 -31
  86. package/hub/team/daemon-pty-tmux-bridge.mjs +0 -146
  87. package/hub/team/dashboard.mjs +0 -322
  88. package/hub/team/event-log.mjs +0 -84
  89. package/hub/team/execution-mode.mjs +0 -324
  90. package/hub/team/extract-completion-payload.mjs +0 -110
  91. package/hub/team/headless-bridge-session.mjs +0 -17
  92. package/hub/team/health-probe.mjs +0 -435
  93. package/hub/team/interactive-native-launcher.mjs +0 -119
  94. package/hub/team/interactive-native-worker.mjs +0 -80
  95. package/hub/team/interactive-tui-transport.mjs +0 -479
  96. package/hub/team/launcher-template.mjs +0 -144
  97. package/hub/team/lead-control.mjs +0 -191
  98. package/hub/team/mcp-selector.mjs +0 -145
  99. package/hub/team/native-supervisor.mjs +0 -460
  100. package/hub/team/native.mjs +0 -727
  101. package/hub/team/recovery-store.mjs +0 -149
  102. package/hub/team/remote-probe.mjs +0 -292
  103. package/hub/team/remote-watcher.mjs +0 -506
  104. package/hub/team/runtime-strategy.mjs +0 -200
  105. package/hub/team/schemas/worker-completion.json +0 -39
  106. package/hub/team/sentinel-capture.mjs +0 -127
  107. package/hub/team/session-sync.mjs +0 -316
  108. package/hub/team/swarm-cli.mjs +0 -390
  109. package/hub/team/swarm-hypervisor.mjs +0 -2206
  110. package/hub/team/swarm-intent.mjs +0 -216
  111. package/hub/team/swarm-locks.mjs +0 -335
  112. package/hub/team/swarm-planner.mjs +0 -371
  113. package/hub/team/swarm-preflight.mjs +0 -366
  114. package/hub/team/swarm-reconciler.mjs +0 -242
  115. package/hub/team/synapse-cli.mjs +0 -458
  116. package/hub/team/synapse-http.mjs +0 -311
  117. package/hub/team/synapse-registry.mjs +0 -618
  118. package/hub/team/tui-remote-adapter.mjs +0 -377
  119. package/hub/team/tui-synapse.mjs +0 -223
  120. package/hub/team/worker-completion-validator.mjs +0 -79
  121. package/hub/team/worker-sandbox.mjs +0 -125
  122. package/hub/team/worktree-lifecycle.mjs +0 -639
  123. package/hub/team/wt-templates.json +0 -43
  124. package/hub/tools.mjs +0 -900
  125. package/hub/workers/antigravity-route-worker.mjs +0 -179
  126. package/hub/workers/codex-app-server-worker.mjs +0 -1067
  127. package/hub/workers/delegator-mcp.mjs +0 -1098
  128. package/hub/workers/lib/jsonrpc-stdio.mjs +0 -204
  129. package/scripts/lib/claudemd-scanner.mjs +0 -223
  130. package/scripts/lib/context.mjs +0 -67
  131. package/scripts/lib/dynamic-route-cli.mjs +0 -107
  132. package/scripts/lib/handoff.mjs +0 -187
  133. package/scripts/lib/logger.mjs +0 -122
  134. package/scripts/lib/stealth-fetch.mjs +0 -176
@@ -1,101 +1,23 @@
1
1
  // hub/workers/factory.mjs — Worker 생성 팩토리
2
2
  //
3
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
4
+ // - 'claude' → ClaudeWorker
5
+ // - 'codex' → CodexMcpWorker
11
6
  //
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 { AntigravityRouteWorker } from "./antigravity-route-worker.mjs";
18
7
  import { ClaudeWorker } from "./claude-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
8
 
79
9
  /**
80
- * @param {'gemini'|'antigravity'|'agy'|'claude'|'codex'|'codex-app-server'|'delegator'} type
10
+ * @param {'claude'|'codex'} type
81
11
  * @param {object} [opts]
82
12
  * @returns {Promise<import('./interface.mjs').IWorker>}
83
13
  */
84
14
  export async function createWorker(type, opts = {}) {
85
15
  switch (type) {
86
- case "gemini":
87
- case "antigravity":
88
- case "agy":
89
- return new AntigravityRouteWorker(opts);
90
16
  case "claude":
91
17
  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);
18
+ case "codex": {
19
+ const { CodexMcpWorker } = await import("./codex-mcp.mjs");
20
+ return new CodexMcpWorker(opts);
99
21
  }
100
22
  default:
101
23
  throw new Error(`Unknown worker type: ${type}`);
@@ -16,15 +16,11 @@
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
19
  */
21
20
 
22
21
  /**
23
22
  * Codex 워커 생성 옵션 (팩토리 레벨)
24
23
  * @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
24
  * @property {string} [command] - codex 실행 바이너리 경로
29
25
  * @property {string[]} [args] - codex CLI 인자 배열
30
26
  * @property {string} [cwd] - 워커 작업 디렉터리
@@ -61,14 +57,12 @@
61
57
  * @property {() => Promise<void>} start
62
58
  * @property {() => Promise<void>} stop
63
59
  * @property {() => boolean} isReady
64
- * @property {'codex'|'codex-app-server'|'gemini'|'antigravity'|'claude'|'delegator'} type
60
+ * @property {'codex'|'gemini'|'antigravity'|'claude'} type
65
61
  */
66
62
 
67
63
  export const WORKER_TYPES = Object.freeze([
68
64
  "codex",
69
- "codex-app-server",
70
65
  "gemini",
71
66
  "antigravity",
72
67
  "claude",
73
- "delegator",
74
68
  ]);
@@ -12,6 +12,17 @@ export class JsonRpcProtocolError extends Error {
12
12
  }
13
13
  }
14
14
 
15
+ /**
16
+ * Thrown when the underlying stream closes unexpectedly.
17
+ */
18
+ export class JsonRpcTransportError extends Error {
19
+ /** @param {string} message @param {{ cause?: unknown }} [options] */
20
+ constructor(message, options = {}) {
21
+ super(message, { cause: options.cause });
22
+ this.name = "JsonRpcTransportError";
23
+ }
24
+ }
25
+
15
26
  export class JsonRpcDispatchBase {
16
27
  /**
17
28
  * @param {object} options
@@ -5,27 +5,26 @@
5
5
  // on the socket — it speaks WebSocket (RFC 6455) after a standard HTTP/1.1
6
6
  // Upgrade handshake, one JSON-RPC message per text frame. Confirmed live against
7
7
  // codex-cli 0.135.0 (see experiments/native-bridge-feasibility/
8
- // codex-app-server-uds-probe.mjs). This is the UDS sibling of the stdio
9
- // `JsonRpcStdioClient`: same public surface (request/notify/onNotification/
10
- // close/isOpen), different framing.
8
+ // codex-app-server-uds-smoke.mjs). Public surface: request/notify/
9
+ // onNotification/close/isOpen.
11
10
  //
12
11
  // Wire framing notes:
13
12
  // - client -> server frames MUST be masked (RFC 6455 §5.3).
14
13
  // - server -> client frames are unmasked.
15
14
  // - one JSON-RPC object per text frame; fragmented frames are reassembled.
16
15
  // - outbound JSON-RPC omits the `"jsonrpc":"2.0"` header (OpenAI App Server
17
- // JSONL variant), matching JsonRpcStdioClient.
16
+ // JSONL variant).
18
17
  //
19
18
  // Zero new dependencies — minimal hand-rolled WS client (no `ws` package).
20
19
 
21
20
  import { createHash, randomBytes } from "node:crypto";
22
21
  import net from "node:net";
23
22
 
24
- import { JsonRpcDispatchBase } from "./jsonrpc-core.mjs";
25
23
  import {
24
+ JsonRpcDispatchBase,
26
25
  JsonRpcProtocolError,
27
26
  JsonRpcTransportError,
28
- } from "./jsonrpc-stdio.mjs";
27
+ } from "./jsonrpc-core.mjs";
29
28
 
30
29
  const WS_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
31
30
  const DEFAULT_MAX_FRAME_SIZE = 16 * 1024 * 1024; // 16 MiB
@@ -475,5 +474,3 @@ export class JsonRpcWsUdsClient extends JsonRpcDispatchBase {
475
474
  this._closeWith("closed", wrapped);
476
475
  }
477
476
  }
478
-
479
- export default JsonRpcWsUdsClient;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@triflux/remote",
3
- "version": "10.50.2",
4
- "description": "triflux remote — team mode, psmux, MCP workers, SQLite store.",
3
+ "version": "10.52.0",
4
+ "description": "triflux remote — team mode, psmux, MCP workers.",
5
5
  "type": "module",
6
6
  "main": "hub/index.mjs",
7
7
  "exports": {
@@ -15,12 +15,7 @@
15
15
  "@triflux/core": "^10.0.0-alpha.1"
16
16
  },
17
17
  "dependencies": {
18
- "@modelcontextprotocol/sdk": "^1.27.1",
19
- "ajv": "^8.18.0",
20
- "better-sqlite3": "^12.10.0",
21
- "pino": "^10.3.1",
22
- "pino-pretty": "^13.1.3",
23
- "zod": "^4.0.0"
18
+ "@modelcontextprotocol/sdk": "^1.27.1"
24
19
  },
25
20
  "files": [
26
21
  "hub",
@@ -1,176 +1,137 @@
1
1
  // Canonical Codex role policy. Concrete model IDs stay in profile config files.
2
2
 
3
+ import { readFileSync } from "node:fs";
4
+ import { pathToFileURL } from "node:url";
3
5
  import {
4
6
  normalizeCodexProfileName,
5
7
  resolveCodexProfileConfigValues,
6
8
  } from "./codex-profile-config.mjs";
9
+ import { resolveGeminiProfileForPurpose } from "./gemini-profiles.mjs";
7
10
 
8
11
  const policy = {
9
12
  executor: {
10
13
  profile: "gpt61_sol_high",
11
14
  timeoutSec: 1080,
12
- runMode: "fg",
13
- opusOversight: false,
14
15
  mcpHint: "implement",
15
16
  subcommand: "exec",
16
17
  },
17
18
  codex: {
18
19
  profile: "gpt61_sol_high",
19
20
  timeoutSec: 1080,
20
- runMode: "fg",
21
- opusOversight: false,
22
21
  mcpHint: "implement",
23
22
  subcommand: "exec",
24
23
  },
25
24
  "build-fixer": {
26
25
  profile: "gpt6_luna_high",
27
26
  timeoutSec: 540,
28
- runMode: "fg",
29
- opusOversight: false,
30
27
  mcpHint: "implement",
31
28
  subcommand: "exec",
32
29
  },
33
30
  cleanup: {
34
31
  profile: "gpt61_sol_med",
35
32
  timeoutSec: 540,
36
- runMode: "fg",
37
- opusOversight: false,
38
33
  mcpHint: "implement",
39
34
  subcommand: "exec",
40
35
  },
41
36
  deslop: {
42
37
  profile: "gpt61_sol_med",
43
38
  timeoutSec: 540,
44
- runMode: "fg",
45
- opusOversight: false,
46
39
  mcpHint: "implement",
47
40
  subcommand: "exec",
48
41
  },
49
42
  debugger: {
50
43
  profile: "gpt6_astra_xhigh",
51
44
  timeoutSec: 900,
52
- runMode: "bg",
53
- opusOversight: false,
54
45
  mcpHint: "implement",
55
46
  subcommand: "exec",
56
47
  },
57
48
  "deep-executor": {
58
49
  profile: "gpt6_astra_xhigh",
59
50
  timeoutSec: 3600,
60
- runMode: "bg",
61
- opusOversight: true,
62
51
  mcpHint: "implement",
63
52
  subcommand: "exec",
64
53
  },
65
54
  architect: {
66
55
  profile: "gpt6_astra_xhigh",
67
56
  timeoutSec: 3600,
68
- runMode: "bg",
69
- opusOversight: true,
70
57
  mcpHint: "analyze",
71
58
  subcommand: "exec",
72
59
  },
73
60
  critic: {
74
61
  profile: "gpt6_astra_xhigh",
75
62
  timeoutSec: 3600,
76
- runMode: "bg",
77
- opusOversight: true,
78
63
  mcpHint: "analyze",
79
64
  subcommand: "exec",
80
65
  },
81
66
  planner: {
82
67
  profile: "gpt6_astra_xhigh",
83
68
  timeoutSec: 3600,
84
- runMode: "fg",
85
- opusOversight: true,
86
69
  mcpHint: "analyze",
87
70
  subcommand: "exec",
88
71
  },
89
72
  analyst: {
90
73
  profile: "gpt6_astra_xhigh",
91
74
  timeoutSec: 3600,
92
- runMode: "fg",
93
- opusOversight: true,
94
75
  mcpHint: "analyze",
95
76
  subcommand: "exec",
96
77
  },
97
78
  "code-reviewer": {
98
79
  profile: "gpt61_sol_high",
99
80
  timeoutSec: 1800,
100
- runMode: "bg",
101
- opusOversight: false,
102
81
  mcpHint: "review",
103
82
  subcommand: "review",
104
83
  },
105
84
  "quality-reviewer": {
106
85
  profile: "gpt61_sol_high",
107
86
  timeoutSec: 1800,
108
- runMode: "bg",
109
- opusOversight: false,
110
87
  mcpHint: "review",
111
88
  subcommand: "review",
112
89
  },
113
90
  "security-reviewer": {
114
91
  profile: "gpt6_astra_xhigh",
115
92
  timeoutSec: 1800,
116
- runMode: "bg",
117
- opusOversight: true,
118
93
  mcpHint: "review",
119
94
  subcommand: "review",
120
95
  },
121
96
  scientist: {
122
97
  profile: "gpt61_sol_high",
123
98
  timeoutSec: 1440,
124
- runMode: "bg",
125
- opusOversight: false,
126
99
  mcpHint: "analyze",
127
100
  subcommand: "exec",
128
101
  },
129
102
  "document-specialist": {
130
103
  profile: "gpt61_sol_high",
131
104
  timeoutSec: 1440,
132
- runMode: "bg",
133
- opusOversight: false,
134
105
  mcpHint: "analyze",
135
106
  subcommand: "exec",
136
107
  },
137
108
  "scientist-deep": {
138
109
  profile: "gpt6_astra_xhigh",
139
110
  timeoutSec: 3600,
140
- runMode: "bg",
141
- opusOversight: false,
142
111
  mcpHint: "analyze",
143
112
  subcommand: "exec",
144
113
  },
145
114
  verifier: {
146
115
  profile: "gpt61_sol_high",
147
116
  timeoutSec: 1200,
148
- runMode: "fg",
149
- opusOversight: false,
150
117
  mcpHint: "review",
151
118
  subcommand: "review",
152
119
  },
153
120
  "test-engineer": {
154
121
  profile: "gpt61_sol_high",
155
122
  timeoutSec: 1200,
156
- runMode: "bg",
157
- opusOversight: false,
158
123
  mcpHint: "implement",
159
124
  subcommand: "exec",
160
125
  },
161
126
  "qa-tester": {
162
127
  profile: "gpt61_sol_high",
163
128
  timeoutSec: 1200,
164
- runMode: "bg",
165
- opusOversight: false,
166
129
  mcpHint: "review",
167
130
  subcommand: "review",
168
131
  },
169
132
  spark: {
170
133
  profile: "gpt6_luna_low",
171
134
  timeoutSec: 180,
172
- runMode: "fg",
173
- opusOversight: false,
174
135
  mcpHint: "implement",
175
136
  subcommand: "exec",
176
137
  },
@@ -179,16 +140,12 @@ const policy = {
179
140
  designer: {
180
141
  profile: "gpt6_astra_xhigh",
181
142
  timeoutSec: 900,
182
- runMode: "bg",
183
- opusOversight: false,
184
143
  mcpHint: "implement",
185
144
  subcommand: "exec",
186
145
  },
187
146
  writer: {
188
147
  profile: "gpt6_luna_high",
189
148
  timeoutSec: 900,
190
- runMode: "bg",
191
- opusOversight: false,
192
149
  mcpHint: "implement",
193
150
  subcommand: "exec",
194
151
  },
@@ -198,7 +155,7 @@ function freezePolicy(entry) {
198
155
  return Object.freeze({ ...entry });
199
156
  }
200
157
 
201
- export const CODEX_AGENT_POLICY = Object.freeze(
158
+ const CODEX_AGENT_POLICY = Object.freeze(
202
159
  Object.fromEntries(
203
160
  Object.entries(policy).map(([agent, entry]) => [
204
161
  agent,
@@ -206,9 +163,9 @@ export const CODEX_AGENT_POLICY = Object.freeze(
206
163
  ]),
207
164
  ),
208
165
  );
209
- export const DEFAULT_CODEX_AGENT = "executor";
166
+ const DEFAULT_CODEX_AGENT = "executor";
210
167
 
211
- export const CANONICAL_CODEX_PROFILE_OVERRIDES = Object.freeze(
168
+ const CANONICAL_CODEX_PROFILE_OVERRIDES = Object.freeze(
212
169
  new Set([
213
170
  "gpt6_luna_low",
214
171
  "gpt6_luna_high",
@@ -219,7 +176,7 @@ export const CANONICAL_CODEX_PROFILE_OVERRIDES = Object.freeze(
219
176
  "gpt6_astra_ultra",
220
177
  ]),
221
178
  );
222
- export const ULTRA_ELIGIBLE_CODEX_AGENTS = Object.freeze(
179
+ const ULTRA_ELIGIBLE_CODEX_AGENTS = Object.freeze(
223
180
  new Set(["deep-executor", "scientist-deep"]),
224
181
  );
225
182
 
@@ -316,39 +273,163 @@ export function resolveNestedCodexAgentProfile(
316
273
  return effort.toLowerCase() === "ultra" ? "gpt6_astra_max" : candidate;
317
274
  }
318
275
 
319
- export function describeCodexAgentPolicy() {
320
- return {
321
- defaultAgent: DEFAULT_CODEX_AGENT,
322
- agents: Object.keys(CODEX_AGENT_POLICY),
323
- };
276
+ const EXPECTED_DURATION_BY_AGENT = Object.freeze({
277
+ writer: 90,
278
+ verifier: 90,
279
+ "qa-tester": 90,
280
+ executor: 300,
281
+ debugger: 300,
282
+ "test-engineer": 300,
283
+ "code-reviewer": 600,
284
+ "security-reviewer": 600,
285
+ architect: 600,
286
+ planner: 600,
287
+ critic: 600,
288
+ analyst: 600,
289
+ scientist: 900,
290
+ "scientist-deep": 900,
291
+ "deep-executor": 900,
292
+ "document-specialist": 900,
293
+ });
294
+
295
+ const NATIVE_CODEX_AGENTS = new Set([
296
+ "explore",
297
+ "verifier",
298
+ "test-engineer",
299
+ "qa-tester",
300
+ ]);
301
+
302
+ function resolveDirectCodexRoutePolicy(agent) {
303
+ if (agent === "explore") {
304
+ return { ...CODEX_AGENT_POLICY.spark, timeoutSec: 600, mcpHint: "analyze" };
305
+ }
306
+ if (agent === "antigravity" || agent === "agy") {
307
+ return CODEX_AGENT_POLICY.designer;
308
+ }
309
+ return resolveCodexAgentPolicy(agent);
310
+ }
311
+
312
+ export function resolveAgentRoute(
313
+ agent,
314
+ map,
315
+ profileOverride = "auto",
316
+ nested = false,
317
+ geminiProfileResolver = resolveGeminiProfileForPurpose,
318
+ ) {
319
+ if (!/^[A-Za-z0-9_.-]+$/.test(agent)) {
320
+ throw new Error(`invalid agent: ${agent}`);
321
+ }
322
+ if (!Object.hasOwn(map, agent)) {
323
+ throw new Error(
324
+ `알 수 없는 에이전트 타입: ${agent}; available: ${Object.keys(map).join(", ")}`,
325
+ );
326
+ }
327
+ const provider = map[agent];
328
+ if (!["codex", "antigravity", "claude"].includes(provider)) {
329
+ throw new Error(`invalid provider for ${agent}: ${provider}`);
330
+ }
331
+ const policy = resolveDirectCodexRoutePolicy(agent);
332
+ const normalizedOverride = normalizeCodexProfileName(profileOverride);
333
+ let resolvedOverride = policy.profile;
334
+ if (normalizedOverride !== "auto") {
335
+ try {
336
+ resolvedOverride = resolveCodexAgentProfile(agent, {
337
+ profileOverride: normalizedOverride,
338
+ nested,
339
+ });
340
+ } catch {
341
+ throw new Error(
342
+ `TFX_CODEX_PROFILE 값이 유효하지 않습니다: ${profileOverride}`,
343
+ );
344
+ }
345
+ }
346
+ return [
347
+ provider,
348
+ policy.profile,
349
+ policy.timeoutSec,
350
+ policy.mcpHint,
351
+ policy.subcommand,
352
+ normalizedOverride,
353
+ resolvedOverride,
354
+ geminiProfileResolver(agent),
355
+ EXPECTED_DURATION_BY_AGENT[agent] ?? 30,
356
+ NATIVE_CODEX_AGENTS.has(agent),
357
+ ];
324
358
  }
325
359
 
326
360
  function parseCliArgs(argv) {
327
361
  const args = [...argv];
328
362
  let format = "json";
329
363
  let agent = DEFAULT_CODEX_AGENT;
364
+ let mapFile = "";
365
+ let profileOverride = "auto";
366
+ let geminiProfilesModule = "";
367
+ let nested = false;
330
368
  while (args.length > 0) {
331
369
  const flag = args.shift();
332
370
  if (flag === "--format") format = args.shift() || "";
333
371
  else if (flag === "--agent") agent = args.shift() || "";
372
+ else if (flag === "--map-file") mapFile = args.shift() || "";
373
+ else if (flag === "--profile-override")
374
+ profileOverride = args.shift() || "";
375
+ else if (flag === "--gemini-profiles-module")
376
+ geminiProfilesModule = args.shift() || "";
377
+ else if (flag === "--nested") nested = true;
334
378
  else throw new Error(`unknown argument: ${flag}`);
335
379
  }
336
- if (!new Set(["json", "tsv"]).has(format)) {
380
+ if (!new Set(["json", "tsv", "route"]).has(format)) {
337
381
  throw new Error(`unsupported format: ${format}`);
338
382
  }
339
- return { format, agent };
383
+ return {
384
+ format,
385
+ agent,
386
+ mapFile,
387
+ profileOverride,
388
+ geminiProfilesModule,
389
+ nested,
390
+ };
340
391
  }
341
392
 
342
- function runCli() {
343
- const { format, agent } = parseCliArgs(process.argv.slice(2));
393
+ async function runCli() {
394
+ const {
395
+ format,
396
+ agent,
397
+ mapFile,
398
+ profileOverride,
399
+ geminiProfilesModule,
400
+ nested,
401
+ } = parseCliArgs(process.argv.slice(2));
402
+ if (format === "route") {
403
+ if (!mapFile) throw new Error("--map-file is required for route format");
404
+ const map = JSON.parse(readFileSync(mapFile, "utf8"));
405
+ let resolver = resolveGeminiProfileForPurpose;
406
+ if (geminiProfilesModule) {
407
+ try {
408
+ const module = await import(pathToFileURL(geminiProfilesModule).href);
409
+ resolver = (agent) => {
410
+ try {
411
+ return module.resolveGeminiProfileForPurpose(agent) || "flash38";
412
+ } catch {
413
+ return "flash38";
414
+ }
415
+ };
416
+ } catch {
417
+ resolver = () => "flash38";
418
+ }
419
+ }
420
+ process.stdout.write(
421
+ resolveAgentRoute(agent, map, profileOverride, nested, resolver).join(
422
+ "\x1e",
423
+ ),
424
+ );
425
+ return;
426
+ }
344
427
  const resolved = resolveCodexAgentPolicy(agent);
345
428
  if (format === "tsv") {
346
429
  process.stdout.write(
347
430
  [
348
431
  resolved.profile,
349
432
  resolved.timeoutSec,
350
- resolved.runMode,
351
- resolved.opusOversight,
352
433
  resolved.mcpHint,
353
434
  resolved.subcommand,
354
435
  ].join("\t"),
@@ -360,7 +441,7 @@ function runCli() {
360
441
 
361
442
  if (import.meta.url === new URL(process.argv[1], "file:").href) {
362
443
  try {
363
- runCli();
444
+ await runCli();
364
445
  } catch (error) {
365
446
  process.stderr.write(`[agent-route-policy] ${error.message}\n`);
366
447
  process.exitCode = 1;
@@ -3,8 +3,6 @@
3
3
  // 책임: Claude 는 외부 CLI 가 아닌 Claude Code Agent 도구로 dispatch 된다. plan() 은
4
4
  // command=null 을 반환하고 호출자가 Agent() 로 라우팅하도록 신호한다.
5
5
  //
6
- // 출처: scripts/tfx-route.sh route_agent() L1062-L1063 (explore|claude),
7
- // L1196-L1203 (TFX_CLI_MODE=gemini fallback), TFX_VERIFIER_OVERRIDE=claude.
8
6
  // timeoutSec은 lane 예상 소요 시간(advisory)이다. hard ceiling 집행은 Bash route가 소유한다.
9
7
 
10
8
  export const id = "claude";
@@ -14,32 +12,22 @@ export const command = null;
14
12
  const AGENT_PROFILES = {
15
13
  explore: {
16
14
  timeoutSec: 600,
17
- runMode: "fg",
18
- opusOversight: false,
19
15
  model: "haiku",
20
16
  },
21
17
  claude: {
22
18
  timeoutSec: 600,
23
- runMode: "fg",
24
- opusOversight: false,
25
19
  model: "haiku",
26
20
  },
27
21
  "test-engineer": {
28
22
  timeoutSec: 1200,
29
- runMode: "bg",
30
- opusOversight: false,
31
23
  model: "sonnet",
32
24
  },
33
25
  "qa-tester": {
34
26
  timeoutSec: 1200,
35
- runMode: "bg",
36
- opusOversight: false,
37
27
  model: "sonnet",
38
28
  },
39
29
  verifier: {
40
30
  timeoutSec: 1200,
41
- runMode: "fg",
42
- opusOversight: false,
43
31
  model: "sonnet",
44
32
  },
45
33
  };
@@ -64,8 +52,6 @@ export function plan({
64
52
  profile: "claude-native",
65
53
  effort: "n/a",
66
54
  timeoutMs: effectiveTimeoutSec * 1000,
67
- runMode: cfg.runMode,
68
- opusOversight: cfg.opusOversight,
69
55
  model: cfg.model,
70
56
  mcpProfile: mcpProfile === "auto" ? null : mcpProfile,
71
57
  promptLength: prompt.length,