u-foo 2.3.31 → 2.4.0

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 (236) hide show
  1. package/README.md +157 -213
  2. package/README.zh-CN.md +151 -197
  3. package/SKILLS/ufoo/SKILL.md +8 -8
  4. package/bin/uagy.js +69 -0
  5. package/bin/uclaude.js +2 -2
  6. package/bin/ucode.js +4 -4
  7. package/bin/ucodex.js +2 -2
  8. package/bin/ufoo.js +5 -23
  9. package/modules/AGENTS.template.md +1 -1
  10. package/modules/bus/SKILLS/ubus/SKILL.md +35 -10
  11. package/package.json +9 -5
  12. package/scripts/chat-app-smoke.js +30 -0
  13. package/scripts/global-chat-switch-benchmark.js +5 -5
  14. package/scripts/ink-demo.js +23 -0
  15. package/scripts/ink-smoke.js +30 -0
  16. package/scripts/ucode-app-smoke.js +36 -0
  17. package/src/{agent → agents/activity}/activityDetector.js +39 -2
  18. package/src/{agent → agents/activity}/activityStatePublisher.js +1 -1
  19. package/src/{agent → agents/activity}/activityStateWriter.js +2 -2
  20. package/src/{agent → agents/activity}/activityTracker.js +1 -1
  21. package/src/agents/activity/index.js +8 -0
  22. package/src/{agent → agents/controller}/controllerToolExecutor.js +4 -4
  23. package/src/agents/controller/index.js +8 -0
  24. package/src/{agent → agents/controller}/loopObservability.js +2 -2
  25. package/src/{agent → agents/controller}/loopRuntime.js +1 -1
  26. package/src/{agent → agents/controller}/ufooAgent.js +9 -9
  27. package/src/agents/index.js +10 -0
  28. package/src/agents/internal/index.js +3 -0
  29. package/src/{agent → agents/internal}/internalRunner.js +45 -22
  30. package/src/agents/launch/agyConversation.js +159 -0
  31. package/src/agents/launch/index.js +12 -0
  32. package/src/{agent → agents/launch}/launchEnvironment.js +2 -3
  33. package/src/{agent → agents/launch}/launcher.js +64 -21
  34. package/src/{agent → agents/launch}/notifier.js +23 -12
  35. package/src/{agent → agents/launch}/ptyRunner.js +44 -12
  36. package/src/{agent → agents/launch}/ptyWrapper.js +2 -2
  37. package/src/{agent → agents/launch}/publisherRouting.js +1 -1
  38. package/src/{agent → agents/launch}/readyDetector.js +23 -0
  39. package/src/{agent → agents/prompts}/defaultBootstrap.js +63 -4
  40. package/src/{group/bootstrap.js → agents/prompts/groupBootstrap.js} +41 -6
  41. package/src/agents/prompts/index.js +8 -0
  42. package/src/{code/prompts → agents/prompts/native}/index.js +1 -1
  43. package/src/{agent → agents/providers}/claudeThreadProvider.js +1 -1
  44. package/src/{agent → agents/providers}/codexThreadProvider.js +1 -1
  45. package/src/{agent → agents/providers}/directAuthStatus.js +184 -1
  46. package/src/agents/providers/index.js +13 -0
  47. package/src/{agent → agents/providers}/upstreamTransport.js +2 -2
  48. package/src/{chat → app/chat}/agentSockets.js +1 -1
  49. package/src/{chat → app/chat}/commandExecutor.js +56 -28
  50. package/src/{chat → app/chat}/commands.js +119 -5
  51. package/src/{chat → app/chat}/daemonConnection.js +1 -1
  52. package/src/{chat → app/chat}/daemonMessageRouter.js +54 -4
  53. package/src/{chat → app/chat}/daemonTransport.js +2 -1
  54. package/src/{chat → app/chat}/dashboardView.js +2 -21
  55. package/src/app/chat/index.js +6 -0
  56. package/src/{chat → app/chat}/inputSubmitHandler.js +38 -13
  57. package/src/{chat → app/chat}/internalAgentLogHistory.js +1 -1
  58. package/src/app/chat/multiWindow/index.js +268 -0
  59. package/src/app/chat/multiWindow/paneLayout.js +84 -0
  60. package/src/app/chat/multiWindow/paneManager.js +299 -0
  61. package/src/app/chat/multiWindow/renderer.js +384 -0
  62. package/src/app/chat/multiWindow/virtualTerminal.js +327 -0
  63. package/src/{chat → app/chat}/projectCloseController.js +1 -1
  64. package/src/app/chat/shellCommand.js +42 -0
  65. package/src/{chat → app/chat}/transport.js +16 -3
  66. package/src/{cli → app/cli}/ctxCoreCommands.js +3 -3
  67. package/src/{doctor/index.js → app/cli/features/doctor.js} +1 -1
  68. package/src/{init/index.js → app/cli/features/init.js} +14 -32
  69. package/src/{cli → app/cli}/groupCoreCommands.js +2 -2
  70. package/src/app/cli/index.js +9 -0
  71. package/src/{cli → app/cli}/onlineCoreCommands.js +5 -5
  72. package/src/{cli.js → app/cli/run.js} +62 -59
  73. package/src/app/index.js +6 -0
  74. package/src/code/agent.js +10 -9
  75. package/src/code/index.js +2 -0
  76. package/src/code/launcher/index.js +9 -0
  77. package/src/{agent → code/launcher}/ucode.js +7 -8
  78. package/src/{agent → code/launcher}/ucodeBootstrap.js +3 -3
  79. package/src/{agent → code/launcher}/ucodeBuild.js +2 -2
  80. package/src/{agent → code/launcher}/ucodeDoctor.js +2 -2
  81. package/src/{agent → code/launcher}/ucodeRuntimeConfig.js +1 -2
  82. package/src/code/nativeRunner.js +4 -4
  83. package/src/code/taskDecomposer.js +5 -4
  84. package/src/code/tui.js +39 -1997
  85. package/src/config.js +15 -2
  86. package/src/{bus → coordination/bus}/activate.js +2 -2
  87. package/src/{bus → coordination/bus}/daemon.js +15 -5
  88. package/src/coordination/bus/envelope.js +173 -0
  89. package/src/{bus → coordination/bus}/index.js +7 -3
  90. package/src/{bus → coordination/bus}/inject.js +11 -3
  91. package/src/{bus → coordination/bus}/message.js +1 -1
  92. package/src/coordination/bus/messageMeta.js +130 -0
  93. package/src/coordination/bus/promptEnvelope.js +65 -0
  94. package/src/{bus → coordination/bus}/shake.js +1 -1
  95. package/src/{bus → coordination/bus}/store.js +3 -3
  96. package/src/{bus → coordination/bus}/subscriber.js +2 -2
  97. package/src/{bus → coordination/bus}/utils.js +2 -2
  98. package/src/{history → coordination/history}/inputTimeline.js +5 -5
  99. package/src/coordination/index.js +10 -0
  100. package/src/{memory → coordination/memory}/historySearch.js +1 -1
  101. package/src/{memory → coordination/memory}/index.js +3 -3
  102. package/src/{report → coordination/report}/store.js +2 -2
  103. package/src/{ufoo → coordination/state}/agentRegistryDiagnostics.js +43 -0
  104. package/src/{status → coordination/status}/index.js +3 -3
  105. package/src/online/bridge.js +2 -2
  106. package/src/{controller → orchestration/controller}/flags.js +1 -1
  107. package/src/{controller → orchestration/controller}/gateRouter.js +1 -1
  108. package/src/orchestration/controller/index.js +10 -0
  109. package/src/{controller → orchestration/controller}/shadowGuard.js +1 -1
  110. package/src/orchestration/groups/bootstrap.js +3 -0
  111. package/src/orchestration/groups/index.js +10 -0
  112. package/src/orchestration/groups/promptProfiles.js +3 -0
  113. package/src/{group → orchestration/groups}/templates.js +1 -1
  114. package/src/{group → orchestration/groups}/validateTemplate.js +1 -1
  115. package/src/orchestration/index.js +7 -0
  116. package/src/orchestration/solo/index.js +3 -0
  117. package/src/{daemon → runtime/daemon}/agentProcessManager.js +1 -1
  118. package/src/{daemon → runtime/daemon}/cronOps.js +3 -2
  119. package/src/{daemon → runtime/daemon}/groupOrchestrator.js +26 -9
  120. package/src/{daemon → runtime/daemon}/index.js +273 -79
  121. package/src/{daemon → runtime/daemon}/ipcServer.js +24 -2
  122. package/src/{daemon → runtime/daemon}/nicknameScope.js +6 -3
  123. package/src/{daemon → runtime/daemon}/ops.js +48 -61
  124. package/src/{daemon → runtime/daemon}/promptLoop.js +1 -1
  125. package/src/{daemon → runtime/daemon}/promptRequest.js +13 -8
  126. package/src/runtime/daemon/providerSessions.js +230 -0
  127. package/src/{daemon → runtime/daemon}/reporting.js +4 -4
  128. package/src/{daemon → runtime/daemon}/run.js +12 -5
  129. package/src/{daemon → runtime/daemon}/soloBootstrap.js +7 -7
  130. package/src/{daemon → runtime/daemon}/status.js +5 -5
  131. package/src/runtime/index.js +10 -0
  132. package/src/runtime/process/nodeExecutable.js +26 -0
  133. package/src/{projects → runtime/projects}/registry.js +1 -1
  134. package/src/{projects → runtime/projects}/runtimes.js +1 -1
  135. package/src/{terminal → runtime/terminal}/adapterRouter.js +0 -10
  136. package/src/{terminal → runtime/terminal}/adapters/internalAdapter.js +0 -4
  137. package/src/tools/handlers/common.js +1 -1
  138. package/src/tools/handlers/listAgents.js +1 -1
  139. package/src/tools/handlers/memory.js +3 -3
  140. package/src/tools/handlers/readBusSummary.js +1 -1
  141. package/src/tools/handlers/readOpenDecisions.js +1 -1
  142. package/src/tools/handlers/readProjectRegistry.js +1 -1
  143. package/src/tools/handlers/readPromptHistory.js +2 -2
  144. package/src/tools/schemaFixtures.js +1 -1
  145. package/src/ui/MIGRATION.md +336 -0
  146. package/src/ui/format/index.js +974 -0
  147. package/src/ui/index.js +9 -0
  148. package/src/ui/ink/ChatApp.js +3674 -0
  149. package/src/ui/ink/DashboardBar.js +685 -0
  150. package/src/ui/ink/InkDemo.js +96 -0
  151. package/src/ui/ink/MultilineInput.js +612 -0
  152. package/src/ui/ink/UcodeApp.js +822 -0
  153. package/src/ui/ink/agentMirror.js +730 -0
  154. package/src/ui/ink/chatReducer.js +359 -0
  155. package/src/ui/runInk.js +57 -0
  156. package/src/bus/messageMeta.js +0 -52
  157. package/src/chat/agentViewController.js +0 -1072
  158. package/src/chat/chatLogController.js +0 -138
  159. package/src/chat/completionController.js +0 -533
  160. package/src/chat/dashboardKeyController.js +0 -573
  161. package/src/chat/index.js +0 -2214
  162. package/src/chat/inputHistoryController.js +0 -135
  163. package/src/chat/inputListenerController.js +0 -470
  164. package/src/chat/layout.js +0 -186
  165. package/src/chat/pasteController.js +0 -81
  166. package/src/chat/statusLineController.js +0 -223
  167. package/src/chat/streamTracker.js +0 -156
  168. package/src/code/config +0 -0
  169. package/src/daemon/providerSessions.js +0 -488
  170. package/src/terminal/adapters/internalPtyAdapter.js +0 -42
  171. /package/src/{code/prompts → agents/prompts/native}/actions.js +0 -0
  172. /package/src/{code/prompts → agents/prompts/native}/efficiency.js +0 -0
  173. /package/src/{code/prompts → agents/prompts/native}/environment.js +0 -0
  174. /package/src/{code/prompts → agents/prompts/native}/identity.js +0 -0
  175. /package/src/{code/prompts → agents/prompts/native}/safety.js +0 -0
  176. /package/src/{code/prompts → agents/prompts/native}/sections.js +0 -0
  177. /package/src/{code/prompts → agents/prompts/native}/system.js +0 -0
  178. /package/src/{code/prompts → agents/prompts/native}/tasks.js +0 -0
  179. /package/src/{code/prompts → agents/prompts/native}/toolDescriptions/bash.js +0 -0
  180. /package/src/{code/prompts → agents/prompts/native}/toolDescriptions/edit.js +0 -0
  181. /package/src/{code/prompts → agents/prompts/native}/toolDescriptions/read.js +0 -0
  182. /package/src/{code/prompts → agents/prompts/native}/toolDescriptions/write.js +0 -0
  183. /package/src/{code/prompts → agents/prompts/native}/ufoo.js +0 -0
  184. /package/src/{group → agents/prompts}/promptProfiles.js +0 -0
  185. /package/src/{agent → agents/providers}/claudeEventTranslator.js +0 -0
  186. /package/src/{agent → agents/providers}/claudeOauthTokenReader.js +0 -0
  187. /package/src/{agent → agents/providers}/claudeSessionFiles.js +0 -0
  188. /package/src/{agent → agents/providers}/codexEventTranslator.js +0 -0
  189. /package/src/{agent → agents/providers}/credentials/claude.js +0 -0
  190. /package/src/{agent → agents/providers}/credentials/codex.js +0 -0
  191. /package/src/{agent → agents/providers}/credentials/index.js +0 -0
  192. /package/src/{chat → app/chat}/agentBar.js +0 -0
  193. /package/src/{chat → app/chat}/agentDirectory.js +0 -0
  194. /package/src/{chat → app/chat}/cronScheduler.js +0 -0
  195. /package/src/{chat → app/chat}/daemonCoordinator.js +0 -0
  196. /package/src/{chat → app/chat}/daemonReconnect.js +0 -0
  197. /package/src/{chat → app/chat}/daemonTransportDefaults.js +0 -0
  198. /package/src/{chat → app/chat}/inputMath.js +0 -0
  199. /package/src/{chat → app/chat}/rawKeyMap.js +0 -0
  200. /package/src/{chat → app/chat}/settingsController.js +0 -0
  201. /package/src/{chat → app/chat}/text.js +0 -0
  202. /package/src/{chat → app/chat}/transientAgentState.js +0 -0
  203. /package/src/{cli → app/cli}/busCoreCommands.js +0 -0
  204. /package/src/{skills/index.js → app/cli/features/skills.js} +0 -0
  205. /package/src/{bus → coordination/bus}/nickname.js +0 -0
  206. /package/src/{bus → coordination/bus}/queue.js +0 -0
  207. /package/src/{context → coordination/context}/decisions.js +0 -0
  208. /package/src/{context → coordination/context}/doctor.js +0 -0
  209. /package/src/{context → coordination/context}/index.js +0 -0
  210. /package/src/{context → coordination/context}/sync.js +0 -0
  211. /package/src/{ufoo → coordination/state}/agentsStore.js +0 -0
  212. /package/src/{ufoo → coordination/state}/paths.js +0 -0
  213. /package/src/{controller → orchestration/controller}/launchRouting.js +0 -0
  214. /package/src/{controller → orchestration/controller}/routerFastPath.js +0 -0
  215. /package/src/{controller → orchestration/controller}/routerFinalize.js +0 -0
  216. /package/src/{group → orchestration/groups}/diagram.js +0 -0
  217. /package/src/{group → orchestration/groups}/templateValidation.js +0 -0
  218. /package/src/{solo → orchestration/solo}/commands.js +0 -0
  219. /package/src/{shared → runtime/contracts}/eventContract.js +0 -0
  220. /package/src/{shared → runtime/contracts}/ptySocketContract.js +0 -0
  221. /package/src/{providerapi → runtime/privacy}/redactor.js +0 -0
  222. /package/src/{providerapi → runtime/privacy}/shadowDiff.js +0 -0
  223. /package/src/{projects → runtime/projects}/identity.js +0 -0
  224. /package/src/{projects → runtime/projects}/index.js +0 -0
  225. /package/src/{projects → runtime/projects}/projectId.js +0 -0
  226. /package/src/{terminal → runtime/terminal}/adapterContract.js +0 -0
  227. /package/src/{terminal → runtime/terminal}/adapters/externalAdapter.js +0 -0
  228. /package/src/{terminal → runtime/terminal}/adapters/hostAdapter.js +0 -0
  229. /package/src/{terminal → runtime/terminal}/adapters/internalQueueAdapter.js +0 -0
  230. /package/src/{terminal → runtime/terminal}/adapters/terminalAdapter.js +0 -0
  231. /package/src/{terminal → runtime/terminal}/adapters/tmuxAdapter.js +0 -0
  232. /package/src/{terminal → runtime/terminal}/detect.js +0 -0
  233. /package/src/{terminal → runtime/terminal}/index.js +0 -0
  234. /package/src/{terminal → runtime/terminal}/iterm2.js +0 -0
  235. /package/src/{utils → ui/format}/banner.js +0 -0
  236. /package/src/{shared → ui/format}/markdownRenderer.js +0 -0
