@rubytech/create-maxy-code 0.1.499 → 0.1.500

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 (57) hide show
  1. package/package.json +1 -1
  2. package/payload/platform/plugins/admin/PLUGIN.md +1 -1
  3. package/payload/platform/plugins/admin/hooks/__tests__/admin-authoring-observer.test.sh +135 -0
  4. package/payload/platform/plugins/admin/hooks/__tests__/prompt-optimiser-directive.test.sh +11 -2
  5. package/payload/platform/plugins/admin/hooks/prompt-optimiser-directive.sh +6 -3
  6. package/payload/platform/plugins/admin/mcp/dist/index.js +52 -107
  7. package/payload/platform/plugins/admin/mcp/dist/index.js.map +1 -1
  8. package/payload/platform/plugins/admin/mcp/dist/skill-resolution.d.ts +8 -4
  9. package/payload/platform/plugins/admin/mcp/dist/skill-resolution.d.ts.map +1 -1
  10. package/payload/platform/plugins/admin/mcp/dist/skill-resolution.js +67 -50
  11. package/payload/platform/plugins/admin/mcp/dist/skill-resolution.js.map +1 -1
  12. package/payload/platform/plugins/admin/mcp/dist/store-skill-core.d.ts +33 -0
  13. package/payload/platform/plugins/admin/mcp/dist/store-skill-core.d.ts.map +1 -0
  14. package/payload/platform/plugins/admin/mcp/dist/store-skill-core.js +122 -0
  15. package/payload/platform/plugins/admin/mcp/dist/store-skill-core.js.map +1 -0
  16. package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +9 -3
  17. package/payload/platform/plugins/contacts/.claude-plugin/plugin.json +1 -1
  18. package/payload/platform/plugins/contacts/PLUGIN.md +1 -1
  19. package/payload/platform/plugins/contacts/mcp/dist/index.js +3 -12
  20. package/payload/platform/plugins/contacts/mcp/dist/index.js.map +1 -1
  21. package/payload/platform/plugins/contacts/mcp/dist/tools/__tests__/contact-lookup.test.js +105 -2
  22. package/payload/platform/plugins/contacts/mcp/dist/tools/__tests__/contact-lookup.test.js.map +1 -1
  23. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-lookup.d.ts +11 -1
  24. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-lookup.d.ts.map +1 -1
  25. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-lookup.js +56 -4
  26. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-lookup.js.map +1 -1
  27. package/payload/platform/plugins/docs/references/contacts-guide.md +2 -0
  28. package/payload/platform/plugins/docs/references/outlook-guide.md +4 -0
  29. package/payload/platform/plugins/docs/references/plugins-guide.md +2 -2
  30. package/payload/platform/plugins/email/skills/email-composition/SKILL.md +9 -1
  31. package/payload/platform/plugins/outlook/skills/outlook/SKILL.md +6 -0
  32. package/payload/platform/plugins/prompt-optimiser/skills/prompt-optimiser/SKILL.md +1 -1
  33. package/payload/platform/plugins/prompt-optimiser/skills/prompt-optimiser/__tests__/restatement-ladder.test.sh +2 -1
  34. package/payload/platform/services/claude-session-manager/dist/index.js +26 -1
  35. package/payload/platform/services/claude-session-manager/dist/index.js.map +1 -1
  36. package/payload/platform/services/claude-session-manager/dist/skill-registration-drift.d.ts +15 -0
  37. package/payload/platform/services/claude-session-manager/dist/skill-registration-drift.d.ts.map +1 -1
  38. package/payload/platform/services/claude-session-manager/dist/skill-registration-drift.js +61 -0
  39. package/payload/platform/services/claude-session-manager/dist/skill-registration-drift.js.map +1 -1
  40. package/payload/platform/templates/agents/admin/IDENTITY.md +1 -1
  41. package/payload/server/public/assets/{AdminLoginScreens-Ce5aBBWF.js → AdminLoginScreens-CKg_Dlpv.js} +1 -1
  42. package/payload/server/public/assets/{admin-BWb8me-E.js → admin-wtSUU5N0.js} +1 -1
  43. package/payload/server/public/assets/chat-DRuIxGtE.js +1 -0
  44. package/payload/server/public/assets/data-BdZCvyvN.js +1 -0
  45. package/payload/server/public/assets/{operator-DCNWUKP_.js → operator-DglDYmdV.js} +1 -1
  46. package/payload/server/public/assets/{page-C4TrTU6r.js → page-C-5sTAE-.js} +2 -2
  47. package/payload/server/public/assets/{page-DUgrGrJp.js → page-C8G5p4jr.js} +1 -1
  48. package/payload/server/public/assets/{public-DXJXQPwe.js → public-DqJjXR4p.js} +1 -1
  49. package/payload/server/public/assets/{useVoiceRecorder-Cu6zvuFx.js → useVoiceRecorder-Cj-VZDKO.js} +1 -1
  50. package/payload/server/public/chat.html +3 -3
  51. package/payload/server/public/data.html +3 -3
  52. package/payload/server/public/index.html +4 -4
  53. package/payload/server/public/operator.html +4 -4
  54. package/payload/server/public/public.html +3 -3
  55. package/payload/server/server.js +42 -6
  56. package/payload/server/public/assets/chat-MvnxPJZ5.js +0 -1
  57. package/payload/server/public/assets/data-BjqC4hyM.js +0 -1
