@triflux/remote 10.50.1 → 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 (120) hide show
  1. package/cto/brief.mjs +0 -12
  2. package/cto/collect.mjs +16 -247
  3. package/cto/current.schema.json +4 -20
  4. package/cto/events.mjs +13 -294
  5. package/cto/hygiene.mjs +26 -346
  6. package/cto/lake-root.mjs +114 -16
  7. package/cto/status.mjs +33 -128
  8. package/hub/index.mjs +0 -8
  9. package/hub/store-adapter.mjs +1 -1
  10. package/hub/team/agent-map.json +0 -1
  11. package/hub/team/ansi.mjs +1 -35
  12. package/hub/team/backend.mjs +9 -145
  13. package/hub/team/cli/commands/attach.mjs +1 -17
  14. package/hub/team/cli/commands/control.mjs +12 -38
  15. package/hub/team/cli/commands/debug.mjs +1 -37
  16. package/hub/team/cli/commands/focus.mjs +5 -31
  17. package/hub/team/cli/commands/interrupt.mjs +6 -25
  18. package/hub/team/cli/commands/kill.mjs +1 -30
  19. package/hub/team/cli/commands/list.mjs +0 -16
  20. package/hub/team/cli/commands/send.mjs +6 -26
  21. package/hub/team/cli/commands/start/index.mjs +41 -133
  22. package/hub/team/cli/commands/start/parse-args.mjs +15 -40
  23. package/hub/team/cli/commands/start/start-headless.mjs +138 -105
  24. package/hub/team/cli/commands/start/start-mux.mjs +0 -3
  25. package/hub/team/cli/commands/status.mjs +2 -57
  26. package/hub/team/cli/commands/stop.mjs +14 -19
  27. package/hub/team/cli/help.mjs +4 -5
  28. package/hub/team/cli/manifest.mjs +5 -1
  29. package/hub/team/cli/render.mjs +0 -12
  30. package/hub/team/cli/services/hub-client.mjs +2 -88
  31. package/hub/team/cli/services/runtime-mode.mjs +27 -98
  32. package/hub/team/handoff.mjs +3 -34
  33. package/hub/team/headless.mjs +14 -755
  34. package/hub/team/orchestrator.mjs +4 -30
  35. package/hub/team/pane.mjs +6 -28
  36. package/hub/team/psmux.mjs +16 -403
  37. package/hub/team/remote-session.mjs +1 -2
  38. package/hub/team/session.mjs +8 -119
  39. package/hub/team/swarm-intent.mjs +0 -198
  40. package/hub/team/terminal-opener.mjs +11 -35
  41. package/hub/team/tui-core.mjs +1 -1
  42. package/hub/team/tui-viewer.mjs +2 -2
  43. package/hub/team/tui.mjs +8 -269
  44. package/hub/team/wt-manager.mjs +2 -2
  45. package/hub/workers/factory.mjs +4 -37
  46. package/hub/workers/lib/jsonrpc-ws-uds.mjs +0 -2
  47. package/package.json +1 -1
  48. package/scripts/lib/agent-route-policy.mjs +162 -14
  49. package/scripts/lib/claudemd-scanner.mjs +2 -199
  50. package/scripts/lib/cli-claude.mjs +0 -2
  51. package/scripts/lib/context.mjs +3 -19
  52. package/scripts/lib/env-probe.mjs +5 -64
  53. package/scripts/lib/gemini-profiles.mjs +2 -69
  54. package/scripts/lib/legacy-mcp-cleanup.mjs +14 -3
  55. package/scripts/lib/machine-profile.mjs +3 -17
  56. package/scripts/lib/mcp-filter.mjs +109 -53
  57. package/scripts/lib/mcp-health.mjs +2 -2
  58. package/scripts/lib/mcp-server-catalog.mjs +2 -2
  59. package/scripts/lib/psmux-info.mjs +25 -104
  60. package/scripts/lib/skill-template.mjs +0 -271
  61. package/cto/hygiene-actions.mjs +0 -355
  62. package/cto/hygiene-notify.mjs +0 -170
  63. package/hub/team/build-worker-prompt.mjs +0 -92
  64. package/hub/team/check-mcp-hub.mjs +0 -49
  65. package/hub/team/claude-native-bridge.mjs +0 -987
  66. package/hub/team/cli/commands/start/start-in-process.mjs +0 -50
  67. package/hub/team/cli/commands/start/start-wt.mjs +0 -86
  68. package/hub/team/cli/services/native-control.mjs +0 -145
  69. package/hub/team/codex-review.mjs +0 -520
  70. package/hub/team/conductor-mesh-bridge.mjs +0 -122
  71. package/hub/team/conductor.mjs +0 -1529
  72. package/hub/team/consensus-meta.mjs +0 -176
  73. package/hub/team/cto-pull.mjs +0 -31
  74. package/hub/team/daemon-pty-tmux-bridge.mjs +0 -146
  75. package/hub/team/dashboard.mjs +0 -322
  76. package/hub/team/event-log.mjs +0 -84
  77. package/hub/team/execution-mode.mjs +0 -324
  78. package/hub/team/extract-completion-payload.mjs +0 -110
  79. package/hub/team/headless-bridge-session.mjs +0 -17
  80. package/hub/team/health-probe.mjs +0 -435
  81. package/hub/team/interactive-native-launcher.mjs +0 -119
  82. package/hub/team/interactive-native-worker.mjs +0 -80
  83. package/hub/team/interactive-tui-transport.mjs +0 -479
  84. package/hub/team/launcher-template.mjs +0 -144
  85. package/hub/team/mcp-selector.mjs +0 -145
  86. package/hub/team/native-supervisor.mjs +0 -460
  87. package/hub/team/native.mjs +0 -727
  88. package/hub/team/recovery-store.mjs +0 -149
  89. package/hub/team/remote-probe.mjs +0 -292
  90. package/hub/team/remote-watcher.mjs +0 -506
  91. package/hub/team/runtime-strategy.mjs +0 -200
  92. package/hub/team/schemas/worker-completion.json +0 -39
  93. package/hub/team/sentinel-capture.mjs +0 -127
  94. package/hub/team/swarm-cli.mjs +0 -390
  95. package/hub/team/swarm-hypervisor.mjs +0 -2206
  96. package/hub/team/swarm-locks.mjs +0 -335
  97. package/hub/team/swarm-planner.mjs +0 -371
  98. package/hub/team/swarm-preflight.mjs +0 -366
  99. package/hub/team/swarm-reconciler.mjs +0 -242
  100. package/hub/team/tui-remote-adapter.mjs +0 -377
  101. package/hub/team/worker-completion-validator.mjs +0 -79
  102. package/hub/team/worktree-lifecycle.mjs +0 -639
  103. package/hub/team/wt-templates.json +0 -43
  104. package/hub/workers/antigravity-route-worker.mjs +0 -179
  105. package/hub/workers/gemini-worker.mjs +0 -217
  106. package/scripts/lib/agent-json.mjs +0 -27
  107. package/scripts/lib/async.mjs +0 -174
  108. package/scripts/lib/cli-agy.mjs +0 -85
  109. package/scripts/lib/cli-codex.mjs +0 -62
  110. package/scripts/lib/cli-gemini.mjs +0 -37
  111. package/scripts/lib/dynamic-route-cli.mjs +0 -107
  112. package/scripts/lib/env.mjs +0 -14
  113. package/scripts/lib/hub.mjs +0 -48
  114. package/scripts/lib/pid.mjs +0 -63
  115. package/scripts/lib/quota.mjs +0 -47
  116. package/scripts/lib/skill-state.mjs +0 -220
  117. package/scripts/lib/team.mjs +0 -75
  118. package/scripts/lib/timeout.mjs +0 -66
  119. package/scripts/lib/tmp.mjs +0 -26
  120. package/scripts/lib/toml.mjs +0 -132
@@ -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
- }