@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,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
- }