@rubytech/create-maxy-code 0.1.386 → 0.1.387

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 (110) hide show
  1. package/dist/__tests__/cron-heartbeat-registration.test.js +5 -0
  2. package/dist/cron-registration.js +1 -0
  3. package/dist/index.js +4 -0
  4. package/package.json +1 -1
  5. package/payload/platform/neo4j/schema.cypher +36 -1
  6. package/payload/platform/plugins/.claude-plugin/marketplace.json +5 -0
  7. package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +61 -1
  8. package/payload/platform/plugins/docs/references/graph.md +11 -0
  9. package/payload/platform/plugins/docs/references/plugins-guide.md +1 -0
  10. package/payload/platform/plugins/docs/references/sweep-guide.md +43 -0
  11. package/payload/platform/plugins/memory/references/schema-estate-agent.md +34 -0
  12. package/payload/platform/plugins/scheduling/PLUGIN.md +17 -1
  13. package/payload/platform/plugins/scheduling/mcp/dist/index.js +12 -0
  14. package/payload/platform/plugins/scheduling/mcp/dist/index.js.map +1 -1
  15. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/agent-dispatch.test.d.ts +2 -0
  16. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/agent-dispatch.test.d.ts.map +1 -0
  17. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/agent-dispatch.test.js +101 -0
  18. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/agent-dispatch.test.js.map +1 -0
  19. package/payload/platform/plugins/scheduling/mcp/dist/lib/agent-dispatch.d.ts +54 -0
  20. package/payload/platform/plugins/scheduling/mcp/dist/lib/agent-dispatch.d.ts.map +1 -0
  21. package/payload/platform/plugins/scheduling/mcp/dist/lib/agent-dispatch.js +117 -0
  22. package/payload/platform/plugins/scheduling/mcp/dist/lib/agent-dispatch.js.map +1 -0
  23. package/payload/platform/plugins/scheduling/mcp/dist/lib/schedule-classify.d.ts +7 -0
  24. package/payload/platform/plugins/scheduling/mcp/dist/lib/schedule-classify.d.ts.map +1 -1
  25. package/payload/platform/plugins/scheduling/mcp/dist/lib/schedule-classify.js +6 -3
  26. package/payload/platform/plugins/scheduling/mcp/dist/lib/schedule-classify.js.map +1 -1
  27. package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/agent-turn-dispatch.test.d.ts +2 -0
  28. package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/agent-turn-dispatch.test.d.ts.map +1 -0
  29. package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/agent-turn-dispatch.test.js +88 -0
  30. package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/agent-turn-dispatch.test.js.map +1 -0
  31. package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/standing-audit.test.d.ts +2 -0
  32. package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/standing-audit.test.d.ts.map +1 -0
  33. package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/standing-audit.test.js +77 -0
  34. package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/standing-audit.test.js.map +1 -0
  35. package/payload/platform/plugins/scheduling/mcp/dist/scripts/agent-turn-dispatch.d.ts +59 -0
  36. package/payload/platform/plugins/scheduling/mcp/dist/scripts/agent-turn-dispatch.d.ts.map +1 -0
  37. package/payload/platform/plugins/scheduling/mcp/dist/scripts/agent-turn-dispatch.js +131 -0
  38. package/payload/platform/plugins/scheduling/mcp/dist/scripts/agent-turn-dispatch.js.map +1 -0
  39. package/payload/platform/plugins/scheduling/mcp/dist/scripts/check-due-events.js +62 -15
  40. package/payload/platform/plugins/scheduling/mcp/dist/scripts/check-due-events.js.map +1 -1
  41. package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-event-agent.test.d.ts +2 -0
  42. package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-event-agent.test.d.ts.map +1 -0
  43. package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-event-agent.test.js +110 -0
  44. package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-event-agent.test.js.map +1 -0
  45. package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-get-agent.test.d.ts +2 -0
  46. package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-get-agent.test.d.ts.map +1 -0
  47. package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-get-agent.test.js +35 -0
  48. package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-get-agent.test.js.map +1 -0
  49. package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-update-agent.test.d.ts +2 -0
  50. package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-update-agent.test.d.ts.map +1 -0
  51. package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-update-agent.test.js +81 -0
  52. package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-update-agent.test.js.map +1 -0
  53. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-event.d.ts +7 -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 +21 -4
  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 +4 -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 +6 -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 +2 -1
  62. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-update.d.ts.map +1 -1
  63. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-update.js +32 -1
  64. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-update.js.map +1 -1
  65. package/payload/platform/plugins/sweep/.claude-plugin/plugin.json +8 -0
  66. package/payload/platform/plugins/sweep/PLUGIN.md +15 -0
  67. package/payload/platform/plugins/sweep/skills/sweep-workflow/SKILL.md +113 -0
  68. package/payload/platform/plugins/whatsapp/PLUGIN.md +1 -1
  69. package/payload/platform/plugins/whatsapp/mcp/dist/index.js +19 -4
  70. package/payload/platform/plugins/whatsapp/mcp/dist/index.js.map +1 -1
  71. package/payload/platform/plugins/whatsapp/references/channels-whatsapp.md +7 -4
  72. package/payload/platform/plugins/whatsapp/skills/manage-whatsapp-config/SKILL.md +10 -0
  73. package/payload/platform/scripts/seed-neo4j.sh +38 -0
  74. package/payload/platform/services/whatsapp-channel/dist/notification.d.ts +5 -0
  75. package/payload/platform/services/whatsapp-channel/dist/notification.d.ts.map +1 -1
  76. package/payload/platform/services/whatsapp-channel/dist/notification.js.map +1 -1
  77. package/payload/platform/services/whatsapp-channel/dist/server.js +1 -1
  78. package/payload/platform/services/whatsapp-channel/dist/server.js.map +1 -1
  79. package/payload/server/public/assets/{AdminLoginScreens-oxqHKL3C.js → AdminLoginScreens-BejIjbmU.js} +1 -1
  80. package/payload/server/public/assets/{AdminShell-DBD6ruS1.js → AdminShell-D2-uBSB5.js} +1 -1
  81. package/payload/server/public/assets/{Checkbox-yr8-ftrE.js → Checkbox-1F1tzqca.js} +1 -1
  82. package/payload/server/public/assets/Transcript-DkGa4CQH.js +2 -0
  83. package/payload/server/public/assets/{admin-CQfZnuFy.js → admin-DqQARkjy.js} +1 -1
  84. package/payload/server/public/assets/{browser-DbdPeDK1.js → browser-nDtEK6RC.js} +1 -1
  85. package/payload/server/public/assets/{calendar-CFdOhe7Q.js → calendar-CO4Bwmho.js} +1 -1
  86. package/payload/server/public/assets/chat-DeIge_bC.js +1 -0
  87. package/payload/server/public/assets/chevron-left-DhVdq0aN.js +1 -0
  88. package/payload/server/public/assets/data-B1GIdzHk.js +1 -0
  89. package/payload/server/public/assets/{graph-Bluocsf_.js → graph-DFyicKd7.js} +1 -1
  90. package/payload/server/public/assets/{graph-labels-BcLJmY-v.js → graph-labels-D3BQdcpD.js} +1 -1
  91. package/payload/server/public/assets/{operator-DjK3MBSS.js → operator-BxrjWdT9.js} +1 -1
  92. package/payload/server/public/assets/{page-DQNYTb2d.js → page-ByDLq_o1.js} +1 -1
  93. package/payload/server/public/assets/{page-zJQTRXB2.js → page-D-Ep4bXd.js} +1 -1
  94. package/payload/server/public/assets/{public-CHCniosp.js → public-DbdqfdYq.js} +1 -1
  95. package/payload/server/public/assets/{rotate-ccw-JDfwjfIL.js → rotate-ccw-BkX8HODe.js} +1 -1
  96. package/payload/server/public/assets/{useSubAccountSwitcher-CfI3J2IX.css → useSubAccountSwitcher-DS0iqSkP.css} +1 -1
  97. package/payload/server/public/browser.html +4 -4
  98. package/payload/server/public/calendar.html +5 -5
  99. package/payload/server/public/chat.html +8 -8
  100. package/payload/server/public/data.html +7 -7
  101. package/payload/server/public/graph.html +8 -8
  102. package/payload/server/public/index.html +10 -10
  103. package/payload/server/public/operator.html +10 -10
  104. package/payload/server/public/public.html +8 -8
  105. package/payload/server/server.js +551 -271
  106. package/payload/server/public/assets/Transcript-FX_w6no6.js +0 -2
  107. package/payload/server/public/assets/chat-BunxcNDk.js +0 -1
  108. package/payload/server/public/assets/chevron-left-Cngmu0Py.js +0 -1
  109. package/payload/server/public/assets/data-IqZlMSwG.js +0 -1
  110. /package/payload/server/public/assets/{useSubAccountSwitcher-BpQ3oKiJ.js → useSubAccountSwitcher-DMbRhLPv.js} +0 -0
