@triflux/remote 10.0.0-alpha.1

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 (68) hide show
  1. package/hub/pipe.mjs +579 -0
  2. package/hub/public/dashboard.html +355 -0
  3. package/hub/public/tray-icon.ico +0 -0
  4. package/hub/public/tray-icon.png +0 -0
  5. package/hub/server.mjs +1124 -0
  6. package/hub/store-adapter.mjs +851 -0
  7. package/hub/store.mjs +897 -0
  8. package/hub/team/agent-map.json +11 -0
  9. package/hub/team/ansi.mjs +379 -0
  10. package/hub/team/backend.mjs +90 -0
  11. package/hub/team/cli/commands/attach.mjs +37 -0
  12. package/hub/team/cli/commands/control.mjs +43 -0
  13. package/hub/team/cli/commands/debug.mjs +74 -0
  14. package/hub/team/cli/commands/focus.mjs +53 -0
  15. package/hub/team/cli/commands/interrupt.mjs +36 -0
  16. package/hub/team/cli/commands/kill.mjs +37 -0
  17. package/hub/team/cli/commands/list.mjs +24 -0
  18. package/hub/team/cli/commands/send.mjs +37 -0
  19. package/hub/team/cli/commands/start/index.mjs +106 -0
  20. package/hub/team/cli/commands/start/parse-args.mjs +130 -0
  21. package/hub/team/cli/commands/start/start-headless.mjs +109 -0
  22. package/hub/team/cli/commands/start/start-in-process.mjs +40 -0
  23. package/hub/team/cli/commands/start/start-mux.mjs +73 -0
  24. package/hub/team/cli/commands/start/start-wt.mjs +69 -0
  25. package/hub/team/cli/commands/status.mjs +87 -0
  26. package/hub/team/cli/commands/stop.mjs +31 -0
  27. package/hub/team/cli/commands/task.mjs +30 -0
  28. package/hub/team/cli/commands/tasks.mjs +13 -0
  29. package/hub/team/cli/help.mjs +42 -0
  30. package/hub/team/cli/index.mjs +41 -0
  31. package/hub/team/cli/manifest.mjs +29 -0
  32. package/hub/team/cli/render.mjs +30 -0
  33. package/hub/team/cli/services/attach-fallback.mjs +54 -0
  34. package/hub/team/cli/services/hub-client.mjs +208 -0
  35. package/hub/team/cli/services/member-selector.mjs +30 -0
  36. package/hub/team/cli/services/native-control.mjs +117 -0
  37. package/hub/team/cli/services/runtime-mode.mjs +62 -0
  38. package/hub/team/cli/services/state-store.mjs +48 -0
  39. package/hub/team/cli/services/task-model.mjs +30 -0
  40. package/hub/team/dashboard-anchor.mjs +14 -0
  41. package/hub/team/dashboard-layout.mjs +33 -0
  42. package/hub/team/dashboard-open.mjs +153 -0
  43. package/hub/team/dashboard.mjs +274 -0
  44. package/hub/team/handoff.mjs +303 -0
  45. package/hub/team/headless.mjs +1149 -0
  46. package/hub/team/native-supervisor.mjs +392 -0
  47. package/hub/team/native.mjs +649 -0
  48. package/hub/team/nativeProxy.mjs +681 -0
  49. package/hub/team/orchestrator.mjs +161 -0
  50. package/hub/team/pane.mjs +153 -0
  51. package/hub/team/psmux.mjs +1354 -0
  52. package/hub/team/routing.mjs +223 -0
  53. package/hub/team/session.mjs +611 -0
  54. package/hub/team/shared.mjs +13 -0
  55. package/hub/team/staleState.mjs +361 -0
  56. package/hub/team/tui-lite.mjs +380 -0
  57. package/hub/team/tui-viewer.mjs +463 -0
  58. package/hub/team/tui.mjs +1245 -0
  59. package/hub/tools.mjs +554 -0
  60. package/hub/tray.mjs +376 -0
  61. package/hub/workers/claude-worker.mjs +475 -0
  62. package/hub/workers/codex-mcp.mjs +504 -0
  63. package/hub/workers/delegator-mcp.mjs +1076 -0
  64. package/hub/workers/factory.mjs +21 -0
  65. package/hub/workers/gemini-worker.mjs +373 -0
  66. package/hub/workers/interface.mjs +52 -0
  67. package/hub/workers/worker-utils.mjs +104 -0
  68. package/package.json +31 -0
