@rubytech/create-maxy-code 0.1.440 → 0.1.442

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 (107) hide show
  1. package/dist/index.js +99 -1
  2. package/package.json +1 -1
  3. package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +64 -8
  4. package/payload/platform/plugins/cloudflare/skills/calendar-site/SKILL.md +15 -5
  5. package/payload/platform/plugins/cloudflare/skills/calendar-site/template/functions/api/calendar/free-busy.ts +152 -0
  6. package/payload/platform/plugins/cloudflare/skills/calendar-site/template/public/index.html +6 -3
  7. package/payload/platform/plugins/cloudflare/skills/calendar-site/template/schema.sql +11 -0
  8. package/payload/platform/plugins/docs/references/admin-ui.md +1 -1
  9. package/payload/platform/plugins/docs/references/calendar-booking.md +1 -1
  10. package/payload/platform/plugins/docs/references/graph.md +19 -3
  11. package/payload/platform/plugins/docs/references/outlook-guide.md +7 -2
  12. package/payload/platform/plugins/docs/references/troubleshooting.md +35 -0
  13. package/payload/platform/plugins/outlook/.claude-plugin/plugin.json +1 -1
  14. package/payload/platform/plugins/outlook/PLUGIN.md +7 -3
  15. package/payload/platform/plugins/outlook/mcp/dist/__tests__/account-register.test.js +100 -10
  16. package/payload/platform/plugins/outlook/mcp/dist/__tests__/account-register.test.js.map +1 -1
  17. package/payload/platform/plugins/outlook/mcp/dist/__tests__/device-flow.test.js +37 -4
  18. package/payload/platform/plugins/outlook/mcp/dist/__tests__/device-flow.test.js.map +1 -1
  19. package/payload/platform/plugins/outlook/mcp/dist/__tests__/mailbox-list.test.d.ts +2 -0
  20. package/payload/platform/plugins/outlook/mcp/dist/__tests__/mailbox-list.test.d.ts.map +1 -0
  21. package/payload/platform/plugins/outlook/mcp/dist/__tests__/mailbox-list.test.js +64 -0
  22. package/payload/platform/plugins/outlook/mcp/dist/__tests__/mailbox-list.test.js.map +1 -0
  23. package/payload/platform/plugins/outlook/mcp/dist/__tests__/mailbox-registry.test.d.ts +2 -0
  24. package/payload/platform/plugins/outlook/mcp/dist/__tests__/mailbox-registry.test.d.ts.map +1 -0
  25. package/payload/platform/plugins/outlook/mcp/dist/__tests__/mailbox-registry.test.js +193 -0
  26. package/payload/platform/plugins/outlook/mcp/dist/__tests__/mailbox-registry.test.js.map +1 -0
  27. package/payload/platform/plugins/outlook/mcp/dist/__tests__/token-store.test.js +58 -1
  28. package/payload/platform/plugins/outlook/mcp/dist/__tests__/token-store.test.js.map +1 -1
  29. package/payload/platform/plugins/outlook/mcp/dist/auth/device-flow.d.ts +27 -3
  30. package/payload/platform/plugins/outlook/mcp/dist/auth/device-flow.d.ts.map +1 -1
  31. package/payload/platform/plugins/outlook/mcp/dist/auth/device-flow.js +52 -11
  32. package/payload/platform/plugins/outlook/mcp/dist/auth/device-flow.js.map +1 -1
  33. package/payload/platform/plugins/outlook/mcp/dist/auth/mailbox-registry.d.ts +81 -0
  34. package/payload/platform/plugins/outlook/mcp/dist/auth/mailbox-registry.d.ts.map +1 -0
  35. package/payload/platform/plugins/outlook/mcp/dist/auth/mailbox-registry.js +171 -0
  36. package/payload/platform/plugins/outlook/mcp/dist/auth/mailbox-registry.js.map +1 -0
  37. package/payload/platform/plugins/outlook/mcp/dist/auth/token-store.d.ts +16 -1
  38. package/payload/platform/plugins/outlook/mcp/dist/auth/token-store.d.ts.map +1 -1
  39. package/payload/platform/plugins/outlook/mcp/dist/auth/token-store.js +25 -3
  40. package/payload/platform/plugins/outlook/mcp/dist/auth/token-store.js.map +1 -1
  41. package/payload/platform/plugins/outlook/mcp/dist/index.js +119 -50
  42. package/payload/platform/plugins/outlook/mcp/dist/index.js.map +1 -1
  43. package/payload/platform/plugins/outlook/mcp/dist/lib/graph-client.js +1 -1
  44. package/payload/platform/plugins/outlook/mcp/dist/lib/graph-client.js.map +1 -1
  45. package/payload/platform/plugins/outlook/mcp/dist/tools/account-register.d.ts +13 -4
  46. package/payload/platform/plugins/outlook/mcp/dist/tools/account-register.d.ts.map +1 -1
  47. package/payload/platform/plugins/outlook/mcp/dist/tools/account-register.js +54 -3
  48. package/payload/platform/plugins/outlook/mcp/dist/tools/account-register.js.map +1 -1
  49. package/payload/platform/plugins/outlook/mcp/dist/tools/mailbox-list.d.ts +26 -0
  50. package/payload/platform/plugins/outlook/mcp/dist/tools/mailbox-list.d.ts.map +1 -0
  51. package/payload/platform/plugins/outlook/mcp/dist/tools/mailbox-list.js +28 -0
  52. package/payload/platform/plugins/outlook/mcp/dist/tools/mailbox-list.js.map +1 -0
  53. package/payload/platform/plugins/outlook/references/auth.md +36 -8
  54. package/payload/platform/plugins/scheduling/mcp/dist/scripts/publish-availability.d.ts +27 -0
  55. package/payload/platform/plugins/scheduling/mcp/dist/scripts/publish-availability.d.ts.map +1 -0
  56. package/payload/platform/plugins/scheduling/mcp/dist/scripts/publish-availability.js +241 -0
  57. package/payload/platform/plugins/scheduling/mcp/dist/scripts/publish-availability.js.map +1 -0
  58. package/payload/platform/plugins/whatsapp/mcp/dist/index.js +20 -22
  59. package/payload/platform/plugins/whatsapp/mcp/dist/index.js.map +1 -1
  60. package/payload/platform/plugins/whatsapp/references/channels-whatsapp.md +3 -1
  61. package/payload/platform/scripts/logs-read.sh +24 -6
  62. package/payload/platform/scripts/logs-read.test.sh +42 -0
  63. package/payload/platform/scripts/rss-sampler.sh +107 -0
  64. package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.d.ts.map +1 -1
  65. package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js +1 -0
  66. package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js.map +1 -1
  67. package/payload/platform/templates/specialists/agents/personal-assistant.md +1 -1
  68. package/payload/server/public/assets/{AdminLoginScreens-CukwQufW.js → AdminLoginScreens-BJVwtpLH.js} +1 -1
  69. package/payload/server/public/assets/{AdminShell-BwEBH6tT.js → AdminShell-CMu8_Xwn.js} +1 -1
  70. package/payload/server/public/assets/{Checkbox-B629mOPp.js → Checkbox-CzD0ORqA.js} +1 -1
  71. package/payload/server/public/assets/{admin-CdFRCUUZ.js → admin-D0yk2LMq.js} +1 -1
  72. package/payload/server/public/assets/{browser-BWR59IkB.js → browser-CvfbgznO.js} +1 -1
  73. package/payload/server/public/assets/{calendar-kuTcxX1t.js → calendar-fw4WethJ.js} +1 -1
  74. package/payload/server/public/assets/chat-DhD-NES_.js +1 -0
  75. package/payload/server/public/assets/chevron-left-B3xSeEDR.js +1 -0
  76. package/payload/server/public/assets/data-CUnDzDox.js +1 -0
  77. package/payload/server/public/assets/{file-download-4mKJxUi-.js → file-download-Px4s0Bhp.js} +1 -1
  78. package/payload/server/public/assets/{graph-NgQDY0Pb.js → graph-DbT8nA6R.js} +2 -2
  79. package/payload/server/public/assets/{graph-labels-B8YAdo3Z.js → graph-labels-C1YIyLiA.js} +1 -1
  80. package/payload/server/public/assets/{maximize-2-CxstTgoK.js → maximize-2-BwphzI9S.js} +1 -1
  81. package/payload/server/public/assets/{operator-oSaB1mya.js → operator-zcIfeB14.js} +1 -1
  82. package/payload/server/public/assets/{page-DMeJHVfA.js → page-6fCnIb45.js} +2 -2
  83. package/payload/server/public/assets/page-CfQFAJia.js +1 -0
  84. package/payload/server/public/assets/{public-C54tl6V4.js → public-C0ehZCdE.js} +1 -1
  85. package/payload/server/public/assets/{rotate-ccw-COGQTi7n.js → rotate-ccw-C7BqkMec.js} +1 -1
  86. package/payload/server/public/assets/{tasks-B97VpwEJ.js → tasks-DlgiCafL.js} +1 -1
  87. package/payload/server/public/assets/{time-entry-format-C09rDHo9.js → time-entry-format-CyNYVDkq.js} +1 -1
  88. package/payload/server/public/assets/{useCopyFeedback-DHoYHiK9.js → useCopyFeedback-ByGUMN94.js} +1 -1
  89. package/payload/server/public/assets/useSubAccountSwitcher-1izQvPiD.css +1 -0
  90. package/payload/server/public/assets/useVoiceRecorder-XgpWvXpk.js +2 -0
  91. package/payload/server/public/browser.html +5 -5
  92. package/payload/server/public/calendar.html +6 -6
  93. package/payload/server/public/chat.html +12 -12
  94. package/payload/server/public/data.html +10 -10
  95. package/payload/server/public/graph.html +11 -11
  96. package/payload/server/public/index.html +13 -13
  97. package/payload/server/public/operator.html +14 -14
  98. package/payload/server/public/public.html +12 -12
  99. package/payload/server/public/tasks.html +5 -5
  100. package/payload/server/server.js +498 -180
  101. package/payload/server/public/assets/chat-ejnq2rck.js +0 -1
  102. package/payload/server/public/assets/chevron-left-u4tjq7fI.js +0 -1
  103. package/payload/server/public/assets/data-dZbOyLdT.js +0 -1
  104. package/payload/server/public/assets/page-DEkI-Gl1.js +0 -1
  105. package/payload/server/public/assets/useSubAccountSwitcher-Dzmd7Aip.css +0 -1
  106. package/payload/server/public/assets/useVoiceRecorder-b7HzTFs0.js +0 -2
  107. /package/payload/server/public/assets/{useSubAccountSwitcher-DLJFISqf.js → useSubAccountSwitcher-s6WlRn52.js} +0 -0