@@ -2,22 +2,22 @@ const fs = require("fs");
2
2
  const path = require("path");
3
3
  const net = require("net");
4
4
  const { spawn, spawnSync } = require("child_process");
5
- const { runUfooAgent, runUfooRouteAgent } = require("../agent/ufooAgent");
5
+ const { runUfooAgent, runUfooRouteAgent } = require("../../agents/controller/ufooAgent");
6
6
  const { launchAgent, closeAgent, getRecoverableAgents, resumeAgents } = require("./ops");
7
7
  const { buildStatus } = require("./status");
8
- const EventBus = require("../bus");
8
+ const EventBus = require("../../coordination/bus");
9
9
  const { AgentProcessManager } = require("./agentProcessManager");
10
- const NicknameManager = require("../bus/nickname");
11
- const { generateInstanceId, subscriberToSafeName } = require("../bus/utils");
10
+ const NicknameManager = require("../../coordination/bus/nickname");
11
+ const { generateInstanceId, subscriberToSafeName } = require("../../coordination/bus/utils");
12
12
  const { createDaemonIpcServer } = require("./ipcServer");
13
- const { IPC_REQUEST_TYPES, IPC_RESPONSE_TYPES, BUS_STATUS_PHASES } = require("../shared/eventContract");
14
- const { getUfooPaths } = require("../ufoo/paths");
13
+ const { IPC_REQUEST_TYPES, IPC_RESPONSE_TYPES, BUS_STATUS_PHASES } = require("../contracts/eventContract");
14
+ const { getUfooPaths } = require("../../coordination/state/paths");
15
15
  const { upsertProjectRuntime, markProjectStopped } = require("../projects");
