@sleep2agi/commhub-server 0.9.0-preview.1 → 0.9.0-preview.100

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 (267) hide show
  1. package/README.md +130 -10
  2. package/bin/commhub.ts +92 -11
  3. package/package.json +8 -7
  4. package/src/ack-create-request-transport.test.ts +159 -0
  5. package/src/ack-create-request.test.ts +331 -0
  6. package/src/admin-delete-network-http.test.ts +228 -0
  7. package/src/admin-networks-http.test.ts +88 -0
  8. package/src/agent-access-batch.test.ts +210 -0
  9. package/src/agent-access.ts +487 -0
  10. package/src/agent-acl-groups-http.test.ts +247 -0
  11. package/src/agent-acl-http.test.ts +520 -0
  12. package/src/agent-acl-slice3-http.test.ts +267 -0
  13. package/src/agent-groups-perf.test.ts +190 -0
  14. package/src/agent-notice.ts +48 -0
  15. package/src/alias-filter.test.ts +72 -0
  16. package/src/alias-filter.ts +42 -0
  17. package/src/api-host-supervisors-can-create-nodes.test.ts +350 -0
  18. package/src/api-host-supervisors-fallback.test.ts +271 -0
  19. package/src/api-nodes-shape.test.ts +188 -0
  20. package/src/auth-sessions-http.test.ts +250 -0
  21. package/src/auth-validate.test.ts +5 -1
  22. package/src/auth.ts +493 -35
  23. package/src/avatar-validate.test.ts +163 -0
  24. package/src/avatar-validate.ts +103 -0
  25. package/src/blocked-exit-on-reply.test.ts +122 -0
  26. package/src/boot-side-effect.test.ts +124 -0
  27. package/src/commhub-bin-args.test.ts +85 -0
  28. package/src/config-apply-sec1.test.ts +270 -1
  29. package/src/config-apply-validate.test.ts +92 -0
  30. package/src/config-apply-validate.ts +81 -3
  31. package/src/create-node-blocked-gate.test.ts +356 -0
  32. package/src/create-node-tool-schema.test.ts +39 -0
  33. package/src/create-node-validate.test.ts +275 -0
  34. package/src/create-node-validate.ts +288 -0
  35. package/src/create-node-workdir.test.ts +255 -0
  36. package/src/create-node.test.ts +354 -0
  37. package/src/create-node.ts +349 -0
  38. package/src/dashboard-slash-routing-http.test.ts +107 -0
  39. package/src/db-adapter-guard.test.ts +148 -0
  40. package/src/db-adapter-translate.test.ts +128 -0
  41. package/src/db-adapter.ts +323 -87
  42. package/src/db-timestamp.test.ts +38 -0
  43. package/src/db-timestamp.ts +15 -0
  44. package/src/db.ts +965 -34
  45. package/src/delivered-without-push.test.ts +140 -0
  46. package/src/department-groups-http.test.ts +209 -0
  47. package/src/department-groups-sync-http.test.ts +328 -0
  48. package/src/department-groups.ts +434 -0
  49. package/src/department-heads-http.test.ts +329 -0
  50. package/src/department-heads.ts +226 -0
  51. package/src/departments-http.test.ts +185 -0
  52. package/src/departments.ts +260 -0
  53. package/src/desktop-message-seam.test.ts +150 -0
  54. package/src/desktop-message.test.ts +368 -0
  55. package/src/dm-file-scope-http.test.ts +178 -0
  56. package/src/ext-token-shared.test.ts +60 -0
  57. package/src/ext-validation-regression.test.ts +131 -0
  58. package/src/external-schedule-edits-http.test.ts +321 -0
  59. package/src/external-schedule-edits-timestamps.test.ts +64 -0
  60. package/src/external-schedule-edits.ts +327 -0
  61. package/src/file-download-authz-dev-open.test.ts +135 -0
  62. package/src/file-download-authz.test.ts +404 -0
  63. package/src/file-download-date-bucket.test.ts +436 -0
  64. package/src/file-network-scope-dev-open.test.ts +97 -0
  65. package/src/file-network-scope.test.ts +732 -0
  66. package/src/group-membership-events-http.test.ts +264 -0
  67. package/src/group-messages-http.test.ts +482 -0
  68. package/src/group-messages.ts +297 -0
  69. package/src/health-redaction.test.ts +642 -0
  70. package/src/http-gzip.test.ts +94 -0
  71. package/src/http-gzip.ts +112 -0
  72. package/src/hub-timestamp.test.ts +38 -0
  73. package/src/hub-timestamp.ts +27 -0
  74. package/src/human-dm.ts +215 -0
  75. package/src/inbox-count-sse.test.ts +310 -0
  76. package/src/inbox-count.ts +29 -0
  77. package/src/index.ts +15 -2172
  78. package/src/lifecycle-guard.ts +50 -0
  79. package/src/list-host-supervisors.test.ts +314 -0
  80. package/src/mcp-context-cost-http.test.ts +119 -0
  81. package/src/mcp-projects-events-http.test.ts +162 -0
  82. package/src/mcp-route-label-http.test.ts +120 -0
  83. package/src/mcp-write-network-resolution.test.ts +429 -0
  84. package/src/member-audit-network-id-http.test.ts +93 -0
  85. package/src/member-presence-http.test.ts +206 -0
  86. package/src/messages-ack-by-agent-http.test.ts +142 -0
  87. package/src/migrate-sqlite-to-pg.test.ts +47 -0
  88. package/src/migrate-sqlite-to-pg.ts +263 -0
  89. package/src/model-auth-notify-http.test.ts +181 -0
  90. package/src/model-auth-notify.ts +116 -0
  91. package/src/network-name-http.test.ts +75 -0
  92. package/src/network-scope.ts +224 -0
  93. package/src/network-token-membership-http.test.ts +139 -0
  94. package/src/node-attrs-validate.ts +88 -0
  95. package/src/node-attrs.test.ts +217 -0
  96. package/src/node-create-request-status-http.test.ts +92 -0
  97. package/src/node-files-transport.test.ts +344 -0
  98. package/src/node-health-dispatch-http.test.ts +279 -0
  99. package/src/node-health-guard.ts +105 -0
  100. package/src/node-health-passthrough.test.ts +115 -0
  101. package/src/node-health-store.ts +72 -0
  102. package/src/node-id-alias.test.ts +188 -0
  103. package/src/node-identity-conflict-http.test.ts +269 -0
  104. package/src/node-identity-conflict.ts +142 -0
  105. package/src/node-identity-mismatch.test.ts +74 -0
  106. package/src/node-lifecycle-controllable.test.ts +64 -0
  107. package/src/node-lifecycle-controllable.ts +51 -0
  108. package/src/node-logs-transport.test.ts +263 -0
  109. package/src/node-permissions-http.test.ts +276 -0
  110. package/src/node-permissions.ts +242 -0
  111. package/src/node-request-retention.test.ts +281 -0
  112. package/src/node-request-retention.ts +120 -0
  113. package/src/node-skills-transport.test.ts +209 -0
  114. package/src/node-token-privilege-http.test.ts +149 -0
  115. package/src/observer-avatar-http.test.ts +431 -0
  116. package/src/observer-push.test.ts +252 -0
  117. package/src/package-version-consistency.test.ts +25 -0
  118. package/src/peer-reply-atomic.test.ts +455 -0
  119. package/src/peer-reply-capability.test.ts +70 -0
  120. package/src/pg-worker.ts +87 -0
  121. package/src/probe-validate.test.ts +221 -0
  122. package/src/probe-validate.ts +104 -0
  123. package/src/probe.test.ts +80 -0
  124. package/src/probe.ts +200 -0
  125. package/src/project-viewer-can-http.test.ts +114 -0
  126. package/src/push.ts +429 -10
  127. package/src/redact-tokens.test.ts +65 -0
  128. package/src/redact-tokens.ts +40 -0
  129. package/src/rename.ts +7 -2
  130. package/src/reply-target-derivation.test.ts +96 -0
  131. package/src/report-status-host-schema.test.ts +211 -0
  132. package/src/report-status-resume-id-handover.test.ts +150 -0
  133. package/src/require-explicit-test-db.ts +9 -0
  134. package/src/requirement-due-reminders-http.test.ts +390 -0
  135. package/src/requirement-due-reminders.ts +310 -0
  136. package/src/requirement-events-http.test.ts +257 -0
  137. package/src/requirement-events.test.ts +45 -0
  138. package/src/requirement-events.ts +144 -0
  139. package/src/requirement-notify.ts +165 -0
  140. package/src/requirement-participant-notify-http.test.ts +309 -0
  141. package/src/requirement-tag-ops-http.test.ts +266 -0
  142. package/src/requirement-tags.ts +89 -0
  143. package/src/requirements-comment-http.test.ts +165 -0
  144. package/src/requirements-completed-at-migration.test.ts +70 -0
  145. package/src/requirements-errors-mcp-http.test.ts +164 -0
  146. package/src/requirements-errors.ts +143 -0
  147. package/src/requirements-etag-http.test.ts +91 -0
  148. package/src/requirements-http.test.ts +709 -0
  149. package/src/requirements-last-event-http.test.ts +344 -0
  150. package/src/requirements-list-mcp-slim-http.test.ts +155 -0
  151. package/src/requirements-list-slim-http.test.ts +319 -0
  152. package/src/requirements-mcp.test.ts +163 -0
  153. package/src/requirements-migrate.ts +225 -0
  154. package/src/requirements-owner-strict-mcp-http.test.ts +145 -0
  155. package/src/requirements-people-http.test.ts +114 -0
  156. package/src/requirements-people-mcp-http.test.ts +225 -0
  157. package/src/requirements-people.ts +127 -0
  158. package/src/requirements-priority-lowest.test.ts +114 -0
  159. package/src/requirements-search-http.test.ts +182 -0
  160. package/src/requirements-search.test.ts +32 -0
  161. package/src/requirements-search.ts +145 -0
  162. package/src/requirements-seq-http.test.ts +193 -0
  163. package/src/requirements-seq-migration.test.ts +63 -0
  164. package/src/requirements-start-date.test.ts +125 -0
  165. package/src/requirements-stats-http.test.ts +288 -0
  166. package/src/requirements-stats.ts +164 -0
  167. package/src/requirements-status-participants-http.test.ts +220 -0
  168. package/src/requirements-upgrade-e2e.test.ts +126 -0
  169. package/src/requirements-upgrade-migration.test.ts +98 -0
  170. package/src/requirements.ts +1761 -0
  171. package/src/resolve-port.test.ts +57 -0
  172. package/src/resolve-port.ts +44 -0
  173. package/src/response-charset.test.ts +130 -0
  174. package/src/rest-explicit-columns-http.test.ts +222 -0
  175. package/src/rest-identity.test.ts +129 -0
  176. package/src/rest-identity.ts +100 -0
  177. package/src/rest-projections.ts +68 -0
  178. package/src/rest-write-network-resolution.test.ts +133 -0
  179. package/src/restricted-files.ts +73 -0
  180. package/src/retention.ts +15 -3
  181. package/src/route-callers-http.test.ts +120 -0
  182. package/src/route-timing.test.ts +211 -0
  183. package/src/route-timing.ts +258 -0
  184. package/src/rules-file-duplicate-alias.test.ts +234 -0
  185. package/src/rules-file-session-target.test.ts +207 -0
  186. package/src/rules-file-transport.test.ts +237 -0
  187. package/src/runtime-label.test.ts +22 -0
  188. package/src/runtime-label.ts +21 -0
  189. package/src/scheduled-doorbell-nodeless-session.test.ts +130 -0
  190. package/src/scheduled-reply-unread-http.test.ts +212 -0
  191. package/src/scheduled-run-terminal.test.ts +268 -0
  192. package/src/scheduled-skip-blocking-task.test.ts +120 -0
  193. package/src/scheduled-stuck-http.test.ts +238 -0
  194. package/src/scheduled-stuck.ts +153 -0
  195. package/src/scheduled-tasks-http.test.ts +638 -0
  196. package/src/scheduled-tasks.ts +641 -0
  197. package/src/send-dedup-request-id.test.ts +229 -0
  198. package/src/send-reply-agent-warning.test.ts +202 -0
  199. package/src/send-reply-alias-optional.test.ts +131 -0
  200. package/src/send-reply-attachments.test.ts +458 -0
  201. package/src/send_dedup.test.ts +54 -0
  202. package/src/send_dedup.ts +52 -6
  203. package/src/serve-error.test.ts +38 -0
  204. package/src/serve-error.ts +27 -0
  205. package/src/server.ts +5038 -0
  206. package/src/shared/external-schedule-contract.ts +60 -0
  207. package/src/shared/probe-host-allowlist-drift.test.ts +54 -0
  208. package/src/shared/probe-host-allowlist.ts +128 -0
  209. package/src/shared/reserved-env-drift.test.ts +43 -0
  210. package/src/shared/reserved-env.test.ts +35 -0
  211. package/src/shared/reserved-env.ts +31 -0
  212. package/src/side-thread-command-transport.test.ts +312 -0
  213. package/src/side-thread-command-transport.ts +318 -0
  214. package/src/side-thread-http-integration.test.ts +233 -0
  215. package/src/side-thread-http.ts +504 -0
  216. package/src/side-thread-production.test.ts +72 -0
  217. package/src/side-thread-production.ts +118 -0
  218. package/src/side-thread.test.ts +1067 -0
  219. package/src/side-thread.ts +2499 -0
  220. package/src/skillhub-http.test.ts +108 -0
  221. package/src/sqlite-close-checkpoint.test.ts +78 -0
  222. package/src/start-node.test.ts +109 -0
  223. package/src/status-alias-filter-http.test.ts +106 -0
  224. package/src/status-alias-resolver-http.test.ts +163 -0
  225. package/src/status-alias-resolver.ts +19 -0
  226. package/src/status-node-id-filter-http.test.ts +161 -0
  227. package/src/status-read-cache-http.test.ts +229 -0
  228. package/src/status-read-cache.ts +122 -0
  229. package/src/stop-delete-node.test.ts +1164 -0
  230. package/src/task-access-http.test.ts +379 -0
  231. package/src/task-access.ts +413 -0
  232. package/src/task-auth-origin-http.test.ts +91 -0
  233. package/src/task-auth-origin.test.ts +24 -0
  234. package/src/task-auth-origin.ts +7 -0
  235. package/src/task-consumption.test.ts +482 -0
  236. package/src/task-diagnostic-http.test.ts +149 -0
  237. package/src/task-diagnostic.test.ts +115 -0
  238. package/src/task-diagnostic.ts +118 -0
  239. package/src/task-expiry-notice-http.test.ts +287 -0
  240. package/src/task-expiry-notice.ts +313 -0
  241. package/src/task-idempotency-mcp.test.ts +142 -0
  242. package/src/task-idempotency.test.ts +60 -0
  243. package/src/task-idempotency.ts +75 -0
  244. package/src/task-lifecycle-watcher.test.ts +204 -0
  245. package/src/task-lifecycle-watcher.ts +49 -0
  246. package/src/task-network-resolution-http.test.ts +141 -0
  247. package/src/task-queue-ahead-http.test.ts +309 -0
  248. package/src/task-queue-ahead-scale.test.ts +211 -0
  249. package/src/task-queue-ahead.ts +186 -0
  250. package/src/task-stale-open-http.test.ts +290 -0
  251. package/src/task-stale-open.ts +103 -0
  252. package/src/tasks-pagination-http.test.ts +141 -0
  253. package/src/tool-audience-http.test.ts +195 -0
  254. package/src/tool-audience.ts +109 -0
  255. package/src/tools-list-json-schema.test.ts +67 -0
  256. package/src/tools.ts +4955 -300
  257. package/src/update-provider.test.ts +234 -0
  258. package/src/upload-early-reject-guard.test.ts +155 -0
  259. package/src/uploads-http.test.ts +97 -15
  260. package/src/uploads.test.ts +66 -0
  261. package/src/uploads.ts +138 -5
  262. package/src/user-inbox-by-agent-http.test.ts +161 -0
  263. package/src/user-inbox-netid-notnull.test.ts +122 -0
  264. package/src/user-inbox-read-http.test.ts +223 -0
  265. package/src/user-inbox-schema.test.ts +54 -0
  266. package/src/vault.test.ts +200 -0
  267. package/src/vault.ts +156 -0
