@nowcrew/daemon 0.5.27 → 0.5.28

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 (77) hide show
  1. package/package.json +2 -2
  2. package/dist/attachments.js +0 -196
  3. package/dist/bound-im-decision.js +0 -22
  4. package/dist/completion-retransmitter.js +0 -77
  5. package/dist/computer-cli.js +0 -274
  6. package/dist/computer-profile-lock.js +0 -395
  7. package/dist/computer-profile.js +0 -364
  8. package/dist/computer-service.js +0 -358
  9. package/dist/config.js +0 -82
  10. package/dist/console-collapse.js +0 -13
  11. package/dist/console-formatter.js +0 -77
  12. package/dist/console-payload.js +0 -73
  13. package/dist/console.js +0 -329
  14. package/dist/daemon-startup-error.js +0 -30
  15. package/dist/execution-backend.js +0 -44
  16. package/dist/execution-event-limit.js +0 -64
  17. package/dist/execution-journal-lock.js +0 -421
  18. package/dist/execution-journal.js +0 -716
  19. package/dist/execution-protocol.js +0 -342
  20. package/dist/execution-recovery.js +0 -95
  21. package/dist/execution-runner.js +0 -659
  22. package/dist/execution-supervisor-child.js +0 -236
  23. package/dist/execution-supervisor.js +0 -316
  24. package/dist/execution-telemetry-journal.js +0 -71
  25. package/dist/external-output.js +0 -114
  26. package/dist/i18n.js +0 -64
  27. package/dist/json-result.js +0 -27
  28. package/dist/list-models.js +0 -92
  29. package/dist/local-executor.js +0 -439
  30. package/dist/log-format.js +0 -10
  31. package/dist/machine-info.js +0 -124
  32. package/dist/main.js +0 -118
  33. package/dist/normalize.js +0 -170
  34. package/dist/origin-decision.js +0 -44
  35. package/dist/platform.js +0 -8
  36. package/dist/prompt.js +0 -307
  37. package/dist/provider-env.js +0 -90
  38. package/dist/remote/claude-bridge.js +0 -402
  39. package/dist/remote/claude-channel.js +0 -164
  40. package/dist/remote/codex-client.js +0 -408
  41. package/dist/remote/codex-runtime.js +0 -77
  42. package/dist/remote/config.js +0 -83
  43. package/dist/remote/gateway.js +0 -572
  44. package/dist/remote/protocol.js +0 -178
  45. package/dist/remote/remote-cli.js +0 -233
  46. package/dist/remote/session-discovery.js +0 -249
  47. package/dist/remote/wrapper.js +0 -40
  48. package/dist/runner.js +0 -234
  49. package/dist/runtime-cancellation.js +0 -74
  50. package/dist/runtime-capabilities.js +0 -43
  51. package/dist/runtime-path.js +0 -60
  52. package/dist/runtimes/claude.js +0 -51
  53. package/dist/runtimes/codex-app-server-runner.js +0 -344
  54. package/dist/runtimes/codex-deepseek-catalog.js +0 -7
  55. package/dist/runtimes/codex-deepseek-config.js +0 -50
  56. package/dist/runtimes/codex.js +0 -53
  57. package/dist/runtimes/kimi-acp-runner.js +0 -364
  58. package/dist/runtimes/kimi.js +0 -45
  59. package/dist/runtimes/progress-watchdog.js +0 -26
  60. package/dist/scheduled-report.js +0 -51
  61. package/dist/scheduled-run-report.js +0 -57
  62. package/dist/serve-lifecycle.js +0 -82
  63. package/dist/serve.js +0 -868
  64. package/dist/session.js +0 -82
  65. package/dist/shared-execution-slots.js +0 -68
  66. package/dist/shutdown-deadline.js +0 -32
  67. package/dist/skill-preview.js +0 -21
  68. package/dist/skills.js +0 -56
  69. package/dist/slog.js +0 -228
  70. package/dist/supervised-runtime.js +0 -104
  71. package/dist/token.js +0 -24
  72. package/dist/unified-diff.js +0 -84
  73. package/dist/websocket-shutdown.js +0 -53
  74. package/dist/win32-job-object.js +0 -193
  75. package/dist/workspace-fs.js +0 -80
  76. package/dist/workspace-import.js +0 -127
  77. package/dist/workspace.js +0 -148
