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.
- package/README.md +157 -213
- package/README.zh-CN.md +151 -197
- package/SKILLS/ufoo/SKILL.md +8 -8
- package/bin/uagy.js +69 -0
- package/bin/uclaude.js +2 -2
- package/bin/ucode.js +4 -4
- package/bin/ucodex.js +2 -2
- package/bin/ufoo.js +5 -23
- package/modules/AGENTS.template.md +1 -1
- package/modules/bus/SKILLS/ubus/SKILL.md +35 -10
- package/package.json +9 -5
- package/scripts/chat-app-smoke.js +30 -0
- package/scripts/global-chat-switch-benchmark.js +5 -5
- package/scripts/ink-demo.js +23 -0
- package/scripts/ink-smoke.js +30 -0
- package/scripts/ucode-app-smoke.js +36 -0
- package/src/{agent → agents/activity}/activityDetector.js +39 -2
- package/src/{agent → agents/activity}/activityStatePublisher.js +1 -1
- package/src/{agent → agents/activity}/activityStateWriter.js +2 -2
- package/src/{agent → agents/activity}/activityTracker.js +1 -1
- package/src/agents/activity/index.js +8 -0
- package/src/{agent → agents/controller}/controllerToolExecutor.js +4 -4
- package/src/agents/controller/index.js +8 -0
- package/src/{agent → agents/controller}/loopObservability.js +2 -2
- package/src/{agent → agents/controller}/loopRuntime.js +1 -1
- package/src/{agent → agents/controller}/ufooAgent.js +9 -9
- package/src/agents/index.js +10 -0
- package/src/agents/internal/index.js +3 -0
- package/src/{agent → agents/internal}/internalRunner.js +45 -22
- package/src/agents/launch/agyConversation.js +159 -0
- package/src/agents/launch/index.js +12 -0
- package/src/{agent → agents/launch}/launchEnvironment.js +2 -3
- package/src/{agent → agents/launch}/launcher.js +64 -21
- package/src/{agent → agents/launch}/notifier.js +23 -12
- package/src/{agent → agents/launch}/ptyRunner.js +44 -12
- package/src/{agent → agents/launch}/ptyWrapper.js +2 -2
- package/src/{agent → agents/launch}/publisherRouting.js +1 -1
- package/src/{agent → agents/launch}/readyDetector.js +23 -0
- package/src/{agent → agents/prompts}/defaultBootstrap.js +63 -4
- package/src/{group/bootstrap.js → agents/prompts/groupBootstrap.js} +41 -6
- package/src/agents/prompts/index.js +8 -0
- package/src/{code/prompts → agents/prompts/native}/index.js +1 -1
- package/src/{agent → agents/providers}/claudeThreadProvider.js +1 -1
- package/src/{agent → agents/providers}/codexThreadProvider.js +1 -1
- package/src/{agent → agents/providers}/directAuthStatus.js +184 -1
- package/src/agents/providers/index.js +13 -0
- package/src/{agent → agents/providers}/upstreamTransport.js +2 -2
- package/src/{chat → app/chat}/agentSockets.js +1 -1
- package/src/{chat → app/chat}/commandExecutor.js +56 -28
- package/src/{chat → app/chat}/commands.js +119 -5
- package/src/{chat → app/chat}/daemonConnection.js +1 -1
- package/src/{chat → app/chat}/daemonMessageRouter.js +54 -4
- package/src/{chat → app/chat}/daemonTransport.js +2 -1
- package/src/{chat → app/chat}/dashboardView.js +2 -21
- package/src/app/chat/index.js +6 -0
- package/src/{chat → app/chat}/inputSubmitHandler.js +38 -13
- package/src/{chat → app/chat}/internalAgentLogHistory.js +1 -1
- package/src/app/chat/multiWindow/index.js +268 -0
- package/src/app/chat/multiWindow/paneLayout.js +84 -0
- package/src/app/chat/multiWindow/paneManager.js +299 -0
- package/src/app/chat/multiWindow/renderer.js +384 -0
- package/src/app/chat/multiWindow/virtualTerminal.js +327 -0
- package/src/{chat → app/chat}/projectCloseController.js +1 -1
- package/src/app/chat/shellCommand.js +42 -0
- package/src/{chat → app/chat}/transport.js +16 -3
- package/src/{cli → app/cli}/ctxCoreCommands.js +3 -3
- package/src/{doctor/index.js → app/cli/features/doctor.js} +1 -1
- package/src/{init/index.js → app/cli/features/init.js} +14 -32
- package/src/{cli → app/cli}/groupCoreCommands.js +2 -2
- package/src/app/cli/index.js +9 -0
- package/src/{cli → app/cli}/onlineCoreCommands.js +5 -5
- package/src/{cli.js → app/cli/run.js} +62 -59
- package/src/app/index.js +6 -0
- package/src/code/agent.js +10 -9
- package/src/code/index.js +2 -0
- package/src/code/launcher/index.js +9 -0
- package/src/{agent → code/launcher}/ucode.js +7 -8
- package/src/{agent → code/launcher}/ucodeBootstrap.js +3 -3
- package/src/{agent → code/launcher}/ucodeBuild.js +2 -2
- package/src/{agent → code/launcher}/ucodeDoctor.js +2 -2
- package/src/{agent → code/launcher}/ucodeRuntimeConfig.js +1 -2
- package/src/code/nativeRunner.js +4 -4
- package/src/code/taskDecomposer.js +5 -4
- package/src/code/tui.js +39 -1997
- package/src/config.js +15 -2
- package/src/{bus → coordination/bus}/activate.js +2 -2
- package/src/{bus → coordination/bus}/daemon.js +15 -5
- package/src/coordination/bus/envelope.js +173 -0
- package/src/{bus → coordination/bus}/index.js +7 -3
- package/src/{bus → coordination/bus}/inject.js +11 -3
- package/src/{bus → coordination/bus}/message.js +1 -1
- package/src/coordination/bus/messageMeta.js +130 -0
- package/src/coordination/bus/promptEnvelope.js +65 -0
- package/src/{bus → coordination/bus}/shake.js +1 -1
- package/src/{bus → coordination/bus}/store.js +3 -3
- package/src/{bus → coordination/bus}/subscriber.js +2 -2
- package/src/{bus → coordination/bus}/utils.js +2 -2
- package/src/{history → coordination/history}/inputTimeline.js +5 -5
- package/src/coordination/index.js +10 -0
- package/src/{memory → coordination/memory}/historySearch.js +1 -1
- package/src/{memory → coordination/memory}/index.js +3 -3
- package/src/{report → coordination/report}/store.js +2 -2
- package/src/{ufoo → coordination/state}/agentRegistryDiagnostics.js +43 -0
- package/src/{status → coordination/status}/index.js +3 -3
- package/src/online/bridge.js +2 -2
- package/src/{controller → orchestration/controller}/flags.js +1 -1
- package/src/{controller → orchestration/controller}/gateRouter.js +1 -1
- package/src/orchestration/controller/index.js +10 -0
- package/src/{controller → orchestration/controller}/shadowGuard.js +1 -1
- package/src/orchestration/groups/bootstrap.js +3 -0
- package/src/orchestration/groups/index.js +10 -0
- package/src/orchestration/groups/promptProfiles.js +3 -0
- package/src/{group → orchestration/groups}/templates.js +1 -1
- package/src/{group → orchestration/groups}/validateTemplate.js +1 -1
- package/src/orchestration/index.js +7 -0
- package/src/orchestration/solo/index.js +3 -0
- package/src/{daemon → runtime/daemon}/agentProcessManager.js +1 -1
- package/src/{daemon → runtime/daemon}/cronOps.js +3 -2
- package/src/{daemon → runtime/daemon}/groupOrchestrator.js +26 -9
- package/src/{daemon → runtime/daemon}/index.js +273 -79
- package/src/{daemon → runtime/daemon}/ipcServer.js +24 -2
- package/src/{daemon → runtime/daemon}/nicknameScope.js +6 -3
- package/src/{daemon → runtime/daemon}/ops.js +48 -61
- package/src/{daemon → runtime/daemon}/promptLoop.js +1 -1
- package/src/{daemon → runtime/daemon}/promptRequest.js +13 -8
- package/src/runtime/daemon/providerSessions.js +230 -0
- package/src/{daemon → runtime/daemon}/reporting.js +4 -4
- package/src/{daemon → runtime/daemon}/run.js +12 -5
- package/src/{daemon → runtime/daemon}/soloBootstrap.js +7 -7
- package/src/{daemon → runtime/daemon}/status.js +5 -5
- package/src/runtime/index.js +10 -0
- package/src/runtime/process/nodeExecutable.js +26 -0
- package/src/{projects → runtime/projects}/registry.js +1 -1
- package/src/{projects → runtime/projects}/runtimes.js +1 -1
- package/src/{terminal → runtime/terminal}/adapterRouter.js +0 -10
- package/src/{terminal → runtime/terminal}/adapters/internalAdapter.js +0 -4
- package/src/tools/handlers/common.js +1 -1
- package/src/tools/handlers/listAgents.js +1 -1
- package/src/tools/handlers/memory.js +3 -3
- package/src/tools/handlers/readBusSummary.js +1 -1
- package/src/tools/handlers/readOpenDecisions.js +1 -1
- package/src/tools/handlers/readProjectRegistry.js +1 -1
- package/src/tools/handlers/readPromptHistory.js +2 -2
- package/src/tools/schemaFixtures.js +1 -1
- package/src/ui/MIGRATION.md +336 -0
- package/src/ui/format/index.js +974 -0
- package/src/ui/index.js +9 -0
- package/src/ui/ink/ChatApp.js +3674 -0
- package/src/ui/ink/DashboardBar.js +685 -0
- package/src/ui/ink/InkDemo.js +96 -0
- package/src/ui/ink/MultilineInput.js +612 -0
- package/src/ui/ink/UcodeApp.js +822 -0
- package/src/ui/ink/agentMirror.js +730 -0
- package/src/ui/ink/chatReducer.js +359 -0
- package/src/ui/runInk.js +57 -0
- package/src/bus/messageMeta.js +0 -52
- package/src/chat/agentViewController.js +0 -1072
- package/src/chat/chatLogController.js +0 -138
- package/src/chat/completionController.js +0 -533
- package/src/chat/dashboardKeyController.js +0 -573
- package/src/chat/index.js +0 -2214
- package/src/chat/inputHistoryController.js +0 -135
- package/src/chat/inputListenerController.js +0 -470
- package/src/chat/layout.js +0 -186
- package/src/chat/pasteController.js +0 -81
- package/src/chat/statusLineController.js +0 -223
- package/src/chat/streamTracker.js +0 -156
- package/src/code/config +0 -0
- package/src/daemon/providerSessions.js +0 -488
- package/src/terminal/adapters/internalPtyAdapter.js +0 -42
- /package/src/{code/prompts → agents/prompts/native}/actions.js +0 -0
- /package/src/{code/prompts → agents/prompts/native}/efficiency.js +0 -0
- /package/src/{code/prompts → agents/prompts/native}/environment.js +0 -0
- /package/src/{code/prompts → agents/prompts/native}/identity.js +0 -0
- /package/src/{code/prompts → agents/prompts/native}/safety.js +0 -0
- /package/src/{code/prompts → agents/prompts/native}/sections.js +0 -0
- /package/src/{code/prompts → agents/prompts/native}/system.js +0 -0
- /package/src/{code/prompts → agents/prompts/native}/tasks.js +0 -0
- /package/src/{code/prompts → agents/prompts/native}/toolDescriptions/bash.js +0 -0
- /package/src/{code/prompts → agents/prompts/native}/toolDescriptions/edit.js +0 -0
- /package/src/{code/prompts → agents/prompts/native}/toolDescriptions/read.js +0 -0
- /package/src/{code/prompts → agents/prompts/native}/toolDescriptions/write.js +0 -0
- /package/src/{code/prompts → agents/prompts/native}/ufoo.js +0 -0
- /package/src/{group → agents/prompts}/promptProfiles.js +0 -0
- /package/src/{agent → agents/providers}/claudeEventTranslator.js +0 -0
- /package/src/{agent → agents/providers}/claudeOauthTokenReader.js +0 -0
- /package/src/{agent → agents/providers}/claudeSessionFiles.js +0 -0
- /package/src/{agent → agents/providers}/codexEventTranslator.js +0 -0
- /package/src/{agent → agents/providers}/credentials/claude.js +0 -0
- /package/src/{agent → agents/providers}/credentials/codex.js +0 -0
- /package/src/{agent → agents/providers}/credentials/index.js +0 -0
- /package/src/{chat → app/chat}/agentBar.js +0 -0
- /package/src/{chat → app/chat}/agentDirectory.js +0 -0
- /package/src/{chat → app/chat}/cronScheduler.js +0 -0
- /package/src/{chat → app/chat}/daemonCoordinator.js +0 -0
- /package/src/{chat → app/chat}/daemonReconnect.js +0 -0
- /package/src/{chat → app/chat}/daemonTransportDefaults.js +0 -0
- /package/src/{chat → app/chat}/inputMath.js +0 -0
- /package/src/{chat → app/chat}/rawKeyMap.js +0 -0
- /package/src/{chat → app/chat}/settingsController.js +0 -0
- /package/src/{chat → app/chat}/text.js +0 -0
- /package/src/{chat → app/chat}/transientAgentState.js +0 -0
- /package/src/{cli → app/cli}/busCoreCommands.js +0 -0
- /package/src/{skills/index.js → app/cli/features/skills.js} +0 -0
- /package/src/{bus → coordination/bus}/nickname.js +0 -0
- /package/src/{bus → coordination/bus}/queue.js +0 -0
- /package/src/{context → coordination/context}/decisions.js +0 -0
- /package/src/{context → coordination/context}/doctor.js +0 -0
- /package/src/{context → coordination/context}/index.js +0 -0
- /package/src/{context → coordination/context}/sync.js +0 -0
- /package/src/{ufoo → coordination/state}/agentsStore.js +0 -0
- /package/src/{ufoo → coordination/state}/paths.js +0 -0
- /package/src/{controller → orchestration/controller}/launchRouting.js +0 -0
- /package/src/{controller → orchestration/controller}/routerFastPath.js +0 -0
- /package/src/{controller → orchestration/controller}/routerFinalize.js +0 -0
- /package/src/{group → orchestration/groups}/diagram.js +0 -0
- /package/src/{group → orchestration/groups}/templateValidation.js +0 -0
- /package/src/{solo → orchestration/solo}/commands.js +0 -0
- /package/src/{shared → runtime/contracts}/eventContract.js +0 -0
- /package/src/{shared → runtime/contracts}/ptySocketContract.js +0 -0
- /package/src/{providerapi → runtime/privacy}/redactor.js +0 -0
- /package/src/{providerapi → runtime/privacy}/shadowDiff.js +0 -0
- /package/src/{projects → runtime/projects}/identity.js +0 -0
- /package/src/{projects → runtime/projects}/index.js +0 -0
- /package/src/{projects → runtime/projects}/projectId.js +0 -0
- /package/src/{terminal → runtime/terminal}/adapterContract.js +0 -0
- /package/src/{terminal → runtime/terminal}/adapters/externalAdapter.js +0 -0
- /package/src/{terminal → runtime/terminal}/adapters/hostAdapter.js +0 -0
- /package/src/{terminal → runtime/terminal}/adapters/internalQueueAdapter.js +0 -0
- /package/src/{terminal → runtime/terminal}/adapters/terminalAdapter.js +0 -0
- /package/src/{terminal → runtime/terminal}/adapters/tmuxAdapter.js +0 -0
- /package/src/{terminal → runtime/terminal}/detect.js +0 -0
- /package/src/{terminal → runtime/terminal}/index.js +0 -0
- /package/src/{terminal → runtime/terminal}/iterm2.js +0 -0
- /package/src/{utils → ui/format}/banner.js +0 -0
- /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("
|
|
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("
|
|
8
|
+
const EventBus = require("../../coordination/bus");
|
|
9
9
|
const { AgentProcessManager } = require("./agentProcessManager");
|
|
10
|
-
const NicknameManager = require("
|
|
11
|
-
const { generateInstanceId, subscriberToSafeName } = require("
|
|
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("../
|
|
14
|
-
const { getUfooPaths } = require("
|
|
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 {
|
|
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("
|
|
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
|
|
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
|
|
614
|
+
const sessionResolveAgentType = agent === "codex"
|
|
532
615
|
? "codex"
|
|
533
616
|
: (agent === "claude" ? "claude-code" : "");
|
|
534
617
|
for (const subscriberId of launchResult.subscriberIds) {
|
|
535
|
-
if (!
|
|
536
|
-
const
|
|
537
|
-
const probeHandle = scheduleProviderSessionProbe({
|
|
618
|
+
if (!sessionResolveAgentType) continue;
|
|
619
|
+
const sessionResolveHandle = scheduleProviderSessionResolve({
|
|
538
620
|
projectRoot,
|
|
539
621
|
subscriberId,
|
|
540
|
-
agentType:
|
|
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
|
-
|
|
632
|
+
sessionResolveHandles.delete(id);
|
|
552
633
|
},
|
|
553
634
|
});
|
|
554
|
-
if (
|
|
555
|
-
|
|
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 (
|
|
844
|
+
if (target === "broadcast") {
|
|
727
845
|
await eventBus.broadcast(item.message, pub, sendOptions);
|
|
728
846
|
} else {
|
|
729
|
-
await eventBus.send(
|
|
847
|
+
await eventBus.send(target, item.message, pub, sendOptions);
|
|
730
848
|
}
|
|
731
|
-
} catch {
|
|
732
|
-
|
|
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(
|
|
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
|
-
|
|
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("
|
|
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(
|
|
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
|
-
|
|
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 (
|
|
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 (!
|
|
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 (!
|
|
2248
|
-
const
|
|
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
|
-
|
|
2407
|
+
sessionResolveHandles.delete(id);
|
|
2263
2408
|
},
|
|
2264
2409
|
});
|
|
2265
|
-
if (
|
|
2266
|
-
|
|
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
|
|
2322
|
-
const handle =
|
|
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
|
-
|
|
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 —
|
|
2339
|
-
const
|
|
2340
|
-
if (
|
|
2341
|
-
log(`agent_ready
|
|
2342
|
-
|
|
2343
|
-
log(`agent_ready
|
|
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
|
|
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("
|
|
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
|
-
|
|
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("
|
|
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
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
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
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
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
|
-
|
|
2710
|
+
if (!anyAlive) break;
|
|
2711
|
+
}
|
|
2712
|
+
// Force kill if still alive.
|
|
2713
|
+
for (const targetPid of pids) {
|
|
2535
2714
|
try {
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
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
|
-
|
|
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 = {
|
|
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("../
|
|
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
|
-
|
|
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
|
}
|