@zqagent/shared 0.0.0-stage → 0.1.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 (73) hide show
  1. package/LICENSE +190 -0
  2. package/README.md +21 -2
  3. package/dist/approval-view.cjs +99 -0
  4. package/dist/approval-view.cjs.map +1 -0
  5. package/dist/approval-view.d.cts +39 -0
  6. package/dist/approval-view.d.ts +39 -0
  7. package/dist/approval-view.js +69 -0
  8. package/dist/approval-view.js.map +1 -0
  9. package/dist/fleet.cjs +263 -0
  10. package/dist/fleet.cjs.map +1 -0
  11. package/dist/fleet.d.cts +284 -0
  12. package/dist/fleet.d.ts +284 -0
  13. package/dist/fleet.js +214 -0
  14. package/dist/fleet.js.map +1 -0
  15. package/dist/hub-api.cjs +901 -0
  16. package/dist/hub-api.cjs.map +1 -0
  17. package/dist/hub-api.d.cts +1677 -0
  18. package/dist/hub-api.d.ts +1677 -0
  19. package/dist/hub-api.js +776 -0
  20. package/dist/hub-api.js.map +1 -0
  21. package/dist/index-DokiNlGJ.d.cts +182 -0
  22. package/dist/index-DokiNlGJ.d.ts +182 -0
  23. package/dist/index.cjs +861 -0
  24. package/dist/index.cjs.map +1 -0
  25. package/dist/index.d.cts +7 -0
  26. package/dist/index.d.ts +7 -0
  27. package/dist/index.js +779 -0
  28. package/dist/index.js.map +1 -0
  29. package/dist/llm-presets.cjs +92 -0
  30. package/dist/llm-presets.cjs.map +1 -0
  31. package/dist/llm-presets.d.cts +28 -0
  32. package/dist/llm-presets.d.ts +28 -0
  33. package/dist/llm-presets.js +66 -0
  34. package/dist/llm-presets.js.map +1 -0
  35. package/dist/media.cjs +111 -0
  36. package/dist/media.cjs.map +1 -0
  37. package/dist/media.d.cts +59 -0
  38. package/dist/media.d.ts +59 -0
  39. package/dist/media.js +79 -0
  40. package/dist/media.js.map +1 -0
  41. package/dist/messages-CiXjdR1Z.d.cts +257 -0
  42. package/dist/messages-CiXjdR1Z.d.ts +257 -0
  43. package/dist/model-capabilities.cjs +105 -0
  44. package/dist/model-capabilities.cjs.map +1 -0
  45. package/dist/model-capabilities.d.cts +46 -0
  46. package/dist/model-capabilities.d.ts +46 -0
  47. package/dist/model-capabilities.js +74 -0
  48. package/dist/model-capabilities.js.map +1 -0
  49. package/dist/notify/index.cjs +375 -0
  50. package/dist/notify/index.cjs.map +1 -0
  51. package/dist/notify/index.d.cts +2 -0
  52. package/dist/notify/index.d.ts +2 -0
  53. package/dist/notify/index.js +330 -0
  54. package/dist/notify/index.js.map +1 -0
  55. package/dist/secrets.cjs +121 -0
  56. package/dist/secrets.cjs.map +1 -0
  57. package/dist/secrets.d.cts +25 -0
  58. package/dist/secrets.d.ts +25 -0
  59. package/dist/secrets.js +82 -0
  60. package/dist/secrets.js.map +1 -0
  61. package/dist/wire/index.cjs +1163 -0
  62. package/dist/wire/index.cjs.map +1 -0
  63. package/dist/wire/index.d.cts +166 -0
  64. package/dist/wire/index.d.ts +166 -0
  65. package/dist/wire/index.js +1081 -0
  66. package/dist/wire/index.js.map +1 -0
  67. package/dist/wire/node.cjs +224 -0
  68. package/dist/wire/node.cjs.map +1 -0
  69. package/dist/wire/node.d.cts +14 -0
  70. package/dist/wire/node.d.ts +14 -0
  71. package/dist/wire/node.js +195 -0
  72. package/dist/wire/node.js.map +1 -0
  73. package/package.json +99 -3