@@ -0,0 +1,54 @@
1
+ export type AgentChannel = "whatsapp" | "telegram";
2
+ export type DestinationValidity = {
3
+ valid: true;
4
+ } | {
5
+ valid: false;
6
+ reason: "destination-not-admin";
7
+ };
8
+ /**
9
+ * Replica of `normalize.ts:normalizeE164` — strip whitespace/dashes/parens, a
10
+ * `whatsapp:` prefix, and a leading `+` or `00`; return digits-only or `''`.
11
+ */
12
+ export declare function normalizeE164(value: string): string;
13
+ /** Replica of `normalize.ts:phonesMatch` — normalized equality, non-empty. */
14
+ export declare function phonesMatch(a: string, b: string): boolean;
15
+ interface WhatsAppAdmins {
16
+ adminPhones: string[];
17
+ accountManagers: Record<string, string>;
18
+ }
19
+ /** Read the channel admin config from `<accountDir>/account.json`. Missing or
20
+ * malformed config yields empty lists (a fail-closed default: no destination
21
+ * validates against empty config). */
22
+ export declare function readChannelAdmins(accountDir: string, channel: "whatsapp"): WhatsAppAdmins;
23
+ export declare function readChannelAdmins(accountDir: string, channel: "telegram"): {
24
+ adminUsers: number[];
25
+ };
26
+ /**
27
+ * A destination is valid iff it is a registered admin or account-manager on
28
+ * that channel:
29
+ * - whatsapp: `phonesMatch` against any `adminPhones` entry OR any
30
+ * `accountManagers` key.
31
+ * - telegram: an integer chat id present in `telegram.adminUsers`.
32
+ */
33
+ export declare function isValidAgentDestination(accountDir: string, channel: AgentChannel, destination: string): DestinationValidity;
34
+ /**
35
+ * Validate an `agentDispatch` at write time (shared by `schedule-event` and
36
+ * `schedule-update` so the two write paths cannot drift on the rule): reject an
37
+ * empty prompt, and reject a destination that is not a registered admin or
38
+ * account-manager on the channel. Throws with a caller-facing message; returns
39
+ * on success. `accountId` is the event's account; its `account.json` under
40
+ * `$PLATFORM_ROOT/../data/accounts/<accountId>` holds the channel admin config.
41
+ */
42
+ export declare function validateAgentDispatch(accountId: string, agentDispatch: {
43
+ channel: AgentChannel;
44
+ destination: string;
45
+ prompt: string;
46
+ }): void;
47
+ /**
48
+ * The sub-account a WhatsApp manager destination is bound to, or null. Telegram
49
+ * has no account-manager surface (house-only, Task 1378 scope), so it always
50
+ * returns null. Used by the standing audit to detect a deleted sub-account.
51
+ */
52
+ export declare function boundSubAccountFor(accountDir: string, destination: string): string | null;
53
+ export {};
54
+ //# sourceMappingURL=agent-dispatch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-dispatch.d.ts","sourceRoot":"","sources":["../../src/lib/agent-dispatch.ts"],"names":[],"mappings":"AAcA,MAAM,MAAM,YAAY,GAAG,UAAU,GAAG,UAAU,CAAC;AAEnD,MAAM,MAAM,mBAAmB,GAC3B;IAAE,KAAK,EAAE,IAAI,CAAA;CAAE,GACf;IAAE,KAAK,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,uBAAuB,CAAA;CAAE,CAAC;AAEtD;;;GAGG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAOnD;AAED,8EAA8E;AAC9E,wBAAgB,WAAW,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAIzD;AAED,UAAU,cAAc;IACtB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACzC;AAED;;uCAEuC;AACvC,wBAAgB,iBAAiB,CAC/B,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,UAAU,GAClB,cAAc,CAAC;AAClB,wBAAgB,iBAAiB,CAC/B,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,UAAU,GAClB;IAAE,UAAU,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC;AA8B5B;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CACrC,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,YAAY,EACrB,WAAW,EAAE,MAAM,GAClB,mBAAmB,CAWrB;AAED;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CACnC,SAAS,EAAE,MAAM,EACjB,aAAa,EAAE;IAAE,OAAO,EAAE,YAAY,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAC5E,IAAI,CAgBN;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAMzF"}
@@ -0,0 +1,117 @@
1
+ /**
2
+ * Task 1378 — agent-bearing scheduled dispatch: destination validation.
3
+ *
4
+ * The scheduling MCP cannot import `platform/ui` (plugin boundary), so this
5
+ * module replicates the minimal E.164 matching from
6
+ * `platform/ui/app/lib/whatsapp/normalize.ts` and reads the channel admin
7
+ * config straight out of the account's `account.json`. One implementation is
8
+ * shared by `schedule-event`, `schedule-update`, and the dispatcher's standing
9
+ * audit so the "is this destination a valid admin/manager" decision cannot
10
+ * drift between the write path and the audit.
11
+ */
12
+ import { readFileSync } from "node:fs";
13
+ import { resolve } from "node:path";
14
+ /**
15
+ * Replica of `normalize.ts:normalizeE164` — strip whitespace/dashes/parens, a
16
+ * `whatsapp:` prefix, and a leading `+` or `00`; return digits-only or `''`.
17
+ */
18
+ export function normalizeE164(value) {
19
+ let candidate = value.trim().replace(/[\s\-().]/g, "");
20
+ candidate = candidate.replace(/^whatsapp:/i, "");
21
+ if (candidate.startsWith("+"))
22
+ candidate = candidate.slice(1);
23
+ else if (candidate.startsWith("00"))
24
+ candidate = candidate.slice(2);
25
+ if (!/^\d+$/.test(candidate))
26
+ return "";
27
+ return candidate;
28
+ }
29
+ /** Replica of `normalize.ts:phonesMatch` — normalized equality, non-empty. */
30
+ export function phonesMatch(a, b) {
31
+ const na = normalizeE164(a);
32
+ const nb = normalizeE164(b);
33
+ return na.length > 0 && na === nb;
34
+ }
35
+ export function readChannelAdmins(accountDir, channel) {
36
+ let config = {};
37
+ try {
38
+ config = JSON.parse(readFileSync(resolve(accountDir, "account.json"), "utf-8"));
39
+ }
40
+ catch {
41
+ config = {};
42
+ }
43
+ if (channel === "whatsapp") {
44
+ const wa = config.whatsapp ?? {};
45
+ const adminPhones = Array.isArray(wa.adminPhones)
46
+ ? wa.adminPhones.filter((p) => typeof p === "string")
47
+ : [];
48
+ const managersRaw = wa.accountManagers ?? {};
49
+ const accountManagers = {};
50
+ for (const [k, v] of Object.entries(managersRaw)) {
51
+ if (typeof v === "string")
52
+ accountManagers[k] = v;
53
+ }
54
+ return { adminPhones, accountManagers };
55
+ }
56
+ const tg = config.telegram ?? {};
57
+ const adminUsers = Array.isArray(tg.adminUsers)
58
+ ? tg.adminUsers.filter((u) => typeof u === "number")
59
+ : [];
60
+ return { adminUsers };
61
+ }
62
+ /**
63
+ * A destination is valid iff it is a registered admin or account-manager on
64
+ * that channel:
65
+ * - whatsapp: `phonesMatch` against any `adminPhones` entry OR any
66
+ * `accountManagers` key.
67
+ * - telegram: an integer chat id present in `telegram.adminUsers`.
68
+ */
69
+ export function isValidAgentDestination(accountDir, channel, destination) {
70
+ if (channel === "whatsapp") {
71
+ const { adminPhones, accountManagers } = readChannelAdmins(accountDir, "whatsapp");
72
+ const inAdmins = adminPhones.some((p) => phonesMatch(p, destination));
73
+ const inManagers = Object.keys(accountManagers).some((k) => phonesMatch(k, destination));
74
+ return inAdmins || inManagers ? { valid: true } : { valid: false, reason: "destination-not-admin" };
75
+ }
76
+ const { adminUsers } = readChannelAdmins(accountDir, "telegram");
77
+ const asNumber = Number(destination);
78
+ const ok = Number.isInteger(asNumber) && adminUsers.includes(asNumber);
79
+ return ok ? { valid: true } : { valid: false, reason: "destination-not-admin" };
80
+ }
81
+ /**
82
+ * Validate an `agentDispatch` at write time (shared by `schedule-event` and
83
+ * `schedule-update` so the two write paths cannot drift on the rule): reject an
84
+ * empty prompt, and reject a destination that is not a registered admin or
85
+ * account-manager on the channel. Throws with a caller-facing message; returns
86
+ * on success. `accountId` is the event's account; its `account.json` under
87
+ * `$PLATFORM_ROOT/../data/accounts/<accountId>` holds the channel admin config.
88
+ */
89
+ export function validateAgentDispatch(accountId, agentDispatch) {
90
+ const { channel, destination, prompt } = agentDispatch;
91
+ if (!prompt || prompt.trim().length === 0) {
92
+ throw new Error("agentDispatch requires a non-empty 'prompt'");
93
+ }
94
+ const platformRoot = process.env.PLATFORM_ROOT;
95
+ if (!platformRoot) {
96
+ throw new Error("PLATFORM_ROOT environment variable is required to validate the agentDispatch destination");
97
+ }
98
+ const accountDir = resolve(platformRoot, "..", "data/accounts", accountId);
99
+ const check = isValidAgentDestination(accountDir, channel, destination);
100
+ if (!check.valid) {
101
+ throw new Error(`agentDispatch destination "${destination}" is not a registered admin/account-manager on ${channel} — reason=${check.reason}`);
102
+ }
103
+ }
104
+ /**
105
+ * The sub-account a WhatsApp manager destination is bound to, or null. Telegram
106
+ * has no account-manager surface (house-only, Task 1378 scope), so it always
107
+ * returns null. Used by the standing audit to detect a deleted sub-account.
108
+ */
109
+ export function boundSubAccountFor(accountDir, destination) {
110
+ const { accountManagers } = readChannelAdmins(accountDir, "whatsapp");
111
+ for (const [managerPhone, subAccountId] of Object.entries(accountManagers)) {
112
+ if (subAccountId && phonesMatch(managerPhone, destination))
113
+ return subAccountId;
114
+ }
115
+ return null;
116
+ }
117
+ //# sourceMappingURL=agent-dispatch.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-dispatch.js","sourceRoot":"","sources":["../../src/lib/agent-dispatch.ts"],"names":[],"mappings":"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"}
@@ -10,6 +10,13 @@ export interface ClassifyInput {
10
10
  tool: string;
11
11
  args?: Record<string, unknown>;
12
12
  };
