botmux 2.88.1 → 2.89.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 (171) hide show
  1. package/dist/bot-registry.d.ts +2 -0
  2. package/dist/bot-registry.d.ts.map +1 -1
  3. package/dist/bot-registry.js +4 -0
  4. package/dist/bot-registry.js.map +1 -1
  5. package/dist/cli/dashboard-endpoint.d.ts.map +1 -1
  6. package/dist/cli/dashboard-endpoint.js +9 -3
  7. package/dist/cli/dashboard-endpoint.js.map +1 -1
  8. package/dist/cli.d.ts.map +1 -1
  9. package/dist/cli.js +11 -3
  10. package/dist/cli.js.map +1 -1
  11. package/dist/core/command-handler.d.ts.map +1 -1
  12. package/dist/core/command-handler.js +9 -1
  13. package/dist/core/command-handler.js.map +1 -1
  14. package/dist/core/dashboard-command/groups.d.ts +20 -0
  15. package/dist/core/dashboard-command/groups.d.ts.map +1 -0
  16. package/dist/core/dashboard-command/groups.js +52 -0
  17. package/dist/core/dashboard-command/groups.js.map +1 -0
  18. package/dist/core/dashboard-command/index.d.ts +30 -0
  19. package/dist/core/dashboard-command/index.d.ts.map +1 -0
  20. package/dist/core/dashboard-command/index.js +81 -0
  21. package/dist/core/dashboard-command/index.js.map +1 -0
  22. package/dist/core/dashboard-command/overview.d.ts +29 -0
  23. package/dist/core/dashboard-command/overview.d.ts.map +1 -0
  24. package/dist/core/dashboard-command/overview.js +61 -0
  25. package/dist/core/dashboard-command/overview.js.map +1 -0
  26. package/dist/core/dashboard-command/owner-gate.d.ts +41 -0
  27. package/dist/core/dashboard-command/owner-gate.d.ts.map +1 -0
  28. package/dist/core/dashboard-command/owner-gate.js +40 -0
  29. package/dist/core/dashboard-command/owner-gate.js.map +1 -0
  30. package/dist/core/dashboard-command/schedules.d.ts +20 -0
  31. package/dist/core/dashboard-command/schedules.d.ts.map +1 -0
  32. package/dist/core/dashboard-command/schedules.js +47 -0
  33. package/dist/core/dashboard-command/schedules.js.map +1 -0
  34. package/dist/core/dashboard-command/sessions.d.ts +22 -0
  35. package/dist/core/dashboard-command/sessions.d.ts.map +1 -0
  36. package/dist/core/dashboard-command/sessions.js +43 -0
  37. package/dist/core/dashboard-command/sessions.js.map +1 -0
  38. package/dist/core/dashboard-command/settings.d.ts +21 -0
  39. package/dist/core/dashboard-command/settings.d.ts.map +1 -0
  40. package/dist/core/dashboard-command/settings.js +55 -0
  41. package/dist/core/dashboard-command/settings.js.map +1 -0
  42. package/dist/core/dashboard-command/stub.d.ts +16 -0
  43. package/dist/core/dashboard-command/stub.d.ts.map +1 -0
  44. package/dist/core/dashboard-command/stub.js +28 -0
  45. package/dist/core/dashboard-command/stub.js.map +1 -0
  46. package/dist/core/dashboard-command/workflows.d.ts +20 -0
  47. package/dist/core/dashboard-command/workflows.d.ts.map +1 -0
  48. package/dist/core/dashboard-command/workflows.js +45 -0
  49. package/dist/core/dashboard-command/workflows.js.map +1 -0
  50. package/dist/core/passthrough-commands.d.ts.map +1 -1
  51. package/dist/core/passthrough-commands.js +1 -1
  52. package/dist/core/passthrough-commands.js.map +1 -1
  53. package/dist/daemon-internal-client-wrapper.d.ts +41 -0
  54. package/dist/daemon-internal-client-wrapper.d.ts.map +1 -0
  55. package/dist/daemon-internal-client-wrapper.js +65 -0
  56. package/dist/daemon-internal-client-wrapper.js.map +1 -0
  57. package/dist/dashboard/aggregator.d.ts +12 -0
  58. package/dist/dashboard/aggregator.d.ts.map +1 -1
  59. package/dist/dashboard/aggregator.js +16 -0
  60. package/dist/dashboard/aggregator.js.map +1 -1
  61. package/dist/dashboard/auth.d.ts +7 -0
  62. package/dist/dashboard/auth.d.ts.map +1 -1
  63. package/dist/dashboard/auth.js +21 -0
  64. package/dist/dashboard/auth.js.map +1 -1
  65. package/dist/dashboard/card-model-types.d.ts +50 -0
  66. package/dist/dashboard/card-model-types.d.ts.map +1 -0
  67. package/dist/dashboard/card-model-types.js +9 -0
  68. package/dist/dashboard/card-model-types.js.map +1 -0
  69. package/dist/dashboard/daemon-internal-api.d.ts +90 -0
  70. package/dist/dashboard/daemon-internal-api.d.ts.map +1 -0
  71. package/dist/dashboard/daemon-internal-api.js +534 -0
  72. package/dist/dashboard/daemon-internal-api.js.map +1 -0
  73. package/dist/dashboard/daemon-internal-auth.d.ts +124 -0
  74. package/dist/dashboard/daemon-internal-auth.d.ts.map +1 -0
  75. package/dist/dashboard/daemon-internal-auth.js +188 -0
  76. package/dist/dashboard/daemon-internal-auth.js.map +1 -0
  77. package/dist/dashboard/daemon-internal-client.d.ts +67 -0
  78. package/dist/dashboard/daemon-internal-client.d.ts.map +1 -0
  79. package/dist/dashboard/daemon-internal-client.js +157 -0
  80. package/dist/dashboard/daemon-internal-client.js.map +1 -0
  81. package/dist/dashboard/dashboard-admins.d.ts +8 -0
  82. package/dist/dashboard/dashboard-admins.d.ts.map +1 -0
  83. package/dist/dashboard/dashboard-admins.js +15 -0
  84. package/dist/dashboard/dashboard-admins.js.map +1 -0
  85. package/dist/dashboard/federation-spoke-api.d.ts +3 -0
  86. package/dist/dashboard/federation-spoke-api.d.ts.map +1 -1
  87. package/dist/dashboard/federation-spoke-api.js +11 -8
  88. package/dist/dashboard/federation-spoke-api.js.map +1 -1
  89. package/dist/dashboard/groups-action-helpers.d.ts +69 -0
  90. package/dist/dashboard/groups-action-helpers.d.ts.map +1 -0
  91. package/dist/dashboard/groups-action-helpers.js +141 -0
  92. package/dist/dashboard/groups-action-helpers.js.map +1 -0
  93. package/dist/dashboard/groups-card-model.d.ts +120 -0
  94. package/dist/dashboard/groups-card-model.d.ts.map +1 -0
  95. package/dist/dashboard/groups-card-model.js +175 -0
  96. package/dist/dashboard/groups-card-model.js.map +1 -0
  97. package/dist/dashboard/schedule-card-model.d.ts +172 -0
  98. package/dist/dashboard/schedule-card-model.d.ts.map +1 -0
  99. package/dist/dashboard/schedule-card-model.js +290 -0
  100. package/dist/dashboard/schedule-card-model.js.map +1 -0
  101. package/dist/dashboard/session-card-model.d.ts +83 -0
  102. package/dist/dashboard/session-card-model.d.ts.map +1 -0
  103. package/dist/dashboard/session-card-model.js +196 -0
  104. package/dist/dashboard/session-card-model.js.map +1 -0
  105. package/dist/dashboard/settings-card-model.d.ts +73 -0
  106. package/dist/dashboard/settings-card-model.d.ts.map +1 -0
  107. package/dist/dashboard/settings-card-model.js +94 -0
  108. package/dist/dashboard/settings-card-model.js.map +1 -0
  109. package/dist/dashboard/settings-owner-resolver.d.ts +32 -0
  110. package/dist/dashboard/settings-owner-resolver.d.ts.map +1 -0
  111. package/dist/dashboard/settings-owner-resolver.js +43 -0
  112. package/dist/dashboard/settings-owner-resolver.js.map +1 -0
  113. package/dist/dashboard/settings-write-applier.d.ts +84 -0
  114. package/dist/dashboard/settings-write-applier.d.ts.map +1 -0
  115. package/dist/dashboard/settings-write-applier.js +119 -0
  116. package/dist/dashboard/settings-write-applier.js.map +1 -0
  117. package/dist/dashboard/workflow-api.d.ts.map +1 -1
  118. package/dist/dashboard/workflow-api.js +38 -119
  119. package/dist/dashboard/workflow-api.js.map +1 -1
  120. package/dist/dashboard/workflow-card-model.d.ts +127 -0
  121. package/dist/dashboard/workflow-card-model.d.ts.map +1 -0
  122. package/dist/dashboard/workflow-card-model.js +186 -0
  123. package/dist/dashboard/workflow-card-model.js.map +1 -0
  124. package/dist/dashboard/workflows-action-helpers.d.ts +105 -0
  125. package/dist/dashboard/workflows-action-helpers.d.ts.map +1 -0
  126. package/dist/dashboard/workflows-action-helpers.js +196 -0
  127. package/dist/dashboard/workflows-action-helpers.js.map +1 -0
  128. package/dist/dashboard.js +196 -269
  129. package/dist/dashboard.js.map +1 -1
  130. package/dist/i18n/en.d.ts.map +1 -1
  131. package/dist/i18n/en.js +266 -0
  132. package/dist/i18n/en.js.map +1 -1
  133. package/dist/i18n/zh.d.ts.map +1 -1
  134. package/dist/i18n/zh.js +266 -0
  135. package/dist/i18n/zh.js.map +1 -1
  136. package/dist/im/lark/card-builder.d.ts +2 -0
  137. package/dist/im/lark/card-builder.d.ts.map +1 -1
  138. package/dist/im/lark/card-builder.js +2 -1
  139. package/dist/im/lark/card-builder.js.map +1 -1
  140. package/dist/im/lark/card-handler.d.ts +54 -2
  141. package/dist/im/lark/card-handler.d.ts.map +1 -1
  142. package/dist/im/lark/card-handler.js +127 -1
  143. package/dist/im/lark/card-handler.js.map +1 -1
  144. package/dist/im/lark/event-dispatcher.d.ts.map +1 -1
  145. package/dist/im/lark/event-dispatcher.js +22 -0
  146. package/dist/im/lark/event-dispatcher.js.map +1 -1
  147. package/dist/im/lark/groups-card.d.ts +90 -0
  148. package/dist/im/lark/groups-card.d.ts.map +1 -0
  149. package/dist/im/lark/groups-card.js +832 -0
  150. package/dist/im/lark/groups-card.js.map +1 -0
  151. package/dist/im/lark/overview-card.d.ts +101 -0
  152. package/dist/im/lark/overview-card.d.ts.map +1 -0
  153. package/dist/im/lark/overview-card.js +475 -0
  154. package/dist/im/lark/overview-card.js.map +1 -0
  155. package/dist/im/lark/schedules-card.d.ts +96 -0
  156. package/dist/im/lark/schedules-card.d.ts.map +1 -0
  157. package/dist/im/lark/schedules-card.js +892 -0
  158. package/dist/im/lark/schedules-card.js.map +1 -0
  159. package/dist/im/lark/sessions-card.d.ts +117 -0
  160. package/dist/im/lark/sessions-card.d.ts.map +1 -0
  161. package/dist/im/lark/sessions-card.js +870 -0
  162. package/dist/im/lark/sessions-card.js.map +1 -0
  163. package/dist/im/lark/settings-card.d.ts +125 -0
  164. package/dist/im/lark/settings-card.d.ts.map +1 -0
  165. package/dist/im/lark/settings-card.js +433 -0
  166. package/dist/im/lark/settings-card.js.map +1 -0
  167. package/dist/im/lark/workflows-card.d.ts +102 -0
  168. package/dist/im/lark/workflows-card.d.ts.map +1 -0
  169. package/dist/im/lark/workflows-card.js +737 -0
  170. package/dist/im/lark/workflows-card.js.map +1 -0
  171. package/package.json +1 -1
