triflux 10.3.0 → 10.3.2

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 (56) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +22 -22
  3. package/LICENSE +21 -21
  4. package/hooks/hook-registry.json +256 -256
  5. package/hub/adaptive-inject.mjs +1 -1
  6. package/hub/assign-callbacks.mjs +120 -120
  7. package/hub/delegator/index.mjs +14 -14
  8. package/hub/delegator/tool-definitions.mjs +35 -35
  9. package/hub/hitl.mjs +143 -143
  10. package/hub/router.mjs +791 -791
  11. package/hub/session-fingerprint.mjs +1 -1
  12. package/hub/team/ansi.mjs +44 -28
  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/conductor.mjs +2 -2
  27. package/hub/team/notify.mjs +1 -1
  28. package/hub/team/orchestrator.mjs +161 -161
  29. package/hub/team/session.mjs +611 -611
  30. package/hub/team/shared.mjs +13 -13
  31. package/hub/team/tui-lite.mjs +4 -4
  32. package/hub/team/tui.mjs +16 -12
  33. package/hub/tray.mjs +368 -368
  34. package/hub/workers/codex-mcp.mjs +507 -507
  35. package/hub/workers/factory.mjs +21 -21
  36. package/hud/constants.mjs +8 -2
  37. package/hud/providers/codex.mjs +11 -0
  38. package/hud/providers/gemini.mjs +21 -0
  39. package/package.json +1 -1
  40. package/scripts/claudemd-sync.mjs +11 -13
  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/setup.mjs +23 -0
  50. package/scripts/test-tfx-route-no-claude-native.mjs +57 -57
  51. package/scripts/tfx-batch-stats.mjs +96 -96
  52. package/skills/.omc/state/agent-replay-8f0e10a9-9693-4410-96f5-a6b07e8ed995.jsonl +1 -0
  53. package/skills/.omc/state/idle-notif-cooldown.json +3 -0
  54. package/skills/.omc/state/last-tool-error.json +7 -0
  55. package/skills/.omc/state/subagent-tracking.json +7 -0
  56. package/skills/tfx-remote-spawn/references/hosts.json +16 -0