@@ -1 +1 @@
1
- {"version":3,"file":"account-register.js","sourceRoot":"","sources":["../../src/tools/account-register.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"account-register.js","sourceRoot":"","sources":["../../src/tools/account-register.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ export interface MailboxListItem {
2
+ /** Opaque directory handle under mailboxes/ — stable even when unreadable. */
3
+ mailboxKey: string;
4
+ /** The mailbox email address, when the blob is readable. */
5
+ mailbox: string | null;
6
+ graphUserId: string | null;
7
+ scopes: string[];
8
+ tokenExpSec: number | null;
9
+ refreshExpired: boolean;
10
+ readable: boolean;
11
+ }
12
+ export interface MailboxListResult {
13
+ count: number;
14
+ /** How many blobs are on disk but do not decrypt/parse — an orphaned store. */
15
+ unreadable: number;
16
+ mailboxes: MailboxListItem[];
17
+ }
18
+ /**
19
+ * List every Outlook mailbox attached to this account. Local only — reads the
20
+ * on-disk stores, never Graph — so it reconciles what is stored against what the
21
+ * agent can address even when Graph is unreachable. An unreadable blob is
22
+ * surfaced (`readable:false`) rather than dropped, closing the no-event blind
23
+ * spot where a mailbox silently falls out of addressability.
24
+ */
25
+ export declare function runMailboxList(accountId: string, accountsDir: string): MailboxListResult;
26
+ //# sourceMappingURL=mailbox-list.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mailbox-list.d.ts","sourceRoot":"","sources":["../../src/tools/mailbox-list.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,eAAe;IAC9B,8EAA8E;IAC9E,UAAU,EAAE,MAAM,CAAC;IACnB,4DAA4D;IAC5D,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,cAAc,EAAE,OAAO,CAAC;IACxB,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,+EAA+E;IAC/E,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,eAAe,EAAE,CAAC;CAC9B;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,iBAAiB,CAiBxF"}
@@ -0,0 +1,28 @@
1
+ import { MailboxRegistry } from "../auth/mailbox-registry.js";
2
+ import { log } from "../lib/log.js";
3
+ /**
4
+ * List every Outlook mailbox attached to this account. Local only — reads the
5
+ * on-disk stores, never Graph — so it reconciles what is stored against what the
6
+ * agent can address even when Graph is unreachable. An unreadable blob is
7
+ * surfaced (`readable:false`) rather than dropped, closing the no-event blind
8
+ * spot where a mailbox silently falls out of addressability.
9
+ */
10
+ export function runMailboxList(accountId, accountsDir) {
11
+ const boxes = new MailboxRegistry(accountId, accountsDir).list();
12
+ const unreadable = boxes.filter((b) => !b.readable).length;
13
+ log({ event: "mailbox-list", account: accountId, count: boxes.length, unreadable });
14
+ return {
15
+ count: boxes.length,
16
+ unreadable,
17
+ mailboxes: boxes.map((b) => ({
18
+ mailboxKey: b.mailboxKey,
19
+ mailbox: b.mail,
20
+ graphUserId: b.graphUserId,
21
+ scopes: b.scopes,
22
+ tokenExpSec: b.tokenExpSec,
23
+ refreshExpired: b.refreshExpired,
24
+ readable: b.readable,
25
+ })),
26
+ };
27
+ }
28
+ //# sourceMappingURL=mailbox-list.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mailbox-list.js","sourceRoot":"","sources":["../../src/tools/mailbox-list.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAC9D,OAAO,EAAE,GAAG,EAAE,MAAM,eAAe,CAAC;AAqBpC;;;;;;GAMG;AACH,MAAM,UAAU,cAAc,CAAC,SAAiB,EAAE,WAAmB;IACnE,MAAM,KAAK,GAAG,IAAI,eAAe,CAAC,SAAS,EAAE,WAAW,CAAC,CAAC,IAAI,EAAE,CAAC;IACjE,MAAM,UAAU,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC;IAC3D,GAAG,CAAC,EAAE,KAAK,EAAE,cAAc,EAAE,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,KAAK,CAAC,MAAM,EAAE,UAAU,EAAE,CAAC,CAAC;IACpF,OAAO;QACL,KAAK,EAAE,KAAK,CAAC,MAAM;QACnB,UAAU;QACV,SAAS,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YAC3B,UAAU,EAAE,CAAC,CAAC,UAAU;YACxB,OAAO,EAAE,CAAC,CAAC,IAAI;YACf,WAAW,EAAE,CAAC,CAAC,WAAW;YAC1B,MAAM,EAAE,CAAC,CAAC,MAAM;YAChB,WAAW,EAAE,CAAC,CAAC,WAAW;YAC1B,cAAc,EAAE,CAAC,CAAC,cAAc;YAChC,QAAQ,EAAE,CAAC,CAAC,QAAQ;SACrB,CAAC,CAAC;KACJ,CAAC;AACJ,CAAC"}
@@ -72,8 +72,8 @@ operator plugin
72
72
  | | < authorization_pending → pending |
73
73
  | | < tokens (once consented) → success |
74
74
  | | <----------------------------------- |
75
- | | encrypt write {tokens.enc} |
76
- | | GET /me (record graphUserId) |
75
+ | | GET /me (resolve graphUserId + mail) |
76
+ | | encrypt write mailboxes/<id>/tokens.enc |
77
77
  | | clear pending (verified re-stat) |
78
78
  | { status: "registered", graphUserId, ... } | |
79
79
  |<---------------------------------------------| |
@@ -83,16 +83,33 @@ operator plugin
83
83
 
84
84
  ## Token store layout
85
85
 
86
- Per-account, mode 0700 directory + mode 0600 files:
86
+ One account can hold several mailboxes. Each mailbox owns its own `tokens.enc`
87
+ under `mailboxes/<graphUserId>/`, and they share the one account-level AES `.key`.
88
+ Account isolation is unchanged — the key is per-account, never shared across
89
+ accounts — while mailboxes within one account share it. Per-account, mode 0700
90
+ directories + mode 0600 files:
87
91
 
88
92
  ```
89
93
  data/accounts/<accountId>/secrets/outlook/
90
- ├── .key # 32 random bytes, base64-encoded — the AES-256-CBC key
91
- ├── tokens.enc # AES-256-CBC ciphertext: "<iv-hex>:<cipher-hex>" of the token JSON blob
92
- └── pending-devicecode.enc # transient the device_code between register + poll; cleared on any terminal outcome
94
+ ├── .key # 32 random bytes, base64 — the AES-256-CBC key, shared by every mailbox on this account
95
+ ├── mailboxes/
96
+ │ ├── <graphUserId-A>/tokens.enc # AES-256-CBC "<iv-hex>:<cipher-hex>" of mailbox A's token JSON blob
97
+ │ └── <graphUserId-B>/tokens.enc # mailbox B — coexists, never overwritten by A
98
+ └── pending-devicecode.enc # transient — the device_code between register + poll; cleared on any terminal outcome
93
99
  ```
94
100
 
95
- The pending file reuses the same `.key`. It is written by `outlook-account-register` and removed (verified by re-stat) when polling reaches `registered`, `expired`, or `denied`, so a device_code never lingers.
101
+ **Migration.** A pre-multi-mailbox install has a single `tokens.enc` at the
102
+ account root. On first read the registry moves it into
103
+ `mailboxes/<graphUserId>/tokens.enc`. Because the AES key is unchanged, the
104
+ ciphertext is valid as-is — the migration is a file move, never a re-encrypt,
105
+ and never forces a re-auth.
106
+
107
+ **One registration at a time.** The pending file is single-slot per account and
108
+ reuses the same `.key`. `outlook-account-register` refuses with an `in-progress`
109
+ signal when a live device-code flow already exists, rather than overwriting it.
110
+ The entry is removed (verified by re-stat) when polling reaches `registered`,
111
+ `expired`, or `denied`, so a device_code never lingers. Genuinely-concurrent
112
+ registrations are out of scope — see `.tasks/1594`.
96
113
 
97
114
  The JSON blob shape:
98
115
 
@@ -103,13 +120,24 @@ The JSON blob shape:
103
120
  accessTokenExpiry: number; // unix ms
104
121
  refreshTokenExpiry: number; // unix ms; ~90 days from issue
105
122
  lastRefresh: number; // unix ms
106
- graphUserId: string | null; // from /me.id
123
+ graphUserId: string | null; // from /me.id — also the mailbox directory key
124
+ mail: string | null; // from /me.mail (or userPrincipalName) — the selectable address
107
125
  scopes: string[]; // granted scopes from token response
108
126
  }
109
127
  ```
110
128
 
111
129
  Atomic write: temp + rename. Either old or new tokens, never partial.
112
130
 
131
+ ## Mailbox selection
132
+
133
+ Every operational tool takes an optional `mailbox` argument (an email address or
134
+ graphUserId). Resolution order: explicit `mailbox` → the sole connected mailbox →
135
+ a typed refusal listing the connected mailboxes when two or more are attached and
136
+ none is named. Each resolution logs `cred-resolve account=<id> mailbox=<id>
137
+ source=arg|sole|none|refused`, so a wrong or absent selector is diagnosable from
138
+ one line. `outlook-mailbox-list` enumerates every mailbox on the account from the
139
+ local stores (no network) and flags any whose blob cannot be read.
140
+
113
141
  ## Refresh
114
142
 
115
143
  Triggered automatically when the access token is within `5 minutes` of expiry (`refreshThresholdMs`). Failure → terminal `token-refresh-failed`, tokens cleared, operator must re-register. Refresh tokens last 90 days.
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Dispatcher script: publish-availability
3
+ *
4
+ * Self-contained, like reconcile-bookings. Publishes the install account's
5
+ * computed free/busy into its Cloudflare D1 `availability_snapshot` table so the
6
+ * public booking page's edge Function keeps serving open times while the device
7
+ * is offline. Runs while the device is ONLINE; when the device is off it simply
8
+ * does not run and the edge keeps serving the last snapshot until it returns.
9
+ *
10
+ * It reads the device's own free/busy from the always-running local server
11
+ * (127.0.0.1:$MAXY_UI_INTERNAL_PORT/api/calendar/free-busy — a gate-exempt public
12
+ * path), so it needs no Neo4j and no tunnel/DNS. The account whose free/busy the
13
+ * endpoint serves is the one the server resolved and hands in via
14
+ * PUBLISH_ACCOUNT_DIR / PUBLISH_ACCOUNT_ID.
15
+ *
16
+ * If the local endpoint is unreachable it makes NO D1 write (result=skipped-
17
+ * unreachable), leaving the last edge snapshot intact. A PID lock prevents
18
+ * overlapping runs. Each run records the outcome to a local last-publish.json so
19
+ * the server's audit tick can report snapshot age independent of this job.
20
+ *
21
+ * Observability — one [calendar-availability] op=publish line per run.
22
+ *
23
+ * PLATFORM_ROOT=... MAXY_UI_INTERNAL_PORT=... PUBLISH_ACCOUNT_DIR=... \
24
+ * PUBLISH_ACCOUNT_ID=... node dist/scripts/publish-availability.js
25
+ */
26
+ export {};
27
+ //# sourceMappingURL=publish-availability.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"publish-availability.d.ts","sourceRoot":"","sources":["../../src/scripts/publish-availability.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG"}
@@ -0,0 +1,241 @@
1
+ /**
2
+ * Dispatcher script: publish-availability
3
+ *
4
+ * Self-contained, like reconcile-bookings. Publishes the install account's
5
+ * computed free/busy into its Cloudflare D1 `availability_snapshot` table so the
6
+ * public booking page's edge Function keeps serving open times while the device
7
+ * is offline. Runs while the device is ONLINE; when the device is off it simply
8
+ * does not run and the edge keeps serving the last snapshot until it returns.
9
+ *
10
+ * It reads the device's own free/busy from the always-running local server
11
+ * (127.0.0.1:$MAXY_UI_INTERNAL_PORT/api/calendar/free-busy — a gate-exempt public
12
+ * path), so it needs no Neo4j and no tunnel/DNS. The account whose free/busy the
13
+ * endpoint serves is the one the server resolved and hands in via
14
+ * PUBLISH_ACCOUNT_DIR / PUBLISH_ACCOUNT_ID.
15
+ *
16
+ * If the local endpoint is unreachable it makes NO D1 write (result=skipped-
17
+ * unreachable), leaving the last edge snapshot intact. A PID lock prevents
18
+ * overlapping runs. Each run records the outcome to a local last-publish.json so
19
+ * the server's audit tick can report snapshot age independent of this job.
20
+ *
21
+ * Observability — one [calendar-availability] op=publish line per run.
22
+ *
23
+ * PLATFORM_ROOT=... MAXY_UI_INTERNAL_PORT=... PUBLISH_ACCOUNT_DIR=... \
24
+ * PUBLISH_ACCOUNT_ID=... node dist/scripts/publish-availability.js
25
+ */
26
+ import { spawnSync } from "node:child_process";
27
+ import { readFileSync, writeFileSync, existsSync, unlinkSync, mkdirSync, mkdtempSync, rmSync } from "node:fs";
28
+ import { resolve, join, basename } from "node:path";
29
+ import { homedir, tmpdir } from "node:os";
30
+ function resolveConfigDir() {
31
+ const platformRoot = process.env.PLATFORM_ROOT;
32
+ if (!platformRoot)
33
+ throw new Error("PLATFORM_ROOT environment variable is required");
34
+ const brandPath = resolve(platformRoot, "config", "brand.json");
35
+ if (!existsSync(brandPath))
36
+ throw new Error(`brand.json not found at ${brandPath}`);
37
+ const brand = JSON.parse(readFileSync(brandPath, "utf-8"));
38
+ if (!brand.configDir)
39
+ throw new Error(`brand.json at ${brandPath} is missing configDir`);
40
+ return brand.configDir;
41
+ }
42
+ const CONFIG_DIR = resolve(homedir(), resolveConfigDir());
43
+ const LOCK_FILE = resolve(CONFIG_DIR, "publish-availability.lock");
44
+ function acquireLock() {
45
+ mkdirSync(CONFIG_DIR, { recursive: true });
46
+ if (existsSync(LOCK_FILE)) {
47
+ const storedPid = readFileSync(LOCK_FILE, "utf-8").trim();
48
+ try {
49
+ process.kill(Number(storedPid), 0);
50
+ return false; // a run is in progress
51
+ }
52
+ catch {
53
+ unlinkSync(LOCK_FILE); // stale
54
+ }
55
+ }
56
+ writeFileSync(LOCK_FILE, String(process.pid));
57
+ return true;
58
+ }
59
+ function releaseLock() {
60
+ try {
61
+ if (existsSync(LOCK_FILE) && readFileSync(LOCK_FILE, "utf-8").trim() === String(process.pid)) {
62
+ unlinkSync(LOCK_FILE);
63
+ }
64
+ }
65
+ catch {
66
+ /* best effort */
67
+ }
68
+ }
69
+ /** Read `KEY=value` pairs from a secrets env file without polluting process.env. */
70
+ function readEnvFile(path) {
71
+ const out = {};
72
+ if (!existsSync(path))
73
+ return out;
74
+ for (const line of readFileSync(path, "utf-8").split("\n")) {
75
+ const m = line.match(/^([A-Z0-9_]+)=(.*)$/);
76
+ if (m)
77
+ out[m[1]] = m[2];
78
+ }
79
+ return out;
80
+ }
81
+ /**
82
+ * Record the outcome for the server's audit tick. lastSuccessAt advances only on
83
+ * a successful D1 write, so a run of failures leaves it stale and the server
84
+ * reports a climbing snapshotAgeMs.
85
+ */
86
+ function writeLastPublish(accountDir, result, slots, lastSuccessAt) {
87
+ const stateDir = join(accountDir, "state", "booking-availability");
88
+ try {
89
+ mkdirSync(stateDir, { recursive: true });
90
+ writeFileSync(join(stateDir, "last-publish.json"), JSON.stringify({ result, slots, lastSuccessAt, at: new Date().toISOString() }));
91
+ }
92
+ catch {
93
+ /* best effort — the log line is the primary signal */
94
+ }
95
+ }
96
+ function readPriorSuccessAt(accountDir) {
97
+ const path = join(accountDir, "state", "booking-availability", "last-publish.json");
98
+ if (!existsSync(path))
99
+ return null;
100
+ try {
101
+ const prior = JSON.parse(readFileSync(path, "utf-8"));
102
+ return prior.lastSuccessAt ?? null;
103
+ }
104
+ catch {
105
+ return null;
106
+ }
107
+ }
108
+ /** UTC midnight of `now`, and `days` after it, as `YYYY-MM-DDT00:00:00Z`. */
109
+ function utcMidnight(now, addDays) {
110
+ const d = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate() + addDays));
111
+ return d.toISOString().replace(/\.\d{3}Z$/, "Z");
112
+ }
113
+ async function main() {
114
+ if (!acquireLock()) {
115
+ console.error("[calendar-availability] op=publish result=skipped reason=lock-held");
116
+ return;
117
+ }
118
+ try {
119
+ const accountDir = process.env.PUBLISH_ACCOUNT_DIR;
120
+ if (!accountDir) {
121
+ console.error("[calendar-availability] op=publish result=skipped reason=no-account");
122
+ return;
123
+ }
124
+ const accountId = process.env.PUBLISH_ACCOUNT_ID ?? basename(accountDir);
125
+ const availPath = join(accountDir, "calendar-availability.json");
126
+ if (!existsSync(availPath)) {
127
+ // No booking site for this account — nothing to publish. Not an error.
128
+ return;
129
+ }
130
+ let avail;
131
+ try {
132
+ avail = JSON.parse(readFileSync(availPath, "utf-8"));
133
+ }
134
+ catch {
135
+ console.error(`[calendar-availability] op=publish accountId=${accountId} result=error reason=bad-availability-json`);
136
+ writeLastPublish(accountDir, "error", null, readPriorSuccessAt(accountDir));
137
+ return;
138
+ }
139
+ const dbName = avail.bookingDbName;
140
+ if (!dbName) {
141
+ console.error(`[calendar-availability] op=publish accountId=${accountId} result=skipped reason=no-booking-db`);
142
+ return;
143
+ }
144
+ const secrets = readEnvFile(join(accountDir, "secrets", "cloudflare.env"));
145
+ const token = secrets.CF_PAGES_D1_TOKEN;
146
+ if (!token) {
147
+ console.error(`[calendar-availability] op=publish accountId=${accountId} result=skipped reason=no-pages-d1-token`);
148
+ return;
149
+ }
150
+ const port = process.env.MAXY_UI_INTERNAL_PORT;
151
+ if (!port) {
152
+ console.error(`[calendar-availability] op=publish accountId=${accountId} result=error reason=no-internal-port`);
153
+ writeLastPublish(accountDir, "error", null, readPriorSuccessAt(accountDir));
154
+ return;
155
+ }
156
+ // Fetch the device's own computed free/busy over a horizon that covers the
157
+ // page's own (operator-set) window, +1 day of margin for the local-midnight
158
+ // boundary the page renders in.
159
+ const daysAhead = typeof avail.daysAhead === "number" && avail.daysAhead > 0 ? Math.floor(avail.daysAhead) : 14;
160
+ const now = new Date();
161
+ const from = utcMidnight(now, 0);
162
+ const to = utcMidnight(now, daysAhead + 1);
163
+ const nowIso = now.toISOString();
164
+ const url = `http://127.0.0.1:${port}/api/calendar/free-busy?from=${encodeURIComponent(from)}&to=${encodeURIComponent(to)}`;
165
+ let payload;
166
+ try {
167
+ // Bound the read so a wedged server cannot hold the PID lock.
168
+ const res = await fetch(url, { signal: AbortSignal.timeout(30_000) });
169
+ if (!res.ok)
170
+ throw new Error(`HTTP ${res.status}`);
171
+ payload = await res.text();
172
+ }
173
+ catch (err) {
174
+ // Device/local endpoint unreachable — leave the last edge snapshot intact.
175
+ console.error(`[calendar-availability] op=publish accountId=${accountId} result=skipped-unreachable err="${err.message}"`);
176
+ writeLastPublish(accountDir, "skipped-unreachable", null, readPriorSuccessAt(accountDir));
177
+ return;
178
+ }
179
+ // Must be JSON carrying a slots array, or we would poison the snapshot.
180
+ let slotCount;
181
+ try {
182
+ const parsed = JSON.parse(payload);
183
+ if (!Array.isArray(parsed.slots))
184
+ throw new Error("no slots array");
185
+ slotCount = parsed.slots.length;
186
+ }
187
+ catch (err) {
188
+ console.error(`[calendar-availability] op=publish accountId=${accountId} result=skipped reason=bad-payload err="${err.message}"`);
189
+ writeLastPublish(accountDir, "skipped", null, readPriorSuccessAt(accountDir));
190
+ return;
191
+ }
192
+ // Upsert via a temp SQL file — robust for a large payload where a --command
193
+ // argv would be fragile. Single quotes are escaped for the SQL literal. The
194
+ // CREATE first makes this self-healing: a booking site provisioned before
195
+ // this feature (its D1 has `bookings` but no `availability_snapshot`) gets
196
+ // the table here on the first publish, without waiting for a skill re-run.
197
+ const esc = payload.replace(/'/g, "''");
198
+ const sql = `CREATE TABLE IF NOT EXISTS availability_snapshot (
199
+ id INTEGER PRIMARY KEY CHECK (id = 1),
200
+ payload TEXT,
201
+ updatedAt TEXT
202
+ );
203
+ INSERT INTO availability_snapshot (id, payload, updatedAt)
204
+ VALUES (1, '${esc}', '${nowIso}')
205
+ ON CONFLICT(id) DO UPDATE SET payload=excluded.payload, updatedAt=excluded.updatedAt;`;
206
+ const workDir = mkdtempSync(join(tmpdir(), "publish-availability-"));
207
+ const sqlFile = join(workDir, "upsert.sql");
208
+ try {
209
+ writeFileSync(sqlFile, sql);
210
+ const wranglerEnv = {
211
+ CLOUDFLARE_API_TOKEN: token,
212
+ ...(secrets.CLOUDFLARE_ACCOUNT_ID ? { CLOUDFLARE_ACCOUNT_ID: secrets.CLOUDFLARE_ACCOUNT_ID } : {}),
213
+ };
214
+ const proc = spawnSync("npx", ["wrangler", "d1", "execute", dbName, "--remote", "--file", sqlFile], {
215
+ env: { ...process.env, ...wranglerEnv },
216
+ encoding: "utf-8",
217
+ maxBuffer: 16 * 1024 * 1024,
218
+ timeout: 60_000,
219
+ });
220
+ if (proc.status !== 0) {
221
+ console.error(`[calendar-availability] op=publish accountId=${accountId} result=error reason=d1-write-failed status=${proc.status}${proc.signal ? ` signal=${proc.signal}` : ""}`);
222
+ writeLastPublish(accountDir, "error", slotCount, readPriorSuccessAt(accountDir));
223
+ return;
224
+ }
225
+ }
226
+ finally {
227
+ rmSync(workDir, { recursive: true, force: true });
228
+ }
229
+ console.error(`[calendar-availability] op=publish accountId=${accountId} slots=${slotCount} result=success`);
230
+ writeLastPublish(accountDir, "success", slotCount, nowIso);
231
+ }
232
+ finally {
233
+ releaseLock();
234
+ }
235
+ }
236
+ main().catch((err) => {
237
+ console.error(`[calendar-availability] op=publish result=error reason=fatal err="${err instanceof Error ? err.message : String(err)}"`);
238
+ releaseLock();
239
+ process.exit(1);
240
+ });
241
+ //# sourceMappingURL=publish-availability.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"publish-availability.js","sourceRoot":"","sources":["../../src/scripts/publish-availability.ts"],"names":[],"mappings":"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"}
@@ -50,6 +50,19 @@ function textResult(text, isError = false) {
50
50
  ...(isError ? { isError: true } : {}),
51
51
  };