@@ -0,0 +1,175 @@
1
+ /**
2
+ * Groups card model (PR1) — pure projection of the dashboard groups matrix
3
+ * (`/api/groups` fan-out, see `dashboard.ts:595-650`) into list-card and
4
+ * detail-card DTOs.
5
+ *
6
+ * Zero IO. Self-contained input shape; no imports from `web/`,
7
+ * `dashboard.ts`, or the registry.
8
+ */
9
+ const DEFAULT_PAGE_SIZE = 20;
10
+ const MAX_PAGE_SIZE = 100;
11
+ const DEFAULT_SUFFIX_LEN = 4;
12
+ /** Return the last `len` characters of a chatId (whole string when too short). */
13
+ export function chatIdSuffix(chatId, len = DEFAULT_SUFFIX_LEN) {
14
+ if (!chatId || chatId.length <= len)
15
+ return chatId ?? '';
16
+ return chatId.slice(-len);
17
+ }
18
+ function coverageStatusOf(member) {
19
+ if (!member)
20
+ return 'unknown';
21
+ if (member.status === 'error')
22
+ return 'error';
23
+ if (member.status === 'unknown')
24
+ return 'unknown';
25
+ if (member.inChat === true)
26
+ return 'in';
27
+ if (member.inChat === false)
28
+ return 'out';
29
+ return 'unknown';
30
+ }
31
+ /**
32
+ * Filter groups by query / missingOnly. missingOnly keeps a chat when
33
+ * at least one configured bot is NOT effectively covered — covers all four
34
+ * gap types: inChat=false / member row absent / status='unknown' / status='error'.
35
+ *
36
+ * When the optional `bots` universe is provided, `missingOnly` also detects
37
+ * chats whose memberBots list omits a configured bot entirely. Without `bots`,
38
+ * it falls back to the three gap types observable from memberBots alone.
39
+ */
40
+ export function filterGroups(chats, filter, bots) {
41
+ let out = chats.slice();
42
+ const q = filter.query?.trim().toLowerCase();
43
+ if (q && q.length > 0) {
44
+ out = out.filter(c => (c.name ?? '').toLowerCase().includes(q) ||
45
+ c.chatId.toLowerCase().includes(q) ||
46
+ (c.ownerId ?? '').toLowerCase().includes(q));
47
+ }
48
+ if (filter.missingOnly === true) {
49
+ out = out.filter(c => {
50
+ if (c.memberBots.some(mb => coverageStatusOf(mb) !== 'in'))
51
+ return true;
52
+ if (bots && bots.length > 0) {
53
+ const present = new Set(c.memberBots.map(mb => mb.larkAppId));
54
+ return bots.some(b => !present.has(b.larkAppId));
55
+ }
56
+ return false;
57
+ });
58
+ }
59
+ return out;
60
+ }
61
+ function clampPageSize(pageSize) {
62
+ if (typeof pageSize !== 'number' || !Number.isFinite(pageSize) || pageSize < 1)
63
+ return DEFAULT_PAGE_SIZE;
64
+ if (pageSize > MAX_PAGE_SIZE)
65
+ return MAX_PAGE_SIZE;
66
+ return Math.floor(pageSize);
67
+ }
68
+ /** Slice a filtered group list into one page. Clamp rules apply. */
69
+ export function paginateGroups(chats, page, pageSize) {
70
+ const total = chats.length;
71
+ const size = clampPageSize(pageSize);
72
+ const totalPages = Math.max(1, Math.ceil(total / size));
73
+ let active = typeof page === 'number' && Number.isFinite(page) ? Math.floor(page) : 1;
74
+ if (active < 1)
75
+ active = 1;
76
+ if (active > totalPages)
77
+ active = totalPages;
78
+ const start = (active - 1) * size;
79
+ return {
80
+ total,
81
+ page: active,
82
+ pageSize: size,
83
+ pageItems: chats.slice(start, start + size),
84
+ };
85
+ }
86
+ /** Build one matrix row DTO. Coverage column order follows the `bots` argument. */
87
+ export function buildGroupRow(chat, bots) {
88
+ const memberMap = new Map();
89
+ for (const mb of chat.memberBots)
90
+ memberMap.set(mb.larkAppId, mb);
91
+ const coverage = bots.map(b => ({
92
+ larkAppId: b.larkAppId,
93
+ botName: b.botName,
94
+ status: coverageStatusOf(memberMap.get(b.larkAppId)),
95
+ }));
96
+ const missingCount = coverage.reduce((n, c) => (c.status === 'in' ? n : n + 1), 0);
97
+ return {
98
+ chatId: chat.chatId,
99
+ chatIdSuffix: chatIdSuffix(chat.chatId),
100
+ name: chat.name ?? chat.chatId,
101
+ ownerId: chat.ownerId,
102
+ coverage,
103
+ missingCount,
104
+ totalBots: bots.length,
105
+ };
106
+ }
107
+ /** Pipeline: filter → paginate → row-mapping. */
108
+ export function buildGroupRows(chats, bots, filter, page, pageSize) {
109
+ const filtered = filterGroups(chats, filter, bots);
110
+ const paged = paginateGroups(filtered, page, pageSize);
111
+ const totalPages = Math.max(1, Math.ceil(paged.total / paged.pageSize));
112
+ return {
113
+ rows: paged.pageItems.map(c => buildGroupRow(c, bots)),
114
+ meta: {
115
+ total: paged.total,
116
+ page: paged.page,
117
+ pageSize: paged.pageSize,
118
+ totalPages,
119
+ },
120
+ };
121
+ }
122
+ /** Build the detail-card DTO including per-bot bind/unbind action availability. */
123
+ export function buildGroupDetail(chat, bots) {
124
+ const memberMap = new Map();
125
+ for (const mb of chat.memberBots)
126
+ memberMap.set(mb.larkAppId, mb);
127
+ const members = bots.map(b => {
128
+ const member = memberMap.get(b.larkAppId);
129
+ const status = coverageStatusOf(member);
130
+ const inChat = status === 'in';
131
+ const oncallRaw = member?.oncallChat;
132
+ // Bound is decided by object presence, not workingDir truthiness — matches
133
+ // `web/groups.ts:440-451` where `m.oncallChat?.workingDir` may be '' yet the
134
+ // bind row still renders as bound.
135
+ const oncallChat = oncallRaw && typeof oncallRaw === 'object'
136
+ ? { chatId: oncallRaw.chatId, workingDir: oncallRaw.workingDir }
137
+ : null;
138
+ const oncallWorkingDir = oncallChat ? oncallChat.workingDir : null;
139
+ const isBound = oncallChat !== null;
140
+ const isOwnerBot = chat.ownerId !== undefined && chat.ownerId === b.larkAppId;
141
+ let bind;
142
+ if (!inChat)
143
+ bind = { enabled: false, reasonKey: 'groups.action.bind.notInChat' };
144
+ else if (isBound)
145
+ bind = { enabled: false, reasonKey: 'groups.action.bind.alreadyBound' };
146
+ else
147
+ bind = { enabled: true };
148
+ let unbind;
149
+ if (!inChat)
150
+ unbind = { enabled: false, reasonKey: 'groups.action.unbind.notInChat' };
151
+ else if (!isBound)
152
+ unbind = { enabled: false, reasonKey: 'groups.action.unbind.notBound' };
153
+ else
154
+ unbind = { enabled: true };
155
+ return {
156
+ larkAppId: b.larkAppId,
157
+ botName: b.botName,
158
+ status,
159
+ isOwnerBot,
160
+ oncallChat,
161
+ oncallWorkingDir,
162
+ hasRole: member?.hasRole === true,
163
+ bind,
164
+ unbind,
165
+ };
166
+ });
167
+ return {
168
+ chatId: chat.chatId,
169
+ chatIdSuffix: chatIdSuffix(chat.chatId),
170
+ name: chat.name ?? chat.chatId,
171
+ ownerId: chat.ownerId,
172
+ members,
173
+ };
174
+ }
175
+ //# sourceMappingURL=groups-card-model.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"groups-card-model.js","sourceRoot":"","sources":["../../src/dashboard/groups-card-model.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,172 @@
1
+ /**
2
+ * Schedule card model (PR1) — pure projection of `ScheduleCardTaskInput` (a
3
+ * minimal task shape mirroring fields from `ScheduledTask`, redeclared here
4
+ * so the model does NOT depend on `ScheduledTask` / `scheduler.ts` /
5
+ * `dashboard-ipc-server.ts` runtime modules) into list-card and detail-card
6
+ * DTOs.
7
+ *
8
+ * Allowed imports:
9
+ * - `import type { ParsedSchedule } from '../types.js'` (type-only, erasable)
10
+ * - `import { Cron } from 'croner'` (third-party, already a runtime dep —
11
+ * used purely for time math on cron expressions; no IO is invoked)
12
+ * - `import type { ... } from './card-model-types.js'`
13
+ *
14
+ * Forbidden: scheduler.ts (ONESHOT_GRACE, tick), schedule-store, dashboard-*.
15
+ */
16
+ import type { ParsedSchedule } from '../types.js';
17
+ import type { ButtonState, PaginationMeta, PaginationParams, StatusDot } from './card-model-types.js';
18
+ /** Minimal task input — self-contained so PR1 model does not pull in `ScheduledTask`. */
19
+ export interface ScheduleCardTaskInput {
20
+ id: string;
21
+ name: string;
22
+ prompt?: string;
23
+ parsed: ParsedSchedule;
24
+ enabled: boolean;
25
+ deliver?: ScheduleDelivery;
26
+ larkAppId?: string;
27
+ /** Human bot label (e.g. `zkd-claude-bot`). Optional — when present the
28
+ * global-scope card row prefixes the row with it so the user can tell
29
+ * which bot owns the schedule. Falls back to a `larkAppId` short suffix
30
+ * when missing. */
31
+ botName?: string;
32
+ chatId?: string;
33
+ /** ISO of the next scheduled run (precomputed by caller). */
34
+ nextRunAt?: string;
35
+ /** ISO of the last completed run. */
36
+ lastRunAt?: string;
37
+ lastStatus?: 'ok' | 'error';
38
+ /** Repeat counter shape mirrors `ScheduledTask.repeat` (`src/types.ts:212`):
39
+ * `times === null` ⇒ forever; finite `times` ⇒ auto-removes after N runs.
40
+ * `completed` counts how many runs have fired. */
41
+ repeat?: {
42
+ times: number | null;
43
+ completed: number;
44
+ };
45
+ }
46
+ export type ScheduleKind = ParsedSchedule['kind'];
47
+ export type ScheduleDelivery = 'origin' | 'local' | 'new-topic';
48
+ export type ScheduleKindChip = ScheduleKind | 'all';
49
+ export interface ScheduleFilterQuery extends PaginationParams {
50
+ /** Case-insensitive substring match against name + prompt. */
51
+ search?: string;
52
+ kind?: ScheduleKindChip;
53
+ enabledOnly?: boolean;
54
+ }
55
+ export interface RowRenderContext {
56
+ /** Epoch ms — required so relative-time outputs are deterministic. */
57
+ nowMs: number;
58
+ /** IANA timezone for cron next-run math; defaults to 'Asia/Shanghai'. */
59
+ timezone?: string;
60
+ /** Cap prompt length in detail DTO; defaults to 200. */
61
+ promptTruncateAt?: number;
62
+ /** How many next-runs to precompute for detail. Defaults to 5. */
63
+ nextRunsCount?: number;
64
+ }
65
+ /** Per-button availability for the 3 schedule actions. */
66
+ export interface ScheduleActionMatrix {
67
+ runNow: ButtonState;
68
+ pause: ButtonState;
69
+ resume: ButtonState;
70
+ }
71
+ export interface ScheduleRowDto {
72
+ id: string;
73
+ name: string;
74
+ /** parsed.display passthrough. */
75
+ displayExpr: string;
76
+ kind: ScheduleKind;
77
+ enabled: boolean;
78
+ /** Human relative form: 'in Xm' / 'overdue' / '—'. */
79
+ nextRunRelative: string;
80
+ /** Human relative form: 'Xm ago' / '—'. */
81
+ lastRunRelative: string;
82
+ /** Semantic flag — renderer decides whether to draw a glyph. */
83
+ errorIndicator: boolean;
84
+ /** Semantic dot, useful for list-row decoration. */
85
+ dot: StatusDot;
86
+ /** Passthrough of `ScheduledTask.repeat` so the row can render `n/N` or `n/∞`. */
87
+ repeat?: {
88
+ times: number | null;
89
+ completed: number;
90
+ };
91
+ actions: ScheduleActionMatrix;
92
+ raw: ScheduleCardTaskInput;
93
+ }
94
+ export interface ScheduleDetailDto {
95
+ id: string;
96
+ name: string;
97
+ enabled: boolean;
98
+ kind: ScheduleKind;
99
+ displayExpr: string;
100
+ deliver: ScheduleDelivery;
101
+ prompt?: string;
102
+ /** True when prompt was longer than promptTruncateAt and got cut. */
103
+ promptTruncated: boolean;
104
+ chatId?: string;
105
+ larkAppId?: string;
106
+ /** Precomputed list of N upcoming runs (ISO strings, strictly increasing). */
107
+ nextRuns: string[];
108
+ nextRunAt?: string;
109
+ lastRunAt?: string;
110
+ lastStatus?: 'ok' | 'error';
111
+ errorIndicator: boolean;
112
+ /** Passthrough of `ScheduledTask.repeat`. */
113
+ repeat?: {
114
+ times: number | null;
115
+ completed: number;
116
+ };
117
+ actions: ScheduleActionMatrix;
118
+ raw: ScheduleCardTaskInput;
119
+ }
120
+ export interface KindCounts {
121
+ all: number;
122
+ once: number;
123
+ interval: number;
124
+ cron: number;
125
+ }
126
+ export interface ScheduleListPage {
127
+ rows: ScheduleRowDto[];
128
+ meta: PaginationMeta;
129
+ kindCounts: KindCounts;
130
+ }
131
+ /** Compute UI availability for the 3 schedule action buttons (pure). */
132
+ export declare function computeButtonAvailability(task: ScheduleCardTaskInput): ScheduleActionMatrix;
133
+ export declare function normalizeScheduleDelivery(deliver: ScheduleCardTaskInput['deliver']): ScheduleDelivery;
134
+ export declare function computeDeliveryButtonAvailability(task: ScheduleCardTaskInput, target: Exclude<ScheduleDelivery, 'local'>): ButtonState;
135
+ /** Build a single ScheduleRowDto for list rendering. */
136
+ export declare function toScheduleRowDto(task: ScheduleCardTaskInput, ctx: RowRenderContext): ScheduleRowDto;
137
+ /** Build the detail-card DTO including precomputed next-N runs and prompt truncation. */
138
+ export declare function toScheduleDetailDto(task: ScheduleCardTaskInput, ctx: RowRenderContext): ScheduleDetailDto;
139
+ /**
140
+ * Compute the next N runs of a task, as ISO strings. Strictly increasing.
141
+ *
142
+ * - `once`: one entry if `runAt >= nowMs` AND `lastRunAt` is absent; else [].
143
+ * - `interval`: minutes>0 required; base = lastRunAt ?? nowMs; aligns to first
144
+ * future run > nowMs.
145
+ * - `cron`: uses `croner` with `Asia/Shanghai` (or override) timezone.
146
+ *
147
+ * Pure: nowMs + timezone injected; no implicit clock reads.
148
+ */
149
+ export declare function computeNextNRuns(task: ScheduleCardTaskInput, n: number, ctx: {
150
+ nowMs: number;
151
+ timezone?: string;
152
+ }): string[];
153
+ /** Tally kinds across the (unfiltered) task pool — used for the kind chip badges. */
154
+ export declare function kindCounts(tasks: ReadonlyArray<ScheduleCardTaskInput>): KindCounts;
155
+ /**
156
+ * Filter tasks by search/kind/enabledOnly. Returns a new array (no mutation).
157
+ * - `search`: case-insensitive substring across `name` and `prompt`.
158
+ * - `kind`: exact match on `parsed.kind`; 'all' or undefined is no-op.
159
+ * - `enabledOnly`: when true, drops tasks with `enabled !== true`.
160
+ */
161
+ export declare function filterSchedules(tasks: ReadonlyArray<ScheduleCardTaskInput>, query: Pick<ScheduleFilterQuery, 'search' | 'kind' | 'enabledOnly'>): ScheduleCardTaskInput[];
162
+ /** Slice an already-filtered list into a single page. Clamp rules apply. */
163
+ export declare function paginateSchedules<T>(items: ReadonlyArray<T>, page: number | undefined, pageSize: number | undefined): {
164
+ items: T[];
165
+ page: number;
166
+ pageSize: number;
167
+ total: number;
168
+ totalPages: number;
169
+ };
170
+ /** End-to-end pipeline: filter → toRow → paginate → counts. Used by the dashboard endpoint. */
171
+ export declare function filterAndPaginateSchedules(tasks: ReadonlyArray<ScheduleCardTaskInput>, query: ScheduleFilterQuery, ctx: RowRenderContext): ScheduleListPage;
172
+ //# sourceMappingURL=schedule-card-model.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schedule-card-model.d.ts","sourceRoot":"","sources":["../../src/dashboard/schedule-card-model.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,290 @@
1
+ /**
2
+ * Schedule card model (PR1) — pure projection of `ScheduleCardTaskInput` (a
3
+ * minimal task shape mirroring fields from `ScheduledTask`, redeclared here
4
+ * so the model does NOT depend on `ScheduledTask` / `scheduler.ts` /
5
+ * `dashboard-ipc-server.ts` runtime modules) into list-card and detail-card
6
+ * DTOs.
7
+ *
8
+ * Allowed imports:
9
+ * - `import type { ParsedSchedule } from '../types.js'` (type-only, erasable)
10
+ * - `import { Cron } from 'croner'` (third-party, already a runtime dep —
11
+ * used purely for time math on cron expressions; no IO is invoked)
12
+ * - `import type { ... } from './card-model-types.js'`
13
+ *
14
+ * Forbidden: scheduler.ts (ONESHOT_GRACE, tick), schedule-store, dashboard-*.
15
+ */
16
+ import { Cron } from 'croner';
17
+ const DEFAULT_TIMEZONE = 'Asia/Shanghai';
18
+ const DEFAULT_PAGE_SIZE = 20;
19
+ const MAX_PAGE_SIZE = 100;
20
+ const DEFAULT_PROMPT_TRUNCATE = 200;
21
+ const DEFAULT_NEXT_RUNS_COUNT = 5;
22
+ function statusDotFor(task) {
23
+ if (!task.enabled) {
24
+ return { tone: 'neutral', pulse: false, label: 'schedules.status.paused' };
25
+ }
26
+ if (task.lastStatus === 'error') {
27
+ return { tone: 'danger', pulse: false, label: 'schedules.status.lastError' };
28
+ }
29
+ return { tone: 'success', pulse: false, label: 'schedules.status.active' };
30
+ }
31
+ function formatNextRun(nextRunAt, nowMs) {
32
+ if (!nextRunAt)
33
+ return '—';
34
+ const ms = Date.parse(nextRunAt);
35
+ if (!Number.isFinite(ms))
36
+ return '—';
37
+ const diff = ms - nowMs;
38
+ if (diff < 0)
39
+ return 'overdue';
40
+ const min = Math.floor(diff / 60_000);
41
+ if (min < 1)
42
+ return 'in <1m';
43
+ if (min < 60)
44
+ return `in ${min}m`;
45
+ const hr = Math.floor(min / 60);
46
+ if (hr < 24)
47
+ return `in ${hr}h`;
48
+ return `in ${Math.floor(hr / 24)}d`;
49
+ }
50
+ function formatLastRun(lastRunAt, nowMs) {
51
+ if (!lastRunAt)
52
+ return '—';
53
+ const ms = Date.parse(lastRunAt);
54
+ if (!Number.isFinite(ms))
55
+ return '—';
56
+ const diff = nowMs - ms;
57
+ if (diff < 0)
58
+ return '—';
59
+ const min = Math.floor(diff / 60_000);
60
+ if (min < 1)
61
+ return '<1m ago';
62
+ if (min < 60)
63
+ return `${min}m ago`;
64
+ const hr = Math.floor(min / 60);
65
+ if (hr < 24)
66
+ return `${hr}h ago`;
67
+ return `${Math.floor(hr / 24)}d ago`;
68
+ }
69
+ /** Compute UI availability for the 3 schedule action buttons (pure). */
70
+ export function computeButtonAvailability(task) {
71
+ if (task.enabled) {
72
+ return {
73
+ runNow: { enabled: true },
74
+ pause: { enabled: true },
75
+ resume: { enabled: false, reasonKey: 'schedules.action.resume.alreadyEnabled' },
76
+ };
77
+ }
78
+ return {
79
+ runNow: { enabled: true },
80
+ pause: { enabled: false, reasonKey: 'schedules.action.pause.alreadyPaused' },
81
+ resume: { enabled: true },
82
+ };
83
+ }
84
+ export function normalizeScheduleDelivery(deliver) {
85
+ return deliver === 'new-topic' || deliver === 'local' ? deliver : 'origin';
86
+ }
87
+ export function computeDeliveryButtonAvailability(task, target) {
88
+ const current = normalizeScheduleDelivery(task.deliver);
89
+ if (current === 'local') {
90
+ return { enabled: false, reasonKey: 'schedules.action.delivery.local' };
91
+ }
92
+ if (current === target) {
93
+ return {
94
+ enabled: false,
95
+ reasonKey: target === 'origin'
96
+ ? 'schedules.action.delivery.alreadyOrigin'
97
+ : 'schedules.action.delivery.alreadyNewTopic',
98
+ };
99
+ }
100
+ return { enabled: true };
101
+ }
102
+ /** Build a single ScheduleRowDto for list rendering. */
103
+ export function toScheduleRowDto(task, ctx) {
104
+ return {
105
+ id: task.id,
106
+ name: task.name,
107
+ displayExpr: task.parsed.display,
108
+ kind: task.parsed.kind,
109
+ enabled: task.enabled,
110
+ nextRunRelative: formatNextRun(task.nextRunAt, ctx.nowMs),
111
+ lastRunRelative: formatLastRun(task.lastRunAt, ctx.nowMs),
112
+ errorIndicator: task.lastStatus === 'error',
113
+ dot: statusDotFor(task),
114
+ repeat: task.repeat,
115
+ actions: computeButtonAvailability(task),
116
+ raw: task,
117
+ };
118
+ }
119
+ /** Build the detail-card DTO including precomputed next-N runs and prompt truncation. */
120
+ export function toScheduleDetailDto(task, ctx) {
121
+ const truncateAt = ctx.promptTruncateAt ?? DEFAULT_PROMPT_TRUNCATE;
122
+ const count = ctx.nextRunsCount ?? DEFAULT_NEXT_RUNS_COUNT;
123
+ const promptRaw = task.prompt ?? '';
124
+ const promptTruncated = promptRaw.length > truncateAt;
125
+ const prompt = promptTruncated ? `${promptRaw.slice(0, truncateAt)}…` : promptRaw;
126
+ return {
127
+ id: task.id,
128
+ name: task.name,
129
+ enabled: task.enabled,
130
+ kind: task.parsed.kind,
131
+ displayExpr: task.parsed.display,
132
+ deliver: normalizeScheduleDelivery(task.deliver),
133
+ prompt: promptRaw.length === 0 ? undefined : prompt,
134
+ promptTruncated,
135
+ chatId: task.chatId,
136
+ larkAppId: task.larkAppId,
137
+ nextRuns: computeNextNRuns(task, count, { nowMs: ctx.nowMs, timezone: ctx.timezone }),
138
+ nextRunAt: task.nextRunAt,
139
+ lastRunAt: task.lastRunAt,
140
+ lastStatus: task.lastStatus,
141
+ errorIndicator: task.lastStatus === 'error',
142
+ repeat: task.repeat,
143
+ actions: computeButtonAvailability(task),
144
+ raw: task,
145
+ };
146
+ }
147
+ /**
148
+ * Compute the next N runs of a task, as ISO strings. Strictly increasing.
149
+ *
150
+ * - `once`: one entry if `runAt >= nowMs` AND `lastRunAt` is absent; else [].
151
+ * - `interval`: minutes>0 required; base = lastRunAt ?? nowMs; aligns to first
152
+ * future run > nowMs.
153
+ * - `cron`: uses `croner` with `Asia/Shanghai` (or override) timezone.
154
+ *
155
+ * Pure: nowMs + timezone injected; no implicit clock reads.
156
+ */
157
+ export function computeNextNRuns(task, n, ctx) {
158
+ if (n <= 0)
159
+ return [];
160
+ const tz = ctx.timezone ?? DEFAULT_TIMEZONE;
161
+ const { parsed } = task;
162
+ if (parsed.kind === 'once') {
163
+ if (task.lastRunAt)
164
+ return [];
165
+ if (!parsed.runAt)
166
+ return [];
167
+ const runAtMs = Date.parse(parsed.runAt);
168
+ if (!Number.isFinite(runAtMs) || runAtMs < ctx.nowMs)
169
+ return [];
170
+ return [new Date(runAtMs).toISOString()];
171
+ }
172
+ if (parsed.kind === 'interval') {
173
+ const minutes = parsed.minutes ?? 0;
174
+ if (minutes <= 0)
175
+ return [];
176
+ const stepMs = minutes * 60_000;
177
+ const lastMs = task.lastRunAt ? Date.parse(task.lastRunAt) : NaN;
178
+ let next = Number.isFinite(lastMs) ? lastMs + stepMs : ctx.nowMs + stepMs;
179
+ if (next <= ctx.nowMs) {
180
+ const skipped = Math.floor((ctx.nowMs - next) / stepMs) + 1;
181
+ next += skipped * stepMs;
182
+ }
183
+ const out = [];
184
+ for (let i = 0; i < n; i++) {
185
+ out.push(new Date(next).toISOString());
186
+ next += stepMs;
187
+ }
188
+ return out;
189
+ }
190
+ if (parsed.kind === 'cron') {
191
+ if (!parsed.expr)
192
+ return [];
193
+ try {
194
+ const cron = new Cron(parsed.expr, { timezone: tz });
195
+ const out = [];
196
+ let prev = new Date(ctx.nowMs);
197
+ for (let i = 0; i < n; i++) {
198
+ const next = cron.nextRun(prev);
199
+ if (!next)
200
+ break;
201
+ out.push(next.toISOString());
202
+ prev = next;
203
+ }
204
+ return out;
205
+ }
206
+ catch {
207
+ return [];
208
+ }
209
+ }
210
+ return [];
211
+ }
212
+ /** Tally kinds across the (unfiltered) task pool — used for the kind chip badges. */
213
+ export function kindCounts(tasks) {
214
+ const counts = { all: tasks.length, once: 0, interval: 0, cron: 0 };
215
+ for (const t of tasks) {
216
+ if (t.parsed.kind === 'once')
217
+ counts.once += 1;
218
+ else if (t.parsed.kind === 'interval')
219
+ counts.interval += 1;
220
+ else if (t.parsed.kind === 'cron')
221
+ counts.cron += 1;
222
+ }
223
+ return counts;
224
+ }
225
+ /**
226
+ * Filter tasks by search/kind/enabledOnly. Returns a new array (no mutation).
227
+ * - `search`: case-insensitive substring across `name` and `prompt`.
228
+ * - `kind`: exact match on `parsed.kind`; 'all' or undefined is no-op.
229
+ * - `enabledOnly`: when true, drops tasks with `enabled !== true`.
230
+ */
231
+ export function filterSchedules(tasks, query) {
232
+ let out = tasks.slice();
233
+ if (query.enabledOnly === true) {
234
+ out = out.filter(t => t.enabled === true);
235
+ }
236
+ if (typeof query.search === 'string' && query.search.trim().length > 0) {
237
+ const needle = query.search.trim().toLowerCase();
238
+ out = out.filter(t => {
239
+ const hay = `${t.name} ${t.prompt ?? ''}`.toLowerCase();
240
+ return hay.includes(needle);
241
+ });
242
+ }
243
+ if (query.kind && query.kind !== 'all') {
244
+ out = out.filter(t => t.parsed.kind === query.kind);
245
+ }
246
+ return out;
247
+ }
248
+ function clampPageSize(pageSize) {
249
+ if (typeof pageSize !== 'number' || !Number.isFinite(pageSize) || pageSize < 1)
250
+ return DEFAULT_PAGE_SIZE;
251
+ if (pageSize > MAX_PAGE_SIZE)
252
+ return MAX_PAGE_SIZE;
253
+ return Math.floor(pageSize);
254
+ }
255
+ /** Slice an already-filtered list into a single page. Clamp rules apply. */
256
+ export function paginateSchedules(items, page, pageSize) {
257
+ const total = items.length;
258
+ const size = clampPageSize(pageSize);
259
+ const totalPages = Math.max(1, Math.ceil(total / size));
260
+ let active = typeof page === 'number' && Number.isFinite(page) ? Math.floor(page) : 1;
261
+ if (active < 1)
262
+ active = 1;
263
+ if (active > totalPages)
264
+ active = totalPages;
265
+ const start = (active - 1) * size;
266
+ return {
267
+ items: items.slice(start, start + size),
268
+ page: active,
269
+ pageSize: size,
270
+ total,
271
+ totalPages,
272
+ };
273
+ }
274
+ /** End-to-end pipeline: filter → toRow → paginate → counts. Used by the dashboard endpoint. */
275
+ export function filterAndPaginateSchedules(tasks, query, ctx) {
276
+ const filtered = filterSchedules(tasks, query);
277
+ const counts = kindCounts(tasks);
278
+ const paged = paginateSchedules(filtered, query.page, query.pageSize);
279
+ return {
280
+ rows: paged.items.map(t => toScheduleRowDto(t, ctx)),
281
+ meta: {
282
+ page: paged.page,
283
+ pageSize: paged.pageSize,
284
+ total: paged.total,
285
+ totalPages: paged.totalPages,
286
+ },
287
+ kindCounts: counts,
288
+ };
289
+ }
290
+ //# sourceMappingURL=schedule-card-model.js.map