13
+ /** Task 1378 — an agent-bearing dispatch is a scheduler primitive, never a
14
+ * meeting: like `action`, its presence forces the :Event bucket. */
15
+ agentDispatch?: {
16
+ channel: "whatsapp" | "telegram";
17
+ destination: string;
18
+ prompt: string;
19
+ };
13
20
  /** startDate is always present (required); endDate present means a bounded slot. */
14
21
  endDate?: string;
15
22
  attendees?: ScheduleAttendee[];
@@ -1 +1 @@
1
- {"version":3,"file":"schedule-classify.d.ts","sourceRoot":"","sources":["../../src/lib/schedule-classify.ts"],"names":[],"mappings":"AAiBA,MAAM,WAAW,gBAAgB;IAC/B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,aAAa;IAC5B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,CAAC;IAC1E,oFAAoF;IACpF,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,gBAAgB,EAAE,CAAC;CAChC;AAED,MAAM,MAAM,aAAa,GAAG,OAAO,GAAG,SAAS,CAAC;AAEhD,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,aAAa,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;CAChB;AAeD,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,aAAa,GAAG,cAAc,CAM1E"}
1
+ {"version":3,"file":"schedule-classify.d.ts","sourceRoot":"","sources":["../../src/lib/schedule-classify.ts"],"names":[],"mappings":"AAkBA,MAAM,WAAW,gBAAgB;IAC/B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,aAAa;IAC5B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,CAAC;IAC1E;yEACqE;IACrE,aAAa,CAAC,EAAE;QAAE,OAAO,EAAE,UAAU,GAAG,UAAU,CAAC;QAAC,WAAW,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAC1F,oFAAoF;IACpF,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,gBAAgB,EAAE,CAAC;CAChC;AAED,MAAM,MAAM,aAAa,GAAG,OAAO,GAAG,SAAS,CAAC;AAEhD,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,aAAa,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;CAChB;AAeD,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,aAAa,GAAG,cAAc,CAO1E"}
@@ -8,9 +8,10 @@
8
8
  //