@@ -0,0 +1,161 @@
1
+ // hub/team/orchestrator.mjs — 작업 분배 + 프롬프트 구성
2
+ // 의존성: pane.mjs만 사용
3
+ import { injectPrompt } from "./pane.mjs";
4
+
5
+ /**
6
+ * 작업 분해 (LLM 없이 구분자 기반)
7
+ * @param {string} taskDescription — 전체 작업 설명
8
+ * @param {number} agentCount — 에이전트 수
9
+ * @returns {string[]} 각 에이전트의 서브태스크
10
+ */
11
+ export function decomposeTask(taskDescription, agentCount) {
12
+ if (agentCount <= 0) return [];
13
+ if (agentCount === 1) return [taskDescription];
14
+
15
+ // '+', ',', '\n' 기준으로 분리
16
+ const parts = taskDescription
17
+ .split(/[+,\n]+/)
18
+ .map((s) => s.trim())
19
+ .filter(Boolean);
20
+
21
+ if (parts.length === 0) return [taskDescription];
22
+
23
+ // 에이전트보다 서브태스크가 적으면 마지막 에이전트에 전체 태스크 부여
24
+ if (parts.length < agentCount) {
25
+ const result = [...parts];
26
+ while (result.length < agentCount) {
27
+ result.push(taskDescription);
28
+ }
29
+ return result;
30
+ }
31
+
32
+ // 에이전트보다 서브태스크가 많으면 앞에서부터 N개, 나머지는 마지막에 합침
33
+ if (parts.length > agentCount) {
34
+ const result = parts.slice(0, agentCount - 1);
35
+ result.push(parts.slice(agentCount - 1).join(" + "));
36
+ return result;
37
+ }
38
+
39
+ return parts;
40
+ }
41
+
42
+ /**
43
+ * 리드(보통 claude) 초기 프롬프트 생성
44
+ * @param {string} taskDescription
45
+ * @param {object} config
46
+ * @param {string} config.agentId
47
+ * @param {string} config.hubUrl
48
+ * @param {string} config.teammateMode
49
+ * @param {Array<{agentId:string, cli:string, subtask:string}>} config.workers
50
+ * @returns {string}
51
+ */
52
+ export function buildLeadPrompt(taskDescription, config) {
53
+ const { agentId, hubUrl, teammateMode = "tmux", workers = [] } = config;
54
+
55
+ const roster = workers
56
+ .map((w, i) => `${i + 1}. ${w.agentId} (${w.cli}) — ${w.subtask}`)
57
+ .join("\n") || "- (워커 없음)";
58
+
59
+ const workerIds = workers.map((w) => w.agentId).join(", ");
60
+
61
+ const bridgePath = "node hub/bridge.mjs";
62
+
63
+ return `리드 에이전트: ${agentId}
64
+
65
+ 목표: ${taskDescription}
66
+ 모드: ${teammateMode}
67
+
68
+ 워커:
69
+ ${roster}
70
+
71
+ 규칙:
72
+ - 가능한 짧고 핵심만 지시/요약(토큰 절약)
73
+ - 워커 제어:
74
+ ${bridgePath} result --agent ${agentId} --topic lead.control
75
+ - 워커 결과 수집:
76
+ ${bridgePath} context --agent ${agentId} --max 20
77
+ - 최종 결과는 topic="task.result"를 모아 통합
78
+
79
+ 워커 ID: ${workerIds || "(없음)"}
80
+ 지금 즉시 워커를 배정하고 병렬 진행을 관리하라.`;
81
+ }
82
+
83
+ /**
84
+ * 워커 초기 프롬프트 생성
85
+ * @param {string} subtask — 이 에이전트의 서브태스크
86
+ * @param {object} config
87
+ * @param {string} config.cli — codex/gemini/claude
88
+ * @param {string} config.agentId — 에이전트 식별자
89
+ * @param {string} config.hubUrl — Hub URL
90
+ * @returns {string}
91
+ */
92
+ export function buildPrompt(subtask, config) {
93
+ const { cli, agentId, hubUrl } = config;
94
+
95
+ const hubBase = hubUrl.replace("/mcp", "");
96
+
97
+ const bridgePath = "node hub/bridge.mjs";
98
+
99
+ return `워커: ${agentId} (${cli})
100
+ 작업: ${subtask}
101
+
102
+ 필수 규칙:
103
+ 1) 간결하게 작업(불필요한 장문 설명 금지)
104
+ 2) 시작 즉시 등록:
105
+ ${bridgePath} register --agent ${agentId} --cli ${cli} --topics lead.control,task.result
106
+ 3) 주기적으로 수신함 확인:
107
+ ${bridgePath} context --agent ${agentId} --max 10
108
+ 4) lead.control 수신 시 즉시 반응 (interrupt/stop/pause/resume)
109
+ 5) 완료 시 결과 발행:
110
+ ${bridgePath} result --agent ${agentId} --topic task.result --file <출력파일>
111
+
112
+ 지금 작업을 시작하라.`;
113
+ }
114
+
115
+ /**
116
+ * 팀 오케스트레이션 실행 — 각 pane에 프롬프트 주입
117
+ * @param {string} sessionName — tmux 세션 이름
118
+ * @param {Array<{target: string, cli: string, subtask: string}>} assignments
119
+ * @param {object} opts
120
+ * @param {string} opts.hubUrl — Hub URL
121
+ * @param {{target:string, cli:string, task:string}|null} opts.lead
122
+ * @param {string} opts.teammateMode
123
+ * @returns {Promise<void>}
124
+ */
125
+ export async function orchestrate(sessionName, assignments, opts = {}) {
126
+ const {
127
+ hubUrl = "http://127.0.0.1:27888/mcp",
128
+ lead = null,
129
+ teammateMode = "tmux",
130
+ } = opts;
131
+
132
+ const workers = assignments.map(({ target, cli, subtask }) => ({
133
+ target,
134
+ cli,
135
+ subtask,
136
+ agentId: `${cli}-${target.split(".").pop()}`,
137
+ }));
138
+
139
+ if (lead?.target) {
140
+ const leadAgentId = `${lead.cli || "claude"}-${lead.target.split(".").pop()}`;
141
+ const leadPrompt = buildLeadPrompt(lead.task || "팀 작업 조율", {
142
+ agentId: leadAgentId,
143
+ hubUrl,
144
+ teammateMode,
145
+ workers: workers.map((w) => ({ agentId: w.agentId, cli: w.cli, subtask: w.subtask })),
146
+ });
147
+ injectPrompt(lead.target, leadPrompt, { useFileRef: true });
148
+ await new Promise((r) => setTimeout(r, 100));
149
+ }
150
+
151
+ for (const worker of workers) {
152
+ const prompt = buildPrompt(worker.subtask, {
153
+ cli: worker.cli,
154
+ agentId: worker.agentId,
155
+ hubUrl,
156
+ sessionName,
157
+ });
158
+ injectPrompt(worker.target, prompt, { useFileRef: true });
159
+ await new Promise((r) => setTimeout(r, 100));
160
+ }
161
+ }
@@ -0,0 +1,153 @@
1
+ // hub/team/pane.mjs — pane별 CLI 실행 + stdin 주입
2
+ // 의존성: child_process, fs, os, path (Node.js 내장)만 사용
3
+ import { writeFileSync, unlinkSync, mkdirSync } from "node:fs";
4
+ import { join } from "node:path";
5
+ import { tmpdir } from "node:os";
6
+ import { detectMultiplexer, tmuxExec } from "./session.mjs";
7
+ import { psmuxExec } from "./psmux.mjs";
8
+
9
+ import { buildExecArgs } from "../codex-adapter.mjs";
10
+
11
+ function quoteArg(value) {
12
+ return `"${String(value).replace(/"/g, '\\"')}"`;
13
+ }
14
+
15
+ function getPsmuxSessionName(target) {
16
+ return String(target).split(":")[0]?.trim() || "";
17
+ }
18
+
19
+ /** Windows 경로를 멀티플렉서용 경로로 변환 */
20
+ function toMuxPath(p) {
21
+ if (process.platform !== "win32") return p;
22
+
23
+ const mux = detectMultiplexer();
24
+
25
+ // psmux는 Windows 네이티브 경로 그대로 사용
26
+ if (mux === "psmux") return p;
27
+
28
+ const normalized = p.replace(/\\/g, "/");
29
+ const m = normalized.match(/^([A-Za-z]):\/(.*)$/);
30
+ if (!m) return normalized;
31
+
32
+ const drive = m[1].toLowerCase();
33
+ const rest = m[2];
34
+
35
+ // wsl tmux는 /mnt/c/... 경로를 사용
36
+ if (mux === "wsl-tmux") {
37
+ return `/mnt/${drive}/${rest}`;
38
+ }
39
+
40
+ // Git Bash/MSYS tmux는 /c/... 경로를 사용
41
+ return `/${drive}/${rest}`;
42
+ }
43
+
44
+ /** 멀티플렉서 커맨드 실행 (session.mjs와 동일 패턴) */
45
+ function muxExec(args, opts = {}) {
46
+ const exec = detectMultiplexer() === "psmux" ? psmuxExec : tmuxExec;
47
+ return exec(args, {
48
+ encoding: "utf8",
49
+ timeout: 10000,
50
+ stdio: ["pipe", "pipe", "pipe"],
51
+ windowsHide: true,
52
+ ...opts,
53
+ });
54
+ }
55
+
56
+ /**
57
+ * CLI 에이전트 시작 커맨드 생성
58
+ * @param {'codex'|'gemini'|'claude'} cli
59
+ * @param {{ trustMode?: boolean }} [options]
60
+ * @returns {string} 실행할 셸 커맨드
61
+ */
62
+ export function buildCliCommand(cli, options = {}) {
63
+ const { trustMode = false } = options;
64
+
65
+ switch (cli) {
66
+ case "codex":
67
+ // trust 모드에서는 exec 서브커맨드 기반 명령을 사용
68
+ if (trustMode) {
69
+ return buildExecArgs({});
70
+ }
71
+ return "codex";
72
+ case "gemini":
73
+ // interactive 모드 — MCP는 ~/.gemini/settings.json에 사전 등록
74
+ return "gemini";
75
+ case "claude":
76
+ // interactive 모드
77
+ return "claude";
78
+ default:
79
+ return cli; // 커스텀 CLI 허용
80
+ }
81
+ }
82
+
83
+ /**
84
+ * pane에 CLI 시작
85
+ * @param {string} target — 예: tfx-multi-abc:0.1
86
+ * @param {string} command — 실행할 커맨드
87
+ */
88
+ export function startCliInPane(target, command) {
89
+ // CLI 시작도 buffer paste를 재사용해 셸/플랫폼별 quoting 차이를 제거한다.
90
+ injectPrompt(target, command);
91
+ }
92
+
93
+ /**
94
+ * pane에 프롬프트 주입 (load-buffer + paste-buffer 방식)
95
+ * 멀티라인 + 특수문자 안전, 크기 제한 없음
96
+ * @param {string} target — 예: tfx-multi-abc:0.1
97
+ * @param {string} prompt — 주입할 텍스트
98
+ */
99
+ /**
100
+ * pane에 프롬프트 주입
101
+ * @param {string} target — 예: tfx-multi-abc:0.1
102
+ * @param {string} prompt — 주입할 텍스트
103
+ * @param {object} [opts]
104
+ * @param {boolean} [opts.useFileRef] — true면 TUI용 @file 참조 방식 (psmux 전용)
105
+ */
106
+ export function injectPrompt(target, prompt, { useFileRef = false } = {}) {
107
+ const tmpDir = join(tmpdir(), "tfx-multi");
108
+ mkdirSync(tmpDir, { recursive: true });
109
+
110
+ const safeTarget = target.replace(/[:.]/g, "-");
111
+ const tmpFile = join(tmpDir, `prompt-${safeTarget}-${Date.now()}.txt`);
112
+
113
+ // psmux + TUI 앱: @file 참조로 주입 (paste-buffer는 TUI와 호환 안 됨)
114
+ if (detectMultiplexer() === "psmux" && useFileRef) {
115
+ writeFileSync(tmpFile, prompt, "utf8");
116
+ const filePath = tmpFile.replace(/\\/g, "/");
117
+ psmuxExec(["select-pane", "-t", target]);
118
+ psmuxExec(["send-keys", "-t", target, "-l", `@${filePath}`]);
119
+ psmuxExec(["send-keys", "-t", target, "Enter"]);
120
+ // TUI가 파일을 읽을 시간을 주고 정리
121
+ setTimeout(() => { try { unlinkSync(tmpFile); } catch {} }, 10000);
122
+ return;
123
+ }
124
+
125
+ try {
126
+ writeFileSync(tmpFile, prompt, "utf8");
127
+
128
+ if (detectMultiplexer() === "psmux") {
129
+ const sessionName = getPsmuxSessionName(target);
130
+ psmuxExec(["load-buffer", "-t", sessionName, toMuxPath(tmpFile)]);
131
+ psmuxExec(["select-pane", "-t", target]);
132
+ psmuxExec(["paste-buffer", "-t", target]);
133
+ psmuxExec(["send-keys", "-t", target, "Enter"]);
134
+ return;
135
+ }
136
+
137
+ // tmux load-buffer → paste-buffer → Enter
138
+ muxExec(`load-buffer ${quoteArg(toMuxPath(tmpFile))}`);
139
+ muxExec(`paste-buffer -t ${target}`);
140
+ muxExec(`send-keys -t ${target} Enter`);
141
+ } finally {
142
+ try { unlinkSync(tmpFile); } catch {}
143
+ }
144
+ }
145
+
146
+ /**
147
+ * pane에 키 입력 전송
148
+ * @param {string} target — 예: tfx-multi-abc:0.1
149
+ * @param {string} keys — tmux 키 표현 (예: 'C-c', 'Enter')
150
+ */
151
+ export function sendKeys(target, keys) {
152
+ muxExec(`send-keys -t ${target} ${keys}`);
153
+ }