@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.
- package/dist/__tests__/email-sampler-unit.test.js +22 -0
- package/dist/index.js +3 -0
- package/dist/port-resolution.js +9 -1
- package/package.json +1 -1
- package/payload/platform/lib/routine-templates/dist/roster.d.ts +17 -1
- package/payload/platform/lib/routine-templates/dist/roster.d.ts.map +1 -1
- package/payload/platform/lib/routine-templates/dist/roster.js +8 -0
- package/payload/platform/lib/routine-templates/dist/roster.js.map +1 -1
- package/payload/platform/lib/routine-templates/src/__tests__/seed.test.ts +26 -1
- package/payload/platform/lib/routine-templates/src/roster.ts +26 -1
- package/payload/platform/lib/telegram-store-read/dist/index.d.ts +59 -0
- package/payload/platform/lib/telegram-store-read/dist/index.d.ts.map +1 -0
- package/payload/platform/lib/telegram-store-read/dist/index.js +219 -0
- package/payload/platform/lib/telegram-store-read/dist/index.js.map +1 -0
- package/payload/platform/lib/telegram-store-read/src/__tests__/fold.test.ts +124 -0
- package/payload/platform/lib/telegram-store-read/src/index.ts +262 -0
- package/payload/platform/lib/telegram-store-read/tsconfig.json +9 -0
- package/payload/platform/lib/telegram-store-read/vitest.config.ts +9 -0
- package/payload/platform/package.json +2 -2
- package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +37 -4
- package/payload/platform/plugins/admin/skills/whats-new/SKILL.md +6 -0
- package/payload/platform/plugins/docs/references/outlook-guide.md +21 -2
- package/payload/platform/plugins/docs/references/telegram-guide.md +15 -1
- package/payload/platform/plugins/email/PLUGIN.md +10 -1
- package/payload/platform/plugins/email/mcp/dist/sampler/__tests__/sample-mailbox.test.js +30 -0
- package/payload/platform/plugins/email/mcp/dist/sampler/__tests__/sample-mailbox.test.js.map +1 -1
- package/payload/platform/plugins/email/mcp/dist/sampler/main.js +49 -3
- package/payload/platform/plugins/email/mcp/dist/sampler/main.js.map +1 -1
- package/payload/platform/plugins/email/mcp/dist/sampler/sample-mailbox.d.ts +5 -0
- package/payload/platform/plugins/email/mcp/dist/sampler/sample-mailbox.d.ts.map +1 -1
- package/payload/platform/plugins/email/mcp/dist/sampler/sample-mailbox.js +10 -5
- package/payload/platform/plugins/email/mcp/dist/sampler/sample-mailbox.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-ingest-section-properties-strip.test.js +11 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-ingest-section-properties-strip.test.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest.js +8 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest.js.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/sample-outlook-mailbox.test.d.ts +2 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/sample-outlook-mailbox.test.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/sample-outlook-mailbox.test.js +324 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/sample-outlook-mailbox.test.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/sampler-cursor.test.d.ts +2 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/sampler-cursor.test.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/sampler-cursor.test.js +102 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/sampler-cursor.test.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/sampler-enumerate.test.d.ts +2 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/sampler-enumerate.test.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/sampler-enumerate.test.js +125 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/sampler-enumerate.test.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/sampler/arm.d.ts +2 -0
- package/payload/platform/plugins/outlook/mcp/dist/sampler/arm.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/sampler/arm.js +109 -0
- package/payload/platform/plugins/outlook/mcp/dist/sampler/arm.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/sampler/cursor.d.ts +25 -0
- package/payload/platform/plugins/outlook/mcp/dist/sampler/cursor.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/sampler/cursor.js +94 -0
- package/payload/platform/plugins/outlook/mcp/dist/sampler/cursor.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/sampler/enumerate.d.ts +41 -0
- package/payload/platform/plugins/outlook/mcp/dist/sampler/enumerate.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/sampler/enumerate.js +122 -0
- package/payload/platform/plugins/outlook/mcp/dist/sampler/enumerate.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/sampler/sample-outlook-mailbox.d.ts +73 -0
- package/payload/platform/plugins/outlook/mcp/dist/sampler/sample-outlook-mailbox.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/sampler/sample-outlook-mailbox.js +162 -0
- package/payload/platform/plugins/outlook/mcp/dist/sampler/sample-outlook-mailbox.js.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/index.js +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/gate-flag.test.d.ts +2 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/gate-flag.test.d.ts.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/gate-flag.test.js +90 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/gate-flag.test.js.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/gate.test.js +26 -4
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/gate.test.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/lib/gate-flag.d.ts +78 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/gate-flag.d.ts.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/gate-flag.js +76 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/gate-flag.js.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/gate.d.ts +8 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/gate.d.ts.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/lib/gate.js +9 -1
- package/payload/platform/plugins/scheduling/mcp/dist/lib/gate.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/gate-dispatch-wiring.test.js +15 -0
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/gate-dispatch-wiring.test.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/agent-turn-dispatch.d.ts +12 -1
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/agent-turn-dispatch.d.ts.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/agent-turn-dispatch.js +34 -1
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/agent-turn-dispatch.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/check-due-events.js +9 -1
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/check-due-events.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/scheduling-gate.test.js +116 -4
- package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/scheduling-gate.test.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/tools/scheduling-gate.d.ts +60 -2
- package/payload/platform/plugins/scheduling/mcp/dist/tools/scheduling-gate.d.ts.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/tools/scheduling-gate.js +101 -4
- package/payload/platform/plugins/scheduling/mcp/dist/tools/scheduling-gate.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js +1 -1
- package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js.map +1 -1
- package/payload/platform/templates/specialists/agents/personal-assistant.md +1 -1
- package/payload/server/public/operator.html +1 -1
- 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":"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"}
|
|
@@ -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"}
|