@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,204 +0,0 @@
1
- // hub/workers/lib/jsonrpc-stdio.mjs
2
- // Minimal line-delimited JSON-RPC 2.0 client over stdio.
3
- // Replaces vscode-jsonrpc for the codex app-server transport.
4
- //
5
- // Wire format (Issue #95 P1 #1): OpenAI App Server JSONL variant omits the
6
- // top-level `"jsonrpc": "2.0"` header on outbound frames. Inbound decode is
7
- // lenient — frames with or without the header are accepted for forward compat.
8
- //
9
- // Contract:
10
- // new JsonRpcStdioClient({ stdin, stdout, onError, maxLineSize })
11
- // request(method, params, timeoutMs=60000) -> Promise<result>
12
- // notify(method, params) -> void
13
- // onNotification(method, cb) -> unsubscribe() ('*' = catch-all)
14
- // close(reason) -> void (idempotent; optional reason marks closing state)
15
- // isOpen() -> boolean
16
- //
17
- // Lifecycle (Issue #95 P1 #3):
18
- // State machine: running | closing | closed
19
- // - `running` is the default. Parse/EOF/max-line errors reject in-flight
20
- // requests and transition to `closed`.
21
- // - `closing` is entered via `close("closing")` before a graceful shutdown;
22
- // EOF in `closing` is a normal termination and does NOT fail-fast.
23
- //
24
- // AC18: any single line whose raw-byte length would exceed maxLineSize is
25
- // rejected at the stream layer (before readline emits it) to defend against
26
- // OOM/DoS from an unresponsive or malicious peer.
27
-
28
- import { createInterface } from "node:readline";
29
-
30
- import { JsonRpcDispatchBase } from "./jsonrpc-core.mjs";
31
-
32
- export { JsonRpcProtocolError } from "./jsonrpc-core.mjs";
33
-
34
- const DEFAULT_MAX_LINE_SIZE = 1024 * 1024; // 1 MiB
35
- const CLOSED_MESSAGE = "JsonRpcStdioClient closed";
36
-
37
- /**
38
- * Thrown (via onError) when an inbound line would exceed maxLineSize.
39
- */
40
- export class MaxLineSizeExceededError extends Error {
41
- /**
42
- * @param {number} size Bytes observed so far in the offending line.
43
- * @param {number} max Configured max.
44
- */
45
- constructor(size, max) {
46
- super(`JSON-RPC line exceeded max size: ${size} > ${max}`);
47
- this.name = "MaxLineSizeExceededError";
48
- this.size = size;
49
- this.max = max;
50
- }
51
- }
52
-
53
- /**
54
- * Thrown when the underlying stream closes unexpectedly (EOF outside `closing`).
55
- */
56
- export class JsonRpcTransportError extends Error {
57
- /** @param {string} message @param {{ cause?: unknown }} [options] */
58
- constructor(message, options = {}) {
59
- super(message, { cause: options.cause });
60
- this.name = "JsonRpcTransportError";
61
- }
62
- }
63
-
64
- /**
65
- * Line-delimited JSON-RPC 2.0 client over a pair of Node streams.
66
- */
67
- export class JsonRpcStdioClient extends JsonRpcDispatchBase {
68
- /**
69
- * @param {object} options
70
- * @param {NodeJS.ReadableStream} options.stdin Server -> client bytes.
71
- * @param {NodeJS.WritableStream} options.stdout Client -> server bytes.
72
- * @param {(err: Error) => void} [options.onError] Protocol error sink.
73
- * @param {number} [options.maxLineSize] Max bytes per inbound line.
74
- */
75
- constructor({ stdin, stdout, onError, maxLineSize = DEFAULT_MAX_LINE_SIZE }) {
76
- if (!stdin || typeof stdin.on !== "function") {
77
- throw new TypeError("JsonRpcStdioClient requires a readable stdin");
78
- }
79
- if (!stdout || typeof stdout.write !== "function") {
80
- throw new TypeError("JsonRpcStdioClient requires a writable stdout");
81
- }
82
-
83
- super({ onError, closedMessage: CLOSED_MESSAGE });
84
-
85
- this._stdin = stdin;
86
- this._stdout = stdout;
87
- this._maxLineSize =
88
- Number.isFinite(maxLineSize) && maxLineSize > 0
89
- ? maxLineSize
90
- : DEFAULT_MAX_LINE_SIZE;
91
-
92
- /** @type {'running'|'closing'|'closed'} */
93
- this._state = "running";
94
-
95
- // AC18: track bytes since last newline at the raw stream layer so an
96
- // oversized line is rejected *before* readline concatenates it internally.
97
- this._pendingLineSize = 0;
98
- this._oversized = false;
99
-
100
- this._onStdinData = (chunk) => this._trackRawBytes(chunk);
101
- this._stdin.on("data", this._onStdinData);
102
-
103
- // Without these handlers, an EPIPE/ERR_STREAM_DESTROYED on either pipe
104
- // would bubble up as an unhandled 'error' and take down the hub process.
105
- this._onStdinError = (err) => this._handleStreamError("stdin", err);
106
- this._onStdoutError = (err) => this._handleStreamError("stdout", err);
107
- if (typeof this._stdin.on === "function") {
108
- this._stdin.on("error", this._onStdinError);
109
- }
110
- if (typeof this._stdout.on === "function") {
111
- this._stdout.on("error", this._onStdoutError);
112
- }
113
-
114
- this._rl = createInterface({ input: this._stdin, crlfDelay: Infinity });
115
- this._rl.on("line", (line) => this._handleInboundMessage(line));
116
- // readline re-emits the input stream 'error' on itself; the raw stdin
117
- // handler above already converts it into a JsonRpcTransportError, so
118
- // suppress the re-emit to avoid an unhandled 'error' on the Interface.
119
- this._rl.on("error", () => {});
120
- this._rl.on("close", () => {
121
- // P1 #3 fail-fast: EOF during `running` is a transport error. Pending
122
- // requests are rejected with JsonRpcTransportError. EOF during `closing`
123
- // is a normal shutdown — pending requests are rejected with the generic
124
- // CLOSED_MESSAGE via close().
125
- if (this._state === "running") {
126
- const err = new JsonRpcTransportError(
127
- "JSON-RPC stream closed unexpectedly (EOF during running state)",
128
- );
129
- this._emitError(err);
130
- this._closeWith("closed", err);
131
- } else if (this._state !== "closed") {
132
- this._closeWith("closed");
133
- }
134
- });
135
- }
136
-
137
- // --- internals ---------------------------------------------------------
138
-
139
- _encodeAndSend(frame) {
140
- if (this._state === "closed") return;
141
- const line = `${JSON.stringify(frame)}\n`;
142
- try {
143
- this._stdout.write(line, (err) => {
144
- if (err) this._handleStreamError("stdout-write", err);
145
- });
146
- } catch (err) {
147
- this._handleStreamError("stdout-write", err);
148
- }
149
- }
150
-
151
- _teardownTransport() {
152
- try {
153
- this._stdin.off?.("data", this._onStdinData);
154
- } catch {
155
- /* ignore */
156
- }
157
- try {
158
- this._rl.close();
159
- } catch {
160
- /* ignore */
161
- }
162
- }
163
-
164
- /**
165
- * Convert a raw stream error into a JsonRpcTransportError, emit it to the
166
- * error sink, and close the client so pending requests are rejected.
167
- * Idempotent: repeated errors after close are swallowed.
168
- * @param {string} which identifier for the originating pipe/operation
169
- * @param {unknown} err raw error from the stream
170
- */
171
- _handleStreamError(which, err) {
172
- if (this._state === "closed") return;
173
- const base = err instanceof Error ? err : new Error(String(err));
174
- const wrapped = new JsonRpcTransportError(
175
- `JSON-RPC stream error on ${which}: ${base.message}`,
176
- );
177
- wrapped.cause = base;
178
- this._emitError(wrapped);
179
- this._closeWith("closed", wrapped);
180
- }
181
-
182
- _trackRawBytes(chunk) {
183
- if (this._oversized || this._state === "closed") return;
184
- const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
185
- for (let i = 0; i < buf.length; i++) {
186
- if (buf[i] === 0x0a /* \n */) {
187
- this._pendingLineSize = 0;
188
- continue;
189
- }
190
- this._pendingLineSize += 1;
191
- if (this._pendingLineSize > this._maxLineSize) {
192
- this._oversized = true;
193
- const err = new MaxLineSizeExceededError(
194
- this._pendingLineSize,
195
- this._maxLineSize,
196
- );
197
- this._emitError(err);
198
- // P1 #3 fail-fast: oversized line → reject pending with the actual error
199
- this._closeWith("closed", err);
200
- return;
201
- }
202
- }
203
- }
204
- }
@@ -1,223 +0,0 @@
1
- import { existsSync, readFileSync, writeFileSync } from "node:fs";
2
- import { homedir } from "node:os";
3
- import { dirname, join, resolve } from "node:path";
4
- import { fileURLToPath } from "node:url";
5
-
6
- export const TFX_START = "<!-- TFX:START -->";
7
- export const TFX_END = "<!-- TFX:END -->";
8
- export const OMC_END = "<!-- OMC:END -->";
9
- const TFX_VERSION_RE = /<!-- TFX:VERSION:([\d.]+) -->/u;
10
-
11
- const LEGACY_PATTERNS = [
12
- /<user_cli_routing>/u,
13
- /Codex Pro 무료 기간/u,
14
- /codex exec --dangerously-bypass.*skip-git-repo-check/u,
15
- /OMC 에이전트 → CLI 매핑/u,
16
- /Spark 가드레일/u,
17
- ];
18
-
19
- const DEFAULT_TFX_TEMPLATE = [
20
- "### triflux CLI routing (managed)",
21
- "- 이 블록은 triflux setup에서 자동으로 관리됩니다.",
22
- "- 직접 수정이 필요하면 블록 바깥에 사용자 섹션을 추가하세요.",
23
- ].join("\n");
24
-
25
- const SCRIPT_DIR = dirname(fileURLToPath(import.meta.url));
26
- const DEFAULT_TEMPLATE_PATH = join(
27
- SCRIPT_DIR,
28
- "..",
29
- "templates",
30
- "claudemd-tfx-section.md",
31
- );
32
-
33
- function resolveVersion(version) {
34
- if (version) return version;
35
- try {
36
- const pkgPath = join(SCRIPT_DIR, "..", "..", "package.json");
37
- const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
38
- return pkg.version || "0.0.0";
39
- } catch {
40
- return "0.0.0";
41
- }
42
- }
43
-
44
- function resolveTemplate(template, templatePath = DEFAULT_TEMPLATE_PATH) {
45
- if (typeof template === "string" && template.trim()) {
46
- return template.trim();
47
- }
48
- if (existsSync(templatePath)) {
49
- const raw = readFileSync(templatePath, "utf8").trim();
50
- if (raw) return raw;
51
- }
52
- return DEFAULT_TFX_TEMPLATE;
53
- }
54
-
55
- function findManagedSection(rawText) {
56
- const raw = String(rawText || "");
57
- const startIdx = raw.indexOf(TFX_START);
58
- const endMarkerIdx = raw.indexOf(TFX_END);
59
- if (startIdx === -1 || endMarkerIdx === -1 || endMarkerIdx <= startIdx) {
60
- return {
61
- found: false,
62
- content: null,
63
- version: null,
64
- startIdx: -1,
65
- endIdx: -1,
66
- };
67
- }
68
-
69
- const endIdx = endMarkerIdx + TFX_END.length;
70
- const content = raw.slice(startIdx, endIdx);
71
- const versionMatch = content.match(TFX_VERSION_RE);
72
-
73
- return {
74
- found: true,
75
- content,
76
- version: versionMatch ? versionMatch[1] : null,
77
- startIdx,
78
- endIdx,
79
- };
80
- }
81
-
82
- function detectLegacyRange(rawText) {
83
- const raw = String(rawText || "");
84
- const matches = LEGACY_PATTERNS.some((pattern) => pattern.test(raw));
85
- if (!matches) {
86
- return { found: false, startIdx: -1, endIdx: -1 };
87
- }
88
-
89
- const startTag = raw.indexOf("<user_cli_routing>");
90
- const endTag = raw.indexOf("</user_cli_routing>");
91
- if (startTag === -1 || endTag === -1 || endTag <= startTag) {
92
- return { found: true, startIdx: -1, endIdx: -1 };
93
- }
94
-
95
- let removeStart = startTag;
96
- const userOverridesComment = "<!-- USER OVERRIDES";
97
- const commentIdx = raw.lastIndexOf(userOverridesComment, startTag);
98
- if (commentIdx !== -1 && startTag - commentIdx < 200) {
99
- removeStart = commentIdx;
100
- }
101
-
102
- return {
103
- found: true,
104
- startIdx: removeStart,
105
- endIdx: endTag + "</user_cli_routing>".length,
106
- };
107
- }
108
-
109
- function normalizeSpacing(text) {
110
- return String(text || "").replace(/\n{3,}/gu, "\n\n");
111
- }
112
-
113
- export function findAllClaudeMdPaths(options = {}) {
114
- const cwd = options.cwd ? resolve(options.cwd) : process.cwd();
115
- const homeDir = options.homeDir ? resolve(options.homeDir) : homedir();
116
- const includeGlobal = options.includeGlobal !== false;
117
- const includeProject = options.includeProject !== false;
118
-
119
- const candidates = [];
120
- if (includeGlobal) candidates.push(join(homeDir, ".claude", "CLAUDE.md"));
121
- if (includeProject) candidates.push(join(cwd, "CLAUDE.md"));
122
-
123
- const seen = new Set();
124
- const paths = [];
125
- for (const candidate of candidates) {
126
- const normalized = resolve(candidate);
127
- if (seen.has(normalized)) continue;
128
- seen.add(normalized);
129
- if (existsSync(normalized)) {
130
- paths.push(normalized);
131
- }
132
- }
133
- return paths;
134
- }
135
-
136
- export function writeSection(filePath, options = {}) {
137
- const absolutePath = resolve(filePath);
138
- const version = resolveVersion(options.version);
139
- const template = resolveTemplate(options.template, options.templatePath);
140
- const block = [
141
- TFX_START,
142
- `<!-- TFX:VERSION:${version} -->`,
143
- template,
144
- TFX_END,
145
- ].join("\n");
146
-
147
- if (!existsSync(absolutePath)) {
148
- writeFileSync(absolutePath, `${block}\n`, "utf8");
149
- return { action: "created", version, path: absolutePath };
150
- }
151
-
152
- const raw = readFileSync(absolutePath, "utf8");
153
- const existing = findManagedSection(raw);
154
-
155
- if (existing.found) {
156
- const nextContent = `${raw.slice(0, existing.startIdx)}${block}${raw.slice(existing.endIdx)}`;
157
- writeFileSync(absolutePath, nextContent, "utf8");
158
- return {
159
- action: "updated",
160
- oldVersion: existing.version,
161
- version,
162
- path: absolutePath,
163
- };
164
- }
165
-
166
- const omcEndIdx = raw.indexOf(OMC_END);
167
- if (omcEndIdx !== -1) {
168
- const insertAt = omcEndIdx + OMC_END.length;
169
- const before = raw.slice(0, insertAt);
170
- const after = raw.slice(insertAt);
171
- const nextContent = `${before}\n${block}${after}`;
172
- writeFileSync(absolutePath, nextContent, "utf8");
173
- return { action: "inserted_after_omc", version, path: absolutePath };
174
- }
175
-
176
- const separator = raw.endsWith("\n") ? "" : "\n";
177
- writeFileSync(absolutePath, `${raw}${separator}${block}\n`, "utf8");
178
- return { action: "appended", version, path: absolutePath };
179
- }
180
-
181
- export function migrateClaudeMd(filePath, options = {}) {
182
- const absolutePath = resolve(filePath);
183
- if (!existsSync(absolutePath)) {
184
- return { action: "no_file", removed: [], path: absolutePath };
185
- }
186
-
187
- const raw = readFileSync(absolutePath, "utf8");
188
- const existing = findManagedSection(raw);
189
- const legacy = detectLegacyRange(raw);
190
-
191
- if (existing.found && !legacy.found) {
192
- return {
193
- action: "already_managed",
194
- removed: [],
195
- version: existing.version,
196
- path: absolutePath,
197
- };
198
- }
199
-
200
- const removed = [];
201
- let nextContent = raw;
202
-
203
- if (legacy.found && legacy.startIdx !== -1 && legacy.endIdx !== -1) {
204
- nextContent = `${nextContent.slice(0, legacy.startIdx)}${nextContent.slice(legacy.endIdx)}`;
205
- removed.push("<user_cli_routing> block");
206
- }
207
-
208
- const existingAfterLegacy = findManagedSection(nextContent);
209
- if (existingAfterLegacy.found) {
210
- nextContent = `${nextContent.slice(0, existingAfterLegacy.startIdx)}${nextContent.slice(existingAfterLegacy.endIdx)}`;
211
- removed.push("old TFX block");
212
- }
213
-
214
- writeFileSync(absolutePath, normalizeSpacing(nextContent), "utf8");
215
- const writeResult = writeSection(absolutePath, options);
216
-
217
- return {
218
- action: "migrated",
219
- removed,
220
- version: writeResult.version,
221
- path: absolutePath,
222
- };
223
- }
@@ -1,67 +0,0 @@
1
- /**
2
- * 요청별 로그 컨텍스트 전파 (AsyncLocalStorage 기반).
3
- *
4
- * Hub HTTP 서버의 요청마다 correlationId를 자동 할당하여,
5
- * 하나의 요청에서 발생한 모든 로그를 추적할 수 있다.
6
- *
7
- * 사용법:
8
- * import { getLogger, getCorrelationId, withRequestContext } from './lib/context.mjs';
9
- *
10
- * // 미들웨어에서 컨텍스트 생성
11
- * withRequestContext({ method: 'POST', path: '/bridge/result' }, () => {
12
- * const log = getLogger();
13
- * log.info({ agentId }, 'bridge.result_received');
14
- * });
15
- *
16
- * // 내부 함수에서 자동 상관 ID
17
- * function processResult() {
18
- * const log = getLogger();
19
- * log.info('result.processed'); // correlationId 자동 포함
20
- * }
21
- */
22
- import { AsyncLocalStorage } from "node:async_hooks";
23
- import { randomUUID } from "node:crypto";
24
-
25
- import { logger } from "./logger.mjs";
26
-
27
- /** @type {AsyncLocalStorage<{logger: import('pino').Logger, correlationId: string}>} */
28
- export const asyncLocalStorage = new AsyncLocalStorage();
29
-
30
- /**
31
- * 현재 요청 컨텍스트의 로거를 반환한다.
32
- * 요청 컨텍스트 밖에서 호출하면 기본 로거를 반환한다.
33
- *
34
- * @returns {import('pino').Logger}
35
- */
36
- export function getLogger() {
37
- return asyncLocalStorage.getStore()?.logger || logger;
38
- }
39
-
40
- /**
41
- * 현재 요청의 상관 ID를 반환한다.
42
- *
43
- * @returns {string|undefined}
44
- */
45
- export function getCorrelationId() {
46
- return asyncLocalStorage.getStore()?.correlationId;
47
- }
48
-
49
- /**
50
- * 요청 컨텍스트를 생성하고 콜백을 실행한다.
51
- *
52
- * @param {object} context — 컨텍스트 필드 (method, path 등)
53
- * @param {string} [context.correlationId] — 외부에서 전달된 상관 ID (없으면 자동 생성)
54
- * @param {function} callback — 컨텍스트 내에서 실행할 함수
55
- * @returns {*}
56
- */
57
- export function withRequestContext(context, callback) {
58
- const correlationId = context.correlationId || randomUUID();
59
- const { correlationId: _, ...rest } = context;
60
-
61
- const store = {
62
- correlationId,
63
- logger: logger.child({ correlationId, ...rest }),
64
- };
65
-
66
- return asyncLocalStorage.run(store, callback);
67
- }
@@ -1,107 +0,0 @@
1
- #!/usr/bin/env node
2
- // scripts/lib/dynamic-route-cli.mjs
3
- //
4
- // Phase 1 Wire-up 3 helper — tfx-route.sh 등 sh 경로에서 dynamic routing
5
- // decision 을 받아 단일 CLI 문자열 (또는 JSON) 으로 출력한다.
6
- //
7
- // 사용:
8
- // node scripts/lib/dynamic-route-cli.mjs --agent-hint codex
9
- // → "codex"
10
- // node scripts/lib/dynamic-route-cli.mjs --agent-hint codex --json
11
- // → {"scenario":"fallback","mode":"codex-default","shards":[{"cli":"codex",...}]}
12
- // node scripts/lib/dynamic-route-cli.mjs --agent-hint codex --field scenario
13
- // → "fallback"
14
- //
15
- // 정책:
16
- // - opt-in env TRIFLUX_DYNAMIC_ROUTING=1|true 미설정 시 즉시 silent exit 0
17
- // (override 없음 = stdout empty, sh caller 가 graceful no-op).
18
- // - error 발생 시 stderr 에 한 줄 + exit 0 (sh caller 가 fallback path 진행).
19
-
20
- import { createDynamicRouter } from "@triflux/core/hub/dynamic-routing-engine.mjs";
21
-
22
- function parseArgs(argv) {
23
- const args = {
24
- taskId: `tfx-route-${process.pid}-${Date.now()}`,
25
- agentHint: "codex",
26
- teamSize: 1,
27
- json: false,
28
- field: null,
29
- };
30
- for (let i = 2; i < argv.length; i++) {
31
- const v = argv[i];
32
- switch (v) {
33
- case "--task-id":
34
- args.taskId = argv[++i] || args.taskId;
35
- break;
36
- case "--agent-hint":
37
- args.agentHint = argv[++i] || args.agentHint;
38
- break;
39
- case "--team-size":
40
- args.teamSize = Number(argv[++i]) || args.teamSize;
41
- break;
42
- case "--json":
43
- args.json = true;
44
- break;
45
- case "--field":
46
- args.field = argv[++i] || null;
47
- break;
48
- case "--help":
49
- case "-h":
50
- process.stdout.write(
51
- "Usage: dynamic-route-cli.mjs [--task-id ID] [--agent-hint CLI] [--team-size N] [--json|--field FIELD]\n",
52
- );
53
- process.exit(0);
54
- break;
55
- }
56
- }
57
- return args;
58
- }
59
-
60
- async function main() {
61
- const args = parseArgs(process.argv);
62
-
63
- // opt-in env 미설정 시 silent no-op (sh caller graceful path)
64
- const flag = process.env.TRIFLUX_DYNAMIC_ROUTING;
65
- if (flag !== "1" && flag !== "true") return;
66
-
67
- let decision;
68
- try {
69
- const router = createDynamicRouter();
70
- decision = await router.routeRequest({
71
- task_id: args.taskId,
72
- team_size: args.teamSize,
73
- agent_hint: args.agentHint,
74
- });
75
- } catch (err) {
76
- process.stderr.write(
77
- `[dynamic-route-cli] error: ${err?.message || String(err)}\n`,
78
- );
79
- return;
80
- }
81
-
82
- if (args.json) {
83
- process.stdout.write(JSON.stringify(decision));
84
- return;
85
- }
86
-
87
- if (args.field) {
88
- const value = decision?.[args.field];
89
- if (value !== undefined && value !== null) {
90
- process.stdout.write(String(value));
91
- }
92
- return;
93
- }
94
-
95
- // 기본 — shards[0].cli 만 stdout 으로 (sh caller 가 즉시 비교)
96
- const cli = decision?.shards?.[0]?.cli;
97
- if (cli) {
98
- process.stdout.write(String(cli));
99
- }
100
- }
101
-
102
- main().catch((err) => {
103
- process.stderr.write(
104
- `[dynamic-route-cli] fatal: ${err?.message || String(err)}\n`,
105
- );
106
- process.exit(0); // sh caller 가 정상 fallback 하도록 always 0
107
- });