16
- const { scheduleProviderSessionProbe, resolveSessionFromFile, persistProviderSession, loadProviderSessionCache } = require("./providerSessions");
16
+ const { scheduleProviderSessionResolve, resolveSessionFromFile, persistProviderSession, loadProviderSessionCache } = require("./providerSessions");
17
17
  const { createTerminalAdapterRouter } = require("../terminal/adapterRouter");
18
18
  const { createDaemonCronController } = require("./cronOps");
19
19
  const { createGroupOrchestrator } = require("./groupOrchestrator");
20
- const { normalizeFormat, renderGroupDiagramFromTemplate, renderGroupDiagramFromRuntime } = require("../group/diagram");
20
+ const { normalizeFormat, renderGroupDiagramFromTemplate, renderGroupDiagramFromRuntime } = require("../../orchestration/groups/diagram");
21
21
  const { runPromptWithAssistant } = require("./promptLoop");
22
22
  const { handlePromptRequest } = require("./promptRequest");
23
23
  const { recordAgentReport } = require("./reporting");
@@ -36,9 +36,10 @@ const {
36
36
  resolveDisplayNickname,
37
37
  resolveScopedNickname,
38
38
  } = require("./nicknameScope");
39
+ const { resolveNodeExecutable } = require("../process/nodeExecutable");
39
40
 
