opencode-oncall 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 (105) hide show
  1. package/LICENSE +151 -0
  2. package/README.md +50 -0
  3. package/dist/common-settings-actions.d.ts +15 -0
  4. package/dist/common-settings-actions.js +48 -0
  5. package/dist/common-settings-store.d.ts +1 -0
  6. package/dist/common-settings-store.js +1 -0
  7. package/dist/index.d.ts +1 -0
  8. package/dist/index.js +1 -0
  9. package/dist/plugin-hooks.d.ts +51 -0
  10. package/dist/plugin-hooks.js +288 -0
  11. package/dist/plugin.d.ts +10 -0
  12. package/dist/plugin.js +115 -0
  13. package/dist/settings-store.d.ts +50 -0
  14. package/dist/settings-store.js +214 -0
  15. package/dist/store-paths.d.ts +16 -0
  16. package/dist/store-paths.js +61 -0
  17. package/dist/ui/wechat-menu.d.ts +26 -0
  18. package/dist/ui/wechat-menu.js +90 -0
  19. package/dist/wechat/bind-flow.d.ts +29 -0
  20. package/dist/wechat/bind-flow.js +207 -0
  21. package/dist/wechat/bridge.d.ts +136 -0
  22. package/dist/wechat/bridge.js +1059 -0
  23. package/dist/wechat/broker-client.d.ts +23 -0
  24. package/dist/wechat/broker-client.js +274 -0
  25. package/dist/wechat/broker-endpoint.d.ts +21 -0
  26. package/dist/wechat/broker-endpoint.js +78 -0
  27. package/dist/wechat/broker-entry.d.ts +123 -0
  28. package/dist/wechat/broker-entry.js +1321 -0
  29. package/dist/wechat/broker-launcher.d.ts +37 -0
  30. package/dist/wechat/broker-launcher.js +418 -0
  31. package/dist/wechat/broker-mutation-queue.d.ts +93 -0
  32. package/dist/wechat/broker-mutation-queue.js +126 -0
  33. package/dist/wechat/broker-server.d.ts +86 -0
  34. package/dist/wechat/broker-server.js +1340 -0
  35. package/dist/wechat/broker-state-store.d.ts +335 -0
  36. package/dist/wechat/broker-state-store.js +1964 -0
  37. package/dist/wechat/command-parser.d.ts +18 -0
  38. package/dist/wechat/command-parser.js +58 -0
  39. package/dist/wechat/compat/jiti-loader.d.ts +27 -0
  40. package/dist/wechat/compat/jiti-loader.js +118 -0
  41. package/dist/wechat/compat/openclaw-account-helpers.d.ts +29 -0
  42. package/dist/wechat/compat/openclaw-account-helpers.js +60 -0
  43. package/dist/wechat/compat/openclaw-bind-helpers.d.ts +29 -0
  44. package/dist/wechat/compat/openclaw-bind-helpers.js +169 -0
  45. package/dist/wechat/compat/openclaw-guided-smoke.d.ts +180 -0
  46. package/dist/wechat/compat/openclaw-guided-smoke.js +1134 -0
  47. package/dist/wechat/compat/openclaw-public-entry.d.ts +33 -0
  48. package/dist/wechat/compat/openclaw-public-entry.js +62 -0
  49. package/dist/wechat/compat/openclaw-public-helpers.d.ts +70 -0
  50. package/dist/wechat/compat/openclaw-public-helpers.js +68 -0
  51. package/dist/wechat/compat/openclaw-qr-gateway.d.ts +15 -0
  52. package/dist/wechat/compat/openclaw-qr-gateway.js +39 -0
  53. package/dist/wechat/compat/openclaw-smoke.d.ts +48 -0
  54. package/dist/wechat/compat/openclaw-smoke.js +100 -0
  55. package/dist/wechat/compat/openclaw-sync-buf.d.ts +24 -0
  56. package/dist/wechat/compat/openclaw-sync-buf.js +80 -0
  57. package/dist/wechat/compat/openclaw-updates-send.d.ts +47 -0
  58. package/dist/wechat/compat/openclaw-updates-send.js +38 -0
  59. package/dist/wechat/compat/qrcode-terminal-loader.d.ts +12 -0
  60. package/dist/wechat/compat/qrcode-terminal-loader.js +16 -0
  61. package/dist/wechat/compat/slash-guard.d.ts +11 -0
  62. package/dist/wechat/compat/slash-guard.js +24 -0
  63. package/dist/wechat/dead-letter-store.d.ts +48 -0
  64. package/dist/wechat/dead-letter-store.js +224 -0
  65. package/dist/wechat/debug-bundle-collector.d.ts +49 -0
  66. package/dist/wechat/debug-bundle-collector.js +580 -0
  67. package/dist/wechat/debug-bundle-flow.d.ts +37 -0
  68. package/dist/wechat/debug-bundle-flow.js +180 -0
  69. package/dist/wechat/debug-bundle-redaction.d.ts +14 -0
  70. package/dist/wechat/debug-bundle-redaction.js +339 -0
  71. package/dist/wechat/handle.d.ts +10 -0
  72. package/dist/wechat/handle.js +57 -0
  73. package/dist/wechat/ipc-auth.d.ts +6 -0
  74. package/dist/wechat/ipc-auth.js +39 -0
  75. package/dist/wechat/latest-account-state-store.d.ts +8 -0
  76. package/dist/wechat/latest-account-state-store.js +38 -0
  77. package/dist/wechat/notification-dispatcher.d.ts +34 -0
  78. package/dist/wechat/notification-dispatcher.js +266 -0
  79. package/dist/wechat/notification-format.d.ts +15 -0
  80. package/dist/wechat/notification-format.js +196 -0
  81. package/dist/wechat/notification-store.d.ts +72 -0
  82. package/dist/wechat/notification-store.js +807 -0
  83. package/dist/wechat/notification-types.d.ts +37 -0
  84. package/dist/wechat/notification-types.js +1 -0
  85. package/dist/wechat/openclaw-account-adapter.d.ts +30 -0
  86. package/dist/wechat/openclaw-account-adapter.js +60 -0
  87. package/dist/wechat/operator-store.d.ts +9 -0
  88. package/dist/wechat/operator-store.js +69 -0
  89. package/dist/wechat/protocol.d.ts +150 -0
  90. package/dist/wechat/protocol.js +197 -0
  91. package/dist/wechat/question-interaction.d.ts +24 -0
  92. package/dist/wechat/question-interaction.js +180 -0
  93. package/dist/wechat/request-store.d.ts +108 -0
  94. package/dist/wechat/request-store.js +669 -0
  95. package/dist/wechat/session-digest.d.ts +50 -0
  96. package/dist/wechat/session-digest.js +167 -0
  97. package/dist/wechat/state-paths.d.ts +26 -0
  98. package/dist/wechat/state-paths.js +92 -0
  99. package/dist/wechat/status-format.d.ts +26 -0
  100. package/dist/wechat/status-format.js +616 -0
  101. package/dist/wechat/token-store.d.ts +20 -0
  102. package/dist/wechat/token-store.js +193 -0
  103. package/dist/wechat/wechat-status-runtime.d.ts +89 -0
  104. package/dist/wechat/wechat-status-runtime.js +518 -0
  105. package/package.json +74 -0