package/dist/index.js ADDED
@@ -0,0 +1,779 @@
1
+ // src/fleet.ts
2
+ import * as crypto2 from "crypto";
3
+
4
+ // src/common.ts
5
+ import * as crypto from "crypto";
6
+ var DEFAULT_CLOCK_SKEW_MS = 6e4;
7
+ function generateToken(bytes = 32) {
8
+ return crypto.randomBytes(bytes).toString("base64url");
9
+ }
10
+ function isFreshTimestamp(t, now = Date.now(), skewMs = DEFAULT_CLOCK_SKEW_MS) {
11
+ return typeof t === "number" && Number.isFinite(t) && Math.abs(now - t) <= skewMs;
12
+ }
13
+ var NonceTracker = class {
14
+ constructor(ttlMs = DEFAULT_CLOCK_SKEW_MS * 5) {
15
+ this.ttlMs = ttlMs;
16
+ }
17
+ ttlMs;
18
+ seen = /* @__PURE__ */ new Map();
19
+ /** 返回 true 表示首次见到(可用);false 表示重放 */
20
+ checkAndRecord(nonce, now = Date.now()) {
21
+ this.prune(now);
22
+ if (this.seen.has(nonce)) return false;
23
+ this.seen.set(nonce, now);
24
+ return true;
25
+ }
26
+ prune(now) {
27
+ for (const [k, ts] of this.seen) {
28
+ if (now - ts > this.ttlMs) this.seen.delete(k);
29
+ }
30
+ }
31
+ };
32
+
33
+ // src/fleet.ts
34
+ var FLEET_TOPIC_PREFIX = "zqagent/fleet";
35
+ var FLEET_CMD_TYPES = [
36
+ "ping",
37
+ "get_node_info",
38
+ "get_node_host",
39
+ "list_projects",
40
+ "create_project",
41
+ "delete_project",
42
+ "list_sessions",
43
+ "get_session_states",
44
+ "create_session",
45
+ "delete_session",
46
+ "get_state",
47
+ "get_events",
48
+ "get_session_turns",
49
+ "get_session_turn_reply",
50
+ "read_session_attachment",
51
+ "get_session_changes",
52
+ "get_session_change_diff",
53
+ "restore_session_change",
54
+ "get_config",
55
+ "update_config",
56
+ "list_models",
57
+ "list_dirs",
58
+ "make_dir",
59
+ "search_dirs",
60
+ "list_files",
61
+ "read_file",
62
+ "read_file_range",
63
+ "file_ticket",
64
+ "download_file",
65
+ "write_file",
66
+ "write_file_base64",
67
+ "create_dir",
68
+ "create_file",
69
+ "delete_files",
70
+ "search_files",
71
+ "search_content",
72
+ "rename_path",
73
+ "move_paths",
74
+ "zip_download",
75
+ "list_tools",
76
+ "list_mcp",
77
+ "update_mcp",
78
+ "test_mcp",
79
+ "project_stats",
80
+ "run_command",
81
+ "list_shell_tasks",
82
+ "kill_shell_task",
83
+ "terminal_list",
84
+ "terminal_create",
85
+ "terminal_replay",
86
+ "terminal_close",
87
+ "terminal_remove",
88
+ "terminal_rename",
89
+ "terminal_history",
90
+ "terminal_settings",
91
+ "terminal_update_settings",
92
+ "git_repos",
93
+ "git_status",
94
+ "git_diff",
95
+ "git_branches",
96
+ "git_refs",
97
+ "git_log",
98
+ "git_show",
99
+ "git_fetch",
100
+ "git_stage",
101
+ "git_unstage",
102
+ "git_commit",
103
+ "git_commit_message",
104
+ "git_checkout",
105
+ "git_pull",
106
+ "git_push",
107
+ "set_node_label",
108
+ "get_usage_summary",
109
+ "get_llm_balance",
110
+ "get_usage_records",
111
+ "get_usage_pricing",
112
+ "update_usage_pricing",
113
+ "get_node_llm",
114
+ "update_node_llm",
115
+ "self_update",
116
+ "refresh_builtin_llm",
117
+ "list_node_llm_models",
118
+ "set_fleet_cred",
119
+ "rename_session",
120
+ "set_session_model",
121
+ "set_session_llm",
122
+ "set_session_mode",
123
+ "set_session_lang",
124
+ "set_session_draft",
125
+ "submit_turn",
126
+ "steer",
127
+ "unsteer",
128
+ "interrupt",
129
+ "resume",
130
+ "approval_answer",
131
+ "question_answer",
132
+ "batch"
133
+ ];
134
+ function fleetCmdTopic(fleetToken, nodeId) {
135
+ return `${FLEET_TOPIC_PREFIX}/${fleetToken}/node/${nodeId}/cmd`;
136
+ }
137
+ function fleetNotifyTopic(fleetToken, nodeId) {
138
+ return `${FLEET_TOPIC_PREFIX}/${fleetToken}/node/${nodeId}/notify`;
139
+ }
140
+ function fleetEventsTopic(fleetToken, nodeId) {
141
+ return `${FLEET_TOPIC_PREFIX}/${fleetToken}/node/${nodeId}/events`;
142
+ }
143
+ function fleetStatusTopic(fleetToken, nodeId) {
144
+ return `${FLEET_TOPIC_PREFIX}/${fleetToken}/node/${nodeId}/status`;
145
+ }
146
+ function fleetInfoTopic(fleetToken, nodeId) {
147
+ return `${FLEET_TOPIC_PREFIX}/${fleetToken}/node/${nodeId}/info`;
148
+ }
149
+ function fleetAckTopic(fleetToken, nodeId, clientId) {
150
+ return `${FLEET_TOPIC_PREFIX}/${fleetToken}/node/${nodeId}/ack/${clientId}`;
151
+ }
152
+ function canonical(env) {
153
+ return JSON.stringify([
154
+ env.t,
155
+ env.nonce,
156
+ env.reqId,
157
+ env.type,
158
+ env.clientId,
159
+ env.nodeId,
160
+ env.projectId ?? "",
161
+ env.sessionId ?? "",
162
+ env.payload
163
+ ]);
164
+ }
165
+ function signFleetCmd(secret, env) {
166
+ return crypto2.createHmac("sha256", Buffer.from(secret, "utf8")).update(canonical(env)).digest("base64url");
167
+ }
168
+ function verifyFleetSig(secret, env) {
169
+ if (typeof env.sig !== "string" || env.sig.length === 0) return false;
170
+ const expected = signFleetCmd(secret, env);
171
+ const a = Buffer.from(expected, "utf8");
172
+ const b = Buffer.from(env.sig, "utf8");
173
+ if (a.length !== b.length) return false;
174
+ return crypto2.timingSafeEqual(a, b);
175
+ }
176
+ function parseFleetEnvelope(raw) {
177
+ if (!raw || typeof raw !== "object") return null;
178
+ const o = raw;
179
+ if (typeof o.t !== "number" || typeof o.nonce !== "string" || typeof o.reqId !== "string" || typeof o.type !== "string" || typeof o.clientId !== "string" || typeof o.nodeId !== "string" || typeof o.sig !== "string") {
180
+ return null;
181
+ }
182
+ if (!FLEET_CMD_TYPES.includes(o.type)) return null;
183
+ if (!o.payload || typeof o.payload !== "object") return null;
184
+ return {
185
+ t: o.t,
186
+ nonce: o.nonce,
187
+ reqId: o.reqId,
188
+ type: o.type,
189
+ clientId: o.clientId,
190
+ nodeId: o.nodeId,
191
+ projectId: typeof o.projectId === "string" ? o.projectId : void 0,
192
+ sessionId: typeof o.sessionId === "string" ? o.sessionId : void 0,
193
+ payload: o.payload,
194
+ sig: o.sig
195
+ };
196
+ }
197
+
198
+ // src/secrets.ts
199
+ import * as crypto3 from "crypto";
200
+ import * as fs from "fs";
201
+ import * as path from "path";
202
+ function encryptWithKey(key, plain) {
203
+ const iv = crypto3.randomBytes(12);
204
+ const cipher = crypto3.createCipheriv("aes-256-gcm", key, iv);
205
+ const enc = Buffer.concat([cipher.update(plain, "utf8"), cipher.final()]);
206
+ const tag = cipher.getAuthTag();
207
+ return [
208
+ "v1",
209
+ iv.toString("base64"),
210
+ tag.toString("base64"),
211
+ enc.toString("base64")
212
+ ].join(":");
213
+ }
214
+ function decryptWithKey(key, payload) {
215
+ try {
216
+ const parts = payload.split(":");
217
+ if (parts.length !== 4 || parts[0] !== "v1") return null;
218
+ const iv = Buffer.from(parts[1], "base64");
219
+ const tag = Buffer.from(parts[2], "base64");
220
+ const data = Buffer.from(parts[3], "base64");
221
+ const decipher = crypto3.createDecipheriv("aes-256-gcm", key, iv);
222
+ decipher.setAuthTag(tag);
223
+ return Buffer.concat([
224
+ decipher.update(data),
225
+ decipher.final()
226
+ ]).toString("utf8");
227
+ } catch {
228
+ return null;
229
+ }
230
+ }
231
+ function loadOrCreateMasterKey(masterKeyFile) {
232
+ if (fs.existsSync(masterKeyFile)) {
233
+ const hex = fs.readFileSync(masterKeyFile, "utf8").trim();
234
+ if (/^[0-9a-f]{64}$/i.test(hex)) {
235
+ return Buffer.from(hex, "hex");
236
+ }
237
+ console.warn(`\u26A0\uFE0F \u4E3B\u5BC6\u94A5\u6587\u4EF6\u635F\u574F\uFF0C\u5DF2\u91CD\u65B0\u751F\u6210\uFF08\u65E7\u5BC6\u6587\u5C06\u65E0\u6CD5\u89E3\u5BC6\uFF09`);
238
+ }
239
+ const key = crypto3.randomBytes(32);
240
+ fs.mkdirSync(path.dirname(masterKeyFile), { recursive: true, mode: 448 });
241
+ fs.writeFileSync(masterKeyFile, key.toString("hex"), {
242
+ mode: 384,
243
+ encoding: "utf8"
244
+ });
245
+ return key;
246
+ }
247
+ function masterKeyFromHex(hex) {
248
+ const h = hex.trim();
249
+ return /^[0-9a-f]{64}$/i.test(h) ? Buffer.from(h, "hex") : null;
250
+ }
251
+ var SecretBox = class _SecretBox {
252
+ constructor(key) {
253
+ this.key = key;
254
+ }
255
+ key;
256
+ /** 主密钥来源一:本机密钥文件(不存在则创建) */
257
+ static fromFile(masterKeyFile) {
258
+ return new _SecretBox(loadOrCreateMasterKey(masterKeyFile));
259
+ }
260
+ /** 主密钥来源二:hex 字符串(如环境变量 SECRET_KEY);非法返回 null */
261
+ static fromHex(hex) {
262
+ const key = masterKeyFromHex(hex);
263
+ return key ? new _SecretBox(key) : null;
264
+ }
265
+ encrypt(plain) {
266
+ return encryptWithKey(this.key, plain);
267
+ }
268
+ decrypt(payload) {
269
+ return decryptWithKey(this.key, payload);
270
+ }
271
+ };
272
+
273
+ // src/approval-view.ts
274
+ var APPROVAL_VALUE_MAX = 120;
275
+ var APPROVAL_COLUMNS = ["\u9879\u76EE", "\u8BE6\u60C5"];
276
+ var TOOL_LABEL = "\u{1F527} \u5DE5\u5177";
277
+ var REASON_LABEL = "\u{1F4AC} \u539F\u56E0";
278
+ function formatValue(v, maxLen) {
279
+ let text;
280
+ if (v == null) {
281
+ text = "";
282
+ } else if (typeof v === "string") {
283
+ text = v;
284
+ } else {
285
+ try {
286
+ text = JSON.stringify(v);
287
+ } catch {
288
+ text = String(v);
289
+ }
290
+ }
291
+ text = text.replace(/\r?\n/g, "\u21B5");
292
+ return text.length > maxLen ? text.slice(0, maxLen) + "\u2026" : text;
293
+ }
294
+ function approvalInputFromData(data) {
295
+ const args = data["arguments"];
296
+ return {
297
+ toolName: String(data["toolName"] ?? ""),
298
+ reason: String(data["reason"] ?? ""),
299
+ arguments: args && typeof args === "object" ? args : void 0
300
+ };
301
+ }
302
+ function approvalFields(input, maxLen = APPROVAL_VALUE_MAX) {
303
+ const fields = [
304
+ { label: TOOL_LABEL, value: formatValue(input.toolName, maxLen) },
305
+ { label: REASON_LABEL, value: formatValue(input.reason, maxLen) }
306
+ ];
307
+ for (const [key, value] of Object.entries(input.arguments ?? {})) {
308
+ fields.push({ label: formatValue(key, maxLen), value: formatValue(value, maxLen) });
309
+ }
310
+ return fields;
311
+ }
312
+ function escapeCell(text) {
313
+ return text.replace(/\|/g, "\\|").replace(/\r?\n/g, "\u21B5");
314
+ }
315
+ function approvalToMarkdownTable(input, opts = {}) {
316
+ const lines = [];
317
+ if (opts.title) lines.push(opts.title, "");
318
+ lines.push(`| ${APPROVAL_COLUMNS[0]} | ${APPROVAL_COLUMNS[1]} |`, "| --- | --- |");
319
+ for (const f of approvalFields(input, opts.maxLen)) {
320
+ lines.push(`| ${escapeCell(f.label)} | ${escapeCell(f.value)} |`);
321
+ }
322
+ return lines.join("\n");
323
+ }
324
+ function escapeHtml(text) {
325
+ return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
326
+ }
327
+ function approvalToHtmlTable(input, maxLen = APPROVAL_VALUE_MAX) {
328
+ const rows = approvalFields(input, maxLen).map(
329
+ (f) => `<tr><th style="text-align:left;padding:4px 12px 4px 0;color:#6b7280;font-weight:500;white-space:nowrap;vertical-align:top;">${escapeHtml(f.label)}</th><td style="padding:4px 0;font-family:ui-monospace,Menlo,Consolas,monospace;">${escapeHtml(f.value)}</td></tr>`
330
+ ).join("");
331
+ return `<table style="border-collapse:collapse;font-size:13px;">${rows}</table>`;
332
+ }
333
+
334
+ // src/llm-presets.ts
335
+ var LLM_PRESETS = [
336
+ {
337
+ id: "openai",
338
+ name: "OpenAI",
339
+ baseURL: "https://api.openai.com/v1",
340
+ // 不预填模型:预填等于替用户拍板一个他既没选过、也可能没有权限的型号,
341
+ // 之后控制台的模型下拉就凭空多出这个「系统默认模型」。填完 Key 刷新模型,
342
+ // 由供应商实际返回的列表来选。
343
+ model: "",
344
+ supportsReasoningEffort: true
345
+ },
346
+ {
347
+ id: "deepseek",
348
+ name: "DeepSeek",
349
+ baseURL: "https://api.deepseek.com",
350
+ model: "deepseek-flash",
351
+ supportsReasoningEffort: false,
352
+ hint: "deepseek-flash = \u901A\u7528\u5BF9\u8BDD, deepseek-reasoner = \u6DF1\u5EA6\u63A8\u7406"
353
+ },
354
+ {
355
+ id: "qwen",
356
+ name: "\u901A\u4E49\u5343\u95EE",
357
+ baseURL: "https://dashscope.aliyuncs.com/compatible-mode/v1",
358
+ model: "qwen-plus",
359
+ supportsReasoningEffort: false
360
+ },
361
+ {
362
+ id: "zhipu",
363
+ name: "\u667A\u8C31 GLM",
364
+ baseURL: "https://open.bigmodel.cn/api/paas/v4",
365
+ model: "glm-4-plus",
366
+ supportsReasoningEffort: false
367
+ },
368
+ {
369
+ id: "moonshot",
370
+ name: "\u6708\u4E4B\u6697\u9762 Kimi",
371
+ baseURL: "https://api.moonshot.cn/v1",
372
+ model: "moonshot-v1-8k",
373
+ supportsReasoningEffort: false
374
+ },
375
+ {
376
+ id: "xiaomi",
377
+ name: "\u5C0F\u7C73 MiMo",
378
+ baseURL: "",
379
+ model: "",
380
+ supportsReasoningEffort: false,
381
+ hint: "\u9700\u81EA\u884C\u586B\u5199\u7AEF\u70B9\u4E0E\u6A21\u578B"
382
+ },
383
+ {
384
+ id: "custom",
385
+ name: "\u5176\u5B83 (OpenAI \u517C\u5BB9)",
386
+ baseURL: "",
387
+ model: "",
388
+ supportsReasoningEffort: true,
389
+ hint: "\u4EFB\u610F\u517C\u5BB9 OpenAI API \u7684\u670D\u52A1\u7AEF\u70B9\uFF08\u5982\u672C\u5730 vLLM \u7B49\uFF09"
390
+ }
391
+ ];
392
+ function llmPreset(id) {
393
+ return LLM_PRESETS.find((p) => p.id === id);
394
+ }
395
+
396
+ // src/media.ts
397
+ var MEDIA_MIME = {
398
+ // 视频
399
+ mp4: { mime: "video/mp4", kind: "video" },
400
+ m4v: { mime: "video/mp4", kind: "video" },
401
+ webm: { mime: "video/webm", kind: "video" },
402
+ ogv: { mime: "video/ogg", kind: "video" },
403
+ mov: { mime: "video/quicktime", kind: "video" },
404
+ // 音频
405
+ mp3: { mime: "audio/mpeg", kind: "audio" },
406
+ wav: { mime: "audio/wav", kind: "audio" },
407
+ m4a: { mime: "audio/mp4", kind: "audio" },
408
+ aac: { mime: "audio/aac", kind: "audio" },
409
+ flac: { mime: "audio/flac", kind: "audio" },
410
+ ogg: { mime: "audio/ogg", kind: "audio" },
411
+ oga: { mime: "audio/ogg", kind: "audio" },
412
+ opus: { mime: "audio/ogg", kind: "audio" },
413
+ weba: { mime: "audio/webm", kind: "audio" }
414
+ };
415
+ function extOf(fileName) {
416
+ return fileName.toLowerCase().split(".").pop() ?? "";
417
+ }
418
+ function mediaInfo(fileName) {
419
+ return MEDIA_MIME[extOf(fileName)] ?? null;
420
+ }
421
+ function mediaMime(fileName) {
422
+ return MEDIA_MIME[extOf(fileName)]?.mime ?? null;
423
+ }
424
+ function mediaKind(fileName) {
425
+ return MEDIA_MIME[extOf(fileName)]?.kind ?? null;
426
+ }
427
+ var DOC_PREVIEW_MIME = {
428
+ pdf: "application/pdf"
429
+ };
430
+ function docPreviewMime(fileName) {
431
+ return DOC_PREVIEW_MIME[extOf(fileName)] ?? null;
432
+ }
433
+ function previewMime(fileName) {
434
+ return mediaMime(fileName) ?? docPreviewMime(fileName);
435
+ }
436
+ function parseByteRange(header, size) {
437
+ const raw = (header ?? "").trim();
438
+ if (!raw) return null;
439
+ const m = /^bytes=(\d*)-(\d*)$/i.exec(raw);
440
+ if (!m) return null;
441
+ const startRaw = m[1] ?? "";
442
+ const endRaw = m[2] ?? "";
443
+ if (!startRaw && !endRaw) return null;
444
+ if (size <= 0) return "unsatisfiable";
445
+ if (!startRaw) {
446
+ const suffix = Number(endRaw);
447
+ if (!Number.isFinite(suffix) || suffix <= 0) return "unsatisfiable";
448
+ return { start: Math.max(size - suffix, 0), end: size - 1 };
449
+ }
450
+ const start = Number(startRaw);
451
+ if (!Number.isFinite(start) || start < 0) return null;
452
+ if (start >= size) return "unsatisfiable";
453
+ if (!endRaw) return { start, end: size - 1 };
454
+ const end = Number(endRaw);
455
+ if (!Number.isFinite(end) || end < start) return null;
456
+ return { start, end: Math.min(end, size - 1) };
457
+ }
458
+ function contentRangeHeader(range, size) {
459
+ return `bytes ${range.start}-${range.end}/${size}`;
460
+ }
461
+ function fileEtag(size, mtimeMs) {
462
+ return `"${size.toString(16)}-${Math.trunc(mtimeMs).toString(16)}"`;
463
+ }
464
+
465
+ // src/types.ts
466
+ var CONTENT_DELTA_TYPES = /* @__PURE__ */ new Set([
467
+ "assistant_delta",
468
+ "reasoning_delta",
469
+ // 节点把「只含内容增量」的一批事件合并成 `{zqBatch:[...]}` 时用的顶层标记。
470
+ // 混入结构事件的一批不能打这个标记,否则整帧被丢会连正文一起丢。
471
+ "content_delta"
472
+ ]);
473
+
474
+ // src/notify/Notifier.ts
475
+ function channelWants(channel, kind) {
476
+ const ev = channel.events;
477
+ switch (kind) {
478
+ case "approval_request":
479
+ return ev.approval;
480
+ case "question_asked":
481
+ return ev.question;
482
+ case "turn_completed":
483
+ case "turn_stopped":
484
+ return ev.completed;
485
+ case "error":
486
+ return ev.error;
487
+ case "session_status":
488
+ return false;
489
+ default:
490
+ return true;
491
+ }
492
+ }
493
+ function notifyTitle(kind) {
494
+ switch (kind) {
495
+ case "approval_request":
496
+ return "\u9700\u8981\u5BA1\u6279";
497
+ case "question_asked":
498
+ return "\u9700\u8981\u56DE\u7B54";
499
+ case "turn_completed":
500
+ return "\u4EFB\u52A1\u5B8C\u6210";
501
+ case "turn_stopped":
502
+ return "\u8FBE\u5230\u8FED\u4EE3\u4E0A\u9650";
503
+ case "error":
504
+ return "\u4EFB\u52A1\u51FA\u9519";
505
+ default:
506
+ return "Agent \u901A\u77E5";
507
+ }
508
+ }
509
+ function notifyBody(ev, maxLen = 300) {
510
+ const d = ev.data;
511
+ switch (ev.kind) {
512
+ case "approval_request":
513
+ return approvalToMarkdownTable(approvalInputFromData(d), {
514
+ title: "\u{1F512} \u9700\u8981\u5BA1\u6279"
515
+ });
516
+ case "question_asked":
517
+ return String(d["question"] ?? "");
518
+ case "turn_completed":
519
+ return `\u72B6\u6001: ${String(d["status"] ?? "completed")}`;
520
+ case "turn_stopped":
521
+ return "\u5DF2\u8FBE\u6700\u5927\u8FED\u4EE3\u6B21\u6570\uFF0C\u53EF\u7EE7\u7EED\u5B8C\u6210\u5269\u4E0B\u7684\u4EFB\u52A1";
522
+ case "error":
523
+ return String(d["error"] ?? "\u672A\u77E5\u9519\u8BEF");
524
+ default:
525
+ return "";
526
+ }
527
+ }
528
+
529
+ // src/notify/NotificationHub.ts
530
+ var NotificationHub = class {
531
+ notifiers = /* @__PURE__ */ new Map();
532
+ /** 注册一个通道实现(幂等,后注册覆盖) */
533
+ register(notifier) {
534
+ this.notifiers.set(notifier.type, notifier);
535
+ }
536
+ /** 已注册的通道类型 */
537
+ registeredTypes() {
538
+ return [...this.notifiers.keys()];
539
+ }
540
+ /**
541
+ * 分发通知到给定的启用通道。
542
+ * 单个通道失败仅记录,不影响其他通道与核心逻辑。
543
+ */
544
+ async dispatch(channels, ev) {
545
+ for (const ch of channels) {
546
+ if (!ch.enabled) continue;
547
+ if (!channelWants(ch, ev.kind)) continue;
548
+ const notifier = this.notifiers.get(ch.type);
549
+ if (!notifier) continue;
550
+ try {
551
+ await notifier.send(ch, ev);
552
+ } catch (e) {
553
+ console.log(
554
+ `[notify] ${ch.type} \u53D1\u9001\u5931\u8D25: ${e instanceof Error ? e.message : String(e)}`
555
+ );
556
+ }
557
+ }
558
+ }
559
+ };
560
+
561
+ // src/notify/notifiers/BarkNotifier.ts
562
+ var TIMEOUT_MS = 1e4;
563
+ var BarkNotifier = class {
564
+ type = "bark";
565
+ async send(config, ev) {
566
+ const key = config.config["key"];
567
+ if (!key) return;
568
+ const server = (config.config["server"] ?? "https://api.day.app").replace(/\/+$/, "");
569
+ const url = `${server}/${encodeURIComponent(key)}/${encodeURIComponent(notifyTitle(ev.kind))}/${encodeURIComponent(notifyBody(ev))}`;
570
+ const res = await fetch(url, {
571
+ method: "GET",
572
+ signal: AbortSignal.timeout(TIMEOUT_MS)
573
+ });
574
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
575
+ }
576
+ };
577
+
578
+ // src/notify/notifiers/ServerChanNotifier.ts
579
+ var TIMEOUT_MS2 = 1e4;
580
+ var ServerChanNotifier = class {
581
+ type = "serverchan";
582
+ async send(config, ev) {
583
+ const sendkey = config.config["sendkey"];
584
+ if (!sendkey) return;
585
+ const url = `https://sctapi.ftqq.com/${encodeURIComponent(sendkey)}.send`;
586
+ const body = new URLSearchParams({
587
+ title: notifyTitle(ev.kind),
588
+ desp: notifyBody(ev)
589
+ });
590
+ const res = await fetch(url, {
591
+ method: "POST",
592
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
593
+ body,
594
+ signal: AbortSignal.timeout(TIMEOUT_MS2)
595
+ });
596
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
597
+ }
598
+ };
599
+
600
+ // src/notify/notifiers/WebhookNotifier.ts
601
+ var TIMEOUT_MS3 = 1e4;
602
+ var WebhookNotifier = class {
603
+ type = "webhook";
604
+ async send(config, ev) {
605
+ const url = config.config["url"];
606
+ if (!url) return;
607
+ const res = await fetch(url, {
608
+ method: "POST",
609
+ headers: { "Content-Type": "application/json" },
610
+ body: JSON.stringify({
611
+ title: notifyTitle(ev.kind),
612
+ body: notifyBody(ev),
613
+ kind: ev.kind,
614
+ sessionId: ev.sessionId,
615
+ ts: ev.ts,
616
+ data: ev.data
617
+ }),
618
+ signal: AbortSignal.timeout(TIMEOUT_MS3)
619
+ });
620
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
621
+ }
622
+ };
623
+
624
+ // src/notify/notifiers/EmailNotifier.ts
625
+ import nodemailer from "nodemailer";
626
+ function defaultTransportFactory(opts) {
627
+ return nodemailer.createTransport(opts);
628
+ }
629
+ function buildText(ev) {
630
+ const d = ev.data;
631
+ switch (ev.kind) {
632
+ case "turn_completed":
633
+ case "turn_stopped": {
634
+ const lines = [];
635
+ lines.push(`\u4F1A\u8BDD: ${ev.sessionId}`);
636
+ lines.push(`\u72B6\u6001: ${String(d["status"] ?? ev.kind)}`);
637
+ if (d["iterations"] !== void 0)
638
+ lines.push(`\u8FED\u4EE3\u6B21\u6570: ${String(d["iterations"])}`);
639
+ const files = d["produced_files"];
640
+ if (Array.isArray(files) && files.length > 0)
641
+ lines.push(`\u4EA7\u51FA\u6587\u4EF6:
642
+ ${files.join("\n ")}`);
643
+ const summary = typeof d["summary"] === "string" ? d["summary"] : "";
644
+ if (summary.trim()) lines.push(`
645
+ \u6458\u8981:
646
+ ${summary}`);
647
+ return lines.join("\n");
648
+ }
649
+ case "approval_request":
650
+ return `${approvalToMarkdownTable(approvalInputFromData(d), {
651
+ title: "\u{1F512} \u9700\u8981\u5BA1\u6279"
652
+ })}
653
+ \u4F1A\u8BDD: ${ev.sessionId}`;
654
+ case "question_asked":
655
+ return `\u95EE\u9898: ${String(d["question"] ?? "")}
656
+ \u4F1A\u8BDD: ${ev.sessionId}`;
657
+ case "error":
658
+ return `\u9519\u8BEF: ${String(d["error"] ?? "\u672A\u77E5\u9519\u8BEF")}
659
+ \u4F1A\u8BDD: ${ev.sessionId}`;
660
+ default:
661
+ return `\u4F1A\u8BDD: ${ev.sessionId}
662
+ \u65F6\u95F4: ${ev.ts}`;
663
+ }
664
+ }
665
+ function escapeHtml2(text) {
666
+ return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
667
+ }
668
+ function buildHtml(ev) {
669
+ if (ev.kind === "approval_request") {
670
+ const table = approvalToHtmlTable(approvalInputFromData(ev.data));
671
+ return table + `<p style="color:#6b7280;font-size:12px;margin:10px 0 0;">\u4F1A\u8BDD: ${escapeHtml2(ev.sessionId)}</p>`;
672
+ }
673
+ return `<pre style="white-space:pre-wrap;font-family:inherit;">${escapeHtml2(buildText(ev))}</pre>`;
674
+ }
675
+ var EmailNotifier = class {
676
+ constructor(transportFactory = defaultTransportFactory) {
677
+ this.transportFactory = transportFactory;
678
+ }
679
+ transportFactory;
680
+ type = "email";
681
+ async send(config, ev) {
682
+ if (ev.kind === "turn_completed") {
683
+ const minSec = Number(config.config["minDurationSeconds"] ?? 60);
684
+ const durationMs = ev.data["durationMs"];
685
+ if (typeof durationMs === "number" && durationMs < minSec * 1e3) {
686
+ return;
687
+ }
688
+ }
689
+ const host = config.config["host"];
690
+ const to = config.config["to"];
691
+ if (!host || !to) return;
692
+ const secure = config.config["secure"] === "true";
693
+ const port = Number(config.config["port"]) || (secure ? 465 : 587);
694
+ const user = config.config["user"] ?? "";
695
+ const pass = config.config["pass"] ?? "";
696
+ const from = config.config["from"] ?? user;
697
+ if (!from) return;
698
+ const prefix = config.config["subjectPrefix"] ?? "[zqagent]";
699
+ const transport = this.transportFactory({
700
+ host,
701
+ port,
702
+ secure,
703
+ auth: user ? { user, pass } : void 0
704
+ });
705
+ try {
706
+ await transport.sendMail({
707
+ from,
708
+ to,
709
+ subject: `${prefix}${notifyTitle(ev.kind)}`,
710
+ text: buildText(ev),
711
+ html: buildHtml(ev)
712
+ });
713
+ } finally {
714
+ try {
715
+ transport.close();
716
+ } catch {
717
+ }
718
+ }
719
+ }
720
+ };
721
+
722
+ // src/notify/index.ts
723
+ function createNotificationHub() {
724
+ const hub = new NotificationHub();
725
+ hub.register(new BarkNotifier());
726
+ hub.register(new ServerChanNotifier());
727
+ hub.register(new WebhookNotifier());
728
+ hub.register(new EmailNotifier());
729
+ return hub;
730
+ }
731
+ export {
732
+ APPROVAL_COLUMNS,
733
+ APPROVAL_VALUE_MAX,
734
+ BarkNotifier,
735
+ CONTENT_DELTA_TYPES,
736
+ DEFAULT_CLOCK_SKEW_MS,
737
+ EmailNotifier,
738
+ FLEET_CMD_TYPES,
739
+ FLEET_TOPIC_PREFIX,
740
+ LLM_PRESETS,
741
+ NonceTracker,
742
+ NotificationHub,
743
+ SecretBox,
744
+ ServerChanNotifier,
745
+ WebhookNotifier,
746
+ approvalFields,
747
+ approvalInputFromData,
748
+ approvalToHtmlTable,
749
+ approvalToMarkdownTable,
750
+ channelWants,
751
+ contentRangeHeader,
752
+ createNotificationHub,
753
+ decryptWithKey,
754
+ docPreviewMime,
755
+ encryptWithKey,
756
+ fileEtag,
757
+ fleetAckTopic,
758
+ fleetCmdTopic,
759
+ fleetEventsTopic,
760
+ fleetInfoTopic,
761
+ fleetNotifyTopic,
762
+ fleetStatusTopic,
763
+ generateToken,
764
+ isFreshTimestamp,
765
+ llmPreset,
766
+ loadOrCreateMasterKey,
767
+ masterKeyFromHex,
768
+ mediaInfo,
769
+ mediaKind,
770
+ mediaMime,
771
+ notifyBody,
772
+ notifyTitle,
773
+ parseByteRange,
774
+ parseFleetEnvelope,
775
+ previewMime,
776
+ signFleetCmd,
777
+ verifyFleetSig
778
+ };
779
+ //# sourceMappingURL=index.js.map