@rubytech/create-maxy-code 0.1.456 → 0.1.458
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/package.json +1 -1
- package/payload/platform/lib/mcp-lifeline/dist/index.d.ts.map +1 -1
- package/payload/platform/lib/mcp-lifeline/dist/index.js +22 -1
- package/payload/platform/lib/mcp-lifeline/dist/index.js.map +1 -1
- package/payload/platform/lib/mcp-lifeline/src/__tests__/lifeline.test.ts +26 -0
- package/payload/platform/lib/mcp-lifeline/src/index.ts +24 -1
- package/payload/platform/lib/storage-broker/dist/__tests__/cf-exec.test.js +135 -0
- package/payload/platform/lib/storage-broker/dist/__tests__/cf-exec.test.js.map +1 -1
- package/payload/platform/lib/storage-broker/dist/cf-exec.d.ts.map +1 -1
- package/payload/platform/lib/storage-broker/dist/cf-exec.js +61 -28
- package/payload/platform/lib/storage-broker/dist/cf-exec.js.map +1 -1
- package/payload/platform/lib/storage-broker/dist/house-credential.d.ts.map +1 -1
- package/payload/platform/lib/storage-broker/dist/house-credential.js +6 -2
- package/payload/platform/lib/storage-broker/dist/house-credential.js.map +1 -1
- package/payload/platform/lib/storage-broker/src/__tests__/cf-exec.test.ts +153 -0
- package/payload/platform/lib/storage-broker/src/cf-exec.ts +85 -41
- package/payload/platform/lib/storage-broker/src/house-credential.ts +6 -2
- package/payload/platform/plugins/cloudflare/bin/__tests__/cf-token.test.sh +145 -4
- package/payload/platform/plugins/cloudflare/bin/cf-token.sh +60 -3
- package/payload/platform/plugins/cloudflare/references/api.md +1 -1
- package/payload/platform/plugins/cloudflare/skills/cloudflare/SKILL.md +1 -1
- package/payload/platform/plugins/contacts/mcp/dist/tools/__tests__/contact-erase.test.d.ts +2 -0
- package/payload/platform/plugins/contacts/mcp/dist/tools/__tests__/contact-erase.test.d.ts.map +1 -0
- package/payload/platform/plugins/contacts/mcp/dist/tools/__tests__/contact-erase.test.js +108 -0
- package/payload/platform/plugins/contacts/mcp/dist/tools/__tests__/contact-erase.test.js.map +1 -0
- package/payload/platform/plugins/contacts/mcp/dist/tools/__tests__/contact-export.test.d.ts +2 -0
- package/payload/platform/plugins/contacts/mcp/dist/tools/__tests__/contact-export.test.d.ts.map +1 -0
- package/payload/platform/plugins/contacts/mcp/dist/tools/__tests__/contact-export.test.js +57 -0
- package/payload/platform/plugins/contacts/mcp/dist/tools/__tests__/contact-export.test.js.map +1 -0
- package/payload/platform/plugins/contacts/mcp/dist/tools/contact-erase.js +2 -2
- package/payload/platform/plugins/contacts/mcp/dist/tools/contact-export.js +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/draft-edit.test.js +16 -3
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/draft-edit.test.js.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/graph-client.test.js +108 -1
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/graph-client.test.js.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/lib/graph-client.d.ts +42 -15
- package/payload/platform/plugins/outlook/mcp/dist/lib/graph-client.d.ts.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/lib/graph-client.js +32 -1
- package/payload/platform/plugins/outlook/mcp/dist/lib/graph-client.js.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/tools/calendar-event.d.ts.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/tools/calendar-event.js +3 -5
- package/payload/platform/plugins/outlook/mcp/dist/tools/calendar-event.js.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/tools/draft-edit.d.ts +7 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/draft-edit.d.ts.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/tools/draft-edit.js +17 -3
- package/payload/platform/plugins/outlook/mcp/dist/tools/draft-edit.js.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/tools/draft.d.ts +6 -1
- package/payload/platform/plugins/outlook/mcp/dist/tools/draft.d.ts.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/tools/draft.js +6 -1
- package/payload/platform/plugins/outlook/mcp/dist/tools/draft.js.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/tools/mail-attachment.d.ts.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/tools/mail-attachment.js +3 -4
- package/payload/platform/plugins/outlook/mcp/dist/tools/mail-attachment.js.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/tools/mail-fetch-body.d.ts.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/tools/mail-fetch-body.js +2 -4
- package/payload/platform/plugins/outlook/mcp/dist/tools/mail-fetch-body.js.map +1 -1
- package/payload/platform/plugins/outlook/references/graph-surfaces.md +14 -2
- package/payload/platform/plugins/storage-broker/PLUGIN.md +1 -1
- package/payload/platform/services/claude-session-manager/dist/index.js +1 -1
- package/payload/platform/services/claude-session-manager/dist/index.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/stuck-turn-recover.d.ts +22 -2
- package/payload/platform/services/claude-session-manager/dist/stuck-turn-recover.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/stuck-turn-recover.js +37 -10
- package/payload/platform/services/claude-session-manager/dist/stuck-turn-recover.js.map +1 -1
- package/payload/server/{chunk-SDYRKIYY.js → chunk-64FGYKNZ.js} +51 -29
- package/payload/server/public/assets/{AdminLoginScreens-Cl7VBbPS.js → AdminLoginScreens-BFbpkEUS.js} +1 -1
- package/payload/server/public/assets/{AdminShell-lBaQG3aT.js → AdminShell-CdOGObaZ.js} +1 -1
- package/payload/server/public/assets/{Checkbox-P8PNtn8n.js → Checkbox-RFD_F0c0.js} +1 -1
- package/payload/server/public/assets/{admin-CB6VgLUJ.js → admin-CYGdL-1O.js} +1 -1
- package/payload/server/public/assets/{browser-CKH8YoIg.js → browser-DVfa_plj.js} +1 -1
- package/payload/server/public/assets/{calendar-xQ-OEVti.js → calendar-JC_Dxa4n.js} +1 -1
- package/payload/server/public/assets/chat-Yf8Nkop8.js +1 -0
- package/payload/server/public/assets/chevron-left-CAiAUBxO.js +1 -0
- package/payload/server/public/assets/data-hqdYXaEn.js +1 -0
- package/payload/server/public/assets/graph-hBUrRSkr.js +51 -0
- package/payload/server/public/assets/{graph-labels-DbL7Cxod.js → graph-labels-CHh6zs8t.js} +1 -1
- package/payload/server/public/assets/{maximize-2-Up8UP3Iu.js → maximize-2-DjxE4rm9.js} +1 -1
- package/payload/server/public/assets/{operator-BE366HH-.js → operator-BqU9SQuu.js} +1 -1
- package/payload/server/public/assets/{page-Wvb1S03b.js → page-B4c4_4HP.js} +1 -1
- package/payload/server/public/assets/{page-CQjjQz0V.js → page-DiQhU6lD.js} +1 -1
- package/payload/server/public/assets/{public-ZkXB-CHy.js → public-Cu_eALgY.js} +1 -1
- package/payload/server/public/assets/{rotate-ccw-TTz_MVO0.js → rotate-ccw-F9DsSTSJ.js} +1 -1
- package/payload/server/public/assets/{tasks-DNCoSRkP.js → tasks-CE-D0f50.js} +1 -1
- package/payload/server/public/assets/{time-entry-format-BooKdDRT.js → time-entry-format-D2rsWhov.js} +1 -1
- package/payload/server/public/assets/{useCopyFeedback-C23_n92I.js → useCopyFeedback-QxOmoobV.js} +1 -1
- package/payload/server/public/assets/{useSelectionMode-c4oZYAW0.js → useSelectionMode-RIzNqFjV.js} +1 -1
- package/payload/server/public/assets/{useSubAccountSwitcher-CHJo5leK.css → useSubAccountSwitcher-BW_fK1Nt.css} +1 -1
- package/payload/server/public/assets/{useVoiceRecorder-BkxW-GL1.js → useVoiceRecorder-WrWurSYI.js} +1 -1
- package/payload/server/public/browser.html +5 -5
- package/payload/server/public/calendar.html +6 -6
- package/payload/server/public/chat.html +12 -12
- package/payload/server/public/data.html +10 -10
- package/payload/server/public/graph.html +11 -11
- package/payload/server/public/index.html +13 -13
- package/payload/server/public/operator.html +14 -14
- package/payload/server/public/public.html +12 -12
- package/payload/server/public/tasks.html +5 -5
- package/payload/server/server.js +2 -2
- package/payload/server/{src-554BYJMN.js → src-S7C4P6TT.js} +1 -1
- package/payload/server/public/assets/chat-DH8K5TeZ.js +0 -1
- package/payload/server/public/assets/chevron-left-Ceky-ebO.js +0 -1
- package/payload/server/public/assets/data-Cvv8Kg9J.js +0 -1
- package/payload/server/public/assets/graph-CrEmAyVE.js +0 -51
- /package/payload/server/public/assets/{useSubAccountSwitcher-6UoTftbJ.js → useSubAccountSwitcher-wo_qGW-I.js} +0 -0
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
// Task 1310 — the email-source :KnowledgeDocument threads reached by
|
|
2
|
+
// contact-erase are matched by (email, accountId), not email alone. Person
|
|
3
|
+
// email uniqueness became composite (accountId, email) in Task 1304, so two
|
|
4
|
+
// accounts can legitimately hold a :Person with the same email. These tests
|
|
5
|
+
// lock the account-scoped Person match key in both the count query and the
|
|
6
|
+
// irreversible DETACH DELETE query, so a future non-account-scoped writer of
|
|
7
|
+
// FROM/TO/CC edges cannot reintroduce a cross-account leak here.
|
|
8
|
+
import { describe, it, expect, beforeEach, vi } from "vitest";
|
|
9
|
+
const mockTx = {
|
|
10
|
+
run: vi.fn().mockResolvedValue({ records: [] }),
|
|
11
|
+
commit: vi.fn().mockResolvedValue(undefined),
|
|
12
|
+
rollback: vi.fn().mockResolvedValue(undefined),
|
|
13
|
+
};
|
|
14
|
+
const mockSession = {
|
|
15
|
+
run: vi.fn(),
|
|
16
|
+
beginTransaction: vi.fn(() => mockTx),
|
|
17
|
+
close: vi.fn().mockResolvedValue(undefined),
|
|
18
|
+
};
|
|
19
|
+
vi.mock("../../lib/neo4j.js", () => ({
|
|
20
|
+
getSession: () => mockSession,
|
|
21
|
+
}));
|
|
22
|
+
vi.mock("../../lib/resolve-person.js", () => ({
|
|
23
|
+
// Literal accountId — this factory evaluates during import hoisting, before
|
|
24
|
+
// the ACCOUNT_ID const below initializes (TDZ). The person-resolve run is
|
|
25
|
+
// overridden per-test anyway, so this return shape is not asserted on.
|
|
26
|
+
resolvePersonMatch: () => ({
|
|
27
|
+
matchClause: "MATCH (p:Person {email: $email, accountId: $accountId})",
|
|
28
|
+
queryParams: {
|
|
29
|
+
email: "jane@example.com",
|
|
30
|
+
accountId: "20028fb5-db26-4de7-bff6-c5ec2d4fc766",
|
|
31
|
+
},
|
|
32
|
+
}),
|
|
33
|
+
}));
|
|
34
|
+
import { contactErase } from "../contact-erase.js";
|
|
35
|
+
const ACCOUNT_ID = "20028fb5-db26-4de7-bff6-c5ec2d4fc766";
|
|
36
|
+
const PERSON_EMAIL = "jane@example.com";
|
|
37
|
+
// A count record whose numeric getters return 0 regardless of accessor shape.
|
|
38
|
+
function zeroCounts(keys) {
|
|
39
|
+
return {
|
|
40
|
+
records: [
|
|
41
|
+
{
|
|
42
|
+
get: (k) => (keys.includes(k) ? { low: 0, valueOf: () => 0 } : 0),
|
|
43
|
+
},
|
|
44
|
+
],
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
function personResolveRecord() {
|
|
48
|
+
return {
|
|
49
|
+
records: [
|
|
50
|
+
{
|
|
51
|
+
get: (k) => {
|
|
52
|
+
switch (k) {
|
|
53
|
+
case "personNodeId":
|
|
54
|
+
return "4:person:9";
|
|
55
|
+
case "personEmail":
|
|
56
|
+
return PERSON_EMAIL;
|
|
57
|
+
case "personPhone":
|
|
58
|
+
return null;
|
|
59
|
+
case "sessionId":
|
|
60
|
+
return null; // skip the conversation-count branch
|
|
61
|
+
default:
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
],
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
beforeEach(() => {
|
|
70
|
+
mockSession.run.mockReset();
|
|
71
|
+
mockSession.beginTransaction.mockClear();
|
|
72
|
+
mockTx.run.mockClear();
|
|
73
|
+
mockTx.commit.mockClear();
|
|
74
|
+
mockTx.rollback.mockClear();
|
|
75
|
+
});
|
|
76
|
+
describe("contact-erase — email-thread match is account-scoped (Task 1310)", () => {
|
|
77
|
+
// With sessionId null the run sequence is:
|
|
78
|
+
// [0] person resolve, [1] visitor counts, [2] email counts, [3] grant counts
|
|
79
|
+
function primePreviewRuns() {
|
|
80
|
+
mockSession.run
|
|
81
|
+
.mockResolvedValueOnce(personResolveRecord())
|
|
82
|
+
.mockResolvedValueOnce(zeroCounts(["visitorSessions", "visitorEvents", "anonVisitors"]))
|
|
83
|
+
.mockResolvedValueOnce(zeroCounts(["emailCount"]))
|
|
84
|
+
.mockResolvedValueOnce(zeroCounts(["grantCount"]));
|
|
85
|
+
}
|
|
86
|
+
it("scopes the email-count Person match by accountId", async () => {
|
|
87
|
+
primePreviewRuns();
|
|
88
|
+
await contactErase({ email: PERSON_EMAIL, accountId: ACCOUNT_ID, confirm: false });
|
|
89
|
+
const [emailCypher, emailParams] = mockSession.run.mock.calls[2];
|
|
90
|
+
expect(emailCypher).toContain("MATCH (p:Person {email: $personEmail, accountId: $accountId})");
|
|
91
|
+
expect(emailParams.accountId).toBe(ACCOUNT_ID);
|
|
92
|
+
});
|
|
93
|
+
it("scopes the DETACH DELETE Person match by accountId", async () => {
|
|
94
|
+
// Confirmed erase: preview runs on session, then the cascade runs on the tx.
|
|
95
|
+
mockSession.run
|
|
96
|
+
.mockResolvedValueOnce(personResolveRecord())
|
|
97
|
+
.mockResolvedValueOnce(zeroCounts(["visitorSessions", "visitorEvents", "anonVisitors"]))
|
|
98
|
+
.mockResolvedValueOnce(zeroCounts(["emailCount"]))
|
|
99
|
+
.mockResolvedValueOnce(zeroCounts(["grantCount"]));
|
|
100
|
+
await contactErase({ email: PERSON_EMAIL, accountId: ACCOUNT_ID, confirm: true });
|
|
101
|
+
const deleteCall = mockTx.run.mock.calls.find(([cypher]) => cypher.includes("DETACH DELETE kd"));
|
|
102
|
+
expect(deleteCall, "email-thread DETACH DELETE query should have run").toBeTruthy();
|
|
103
|
+
const [deleteCypher, deleteParams] = deleteCall;
|
|
104
|
+
expect(deleteCypher).toContain("MATCH (p:Person {email: $personEmail, accountId: $accountId})");
|
|
105
|
+
expect(deleteParams.accountId).toBe(ACCOUNT_ID);
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
//# sourceMappingURL=contact-erase.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contact-erase.test.js","sourceRoot":"","sources":["../../../src/tools/__tests__/contact-erase.test.ts"],"names":[],"mappings":"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"}
|
package/payload/platform/plugins/contacts/mcp/dist/tools/__tests__/contact-export.test.d.ts.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contact-export.test.d.ts","sourceRoot":"","sources":["../../../src/tools/__tests__/contact-export.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// Task 1310 — the email-source :KnowledgeDocument threads gathered by
|
|
2
|
+
// contact-export are matched by (email, accountId), not email alone. Person
|
|
3
|
+
// email uniqueness became composite (accountId, email) in Task 1304, so two
|
|
4
|
+
// accounts can legitimately hold a :Person with the same email. This test
|
|
5
|
+
// locks the account-scoped Person match key so a future non-account-scoped
|
|
6
|
+
// writer of FROM/TO/CC edges cannot leak another account's email threads into
|
|
7
|
+
// this account's export.
|
|
8
|
+
import { describe, it, expect, beforeEach, vi } from "vitest";
|
|
9
|
+
const mockSession = {
|
|
10
|
+
run: vi.fn(),
|
|
11
|
+
close: vi.fn().mockResolvedValue(undefined),
|
|
12
|
+
};
|
|
13
|
+
vi.mock("../../lib/neo4j.js", () => ({
|
|
14
|
+
getSession: () => mockSession,
|
|
15
|
+
}));
|
|
16
|
+
vi.mock("../../lib/resolve-person.js", () => ({
|
|
17
|
+
resolvePersonMatch: () => ({
|
|
18
|
+
matchClause: "MATCH (p:Person {email: $email, accountId: $accountId})",
|
|
19
|
+
queryParams: {
|
|
20
|
+
email: "jane@example.com",
|
|
21
|
+
accountId: "20028fb5-db26-4de7-bff6-c5ec2d4fc766",
|
|
22
|
+
},
|
|
23
|
+
}),
|
|
24
|
+
}));
|
|
25
|
+
import { contactExport } from "../contact-export.js";
|
|
26
|
+
const ACCOUNT_ID = "20028fb5-db26-4de7-bff6-c5ec2d4fc766";
|
|
27
|
+
const PERSON_EMAIL = "jane@example.com";
|
|
28
|
+
function personResolveRecord() {
|
|
29
|
+
return {
|
|
30
|
+
records: [
|
|
31
|
+
{
|
|
32
|
+
get: (k) => k === "nodeId"
|
|
33
|
+
? "4:person:9"
|
|
34
|
+
: { properties: { email: PERSON_EMAIL } }, // no sessionId → skip conversation step
|
|
35
|
+
},
|
|
36
|
+
],
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
beforeEach(() => {
|
|
40
|
+
mockSession.run.mockReset();
|
|
41
|
+
mockSession.close.mockClear();
|
|
42
|
+
});
|
|
43
|
+
describe("contact-export — email-thread match is account-scoped (Task 1310)", () => {
|
|
44
|
+
it("scopes the email-gather Person match by accountId", async () => {
|
|
45
|
+
// Run sequence with no sessionId:
|
|
46
|
+
// [0] person resolve, [1] access grants, [2] email gather
|
|
47
|
+
mockSession.run
|
|
48
|
+
.mockResolvedValueOnce(personResolveRecord())
|
|
49
|
+
.mockResolvedValueOnce({ records: [] })
|
|
50
|
+
.mockResolvedValueOnce({ records: [] });
|
|
51
|
+
await contactExport({ email: PERSON_EMAIL, accountId: ACCOUNT_ID });
|
|
52
|
+
const [emailCypher, emailParams] = mockSession.run.mock.calls[2];
|
|
53
|
+
expect(emailCypher).toContain("MATCH (p:Person {email: $personEmail, accountId: $accountId})");
|
|
54
|
+
expect(emailParams.accountId).toBe(ACCOUNT_ID);
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
//# sourceMappingURL=contact-export.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contact-export.test.js","sourceRoot":"","sources":["../../../src/tools/__tests__/contact-export.test.ts"],"names":[],"mappings":"AAAA,sEAAsE;AACtE,4EAA4E;AAC5E,4EAA4E;AAC5E,0EAA0E;AAC1E,2EAA2E;AAC3E,8EAA8E;AAC9E,yBAAyB;AAEzB,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAE9D,MAAM,WAAW,GAAG;IAClB,GAAG,EAAE,EAAE,CAAC,EAAE,EAAE;IACZ,KAAK,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,iBAAiB,CAAC,SAAS,CAAC;CAC5C,CAAC;AAEF,EAAE,CAAC,IAAI,CAAC,oBAAoB,EAAE,GAAG,EAAE,CAAC,CAAC;IACnC,UAAU,EAAE,GAAG,EAAE,CAAC,WAAW;CAC9B,CAAC,CAAC,CAAC;AAEJ,EAAE,CAAC,IAAI,CAAC,6BAA6B,EAAE,GAAG,EAAE,CAAC,CAAC;IAC5C,kBAAkB,EAAE,GAAG,EAAE,CAAC,CAAC;QACzB,WAAW,EAAE,yDAAyD;QACtE,WAAW,EAAE;YACX,KAAK,EAAE,kBAAkB;YACzB,SAAS,EAAE,sCAAsC;SAClD;KACF,CAAC;CACH,CAAC,CAAC,CAAC;AAEJ,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAErD,MAAM,UAAU,GAAG,sCAAsC,CAAC;AAC1D,MAAM,YAAY,GAAG,kBAAkB,CAAC;AAExC,SAAS,mBAAmB;IAC1B,OAAO;QACL,OAAO,EAAE;YACP;gBACE,GAAG,EAAE,CAAC,CAAS,EAAE,EAAE,CACjB,CAAC,KAAK,QAAQ;oBACZ,CAAC,CAAC,YAAY;oBACd,CAAC,CAAC,EAAE,UAAU,EAAE,EAAE,KAAK,EAAE,YAAY,EAAE,EAAE,EAAE,wCAAwC;aACxF;SACF;KACF,CAAC;AACJ,CAAC;AAED,UAAU,CAAC,GAAG,EAAE;IACd,WAAW,CAAC,GAAG,CAAC,SAAS,EAAE,CAAC;IAC5B,WAAW,CAAC,KAAK,CAAC,SAAS,EAAE,CAAC;AAChC,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,mEAAmE,EAAE,GAAG,EAAE;IACjF,EAAE,CAAC,mDAAmD,EAAE,KAAK,IAAI,EAAE;QACjE,kCAAkC;QAClC,4DAA4D;QAC5D,WAAW,CAAC,GAAG;aACZ,qBAAqB,CAAC,mBAAmB,EAAE,CAAC;aAC5C,qBAAqB,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;aACtC,qBAAqB,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC,CAAC;QAE1C,MAAM,aAAa,CAAC,EAAE,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,UAAU,EAAE,CAAC,CAAC;QAEpE,MAAM,CAAC,WAAW,EAAE,WAAW,CAAC,GAAG,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QACjE,MAAM,CAAC,WAAW,CAAC,CAAC,SAAS,CAAC,+DAA+D,CAAC,CAAC;QAC/F,MAAM,CAAC,WAAW,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IACjD,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -86,7 +86,7 @@ export async function contactErase(params) {
|
|
|
86
86
|
// :KnowledgeDocument {source:'email'} linked to :Person via address edges.
|
|
87
87
|
if (personEmail) {
|
|
88
88
|
const emailCountResult = await session.run(`MATCH (kd:KnowledgeDocument {source: 'email', accountId: $accountId})
|
|
89
|
-
MATCH (p:Person {email: $personEmail})
|
|
89
|
+
MATCH (p:Person {email: $personEmail, accountId: $accountId})
|
|
90
90
|
WHERE (kd)-[:FROM]->(p) OR (kd)-[:TO]->(p) OR (kd)-[:CC]->(p)
|
|
91
91
|
RETURN count(DISTINCT kd) AS emailCount`, { personEmail, accountId });
|
|
92
92
|
if (emailCountResult.records.length > 0) {
|
|
@@ -129,7 +129,7 @@ export async function contactErase(params) {
|
|
|
129
129
|
// thread is one :KnowledgeDocument {source:'email'}.
|
|
130
130
|
if (personEmail) {
|
|
131
131
|
await tx.run(`MATCH (kd:KnowledgeDocument {source: 'email', accountId: $accountId})
|
|
132
|
-
MATCH (p:Person {email: $personEmail})
|
|
132
|
+
MATCH (p:Person {email: $personEmail, accountId: $accountId})
|
|
133
133
|
WHERE (kd)-[:FROM]->(p) OR (kd)-[:TO]->(p) OR (kd)-[:CC]->(p)
|
|
134
134
|
DETACH DELETE kd`, { personEmail, accountId });
|
|
135
135
|
}
|
|
@@ -74,7 +74,7 @@ export async function contactExport(params) {
|
|
|
74
74
|
const personEmail = personProps.email;
|
|
75
75
|
if (personEmail) {
|
|
76
76
|
const emailResult = await session.run(`MATCH (kd:KnowledgeDocument {source: 'email', accountId: $accountId})
|
|
77
|
-
MATCH (p:Person {email: $personEmail})
|
|
77
|
+
MATCH (p:Person {email: $personEmail, accountId: $accountId})
|
|
78
78
|
WHERE (kd)-[:FROM]->(p) OR (kd)-[:TO]->(p) OR (kd)-[:CC]->(p)
|
|
79
79
|
RETURN kd, elementId(kd) AS eNodeId
|
|
80
80
|
ORDER BY kd.lastReceivedAt DESC`, { personEmail, accountId });
|
|
@@ -31,11 +31,14 @@ function mockGraph(status, bodyObj) {
|
|
|
31
31
|
});
|
|
32
32
|
return { restore: () => { globalThis.fetch = original; }, calls };
|
|
33
33
|
}
|
|
34
|
-
test("outlook-draft-edit: PATCHes only supplied fields", async () => {
|
|
35
|
-
|
|
34
|
+
test("outlook-draft-edit: PATCHes only supplied fields, and reports the id Graph returns", async () => {
|
|
35
|
+
// The mock returns a DIFFERENT id from the one sent. That is the whole point:
|
|
36
|
+
// the previous mock echoed the id it was handed, so the test passed whether or
|
|
37
|
+
// not the code read the response at all (Task 1688).
|
|
38
|
+
const mock = mockGraph(200, { id: "DRAFT9-RESAVED" });
|
|
36
39
|
try {
|
|
37
40
|
const r = await runDraftEdit(configWithToken("acct-de-1"), { draftId: "DRAFT9", subject: "Revised", body: "new text" });
|
|
38
|
-
assert.deepEqual(r, { draftId: "DRAFT9", updated: true });
|
|
41
|
+
assert.deepEqual(r, { draftId: "DRAFT9-RESAVED", updated: true });
|
|
39
42
|
assert.equal(mock.calls.length, 1);
|
|
40
43
|
assert.equal(mock.calls[0].method, "PATCH");
|
|
41
44
|
assert.match(mock.calls[0].url, /\/me\/messages\/DRAFT9$/);
|
|
@@ -47,6 +50,16 @@ test("outlook-draft-edit: PATCHes only supplied fields", async () => {
|
|
|
47
50
|
mock.restore();
|
|
48
51
|
}
|
|
49
52
|
});
|
|
53
|
+
test("outlook-draft-edit: refuses to claim success when the response carries no id", async () => {
|
|
54
|
+
// Mirrors runDraft: never return a handle we did not read back.
|
|
55
|
+
const mock = mockGraph(200, {});
|
|
56
|
+
try {
|
|
57
|
+
await assert.rejects(runDraftEdit(configWithToken("acct-de-noid"), { draftId: "DRAFT9", subject: "Revised" }), /returned no id/);
|
|
58
|
+
}
|
|
59
|
+
finally {
|
|
60
|
+
mock.restore();
|
|
61
|
+
}
|
|
62
|
+
});
|
|
50
63
|
test("outlook-draft-edit: isHtml sets HTML content type", async () => {
|
|
51
64
|
const mock = mockGraph(200, { id: "DRAFT9" });
|
|
52
65
|
try {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"draft-edit.test.js","sourceRoot":"","sources":["../../src/__tests__/draft-edit.test.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,MAAM,MAAM,oBAAoB,CAAC;AACxC,OAAO,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AACtC,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAEpD,OAAO,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAEtD,SAAS,eAAe,CAAC,SAAiB;IACxC,MAAM,GAAG,GAAG,WAAW,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,oBAAoB,CAAC,CAAC,CAAC;IAC9D,MAAM,UAAU,GAAG,IAAI,UAAU,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC;IAClD,UAAU,CAAC,KAAK,CAAC,cAAc,EAAE,eAAe,EAAE,IAAI,EAAE,EAAE,MAAM,EAAE,CAAC,gBAAgB,CAAC,EAAE,CAAC,CAAC;IACxF,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,QAAQ,EAAE,QAAQ,EAAE,SAAS,EAAE,UAAU,EAAE,CAAC;AAChF,CAAC;AAGD,SAAS,SAAS,CAAC,MAAc,EAAE,OAAuB;IACxD,MAAM,KAAK,GAAmB,EAAE,CAAC;IACjC,MAAM,QAAQ,GAAG,UAAU,CAAC,KAAK,CAAC;IAClC,UAAU,CAAC,KAAK,GAAG,CAAC,KAAK,EAAE,KAA6B,EAAE,IAAkB,EAAE,EAAE;QAC9E,MAAM,GAAG,GAAG,KAAK,YAAY,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC;QACpD,MAAM,GAAG,GAAG,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAC;QACjF,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,qBAAqB,CAAC;YAAE,OAAO,QAAQ,CAAC,KAAuC,EAAE,IAAI,CAAC,CAAC;QACzG,IAAI,IAAI,GAAkB,IAAI,CAAC;QAC/B,IAAI,GAAG;YAAE,IAAI,GAAG,MAAM,GAAG,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;aACtD,IAAI,OAAO,IAAI,EAAE,IAAI,KAAK,QAAQ;YAAE,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;QAC1D,KAAK,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,IAAI,IAAI,EAAE,MAAM,IAAI,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QACxE,MAAM,OAAO,GAAG,OAAO,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;QAClE,MAAM,OAAO,GAAG,OAAO,KAAK,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,cAAc,EAAE,kBAAkB,EAAE,CAAC;QACtF,OAAO,IAAI,QAAQ,CAAC,OAAO,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,CAAC;IACpD,CAAC,CAAiB,CAAC;IACnB,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,GAAG,UAAU,CAAC,KAAK,GAAG,QAAQ,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC;AACpE,CAAC;AAED,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"draft-edit.test.js","sourceRoot":"","sources":["../../src/__tests__/draft-edit.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,6 +1,36 @@
|
|
|
1
1
|
import test from "node:test";
|
|
2
2
|
import assert from "node:assert/strict";
|
|
3
|
-
import {
|
|
3
|
+
import { mkdtempSync } from "node:fs";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import { classifyGraphError, createGraphClient, callGraph, patchGraph, IMMUTABLE_ID_PREFER } from "../lib/graph-client.js";
|
|
7
|
+
import { TokenStore } from "../auth/token-store.js";
|
|
8
|
+
/** A config whose TokenStore holds a live (unexpired) access token. */
|
|
9
|
+
function configWithToken(accountId) {
|
|
10
|
+
const dir = mkdtempSync(join(tmpdir(), "outlook-graphclient-"));
|
|
11
|
+
const tokenStore = new TokenStore(accountId, dir);
|
|
12
|
+
tokenStore.store("access-token", "refresh-token", 3600, { scopes: ["Mail.ReadWrite"] });
|
|
13
|
+
return { clientId: "test-client", tenantId: "common", accountId, tokenStore };
|
|
14
|
+
}
|
|
15
|
+
/** Capture the headers of every outbound Graph request. */
|
|
16
|
+
function captureHeaders() {
|
|
17
|
+
const seen = [];
|
|
18
|
+
const original = globalThis.fetch;
|
|
19
|
+
globalThis.fetch = (async (input, init) => {
|
|
20
|
+
const req = input instanceof Request ? input : null;
|
|
21
|
+
const headers = req ? req.headers : new Headers(init?.headers ?? {});
|
|
22
|
+
const flat = {};
|
|
23
|
+
headers.forEach((v, k) => {
|
|
24
|
+
flat[k.toLowerCase()] = v;
|
|
25
|
+
});
|
|
26
|
+
seen.push(flat);
|
|
27
|
+
return new Response(JSON.stringify({ id: "X" }), {
|
|
28
|
+
status: 200,
|
|
29
|
+
headers: { "content-type": "application/json" },
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
return { restore: () => { globalThis.fetch = original; }, seen };
|
|
33
|
+
}
|
|
4
34
|
test("classifyGraphError 401 → auth", () => {
|
|
5
35
|
const cls = classifyGraphError({
|
|
6
36
|
statusCode: 401,
|
|
@@ -104,4 +134,81 @@ test("classifyGraphError reads Retry-After via fetch-style response.headers.get"
|
|
|
104
134
|
assert.equal(cls.retryAfterMs, 10000);
|
|
105
135
|
}
|
|
106
136
|
});
|
|
137
|
+
test("every Graph request carries the immutable-id Prefer header", async () => {
|
|
138
|
+
const cap = captureHeaders();
|
|
139
|
+
try {
|
|
140
|
+
const client = createGraphClient(configWithToken("acct-prefer-1"));
|
|
141
|
+
await client.api("/me/messages/X").get();
|
|
142
|
+
}
|
|
143
|
+
finally {
|
|
144
|
+
cap.restore();
|
|
145
|
+
}
|
|
146
|
+
assert.equal(cap.seen.length, 1);
|
|
147
|
+
assert.equal(cap.seen[0]["prefer"], IMMUTABLE_ID_PREFER);
|
|
148
|
+
});
|
|
149
|
+
test("a per-request .header() does not displace the Prefer header", async () => {
|
|
150
|
+
// mail-search sets ConsistencyLevel per request. If that dropped the central
|
|
151
|
+
// Prefer, search alone would keep returning REGULAR ids while every other
|
|
152
|
+
// tool used immutable ones — a silent mixed-namespace break (Task 1688).
|
|
153
|
+
const cap = captureHeaders();
|
|
154
|
+
try {
|
|
155
|
+
const client = createGraphClient(configWithToken("acct-prefer-2"));
|
|
156
|
+
await client.api("/me/messages/X").header("ConsistencyLevel", "eventual").get();
|
|
157
|
+
}
|
|
158
|
+
finally {
|
|
159
|
+
cap.restore();
|
|
160
|
+
}
|
|
161
|
+
assert.equal(cap.seen.length, 1);
|
|
162
|
+
assert.equal(cap.seen[0]["prefer"], IMMUTABLE_ID_PREFER);
|
|
163
|
+
assert.equal(cap.seen[0]["consistencylevel"], "eventual");
|
|
164
|
+
});
|
|
165
|
+
test("graph-error names the target of an id-addressed failure", async () => {
|
|
166
|
+
// Without `target` the log records that SOMETHING 404'd but not what, which
|
|
167
|
+
// is why Task 1688 needed the session JSONL to identify the dead draft.
|
|
168
|
+
const lines = [];
|
|
169
|
+
const origWrite = process.stderr.write.bind(process.stderr);
|
|
170
|
+
process.stderr.write = (s) => {
|
|
171
|
+
lines.push(s);
|
|
172
|
+
return true;
|
|
173
|
+
};
|
|
174
|
+
const origFetch = globalThis.fetch;
|
|
175
|
+
globalThis.fetch = (async () => new Response(JSON.stringify({ error: { code: "ErrorItemNotFound" } }), {
|
|
176
|
+
status: 404,
|
|
177
|
+
headers: { "content-type": "application/json" },
|
|
178
|
+
}));
|
|
179
|
+
try {
|
|
180
|
+
await assert.rejects(() => callGraph(configWithToken("acct-target-1"), async (c) => c.api("/me/messages/DEADBEEF").get(), { tool: "mail-fetch-body", target: "/me/messages/DEADBEEF" }));
|
|
181
|
+
}
|
|
182
|
+
finally {
|
|
183
|
+
globalThis.fetch = origFetch;
|
|
184
|
+
process.stderr.write = origWrite;
|
|
185
|
+
}
|
|
186
|
+
const errLine = lines.find((l) => l.includes("graph-error"));
|
|
187
|
+
assert.ok(errLine, "expected a graph-error line");
|
|
188
|
+
assert.match(errLine, /status=404/);
|
|
189
|
+
assert.match(errLine, /DEADBEEF/);
|
|
190
|
+
});
|
|
191
|
+
test("a write's graph-error names its path without the caller passing target", async () => {
|
|
192
|
+
const lines = [];
|
|
193
|
+
const origWrite = process.stderr.write.bind(process.stderr);
|
|
194
|
+
process.stderr.write = (s) => {
|
|
195
|
+
lines.push(s);
|
|
196
|
+
return true;
|
|
197
|
+
};
|
|
198
|
+
const origFetch = globalThis.fetch;
|
|
199
|
+
globalThis.fetch = (async () => new Response(JSON.stringify({ error: { code: "ErrorItemNotFound" } }), {
|
|
200
|
+
status: 404,
|
|
201
|
+
headers: { "content-type": "application/json" },
|
|
202
|
+
}));
|
|
203
|
+
try {
|
|
204
|
+
await assert.rejects(() => patchGraph(configWithToken("acct-target-2"), "/me/messages/CAFEBABE", { subject: "x" }, { tool: "draft-edit" }));
|
|
205
|
+
}
|
|
206
|
+
finally {
|
|
207
|
+
globalThis.fetch = origFetch;
|
|
208
|
+
process.stderr.write = origWrite;
|
|
209
|
+
}
|
|
210
|
+
const errLine = lines.find((l) => l.includes("graph-error"));
|
|
211
|
+
assert.ok(errLine, "expected a graph-error line");
|
|
212
|
+
assert.match(errLine, /CAFEBABE/);
|
|
213
|
+
});
|
|
107
214
|
//# sourceMappingURL=graph-client.test.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"graph-client.test.js","sourceRoot":"","sources":["../../src/__tests__/graph-client.test.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,MAAM,MAAM,oBAAoB,CAAC;AACxC,OAAO,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"graph-client.test.js","sourceRoot":"","sources":["../../src/__tests__/graph-client.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -17,6 +17,20 @@ export interface GraphClientConfig {
|
|
|
17
17
|
accountId: string;
|
|
18
18
|
tokenStore: TokenStore;
|
|
19
19
|
}
|
|
20
|
+
/**
|
|
21
|
+
* Options shared by every Graph call helper.
|
|
22
|
+
*
|
|
23
|
+
* `tool` names the caller, and drives the scope-insufficient and rate-limit
|
|
24
|
+
* messages. `target` is the specific resource path an id-addressed call acts on:
|
|
25
|
+
* without it a `graph-error` records that *something* 404'd but not *what*,
|
|
26
|
+
* which is why identifying the dead draft in Task 1688 required the session
|
|
27
|
+
* JSONL rather than the log. Collection queries address no single item and
|
|
28
|
+
* correctly leave it unset.
|
|
29
|
+
*/
|
|
30
|
+
export interface GraphCallOpts {
|
|
31
|
+
tool: string;
|
|
32
|
+
target?: string;
|
|
33
|
+
}
|
|
20
34
|
export type GraphErrorClass = {
|
|
21
35
|
kind: "auth";
|
|
22
36
|
statusCode: number;
|
|
@@ -46,6 +60,29 @@ export type GraphErrorClass = {
|
|
|
46
60
|
code: string;
|
|
47
61
|
};
|
|
48
62
|
export declare function classifyGraphError(err: unknown): GraphErrorClass;
|
|
63
|
+
/**
|
|
64
|
+
* Opt into Graph immutable item ids.
|
|
65
|
+
*
|
|
66
|
+
* Outlook item ids are NOT stable: Graph rotates an item's id when the item is
|
|
67
|
+
* moved or re-saved — including by the operator's own mail client. That is what
|
|
68
|
+
* killed a live draft handle in Task 1688: our PATCH returned 200, and four
|
|
69
|
+
* minutes later the same id returned 404 ErrorItemNotFound, so the agent
|
|
70
|
+
* concluded the draft had vanished and rebuilt it. An immutable id survives that
|
|
71
|
+
* event; a regular id does not.
|
|
72
|
+
*
|
|
73
|
+
* This rides on the one client factory because it is plugin-wide by necessity,
|
|
74
|
+
* not preference: the immutable and default namespaces carry DIFFERENT values
|
|
75
|
+
* for the same item, so a per-tool opt-in would have one tool emitting ids that
|
|
76
|
+
* another cannot address. Never set this per-tool.
|
|
77
|
+
*
|
|
78
|
+
* TRAP for whoever adds the next header: `Prefer` is also how Graph carries
|
|
79
|
+
* `outlook.timezone` and `odata.maxpagesize`. A per-request
|
|
80
|
+
* `.header("Prefer", ...)` REPLACES this one for that call, silently dropping
|
|
81
|
+
* that tool back to regular ids — the mixed-namespace break, invisible until an
|
|
82
|
+
* id passed between tools 404s. `Prefer` is a comma-separated multi-value
|
|
83
|
+
* header: append to this value, never set `Prefer` on its own.
|
|
84
|
+
*/
|
|
85
|
+
export declare const IMMUTABLE_ID_PREFER = "IdType=\"ImmutableId\"";
|
|
49
86
|
/**
|
|
50
87
|
* Factory wrapping `Client.init`. Exported as a named function so tests can
|
|
51
88
|
* `vi.spyOn(graphClient, "createGraphClient")` reliably (avoids the
|
|
@@ -66,9 +103,7 @@ export declare function getOrRefreshAccessToken(config: GraphClientConfig, force
|
|
|
66
103
|
* retry once), and respect for 429 Retry-After. All terminal classes throw
|
|
67
104
|
* with operator-readable messages.
|
|
68
105
|
*/
|
|
69
|
-
export declare function callGraph<T>(config: GraphClientConfig, invoke: (client: Client) => Promise<T>, opts?:
|
|
70
|
-
tool: string;
|
|
71
|
-
}): Promise<T>;
|
|
106
|
+
export declare function callGraph<T>(config: GraphClientConfig, invoke: (client: Client) => Promise<T>, opts?: GraphCallOpts): Promise<T>;
|
|
72
107
|
/** A single page of a Graph collection: the items plus the opaque continuation. */
|
|
73
108
|
export interface GraphPage<T> {
|
|
74
109
|
value: T[];
|
|
@@ -85,27 +120,19 @@ export interface GraphPage<T> {
|
|
|
85
120
|
*/
|
|
86
121
|
export declare function callGraphPaged<T>(config: GraphClientConfig, buildRequest: (client: Client) => {
|
|
87
122
|
get(): Promise<unknown>;
|
|
88
|
-
}, opts?:
|
|
89
|
-
tool: string;
|
|
90
|
-
}): Promise<GraphPage<T>>;
|
|
123
|
+
}, opts?: GraphCallOpts): Promise<GraphPage<T>>;
|
|
91
124
|
/** POST to Graph. See `writeGraph`. */
|
|
92
|
-
export declare function postGraph(config: GraphClientConfig, path: string, body: object | undefined, opts?: {
|
|
93
|
-
tool: string;
|
|
94
|
-
}): Promise<{
|
|
125
|
+
export declare function postGraph(config: GraphClientConfig, path: string, body: object | undefined, opts?: GraphCallOpts): Promise<{
|
|
95
126
|
status: number;
|
|
96
127
|
json: unknown | null;
|
|
97
128
|
}>;
|
|
98
129
|
/** PATCH to Graph. See `writeGraph`. */
|
|
99
|
-
export declare function patchGraph(config: GraphClientConfig, path: string, body: object | undefined, opts?: {
|
|
100
|
-
tool: string;
|
|
101
|
-
}): Promise<{
|
|
130
|
+
export declare function patchGraph(config: GraphClientConfig, path: string, body: object | undefined, opts?: GraphCallOpts): Promise<{
|
|
102
131
|
status: number;
|
|
103
132
|
json: unknown | null;
|
|
104
133
|
}>;
|
|
105
134
|
/** DELETE to Graph. See `writeGraph`. */
|
|
106
|
-
export declare function deleteGraph(config: GraphClientConfig, path: string, opts?: {
|
|
107
|
-
tool: string;
|
|
108
|
-
}): Promise<{
|
|
135
|
+
export declare function deleteGraph(config: GraphClientConfig, path: string, opts?: GraphCallOpts): Promise<{
|
|
109
136
|
status: number;
|
|
110
137
|
json: unknown | null;
|
|
111
138
|
}>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"graph-client.d.ts","sourceRoot":"","sources":["../../src/lib/graph-client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,MAAM,EAAgB,MAAM,mCAAmC,CAAC;AAEzE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAGzD,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,UAAU,CAAC;CACxB;AAED,MAAM,MAAM,eAAe,GACvB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAClD;IAAE,IAAI,EAAE,wBAAwB,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE,GAC5E;IAAE,IAAI,EAAE,qBAAqB,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,GACnD;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAA;CAAE,GAC5D;IAAE,IAAI,EAAE,oBAAoB,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAChE;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GACjD;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAqDxD,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,OAAO,GAAG,eAAe,CA8BhE;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,iBAAiB,GAAG,MAAM,
|
|
1
|
+
{"version":3,"file":"graph-client.d.ts","sourceRoot":"","sources":["../../src/lib/graph-client.ts"],"names":[],"mappings":"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"}
|
|
@@ -78,6 +78,29 @@ export function classifyGraphError(err) {
|
|
|
78
78
|
}
|
|
79
79
|
return { kind: "other", statusCode, code: code || "Unknown" };
|
|
80
80
|
}
|
|
81
|
+
/**
|
|
82
|
+
* Opt into Graph immutable item ids.
|
|
83
|
+
*
|
|
84
|
+
* Outlook item ids are NOT stable: Graph rotates an item's id when the item is
|
|
85
|
+
* moved or re-saved — including by the operator's own mail client. That is what
|
|
86
|
+
* killed a live draft handle in Task 1688: our PATCH returned 200, and four
|
|
87
|
+
* minutes later the same id returned 404 ErrorItemNotFound, so the agent
|
|
88
|
+
* concluded the draft had vanished and rebuilt it. An immutable id survives that
|
|
89
|
+
* event; a regular id does not.
|
|
90
|
+
*
|
|
91
|
+
* This rides on the one client factory because it is plugin-wide by necessity,
|
|
92
|
+
* not preference: the immutable and default namespaces carry DIFFERENT values
|
|
93
|
+
* for the same item, so a per-tool opt-in would have one tool emitting ids that
|
|
94
|
+
* another cannot address. Never set this per-tool.
|
|
95
|
+
*
|
|
96
|
+
* TRAP for whoever adds the next header: `Prefer` is also how Graph carries
|
|
97
|
+
* `outlook.timezone` and `odata.maxpagesize`. A per-request
|
|
98
|
+
* `.header("Prefer", ...)` REPLACES this one for that call, silently dropping
|
|
99
|
+
* that tool back to regular ids — the mixed-namespace break, invisible until an
|
|
100
|
+
* id passed between tools 404s. `Prefer` is a comma-separated multi-value
|
|
101
|
+
* header: append to this value, never set `Prefer` on its own.
|
|
102
|
+
*/
|
|
103
|
+
export const IMMUTABLE_ID_PREFER = 'IdType="ImmutableId"';
|
|
81
104
|
/**
|
|
82
105
|
* Factory wrapping `Client.init`. Exported as a named function so tests can
|
|
83
106
|
* `vi.spyOn(graphClient, "createGraphClient")` reliably (avoids the
|
|
@@ -85,6 +108,10 @@ export function classifyGraphError(err) {
|
|
|
85
108
|
*/
|
|
86
109
|
export function createGraphClient(config) {
|
|
87
110
|
return Client.init({
|
|
111
|
+
// Applies to every request, and is merged with — not displaced by — a
|
|
112
|
+
// per-request `.header()` (mail-search's ConsistencyLevel). Both properties
|
|
113
|
+
// are asserted in graph-client.test.ts and were verified against live Graph.
|
|
114
|
+
fetchOptions: { headers: { Prefer: IMMUTABLE_ID_PREFER } },
|
|
88
115
|
authProvider: async (done) => {
|
|
89
116
|
try {
|
|
90
117
|
const token = await getOrRefreshAccessToken(config);
|
|
@@ -163,6 +190,7 @@ export async function callGraph(config, invoke, opts = { tool: "graph" }) {
|
|
|
163
190
|
account: config.accountId,
|
|
164
191
|
status: cls.statusCode,
|
|
165
192
|
code: "code" in cls ? cls.code : "",
|
|
193
|
+
target: opts.target ?? "",
|
|
166
194
|
retryAfterMs: cls.kind === "rate-limit-recoverable" ? cls.retryAfterMs : null,
|
|
167
195
|
});
|
|
168
196
|
if (cls.kind === "on-prem") {
|
|
@@ -275,7 +303,10 @@ async function writeGraph(config, method, path, body, opts) {
|
|
|
275
303
|
json = await raw.json().catch(() => null);
|
|
276
304
|
}
|
|
277
305
|
return { status: raw.status, json };
|
|
278
|
-
},
|
|
306
|
+
},
|
|
307
|
+
// Every write already knows its path, so a failing write names its target
|
|
308
|
+
// without the caller threading one through.
|
|
309
|
+
{ tool: opts.tool, target: opts.target ?? path });
|
|
279
310
|
}
|
|
280
311
|
/** POST to Graph. See `writeGraph`. */
|
|
281
312
|
export async function postGraph(config, path, body, opts = { tool: "graph" }) {
|