40
41
  let providerSessions = null;
41
- let probeHandles = new Map();
42
+ let sessionResolveHandles = new Map();
42
43
  let daemonCronController = null;
43
44
  let daemonGroupOrchestrator = null;
44
45
  const PROJECT_RUNTIME_HEARTBEAT_MS = 10 * 1000;
@@ -52,6 +53,7 @@ function normalizeBusAgentType(agentType = "") {
52
53
  if (!value) return "claude-code";
53
54
  if (value === "codex") return "codex";
54
55
  if (value === "claude" || value === "claude-code") return "claude-code";
56
+ if (value === "agy" || value === "antigravity") return "agy";
55
57
  if (value === "ufoo" || value === "ucode" || value === "ufoo-code") return "ufoo-code";
56
58
  return value;
57
59
  }
@@ -60,6 +62,7 @@ function normalizeLaunchAgent(agent = "") {
60
62
  const value = String(agent || "").trim().toLowerCase();
61
63
  if (value === "codex") return "codex";
62
64
  if (value === "claude" || value === "claude-code") return "claude";
65
+ if (value === "agy" || value === "antigravity") return "agy";
63
66
  if (value === "ufoo" || value === "ucode" || value === "ufoo-code") return "ufoo";
64
67
  return "";
65
68
  }
@@ -124,6 +127,15 @@ function logPath(projectRoot) {
124
127
  return getUfooPaths(projectRoot).ufooDaemonLog;
125
128
  }
126
129
 
130
+ function appendControlLog(projectRoot, msg) {
131
+ try {
132
+ ensureDir(path.dirname(logPath(projectRoot)));
133
+ fs.appendFileSync(logPath(projectRoot), `[daemon-control] ${new Date().toISOString()} ${msg}\n`);
134
+ } catch {
135
+ // ignore control logging errors
136
+ }
137
+ }
138
+
127
139
  function writePid(projectRoot) {
128
140
  fs.writeFileSync(pidPath(projectRoot), String(process.pid));
129
141
  }
@@ -151,6 +163,10 @@ function checkPid(pid) {
151
163
  }
152
164
  }
153
165
 
