@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.
- package/dist/__tests__/cron-heartbeat-registration.test.js +5 -0
- package/dist/cron-registration.js +1 -0
- package/dist/index.js +4 -0
- package/package.json +1 -1
- package/payload/platform/neo4j/schema.cypher +36 -1
- package/payload/platform/plugins/.claude-plugin/marketplace.json +5 -0
- package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +61 -1
- package/payload/platform/plugins/docs/references/graph.md +11 -0
- package/payload/platform/plugins/docs/references/plugins-guide.md +1 -0
- package/payload/platform/plugins/docs/references/sweep-guide.md +43 -0
- package/payload/platform/plugins/memory/references/schema-estate-agent.md +34 -0
- package/payload/platform/plugins/scheduling/PLUGIN.md +17 -1
- package/payload/platform/plugins/scheduling/mcp/dist/index.js +12 -0
- package/payload/platform/plugins/scheduling/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/agent-dispatch.test.d.ts +2 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/agent-dispatch.test.d.ts.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/agent-dispatch.test.js +101 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/agent-dispatch.test.js.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/agent-dispatch.d.ts +54 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/agent-dispatch.d.ts.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/agent-dispatch.js +117 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/agent-dispatch.js.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/schedule-classify.d.ts +7 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/schedule-classify.d.ts.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/lib/schedule-classify.js +6 -3
- package/payload/platform/plugins/scheduling/mcp/dist/lib/schedule-classify.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/agent-turn-dispatch.test.d.ts +2 -0
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/agent-turn-dispatch.test.d.ts.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/agent-turn-dispatch.test.js +88 -0
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/agent-turn-dispatch.test.js.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/standing-audit.test.d.ts +2 -0
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/standing-audit.test.d.ts.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/standing-audit.test.js +77 -0
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/standing-audit.test.js.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/agent-turn-dispatch.d.ts +59 -0
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/agent-turn-dispatch.d.ts.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/agent-turn-dispatch.js +131 -0
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/agent-turn-dispatch.js.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/check-due-events.js +62 -15
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/check-due-events.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-event-agent.test.d.ts +2 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-event-agent.test.d.ts.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-event-agent.test.js +110 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-event-agent.test.js.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-get-agent.test.d.ts +2 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-get-agent.test.d.ts.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-get-agent.test.js +35 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-get-agent.test.js.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-update-agent.test.d.ts +2 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-update-agent.test.d.ts.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-update-agent.test.js +81 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-update-agent.test.js.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-event.d.ts +7 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-event.d.ts.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-event.js +21 -4
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-event.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-get.d.ts +4 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-get.d.ts.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-get.js +6 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-get.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-update.d.ts +2 -1
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-update.d.ts.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-update.js +32 -1
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-update.js.map +1 -1
- package/payload/platform/plugins/sweep/.claude-plugin/plugin.json +8 -0
- package/payload/platform/plugins/sweep/PLUGIN.md +15 -0
- package/payload/platform/plugins/sweep/skills/sweep-workflow/SKILL.md +113 -0
- package/payload/platform/plugins/whatsapp/PLUGIN.md +1 -1
- package/payload/platform/plugins/whatsapp/mcp/dist/index.js +19 -4
- package/payload/platform/plugins/whatsapp/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/whatsapp/references/channels-whatsapp.md +7 -4
- package/payload/platform/plugins/whatsapp/skills/manage-whatsapp-config/SKILL.md +10 -0
- package/payload/platform/scripts/seed-neo4j.sh +38 -0
- package/payload/platform/services/whatsapp-channel/dist/notification.d.ts +5 -0
- package/payload/platform/services/whatsapp-channel/dist/notification.d.ts.map +1 -1
- package/payload/platform/services/whatsapp-channel/dist/notification.js.map +1 -1
- package/payload/platform/services/whatsapp-channel/dist/server.js +1 -1
- package/payload/platform/services/whatsapp-channel/dist/server.js.map +1 -1
- package/payload/server/public/assets/{AdminLoginScreens-oxqHKL3C.js → AdminLoginScreens-BejIjbmU.js} +1 -1
- package/payload/server/public/assets/{AdminShell-DBD6ruS1.js → AdminShell-D2-uBSB5.js} +1 -1
- package/payload/server/public/assets/{Checkbox-yr8-ftrE.js → Checkbox-1F1tzqca.js} +1 -1
- package/payload/server/public/assets/Transcript-DkGa4CQH.js +2 -0
- package/payload/server/public/assets/{admin-CQfZnuFy.js → admin-DqQARkjy.js} +1 -1
- package/payload/server/public/assets/{browser-DbdPeDK1.js → browser-nDtEK6RC.js} +1 -1
- package/payload/server/public/assets/{calendar-CFdOhe7Q.js → calendar-CO4Bwmho.js} +1 -1
- package/payload/server/public/assets/chat-DeIge_bC.js +1 -0
- package/payload/server/public/assets/chevron-left-DhVdq0aN.js +1 -0
- package/payload/server/public/assets/data-B1GIdzHk.js +1 -0
- package/payload/server/public/assets/{graph-Bluocsf_.js → graph-DFyicKd7.js} +1 -1
- package/payload/server/public/assets/{graph-labels-BcLJmY-v.js → graph-labels-D3BQdcpD.js} +1 -1
- package/payload/server/public/assets/{operator-DjK3MBSS.js → operator-BxrjWdT9.js} +1 -1
- package/payload/server/public/assets/{page-DQNYTb2d.js → page-ByDLq_o1.js} +1 -1
- package/payload/server/public/assets/{page-zJQTRXB2.js → page-D-Ep4bXd.js} +1 -1
- package/payload/server/public/assets/{public-CHCniosp.js → public-DbdqfdYq.js} +1 -1
- package/payload/server/public/assets/{rotate-ccw-JDfwjfIL.js → rotate-ccw-BkX8HODe.js} +1 -1
- package/payload/server/public/assets/{useSubAccountSwitcher-CfI3J2IX.css → useSubAccountSwitcher-DS0iqSkP.css} +1 -1
- package/payload/server/public/browser.html +4 -4
- package/payload/server/public/calendar.html +5 -5
- package/payload/server/public/chat.html +8 -8
- package/payload/server/public/data.html +7 -7
- package/payload/server/public/graph.html +8 -8
- package/payload/server/public/index.html +10 -10
- package/payload/server/public/operator.html +10 -10
- package/payload/server/public/public.html +8 -8
- package/payload/server/server.js +551 -271
- package/payload/server/public/assets/Transcript-FX_w6no6.js +0 -2
- package/payload/server/public/assets/chat-BunxcNDk.js +0 -1
- package/payload/server/public/assets/chevron-left-Cngmu0Py.js +0 -1
- package/payload/server/public/assets/data-IqZlMSwG.js +0 -1
- /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":"
|
|
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.
|
|
12
|
-
// 4.
|
|
13
|
-
// 5.
|
|
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;
|
|
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 @@
|
|
|
1
|
+
{"version":3,"file":"agent-turn-dispatch.test.d.ts","sourceRoot":"","sources":["../../../src/scripts/__tests__/agent-turn-dispatch.test.ts"],"names":[],"mappings":""}
|
package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/agent-turn-dispatch.test.js
ADDED
|
@@ -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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"}
|
package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/standing-audit.test.d.ts.map
ADDED
|
@@ -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
|
package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/standing-audit.test.js.map
ADDED
|
@@ -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
|