@@ -1,364 +0,0 @@
1
- import { once } from "node:events";
2
- import { parseArgs } from "node:util";
3
- import { Readable, Writable } from "node:stream";
4
- import { pathToFileURL } from "node:url";
5
- import spawn from "cross-spawn";
6
- import { PROTOCOL_VERSION, client, methods, ndJsonStream, } from "@agentclientprotocol/sdk";
7
- import { augmentedPath } from "../runtime-path.js";
8
- import { startFirstProgressWatchdog } from "./progress-watchdog.js";
9
- import { assertKimiLegacyPromptFits, buildKimiArgs } from "./kimi.js";
10
- const ERROR_MESSAGE_CAP = 2_000;
11
- const PROBE_TIMEOUT_MS = 5_000;
12
- function jsonLine(event) {
13
- if (process.stdout.write(`${JSON.stringify(event)}\n`))
14
- return Promise.resolve();
15
- return once(process.stdout, "drain").then(() => undefined);
16
- }
17
- function textContent(content) {
18
- if (typeof content === "string")
19
- return content;
20
- if (!Array.isArray(content))
21
- return "";
22
- return content.flatMap((part) => {
23
- if (part && typeof part === "object" && "text" in part && typeof part.text === "string") {
24
- return [part.text];
25
- }
26
- if (part && typeof part === "object" && "content" in part
27
- && part.content && typeof part.content === "object"
28
- && "type" in part.content && part.content.type === "text"
29
- && "text" in part.content && typeof part.content.text === "string") {
30
- return [part.content.text];
31
- }
32
- return [];
33
- }).join("\n");
34
- }
35
- /** Translate stable ACP updates into daemon-owned NDJSON, without exposing thought chunks. */
36
- export function mapKimiAcpUpdate(update) {
37
- if (update.sessionUpdate === "agent_message_chunk" && update.content.type === "text") {
38
- return [{ type: "kimi.acp.text_delta", text: update.content.text }];
39
- }
40
- if (update.sessionUpdate === "tool_call") {
41
- return [{
42
- type: "kimi.acp.tool_call",
43
- id: update.toolCallId,
44
- title: update.title,
45
- ...(update.kind === undefined ? {} : { kind: update.kind }),
46
- ...(update.status === undefined ? {} : { status: update.status }),
47
- ...(update.rawInput === undefined ? {} : { input: update.rawInput }),
48
- }];
49
- }
50
- if (update.sessionUpdate === "tool_call_update") {
51
- const output = textContent(update.content);
52
- return [{
53
- type: "kimi.acp.tool_result",
54
- id: update.toolCallId,
55
- ...(update.status === undefined ? {} : { status: update.status }),
56
- ...(output ? { content: output } : {}),
57
- }];
58
- }
59
- return [];
60
- }
61
- function safeErrorMessage(error, prompt) {
62
- const raw = error instanceof Error ? error.message : String(error);
63
- const redacted = prompt && raw.includes(prompt) ? raw.replaceAll(prompt, "[prompt redacted]") : raw;
64
- return redacted.slice(0, ERROR_MESSAGE_CAP);
65
- }
66
- export function isKimiAuthenticationRequired(error) {
67
- const message = error instanceof Error ? error.message : String(error);
68
- return /\bauthentication required\b/i.test(message);
69
- }
70
- export function kimiResumeMethod(capabilities) {
71
- if (capabilities?.sessionCapabilities?.resume != null)
72
- return "resume";
73
- if (capabilities?.loadSession)
74
- return "load";
75
- return null;
76
- }
77
- /** Full access may approve an operation, but it must never fabricate an answer to an agent question. */
78
- export function selectKimiPermission(params) {
79
- const allowOnce = params.options.filter((option) => option.kind === "allow_once");
80
- if (params.toolCall.title === "AskUserQuestion" || allowOnce.length > 1) {
81
- return { outcome: { outcome: "cancelled" } };
82
- }
83
- const allowed = allowOnce[0]
84
- ?? (params.options.filter((option) => option.kind === "allow_always").length === 1
85
- ? params.options.find((option) => option.kind === "allow_always")
86
- : undefined);
87
- return allowed === undefined
88
- ? { outcome: { outcome: "cancelled" } }
89
- : { outcome: { outcome: "selected", optionId: allowed.optionId } };
90
- }
91
- async function readPrompt() {
92
- process.stdin.setEncoding("utf8");
93
- let prompt = "";
94
- for await (const chunk of process.stdin)
95
- prompt += String(chunk);
96
- if (!prompt)
97
- throw new Error("Kimi ACP prompt is empty");
98
- return prompt;
99
- }
100
- async function stopChild(child) {
101
- if (child.exitCode !== null || child.signalCode !== null)
102
- return;
103
- const closed = once(child, "close").then(() => undefined);
104
- child.kill("SIGTERM");
105
- let timer;
106
- const graceful = await Promise.race([
107
- closed.then(() => true),
108
- new Promise((resolve) => { timer = setTimeout(() => resolve(false), 1_000); }),
109
- ]);
110
- if (timer !== undefined)
111
- clearTimeout(timer);
112
- if (!graceful && child.exitCode === null && child.signalCode === null) {
113
- child.kill("SIGKILL");
114
- await closed;
115
- }
116
- }
117
- /** Probe the ACP transport without starting a session or forcing an optional interactive login flow. */
118
- export async function probeKimiAcp(options, spawnProcess = spawn) {
119
- const child = spawnProcess(options.bin, ["acp"], {
120
- cwd: process.cwd(),
121
- // probe 在 daemon 自身 PATH 下运行,补上用户级 CLI 目录,与 which 探测保持一致。
122
- env: { ...process.env, PATH: augmentedPath() },
123
- stdio: ["pipe", "pipe", "pipe"],
124
- });
125
- if (child.stdin === null || child.stdout === null || child.stderr === null)
126
- return false;
127
- child.stderr.resume();
128
- const app = client({ name: "nowcrew-daemon-kimi-probe" })
129
- .onRequest(methods.client.session.requestPermission, () => ({
130
- outcome: { outcome: "cancelled" },
131
- }))
132
- .onNotification(methods.client.session.update, () => undefined);
133
- let timeout;
134
- try {
135
- const stream = ndJsonStream(Writable.toWeb(child.stdin), Readable.toWeb(child.stdout));
136
- const connected = app.connectWith(stream, async (context) => {
137
- await context.request(methods.agent.initialize, {
138
- protocolVersion: PROTOCOL_VERSION,
139
- clientCapabilities: {},
140
- clientInfo: { name: "nowcrew-daemon", version: "1" },
141
- });
142
- return true;
143
- });
144
- const result = await Promise.race([
145
- connected,
146
- new Promise((resolve) => {
147
- timeout = setTimeout(() => {
148
- void stopChild(child);
149
- resolve(false);
150
- }, PROBE_TIMEOUT_MS);
151
- }),
152
- ]);
153
- return result;
154
- }
155
- catch {
156
- return false;
157
- }
158
- finally {
159
- if (timeout !== undefined)
160
- clearTimeout(timeout);
161
- await stopChild(child);
162
- }
163
- }
164
- export async function runKimiAcp(options) {
165
- const prompt = await readPrompt();
166
- const child = spawn(options.bin, ["acp"], {
167
- cwd: process.cwd(),
168
- env: process.env,
169
- stdio: ["pipe", "pipe", "pipe"],
170
- });
171
- let runtimeChild = child;
172
- if (child.stdin === null || child.stdout === null || child.stderr === null) {
173
- throw new Error("Kimi ACP process did not expose stdio");
174
- }
175
- child.stderr.pipe(process.stderr, { end: false });
176
- let context = null;
177
- let sessionId = null;
178
- let acpSemanticProgress = false;
179
- let progressTimedOut = false;
180
- let cancelling = false;
181
- const cancel = async () => {
182
- if (cancelling)
183
- return;
184
- cancelling = true;
185
- if (context !== null && sessionId !== null) {
186
- await context.notify(methods.agent.session.cancel, { sessionId }).catch(() => undefined);
187
- }
188
- await stopChild(runtimeChild);
189
- };
190
- const onSignal = () => {
191
- void cancel().finally(() => process.exit(130));
192
- };
193
- process.once("SIGTERM", onSignal);
194
- process.once("SIGINT", onSignal);
195
- const app = client({ name: "nowcrew-daemon-kimi" })
196
- .onRequest(methods.client.session.requestPermission, ({ params }) => selectKimiPermission(params))
197
- .onNotification(methods.client.session.update, async ({ params }) => {
198
- acpSemanticProgress = true;
199
- firstProgress.observe();
200
- for (const event of mapKimiAcpUpdate(params.update))
201
- await jsonLine(event);
202
- });
203
- let firstProgress = startFirstProgressWatchdog(() => undefined);
204
- firstProgress.stop();
205
- try {
206
- const stream = ndJsonStream(Writable.toWeb(child.stdin), Readable.toWeb(child.stdout));
207
- const result = await app.connectWith(stream, async (nextContext) => {
208
- context = nextContext;
209
- const initialized = await nextContext.request(methods.agent.initialize, {
210
- protocolVersion: PROTOCOL_VERSION,
211
- clientCapabilities: {},
212
- clientInfo: { name: "nowcrew-daemon", version: "1" },
213
- });
214
- if (process.env.CREW_KIMI_ACP_DEBUG === "1") {
215
- process.stderr.write(`Kimi ACP auth methods: ${JSON.stringify(initialized.authMethods ?? [])}\n`);
216
- }
217
- if (options.resume && options.sessionId) {
218
- const resumeMethod = kimiResumeMethod(initialized.agentCapabilities);
219
- if (resumeMethod === "resume") {
220
- await nextContext.request(methods.agent.session.resume, {
221
- sessionId: options.sessionId,
222
- cwd: process.cwd(),
223
- mcpServers: [],
224
- });
225
- }
226
- else if (resumeMethod === "load") {
227
- await nextContext.request(methods.agent.session.load, {
228
- sessionId: options.sessionId,
229
- cwd: process.cwd(),
230
- mcpServers: [],
231
- });
232
- }
233
- else {
234
- throw new Error("Kimi ACP does not advertise session resume support");
235
- }
236
- sessionId = options.sessionId;
237
- }
238
- else {
239
- const session = await nextContext.request(methods.agent.session.new, {
240
- cwd: process.cwd(),
241
- mcpServers: [],
242
- });
243
- sessionId = session.sessionId;
244
- }
245
- await jsonLine({ type: "thread.started", thread_id: sessionId });
246
- if (options.model) {
247
- await nextContext.request(methods.agent.session.setConfigOption, {
248
- sessionId,
249
- configId: "model",
250
- value: options.model,
251
- });
252
- }
253
- firstProgress = startFirstProgressWatchdog(() => {
254
- progressTimedOut = true;
255
- void cancel();
256
- });
257
- return nextContext.request(methods.agent.session.prompt, {
258
- sessionId,
259
- prompt: [{ type: "text", text: prompt }],
260
- });
261
- });
262
- firstProgress.stop();
263
- if (progressTimedOut) {
264
- process.stderr.write("Kimi produced no semantic progress within the startup window\n");
265
- return 1;
266
- }
267
- const usage = result.usage;
268
- await jsonLine({
269
- type: "turn.completed",
270
- stop_reason: result.stopReason,
271
- ...(usage === undefined || usage === null ? {} : {
272
- usage: {
273
- input_tokens: usage.inputTokens,
274
- output_tokens: usage.outputTokens,
275
- cache_read_input_tokens: usage.cachedReadTokens ?? 0,
276
- cache_creation_input_tokens: usage.cachedWriteTokens ?? 0,
277
- },
278
- }),
279
- });
280
- return result.stopReason === "end_turn" ? 0 : result.stopReason === "cancelled" ? 130 : 1;
281
- }
282
- catch (error) {
283
- if (progressTimedOut) {
284
- process.stderr.write("Kimi produced no semantic progress within the startup window\n");
285
- return 1;
286
- }
287
- if (!acpSemanticProgress && isKimiAuthenticationRequired(error)) {
288
- firstProgress.stop();
289
- await stopChild(child);
290
- process.stderr.write("Kimi ACP requires account login; falling back to configured CLI provider transport\n");
291
- assertKimiLegacyPromptFits(prompt);
292
- const fallback = spawn(options.bin, buildKimiArgs({
293
- wakePrompt: prompt,
294
- effectivePermission: "full_access",
295
- ...(options.model === undefined ? {} : { model: options.model }),
296
- ...(options.resume && options.sessionId ? { sessionId: options.sessionId } : {}),
297
- }), {
298
- cwd: process.cwd(),
299
- env: process.env,
300
- stdio: ["ignore", "pipe", "pipe"],
301
- });
302
- runtimeChild = fallback;
303
- if (fallback.stdout === null || fallback.stderr === null) {
304
- throw new Error("Kimi CLI fallback did not expose output streams");
305
- }
306
- firstProgress = startFirstProgressWatchdog(() => {
307
- progressTimedOut = true;
308
- void stopChild(fallback);
309
- });
310
- fallback.stdout.on("data", () => firstProgress.observe());
311
- fallback.stdout.pipe(process.stdout, { end: false });
312
- fallback.stderr.pipe(process.stderr, { end: false });
313
- const code = await new Promise((resolve, reject) => {
314
- fallback.once("error", reject);
315
- fallback.once("close", (exitCode, signal) => {
316
- resolve(exitCode ?? (signal === null ? 1 : 128));
317
- });
318
- });
319
- firstProgress.stop();
320
- if (progressTimedOut) {
321
- process.stderr.write("Kimi CLI fallback produced no output within the startup window\n");
322
- return 1;
323
- }
324
- return code;
325
- }
326
- process.stderr.write(`Kimi ACP execution failed: ${safeErrorMessage(error, prompt)}\n`);
327
- return 1;
328
- }
329
- finally {
330
- firstProgress.stop();
331
- process.off("SIGTERM", onSignal);
332
- process.off("SIGINT", onSignal);
333
- await stopChild(child);
334
- }
335
- }
336
- function optionsFromArgv(argv) {
337
- const { values } = parseArgs({
338
- args: [...argv],
339
- options: {
340
- bin: { type: "string" },
341
- model: { type: "string" },
342
- session: { type: "string" },
343
- resume: { type: "boolean", default: false },
344
- },
345
- });
346
- if (!values.bin)
347
- throw new Error("--bin is required");
348
- if (values.resume && !values.session)
349
- throw new Error("--resume requires --session");
350
- return {
351
- bin: values.bin,
352
- ...(values.model ? { model: values.model } : {}),
353
- ...(values.session ? { sessionId: values.session } : {}),
354
- ...(values.resume ? { resume: true } : {}),
355
- };
356
- }
357
- if (process.argv[1] !== undefined && import.meta.url === pathToFileURL(process.argv[1]).href) {
358
- runKimiAcp(optionsFromArgv(process.argv.slice(2)))
359
- .then((code) => { process.exitCode = code; })
360
- .catch((error) => {
361
- process.stderr.write(`Kimi ACP runner failed: ${safeErrorMessage(error, "")}\n`);
362
- process.exitCode = 1;
363
- });
364
- }
@@ -1,45 +0,0 @@
1
- /**
2
- * Kimi Code CLI runtime adapter: non-interactive prompt mode with stream-json output.
3
- *
4
- * 事实依据(kimi-code 0.23.0 本机实测 + 官方文档 www.kimi.com/code/docs):
5
- * - `kimi -p <prompt> --output-format stream-json`:单次非交互执行,stdout 每行一个 JSON。
6
- * - `-p` 固定 auto 权限(自动批准普通工具调用),且与 --yolo/--auto/--plan 互斥,
7
- * 故 dangerous 无需(也不能)映射任何 flag。
8
- * - 无 system prompt 注入参数 → 与 codex 同法:systemPrompt 拼在 wakePrompt 前。
9
- * - 鉴权是机器级的(`kimi login` 或 ~/.kimi-code/config.toml),不读 shell 环境变量。
10
- */
11
- // cross-spawn:win32 上 npm CLI 是 .cmd shim,node 原生 spawn 不带 shell 无法执行(ENOENT/EINVAL)
12
- import spawn from "cross-spawn";
13
- // Windows CreateProcess receives one UTF-16 command line. Reserve room for the executable, flags,
14
- // model and cmd shim quoting instead of relying on the theoretical 32767-character ceiling.
15
- export const KIMI_LEGACY_PROMPT_MAX_UTF16 = 28_000;
16
- export function assertKimiLegacyPromptFits(prompt, platform = process.platform) {
17
- if (platform !== "win32" || prompt.length <= KIMI_LEGACY_PROMPT_MAX_UTF16)
18
- return;
19
- throw new Error(`Kimi legacy prompt exceeds the Windows argv limit (${prompt.length}/${KIMI_LEGACY_PROMPT_MAX_UTF16}); `
20
- + "shorten the prompt (protocol-v1 remains disabled on Windows until Job Object ownership is available)");
21
- }
22
- // Kimi Code 思考强度档位(kimi-code 0.23.0 实测+源码):无 CLI 参数,
23
- // 由 runner 经 KIMI_MODEL_THINKING_EFFORT env 注入;白名单外的值不注。
24
- export const KIMI_EFFORT_LEVELS = ["low", "medium", "high", "xhigh", "max"];
25
- export function buildKimiArgs(input) {
26
- if (input.effectivePermission !== undefined && input.effectivePermission !== "full_access") {
27
- throw new Error(`Kimi prompt mode cannot enforce ${input.effectivePermission} permission`);
28
- }
29
- const args = ["--output-format", "stream-json"];
30
- if (input.model)
31
- args.push("--model", input.model);
32
- if (input.sessionId)
33
- args.push("--session", input.sessionId);
34
- args.push("--prompt", input.wakePrompt);
35
- return args;
36
- }
37
- export function spawnKimi(input) {
38
- assertKimiLegacyPromptFits(input.wakePrompt);
39
- // stdio 固定 ignore/pipe/pipe,stdout/stderr 必为 Readable;cross-spawn 类型不带该细化,断言之
40
- return spawn(input.bin, buildKimiArgs(input), {
41
- cwd: input.cwd,
42
- env: input.env,
43
- stdio: ["ignore", "pipe", "pipe"],
44
- });
45
- }
@@ -1,26 +0,0 @@
1
- export const DEFAULT_FIRST_PROGRESS_TIMEOUT_MS = 120_000;
2
- /**
3
- * Bound the silent gap after a protocol turn starts. Once any semantic notification arrives,
4
- * the runtime's configured total timeout remains authoritative; long-running tools are not killed
5
- * merely because they produce no output.
6
- */
7
- export function startFirstProgressWatchdog(onTimeout, timeoutMs = DEFAULT_FIRST_PROGRESS_TIMEOUT_MS) {
8
- if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
9
- throw new RangeError("First-progress timeout must be a positive finite number");
10
- }
11
- let active = true;
12
- const timer = setTimeout(() => {
13
- if (!active)
14
- return;
15
- active = false;
16
- onTimeout();
17
- }, timeoutMs);
18
- timer.unref?.();
19
- const stop = () => {
20
- if (!active)
21
- return;
22
- active = false;
23
- clearTimeout(timer);
24
- };
25
- return { observe: stop, stop };
26
- }
@@ -1,51 +0,0 @@
1
- export function normalizeScheduledPolicy(value) {
2
- return value === "always_report" ? "always_report" : "silent_unless_report";
3
- }
4
- export function normalizeExternalNotificationPolicy(value) {
5
- return value === "agent_decides" ? "agent_decides" : "disabled";
6
- }
7
- export function normalizeScheduledContext(input) {
8
- return {
9
- jobId: input.jobId,
10
- runId: input.runId,
11
- title: input.title?.trim() || "Scheduled job",
12
- outputPolicy: normalizeScheduledPolicy(input.outputPolicy),
13
- externalNotificationPolicy: normalizeExternalNotificationPolicy(input.externalNotificationPolicy),
14
- };
15
- }
16
- export async function deliverScheduledReport(input) {
17
- const title = input.title.trim() || "Scheduled job";
18
- let source = "none";
19
- let content = null;
20
- if (input.exitCode !== 0) {
21
- source = "failure_notice";
22
- const detail = input.errorMessage?.trim().slice(0, 500);
23
- content = `Scheduled job "${title}" failed${detail ? `: ${detail}` : ` (exit ${input.exitCode})`}.`;
24
- }
25
- else if (input.policy === "always_report") {
26
- if (input.finalText?.trim()) {
27
- source = "runtime_final";
28
- content = input.finalText.trim();
29
- }
30
- else {
31
- source = "empty_notice";
32
- content = `Scheduled job "${title}" completed without a usable report.`;
33
- }
34
- }
35
- if (!content) {
36
- return { required: false, attempted: false, delivered: false, source: "none" };
37
- }
38
- try {
39
- const sent = await input.send(content);
40
- return {
41
- required: true,
42
- attempted: true,
43
- delivered: sent.delivered,
44
- status: sent.status,
45
- source,
46
- };
47
- }
48
- catch {
49
- return { required: true, attempted: true, delivered: false, source };
50
- }
51
- }
@@ -1,57 +0,0 @@
1
- function fields(input) {
2
- return {
3
- ...(input.scheduledRunId ? { scheduled_run_id: input.scheduledRunId } : {}),
4
- run_id: input.runId,
5
- agent_handle: input.agentHandle,
6
- channel_id: input.channelId,
7
- exit_code: input.exitCode,
8
- ...(input.runtime ? { runtime: input.runtime } : {}),
9
- ...(input.model ? { model: input.model } : {}),
10
- };
11
- }
12
- export function reportAgentRunComplete(socket, input, log) {
13
- const eventPrefix = input.scheduledRunId ? "scheduled_run" : "run";
14
- if (!socket) {
15
- log(`${eventPrefix}.complete_send_failed`, "Agent 完成回报未发送:控制面连接不可用", {
16
- level: "WARN", ...fields(input), error_message: "control socket unavailable",
17
- });
18
- return;
19
- }
20
- const payload = JSON.stringify({
21
- type: "agent:run-complete",
22
- runId: input.runId,
23
- agentHandle: input.agentHandle,
24
- channelId: input.channelId,
25
- ...(input.threadId !== undefined ? { threadId: input.threadId } : {}),
26
- ...(input.scheduledRunId ? { scheduledRunId: input.scheduledRunId } : {}),
27
- exitCode: input.exitCode,
28
- ...(input.wakeOrigin ? { wakeOrigin: input.wakeOrigin } : {}),
29
- ...(input.originDecision ? { originDecision: input.originDecision } : {}),
30
- ...(input.contextUpToSeq !== undefined ? { contextUpToSeq: input.contextUpToSeq } : {}),
31
- ...(input.runtime ? { runtime: input.runtime } : {}),
32
- ...(input.model !== undefined ? { model: input.model } : {}),
33
- ...(input.resumed !== undefined ? { resumed: input.resumed } : {}),
34
- ...(input.errorMessage ? { errorMessage: input.errorMessage } : {}),
35
- ...(input.usage ? { usage: input.usage } : {}),
36
- ...(input.report ? { report: input.report } : {}),
37
- });
38
- try {
39
- socket.send(payload, (error) => {
40
- if (error) {
41
- log(`${eventPrefix}.complete_send_failed`, "Agent 完成回报发送失败", {
42
- level: "WARN", ...fields(input), error_message: error.message,
43
- });
44
- return;
45
- }
46
- log(`${eventPrefix}.complete_sent`, "Agent 完成回报已发送", fields(input));
47
- });
48
- }
49
- catch (error) {
50
- log(`${eventPrefix}.complete_send_failed`, "Agent 完成回报发送失败", {
51
- level: "WARN", ...fields(input), error_message: error.message,
52
- });
53
- }
54
- }
55
- export function reportScheduledRunComplete(socket, input, log) {
56
- reportAgentRunComplete(socket, input, log);
57
- }
@@ -1,82 +0,0 @@
1
- import { dslog, flushSlog } from "./slog.js";
2
- function defaultDiagnose(diagnostic) {
3
- const fields = {
4
- signal: diagnostic.signal,
5
- stage: diagnostic.stage,
6
- active_execution_count: diagnostic.activeExecutionCount,
7
- active_legacy_count: diagnostic.activeLegacyCount,
8
- deadline_ms: diagnostic.deadlineMs,
9
- ...(diagnostic.error === undefined ? {} : { error: diagnostic.error }),
10
- };
11
- try {
12
- dslog(diagnostic.stage === "failed" ? "daemon.shutdown_failed" : "daemon.shutdown", `daemon shutdown ${diagnostic.stage}`, { level: diagnostic.stage === "failed" ? "ERROR" : "INFO", ...fields });
13
- process.stderr.write(`${JSON.stringify({
14
- level: diagnostic.stage === "failed" ? "ERROR" : "INFO",
15
- event_type: diagnostic.stage === "failed" ? "daemon.shutdown_failed" : "daemon.shutdown",
16
- ...fields,
17
- })}\n`);
18
- }
19
- catch { /* shutdown diagnostics must never interrupt cleanup */ }
20
- }
21
- export async function runServeLifecycle(service, dependencies = {}) {
22
- const signals = dependencies.signals ?? process;
23
- const flush = dependencies.flush ?? flushSlog;
24
- const diagnose = dependencies.diagnose ?? defaultDiagnose;
25
- let receivedSignal = null;
26
- let resolveSignal;
27
- const signalReceived = new Promise((resolve) => { resolveSignal = resolve; });
28
- const receive = (signal) => {
29
- if (receivedSignal !== null)
30
- return;
31
- receivedSignal = signal;
32
- resolveSignal(signal);
33
- };
34
- const onSigint = () => receive("SIGINT");
35
- const onSigterm = () => receive("SIGTERM");
36
- signals.on("SIGINT", onSigint);
37
- signals.on("SIGTERM", onSigterm);
38
- try {
39
- try {
40
- await service.ready;
41
- }
42
- catch (readyError) {
43
- try {
44
- await service.stop();
45
- }
46
- catch { /* preserve the readiness error */ }
47
- try {
48
- await flush();
49
- }
50
- catch { /* slog is best effort */ }
51
- throw readyError;
52
- }
53
- const signal = await signalReceived;
54
- const snapshot = service.shutdownSnapshot();
55
- diagnose({ signal, stage: "stopping", ...snapshot });
56
- let stopError;
57
- try {
58
- await service.stop();
59
- diagnose({ signal, stage: "completed", ...snapshot });
60
- }
61
- catch (error) {
62
- stopError = error;
63
- diagnose({
64
- signal,
65
- stage: "failed",
66
- error: error instanceof Error ? error.message : String(error),
67
- ...snapshot,
68
- });
69
- }
70
- try {
71
- await flush();
72
- }
73
- catch { /* slog owns its own spool fallback */ }
74
- if (stopError !== undefined)
75
- throw stopError;
76
- return { signal };
77
- }
78
- finally {
79
- signals.off("SIGINT", onSigint);
80
- signals.off("SIGTERM", onSigterm);
81
- }
82
- }