@rubytech/create-maxy-code 0.1.603 → 0.1.604

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 (120) hide show
  1. package/dist/__tests__/email-sampler-unit.test.js +22 -0
  2. package/dist/index.js +3 -0
  3. package/dist/port-resolution.js +9 -1
  4. package/package.json +1 -1
  5. package/payload/platform/lib/routine-templates/dist/roster.d.ts +17 -1
  6. package/payload/platform/lib/routine-templates/dist/roster.d.ts.map +1 -1
  7. package/payload/platform/lib/routine-templates/dist/roster.js +8 -0
  8. package/payload/platform/lib/routine-templates/dist/roster.js.map +1 -1
  9. package/payload/platform/lib/routine-templates/src/__tests__/seed.test.ts +26 -1
  10. package/payload/platform/lib/routine-templates/src/roster.ts +26 -1
  11. package/payload/platform/lib/telegram-store-read/dist/index.d.ts +59 -0
  12. package/payload/platform/lib/telegram-store-read/dist/index.d.ts.map +1 -0
  13. package/payload/platform/lib/telegram-store-read/dist/index.js +219 -0
  14. package/payload/platform/lib/telegram-store-read/dist/index.js.map +1 -0
  15. package/payload/platform/lib/telegram-store-read/src/__tests__/fold.test.ts +124 -0
  16. package/payload/platform/lib/telegram-store-read/src/index.ts +262 -0
  17. package/payload/platform/lib/telegram-store-read/tsconfig.json +9 -0
  18. package/payload/platform/lib/telegram-store-read/vitest.config.ts +9 -0
  19. package/payload/platform/package.json +2 -2
  20. package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +37 -4
  21. package/payload/platform/plugins/admin/skills/whats-new/SKILL.md +6 -0
  22. package/payload/platform/plugins/docs/references/outlook-guide.md +21 -2
  23. package/payload/platform/plugins/docs/references/telegram-guide.md +15 -1
  24. package/payload/platform/plugins/email/PLUGIN.md +10 -1
  25. package/payload/platform/plugins/email/mcp/dist/sampler/__tests__/sample-mailbox.test.js +30 -0
  26. package/payload/platform/plugins/email/mcp/dist/sampler/__tests__/sample-mailbox.test.js.map +1 -1
  27. package/payload/platform/plugins/email/mcp/dist/sampler/main.js +49 -3
  28. package/payload/platform/plugins/email/mcp/dist/sampler/main.js.map +1 -1
  29. package/payload/platform/plugins/email/mcp/dist/sampler/sample-mailbox.d.ts +5 -0
  30. package/payload/platform/plugins/email/mcp/dist/sampler/sample-mailbox.d.ts.map +1 -1
  31. package/payload/platform/plugins/email/mcp/dist/sampler/sample-mailbox.js +10 -5
  32. package/payload/platform/plugins/email/mcp/dist/sampler/sample-mailbox.js.map +1 -1
  33. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-ingest-section-properties-strip.test.js +11 -0
  34. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-ingest-section-properties-strip.test.js.map +1 -1
  35. package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest.d.ts.map +1 -1
  36. package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest.js +8 -0
  37. package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest.js.map +1 -1
  38. package/payload/platform/plugins/outlook/mcp/dist/__tests__/sample-outlook-mailbox.test.d.ts +2 -0
  39. package/payload/platform/plugins/outlook/mcp/dist/__tests__/sample-outlook-mailbox.test.d.ts.map +1 -0
  40. package/payload/platform/plugins/outlook/mcp/dist/__tests__/sample-outlook-mailbox.test.js +324 -0
  41. package/payload/platform/plugins/outlook/mcp/dist/__tests__/sample-outlook-mailbox.test.js.map +1 -0
  42. package/payload/platform/plugins/outlook/mcp/dist/__tests__/sampler-cursor.test.d.ts +2 -0
  43. package/payload/platform/plugins/outlook/mcp/dist/__tests__/sampler-cursor.test.d.ts.map +1 -0
  44. package/payload/platform/plugins/outlook/mcp/dist/__tests__/sampler-cursor.test.js +102 -0
  45. package/payload/platform/plugins/outlook/mcp/dist/__tests__/sampler-cursor.test.js.map +1 -0
  46. package/payload/platform/plugins/outlook/mcp/dist/__tests__/sampler-enumerate.test.d.ts +2 -0
  47. package/payload/platform/plugins/outlook/mcp/dist/__tests__/sampler-enumerate.test.d.ts.map +1 -0
  48. package/payload/platform/plugins/outlook/mcp/dist/__tests__/sampler-enumerate.test.js +125 -0
  49. package/payload/platform/plugins/outlook/mcp/dist/__tests__/sampler-enumerate.test.js.map +1 -0
  50. package/payload/platform/plugins/outlook/mcp/dist/sampler/arm.d.ts +2 -0
  51. package/payload/platform/plugins/outlook/mcp/dist/sampler/arm.d.ts.map +1 -0
  52. package/payload/platform/plugins/outlook/mcp/dist/sampler/arm.js +109 -0
  53. package/payload/platform/plugins/outlook/mcp/dist/sampler/arm.js.map +1 -0
  54. package/payload/platform/plugins/outlook/mcp/dist/sampler/cursor.d.ts +25 -0
  55. package/payload/platform/plugins/outlook/mcp/dist/sampler/cursor.d.ts.map +1 -0
  56. package/payload/platform/plugins/outlook/mcp/dist/sampler/cursor.js +94 -0
  57. package/payload/platform/plugins/outlook/mcp/dist/sampler/cursor.js.map +1 -0
  58. package/payload/platform/plugins/outlook/mcp/dist/sampler/enumerate.d.ts +41 -0
  59. package/payload/platform/plugins/outlook/mcp/dist/sampler/enumerate.d.ts.map +1 -0
  60. package/payload/platform/plugins/outlook/mcp/dist/sampler/enumerate.js +122 -0
  61. package/payload/platform/plugins/outlook/mcp/dist/sampler/enumerate.js.map +1 -0
  62. package/payload/platform/plugins/outlook/mcp/dist/sampler/sample-outlook-mailbox.d.ts +73 -0
  63. package/payload/platform/plugins/outlook/mcp/dist/sampler/sample-outlook-mailbox.d.ts.map +1 -0
  64. package/payload/platform/plugins/outlook/mcp/dist/sampler/sample-outlook-mailbox.js +162 -0
  65. package/payload/platform/plugins/outlook/mcp/dist/sampler/sample-outlook-mailbox.js.map +1 -0
  66. package/payload/platform/plugins/scheduling/mcp/dist/index.js +1 -1
  67. package/payload/platform/plugins/scheduling/mcp/dist/index.js.map +1 -1
  68. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/gate-flag.test.d.ts +2 -0
  69. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/gate-flag.test.d.ts.map +1 -0
  70. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/gate-flag.test.js +90 -0
  71. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/gate-flag.test.js.map +1 -0
  72. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/gate.test.js +26 -4
  73. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/gate.test.js.map +1 -1
  74. package/payload/platform/plugins/scheduling/mcp/dist/lib/gate-flag.d.ts +78 -0
  75. package/payload/platform/plugins/scheduling/mcp/dist/lib/gate-flag.d.ts.map +1 -0
  76. package/payload/platform/plugins/scheduling/mcp/dist/lib/gate-flag.js +76 -0
  77. package/payload/platform/plugins/scheduling/mcp/dist/lib/gate-flag.js.map +1 -0
  78. package/payload/platform/plugins/scheduling/mcp/dist/lib/gate.d.ts +8 -0
  79. package/payload/platform/plugins/scheduling/mcp/dist/lib/gate.d.ts.map +1 -1
  80. package/payload/platform/plugins/scheduling/mcp/dist/lib/gate.js +9 -1
  81. package/payload/platform/plugins/scheduling/mcp/dist/lib/gate.js.map +1 -1
  82. package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/gate-dispatch-wiring.test.js +15 -0
  83. package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/gate-dispatch-wiring.test.js.map +1 -1
  84. package/payload/platform/plugins/scheduling/mcp/dist/scripts/agent-turn-dispatch.d.ts +12 -1
  85. package/payload/platform/plugins/scheduling/mcp/dist/scripts/agent-turn-dispatch.d.ts.map +1 -1
  86. package/payload/platform/plugins/scheduling/mcp/dist/scripts/agent-turn-dispatch.js +34 -1
  87. package/payload/platform/plugins/scheduling/mcp/dist/scripts/agent-turn-dispatch.js.map +1 -1
  88. package/payload/platform/plugins/scheduling/mcp/dist/scripts/check-due-events.js +9 -1
  89. package/payload/platform/plugins/scheduling/mcp/dist/scripts/check-due-events.js.map +1 -1
  90. package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/scheduling-gate.test.js +116 -4
  91. package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/scheduling-gate.test.js.map +1 -1
  92. package/payload/platform/plugins/scheduling/mcp/dist/tools/scheduling-gate.d.ts +60 -2
  93. package/payload/platform/plugins/scheduling/mcp/dist/tools/scheduling-gate.d.ts.map +1 -1
  94. package/payload/platform/plugins/scheduling/mcp/dist/tools/scheduling-gate.js +101 -4
  95. package/payload/platform/plugins/scheduling/mcp/dist/tools/scheduling-gate.js.map +1 -1
  96. package/payload/platform/plugins/telegram/PLUGIN.md +1 -1
  97. package/payload/platform/plugins/telegram/mcp/dist/__tests__/messages.test.d.ts +2 -0
  98. package/payload/platform/plugins/telegram/mcp/dist/__tests__/messages.test.d.ts.map +1 -0
  99. package/payload/platform/plugins/telegram/mcp/dist/__tests__/messages.test.js +157 -0
  100. package/payload/platform/plugins/telegram/mcp/dist/__tests__/messages.test.js.map +1 -0
  101. package/payload/platform/plugins/telegram/mcp/dist/__tests__/public-pin.test.js +2 -2
  102. package/payload/platform/plugins/telegram/mcp/dist/__tests__/public-pin.test.js.map +1 -1
  103. package/payload/platform/plugins/telegram/mcp/dist/index.js +32 -34
  104. package/payload/platform/plugins/telegram/mcp/dist/index.js.map +1 -1
  105. package/payload/platform/plugins/telegram/mcp/dist/lib/public-pin.js +1 -1
  106. package/payload/platform/plugins/telegram/mcp/dist/lib/public-pin.js.map +1 -1
  107. package/payload/platform/plugins/telegram/mcp/dist/tools/messages.d.ts +27 -0
  108. package/payload/platform/plugins/telegram/mcp/dist/tools/messages.d.ts.map +1 -0
  109. package/payload/platform/plugins/telegram/mcp/dist/tools/messages.js +152 -0
  110. package/payload/platform/plugins/telegram/mcp/dist/tools/messages.js.map +1 -0
  111. package/payload/platform/plugins/telegram/skills/build/SKILL.md +1 -1
  112. package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js +1 -1
  113. package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js.map +1 -1
  114. package/payload/platform/templates/specialists/agents/personal-assistant.md +1 -1
  115. package/payload/server/public/operator.html +1 -1
  116. package/payload/server/server.js +69 -5
  117. package/payload/platform/plugins/telegram/mcp/dist/tools/message-history.d.ts +0 -16
  118. package/payload/platform/plugins/telegram/mcp/dist/tools/message-history.d.ts.map +0 -1
  119. package/payload/platform/plugins/telegram/mcp/dist/tools/message-history.js +0 -68
  120. package/payload/platform/plugins/telegram/mcp/dist/tools/message-history.js.map +0 -1
