@triflux/remote 10.50.2 → 10.51.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 (97) hide show
  1. package/cto/brief.mjs +0 -12
  2. package/cto/collect.mjs +0 -73
  3. package/cto/current.schema.json +0 -4
  4. package/cto/status.mjs +2 -50
  5. package/hub/index.mjs +0 -8
  6. package/hub/team/agent-map.json +0 -1
  7. package/hub/team/ansi.mjs +1 -35
  8. package/hub/team/backend.mjs +9 -145
  9. package/hub/team/cli/commands/attach.mjs +1 -17
  10. package/hub/team/cli/commands/control.mjs +12 -38
  11. package/hub/team/cli/commands/debug.mjs +1 -37
  12. package/hub/team/cli/commands/focus.mjs +5 -31
  13. package/hub/team/cli/commands/interrupt.mjs +6 -25
  14. package/hub/team/cli/commands/kill.mjs +1 -30
  15. package/hub/team/cli/commands/list.mjs +0 -16
  16. package/hub/team/cli/commands/send.mjs +6 -26
  17. package/hub/team/cli/commands/start/index.mjs +41 -133
  18. package/hub/team/cli/commands/start/parse-args.mjs +15 -40
  19. package/hub/team/cli/commands/start/start-headless.mjs +138 -105
  20. package/hub/team/cli/commands/start/start-mux.mjs +0 -3
  21. package/hub/team/cli/commands/status.mjs +2 -57
  22. package/hub/team/cli/commands/stop.mjs +14 -19
  23. package/hub/team/cli/help.mjs +4 -5
  24. package/hub/team/cli/manifest.mjs +5 -1
  25. package/hub/team/cli/render.mjs +0 -12
  26. package/hub/team/cli/services/hub-client.mjs +2 -88
  27. package/hub/team/cli/services/runtime-mode.mjs +27 -98
  28. package/hub/team/handoff.mjs +3 -34
  29. package/hub/team/headless.mjs +13 -754
  30. package/hub/team/orchestrator.mjs +4 -30
  31. package/hub/team/pane.mjs +6 -28
  32. package/hub/team/psmux.mjs +0 -1
  33. package/hub/team/remote-session.mjs +1 -2
  34. package/hub/team/session.mjs +7 -91
  35. package/hub/team/swarm-intent.mjs +0 -198
  36. package/hub/team/tui-core.mjs +1 -1
  37. package/hub/team/tui-viewer.mjs +2 -2
  38. package/hub/team/tui.mjs +8 -269
  39. package/hub/team/wt-manager.mjs +2 -2
  40. package/hub/workers/factory.mjs +4 -37
  41. package/hub/workers/lib/jsonrpc-ws-uds.mjs +0 -2
  42. package/package.json +1 -1
  43. package/scripts/lib/agent-route-policy.mjs +162 -14
  44. package/scripts/lib/claudemd-scanner.mjs +2 -199
  45. package/scripts/lib/cli-claude.mjs +0 -2
  46. package/scripts/lib/context.mjs +3 -19
  47. package/scripts/lib/env-probe.mjs +5 -64
  48. package/scripts/lib/gemini-profiles.mjs +2 -69
  49. package/scripts/lib/machine-profile.mjs +3 -17
  50. package/scripts/lib/mcp-filter.mjs +109 -53
  51. package/scripts/lib/mcp-health.mjs +2 -2
  52. package/scripts/lib/mcp-server-catalog.mjs +2 -2
  53. package/scripts/lib/psmux-info.mjs +11 -59
  54. package/scripts/lib/skill-template.mjs +0 -245
  55. package/hub/team/build-worker-prompt.mjs +0 -92
  56. package/hub/team/check-mcp-hub.mjs +0 -49
  57. package/hub/team/claude-native-bridge.mjs +0 -987
  58. package/hub/team/cli/commands/start/start-in-process.mjs +0 -50
  59. package/hub/team/cli/commands/start/start-wt.mjs +0 -86
  60. package/hub/team/cli/services/native-control.mjs +0 -145
  61. package/hub/team/codex-review.mjs +0 -520
  62. package/hub/team/conductor-mesh-bridge.mjs +0 -122
  63. package/hub/team/conductor.mjs +0 -1529
  64. package/hub/team/consensus-meta.mjs +0 -176
  65. package/hub/team/cto-pull.mjs +0 -31
  66. package/hub/team/daemon-pty-tmux-bridge.mjs +0 -146
  67. package/hub/team/dashboard.mjs +0 -322
  68. package/hub/team/event-log.mjs +0 -84
  69. package/hub/team/execution-mode.mjs +0 -324
  70. package/hub/team/extract-completion-payload.mjs +0 -110
  71. package/hub/team/headless-bridge-session.mjs +0 -17
  72. package/hub/team/health-probe.mjs +0 -435
  73. package/hub/team/interactive-native-launcher.mjs +0 -119
  74. package/hub/team/interactive-native-worker.mjs +0 -80
  75. package/hub/team/interactive-tui-transport.mjs +0 -479
  76. package/hub/team/launcher-template.mjs +0 -144
  77. package/hub/team/mcp-selector.mjs +0 -145
  78. package/hub/team/native-supervisor.mjs +0 -460
  79. package/hub/team/native.mjs +0 -727
  80. package/hub/team/recovery-store.mjs +0 -149
  81. package/hub/team/remote-probe.mjs +0 -292
  82. package/hub/team/remote-watcher.mjs +0 -506
  83. package/hub/team/runtime-strategy.mjs +0 -200
  84. package/hub/team/schemas/worker-completion.json +0 -39
  85. package/hub/team/sentinel-capture.mjs +0 -127
  86. package/hub/team/swarm-cli.mjs +0 -390
  87. package/hub/team/swarm-hypervisor.mjs +0 -2206
  88. package/hub/team/swarm-locks.mjs +0 -335
  89. package/hub/team/swarm-planner.mjs +0 -371
  90. package/hub/team/swarm-preflight.mjs +0 -366
  91. package/hub/team/swarm-reconciler.mjs +0 -242
  92. package/hub/team/tui-remote-adapter.mjs +0 -377
  93. package/hub/team/worker-completion-validator.mjs +0 -79
  94. package/hub/team/worktree-lifecycle.mjs +0 -639
  95. package/hub/team/wt-templates.json +0 -43
  96. package/hub/workers/antigravity-route-worker.mjs +0 -179
  97. package/scripts/lib/dynamic-route-cli.mjs +0 -107