@@ -0,0 +1,153 @@
1
+ // #464 —— 定时任务被一次没结束的执行卡住:告警 + 超时放行。
2
+ //
3
+ // 现场:overlap=skip 的排程,上一次派出的任务节点一直没有终态回复 → 之后每一次都记成
4
+ // skipped / previous_run_active,没有超时、也没人被告诉(当天 ~25 分钟,10-01 ~7 小时)。
5
+ //
6
+ // 1. 告警:同一个「卡住的任务」(blocked_by_task_id)连续挡掉 N 次 → 给排程的创建者发**一条**通知
7
+ // (agent-notice.ts,与 #462 同一条路,出现在与目标节点的会话里)。按挡路的任务计数,所以
8
+ // 一段卡住只发一次;任务一换(下一次真派出去了)自然重新上膛。不依赖内存,Hub 重启不会重发。
9
+ // 2. 超时:挡路的任务开着超过 stuckTimeoutMs(间隔)就按**已有的** expired 状态结束它(同 TTL 巡检:
10
+ // tasks.status='expired' + 收件箱行置 acked + db.ts 的镜像把这次 run 记成 expired / task_expired),
11
+ // 然后这一次照常派发。任务行不删,照样能查;晚到的回复被 send_reply 以 reply_task_terminal 拒掉,
12
+ // 什么都不改(不重开 run、不碰新派出去的那条)。
13
+ // 如果超时发生在 N 次之前(长间隔的排程,比如每天一次),那一刻补发一条「已超时结束」—— 每段卡住
14
+ // 恰好一条通知。
15
+ //
16
+ // 默认值(PR 里有推导):N=3;超时 = clamp(6 × 间隔, 下限 1 小时, 上限 24 小时)。
17
+ // 下限 1 小时:一轮 agent 任务正常要跑几十分钟,不能因为排程间隔短就把还在干活的任务掐掉。
18
+ // 上限 24 小时:Hub 本来就给定时任务 24 小时的 expires_at(没被取走时由巡检过期),开着超过一天的
19
+ // 定时任务不是「还在做」。6 × 间隔:间隔长的排程(每小时一次)给它 6 小时,而不是 1 小时。
20
+ // 环境变量可改(运维 / 测试):COMMHUB_SCHEDULE_STUCK_NOTICE_SKIPS、COMMHUB_SCHEDULE_STUCK_TIMEOUT_FACTOR、
21
+ // COMMHUB_SCHEDULE_STUCK_TIMEOUT_FLOOR_SEC、COMMHUB_SCHEDULE_STUCK_TIMEOUT_CAP_SEC。
22
+
23
+ import { db, syncScheduledRunForTask } from "./db.js";
24
+ import { parseDbTimestampMs } from "./db-timestamp.js";
25
+ import { sendAgentNotice } from "./agent-notice.js";
26
+
27
+ export const SCHEDULE_STUCK_NOTICE_KIND = "schedule_stuck";
28
+
29
+ function envNumber(name: string, fallback: number, min: number): number {
30
+ const raw = process.env[name];
31
+ if (raw === undefined || raw.trim() === "") return fallback;
32
+ const n = Number(raw);
33
+ return Number.isFinite(n) && n >= min ? n : fallback;
34
+ }
35
+
36
+ export function stuckNoticeSkips(): number {
37
+ return Math.floor(envNumber("COMMHUB_SCHEDULE_STUCK_NOTICE_SKIPS", 3, 1));
38
+ }
39
+
40
+ /** 排程的名义间隔(秒):interval 取 every_seconds;daily / weekly 取一天(两次之间最短一天);once 取 0。 */
41
+ export function nominalIntervalSeconds(scheduleJson: string): number {
42
+ try {
43
+ const spec = JSON.parse(scheduleJson) as { type?: string; every_seconds?: number };
44
+ if (spec.type === "interval" && Number.isFinite(spec.every_seconds)) return Number(spec.every_seconds);
45
+ if (spec.type === "daily" || spec.type === "weekly") return 86_400;
46
+ } catch {}
47
+ return 0;
48
+ }
49
+
50
+ export function stuckTimeoutMs(scheduleJson: string): number {
51
+ const factor = envNumber("COMMHUB_SCHEDULE_STUCK_TIMEOUT_FACTOR", 6, 0);
52
+ const floor = envNumber("COMMHUB_SCHEDULE_STUCK_TIMEOUT_FLOOR_SEC", 3_600, 1);
53
+ const cap = Math.max(floor, envNumber("COMMHUB_SCHEDULE_STUCK_TIMEOUT_CAP_SEC", 86_400, 1));
54
+ return Math.min(cap, Math.max(floor, factor * nominalIntervalSeconds(scheduleJson))) * 1000;
55
+ }
56
+
57
+ type Blocker = { task_id: string; created_at: string; status: string };
58
+
59
+ /** 挡路的任务开了多久已经到超时 → true。读不出时间 = 不超时(宁可多挡一次,不错杀)。 */
60
+ export function blockerTimedOut(blocker: Pick<Blocker, "created_at">, scheduleJson: string, now: Date): boolean {
61
+ const started = parseDbTimestampMs(blocker.created_at);
62
+ if (!Number.isFinite(started)) return false;
63
+ return now.getTime() - started >= stuckTimeoutMs(scheduleJson);
64
+ }
65
+
66
+ /**
67
+ * 在调用方的事务里按超时结束挡路的任务。只动还开着的任务(条件写),返回是否真的结束了它。
68
+ * 与 TTL 巡检(server.ts patrolExpiredTasks)同一套写法:status=expired、收件箱行置 acked、镜像到 run。
69
+ */
70
+ export function expireStuckBlocker(taskId: string, networkId: string, ageMs: number, limitMs: number): boolean {
71
+ const changed = db.run(
72
+ `UPDATE tasks SET status = 'expired', completed_at = datetime('now')
73
+ WHERE task_id = ?1 AND network_id = ?2 AND status IN ('created', 'delivered', 'acked', 'running')`,
74
+ [taskId, networkId],
75
+ );
76
+ if (changed.changes < 1) return false;
77
+ db.run("UPDATE inbox SET acked = 1 WHERE COALESCE(task_id, id) = ?1 AND acked = 0", [taskId]);
78
+ syncScheduledRunForTask(taskId, networkId);
79
+ db.run(
80
+ "UPDATE scheduled_task_runs SET error_message = ?1 WHERE task_id = ?2 AND network_id = ?3",
81
+ [`timed out by the scheduler: open for ${Math.round(ageMs / 60_000)} min (limit ${Math.round(limitMs / 60_000)} min); the next occurrence was dispatched`, taskId, networkId],
82
+ );
83
+ return true;
84
+ }
85
+
86
+ /** 这个任务已经挡掉了几次(只算 previous_run_active 的跳过)。 */
87
+ export function skipsBlockedBy(scheduleId: string, taskId: string): number {
88
+ return db.get<{ n: number }>(
89
+ "SELECT COUNT(*) AS n FROM scheduled_task_runs WHERE schedule_id = ?1 AND blocked_by_task_id = ?2 AND error_code = 'previous_run_active'",
90
+ scheduleId, taskId,
91
+ )?.n ?? 0;
92
+ }
93
+
94
+ export type StuckNotice = {
95
+ reason: "stuck" | "timed_out";
96
+ scheduleId: string;
97
+ scheduleName: string;
98
+ networkId: string;
99
+ createdBy: string | null;
100
+ alias: string;
101
+ timezone: string;
102
+ taskId: string;
103
+ taskCreatedAt: string;
104
+ skips: number;
105
+ timeoutMs: number;
106
+ };
107
+
108
+ function fmtTime(dbTs: string, timezone: string): string {
109
+ const ms = parseDbTimestampMs(dbTs);
110
+ if (!Number.isFinite(ms)) return dbTs;
111
+ try {
112
+ const s = new Intl.DateTimeFormat("sv-SE", { timeZone: timezone, year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", hourCycle: "h23" }).format(new Date(ms));
113
+ return `${s} (${timezone})`;
114
+ } catch { return dbTs; }
115
+ }
116
+
117
+ function fmtDuration(ms: number): string {
118
+ const min = Math.round(ms / 60_000);
119
+ if (min < 60) return `${min} 分钟`;
120
+ const h = Math.floor(min / 60), m = min % 60;
121
+ return m ? `${h} 小时 ${m} 分钟` : `${h} 小时`;
122
+ }
123
+
124
+ export function stuckNoticeText(n: StuckNotice): { title: string; text: string } {
125
+ const started = fmtTime(n.taskCreatedAt, n.timezone);
126
+ if (n.reason === "stuck") {
127
+ return {
128
+ title: "定时任务被卡住",
129
+ text: `定时任务「${n.scheduleName}」被上一次的执行卡住了:任务 ${n.taskId} 于 ${started} 派给节点 ${n.alias},到现在还没有完成,之后的 ${n.skips} 次执行都被跳过。\n\n`
130
+ + `它开着超过 ${fmtDuration(n.timeoutMs)} 后会被自动按超时结束,下一次照常派发。等不及可以在任务列表里取消这条任务。`,
131
+ };
132
+ }
133
+ return {
134
+ title: "定时任务已超时放行",
135
+ text: `定时任务「${n.scheduleName}」上一次的任务 ${n.taskId}(${started} 派给节点 ${n.alias})开着超过 ${fmtDuration(n.timeoutMs)} 还没有完成,已按超时结束,不再挡后面的执行。`,
136
+ };
137
+ }
138
+
139
+ /** 提交之后调用。没有创建者 / 创建者不在网络里 → 不发。 */
140
+ export function sendStuckNotice(n: StuckNotice): string | null {
141
+ if (!n.createdBy) return null;
142
+ try {
143
+ const { title, text } = stuckNoticeText(n);
144
+ return sendAgentNotice({
145
+ networkId: n.networkId, userId: n.createdBy, fromAlias: n.alias, kind: SCHEDULE_STUCK_NOTICE_KIND, title, text,
146
+ meta: { schedule_stuck: { reason: n.reason, schedule_id: n.scheduleId, blocked_by_task_id: n.taskId, skips: n.skips } },
147
+ idPrefix: "dm_sched_",
148
+ });
149
+ } catch (e: any) {
150
+ console.error(`[scheduled-tasks] stuck notice failed schedule=${n.scheduleId}: ${e?.message || e}`);
151
+ return null;
152
+ }
153
+ }