@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,149 +0,0 @@
1
- // hub/team/recovery-store.mjs — Best-effort preservation of worker changes
2
- // before a dirty worktree is removed. Uses file-based `.patch` + JSON manifest
3
- // instead of git stash, which is risky to pop under concurrent worktree use.
4
-
5
- import { execFile } from "node:child_process";
6
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
7
- import { join, resolve } from "node:path";
8
-
9
- const DEFAULT_RECOVERY_DIR = ".codex-swarm/recovery";
10
-
11
- function git(args, cwd) {
12
- return new Promise((res) => {
13
- execFile(
14
- "git",
15
- args,
16
- { cwd, windowsHide: true, timeout: 30_000, maxBuffer: 64 * 1024 * 1024 },
17
- (err, stdout, stderr) => {
18
- if (err) {
19
- res({ ok: false, stderr: stderr?.toString() || err.message });
20
- } else {
21
- res({ ok: true, stdout });
22
- }
23
- },
24
- );
25
- });
26
- }
27
-
28
- function manifestPath(recoveryDir) {
29
- return join(recoveryDir, "manifest.json");
30
- }
31
-
32
- function readManifestFile(recoveryDir) {
33
- const path = manifestPath(recoveryDir);
34
- if (!existsSync(path)) return { entries: [] };
35
- try {
36
- const raw = readFileSync(path, "utf8");
37
- const parsed = JSON.parse(raw);
38
- if (parsed && Array.isArray(parsed.entries)) return parsed;
39
- return { entries: [] };
40
- } catch {
41
- return { entries: [] };
42
- }
43
- }
44
-
45
- function writeManifestFile(recoveryDir, data) {
46
- writeFileSync(manifestPath(recoveryDir), JSON.stringify(data, null, 2));
47
- }
48
-
49
- /**
50
- * Preserve an uncommitted worker patch before the worktree is removed.
51
- *
52
- * Contract:
53
- * - Never throws.
54
- * - Dirty worktree → write {recoveryDir}/{shardId}.patch, append manifest entry.
55
- * - Clean worktree → return {ok: true, skipped: true}, no files written.
56
- * - Missing/invalid → return {ok: false, reason}, no throw.
57
- *
58
- * Patch captures `git diff HEAD` output (unified diff) — this covers both
59
- * staged and unstaged changes relative to the shard's base commit.
60
- *
61
- * @param {object} opts
62
- * @param {string} opts.worktreePath — absolute path to the worker worktree
63
- * @param {string} opts.shardId — shard identifier (used for filename)
64
- * @param {string} [opts.recoveryDir] — default: `<worktreePath>/.codex-swarm/recovery`
65
- * when running inside worktree root;
66
- * callers typically pass repo-root relative path.
67
- * @returns {Promise<
68
- * | { ok: true, patchPath: string, manifestPath: string }
69
- * | { ok: true, skipped: true }
70
- * | { ok: false, reason: string }
71
- * >}
72
- */
73
- export async function preserveWorktreePatch(opts = {}) {
74
- const { worktreePath, shardId } = opts;
75
-
76
- if (!worktreePath || typeof worktreePath !== "string") {
77
- return { ok: false, reason: "missing_worktree_path" };
78
- }
79
- if (!shardId || typeof shardId !== "string") {
80
- return { ok: false, reason: "missing_shard_id" };
81
- }
82
- if (!existsSync(worktreePath)) {
83
- return { ok: false, reason: "worktree_path_not_found" };
84
- }
85
-
86
- const recoveryDir = opts.recoveryDir
87
- ? resolve(opts.recoveryDir)
88
- : resolve(worktreePath, DEFAULT_RECOVERY_DIR);
89
-
90
- // Quickly classify dirty vs clean. `git status --porcelain` is safe and fast.
91
- const status = await git(["status", "--porcelain"], worktreePath);
92
- if (!status.ok) {
93
- return { ok: false, reason: `git_status_failed: ${status.stderr}` };
94
- }
95
-
96
- if (status.stdout.trim().length === 0) {
97
- return { ok: true, skipped: true };
98
- }
99
-
100
- // Dirty — capture diff.
101
- const diff = await git(["diff", "HEAD"], worktreePath);
102
- if (!diff.ok) {
103
- return { ok: false, reason: `git_diff_failed: ${diff.stderr}` };
104
- }
105
-
106
- try {
107
- mkdirSync(recoveryDir, { recursive: true });
108
- } catch (err) {
109
- return { ok: false, reason: `mkdir_failed: ${err.message}` };
110
- }
111
-
112
- const safeShardId = shardId.replace(/[^a-zA-Z0-9._-]/g, "_");
113
- const patchPath = join(recoveryDir, `${safeShardId}.patch`);
114
- try {
115
- writeFileSync(patchPath, diff.stdout);
116
- } catch (err) {
117
- return { ok: false, reason: `patch_write_failed: ${err.message}` };
118
- }
119
-
120
- const manifest = readManifestFile(recoveryDir);
121
- manifest.entries.push({
122
- shard: shardId,
123
- patch: patchPath,
124
- timestamp: new Date().toISOString(),
125
- });
126
- try {
127
- writeManifestFile(recoveryDir, manifest);
128
- } catch (err) {
129
- return { ok: false, reason: `manifest_write_failed: ${err.message}` };
130
- }
131
-
132
- return { ok: true, patchPath, manifestPath: manifestPath(recoveryDir) };
133
- }
134
-
135
- /**
136
- * Read the recovery manifest. Returns {entries: []} if missing or invalid.
137
- * @param {string} recoveryDir
138
- * @returns {{ entries: Array<{ shard: string, patch: string, timestamp: string }> }}
139
- */
140
- export function readManifest(recoveryDir) {
141
- if (!recoveryDir || typeof recoveryDir !== "string") {
142
- return { entries: [] };
143
- }
144
- return readManifestFile(resolve(recoveryDir));
145
- }
146
-
147
- export const RECOVERY_DEFAULTS = Object.freeze({
148
- dir: DEFAULT_RECOVERY_DIR,
149
- });
@@ -1,292 +0,0 @@
1
- // hub/team/remote-probe.mjs — SSH 경유 원격 세션 health probe
2
- // health-probe.mjs와 동일 인터페이스: { start, stop, probe, getStatus }
3
- // child process PID 대신 SSH capture-pane 폴링으로 상태 추적.
4
- //
5
- // L0: SSH 연결 + psmux 세션 존재 확인
6
- // L1: capture-pane 출력 변화 감지 (advancing)
7
- // L1.5: INPUT_WAIT 패턴 감지 (detectInputWait 재사용)
8
- // L3: 완료 토큰 감지 (__TRIFLUX_DONE__ 또는 프롬프트 idle)
9
-
10
- import { execFileSync } from "node:child_process";
11
- import { detectInputWait, PROBE_DEFAULTS } from "./health-probe.mjs";
12
-
13
- /** 완료 토큰 패턴 */
14
- const COMPLETION_TOKEN_RE = /__TRIFLUX_DONE__/;
15
-
16
- /** 프롬프트 idle 패턴 (Claude Code 프롬프트 복귀) */
17
- const _PROMPT_IDLE_RE = /(\u276f|\u2795|>\s*$)/;
18
-
19
- /**
20
- * SSH 경유로 원격 psmux capture-pane 실행.
21
- * @param {string} host — SSH 호스트
22
- * @param {string} paneTarget — psmux pane target (e.g. "session:0.0")
23
- * @param {number} lines — 캡처할 줄 수
24
- * @param {object} [deps] — 테스트용 의존성 주입
25
- * @returns {string|null} 캡처 텍스트 또는 null (실패)
26
- */
27
- export function sshCapturePane(host, paneTarget, lines = 20, deps = {}) {
28
- const execFn = deps.execFileSync || execFileSync;
29
- try {
30
- const output = execFn(
31
- "ssh",
32
- [
33
- "-o",
34
- "ConnectTimeout=5",
35
- "-o",
36
- "BatchMode=yes",
37
- host,
38
- `psmux capture-pane -t ${paneTarget} -p -S -`,
39
- ],
40
- {
41
- encoding: "utf8",
42
- timeout: 10_000,
43
- windowsHide: true,
44
- stdio: ["ignore", "pipe", "ignore"],
45
- },
46
- );
47
- const nonEmpty = output.split("\n").filter((line) => line.trim() !== "");
48
- return nonEmpty.slice(-lines).join("\n");
49
- } catch {
50
- return null;
51
- }
52
- }
53
-
54
- /**
55
- * SSH 경유로 원격 psmux 세션 존재 여부 확인.
56
- * @param {string} host
57
- * @param {string} sessionName
58
- * @param {object} [deps]
59
- * @returns {boolean}
60
- */
61
- export function sshSessionExists(host, sessionName, deps = {}) {
62
- const execFn = deps.execFileSync || execFileSync;
63
- try {
64
- execFn(
65
- "ssh",
66
- [
67
- "-o",
68
- "ConnectTimeout=5",
69
- "-o",
70
- "BatchMode=yes",
71
- host,
72
- `psmux has-session -t ${sessionName}`,
73
- ],
74
- {
75
- timeout: 10_000,
76
- windowsHide: true,
77
- stdio: ["ignore", "ignore", "ignore"],
78
- },
79
- );
80
- return true;
81
- } catch {
82
- return false;
83
- }
84
- }
85
-
86
- /**
87
- * Remote health probe 팩토리.
88
- * health-probe.mjs의 createHealthProbe와 동일 인터페이스를 제공하되,
89
- * SSH capture-pane 폴링 기반으로 동작한다.
90
- *
91
- * @param {object} session — probe 대상 세션 정보
92
- * @param {string} session.host — SSH 호스트
93
- * @param {string} session.paneTarget — psmux pane target
94
- * @param {string} session.sessionName — psmux 세션 이름
95
- * @param {object} [opts] — PROBE_DEFAULTS 오버라이드
96
- * @param {function} [opts.onProbe] — (result) => void 콜백
97
- * @param {object} [opts.deps] — 테스트용 의존성 주입
98
- * @returns {{ start, stop, probe, getStatus, started }}
99
- */
100
- export function createRemoteProbe(session, opts = {}) {
101
- const config = { ...PROBE_DEFAULTS, ...opts };
102
- const deps = opts.deps || {};
103
- let timer = null;
104
- let started = false;
105
-
106
- // L1 tracking — 출력 변화 감지
107
- let lastCaptureHash = "";
108
- let lastOutputChangeAt = Date.now();
109
-
110
- // L3 tracking — 완료 토큰 / prompt idle
111
- let promptAcked = false;
112
- let spawnedAt = Date.now();
113
-
114
- const status = {
115
- l0: null,
116
- l1: null,
117
- l2: "skip", // 원격은 MCP L2 미지원
118
- l3: null,
119
- lastProbeAt: null,
120
- inputWaitPattern: null,
121
- };
122
-
123
- /**
124
- * L0: SSH 연결 + psmux 세션 존재 확인.
125
- */
126
- function probeL0() {
127
- const exists = sshSessionExists(session.host, session.sessionName, deps);
128
- status.l0 = exists ? "ok" : "fail";
129
- return status.l0;
130
- }
131
-
132
- /**
133
- * L1 + L1.5: capture-pane 출력 변화 + INPUT_WAIT 감지.
134
- * @param {string|null} captured — 이미 캡처된 텍스트 (L0에서 재사용)
135
- */
136
- function probeL1(captured) {
137
- const now = Date.now();
138
-
139
- if (captured == null) {
140
- // 캡처 실패 — 이전 상태 유지
141
- return status.l1 || null;
142
- }
143
-
144
- // 단순 해시: 길이 + 처음/끝 문자 조합 (crypto 불필요)
145
- const hash = `${captured.length}:${captured.slice(0, 32)}:${captured.slice(-32)}`;
146
-
147
- if (hash !== lastCaptureHash) {
148
- lastCaptureHash = hash;
149
- lastOutputChangeAt = now;
150
- status.l1 = "ok";
151
- status.inputWaitPattern = null;
152
- return "ok";
153
- }
154
-
155
- const silenceMs = now - lastOutputChangeAt;
156
-
157
- if (silenceMs >= config.l1ThresholdMs) {
158
- // L1.5: INPUT_WAIT 패턴 감지
159
- const inputWait = detectInputWait(captured);
160
-
161
- if (inputWait.detected) {
162
- status.l1 = "input_wait";
163
- status.inputWaitPattern = inputWait.pattern;
164
- return "input_wait";
165
- }
166
-
167
- status.l1 = "stall";
168
- status.inputWaitPattern = null;
169
- return "stall";
170
- }
171
-
172
- status.l1 = "ok";
173
- return "ok";
174
- }
175
-
176
- /**
177
- * L3: 완료 토큰 또는 프롬프트 idle 감지.
178
- * @param {string|null} captured
179
- */
180
- function probeL3(captured) {
181
- if (promptAcked) {
182
- status.l3 = "ok";
183
- return "ok";
184
- }
185
-
186
- if (captured != null && captured.length > 0) {
187
- // 완료 토큰 감지 → 즉시 ok
188
- if (COMPLETION_TOKEN_RE.test(captured)) {
189
- promptAcked = true;
190
- status.l3 = "completed";
191
- return "completed";
192
- }
193
-
194
- // 출력이 있으면 prompt acknowledged
195
- promptAcked = true;
196
- status.l3 = "ok";
197
- return "ok";
198
- }
199
-
200
- const elapsed = Date.now() - spawnedAt;
201
- if (elapsed >= config.l3ThresholdMs) {
202
- status.l3 = "timeout";
203
- return "timeout";
204
- }
205
-
206
- status.l3 = null;
207
- return null;
208
- }
209
-
210
- /**
211
- * 전체 probe 실행 (L0 → capture → L1 → L3).
212
- * @returns {Promise<object>} probe 결과
213
- */
214
- async function probe() {
215
- const l0 = probeL0();
216
-
217
- // L0 실패 시 나머지 probe 스킵
218
- let captured = null;
219
- if (l0 === "ok") {
220
- captured = sshCapturePane(session.host, session.paneTarget, 20, deps);
221
- }
222
-
223
- const l1 = probeL1(captured);
224
- const l3 = probeL3(captured);
225
-
226
- const result = {
227
- l0,
228
- l1,
229
- l2: "skip",
230
- l3,
231
- inputWaitPattern: status.inputWaitPattern,
232
- ts: Date.now(),
233
- };
234
- status.lastProbeAt = result.ts;
235
-
236
- if (typeof config.onProbe === "function") {
237
- config.onProbe(result);
238
- }
239
-
240
- return result;
241
- }
242
-
243
- function start() {
244
- if (started) return;
245
- started = true;
246
- spawnedAt = Date.now();
247
- lastOutputChangeAt = Date.now();
248
- lastCaptureHash = "";
249
- promptAcked = false;
250
-
251
- timer = setInterval(() => {
252
- void probe();
253
- }, config.intervalMs);
254
- timer.unref?.();
255
-
256
- // 즉시 첫 probe 실행
257
- void probe();
258
- }
259
-
260
- function stop() {
261
- if (!started) return;
262
- started = false;
263
- if (timer) {
264
- clearInterval(timer);
265
- timer = null;
266
- }
267
- }
268
-
269
- /** tracking 리셋 (restart 후 호출) */
270
- function resetTracking() {
271
- lastCaptureHash = "";
272
- lastOutputChangeAt = Date.now();
273
- promptAcked = false;
274
- spawnedAt = Date.now();
275
- status.l0 = null;
276
- status.l1 = null;
277
- status.l2 = "skip";
278
- status.l3 = null;
279
- status.inputWaitPattern = null;
280
- }
281
-
282
- return Object.freeze({
283
- start,
284
- stop,
285
- probe,
286
- resetTracking,
287
- getStatus: () => ({ ...status }),
288
- get started() {
289
- return started;
290
- },
291
- });
292
- }