triflux 10.3.2 → 10.3.3

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 (55) hide show
  1. package/.claude-plugin/plugin.json +22 -22
  2. package/LICENSE +21 -21
  3. package/README.ko.md +16 -0
  4. package/README.md +8 -0
  5. package/hooks/hook-registry.json +256 -256
  6. package/hub/adaptive-inject.mjs +1 -1
  7. package/hub/assign-callbacks.mjs +120 -120
  8. package/hub/delegator/index.mjs +14 -14
  9. package/hub/delegator/tool-definitions.mjs +35 -35
  10. package/hub/hitl.mjs +143 -143
  11. package/hub/router.mjs +791 -791
  12. package/hub/session-fingerprint.mjs +1 -1
  13. package/hub/team/cli/commands/attach.mjs +37 -37
  14. package/hub/team/cli/commands/debug.mjs +74 -74
  15. package/hub/team/cli/commands/focus.mjs +53 -53
  16. package/hub/team/cli/commands/list.mjs +24 -24
  17. package/hub/team/cli/commands/start/start-in-process.mjs +40 -40
  18. package/hub/team/cli/commands/start/start-mux.mjs +73 -73
  19. package/hub/team/cli/commands/start/start-wt.mjs +69 -69
  20. package/hub/team/cli/commands/tasks.mjs +13 -13
  21. package/hub/team/cli/render.mjs +30 -30
  22. package/hub/team/cli/services/attach-fallback.mjs +54 -54
  23. package/hub/team/cli/services/member-selector.mjs +30 -30
  24. package/hub/team/cli/services/native-control.mjs +116 -116
  25. package/hub/team/cli/services/task-model.mjs +30 -30
  26. package/hub/team/notify.mjs +1 -1
  27. package/hub/team/orchestrator.mjs +161 -161
  28. package/hub/team/session.mjs +611 -611
  29. package/hub/team/shared.mjs +13 -13
  30. package/hub/tray.mjs +368 -368
  31. package/hub/workers/codex-mcp.mjs +507 -507
  32. package/hub/workers/factory.mjs +21 -21
  33. package/mesh/index.mjs +63 -0
  34. package/mesh/mesh-budget.mjs +128 -0
  35. package/mesh/mesh-heartbeat.mjs +100 -0
  36. package/mesh/mesh-protocol.mjs +96 -0
  37. package/mesh/mesh-queue.mjs +165 -0
  38. package/mesh/mesh-registry.mjs +78 -0
  39. package/mesh/mesh-router.mjs +76 -0
  40. package/package.json +2 -1
  41. package/scripts/completions/tfx.bash +47 -47
  42. package/scripts/completions/tfx.fish +44 -44
  43. package/scripts/completions/tfx.zsh +83 -83
  44. package/scripts/hub-ensure.mjs +120 -120
  45. package/scripts/keyword-detector.mjs +272 -272
  46. package/scripts/keyword-rules-expander.mjs +521 -521
  47. package/scripts/lib/mcp-server-catalog.mjs +118 -118
  48. package/scripts/notion-read.mjs +553 -553
  49. package/scripts/test-tfx-route-no-claude-native.mjs +57 -57
  50. package/scripts/tfx-batch-stats.mjs +96 -96
  51. package/skills/.omc/state/agent-replay-8f0e10a9-9693-4410-96f5-a6b07e8ed995.jsonl +0 -1
  52. package/skills/.omc/state/idle-notif-cooldown.json +0 -3
  53. package/skills/.omc/state/last-tool-error.json +0 -7
  54. package/skills/.omc/state/subagent-tracking.json +0 -7
  55. package/skills/tfx-remote-spawn/references/hosts.json +0 -16
