triflux 10.3.2 β†’ 10.3.4

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 (65) 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/lib/path-utils.mjs +167 -0
  12. package/hub/router.mjs +791 -791
  13. package/hub/session-fingerprint.mjs +1 -1
  14. package/hub/team/cli/commands/attach.mjs +37 -37
  15. package/hub/team/cli/commands/debug.mjs +74 -74
  16. package/hub/team/cli/commands/focus.mjs +53 -53
  17. package/hub/team/cli/commands/list.mjs +24 -24
  18. package/hub/team/cli/commands/start/start-in-process.mjs +40 -40
  19. package/hub/team/cli/commands/start/start-mux.mjs +73 -73
  20. package/hub/team/cli/commands/start/start-wt.mjs +69 -69
  21. package/hub/team/cli/commands/tasks.mjs +13 -13
  22. package/hub/team/cli/render.mjs +30 -30
  23. package/hub/team/cli/services/attach-fallback.mjs +54 -54
  24. package/hub/team/cli/services/member-selector.mjs +30 -30
  25. package/hub/team/cli/services/native-control.mjs +116 -116
  26. package/hub/team/cli/services/task-model.mjs +30 -30
  27. package/hub/team/notify.mjs +1 -1
  28. package/hub/team/orchestrator.mjs +161 -161
  29. package/hub/team/runtime-strategy.mjs +74 -0
  30. package/hub/team/session.mjs +611 -611
  31. package/hub/team/shared.mjs +13 -13
  32. package/hub/team/worktree-lifecycle.mjs +61 -2
  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/hud-qos-status.mjs +17 -3
  37. package/hud/mission-board.mjs +53 -0
  38. package/hud/providers/claude.mjs +95 -22
  39. package/hud/renderers.mjs +39 -5
  40. package/mesh/index.mjs +63 -0
  41. package/mesh/mesh-budget.mjs +128 -0
  42. package/mesh/mesh-heartbeat.mjs +100 -0
  43. package/mesh/mesh-protocol.mjs +96 -0
  44. package/mesh/mesh-queue.mjs +165 -0
  45. package/mesh/mesh-registry.mjs +78 -0
  46. package/mesh/mesh-router.mjs +76 -0
  47. package/package.json +2 -1
  48. package/scripts/completions/tfx.bash +47 -47
  49. package/scripts/completions/tfx.fish +44 -44
  50. package/scripts/completions/tfx.zsh +83 -83
  51. package/scripts/demo.mjs +169 -0
  52. package/scripts/headless-guard.mjs +16 -4
  53. package/scripts/hub-ensure.mjs +120 -120
  54. package/scripts/keyword-detector.mjs +272 -272
  55. package/scripts/keyword-rules-expander.mjs +521 -521
  56. package/scripts/lib/mcp-server-catalog.mjs +118 -118
  57. package/scripts/lib/skill-state.mjs +220 -0
  58. package/scripts/notion-read.mjs +553 -553
  59. package/scripts/test-tfx-route-no-claude-native.mjs +57 -57
  60. package/scripts/tfx-batch-stats.mjs +96 -96
  61. package/skills/.omc/state/agent-replay-8f0e10a9-9693-4410-96f5-a6b07e8ed995.jsonl +0 -1
  62. package/skills/.omc/state/idle-notif-cooldown.json +0 -3
  63. package/skills/.omc/state/last-tool-error.json +0 -7
  64. package/skills/.omc/state/subagent-tracking.json +0 -7
  65. package/skills/tfx-remote-spawn/references/hosts.json +0 -16