@@ -1,73 +1,73 @@
1
- import { join } from "node:path";
2
-
3
- import { buildCliCommand, startCliInPane } from "../../../pane.mjs";
4
- import { orchestrate } from "../../../orchestrator.mjs";
5
- import { attachSession, configureTeammateKeybindings, createSession } from "../../../session.mjs";
6
- import { BOLD, DIM, GREEN, RESET } from "../../../shared.mjs";
7
- import { toAgentId } from "../../services/member-selector.mjs";
8
- import { PKG_ROOT, TEAM_PROFILE } from "../../services/state-store.mjs";
9
- import { buildTasks } from "../../services/task-model.mjs";
10
- import { ok, warn } from "../../render.mjs";
11
-
12
- export async function startMuxTeam({ sessionId, task, lead, agents, subtasks, layout, hubUrl, teammateMode }) {
13
- const paneCount = agents.length + 1;
14
- const effectiveLayout = paneCount <= 4 ? layout : (layout === "Nx1" ? "Nx1" : "1xN");
15
- console.log(` 레이아웃: ${effectiveLayout} (${paneCount} panes)`);
16
-
17
- const session = createSession(sessionId, { layout: effectiveLayout, paneCount });
18
- const leadTarget = session.panes[0];
19
- startCliInPane(leadTarget, buildCliCommand(lead));
20
-
21
- const members = [{ role: "lead", name: "lead", cli: lead, pane: leadTarget, agentId: toAgentId(lead, leadTarget) }];
22
- const assignments = [];
23
- for (let index = 0; index < agents.length; index += 1) {
24
- const cli = agents[index];
25
- const pane = session.panes[index + 1];
26
- startCliInPane(pane, buildCliCommand(cli));
27
- const worker = { role: "worker", name: `${cli}-${index + 1}`, cli, pane, subtask: subtasks[index], agentId: toAgentId(cli, pane) };
28
- members.push(worker);
29
- assignments.push({ target: pane, cli, subtask: subtasks[index] });
30
- }
31
-
32
- ok("CLI 초기화 대기 (3초)...");
33
- await new Promise((resolve) => setTimeout(resolve, 3000));
34
- await orchestrate(sessionId, assignments, { hubUrl, teammateMode, lead: { target: leadTarget, cli: lead, task } });
35
- ok("리드/워커 프롬프트 주입 완료");
36
-
37
- return {
38
- sessionName: sessionId,
39
- task,
40
- lead,
41
- agents,
42
- layout: effectiveLayout,
43
- teammateMode,
44
- startedAt: Date.now(),
45
- hubUrl,
46
- members,
47
- panes: Object.fromEntries(members.map((member) => [member.pane, {
48
- role: member.role,
49
- name: member.name,
50
- cli: member.cli,
51
- agentId: member.agentId,
52
- subtask: member.subtask || null,
53
- }])),
54
- tasks: buildTasks(subtasks, members.filter((member) => member.role === "worker")),
55
- postSave() {
56
- const profilePrefix = TEAM_PROFILE === "team" ? "" : `TFX_TEAM_PROFILE=${TEAM_PROFILE} `;
57
- const taskListCommand = `${profilePrefix}${process.execPath} ${join(PKG_ROOT, "bin", "triflux.mjs")} team tasks`;
58
- configureTeammateKeybindings(sessionId, { inProcess: false, taskListCommand });
59
- console.log(`\n ${GREEN}${BOLD}팀 세션 준비 완료${RESET}`);
60
- console.log(` ${DIM}Shift+Down: 다음 팀메이트 전환${RESET}`);
61
- console.log(` ${DIM}Shift+Tab / Shift+Left: 이전 팀메이트 전환${RESET}`);
62
- console.log(` ${DIM}Escape: 현재 팀메이트 인터럽트${RESET}`);
63
- console.log(` ${DIM}Ctrl+T: 태스크 목록${RESET}`);
64
- console.log(` ${DIM}참고: Shift+Up은 Claude Code 미지원 (scroll-up 충돌). Shift+Tab 사용${RESET}`);
65
- console.log(` ${DIM}Ctrl+B → D: 세션 분리 (백그라운드)${RESET}\n`);
66
- if (process.stdout.isTTY && process.stdin.isTTY) attachSession(sessionId);
67
- else {
68
- warn("TTY 미지원 환경이라 자동 attach를 생략함");
69
- console.log(` ${DIM}수동 연결: tfx multi attach${RESET}\n`);
70
- }
71
- },
72
- };
73
- }
1
+ import { join } from "node:path";
2
+
3
+ import { buildCliCommand, startCliInPane } from "../../../pane.mjs";
4
+ import { orchestrate } from "../../../orchestrator.mjs";
5
+ import { attachSession, configureTeammateKeybindings, createSession } from "../../../session.mjs";
6
+ import { BOLD, DIM, GREEN, RESET } from "../../../shared.mjs";
7
+ import { toAgentId } from "../../services/member-selector.mjs";
8
+ import { PKG_ROOT, TEAM_PROFILE } from "../../services/state-store.mjs";
9
+ import { buildTasks } from "../../services/task-model.mjs";
10
+ import { ok, warn } from "../../render.mjs";
11
+
12
+ export async function startMuxTeam({ sessionId, task, lead, agents, subtasks, layout, hubUrl, teammateMode }) {
13
+ const paneCount = agents.length + 1;
14
+ const effectiveLayout = paneCount <= 4 ? layout : (layout === "Nx1" ? "Nx1" : "1xN");
15
+ console.log(` 레이아웃: ${effectiveLayout} (${paneCount} panes)`);
16
+
17
+ const session = createSession(sessionId, { layout: effectiveLayout, paneCount });
18
+ const leadTarget = session.panes[0];
19
+ startCliInPane(leadTarget, buildCliCommand(lead));
20
+
21
+ const members = [{ role: "lead", name: "lead", cli: lead, pane: leadTarget, agentId: toAgentId(lead, leadTarget) }];
22
+ const assignments = [];
23
+ for (let index = 0; index < agents.length; index += 1) {
24
+ const cli = agents[index];
25
+ const pane = session.panes[index + 1];
26
+ startCliInPane(pane, buildCliCommand(cli));
27
+ const worker = { role: "worker", name: `${cli}-${index + 1}`, cli, pane, subtask: subtasks[index], agentId: toAgentId(cli, pane) };
28
+ members.push(worker);
29
+ assignments.push({ target: pane, cli, subtask: subtasks[index] });
30
+ }
31
+
32
+ ok("CLI 초기화 대기 (3초)...");
33
+ await new Promise((resolve) => setTimeout(resolve, 3000));
34
+ await orchestrate(sessionId, assignments, { hubUrl, teammateMode, lead: { target: leadTarget, cli: lead, task } });
35
+ ok("리드/워커 프롬프트 주입 완료");
36
+
37
+ return {
38
+ sessionName: sessionId,
39
+ task,
40
+ lead,
41
+ agents,
42
+ layout: effectiveLayout,
43
+ teammateMode,
44
+ startedAt: Date.now(),
45
+ hubUrl,
46
+ members,
47
+ panes: Object.fromEntries(members.map((member) => [member.pane, {
48
+ role: member.role,
49
+ name: member.name,
50
+ cli: member.cli,
51
+ agentId: member.agentId,
52
+ subtask: member.subtask || null,
53
+ }])),
54
+ tasks: buildTasks(subtasks, members.filter((member) => member.role === "worker")),
55
+ postSave() {
56
+ const profilePrefix = TEAM_PROFILE === "team" ? "" : `TFX_TEAM_PROFILE=${TEAM_PROFILE} `;
57
+ const taskListCommand = `${profilePrefix}${process.execPath} ${join(PKG_ROOT, "bin", "triflux.mjs")} team tasks`;
58
+ configureTeammateKeybindings(sessionId, { inProcess: false, taskListCommand });
59
+ console.log(`\n ${GREEN}${BOLD}팀 세션 준비 완료${RESET}`);
60
+ console.log(` ${DIM}Shift+Down: 다음 팀메이트 전환${RESET}`);
61
+ console.log(` ${DIM}Shift+Tab / Shift+Left: 이전 팀메이트 전환${RESET}`);
62
+ console.log(` ${DIM}Escape: 현재 팀메이트 인터럽트${RESET}`);
63
+ console.log(` ${DIM}Ctrl+T: 태스크 목록${RESET}`);
64
+ console.log(` ${DIM}참고: Shift+Up은 Claude Code 미지원 (scroll-up 충돌). Shift+Tab 사용${RESET}`);
65
+ console.log(` ${DIM}Ctrl+B → D: 세션 분리 (백그라운드)${RESET}\n`);
66
+ if (process.stdout.isTTY && process.stdin.isTTY) attachSession(sessionId);
67
+ else {
68
+ warn("TTY 미지원 환경이라 자동 attach를 생략함");
69
+ console.log(` ${DIM}수동 연결: tfx multi attach${RESET}\n`);
70
+ }
71
+ },
72
+ };
73
+ }
@@ -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
+ }