@@ -19,11 +19,17 @@ vi.mock("../../../../../../lib/graph-trash/dist/index.js", () => ({
19
19
  vi.mock("neo4j-driver", () => ({
20
20
  default: { int: (v) => v },
21
21
  }));
22
- import { contactLookup, normalizeNameQuery } from "../contact-lookup.js";
22
+ import { contactLookup, normalizeNameQuery, formatContactResults } from "../contact-lookup.js";
23
23
  const ACCOUNT_ID = "20028fb5-db26-4de7-bff6-c5ec2d4fc766";
24
24
  function personRecord(props, nodeId = "4:person:9") {
25
25
  return {
26
- get: (k) => (k === "nodeId" ? nodeId : { properties: props }),
26
+ get: (k) => {
27
+ if (k === "nodeId")
28
+ return nodeId;
29
+ if (k === "prefs")
30
+ return [];
31
+ return { properties: props };
32
+ },
27
33
  };
28
34
  }
29
35
  beforeEach(() => {
@@ -68,4 +74,101 @@ describe("contact-lookup — composed full-name match (Task 985)", () => {
68
74
  expect(params.name).toBe("Jane Smith");
69
75
  });
70
76
  });
77
+ function personWithPrefsRecord(props, prefs, nodeId = "4:person:9") {
78
+ return {
79
+ get: (k) => {
80
+ if (k === "nodeId")
81
+ return nodeId;
82
+ if (k === "prefs")
83
+ return prefs.map((p) => ({ properties: p }));
84
+ return { properties: props };
85
+ },
86
+ };
87
+ }
88
+ describe("contact-lookup — preference interrogation (Task 1953)", () => {
89
+ it("query carries the HAS_PREFERENCE traversal and the applicability filter", async () => {
90
+ mockSession.run.mockResolvedValueOnce({ records: [] });
91
+ await contactLookup({ email: "dale@acme.com", accountId: ACCOUNT_ID });
92
+ const [cypher] = mockSession.run.mock.calls[0];
93
+ expect(cypher).toContain("OPTIONAL MATCH (p)-[:HAS_PREFERENCE]->(pref:Preference)");
94
+ expect(cypher).toContain("coalesce(pref.notApplicable, false) = false");
95
+ expect(cypher).toContain("collect(pref) AS prefs");
96
+ });
97
+ it("returns the full property bag and the mapped preferences for a resolved contact", async () => {
98
+ mockSession.run.mockResolvedValueOnce({
99
+ records: [
100
+ personWithPrefsRecord({ givenName: "Ann", familyName: "Woolgrove", email: "ann@acme.com", jobTitle: "Buyer", alwaysCc: "dale@acme.com", source: "manual", status: "active", createdOn: "2026-01-01" }, [{ category: "communication", key: "cc_dale", value: "always CC Dale", confidence: 0.9, observedAt: "2026-02-01" }]),
101
+ ],
102
+ });
103
+ const hits = await contactLookup({ email: "ann@acme.com", accountId: ACCOUNT_ID });
104
+ expect(hits).toHaveLength(1);
105
+ expect(hits[0].properties.alwaysCc).toBe("dale@acme.com");
106
+ expect(hits[0].properties.jobTitle).toBe("Buyer");
107
+ expect(hits[0].preferences).toHaveLength(1);
108
+ expect(hits[0].preferences[0]).toEqual({
109
+ category: "communication",
110
+ key: "cc_dale",
111
+ value: "always CC Dale",
112
+ confidence: 0.9,
113
+ observedAt: "2026-02-01",
114
+ });
115
+ });
116
+ it("returns an empty preferences array when the contact has none", async () => {
117
+ mockSession.run.mockResolvedValueOnce({
118
+ records: [
119
+ personWithPrefsRecord({ givenName: "Ben", familyName: "Terry", email: "ben@acme.com", source: "manual", status: "active", createdOn: "2026-01-02" }, []),
120
+ ],
121
+ });
122
+ const hits = await contactLookup({ email: "ben@acme.com", accountId: ACCOUNT_ID });
123
+ expect(hits[0].preferences).toEqual([]);
124
+ expect(hits[0].properties.givenName).toBe("Ben");
125
+ });
126
+ });
127
+ describe("formatContactResults — surfaces open-by-default properties (Task 1953 review finding)", () => {
128
+ function baseContact(overrides = {}) {
129
+ return {
130
+ nodeId: "4:person:9",
131
+ givenName: "Ann",
132
+ familyName: "Woolgrove",
133
+ email: "ann@acme.com",
134
+ source: "manual",
135
+ status: "active",
136
+ createdOn: "2026-01-01",
137
+ properties: {},
138
+ preferences: [],
139
+ ...overrides,
140
+ };
141
+ }
142
+ it("renders a Properties block containing a routing property like alwaysCc", () => {
143
+ const text = formatContactResults([
144
+ baseContact({ properties: { alwaysCc: "dale@acme.com" } }),
145
+ ]);
146
+ expect(text).toContain("Properties:");
147
+ expect(text).toContain("- alwaysCc: dale@acme.com");
148
+ });
149
+ it("does not repeat accountId even when present in properties", () => {
150
+ const text = formatContactResults([
151
+ baseContact({
152
+ properties: { alwaysCc: "dale@acme.com", accountId: "20028fb5-db26-4de7-bff6-c5ec2d4fc766" },
153
+ }),
154
+ ]);
155
+ expect(text).not.toContain("accountId");
156
+ });
157
+ it("still renders Preferences and jobTitle alongside Properties (regression)", () => {
158
+ const text = formatContactResults([
159
+ baseContact({
160
+ jobTitle: "Buyer",
161
+ properties: { alwaysCc: "dale@acme.com" },
162
+ preferences: [
163
+ { category: "communication", key: "cc_dale", value: "always CC Dale", confidence: 0.9, observedAt: "2026-02-01" },
164
+ ],
165
+ }),
166
+ ]);
167
+ expect(text).toContain("jobTitle: Buyer");
168
+ expect(text).toContain("Preferences:");
169
+ expect(text).toContain("- always CC Dale");
170
+ expect(text).toContain("Properties:");
171
+ expect(text).toContain("- alwaysCc: dale@acme.com");
172
+ });
173
+ });
71
174
  //# sourceMappingURL=contact-lookup.test.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"contact-lookup.test.js","sourceRoot":"","sources":["../../../src/tools/__tests__/contact-lookup.test.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"contact-lookup.test.js","sourceRoot":"","sources":["../../../src/tools/__tests__/contact-lookup.test.ts"],"names":[],"mappings":"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"}
@@ -6,7 +6,14 @@ interface ContactLookupParams {
6
6
  limit?: number;
7
7
  }
8
8
  export declare function normalizeNameQuery(name: string): string;
9
- interface ContactRecord {
9
+ interface PreferenceRecord {
10
+ category: string;
11
+ key: string;
12
+ value: string;
13
+ confidence: number | null;
14
+ observedAt: string | null;
15
+ }
16
+ export interface ContactRecord {
10
17
  nodeId: string;
11
18
  givenName: string;
12
19
  familyName?: string;
@@ -16,7 +23,10 @@ interface ContactRecord {
16
23
  source: string;
17
24
  status: string;
18
25
  createdOn: string;
26
+ properties: Record<string, unknown>;
27
+ preferences: PreferenceRecord[];
19
28
  }
20
29
  export declare function contactLookup(params: ContactLookupParams): Promise<ContactRecord[]>;
30
+ export declare function formatContactResults(results: ContactRecord[]): string;
21
31
  export {};
22
32
  //# sourceMappingURL=contact-lookup.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"contact-lookup.d.ts","sourceRoot":"","sources":["../../src/tools/contact-lookup.ts"],"names":[],"mappings":"AAIA,UAAU,mBAAmB;IAC3B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAOD,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEvD;AAED,UAAU,aAAa;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,wBAAsB,aAAa,CACjC,MAAM,EAAE,mBAAmB,GAC1B,OAAO,CAAC,aAAa,EAAE,CAAC,CAiE1B"}
1
+ {"version":3,"file":"contact-lookup.d.ts","sourceRoot":"","sources":["../../src/tools/contact-lookup.ts"],"names":[],"mappings":"AAIA,UAAU,mBAAmB;IAC3B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAOD,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEvD;AAED,UAAU,gBAAgB;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAED,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACpC,WAAW,EAAE,gBAAgB,EAAE,CAAC;CACjC;AAED,wBAAsB,aAAa,CACjC,MAAM,EAAE,mBAAmB,GAC1B,OAAO,CAAC,aAAa,EAAE,CAAC,CAwF1B;AAUD,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,aAAa,EAAE,GAAG,MAAM,CA0BrE"}
@@ -16,12 +16,16 @@ export async function contactLookup(params) {
16
16
  let query;
17
17
  let queryParams;
18
18
  const trashFilter = notTrashed("p");
19
+ const prefFilter = `${notTrashed("pref")} AND coalesce(pref.notApplicable, false) = false`;
19
20
  if (email) {
20
21
  query = `
21
22
  MATCH (p:Person {accountId: $accountId})
22
23
  WHERE p.email = $email AND ${trashFilter}
23
- RETURN p, elementId(p) AS nodeId
24
+ WITH p, elementId(p) AS nodeId
24
25
  LIMIT $limit
26
+ OPTIONAL MATCH (p)-[:HAS_PREFERENCE]->(pref:Preference)
27
+ WHERE ${prefFilter}
28
+ RETURN p, nodeId, collect(pref) AS prefs
25
29
  `;
26
30
  queryParams = { email: email.toLowerCase(), accountId, limit: neo4j.int(limit) };
27
31
  }
@@ -29,8 +33,11 @@ export async function contactLookup(params) {
29
33
  query = `
30
34
  MATCH (p:Person {accountId: $accountId})
31
35
  WHERE p.telephone = $telephone AND ${trashFilter}
32
- RETURN p, elementId(p) AS nodeId
36
+ WITH p, elementId(p) AS nodeId
33
37
  LIMIT $limit
38
+ OPTIONAL MATCH (p)-[:HAS_PREFERENCE]->(pref:Preference)
39
+ WHERE ${prefFilter}
40
+ RETURN p, nodeId, collect(pref) AS prefs
34
41
  `;
35
42
  queryParams = { telephone: telephone.trim(), accountId, limit: neo4j.int(limit) };
36
43
  }
@@ -39,8 +46,11 @@ export async function contactLookup(params) {
39
46
  MATCH (p:Person {accountId: $accountId})
40
47
  WHERE toLower(trim(coalesce(p.givenName, '') + ' ' + coalesce(p.familyName, ''))) CONTAINS toLower($name)
41
48
  AND ${trashFilter}
42
- RETURN p, elementId(p) AS nodeId
49
+ WITH p, elementId(p) AS nodeId
43
50
  LIMIT $limit
51
+ OPTIONAL MATCH (p)-[:HAS_PREFERENCE]->(pref:Preference)
52
+ WHERE ${prefFilter}
53
+ RETURN p, nodeId, collect(pref) AS prefs
44
54
  `;
45
55
  queryParams = { name: normalizeNameQuery(name), accountId, limit: neo4j.int(limit) };
46
56
  }
@@ -48,15 +58,26 @@ export async function contactLookup(params) {
48
58
  query = `
49
59
  MATCH (p:Person {accountId: $accountId})
50
60
  WHERE ${trashFilter}
51
- RETURN p, elementId(p) AS nodeId
61
+ WITH p, elementId(p) AS nodeId
52
62
  ORDER BY p.createdOn DESC
53
63
  LIMIT $limit
64
+ OPTIONAL MATCH (p)-[:HAS_PREFERENCE]->(pref:Preference)
65
+ WHERE ${prefFilter}
66
+ RETURN p, nodeId, collect(pref) AS prefs
54
67
  `;
55
68
  queryParams = { accountId, limit: neo4j.int(limit) };
56
69
  }
57
70
  const result = await session.run(query, queryParams);
58
71
  return result.records.map((record) => {
59
72
  const p = record.get("p").properties;
73
+ const prefNodes = record.get("prefs") ?? [];
74
+ const preferences = prefNodes.map((n) => ({
75
+ category: n.properties.category,
76
+ key: n.properties.key,
77
+ value: n.properties.value,
78
+ confidence: n.properties.confidence ?? null,
79
+ observedAt: n.properties.observedAt ?? null,
80
+ }));
60
81
  return {
61
82
  nodeId: record.get("nodeId"),
62
83
  givenName: p.givenName,
@@ -67,6 +88,8 @@ export async function contactLookup(params) {
67
88
  source: p.source,
68
89
  status: p.status,
69
90
  createdOn: p.createdOn,
91
+ properties: p,
92
+ preferences,
70
93
  };
71
94
  });
72
95
  }
@@ -74,4 +97,33 @@ export async function contactLookup(params) {
74
97
  await session.close();
75
98
  }
76
99
  }
100
+ // Keys already shown in the head/jobTitle lines, or internal, so they are not
101
+ // repeated in the Properties block. accountId is account-scoping; embedding-like
102
+ // and structured values are skipped by the scalar check below.
103
+ const IDENTITY_AND_INTERNAL_KEYS = new Set([
104
+ "givenName", "familyName", "email", "telephone", "jobTitle",
105
+ "source", "status", "createdOn", "accountId",
106
+ ]);
107
+ export function formatContactResults(results) {
108
+ return results
109
+ .map((c) => {
110
+ const identifierPart = c.email
111
+ ? `<${c.email}>`
112
+ : c.telephone
113
+ ? `<${c.telephone}>`
114
+ : "";
115
+ const head = `${c.givenName}${c.familyName ? ` ${c.familyName}` : ""} ${identifierPart} — ${c.status} (from ${c.source}, ${c.createdOn})`;
116
+ const jobLine = c.jobTitle ? `\n jobTitle: ${c.jobTitle}` : "";
117
+ const propEntries = Object.entries(c.properties).filter(([k, v]) => !IDENTITY_AND_INTERNAL_KEYS.has(k) &&
118
+ (typeof v === "string" || typeof v === "number" || typeof v === "boolean"));
119
+ const propLines = propEntries.length > 0
120
+ ? `\n Properties:\n${propEntries.map(([k, v]) => ` - ${k}: ${v}`).join("\n")}`
121
+ : "";
122
+ const prefLines = c.preferences.length > 0
123
+ ? `\n Preferences:\n${c.preferences.map((p) => ` - ${p.value}`).join("\n")}`
124
+ : "";
125
+ return `${head}${jobLine}${propLines}${prefLines}`;
126
+ })
127
+ .join("\n");
128
+ }
77
129
  //# sourceMappingURL=contact-lookup.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"contact-lookup.js","sourceRoot":"","sources":["../../src/tools/contact-lookup.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,cAAc,CAAC;AACjC,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,UAAU,EAAE,MAAM,8CAA8C,CAAC;AAU1E,yEAAyE;AACzE,gFAAgF;AAChF,0EAA0E;AAC1E,8EAA8E;AAC9E,gFAAgF;AAChF,MAAM,UAAU,kBAAkB,CAAC,IAAY;IAC7C,OAAO,IAAI,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;AAC1C,CAAC;AAcD,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,MAA2B;IAE3B,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,GAAG,EAAE,EAAE,GAAG,MAAM,CAAC;IAEjE,MAAM,OAAO,GAAG,UAAU,EAAE,CAAC;IAE7B,IAAI,CAAC;QACH,IAAI,KAAa,CAAC;QAClB,IAAI,WAAoC,CAAC;QAEzC,MAAM,WAAW,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC;QACpC,IAAI,KAAK,EAAE,CAAC;YACV,KAAK,GAAG;;qCAEuB,WAAW;;;OAGzC,CAAC;YACF,WAAW,GAAG,EAAE,KAAK,EAAE,KAAK,CAAC,WAAW,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;QACnF,CAAC;aAAM,IAAI,SAAS,EAAE,CAAC;YACrB,KAAK,GAAG;;6CAE+B,WAAW;;;OAGjD,CAAC;YACF,WAAW,GAAG,EAAE,SAAS,EAAE,SAAS,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;QACpF,CAAC;aAAM,IAAI,IAAI,EAAE,CAAC;YAChB,KAAK,GAAG;;;iBAGG,WAAW;;;OAGrB,CAAC;YACF,WAAW,GAAG,EAAE,IAAI,EAAE,kBAAkB,CAAC,IAAI,CAAC,EAAE,SAAS,EAAE,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;QACvF,CAAC;aAAM,CAAC;YACN,KAAK,GAAG;;gBAEE,WAAW;;;;OAIpB,CAAC;YACF,WAAW,GAAG,EAAE,SAAS,EAAE,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;QACvD,CAAC;QAED,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,WAAW,CAAC,CAAC;QAErD,OAAO,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE;YACnC,MAAM,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,UAAU,CAAC;YACrC,OAAO;gBACL,MAAM,EAAE,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAW;gBACtC,SAAS,EAAE,CAAC,CAAC,SAAmB;gBAChC,UAAU,EAAE,CAAC,CAAC,UAAgC;gBAC9C,KAAK,EAAE,CAAC,CAAC,KAA2B;gBACpC,SAAS,EAAE,CAAC,CAAC,SAA+B;gBAC5C,QAAQ,EAAE,CAAC,CAAC,QAA8B;gBAC1C,MAAM,EAAE,CAAC,CAAC,MAAgB;gBAC1B,MAAM,EAAE,CAAC,CAAC,MAAgB;gBAC1B,SAAS,EAAE,CAAC,CAAC,SAAmB;aACjC,CAAC;QACJ,CAAC,CAAC,CAAC;IACL,CAAC;YAAS,CAAC;QACT,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC;IACxB,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"contact-lookup.js","sourceRoot":"","sources":["../../src/tools/contact-lookup.ts"],"names":[],"mappings":"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"}
@@ -27,6 +27,8 @@ Ask naturally:
27
27
 
28
28
  {{productName}} searches by name, email, phone number, or any detail you provide.
29
29
 
30
+ When {{productName}} drafts an email to a contact, it reads that contact's record first. If the record carries routing you have set (for example an address to always copy, or a preferred send-to address), {{productName}} applies it to the draft and shows it to you for approval before anything is sent.
31
+
30
32
  ## Updating a Contact
31
33
 
32
34
  Tell {{productName}} what changed:
@@ -39,6 +39,10 @@ The `outlook` plugin gives the admin agent access to Microsoft 365 / Outlook.com
39
39
  | `outlook-draft-edit` | Update an existing draft in place (`PATCH /me/messages/{draftId}`) and/or add and remove attachments. Graph drafts are mutable — pass draftId plus the fields to change (to/cc/bcc/subject/body/isHtml/attachments/removeAttachments). `attachments` adds; `removeAttachments` takes files off by name or attachment id; both together replace a file in one call. Sent messages cannot be edited. Needs `Mail.ReadWrite`. |
40
40
  | `outlook-draft-send` | Send an existing draft by id (`POST /me/messages/{id}/send`). Graph consumes the draft, returns 202. Needs `Mail.Send`. |
41
41
 
42
+ ## Drafting to a Contact
43
+
44
+ When the admin agent drafts or sends a message to a known contact, it reads that contact's record first and applies any routing the record carries, for example a preferred send-to address, showing the result for your approval before anything is sent.
45
+
42
46
  ## Observability
43
47
 
44
48
  All log lines start with `[outlook-mcp]` and write to `server.log`. They are key=value, account-scoped:
@@ -136,9 +136,9 @@ Ask {{productName}}: "What plugins do I have?" or "List my plugins."
136
136
 
137
137
  Skills you create at runtime through the admin `skill-builder` skill are saved on disk as their own plugin under `data/accounts/{accountId}/plugins/{pluginName}/`. The admin agent calls `mcp__plugin_admin_admin__store-skill`, which composes `PLUGIN.md` (on first call) and `skills/{skillName}/SKILL.md` plus any reference files. The agent supplies `pluginName`, `skillName`, `description`, `publicEmbed`, `body`, and optional references — the path is computed by the tool, never by the agent.
138
138
 
139
- These operator-authored plugins survive reinstall because the installer's wipe zone excludes `data/`. At admin session start the platform mirrors `data/accounts/{accountId}/plugins/*` into the runtime plugins directory so the same `parsePluginFrontmatter` / `assemblePublicPluginContent` / `loadEmbeddedPlugins` loaders that read shipped and premium plugins also pick up operator-authored ones no special-case loader path. The admin agent sees every operator skill by default; per-skill `publicEmbed: true|false` controls which skills surface to the public agent.
139
+ These operator-authored plugins survive reinstall because the installer's wipe zone excludes `data/`. They are loaded directly from `data/accounts/{accountId}/plugins/*`: the spawn manifest and the admin skill resolvers (`skill-load`, `skill-find`, `skill-search`, `plugin-read`) walk that per-account root alongside the shipped and premium plugin roots, so no copy into the platform plugins tree is made or needed. `store-skill` confirms the written skill resolves through that same walk before reporting success. Operator plugins are always active for their own account; the admin agent sees every operator skill by default, and per-skill `publicEmbed: true|false` controls which skills surface to the public agent.
140
140
 
141
- To edit an operator-authored skill later, ask {{productName}} to update it — the admin agent re-runs `store-skill` for the same `pluginName`/`skillName` and the new content overwrites in place. To remove one, delete the directory under `data/accounts/{accountId}/plugins/{pluginName}/skills/{skillName}/` (or the whole plugin) — the next session start re-mirrors the remaining skills only.
141
+ To edit an operator-authored skill later, ask {{productName}} to update it — the admin agent re-runs `store-skill` for the same `pluginName`/`skillName` and the new content overwrites in place. To remove one, delete the directory under `data/accounts/{accountId}/plugins/{pluginName}/skills/{skillName}/` (or the whole plugin) — the next session no longer surfaces it, since skills load directly from that path.
142
142
 
143
143
  `pluginName` collisions with shipped plugin names are refused by `store-skill` with a structured error. See [.docs/agents.md](../../../../.docs/agents.md) § "Operator-authored skills as plugin files" for the full contract.
144
144
 
@@ -71,12 +71,18 @@ Compose the reply. Anchor to:
71
71
 
72
72
  Keep drafts tight. Email is not a wall-of-text medium.
73
73
 
74
+ ### 5b. Resolve the recipient and read their contact record
75
+
76
+ Before presenting any draft (reply or new outbound), resolve each named recipient to their `:Person` with `contact-lookup` (by email when you have it, else by name). When the contact resolves, read its properties and preferences: if the record carries routing (an always-CC, an alternate send-to address), apply it to the draft To/Cc and surface it in the presentation for the operator to approve. An explicit operator instruction on this send always overrides the stored routing. When the contact does not resolve, or carries no routing, leave Cc empty and draft to the operator-named address only. Never invent a recipient, and never create a `:Person` here.
77
+
78
+ Emit narration `[email-composition] contact-lookup recipient=<addr> resolved=<bool> prefsFound=<n> applied=<n>` for each resolved recipient, before the draft is presented.
79
+
74
80
  ### 6. Present
75
81
 
76
82
  Show the draft inline with:
77
83
 
78
84
  - **To** — recipient address (the original sender, unless the operator asked for `replyAll`).
79
- - **Cc / Bcc** — empty unless the operator explicitly named one. Never copy people forward from the original thread without being asked.
85
+ - **Cc / Bcc** — empty unless the operator explicitly named one, or the recipient's contact record carries routing read in step 5b (an always-CC or alternate address), which is applied here and shown for approval. Never copy people forward from the original thread without being asked.
80
86
  - **Subject** — the threaded subject (the original with `Re:` if not already prefixed; `email-reply` handles this).
81
87
  - **Body** — the draft.
82
88
  - **Notes** — any flags from step 3 (tone, attachments, OOO skip-already-applied).
@@ -168,6 +174,7 @@ Delete is move-to-Trash only. There is no permanent/hard delete, no delete-by-cr
168
174
  |-----------|-----------|
169
175
  | Multiple threads match the operator's description | Surface 3-5 candidates and ask. Never guess. |
170
176
  | CC / BCC in the original thread | Reply to sender only by default. Ask explicitly before copying others. |
177
+ | Recipient's contact record carries an always-CC or alternate address | Apply it to To/Cc in the draft and show it for approval. Explicit operator instruction on this send overrides it. |
171
178
  | `replyAll` requested | Pass `replyAll: true` to `email-reply`. Otherwise omit (defaults false). |
172
179
  | Original thread has attachments referenced in the draft | List attachment names; ask whether to forward. |
173
180
  | Inbound is an out-of-office autoreply | Skip. Flag to operator. Do not draft. |
@@ -201,6 +208,7 @@ The skill emits narration at each major step so a session log makes the full dra
201
208
  | Thread locked | `[email-composition] thread-identified messageId=<id> subject="<subject>"` |
202
209
  | Multiple matches | `[email-composition] ambiguous-thread candidates=<n>` |
203
210
  | Draft shown | `[email-composition] draft-presented recipient=<addr> subjectPrefix="<first-60-chars>"` |
211
+ | Contact resolved | `[email-composition] contact-lookup recipient=<addr> resolved=<bool> prefsFound=<n> applied=<n>` |
204
212
  | Send approved | `[email-composition] send-confirmed` |
205
213
  | Draft saved (unsent) | `[email-composition] draft-saved folder=<drafts-folder> uid=<uid>` |
206
214
  | Draft revised | `[email-composition] draft-edited targetUid=<old> newUid=<new>` |
@@ -68,6 +68,12 @@ Writing operator-reviewed messages into the business graph is the `outlook-mail-
68
68
 
69
69
  - `outlook-mail-otp-extract sender=<domain>` — poll the inbox for a verification code from a sender (e.g. `cloudflare.com`). Optional `subjectPattern` (regex) and `timeout` (seconds, default 60). Reads full bodies to extract the code. Used during service authentication.
70
70
 
71
+ ## Resolve the recipient before drafting
72
+
73
+ Before drafting or sending any message, resolve each named recipient to their `:Person` with `contact-lookup` (by email when you have it, else by name). When the contact resolves and its record carries routing (an always-CC or an alternate send-to address), apply it to the draft `to`/`cc` and show it for the operator to approve. An explicit operator instruction on this send always overrides the stored routing. When the contact does not resolve, or carries no routing, draft to the operator-named address only, leave `cc` empty, and never invent a recipient or create a `:Person` here.
74
+
75
+ Emit narration `[outlook] contact-lookup recipient=<addr> resolved=<bool> prefsFound=<n> applied=<n>` for each resolved recipient, before the draft is presented.
76
+
71
77
  ## Sending and drafting
72
78
 
73
79
  - `outlook-mail-send` — send a message. `to` is an array of addresses (at least one); `cc`/`bcc` optional arrays; `subject`; `body`; `isHtml` true for an HTML body, otherwise plain text; `attachments` an array of file paths inside the account directory, up to 25 MB each. A copy is saved to Sent Items. Success is confirmed on Graph's 202 response.
@@ -23,7 +23,7 @@ This skill is invoked two ways, and they want opposite outputs. Decide which one
23
23
  - Do not invent requirements or add scope the user did not ask for. If the prompt was already clear, say so in one line and restate it tightly.
24
24
  - The restated brief is bound by the three doctrines (see `/doctrine`): precise/concise/plain, no speculation, observability-first. If the user's wording carries a speculative claim or skips an observability step the doctrines require, the restatement corrects it rather than echoing the slip.
25
25
  - Reminder — source of knowledge: every factual claim in the restated brief, and in the work you then do from it, traces to one of IDENTITY's four permissible sources (a graph read this session, a tool output returned this session, content the operator pasted into this conversation, or the body of a loaded skill such as platform-architecture). Pretraining recall is not a source. If a fact the brief needs does not trace to one of the four, do not bake it in from memory — read the graph, run the tool, load the skill, or ask the operator first.
26
- - End the restated brief with the three-tier routing ladder, applied to the restated task in order. **(1)** Delegate to the specialist that owns the deliverable's type via the Agent tool: ownership covers origination, not just the final render, and the brief states the outcome plus the binding constraints, never the lines, anchors, or literal text to insert. **(2)** Only if no installed specialist owns it, load a skill from the admin-usable list with skill-load and do the work from this seat. **(3)** Only if neither fits, author it directly freestyle inline authoring is the last resort, not a peer of the two routes above. The live specialist roster and admin-usable list are injected each turn by the standing prompt-optimiser directive; the ranked ladder is the classifier. The closing line is not the trigger; the classifier is.
26
+ - End the restated brief with the four-rung routing ladder, applied to the restated task in order. **(1)** Delegate to the specialist that owns the deliverable's type via the Agent tool: ownership covers origination, not just the final render, and the brief states the outcome plus the binding constraints, never the lines, anchors, or literal text to insert. **(2)** Only if no installed specialist owns it, load a skill from the admin-usable list with skill-load and do the work from this seat. **(3)** Only if neither a specialist nor an admin skill already owns it, decide whether the deliverable recurs: a deliverable the business issues repeatedly, or one the operator gives a fixed template, numbering, naming, or filing rule for, is authored once as its own skill by loading skill-builder with skill-load (or agent-builder for a worker agent), then produced through that skill, not hand-built here. **(4)** Only if the deliverable is a genuine one-off, author it directly: freestyle inline authoring is the last resort, not a peer of the routes above. The live specialist roster and admin-usable list are injected each turn by the standing prompt-optimiser directive; the ranked ladder is the classifier. The closing line is not the trigger; the classifier is.
27
27
 
28
28
  Then do the work from the restated task. Everything below this section is for the other mode and does not apply — stop reading here.
29
29
 
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env bash
2
- # Task 698 — the prompt-optimiser restatement mode must end with the three-tier
2
+ # Task 698 — the prompt-optimiser restatement mode must end with the four-rung
3
3
  # routing ladder, not the lone "Delegate this task to the appropriate specialist."
4
4
  # line it carried before.
5
5
  set -u
@@ -14,6 +14,7 @@ chk "lone delegate line removed" '! grep -q "Delegate this task to the appropr
14
14
  chk "tier 1 delegate to specialist" 'grep -qiE "delegate to (a |the )?specialist" "$SK"'
15
15
  chk "tier 2 admin-usable skill" 'grep -qi "admin-usable" "$SK"'
16
16
  chk "tier 3 freestyle last resort" 'grep -qiE "freestyle|last resort" "$SK"'
17
+ chk "rung: recurring deliverable -> skill-builder" 'grep -qi "skill-builder" "$SK"'
17
18
  chk "restatement section present" 'grep -qi "In-session restatement mode" "$SK"'
18
19
 
19
20
  echo "----"
@@ -50,7 +50,7 @@ import { projectsDirForCwd, sessionsDir, jsonlPathForSessionId, slugForExistingJ
50
50
  import { readSidecar } from './session-sidecar.js';
51
51
  import { runSystemPromptSelfTest, runIdentityDriftAssertion, runIdentityForbiddenTokenCheck } from './system-prompt.js';
52
52
  import { assertSpecialistDrift } from './specialist-drift.js';
53
- import { assertSkillRegistrationDrift, buildSubToMarketplace, readCcRegistrationSurfaces, readAccountEnabledPlugins, } from './skill-registration-drift.js';
53
+ import { assertSkillRegistrationDrift, buildSubToMarketplace, readCcRegistrationSurfaces, readAccountEnabledPlugins, auditAccountPluginSkills, } from './skill-registration-drift.js';
54
54
  import { assertToolRegistrationDrift, readToolPluginManifestStates, } from './tool-registration-drift.js';
55
55
  import { AccountTitleStores } from './account-title-stores.js';
56
56
  import { WaChannelStore } from './wa-channel-store.js';
@@ -463,6 +463,31 @@ async function main() {
463
463
  log(`startup-self-test skill-registration=ok inspected=${drift.inspected}`);
464
464
  }
465
465
  }
466
+ // Task 1962 — boot-time census of operator-authored account plugin skills.
467
+ // These live under <accountDir>/plugins/<plugin>/skills/<slug>/ and are loaded
468
+ // directly from that root (no platform mirror). Emitting one line per account
469
+ // makes the operator-skill population visible at boot — the observability that
470
+ // was missing when a canonical account copy loaded nowhere. A slug owned by
471
+ // more than one of an account's plugins is FATAL-logged (the resolvers surface
472
+ // it as ambiguous). Non-exiting by design, mirroring the gates above.
473
+ {
474
+ const accountsRoot = dirname(config.accountDir);
475
+ for (const row of auditAccountPluginSkills(accountsRoot)) {
476
+ log(`[account-plugin-skills] op=census accountId=${row.accountId} plugins=${row.plugins} skills=${row.skills} collisions=${row.collisions.length}`);
477
+ for (const slug of row.collisions) {
478
+ const line = `[account-plugin-skills] op=census FATAL accountId=${row.accountId} slug=${slug} reason=ambiguous-across-account-plugins`;
479
+ log(line);
480
+ try {
481
+ spawnSync('systemd-cat', ['-t', 'maxy-csm', '-p', 'err'], {
482
+ input: line + '\n',
483
+ stdio: ['pipe', 'ignore', 'ignore'],
484
+ timeout: 1_000,
485
+ });
486
+ }
487
+ catch { /* journald mirror is best-effort */ }
488
+ }
489
+ }
490
+ }
466
491
  // Task 1574 — boot-time tool-availability reconciliation. A tool-bearing
467
492
  // plugin on `mcp-manifest: skip` keeps its PLUGIN.md `mcp:` block (so the
468
493
  // source-level mcp-allowlist-without-server gate passes) while the generator