@@ -1,69 +1,69 @@
1
- import { createWtSession } from "../../../session.mjs";
2
- import { buildCliCommand } from "../../../pane.mjs";
3
- import { toAgentId } from "../../services/member-selector.mjs";
4
- import { buildTasks } from "../../services/task-model.mjs";
5
- import { warn } from "../../render.mjs";
6
-
7
- export async function startWtTeam({ sessionId, task, lead, agents, subtasks, layout, hubUrl }) {
8
- const paneCount = agents.length + 1;
9
- const effectiveLayout = layout === "Nx1" ? "Nx1" : "1xN";
10
- if (layout !== effectiveLayout) warn(`wt 모드에서 ${layout} 레이아웃은 미지원 — ${effectiveLayout}로 대체`);
11
- console.log(` 레이아웃: ${effectiveLayout} (${paneCount} panes)`);
12
-
13
- const session = createWtSession(sessionId, {
14
- layout: effectiveLayout,
15
- paneCommands: [
16
- { title: `${sessionId}-lead`, command: buildCliCommand(lead) },
17
- ...agents.map((cli, index) => ({
18
- title: `${sessionId}-${cli}-${index + 1}`,
19
- command: buildCliCommand(cli),
20
- })),
21
- ],
22
- });
23
-
24
- const members = [
25
- {
26
- role: "lead",
27
- name: "lead",
28
- cli: lead,
29
- pane: session.panes[0] || "wt:0",
30
- agentId: toAgentId(lead, session.panes[0] || "wt:0"),
31
- },
32
- ...agents.map((cli, index) => {
33
- const pane = session.panes[index + 1] || `wt:${index + 1}`;
34
- return {
35
- role: "worker",
36
- name: `${cli}-${index + 1}`,
37
- cli,
38
- pane,
39
- subtask: subtasks[index],
40
- agentId: toAgentId(cli, pane),
41
- };
42
- }),
43
- ];
44
-
45
- return {
46
- sessionName: sessionId,
47
- task,
48
- lead,
49
- agents,
50
- layout: effectiveLayout,
51
- teammateMode: "wt",
52
- startedAt: Date.now(),
53
- hubUrl,
54
- members,
55
- panes: Object.fromEntries(members.map((member) => [member.pane, {
56
- role: member.role,
57
- name: member.name,
58
- cli: member.cli,
59
- agentId: member.agentId,
60
- subtask: member.subtask || null,
61
- }])),
62
- tasks: buildTasks(subtasks, members.filter((member) => member.role === "worker")),
63
- wt: {
64
- windowId: 0,
65
- layout: effectiveLayout,
66
- paneCount: session.paneCount,
67
- },
68
- };
69
- }
1
+ import { createWtSession } from "../../../session.mjs";
2
+ import { buildCliCommand } from "../../../pane.mjs";
3
+ import { toAgentId } from "../../services/member-selector.mjs";
4
+ import { buildTasks } from "../../services/task-model.mjs";
5
+ import { warn } from "../../render.mjs";
6
+
7
+ export async function startWtTeam({ sessionId, task, lead, agents, subtasks, layout, hubUrl }) {
8
+ const paneCount = agents.length + 1;
9
+ const effectiveLayout = layout === "Nx1" ? "Nx1" : "1xN";
10
+ if (layout !== effectiveLayout) warn(`wt 모드에서 ${layout} 레이아웃은 미지원 — ${effectiveLayout}로 대체`);
11
+ console.log(` 레이아웃: ${effectiveLayout} (${paneCount} panes)`);
12
+
13
+ const session = createWtSession(sessionId, {
14
+ layout: effectiveLayout,
15
+ paneCommands: [
16
+ { title: `${sessionId}-lead`, command: buildCliCommand(lead) },
17
+ ...agents.map((cli, index) => ({
18
+ title: `${sessionId}-${cli}-${index + 1}`,
19
+ command: buildCliCommand(cli),
20
+ })),
21
+ ],
22
+ });
23
+
24
+ const members = [
25
+ {
26
+ role: "lead",
27
+ name: "lead",
28
+ cli: lead,
29
+ pane: session.panes[0] || "wt:0",
30
+ agentId: toAgentId(lead, session.panes[0] || "wt:0"),
31
+ },
32
+ ...agents.map((cli, index) => {
33
+ const pane = session.panes[index + 1] || `wt:${index + 1}`;
34
+ return {
35
+ role: "worker",
36
+ name: `${cli}-${index + 1}`,
37
+ cli,
38
+ pane,
39
+ subtask: subtasks[index],
40
+ agentId: toAgentId(cli, pane),
41
+ };
42
+ }),
43
+ ];
44
+
45
+ return {
46
+ sessionName: sessionId,
47
+ task,
48
+ lead,
49
+ agents,
50
+ layout: effectiveLayout,
51
+ teammateMode: "wt",
52
+ startedAt: Date.now(),
53
+ hubUrl,
54
+ members,
55
+ panes: Object.fromEntries(members.map((member) => [member.pane, {
56
+ role: member.role,
57
+ name: member.name,
58
+ cli: member.cli,
59
+ agentId: member.agentId,
60
+ subtask: member.subtask || null,
61
+ }])),
62
+ tasks: buildTasks(subtasks, members.filter((member) => member.role === "worker")),
63
+ wt: {
64
+ windowId: 0,
65
+ layout: effectiveLayout,
66
+ paneCount: session.paneCount,
67
+ },
68
+ };
69
+ }
@@ -1,13 +1,13 @@
1
- import { DIM, RESET } from "../../shared.mjs";
2
- import { isTeamAlive } from "../services/runtime-mode.mjs";
3
- import { loadTeamState } from "../services/state-store.mjs";
4
- import { renderTasks } from "../render.mjs";
5
-
6
- export function teamTasks() {
7
- const state = loadTeamState();
8
- if (!state || !isTeamAlive(state)) {
9
- console.log(`\n ${DIM}활성 팀 세션 없음${RESET}\n`);
10
- return;
11
- }
12
- renderTasks(state.tasks || []);
13
- }
1
+ import { DIM, RESET } from "../../shared.mjs";
2
+ import { isTeamAlive } from "../services/runtime-mode.mjs";
3
+ import { loadTeamState } from "../services/state-store.mjs";
4
+ import { renderTasks } from "../render.mjs";
5
+
6
+ export function teamTasks() {
7
+ const state = loadTeamState();
8
+ if (!state || !isTeamAlive(state)) {
9
+ console.log(`\n ${DIM}활성 팀 세션 없음${RESET}\n`);
10
+ return;
11
+ }
12
+ renderTasks(state.tasks || []);
13
+ }
@@ -1,30 +1,30 @@
1
- import { AMBER, BOLD, DIM, GRAY, GREEN, RED, RESET, WHITE, YELLOW } from "../shared.mjs";
2
-
3
- export function ok(msg) { console.log(` ${GREEN}✓${RESET} ${msg}`); }
4
- export function warn(msg) { console.log(` ${YELLOW}⚠${RESET} ${msg}`); }
5
- export function fail(msg) { console.log(` ${RED}✗${RESET} ${msg}`); }
6
-
7
- export function renderTasks(tasks = []) {
8
- if (!tasks.length) {
9
- console.log(`\n ${DIM}태스크 없음${RESET}\n`);
10
- return;
11
- }
12
-
13
- console.log(`\n ${AMBER}${BOLD}⬡ Team Tasks${RESET}\n`);
14
- for (const task of tasks) {
15
- const dep = task.depends_on?.length ? ` ${DIM}(deps: ${task.depends_on.join(",")})${RESET}` : "";
16
- const owner = task.owner ? ` ${GRAY}[${task.owner}]${RESET}` : "";
17
- console.log(` ${WHITE}${task.id}${RESET} ${String(task.status || "").padEnd(11)} ${task.title}${owner}${dep}`);
18
- }
19
- console.log("");
20
- }
21
-
22
- export function formatCompletionSuffix(member) {
23
- if (!member?.completionStatus) return "";
24
- if (member.completionStatus === "abnormal") {
25
- return ` ${RED}[abnormal:${member.completionReason || "unknown"}]${RESET}`;
26
- }
27
- if (member.completionStatus === "normal") return ` ${GREEN}[route-ok]${RESET}`;
28
- if (member.completionStatus === "unchecked") return ` ${GRAY}[route-unchecked]${RESET}`;
29
- return "";
30
- }
1
+ import { AMBER, BOLD, DIM, GRAY, GREEN, RED, RESET, WHITE, YELLOW } from "../shared.mjs";
2
+
3
+ export function ok(msg) { console.log(` ${GREEN}✓${RESET} ${msg}`); }
4
+ export function warn(msg) { console.log(` ${YELLOW}⚠${RESET} ${msg}`); }
5
+ export function fail(msg) { console.log(` ${RED}✗${RESET} ${msg}`); }
6
+
7
+ export function renderTasks(tasks = []) {
8
+ if (!tasks.length) {
9
+ console.log(`\n ${DIM}태스크 없음${RESET}\n`);
10
+ return;
11
+ }
12
+
13
+ console.log(`\n ${AMBER}${BOLD}⬡ Team Tasks${RESET}\n`);
14
+ for (const task of tasks) {
15
+ const dep = task.depends_on?.length ? ` ${DIM}(deps: ${task.depends_on.join(",")})${RESET}` : "";
16
+ const owner = task.owner ? ` ${GRAY}[${task.owner}]${RESET}` : "";
17
+ console.log(` ${WHITE}${task.id}${RESET} ${String(task.status || "").padEnd(11)} ${task.title}${owner}${dep}`);
18
+ }
19
+ console.log("");
20
+ }
21
+
22
+ export function formatCompletionSuffix(member) {
23
+ if (!member?.completionStatus) return "";
24
+ if (member.completionStatus === "abnormal") {
25
+ return ` ${RED}[abnormal:${member.completionReason || "unknown"}]${RESET}`;
26
+ }
27
+ if (member.completionStatus === "normal") return ` ${GREEN}[route-ok]${RESET}`;
28
+ if (member.completionStatus === "unchecked") return ` ${GRAY}[route-unchecked]${RESET}`;
29
+ return "";
30
+ }
@@ -1,54 +1,54 @@
1
- import { spawn } from "node:child_process";
2
-
3
- import {
4
- getSessionAttachedCount,
5
- hasWindowsTerminal,
6
- resolveAttachCommand,
7
- } from "../../session.mjs";
8
- import { PKG_ROOT } from "./state-store.mjs";
9
-
10
- export async function launchAttachInWindowsTerminal(sessionName) {
11
- if (!hasWindowsTerminal()) return false;
12
-
13
- let attachSpec;
14
- try {
15
- attachSpec = resolveAttachCommand(sessionName);
16
- } catch {
17
- return false;
18
- }
19
-
20
- const beforeAttached = getSessionAttachedCount(sessionName);
21
- try {
22
- const child = spawn("wt", ["-w", "0", "split-pane", "-V", "-d", PKG_ROOT, attachSpec.command, ...attachSpec.args], {
23
- detached: true,
24
- stdio: "ignore",
25
- windowsHide: false,
26
- });
27
- child.unref();
28
-
29
- if (beforeAttached == null) return true;
30
- const deadline = Date.now() + 3500;
31
- while (Date.now() < deadline) {
32
- await new Promise((resolve) => setTimeout(resolve, 120));
33
- const nowAttached = getSessionAttachedCount(sessionName);
34
- if (typeof nowAttached === "number" && nowAttached > beforeAttached) return true;
35
- }
36
- } catch {}
37
- return false;
38
- }
39
-
40
- export function buildManualAttachCommand(sessionName) {
41
- try {
42
- const spec = resolveAttachCommand(sessionName);
43
- return [spec.command, ...spec.args].map((value) => {
44
- const text = String(value);
45
- return /\s/.test(text) ? `"${text.replace(/"/g, '\\"')}"` : text;
46
- }).join(" ");
47
- } catch {
48
- return `tmux attach-session -t ${sessionName}`;
49
- }
50
- }
51
-
52
- export function wantsWtAttachFallback(args = [], env = process.env) {
53
- return args.includes("--wt") || args.includes("--spawn-wt") || env.TFX_ATTACH_WT_AUTO === "1";
54
- }
1
+ import { spawn } from "node:child_process";
2
+
3
+ import {
4
+ getSessionAttachedCount,
5
+ hasWindowsTerminal,
6
+ resolveAttachCommand,
7
+ } from "../../session.mjs";
8
+ import { PKG_ROOT } from "./state-store.mjs";
9
+
10
+ export async function launchAttachInWindowsTerminal(sessionName) {
11
+ if (!hasWindowsTerminal()) return false;
12
+
13
+ let attachSpec;
14
+ try {
15
+ attachSpec = resolveAttachCommand(sessionName);
16
+ } catch {
17
+ return false;
18
+ }
19
+
20
+ const beforeAttached = getSessionAttachedCount(sessionName);
21
+ try {
22
+ const child = spawn("wt", ["-w", "0", "split-pane", "-V", "-d", PKG_ROOT, attachSpec.command, ...attachSpec.args], {
23
+ detached: true,
24
+ stdio: "ignore",
25
+ windowsHide: false,
26
+ });
27
+ child.unref();
28
+
29
+ if (beforeAttached == null) return true;
30
+ const deadline = Date.now() + 3500;
31
+ while (Date.now() < deadline) {
32
+ await new Promise((resolve) => setTimeout(resolve, 120));
33
+ const nowAttached = getSessionAttachedCount(sessionName);
34
+ if (typeof nowAttached === "number" && nowAttached > beforeAttached) return true;
35
+ }
36
+ } catch {}
37
+ return false;
38
+ }
39
+
40
+ export function buildManualAttachCommand(sessionName) {
41
+ try {
42
+ const spec = resolveAttachCommand(sessionName);
43
+ return [spec.command, ...spec.args].map((value) => {
44
+ const text = String(value);
45
+ return /\s/.test(text) ? `"${text.replace(/"/g, '\\"')}"` : text;
46
+ }).join(" ");
47
+ } catch {
48
+ return `tmux attach-session -t ${sessionName}`;
49
+ }
50
+ }
51
+
52
+ export function wantsWtAttachFallback(args = [], env = process.env) {
53
+ return args.includes("--wt") || args.includes("--spawn-wt") || env.TFX_ATTACH_WT_AUTO === "1";
54
+ }
@@ -1,30 +1,30 @@
1
- export function resolveMember(state, selector) {
2
- const members = state?.members || [];
3
- if (!selector) return null;
4
-
5
- const direct = members.find((member) => (
6
- member.name === selector || member.role === selector || member.agentId === selector
7
- ));
8
- if (direct) return direct;
9
-
10
- const workerAlias = /^worker-(\d+)$/i.exec(selector);
11
- if (workerAlias) {
12
- const index = parseInt(workerAlias[1], 10) - 1;
13
- const workers = members.filter((member) => member.role === "worker");
14
- if (index >= 0 && index < workers.length) return workers[index];
15
- }
16
-
17
- const numeric = parseInt(selector, 10);
18
- if (!Number.isNaN(numeric)) {
19
- const byPane = members.find((member) => member.pane?.endsWith(`.${numeric}`) || member.pane?.endsWith(`:${numeric}`));
20
- if (byPane) return byPane;
21
- if (numeric >= 1 && numeric <= members.length) return members[numeric - 1];
22
- }
23
-
24
- return null;
25
- }
26
-
27
- export function toAgentId(cli, target) {
28
- const suffix = String(target).split(/[:.]/).pop();
29
- return `${cli}-${suffix}`;
30
- }
1
+ export function resolveMember(state, selector) {
2
+ const members = state?.members || [];
3
+ if (!selector) return null;
4
+
5
+ const direct = members.find((member) => (
6
+ member.name === selector || member.role === selector || member.agentId === selector
7
+ ));
8
+ if (direct) return direct;
9
+
10
+ const workerAlias = /^worker-(\d+)$/i.exec(selector);
11
+ if (workerAlias) {
12
+ const index = parseInt(workerAlias[1], 10) - 1;
13
+ const workers = members.filter((member) => member.role === "worker");
14
+ if (index >= 0 && index < workers.length) return workers[index];
15
+ }
16
+
17
+ const numeric = parseInt(selector, 10);
18
+ if (!Number.isNaN(numeric)) {
19
+ const byPane = members.find((member) => member.pane?.endsWith(`.${numeric}`) || member.pane?.endsWith(`:${numeric}`));
20
+ if (byPane) return byPane;
21
+ if (numeric >= 1 && numeric <= members.length) return members[numeric - 1];
22
+ }
23
+
24
+ return null;
25
+ }
26
+
27
+ export function toAgentId(cli, target) {
28
+ const suffix = String(target).split(/[:.]/).pop();
29
+ return `${cli}-${suffix}`;
30
+ }
@@ -1,117 +1,117 @@
1
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
- import { join } from "node:path";
3
- import { spawn } from "node:child_process";
4
-
5
- import { buildLeadPrompt, buildPrompt } from "../../orchestrator.mjs";
6
- import { HUB_PID_DIR, PKG_ROOT } from "./state-store.mjs";
7
-
8
- import { buildExecArgs } from "../../../codex-adapter.mjs";
9
-
10
- export function buildNativeCliCommand(cli) {
11
- switch (cli) {
12
- case "codex":
13
- return buildExecArgs({});
14
- case "gemini":
15
- return "gemini";
16
- case "claude":
17
- return "claude";
18
- default:
19
- return cli;
20
- }
21
- }
22
-
23
- export async function startNativeSupervisor({ sessionId, task, lead, agents, subtasks, hubUrl }) {
24
- const configPath = join(HUB_PID_DIR, `team-native-${sessionId}.config.json`);
25
- const runtimePath = join(HUB_PID_DIR, `team-native-${sessionId}.runtime.json`);
26
- const logsDir = join(HUB_PID_DIR, "team-logs", sessionId);
27
- mkdirSync(logsDir, { recursive: true });
28
-
29
- const leadMember = {
30
- role: "lead",
31
- name: "lead",
32
- cli: lead,
33
- agentId: `${lead}-lead`,
34
- command: buildNativeCliCommand(lead),
35
- };
36
- const workers = agents.map((cli, index) => ({
37
- role: "worker",
38
- name: `${cli}-${index + 1}`,
39
- cli,
40
- agentId: `${cli}-w${index + 1}`,
41
- command: buildNativeCliCommand(cli),
42
- subtask: subtasks[index],
43
- }));
44
- const members = [
45
- {
46
- ...leadMember,
47
- prompt: buildLeadPrompt(task, {
48
- agentId: leadMember.agentId,
49
- hubUrl,
50
- teammateMode: "in-process",
51
- workers: workers.map((worker) => ({
52
- agentId: worker.agentId,
53
- cli: worker.cli,
54
- subtask: worker.subtask,
55
- })),
56
- }),
57
- },
58
- ...workers.map((worker) => ({
59
- ...worker,
60
- prompt: buildPrompt(worker.subtask, { cli: worker.cli, agentId: worker.agentId, hubUrl }),
61
- })),
62
- ];
63
-
64
- writeFileSync(configPath, JSON.stringify({
65
- sessionName: sessionId,
66
- hubUrl,
67
- startupDelayMs: 3000,
68
- logsDir,
69
- runtimeFile: runtimePath,
70
- members,
71
- }, null, 2) + "\n");
72
-
73
- const child = spawn(process.execPath, [join(PKG_ROOT, "hub", "team", "native-supervisor.mjs"), "--config", configPath], {
74
- detached: true,
75
- stdio: "ignore",
76
- env: { ...process.env },
77
- windowsHide: true,
78
- });
79
- child.unref();
80
-
81
- const deadline = Date.now() + 5000;
82
- while (Date.now() < deadline) {
83
- if (existsSync(runtimePath)) {
84
- try {
85
- const runtime = JSON.parse(readFileSync(runtimePath, "utf8"));
86
- return { runtime, members };
87
- } catch {}
88
- }
89
- await new Promise((resolve) => setTimeout(resolve, 100));
90
- }
91
-
92
- return { runtime: null, members };
93
- }
94
-
95
- export async function nativeRequest(state, path, body = {}) {
96
- if (!state?.native?.controlUrl) return null;
97
- try {
98
- const res = await fetch(`${state.native.controlUrl}${path}`, {
99
- method: "POST",
100
- headers: { "Content-Type": "application/json" },
101
- body: JSON.stringify(body),
102
- });
103
- return await res.json();
104
- } catch {
105
- return null;
106
- }
107
- }
108
-
109
- export async function nativeGetStatus(state) {
110
- if (!state?.native?.controlUrl) return null;
111
- try {
112
- const res = await fetch(`${state.native.controlUrl}/status`);
113
- return await res.json();
114
- } catch {
115
- return null;
116
- }
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { spawn } from "node:child_process";
4
+
5
+ import { buildLeadPrompt, buildPrompt } from "../../orchestrator.mjs";
6
+ import { HUB_PID_DIR, PKG_ROOT } from "./state-store.mjs";
7
+
8
+ import { buildExecArgs } from "../../../codex-adapter.mjs";
9
+
10
+ export function buildNativeCliCommand(cli) {
11
+ switch (cli) {
12
+ case "codex":
13
+ return buildExecArgs({});
14
+ case "gemini":
15
+ return "gemini";
16
+ case "claude":
17
+ return "claude";
18
+ default:
19
+ return cli;
20
+ }
21
+ }
22
+
23
+ export async function startNativeSupervisor({ sessionId, task, lead, agents, subtasks, hubUrl }) {
24
+ const configPath = join(HUB_PID_DIR, `team-native-${sessionId}.config.json`);
25
+ const runtimePath = join(HUB_PID_DIR, `team-native-${sessionId}.runtime.json`);
26
+ const logsDir = join(HUB_PID_DIR, "team-logs", sessionId);
27
+ mkdirSync(logsDir, { recursive: true });
28
+
29
+ const leadMember = {
30
+ role: "lead",
31
+ name: "lead",
32
+ cli: lead,
33
+ agentId: `${lead}-lead`,
34
+ command: buildNativeCliCommand(lead),
35
+ };
36
+ const workers = agents.map((cli, index) => ({
37
+ role: "worker",
38
+ name: `${cli}-${index + 1}`,
39
+ cli,
40
+ agentId: `${cli}-w${index + 1}`,
41
+ command: buildNativeCliCommand(cli),
42
+ subtask: subtasks[index],
43
+ }));
44
+ const members = [
45
+ {
46
+ ...leadMember,
47
+ prompt: buildLeadPrompt(task, {
48
+ agentId: leadMember.agentId,
49
+ hubUrl,
50
+ teammateMode: "in-process",
51
+ workers: workers.map((worker) => ({
52
+ agentId: worker.agentId,
53
+ cli: worker.cli,
54
+ subtask: worker.subtask,
55
+ })),
56
+ }),
57
+ },
58
+ ...workers.map((worker) => ({
59
+ ...worker,
60
+ prompt: buildPrompt(worker.subtask, { cli: worker.cli, agentId: worker.agentId, hubUrl }),
61
+ })),
62
+ ];
63
+
64
+ writeFileSync(configPath, JSON.stringify({
65
+ sessionName: sessionId,
66
+ hubUrl,
67
+ startupDelayMs: 3000,
68
+ logsDir,
69
+ runtimeFile: runtimePath,
70
+ members,
71
+ }, null, 2) + "\n");
72
+
73
+ const child = spawn(process.execPath, [join(PKG_ROOT, "hub", "team", "native-supervisor.mjs"), "--config", configPath], {
74
+ detached: true,
75
+ stdio: "ignore",
76
+ env: { ...process.env },
77
+ windowsHide: true,
78
+ });
79
+ child.unref();
80
+
81
+ const deadline = Date.now() + 5000;
82
+ while (Date.now() < deadline) {
83
+ if (existsSync(runtimePath)) {
84
+ try {
85
+ const runtime = JSON.parse(readFileSync(runtimePath, "utf8"));
86
+ return { runtime, members };
87
+ } catch {}
88
+ }
89
+ await new Promise((resolve) => setTimeout(resolve, 100));
90
+ }
91
+
92
+ return { runtime: null, members };
93
+ }
94
+
95
+ export async function nativeRequest(state, path, body = {}) {
96
+ if (!state?.native?.controlUrl) return null;
97
+ try {
98
+ const res = await fetch(`${state.native.controlUrl}${path}`, {
99
+ method: "POST",
100
+ headers: { "Content-Type": "application/json" },
101
+ body: JSON.stringify(body),
102
+ });
103
+ return await res.json();
104
+ } catch {
105
+ return null;
106
+ }
107
+ }
108
+
109
+ export async function nativeGetStatus(state) {
110
+ if (!state?.native?.controlUrl) return null;
111
+ try {
112
+ const res = await fetch(`${state.native.controlUrl}/status`);
113
+ return await res.json();
114
+ } catch {
115
+ return null;
116
+ }
117
117
  }