@@ -0,0 +1,262 @@
1
+ /**
2
+ * Task 2709 — reading one Telegram conversation back off disk, patches folded.
3
+ *
4
+ * The store is the only complete record of a Telegram conversation. No platform
5
+ * path writes a `:Message` node for Telegram: `persistMessage` in
6
+ * `ui/app/lib/neo4j-store.ts` has one caller, the webchat customer-inbound path,
7
+ * so a `:Message` carrying `channel:'telegram'` was written by an agent calling
8
+ * `memory-write` and records only what that agent chose to save. On 2026-08-13
9
+ * that was every inbound and one of ten outbound, and the agent read it back as
10
+ * the whole conversation.
11
+ *
12
+ * A message row is never rewritten once appended — `telegram-reader.ts` streams
13
+ * byte offsets as SSE resume ids, so a read-modify-write would invalidate every
14
+ * offset behind it. A correction is therefore a SECOND line, a
15
+ * `TelegramUpdateRecord`, naming its target. Reading the message rows alone
16
+ * yields the pre-patch text: a voice note's placeholder instead of its
17
+ * transcript, an edited message's original wording, and a withdrawn message with
18
+ * nothing saying it was withdrawn. Folding is what makes the read truthful,
19
+ * which is why this is a library and not four lines at the call site.
20
+ *
21
+ * Attachment ids come back RAW. `telegram-reader.ts` resolves each one to
22
+ * servable metadata against an uploads directory, because it is answering a
23
+ * browser; an agent has no use for that URL, and importing the uploads directory
24
+ * into the MCP would drag an HTTP concern across the plugin boundary. The three
25
+ * rules that ARE shared — body last-write-wins, buttons last-write-wins, deleted
26
+ * sticky-true — are pinned against the route by
27
+ * `ui/app/lib/telegram/__tests__/store-read-boundary.test.ts`.
28
+ *
29
+ * The key and filename rules are imported from `telegram-reach` rather than
30
+ * restated. That module is already the one derivation the store writer uses, and
31
+ * Task 2644 is the record of what a second derivation costs.
32
+ */
33
+ import { existsSync, readFileSync, readdirSync } from "node:fs";
34
+ import { join } from "node:path";
35
+ import {
36
+ channelKeyToFileStem,
37
+ fileStemToChannelKey,
38
+ isStorableChannelKey,
39
+ } from "../../telegram-reach/dist/index.js";
40
+
41
+ /** One stored message with every patch that names it already applied. */
42
+ export interface FoldedMessage {
43
+ messageId: string;
44
+ /** ISO 8601, this install's clock at write time — NOT Telegram's own second. */
45
+ dateSent: string;
46
+ /** The patched body when an update replaced it, else the row's own. */
47
+ body: string;
48
+ /** 'inbound' | 'agent' | 'operator-manual'. Kept as the string the store
49
+ * holds: a reader that meets an origin this build does not know should show
50
+ * it, not drop the message. */
51
+ origin: string;
52
+ fromMe: boolean;
53
+ senderId: string;
54
+ senderName: string | null;
55
+ /** The sender withdrew this at their end. The body is KEPT: Telegram sends
56
+ * none on a deletion, so the stored body is the only account anywhere of what
57
+ * was withdrawn. */
58
+ deleted: boolean;
59
+ /** Servable attachment ids in arrival order, de-duplicated. Raw ids, never
60
+ * resolved metadata — see the file header. */
61
+ attachmentIds: string[];
62
+ /** A card's inline-keyboard labels. Null on an ordinary message that never
63
+ * carried a keyboard; an EMPTY ARRAY when an edit removed the one it had.
64
+ * The two are different facts and the distinction is the route's, kept here
65
+ * so the agent and the operator cannot read one card two ways. */
66
+ buttons: { text: string; data: string | null }[] | null;
67
+ }
68
+
69
+ export interface ConversationCensus {
70
+ /** Store files in this directory holding a storable channel key. */
71
+ conversations: number;
72
+ /** Message rows across all of them. Update rows are patches, not messages, and
73
+ * counting them would inflate the total. */
74
+ rows: number;
75
+ /** Newest `dateSent` anywhere in the directory, or null when empty. */
76
+ newest: string | null;
77
+ }
78
+
79
+ /** The fields this module reads. Declared structurally rather than imported from
80
+ * `ui/app/lib/telegram/message-store.ts`: the plugin cannot import from
81
+ * `platform/ui/app`, which is the same boundary `telegram-reach` sits behind. */
82
+ interface StoreRowLike {
83
+ kind?: unknown;
84
+ messageId?: unknown;
85
+ targetMessageId?: unknown;
86
+ dateSent?: unknown;
87
+ body?: unknown;
88
+ fromMe?: unknown;
89
+ senderId?: unknown;
90
+ senderName?: unknown;
91
+ origin?: unknown;
92
+ deleted?: unknown;
93
+ attachmentIds?: unknown;
94
+ buttons?: unknown;
95
+ }
96
+
97
+ function parseLines(raw: string): StoreRowLike[] {
98
+ const out: StoreRowLike[] = [];
99
+ for (const line of raw.split("\n")) {
100
+ if (!line.trim()) continue;
101
+ try {
102
+ out.push(JSON.parse(line) as StoreRowLike);
103
+ } catch {
104
+ // A partial write from a crash mid-append is skipped, never fatal: one
105
+ // corrupt line must not hide every other message in the conversation.
106
+ }
107
+ }
108
+ return out;
109
+ }
110
+
111
+ function isUpdateRow(row: StoreRowLike): boolean {
112
+ return row.kind === "update";
113
+ }
114
+
115
+ function readButtons(value: unknown): { text: string; data: string | null }[] | null {
116
+ if (!Array.isArray(value)) return null;
117
+ const out: { text: string; data: string | null }[] = [];
118
+ for (const b of value) {
119
+ if (!b || typeof b !== "object") continue;
120
+ const { text, data } = b as { text?: unknown; data?: unknown };
121
+ if (typeof text !== "string") continue;
122
+ out.push({ text, data: typeof data === "string" ? data : null });
123
+ }
124
+ return out.length > 0 ? out : null;
125
+ }
126
+
127
+ interface Patch {
128
+ body?: string;
129
+ buttons?: { text: string; data: string | null }[];
130
+ deleted?: true;
131
+ attachmentIds: string[];
132
+ }
133
+
134
+ /** Fold every update row into one patch per target id.
135
+ *
136
+ * Mirrors `telegram-reader.ts:77-106`. Later rows win for the body and the
137
+ * buttons. Attachment ids accumulate in arrival order and are de-duplicated per
138
+ * target, because two rows can legitimately name the same id: `appendMessage`
139
+ * dedupes a Telegram redelivery, but the route does not read that result — it
140
+ * downloads again, resolves the SAME deterministic id, and patches again.
141
+ * `deleted` is sticky: a withdrawal is not undone by a later transcript or
142
+ * media patch, and letting one clear it would hide the withdrawal. */
143
+ function foldPatches(rows: StoreRowLike[]): Map<string, Patch> {
144
+ const out = new Map<string, Patch>();
145
+ for (const row of rows) {
146
+ if (!isUpdateRow(row)) continue;
147
+ if (typeof row.targetMessageId !== "string") continue;
148
+ const patch = out.get(row.targetMessageId) ?? { attachmentIds: [] };
149
+ if (Array.isArray(row.attachmentIds)) {
150
+ for (const id of row.attachmentIds) {
151
+ if (typeof id !== "string") continue;
152
+ if (patch.attachmentIds.includes(id)) continue;
153
+ patch.attachmentIds.push(id);
154
+ }
155
+ }
156
+ if (typeof row.body === "string") patch.body = row.body;
157
+ // Keyed on the field BEING an array, not on it holding anything. An operator
158
+ // editing a card down to no buttons produces `buttons: []`, and that is a
159
+ // removal: the route reads it as one, because an empty array is truthy
160
+ // there. Treating it as "no patch" would keep showing the agent buttons the
161
+ // customer can no longer see, with nothing failing while the two surfaces
162
+ // disagreed.
163
+ if (Array.isArray(row.buttons)) patch.buttons = readButtons(row.buttons) ?? [];
164
+ if (row.deleted === true) patch.deleted = true;
165
+ out.set(row.targetMessageId, patch);
166
+ }
167
+ return out;
168
+ }
169
+
170
+ function conversationFile(storeDir: string, channelKey: string): string | null {
171
+ // The guard, not the path. Both halves of a key are Telegram numeric ids
172
+ // around an optional literal `biz`, so a key that passes cannot carry a `.`,
173
+ // `/` or `\`, and one that fails is refused rather than sanitised into a path
174
+ // nobody asked for.
175
+ if (!isStorableChannelKey(channelKey)) return null;
176
+ return join(storeDir, `${channelKeyToFileStem(channelKey)}.jsonl`);
177
+ }
178
+
179
+ /**
180
+ * One conversation, oldest first, with every patch applied.
181
+ *
182
+ * Empty for an unstorable key, a missing directory and a conversation with no
183
+ * file. Those are three different facts, and none of them is an error the caller
184
+ * can act on differently: all three mean there is nothing stored to show.
185
+ */
186
+ export function readFoldedConversation(storeDir: string, channelKey: string): FoldedMessage[] {
187
+ const file = conversationFile(storeDir, channelKey);
188
+ if (file === null || !existsSync(file)) return [];
189
+ let raw: string;
190
+ try {
191
+ raw = readFileSync(file, "utf8");
192
+ } catch {
193
+ return [];
194
+ }
195
+ const rows = parseLines(raw);
196
+ const patches = foldPatches(rows);
197
+ const out: FoldedMessage[] = [];
198
+ for (const row of rows) {
199
+ if (isUpdateRow(row)) continue;
200
+ if (typeof row.messageId !== "string") continue;
201
+ if (typeof row.dateSent !== "string") continue;
202
+ const patch = patches.get(row.messageId);
203
+ out.push({
204
+ messageId: row.messageId,
205
+ dateSent: row.dateSent,
206
+ body: patch?.body ?? (typeof row.body === "string" ? row.body : ""),
207
+ origin: typeof row.origin === "string" ? row.origin : "inbound",
208
+ fromMe: row.fromMe === true,
209
+ senderId: typeof row.senderId === "string" ? row.senderId : "",
210
+ senderName: typeof row.senderName === "string" ? row.senderName : null,
211
+ deleted: patch?.deleted === true,
212
+ attachmentIds: patch?.attachmentIds ?? [],
213
+ buttons: patch?.buttons ?? readButtons(row.buttons),
214
+ });
215
+ }
216
+ // Sort AFTER folding, so a patch that arrived before its target in file order
217
+ // still lands on it. Ties keep file order, which is arrival order.
218
+ return out.sort((a, b) => a.dateSent.localeCompare(b.dateSent));
219
+ }
220
+
221
+ /**
222
+ * What this account's store actually holds.
223
+ *
224
+ * `conversations` beside `rows` is load-bearing. A tool that returns nothing
225
+ * looks identical to a conversation that is empty, and `conversations=0
226
+ * rows=0` is also exactly what a reader pointed at the wrong directory prints —
227
+ * which is the failure Task 2644 shipped and Task 2557 named. With both numbers,
228
+ * a zero result on an account showing `conversations>0` is a wrong chat id, and
229
+ * `conversations=0` on a live account is a dead reader.
230
+ */
231
+ export function conversationCensus(storeDir: string): ConversationCensus {
232
+ const empty: ConversationCensus = { conversations: 0, rows: 0, newest: null };
233
+ if (!storeDir || !existsSync(storeDir)) return empty;
234
+ let entries: string[];
235
+ try {
236
+ entries = readdirSync(storeDir);
237
+ } catch {
238
+ return empty;
239
+ }
240
+ let conversations = 0;
241
+ let rows = 0;
242
+ let newest: string | null = null;
243
+ for (const entry of entries) {
244
+ if (!entry.endsWith(".jsonl")) continue;
245
+ const channelKey = fileStemToChannelKey(entry.slice(0, -".jsonl".length));
246
+ if (!isStorableChannelKey(channelKey)) continue;
247
+ conversations += 1;
248
+ let raw: string;
249
+ try {
250
+ raw = readFileSync(join(storeDir, entry), "utf8");
251
+ } catch {
252
+ continue;
253
+ }
254
+ for (const row of parseLines(raw)) {
255
+ if (isUpdateRow(row)) continue;
256
+ rows += 1;
257
+ if (typeof row.dateSent !== "string") continue;
258
+ if (newest === null || newest.localeCompare(row.dateSent) < 0) newest = row.dateSent;
259
+ }
260
+ }
261
+ return { conversations, rows, newest };
262
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "outDir": "dist",
5
+ "rootDir": "src"
6
+ },
7
+ "include": ["src"],
8
+ "exclude": ["src/__tests__"]
9
+ }
@@ -0,0 +1,9 @@
1
+ import { defineConfig } from "vitest/config";
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ environment: "node",
6
+ globals: false,
7
+ include: ["src/__tests__/**/*.test.ts"],
8
+ },
9
+ });
@@ -10,8 +10,8 @@
10
10
  "pdf-lib": "^1.17.1"
