@rubytech/create-realagent-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 (100) 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/services/claude-session-manager/dist/canonical-tool-names.generated.js +1 -1
  97. package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js.map +1 -1
  98. package/payload/platform/templates/specialists/agents/personal-assistant.md +1 -1
  99. package/payload/server/public/operator.html +1 -1
  100. package/payload/server/server.js +69 -5
@@ -0,0 +1,109 @@
1
+ /**
2
+ * The Outlook arm of the email sampler (Task 2683).
3
+ *
4
+ * One exported function, called once per sampler cycle by the email plugin's
5
+ * `sampler/main.ts`. It lives here rather than in the email plugin because every
6
+ * reuse target — the mailbox registry, the token store, the Graph client, the
7
+ * list and body tools, the archive dispatcher — is a sibling of this file. The
8
+ * email sampler reaches it by resolving this module's compiled path against
9
+ * PLATFORM_ROOT, the same way `scheduling`'s `check-due-events` reaches
10
+ * `plugins/outlook/mcp/dist/scripts/draft-reconcile.js`.
11
+ *
12
+ * Both arms write to one log, because Task 2683's verification names
13
+ * `logs-read.sh --tail email-sampler` filtered on `provider=outlook`.
14
+ */
15
+ import { resolve } from "node:path";
16
+ import { MailboxRegistry } from "../auth/mailbox-registry.js";
17
+ import { dispatchOutlookMessagesToConversationArchive } from "../lib/conversation-archive-dispatch.js";
18
+ import { runMailFetchBody } from "../tools/mail-fetch-body.js";
19
+ import { runMailList } from "../tools/mail-list.js";
20
+ import { readCursor, writeCursor } from "./cursor.js";
21
+ import { censusLine, enumerateSampleableMailboxes, } from "./enumerate.js";
22
+ import { sampleOutlookMailbox } from "./sample-outlook-mailbox.js";
23
+ /** One page per list call; the sampler pages to exhaustion. 100 keeps the common
24
+ * case to a single call and stays well inside mail-list's 250 clamp. */
25
+ const PAGE_SIZE = 100;
26
+ /** Same derivation the MCP server's index.ts uses, so the arm and the tools
27
+ * address one accounts directory. */
28
+ function accountsDirFrom(platformRoot) {
29
+ return resolve(platformRoot, "..", "data/accounts");
30
+ }
31
+ /**
32
+ * A Graph config addressing exactly one mailbox. The tokenStore is always a
33
+ * signed-in mailbox's; a delegated mailbox has no sign-in of its own and is
34
+ * reached by `mailboxPathPrefix`, which `callGraph` substitutes for the `/me`
35
+ * segment the tools request.
36
+ */
37
+ function configFor(target, accountsDir, clientId, tenantId) {
38
+ const registry = new MailboxRegistry(target.accountId, accountsDir);
39
+ return {
40
+ clientId,
41
+ tenantId,
42
+ accountId: target.accountId,
43
+ tokenStore: registry.storeFor(target.mailboxKey),
44
+ mailboxAddress: target.mailbox,
45
+ mailboxPathPrefix: target.pathPrefix,
46
+ };
47
+ }
48
+ export async function runOutlookArm(log) {
49
+ const clientId = process.env.OUTLOOK_CLIENT_ID ?? "";
50
+ const tenantId = process.env.OUTLOOK_TENANT_ID ?? "common";
51
+ if (!clientId) {
52
+ // A brand with no Azure app cannot mint or refresh a token. Saying so once
53
+ // per cycle beats one auth error per mailbox per cycle for ever.
54
+ log("op=sample-skip provider=outlook detail=no-client-id");
55
+ return;
56
+ }
57
+ const platformRoot = process.env.PLATFORM_ROOT;
58
+ if (!platformRoot) {
59
+ log("op=sample-error mailbox=<enumeration> provider=outlook detail=PLATFORM_ROOT-unset");
60
+ return;
61
+ }
62
+ const accountsDir = accountsDirFrom(platformRoot);
63
+ const enumerated = enumerateSampleableMailboxes(accountsDir);
64
+ const { sampleable, skipped } = enumerated;
65
+ // One census per cycle, before the per-mailbox lines and emitted even when
66
+ // every count is zero. Without it an install with no Outlook mailbox produces
67
+ // no output at all, which is indistinguishable from the arm never running,
68
+ // and those two have different remedies.
69
+ log(censusLine(enumerated));
70
+ for (const s of skipped) {
71
+ log(`op=sample-error mailbox=${s.mailbox} provider=outlook detail=${s.reason}`);
72
+ }
73
+ for (const target of sampleable) {
74
+ const config = configFor(target, accountsDir, clientId, tenantId);
75
+ const deps = {
76
+ list: (_t, since, cursor) => runMailList(config, { folder: "Inbox", top: PAGE_SIZE, since, cursor }),
77
+ fetchBody: async (_t, messageId) => {
78
+ const b = await runMailFetchBody(config, { messageId });
79
+ return {
80
+ id: b.id,
81
+ subject: b.subject,
82
+ from: b.from,
83
+ fromName: b.fromName,
84
+ to: b.to,
85
+ cc: b.cc,
86
+ conversationId: b.conversationId,
87
+ body: b.body,
88
+ receivedDateTime: b.receivedDateTime,
89
+ };
90
+ },
91
+ write: (m) => dispatchOutlookMessagesToConversationArchive(m),
92
+ readCursor: (t) => readCursor(accountsDir, t.accountId, t.mailbox),
93
+ writeCursor: (t, c) => writeCursor(accountsDir, t.accountId, t.mailbox, c),
94
+ log,
95
+ now: () => Date.now(),
96
+ };
97
+ try {
98
+ await sampleOutlookMailbox(deps, target);
99
+ }
100
+ catch (e) {
101
+ // sampleOutlookMailbox handles its own fetch and write failures; this
102
+ // catches anything thrown outside them (a cursor write, a token-store
103
+ // read) so one mailbox never ends the cycle for the rest.
104
+ log(`op=sample-error mailbox=${target.mailbox} provider=outlook ` +
105
+ `detail=${e instanceof Error ? e.message : String(e)}`);
106
+ }
107
+ }
108
+ }
109
+ //# sourceMappingURL=arm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"arm.js","sourceRoot":"","sources":["../../src/sampler/arm.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ export interface OutlookSamplerCursor {
2
+ /** Raw `receivedDateTime` of the newest message written, as Graph returned it.
3
+ * Raw so nothing here can shift an instant by reformatting it. */
4
+ newestReceivedAt: string;
5
+ /** Ids already written that carry exactly `newestReceivedAt`. */
6
+ idsAtNewest: string[];
7
+ }
8
+ export type OutlookSamplerCursors = Record<string, OutlookSamplerCursor>;
9
+ /**
10
+ * Every cursor for one account. A missing or unparseable file reads as an empty
11
+ * map rather than throwing: the sampler runs unattended, so a corrupt file must
12
+ * degrade to a re-backfill of that account, never stop the cycle.
13
+ */
14
+ export declare function readCursors(accountsDir: string, accountId: string): OutlookSamplerCursors;
15
+ /** One mailbox's cursor, or null when it has never been sampled. An entry whose
16
+ * instant is not a string is treated as absent — a half-written record must
17
+ * read as "start again", never as a position. */
18
+ export declare function readCursor(accountsDir: string, accountId: string, mailbox: string): OutlookSamplerCursor | null;
19
+ /**
20
+ * Replace one mailbox's cursor, preserving every other mailbox's on the account.
21
+ * Atomic via temp + rename, so a crash mid-write leaves the previous file whole;
22
+ * a torn file would re-backfill every mailbox on the account.
23
+ */
24
+ export declare function writeCursor(accountsDir: string, accountId: string, mailbox: string, cursor: OutlookSamplerCursor): void;
25
+ //# sourceMappingURL=cursor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cursor.d.ts","sourceRoot":"","sources":["../../src/sampler/cursor.ts"],"names":[],"mappings":"AA6BA,MAAM,WAAW,oBAAoB;IACnC;uEACmE;IACnE,gBAAgB,EAAE,MAAM,CAAC;IACzB,iEAAiE;IACjE,WAAW,EAAE,MAAM,EAAE,CAAC;CACvB;AAED,MAAM,MAAM,qBAAqB,GAAG,MAAM,CAAC,MAAM,EAAE,oBAAoB,CAAC,CAAC;AAezE;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,WAAW,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,qBAAqB,CAUzF;AAED;;kDAEkD;AAClD,wBAAgB,UAAU,CACxB,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,GACd,oBAAoB,GAAG,IAAI,CAW7B;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CACzB,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,oBAAoB,GAC3B,IAAI,CAqBN"}
@@ -0,0 +1,94 @@
1
+ /**
2
+ * The Outlook sampler's own cursor store (Task 2683).
3
+ *
4
+ * Outlook mailboxes have no `:EmailAccount` node, so there is no graph property
5
+ * to hold a cursor the way the IMAP arm holds `samplerCursorUid`. One JSON file
6
+ * per account, keyed by the mailbox's SMTP address, covers signed-in and
7
+ * delegated mailboxes with one shape. It holds no secret, which is the same
8
+ * position `delegated.json` already occupies in the same 0700 directory.
9
+ *
10
+ * The cursor is an instant plus the ids seen at that instant. A timestamp alone
11
+ * cannot be both lossless and duplicate-free: `receivedDateTime gt cursor` drops
12
+ * a second message that arrived at the identical instant, and `ge cursor`
13
+ * re-ingests the boundary message on every cycle for ever. Querying `ge` and
14
+ * excluding the ids already written at that exact instant loses nothing and
15
+ * repeats nothing.
16
+ *
17
+ * Written only by the sampler, which is a single process, so no lock is needed.
18
+ */
19
+ import { existsSync, mkdirSync, readFileSync, renameSync, unlinkSync, writeFileSync, } from "node:fs";
20
+ import { randomBytes } from "node:crypto";
21
+ import { join } from "node:path";
22
+ /** The one address form used as a key, matching `delegated-store.ts`. */
23
+ function key(mailbox) {
24
+ return mailbox.trim().toLowerCase();
25
+ }
26
+ function secretsDir(accountsDir, accountId) {
27
+ return join(accountsDir, accountId, "secrets", "outlook");
28
+ }
29
+ function cursorsPath(accountsDir, accountId) {
30
+ return join(secretsDir(accountsDir, accountId), "sampler-cursors.json");
31
+ }
32
+ /**
33
+ * Every cursor for one account. A missing or unparseable file reads as an empty
34
+ * map rather than throwing: the sampler runs unattended, so a corrupt file must
35
+ * degrade to a re-backfill of that account, never stop the cycle.
36
+ */
37
+ export function readCursors(accountsDir, accountId) {
38
+ try {
39
+ const parsed = JSON.parse(readFileSync(cursorsPath(accountsDir, accountId), "utf-8"));
40
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
41
+ return {};
42
+ return parsed;
43
+ }
44
+ catch {
45
+ return {};
46
+ }
47
+ }
48
+ /** One mailbox's cursor, or null when it has never been sampled. An entry whose
49
+ * instant is not a string is treated as absent — a half-written record must
50
+ * read as "start again", never as a position. */
51
+ export function readCursor(accountsDir, accountId, mailbox) {
52
+ const all = readCursors(accountsDir, accountId);
53
+ const k = key(mailbox);
54
+ // Own-property check, not a bare index: a plain object answers "constructor"
55
+ // with an inherited function, which would read as a stored cursor.
56
+ const entry = Object.hasOwn(all, k) ? all[k] : undefined;
57
+ if (!entry || typeof entry.newestReceivedAt !== "string")
58
+ return null;
59
+ return {
60
+ newestReceivedAt: entry.newestReceivedAt,
61
+ idsAtNewest: Array.isArray(entry.idsAtNewest) ? entry.idsAtNewest : [],
62
+ };
63
+ }
64
+ /**
65
+ * Replace one mailbox's cursor, preserving every other mailbox's on the account.
66
+ * Atomic via temp + rename, so a crash mid-write leaves the previous file whole;
67
+ * a torn file would re-backfill every mailbox on the account.
68
+ */
69
+ export function writeCursor(accountsDir, accountId, mailbox, cursor) {
70
+ const path = cursorsPath(accountsDir, accountId);
71
+ const next = {
72
+ ...readCursors(accountsDir, accountId),
73
+ [key(mailbox)]: cursor,
74
+ };
75
+ mkdirSync(secretsDir(accountsDir, accountId), { recursive: true, mode: 0o700 });
76
+ const temp = `${path}.${process.pid}.${randomBytes(6).toString("hex")}.tmp`;
77
+ try {
78
+ writeFileSync(temp, JSON.stringify(next, null, 2), { mode: 0o600 });
79
+ renameSync(temp, path);
80
+ }
81
+ catch (err) {
82
+ // Unique temp names are never reused, so a failed write or rename would
83
+ // otherwise leave a file nothing ever cleans up.
84
+ try {
85
+ if (existsSync(temp))
86
+ unlinkSync(temp);
87
+ }
88
+ catch {
89
+ /* best effort — the original error is what matters */
90
+ }
91
+ throw err;
92
+ }
93
+ }
94
+ //# sourceMappingURL=cursor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cursor.js","sourceRoot":"","sources":["../../src/sampler/cursor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,41 @@
1
+ export interface SampleableMailbox {
2
+ accountId: string;
3
+ /** SMTP address. The cursor key and the `mailbox=` log field. */
4
+ mailbox: string;
5
+ /** The TokenStore key whose tokens perform the read. On a delegated mailbox
6
+ * this is the DELEGATOR's key — a shared mailbox never signs in. */
7
+ mailboxKey: string;
8
+ /** "/me" for a signed-in mailbox, "/users/<encoded address>" for a delegated one. */
9
+ pathPrefix: string;
10
+ /** The delegator's address on a delegated mailbox, else null. */
11
+ via: string | null;
12
+ }
13
+ export interface SkippedMailbox {
14
+ accountId: string;
15
+ mailbox: string;
16
+ reason: string;
17
+ }
18
+ /**
19
+ * Account directories that are provisioned: a UUID name holding a parseable
20
+ * `account.json`. A present-but-unparseable file excludes the directory, the
21
+ * same corruption discipline `account-enumeration` applies.
22
+ */
23
+ export declare function listProvisionedAccountIds(accountsDir: string): string[];
24
+ export interface EnumerationResult {
25
+ sampleable: SampleableMailbox[];
26
+ skipped: SkippedMailbox[];
27
+ /** Provisioned accounts walked. Carried out so the census reports it without
28
+ * a second directory read, and so `accounts=0` is distinguishable from an
29
+ * accountsDir that resolved somewhere that does not exist. */
30
+ accounts: number;
31
+ }
32
+ /**
33
+ * The per-cycle census line, emitted even when every count is zero.
34
+ *
35
+ * Pure, so the "never silent" guarantee is testable without a directory. An arm
36
+ * that emits nothing on an install with no Outlook mailbox is indistinguishable
37
+ * from an arm that never ran, and those two have different remedies.
38
+ */
39
+ export declare function censusLine(result: EnumerationResult): string;
40
+ export declare function enumerateSampleableMailboxes(accountsDir: string): EnumerationResult;
41
+ //# sourceMappingURL=enumerate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"enumerate.d.ts","sourceRoot":"","sources":["../../src/sampler/enumerate.ts"],"names":[],"mappings":"AAuBA,MAAM,WAAW,iBAAiB;IAChC,SAAS,EAAE,MAAM,CAAC;IAClB,iEAAiE;IACjE,OAAO,EAAE,MAAM,CAAC;IAChB;yEACqE;IACrE,UAAU,EAAE,MAAM,CAAC;IACnB,qFAAqF;IACrF,UAAU,EAAE,MAAM,CAAC;IACnB,iEAAiE;IACjE,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;CACpB;AAED,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;CAChB;AAMD;;;;GAIG;AACH,wBAAgB,yBAAyB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,EAAE,CAkBvE;AAED,MAAM,WAAW,iBAAiB;IAChC,UAAU,EAAE,iBAAiB,EAAE,CAAC;IAChC,OAAO,EAAE,cAAc,EAAE,CAAC;IAC1B;;mEAE+D;IAC/D,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,iBAAiB,GAAG,MAAM,CAM5D;AAED,wBAAgB,4BAA4B,CAAC,WAAW,EAAE,MAAM,GAAG,iBAAiB,CA6DnF"}
@@ -0,0 +1,122 @@
1
+ /**
2
+ * Every Outlook mailbox on the install the sampler can read (Task 2683).
3
+ *
4
+ * Walks the accounts directory directly rather than calling
5
+ * `enumerateValidAccountIds`, which caches per process keyed on `accountsDir`.
6
+ * The sampler is a long-lived service, so a sub-account provisioned by the
7
+ * runtime `account_create` lifecycle after boot would stay invisible to a cached
8
+ * list for the life of the process. The validity rule is the same one that
9
+ * helper applies: a UUID directory name holding a parseable `account.json`.
10
+ *
11
+ * Signed-in and delegated mailboxes are both returned, distinguished by
12
+ * `pathPrefix` and `via`. A record that cannot be read is returned in `skipped`
13
+ * carrying its reason rather than dropped, so "this mailbox is idle" and "this
14
+ * mailbox is broken" never look alike in the sampler log.
15
+ */
16
+ import { readdirSync, readFileSync } from "node:fs";
17
+ import { join } from "node:path";
18
+ import { MailboxRegistry } from "../auth/mailbox-registry.js";
19
+ import { DelegatedStore } from "../auth/delegated-store.js";
20
+ const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
21
+ function msg(e) {
22
+ return e instanceof Error ? e.message : String(e);
23
+ }
24
+ /**
25
+ * Account directories that are provisioned: a UUID name holding a parseable
26
+ * `account.json`. A present-but-unparseable file excludes the directory, the
27
+ * same corruption discipline `account-enumeration` applies.
28
+ */
29
+ export function listProvisionedAccountIds(accountsDir) {
30
+ let names;
31
+ try {
32
+ names = readdirSync(accountsDir);
33
+ }
34
+ catch {
35
+ return [];
36
+ }
37
+ const out = [];
38
+ for (const name of names) {
39
+ if (!UUID_RE.test(name))
40
+ continue;
41
+ try {
42
+ JSON.parse(readFileSync(join(accountsDir, name, "account.json"), "utf-8"));
43
+ out.push(name);
44
+ }
45
+ catch {
46
+ /* absent or unparseable account.json — not a provisioned account */
47
+ }
48
+ }
49
+ return out.sort();
50
+ }
51
+ /**
52
+ * The per-cycle census line, emitted even when every count is zero.
53
+ *
54
+ * Pure, so the "never silent" guarantee is testable without a directory. An arm
55
+ * that emits nothing on an install with no Outlook mailbox is indistinguishable
56
+ * from an arm that never ran, and those two have different remedies.
57
+ */
58
+ export function censusLine(result) {
59
+ const delegated = result.sampleable.filter((m) => m.via !== null).length;
60
+ return (`op=sample-census provider=outlook accounts=${result.accounts} ` +
61
+ `mailboxes=${result.sampleable.length} delegated=${delegated} skipped=${result.skipped.length}`);
62
+ }
63
+ export function enumerateSampleableMailboxes(accountsDir) {
64
+ const sampleable = [];
65
+ const skipped = [];
66
+ const accountIds = listProvisionedAccountIds(accountsDir);
67
+ for (const accountId of accountIds) {
68
+ const registry = new MailboxRegistry(accountId, accountsDir);
69
+ let descriptors;
70
+ try {
71
+ descriptors = registry.list();
72
+ }
73
+ catch (err) {
74
+ skipped.push({ accountId, mailbox: "<registry>", reason: msg(err) });
75
+ continue;
76
+ }
77
+ for (const d of descriptors) {
78
+ // Only `ok` is addressable. `refresh-expired` in particular cannot mint a
79
+ // token and needs re-auth, so sampling it would produce a permanent error
80
+ // stream rather than mail.
81
+ if (d.status !== "ok" || !d.mail) {
82
+ skipped.push({ accountId, mailbox: d.mail ?? d.mailboxKey, reason: d.status });
83
+ continue;
84
+ }
85
+ sampleable.push({
86
+ accountId,
87
+ mailbox: d.mail,
88
+ mailboxKey: d.mailboxKey,
89
+ pathPrefix: "/me",
90
+ via: null,
91
+ });
92
+ }
93
+ // Delegated records are resolved THROUGH the registry rather than read as
94
+ // raw addresses, so a shared mailbox whose delegator has gone is reported as
95
+ // delegator-missing rather than silently omitted: the record exists, and the
96
+ // remedy is to re-register the delegator or drop the record.
97
+ let delegated;
98
+ try {
99
+ delegated = new DelegatedStore(accountId, accountsDir).read();
100
+ }
101
+ catch (err) {
102
+ skipped.push({ accountId, mailbox: "<delegated>", reason: msg(err) });
103
+ continue;
104
+ }
105
+ for (const address of Object.keys(delegated).sort()) {
106
+ const resolved = registry.resolve(address);
107
+ if (resolved.kind !== "ok") {
108
+ skipped.push({ accountId, mailbox: address, reason: resolved.kind });
109
+ continue;
110
+ }
111
+ sampleable.push({
112
+ accountId,
113
+ mailbox: address,
114
+ mailboxKey: resolved.descriptor.mailboxKey,
115
+ pathPrefix: resolved.pathPrefix,
116
+ via: resolved.via,
117
+ });
118
+ }
119
+ }
120
+ return { sampleable, skipped, accounts: accountIds.length };
121
+ }
122
+ //# sourceMappingURL=enumerate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"enumerate.js","sourceRoot":"","sources":["../../src/sampler/enumerate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACpD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,OAAO,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAC9D,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAE5D,MAAM,OAAO,GAAG,iEAAiE,CAAC;AAqBlF,SAAS,GAAG,CAAC,CAAU;IACrB,OAAO,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;AACpD,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,yBAAyB,CAAC,WAAmB;IAC3D,IAAI,KAAe,CAAC;IACpB,IAAI,CAAC;QACH,KAAK,GAAG,WAAW,CAAC,WAAW,CAAC,CAAC;IACnC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,MAAM,GAAG,GAAa,EAAE,CAAC;IACzB,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC;YAAE,SAAS;QAClC,IAAI,CAAC;YACH,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,CAAC,WAAW,EAAE,IAAI,EAAE,cAAc,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC;YAC3E,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACjB,CAAC;QAAC,MAAM,CAAC;YACP,oEAAoE;QACtE,CAAC;IACH,CAAC;IACD,OAAO,GAAG,CAAC,IAAI,EAAE,CAAC;AACpB,CAAC;AAWD;;;;;;GAMG;AACH,MAAM,UAAU,UAAU,CAAC,MAAyB;IAClD,MAAM,SAAS,GAAG,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,KAAK,IAAI,CAAC,CAAC,MAAM,CAAC;IACzE,OAAO,CACL,8CAA8C,MAAM,CAAC,QAAQ,GAAG;QAChE,aAAa,MAAM,CAAC,UAAU,CAAC,MAAM,cAAc,SAAS,YAAY,MAAM,CAAC,OAAO,CAAC,MAAM,EAAE,CAChG,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,4BAA4B,CAAC,WAAmB;IAC9D,MAAM,UAAU,GAAwB,EAAE,CAAC;IAC3C,MAAM,OAAO,GAAqB,EAAE,CAAC;IACrC,MAAM,UAAU,GAAG,yBAAyB,CAAC,WAAW,CAAC,CAAC;IAE1D,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;QACnC,MAAM,QAAQ,GAAG,IAAI,eAAe,CAAC,SAAS,EAAE,WAAW,CAAC,CAAC;QAE7D,IAAI,WAAW,CAAC;QAChB,IAAI,CAAC;YACH,WAAW,GAAG,QAAQ,CAAC,IAAI,EAAE,CAAC;QAChC,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,CAAC,IAAI,CAAC,EAAE,SAAS,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,EAAE,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YACrE,SAAS;QACX,CAAC;QAED,KAAK,MAAM,CAAC,IAAI,WAAW,EAAE,CAAC;YAC5B,0EAA0E;YAC1E,0EAA0E;YAC1E,2BAA2B;YAC3B,IAAI,CAAC,CAAC,MAAM,KAAK,IAAI,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;gBACjC,OAAO,CAAC,IAAI,CAAC,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,UAAU,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC;gBAC/E,SAAS;YACX,CAAC;YACD,UAAU,CAAC,IAAI,CAAC;gBACd,SAAS;gBACT,OAAO,EAAE,CAAC,CAAC,IAAI;gBACf,UAAU,EAAE,CAAC,CAAC,UAAU;gBACxB,UAAU,EAAE,KAAK;gBACjB,GAAG,EAAE,IAAI;aACV,CAAC,CAAC;QACL,CAAC;QAED,0EAA0E;QAC1E,6EAA6E;QAC7E,6EAA6E;QAC7E,6DAA6D;QAC7D,IAAI,SAAkC,CAAC;QACvC,IAAI,CAAC;YACH,SAAS,GAAG,IAAI,cAAc,CAAC,SAAS,EAAE,WAAW,CAAC,CAAC,IAAI,EAAE,CAAC;QAChE,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,CAAC,IAAI,CAAC,EAAE,SAAS,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,EAAE,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YACtE,SAAS;QACX,CAAC;QACD,KAAK,MAAM,OAAO,IAAI,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC;YACpD,MAAM,QAAQ,GAAG,QAAQ,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;YAC3C,IAAI,QAAQ,CAAC,IAAI,KAAK,IAAI,EAAE,CAAC;gBAC3B,OAAO,CAAC,IAAI,CAAC,EAAE,SAAS,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC;gBACrE,SAAS;YACX,CAAC;YACD,UAAU,CAAC,IAAI,CAAC;gBACd,SAAS;gBACT,OAAO,EAAE,OAAO;gBAChB,UAAU,EAAE,QAAQ,CAAC,UAAU,CAAC,UAAU;gBAC1C,UAAU,EAAE,QAAQ,CAAC,UAAU;gBAC/B,GAAG,EAAE,QAAQ,CAAC,GAAG;aAClB,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,QAAQ,EAAE,UAAU,CAAC,MAAM,EAAE,CAAC;AAC9D,CAAC"}
@@ -0,0 +1,73 @@
1
+ import type { DispatchResult, OutlookMessageData } from "../lib/conversation-archive-dispatch.js";
2
+ import type { OutlookSamplerCursor } from "./cursor.js";
3
+ import type { SampleableMailbox } from "./enumerate.js";
4
+ /**
5
+ * The Outlook sampler's single ingest step (Task 2683) — the mirror of the IMAP
6
+ * arm's `sampleMailbox`. Every dependency is injected so each branch is testable
7
+ * without Microsoft Graph, Neo4j, or a clock.
8
+ */
9
+ /** First-cycle floor for a mailbox with no cursor. Matches the IMAP arm's
10
+ * first-poll window (`imap.ts:1351`); without it the first cycle would page a
11
+ * mailbox's entire history. */
12
+ export declare const BACKFILL_WINDOW_MS: number;
13
+ /** The list fields this arm reads, a subset of `MailListItem`. */
14
+ export interface ListedMessage {
15
+ id: string;
16
+ receivedDateTime: string;
17
+ }
18
+ /** The body fields this arm reads, a subset of `FetchBodyResult`. */
19
+ export interface FetchedBody {
20
+ id: string;
21
+ subject: string | null;
22
+ from: string | null;
23
+ fromName: string | null;
24
+ to: string[];
25
+ cc: string[];
26
+ conversationId: string | null;
27
+ body: string;
28
+ receivedDateTime: string;
29
+ }
30
+ export interface OutlookSamplerDeps {
31
+ /** One page of Inbox messages at or after `since`, newest first. On a
32
+ * continuation call `cursor` is set and `since` is undefined, because a
33
+ * nextLink already encodes the whole window. */
34
+ list: (target: SampleableMailbox, since: string | undefined, cursor: string | undefined) => Promise<{
35
+ items: ListedMessage[];
36
+ nextCursor: string | null;
37
+ }>;
38
+ fetchBody: (target: SampleableMailbox, messageId: string) => Promise<FetchedBody>;
39
+ write: (messages: OutlookMessageData[]) => Promise<DispatchResult>;
40
+ readCursor: (target: SampleableMailbox) => OutlookSamplerCursor | null;
41
+ writeCursor: (target: SampleableMailbox, cursor: OutlookSamplerCursor) => void;
42
+ log: (line: string) => void;
43
+ now: () => number;
44
+ }
45
+ export interface OutlookSampleResult {
46
+ mailbox: string;
47
+ fetched: number;
48
+ cursor: string | null;
49
+ error?: string;
50
+ }
51
+ /**
52
+ * Sample one Outlook mailbox: page every Inbox message at or after the cursor
53
+ * instant, drop the ids already written at that instant, fetch each remaining
54
+ * body in full, write the batch, then advance the cursor.
55
+ *
56
+ * Cases, in order:
57
+ * - a list call throws → op=sample-error, cursor unchanged, retried next cycle.
58
+ * - nothing left after the id exclusion → no-op, cursor unchanged.
59
+ * - a body fetch throws → op=sample-error, nothing written, cursor unchanged.
60
+ * - the write throws → op=sample-error, cursor unchanged, retried next cycle.
61
+ * - otherwise → advance to the newest instant written, carrying every id at
62
+ * that instant. Ids from the prior cursor are carried forward when the
63
+ * instant is unchanged, so a third message arriving at that same instant is
64
+ * still excluded on the cycle after next.
65
+ *
66
+ * One behaviour is inherited from `dispatchOutlookMessagesToConversationArchive`
67
+ * rather than introduced here: it does not throw when it skips a thread whose
68
+ * participants resolve to no :Person or :AdminUser, it returns counts. The
69
+ * cursor therefore advances past a skipped thread. The IMAP arm behaves
70
+ * identically, and matching it is what "same contract" means for this task.
71
+ */
72
+ export declare function sampleOutlookMailbox(deps: OutlookSamplerDeps, target: SampleableMailbox): Promise<OutlookSampleResult>;
73
+ //# sourceMappingURL=sample-outlook-mailbox.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sample-outlook-mailbox.d.ts","sourceRoot":"","sources":["../../src/sampler/sample-outlook-mailbox.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,cAAc,EACd,kBAAkB,EACnB,MAAM,yCAAyC,CAAC;AACjD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AACxD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAExD;;;;GAIG;AAEH;;gCAEgC;AAChC,eAAO,MAAM,kBAAkB,QAA0B,CAAC;AAE1D,kEAAkE;AAClE,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AAED,qEAAqE;AACrE,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,EAAE,EAAE,MAAM,EAAE,CAAC;IACb,EAAE,EAAE,MAAM,EAAE,CAAC;IACb,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,kBAAkB;IACjC;;qDAEiD;IACjD,IAAI,EAAE,CACJ,MAAM,EAAE,iBAAiB,EACzB,KAAK,EAAE,MAAM,GAAG,SAAS,EACzB,MAAM,EAAE,MAAM,GAAG,SAAS,KACvB,OAAO,CAAC;QAAE,KAAK,EAAE,aAAa,EAAE,CAAC;QAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC,CAAC;IACpE,SAAS,EAAE,CAAC,MAAM,EAAE,iBAAiB,EAAE,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,WAAW,CAAC,CAAC;IAClF,KAAK,EAAE,CAAC,QAAQ,EAAE,kBAAkB,EAAE,KAAK,OAAO,CAAC,cAAc,CAAC,CAAC;IACnE,UAAU,EAAE,CAAC,MAAM,EAAE,iBAAiB,KAAK,oBAAoB,GAAG,IAAI,CAAC;IACvE,WAAW,EAAE,CAAC,MAAM,EAAE,iBAAiB,EAAE,MAAM,EAAE,oBAAoB,KAAK,IAAI,CAAC;IAC/E,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5B,GAAG,EAAE,MAAM,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AA8BD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAsB,oBAAoB,CACxC,IAAI,EAAE,kBAAkB,EACxB,MAAM,EAAE,iBAAiB,GACxB,OAAO,CAAC,mBAAmB,CAAC,CAqH9B"}