52
52
  }
53
+ // Task 1606 — render a recall row's attachment as an indented suffix the agent
54
+ // can act on. `path` points at the house-stored bytes (an image renders when the
55
+ // agent Reads it); `readableTextPath`, when the platform has extracted text, is
56
+ // the plain-text sibling to Read instead of the binary. Empty string when the
57
+ // row has no attachment, so callers append unconditionally.
58
+ function attachmentSuffix(att) {
59
+ if (!att || !att.path)
60
+ return "";
61
+ let s = `\n [attachment: ${att.filename ?? "file"} (${att.mimeType ?? "unknown"}); Read: ${att.path}]`;
62
+ if (att.readableTextPath)
63
+ s += `\n [readable text; Read: ${att.readableTextPath}]`;
64
+ return s;
65
+ }
53
66
  // ─── whatsapp-login-start ──────────────────────────────────────────────
54
67
  lifelineTool(server, "whatsapp-login-start", 'Start WhatsApp pairing. Requires the operator\'s WhatsApp phone number (E.164). Returns an 8-character pairing code the operator types into WhatsApp → Linked Devices → Link a Device → "Link with phone number instead." No QR image — this surface is an async chat where a rotating QR cannot be scanned in time.', {
55
68
  phone: z.string().describe("WhatsApp phone number in E.164 (e.g. +441234567890) — the number being linked"),
@@ -294,7 +307,7 @@ lifelineTool(server, "whatsapp-send-admin", "Send a WhatsApp text message to a p
294
307
  }
295
308
  });
