@rubytech/create-sitedesk-code 0.1.550 → 0.1.552

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 (128) hide show
  1. package/package.json +1 -1
  2. package/payload/platform/lib/agent-dispatch-rule/dist/index.d.ts +22 -8
  3. package/payload/platform/lib/agent-dispatch-rule/dist/index.d.ts.map +1 -1
  4. package/payload/platform/lib/agent-dispatch-rule/dist/index.js +47 -13
  5. package/payload/platform/lib/agent-dispatch-rule/dist/index.js.map +1 -1
  6. package/payload/platform/lib/agent-dispatch-rule/src/__tests__/rule.test.ts +68 -9
  7. package/payload/platform/lib/agent-dispatch-rule/src/index.ts +60 -16
  8. package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +3 -3
  9. package/payload/platform/plugins/admin/skills/whats-new/SKILL.md +9 -0
  10. package/payload/platform/plugins/docs/references/admin-ui.md +2 -2
  11. package/payload/platform/plugins/scheduling/PLUGIN.md +22 -0
  12. package/payload/platform/plugins/scheduling/mcp/dist/index.js +107 -6
  13. package/payload/platform/plugins/scheduling/mcp/dist/index.js.map +1 -1
  14. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/agent-dispatch.test.js +2 -2
  15. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/agent-dispatch.test.js.map +1 -1
  16. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/connector-freebusy.test.d.ts +2 -0
  17. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/connector-freebusy.test.d.ts.map +1 -0
  18. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/connector-freebusy.test.js +216 -0
  19. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/connector-freebusy.test.js.map +1 -0
  20. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/connector-sync-provision.test.js +2 -2
  21. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/connector-sync-provision.test.js.map +1 -1
  22. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/gate-audits.test.d.ts +2 -0
  23. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/gate-audits.test.d.ts.map +1 -0
  24. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/gate-audits.test.js +128 -0
  25. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/gate-audits.test.js.map +1 -0
  26. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/gate.test.d.ts +2 -0
  27. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/gate.test.d.ts.map +1 -0
  28. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/gate.test.js +135 -0
  29. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/gate.test.js.map +1 -0
  30. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/schedule-audit.test.js +132 -0
  31. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/schedule-audit.test.js.map +1 -1
  32. package/payload/platform/plugins/scheduling/mcp/dist/lib/connector-freebusy.d.ts +86 -0
  33. package/payload/platform/plugins/scheduling/mcp/dist/lib/connector-freebusy.d.ts.map +1 -0
  34. package/payload/platform/plugins/scheduling/mcp/dist/lib/connector-freebusy.js +240 -0
  35. package/payload/platform/plugins/scheduling/mcp/dist/lib/connector-freebusy.js.map +1 -0
  36. package/payload/platform/plugins/scheduling/mcp/dist/lib/connector-sync-provision.d.ts.map +1 -1
  37. package/payload/platform/plugins/scheduling/mcp/dist/lib/connector-sync-provision.js +21 -8
  38. package/payload/platform/plugins/scheduling/mcp/dist/lib/connector-sync-provision.js.map +1 -1
  39. package/payload/platform/plugins/scheduling/mcp/dist/lib/gate.d.ts +92 -0
  40. package/payload/platform/plugins/scheduling/mcp/dist/lib/gate.d.ts.map +1 -0
  41. package/payload/platform/plugins/scheduling/mcp/dist/lib/gate.js +193 -0
  42. package/payload/platform/plugins/scheduling/mcp/dist/lib/gate.js.map +1 -0
  43. package/payload/platform/plugins/scheduling/mcp/dist/lib/routine-run.d.ts +8 -1
  44. package/payload/platform/plugins/scheduling/mcp/dist/lib/routine-run.d.ts.map +1 -1
  45. package/payload/platform/plugins/scheduling/mcp/dist/lib/routine-run.js.map +1 -1
  46. package/payload/platform/plugins/scheduling/mcp/dist/lib/schedule-audit.d.ts +94 -1
  47. package/payload/platform/plugins/scheduling/mcp/dist/lib/schedule-audit.d.ts.map +1 -1
  48. package/payload/platform/plugins/scheduling/mcp/dist/lib/schedule-audit.js +299 -0
  49. package/payload/platform/plugins/scheduling/mcp/dist/lib/schedule-audit.js.map +1 -1
  50. package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/gate-dispatch-wiring.test.d.ts +2 -0
  51. package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/gate-dispatch-wiring.test.d.ts.map +1 -0
  52. package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/gate-dispatch-wiring.test.js +146 -0
  53. package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/gate-dispatch-wiring.test.js.map +1 -0
  54. package/payload/platform/plugins/scheduling/mcp/dist/scripts/agent-turn-dispatch.d.ts +27 -0
  55. package/payload/platform/plugins/scheduling/mcp/dist/scripts/agent-turn-dispatch.d.ts.map +1 -1
  56. package/payload/platform/plugins/scheduling/mcp/dist/scripts/agent-turn-dispatch.js +18 -0
  57. package/payload/platform/plugins/scheduling/mcp/dist/scripts/agent-turn-dispatch.js.map +1 -1
  58. package/payload/platform/plugins/scheduling/mcp/dist/scripts/check-due-events.js +119 -64
  59. package/payload/platform/plugins/scheduling/mcp/dist/scripts/check-due-events.js.map +1 -1
  60. package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-connector-busy-sync.test.js +84 -1
  61. package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-connector-busy-sync.test.js.map +1 -1
  62. package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-gate-write.test.d.ts +2 -0
  63. package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-gate-write.test.d.ts.map +1 -0
  64. package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-gate-write.test.js +218 -0
  65. package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-gate-write.test.js.map +1 -0
  66. package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/scheduling-gate.test.d.ts +2 -0
  67. package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/scheduling-gate.test.d.ts.map +1 -0
  68. package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/scheduling-gate.test.js +291 -0
  69. package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/scheduling-gate.test.js.map +1 -0
  70. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-connector-busy-sync.d.ts +26 -2
  71. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-connector-busy-sync.d.ts.map +1 -1
  72. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-connector-busy-sync.js +51 -2
  73. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-connector-busy-sync.js.map +1 -1
  74. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-event.d.ts +31 -0
  75. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-event.d.ts.map +1 -1
  76. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-event.js +44 -1
  77. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-event.js.map +1 -1
  78. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-update.d.ts +3 -1
  79. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-update.d.ts.map +1 -1
  80. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-update.js +57 -3
  81. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-update.js.map +1 -1
  82. package/payload/platform/plugins/scheduling/mcp/dist/tools/scheduling-gate.d.ts +148 -0
  83. package/payload/platform/plugins/scheduling/mcp/dist/tools/scheduling-gate.d.ts.map +1 -0
  84. package/payload/platform/plugins/scheduling/mcp/dist/tools/scheduling-gate.js +410 -0
  85. package/payload/platform/plugins/scheduling/mcp/dist/tools/scheduling-gate.js.map +1 -0
  86. package/payload/platform/plugins/scheduling/mcp/package.json +1 -0
  87. package/payload/platform/plugins/telegram/.claude-plugin/plugin.json +1 -1
  88. package/payload/platform/plugins/telegram/PLUGIN.md +6 -4
  89. package/payload/platform/plugins/telegram/mcp/dist/index.js +62 -38
  90. package/payload/platform/plugins/telegram/mcp/dist/index.js.map +1 -1
  91. package/payload/platform/plugins/telegram/mcp/dist/lib/account-token.d.ts +18 -5
  92. package/payload/platform/plugins/telegram/mcp/dist/lib/account-token.d.ts.map +1 -1
  93. package/payload/platform/plugins/telegram/mcp/dist/lib/account-token.js +34 -6
  94. package/payload/platform/plugins/telegram/mcp/dist/lib/account-token.js.map +1 -1
  95. package/payload/platform/plugins/telegram/mcp/dist/lib/account-write.d.ts +25 -8
  96. package/payload/platform/plugins/telegram/mcp/dist/lib/account-write.d.ts.map +1 -1
  97. package/payload/platform/plugins/telegram/mcp/dist/lib/account-write.js +28 -16
  98. package/payload/platform/plugins/telegram/mcp/dist/lib/account-write.js.map +1 -1
  99. package/payload/platform/plugins/telegram/mcp/dist/lib/agent-active.d.ts +12 -0
  100. package/payload/platform/plugins/telegram/mcp/dist/lib/agent-active.d.ts.map +1 -0
  101. package/payload/platform/plugins/telegram/mcp/dist/lib/agent-active.js +26 -0
  102. package/payload/platform/plugins/telegram/mcp/dist/lib/agent-active.js.map +1 -0
  103. package/payload/platform/plugins/telegram/mcp/dist/lib/secret-path.d.ts +16 -13
  104. package/payload/platform/plugins/telegram/mcp/dist/lib/secret-path.d.ts.map +1 -1
  105. package/payload/platform/plugins/telegram/mcp/dist/lib/secret-path.js +19 -23
  106. package/payload/platform/plugins/telegram/mcp/dist/lib/secret-path.js.map +1 -1
  107. package/payload/platform/plugins/telegram/mcp/dist/lib/webhook-url.d.ts +9 -7
  108. package/payload/platform/plugins/telegram/mcp/dist/lib/webhook-url.d.ts.map +1 -1
  109. package/payload/platform/plugins/telegram/mcp/dist/lib/webhook-url.js +11 -8
  110. package/payload/platform/plugins/telegram/mcp/dist/lib/webhook-url.js.map +1 -1
  111. package/payload/platform/plugins/telegram/mcp/dist/tools/message.d.ts +4 -0
  112. package/payload/platform/plugins/telegram/mcp/dist/tools/message.d.ts.map +1 -1
  113. package/payload/platform/plugins/telegram/mcp/dist/tools/message.js +13 -3
  114. package/payload/platform/plugins/telegram/mcp/dist/tools/message.js.map +1 -1
  115. package/payload/platform/plugins/telegram/references/setup-guide.md +16 -8
  116. package/payload/platform/plugins/telegram/skills/configure/SKILL.md +30 -32
  117. package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.d.ts.map +1 -1
  118. package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js +1 -0
  119. package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js.map +1 -1
  120. package/payload/platform/services/claude-session-manager/dist/pty-census.d.ts +51 -4
  121. package/payload/platform/services/claude-session-manager/dist/pty-census.d.ts.map +1 -1
  122. package/payload/platform/services/claude-session-manager/dist/pty-census.js +60 -10
  123. package/payload/platform/services/claude-session-manager/dist/pty-census.js.map +1 -1
  124. package/payload/platform/services/claude-session-manager/dist/slice-memory-policy.d.ts +64 -1
  125. package/payload/platform/services/claude-session-manager/dist/slice-memory-policy.d.ts.map +1 -1
  126. package/payload/platform/services/claude-session-manager/dist/slice-memory-policy.js +189 -6
  127. package/payload/platform/services/claude-session-manager/dist/slice-memory-policy.js.map +1 -1
  128. package/payload/server/server.js +510 -240
