@rubytech/create-maxy-code 0.1.613 → 0.1.614

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 (75) hide show
  1. package/package.json +1 -1
  2. package/payload/platform/lib/agent-dispatch-rule/dist/index.d.ts +34 -0
  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 +39 -0
  5. package/payload/platform/lib/agent-dispatch-rule/dist/index.js.map +1 -1
  6. package/payload/platform/lib/agent-dispatch-rule/src/__tests__/carrier-verdict.test.ts +92 -0
  7. package/payload/platform/lib/agent-dispatch-rule/src/index.ts +59 -0
  8. package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +46 -1
  9. package/payload/platform/plugins/admin/skills/whats-new/SKILL.md +6 -0
  10. package/payload/platform/plugins/docs/references/telegram-guide.md +45 -0
  11. package/payload/platform/plugins/scheduling/mcp/dist/index.js +2 -0
  12. package/payload/platform/plugins/scheduling/mcp/dist/index.js.map +1 -1
  13. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/agent-dispatch-carrier.test.d.ts +2 -0
  14. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/agent-dispatch-carrier.test.d.ts.map +1 -0
  15. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/agent-dispatch-carrier.test.js +72 -0
  16. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/agent-dispatch-carrier.test.js.map +1 -0
  17. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/routine-delivery-reconcile.test.d.ts +2 -0
  18. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/routine-delivery-reconcile.test.d.ts.map +1 -0
  19. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/routine-delivery-reconcile.test.js +327 -0
  20. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/routine-delivery-reconcile.test.js.map +1 -0
  21. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/routine-delivery.test.d.ts +2 -0
  22. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/routine-delivery.test.d.ts.map +1 -0
  23. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/routine-delivery.test.js +130 -0
  24. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/routine-delivery.test.js.map +1 -0
  25. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/routine-run.test.js +27 -58
  26. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/routine-run.test.js.map +1 -1
  27. package/payload/platform/plugins/scheduling/mcp/dist/lib/agent-dispatch.d.ts +8 -3
  28. package/payload/platform/plugins/scheduling/mcp/dist/lib/agent-dispatch.d.ts.map +1 -1
  29. package/payload/platform/plugins/scheduling/mcp/dist/lib/agent-dispatch.js +37 -3
  30. package/payload/platform/plugins/scheduling/mcp/dist/lib/agent-dispatch.js.map +1 -1
  31. package/payload/platform/plugins/scheduling/mcp/dist/lib/routine-delivery.d.ts +67 -0
  32. package/payload/platform/plugins/scheduling/mcp/dist/lib/routine-delivery.d.ts.map +1 -0
  33. package/payload/platform/plugins/scheduling/mcp/dist/lib/routine-delivery.js +133 -0
  34. package/payload/platform/plugins/scheduling/mcp/dist/lib/routine-delivery.js.map +1 -0
  35. package/payload/platform/plugins/scheduling/mcp/dist/lib/routine-run.d.ts +67 -12
  36. package/payload/platform/plugins/scheduling/mcp/dist/lib/routine-run.d.ts.map +1 -1
  37. package/payload/platform/plugins/scheduling/mcp/dist/lib/routine-run.js +226 -72
  38. package/payload/platform/plugins/scheduling/mcp/dist/lib/routine-run.js.map +1 -1
  39. package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/agent-turn-dispatch.test.js +66 -4
  40. package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/agent-turn-dispatch.test.js.map +1 -1
  41. package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/reconcile-run-controls.test.d.ts +2 -0
  42. package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/reconcile-run-controls.test.d.ts.map +1 -0
  43. package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/reconcile-run-controls.test.js +45 -0
  44. package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/reconcile-run-controls.test.js.map +1 -0
  45. package/payload/platform/plugins/scheduling/mcp/dist/scripts/agent-turn-dispatch.d.ts +3 -1
  46. package/payload/platform/plugins/scheduling/mcp/dist/scripts/agent-turn-dispatch.d.ts.map +1 -1
  47. package/payload/platform/plugins/scheduling/mcp/dist/scripts/agent-turn-dispatch.js +32 -7
  48. package/payload/platform/plugins/scheduling/mcp/dist/scripts/agent-turn-dispatch.js.map +1 -1
  49. package/payload/platform/plugins/scheduling/mcp/dist/scripts/check-due-events.js +12 -1
  50. package/payload/platform/plugins/scheduling/mcp/dist/scripts/check-due-events.js.map +1 -1
  51. package/payload/platform/plugins/scheduling/mcp/dist/scripts/reconcile-bookings.js +15 -2
  52. package/payload/platform/plugins/scheduling/mcp/dist/scripts/reconcile-bookings.js.map +1 -1
  53. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-event.d.ts +14 -0
  54. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-event.d.ts.map +1 -1
  55. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-event.js +4 -0
  56. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-event.js.map +1 -1
  57. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-get.d.ts +3 -0
  58. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-get.d.ts.map +1 -1
  59. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-get.js +2 -0
  60. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-get.js.map +1 -1
  61. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-update.d.ts.map +1 -1
  62. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-update.js +14 -0
  63. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-update.js.map +1 -1
  64. package/payload/platform/plugins/telegram/mcp/dist/__tests__/card-row.test.d.ts +2 -0
  65. package/payload/platform/plugins/telegram/mcp/dist/__tests__/card-row.test.d.ts.map +1 -0
  66. package/payload/platform/plugins/telegram/mcp/dist/__tests__/card-row.test.js +237 -0
  67. package/payload/platform/plugins/telegram/mcp/dist/__tests__/card-row.test.js.map +1 -0
  68. package/payload/platform/plugins/telegram/mcp/dist/lib/card-row.d.ts +96 -0
  69. package/payload/platform/plugins/telegram/mcp/dist/lib/card-row.d.ts.map +1 -0
  70. package/payload/platform/plugins/telegram/mcp/dist/lib/card-row.js +156 -0
  71. package/payload/platform/plugins/telegram/mcp/dist/lib/card-row.js.map +1 -0
  72. package/payload/platform/plugins/telegram/mcp/dist/tools/card.d.ts.map +1 -1
  73. package/payload/platform/plugins/telegram/mcp/dist/tools/card.js +53 -0
  74. package/payload/platform/plugins/telegram/mcp/dist/tools/card.js.map +1 -1
  75. package/payload/server/server.js +663 -620