296
309
  // ─── whatsapp-conversations ──────────────────────────────────────────
297
- lifelineTool(server, "whatsapp-conversations", "List WhatsApp conversations with recent message activity. Returns each conversation's JID, type (group or DM), last message timestamp, and message count. Messages are from the in-memory store (clears on server restart). Use this to see which conversations have recent activity before retrieving specific messages with whatsapp-messages.", {
310
+ lifelineTool(server, "whatsapp-conversations", "List WhatsApp conversations with recent message activity. Returns each conversation's JID, type (group or DM), last message timestamp, and message count. Reads the durable on-disk message store; on a sub-account session it lists the house-stored threads bound to that sub-account. Use this to see which conversations have recent activity before retrieving specific messages with whatsapp-messages.", {
298
311
  accountId: z.string().optional().describe('Account ID. Defaults to the calling session’s own account; the house admin may pass another account id explicitly.'),
299
312
  }, async ({ accountId }) => {
300
313
  try {
@@ -322,7 +335,7 @@ lifelineTool(server, "whatsapp-conversations", "List WhatsApp conversations with
322
335
  }
323
336
  });
324
337
  // ─── whatsapp-messages ───────────────────────────────────────────────
325
- lifelineTool(server, "whatsapp-messages", "Retrieve stored WhatsApp messages for a specific conversation. Returns messages in chronological order (oldest first) with sender, body, timestamp, and reply context. Messages are from the in-memory store (clears on server restart). Use whatsapp-conversations first to see available JIDs.", {
338
+ lifelineTool(server, "whatsapp-messages", "Retrieve stored WhatsApp messages for a specific conversation. Returns messages in chronological order (oldest first) with sender, body, and timestamp. Reads the durable on-disk message store; on a sub-account session it reads the house-stored thread when its counterparty is bound to that sub-account. Use whatsapp-conversations first to see available JIDs.", {
326
339
  jid: z.string().describe("Conversation JID (phone@s.whatsapp.net for DMs, id@g.us for groups)"),
327
340
  accountId: z.string().optional().describe('Account ID. Defaults to the calling session’s own account; the house admin may pass another account id explicitly.'),
328
341
  limit: z.number().optional().describe("Max number of most recent messages to return (default: all)"),
@@ -348,6 +361,7 @@ lifelineTool(server, "whatsapp-messages", "Retrieve stored WhatsApp messages for
348
361
  if (m.quoted?.body) {
349
362
  line += `\n ↳ replying to: "${m.quoted.body}"`;
350
363
  }
364
+ line += attachmentSuffix(m.attachment);
351
365
  return line;
352
366
  });
353
367
  return textResult(`Messages for ${jid} (${messages.length}):\n${lines.join("\n")}`);
@@ -366,8 +380,8 @@ lifelineTool(server, "whatsapp-messages", "Retrieve stored WhatsApp messages for
366
380
  // filter (m:Message:WhatsAppMessage)-[:PART_OF]->(c {sessionKey}).
367
381
  //
368
382
  // Pass either `sessionKey` (direct) or `jid` (+ optional `accountId`) to
369
- // derive the public-agent sessionKey. The divergence header surfaces
370
- // graph-row vs in-memory-store count drift as an operator-visible signal.
383
+ // derive the public-agent sessionKey. All three agent read tools now read the
384
+ // same durable disk store with the Task 1491 recall projection (Task 1605).
371
385
  lifelineTool(server, "whatsapp-conversation-graph-state", [
372
386
  "Read persisted WhatsApp message state deterministically — the durable on-disk store, not the ephemeral in-memory ring buffer.",
373
387
  "Pass `jid` (single conversation) to get that conversation's messages plus its `sessionId`.",
@@ -396,7 +410,7 @@ lifelineTool(server, "whatsapp-conversation-graph-state", [
396
410
  const ts = r.dateSent ?? "(no-date)";
397
411
  const direction = r.fromMe ? "→" : "←";
398
412
  const who = r.senderName ? `${r.senderName} (${r.senderTelephone})` : r.senderTelephone ?? "(unknown)";
399
- return `[${ts}] ${direction} ${r.remoteJid} ${who}: ${r.body ?? "(no body)"}`;
413
+ return `[${ts}] ${direction} ${r.remoteJid} ${who}: ${r.body ?? "(no body)"}${attachmentSuffix(r.attachment)}`;
400
414
  });
401
415
  return textResult([header, "", ...lines].join("\n"));
402
416
  }
@@ -411,25 +425,12 @@ lifelineTool(server, "whatsapp-conversation-graph-state", [
411
425
  const result = await callApi("whatsapp-conversation-graph-state", `/api/whatsapp/conversation-graph-state?${paramsSingle}`, "GET", undefined, callerHeaders);
412
426
  if (result.error)
413
427
  return textResult(`Graph state failed: ${result.error}`, true);
414
- let memoryCount = null;
415
- if (jid) {
416
- try {
417
- const mem = await callApi("whatsapp-conversation-graph-state", `/api/whatsapp/messages?accountId=${encodeURIComponent(id)}&jid=${encodeURIComponent(jid)}`, "GET", undefined, callerHeaders);
418
- if (Array.isArray(mem.messages))
419
- memoryCount = mem.messages.length;
420
- }
421
- catch {
422
- // Best-effort — divergence header omits memory count if the call fails.
423
- }
424
- }
425
428
  const rows = result.graphRows ?? [];
426
429
  const headerParts = [
427
430
  `sessionKey=${result.sessionKey}`,
428
431
  `sessionId=${result.sessionId ?? "null"}`,
429
432
  `graphRows=${rows.length}`,
430
433
  ];
431
- if (memoryCount !== null)
432
- headerParts.push(`memoryRows=${memoryCount}`);
433
434
  headerParts.push(`ms=${result.ms}`);
434
435
  const header = `[${headerParts.join(" ")}]`;
435
436
  if (rows.length === 0) {
@@ -437,16 +438,13 @@ lifelineTool(server, "whatsapp-conversation-graph-state", [
437
438
  header,
438
439
  "",
439
440
  "No messages persisted on this Conversation node.",
440
- memoryCount !== null && memoryCount > 0
441
- ? `In-memory store has ${memoryCount} message(s) — divergence: writer pipeline may not have flushed to disk yet.`
442
- : "In-memory store also empty (or could not be queried).",
443
441
  ].join("\n"));
444
442
  }
445
443
  const lines = rows.map((r) => {
446
444
  const ts = r.dateSent ? r.dateSent : "(no-date)";
447
445
  const direction = r.fromMe ? "→" : "←";
448
446
  const sender = r.senderTelephone ?? "(unknown)";
449
- return `[${ts}] ${direction} ${sender}: ${r.body ?? "(no body)"}`;
447
+ return `[${ts}] ${direction} ${sender}: ${r.body ?? "(no body)"}${attachmentSuffix(r.attachment)}`;
450
448
  });
451
449
  return textResult([header, "", ...lines].join("\n"));
452
450
  }