@@ -0,0 +1,410 @@
1
+ /**
2
+ * Task 2378 — the precondition a gated routine calls before the model is
3
+ * invoked.
4
+ *
5
+ * One tool serves every routine because the dispatcher evaluates exactly one
6
+ * gate per event, and each binding names its own checks in `gateArgs`. Four
7
+ * check types, each drawn from what the surveyed routines already do the
8
+ * expensive way — after a full model turn had already been paid for.
9
+ *
10
+ * Every check reads a STRUCTURED source. None parses another tool's prose:
11
+ * deciding whether to spend a model turn by matching words in a third-party
12
+ * tool's wording is exactly what the plugin doctrine forbids, and it is why the
13
+ * D1 check reads the query envelope and the WhatsApp check reads the status
14
+ * JSON rather than either tool's rendered text.
15
+ *
16
+ * Direction of failure is uniform and deliberate: any check that errors, or
17
+ * whose source is unreachable, contributes `hasWork: true` and its own name in
18
+ * `reasons`. A gate that fails closed silently mutes a routine and reads healthy
19
+ * on every surface, which is the Task 2313 class this must not recreate.
20
+ */
21
+ import { createHash } from "node:crypto";
22
+ import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
23
+ import { dirname, resolve } from "node:path";
24
+ import { getSession } from "../lib/neo4j.js";
25
+ /**
26
+ * Rooted at the INSTALL, not at the account.
27
+ *
28
+ * `eventId` is a UUID, so the filename is already unique across accounts and no
29
+ * account segment adds anything. What an account segment would add is a way for
30
+ * the two callers to disagree: the gate runs under the dispatcher's
31
+ * `ACCOUNT_ID` (the event's account) and the commit runs inside the agent turn,
32
+ * whose account is resolved by the channel. A path that depended on that
33
+ * resolution would let the commit write one file while the next gate read
34
+ * another, and the symptom would be a routine that never stops reporting work.
35
+ */
36
+ export function watermarkPath(eventId, platformRoot) {
37
+ return resolve(platformRoot, "..", "state", "scheduling-gate", `${eventId}.json`);
38
+ }
39
+ export function readWatermarks(eventId, platformRoot) {
40
+ try {
41
+ const raw = readFileSync(watermarkPath(eventId, platformRoot), "utf-8");
42
+ const parsed = JSON.parse(raw);
43
+ return parsed && typeof parsed === "object" ? parsed : {};
44
+ }
45
+ catch {
46
+ // A first run has no file, and an unreadable one must not be a hard error:
47
+ // an empty watermark set makes every watermarked check report work, which
48
+ // is the fail-open direction the whole tool holds to.
49
+ return {};
50
+ }
51
+ }
52
+ function writeWatermarks(eventId, platformRoot, marks) {
53
+ const path = watermarkPath(eventId, platformRoot);
54
+ mkdirSync(dirname(path), { recursive: true });
55
+ writeFileSync(path, JSON.stringify(marks, null, 2));
56
+ }
57
+ // ---------------------------------------------------------------------------
58
+ // Real sources
59
+ // ---------------------------------------------------------------------------
60
+ function loopbackBase() {
61
+ // PLATFORM_PORT is what every plugin resolves its loopback base from. The
62
+ // dispatcher stamps it from MAXY_UI_INTERNAL_PORT when the cron env carries
63
+ // only the latter (lib/gate.ts), so both names are accepted here rather than
64
+ // making this tool the one place that disagrees.
65
+ const port = process.env.PLATFORM_PORT ?? process.env.MAXY_UI_INTERNAL_PORT;
66
+ if (!port) {
67
+ throw new Error("neither PLATFORM_PORT nor MAXY_UI_INTERNAL_PORT is set — no loopback base URL");
68
+ }
69
+ return `http://127.0.0.1:${port}`;
70
+ }
71
+ async function getJson(path, accountId) {
72
+ const res = await fetch(`${loopbackBase()}${path}`, {
73
+ headers: { "x-maxy-caller-account": accountId },
74
+ });
75
+ if (!res.ok)
76
+ throw new Error(`HTTP ${res.status} on GET ${path}`);
77
+ return (await res.json());
78
+ }
79
+ async function postJson(path, accountId, body) {
80
+ const res = await fetch(`${loopbackBase()}${path}`, {
81
+ method: "POST",
82
+ headers: { "Content-Type": "application/json", "x-maxy-caller-account": accountId },
83
+ body: JSON.stringify(body),
84
+ });
85
+ if (!res.ok)
86
+ throw new Error(`HTTP ${res.status} on POST ${path}`);
87
+ return (await res.json());
88
+ }
89
+ /**
90
+ * D1: the first column of the first row of a single count query.
91
+ *
92
+ * The envelope is `[{results: [...]}]`, unwrapped the same way
93
+ * `parsePendingRows` (ui/server/portal-uploads-pull.ts:458) does, because both
94
+ * read the same `d1Query` return. Getting the shape wrong yields zero rows,
95
+ * which is indistinguishable from an empty result and would make this check
96
+ * report "no work" for ever — so an envelope this cannot understand throws and
97
+ * is counted as work rather than parsed optimistically.
98
+ */
99
+ export function parseD1Count(payload) {
100
+ const result = payload?.result;
101
+ const envelope = Array.isArray(result) ? result[0] : null;
102
+ const rows = envelope?.results;
103
+ if (!Array.isArray(rows)) {
104
+ throw new Error("unexpected d1 payload: envelope carries no results array");
105
+ }
106
+ if (rows.length === 0)
107
+ return 0;
108
+ const first = rows[0];
109
+ const values = Object.values(first);
110
+ if (values.length === 0)
111
+ return 0;
112
+ const n = Number(values[0]);
113
+ if (Number.isNaN(n))
114
+ throw new Error(`d1 first column is not a number: ${String(values[0])}`);
115
+ return n;
116
+ }
117
+ /**
118
+ * The calendar digest.
119
+ *
120
+ * The task specified a Google sync token. No sync token exists anywhere in this
121
+ * tree — `plugins/google/mcp/src/tools/calendar-list.ts` queries with `timeMin`
122
+ * and stores nothing — so there is no token to compare. What DOES exist, inside
123
+ * this plugin's own graph, is the `:ConnectorBusy` set that
124
+ * `schedule-connector-busy-sync` wholesale-replaces on every connector sync. A
125
+ * digest over that set changes exactly when the connector calendar changes,
126
+ * which is the signal the sync token was wanted for, and it needs no credential
127
+ * and no cross-plugin call.
128
+ *
129
+ * The connector name is inside the digest so that re-authorising against a
130
+ * different provider reads as a change even when the busy blocks happen to
131
+ * coincide.
132
+ */
133
+ export function digestBusy(connector, intervals) {
134
+ const sorted = [...intervals].sort((a, b) => a.start === b.start ? a.end.localeCompare(b.end) : a.start.localeCompare(b.start));
135
+ const body = sorted.map((iv) => `${iv.start}/${iv.end}`).join(",");
136
+ return createHash("sha256").update(`${connector}\n${body}`).digest("hex");
137
+ }
138
+ /**
139
+ * IMAP config for a mailbox, read from the `:EmailAccount` node the email plugin
140
+ * writes at setup. Read rather than imported: pulling the email plugin's
141
+ * `credentials.ts` across the plugin boundary would make scheduling's build
142
+ * depend on email's, and the node is the same source that module reads.
143
+ */
144
+ async function readMailboxConfig(accountId, mailbox) {
145
+ const session = getSession();
146
+ try {
147
+ const res = await session.run(`MATCH (e:EmailAccount {accountId: $accountId, email: $mailbox})
148
+ RETURN e.imapHost AS host, e.imapPort AS port, e.imapSecurity AS security LIMIT 1`, { accountId, mailbox });
149
+ if (res.records.length === 0)
150
+ return null;
151
+ const r = res.records[0];
152
+ const rawPort = r.get("port");
153
+ const port = typeof rawPort === "number"
154
+ ? rawPort
155
+ : rawPort && typeof rawPort.toNumber === "function"
156
+ ? rawPort.toNumber()
157
+ : Number(rawPort ?? 993);
158
+ const host = r.get("host");
159
+ if (!host)
160
+ return null;
161
+ return { host, port, secure: r.get("security") === "tls" };
162
+ }
163
+ finally {
164
+ await session.close();
165
+ }
166
+ }
167
+ /** The brand-rooted store the email plugin writes at `config/email-credentials.json`. */
168
+ function readMailboxPassword(mailbox) {
169
+ const path = resolve(process.env.PLATFORM_ROOT, "config", "email-credentials.json");
170
+ const store = JSON.parse(readFileSync(path, "utf-8"));
171
+ const pass = store[mailbox];
172
+ if (!pass || pass.trim() === "") {
173
+ throw new Error(`no stored credential for ${mailbox} at ${path}`);
174
+ }
175
+ return pass;
176
+ }
177
+ /**
178
+ * The mail probe: is there a UID above the watermark, optionally with a matching
179
+ * subject.
180
+ *
181
+ * It opens IMAP itself rather than reading `(:EmailAccount).lastFetchedUid`,
182
+ * which the email plugin already maintains. That watermark is advanced by the
183
+ * agent's own `email-fetch` calls, so a gate reading it would mute itself
184
+ * permanently at its first skip: no turn, no fetch, no movement, no work ever
185
+ * reported again. The probe has to be independent of the thing it gates.
186
+ */
187
+ const realMail = async (check, sinceUid, accountId) => {
188
+ const config = await readMailboxConfig(accountId, check.mailbox);
189
+ if (!config)
190
+ throw new Error(`no :EmailAccount node for ${check.mailbox}`);
191
+ const password = readMailboxPassword(check.mailbox);
192
+ const { ImapFlow } = await import("imapflow");
193
+ const client = new ImapFlow({
194
+ host: config.host,
195
+ port: config.port,
196
+ secure: config.secure,
197
+ auth: { user: check.mailbox, pass: password },
198
+ logger: false,
199
+ tls: { rejectUnauthorized: true },
200
+ connectionTimeout: 30_000,
201
+ greetingTimeout: 30_000,
202
+ socketTimeout: 30_000,
203
+ });
204
+ await client.connect();
205
+ try {
206
+ const lock = await client.getMailboxLock(check.folder ?? "INBOX");
207
+ try {
208
+ const box = client.mailbox;
209
+ const uidValidity = box && typeof box !== "boolean" ? Number(box.uidValidity) : null;
210
+ // `UID SEARCH UID <n+1>:*` is the whole probe. Nothing is fetched: the
211
+ // question is only whether anything arrived, and a body fetch here would
212
+ // cost what the gate exists to save.
213
+ const uids = await client.search({ uid: `${sinceUid + 1}:*` }, { uid: true });
214
+ let matched = Array.isArray(uids) ? uids : [];
215
+ if (check.subjectPrefixes && check.subjectPrefixes.length > 0 && matched.length > 0) {
216
+ const perPrefix = await Promise.all(check.subjectPrefixes.map((p) => client.search({ uid: `${sinceUid + 1}:*`, header: { subject: p } }, { uid: true })));
217
+ const union = new Set();
218
+ for (const set of perPrefix)
219
+ for (const u of Array.isArray(set) ? set : [])
220
+ union.add(u);
221
+ matched = [...union];
222
+ }
223
+ // The highest UID SEEN, not the highest MATCHED: committing a matched-only
224
+ // high-water mark would re-examine every non-matching message on the next
225
+ // fire for ever, and committing below what was seen would replay them.
226
+ const seen = Array.isArray(uids) ? uids : [];
227
+ const highestUid = seen.length > 0 ? Math.max(...seen) : sinceUid;
228
+ return { matched: matched.length > 0, highestUid, uidValidity };
229
+ }
230
+ finally {
231
+ lock.release();
232
+ }
233
+ }
234
+ finally {
235
+ try {
236
+ await client.logout();
237
+ }
238
+ catch {
239
+ try {
240
+ client.close();
241
+ }
242
+ catch {
243
+ // best effort
244
+ }
245
+ }
246
+ }
247
+ };
248
+ export const REAL_SOURCES = {
249
+ mail: realMail,
250
+ d1: async (check, accountId) => {
251
+ const body = await postJson("/api/storage/d1/query", accountId, {
252
+ name: check.database,
253
+ sql: check.sql,
254
+ });
255
+ if (typeof body.error === "string")
256
+ throw new Error(body.error);
257
+ return parseD1Count(body);
258
+ },
259
+ whatsappStatus: async (accountId) => {
260
+ const body = await getJson("/api/whatsapp/status", accountId);
261
+ const accounts = Array.isArray(body.accounts) ? body.accounts : [];
262
+ // Connected means EVERY linked account's socket is up. An install with one
263
+ // disconnected socket among several has a channel that cannot deliver, and
264
+ // reporting that as connected is the case the check exists to catch.
265
+ const connected = accounts.length > 0 && accounts.every((a) => a.connected === true);
266
+ return { connected };
267
+ },
268
+ whatsappInbound: async (accountId, sinceEpochSec) => {
269
+ const convBody = await getJson("/api/whatsapp/conversations", accountId);
270
+ const conversations = Array.isArray(convBody.conversations) ? convBody.conversations : [];
271
+ let latest = sinceEpochSec;
272
+ let newer = false;
273
+ // The conversation list carries the last message of each thread whichever
274
+ // direction it went, so it narrows the candidate set and cannot answer on
275
+ // its own — our own outbound reply moves the same field. Only threads that
276
+ // moved are opened, so a quiet quarter-hour costs exactly one request.
277
+ for (const conv of conversations) {
278
+ const c = conv;
279
+ const ts = typeof c.lastMessageTimestamp === "number" ? c.lastMessageTimestamp : 0;
280
+ if (typeof c.jid !== "string" || ts <= sinceEpochSec)
281
+ continue;
282
+ const msgBody = await getJson(`/api/whatsapp/messages?jid=${encodeURIComponent(c.jid)}&limit=50`, accountId);
283
+ const messages = Array.isArray(msgBody.messages) ? msgBody.messages : [];
284
+ for (const m of messages) {
285
+ const msg = m;
286
+ const mts = typeof msg.timestamp === "number" ? msg.timestamp : 0;
287
+ if (mts > latest)
288
+ latest = mts;
289
+ if (msg.fromMe !== true && mts > sinceEpochSec)
290
+ newer = true;
291
+ }
292
+ }
293
+ return { latest, newer };
294
+ },
295
+ calendarDigest: async (check, accountId) => {
296
+ const session = getSession();
297
+ try {
298
+ const res = await session.run(`MATCH (b:ConnectorBusy {accountId: $accountId})
299
+ RETURN toString(b.startsAt) AS start, toString(b.endsAt) AS end`, { accountId });
300
+ return digestBusy(check.connector, res.records.map((r) => ({ start: r.get("start"), end: r.get("end") })));
301
+ }
302
+ finally {
303
+ await session.close();
304
+ }
305
+ },
306
+ };
307
+ // ---------------------------------------------------------------------------
308
+ // Evaluation
309
+ // ---------------------------------------------------------------------------
310
+ async function probe(check, marks, accountId, sources) {
311
+ switch (check.type) {
312
+ case "mail": {
313
+ const key = `${check.mailbox}|${check.folder ?? "INBOX"}`;
314
+ const stored = marks.mail?.[key];
315
+ const since = stored?.uid ?? 0;
316
+ const res = await sources.mail(check, since, accountId);
317
+ // A UIDVALIDITY change means the server renumbered the folder, so the
318
+ // stored UID names a different message. Treating that as work is the only
319
+ // honest answer: the alternative is comparing against a number that no
320
+ // longer refers to anything.
321
+ const renumbered = stored != null && stored.uidValidity != null && res.uidValidity !== stored.uidValidity;
322
+ return {
323
+ hasWork: res.matched || renumbered,
324
+ mark: (m) => {
325
+ m.mail = { ...(m.mail ?? {}), [key]: { uid: res.highestUid, uidValidity: res.uidValidity } };
326
+ return key;
327
+ },
328
+ };
329
+ }
330
+ case "d1": {
331
+ const count = await sources.d1(check, accountId);
332
+ return { hasWork: count > 0, mark: null };
333
+ }
334
+ case "whatsapp": {
335
+ if (check.scope === "status") {
336
+ const { connected } = await sources.whatsappStatus(accountId);
337
+ return { hasWork: !connected, mark: null };
338
+ }
339
+ const since = marks.whatsapp?.inbound ?? 0;
340
+ const res = await sources.whatsappInbound(accountId, since);
341
+ return {
342
+ hasWork: res.newer,
343
+ mark: (m) => {
344
+ m.whatsapp = { ...(m.whatsapp ?? {}), inbound: res.latest };
345
+ return "whatsapp";
346
+ },
347
+ };
348
+ }
349
+ case "calendar": {
350
+ const digest = await sources.calendarDigest(check, accountId);
351
+ const stored = marks.calendar?.digest;
352
+ return {
353
+ hasWork: stored !== digest,
354
+ mark: (m) => {
355
+ m.calendar = { ...(m.calendar ?? {}), digest };
356
+ return "calendar";
357
+ },
358
+ };
359
+ }
360
+ }
361
+ }
362
+ /**
363
+ * Run every check and answer whether the routine has work.
364
+ *
365
+ * `hasWork` is true when ANY check is true, which is what makes one tool serve
366
+ * every binding: a routine names the sources it cares about and the disjunction
367
+ * is the routine's precondition.
368
+ *
369
+ * On a commit, every named check is re-probed and its current position written.
370
+ * Re-probing rather than reusing an earlier read is the point — the commit runs
371
+ * at the END of the agent turn, and anything that arrived while the turn ran has
372
+ * not been seen by the model, so it must stay above the watermark.
373
+ */
374
+ export async function runSchedulingGate(params, accountId, platformRoot, sources = REAL_SOURCES) {
375
+ const started = Date.now();
376
+ const marks = readWatermarks(params.eventId, platformRoot);
377
+ const reasons = [];
378
+ const advanced = [];
379
+ let hasWork = false;
380
+ for (const check of params.checks) {
381
+ let outcome;
382
+ try {
383
+ outcome = await probe(check, marks, accountId, sources);
384
+ }
385
+ catch (err) {
386
+ const detail = err instanceof Error ? err.message : String(err);
387
+ console.error(`[scheduling-gate] op=check-error kind=${check.type} detail=${detail} treatedAs=has-work`);
388
+ hasWork = true;
389
+ if (!reasons.includes(check.type))
390
+ reasons.push(check.type);
391
+ continue;
392
+ }
393
+ if (outcome.hasWork) {
394
+ hasWork = true;
395
+ if (!reasons.includes(check.type))
396
+ reasons.push(check.type);
397
+ }
398
+ if (params.commit && outcome.mark)
399
+ advanced.push(outcome.mark(marks));
400
+ }
401
+ if (params.commit) {
402
+ marks.lastCommitAt = new Date().toISOString();
403
+ writeWatermarks(params.eventId, platformRoot, marks);
404
+ console.error(`[scheduling-gate] op=commit eventId=${params.eventId.slice(0, 8)} advanced=${advanced.join(",")}`);
405
+ }
406
+ console.error(`[scheduling-gate] op=checked hasWork=${hasWork} reasons=${reasons.join(",")} ` +
407
+ `checks=${params.checks.length} ms=${Date.now() - started}`);
408
+ return params.commit ? { hasWork, reasons, advanced } : { hasWork, reasons };
409
+ }
410
+ //# sourceMappingURL=scheduling-gate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scheduling-gate.js","sourceRoot":"","sources":["../../src/tools/scheduling-gate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACjE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAsE7C;;;;;;;;;;GAUG;AACH,MAAM,UAAU,aAAa,CAAC,OAAe,EAAE,YAAoB;IACjE,OAAO,OAAO,CAAC,YAAY,EAAE,IAAI,EAAE,OAAO,EAAE,iBAAiB,EAAE,GAAG,OAAO,OAAO,CAAC,CAAC;AACpF,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,OAAe,EAAE,YAAoB;IAClE,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,YAAY,CAAC,aAAa,CAAC,OAAO,EAAE,YAAY,CAAC,EAAE,OAAO,CAAC,CAAC;QACxE,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAmB,CAAC;QACjD,OAAO,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;IAC5D,CAAC;IAAC,MAAM,CAAC;QACP,2EAA2E;QAC3E,0EAA0E;QAC1E,sDAAsD;QACtD,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED,SAAS,eAAe,CAAC,OAAe,EAAE,YAAoB,EAAE,KAAqB;IACnF,MAAM,IAAI,GAAG,aAAa,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC;IAClD,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC9C,aAAa,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;AACtD,CAAC;AAiCD,8EAA8E;AAC9E,eAAe;AACf,8EAA8E;AAE9E,SAAS,YAAY;IACnB,0EAA0E;IAC1E,4EAA4E;IAC5E,6EAA6E;IAC7E,iDAAiD;IACjD,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,aAAa,IAAI,OAAO,CAAC,GAAG,CAAC,qBAAqB,CAAC;IAC5E,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,MAAM,IAAI,KAAK,CAAC,+EAA+E,CAAC,CAAC;IACnG,CAAC;IACD,OAAO,oBAAoB,IAAI,EAAE,CAAC;AACpC,CAAC;AAED,KAAK,UAAU,OAAO,CAAC,IAAY,EAAE,SAAiB;IACpD,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,YAAY,EAAE,GAAG,IAAI,EAAE,EAAE;QAClD,OAAO,EAAE,EAAE,uBAAuB,EAAE,SAAS,EAAE;KAChD,CAAC,CAAC;IACH,IAAI,CAAC,GAAG,CAAC,EAAE;QAAE,MAAM,IAAI,KAAK,CAAC,QAAQ,GAAG,CAAC,MAAM,WAAW,IAAI,EAAE,CAAC,CAAC;IAClE,OAAO,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAA4B,CAAC;AACvD,CAAC;AAED,KAAK,UAAU,QAAQ,CACrB,IAAY,EACZ,SAAiB,EACjB,IAAa;IAEb,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,YAAY,EAAE,GAAG,IAAI,EAAE,EAAE;QAClD,MAAM,EAAE,MAAM;QACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,uBAAuB,EAAE,SAAS,EAAE;QACnF,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC;KAC3B,CAAC,CAAC;IACH,IAAI,CAAC,GAAG,CAAC,EAAE;QAAE,MAAM,IAAI,KAAK,CAAC,QAAQ,GAAG,CAAC,MAAM,YAAY,IAAI,EAAE,CAAC,CAAC;IACnE,OAAO,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAA4B,CAAC;AACvD,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,YAAY,CAAC,OAAgB;IAC3C,MAAM,MAAM,GAAI,OAAuC,EAAE,MAAM,CAAC;IAChE,MAAM,QAAQ,GAAG,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IAC1D,MAAM,IAAI,GAAI,QAAyC,EAAE,OAAO,CAAC;IACjE,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;QACzB,MAAM,IAAI,KAAK,CAAC,0DAA0D,CAAC,CAAC;IAC9E,CAAC;IACD,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,CAAC,CAAC;IAChC,MAAM,KAAK,GAAG,IAAI,CAAC,CAAC,CAA4B,CAAC;IACjD,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACpC,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,CAAC,CAAC;IAClC,MAAM,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;IAC5B,IAAI,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,oCAAoC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IAC9F,OAAO,CAAC,CAAC;AACX,CAAC;AAED;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,UAAU,CACxB,SAAiB,EACjB,SAAgD;IAEhD,MAAM,MAAM,GAAG,CAAC,GAAG,SAAS,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAC1C,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC,CAAC,KAAK,CAAC,CAClF,CAAC;IACF,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,IAAI,EAAE,CAAC,GAAG,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACnE,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,GAAG,SAAS,KAAK,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAC5E,CAAC;AAED;;;;;GAKG;AACH,KAAK,UAAU,iBAAiB,CAC9B,SAAiB,EACjB,OAAe;IAEf,MAAM,OAAO,GAAG,UAAU,EAAE,CAAC;IAC7B,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,GAAG,CAC3B;yFACmF,EACnF,EAAE,SAAS,EAAE,OAAO,EAAE,CACvB,CAAC;QACF,IAAI,GAAG,CAAC,OAAO,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,IAAI,CAAC;QAC1C,MAAM,CAAC,GAAG,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;QACzB,MAAM,OAAO,GAAG,CAAC,CAAC,GAAG,CAAC,MAAM,CAAY,CAAC;QACzC,MAAM,IAAI,GACR,OAAO,OAAO,KAAK,QAAQ;YACzB,CAAC,CAAC,OAAO;YACT,CAAC,CAAC,OAAO,IAAI,OAAQ,OAAuC,CAAC,QAAQ,KAAK,UAAU;gBAClF,CAAC,CAAE,OAAsC,CAAC,QAAQ,EAAE;gBACpD,CAAC,CAAC,MAAM,CAAC,OAAO,IAAI,GAAG,CAAC,CAAC;QAC/B,MAAM,IAAI,GAAG,CAAC,CAAC,GAAG,CAAC,MAAM,CAAkB,CAAC;QAC5C,IAAI,CAAC,IAAI;YAAE,OAAO,IAAI,CAAC;QACvB,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAG,CAAC,CAAC,GAAG,CAAC,UAAU,CAAmB,KAAK,KAAK,EAAE,CAAC;IAChF,CAAC;YAAS,CAAC;QACT,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC;IACxB,CAAC;AACH,CAAC;AAED,yFAAyF;AACzF,SAAS,mBAAmB,CAAC,OAAe;IAC1C,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,aAAc,EAAE,QAAQ,EAAE,wBAAwB,CAAC,CAAC;IACrF,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAA2B,CAAC;IAChF,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC;IAC5B,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;QAChC,MAAM,IAAI,KAAK,CAAC,4BAA4B,OAAO,OAAO,IAAI,EAAE,CAAC,CAAC;IACpE,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,QAAQ,GAAwB,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,EAAE;IACzE,MAAM,MAAM,GAAG,MAAM,iBAAiB,CAAC,SAAS,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;IACjE,IAAI,CAAC,MAAM;QAAE,MAAM,IAAI,KAAK,CAAC,6BAA6B,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;IAC3E,MAAM,QAAQ,GAAG,mBAAmB,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IACpD,MAAM,EAAE,QAAQ,EAAE,GAAG,MAAM,MAAM,CAAC,UAAU,CAAC,CAAC;IAC9C,MAAM,MAAM,GAAG,IAAI,QAAQ,CAAC;QAC1B,IAAI,EAAE,MAAM,CAAC,IAAI;QACjB,IAAI,EAAE,MAAM,CAAC,IAAI;QACjB,MAAM,EAAE,MAAM,CAAC,MAAM;QACrB,IAAI,EAAE,EAAE,IAAI,EAAE,KAAK,CAAC,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE;QAC7C,MAAM,EAAE,KAAK;QACb,GAAG,EAAE,EAAE,kBAAkB,EAAE,IAAI,EAAE;QACjC,iBAAiB,EAAE,MAAM;QACzB,eAAe,EAAE,MAAM;QACvB,aAAa,EAAE,MAAM;KACtB,CAAC,CAAC;IACH,MAAM,MAAM,CAAC,OAAO,EAAE,CAAC;IACvB,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,cAAc,CAAC,KAAK,CAAC,MAAM,IAAI,OAAO,CAAC,CAAC;QAClE,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,MAAM,CAAC,OAAO,CAAC;YAC3B,MAAM,WAAW,GAAG,GAAG,IAAI,OAAO,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;YACrF,uEAAuE;YACvE,yEAAyE;YACzE,qCAAqC;YACrC,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,EAAE,GAAG,EAAE,GAAG,QAAQ,GAAG,CAAC,IAAI,EAAE,EAAE,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC;YAC9E,IAAI,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;YAC9C,IAAI,KAAK,CAAC,eAAe,IAAI,KAAK,CAAC,eAAe,CAAC,MAAM,GAAG,CAAC,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACpF,MAAM,SAAS,GAAG,MAAM,OAAO,CAAC,GAAG,CACjC,KAAK,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAC9B,MAAM,CAAC,MAAM,CAAC,EAAE,GAAG,EAAE,GAAG,QAAQ,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE,CAAC,EAAE,EAAE,EAAE,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC,CACnF,CACF,CAAC;gBACF,MAAM,KAAK,GAAG,IAAI,GAAG,EAAU,CAAC;gBAChC,KAAK,MAAM,GAAG,IAAI,SAAS;oBAAE,KAAK,MAAM,CAAC,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE;wBAAE,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;gBACzF,OAAO,GAAG,CAAC,GAAG,KAAK,CAAC,CAAC;YACvB,CAAC;YACD,2EAA2E;YAC3E,0EAA0E;YAC1E,uEAAuE;YACvE,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;YAC7C,MAAM,UAAU,GAAG,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC;YAClE,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,UAAU,EAAE,WAAW,EAAE,CAAC;QAClE,CAAC;gBAAS,CAAC;YACT,IAAI,CAAC,OAAO,EAAE,CAAC;QACjB,CAAC;IACH,CAAC;YAAS,CAAC;QACT,IAAI,CAAC;YACH,MAAM,MAAM,CAAC,MAAM,EAAE,CAAC;QACxB,CAAC;QAAC,MAAM,CAAC;YACP,IAAI,CAAC;gBACH,MAAM,CAAC,KAAK,EAAE,CAAC;YACjB,CAAC;YAAC,MAAM,CAAC;gBACP,cAAc;YAChB,CAAC;QACH,CAAC;IACH,CAAC;AACH,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,YAAY,GAAgB;IACvC,IAAI,EAAE,QAAQ;IACd,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,EAAE;QAC7B,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,uBAAuB,EAAE,SAAS,EAAE;YAC9D,IAAI,EAAE,KAAK,CAAC,QAAQ;YACpB,GAAG,EAAE,KAAK,CAAC,GAAG;SACf,CAAC,CAAC;QACH,IAAI,OAAO,IAAI,CAAC,KAAK,KAAK,QAAQ;YAAE,MAAM,IAAI,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAChE,OAAO,YAAY,CAAC,IAAI,CAAC,CAAC;IAC5B,CAAC;IACD,cAAc,EAAE,KAAK,EAAE,SAAS,EAAE,EAAE;QAClC,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,sBAAsB,EAAE,SAAS,CAAC,CAAC;QAC9D,MAAM,QAAQ,GAAG,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC;QACnE,2EAA2E;QAC3E,2EAA2E;QAC3E,qEAAqE;QACrE,MAAM,SAAS,GACb,QAAQ,CAAC,MAAM,GAAG,CAAC,IAAI,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAE,CAA6B,CAAC,SAAS,KAAK,IAAI,CAAC,CAAC;QAClG,OAAO,EAAE,SAAS,EAAE,CAAC;IACvB,CAAC;IACD,eAAe,EAAE,KAAK,EAAE,SAAS,EAAE,aAAa,EAAE,EAAE;QAClD,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,6BAA6B,EAAE,SAAS,CAAC,CAAC;QACzE,MAAM,aAAa,GAAG,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,CAAC;QAC1F,IAAI,MAAM,GAAG,aAAa,CAAC;QAC3B,IAAI,KAAK,GAAG,KAAK,CAAC;QAClB,0EAA0E;QAC1E,0EAA0E;QAC1E,2EAA2E;QAC3E,uEAAuE;QACvE,KAAK,MAAM,IAAI,IAAI,aAAa,EAAE,CAAC;YACjC,MAAM,CAAC,GAAG,IAAyD,CAAC;YACpE,MAAM,EAAE,GAAG,OAAO,CAAC,CAAC,oBAAoB,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,oBAAoB,CAAC,CAAC,CAAC,CAAC,CAAC;YACnF,IAAI,OAAO,CAAC,CAAC,GAAG,KAAK,QAAQ,IAAI,EAAE,IAAI,aAAa;gBAAE,SAAS;YAC/D,MAAM,OAAO,GAAG,MAAM,OAAO,CAC3B,8BAA8B,kBAAkB,CAAC,CAAC,CAAC,GAAG,CAAC,WAAW,EAClE,SAAS,CACV,CAAC;YACF,MAAM,QAAQ,GAAG,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC;YACzE,KAAK,MAAM,CAAC,IAAI,QAAQ,EAAE,CAAC;gBACzB,MAAM,GAAG,GAAG,CAA8C,CAAC;gBAC3D,MAAM,GAAG,GAAG,OAAO,GAAG,CAAC,SAAS,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC;gBAClE,IAAI,GAAG,GAAG,MAAM;oBAAE,MAAM,GAAG,GAAG,CAAC;gBAC/B,IAAI,GAAG,CAAC,MAAM,KAAK,IAAI,IAAI,GAAG,GAAG,aAAa;oBAAE,KAAK,GAAG,IAAI,CAAC;YAC/D,CAAC;QACH,CAAC;QACD,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;IAC3B,CAAC;IACD,cAAc,EAAE,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,EAAE;QACzC,MAAM,OAAO,GAAG,UAAU,EAAE,CAAC;QAC7B,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,GAAG,CAC3B;yEACiE,EACjE,EAAE,SAAS,EAAE,CACd,CAAC;YACF,OAAO,UAAU,CACf,KAAK,CAAC,SAAS,EACf,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,GAAG,CAAC,OAAO,CAAW,EAAE,GAAG,EAAE,CAAC,CAAC,GAAG,CAAC,KAAK,CAAW,EAAE,CAAC,CAAC,CAC3F,CAAC;QACJ,CAAC;gBAAS,CAAC;YACT,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC;QACxB,CAAC;IACH,CAAC;CACF,CAAC;AAEF,8EAA8E;AAC9E,aAAa;AACb,8EAA8E;AAE9E,KAAK,UAAU,KAAK,CAClB,KAAgB,EAChB,KAAqB,EACrB,SAAiB,EACjB,OAAoB;IAEpB,QAAQ,KAAK,CAAC,IAAI,EAAE,CAAC;QACnB,KAAK,MAAM,CAAC,CAAC,CAAC;YACZ,MAAM,GAAG,GAAG,GAAG,KAAK,CAAC,OAAO,IAAI,KAAK,CAAC,MAAM,IAAI,OAAO,EAAE,CAAC;YAC1D,MAAM,MAAM,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC;YACjC,MAAM,KAAK,GAAG,MAAM,EAAE,GAAG,IAAI,CAAC,CAAC;YAC/B,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,KAAK,EAAE,KAAK,EAAE,SAAS,CAAC,CAAC;YACxD,sEAAsE;YACtE,0EAA0E;YAC1E,uEAAuE;YACvE,6BAA6B;YAC7B,MAAM,UAAU,GACd,MAAM,IAAI,IAAI,IAAI,MAAM,CAAC,WAAW,IAAI,IAAI,IAAI,GAAG,CAAC,WAAW,KAAK,MAAM,CAAC,WAAW,CAAC;YACzF,OAAO;gBACL,OAAO,EAAE,GAAG,CAAC,OAAO,IAAI,UAAU;gBAClC,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE;oBACV,CAAC,CAAC,IAAI,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,IAAI,EAAE,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,EAAE,GAAG,EAAE,GAAG,CAAC,UAAU,EAAE,WAAW,EAAE,GAAG,CAAC,WAAW,EAAE,EAAE,CAAC;oBAC7F,OAAO,GAAG,CAAC;gBACb,CAAC;aACF,CAAC;QACJ,CAAC;QACD,KAAK,IAAI,CAAC,CAAC,CAAC;YACV,MAAM,KAAK,GAAG,MAAM,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC;YACjD,OAAO,EAAE,OAAO,EAAE,KAAK,GAAG,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;QAC5C,CAAC;QACD,KAAK,UAAU,CAAC,CAAC,CAAC;YAChB,IAAI,KAAK,CAAC,KAAK,KAAK,QAAQ,EAAE,CAAC;gBAC7B,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,OAAO,CAAC,cAAc,CAAC,SAAS,CAAC,CAAC;gBAC9D,OAAO,EAAE,OAAO,EAAE,CAAC,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;YAC7C,CAAC;YACD,MAAM,KAAK,GAAG,KAAK,CAAC,QAAQ,EAAE,OAAO,IAAI,CAAC,CAAC;YAC3C,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,eAAe,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;YAC5D,OAAO;gBACL,OAAO,EAAE,GAAG,CAAC,KAAK;gBAClB,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE;oBACV,CAAC,CAAC,QAAQ,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC,QAAQ,IAAI,EAAE,CAAC,EAAE,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,CAAC;oBAC5D,OAAO,UAAU,CAAC;gBACpB,CAAC;aACF,CAAC;QACJ,CAAC;QACD,KAAK,UAAU,CAAC,CAAC,CAAC;YAChB,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,cAAc,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC;YAC9D,MAAM,MAAM,GAAG,KAAK,CAAC,QAAQ,EAAE,MAAM,CAAC;YACtC,OAAO;gBACL,OAAO,EAAE,MAAM,KAAK,MAAM;gBAC1B,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE;oBACV,CAAC,CAAC,QAAQ,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC,QAAQ,IAAI,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC;oBAC/C,OAAO,UAAU,CAAC;gBACpB,CAAC;aACF,CAAC;QACJ,CAAC;IACH,CAAC;AACH,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,MAA4B,EAC5B,SAAiB,EACjB,YAAoB,EACpB,UAAuB,YAAY;IAEnC,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAC3B,MAAM,KAAK,GAAG,cAAc,CAAC,MAAM,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC;IAC3D,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,MAAM,QAAQ,GAAa,EAAE,CAAC;IAC9B,IAAI,OAAO,GAAG,KAAK,CAAC;IAEpB,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;QAClC,IAAI,OAAqB,CAAC;QAC1B,IAAI,CAAC;YACH,OAAO,GAAG,MAAM,KAAK,CAAC,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,OAAO,CAAC,CAAC;QAC1D,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,MAAM,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YAChE,OAAO,CAAC,KAAK,CACX,yCAAyC,KAAK,CAAC,IAAI,WAAW,MAAM,qBAAqB,CAC1F,CAAC;YACF,OAAO,GAAG,IAAI,CAAC;YACf,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC;gBAAE,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YAC5D,SAAS;QACX,CAAC;QACD,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;YACpB,OAAO,GAAG,IAAI,CAAC;YACf,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC;gBAAE,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAC9D,CAAC;QACD,IAAI,MAAM,CAAC,MAAM,IAAI,OAAO,CAAC,IAAI;YAAE,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;IACxE,CAAC;IAED,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;QAClB,KAAK,CAAC,YAAY,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;QAC9C,eAAe,CAAC,MAAM,CAAC,OAAO,EAAE,YAAY,EAAE,KAAK,CAAC,CAAC;QACrD,OAAO,CAAC,KAAK,CACX,uCAAuC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,aAAa,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CACnG,CAAC;IACJ,CAAC;IAED,OAAO,CAAC,KAAK,CACX,wCAAwC,OAAO,YAAY,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG;QAC7E,UAAU,MAAM,CAAC,MAAM,CAAC,MAAM,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,OAAO,EAAE,CAC9D,CAAC;IAEF,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC;AAC/E,CAAC"}
@@ -12,6 +12,7 @@
12
12
  "dependencies": {
13
13
  "@modelcontextprotocol/sdk": "^1.12.1",
14
14
  "cron-parser": "^4.9.0",
15
+ "imapflow": "^1.0.171",
15
16
  "neo4j-driver": "^5.28.1",
16
17
  "zod": "^3.24.0"
17
18
  },
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "telegram",
3
- "description": "Guide users through connecting a Telegram bot — creating a bot via BotFather, finding user IDs, and configuring admin vs public bots.",
3
+ "description": "Guide users through connecting Telegram bots — creating a bot via BotFather, finding user IDs, and registering any number of bots, each with its own role, access policy and agent.",
4
4
  "version": "0.1.0",