9
9
  // 1. recurrence present -> Event (recurring trigger)
10
10
  // 2. action present -> Event (action dispatcher)
11
- // 3. a resolvable counterparty -> Meeting (call/appointment with someone)
12
- // 4. bounded slot (endDate present) -> Meeting (a time-blocked appointment)
13
- // 5. otherwise -> Event (one-time reminder)
11
+ // 3. agentDispatch present -> Event (agent-turn dispatcher)
12
+ // 4. a resolvable counterparty -> Meeting (call/appointment with someone)
13
+ // 5. bounded slot (endDate present) -> Meeting (a time-blocked appointment)
14
+ // 6. otherwise -> Event (one-time reminder)
14
15
  //
15
16
  // The `reason` is emitted in the observability line so a mis-bucketed write is
16
17
  // greppable without reading the graph.
@@ -29,6 +30,8 @@ export function classifyScheduleWrite(input) {
29
30
  return { label: "Event", reason: "recurrence" };
30
31
  if (input.action)
31
32
  return { label: "Event", reason: "action" };
33
+ if (input.agentDispatch)
34
+ return { label: "Event", reason: "agent-dispatch" };
32
35
  if (hasResolvableCounterparty(input.attendees))
33
36
  return { label: "Meeting", reason: "counterparty" };
34
37
  if (input.endDate)
@@ -1 +1 @@
1
- {"version":3,"file":"schedule-classify.js","sourceRoot":"","sources":["../../src/lib/schedule-classify.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,wEAAwE;AACxE,gFAAgF;AAChF,mFAAmF;AACnF,+EAA+E;AAC/E,2EAA2E;AAC3E,6EAA6E;AAC7E,EAAE;AACF,oEAAoE;AACpE,oEAAoE;AACpE,iFAAiF;AACjF,8EAA8E;AAC9E,qEAAqE;AACrE,EAAE;AACF,+EAA+E;AAC/E,uCAAuC;AAuBvC;;;;;;GAMG;AACH,SAAS,yBAAyB,CAAC,SAAyC;IAC1E,OAAO,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC,IAAI,CAC3B,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC,CAC1F,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,qBAAqB,CAAC,KAAoB;IACxD,IAAI,KAAK,CAAC,UAAU;QAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,CAAC;IACtE,IAAI,KAAK,CAAC,MAAM;QAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC;IAC9D,IAAI,yBAAyB,CAAC,KAAK,CAAC,SAAS,CAAC;QAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,cAAc,EAAE,CAAC;IACpG,IAAI,KAAK,CAAC,OAAO;QAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC;IAClE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC;AAChD,CAAC"}
1
+ {"version":3,"file":"schedule-classify.js","sourceRoot":"","sources":["../../src/lib/schedule-classify.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,wEAAwE;AACxE,gFAAgF;AAChF,mFAAmF;AACnF,+EAA+E;AAC/E,2EAA2E;AAC3E,6EAA6E;AAC7E,EAAE;AACF,oEAAoE;AACpE,oEAAoE;AACpE,yEAAyE;AACzE,iFAAiF;AACjF,8EAA8E;AAC9E,qEAAqE;AACrE,EAAE;AACF,+EAA+E;AAC/E,uCAAuC;AA0BvC;;;;;;GAMG;AACH,SAAS,yBAAyB,CAAC,SAAyC;IAC1E,OAAO,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC,IAAI,CAC3B,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC,CAC1F,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,qBAAqB,CAAC,KAAoB;IACxD,IAAI,KAAK,CAAC,UAAU;QAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,CAAC;IACtE,IAAI,KAAK,CAAC,MAAM;QAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC;IAC9D,IAAI,KAAK,CAAC,aAAa;QAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,gBAAgB,EAAE,CAAC;IAC7E,IAAI,yBAAyB,CAAC,KAAK,CAAC,SAAS,CAAC;QAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,cAAc,EAAE,CAAC;IACpG,IAAI,KAAK,CAAC,OAAO;QAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC;IAClE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC;AAChD,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=agent-turn-dispatch.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-turn-dispatch.test.d.ts","sourceRoot":"","sources":["../../../src/scripts/__tests__/agent-turn-dispatch.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,88 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
2
+ import { chooseDispatchMode, dispatchAgentTurn } from "../agent-turn-dispatch.js";
3
+ describe("chooseDispatchMode", () => {
4
+ it("selects agent when the three agent fields are present", () => {
5
+ const m = chooseDispatchMode({
6
+ agentChannel: "whatsapp",
7
+ agentDestination: "+447504472444",
8
+ agentPrompt: "reply OK",
9
+ actionPlugin: null,
10
+ actionTool: null,
11
+ actionArgs: null,
12
+ });
13
+ expect(m).toEqual({ kind: "agent", channel: "whatsapp", destination: "+447504472444", prompt: "reply OK" });
14
+ });
15
+ it("selects action when action fields are present and no agent dispatch", () => {
16
+ const m = chooseDispatchMode({
17
+ agentChannel: null,
18
+ agentDestination: null,
19
+ agentPrompt: null,
20
+ actionPlugin: "memory",
21
+ actionTool: "memory-search",
22
+ actionArgs: null,
23
+ });
24
+ expect(m).toEqual({ kind: "action", plugin: "memory", tool: "memory-search", args: null });
25
+ });
26
+ it("selects none when neither is present", () => {
27
+ const m = chooseDispatchMode({
28
+ agentChannel: null,
29
+ agentDestination: null,
30
+ agentPrompt: null,
31
+ actionPlugin: null,
32
+ actionTool: null,
33
+ actionArgs: null,
34
+ });
35
+ expect(m).toEqual({ kind: "none" });
36
+ });
37
+ });
38
+ function fakeDriver() {
39
+ const runs = [];
40
+ return {
41
+ runs,
42
+ session: () => ({
43
+ run: vi.fn(async (query, params) => {
44
+ runs.push({ query, params });
45
+ return { records: [] };
46
+ }),
47
+ close: vi.fn(async () => { }),
48
+ }),
49
+ };
50
+ }
51
+ describe("dispatchAgentTurn", () => {
52
+ const originalFetch = globalThis.fetch;
53
+ beforeEach(() => {
54
+ process.env.MAXY_UI_INTERNAL_PORT = "19200";
55
+ });
56
+ afterEach(() => {
57
+ globalThis.fetch = originalFetch;
58
+ vi.clearAllMocks();
59
+ });
60
+ it("POSTs the inject route and records lastDispatchResult=accepted on 200", async () => {
61
+ const calls = [];
62
+ globalThis.fetch = vi.fn(async (url, init) => {
63
+ calls.push({ url, body: JSON.parse(init.body) });
64
+ return { ok: true, status: 200 };
65
+ });
66
+ const driver = fakeDriver();
67
+ await dispatchAgentTurn("evt-1", "whatsapp", "+447504472444", "reply OK", "acct-house", null, driver);
68
+ expect(calls).toHaveLength(1);
69
+ expect(calls[0].url).toBe("http://127.0.0.1:19200/api/channel/schedule-inject");
70
+ expect(calls[0].body).toEqual({
71
+ channel: "whatsapp",
72
+ accountId: "acct-house",
73
+ destination: "+447504472444",
74
+ prompt: "reply OK",
75
+ eventId: "evt-1",
76
+ });
77
+ const write = driver.runs.find((r) => r.query.includes("lastDispatchResult"));
78
+ expect(write?.params.lastDispatchResult).toBe("accepted");
79
+ });
80
+ it("records lastDispatchResult=error:404 on a non-ok response", async () => {
81
+ globalThis.fetch = vi.fn(async () => ({ ok: false, status: 404 }));
82
+ const driver = fakeDriver();
83
+ await dispatchAgentTurn("evt-2", "telegram", "12345678", "hello", "acct-house", null, driver);
84
+ const write = driver.runs.find((r) => r.query.includes("lastDispatchResult"));
85
+ expect(write?.params.lastDispatchResult).toBe("error:404");
86
+ });
87
+ });
88
+ //# sourceMappingURL=agent-turn-dispatch.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-turn-dispatch.test.js","sourceRoot":"","sources":["../../../src/scripts/__tests__/agent-turn-dispatch.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=standing-audit.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"standing-audit.test.d.ts","sourceRoot":"","sources":["../../../src/scripts/__tests__/standing-audit.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,77 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
2
+ import { mkdtempSync, rmSync, mkdirSync, writeFileSync } from "node:fs";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import { runStandingAudit } from "../agent-turn-dispatch.js";
6
+ function driverReturning(rows) {
7
+ return {
8
+ session: () => ({
9
+ run: vi.fn(async () => ({
10
+ records: rows.map((r) => ({ get: (k) => r[k] })),
11
+ })),
12
+ close: vi.fn(async () => { }),
13
+ }),
14
+ };
15
+ }
16
+ let accountsDir;
17
+ const HOUSE = "acct-house";
18
+ function seedHouse(waConfig) {
19
+ const dir = join(accountsDir, HOUSE);
20
+ mkdirSync(dir, { recursive: true });
21
+ writeFileSync(join(dir, "account.json"), JSON.stringify({ whatsapp: waConfig }), "utf-8");
22
+ }
23
+ let errSpy;
24
+ function auditLines() {
25
+ return errSpy.mock.calls
26
+ .map((c) => String(c[0]))
27
+ .filter((l) => l.includes("[schedule-audit]"));
28
+ }
29
+ beforeEach(() => {
30
+ accountsDir = mkdtempSync(join(tmpdir(), "audit-accounts-"));
31
+ errSpy = vi.spyOn(console, "error").mockImplementation(() => { });
32
+ });
33
+ afterEach(() => {
34
+ rmSync(accountsDir, { recursive: true, force: true });
35
+ errSpy.mockRestore();
36
+ vi.clearAllMocks();
37
+ });
38
+ describe("runStandingAudit", () => {
39
+ it("emits destination-not-admin when the destination left adminPhones", async () => {
40
+ seedHouse({ adminPhones: ["+447111111111"] }); // destination not present
41
+ const driver = driverReturning([
42
+ { eventId: "e1", accountId: HOUSE, agentChannel: "whatsapp", agentDestination: "+447504472444" },
43
+ ]);
44
+ await runStandingAudit(driver, [HOUSE], accountsDir);
45
+ expect(auditLines()).toEqual([
46
+ expect.stringContaining("op=stale-dispatch eventId=e1 reason=destination-not-admin"),
47
+ ]);
48
+ });
49
+ it("emits sub-account-missing when a manager binding points at a deleted sub-account", async () => {
50
+ seedHouse({ accountManagers: { "447504472444": "sub-gone" } }); // sub dir never created
51
+ const driver = driverReturning([
52
+ { eventId: "e2", accountId: HOUSE, agentChannel: "whatsapp", agentDestination: "+447504472444" },
53
+ ]);
54
+ await runStandingAudit(driver, [HOUSE], accountsDir);
55
+ expect(auditLines()).toEqual([
56
+ expect.stringContaining("op=stale-dispatch eventId=e2 reason=sub-account-missing"),
57
+ ]);
58
+ });
59
+ it("emits nothing for a healthy binding (manager + present sub-account)", async () => {
60
+ seedHouse({ accountManagers: { "447504472444": "sub-ok" } });
61
+ mkdirSync(join(accountsDir, "sub-ok"), { recursive: true });
62
+ const driver = driverReturning([
63
+ { eventId: "e3", accountId: HOUSE, agentChannel: "whatsapp", agentDestination: "+447504472444" },
64
+ ]);
65
+ await runStandingAudit(driver, [HOUSE], accountsDir);
66
+ expect(auditLines()).toEqual([]);
67
+ });
68
+ it("emits nothing for a healthy plain admin destination", async () => {
69
+ seedHouse({ adminPhones: ["+447504472444"] });
70
+ const driver = driverReturning([
71
+ { eventId: "e4", accountId: HOUSE, agentChannel: "whatsapp", agentDestination: "+447504472444" },
72
+ ]);
73
+ await runStandingAudit(driver, [HOUSE], accountsDir);
74
+ expect(auditLines()).toEqual([]);
75
+ });
76
+ });
77
+ //# sourceMappingURL=standing-audit.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"standing-audit.test.js","sourceRoot":"","sources":["../../../src/scripts/__tests__/standing-audit.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Task 1378 — agent-bearing scheduled dispatch.
3
+ *
4
+ * When a due `:Event` carries an agent dispatch (`agentChannel`/
5
+ * `agentDestination`/`agentPrompt`) instead of a tool-only `action`, the
6
+ * heartbeat POSTs the prompt to the UI server's loopback inject route rather
7
+ * than spawning an MCP tool. The route builds a synthetic admin inbound so the
8
+ * normal channel spawn/resume/reply machine runs the turn. This module owns the
9
+ * routing decision and the POST + node-recording, kept out of the script's
10
+ * `main()` so it is unit-testable without executing the heartbeat.
11
+ */
12
+ import { type Driver } from "neo4j-driver";
13
+ export type DispatchMode = {
14
+ kind: "agent";
15
+ channel: string;
16
+ destination: string;
17
+ prompt: string;
18
+ } | {
19
+ kind: "action";
20
+ plugin: string;
21
+ tool: string;
22
+ args: string | null;
23
+ } | {
24
+ kind: "none";
25
+ };
26
+ /**
27
+ * Decide how a due event dispatches from its selected node fields. An event
28
+ * carries at most one mode (enforced at write); agent dispatch takes precedence
29
+ * so a malformed both-set node still routes deterministically.
30
+ */
31
+ export declare function chooseDispatchMode(fields: {
32
+ agentChannel: string | null;
33
+ agentDestination: string | null;
34
+ agentPrompt: string | null;
35
+ actionPlugin: string | null;
36
+ actionTool: string | null;
37
+ actionArgs: string | null;
38
+ }): DispatchMode;
39
+ /**
40
+ * Fire an agent-bearing dispatch: POST the prompt to the UI server's loopback
41
+ * inject route and record the outcome on the `:Event` node exactly as
42
+ * `dispatchAction` does (`lastDispatchResult`/`lastDispatchAt` + a note). On the
43
+ * recurring path `nextRun` has already been advanced by the caller; it is
44
+ * passed here only for the log line. Never throws — a POST/Neo4j failure is
45
+ * recorded, not propagated, so one event never aborts the sweep.
46
+ */
47
+ export declare function dispatchAgentTurn(eventId: string, channel: string, destination: string, prompt: string, eventAccountId: string, nextRun: string | null, driver: Driver): Promise<void>;
48
+ /**
49
+ * The no-event detector. Each heartbeat tick, over every agent-dispatch event
50
+ * for a local account, re-checks the destination binding against the current
51
+ * channel config so a destination removed from `adminPhones`/`accountManagers`
52
+ * out of band — or a manager whose sub-account was deleted — is surfaced before
53
+ * the next fire silently mis-dispatches. Independent of any inbound: it reads
54
+ * the node and the on-disk config, nothing else. Emits one
55
+ * `[schedule-audit] op=stale-dispatch` line per invalid binding; a healthy
56
+ * binding emits nothing.
57
+ */
58
+ export declare function runStandingAudit(driver: Driver, localAccountIds: string[], accountsDir: string): Promise<void>;
59
+ //# sourceMappingURL=agent-turn-dispatch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-turn-dispatch.d.ts","sourceRoot":"","sources":["../../src/scripts/agent-turn-dispatch.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,EAAE,KAAK,MAAM,EAAE,MAAM,cAAc,CAAC;AAS3C,MAAM,MAAM,YAAY,GACpB;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GACvE;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,GACrE;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAErB;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE;IACzC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B,GAAG,YAAY,CAaf;AAED;;;;;;;GAOG;AACH,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,EACf,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,MAAM,EACd,cAAc,EAAE,MAAM,EACtB,OAAO,EAAE,MAAM,GAAG,IAAI,EACtB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,IAAI,CAAC,CA0Df;AAED;;;;;;;;;GASG;AACH,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,MAAM,EACd,eAAe,EAAE,MAAM,EAAE,EACzB,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,IAAI,CAAC,CAuCf"}
@@ -0,0 +1,131 @@
1
+ import { existsSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { isValidAgentDestination, boundSubAccountFor, } from "../lib/agent-dispatch.js";
4
+ /**
5
+ * Decide how a due event dispatches from its selected node fields. An event
6
+ * carries at most one mode (enforced at write); agent dispatch takes precedence
7
+ * so a malformed both-set node still routes deterministically.
8
+ */
9
+ export function chooseDispatchMode(fields) {
10
+ if (fields.agentChannel && fields.agentDestination && fields.agentPrompt) {
11
+ return {
12
+ kind: "agent",
13
+ channel: fields.agentChannel,
14
+ destination: fields.agentDestination,
15
+ prompt: fields.agentPrompt,
16
+ };
17
+ }
18
+ if (fields.actionPlugin && fields.actionTool) {
19
+ return { kind: "action", plugin: fields.actionPlugin, tool: fields.actionTool, args: fields.actionArgs };
20
+ }
21
+ return { kind: "none" };
22
+ }
23
+ /**
24
+ * Fire an agent-bearing dispatch: POST the prompt to the UI server's loopback
25
+ * inject route and record the outcome on the `:Event` node exactly as
26
+ * `dispatchAction` does (`lastDispatchResult`/`lastDispatchAt` + a note). On the
27
+ * recurring path `nextRun` has already been advanced by the caller; it is
28
+ * passed here only for the log line. Never throws — a POST/Neo4j failure is
29
+ * recorded, not propagated, so one event never aborts the sweep.
30
+ */
31
+ export async function dispatchAgentTurn(eventId, channel, destination, prompt, eventAccountId, nextRun, driver) {
32
+ const port = process.env.MAXY_UI_INTERNAL_PORT;
33
+ const promptBytes = Buffer.byteLength(prompt, "utf8");
34
+ const recurring = nextRun !== null;
35
+ console.error(`[schedule-dispatch] op=due eventId=${eventId} channel=${channel} destination=${destination} account=${eventAccountId} recurring=${recurring}`);
36
+ console.error(`[schedule-dispatch] op=agent-inject eventId=${eventId} channel=${channel} destination=${destination} promptBytes=${promptBytes} nextRun=${nextRun ?? "-"}`);
37
+ let httpStatus = 0;
38
+ let lastDispatchResult;
39
+ try {
40
+ if (!port) {
41
+ throw new Error("MAXY_UI_INTERNAL_PORT not set — cannot reach the schedule-inject route");
42
+ }
43
+ const res = await fetch(`http://127.0.0.1:${port}/api/channel/schedule-inject`, {
44
+ method: "POST",
45
+ headers: { "Content-Type": "application/json" },
46
+ body: JSON.stringify({ channel, accountId: eventAccountId, destination, prompt, eventId }),
47
+ });
48
+ httpStatus = res.status;
49
+ lastDispatchResult = res.ok ? "accepted" : `error:${res.status}`;
50
+ }
51
+ catch (err) {
52
+ lastDispatchResult = `error:${err instanceof Error ? err.message : String(err)}`;
53
+ }
54
+ const nowIso = new Date().toISOString();
55
+ const logSession = driver.session();
56
+ try {
57
+ await logSession.run(`MATCH (e:Event {eventId: $eventId})
58
+ SET e.lastDispatchResult = $lastDispatchResult,
59
+ e.lastDispatchAt = $now,
60
+ e.notes = COALESCE(e.notes, '') + $noteEntry`, {
61
+ eventId,
62
+ lastDispatchResult,
63
+ now: nowIso,
64
+ noteEntry: `\n[${nowIso}] Agent dispatch: ${channel} → ${destination} — ${lastDispatchResult}`,
65
+ });
66
+ }
67
+ catch {
68
+ // Neo4j logging failed — the console line below is the only record.
69
+ }
70
+ finally {
71
+ // Guard the close too: a rejecting close() (driver tear-down mid-tick) must
72
+ // not propagate, or it would abort the one-time sweep loop (which awaits
73
+ // this with no surrounding try/catch). Honours the "never throws" contract.
74
+ try {
75
+ await logSession.close();
76
+ }
77
+ catch {
78
+ // ignore
79
+ }
80
+ }
81
+ console.error(`[schedule-dispatch] op=inject-result eventId=${eventId} httpStatus=${httpStatus} lastDispatchResult=${lastDispatchResult}`);
82
+ }
83
+ /**
84
+ * The no-event detector. Each heartbeat tick, over every agent-dispatch event
85
+ * for a local account, re-checks the destination binding against the current
86
+ * channel config so a destination removed from `adminPhones`/`accountManagers`
87
+ * out of band — or a manager whose sub-account was deleted — is surfaced before
88
+ * the next fire silently mis-dispatches. Independent of any inbound: it reads
89
+ * the node and the on-disk config, nothing else. Emits one
90
+ * `[schedule-audit] op=stale-dispatch` line per invalid binding; a healthy
91
+ * binding emits nothing.
92
+ */
93
+ export async function runStandingAudit(driver, localAccountIds, accountsDir) {
94
+ if (localAccountIds.length === 0)
95
+ return;
96
+ const session = driver.session();
97
+ let rows;
98
+ try {
99
+ const res = await session.run(`MATCH (e:Event)
100
+ WHERE e.agentChannel IS NOT NULL
101
+ AND e.accountId IN $localAccountIds
102
+ RETURN e.eventId AS eventId, e.accountId AS accountId,
103
+ e.agentChannel AS agentChannel, e.agentDestination AS agentDestination`, { localAccountIds });
104
+ rows = res.records.map((r) => ({
105
+ eventId: r.get("eventId"),
106
+ accountId: r.get("accountId"),
107
+ agentChannel: r.get("agentChannel"),
108
+ agentDestination: r.get("agentDestination"),
109
+ }));
110
+ }
111
+ finally {
112
+ await session.close();
113
+ }
114
+ for (const row of rows) {
115
+ const accountDir = join(accountsDir, row.accountId);
116
+ const check = isValidAgentDestination(accountDir, row.agentChannel, row.agentDestination);
117
+ if (!check.valid) {
118
+ console.error(`[schedule-audit] op=stale-dispatch eventId=${row.eventId} reason=destination-not-admin`);
119
+ continue;
120
+ }
121
+ // A still-valid WhatsApp manager destination whose bound sub-account directory
122
+ // is gone would spawn against a missing account — surface it distinctly.
123
+ if (row.agentChannel === "whatsapp") {
124
+ const sub = boundSubAccountFor(accountDir, row.agentDestination);
125
+ if (sub && !existsSync(join(accountsDir, sub))) {
126
+ console.error(`[schedule-audit] op=stale-dispatch eventId=${row.eventId} reason=sub-account-missing`);
127
+ }
128
+ }
129
+ }
130
+ }
131
+ //# sourceMappingURL=agent-turn-dispatch.js.map