@@ -1,377 +0,0 @@
1
- // hub/team/tui-remote-adapter.mjs — 원격 세션을 TUI 워커 형식으로 변환하는 어댑터
2
- //
3
- // conductor.mjs의 stateChange 이벤트(primary) + remote-watcher.mjs(supplemental)를
4
- // tui.mjs updateWorker() 호환 형식으로 변환한다.
5
- // 완료/실패 시 notify.mjs 자동 호출.
6
-
7
- import { EventEmitter } from "node:events";
8
-
9
- import { readHosts } from "@triflux/core/hub/lib/hosts-compat.mjs";
10
- import { STATES } from "./conductor.mjs";
11
-
12
- // ── 상수 ─────────────────────────────────────────────────────────────────────
13
-
14
- const CONDUCTOR_STATE_TO_TUI_STATUS = Object.freeze({
15
- [STATES.INIT]: "pending",
16
- [STATES.STARTING]: "pending",
17
- [STATES.HEALTHY]: "running",
18
- [STATES.STALLED]: "running",
19
- [STATES.INPUT_WAIT]: "running",
20
- [STATES.FAILED]: "running",
21
- [STATES.RESTARTING]: "running",
22
- [STATES.COMPLETED]: "completed",
23
- [STATES.DEAD]: "failed",
24
- });
25
-
26
- const SESSION_PREFIX = "tfx-spawn-";
27
-
28
- // ── 유틸 ─────────────────────────────────────────────────────────────────────
29
-
30
- function resolveSshUser(hostsData, host) {
31
- if (!host || !hostsData?.hosts) return null;
32
- return hostsData.hosts[host]?.ssh_user || null;
33
- }
34
-
35
- /**
36
- * tfx-spawn-{host}-{id} 형식에서 host를 추출한다.
37
- * @param {string} sessionName
38
- * @returns {string|null}
39
- */
40
- function resolveHostFromSessionName(sessionName) {
41
- if (!sessionName?.startsWith(SESSION_PREFIX)) return null;
42
- const rest = sessionName.slice(SESSION_PREFIX.length);
43
- const dashIdx = rest.indexOf("-");
44
- if (dashIdx === -1) return rest || null;
45
- return rest.slice(0, dashIdx) || null;
46
- }
47
-
48
- /**
49
- * sessionName에서 role을 파생한다.
50
- * conductor config.id가 있으면 우선 사용, 없으면 sessionName 자체.
51
- */
52
- function resolveRole(configId, sessionName) {
53
- return configId || sessionName || "unknown";
54
- }
55
-
56
- function buildPaneName(sessionName) {
57
- return `remote:${sessionName}`;
58
- }
59
-
60
- function mapConductorStateToStatus(conductorState) {
61
- return CONDUCTOR_STATE_TO_TUI_STATUS[conductorState] || "pending";
62
- }
63
-
64
- /**
65
- * conductor snapshot 엔트리 → TUI 워커 데이터.
66
- * @param {object} entry — conductor getSnapshot() 엔트리
67
- * @param {object} watcherSession — remote-watcher의 세션 레코드 (nullable)
68
- * @param {object} hostsData — hosts.json 데이터
69
- * @returns {object}
70
- */
71
- function buildWorkerData(entry, watcherSession, hostsData) {
72
- const host = entry.host || resolveHostFromSessionName(entry.id);
73
- const snapshot = watcherSession?.lastOutput || "";
74
- const probeLevel =
75
- entry.health?.level || watcherSession?.lastProbeLevel || null;
76
-
77
- return Object.freeze({
78
- cli: entry.agent || "claude",
79
- role: resolveRole(entry.id, entry.id),
80
- status: mapConductorStateToStatus(entry.state),
81
- host: host || "unknown",
82
- remote: true,
83
- sshUser: resolveSshUser(hostsData, host),
84
- sessionName: entry.id,
85
- snapshot,
86
- conductor: Object.freeze({
87
- state: entry.state,
88
- restarts: entry.restarts || 0,
89
- probeLevel,
90
- }),
91
- });
92
- }
93
-
94
- /**
95
- * remote-watcher 전용 엔트리 → TUI 워커 데이터 (conductor 미등록 세션).
96
- */
97
- function buildWatcherOnlyWorkerData(watcherRecord, hostsData) {
98
- const host =
99
- watcherRecord.host || resolveHostFromSessionName(watcherRecord.sessionName);
100
-
101
- return Object.freeze({
102
- cli: "claude",
103
- role: resolveRole(null, watcherRecord.sessionName),
104
- status:
105
- watcherRecord.state === "completed"
106
- ? "completed"
107
- : watcherRecord.state === "failed"
108
- ? "failed"
109
- : "running",
110
- host: host || "unknown",
111
- remote: true,
112
- sshUser: resolveSshUser(hostsData, host),
113
- sessionName: watcherRecord.sessionName,
114
- snapshot: watcherRecord.lastOutput || "",
115
- conductor: null,
116
- });
117
- }
118
-
119
- // ── 팩토리 ───────────────────────────────────────────────────────────────────
120
-
121
- /**
122
- * 원격 세션 어댑터 팩토리.
123
- *
124
- * @param {object} opts
125
- * @param {object} opts.conductor — createConductor() 인스턴스
126
- * @param {object} [opts.watcher] — createRemoteWatcher() 인스턴스 (nullable)
127
- * @param {object} [opts.notifier] — createNotifier() 인스턴스 (nullable)
128
- * @param {string} [opts.repoRoot] — hosts fallback 탐색용 repo root override
129
- * @param {number} [opts.pollMs=10000] — conductor snapshot 폴링 간격
130
- * @param {object} [opts.deps] — 테스트용 의존성 주입
131
- * @returns {{ start, stop, getWorkers, on, off }}
132
- */
133
- export function createRemoteAdapter(opts = {}) {
134
- const {
135
- conductor,
136
- watcher = null,
137
- notifier = null,
138
- pollMs = 10_000,
139
- deps = {},
140
- } = opts;
141
-
142
- if (!conductor) throw new Error("conductor is required");
143
-
144
- const loadHosts = deps.readHosts || readHosts;
145
- const setIntervalFn = deps.setInterval || setInterval;
146
- const clearIntervalFn = deps.clearInterval || clearInterval;
147
- const _nowFn = deps.now || Date.now;
148
-
149
- const emitter = new EventEmitter();
150
- let hostsData = loadHosts(opts.repoRoot);
151
- let workers = new Map();
152
- let pollHandle = null;
153
- let running = false;
154
-
155
- // ── conductor stateChange 핸들러 (primary) ──
156
-
157
- function handleStateChange({ sessionId, from, to, reason }) {
158
- const snapshots = conductor.getSnapshot();
159
- const entry = snapshots.find((s) => s.id === sessionId);
160
- if (!entry?.remote) return;
161
-
162
- const watcherStatus = getWatcherSession(sessionId);
163
- const workerData = buildWorkerData(entry, watcherStatus, hostsData);
164
- const paneName = buildPaneName(sessionId);
165
-
166
- workers = new Map(workers);
167
- workers.set(paneName, workerData);
168
-
169
- emitter.emit("workerUpdate", { ...workerData, paneName });
170
-
171
- if (to === STATES.COMPLETED) {
172
- emitter.emit("workerCompleted", {
173
- name: paneName,
174
- host: workerData.host,
175
- exitCode: 0,
176
- });
177
- notifyIfAvailable({
178
- type: "completed",
179
- sessionId,
180
- host: workerData.host,
181
- summary: `completed (${reason})`,
182
- });
183
- } else if (to === STATES.DEAD) {
184
- emitter.emit("workerFailed", {
185
- name: paneName,
186
- host: workerData.host,
187
- reason,
188
- });
189
- notifyIfAvailable({
190
- type: "failed",
191
- sessionId,
192
- host: workerData.host,
193
- summary: `dead: ${reason}`,
194
- });
195
- } else if (to === STATES.INPUT_WAIT) {
196
- emitter.emit("workerInputWait", {
197
- name: paneName,
198
- host: workerData.host,
199
- pattern: reason,
200
- });
201
- notifyIfAvailable({
202
- type: "inputWait",
203
- sessionId,
204
- host: workerData.host,
205
- summary: `input_wait: ${reason}`,
206
- });
207
- }
208
- }
209
-
210
- // ── remote-watcher 이벤트 핸들러 (supplemental) ──
211
-
212
- function handleWatcherCompleted({ sessionName, exitCode, host }) {
213
- if (isConductorTracked(sessionName)) return;
214
-
215
- const paneName = buildPaneName(sessionName);
216
- notifyIfAvailable({
217
- type: "completed",
218
- sessionId: sessionName,
219
- host: host || resolveHostFromSessionName(sessionName),
220
- summary: `exit ${exitCode ?? 0}`,
221
- });
222
- emitter.emit("workerCompleted", {
223
- name: paneName,
224
- host: host || resolveHostFromSessionName(sessionName),
225
- exitCode: exitCode ?? 0,
226
- });
227
- }
228
-
229
- function handleWatcherFailed({ sessionName, reason, host }) {
230
- if (isConductorTracked(sessionName)) return;
231
-
232
- const paneName = buildPaneName(sessionName);
233
- notifyIfAvailable({
234
- type: "failed",
235
- sessionId: sessionName,
236
- host: host || resolveHostFromSessionName(sessionName),
237
- summary: reason || "session failed",
238
- });
239
- emitter.emit("workerFailed", {
240
- name: paneName,
241
- host: host || resolveHostFromSessionName(sessionName),
242
- reason: reason || "session failed",
243
- });
244
- }
245
-
246
- function handleWatcherInputWait({ sessionName, inputWaitPattern, host }) {
247
- if (isConductorTracked(sessionName)) return;
248
-
249
- const paneName = buildPaneName(sessionName);
250
- notifyIfAvailable({
251
- type: "inputWait",
252
- sessionId: sessionName,
253
- host: host || resolveHostFromSessionName(sessionName),
254
- summary: `input_wait: ${inputWaitPattern || "unknown"}`,
255
- });
256
- emitter.emit("workerInputWait", {
257
- name: paneName,
258
- host: host || resolveHostFromSessionName(sessionName),
259
- pattern: inputWaitPattern || "unknown",
260
- });
261
- }
262
-
263
- // ── 내부 헬퍼 ──
264
-
265
- function isConductorTracked(sessionName) {
266
- const snapshots = conductor.getSnapshot();
267
- return snapshots.some((s) => s.id === sessionName && s.remote);
268
- }
269
-
270
- function getWatcherSession(sessionName) {
271
- if (!watcher) return null;
272
- const status = watcher.getStatus();
273
- return status.sessions?.[sessionName] || null;
274
- }
275
-
276
- function notifyIfAvailable(event) {
277
- if (!notifier) return;
278
- try {
279
- notifier.notify(event);
280
- } catch {
281
- // notify 실패는 adapter를 중단시키지 않는다
282
- }
283
- }
284
-
285
- /**
286
- * conductor snapshot을 폴링하여 모든 원격 워커를 갱신.
287
- */
288
- function pollConductorSnapshot() {
289
- const snapshots = conductor.getSnapshot();
290
- const nextWorkers = new Map(workers);
291
- const conductorSessionIds = new Set();
292
-
293
- for (const entry of snapshots) {
294
- if (!entry.remote) continue;
295
- conductorSessionIds.add(entry.id);
296
-
297
- const watcherSession = getWatcherSession(entry.id);
298
- const workerData = buildWorkerData(entry, watcherSession, hostsData);
299
- const paneName = buildPaneName(entry.id);
300
- nextWorkers.set(paneName, workerData);
301
- }
302
-
303
- // watcher-only 세션 (conductor 미등록)
304
- if (watcher) {
305
- const watcherStatus = watcher.getStatus();
306
- for (const [sessionName, record] of Object.entries(
307
- watcherStatus.sessions || {},
308
- )) {
309
- if (conductorSessionIds.has(sessionName)) continue;
310
- const paneName = buildPaneName(sessionName);
311
- const workerData = buildWatcherOnlyWorkerData(record, hostsData);
312
- nextWorkers.set(paneName, workerData);
313
- }
314
- }
315
-
316
- workers = nextWorkers;
317
- }
318
-
319
- // ── 공개 API ───────────────────────────────────────────────────────────────
320
-
321
- function start() {
322
- if (running) return;
323
- running = true;
324
-
325
- // hosts.json 리로드
326
- hostsData = loadHosts(opts.repoRoot);
327
-
328
- // conductor stateChange 구독
329
- conductor.on("stateChange", handleStateChange);
330
-
331
- // watcher 이벤트 구독
332
- if (watcher) {
333
- watcher.on("sessionCompleted", handleWatcherCompleted);
334
- watcher.on("sessionFailed", handleWatcherFailed);
335
- watcher.on("sessionInputWait", handleWatcherInputWait);
336
- }
337
-
338
- // 초기 snapshot 로드
339
- pollConductorSnapshot();
340
-
341
- // 주기적 폴링 (snapshot 갱신용 — stateChange가 primary)
342
- pollHandle = setIntervalFn(() => {
343
- pollConductorSnapshot();
344
- }, pollMs);
345
- pollHandle?.unref?.();
346
- }
347
-
348
- function stop() {
349
- if (!running) return;
350
- running = false;
351
-
352
- conductor.off("stateChange", handleStateChange);
353
-
354
- if (watcher) {
355
- watcher.off("sessionCompleted", handleWatcherCompleted);
356
- watcher.off("sessionFailed", handleWatcherFailed);
357
- watcher.off("sessionInputWait", handleWatcherInputWait);
358
- }
359
-
360
- if (pollHandle) {
361
- clearIntervalFn(pollHandle);
362
- pollHandle = null;
363
- }
364
- }
365
-
366
- function getWorkers() {
367
- return new Map(workers);
368
- }
369
-
370
- return Object.freeze({
371
- start,
372
- stop,
373
- getWorkers,
374
- on: emitter.on.bind(emitter),
375
- off: emitter.off.bind(emitter),
376
- });
377
- }
@@ -1,79 +0,0 @@
1
- // hub/team/worker-completion-validator.mjs — Enforce worker completion schema.
2
- // Issue #115 Lane 1 / F7_worker_did_not_commit.
3
- //
4
- // A shard worker that reports `status: "ok"` MUST include a non-empty
5
- // `commits_made` array. Non-ok statuses are valid terminal reports, but they
6
- // are not allowed to masquerade as successful completion.
7
-
8
- import { readFileSync } from "node:fs";
9
- import { dirname, join } from "node:path";
10
- import { fileURLToPath } from "node:url";
11
-
12
- import Ajv from "ajv";
13
-
14
- const schemaPath = join(
15
- dirname(fileURLToPath(import.meta.url)),
16
- "schemas",
17
- "worker-completion.json",
18
- );
19
- const schema = JSON.parse(readFileSync(schemaPath, "utf8"));
20
-
21
- const ajv = new Ajv({ allErrors: true, strict: false });
22
- const compiled = ajv.compile(schema);
23
-
24
- /**
25
- * Validate a worker completion payload against worker-completion.json.
26
- * Returns `{ ok: true }` when the payload satisfies the schema, otherwise
27
- * `{ ok: false, reason }` with a short human-readable reason string.
28
- *
29
- * @param {unknown} payload
30
- * @returns {{ ok: boolean, reason?: string }}
31
- */
32
- export function validateWorkerCompletion(payload) {
33
- if (payload === null || typeof payload !== "object") {
34
- return { ok: false, reason: "payload_not_object" };
35
- }
36
-
37
- const status = payload.status;
38
- if (!["ok", "failed", "blocked"].includes(status)) {
39
- return { ok: false, reason: `invalid_status:${status ?? "undefined"}` };
40
- }
41
-
42
- const schemaPayload =
43
- status === "blocked" ? { ...payload, status: "failed" } : payload;
44
- const valid = compiled(schemaPayload);
45
- if (!valid) {
46
- const firstError = compiled.errors?.[0];
47
- const reason = formatError(firstError, payload);
48
- return { ok: false, reason };
49
- }
50
-
51
- return { ok: true };
52
- }
53
-
54
- function formatError(err, payload) {
55
- if (!err) return "schema_validation_failed";
56
-
57
- // Map Ajv keywords to short, actionable reasons.
58
- if (
59
- err.keyword === "required" &&
60
- err.params?.missingProperty === "commits_made"
61
- ) {
62
- return "missing_commits_made";
63
- }
64
- if (err.keyword === "minItems" && err.instancePath === "/commits_made") {
65
- return "empty_commits_made";
66
- }
67
- if (err.keyword === "enum" && err.instancePath === "/status") {
68
- return `invalid_status:${payload?.status ?? "undefined"}`;
69
- }
70
- if (err.keyword === "type" && err.instancePath === "/commits_made") {
71
- return "commits_made_not_array";
72
- }
73
- if (err.keyword === "required" && err.params?.missingProperty === "status") {
74
- return "missing_status";
75
- }
76
-
77
- const path = err.instancePath || "(root)";
78
- return `${err.keyword}@${path}:${err.message}`;
79
- }