11
11
  },
12
12
  "scripts": {
13
- "build": "tsc -p lib/account-schema-regions/tsconfig.json && tsc -p lib/account-ingest-token/tsconfig.json && tsc -p lib/models/tsconfig.json && tsc -p lib/agent-slug/tsconfig.json && tsc -p lib/mcp-spawn-tee/tsconfig.json && tsc -p lib/mcp-lifeline/tsconfig.json && tsc -p lib/mcp-eager/tsconfig.json && tsc -p lib/account-enumeration/tsconfig.json && tsc -p lib/account-scope/tsconfig.json && tsc -p lib/shared-folder/tsconfig.json && tsc -p lib/graph-write/tsconfig.json && tsc -p lib/ledger-core/tsconfig.json && tsc -p lib/embed-client/tsconfig.json && tsc -p lib/graph-mcp/tsconfig.json && tsc -p lib/graph-trash/tsconfig.json && tsc -p lib/active-rules/tsconfig.json && tsc -p lib/admin-conversation-purge/tsconfig.json && tsc -p lib/graph-search/tsconfig.json && tsc -p lib/graph-style/tsconfig.json && tsc -p lib/device-url/tsconfig.json && tsc -p lib/brand-templating/tsconfig.json && tsc -p lib/entitlement/tsconfig.json && tsc -p lib/task-secrets/tsconfig.json && tsc -p lib/admins-write/tsconfig.json && tsc -p lib/admin-access-password/tsconfig.json && tsc -p lib/persistent-components/tsconfig.json && tsc -p lib/require-port-env/tsconfig.json && tsc -p lib/aeo-llms-txt-writer/tsconfig.json && tsc -p lib/obsidian-parser/tsconfig.json && tsc -p lib/storage-broker/tsconfig.json && tsc -p lib/routine-templates/tsconfig.json && tsc -p lib/agent-dispatch-rule/tsconfig.json && tsc -p lib/canonical-instant/tsconfig.json && tsc -p lib/dispatch-read/tsconfig.json && tsc -p lib/dispatch-write/tsconfig.json && tsc -p lib/learning-constants/tsconfig.json && tsc -p lib/telegram-button-labels/tsconfig.json && tsc -p lib/telegram-media/tsconfig.json && tsc -p lib/telegram-managed-bot/tsconfig.json && tsc -p lib/telegram-reach/tsconfig.json && tsc -p services/claude-session-manager/tsconfig.json && tsc -p services/whatsapp-channel/tsconfig.json && tsc -p services/webchat-channel/tsconfig.json && tsc -p services/telegram-channel/tsconfig.json && NODE_OPTIONS='--max-old-space-size=8192' tsc -b plugins/*/mcp/tsconfig.json",
14
- "build:lib": "tsc -p lib/account-schema-regions/tsconfig.json && tsc -p lib/account-ingest-token/tsconfig.json && tsc -p lib/models/tsconfig.json && tsc -p lib/agent-slug/tsconfig.json && tsc -p lib/mcp-spawn-tee/tsconfig.json && tsc -p lib/mcp-lifeline/tsconfig.json && tsc -p lib/mcp-eager/tsconfig.json && tsc -p lib/account-enumeration/tsconfig.json && tsc -p lib/account-scope/tsconfig.json && tsc -p lib/shared-folder/tsconfig.json && tsc -p lib/graph-write/tsconfig.json && tsc -p lib/ledger-core/tsconfig.json && tsc -p lib/embed-client/tsconfig.json && tsc -p lib/graph-mcp/tsconfig.json && tsc -p lib/graph-trash/tsconfig.json && tsc -p lib/active-rules/tsconfig.json && tsc -p lib/admin-conversation-purge/tsconfig.json && tsc -p lib/graph-search/tsconfig.json && tsc -p lib/graph-style/tsconfig.json && tsc -p lib/device-url/tsconfig.json && tsc -p lib/brand-templating/tsconfig.json && tsc -p lib/entitlement/tsconfig.json && tsc -p lib/task-secrets/tsconfig.json && tsc -p lib/admins-write/tsconfig.json && tsc -p lib/admin-access-password/tsconfig.json && tsc -p lib/persistent-components/tsconfig.json && tsc -p lib/require-port-env/tsconfig.json && tsc -p lib/aeo-llms-txt-writer/tsconfig.json && tsc -p lib/obsidian-parser/tsconfig.json && tsc -p lib/storage-broker/tsconfig.json && tsc -p lib/routine-templates/tsconfig.json && tsc -p lib/agent-dispatch-rule/tsconfig.json && tsc -p lib/shared-folder/tsconfig.json && tsc -p lib/canonical-instant/tsconfig.json && tsc -p lib/dispatch-read/tsconfig.json && tsc -p lib/dispatch-write/tsconfig.json && tsc -p lib/telegram-button-labels/tsconfig.json && tsc -p lib/telegram-media/tsconfig.json && tsc -p lib/telegram-managed-bot/tsconfig.json && tsc -p lib/telegram-reach/tsconfig.json",
13
+ "build": "tsc -p lib/account-schema-regions/tsconfig.json && tsc -p lib/account-ingest-token/tsconfig.json && tsc -p lib/models/tsconfig.json && tsc -p lib/agent-slug/tsconfig.json && tsc -p lib/mcp-spawn-tee/tsconfig.json && tsc -p lib/mcp-lifeline/tsconfig.json && tsc -p lib/mcp-eager/tsconfig.json && tsc -p lib/account-enumeration/tsconfig.json && tsc -p lib/account-scope/tsconfig.json && tsc -p lib/shared-folder/tsconfig.json && tsc -p lib/graph-write/tsconfig.json && tsc -p lib/ledger-core/tsconfig.json && tsc -p lib/embed-client/tsconfig.json && tsc -p lib/graph-mcp/tsconfig.json && tsc -p lib/graph-trash/tsconfig.json && tsc -p lib/active-rules/tsconfig.json && tsc -p lib/admin-conversation-purge/tsconfig.json && tsc -p lib/graph-search/tsconfig.json && tsc -p lib/graph-style/tsconfig.json && tsc -p lib/device-url/tsconfig.json && tsc -p lib/brand-templating/tsconfig.json && tsc -p lib/entitlement/tsconfig.json && tsc -p lib/task-secrets/tsconfig.json && tsc -p lib/admins-write/tsconfig.json && tsc -p lib/admin-access-password/tsconfig.json && tsc -p lib/persistent-components/tsconfig.json && tsc -p lib/require-port-env/tsconfig.json && tsc -p lib/aeo-llms-txt-writer/tsconfig.json && tsc -p lib/obsidian-parser/tsconfig.json && tsc -p lib/storage-broker/tsconfig.json && tsc -p lib/routine-templates/tsconfig.json && tsc -p lib/agent-dispatch-rule/tsconfig.json && tsc -p lib/canonical-instant/tsconfig.json && tsc -p lib/dispatch-read/tsconfig.json && tsc -p lib/dispatch-write/tsconfig.json && tsc -p lib/learning-constants/tsconfig.json && tsc -p lib/telegram-button-labels/tsconfig.json && tsc -p lib/telegram-media/tsconfig.json && tsc -p lib/telegram-managed-bot/tsconfig.json && tsc -p lib/telegram-reach/tsconfig.json && tsc -p lib/telegram-store-read/tsconfig.json && tsc -p services/claude-session-manager/tsconfig.json && tsc -p services/whatsapp-channel/tsconfig.json && tsc -p services/webchat-channel/tsconfig.json && tsc -p services/telegram-channel/tsconfig.json && NODE_OPTIONS='--max-old-space-size=8192' tsc -b plugins/*/mcp/tsconfig.json",
14
+ "build:lib": "tsc -p lib/account-schema-regions/tsconfig.json && tsc -p lib/account-ingest-token/tsconfig.json && tsc -p lib/models/tsconfig.json && tsc -p lib/agent-slug/tsconfig.json && tsc -p lib/mcp-spawn-tee/tsconfig.json && tsc -p lib/mcp-lifeline/tsconfig.json && tsc -p lib/mcp-eager/tsconfig.json && tsc -p lib/account-enumeration/tsconfig.json && tsc -p lib/account-scope/tsconfig.json && tsc -p lib/shared-folder/tsconfig.json && tsc -p lib/graph-write/tsconfig.json && tsc -p lib/ledger-core/tsconfig.json && tsc -p lib/embed-client/tsconfig.json && tsc -p lib/graph-mcp/tsconfig.json && tsc -p lib/graph-trash/tsconfig.json && tsc -p lib/active-rules/tsconfig.json && tsc -p lib/admin-conversation-purge/tsconfig.json && tsc -p lib/graph-search/tsconfig.json && tsc -p lib/graph-style/tsconfig.json && tsc -p lib/device-url/tsconfig.json && tsc -p lib/brand-templating/tsconfig.json && tsc -p lib/entitlement/tsconfig.json && tsc -p lib/task-secrets/tsconfig.json && tsc -p lib/admins-write/tsconfig.json && tsc -p lib/admin-access-password/tsconfig.json && tsc -p lib/persistent-components/tsconfig.json && tsc -p lib/require-port-env/tsconfig.json && tsc -p lib/aeo-llms-txt-writer/tsconfig.json && tsc -p lib/obsidian-parser/tsconfig.json && tsc -p lib/storage-broker/tsconfig.json && tsc -p lib/routine-templates/tsconfig.json && tsc -p lib/agent-dispatch-rule/tsconfig.json && tsc -p lib/shared-folder/tsconfig.json && tsc -p lib/canonical-instant/tsconfig.json && tsc -p lib/dispatch-read/tsconfig.json && tsc -p lib/dispatch-write/tsconfig.json && tsc -p lib/telegram-button-labels/tsconfig.json && tsc -p lib/telegram-media/tsconfig.json && tsc -p lib/telegram-managed-bot/tsconfig.json && tsc -p lib/telegram-reach/tsconfig.json && tsc -p lib/telegram-store-read/tsconfig.json",
15
15
  "clean:dist": "node scripts/check-orphan-dist.mjs --remove",
16
16
  "gen:canonical-tools": "node scripts/generate-canonical-tool-names.mjs",
17
17
  "build:memory": "tsc -p plugins/memory/mcp/tsconfig.json",
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: platform-architecture
3
3
  description: Use when grounding any documented-surface claim about what Maxy ships — plugins, skills, specialists, install/deploy flows, internals. This is the install catalogue, not evidence of what is enabled on the current account. For install state on this account, call `capabilities-here`; for documented surface, cite the `Source:` URL inline.
4
- content-hash: sha256:2a7e28897d543df02b543552d72a457eca3956ab090a4bc5b886b487eb950d3e
4
+ content-hash: sha256:5a02da7ca027a086a15c10f3590e339d181f1854e4573c013d94685e49263436
5
5
  brand: maxy-code
6
6
  product-name: Maxy
7
7
  ---
@@ -2971,7 +2971,21 @@ A group's ID is a long negative number, like `-1001234567890`. To find it, @ment
2971
2971
 
2972
2972
  ## Message History
2973
2973
 
2974
- Ask Maxy: "What messages has the bot received?" or "Show recent Telegram activity."
2974
+ Ask Maxy to read a conversation back: "What did I say to Sam on Telegram?" or "Show me the whole
2975
+ chat with that driver." It returns the conversation both ways round, oldest first: every message the bot
2976
+ received and every one it sent. A voice note reads as its transcript, an edited message as its new wording,
2977
+ and a message the other person deleted is shown struck out rather than quietly dropped.
2978
+
2979
+ Give it the chat id, which you can get from "Who can you reach on Telegram?". In a group, the id is the
2980
+ group's own.
2981
+
2982
+ Two things to know:
2983
+
2984
+ - **History starts when this machine started storing it.** A conversation that predates the install, or that
2985
+ ran while the machine was off, is not there to read. Telegram's Bot API offers no way to fetch it back.
2986
+ - **If the same chat id belongs to two conversations, it will ask which you meant** rather than guess. That
2987
+ happens when two of your bots both talk to the same person, and when a connected business account's
2988
+ customer has the same id as your own chat with that bot.
2975
2989
 
2976
2990
  ## Troubleshooting
2977
2991
 
@@ -3130,7 +3144,7 @@ The `outlook` plugin gives the admin agent access to Microsoft 365 / Outlook.com
3130
3144
  | `outlook-mail-attachment` | List or download any message's attachments — received or draft. With `messageId` alone, LISTS them (`id`, `name`, `contentType`, `size`, `isInline`, and a `kind` of file/item/reference) via `GET /me/messages/{id}/attachments` and `$select` (no bytes). A draft is a message in the same collection, so passing a draftId lists what that draft carries; the returned `name`/`id` is what `outlook-draft-edit`'s `removeAttachments` takes. With `messageId`+`attachmentId`, DOWNLOADS one file attachment: its bytes are written to `{accountDir}/uploads/outlook/<messageHash>/<bytesHash>-<name>` at `0o600` and the saved path is returned, ready for the Read tool, `SendUserFile`, or an `outlook-mail-reply` attachment. Only file attachments download — item/reference kinds, empty payloads, and anything over 25 MB are refused with nothing written. `outlook-mail-list` / `outlook-mail-search` / `outlook-mail-fetch-body` report `hasAttachments` so the agent knows when to call this. Needs `Mail.Read`. |
3131
3145
  | `outlook-mail-reply` | Reply in-thread (`createReply` / `createReplyAll` + send). Threads natively via conversationId. `replyAll`, added cc/bcc, and account-scoped file attachments (up to 25 MB per file; files over Graph's ~3 MB inline limit use a chunked upload session). Body is plain text, prepended above the quote. A reply whose recipients resolve **only** to the sending mailbox is **refused** before the send and its draft deleted: Graph addresses a reply to the original message's sender, so replying to your own sent message addresses it to yourself. An explicit `cc` does not exempt it — use `outlook-mail-send` with an explicit `to`. The resolved recipients are logged as `to=`. Needs `Mail.ReadWrite` + `Mail.Send`. |
3132
3146
  | `outlook-mail-delete` | Move messages to Deleted Items (`POST /me/messages/{id}/move`). Recoverable — never hard-deletes. An id already gone is counted not-moved. Needs `Mail.ReadWrite`. |
3133
- | `outlook-mail-ingest` | Write operator-reviewed messages into the business graph as `:ConversationArchive {source:'email'}` threads (the analogue of the IMAP `email-ingest`). Input `decisions`: one `{messageId, disposition:'ingest'\|'discard'}` per listed message. Each `ingest` message's full body + envelope is fetched, threads group by Graph `conversationId`, and participants resolve closed-set against Neo4j (`:Person`/`:AdminUser` by `accountId`); an unresolved From/To/Cc address skips its whole thread and is returned to the operator (no `:Person` auto-create). Resolvable threads dispatch through the shared `conversation-archive-ingest.sh --source email`. Human-in-the-loop: nothing lands without an explicit per-message decision. The plugin's only Neo4j coupling. Needs `Mail.Read`. |
3147
+ | `outlook-mail-ingest` | Write operator-reviewed messages into the business graph as `:ConversationArchive {source:'email'}` threads (the analogue of the IMAP `email-ingest`). Input `decisions`: one `{messageId, disposition:'ingest'\|'discard'}` per listed message. Each `ingest` message's full body + envelope is fetched, threads group by Graph `conversationId`, and participants resolve closed-set against Neo4j (`:Person`/`:AdminUser` by `accountId`); an unresolved From/To/Cc address skips its whole thread and is returned to the operator (no `:Person` auto-create). Resolvable threads dispatch through the shared `conversation-archive-ingest.sh --source email`. Human-in-the-loop: nothing lands without an explicit per-message decision. One of the plugin's two Neo4j couplings; the other is the email-sampler's Outlook arm below, which writes through the same dispatcher with no operator decision at all. Needs `Mail.Read`. |
3134
3148
  | `outlook-mail-otp-extract` | Poll the inbox for a one-time code from a sender (domain). Reads full bodies to extract it; optional subject regex and timeout (default 60 s). |
3135
3149
  | `outlook-calendar-list` | Calendar events in next rangeDays days (default 7, max 365). |
3136
3150
  | `outlook-calendar-event` | Full detail of a single event by id. |
@@ -3167,6 +3181,20 @@ The attach call checks the access before it stores anything, so what it says bac
3167
3181
 
3168
3182
  Mail only: calendar and contacts tools refuse on a shared mailbox and say so. If a shared mailbox stops answering, the delegation was probably removed in Exchange — `outlook-mailbox-list verify:true` reports which ones are still reachable.
3169
3183
 
3184
+ An attached shared mailbox is also sampled into the graph on a timer, the same as a signed-in one. See the sampler section below.
3185
+
3186
+ ## Deterministic ingest into the graph
3187
+
3188
+ Separately from `outlook-mail-ingest`, which needs an operator decision per message, the `<brand>-email-sampler.service` unit writes inbound Outlook mail into the graph on a timer with no agent turn. It samples every signed-in mailbox whose status is `ok` and every attached shared mailbox, across every account on the install, and writes `:ConversationArchive {source:'email'}` through the same dispatcher `outlook-mail-ingest` uses. Bodies are stored whole.
3189
+
3190
+ Its cursor is the newest `receivedDateTime` written plus the ids seen at that instant, held per account in `secrets/outlook/sampler-cursors.json` (mode 0600, keyed by SMTP address). The id set is what makes a timestamp cursor safe: filtering `gt` would drop a message arriving at the same instant as the boundary, and `ge` alone would re-ingest the boundary on every cycle.
3191
+
3192
+ A mailbox with no cursor backfills 7 days on its first pass. A brand with no `OUTLOOK_CLIENT_ID` runs no Outlook arm at all and logs one skip per cycle rather than an auth error per mailbox.
3193
+
3194
+ Two things behave the way `outlook-mail-ingest` behaves, because it is the same dispatcher. Participants resolve closed-set, so a thread with an unknown From/To/Cc address is skipped and its mail does not land; and attachments are not archived (the sampler writes text threads only).
3195
+
3196
+ Read it with `logs-read.sh --tail email-sampler`, filtering `provider=outlook`. A delegated mailbox's line carries `via=<delegator>`, which is what separates "the shared mailbox is quiet" from "the delegator stopped answering". Not yet proven on a live install.
3197
+
3170
3198
  ## Drafting to a Contact
3171
3199
 
3172
3200
  When the admin agent drafts or sends a message to a known contact, it reads that contact's record first and applies any routing the record carries, for example a preferred send-to address, showing the result for your approval before anything is sent.
@@ -3207,6 +3235,11 @@ All log lines start with `[outlook-mcp]` and write to `server.log`, except the d
3207
3235
  | Mail ingest (per message) | `mail-ingest op=ingest id=<id> conversationId=<id> disposition=ingest` |
3208
3236
  | Mail ingest (per thread) | `mail-ingest op=thread-dispatched root=<key> messages=<N> participants=<N>` (or `op=thread-failed root=<key> status=<N> err=<msg>`; `status=null` with a non-`none` `err` is a spawn launch failure, not a script rejection) |
3209
3237
  | Mail ingest (summary) | `mail-ingest op=summary ingested=<N> discarded=<N> threadsDispatched=<N> threadsSkipped=<N> unresolved=<N>` — one per call; `threadsSkipped` with `unresolved>0` is the closed-set resolution refusing an unknown participant, not a failure |
3238
+ | Sampler census, per cycle | `op=sample-census provider=outlook accounts=<n> mailboxes=<n> delegated=<n> skipped=<n>` — emitted every cycle even when every count is zero, so an install with no Outlook mailbox still says so; without it, silence would be indistinguishable from the arm never running. `accounts=0` means the accounts directory resolved somewhere that does not exist |
3239
+ | Sampler, per mailbox per cycle | `op=sample mailbox=<addr> provider=outlook fetched=<n> threads=<n> skipped=<n> cursor=<iso>` — NOT in `server.log`; this arm writes to the email-sampler log, read with `logs-read.sh --tail email-sampler`. `via=<delegator>` is added on a delegated mailbox. `fetched` counts messages read, `threads` counts what actually landed in the graph, and `skipped` counts threads the closed-set participant rule refused; `fetched>0 threads=0` means nothing was written even though the cursor advanced. An idle mailbox logs `fetched=0` with no thread counts |
3240
+ | Sampler, unresolved participants | `op=sample-unresolved mailbox=<addr> provider=outlook threads=<n> addresses=<csv>` — only when a thread was refused. The addresses are what to add as `:Person` or `:AdminUser` for the account; until then that mail stays out of the graph and the cursor has already passed it |
3241
+ | Sampler failure | `op=sample-error mailbox=<addr> provider=outlook detail=<msg>` — the cursor is not advanced, so the same window is retried next cycle |
3242
+ | Sampler inert | `op=sample-skip provider=outlook detail=no-client-id` — one line per cycle on a brand with no Azure app, instead of one auth error per mailbox |
3210
3243
  | Calendar list | `calendar-list account=<id> rangeDays=<N> count=<N> elapsedMs=<N>` |
3211
3244
  | Calendar event | `calendar-event account=<id> eventId=<trunc-12> elapsedMs=<N>` |
3212
3245
  | Event created | `cal-create-request account=<id> subj=<trunc-64>` then `event-created account=<id> eventId=<trunc-16> status=201`. A non-201 logs `event-create-failed ... status=<N>` and throws. A 201 whose body carried no readable id logs `event-created-id-unknown account=<id> status=201` and returns a created-but-id-unknown result (the event exists; do not retry) rather than a failure. |
@@ -3290,7 +3323,7 @@ Latency triage: `mail-list count=0 elapsedMs<200` consistent → permissions iss
3290
3323
 
3291
3324
  ## Out of scope
3292
3325
 
3293
- Mail move to arbitrary folders / flag, hard (permanent) delete, removing an attachment from a **sent** message (Graph forbids it), attachment-byte archival into the graph (`outlook-mail-ingest` lands text threads only; the IMAP `:DigitalDocument` archival is a separate concern), contacts write, recurring-series occurrence editing, the `scheduling` Neo4j calendar and any auto-sync with it, OneDrive / Files, push notifications, on-premises Exchange, M365 admin scopes (`User.Read.All`, `AuditLog.Read.All`), public-agent exposure, multi-tenant federation. Mail read, full-body read, inbound attachment list/download, reply, delete (to Deleted Items), and compose — all four outbound paths carrying attachments, with a draft's attachments editable in place (add, remove, replace) — are supported (`outlook-mail-fetch-body`, `outlook-mail-attachment`, `outlook-mail-reply`, `outlook-mail-delete`, `outlook-mail-ingest`, `outlook-mail-otp-extract`, `outlook-mail-send`, `outlook-draft`, `outlook-draft-edit`, `outlook-draft-send`), and the calendar is read plus control. See `platform/plugins/outlook/PLUGIN.md` for the full out-of-scope list.
3326
+ Mail move to arbitrary folders / flag, hard (permanent) delete, removing an attachment from a **sent** message (Graph forbids it), attachment-byte archival into the graph (`outlook-mail-ingest` and the email-sampler's Outlook arm both land text threads only; the IMAP `:DigitalDocument` archival is a separate concern), contacts write, recurring-series occurrence editing, the `scheduling` Neo4j calendar and any auto-sync with it, OneDrive / Files, push notifications, on-premises Exchange, M365 admin scopes (`User.Read.All`, `AuditLog.Read.All`), public-agent exposure, multi-tenant federation. Mail read, full-body read, inbound attachment list/download, reply, delete (to Deleted Items), and compose — all four outbound paths carrying attachments, with a draft's attachments editable in place (add, remove, replace) — are supported (`outlook-mail-fetch-body`, `outlook-mail-attachment`, `outlook-mail-reply`, `outlook-mail-delete`, `outlook-mail-ingest`, `outlook-mail-otp-extract`, `outlook-mail-send`, `outlook-draft`, `outlook-draft-edit`, `outlook-draft-send`), and the calendar is read plus control. See `platform/plugins/outlook/PLUGIN.md` for the full out-of-scope list.
3294
3327
 
3295
3328
  ---
3296
3329
  # Google Calendar
@@ -9,6 +9,12 @@ Invoked by the admin agent directly.
9
9
 
10
10
  This is the platform's release timeline, newest first. Each entry shows the date it shipped and the version it shipped in, so you can tell the operator how current their install is. To compare, read the installed version from `capabilities-here` and match it against the versions below. Keep answers high level and in plain English; this is a summary, not a full commit log.
11
11
 
12
+ ## 2026-08-13 (0.1.604)
13
+
14
+ - Your assistant can now read a Telegram conversation back to you, both sides of it, from the record kept on your own machine. Ask for the chat with someone and you get every message they sent and every one the bot sent, in order, with a voice note shown as its transcript and a message they deleted marked as deleted.
15
+ - The tool that was supposed to do this had never worked for anyone, on any account. Asking about a past Telegram conversation used to fall back on the handful of notes an assistant happened to save at the time, which is how a twenty-message exchange could be reported back as eleven, with only one of the bot's own replies among them.
16
+ - An hourly check now reports any conversation notes saved that way, so the same gap cannot reopen unnoticed.
17
+
12
18
  ## 2026-08-13 (0.1.603)
13
19
 
14
20
  - Your Telegram assistant now knows who it is talking to. When someone's Telegram ID is saved on their contact record, every message from them arrives with their name attached, instead of the assistant seeing only a number or whatever that person's Telegram profile happens to be called.
@@ -22,7 +22,7 @@ The `outlook` plugin gives the admin agent access to Microsoft 365 / Outlook.com
22
22
  | `outlook-mail-attachment` | List or download any message's attachments — received or draft. With `messageId` alone, LISTS them (`id`, `name`, `contentType`, `size`, `isInline`, and a `kind` of file/item/reference) via `GET /me/messages/{id}/attachments` and `$select` (no bytes). A draft is a message in the same collection, so passing a draftId lists what that draft carries; the returned `name`/`id` is what `outlook-draft-edit`'s `removeAttachments` takes. With `messageId`+`attachmentId`, DOWNLOADS one file attachment: its bytes are written to `{accountDir}/uploads/outlook/<messageHash>/<bytesHash>-<name>` at `0o600` and the saved path is returned, ready for the Read tool, `SendUserFile`, or an `outlook-mail-reply` attachment. Only file attachments download — item/reference kinds, empty payloads, and anything over 25 MB are refused with nothing written. `outlook-mail-list` / `outlook-mail-search` / `outlook-mail-fetch-body` report `hasAttachments` so the agent knows when to call this. Needs `Mail.Read`. |
23
23
  | `outlook-mail-reply` | Reply in-thread (`createReply` / `createReplyAll` + send). Threads natively via conversationId. `replyAll`, added cc/bcc, and account-scoped file attachments (up to 25 MB per file; files over Graph's ~3 MB inline limit use a chunked upload session). Body is plain text, prepended above the quote. A reply whose recipients resolve **only** to the sending mailbox is **refused** before the send and its draft deleted: Graph addresses a reply to the original message's sender, so replying to your own sent message addresses it to yourself. An explicit `cc` does not exempt it — use `outlook-mail-send` with an explicit `to`. The resolved recipients are logged as `to=`. Needs `Mail.ReadWrite` + `Mail.Send`. |
24
24
  | `outlook-mail-delete` | Move messages to Deleted Items (`POST /me/messages/{id}/move`). Recoverable — never hard-deletes. An id already gone is counted not-moved. Needs `Mail.ReadWrite`. |
25
- | `outlook-mail-ingest` | Write operator-reviewed messages into the business graph as `:ConversationArchive {source:'email'}` threads (the analogue of the IMAP `email-ingest`). Input `decisions`: one `{messageId, disposition:'ingest'\|'discard'}` per listed message. Each `ingest` message's full body + envelope is fetched, threads group by Graph `conversationId`, and participants resolve closed-set against Neo4j (`:Person`/`:AdminUser` by `accountId`); an unresolved From/To/Cc address skips its whole thread and is returned to the operator (no `:Person` auto-create). Resolvable threads dispatch through the shared `conversation-archive-ingest.sh --source email`. Human-in-the-loop: nothing lands without an explicit per-message decision. The plugin's only Neo4j coupling. Needs `Mail.Read`. |
25
+ | `outlook-mail-ingest` | Write operator-reviewed messages into the business graph as `:ConversationArchive {source:'email'}` threads (the analogue of the IMAP `email-ingest`). Input `decisions`: one `{messageId, disposition:'ingest'\|'discard'}` per listed message. Each `ingest` message's full body + envelope is fetched, threads group by Graph `conversationId`, and participants resolve closed-set against Neo4j (`:Person`/`:AdminUser` by `accountId`); an unresolved From/To/Cc address skips its whole thread and is returned to the operator (no `:Person` auto-create). Resolvable threads dispatch through the shared `conversation-archive-ingest.sh --source email`. Human-in-the-loop: nothing lands without an explicit per-message decision. One of the plugin's two Neo4j couplings; the other is the email-sampler's Outlook arm below, which writes through the same dispatcher with no operator decision at all. Needs `Mail.Read`. |
26
26
  | `outlook-mail-otp-extract` | Poll the inbox for a one-time code from a sender (domain). Reads full bodies to extract it; optional subject regex and timeout (default 60 s). |
27
27
  | `outlook-calendar-list` | Calendar events in next rangeDays days (default 7, max 365). |
28
28
  | `outlook-calendar-event` | Full detail of a single event by id. |
@@ -59,6 +59,20 @@ The attach call checks the access before it stores anything, so what it says bac
59
59
 
60
60
  Mail only: calendar and contacts tools refuse on a shared mailbox and say so. If a shared mailbox stops answering, the delegation was probably removed in Exchange — `outlook-mailbox-list verify:true` reports which ones are still reachable.
61
61
 
62
+ An attached shared mailbox is also sampled into the graph on a timer, the same as a signed-in one. See the sampler section below.
63
+
64
+ ## Deterministic ingest into the graph
65
+
66
+ Separately from `outlook-mail-ingest`, which needs an operator decision per message, the `<brand>-email-sampler.service` unit writes inbound Outlook mail into the graph on a timer with no agent turn. It samples every signed-in mailbox whose status is `ok` and every attached shared mailbox, across every account on the install, and writes `:ConversationArchive {source:'email'}` through the same dispatcher `outlook-mail-ingest` uses. Bodies are stored whole.
67
+
68
+ Its cursor is the newest `receivedDateTime` written plus the ids seen at that instant, held per account in `secrets/outlook/sampler-cursors.json` (mode 0600, keyed by SMTP address). The id set is what makes a timestamp cursor safe: filtering `gt` would drop a message arriving at the same instant as the boundary, and `ge` alone would re-ingest the boundary on every cycle.
69
+
70
+ A mailbox with no cursor backfills 7 days on its first pass. A brand with no `OUTLOOK_CLIENT_ID` runs no Outlook arm at all and logs one skip per cycle rather than an auth error per mailbox.
71
+
72
+ Two things behave the way `outlook-mail-ingest` behaves, because it is the same dispatcher. Participants resolve closed-set, so a thread with an unknown From/To/Cc address is skipped and its mail does not land; and attachments are not archived (the sampler writes text threads only).
73
+
74
+ Read it with `logs-read.sh --tail email-sampler`, filtering `provider=outlook`. A delegated mailbox's line carries `via=<delegator>`, which is what separates "the shared mailbox is quiet" from "the delegator stopped answering". Not yet proven on a live install.
75
+
62
76
  ## Drafting to a Contact
63
77
 
64
78
  When the admin agent drafts or sends a message to a known contact, it reads that contact's record first and applies any routing the record carries, for example a preferred send-to address, showing the result for your approval before anything is sent.
@@ -99,6 +113,11 @@ All log lines start with `[outlook-mcp]` and write to `server.log`, except the d
99
113
  | Mail ingest (per message) | `mail-ingest op=ingest id=<id> conversationId=<id> disposition=ingest` |
100
114
  | Mail ingest (per thread) | `mail-ingest op=thread-dispatched root=<key> messages=<N> participants=<N>` (or `op=thread-failed root=<key> status=<N> err=<msg>`; `status=null` with a non-`none` `err` is a spawn launch failure, not a script rejection) |
101
115
  | Mail ingest (summary) | `mail-ingest op=summary ingested=<N> discarded=<N> threadsDispatched=<N> threadsSkipped=<N> unresolved=<N>` — one per call; `threadsSkipped` with `unresolved>0` is the closed-set resolution refusing an unknown participant, not a failure |
116
+ | Sampler census, per cycle | `op=sample-census provider=outlook accounts=<n> mailboxes=<n> delegated=<n> skipped=<n>` — emitted every cycle even when every count is zero, so an install with no Outlook mailbox still says so; without it, silence would be indistinguishable from the arm never running. `accounts=0` means the accounts directory resolved somewhere that does not exist |
117
+ | Sampler, per mailbox per cycle | `op=sample mailbox=<addr> provider=outlook fetched=<n> threads=<n> skipped=<n> cursor=<iso>` — NOT in `server.log`; this arm writes to the email-sampler log, read with `logs-read.sh --tail email-sampler`. `via=<delegator>` is added on a delegated mailbox. `fetched` counts messages read, `threads` counts what actually landed in the graph, and `skipped` counts threads the closed-set participant rule refused; `fetched>0 threads=0` means nothing was written even though the cursor advanced. An idle mailbox logs `fetched=0` with no thread counts |
118
+ | Sampler, unresolved participants | `op=sample-unresolved mailbox=<addr> provider=outlook threads=<n> addresses=<csv>` — only when a thread was refused. The addresses are what to add as `:Person` or `:AdminUser` for the account; until then that mail stays out of the graph and the cursor has already passed it |
119
+ | Sampler failure | `op=sample-error mailbox=<addr> provider=outlook detail=<msg>` — the cursor is not advanced, so the same window is retried next cycle |
120
+ | Sampler inert | `op=sample-skip provider=outlook detail=no-client-id` — one line per cycle on a brand with no Azure app, instead of one auth error per mailbox |
102
121
  | Calendar list | `calendar-list account=<id> rangeDays=<N> count=<N> elapsedMs=<N>` |
103
122
  | Calendar event | `calendar-event account=<id> eventId=<trunc-12> elapsedMs=<N>` |
104
123
  | Event created | `cal-create-request account=<id> subj=<trunc-64>` then `event-created account=<id> eventId=<trunc-16> status=201`. A non-201 logs `event-create-failed ... status=<N>` and throws. A 201 whose body carried no readable id logs `event-created-id-unknown account=<id> status=201` and returns a created-but-id-unknown result (the event exists; do not retry) rather than a failure. |
@@ -182,4 +201,4 @@ Latency triage: `mail-list count=0 elapsedMs<200` consistent → permissions iss
182
201
 
183
202
  ## Out of scope
184
203
 
185
- Mail move to arbitrary folders / flag, hard (permanent) delete, removing an attachment from a **sent** message (Graph forbids it), attachment-byte archival into the graph (`outlook-mail-ingest` lands text threads only; the IMAP `:DigitalDocument` archival is a separate concern), contacts write, recurring-series occurrence editing, the `scheduling` Neo4j calendar and any auto-sync with it, OneDrive / Files, push notifications, on-premises Exchange, M365 admin scopes (`User.Read.All`, `AuditLog.Read.All`), public-agent exposure, multi-tenant federation. Mail read, full-body read, inbound attachment list/download, reply, delete (to Deleted Items), and compose — all four outbound paths carrying attachments, with a draft's attachments editable in place (add, remove, replace) — are supported (`outlook-mail-fetch-body`, `outlook-mail-attachment`, `outlook-mail-reply`, `outlook-mail-delete`, `outlook-mail-ingest`, `outlook-mail-otp-extract`, `outlook-mail-send`, `outlook-draft`, `outlook-draft-edit`, `outlook-draft-send`), and the calendar is read plus control. See `platform/plugins/outlook/PLUGIN.md` for the full out-of-scope list.
204
+ Mail move to arbitrary folders / flag, hard (permanent) delete, removing an attachment from a **sent** message (Graph forbids it), attachment-byte archival into the graph (`outlook-mail-ingest` and the email-sampler's Outlook arm both land text threads only; the IMAP `:DigitalDocument` archival is a separate concern), contacts write, recurring-series occurrence editing, the `scheduling` Neo4j calendar and any auto-sync with it, OneDrive / Files, push notifications, on-premises Exchange, M365 admin scopes (`User.Read.All`, `AuditLog.Read.All`), public-agent exposure, multi-tenant federation. Mail read, full-body read, inbound attachment list/download, reply, delete (to Deleted Items), and compose — all four outbound paths carrying attachments, with a draft's attachments editable in place (add, remove, replace) — are supported (`outlook-mail-fetch-body`, `outlook-mail-attachment`, `outlook-mail-reply`, `outlook-mail-delete`, `outlook-mail-ingest`, `outlook-mail-otp-extract`, `outlook-mail-send`, `outlook-draft`, `outlook-draft-edit`, `outlook-draft-send`), and the calendar is read plus control. See `platform/plugins/outlook/PLUGIN.md` for the full out-of-scope list.
@@ -150,7 +150,21 @@ A group's ID is a long negative number, like `-1001234567890`. To find it, @ment
150
150
 
151
151
  ## Message History
152
152
 
153
- Ask {{productName}}: "What messages has the bot received?" or "Show recent Telegram activity."
153
+ Ask {{productName}} to read a conversation back: "What did I say to Sam on Telegram?" or "Show me the whole
154
+ chat with that driver." It returns the conversation both ways round, oldest first: every message the bot
155
+ received and every one it sent. A voice note reads as its transcript, an edited message as its new wording,
156
+ and a message the other person deleted is shown struck out rather than quietly dropped.
157
+
158
+ Give it the chat id, which you can get from "Who can you reach on Telegram?". In a group, the id is the
159
+ group's own.
160
+
161
+ Two things to know:
162
+
163
+ - **History starts when this machine started storing it.** A conversation that predates the install, or that
164
+ ran while the machine was off, is not there to read. Telegram's Bot API offers no way to fetch it back.
165
+ - **If the same chat id belongs to two conversations, it will ask which you meant** rather than guess. That
166
+ happens when two of your bots both talk to the same person, and when a connected business account's
167
+ customer has the same id as your own chat with that bot.
154
168
 
155
169
  ## Troubleshooting
156
170
 
@@ -145,7 +145,16 @@ Manages the agent's own dedicated email account — IMAP for reading, SMTP for s
145
145
 
146
146
  There are two independent paths into the graph, both writing `:ConversationArchive {source:'email'}`:
147
147
 
148
- - **Deterministic ingest — the `email-sampler` service.** A per-brand, always-on systemd user unit (`<brand>-email-sampler.service`) runs the plugin's compiled `dist/sampler/main.js`. Each cycle it enumerates every connected IMAP mailbox on the install and writes every inbound message above its own cursor, with no agent turn and no approval step. Its cursor is `:EmailAccount.samplerCursorUid` + `samplerUidValidity`, written only by the sampler and kept strictly independent of `lastFetchedUid` (which `email-fetch` owns). This is the foundation a gate that reads email-in-graph can trust: every inbound message lands within one cycle. Observability: one `op=sample mailbox=<addr> fetched=<n> cursor=<uid>` line per mailbox per cycle (`op=sample-error` on failure, cursor unadvanced), read with `logs-read.sh --tail email-sampler`. Attachment archival by the sampler is deferred (see the task follow-up).
148
+ - **Deterministic ingest — the `email-sampler` service.** A per-brand, always-on systemd user unit (`<brand>-email-sampler.service`) runs the plugin's compiled `dist/sampler/main.js`. Each cycle it enumerates every connected mailbox on the install and writes every inbound message above that mailbox's own cursor, with no agent turn and no approval step. This is the foundation a gate that reads email-in-graph can trust: every inbound message lands within one cycle. Attachment archival by the sampler is deferred (see the task follow-up).
149
+
150
+ The service has **two arms in one process**, distinguished in the log by `provider=`:
151
+
152
+ - **IMAP.** Enumerates every `:EmailAccount` node. Its cursor is `:EmailAccount.samplerCursorUid` + `samplerUidValidity`, written only by the sampler and kept strictly independent of `lastFetchedUid` (which `email-fetch` owns). Stores the first 500 characters of a body, the same `previewBody` cap the read tools use.
153
+ - **Outlook.** Microsoft 365 and Outlook.com mailboxes have no `:EmailAccount` node, so this arm enumerates the on-disk credential store instead: every signed-in mailbox under `secrets/outlook/mailboxes/` whose status is `ok`, plus every shared mailbox in that account's `delegated.json`, read through its delegator's tokens. Its cursor is the newest `receivedDateTime` written plus the ids seen at that instant, held in `secrets/outlook/sampler-cursors.json` (mode 0600, keyed by SMTP address). The id set is what makes a timestamp cursor safe: `gt` would drop a message arriving at the same instant as the boundary, `ge` alone would re-ingest the boundary every cycle. Stores the **whole** body, untruncated. The arm's code lives in the outlook plugin (`plugins/outlook/mcp/src/sampler/`) beside the tools it reuses, and the email sampler imports its compiled `dist/sampler/arm.js` by path at runtime; an unbuilt outlook plugin is a logged skip that never stops IMAP ingest.
154
+
155
+ Observability for both: `logs-read.sh --tail email-sampler`. One line per mailbox per cycle, `op=sample mailbox=<addr> provider=imap|outlook fetched=<n> cursor=<pos>`, with `via=<delegator>` added on a delegated Outlook mailbox, and `op=sample-error … provider=<arm>` on failure (cursor unadvanced). `provider=` is a total filter: every arm's line carries one, so filtering never silently returns a subset. The Outlook arm also emits one `op=sample-census provider=outlook accounts=<n> mailboxes=<n> delegated=<n> skipped=<n>` per cycle, even when every count is zero, so an install with no Outlook mailbox is distinguishable from an arm that never ran. `fetched=0` on an idle mailbox is healthy; a cursor that never advances while mail arrives is the failure.
156
+
157
+ A mailbox with no cursor backfills 7 days on its first cycle, on both arms. Mail older than that window is never sampled. The Outlook arm is not yet proven on a live install.
149
158
 
150
159
  - **Operator-initiated ingest — human-in-the-loop.** When the operator says "check my emails", `email-fetch` lists new messages and the operator approves each one; only the approved subset is written via `email-ingest`, which also fetches and archives attachment bytes. See `skills/email-ingest/SKILL.md` for the conversation flow and `skills/email-composition/SKILL.md` for drafting replies once a thread is stored.
151
160
 
@@ -93,4 +93,34 @@ describe("sampleMailbox", () => {
93
93
  expect(r).toMatchObject({ fetched: 0, cursor: 0 });
94
94
  });
95
95
  });
96
+ describe("provider label (Task 2683)", () => {
97
+ it("carries provider=imap on every emitted line, success and error", async () => {
98
+ const written = deps({
99
+ readCursor: vi.fn(async () => ({ cursorUid: 10, uidValidity: 100 })),
100
+ fetchSince: vi.fn(async () => ({ uidValidity: 100, emails: [email(11)], maxUid: 11 })),
101
+ });
102
+ await sampleMailbox(written, account);
103
+ const idle = deps({ readCursor: vi.fn(async () => ({ cursorUid: 10, uidValidity: 100 })) });
104
+ await sampleMailbox(idle, account);
105
+ const reset = deps({
106
+ readCursor: vi.fn(async () => ({ cursorUid: 10, uidValidity: 99 })),
107
+ fetchSince: vi.fn(async () => ({ uidValidity: 100, emails: [], maxUid: 0 })),
108
+ });
109
+ await sampleMailbox(reset, account);
110
+ const failed = deps({
111
+ fetchSince: vi.fn(async () => {
112
+ throw new Error("imap down");
113
+ }),
114
+ });
115
+ await sampleMailbox(failed, account);
116
+ // Without this label `provider=` is not a total filter once the Outlook arm
117
+ // shares the log: filtering on it would return zero IMAP rows.
118
+ for (const d of [written, idle, reset, failed]) {
119
+ const lines = d.log.mock.calls.map((c) => String(c[0]));
120
+ expect(lines.length).toBeGreaterThan(0);
121
+ for (const line of lines)
122
+ expect(line).toContain("provider=imap");
123
+ }
124
+ });
125
+ });
96
126
  //# sourceMappingURL=sample-mailbox.test.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"sample-mailbox.test.js","sourceRoot":"","sources":["../../../src/sampler/__tests__/sample-mailbox.test.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"sample-mailbox.test.js","sourceRoot":"","sources":["../../../src/sampler/__tests__/sample-mailbox.test.ts"],"names":[],"mappings":"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"}