u-foo 1.0.3 → 1.1.9

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 (179) hide show
  1. package/README.md +110 -11
  2. package/README.zh-CN.md +9 -7
  3. package/SKILLS/ufoo/SKILL.md +132 -0
  4. package/SKILLS/uinit/SKILL.md +78 -0
  5. package/SKILLS/ustatus/SKILL.md +36 -0
  6. package/bin/uclaude.js +13 -0
  7. package/bin/ucode-core.js +15 -0
  8. package/bin/ucode.js +125 -0
  9. package/bin/ucodex.js +13 -0
  10. package/bin/ufoo +9 -31
  11. package/bin/ufoo-assistant-agent.js +5 -0
  12. package/bin/ufoo-engine.js +25 -0
  13. package/bin/ufoo.js +17 -0
  14. package/modules/AGENTS.template.md +29 -11
  15. package/modules/bus/README.md +33 -25
  16. package/modules/bus/SKILLS/ubus/SKILL.md +19 -8
  17. package/modules/context/README.md +18 -40
  18. package/modules/context/SKILLS/uctx/SKILL.md +63 -1
  19. package/modules/online/SKILLS/ufoo-online/SKILL.md +144 -0
  20. package/package.json +25 -4
  21. package/scripts/import-pi-mono.js +124 -0
  22. package/scripts/postinstall.js +30 -0
  23. package/scripts/sync-claude-skills.sh +21 -0
  24. package/src/agent/cliRunner.js +554 -33
  25. package/src/agent/internalRunner.js +150 -56
  26. package/src/agent/launcher.js +754 -0
  27. package/src/agent/normalizeOutput.js +1 -1
  28. package/src/agent/notifier.js +340 -0
  29. package/src/agent/ptyRunner.js +847 -0
  30. package/src/agent/ptyWrapper.js +379 -0
  31. package/src/agent/readyDetector.js +175 -0
  32. package/src/agent/ucode.js +443 -0
  33. package/src/agent/ucodeBootstrap.js +113 -0
  34. package/src/agent/ucodeBuild.js +67 -0
  35. package/src/agent/ucodeDoctor.js +184 -0
  36. package/src/agent/ucodeRuntimeConfig.js +129 -0
  37. package/src/agent/ufooAgent.js +46 -42
  38. package/src/assistant/agent.js +260 -0
  39. package/src/assistant/bridge.js +172 -0
  40. package/src/assistant/engine.js +252 -0
  41. package/src/assistant/stdio.js +58 -0
  42. package/src/assistant/ufooEngineCli.js +306 -0
  43. package/src/bus/activate.js +172 -0
  44. package/src/bus/daemon.js +436 -0
  45. package/src/bus/index.js +842 -0
  46. package/src/bus/inject.js +315 -0
  47. package/src/bus/message.js +430 -0
  48. package/src/bus/nickname.js +88 -0
  49. package/src/bus/queue.js +136 -0
  50. package/src/bus/shake.js +26 -0
  51. package/src/bus/store.js +189 -0
  52. package/src/bus/subscriber.js +312 -0
  53. package/src/bus/utils.js +363 -0
  54. package/src/chat/agentBar.js +117 -0
  55. package/src/chat/agentDirectory.js +88 -0
  56. package/src/chat/agentSockets.js +225 -0
  57. package/src/chat/agentViewController.js +298 -0
  58. package/src/chat/chatLogController.js +115 -0
  59. package/src/chat/commandExecutor.js +700 -0
  60. package/src/chat/commands.js +132 -0
  61. package/src/chat/completionController.js +414 -0
  62. package/src/chat/cronScheduler.js +160 -0
  63. package/src/chat/daemonConnection.js +166 -0
  64. package/src/chat/daemonCoordinator.js +64 -0
  65. package/src/chat/daemonMessageRouter.js +257 -0
  66. package/src/chat/daemonReconnect.js +41 -0
  67. package/src/chat/daemonTransport.js +36 -0
  68. package/src/chat/daemonTransportDefaults.js +10 -0
  69. package/src/chat/dashboardKeyController.js +480 -0
  70. package/src/chat/dashboardView.js +154 -0
  71. package/src/chat/index.js +1011 -1392
  72. package/src/chat/inputHistoryController.js +105 -0
  73. package/src/chat/inputListenerController.js +304 -0
  74. package/src/chat/inputMath.js +104 -0
  75. package/src/chat/inputSubmitHandler.js +171 -0
  76. package/src/chat/layout.js +165 -0
  77. package/src/chat/pasteController.js +81 -0
  78. package/src/chat/rawKeyMap.js +42 -0
  79. package/src/chat/settingsController.js +132 -0
  80. package/src/chat/statusLineController.js +177 -0
  81. package/src/chat/streamTracker.js +138 -0
  82. package/src/chat/text.js +70 -0
  83. package/src/chat/transport.js +61 -0
  84. package/src/cli/busCoreCommands.js +59 -0
  85. package/src/cli/ctxCoreCommands.js +199 -0
  86. package/src/cli/onlineCoreCommands.js +379 -0
  87. package/src/cli.js +1162 -96
  88. package/src/code/README.md +29 -0
  89. package/src/code/UCODE_PROMPT.md +32 -0
  90. package/src/code/agent.js +1651 -0
  91. package/src/code/cli.js +158 -0
  92. package/src/code/config +0 -0
  93. package/src/code/dispatch.js +42 -0
  94. package/src/code/index.js +70 -0
  95. package/src/code/nativeRunner.js +1213 -0
  96. package/src/code/runtime.js +154 -0
  97. package/src/code/sessionStore.js +162 -0
  98. package/src/code/taskDecomposer.js +269 -0
  99. package/src/code/tools/bash.js +53 -0
  100. package/src/code/tools/common.js +42 -0
  101. package/src/code/tools/edit.js +70 -0
  102. package/src/code/tools/read.js +44 -0
  103. package/src/code/tools/write.js +35 -0
  104. package/src/code/tui.js +1580 -0
  105. package/src/config.js +56 -3
  106. package/src/context/decisions.js +324 -0
  107. package/src/context/doctor.js +183 -0
  108. package/src/context/index.js +55 -0
  109. package/src/context/sync.js +127 -0
  110. package/src/daemon/agentProcessManager.js +74 -0
  111. package/src/daemon/cronOps.js +241 -0
  112. package/src/daemon/index.js +998 -170
  113. package/src/daemon/ipcServer.js +99 -0
  114. package/src/daemon/ops.js +630 -48
  115. package/src/daemon/promptLoop.js +319 -0
  116. package/src/daemon/promptRequest.js +101 -0
  117. package/src/daemon/providerSessions.js +306 -0
  118. package/src/daemon/reporting.js +90 -0
  119. package/src/daemon/run.js +31 -1
  120. package/src/daemon/status.js +48 -8
  121. package/src/doctor/index.js +50 -0
  122. package/src/init/index.js +318 -0
  123. package/src/online/bridge.js +663 -0
  124. package/src/online/client.js +245 -0
  125. package/src/online/runner.js +253 -0
  126. package/src/online/server.js +992 -0
  127. package/src/online/tokens.js +103 -0
  128. package/src/report/store.js +331 -0
  129. package/src/shared/eventContract.js +35 -0
  130. package/src/shared/ptySocketContract.js +21 -0
  131. package/src/skills/index.js +159 -0
  132. package/src/status/index.js +285 -0
  133. package/src/terminal/adapterContract.js +87 -0
  134. package/src/terminal/adapterRouter.js +84 -0
  135. package/src/terminal/adapters/externalAdapter.js +14 -0
  136. package/src/terminal/adapters/internalAdapter.js +13 -0
  137. package/src/terminal/adapters/internalPtyAdapter.js +42 -0
  138. package/src/terminal/adapters/internalQueueAdapter.js +37 -0
  139. package/src/terminal/adapters/terminalAdapter.js +31 -0
  140. package/src/terminal/adapters/tmuxAdapter.js +30 -0
  141. package/src/terminal/detect.js +64 -0
  142. package/src/terminal/index.js +8 -0
  143. package/src/terminal/iterm2.js +126 -0
  144. package/src/ufoo/agentsStore.js +107 -0
  145. package/src/ufoo/paths.js +46 -0
  146. package/src/utils/banner.js +76 -0
  147. package/bin/uclaude +0 -65
  148. package/bin/ucodex +0 -65
  149. package/modules/bus/scripts/bus-alert.sh +0 -185
  150. package/modules/bus/scripts/bus-listen.sh +0 -117
  151. package/modules/context/ASSUMPTIONS.md +0 -7
  152. package/modules/context/CONSTRAINTS.md +0 -7
  153. package/modules/context/CONTEXT-STRUCTURE.md +0 -49
  154. package/modules/context/DECISION-PROTOCOL.md +0 -62
  155. package/modules/context/HANDOFF.md +0 -33
  156. package/modules/context/RULES.md +0 -15
  157. package/modules/context/SKILLS/README.md +0 -14
  158. package/modules/context/SYSTEM.md +0 -18
  159. package/modules/context/TEMPLATES/assumptions.md +0 -4
  160. package/modules/context/TEMPLATES/constraints.md +0 -4
  161. package/modules/context/TEMPLATES/decision.md +0 -16
  162. package/modules/context/TEMPLATES/project-context-readme.md +0 -6
  163. package/modules/context/TEMPLATES/system.md +0 -3
  164. package/modules/context/TEMPLATES/terminology.md +0 -4
  165. package/modules/context/TERMINOLOGY.md +0 -10
  166. package/scripts/banner.sh +0 -89
  167. package/scripts/bus-alert.sh +0 -6
  168. package/scripts/bus-autotrigger.sh +0 -6
  169. package/scripts/bus-daemon.sh +0 -231
  170. package/scripts/bus-inject.sh +0 -144
  171. package/scripts/bus-listen.sh +0 -6
  172. package/scripts/bus.sh +0 -984
  173. package/scripts/context-decisions.sh +0 -167
  174. package/scripts/context-doctor.sh +0 -72
  175. package/scripts/context-lint.sh +0 -110
  176. package/scripts/doctor.sh +0 -22
  177. package/scripts/init.sh +0 -247
  178. package/scripts/skills.sh +0 -113
  179. package/scripts/status.sh +0 -125
