@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.
- package/cto/brief.mjs +0 -20
- package/cto/collect.mjs +1 -200
- package/cto/current.schema.json +2 -23
- package/cto/hygiene.mjs +2 -61
- package/cto/lake-root.mjs +0 -56
- package/cto/status.mjs +5 -187
- package/hub/index.mjs +0 -17
- package/hub/team/agent-map.json +0 -1
- package/hub/team/agents-row-attach.py +120 -0
- package/hub/team/agents-row.mjs +200 -0
- package/hub/team/ansi.mjs +1 -35
- package/hub/team/backend.mjs +9 -145
- package/hub/team/claude-daemon-control.mjs +1 -2
- package/hub/team/claude-session-projection.mjs +5 -73
- package/hub/team/cli/commands/attach.mjs +1 -17
- package/hub/team/cli/commands/control.mjs +12 -38
- package/hub/team/cli/commands/debug.mjs +2 -39
- package/hub/team/cli/commands/focus.mjs +5 -31
- package/hub/team/cli/commands/interrupt.mjs +6 -25
- package/hub/team/cli/commands/kill.mjs +1 -30
- package/hub/team/cli/commands/list.mjs +0 -16
- package/hub/team/cli/commands/send.mjs +6 -26
- package/hub/team/cli/commands/start/index.mjs +42 -133
- package/hub/team/cli/commands/start/parse-args.mjs +15 -40
- package/hub/team/cli/commands/start/start-headless.mjs +138 -105
- package/hub/team/cli/commands/start/start-mux.mjs +2 -7
- package/hub/team/cli/commands/status.mjs +2 -57
- package/hub/team/cli/commands/stop.mjs +14 -19
- package/hub/team/cli/help.mjs +4 -5
- package/hub/team/cli/manifest.mjs +5 -1
- package/hub/team/cli/render.mjs +0 -12
- package/hub/team/cli/services/runtime-mode.mjs +27 -98
- package/hub/team/cli/services/state-store.mjs +5 -20
- package/hub/team/handoff.mjs +3 -34
- package/hub/team/headless.mjs +108 -1296
- package/hub/team/orchestrator.mjs +4 -30
- package/hub/team/pane.mjs +6 -28
- package/hub/team/psmux.mjs +3 -34
- package/hub/team/remote-session.mjs +1 -2
- package/hub/team/session-context.mjs +1 -1
- package/hub/team/session.mjs +7 -91
- package/hub/team/tui-core.mjs +1 -1
- package/hub/team/tui-viewer.mjs +11 -58
- package/hub/team/tui.mjs +8 -306
- package/hub/team/uds-orchestrator.mjs +2 -580
- package/hub/team/wt-manager.mjs +2 -2
- package/hub/workers/factory.mjs +6 -84
- package/hub/workers/interface.mjs +1 -7
- package/hub/workers/lib/jsonrpc-core.mjs +11 -0
- package/hub/workers/lib/jsonrpc-ws-uds.mjs +5 -8
- package/package.json +3 -8
- package/scripts/lib/agent-route-policy.mjs +143 -62
- package/scripts/lib/cli-claude.mjs +0 -14
- package/scripts/lib/env-probe.mjs +2 -131
- package/scripts/lib/gemini-profiles.mjs +2 -69
- package/scripts/lib/legacy-mcp-cleanup.mjs +283 -0
- package/scripts/lib/machine-profile.mjs +3 -17
- package/scripts/lib/mcp-filter.mjs +109 -53
- package/scripts/lib/mcp-guard-engine.mjs +40 -98
- package/scripts/lib/mcp-health.mjs +2 -2
- package/scripts/lib/mcp-server-catalog.mjs +2 -2
- package/scripts/lib/psmux-info.mjs +11 -59
- package/scripts/lib/skill-template.mjs +0 -245
- package/hub/hub-lifecycle.mjs +0 -248
- package/hub/pipe.mjs +0 -825
- package/hub/promote-penalties.mjs +0 -138
- package/hub/public/dashboard.html +0 -416
- package/hub/schema.sql +0 -209
- package/hub/server.mjs +0 -3048
- package/hub/store-adapter.mjs +0 -182
- package/hub/store.mjs +0 -1736
- package/hub/team/build-worker-prompt.mjs +0 -92
- package/hub/team/check-mcp-hub.mjs +0 -49
- package/hub/team/claude-native-bridge.mjs +0 -987
- package/hub/team/cli/commands/start/start-in-process.mjs +0 -50
- package/hub/team/cli/commands/start/start-wt.mjs +0 -86
- package/hub/team/cli/services/hub-client.mjs +0 -264
- package/hub/team/cli/services/native-control.mjs +0 -145
- package/hub/team/codex-review.mjs +0 -520
- package/hub/team/conductor-mesh-bridge.mjs +0 -122
- package/hub/team/conductor-registry.mjs +0 -121
- package/hub/team/conductor.mjs +0 -1529
- package/hub/team/consensus-meta.mjs +0 -176
- package/hub/team/cto-auto-collect.mjs +0 -115
- package/hub/team/cto-pull.mjs +0 -31
- package/hub/team/daemon-pty-tmux-bridge.mjs +0 -146
- package/hub/team/dashboard.mjs +0 -322
- package/hub/team/event-log.mjs +0 -84
- package/hub/team/execution-mode.mjs +0 -324
- package/hub/team/extract-completion-payload.mjs +0 -110
- package/hub/team/headless-bridge-session.mjs +0 -17
- package/hub/team/health-probe.mjs +0 -435
- package/hub/team/interactive-native-launcher.mjs +0 -119
- package/hub/team/interactive-native-worker.mjs +0 -80
- package/hub/team/interactive-tui-transport.mjs +0 -479
- package/hub/team/launcher-template.mjs +0 -144
- package/hub/team/lead-control.mjs +0 -191
- package/hub/team/mcp-selector.mjs +0 -145
- package/hub/team/native-supervisor.mjs +0 -460
- package/hub/team/native.mjs +0 -727
- package/hub/team/recovery-store.mjs +0 -149
- package/hub/team/remote-probe.mjs +0 -292
- package/hub/team/remote-watcher.mjs +0 -506
- package/hub/team/runtime-strategy.mjs +0 -200
- package/hub/team/schemas/worker-completion.json +0 -39
- package/hub/team/sentinel-capture.mjs +0 -127
- package/hub/team/session-sync.mjs +0 -316
- package/hub/team/swarm-cli.mjs +0 -390
- package/hub/team/swarm-hypervisor.mjs +0 -2206
- package/hub/team/swarm-intent.mjs +0 -216
- package/hub/team/swarm-locks.mjs +0 -335
- package/hub/team/swarm-planner.mjs +0 -371
- package/hub/team/swarm-preflight.mjs +0 -366
- package/hub/team/swarm-reconciler.mjs +0 -242
- package/hub/team/synapse-cli.mjs +0 -458
- package/hub/team/synapse-http.mjs +0 -311
- package/hub/team/synapse-registry.mjs +0 -618
- package/hub/team/tui-remote-adapter.mjs +0 -377
- package/hub/team/tui-synapse.mjs +0 -223
- package/hub/team/worker-completion-validator.mjs +0 -79
- package/hub/team/worker-sandbox.mjs +0 -125
- package/hub/team/worktree-lifecycle.mjs +0 -639
- package/hub/team/wt-templates.json +0 -43
- package/hub/tools.mjs +0 -900
- package/hub/workers/antigravity-route-worker.mjs +0 -179
- package/hub/workers/codex-app-server-worker.mjs +0 -1067
- package/hub/workers/delegator-mcp.mjs +0 -1098
- package/hub/workers/lib/jsonrpc-stdio.mjs +0 -204
- package/scripts/lib/claudemd-scanner.mjs +0 -223
- package/scripts/lib/context.mjs +0 -67
- package/scripts/lib/dynamic-route-cli.mjs +0 -107
- package/scripts/lib/handoff.mjs +0 -187
- package/scripts/lib/logger.mjs +0 -122
- package/scripts/lib/stealth-fetch.mjs +0 -176
|
@@ -44,13 +44,12 @@ export function decomposeTask(taskDescription, agentCount) {
|
|
|
44
44
|
* @param {string} taskDescription
|
|
45
45
|
* @param {object} config
|
|
46
46
|
* @param {string} config.agentId
|
|
47
|
-
* @param {string} config.hubUrl
|
|
48
47
|
* @param {string} config.teammateMode
|
|
49
48
|
* @param {Array<{agentId:string, cli:string, subtask:string}>} config.workers
|
|
50
49
|
* @returns {string}
|
|
51
50
|
*/
|
|
52
51
|
export function buildLeadPrompt(taskDescription, config) {
|
|
53
|
-
const { agentId,
|
|
52
|
+
const { agentId, teammateMode = "tmux", workers = [] } = config;
|
|
54
53
|
|
|
55
54
|
const roster =
|
|
56
55
|
workers
|
|
@@ -59,11 +58,6 @@ export function buildLeadPrompt(taskDescription, config) {
|
|
|
59
58
|
|
|
60
59
|
const workerIds = workers.map((w) => w.agentId).join(", ");
|
|
61
60
|
|
|
62
|
-
// TODO: Require repoRoot once all callers pass absolute repository roots.
|
|
63
|
-
const bridgePath = repoRoot
|
|
64
|
-
? `node ${String(repoRoot).replace(/\/+$/, "")}/hub/bridge.mjs`
|
|
65
|
-
: "node hub/bridge.mjs";
|
|
66
|
-
|
|
67
61
|
return `리드 에이전트: ${agentId}
|
|
68
62
|
|
|
69
63
|
목표: ${taskDescription}
|
|
@@ -74,12 +68,7 @@ ${roster}
|
|
|
74
68
|
|
|
75
69
|
규칙:
|
|
76
70
|
- 가능한 짧고 핵심만 지시/요약(토큰 절약)
|
|
77
|
-
-
|
|
78
|
-
${bridgePath} result --agent ${agentId} --topic lead.control
|
|
79
|
-
- 워커 결과 수집:
|
|
80
|
-
${bridgePath} context --agent ${agentId} --max 20
|
|
81
|
-
- 최종 결과는 topic="task.result"를 모아 통합
|
|
82
|
-
- 모든 워커의 task.result 수신 후 결과를 통합하고 종료하라. 워커가 무응답이면 상태를 보고하고 중단하라.
|
|
71
|
+
- 모든 워커의 결과와 출력 파일을 확인하고 통합한다. 무응답 워커는 상태를 보고하고 중단한다.
|
|
83
72
|
- 증거(커밋/테스트/파일) 없는 완료 주장은 통합하지 말고 해당 워커에 redo 를 지시하라.
|
|
84
73
|
|
|
85
74
|
워커 ID: ${workerIds || "(없음)"}
|
|
@@ -92,28 +81,17 @@ ${roster}
|
|
|
92
81
|
* @param {object} config
|
|
93
82
|
* @param {string} config.cli — codex/gemini/claude
|
|
94
83
|
* @param {string} config.agentId — 에이전트 식별자
|
|
95
|
-
* @param {string} config.hubUrl — Hub URL
|
|
96
84
|
* @returns {string}
|
|
97
85
|
*/
|
|
98
86
|
export function buildPrompt(subtask, config) {
|
|
99
|
-
const { cli, agentId
|
|
100
|
-
|
|
101
|
-
const _hubBase = hubUrl.replace("/mcp", "");
|
|
102
|
-
|
|
103
|
-
const bridgePath = "node hub/bridge.mjs";
|
|
87
|
+
const { cli, agentId } = config;
|
|
104
88
|
|
|
105
89
|
return `워커: ${agentId} (${cli})
|
|
106
90
|
작업: ${subtask}
|
|
107
91
|
|
|
108
92
|
필수 규칙:
|
|
109
93
|
1) 간결하게 작업(불필요한 장문 설명 금지)
|
|
110
|
-
2)
|
|
111
|
-
${bridgePath} register --agent ${agentId} --cli ${cli} --topics lead.control,task.result
|
|
112
|
-
3) 주기적으로 수신함 확인:
|
|
113
|
-
${bridgePath} context --agent ${agentId} --max 10
|
|
114
|
-
4) lead.control 수신 시 즉시 반응 (interrupt/stop/pause/resume)
|
|
115
|
-
5) 완료 시 결과 발행:
|
|
116
|
-
${bridgePath} result --agent ${agentId} --topic task.result --file <출력파일>
|
|
94
|
+
2) 완료 시 변경 파일, 검증 결과와 출력 파일 경로를 보고한다
|
|
117
95
|
|
|
118
96
|
지금 작업을 시작하라.`;
|
|
119
97
|
}
|
|
@@ -123,7 +101,6 @@ export function buildPrompt(subtask, config) {
|
|
|
123
101
|
* @param {string} sessionName — tmux 세션 이름
|
|
124
102
|
* @param {Array<{target: string, cli: string, subtask: string}>} assignments
|
|
125
103
|
* @param {object} opts
|
|
126
|
-
* @param {string} opts.hubUrl — Hub URL
|
|
127
104
|
* @param {{target:string, cli:string, task:string}|null} opts.lead
|
|
128
105
|
* @param {string} opts.teammateMode
|
|
129
106
|
* @param {(failure: {target:string, cli:string, role:string, message:string}) => void} [opts.onInjectionFailure]
|
|
@@ -131,7 +108,6 @@ export function buildPrompt(subtask, config) {
|
|
|
131
108
|
*/
|
|
132
109
|
export async function orchestrate(sessionName, assignments, opts = {}) {
|
|
133
110
|
const {
|
|
134
|
-
hubUrl = "http://127.0.0.1:27888/mcp",
|
|
135
111
|
lead = null,
|
|
136
112
|
teammateMode = "tmux",
|
|
137
113
|
injectPrompt = defaultInjectPrompt,
|
|
@@ -165,7 +141,6 @@ export async function orchestrate(sessionName, assignments, opts = {}) {
|
|
|
165
141
|
const leadAgentId = `${lead.cli || "claude"}-${lead.target.split(".").pop()}`;
|
|
166
142
|
const leadPrompt = buildLeadPrompt(lead.task || "팀 작업 조율", {
|
|
167
143
|
agentId: leadAgentId,
|
|
168
|
-
hubUrl,
|
|
169
144
|
teammateMode,
|
|
170
145
|
workers: workers.map((w) => ({
|
|
171
146
|
agentId: w.agentId,
|
|
@@ -188,7 +163,6 @@ export async function orchestrate(sessionName, assignments, opts = {}) {
|
|
|
188
163
|
const prompt = buildPrompt(worker.subtask, {
|
|
189
164
|
cli: worker.cli,
|
|
190
165
|
agentId: worker.agentId,
|
|
191
|
-
hubUrl,
|
|
192
166
|
sessionName,
|
|
193
167
|
});
|
|
194
168
|
try {
|
package/hub/team/pane.mjs
CHANGED
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
import { mkdirSync, unlinkSync, writeFileSync } from "node:fs";
|
|
4
4
|
import { tmpdir } from "node:os";
|
|
5
5
|
import { join } from "node:path";
|
|
6
|
-
import { buildExecArgs } from "@triflux/core/hub/codex-adapter.mjs";
|
|
7
6
|
import { psmuxExec } from "./psmux.mjs";
|
|
8
7
|
import { detectMultiplexer, tmuxExec } from "./session.mjs";
|
|
9
8
|
|
|
@@ -49,23 +48,13 @@ function muxExec(args, opts = {}) {
|
|
|
49
48
|
|
|
50
49
|
/**
|
|
51
50
|
* CLI 에이전트 시작 커맨드 생성
|
|
52
|
-
* @param {'codex'|'
|
|
53
|
-
* @param {{ trustMode?: boolean }} [options]
|
|
51
|
+
* @param {'codex'|'antigravity'|'claude'} cli
|
|
54
52
|
* @returns {string} 실행할 셸 커맨드
|
|
55
53
|
*/
|
|
56
|
-
export function buildCliCommand(cli
|
|
57
|
-
const { trustMode = false } = options;
|
|
58
|
-
|
|
54
|
+
export function buildCliCommand(cli) {
|
|
59
55
|
switch (cli) {
|
|
60
56
|
case "codex":
|
|
61
|
-
// trust 모드에서는 exec 서브커맨드 기반 명령을 사용
|
|
62
|
-
if (trustMode) {
|
|
63
|
-
return buildExecArgs({});
|
|
64
|
-
}
|
|
65
57
|
return "codex --dangerously-bypass-approvals-and-sandbox";
|
|
66
|
-
case "gemini":
|
|
67
|
-
// interactive 모드 — MCP는 ~/.gemini/settings.json에 사전 등록
|
|
68
|
-
return "gemini";
|
|
69
58
|
case "claude":
|
|
70
59
|
// interactive 모드
|
|
71
60
|
return "claude";
|
|
@@ -87,17 +76,8 @@ export function startCliInPane(target, command) {
|
|
|
87
76
|
/**
|
|
88
77
|
* psmux `@file` 참조 주입이 가능한 CLI인지 판정한다.
|
|
89
78
|
*
|
|
90
|
-
* Codex TUI는
|
|
91
|
-
*
|
|
92
|
-
* 본체 L3271-3313 에서 `ActivePopup::File(popup)` 분기; 빈 query 상태는
|
|
93
|
-
* `file_search_popup.rs:54-63` 의 `set_empty_prompt()` 가 정의). 팝업이 떠 있는
|
|
94
|
-
* 동안의 Enter 처리는 같은 파일 composer의 Enter handler (`chat_composer.rs:1585-1645`
|
|
95
|
-
* 기준) 에서 popup 선택 path insert 또는 dismiss 로 분기하므로 prompt submit 으로
|
|
96
|
-
* 전달되지 않는다. Gemini CLI의 `@path`는 공식 client-side file-content inject
|
|
97
|
-
* 이므로 유지. Codex TUI에서 `@path` 선택은 path 문자열만 textarea에 insert할
|
|
98
|
-
* 뿐 Gemini처럼 파일 내용을 inject하지 않으며, 별도 file-injection slash
|
|
99
|
-
* command 도 존재하지 않음 (slash_commands / prompt_args / skill_popup /
|
|
100
|
-
* command_popup 소스 전수 grep 결과 0건, 2026-04-19 검증 — absence-based).
|
|
79
|
+
* Codex TUI의 @path는 파일 검색 팝업을 열어 Enter 제출을 가로막는다.
|
|
80
|
+
* Gemini CLI의 파일 내용 주입은 유지한다.
|
|
101
81
|
*
|
|
102
82
|
* @param {{ multiplexer: string, useFileRef: boolean, cli: string|null }} args
|
|
103
83
|
*/
|
|
@@ -156,9 +136,7 @@ function waitForComposerReady(target) {
|
|
|
156
136
|
* capture 불가 또는 재시도 소진을 성공으로 숨기지 않고 오류로 반환한다.
|
|
157
137
|
*/
|
|
158
138
|
function isAntigravityCli(cli) {
|
|
159
|
-
return ["agy", "antigravity"
|
|
160
|
-
String(cli || "").toLowerCase(),
|
|
161
|
-
);
|
|
139
|
+
return ["agy", "antigravity"].includes(String(cli || "").toLowerCase());
|
|
162
140
|
}
|
|
163
141
|
|
|
164
142
|
function promptComposerNeedle(prompt) {
|
|
@@ -254,7 +232,7 @@ function confirmSubmit(target, prompt, cli, sendEnter) {
|
|
|
254
232
|
* @param {string} prompt — 주입할 텍스트
|
|
255
233
|
* @param {object} [opts]
|
|
256
234
|
* @param {boolean} [opts.useFileRef] — true면 TUI용 @file 참조 방식 요청 (psmux 전용). Codex에서는 자동으로 paste-buffer 경로로 fallback.
|
|
257
|
-
* @param {'codex'|'
|
|
235
|
+
* @param {'codex'|'antigravity'|'claude'|null} [opts.cli]: 대상 CLI. Codex일 때 @ intercept를 회피하기 위해 paste-buffer 경로를 강제한다.
|
|
258
236
|
*/
|
|
259
237
|
export function injectPrompt(
|
|
260
238
|
target,
|
package/hub/team/psmux.mjs
CHANGED
|
@@ -931,22 +931,8 @@ function killOrphanPipeHelpers(sessionName) {
|
|
|
931
931
|
*/
|
|
932
932
|
function killOrphanMcpProcesses(sessionName) {
|
|
933
933
|
if (!IS_WINDOWS) {
|
|
934
|
-
// macOS/Linux: 세션별 스코핑
|
|
934
|
+
// macOS/Linux: 세션별 스코핑
|
|
935
935
|
const safeSessionUnix = sanitizePathPart(sessionName);
|
|
936
|
-
let hubPidUnix = 0;
|
|
937
|
-
try {
|
|
938
|
-
const hubPidFile = join(
|
|
939
|
-
homedir(),
|
|
940
|
-
".claude",
|
|
941
|
-
"cache",
|
|
942
|
-
"tfx-hub",
|
|
943
|
-
"hub.pid",
|
|
944
|
-
);
|
|
945
|
-
if (existsSync(hubPidFile)) {
|
|
946
|
-
const info = JSON.parse(readFileSync(hubPidFile, "utf8"));
|
|
947
|
-
hubPidUnix = Number(info.pid) || 0;
|
|
948
|
-
}
|
|
949
|
-
} catch {}
|
|
950
936
|
try {
|
|
951
937
|
// MCP/result files live under `tfx-headless/<session>-<pane>.txt`, so
|
|
952
938
|
// match `tfx-headless/<session>` with a trailing boundary to avoid
|
|
@@ -963,7 +949,7 @@ function killOrphanMcpProcesses(sessionName) {
|
|
|
963
949
|
if (pids) {
|
|
964
950
|
for (const p of pids.split("\n").filter(Boolean)) {
|
|
965
951
|
const numPid = Number(p);
|
|
966
|
-
if (numPid
|
|
952
|
+
if (numPid <= 0) continue;
|
|
967
953
|
try {
|
|
968
954
|
process.kill(numPid, "SIGTERM");
|
|
969
955
|
} catch {}
|
|
@@ -975,22 +961,6 @@ function killOrphanMcpProcesses(sessionName) {
|
|
|
975
961
|
// Windows: escape + trailing boundary (mirror of the macOS branch).
|
|
976
962
|
const safeSession = escapeRegex(sanitizePathPart(sessionName));
|
|
977
963
|
|
|
978
|
-
// Hub PID 보호 — Hub 프로세스를 고아로 잘못 식별하지 않도록
|
|
979
|
-
let hubPid = null;
|
|
980
|
-
try {
|
|
981
|
-
const hubPidPath = join(
|
|
982
|
-
homedir(),
|
|
983
|
-
".claude",
|
|
984
|
-
"cache",
|
|
985
|
-
"tfx-hub",
|
|
986
|
-
"hub.pid",
|
|
987
|
-
);
|
|
988
|
-
if (existsSync(hubPidPath)) {
|
|
989
|
-
const hubInfo = JSON.parse(readFileSync(hubPidPath, "utf8"));
|
|
990
|
-
hubPid = Number(hubInfo?.pid);
|
|
991
|
-
}
|
|
992
|
-
} catch {}
|
|
993
|
-
|
|
994
964
|
try {
|
|
995
965
|
// 세션 결과 디렉토리 패턴으로 MCP 서버 프로세스 식별
|
|
996
966
|
const output = childProcess.execSync(
|
|
@@ -1005,7 +975,7 @@ function killOrphanMcpProcesses(sessionName) {
|
|
|
1005
975
|
const pids = output
|
|
1006
976
|
.split(/\r?\n/)
|
|
1007
977
|
.map((l) => Number.parseInt(l.trim(), 10))
|
|
1008
|
-
.filter((p) => Number.isFinite(p) && p > 0
|
|
978
|
+
.filter((p) => Number.isFinite(p) && p > 0);
|
|
1009
979
|
for (const pid of pids) {
|
|
1010
980
|
killProcessTree(pid);
|
|
1011
981
|
}
|
|
@@ -1623,7 +1593,6 @@ export async function waitForPattern(
|
|
|
1623
1593
|
|
|
1624
1594
|
/**
|
|
1625
1595
|
* 완료 토큰이 찍힐 때까지 대기하고 exit code를 파싱한다.
|
|
1626
|
-
* NOTE: 주 채널은 headless.waitForCompletionWithStallDetect이며, 본 함수는 fallback 채널이다.
|
|
1627
1596
|
* @param {string} sessionName
|
|
1628
1597
|
* @param {string} paneNameOrTarget
|
|
1629
1598
|
* @param {string} token
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
//
|
|
2
|
-
// Extracted from scripts/remote-spawn.mjs for reuse by swarm-hypervisor.
|
|
1
|
+
// Remote session primitives.
|
|
3
2
|
// Pure functions + SSH operations. No psmux, no WT, no CLI arg parsing.
|
|
4
3
|
|
|
5
4
|
import { execFileSync } from "node:child_process";
|
|
@@ -96,7 +96,7 @@ export function contextGuard(
|
|
|
96
96
|
: {};
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
-
async function findCodexRollout(threadId) {
|
|
99
|
+
export async function findCodexRollout(threadId) {
|
|
100
100
|
if (!/^[a-zA-Z0-9-]+$/u.test(threadId || "")) return null;
|
|
101
101
|
const root = path.join(
|
|
102
102
|
process.env.CODEX_HOME || path.join(homedir(), ".codex"),
|
package/hub/team/session.mjs
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
//
|
|
2
|
-
// 의존성: child_process (Node.js 내장)만 사용
|
|
1
|
+
// tmux/psmux 세션 생명주기 관리
|
|
3
2
|
import { execSync, spawnSync } from "node:child_process";
|
|
4
3
|
import { resolveGitBashExecutable } from "@triflux/core/hub/lib/bash-path.mjs";
|
|
5
4
|
import { getEnvironment } from "@triflux/core/hub/lib/env-detect.mjs";
|
|
@@ -16,7 +15,6 @@ import {
|
|
|
16
15
|
psmuxExec,
|
|
17
16
|
psmuxSessionExists,
|
|
18
17
|
} from "./psmux.mjs";
|
|
19
|
-
import { createWtManager } from "./wt-manager.mjs";
|
|
20
18
|
|
|
21
19
|
/** Windows Terminal 실행 파일 존재 여부 */
|
|
22
20
|
export function hasWindowsTerminal() {
|
|
@@ -193,77 +191,6 @@ export function resolveAttachCommand(sessionName, opts = {}) {
|
|
|
193
191
|
};
|
|
194
192
|
}
|
|
195
193
|
|
|
196
|
-
/**
|
|
197
|
-
* Windows Terminal pane 포커스 이동
|
|
198
|
-
* @param {number} paneIndex - createWtSession()에서 생성한 pane 인덱스(0 기반)
|
|
199
|
-
* @param {object} opts
|
|
200
|
-
* @param {'1xN'|'Nx1'} opts.layout
|
|
201
|
-
* @returns {boolean}
|
|
202
|
-
*/
|
|
203
|
-
export function focusWtPane(paneIndex, opts = {}) {
|
|
204
|
-
// wt() 제거로 인해 일시 비활성화 (탭 기반 전환 필요)
|
|
205
|
-
return false;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
/**
|
|
209
|
-
* Windows Terminal에서 생성한 팀 pane 정리
|
|
210
|
-
* @param {object} opts
|
|
211
|
-
* @param {'1xN'|'Nx1'} opts.layout
|
|
212
|
-
* @param {number} opts.paneCount
|
|
213
|
-
* @returns {number} 닫힌 pane 수 (best-effort)
|
|
214
|
-
*/
|
|
215
|
-
export function closeWtSession(opts = {}) {
|
|
216
|
-
// wt() 제거로 인해 일시 비활성화 (탭 기반 전환 필요)
|
|
217
|
-
return 0;
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
/**
|
|
221
|
-
* Windows Terminal 독립 모드 세션 생성 (비동기)
|
|
222
|
-
* @param {string} sessionName
|
|
223
|
-
* @param {object} opts
|
|
224
|
-
* @param {'1xN'|'Nx1'} opts.layout
|
|
225
|
-
* @param {Array<{title:string,command:string,cwd?:string}>} opts.paneCommands
|
|
226
|
-
* @returns {Promise<{ sessionName: string, panes: string[], titles: string[], layout: '1xN'|'Nx1', paneCount: number, anchorPane: string }>}
|
|
227
|
-
*/
|
|
228
|
-
export async function createWtSession(sessionName, opts = {}) {
|
|
229
|
-
const { layout = "1xN", paneCommands = [] } = opts;
|
|
230
|
-
|
|
231
|
-
if (!hasWindowsTerminalSession()) {
|
|
232
|
-
throw new Error("WT_SESSION 미감지");
|
|
233
|
-
}
|
|
234
|
-
if (!getEnvironment().terminal.hasWt) {
|
|
235
|
-
throw new Error("wt.exe 미발견");
|
|
236
|
-
}
|
|
237
|
-
if (!Array.isArray(paneCommands) || paneCommands.length === 0) {
|
|
238
|
-
throw new Error("paneCommands가 비어 있음");
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
const wtManager = createWtManager();
|
|
242
|
-
const panes = [];
|
|
243
|
-
const titles = [];
|
|
244
|
-
|
|
245
|
-
for (let i = 0; i < paneCommands.length; i++) {
|
|
246
|
-
const pane = paneCommands[i] || {};
|
|
247
|
-
const title = pane.title || `${sessionName}-${i + 1}`;
|
|
248
|
-
const command = String(pane.command || "").trim();
|
|
249
|
-
const cwd = pane.cwd || process.cwd();
|
|
250
|
-
if (!command) continue;
|
|
251
|
-
|
|
252
|
-
await wtManager.createTab({ title, command, cwd });
|
|
253
|
-
panes.push(`wt:${i}`);
|
|
254
|
-
titles.push(title);
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
return {
|
|
258
|
-
sessionName,
|
|
259
|
-
panes,
|
|
260
|
-
titles,
|
|
261
|
-
layout: layout === "Nx1" ? "Nx1" : "1xN",
|
|
262
|
-
paneCount: panes.length,
|
|
263
|
-
anchorPane: "wt:anchor",
|
|
264
|
-
};
|
|
265
|
-
}
|
|
266
|
-
|
|
267
194
|
/**
|
|
268
195
|
* tmux 세션 생성 + 레이아웃 분할
|
|
269
196
|
* @param {string} sessionName — 세션 이름
|
|
@@ -375,23 +302,12 @@ export function configureTeammateKeybindings(sessionName, opts = {}) {
|
|
|
375
302
|
? `'select-pane -t :.- \\; resize-pane -Z'`
|
|
376
303
|
: `'select-pane -t :.-'`;
|
|
377
304
|
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
`bind-key -T root -n S-Up if-shell -F '${cond}' ${bindPrev} 'send-keys S-Up'`,
|
|
385
|
-
);
|
|
386
|
-
} else {
|
|
387
|
-
// 분할 뷰에서 팀메이트 순환
|
|
388
|
-
tmux(
|
|
389
|
-
`bind-key -T root -n S-Down if-shell -F '${cond}' ${bindNext} 'send-keys S-Down'`,
|
|
390
|
-
);
|
|
391
|
-
tmux(
|
|
392
|
-
`bind-key -T root -n S-Up if-shell -F '${cond}' ${bindPrev} 'send-keys S-Up'`,
|
|
393
|
-
);
|
|
394
|
-
}
|
|
305
|
+
tmux(
|
|
306
|
+
`bind-key -T root -n S-Down if-shell -F '${cond}' ${bindNext} 'send-keys S-Down'`,
|
|
307
|
+
);
|
|
308
|
+
tmux(
|
|
309
|
+
`bind-key -T root -n S-Up if-shell -F '${cond}' ${bindPrev} 'send-keys S-Up'`,
|
|
310
|
+
);
|
|
395
311
|
|
|
396
312
|
// 대체 키: 일부 환경에서 S-Up이 누락될 때 사용
|
|
397
313
|
tmux(
|
package/hub/team/tui-core.mjs
CHANGED
|
@@ -195,7 +195,7 @@ export function normalizeWorkerState(existing = {}, state = {}, opts = {}) {
|
|
|
195
195
|
|
|
196
196
|
// ── 색상 헬퍼 ─────────────────────────────────────────────────────────────
|
|
197
197
|
export function cliColor(cli) {
|
|
198
|
-
if (cli === "
|
|
198
|
+
if (cli === "antigravity" || cli === "agy") return FG.antigravity;
|
|
199
199
|
if (cli === "claude") return FG.claude;
|
|
200
200
|
if (cli === "codex") return FG.codex;
|
|
201
201
|
return FG.white;
|
package/hub/team/tui-viewer.mjs
CHANGED
|
@@ -25,12 +25,7 @@ function argVal(flag) {
|
|
|
25
25
|
const SESSION = argVal("--session");
|
|
26
26
|
const RESULT_DIR = argVal("--result-dir") ?? join(tmpdir(), "tfx-headless");
|
|
27
27
|
const LAYOUT = argVal("--layout") ?? "single";
|
|
28
|
-
const SOURCE = argVal("--source") === "files" ? "files" : "mux";
|
|
29
28
|
const LEAD_PANE = argVal("--lead-pane");
|
|
30
|
-
const parsedWorkerCount = Number.parseInt(argVal("--workers") || "0", 10);
|
|
31
|
-
const WORKER_COUNT = Number.isFinite(parsedWorkerCount)
|
|
32
|
-
? Math.min(64, Math.max(0, parsedWorkerCount))
|
|
33
|
-
: 0;
|
|
34
29
|
const MUX_BIN = getMultiplexerType();
|
|
35
30
|
|
|
36
31
|
if (!SESSION || !/^[a-zA-Z0-9_-]+$/u.test(SESSION)) {
|
|
@@ -39,22 +34,15 @@ if (!SESSION || !/^[a-zA-Z0-9_-]+$/u.test(SESSION)) {
|
|
|
39
34
|
);
|
|
40
35
|
process.exit(1);
|
|
41
36
|
}
|
|
42
|
-
|
|
43
|
-
|
|
37
|
+
try {
|
|
38
|
+
execFileSync(MUX_BIN, ["-V"], { encoding: "utf8", timeout: 2000 });
|
|
39
|
+
} catch {
|
|
40
|
+
process.stderr.write(
|
|
41
|
+
`ERROR: primary multiplexer 미설치. 설치 방법:\n${formatPsmuxInstallGuidance(" ")}\n`,
|
|
42
|
+
);
|
|
44
43
|
process.exit(1);
|
|
45
44
|
}
|
|
46
45
|
|
|
47
|
-
if (SOURCE === "mux") {
|
|
48
|
-
try {
|
|
49
|
-
execFileSync(MUX_BIN, ["-V"], { encoding: "utf8", timeout: 2000 });
|
|
50
|
-
} catch {
|
|
51
|
-
process.stderr.write(
|
|
52
|
-
`ERROR: primary multiplexer 미설치. 설치 방법:\n${formatPsmuxInstallGuidance(" ")}\n`,
|
|
53
|
-
);
|
|
54
|
-
process.exit(1);
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
46
|
// ── 메모리 보호 상수 ──
|
|
59
47
|
const MAX_BODY_BYTES = 10240;
|
|
60
48
|
|
|
@@ -182,13 +170,6 @@ function estimateProgress(lines, context = {}) {
|
|
|
182
170
|
|
|
183
171
|
// ── psmux 래퍼 ──
|
|
184
172
|
function listPanes() {
|
|
185
|
-
if (SOURCE === "files") {
|
|
186
|
-
return Array.from({ length: WORKER_COUNT }, (_, index) => ({
|
|
187
|
-
index: index + 1,
|
|
188
|
-
title: `worker-${index + 1} (daemon)`,
|
|
189
|
-
pid: "",
|
|
190
|
-
}));
|
|
191
|
-
}
|
|
192
173
|
try {
|
|
193
174
|
const out = execFileSync(
|
|
194
175
|
MUX_BIN,
|
|
@@ -215,32 +196,6 @@ function listPanes() {
|
|
|
215
196
|
}
|
|
216
197
|
|
|
217
198
|
function capturePane(paneIdx, lines = 20) {
|
|
218
|
-
if (SOURCE === "files") {
|
|
219
|
-
const base = join(RESULT_DIR, `${SESSION}-worker-${paneIdx}.txt`);
|
|
220
|
-
const artifacts = [
|
|
221
|
-
{ path: base, label: "output" },
|
|
222
|
-
{ path: `${base}.partial`, label: "daemon" },
|
|
223
|
-
{ path: `${base}.err`, label: "stderr" },
|
|
224
|
-
];
|
|
225
|
-
const snapshots = [];
|
|
226
|
-
for (const artifact of artifacts) {
|
|
227
|
-
try {
|
|
228
|
-
const content = readFileSync(artifact.path, "utf8").trim();
|
|
229
|
-
if (!content) continue;
|
|
230
|
-
snapshots.push(
|
|
231
|
-
`[${artifact.label}]\n${content.split("\n").slice(-lines).join("\n")}`,
|
|
232
|
-
);
|
|
233
|
-
} catch {
|
|
234
|
-
/* artifact is optional while the daemon job is starting */
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
return (
|
|
238
|
-
snapshots.join("\n") ||
|
|
239
|
-
`worker-${paneIdx} daemon active · waiting for output · ${Math.round(
|
|
240
|
-
(Date.now() - startTime) / 1000,
|
|
241
|
-
)}s`
|
|
242
|
-
);
|
|
243
|
-
}
|
|
244
199
|
try {
|
|
245
200
|
return execFileSync(
|
|
246
201
|
MUX_BIN,
|
|
@@ -358,8 +313,8 @@ function ingest() {
|
|
|
358
313
|
|
|
359
314
|
// CLI 타입 감지
|
|
360
315
|
let cli = "codex";
|
|
361
|
-
if (pane.title.includes("
|
|
362
|
-
cli = "
|
|
316
|
+
if (pane.title.includes("antigravity") || pane.title.includes("🔵"))
|
|
317
|
+
cli = "antigravity";
|
|
363
318
|
else if (pane.title.includes("claude") || pane.title.includes("🟠"))
|
|
364
319
|
cli = "claude";
|
|
365
320
|
ws.title = pane.title;
|
|
@@ -417,7 +372,7 @@ function ingest() {
|
|
|
417
372
|
const tokens = extractTokenLabel(snapshot);
|
|
418
373
|
const elapsed = Math.round((Date.now() - startTime) / 1000);
|
|
419
374
|
|
|
420
|
-
if (
|
|
375
|
+
if (resultSize > 10 || shellReturned) {
|
|
421
376
|
const resultContent = existsSync(resultFile)
|
|
422
377
|
? readFileSync(resultFile, "utf8")
|
|
423
378
|
: snapshot;
|
|
@@ -459,10 +414,8 @@ function ingest() {
|
|
|
459
414
|
// 진행 중
|
|
460
415
|
const progress = estimateProgress(lines, {
|
|
461
416
|
tokens,
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
resultSize: SOURCE === "files" ? 0 : resultSize,
|
|
465
|
-
shellReturned: SOURCE === "files" ? false : shellReturned,
|
|
417
|
+
resultSize,
|
|
418
|
+
shellReturned,
|
|
466
419
|
done: false,
|
|
467
420
|
});
|
|
468
421
|
const verdict = lastLine;
|