@@ -0,0 +1,616 @@
1
+ const HIGHLIGHT_ORDER = {
2
+ permission: 0,
3
+ question: 1,
4
+ "running-tool": 2,
5
+ "completed-tool": 3,
6
+ todo: 4,
7
+ status: 5,
8
+ };
9
+ function asObject(value) {
10
+ if (typeof value !== "object" || value === null) {
11
+ return {};
12
+ }
13
+ return value;
14
+ }
15
+ function isNonEmptyString(value) {
16
+ return typeof value === "string" && value.trim().length > 0;
17
+ }
18
+ function toFiniteNumber(value, fallback = 0) {
19
+ if (typeof value === "number" && Number.isFinite(value)) {
20
+ return value;
21
+ }
22
+ return fallback;
23
+ }
24
+ function dedupeAndSortStrings(values) {
25
+ return [...new Set(values)].sort((a, b) => a.localeCompare(b));
26
+ }
27
+ function isHighlightKind(value) {
28
+ return (value === "permission" ||
29
+ value === "question" ||
30
+ value === "running-tool" ||
31
+ value === "completed-tool" ||
32
+ value === "todo" ||
33
+ value === "status");
34
+ }
35
+ function normalizeHighlight(value) {
36
+ const record = asObject(value);
37
+ if (!isHighlightKind(record.kind)) {
38
+ return null;
39
+ }
40
+ if (!isNonEmptyString(record.text)) {
41
+ return null;
42
+ }
43
+ return {
44
+ kind: record.kind,
45
+ text: record.text,
46
+ };
47
+ }
48
+ function isTodoStatus(value) {
49
+ return value === "pending" || value === "in_progress" || value === "completed" || value === "cancelled";
50
+ }
51
+ function normalizeTodoItem(value) {
52
+ const record = asObject(value);
53
+ if (!isTodoStatus(record.status) || !isNonEmptyString(record.content)) {
54
+ return null;
55
+ }
56
+ return {
57
+ status: record.status,
58
+ content: record.content.trim(),
59
+ };
60
+ }
61
+ function normalizeSessionDigest(value) {
62
+ const record = asObject(value);
63
+ if (!isNonEmptyString(record.sessionID)) {
64
+ return null;
65
+ }
66
+ const statusValue = record.status;
67
+ const normalizedStatus = statusValue === "busy" || statusValue === "idle" || statusValue === "retry" || statusValue === "unknown"
68
+ ? statusValue
69
+ : "unknown";
70
+ const highlightsRaw = Array.isArray(record.highlights) ? record.highlights : [];
71
+ const highlights = highlightsRaw
72
+ .map((item) => normalizeHighlight(item))
73
+ .filter((item) => item !== null);
74
+ const todoItems = Array.isArray(record.todoItems)
75
+ ? record.todoItems
76
+ .map((item) => normalizeTodoItem(item))
77
+ .filter((item) => item !== null)
78
+ : undefined;
79
+ return {
80
+ sessionID: record.sessionID,
81
+ ...(isNonEmptyString(record.parentID) ? { parentID: record.parentID } : {}),
82
+ title: isNonEmptyString(record.title) ? record.title : "",
83
+ directory: isNonEmptyString(record.directory) ? record.directory : "",
84
+ updatedAt: toFiniteNumber(record.updatedAt),
85
+ status: normalizedStatus,
86
+ pendingQuestionCount: toFiniteNumber(record.pendingQuestionCount),
87
+ pendingPermissionCount: toFiniteNumber(record.pendingPermissionCount),
88
+ todoSummary: {
89
+ total: toFiniteNumber(asObject(record.todoSummary).total),
90
+ inProgress: toFiniteNumber(asObject(record.todoSummary).inProgress),
91
+ completed: toFiniteNumber(asObject(record.todoSummary).completed),
92
+ },
93
+ unavailable: toSessionUnavailable(record.unavailable),
94
+ highlights,
95
+ ...(todoItems !== undefined ? { todoItems } : {}),
96
+ ...(Array.isArray(record.questionHighlights)
97
+ ? {
98
+ questionHighlights: record.questionHighlights
99
+ .filter((item) => typeof item === "string" && item.trim().length > 0)
100
+ .map((item) => item.trim()),
101
+ }
102
+ : {}),
103
+ };
104
+ }
105
+ function toSessionDigestArray(value) {
106
+ if (!Array.isArray(value)) {
107
+ return [];
108
+ }
109
+ return value.map((item) => normalizeSessionDigest(item)).filter((item) => item !== null);
110
+ }
111
+ function toSessionUnavailable(value) {
112
+ if (!Array.isArray(value)) {
113
+ return [];
114
+ }
115
+ return dedupeAndSortStrings(value.filter((item) => item === "messages" || item === "todo")).filter((item) => item === "messages" || item === "todo");
116
+ }
117
+ function toInstanceUnavailable(value) {
118
+ if (!Array.isArray(value)) {
119
+ return [];
120
+ }
121
+ return dedupeAndSortStrings(value.filter((item) => typeof item === "string" && item.length > 0));
122
+ }
123
+ function normalizeSnapshot(snapshot) {
124
+ const record = asObject(snapshot);
125
+ return {
126
+ instanceID: isNonEmptyString(record.instanceID) ? record.instanceID : "unknown-instance",
127
+ instanceName: isNonEmptyString(record.instanceName) ? record.instanceName : "",
128
+ pid: typeof record.pid === "number" && Number.isFinite(record.pid) ? record.pid : 0,
129
+ projectName: isNonEmptyString(record.projectName) ? record.projectName : undefined,
130
+ directory: isNonEmptyString(record.directory) ? record.directory : "",
131
+ collectedAt: typeof record.collectedAt === "number" && Number.isFinite(record.collectedAt) ? record.collectedAt : 0,
132
+ sessions: toSessionDigestArray(record.sessions),
133
+ unavailable: toInstanceUnavailable(record.unavailable),
134
+ };
135
+ }
136
+ function sortHighlights(highlights) {
137
+ return [...highlights].sort((a, b) => {
138
+ const orderA = HIGHLIGHT_ORDER[a.kind] ?? 999;
139
+ const orderB = HIGHLIGHT_ORDER[b.kind] ?? 999;
140
+ if (orderA !== orderB) {
141
+ return orderA - orderB;
142
+ }
143
+ return a.text.localeCompare(b.text);
144
+ });
145
+ }
146
+ function pickTopSessions(sessions) {
147
+ const hasChildOrSubagent = sessions.some((session) => isChildOrSubagentSession(session));
148
+ const mainSessions = sessions.filter((session) => !isChildOrSubagentSession(session));
149
+ const candidates = hasChildOrSubagent ? mainSessions : sessions;
150
+ const limit = hasChildOrSubagent ? 1 : 3;
151
+ return [...candidates]
152
+ .sort((a, b) => {
153
+ const ua = typeof a.updatedAt === "number" && Number.isFinite(a.updatedAt) ? a.updatedAt : 0;
154
+ const ub = typeof b.updatedAt === "number" && Number.isFinite(b.updatedAt) ? b.updatedAt : 0;
155
+ if (ub !== ua) {
156
+ return ub - ua;
157
+ }
158
+ return a.sessionID.localeCompare(b.sessionID);
159
+ })
160
+ .slice(0, limit);
161
+ }
162
+ function isLikelySubagentTitle(title) {
163
+ return typeof title === "string" && /\(@[^)]*\bsubagent\)/i.test(title);
164
+ }
165
+ function isChildOrSubagentSession(session) {
166
+ return isNonEmptyString(session.parentID) || isLikelySubagentTitle(session.title);
167
+ }
168
+ function formatSessionTags(session) {
169
+ return [
170
+ session.status === "busy" ? "#busy" : session.status === "idle" ? "#idle" : `#${session.status}`,
171
+ `#todo:${session.todoSummary.total}`,
172
+ `#question:${session.pendingQuestionCount}`,
173
+ `#permission:${session.pendingPermissionCount}`,
174
+ ].map((tag) => `\`${tag}\``).join(" ");
175
+ }
176
+ function formatTodoItem(todo) {
177
+ const prefix = todo.status === "completed"
178
+ ? "[x]"
179
+ : todo.status === "in_progress"
180
+ ? "[-]"
181
+ : todo.status === "cancelled"
182
+ ? "[~]"
183
+ : "[ ]";
184
+ return `${prefix} ${todo.content}`;
185
+ }
186
+ function formatInstanceTitle(snapshot, fallback) {
187
+ const name = isNonEmptyString(snapshot.instanceName) ? snapshot.instanceName.trim() : fallback;
188
+ return `## 实例:${name || "未命名实例"}`;
189
+ }
190
+ function formatSessionTitle(session) {
191
+ const title = isNonEmptyString(session.title) ? session.title.trim() : "未命名会话";
192
+ return `### 会话:${title}`;
193
+ }
194
+ function formatQuestionSummary(prompt) {
195
+ const record = asObject(prompt);
196
+ if (isNonEmptyString(record.title)) {
197
+ return record.title.trim();
198
+ }
199
+ if (isNonEmptyString(record.body)) {
200
+ return record.body.trim();
201
+ }
202
+ return "待回复问题";
203
+ }
204
+ function formatPermissionSummary(prompt) {
205
+ const record = asObject(prompt);
206
+ const title = isNonEmptyString(record.title) ? record.title.trim() : "";
207
+ const description = isNonEmptyString(record.description) ? record.description.trim() : "";
208
+ if (title && description) {
209
+ return `${title}:${description}`;
210
+ }
211
+ if (title) {
212
+ return title;
213
+ }
214
+ if (description) {
215
+ return description;
216
+ }
217
+ return "待处理权限请求";
218
+ }
219
+ function formatNaturalStopSummary(record) {
220
+ return isNonEmptyString(record.redactedSummary)
221
+ ? record.redactedSummary.trim()
222
+ : "需要补充自然中止说明";
223
+ }
224
+ function sortActionItems(items) {
225
+ return [...items].sort((left, right) => {
226
+ const leftHasCreatedAt = typeof left.createdAt === "number" && Number.isFinite(left.createdAt);
227
+ const rightHasCreatedAt = typeof right.createdAt === "number" && Number.isFinite(right.createdAt);
228
+ if (leftHasCreatedAt && rightHasCreatedAt && left.createdAt !== right.createdAt) {
229
+ return left.createdAt - right.createdAt;
230
+ }
231
+ if (leftHasCreatedAt !== rightHasCreatedAt) {
232
+ return leftHasCreatedAt ? -1 : 1;
233
+ }
234
+ return left.handle.localeCompare(right.handle);
235
+ });
236
+ }
237
+ function listActiveQuestionTodoItems(view) {
238
+ if (!view) {
239
+ return [];
240
+ }
241
+ const items = Object.values(view.active.questions)
242
+ .map((value) => asObject(value))
243
+ .map((record) => {
244
+ const handle = isNonEmptyString(record.handle) ? record.handle.trim() : "";
245
+ if (!handle) {
246
+ return null;
247
+ }
248
+ const instanceID = isNonEmptyString(record.scopeKey)
249
+ ? record.scopeKey.trim()
250
+ : isNonEmptyString(record.instanceID)
251
+ ? record.instanceID.trim()
252
+ : undefined;
253
+ return {
254
+ handle,
255
+ summary: formatQuestionSummary(record.prompt),
256
+ ...(instanceID ? { instanceID } : {}),
257
+ ...(typeof record.createdAt === "number" && Number.isFinite(record.createdAt) ? { createdAt: record.createdAt } : {}),
258
+ };
259
+ })
260
+ .filter((item) => item !== null);
261
+ return sortActionItems(items);
262
+ }
263
+ function listActivePermissionTodoItems(view) {
264
+ if (!view) {
265
+ return [];
266
+ }
267
+ const items = Object.values(view.active.permissions)
268
+ .map((value) => asObject(value))
269
+ .map((record) => {
270
+ const handle = isNonEmptyString(record.handle) ? record.handle.trim() : "";
271
+ if (!handle) {
272
+ return null;
273
+ }
274
+ return {
275
+ handle,
276
+ summary: formatPermissionSummary(record.prompt),
277
+ ...(typeof record.createdAt === "number" && Number.isFinite(record.createdAt) ? { createdAt: record.createdAt } : {}),
278
+ };
279
+ })
280
+ .filter((item) => item !== null);
281
+ return sortActionItems(items);
282
+ }
283
+ function listActiveNaturalStopTodoItems(view) {
284
+ if (!view) {
285
+ return [];
286
+ }
287
+ const items = Object.values(view.active.naturalStops)
288
+ .map((value) => asObject(value))
289
+ .map((record) => {
290
+ const handle = isNonEmptyString(record.handle) ? record.handle.trim() : "";
291
+ if (!handle) {
292
+ return null;
293
+ }
294
+ return {
295
+ handle,
296
+ summary: formatNaturalStopSummary(record),
297
+ ...(isNonEmptyString(record.severityAdvice) ? { severityAdvice: record.severityAdvice.trim() } : {}),
298
+ ...(typeof record.createdAt === "number" && Number.isFinite(record.createdAt) ? { createdAt: record.createdAt } : {}),
299
+ };
300
+ })
301
+ .filter((item) => item !== null);
302
+ return sortActionItems(items);
303
+ }
304
+ function formatStatusQuestionItem(item) {
305
+ return [
306
+ "待回复问题",
307
+ `QID:${item.handle}`,
308
+ `摘要:${item.summary}`,
309
+ `回复:/reply ${item.handle} 你的回复`,
310
+ ];
311
+ }
312
+ function listBrokerViewInstanceIDs(view) {
313
+ const ids = new Set();
314
+ for (const instanceID of Object.keys(view.connections)) {
315
+ if (instanceID.trim().length > 0) {
316
+ ids.add(instanceID);
317
+ }
318
+ }
319
+ for (const instanceID of Object.keys(view.active.instances)) {
320
+ if (instanceID.trim().length > 0) {
321
+ ids.add(instanceID);
322
+ }
323
+ }
324
+ for (const record of Object.values(view.active.sessions)) {
325
+ const candidate = isNonEmptyString(asObject(record).instanceID) ? String(asObject(record).instanceID).trim() : "";
326
+ if (candidate.length > 0) {
327
+ ids.add(candidate);
328
+ }
329
+ }
330
+ for (const record of Object.values(view.active.retryErrors)) {
331
+ const candidate = isNonEmptyString(asObject(record).instanceID) ? String(asObject(record).instanceID).trim() : "";
332
+ if (candidate.length > 0) {
333
+ ids.add(candidate);
334
+ }
335
+ }
336
+ return [...ids].sort((left, right) => left.localeCompare(right));
337
+ }
338
+ function countActiveItemsBySession(records, instanceID, sessionID) {
339
+ return Object.values(records).filter((record) => (asObject(record).instanceID === instanceID && asObject(record).sessionID === sessionID)).length;
340
+ }
341
+ function createRetryStatusHighlights(record) {
342
+ const highlights = [];
343
+ if (isNonEmptyString(record.action)) {
344
+ highlights.push({ kind: "status", text: `动作:${record.action.trim()}` });
345
+ }
346
+ if (isNonEmptyString(record.redactedSummary)) {
347
+ highlights.push({ kind: "status", text: `原因摘要:${record.redactedSummary.trim()}` });
348
+ }
349
+ if (isNonEmptyString(record.severityAdvice)) {
350
+ highlights.push({ kind: "status", text: `处理建议:${record.severityAdvice.trim()}` });
351
+ }
352
+ return highlights;
353
+ }
354
+ function buildBrokerViewSnapshot(view, instanceID) {
355
+ const instanceRecord = asObject(view.active.instances[instanceID]);
356
+ const sessions = Object.values(view.active.sessions)
357
+ .map((record) => asObject(record))
358
+ .filter((record) => record.instanceID === instanceID && isNonEmptyString(record.sessionID))
359
+ .map((record) => ({
360
+ sessionID: String(record.sessionID).trim(),
361
+ ...(isNonEmptyString(record.parentID) ? { parentID: record.parentID } : {}),
362
+ title: isNonEmptyString(record.title) ? record.title : "",
363
+ directory: isNonEmptyString(record.directory) ? record.directory : "",
364
+ updatedAt: toFiniteNumber(record.updatedAt),
365
+ status: (record.status === "busy" || record.status === "idle" || record.status === "retry" || record.status === "unknown"
366
+ ? record.status
367
+ : "unknown"),
368
+ pendingQuestionCount: typeof record.pendingQuestionCount === "number"
369
+ ? record.pendingQuestionCount
370
+ : countActiveItemsBySession(view.active.questions, instanceID, String(record.sessionID).trim()),
371
+ pendingPermissionCount: typeof record.pendingPermissionCount === "number"
372
+ ? record.pendingPermissionCount
373
+ : countActiveItemsBySession(view.active.permissions, instanceID, String(record.sessionID).trim()),
374
+ todoSummary: {
375
+ total: toFiniteNumber(asObject(record.todoSummary).total),
376
+ inProgress: toFiniteNumber(asObject(record.todoSummary).inProgress),
377
+ completed: toFiniteNumber(asObject(record.todoSummary).completed),
378
+ },
379
+ unavailable: toSessionUnavailable(record.unavailable),
380
+ highlights: Array.isArray(record.highlights) ? record.highlights : [],
381
+ ...(Array.isArray(record.todoItems) ? { todoItems: record.todoItems } : {}),
382
+ ...(Array.isArray(record.questionHighlights) ? { questionHighlights: record.questionHighlights } : {}),
383
+ }));
384
+ const sessionByID = new Map(sessions.map((session) => [session.sessionID, session]));
385
+ let syntheticRetryIndex = 0;
386
+ for (const retryRecord of Object.values(view.active.retryErrors)) {
387
+ const record = asObject(retryRecord);
388
+ if (record.instanceID !== instanceID) {
389
+ continue;
390
+ }
391
+ const retrySessionID = isNonEmptyString(record.sessionID)
392
+ ? record.sessionID.trim()
393
+ : `retry-${syntheticRetryIndex + 1}`;
394
+ let session = sessionByID.get(retrySessionID);
395
+ if (!session) {
396
+ syntheticRetryIndex += 1;
397
+ session = {
398
+ sessionID: retrySessionID,
399
+ title: "通知投递异常",
400
+ directory: "",
401
+ updatedAt: toFiniteNumber(record.updatedAt),
402
+ status: "retry",
403
+ pendingQuestionCount: 0,
404
+ pendingPermissionCount: 0,
405
+ todoSummary: {
406
+ total: 0,
407
+ inProgress: 0,
408
+ completed: 0,
409
+ },
410
+ unavailable: [],
411
+ highlights: [],
412
+ };
413
+ sessions.push(session);
414
+ sessionByID.set(retrySessionID, session);
415
+ }
416
+ session.status = "retry";
417
+ session.updatedAt = Math.max(session.updatedAt, toFiniteNumber(record.updatedAt, session.updatedAt));
418
+ session.highlights = [
419
+ ...sortHighlights(Array.isArray(session.highlights) ? session.highlights : []),
420
+ ...createRetryStatusHighlights(record),
421
+ ];
422
+ }
423
+ return {
424
+ instanceID,
425
+ instanceName: (isNonEmptyString(instanceRecord.displayName) ? instanceRecord.displayName : undefined)
426
+ ?? (isNonEmptyString(instanceRecord.instanceName) ? instanceRecord.instanceName : undefined)
427
+ ?? "",
428
+ pid: typeof instanceRecord.pid === "number" && Number.isFinite(instanceRecord.pid) ? instanceRecord.pid : 0,
429
+ projectName: isNonEmptyString(instanceRecord.projectName) ? instanceRecord.projectName : undefined,
430
+ directory: (isNonEmptyString(instanceRecord.projectDir) ? instanceRecord.projectDir : undefined)
431
+ ?? (isNonEmptyString(instanceRecord.directory) ? instanceRecord.directory : undefined)
432
+ ?? "",
433
+ collectedAt: (typeof instanceRecord.updatedAt === "number" && Number.isFinite(instanceRecord.updatedAt) ? instanceRecord.updatedAt : undefined)
434
+ ?? (typeof instanceRecord.connectedAt === "number" && Number.isFinite(instanceRecord.connectedAt) ? instanceRecord.connectedAt : undefined)
435
+ ?? 0,
436
+ sessions,
437
+ unavailable: toInstanceUnavailable(instanceRecord.unavailable),
438
+ };
439
+ }
440
+ export function formatInstanceStatusSnapshot(snapshotInput) {
441
+ const snapshot = normalizeSnapshot(snapshotInput);
442
+ const lines = [];
443
+ const name = snapshot.instanceName || snapshot.instanceID;
444
+ lines.push(`instance: ${name} (${snapshot.instanceID})`);
445
+ const instanceUnavailable = toInstanceUnavailable(snapshot.unavailable);
446
+ if (instanceUnavailable.length > 0) {
447
+ lines.push(`instance unavailable: ${instanceUnavailable.join(", ")}`);
448
+ }
449
+ const sessions = pickTopSessions(snapshot.sessions);
450
+ if (sessions.length === 0) {
451
+ lines.push("- no active sessions");
452
+ return lines.join("\n");
453
+ }
454
+ for (const session of sessions) {
455
+ const title = isNonEmptyString(session.title) ? session.title : session.sessionID;
456
+ lines.push(`- session ${session.sessionID}: ${title}`);
457
+ const sessionUnavailable = toSessionUnavailable(session.unavailable);
458
+ if (sessionUnavailable.length > 0) {
459
+ lines.push(` session unavailable: ${sessionUnavailable.join(", ")}`);
460
+ }
461
+ const highlights = sortHighlights(Array.isArray(session.highlights) ? session.highlights : []);
462
+ for (const highlight of highlights) {
463
+ lines.push(` ${highlight.text}`);
464
+ }
465
+ }
466
+ return lines.join("\n");
467
+ }
468
+ export function formatAggregatedStatusReply(input) {
469
+ const instances = Array.isArray(input.instances) ? input.instances : [];
470
+ const activeQuestions = Array.isArray(input.activeQuestions) ? input.activeQuestions : [];
471
+ if (instances.length === 0 && activeQuestions.length === 0) {
472
+ return "wechat status: no online instances";
473
+ }
474
+ const sections = [];
475
+ sections.push("wechat status");
476
+ const questionsByInstance = new Map();
477
+ for (const item of activeQuestions) {
478
+ if (!item.instanceID) {
479
+ continue;
480
+ }
481
+ const existing = questionsByInstance.get(item.instanceID) ?? [];
482
+ existing.push(item);
483
+ questionsByInstance.set(item.instanceID, existing);
484
+ }
485
+ const renderedQuestionHandles = new Set();
486
+ const renderQuestions = (questions) => {
487
+ for (const question of questions) {
488
+ if (renderedQuestionHandles.has(question.handle)) {
489
+ continue;
490
+ }
491
+ sections.push(...formatStatusQuestionItem(question));
492
+ renderedQuestionHandles.add(question.handle);
493
+ }
494
+ };
495
+ for (const instance of instances) {
496
+ if (instance.status === "timeout/unreachable") {
497
+ sections.push("## 实例:timeout/unreachable");
498
+ sections.push("---");
499
+ sections.push("timeout/unreachable");
500
+ renderQuestions(questionsByInstance.get(instance.instanceID) ?? []);
501
+ continue;
502
+ }
503
+ const snapshot = normalizeSnapshot(instance.snapshot);
504
+ sections.push(formatInstanceTitle(snapshot, "未命名实例"));
505
+ sections.push("---");
506
+ const sessions = pickTopSessions(snapshot.sessions);
507
+ if (sessions.length === 0) {
508
+ sections.push("- no active sessions");
509
+ }
510
+ else {
511
+ for (const session of sessions) {
512
+ sections.push(formatSessionTitle(session));
513
+ sections.push(formatSessionTags(session));
514
+ for (const todo of session.todoItems ?? []) {
515
+ sections.push(formatTodoItem(todo));
516
+ }
517
+ for (const question of session.questionHighlights ?? []) {
518
+ sections.push(question);
519
+ }
520
+ const sessionUnavailable = toSessionUnavailable(session.unavailable);
521
+ if (sessionUnavailable.length > 0) {
522
+ sections.push(`session unavailable: ${sessionUnavailable.join(", ")}`);
523
+ }
524
+ for (const highlight of sortHighlights(session.highlights)) {
525
+ if (highlight.kind !== "todo" && highlight.kind !== "question" && (highlight.kind !== "status" || session.status === "retry")) {
526
+ sections.push(highlight.text);
527
+ }
528
+ }
529
+ }
530
+ }
531
+ renderQuestions(questionsByInstance.get(instance.instanceID) ?? []);
532
+ const instanceUnavailable = toInstanceUnavailable(snapshot.unavailable);
533
+ if (instanceUnavailable.length > 0) {
534
+ sections.push(`instance unavailable: ${instanceUnavailable.join(", ")}`);
535
+ }
536
+ }
537
+ const remainingQuestions = activeQuestions
538
+ .filter((item) => !renderedQuestionHandles.has(item.handle))
539
+ .filter((item, index, array) => array.findIndex((candidate) => candidate.handle === item.handle) === index);
540
+ if (remainingQuestions.length > 0) {
541
+ sections.push("## 实例:未知实例");
542
+ sections.push("---");
543
+ for (const question of remainingQuestions) {
544
+ sections.push(...formatStatusQuestionItem(question));
545
+ }
546
+ }
547
+ return sections.join("\n");
548
+ }
549
+ export function buildAggregatedStatusInstancesFromBrokerView(view) {
550
+ if (!view) {
551
+ return [];
552
+ }
553
+ return listBrokerViewInstanceIDs(view).map((instanceID) => {
554
+ const instanceRecord = asObject(view.active.instances[instanceID]);
555
+ const connectionGroup = view.connections[instanceID];
556
+ const isOnline = Object.values(connectionGroup ?? {}).some((connection) => connection.online)
557
+ || instanceRecord.online === true;
558
+ if (!isOnline) {
559
+ return {
560
+ instanceID,
561
+ status: "timeout/unreachable",
562
+ };
563
+ }
564
+ return {
565
+ instanceID,
566
+ status: "ok",
567
+ snapshot: buildBrokerViewSnapshot(view, instanceID),
568
+ };
569
+ });
570
+ }
571
+ export function formatAggregatedStatusReplyFromBrokerView(view) {
572
+ return formatAggregatedStatusReply({
573
+ requestId: "broker-authoritative-view",
574
+ instances: buildAggregatedStatusInstancesFromBrokerView(view),
575
+ activeQuestions: listActiveQuestionTodoItems(view),
576
+ });
577
+ }
578
+ export function formatTodoReplyFromBrokerView(view) {
579
+ const questions = listActiveQuestionTodoItems(view);
580
+ const permissions = listActivePermissionTodoItems(view);
581
+ const naturalStops = listActiveNaturalStopTodoItems(view);
582
+ if (questions.length === 0 && permissions.length === 0 && naturalStops.length === 0) {
583
+ return "当前没有待回复或待处理事项";
584
+ }
585
+ const lines = ["待处理事项"];
586
+ if (questions.length > 0) {
587
+ lines.push("", "【问题】");
588
+ for (const item of questions) {
589
+ lines.push(`- QID:${item.handle}`);
590
+ lines.push(` 摘要:${item.summary}`);
591
+ lines.push(` 回复:/reply ${item.handle} 你的回复`);
592
+ }
593
+ }
594
+ if (permissions.length > 0) {
595
+ lines.push("", "【权限】");
596
+ for (const item of permissions) {
597
+ lines.push(`- PID:${item.handle}`);
598
+ lines.push(` 摘要:${item.summary}`);
599
+ lines.push(` 允许一次:/allow ${item.handle} once`);
600
+ lines.push(` 始终允许:/allow ${item.handle} always`);
601
+ lines.push(` 拒绝:/allow ${item.handle} reject`);
602
+ }
603
+ }
604
+ if (naturalStops.length > 0) {
605
+ lines.push("", "【自然结束】");
606
+ for (const item of naturalStops) {
607
+ lines.push(`- SID:${item.handle}`);
608
+ lines.push(` 摘要:${item.summary}`);
609
+ if (item.severityAdvice) {
610
+ lines.push(` 建议:${item.severityAdvice}`);
611
+ }
612
+ lines.push(` 回复:/reply ${item.handle} 继续处理`);
613
+ }
614
+ }
615
+ return lines.join("\n");
616
+ }
@@ -0,0 +1,20 @@
1
+ export type TokenSource = "question" | "permission" | "message";
2
+ export declare const NOTIFICATION_DELIVERY_FAILED_STALE_REASON = "notification-delivery-failed";
3
+ export type TokenState = {
4
+ contextToken: string;
5
+ updatedAt: number;
6
+ source: TokenSource;
7
+ sourceRef?: string;
8
+ staleReason?: string;
9
+ };
10
+ type TokenKey = {
11
+ wechatAccountId: string;
12
+ userId: string;
13
+ };
14
+ export declare function readTokenState(wechatAccountId: string, userId: string): Promise<TokenState | undefined>;
15
+ export declare function isLiveTokenState(state: TokenState | undefined): state is TokenState;
16
+ export declare function upsertInboundToken(input: TokenKey & TokenState): Promise<TokenState>;
17
+ export declare function markTokenStale(input: TokenKey & {
18
+ staleReason: string;
19
+ }): Promise<TokenState>;
20
+ export {};