@@ -0,0 +1,58 @@
1
+ const { runAssistantAgentTask } = require("./agent");
2
+
3
+ function readStdin() {
4
+ return new Promise((resolve) => {
5
+ let data = "";
6
+ process.stdin.setEncoding("utf8");
7
+ process.stdin.on("data", (chunk) => {
8
+ data += chunk;
9
+ });
10
+ process.stdin.on("end", () => resolve(data));
11
+ process.stdin.resume();
12
+ });
13
+ }
14
+
15
+ async function runAssistantStdio() {
16
+ const startedAt = Date.now();
17
+ try {
18
+ const input = await readStdin();
19
+ const line = String(input || "")
20
+ .split(/\r?\n/)
21
+ .map((part) => part.trim())
22
+ .find(Boolean);
23
+
24
+ if (!line) {
25
+ process.stdout.write(
26
+ `${JSON.stringify({
27
+ ok: false,
28
+ summary: "",
29
+ artifacts: [],
30
+ logs: [],
31
+ error: "missing request payload",
32
+ metrics: { duration_ms: Date.now() - startedAt },
33
+ })}\n`
34
+ );
35
+ process.exitCode = 1;
36
+ return;
37
+ }
38
+
39
+ const payload = JSON.parse(line);
40
+ const result = await runAssistantAgentTask(payload);
41
+ process.stdout.write(`${JSON.stringify(result)}\n`);
42
+ process.exitCode = result.ok ? 0 : 1;
43
+ } catch (err) {
44
+ process.stdout.write(
45
+ `${JSON.stringify({
46
+ ok: false,
47
+ summary: "",
48
+ artifacts: [],
49
+ logs: [],
50
+ error: err && err.message ? err.message : "assistant stdio failed",
51
+ metrics: { duration_ms: Date.now() - startedAt },
52
+ })}\n`
53
+ );
54
+ process.exitCode = 1;
55
+ }
56
+ }
57
+
58
+ module.exports = { runAssistantStdio };
@@ -0,0 +1,306 @@
1
+ const { runCliAgent } = require("../agent/cliRunner");
2
+ const { normalizeCliOutput } = require("../agent/normalizeOutput");
3
+ const { loadConfig } = require("../config");
4
+
5
+ function normalizeProvider(value, fallback = "codex-cli") {
6
+ const raw = String(value || "").trim().toLowerCase();
7
+ if (!raw) return fallback;
8
+ if (raw === "codex" || raw === "codex-cli") return "codex-cli";
9
+ if (raw === "claude" || raw === "claude-cli") return "claude-cli";
10
+ return fallback;
11
+ }
12
+
13
+ function parseAssistantTaskArgs(argv = []) {
14
+ const options = {
15
+ assistantTask: false,
16
+ json: false,
17
+ cwd: "",
18
+ model: "",
19
+ sessionId: "",
20
+ provider: "",
21
+ kind: "mixed",
22
+ context: "",
23
+ expect: "",
24
+ task: "",
25
+ };
26
+
27
+ const args = Array.isArray(argv) ? argv.slice() : [];
28
+ const rest = [];
29
+ for (let i = 0; i < args.length; i += 1) {
30
+ const arg = args[i];
31
+ if (arg === "--assistant-task") {
32
+ options.assistantTask = true;
33
+ continue;
34
+ }
35
+ if (arg === "--json") {
36
+ options.json = true;
37
+ continue;
38
+ }
39
+ if (arg === "--cwd") {
40
+ options.cwd = args[++i] || "";
41
+ continue;
42
+ }
43
+ if (arg === "--model") {
44
+ options.model = args[++i] || "";
45
+ continue;
46
+ }
47
+ if (arg === "--session-id") {
48
+ options.sessionId = args[++i] || "";
49
+ continue;
50
+ }
51
+ if (arg === "--provider") {
52
+ options.provider = args[++i] || "";
53
+ continue;
54
+ }
55
+ if (arg === "--kind") {
56
+ options.kind = args[++i] || "mixed";
57
+ continue;
58
+ }
59
+ if (arg === "--context") {
60
+ options.context = args[++i] || "";
61
+ continue;
62
+ }
63
+ if (arg === "--expect") {
64
+ options.expect = args[++i] || "";
65
+ continue;
66
+ }
67
+ rest.push(arg);
68
+ }
69
+ options.task = rest.join(" ").trim();
70
+ return options;
71
+ }
72
+
73
+ function buildPrompt({ kind = "mixed", context = "", task = "", expect = "" } = {}) {
74
+ const lines = [];
75
+ lines.push(`Task kind: ${kind || "mixed"}`);
76
+ if (context) {
77
+ lines.push("Context:");
78
+ lines.push(context);
79
+ }
80
+ lines.push("Task:");
81
+ lines.push(task || "");
82
+ if (expect) {
83
+ lines.push("Expected result:");
84
+ lines.push(expect);
85
+ }
86
+ return lines.join("\n");
87
+ }
88
+
89
+ function buildSystemPrompt() {
90
+ return [
91
+ "You are ufoo-engine, a self-hosted assistant core.",
92
+ "Return ONLY valid JSON.",
93
+ "Schema:",
94
+ "{",
95
+ ' "ok": true|false,',
96
+ ' "summary": "string",',
97
+ ' "artifacts": ["string"],',
98
+ ' "logs": ["string"],',
99
+ ' "error": "string",',
100
+ ' "metrics": {"key":"value"}',
101
+ "}",
102
+ "Rules:",
103
+ "- summary should be concise and actionable.",
104
+ "- error must be non-empty only when ok=false.",
105
+ "- Do not output markdown wrappers.",
106
+ ].join("\n");
107
+ }
108
+
109
+ function normalizeEngineResult(parsed, fallbackError = "") {
110
+ if (!parsed || typeof parsed !== "object") {
111
+ const text = String(parsed || "").trim();
112
+ if (text) {
113
+ return {
114
+ ok: true,
115
+ summary: text,
116
+ artifacts: [],
117
+ logs: [],
118
+ error: "",
119
+ metrics: {},
120
+ };
121
+ }
122
+ return {
123
+ ok: false,
124
+ summary: "",
125
+ artifacts: [],
126
+ logs: [],
127
+ error: fallbackError || "ufoo-engine invalid response",
128
+ metrics: {},
129
+ };
130
+ }
131
+
132
+ return {
133
+ ok: parsed.ok !== false,
134
+ summary: typeof parsed.summary === "string" ? parsed.summary : "",
135
+ artifacts: Array.isArray(parsed.artifacts) ? parsed.artifacts : [],
136
+ logs: Array.isArray(parsed.logs) ? parsed.logs : [],
137
+ error: typeof parsed.error === "string" ? parsed.error : "",
138
+ metrics: parsed.metrics && typeof parsed.metrics === "object" ? parsed.metrics : {},
139
+ };
140
+ }
141
+
142
+ function isSessionError(errorText = "") {
143
+ const text = String(errorText || "").toLowerCase();
144
+ return text.includes("session id")
145
+ || text.includes("session-id")
146
+ || text.includes("already in use");
147
+ }
148
+
149
+ function parseStdinPayload(stdinText = "") {
150
+ const line = String(stdinText || "")
151
+ .split(/\r?\n/)
152
+ .map((part) => part.trim())
153
+ .find(Boolean);
154
+ if (!line) return null;
155
+ try {
156
+ return JSON.parse(line);
157
+ } catch {
158
+ return null;
159
+ }
160
+ }
161
+
162
+ async function runEngineTask(taskInput, deps = {}) {
163
+ const {
164
+ runCliAgentImpl = runCliAgent,
165
+ normalizeCliOutputImpl = normalizeCliOutput,
166
+ loadConfigImpl = loadConfig,
167
+ env = process.env,
168
+ cwd = process.cwd(),
169
+ } = deps;
170
+
171
+ const projectRoot = taskInput.cwd || taskInput.projectRoot || cwd;
172
+ const config = loadConfigImpl(projectRoot);
173
+ const provider = normalizeProvider(
174
+ taskInput.provider || env.UFOO_UFOO_ENGINE_PROVIDER || config.agentProvider,
175
+ "codex-cli"
176
+ );
177
+ const model =
178
+ String(taskInput.model || "").trim()
179
+ || String(env.UFOO_UFOO_ENGINE_MODEL || "").trim()
180
+ || String(config.agentModel || "").trim()
181
+ || (provider === "claude-cli" ? "opus" : "");
182
+
183
+ const systemPrompt = buildSystemPrompt();
184
+ const prompt = buildPrompt({
185
+ kind: taskInput.kind,
186
+ context: taskInput.context,
187
+ task: taskInput.task,
188
+ expect: taskInput.expect,
189
+ });
190
+ const timeoutMs = Number.isFinite(taskInput.timeoutMs) ? taskInput.timeoutMs : 60000;
191
+
192
+ const runOnce = async (sessionId) => runCliAgentImpl({
193
+ provider,
194
+ model,
195
+ prompt,
196
+ systemPrompt,
197
+ sessionId: sessionId || undefined,
198
+ disableSession: false,
199
+ cwd: projectRoot,
200
+ timeoutMs,
201
+ sandbox: taskInput.kind === "explore" ? "read-only" : "workspace-write",
202
+ });
203
+
204
+ let cliRes = await runOnce(taskInput.sessionId || "");
205
+ if (!cliRes.ok && taskInput.sessionId && isSessionError(cliRes.error)) {
206
+ cliRes = await runOnce("");
207
+ }
208
+
209
+ if (!cliRes.ok) {
210
+ return {
211
+ ok: false,
212
+ summary: "",
213
+ artifacts: [],
214
+ logs: [],
215
+ error: cliRes.error || "ufoo-engine cli failed",
216
+ metrics: {},
217
+ session_id: "",
218
+ };
219
+ }
220
+
221
+ const normalized = normalizeCliOutputImpl(cliRes.output);
222
+ let parsed;
223
+ try {
224
+ parsed = JSON.parse(normalized);
225
+ } catch {
226
+ parsed = normalized;
227
+ }
228
+ const result = normalizeEngineResult(parsed);
229
+ return {
230
+ ...result,
231
+ session_id: cliRes.sessionId || "",
232
+ };
233
+ }
234
+
235
+ async function runUfooEngineCli({ argv = [], stdinText = "", deps = {} } = {}) {
236
+ const options = parseAssistantTaskArgs(argv);
237
+
238
+ let taskInput;
239
+ if (options.assistantTask) {
240
+ if (!options.task) {
241
+ const error = {
242
+ ok: false,
243
+ summary: "",
244
+ artifacts: [],
245
+ logs: [],
246
+ error: "missing task",
247
+ metrics: {},
248
+ };
249
+ return { exitCode: 1, output: `${JSON.stringify(error)}\n` };
250
+ }
251
+ taskInput = {
252
+ task: options.task,
253
+ kind: options.kind,
254
+ context: options.context,
255
+ expect: options.expect,
256
+ provider: options.provider,
257
+ model: options.model,
258
+ sessionId: options.sessionId,
259
+ cwd: options.cwd,
260
+ timeoutMs: 60000,
261
+ };
262
+ } else {
263
+ const payload = parseStdinPayload(stdinText);
264
+ if (!payload || typeof payload !== "object") {
265
+ const error = {
266
+ ok: false,
267
+ summary: "",
268
+ artifacts: [],
269
+ logs: [],
270
+ error: "missing request payload",
271
+ metrics: {},
272
+ };
273
+ return { exitCode: 1, output: `${JSON.stringify(error)}\n` };
274
+ }
275
+ taskInput = {
276
+ task: typeof payload.task === "string" ? payload.task : "",
277
+ kind: typeof payload.kind === "string" ? payload.kind : "mixed",
278
+ context: typeof payload.context === "string" ? payload.context : "",
279
+ expect: typeof payload.expect === "string" ? payload.expect : "",
280
+ provider: typeof payload.provider === "string" ? payload.provider : "",
281
+ model: typeof payload.model === "string" ? payload.model : "",
282
+ sessionId: typeof payload.session_id === "string" ? payload.session_id : "",
283
+ cwd: typeof payload.project_root === "string" ? payload.project_root : "",
284
+ timeoutMs: Number.isFinite(payload.timeout_ms) ? payload.timeout_ms : 60000,
285
+ };
286
+ }
287
+
288
+ const result = await runEngineTask(taskInput, deps);
289
+ const output = `${JSON.stringify(result)}\n`;
290
+ return {
291
+ exitCode: result.ok === false ? 1 : 0,
292
+ output,
293
+ };
294
+ }
295
+
296
+ module.exports = {
297
+ normalizeProvider,
298
+ parseAssistantTaskArgs,
299
+ buildPrompt,
300
+ buildSystemPrompt,
301
+ normalizeEngineResult,
302
+ parseStdinPayload,
303
+ runEngineTask,
304
+ runUfooEngineCli,
305
+ isSessionError,
306
+ };
@@ -0,0 +1,172 @@
1
+ const fs = require("fs");
2
+ const { getUfooPaths } = require("../ufoo/paths");
3
+ const { spawn, spawnSync } = require("child_process");
4
+ const { createTerminalAdapterRouter } = require("../terminal/adapterRouter");
5
+
6
+ /**
7
+ * 激活指定 agent 的终端
8
+ *
9
+ * 支持的方式:
10
+ * - tmux: 使用 tmux select-pane 激活
11
+ * - terminal: 使用 AppleScript 通过 tty 定位并激活 Terminal.app 的 tab/window
12
+ * - internal: 不支持自动激活(由 chat PTY view 处理)
13
+ */
14
+ class AgentActivator {
15
+ constructor(projectRoot) {
16
+ this.projectRoot = projectRoot;
17
+ const paths = getUfooPaths(projectRoot);
18
+ this.agentsFile = paths.agentsFile;
19
+ }
20
+
21
+ /**
22
+ * 获取 agent 信息
23
+ */
24
+ getAgentInfo(agentId) {
25
+ try {
26
+ if (!fs.existsSync(this.agentsFile)) {
27
+ throw new Error("Bus not initialized");
28
+ }
29
+
30
+ const busData = JSON.parse(fs.readFileSync(this.agentsFile, "utf8"));
31
+ const meta = busData.agents?.[agentId];
32
+
33
+ if (!meta) {
34
+ throw new Error(`Agent not found: ${agentId}`);
35
+ }
36
+
37
+ return {
38
+ id: agentId,
39
+ nickname: meta.nickname || "",
40
+ tty: meta.tty || "",
41
+ tmux_pane: meta.tmux_pane || "",
42
+ launch_mode: meta.launch_mode || "",
43
+ };
44
+ } catch (err) {
45
+ throw new Error(`Failed to get agent info: ${err.message}`);
46
+ }
47
+ }
48
+
49
+ /**
50
+ * 激活 tmux pane
51
+ */
52
+ activateTmuxPane(paneId) {
53
+ return new Promise((resolve, reject) => {
54
+ // 首先检查 pane 是否存在
55
+ const checkProc = spawn("tmux", ["list-panes", "-a", "-F", "#{pane_id}"]);
56
+ let output = "";
57
+
58
+ checkProc.stdout.on("data", (data) => {
59
+ output += data.toString();
60
+ });
61
+
62
+ checkProc.on("close", (code) => {
63
+ if (code !== 0) {
64
+ reject(new Error("tmux is not running"));
65
+ return;
66
+ }
67
+
68
+ const panes = output.trim().split("\n");
69
+ if (!panes.includes(paneId)) {
70
+ reject(new Error(`tmux pane not found: ${paneId}`));
71
+ return;
72
+ }
73
+
74
+ // 激活 pane(选择 window 和 pane)
75
+ const selectProc = spawn("tmux", ["select-pane", "-t", paneId]);
76
+
77
+ selectProc.on("close", (selectCode) => {
78
+ if (selectCode === 0) {
79
+ resolve();
80
+ } else {
81
+ reject(new Error("Failed to select tmux pane"));
82
+ }
83
+ });
84
+
85
+ selectProc.on("error", reject);
86
+ });
87
+
88
+ checkProc.on("error", reject);
89
+ });
90
+ }
91
+
92
+ /**
93
+ * 通过 tty 激活 Terminal.app 中对应的 tab/window
94
+ */
95
+ activateTerminalByTty(ttyPath) {
96
+ if (process.platform !== "darwin") {
97
+ throw new Error("Terminal activation is only supported on macOS");
98
+ }
99
+ if (!ttyPath) {
100
+ throw new Error("Cannot activate: tty path required");
101
+ }
102
+
103
+ const script = `
104
+ tell application "Terminal"
105
+ repeat with w in windows
106
+ repeat with t in tabs of w
107
+ if tty of t is "${ttyPath.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}" then
108
+ set selected tab of w to t
109
+ set index of w to 1
110
+ activate
111
+ return "ok"
112
+ end if
113
+ end repeat
114
+ end repeat
115
+ return "not found"
116
+ end tell`;
117
+
118
+ const result = spawnSync("osascript", ["-e", script], {
119
+ encoding: "utf8",
120
+ timeout: 5000,
121
+ });
122
+
123
+ if (result.status !== 0) {
124
+ throw new Error(`AppleScript failed: ${(result.stderr || "").trim()}`);
125
+ }
126
+
127
+ const output = (result.stdout || "").trim();
128
+ if (output === "not found") {
129
+ throw new Error(`Terminal tab not found for tty: ${ttyPath}`);
130
+ }
131
+ }
132
+
133
+ /**
134
+ * 激活 agent 的终端
135
+ */
136
+ async activate(agentId) {
137
+ const info = this.getAgentInfo(agentId);
138
+
139
+ const activateTerminal = async () => {
140
+ if (info.tty) {
141
+ this.activateTerminalByTty(info.tty);
142
+ return;
143
+ }
144
+ throw new Error("Cannot activate: missing tty or tmux_pane for agent");
145
+ };
146
+
147
+ const activateTmux = async () => {
148
+ if (info.tmux_pane) {
149
+ await this.activateTmuxPane(info.tmux_pane);
150
+ return;
151
+ }
152
+ throw new Error("Cannot activate: missing tty or tmux_pane for agent");
153
+ };
154
+
155
+ const adapterRouter = createTerminalAdapterRouter({
156
+ activateTerminal,
157
+ activateTmux,
158
+ });
159
+ const adapter = adapterRouter.getAdapter({ launchMode: info.launch_mode, agentId });
160
+
161
+ if (!adapter.capabilities.supportsActivate) {
162
+ if (adapter.capabilities.supportsInternalQueueLoop) {
163
+ throw new Error("Internal mode agents cannot be activated (no terminal window)");
164
+ }
165
+ throw new Error("Cannot activate: missing tty or tmux_pane for agent");
166
+ }
167
+
168
+ await adapter.activate();
169
+ }
170
+ }
171
+
172
+ module.exports = AgentActivator;