@@ -1,30 +1,30 @@
1
- export function buildTasks(subtasks, workers) {
2
- return subtasks.map((subtask, index) => ({
3
- id: `T${index + 1}`,
4
- title: subtask,
5
- owner: workers[index]?.name || null,
6
- status: "pending",
7
- depends_on: index === 0 ? [] : [`T${index}`],
8
- }));
9
- }
10
-
11
- export function normalizeTaskStatus(action) {
12
- const value = String(action || "").toLowerCase();
13
- if (value === "done" || value === "complete" || value === "completed") return "completed";
14
- if (value === "progress" || value === "in-progress" || value === "in_progress") return "in_progress";
15
- if (value === "pending") return "pending";
16
- return null;
17
- }
18
-
19
- export function updateTaskStatus(tasks = [], taskId, nextStatus) {
20
- const normalizedId = String(taskId || "").toUpperCase();
21
- const target = tasks.find((task) => String(task.id).toUpperCase() === normalizedId);
22
- if (!target) return { tasks, target: null };
23
-
24
- return {
25
- target: { ...target, status: nextStatus },
26
- tasks: tasks.map((task) => (
27
- String(task.id).toUpperCase() === normalizedId ? { ...task, status: nextStatus } : task
28
- )),
29
- };
30
- }
1
+ export function buildTasks(subtasks, workers) {
2
+ return subtasks.map((subtask, index) => ({
3
+ id: `T${index + 1}`,
4
+ title: subtask,
5
+ owner: workers[index]?.name || null,
6
+ status: "pending",
7
+ depends_on: index === 0 ? [] : [`T${index}`],
8
+ }));
9
+ }
10
+
11
+ export function normalizeTaskStatus(action) {
12
+ const value = String(action || "").toLowerCase();
13
+ if (value === "done" || value === "complete" || value === "completed") return "completed";
14
+ if (value === "progress" || value === "in-progress" || value === "in_progress") return "in_progress";
15
+ if (value === "pending") return "pending";
16
+ return null;
17
+ }
18
+
19
+ export function updateTaskStatus(tasks = [], taskId, nextStatus) {
20
+ const normalizedId = String(taskId || "").toUpperCase();
21
+ const target = tasks.find((task) => String(task.id).toUpperCase() === normalizedId);
22
+ if (!target) return { tasks, target: null };
23
+
24
+ return {
25
+ target: { ...target, status: nextStatus },
26
+ tasks: tasks.map((task) => (
27
+ String(task.id).toUpperCase() === normalizedId ? { ...task, status: nextStatus } : task
28
+ )),
29
+ };
30
+ }
@@ -290,4 +290,4 @@ export function createNotifier(opts = {}) {
290
290
  });
291
291
 
292
292
  return createNotifierInstance(normalizeChannels(opts.channels, env), deps);
293
- }
293
+ }
@@ -1,161 +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, 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
- }
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, 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,74 @@
1
+ import {
2
+ createPsmuxSession,
3
+ killPsmuxSession,
4
+ psmuxSessionExists,
5
+ } from "./psmux.mjs";
6
+
7
+ /**
8
+ * @typedef {object} RuntimeStatus
9
+ * @property {string} name
10
+ * @property {string} sessionName
11
+ * @property {boolean} alive
12
+ */
13
+
14
+ /**
15
+ * @typedef {object} TeamRuntime
16
+ * @property {(sessionName: string, opts?: object) => unknown} start
17
+ * @property {(sessionName: string) => void} stop
18
+ * @property {(sessionName: string) => boolean} isAlive
19
+ * @property {(sessionName: string) => RuntimeStatus} getStatus
20
+ */
21
+
22
+ const defaultPsmuxAdapter = {
23
+ createSession: createPsmuxSession,
24
+ killSession: killPsmuxSession,
25
+ hasSession: psmuxSessionExists,
26
+ };
27
+
28
+ /**
29
+ * @param {{
30
+ * createSession: typeof createPsmuxSession,
31
+ * killSession: typeof killPsmuxSession,
32
+ * hasSession: typeof psmuxSessionExists,
33
+ * }} [adapter]
34
+ * @returns {TeamRuntime & { name: "psmux" }}
35
+ */
36
+ export function createPsmuxRuntime(adapter = defaultPsmuxAdapter) {
37
+ return {
38
+ name: "psmux",
39
+ start(sessionName, opts = {}) {
40
+ return adapter.createSession(sessionName, opts);
41
+ },
42
+ stop(sessionName) {
43
+ adapter.killSession(sessionName);
44
+ },
45
+ isAlive(sessionName) {
46
+ return adapter.hasSession(sessionName);
47
+ },
48
+ getStatus(sessionName) {
49
+ return {
50
+ name: "psmux",
51
+ sessionName,
52
+ alive: adapter.hasSession(sessionName),
53
+ };
54
+ },
55
+ };
56
+ }
57
+
58
+ /**
59
+ * @param {string} mode
60
+ * @returns {TeamRuntime & { name: string }}
61
+ */
62
+ export function createRuntime(mode) {
63
+ const normalizedMode = String(mode || "").trim().toLowerCase();
64
+
65
+ if (normalizedMode === "psmux") {
66
+ return createPsmuxRuntime();
67
+ }
68
+
69
+ if (normalizedMode === "native" || normalizedMode === "wt") {
70
+ throw new Error(`Runtime mode "${normalizedMode}" is not implemented yet.`);
71
+ }
72
+
73
+ throw new Error(`Unsupported runtime mode: ${mode}`);
74
+ }