166
+ function pidAlive(pid) {
167
+ return checkPid(pid).alive;
168
+ }
169
+
154
170
  function readProcessArgs(pid) {
155
171
  if (!Number.isFinite(pid) || pid <= 0) return "";
156
172
  try {
@@ -171,6 +187,32 @@ function readProcessArgs(pid) {
171
187
  return "";
172
188
  }
173
189
 
190
+ function readProcessCwd(pid) {
191
+ if (!Number.isFinite(pid) || pid <= 0) return "";
192
+ try {
193
+ const res = spawnSync("lsof", ["-a", "-p", String(pid), "-d", "cwd", "-Fn"], {
194
+ encoding: "utf8",
195
+ stdio: ["ignore", "pipe", "ignore"],
196
+ });
197
+ if (!res || res.status !== 0 || !res.stdout) return "";
198
+ for (const line of String(res.stdout || "").split(/\r?\n/)) {
199
+ if (line.startsWith("n")) return line.slice(1);
200
+ }
201
+ } catch {
202
+ // ignore
203
+ }
204
+ return "";
205
+ }
206
+
207
+ function sameProjectRoot(a, b) {
208
+ if (!a || !b) return false;
209
+ try {
210
+ return fs.realpathSync(a) === fs.realpathSync(b);
211
+ } catch {
212
+ return path.resolve(a) === path.resolve(b);
213
+ }
214
+ }
215
+
174
216
  function isLikelyDaemonProcess(pid) {
175
217
  const args = readProcessArgs(pid);
176
218
  if (!args || args === "__EPERM__") return null;
@@ -178,10 +220,41 @@ function isLikelyDaemonProcess(pid) {
178
220
  const hasCliPattern = /\bufoo\s+daemon\s+(--start|start)\b/.test(text);
179
221
  const hasNodePattern = /\bufoo\.js\s+daemon\s+(--start|start)\b/.test(text);
180
222
  if (hasCliPattern || hasNodePattern) return true;
181
- if (text.includes("/src/daemon/run.js")) return true;
223
+ if (text.includes("/src/runtime/daemon/run.js")) return true;
182
224
  return false;
183
225
  }
184
226
 
227
+ function isPidFileDaemonForProject(projectRoot, pid, socketOwnerPids = new Set()) {
228
+ if (!Number.isFinite(pid) || pid <= 0) return false;
229
+ if (socketOwnerPids.has(pid)) return true;
230
+ if (looksLikeRunningDaemon(projectRoot, pid)) return true;
231
+ if (isLikelyDaemonProcess(pid) !== true) return false;
232
+ const cwd = readProcessCwd(pid);
233
+ return sameProjectRoot(cwd, projectRoot);
234
+ }
235
+
236
+ function socketOwnerDaemonPids(projectRoot) {
237
+ const sock = socketPath(projectRoot);
238
+ const out = new Set();
239
+ try {
240
+ const res = spawnSync("lsof", ["-nP", "-U"], {
241
+ encoding: "utf8",
242
+ stdio: ["ignore", "pipe", "ignore"],
243
+ });
244
+ if (!res || res.status !== 0 || !res.stdout) return [];
245
+ for (const line of String(res.stdout || "").split(/\r?\n/)) {
246
+ if (!line.includes(sock)) continue;
247
+ const parts = line.trim().split(/\s+/);
248
+ const pid = parseInt(parts[1], 10);
249
+ if (!Number.isFinite(pid) || pid <= 0) continue;
250
+ if (isLikelyDaemonProcess(pid) === true) out.add(pid);
251
+ }
252
+ } catch {
253
+ // ignore lsof failures; pid file fallback still applies
254
+ }
255
+ return Array.from(out);
256
+ }
257
+
185
258
  function looksLikeRunningDaemon(projectRoot, pid) {
186
259
  const state = checkPid(pid);
187
260
  if (!state.alive) return false;
@@ -216,6 +289,16 @@ function cleanupStaleState(projectRoot) {
216
289
  removeSocket(projectRoot);
217
290
  }
218
291
 
292
+ function removePidIfOwned(projectRoot, expectedPid = process.pid) {
293
+ const pid = readPid(projectRoot);
294
+ if (pid !== expectedPid) return;
295
+ try {
296
+ fs.unlinkSync(pidPath(projectRoot));
297
+ } catch {
298
+ // ignore cleanup errors
299
+ }
300
+ }
301
+
219
302
  function removeSocket(projectRoot) {
220
303
  const sock = socketPath(projectRoot);
221
304
  if (fs.existsSync(sock)) fs.unlinkSync(sock);
@@ -528,17 +611,15 @@ async function handleOps(projectRoot, ops = [], processManager = null) {
528
611
  op.require_activity_monitor === true || op.requireActivityMonitor === true,
529
612
  });
530
613
  if (launchResult.mode === "internal" && launchResult.subscriberIds && launchResult.subscriberIds.length > 0) {
531
- const probeAgentType = agent === "codex"
614
+ const sessionResolveAgentType = agent === "codex"
532
615
  ? "codex"
533
616
  : (agent === "claude" ? "claude-code" : "");
534
617
  for (const subscriberId of launchResult.subscriberIds) {
535
- if (!probeAgentType) continue;
536
- const resolvedNickname = resolveSubscriberNickname(projectRoot, subscriberId) || nickname;
537
- const probeHandle = scheduleProviderSessionProbe({
618
+ if (!sessionResolveAgentType) continue;
619
+ const sessionResolveHandle = scheduleProviderSessionResolve({
538
620
  projectRoot,
539
621
  subscriberId,
540
- agentType: probeAgentType,
541
- nickname: resolvedNickname,
622
+ agentType: sessionResolveAgentType,
542
623
  agentCwd: projectRoot,
543
624
  onResolved: (id, resolved) => {
544
625
  if (providerSessions) {
@@ -548,11 +629,11 @@ async function handleOps(projectRoot, ops = [], processManager = null) {
548
629
  updated_at: new Date().toISOString(),
549
630
  });
550
631
  }
551
- probeHandles.delete(id);
632
+ sessionResolveHandles.delete(id);
552
633
  },
553
634
  });
554
- if (probeHandle) {
555
- probeHandles.set(subscriberId, probeHandle);
635
+ if (sessionResolveHandle) {
636
+ sessionResolveHandles.set(subscriberId, sessionResolveHandle);
556
637
  }
557
638
  }
558
639
  }
@@ -715,21 +796,61 @@ async function dispatchMessages(projectRoot, dispatch = []) {
715
796
  const eventBus = new EventBus(projectRoot);
716
797
  // Always use "ufoo-agent" as the publisher for daemon messages
717
798
  const defaultPublisher = "ufoo-agent";
799
+ const resolveDispatchTarget = (target) => {
800
+ const raw = String(target || "").trim();
801
+ if (!raw || raw === "broadcast") return raw;
802
+ try {
803
+ eventBus.ensureBus();
804
+ eventBus.loadBusData();
805
+ const agents = eventBus.busData && eventBus.busData.agents && typeof eventBus.busData.agents === "object"
806
+ ? eventBus.busData.agents
807
+ : {};
808
+ if (agents[raw]) return raw;
809
+
810
+ const candidates = [raw];
811
+ const scoped = applyProjectNicknamePrefix(projectRoot, raw);
812
+ if (scoped && scoped !== raw) candidates.push(scoped);
813
+
814
+ for (const candidate of candidates) {
815
+ for (const [id, meta] of Object.entries(agents)) {
816
+ if (!meta || meta.status !== "active") continue;
817
+ if (
818
+ meta.nickname === candidate
819
+ || meta.scoped_nickname === candidate
820
+ || meta.display_nickname === candidate
821
+ ) {
822
+ return id;
823
+ }
824
+ }
825
+ }
826
+
827
+ if (eventBus.messageManager && eventBus.messageManager.resolveTarget(raw).length > 0) {
828
+ return raw;
829
+ }
830
+ } catch {
831
+ // Fall through to the original target; send will surface/log the failure below.
832
+ }
833
+ return raw;
834
+ };
718
835
  for (const item of dispatch) {
719
836
  if (!item || !item.target || !item.message) continue;
720
837
  const pub = item.publisher || defaultPublisher;
838
+ const target = resolveDispatchTarget(item.target);
721
839
  const sendOptions = {
722
840
  injectionMode: item.injection_mode,
723
841
  source: item.source,
724
842
  };
725
843
  try {
726
- if (item.target === "broadcast") {
844
+ if (target === "broadcast") {
727
845
  await eventBus.broadcast(item.message, pub, sendOptions);
728
846
  } else {
729
- await eventBus.send(item.target, item.message, pub, sendOptions);
847
+ await eventBus.send(target, item.message, pub, sendOptions);
730
848
  }
731
- } catch {
732
- // ignore dispatch failures
849
+ } catch (err) {
850
+ appendControlLog(
851
+ projectRoot,
852
+ `dispatch failed target=${JSON.stringify(item.target)} resolved=${JSON.stringify(target)} error=${err && err.message ? err.message : String(err)}`
853
+ );
733
854
  }
734
855
  }
735
856
  }
@@ -1093,8 +1214,26 @@ function startDaemon({ projectRoot, provider, model, resumeMode = "auto" }) {
1093
1214
  writePid(projectRoot);
1094
1215
 
1095
1216
  const logFile = fs.createWriteStream(logPath(projectRoot), { flags: "a" });
1217
+ const formatLogLine = (msg) => `[daemon] ${new Date().toISOString()} ${msg}\n`;
1096
1218
  const log = (msg) => {
1097
- logFile.write(`[daemon] ${new Date().toISOString()} ${msg}\n`);
1219
+ logFile.write(formatLogLine(msg));
1220
+ };
1221
+ const logSync = (msg) => {
1222
+ const line = formatLogLine(msg);
1223
+ try {
1224
+ fs.appendFileSync(logPath(projectRoot), line);
1225
+ } catch {
1226
+ try {
1227
+ logFile.write(line);
1228
+ } catch {
1229
+ // ignore fatal logging errors
1230
+ }
1231
+ }
1232
+ };
1233
+ const formatFatalReason = (err) => {
1234
+ if (!err) return "unknown";
1235
+ if (err instanceof Error) return err.stack || err.message;
1236
+ return String(err);
1098
1237
  };
1099
1238
  const publishProjectRuntime = (status = "running") => {
1100
1239
  if (isGlobalControllerProjectRoot(projectRoot)) {
@@ -1119,7 +1258,7 @@ function startDaemon({ projectRoot, provider, model, resumeMode = "auto" }) {
1119
1258
 
1120
1259
  // Provider session cache (in-memory)
1121
1260
  providerSessions = loadProviderSessionCache(projectRoot);
1122
- probeHandles = new Map();
1261
+ sessionResolveHandles = new Map();
1123
1262
  daemonCronController = createDaemonCronController({
1124
1263
  projectRoot,
1125
1264
  dispatch: async ({ taskId, target, message }) => {
@@ -1219,19 +1358,20 @@ function startDaemon({ projectRoot, provider, model, resumeMode = "auto" }) {
1219
1358
  }
1220
1359
  const targetPaths = getUfooPaths(root);
1221
1360
  if (!fs.existsSync(targetPaths.ufooDir)) {
1222
- const repoRoot = path.join(__dirname, "..", "..");
1223
- const init = new (require("../init"))(repoRoot);
1361
+ const repoRoot = path.join(__dirname, "..", "..", "..");
1362
+ const init = new (require("../../app/cli/features/init"))(repoRoot);
1224
1363
  await init.init({ modules: "context,bus", project: root });
1225
1364
  }
1226
1365
  if (!isRunning(root)) {
1227
1366
  cleanupStaleState(root);
1228
- const daemonBin = path.join(__dirname, "..", "..", "bin", "ufoo.js");
1229
- const child = spawn(process.execPath, [daemonBin, "daemon", "--start"], {
1367
+ const daemonBin = path.join(__dirname, "..", "..", "..", "bin", "ufoo.js");
1368
+ const child = spawn(resolveNodeExecutable(), [daemonBin, "daemon", "--start"], {
1230
1369
  detached: true,
1231
1370
  stdio: "ignore",
1232
1371
  cwd: root,
1233
1372
  env: process.env,
1234
1373
  });
1374
+ child.on("error", () => {});
1235
1375
  child.unref();
1236
1376
  }
1237
1377
 
@@ -1483,7 +1623,7 @@ function startDaemon({ projectRoot, provider, model, resumeMode = "auto" }) {
1483
1623
  socket.write(
1484
1624
  `${JSON.stringify({
1485
1625
  type: IPC_RESPONSE_TYPES.ERROR,
1486
- error: "launch_agent requires agent=codex|claude|ucode",
1626
+ error: "launch_agent requires agent=codex|claude|agy|ucode",
1487
1627
  })}
1488
1628
  `,
1489
1629
  );
@@ -1520,9 +1660,9 @@ function startDaemon({ projectRoot, provider, model, resumeMode = "auto" }) {
1520
1660
  : null,
1521
1661
  };
1522
1662
  let soloLaunchBootstrap = null;
1523
- if (requestedProfile && (normalizedAgent === "ufoo" || normalizedAgent === "claude" || normalizedAgent === "codex")) {
1524
- const agentTypeMap = { ufoo: "ufoo-code", claude: "claude-code", codex: "codex" };
1525
- const defaultNickMap = { ufoo: "ucode", claude: "claude", codex: "codex" };
1663
+ if (requestedProfile && (normalizedAgent === "ufoo" || normalizedAgent === "claude" || normalizedAgent === "codex" || normalizedAgent === "agy")) {
1664
+ const agentTypeMap = { ufoo: "ufoo-code", claude: "claude-code", codex: "codex", agy: "agy" };
1665
+ const defaultNickMap = { ufoo: "ucode", claude: "claude", codex: "codex", agy: "agy" };
1526
1666
  const agentTypeForBootstrap = agentTypeMap[normalizedAgent];
1527
1667
  const soloNickname = explicitNickname || defaultNickMap[normalizedAgent];
1528
1668
  const profileResult = resolveSoloPromptProfile(projectRoot, requestedProfile);
@@ -1567,6 +1707,12 @@ function startDaemon({ projectRoot, provider, model, resumeMode = "auto" }) {
1567
1707
  ...(Array.isArray(op.extra_args) ? op.extra_args : []),
1568
1708
  built.promptText,
1569
1709
  ];
1710
+ } else if (normalizedAgent === "agy") {
1711
+ // agy: bootstrap via -i (alias for --prompt-interactive)
1712
+ op.extra_args = [
1713
+ ...(Array.isArray(op.extra_args) ? op.extra_args : []),
1714
+ "-i", built.promptText,
1715
+ ];
1570
1716
  }
1571
1717
  soloLaunchBootstrap = {
1572
1718
  requested_profile: profileResult.requested_profile,
@@ -2146,7 +2292,7 @@ function startDaemon({ projectRoot, provider, model, resumeMode = "auto" }) {
2146
2292
  hostName,
2147
2293
  hostSessionId,
2148
2294
  hostCapabilities,
2149
- skipProbe,
2295
+ skipSessionResolve,
2150
2296
  } = req;
2151
2297
  if (!agentType) {
2152
2298
  socket.write(
@@ -2205,7 +2351,7 @@ function startDaemon({ projectRoot, provider, model, resumeMode = "auto" }) {
2205
2351
  reuseSessionId,
2206
2352
  reuseProviderSessionId,
2207
2353
  };
2208
- if (skipProbe) joinOptions.skipProbe = true;
2354
+ if (skipSessionResolve) joinOptions.skipSessionResolve = true;
2209
2355
 
2210
2356
  let finalNickname = nickname || "";
2211
2357
  let scopedNickname = applyProjectNicknamePrefix(projectRoot, finalNickname, {
@@ -2234,7 +2380,7 @@ function startDaemon({ projectRoot, provider, model, resumeMode = "auto" }) {
2234
2380
  eventBus.saveBusData();
2235
2381
  const resolvedNickname = resolveSubscriberNickname(projectRoot, subscriberId) || finalNickname || "";
2236
2382
 
2237
- if (!skipProbe && reuseProviderSessionId) {
2383
+ if (!skipSessionResolve && reuseProviderSessionId) {
2238
2384
  if (providerSessions) {
2239
2385
  providerSessions.set(subscriberId, {
2240
2386
  sessionId: reuseProviderSessionId,
@@ -2244,12 +2390,11 @@ function startDaemon({ projectRoot, provider, model, resumeMode = "auto" }) {
2244
2390
  }
2245
2391
  }
2246
2392
 
2247
- if (!skipProbe) {
2248
- const probeHandle = scheduleProviderSessionProbe({
2393
+ if (!skipSessionResolve) {
2394
+ const sessionResolveHandle = scheduleProviderSessionResolve({
2249
2395
  projectRoot,
2250
2396
  subscriberId,
2251
2397
  agentType,
2252
- nickname: resolvedNickname,
2253
2398
  agentCwd: projectRoot,
2254
2399
  onResolved: (id, resolved) => {
2255
2400
  if (providerSessions) {
@@ -2259,11 +2404,11 @@ function startDaemon({ projectRoot, provider, model, resumeMode = "auto" }) {
2259
2404
  updated_at: new Date().toISOString(),
2260
2405
  });
2261
2406
  }
2262
- probeHandles.delete(id);
2407
+ sessionResolveHandles.delete(id);
2263
2408
  },
2264
2409
  });
2265
- if (probeHandle) {
2266
- probeHandles.set(subscriberId, probeHandle);
2410
+ if (sessionResolveHandle) {
2411
+ sessionResolveHandles.set(subscriberId, sessionResolveHandle);
2267
2412
  }
2268
2413
  }
2269
2414
  socket.write(
@@ -2318,12 +2463,12 @@ function startDaemon({ projectRoot, provider, model, resumeMode = "auto" }) {
2318
2463
  updated_at: new Date().toISOString(),
2319
2464
  });
2320
2465
  }
2321
- // Cancel the scheduled probe to prevent redundant /ufoo injection
2322
- const handle = probeHandles.get(subscriberId);
2466
+ // Cancel the scheduled resolver; AGENT_READY already found the session file.
2467
+ const handle = sessionResolveHandles.get(subscriberId);
2323
2468
  if (handle && typeof handle.cancel === "function") {
2324
2469
  handle.cancel();
2325
2470
  }
2326
- probeHandles.delete(subscriberId);
2471
+ sessionResolveHandles.delete(subscriberId);
2327
2472
  return;
2328
2473
  }
2329
2474
 
@@ -2335,15 +2480,15 @@ function startDaemon({ projectRoot, provider, model, resumeMode = "auto" }) {
2335
2480
  }
2336
2481
  }
2337
2482
 
2338
- // Exhausted retries or no pid — fall back to scheduled probe
2339
- const probeHandle = probeHandles.get(subscriberId);
2340
- if (probeHandle && typeof probeHandle.triggerNow === "function") {
2341
- log(`agent_ready falling back to probe for ${subscriberId}`);
2342
- probeHandle.triggerNow().catch((err) => {
2343
- log(`agent_ready probe trigger failed for ${subscriberId}: ${err.message}`);
2483
+ // Exhausted retries or no pid — trigger the scheduled file resolver.
2484
+ const sessionResolveHandle = sessionResolveHandles.get(subscriberId);
2485
+ if (sessionResolveHandle && typeof sessionResolveHandle.triggerNow === "function") {
2486
+ log(`agent_ready triggering scheduled session resolver for ${subscriberId}`);
2487
+ sessionResolveHandle.triggerNow().catch((err) => {
2488
+ log(`agent_ready session resolver trigger failed for ${subscriberId}: ${err.message}`);
2344
2489
  });
2345
2490
  } else {
2346
- log(`agent_ready no probe handle found for ${subscriberId}`);
2491
+ log(`agent_ready no session resolver handle found for ${subscriberId}`);
2347
2492
  }
2348
2493
  };
2349
2494
 
@@ -2361,7 +2506,7 @@ function startDaemon({ projectRoot, provider, model, resumeMode = "auto" }) {
2361
2506
  log(`Started pid=${process.pid}`);
2362
2507
 
2363
2508
  // 清理旧 daemon 留下的孤儿 internal agent 进程
2364
- const EventBus = require("../bus");
2509
+ const EventBus = require("../../coordination/bus");
2365
2510
  const { spawnSync } = require("child_process");
2366
2511
  const eventBus = new EventBus(projectRoot);
2367
2512
  try {
@@ -2462,10 +2607,11 @@ function startDaemon({ projectRoot, provider, model, resumeMode = "auto" }) {
2462
2607
  }
2463
2608
 
2464
2609
  let cleanedUp = false;
2465
- const cleanup = () => {
2610
+ const cleanup = (reason = "exit", options = {}) => {
2466
2611
  if (cleanedUp) return;
2467
2612
  cleanedUp = true;
2468
- log(`Shutting down daemon (managed agents: ${processManager.count()})`);
2613
+ const writeLog = options.sync ? logSync : log;
2614
+ writeLog(`Shutting down daemon reason=${reason} (managed agents: ${processManager.count()})`);
2469
2615
  clearInterval(runtimeHeartbeat);
2470
2616
  try {
2471
2617
  if (!isGlobalControllerProjectRoot(projectRoot)) {
@@ -2487,6 +2633,7 @@ function startDaemon({ projectRoot, provider, model, resumeMode = "auto" }) {
2487
2633
  ipcServer.stop();
2488
2634
  busBridge.stop();
2489
2635
  removeSocket(projectRoot);
2636
+ removePidIfOwned(projectRoot);
2490
2637
 
2491
2638
  // 释放锁文件
2492
2639
  try {
@@ -2502,46 +2649,84 @@ function startDaemon({ projectRoot, provider, model, resumeMode = "auto" }) {
2502
2649
  }
2503
2650
  };
2504
2651
 
2505
- process.on("exit", cleanup);
2652
+ process.on("beforeExit", (code) => {
2653
+ logSync(`beforeExit code=${code}`);
2654
+ });
2655
+ process.on("exit", (code) => {
2656
+ cleanup(`exit code=${code}`, { sync: true });
2657
+ });
2506
2658
  process.on("SIGTERM", () => {
2507
- cleanup();
2659
+ cleanup("SIGTERM", { sync: true });
2508
2660
  process.exit(0);
2509
2661
  });
2510
2662
  process.on("SIGINT", () => {
2511
- cleanup();
2663
+ cleanup("SIGINT", { sync: true });
2512
2664
  process.exit(0);
2513
2665
  });
2666
+ process.on("uncaughtException", (err) => {
2667
+ logSync(`uncaughtException: ${formatFatalReason(err)}`);
2668
+ cleanup("uncaughtException", { sync: true });
2669
+ process.exit(1);
2670
+ });
2671
+ process.on("unhandledRejection", (reason) => {
2672
+ logSync(`unhandledRejection: ${formatFatalReason(reason)}`);
2673
+ cleanup("unhandledRejection", { sync: true });
2674
+ process.exit(1);
2675
+ });
2514
2676
  }
2515
2677
 
2516
- function stopDaemon(projectRoot) {
2678
+ function stopDaemon(projectRoot, options = {}) {
2517
2679
  const pid = readPid(projectRoot);
2518
- if (!pid) {
2519
- removeSocket(projectRoot);
2520
- return false;
2680
+ const pids = new Set(socketOwnerDaemonPids(projectRoot));
2681
+ if (pid && isPidFileDaemonForProject(projectRoot, pid, pids)) {
2682
+ pids.add(pid);
2521
2683
  }
2684
+ const source = String(
2685
+ options.source
2686
+ || process.env.UFOO_DAEMON_STOP_SOURCE
2687
+ || `pid=${process.pid} cwd=${process.cwd()} argv=${process.argv.join(" ")}`
2688
+ ).slice(0, 1200);
2689
+ appendControlLog(
2690
+ projectRoot,
2691
+ `stop requested source=${JSON.stringify(source)} pid_file=${pid || ""} target_pids=[${Array.from(pids).join(",")}]`
2692
+ );
2522
2693
  let killed = false;
2523
- try {
2524
- process.kill(pid, "SIGTERM");
2525
- const started = Date.now();
2526
- while (Date.now() - started < 1500) {
2527
- try {
2528
- process.kill(pid, 0);
2529
- } catch {
2530
- killed = true;
2531
- break;
2694
+ for (const targetPid of pids) {
2695
+ try {
2696
+ process.kill(targetPid, "SIGTERM");
2697
+ killed = true;
2698
+ } catch {
2699
+ // ignore kill errors (e.g., already dead)
2700
+ }
2701
+ }
2702
+ const started = Date.now();
2703
+ while (Date.now() - started < 1500) {
2704
+ let anyAlive = false;
2705
+ for (const targetPid of pids) {
2706
+ if (pidAlive(targetPid)) {
2707
+ anyAlive = true;
2532
2708
  }
2533
2709
  }
2534
- // Force kill if still alive.
2710
+ if (!anyAlive) break;
2711
+ }
2712
+ // Force kill if still alive.
2713
+ for (const targetPid of pids) {
2535
2714
  try {
2536
- process.kill(pid, 0);
2537
- process.kill(pid, "SIGKILL");
2538
- killed = true;
2715
+ if (pidAlive(targetPid)) {
2716
+ process.kill(targetPid, "SIGKILL");
2717
+ killed = true;
2718
+ }
2539
2719
  } catch {
2540
2720
  // ignore if already dead
2541
2721
  }
2542
- } catch {
2543
- // ignore kill errors (e.g., already dead)
2544
2722
  }
2723
+
2724
+ const stillAlive = Array.from(pids).filter((targetPid) => pidAlive(targetPid));
2725
+ if (stillAlive.length > 0) {
2726
+ appendControlLog(projectRoot, `stop failed still_alive=[${stillAlive.join(",")}]`);
2727
+ return false;
2728
+ }
2729
+
2545
2730
  try {
2546
2731
  fs.unlinkSync(pidPath(projectRoot));
2547
2732
  } catch {
@@ -2567,7 +2752,16 @@ function stopDaemon(projectRoot) {
2567
2752
  // ignore
2568
2753
  }
2569
2754
 
2570
- return killed;
2755
+ const stopped = killed || pids.size === 0;
2756
+ appendControlLog(projectRoot, `stop completed stopped=${stopped} killed=${killed} target_count=${pids.size}`);
2757
+ return stopped;
2571
2758
  }
2572
2759
 
2573
- module.exports = { startDaemon, stopDaemon, isRunning, cleanupStaleState, socketPath };
2760
+ module.exports = {
2761
+ startDaemon,
2762
+ stopDaemon,
2763
+ isRunning,
2764
+ cleanupStaleState,
2765
+ socketPath,
2766
+ dispatchMessages,
2767
+ };
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
 
3
3
  const net = require("net");
4
- const { IPC_RESPONSE_TYPES } = require("../shared/eventContract");
4
+ const { IPC_RESPONSE_TYPES } = require("../contracts/eventContract");
5
5
 
6
6
  function createDaemonIpcServer(options = {}) {
7
7
  const {
@@ -56,6 +56,9 @@ function createDaemonIpcServer(options = {}) {
56
56
  const server = net.createServer((socket) => {
57
57
  sockets.add(socket);
58
58
  socket.on("close", () => sockets.delete(socket));
59
+ socket.on("error", (err) => {
60
+ log(`ipc socket error: ${err && err.message ? err.message : String(err || "unknown error")}`);
61
+ });
59
62
  let buffer = "";
60
63
  socket.on("data", async (data) => {
61
64
  buffer += data.toString("utf8");
@@ -66,12 +69,31 @@ function createDaemonIpcServer(options = {}) {
66
69
  const items = parseJsonLines(line);
67
70
  for (const req of items) {
68
71
  if (!req || typeof req !== "object") continue;
69
- await handleRequest(req, socket);
72
+ try {
73
+ await handleRequest(req, socket);
74
+ } catch (err) {
75
+ const message = err && err.message ? err.message : String(err || "request failed");
76
+ const requestType = String(req.type || "unknown");
77
+ log(`ipc request failed type=${requestType}: ${err && err.stack ? err.stack : message}`);
78
+ try {
79
+ socket.write(`${JSON.stringify({
80
+ type: IPC_RESPONSE_TYPES.ERROR,
81
+ error: message,
82
+ request_type: requestType,
83
+ })}\n`);
84
+ } catch {
85
+ // ignore failed error replies
86
+ }
87
+ }
70
88
  }
71
89
  }
72
90
  });
73
91
  });
74
92
 
93
+ server.on("error", (err) => {
94
+ log(`ipc server error: ${err && err.message ? err.message : String(err || "unknown error")}`);
95
+ });
96
+
75
97
  function listen(sockPath) {
76
98
  server.listen(sockPath);
77
99
  }