5
5
  "author": {
6
6
  "name": "Rubytech LLC"
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: telegram
3
- description: Guide users through connecting a Telegram bot — creating a bot via BotFather, finding user IDs, and configuring admin vs public bots.
3
+ description: Guide users through connecting Telegram bots — creating a bot via BotFather, finding user IDs, and registering any number of bots, each with its own role, access policy and agent.
4
4
  metadata: {"platform":{"optional":true}}
5
5
  tools:
6
6
  - name: message
@@ -44,14 +44,16 @@ Walks users through connecting Telegram bots. Covers bot creation, token configu
44
44
  ## What it unlocks
45
45
 
46
46
  - Telegram as a messaging channel — DMs and group conversations routed to the AI assistant
47
- - Multi-bot support separate admin and public bots with different access policies
47
+ - Any number of bots per account, each with its own role, its own access policy and its own agent
48
48
  - Admin routing — restrict a bot to admin-only access via numeric user ID bindings
49
49
 
50
50
  ## Setup
51
51
 
52
- Connecting a Telegram bot runs through the `telegram:configure` skill (`skills/configure/SKILL.md`) — the brand-aware, PTY-only setup flow. It is the maxy `telegram:configure`; on `-code` brands the upstream `claude-plugins-official/telegram` plugin is not installed, so there is no shadowing. The skill stores the BotFather token under the brand install, registers the webhook on the `/api/telegram/` edge path, sets access policy, and selects the responding agent.
52
+ Connecting a Telegram bot runs through the `telegram:configure` skill (`skills/configure/SKILL.md`) — the brand-aware, PTY-only setup flow. It is the maxy `telegram:configure`; on `-code` brands the upstream `claude-plugins-official/telegram` plugin is not installed, so there is no shadowing. The skill stores the BotFather token under the brand install, registers the webhook on the `/api/telegram/` edge path, and sets the bot's role, access policy and agent.
53
53
 
54
- Each account owns its own bot. `telegram-webhook-register` stamps the calling account onto the registered URL as `account=<accountId>` and writes that account's own secret file (`~/.<brand>/.telegram-{admin-}webhook-secret.<accountId>`), so registering one account's bot cannot disturb another's. A bot registered before the account parameter existed answers `reason=missing-account-param` until it is registered once more; the standing `[telegram-audit]` line names the account that needs it.
54
+ An account holds `telegram.bots[]`, an array keyed on each bot's own Telegram id (the token's `:`-prefix). Each entry carries its own `token`, an explicit `role` (`admin` or `public`, never derived from a slug, because it selects the spawn path), the policy for that bot alone, and on a public entry only — the `agent` it answers as. An admin entry names no agent: it reads `agents/admin` by construction. `telegram-webhook-register` derives the bot id from the token, stamps `?bot=<botId>` onto the registered URL, and replaces only the entry with that id, so registering one bot cannot disturb another in any account. Secrets live at `~/.<brand>/telegram-secrets/<botId>` (directory 700, file 600).
55
+
56
+ A bot registered before the registry answers `reason=unknown-bot` until it is registered once more; the boot converter rewrites the old two-slot config in place but makes no outbound call, and the standing `[telegram-audit]` line names every bot that has not re-registered.
55
57
 
56
58
  ## References
57
59