@@ -0,0 +1,72 @@
1
+ // @vitest-environment node
2
+ /**
3
+ * Task 2776 — write-time validation of a named carrier.
4
+ *
5
+ * Both write paths call `validateAgentDispatch` (schedule-event.ts:503,
6
+ * schedule-update.ts:570), so extending that one function covers the pair and
7
+ * they cannot drift on the rule.
8
+ */
9
+ import { describe, expect, it, beforeEach, afterEach, vi } from "vitest";
10
+ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
11
+ import { tmpdir } from "node:os";
12
+ import { join } from "node:path";
13
+ import { validateAgentDispatch } from "../agent-dispatch.js";
14
+ const ACCT = "5bc2c0e2-206c-4fee-b274-d87736b4fce1";
15
+ let root;
16
+ beforeEach(() => {
17
+ root = mkdtempSync(join(tmpdir(), "dispatch-"));
18
+ const dir = join(root, "data", "accounts", ACCT);
19
+ mkdirSync(dir, { recursive: true });
20
+ writeFileSync(join(dir, "account.json"), JSON.stringify({
21
+ telegram: {
22
+ bots: [
23
+ { id: "8711566959", role: "admin", adminUsers: [8992591236] },
24
+ // A SECOND admin bot lists 1311221864, so that destination passes the
25
+ // account-wide registration check and the named-carrier check is what
26
+ // the not-admin / not-listed cases below actually exercise. Without
27
+ // it they would fail one guard earlier, on reason=not-registered.
28
+ { id: "8700000001", role: "admin", adminUsers: [1311221864] },
29
+ { id: "8804050655", role: "specialist", adminUsers: [1311221864] },
30
+ ],
31
+ },
32
+ }));
33
+ vi.stubEnv("PLATFORM_ROOT", join(root, "platform"));
34
+ });
35
+ afterEach(() => {
36
+ vi.unstubAllEnvs();
37
+ rmSync(root, { recursive: true, force: true });
38
+ });
39
+ const base = { channel: "telegram", destination: "8992591236", prompt: "check" };
40
+ describe("validateAgentDispatch — named carrier (Task 2776)", () => {
41
+ it("accepts a dispatch naming an admin bot that lists the destination", () => {
42
+ expect(() => validateAgentDispatch(ACCT, { ...base, botId: "8711566959" })).not.toThrow();
43
+ });
44
+ it("accepts a dispatch naming no bot, exactly as before", () => {
45
+ expect(() => validateAgentDispatch(ACCT, base)).not.toThrow();
46
+ });
47
+ it("treats an empty botId as naming none rather than as a bot that is absent", () => {
48
+ expect(() => validateAgentDispatch(ACCT, { ...base, botId: "" })).not.toThrow();
49
+ });
50
+ it("rejects a bot that is not this account's, naming reason=absent", () => {
51
+ expect(() => validateAgentDispatch(ACCT, { ...base, botId: "9999999999" })).toThrow(/reason=absent/);
52
+ });
53
+ it("rejects a bot demoted off role admin, naming reason=not-admin", () => {
54
+ // The measured case: the carrier is now the driver-relay specialist bot.
55
+ expect(() => validateAgentDispatch(ACCT, { ...base, destination: "1311221864", botId: "8804050655" })).toThrow(/reason=not-admin/);
56
+ });
57
+ it("rejects an admin bot that does not list the destination, naming reason=not-listed", () => {
58
+ expect(() => validateAgentDispatch(ACCT, { ...base, destination: "1311221864", botId: "8711566959" })).toThrow(/reason=not-listed/);
59
+ });
60
+ it("names the offending bot in the refusal, so the operator can act on it", () => {
61
+ expect(() => validateAgentDispatch(ACCT, { ...base, botId: "9999999999" })).toThrow(/9999999999/);
62
+ });
63
+ it("rejects a botId on a whatsapp dispatch — there is no bot to name", () => {
64
+ expect(() => validateAgentDispatch(ACCT, {
65
+ channel: "whatsapp",
66
+ destination: "+447700900000",
67
+ prompt: "check",
68
+ botId: "8711566959",
69
+ })).toThrow(/only a telegram dispatch/);
70
+ });
71
+ });
72
+ //# sourceMappingURL=agent-dispatch-carrier.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-dispatch-carrier.test.js","sourceRoot":"","sources":["../../../src/lib/__tests__/agent-dispatch-carrier.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=routine-delivery-reconcile.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routine-delivery-reconcile.test.d.ts","sourceRoot":"","sources":["../../../src/lib/__tests__/routine-delivery-reconcile.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,327 @@
1
+ // @vitest-environment node
2
+ /**
3
+ * Task 2776 — delivery established from the outbound store, and the standing
4
+ * reconciliation over it.
5
+ *
6
+ * Delivery is NOT read from the transcript: a routine that legitimately reports
7
+ * nothing and one that failed to send look identical there, which is the
8
+ * conflation this task removes. A store row exists only because a send was
9
+ * confirmed.
10
+ */
11
+ import { describe, it, expect, vi } from "vitest";
12
+ import { readFileSync } from "node:fs";
13
+ import { join } from "node:path";
14
+ import { reconcileRoutineDelivery, routineDeliveryCensus, OUTPUT_EXCERPT_CAP, DELIVERY_WINDOW_HOURS, } from "../routine-run.js";
15
+ /** Fixed clock, an hour after the fixture runs, so they sit inside the window. */
16
+ const NOW = Date.parse("2026-08-16T17:00:00Z");
17
+ /** A driver whose FIRST query returns `rows` and whose later queries are
18
+ * recorded as writes — the stub shape recordRunSession's tests already use. */
19
+ function deliveryDriver(rows) {
20
+ const writes = [];
21
+ let first = true;
22
+ return {
23
+ writes,
24
+ session: () => ({
25
+ run: vi.fn(async (_q, params) => {
26
+ if (first) {
27
+ first = false;
28
+ return { records: rows.map((r) => ({ get: (k) => r[k] })) };
29
+ }
30
+ writes.push(params);
31
+ return { records: [] };
32
+ }),
33
+ close: vi.fn(async () => { }),
34
+ }),
35
+ };
36
+ }
37
+ const run = (over = {}) => ({
38
+ runId: "r1",
39
+ sessionKey: "s1",
40
+ target: "telegram:8992591236",
41
+ eventId: "evt-1",
42
+ startedAt: "2026-08-16T16:10:00Z",
43
+ deliveredMessageId: null,
44
+ ...over,
45
+ });
46
+ const sent = (over = {}) => ({
47
+ messageId: "telegram:acct:8711566959:8992591236:out:2026-08-16T16:10:05Z",
48
+ sessionId: "s1",
49
+ createdAt: "2026-08-16T16:10:05Z",
50
+ body: "the report",
51
+ ...over,
52
+ });
53
+ function captureErrors() {
54
+ const lines = [];
55
+ const spy = vi
56
+ .spyOn(console, "error")
57
+ .mockImplementation((...a) => void lines.push(a.join(" ")));
58
+ return { lines, restore: () => spy.mockRestore() };
59
+ }
60
+ describe("reconcileRoutineDelivery", () => {
61
+ it("stamps deliveredMessageId and the excerpt from the run's own store rows", async () => {
62
+ const driver = deliveryDriver([run()]);
63
+ const res = await reconcileRoutineDelivery(driver, "acct", "/root", NOW, {
64
+ readRows: () => [sent()],
65
+ });
66
+ expect(res.stamped).toBe(1);
67
+ expect(driver.writes[0].deliveredMessageId).toBe(sent().messageId);
68
+ expect(driver.writes[0].outputExcerpt).toBe("the report");
69
+ });
70
+ it("leaves a run with no matching row unstamped", async () => {
71
+ const driver = deliveryDriver([run()]);
72
+ const res = await reconcileRoutineDelivery(driver, "acct", "/root", NOW, {
73
+ readRows: () => [],
74
+ });
75
+ expect(res.stamped).toBe(0);
76
+ expect(driver.writes).toEqual([]);
77
+ });
78
+ it("skips a run already stamped, so the pass is idempotent", async () => {
79
+ // A delivery arriving after promotion is picked up on a later tick rather
80
+ // than recorded as silence forever; a stamped run is never re-read.
81
+ const driver = deliveryDriver([run({ deliveredMessageId: "already" })]);
82
+ const res = await reconcileRoutineDelivery(driver, "acct", "/root", NOW, {
83
+ readRows: () => [sent()],
84
+ });
85
+ expect(res.stamped).toBe(0);
86
+ });
87
+ it("joins several rows into one excerpt and stamps the FIRST", async () => {
88
+ const driver = deliveryDriver([run()]);
89
+ await reconcileRoutineDelivery(driver, "acct", "/root", NOW, {
90
+ readRows: () => [
91
+ sent({ messageId: "out-1", createdAt: "2026-08-16T16:10:05Z", body: "first" }),
92
+ sent({ messageId: "out-2", createdAt: "2026-08-16T16:10:06Z", body: "second" }),
93
+ ],
94
+ });
95
+ expect(driver.writes[0].deliveredMessageId).toBe("out-1");
96
+ expect(driver.writes[0].outputExcerpt).toBe("first\n\nsecond");
97
+ });
98
+ it("caps the excerpt at OUTPUT_EXCERPT_CAP", async () => {
99
+ const driver = deliveryDriver([run()]);
100
+ await reconcileRoutineDelivery(driver, "acct", "/root", NOW, {
101
+ readRows: () => [sent({ body: "x".repeat(3000) })],
102
+ });
103
+ expect(driver.writes[0].outputExcerpt.length).toBe(OUTPUT_EXCERPT_CAP);
104
+ });
105
+ it("bounds a run's window by the next run on the SAME session", async () => {
106
+ // Without the ceiling, run 1's window runs to end of time and both runs are
107
+ // credited with one answer.
108
+ const driver = deliveryDriver([
109
+ run({ runId: "r1", startedAt: "2026-08-16T16:10:00Z" }),
110
+ run({ runId: "r2", startedAt: "2026-08-16T16:15:00Z" }),
111
+ ]);
112
+ await reconcileRoutineDelivery(driver, "acct", "/root", NOW, {
113
+ readRows: () => [sent({ messageId: "late", createdAt: "2026-08-16T16:16:00Z" })],
114
+ });
115
+ expect(driver.writes).toHaveLength(1);
116
+ expect(driver.writes[0].runId).toBe("r2");
117
+ });
118
+ it("does not bound a run by the next run on a DIFFERENT session", async () => {
119
+ const driver = deliveryDriver([
120
+ run({ runId: "r1", sessionKey: "s1", startedAt: "2026-08-16T16:10:00Z" }),
121
+ run({ runId: "r2", sessionKey: "s2", startedAt: "2026-08-16T16:11:00Z" }),
122
+ ]);
123
+ await reconcileRoutineDelivery(driver, "acct", "/root", NOW, {
124
+ readRows: () => [sent({ sessionId: "s1", createdAt: "2026-08-16T16:12:00Z" })],
125
+ });
126
+ expect(driver.writes.map((w) => w.runId)).toEqual(["r1"]);
127
+ });
128
+ it("reads the store for the run's own destination", async () => {
129
+ const seen = [];
130
+ const driver = deliveryDriver([run()]);
131
+ await reconcileRoutineDelivery(driver, "acct", "/root", NOW, {
132
+ readRows: (_root, _acct, dest) => {
133
+ seen.push(dest);
134
+ return [];
135
+ },
136
+ });
137
+ expect(seen).toEqual(["8992591236"]);
138
+ });
139
+ it("emits op=routine-delivered naming the event, the carrier and the chat", async () => {
140
+ const { lines, restore } = captureErrors();
141
+ const driver = deliveryDriver([run()]);
142
+ await reconcileRoutineDelivery(driver, "acct", "/root", NOW, { readRows: () => [sent()] });
143
+ expect(lines.join("\n")).toContain("op=routine-delivered event=evt-1 run=r1");
144
+ // The carrier comes off the row id, so the line names the bot that ACTUALLY
145
+ // carried the message rather than the one the routine named — which is how a
146
+ // moved carrier becomes visible on the delivery itself.
147
+ expect(lines.join("\n")).toContain("botId=8711566959 chatId=8992591236");
148
+ restore();
149
+ });
150
+ it("never throws when the query fails", async () => {
151
+ const { lines, restore } = captureErrors();
152
+ const driver = {
153
+ session: () => ({
154
+ run: async () => {
155
+ throw new Error("down");
156
+ },
157
+ close: async () => { },
158
+ }),
159
+ };
160
+ await expect(reconcileRoutineDelivery(driver, "acct", "/root", NOW)).resolves.toEqual({
161
+ stamped: 0,
162
+ });
163
+ expect(lines.join("\n")).toContain("op=delivery-reconcile-error");
164
+ restore();
165
+ });
166
+ });
167
+ describe("routineDeliveryCensus", () => {
168
+ function censusDriver(row) {
169
+ return {
170
+ session: () => ({
171
+ run: vi.fn(async () => ({
172
+ records: row === null ? [] : [{ get: (k) => row[k] }],
173
+ })),
174
+ close: vi.fn(async () => { }),
175
+ }),
176
+ };
177
+ }
178
+ it("counts delivered against accepted and names the silent events", async () => {
179
+ // `silent` is not an error on its own — a routine told to report only when
180
+ // something is new is silent by design — so the line names the events rather
181
+ // than alarming on the count.
182
+ const { lines, restore } = captureErrors();
183
+ const driver = censusDriver({ accepted: 3, delivered: 1, silentEvents: ["evt-a", "evt-b"] });
184
+ await routineDeliveryCensus(driver, "acct-1234-rest", 1_000_000_000_000);
185
+ expect(lines.join("\n")).toContain("[routine-delivery-census] accountId=acct-123 accepted=3 delivered=1 silent=2 window=24h");
186
+ expect(lines.join("\n")).toContain("silent-events=[evt-a,evt-b]");
187
+ restore();
188
+ });
189
+ it("omits silent-events when nothing is silent", async () => {
190
+ const { lines, restore } = captureErrors();
191
+ const driver = censusDriver({ accepted: 2, delivered: 2, silentEvents: [] });
192
+ await routineDeliveryCensus(driver, "acct-1234-rest", 1_000_000_000_000);
193
+ expect(lines.join("\n")).toContain("accepted=2 delivered=2 silent=0");
194
+ expect(lines.join("\n")).not.toContain("silent-events=");
195
+ restore();
196
+ });
197
+ it("emits its line on an account with no runs at all", async () => {
198
+ // Emitted every sweep including zero: a check that is silent when healthy
199
+ // cannot be told apart from one that stopped running.
200
+ const { lines, restore } = captureErrors();
201
+ await routineDeliveryCensus(censusDriver(null), "acct-1234-rest", 1_000_000_000_000);
202
+ expect(lines.join("\n")).toContain("accepted=0 delivered=0 silent=0");
203
+ restore();
204
+ });
205
+ it("reports rather than throws when the query fails", async () => {
206
+ const { lines, restore } = captureErrors();
207
+ const driver = {
208
+ session: () => ({
209
+ run: async () => {
210
+ throw new Error("down");
211
+ },
212
+ close: async () => { },
213
+ }),
214
+ };
215
+ await expect(routineDeliveryCensus(driver, "acct-1234-rest", 1_000_000_000_000)).resolves.toBeUndefined();
216
+ expect(lines.join("\n")).toContain("routine-delivery-census-error");
217
+ restore();
218
+ });
219
+ });
220
+ // Task 2776 — the Cypher itself, pinned at source.
221
+ //
222
+ // The stubbed-driver tests above prove the line shape and the silent arithmetic,
223
+ // but they return fixed rows regardless of the query, so they cannot catch a
224
+ // wrong aggregate: replacing `count(r.deliveredMessageId)` with `count(r)` makes
225
+ // the census report every accepted wake as delivered and leaves all fourteen of
226
+ // them green. There is no Neo4j in the unit suite to execute the query against,
227
+ // so the clauses that carry the meaning are asserted here — the same
228
+ // source-assertion idiom `gate-dispatch-wiring.test.ts` uses for wiring it
229
+ // cannot execute.
230
+ describe("routineDeliveryCensus — the query's load-bearing clauses", () => {
231
+ const SOURCE = readFileSync(join(__dirname, "../routine-run.ts"), "utf-8");
232
+ const census = SOURCE.slice(SOURCE.indexOf("export async function routineDeliveryCensus"));
233
+ it("counts the PROPERTY, not the row — count(r) would report every wake delivered", () => {
234
+ expect(census).toContain("count(r.deliveredMessageId) AS delivered");
235
+ });
236
+ it("counts only accepted wakes, so a refused dispatch is never called silent", () => {
237
+ expect(census).toContain("r.dispatchResult = 'accepted'");
238
+ });
239
+ it("is scoped to telegram — a whatsapp run can never be stamped and would read silent forever", () => {
240
+ expect(census).toContain("r.target STARTS WITH 'telegram:'");
241
+ });
242
+ it("bounds by the window, which is what makes the change forward-only", () => {
243
+ expect(census).toContain("r.startedAt >= $since");
244
+ });
245
+ it("collects the silent events, because a bare count names nothing to act on", () => {
246
+ expect(census).toContain("WHEN r.deliveredMessageId IS NULL THEN r.eventId");
247
+ });
248
+ });
249
+ // Task 2776 — the reconciliation's own query.
250
+ describe("reconcileRoutineDelivery — the query's load-bearing clauses", () => {
251
+ const SOURCE = readFileSync(join(__dirname, "../routine-run.ts"), "utf-8");
252
+ const fn = SOURCE.slice(SOURCE.indexOf("export async function reconcileRoutineDelivery"), SOURCE.indexOf("export async function routineDeliveryCensus"));
253
+ it("orders by session then start, so each run's ceiling is its successor's start", () => {
254
+ expect(fn).toContain("ORDER BY r.sessionKey, r.startedAt");
255
+ });
256
+ it("selects runs of EVERY status — a run can deliver before promotion sees it", () => {
257
+ expect(fn).not.toContain("r.status =");
258
+ });
259
+ it("requires a sessionKey, which is the join key onto the store", () => {
260
+ expect(fn).toContain("r.sessionKey IS NOT NULL");
261
+ });
262
+ });
263
+ // Task 2776, review findings — the two bounds that keep this pass from being a
264
+ // backfill and from re-reading the store once per run.
265
+ describe("reconcileRoutineDelivery — bounded work", () => {
266
+ it("reads the store ONCE per destination, not once per run", async () => {
267
+ // Every run against one chat shares a destination. Reading inside the loop
268
+ // parsed the same files once per run and discarded all but one result.
269
+ let reads = 0;
270
+ const driver = deliveryDriver([
271
+ run({ runId: "r1", startedAt: "2026-08-16T16:10:00Z" }),
272
+ run({ runId: "r2", startedAt: "2026-08-16T16:15:00Z" }),
273
+ run({ runId: "r3", startedAt: "2026-08-16T16:20:00Z" }),
274
+ ]);
275
+ await reconcileRoutineDelivery(driver, "acct", "/root", NOW, {
276
+ readRows: () => {
277
+ reads++;
278
+ return [];
279
+ },
280
+ });
281
+ expect(reads).toBe(1);
282
+ });
283
+ it("reads once per DISTINCT destination when runs span several chats", async () => {
284
+ const seen = [];
285
+ const driver = deliveryDriver([
286
+ run({ runId: "r1", sessionKey: "s1", target: "telegram:111" }),
287
+ run({ runId: "r2", sessionKey: "s2", target: "telegram:222" }),
288
+ run({ runId: "r3", sessionKey: "s3", target: "telegram:111" }),
289
+ ]);
290
+ await reconcileRoutineDelivery(driver, "acct", "/root", NOW, {
291
+ readRows: (_r, _a, dest) => {
292
+ seen.push(dest);
293
+ return [];
294
+ },
295
+ });
296
+ expect(seen.sort()).toEqual(["111", "222"]);
297
+ });
298
+ it("bounds the selection by the delivery window, so it is not a backfill", async () => {
299
+ const driver = deliveryDriver([run()]);
300
+ let bound;
301
+ const spy = driver.session;
302
+ driver.session = () => {
303
+ const s = spy();
304
+ const inner = s.run;
305
+ s.run = (async (q, params) => {
306
+ if (typeof params.since === "string")
307
+ bound = params.since;
308
+ return inner(q, params);
309
+ });
310
+ return s;
311
+ };
312
+ await reconcileRoutineDelivery(driver, "acct", "/root", NOW, { readRows: () => [] });
313
+ expect(bound).toBe(new Date(NOW - DELIVERY_WINDOW_HOURS * 3600_000).toISOString());
314
+ });
315
+ it("uses the SAME window as the census, so the census cannot outrun it", () => {
316
+ // A reconciliation window shorter than the census's would report as silent a
317
+ // run this pass had already stopped trying to stamp — an alarm about the
318
+ // checker rather than about the routine.
319
+ const SOURCE = readFileSync(join(__dirname, "../routine-run.ts"), "utf-8");
320
+ const reconcile = SOURCE.slice(SOURCE.indexOf("export async function reconcileRoutineDelivery"), SOURCE.indexOf("export async function routineDeliveryCensus"));
321
+ const census = SOURCE.slice(SOURCE.indexOf("export async function routineDeliveryCensus"));
322
+ expect(reconcile).toContain("DELIVERY_WINDOW_HOURS");
323
+ expect(census).toContain("DELIVERY_WINDOW_HOURS");
324
+ expect(reconcile).toContain("r.startedAt >= $since");
325
+ });
326
+ });
327
+ //# sourceMappingURL=routine-delivery-reconcile.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routine-delivery-reconcile.test.js","sourceRoot":"","sources":["../../../src/lib/__tests__/routine-delivery-reconcile.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=routine-delivery.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routine-delivery.test.d.ts","sourceRoot":"","sources":["../../../src/lib/__tests__/routine-delivery.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,130 @@
1
+ // @vitest-environment node
2
+ /**
3
+ * Task 2776 — the store join that establishes a routine's delivery.
4
+ */
5
+ import { describe, expect, it, beforeEach, afterEach, vi } from "vitest";
6
+ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
7
+ import { tmpdir } from "node:os";
8
+ import { join } from "node:path";
9
+ import { selectDeliveredRows, readAgentRowsForDestination, } from "../routine-delivery.js";
10
+ const SK = "session-abc";
11
+ const row = (messageId, sessionId, createdAt, body = "hi") => ({
12
+ messageId,
13
+ sessionId,
14
+ createdAt,
15
+ body,
16
+ });
17
+ describe("selectDeliveredRows", () => {
18
+ it("keeps a row on this session inside the window", () => {
19
+ expect(selectDeliveredRows([row("a", SK, "2026-08-16T16:10:05Z")], SK, "2026-08-16T16:10:00Z", "2026-08-16T16:15:00Z")).toHaveLength(1);
20
+ });
21
+ it("drops a row belonging to another session on the same conversation", () => {
22
+ // The operator's own traffic shares this conversation. Without the session
23
+ // match, a routine that has been dead for a week would be credited with the
24
+ // operator's own answers.
25
+ expect(selectDeliveredRows([row("a", "other", "2026-08-16T16:10:05Z")], SK, "2026-08-16T16:10:00Z", null))
26
+ .toEqual([]);
27
+ });
28
+ it("drops a row written before the run started", () => {
29
+ expect(selectDeliveredRows([row("a", SK, "2026-08-16T16:09:59Z")], SK, "2026-08-16T16:10:00Z", null))
30
+ .toEqual([]);
31
+ });
32
+ it("drops a row at the ceiling — it belongs to the next run", () => {
33
+ // Half-open at the top, for the reason promoteRoutineRuns bounds its own
34
+ // window: without it run N and run N+1 are both credited with one answer.
35
+ expect(selectDeliveredRows([row("a", SK, "2026-08-16T16:15:00Z")], SK, "2026-08-16T16:10:00Z", "2026-08-16T16:15:00Z")).toEqual([]);
36
+ });
37
+ it("keeps a row exactly at the run's start instant", () => {
38
+ expect(selectDeliveredRows([row("a", SK, "2026-08-16T16:10:00Z")], SK, "2026-08-16T16:10:00Z", null))
39
+ .toHaveLength(1);
40
+ });
41
+ it("keeps a late row when the window is still open", () => {
42
+ // Null ceiling means this is the newest run on the session: its window runs
43
+ // to now, so a slow turn is still attributed rather than read as silence.
44
+ expect(selectDeliveredRows([row("a", SK, "2026-08-16T23:00:00Z")], SK, "2026-08-16T16:10:00Z", null))
45
+ .toHaveLength(1);
46
+ });
47
+ it("returns rows oldest first, so the first is the delivery", () => {
48
+ const rows = [row("b", SK, "2026-08-16T16:10:09Z"), row("a", SK, "2026-08-16T16:10:05Z")];
49
+ expect(selectDeliveredRows(rows, SK, "2026-08-16T16:10:00Z", null).map((r) => r.messageId))
50
+ .toEqual(["a", "b"]);
51
+ });
52
+ it("returns [] for a run whose session sent nothing", () => {
53
+ expect(selectDeliveredRows([], SK, "2026-08-16T16:10:00Z", null)).toEqual([]);
54
+ });
55
+ });
56
+ describe("readAgentRowsForDestination", () => {
57
+ const ACCT = "acct-1";
58
+ let root;
59
+ let dir;
60
+ const agentRow = (messageId, sessionId) => JSON.stringify({
61
+ messageId,
62
+ sessionId,
63
+ createdAt: "2026-08-16T16:10:05Z",
64
+ body: "sent",
65
+ origin: "agent",
66
+ fromMe: true,
67
+ });
68
+ beforeEach(() => {
69
+ root = mkdtempSync(join(tmpdir(), "store-"));
70
+ vi.stubEnv("MAXY_DIR_OVERRIDE", root);
71
+ dir = join(root, "data", "telegram-messages", ACCT);
72
+ mkdirSync(dir, { recursive: true });
73
+ const inbound = JSON.stringify({
74
+ messageId: "in-1",
75
+ sessionId: SK,
76
+ createdAt: "2026-08-16T16:10:01Z",
77
+ body: "asked",
78
+ origin: "inbound",
79
+ fromMe: false,
80
+ });
81
+ writeFileSync(join(dir, "8711566959_8992591236.jsonl"), `${agentRow("out-1", SK)}\n${inbound}\n`);
82
+ writeFileSync(join(dir, "8804050655_8992591236.jsonl"), `${agentRow("out-2", SK)}\n`);
83
+ writeFileSync(join(dir, "8711566959_1311221864.jsonl"), `${agentRow("other-chat", SK)}\n`);
84
+ });
85
+ afterEach(() => {
86
+ vi.unstubAllEnvs();
87
+ rmSync(root, { recursive: true, force: true });
88
+ });
89
+ it("reads agent rows from EVERY bot's file for that destination", () => {
90
+ // Not just the expected carrier's file: a routine whose carrier moved
91
+ // delivered to a real chat on the wrong bot, and reading one file would
92
+ // record that as silence.
93
+ expect(readAgentRowsForDestination("/unused", ACCT, "8992591236").map((r) => r.messageId).sort())
94
+ .toEqual(["out-1", "out-2"]);
95
+ });
96
+ it("excludes inbound rows", () => {
97
+ expect(readAgentRowsForDestination("/unused", ACCT, "8992591236").map((r) => r.messageId))
98
+ .not.toContain("in-1");
99
+ });
100
+ it("excludes another conversation's rows", () => {
101
+ expect(readAgentRowsForDestination("/unused", ACCT, "8992591236").map((r) => r.messageId))
102
+ .not.toContain("other-chat");
103
+ });
104
+ it("does not match a destination that is only a suffix of another chat id", () => {
105
+ // `_1221864.jsonl` must not match the file for chat 1311221864.
106
+ writeFileSync(join(dir, "8711566959_11311221864.jsonl"), `${agentRow("longer", SK)}\n`);
107
+ expect(readAgentRowsForDestination("/unused", ACCT, "1311221864").map((r) => r.messageId))
108
+ .toEqual(["other-chat"]);
109
+ });
110
+ it("returns [] for an account with no store directory", () => {
111
+ expect(readAgentRowsForDestination("/unused", "absent-acct", "8992591236")).toEqual([]);
112
+ });
113
+ it("skips a malformed line rather than throwing", () => {
114
+ // The window is a byte range of a file being appended to, so a partial
115
+ // trailing line is normal, not exceptional.
116
+ writeFileSync(join(dir, "8711566959_8992591236.jsonl"), `{not json\n${agentRow("out-1", SK)}\n{"partial`);
117
+ expect(readAgentRowsForDestination("/unused", ACCT, "8992591236").map((r) => r.messageId).sort())
118
+ .toEqual(["out-1", "out-2"]);
119
+ });
120
+ it("skips a row missing the fields the join reads", () => {
121
+ writeFileSync(join(dir, "8711566959_8992591236.jsonl"), `${JSON.stringify({ messageId: "no-session", createdAt: "x", origin: "agent", fromMe: true })}\n`);
122
+ expect(readAgentRowsForDestination("/unused", ACCT, "8992591236").map((r) => r.messageId))
123
+ .toEqual(["out-2"]);
124
+ });
125
+ it("never throws on an unreadable store root", () => {
126
+ vi.stubEnv("MAXY_DIR_OVERRIDE", join(root, "does", "not", "exist"));
127
+ expect(() => readAgentRowsForDestination("/unused", ACCT, "8992591236")).not.toThrow();
128
+ });
129
+ });
130
+ //# sourceMappingURL=routine-delivery.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routine-delivery.test.js","sourceRoot":"","sources":["../../../src/lib/__tests__/routine-delivery.test.ts"],"names":[],"mappings":"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"}