@rubytech/create-maxy-code 0.1.88 → 0.1.91

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 (130) hide show
  1. package/dist/__tests__/entitlement-flag.test.js +43 -0
  2. package/dist/__tests__/tier-flag.test.js +53 -0
  3. package/dist/index.js +99 -0
  4. package/dist/port-resolution.js +1 -1
  5. package/dist/tier-flag.js +85 -0
  6. package/package.json +1 -1
  7. package/payload/platform/lib/admin-conversation-purge/dist/index.d.ts +54 -0
  8. package/payload/platform/lib/admin-conversation-purge/dist/index.d.ts.map +1 -0
  9. package/payload/platform/lib/admin-conversation-purge/dist/index.js +84 -0
  10. package/payload/platform/lib/admin-conversation-purge/dist/index.js.map +1 -0
  11. package/payload/platform/lib/admin-conversation-purge/src/index.ts +120 -0
  12. package/payload/platform/lib/admin-conversation-purge/tsconfig.json +8 -0
  13. package/payload/platform/lib/graph-trash/dist/index.d.ts +12 -1
  14. package/payload/platform/lib/graph-trash/dist/index.d.ts.map +1 -1
  15. package/payload/platform/lib/graph-trash/dist/index.js +8 -1
  16. package/payload/platform/lib/graph-trash/dist/index.js.map +1 -1
  17. package/payload/platform/lib/graph-trash/src/index.ts +20 -2
  18. package/payload/platform/package.json +2 -2
  19. package/payload/platform/plugins/.claude-plugin/marketplace.json +5 -0
  20. package/payload/platform/plugins/admin/PLUGIN.md +1 -0
  21. package/payload/platform/plugins/admin/hooks/__tests__/askuserquestion-investigate-gate.test.sh +176 -0
  22. package/payload/platform/plugins/admin/hooks/askuserquestion-investigate-gate.sh +257 -0
  23. package/payload/platform/plugins/admin/skills/admin-user-management/SKILL.md +2 -0
  24. package/payload/platform/plugins/docs/references/admin-session.md +5 -1
  25. package/payload/platform/plugins/docs/references/linkedin-extension.md +49 -0
  26. package/payload/platform/plugins/linkedin-extension/.claude-plugin/plugin.json +8 -0
  27. package/payload/platform/plugins/linkedin-extension/PLUGIN.md +58 -0
  28. package/payload/platform/plugins/linkedin-extension/extension/README.md +44 -0
  29. package/payload/platform/plugins/linkedin-extension/extension/__tests__/fixtures/profile.html +34 -0
  30. package/payload/platform/plugins/linkedin-extension/extension/__tests__/fixtures/thread.html +36 -0
  31. package/payload/platform/plugins/linkedin-extension/extension/assets/pill.css +52 -0
  32. package/payload/platform/plugins/linkedin-extension/extension/background/sw.js +60 -0
  33. package/payload/platform/plugins/linkedin-extension/extension/content/extractors.js +127 -0
  34. package/payload/platform/plugins/linkedin-extension/extension/content/profile.js +82 -0
  35. package/payload/platform/plugins/linkedin-extension/extension/content/thread.js +84 -0
  36. package/payload/platform/plugins/linkedin-extension/extension/manifest.json +32 -0
  37. package/payload/platform/plugins/linkedin-extension/extension/options/options.html +33 -0
  38. package/payload/platform/plugins/linkedin-extension/extension/options/options.js +30 -0
  39. package/payload/platform/plugins/linkedin-extension/skills/linkedin-extension/SKILL.md +91 -0
  40. package/payload/platform/plugins/memory/mcp/dist/lib/neo4j.d.ts +14 -0
  41. package/payload/platform/plugins/memory/mcp/dist/lib/neo4j.d.ts.map +1 -1
  42. package/payload/platform/plugins/memory/mcp/dist/lib/neo4j.js +28 -0
  43. package/payload/platform/plugins/memory/mcp/dist/lib/neo4j.js.map +1 -1
  44. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-empty-trash-admin-conversation.test.d.ts +2 -0
  45. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-empty-trash-admin-conversation.test.d.ts.map +1 -0
  46. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-empty-trash-admin-conversation.test.js +168 -0
  47. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-empty-trash-admin-conversation.test.js.map +1 -0
  48. package/payload/platform/plugins/memory/mcp/dist/tools/memory-empty-trash.d.ts +22 -3
  49. package/payload/platform/plugins/memory/mcp/dist/tools/memory-empty-trash.d.ts.map +1 -1
  50. package/payload/platform/plugins/memory/mcp/dist/tools/memory-empty-trash.js +61 -13
  51. package/payload/platform/plugins/memory/mcp/dist/tools/memory-empty-trash.js.map +1 -1
  52. package/payload/platform/scripts/seed-neo4j.sh +12 -0
  53. package/payload/platform/templates/agents/admin/IDENTITY.md +3 -1
  54. package/payload/platform/templates/specialists/agents/database-operator.md +6 -2
  55. package/payload/premium-plugins/real-agent/plugins/brochures/skills/make-brochure/SKILL.md +1 -1
  56. package/payload/server/public/assets/{ChatInput-BzjU6OoA.css → ChatInput-dLWJ0Q8I.css} +1 -1
  57. package/payload/server/public/assets/{Checkbox-BWLS5Isf.js → Checkbox-Bx8mRHQ3.js} +1 -1
  58. package/payload/server/public/assets/{admin-DjU0q63n.js → admin-C57bDUgs.js} +6 -6
  59. package/payload/server/public/assets/{architectureDiagram-Q4EWVU46-lcncWUol.js → architectureDiagram-Q4EWVU46-BEW_bD50.js} +1 -1
  60. package/payload/server/public/assets/{blockDiagram-DXYQGD6D-SqlxCp6m.js → blockDiagram-DXYQGD6D-DFLT98C3.js} +1 -1
  61. package/payload/server/public/assets/{c4Diagram-AHTNJAMY-DWzvXvDc.js → c4Diagram-AHTNJAMY-BemKtROA.js} +1 -1
  62. package/payload/server/public/assets/channel-CfpByn-m.js +1 -0
  63. package/payload/server/public/assets/{chunk-336JU56O-Bz-tPZhS.js → chunk-336JU56O-BL8fGJ7_.js} +2 -2
  64. package/payload/server/public/assets/{chunk-426QAEUC-MYkbqx1u.js → chunk-426QAEUC-BD6by0MP.js} +1 -1
  65. package/payload/server/public/assets/{chunk-4TB4RGXK-DfZRQtaZ.js → chunk-4TB4RGXK-DX-cb6Nd.js} +1 -1
  66. package/payload/server/public/assets/{chunk-5FUZZQ4R-BFMrOiNe.js → chunk-5FUZZQ4R-B2QqtIZS.js} +1 -1
  67. package/payload/server/public/assets/{chunk-5PVQY5BW-64laDovl.js → chunk-5PVQY5BW-CzJWmsmd.js} +1 -1
  68. package/payload/server/public/assets/{chunk-EDXVE4YY-dClAHBNd.js → chunk-EDXVE4YY-DP9vcvEU.js} +1 -1
  69. package/payload/server/public/assets/{chunk-ENJZ2VHE-Bz4gXG1p.js → chunk-ENJZ2VHE-D5xWEjF0.js} +1 -1
  70. package/payload/server/public/assets/{chunk-ICPOFSXX-D5pWrJcN.js → chunk-ICPOFSXX-AKduf9o4.js} +1 -1
  71. package/payload/server/public/assets/{chunk-OYMX7WX6-BCiWtqaK.js → chunk-OYMX7WX6-D6jsVNyQ.js} +1 -1
  72. package/payload/server/public/assets/{chunk-U2HBQHQK-Brql314T.js → chunk-U2HBQHQK-BNAnTYh8.js} +1 -1
  73. package/payload/server/public/assets/{chunk-X2U36JSP-C-79tMym.js → chunk-X2U36JSP-UdQhYDnh.js} +1 -1
  74. package/payload/server/public/assets/{chunk-YZCP3GAM-G4idHEoZ.js → chunk-YZCP3GAM-DSSmSGmS.js} +1 -1
  75. package/payload/server/public/assets/{chunk-ZZ45TVLE-DyHGASIn.js → chunk-ZZ45TVLE-Czm7IDMC.js} +1 -1
  76. package/payload/server/public/assets/classDiagram-6PBFFD2Q-DNc9R1IX.js +1 -0
  77. package/payload/server/public/assets/classDiagram-v2-HSJHXN6E-DM1DQh8N.js +1 -0
  78. package/payload/server/public/assets/clone-BSrNdvLW.js +1 -0
  79. package/payload/server/public/assets/{dagre-KV5264BT--Bba_VRf.js → dagre-KV5264BT-CBuRCJE7.js} +1 -1
  80. package/payload/server/public/assets/{dagre-DfJ8a8tG.js → dagre-hSIMHJcO.js} +1 -1
  81. package/payload/server/public/assets/data-3n20lir5.js +1 -0
  82. package/payload/server/public/assets/{diagram-5BDNPKRD-CfUotMr9.js → diagram-5BDNPKRD-dbMrH0le.js} +1 -1
  83. package/payload/server/public/assets/{diagram-G4DWMVQ6-BWZJChPS.js → diagram-G4DWMVQ6-DBc6hs9a.js} +1 -1
  84. package/payload/server/public/assets/{diagram-MMDJMWI5-BExS6IWb.js → diagram-MMDJMWI5-ubSGybUk.js} +1 -1
  85. package/payload/server/public/assets/{diagram-TYMM5635-DQ4t_wIt.js → diagram-TYMM5635-WxL3cv-9.js} +1 -1
  86. package/payload/server/public/assets/{erDiagram-SMLLAGMA-f1sfB-AQ.js → erDiagram-SMLLAGMA-CrBBOAN8.js} +1 -1
  87. package/payload/server/public/assets/{flowDiagram-DWJPFMVM-CZexxT3v.js → flowDiagram-DWJPFMVM-CXUq5Hzt.js} +1 -1
  88. package/payload/server/public/assets/{ganttDiagram-T4ZO3ILL-BNcFUX4K.js → ganttDiagram-T4ZO3ILL-vTCBnYVS.js} +1 -1
  89. package/payload/server/public/assets/{gitGraphDiagram-UUTBAWPF-soyylUaR.js → gitGraphDiagram-UUTBAWPF-BHrj_bRa.js} +1 -1
  90. package/payload/server/public/assets/graph-ChwMpxn_.js +1 -0
  91. package/payload/server/public/assets/graph-labels-CUgJgIwl.js +1 -0
  92. package/payload/server/public/assets/{graphlib-B2ezMQY4.js → graphlib-CDO75l2X.js} +1 -1
  93. package/payload/server/public/assets/{infoDiagram-42DDH7IO-4xmenbOP.js → infoDiagram-42DDH7IO-CcQdaOhi.js} +1 -1
  94. package/payload/server/public/assets/{ishikawaDiagram-UXIWVN3A-C_D6CqVq.js → ishikawaDiagram-UXIWVN3A-3KvltFgV.js} +1 -1
  95. package/payload/server/public/assets/{journeyDiagram-VCZTEJTY-DUDx0c35.js → journeyDiagram-VCZTEJTY-DZCXaGFE.js} +1 -1
  96. package/payload/server/public/assets/{kanban-definition-6JOO6SKY-BNr7ErhZ.js → kanban-definition-6JOO6SKY-DAIAKy1-.js} +1 -1
  97. package/payload/server/public/assets/{lib-iKom7FFe.js → lib-CCLURO3E.js} +1 -1
  98. package/payload/server/public/assets/{line-Dvs5wrj5.js → line-C8iCE2Wx.js} +1 -1
  99. package/payload/server/public/assets/{mermaid-parser.core-D0ksZQlW.js → mermaid-parser.core-ElvUhnV9.js} +1 -1
  100. package/payload/server/public/assets/{mermaid.core--Btu4mYy.js → mermaid.core-7dSH31Ao.js} +3 -3
  101. package/payload/server/public/assets/{mindmap-definition-QFDTVHPH-DfyZTghB.js → mindmap-definition-QFDTVHPH-FZ0rcfac.js} +1 -1
  102. package/payload/server/public/assets/{page-BnBmdRbo.js → page-D3bFmqF4.js} +1 -1
  103. package/payload/server/public/assets/{page-x6ZXWwMx.js → page-DNjUtT5q.js} +18 -17
  104. package/payload/server/public/assets/{pieDiagram-DEJITSTG-BiG9YoLj.js → pieDiagram-DEJITSTG-CDuvMZbO.js} +1 -1
  105. package/payload/server/public/assets/{public-DW7yINk7.js → public-g_2skbnY.js} +3 -3
  106. package/payload/server/public/assets/{quadrantDiagram-34T5L4WZ-CVufUsie.js → quadrantDiagram-34T5L4WZ-xDjhkqNc.js} +1 -1
  107. package/payload/server/public/assets/{requirementDiagram-MS252O5E-UKCptCPk.js → requirementDiagram-MS252O5E-BPKpyT0n.js} +1 -1
  108. package/payload/server/public/assets/{sankeyDiagram-XADWPNL6-e8iZYL5P.js → sankeyDiagram-XADWPNL6-BvFIrihC.js} +1 -1
  109. package/payload/server/public/assets/{sequenceDiagram-FGHM5R23-CfZqVRxe.js → sequenceDiagram-FGHM5R23-C6G1oAE0.js} +1 -1
  110. package/payload/server/public/assets/{stateDiagram-FHFEXIEX-DrWX8OY8.js → stateDiagram-FHFEXIEX-DbOJT_Xh.js} +1 -1
  111. package/payload/server/public/assets/stateDiagram-v2-QKLJ7IA2-Ce4GFk-H.js +1 -0
  112. package/payload/server/public/assets/{timeline-definition-GMOUNBTQ-Blec2Yhc.js → timeline-definition-GMOUNBTQ-D1DSxHh6.js} +1 -1
  113. package/payload/server/public/assets/{vennDiagram-DHZGUBPP-BtACuGgu.js → vennDiagram-DHZGUBPP-BYoSeMdo.js} +1 -1
  114. package/payload/server/public/assets/{wardleyDiagram-NUSXRM2D-BWT7orYV.js → wardleyDiagram-NUSXRM2D-DH3kZdea.js} +1 -1
  115. package/payload/server/public/assets/{xychartDiagram-5P7HB3ND-DBzoIRiJ.js → xychartDiagram-5P7HB3ND-C6MROMLG.js} +1 -1
  116. package/payload/server/public/data.html +5 -5
  117. package/payload/server/public/graph.html +6 -6
  118. package/payload/server/public/index.html +8 -8
  119. package/payload/server/public/public.html +5 -5
  120. package/payload/server/server-init.cjs +35 -3
  121. package/payload/server/server.js +547 -118
  122. package/payload/server/public/assets/channel-dZdJT-Ou.js +0 -1
  123. package/payload/server/public/assets/classDiagram-6PBFFD2Q-CP8AWLB9.js +0 -1
  124. package/payload/server/public/assets/classDiagram-v2-HSJHXN6E-bUe6tAo9.js +0 -1
  125. package/payload/server/public/assets/clone-gXHQCPU0.js +0 -1
  126. package/payload/server/public/assets/data-Bt6BTjvP.js +0 -1
  127. package/payload/server/public/assets/graph-labels-BeDsoEVY.js +0 -1
  128. package/payload/server/public/assets/graph-szVcmw8D.js +0 -1
  129. package/payload/server/public/assets/stateDiagram-v2-QKLJ7IA2-CvKXSO97.js +0 -1
  130. /package/payload/server/public/assets/{ChatInput-AR8ym-HW.js → ChatInput-DoS80kaC.js} +0 -0
@@ -0,0 +1,32 @@
1
+ {
2
+ "manifest_version": 3,
3
+ "name": "Maxy LinkedIn Ingest",
4
+ "version": "0.1.0",
5
+ "description": "Add an Add-to-Maxy pill to LinkedIn profiles and DM threads. One click captures the page and posts to your Maxy admin.",
6
+ "permissions": ["storage", "activeTab"],
7
+ "host_permissions": ["https://www.linkedin.com/*"],
8
+ "background": {
9
+ "service_worker": "background/sw.js"
10
+ },
11
+ "content_scripts": [
12
+ {
13
+ "matches": ["https://www.linkedin.com/in/*"],
14
+ "js": ["content/extractors.js", "content/profile.js"],
15
+ "css": ["assets/pill.css"],
16
+ "run_at": "document_idle"
17
+ },
18
+ {
19
+ "matches": ["https://www.linkedin.com/messaging/thread/*"],
20
+ "js": ["content/extractors.js", "content/thread.js"],
21
+ "css": ["assets/pill.css"],
22
+ "run_at": "document_idle"
23
+ }
24
+ ],
25
+ "options_ui": {
26
+ "page": "options/options.html",
27
+ "open_in_tab": true
28
+ },
29
+ "action": {
30
+ "default_title": "Maxy LinkedIn Ingest — open options"
31
+ }
32
+ }
@@ -0,0 +1,33 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <title>Maxy LinkedIn Ingest — Options</title>
6
+ <style>
7
+ body { font: 14px/1.5 -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; max-width: 480px; margin: 32px auto; padding: 0 16px; color: #111; }
8
+ h1 { font-size: 18px; margin: 0 0 12px; }
9
+ p { margin: 8px 0 16px; color: #444; }
10
+ label { display: block; font-weight: 600; margin: 12px 0 4px; }
11
+ input { width: 100%; box-sizing: border-box; padding: 8px 10px; border: 1px solid #ccc; border-radius: 6px; font: inherit; }
12
+ button { margin-top: 16px; padding: 8px 14px; border: 0; border-radius: 6px; background: #0a66c2; color: #fff; font: inherit; cursor: pointer; }
13
+ button:hover { background: #084d92; }
14
+ .status { margin-top: 12px; min-height: 1.2em; color: #155724; }
15
+ .status.error { color: #b00020; }
16
+ </style>
17
+ </head>
18
+ <body>
19
+ <h1>Maxy LinkedIn Ingest</h1>
20
+ <p>Point the extension at your Maxy admin host and paste your admin session key. The pill on LinkedIn will then post one-click captures to your admin.</p>
21
+
22
+ <label for="admin-host">Admin host</label>
23
+ <input id="admin-host" type="url" placeholder="https://your-tunnel.example.com" />
24
+
25
+ <label for="session-key">Session key</label>
26
+ <input id="session-key" type="password" placeholder="paste the value of session_key from your admin browser" />
27
+
28
+ <button id="save">Save</button>
29
+ <div id="status" class="status"></div>
30
+
31
+ <script src="options.js"></script>
32
+ </body>
33
+ </html>
@@ -0,0 +1,30 @@
1
+ // Options-page glue. Reads existing values, saves on click.
2
+
3
+ const $host = document.getElementById('admin-host')
4
+ const $key = document.getElementById('session-key')
5
+ const $btn = document.getElementById('save')
6
+ const $status = document.getElementById('status')
7
+
8
+ function setStatus(text, isError) {
9
+ $status.textContent = text
10
+ $status.classList.toggle('error', !!isError)
11
+ }
12
+
13
+ async function load() {
14
+ const stored = await chrome.storage.local.get(['adminHost', 'sessionKey'])
15
+ if (typeof stored.adminHost === 'string') $host.value = stored.adminHost
16
+ if (typeof stored.sessionKey === 'string') $key.value = stored.sessionKey
17
+ }
18
+
19
+ async function save() {
20
+ const adminHost = $host.value.trim()
21
+ const sessionKey = $key.value.trim()
22
+ if (!adminHost) return setStatus('Admin host is required.', true)
23
+ if (!/^https?:\/\//.test(adminHost)) return setStatus('Admin host must start with http(s)://.', true)
24
+ if (!sessionKey) return setStatus('Session key is required.', true)
25
+ await chrome.storage.local.set({ adminHost: adminHost, sessionKey: sessionKey })
26
+ setStatus('Saved.', false)
27
+ }
28
+
29
+ $btn.addEventListener('click', save)
30
+ load()
@@ -0,0 +1,91 @@
1
+ ---
2
+ name: linkedin-extension
3
+ description: Payload contract and DOM-selector rules for the LinkedIn one-click ingest Chrome extension. Carries no extraction prompts — classification stays in `document-ingest` and stays generic. Reference for extension authors and for tests pinning the wire shape between the extension and `platform/ui/server/routes/admin/linkedin-ingest.ts`.
4
+ ---
5
+
6
+ # LinkedIn Extension — payload & selectors
7
+
8
+ This skill carries two contracts. Neither is enforced by an LLM; both are enforced by the route schema and the extractor unit tests.
9
+
10
+ ## 1. Wire payload
11
+
12
+ Every POST from the extension to `/api/admin/linkedin-ingest` carries one JSON envelope:
13
+
14
+ ```ts
15
+ type Payload = {
16
+ source: 'linkedin' // literal — gates the route's source enum
17
+ kind: 'profile' | 'thread' // dispatches to the matching extractor branch
18
+ capturedAt: string // ISO 8601 timestamp from the extension clock
19
+ pageUrl: string // canonical https://www.linkedin.com/... URL
20
+ dispatchId: string // RFC-4122 UUID minted on extension side
21
+ profile?: ProfilePayload // present iff kind === 'profile'
22
+ thread?: ThreadPayload // present iff kind === 'thread'
23
+ }
24
+
25
+ type ProfilePayload = {
26
+ name: string // headline-card name
27
+ headline: string // tagline under the name
28
+ location: string // city / country line
29
+ currentTitle: string | null // top experience row, title side
30
+ currentCompany: string | null // top experience row, company side
31
+ bodyText: string // full visible text of the main column
32
+ }
33
+
34
+ type ThreadPayload = {
35
+ participants: Array<{
36
+ name: string // displayed name (deduped, in chronological order of first appearance)
37
+ profileUrl: string | null // /in/<slug>/ when present in the DOM
38
+ }>
39
+ bodyText: string // chronological transcript: "Name [time]: text" per message
40
+ }
41
+ ```
42
+
43
+ The route rejects with 400 if any required field is missing, the `source` is not `linkedin`, or `kind` is not in the enum. `pageUrl` must be an `https://www.linkedin.com/...` URL — otherwise 400.
44
+
45
+ `dispatchId` is the single cross-cut key. The route logs it, `database-operator` echoes it in its initial-message header, and every `[linkedin-ingest-write]` log line carries it.
46
+
47
+ ## 2. DOM selectors
48
+
49
+ Selectors are versioned with the test fixtures (`extension/__tests__/fixtures/{profile,thread}.html`, captured 2026-05-22). Each field has a primary selector and one fallback. If both miss for a required field, the extractor returns a structured error listing the missing fields — never a partial payload.
50
+
51
+ ### Profile (`linkedin.com/in/<slug>/`)
52
+
53
+ | Field | Primary | Fallback |
54
+ |---|---|---|
55
+ | `name` | `h1.text-heading-xlarge` | `section.top-card-layout h1` |
56
+ | `headline` | `[data-test-headline]` | `.text-body-medium.break-words` |
57
+ | `location` | `[data-test-location]` | `.text-body-small.inline.t-black--light:first-of-type` |
58
+ | `currentTitle` | `[data-test-current-position] .position-title` | `section#experience li:first-child .t-bold > span:first-child` |
59
+ | `currentCompany` | `[data-test-current-position] .position-company` | `section#experience li:first-child .t-normal > span:first-child` |
60
+ | `bodyText` | `main` | `body` |
61
+
62
+ Required: `name`, `headline`, `bodyText`. `location`, `currentTitle`, `currentCompany` are optional — `null` for missing.
63
+
64
+ ### Thread (`linkedin.com/messaging/thread/<id>/`)
65
+
66
+ | Field | Primary | Fallback |
67
+ |---|---|---|
68
+ | Thread root | `.msg-thread` | `[data-test-thread-root]` |
69
+ | Message item | `.msg-s-message-list__event` (within root) | `[data-test-message-item]` |
70
+ | Sender name | `.msg-s-event-listitem__name` | `[data-test-sender-name]` |
71
+ | Sender profile URL | `a.msg-s-event-listitem__link[href*="/in/"]` | `[data-test-sender-link]` |
72
+ | Message timestamp | `time.msg-s-event-listitem__timestamp[datetime]` | `[data-test-message-time][datetime]` |
73
+ | Message body | `.msg-s-event-listitem__body` | `[data-test-message-body]` |
74
+
75
+ Required: at least one message with a sender and a body. Empty threads → structured error.
76
+
77
+ ## Adding a new selector
78
+
79
+ When LinkedIn ships a DOM change that breaks the extractor:
80
+
81
+ 1. Capture a fresh fixture by saving the live page HTML into `extension/__tests__/fixtures/<kind>.html` (overwrite).
82
+ 2. Update the relevant primary selector. Keep the prior selector as fallback if it might still hit on lagging clients.
83
+ 3. Re-run `vitest extension`. The fixture-driven tests pin the wire shape — they will fail loudly if the change drops a required field.
84
+
85
+ The skill version date in this file's preamble is the capture date for the fixtures. Bump it when the fixtures change.
86
+
87
+ ## What this skill is NOT for
88
+
89
+ - Classification prompts — those live in `platform/plugins/memory/skills/document-ingest/` and stay generic across every future communication surface.
90
+ - Graph-write Cypher — that lives in `platform/plugins/memory/mcp/`.
91
+ - Selectors for any other LinkedIn surface (search, company pages, posts, Sales Navigator). Those are separate future tasks.
@@ -2,4 +2,18 @@ import { Driver, Session } from "neo4j-driver";
2
2
  export declare function getDriver(): Driver;
3
3
  export declare function getSession(): Session;
4
4
  export declare function closeDriver(): Promise<void>;
5
+ /**
6
+ * Task 237 — walk `:HAS_TRANSCRIPT` from a `:Conversation` (trashed or not)
7
+ * by elementId. Required by `memory-empty-trash`'s `:AdminConversation`
8
+ * defense-in-depth: a `:Trashed :Conversation` has its `conversationId`
9
+ * nulled (the unique-key snapshot moves to `_trashedKeys`), so
10
+ * conversationId-keyed lookups can't find it. elementId survives the
11
+ * trash transition unchanged.
12
+ *
13
+ * Account-scoped: `:Conversation.accountId` is NOT in the trash
14
+ * unique-key snapshot, so it stays live and we filter on it for
15
+ * defense-in-depth even though the caller already selected the candidate
16
+ * from its own account-scoped sweep.
17
+ */
18
+ export declare function getClaudeSessionIdsByConversationElementId(elementId: string, accountId: string): Promise<string[]>;
5
19
  //# sourceMappingURL=neo4j.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"neo4j.d.ts","sourceRoot":"","sources":["../../src/lib/neo4j.ts"],"names":[],"mappings":"AAAA,OAAc,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AA+BtD,wBAAgB,SAAS,IAAI,MAAM,CAgBlC;AAED,wBAAgB,UAAU,IAAI,OAAO,CAEpC;AAED,wBAAsB,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC,CAKjD"}
1
+ {"version":3,"file":"neo4j.d.ts","sourceRoot":"","sources":["../../src/lib/neo4j.ts"],"names":[],"mappings":"AAAA,OAAc,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AA+BtD,wBAAgB,SAAS,IAAI,MAAM,CAgBlC;AAED,wBAAgB,UAAU,IAAI,OAAO,CAEpC;AAED,wBAAsB,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC,CAKjD;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,0CAA0C,CAC9D,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,MAAM,EAAE,CAAC,CAgBnB"}
@@ -45,4 +45,32 @@ export async function closeDriver() {
45
45
  driver = null;
46
46
  }
47
47
  }
48
+ /**
49
+ * Task 237 — walk `:HAS_TRANSCRIPT` from a `:Conversation` (trashed or not)
50
+ * by elementId. Required by `memory-empty-trash`'s `:AdminConversation`
51
+ * defense-in-depth: a `:Trashed :Conversation` has its `conversationId`
52
+ * nulled (the unique-key snapshot moves to `_trashedKeys`), so
53
+ * conversationId-keyed lookups can't find it. elementId survives the
54
+ * trash transition unchanged.
55
+ *
56
+ * Account-scoped: `:Conversation.accountId` is NOT in the trash
57
+ * unique-key snapshot, so it stays live and we filter on it for
58
+ * defense-in-depth even though the caller already selected the candidate
59
+ * from its own account-scoped sweep.
60
+ */
61
+ export async function getClaudeSessionIdsByConversationElementId(elementId, accountId) {
62
+ const session = getSession();
63
+ try {
64
+ const result = await session.run(`MATCH (c) WHERE elementId(c) = $elementId AND c.accountId = $accountId
65
+ MATCH (c)-[:HAS_TRANSCRIPT]->(t:ClaudeSession)
66
+ RETURN t.claudeSessionId AS claudeSessionId
67
+ ORDER BY t.createdAt ASC`, { elementId, accountId });
68
+ return result.records
69
+ .map((r) => r.get("claudeSessionId"))
70
+ .filter((v) => typeof v === "string" && v.length > 0);
71
+ }
72
+ finally {
73
+ await session.close();
74
+ }
75
+ }
48
76
  //# sourceMappingURL=neo4j.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"neo4j.js","sourceRoot":"","sources":["../../src/lib/neo4j.ts"],"names":[],"mappings":"AAAA,OAAO,KAA0B,MAAM,cAAc,CAAC;AACtD,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,IAAI,MAAM,GAAkB,IAAI,CAAC;AAEjC,SAAS,YAAY;IACnB,IAAI,OAAO,CAAC,GAAG,CAAC,cAAc;QAAE,OAAO,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC;IAElE,qDAAqD;IACrD,yEAAyE;IACzE,oEAAoE;IACpE,oEAAoE;IACpE,kEAAkE;IAClE,oEAAoE;IACpE,+DAA+D;IAC/D,+DAA+D;IAC/D,MAAM,YAAY,GAAG,OAAO,CAC1B,OAAO,CAAC,GAAG,CAAC,aAAa,IAAI,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,gBAAgB,CAAC,EAC3E,wBAAwB,CACzB,CAAC;IAEF,IAAI,CAAC;QACH,OAAO,YAAY,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC;IACpD,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,KAAK,CACb,yCAAyC,YAAY,gCAAgC,CACtF,CAAC;IACJ,CAAC;AACH,CAAC;AAED,MAAM,UAAU,SAAS;IACvB,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,6DAA6D;QAC7D,oEAAoE;QACpE,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC;QAClC,IAAI,CAAC,GAAG,EAAE,CAAC;YACT,MAAM,IAAI,KAAK,CACb,yEAAyE,CAC1E,CAAC;QACJ,CAAC;QACD,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,UAAU,IAAI,OAAO,CAAC;QAC/C,MAAM,QAAQ,GAAG,YAAY,EAAE,CAAC;QAChC,OAAO,CAAC,KAAK,CAAC,+BAA+B,GAAG,EAAE,CAAC,CAAC;QACpD,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC;IAC/D,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,MAAM,UAAU,UAAU;IACxB,OAAO,SAAS,EAAE,CAAC,OAAO,EAAE,CAAC;AAC/B,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,WAAW;IAC/B,IAAI,MAAM,EAAE,CAAC;QACX,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC;QACrB,MAAM,GAAG,IAAI,CAAC;IAChB,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"neo4j.js","sourceRoot":"","sources":["../../src/lib/neo4j.ts"],"names":[],"mappings":"AAAA,OAAO,KAA0B,MAAM,cAAc,CAAC;AACtD,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,IAAI,MAAM,GAAkB,IAAI,CAAC;AAEjC,SAAS,YAAY;IACnB,IAAI,OAAO,CAAC,GAAG,CAAC,cAAc;QAAE,OAAO,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC;IAElE,qDAAqD;IACrD,yEAAyE;IACzE,oEAAoE;IACpE,oEAAoE;IACpE,kEAAkE;IAClE,oEAAoE;IACpE,+DAA+D;IAC/D,+DAA+D;IAC/D,MAAM,YAAY,GAAG,OAAO,CAC1B,OAAO,CAAC,GAAG,CAAC,aAAa,IAAI,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,gBAAgB,CAAC,EAC3E,wBAAwB,CACzB,CAAC;IAEF,IAAI,CAAC;QACH,OAAO,YAAY,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC;IACpD,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,KAAK,CACb,yCAAyC,YAAY,gCAAgC,CACtF,CAAC;IACJ,CAAC;AACH,CAAC;AAED,MAAM,UAAU,SAAS;IACvB,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,6DAA6D;QAC7D,oEAAoE;QACpE,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC;QAClC,IAAI,CAAC,GAAG,EAAE,CAAC;YACT,MAAM,IAAI,KAAK,CACb,yEAAyE,CAC1E,CAAC;QACJ,CAAC;QACD,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,UAAU,IAAI,OAAO,CAAC;QAC/C,MAAM,QAAQ,GAAG,YAAY,EAAE,CAAC;QAChC,OAAO,CAAC,KAAK,CAAC,+BAA+B,GAAG,EAAE,CAAC,CAAC;QACpD,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC;IAC/D,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,MAAM,UAAU,UAAU;IACxB,OAAO,SAAS,EAAE,CAAC,OAAO,EAAE,CAAC;AAC/B,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,WAAW;IAC/B,IAAI,MAAM,EAAE,CAAC;QACX,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC;QACrB,MAAM,GAAG,IAAI,CAAC;IAChB,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,KAAK,UAAU,0CAA0C,CAC9D,SAAiB,EACjB,SAAiB;IAEjB,MAAM,OAAO,GAAG,UAAU,EAAE,CAAC;IAC7B,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,GAAG,CAC9B;;;gCAG0B,EAC1B,EAAE,SAAS,EAAE,SAAS,EAAE,CACzB,CAAC;QACF,OAAO,MAAM,CAAC,OAAO;aAClB,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC;aACpC,MAAM,CAAC,CAAC,CAAC,EAAe,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IACvE,CAAC;YAAS,CAAC;QACT,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC;IACxB,CAAC;AACH,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=memory-empty-trash-admin-conversation.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memory-empty-trash-admin-conversation.test.d.ts","sourceRoot":"","sources":["../../../src/tools/__tests__/memory-empty-trash-admin-conversation.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,168 @@
1
+ // Task 237 — memory-empty-trash :AdminConversation defense-in-depth.
2
+ import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
3
+ import { setupEmitCapture, makeMockSession } from "./_helpers/emit-capture.js";
4
+ const mockSession = makeMockSession();
5
+ vi.mock("../../lib/neo4j.js", () => ({
6
+ getSession: () => mockSession,
7
+ getClaudeSessionIdsByConversationElementId: vi.fn(),
8
+ }));
9
+ vi.mock("../../lib/attachments.js", () => ({
10
+ removeAttachmentDir: vi.fn().mockResolvedValue(false),
11
+ }));
12
+ import { memoryEmptyTrash } from "../memory-empty-trash.js";
13
+ import { getClaudeSessionIdsByConversationElementId } from "../../lib/neo4j.js";
14
+ const lookupMock = vi.mocked(getClaudeSessionIdsByConversationElementId);
15
+ const ACCOUNT_ID = "acct-1";
16
+ const CONVERSATION_ID = "conv-11111111-1111-1111-1111-111111111111";
17
+ const ELEMENT_ID = "4:000:42";
18
+ const CLAUDE_A = "css-aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa";
19
+ const CLAUDE_B = "css-bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb";
20
+ function stageEmptyTrashCypherForOne(candidate, options = { expectDetachDelete: true }) {
21
+ const state = { detachDeleteCalls: 0, runs: 0 };
22
+ mockSession.run = vi.fn(async (cypher) => {
23
+ state.runs++;
24
+ // emptyTrash's candidate query
25
+ if (cypher.includes("MATCH (n:Trashed)")) {
26
+ const keysJson = candidate.conversationId
27
+ ? JSON.stringify({ conversationId: candidate.conversationId })
28
+ : JSON.stringify({});
29
+ return {
30
+ records: [
31
+ {
32
+ get: (k) => {
33
+ switch (k) {
34
+ case "eid": return ELEMENT_ID;
35
+ case "labels": return [...candidate.labels, "Trashed"];
36
+ case "trashedAt": return "2026-04-01T00:00:00.000Z";
37
+ case "trashedBy": return "test";
38
+ case "keysJson": return keysJson;
39
+ default: return undefined;
40
+ }
41
+ },
42
+ },
43
+ ],
44
+ };
45
+ }
46
+ if (cypher.includes("DETACH DELETE n")) {
47
+ state.detachDeleteCalls++;
48
+ if (!options.expectDetachDelete) {
49
+ throw new Error(`unexpected DETACH DELETE — candidate should have been skipped`);
50
+ }
51
+ return { records: [] };
52
+ }
53
+ throw new Error(`unexpected cypher: ${cypher.slice(0, 80)}`);
54
+ });
55
+ return state;
56
+ }
57
+ beforeEach(() => {
58
+ lookupMock.mockReset();
59
+ delete process.env.CLAUDE_SESSION_MANAGER_PORT;
60
+ });
61
+ afterEach(() => {
62
+ vi.restoreAllMocks();
63
+ delete process.env.CLAUDE_SESSION_MANAGER_PORT;
64
+ });
65
+ describe("memory-empty-trash :AdminConversation onEmpty", () => {
66
+ it("purges JSONL for a stale :Trashed :AdminConversation and DETACH DELETEs the node", async () => {
67
+ process.env.CLAUDE_SESSION_MANAGER_PORT = "9999";
68
+ const state = stageEmptyTrashCypherForOne({ labels: ["Conversation", "AdminConversation"], conversationId: CONVERSATION_ID }, { expectDetachDelete: true });
69
+ lookupMock.mockResolvedValue([CLAUDE_A, CLAUDE_B]);
70
+ const fetchCalls = [];
71
+ const fetchImpl = vi.fn(async (url, init) => {
72
+ fetchCalls.push({ url: String(url), method: init?.method ?? "GET" });
73
+ return new Response(null, { status: 204 });
74
+ });
75
+ const emit = setupEmitCapture();
76
+ try {
77
+ const result = await memoryEmptyTrash({ accountId: ACCOUNT_ID, fetchImpl });
78
+ expect(result.emptied).toBe(1);
79
+ expect(result.adminConversationsPurged).toBe(1);
80
+ expect(state.detachDeleteCalls).toBe(1);
81
+ expect(fetchCalls).toEqual([
82
+ { url: `http://127.0.0.1:9999/${CLAUDE_A}`, method: "DELETE" },
83
+ { url: `http://127.0.0.1:9999/${CLAUDE_B}`, method: "DELETE" },
84
+ ]);
85
+ expect(lookupMock).toHaveBeenCalledWith(ELEMENT_ID, ACCOUNT_ID);
86
+ }
87
+ finally {
88
+ emit.restore();
89
+ }
90
+ });
91
+ it("skips candidate (no DETACH DELETE) when manager returns 5xx for a JSONL", async () => {
92
+ process.env.CLAUDE_SESSION_MANAGER_PORT = "9999";
93
+ const state = stageEmptyTrashCypherForOne({ labels: ["Conversation", "AdminConversation"], conversationId: CONVERSATION_ID }, { expectDetachDelete: false });
94
+ lookupMock.mockResolvedValue([CLAUDE_A]);
95
+ const fetchImpl = vi.fn(async () => new Response('{"error":"disk-fail"}', {
96
+ status: 500,
97
+ headers: { "content-type": "application/json" },
98
+ }));
99
+ const stderrSpy = vi.spyOn(process.stderr, "write").mockImplementation(() => true);
100
+ const emit = setupEmitCapture();
101
+ try {
102
+ const result = await memoryEmptyTrash({ accountId: ACCOUNT_ID, fetchImpl });
103
+ expect(result.emptied).toBe(0);
104
+ expect(result.adminConversationsPurged).toBe(0);
105
+ expect(state.detachDeleteCalls).toBe(0);
106
+ const lines = stderrSpy.mock.calls.map((c) => String(c[0]));
107
+ expect(lines.some((l) => l.includes("[trash:empty-run]") && l.includes("reason=jsonl-purge-failed"))).toBe(true);
108
+ }
109
+ finally {
110
+ stderrSpy.mockRestore();
111
+ emit.restore();
112
+ }
113
+ });
114
+ it("skips candidate when CLAUDE_SESSION_MANAGER_PORT is unset (no DETACH DELETE)", async () => {
115
+ // env var deliberately not set
116
+ const state = stageEmptyTrashCypherForOne({ labels: ["Conversation", "AdminConversation"], conversationId: CONVERSATION_ID }, { expectDetachDelete: false });
117
+ lookupMock.mockResolvedValue([CLAUDE_A]);
118
+ const fetchImpl = vi.fn();
119
+ const stderrSpy = vi.spyOn(process.stderr, "write").mockImplementation(() => true);
120
+ const emit = setupEmitCapture();
121
+ try {
122
+ const result = await memoryEmptyTrash({ accountId: ACCOUNT_ID, fetchImpl });
123
+ expect(result.emptied).toBe(0);
124
+ expect(result.adminConversationsPurged).toBe(0);
125
+ expect(state.detachDeleteCalls).toBe(0);
126
+ expect(fetchImpl.mock.calls).toHaveLength(0);
127
+ const lines = stderrSpy.mock.calls.map((c) => String(c[0]));
128
+ expect(lines.some((l) => l.includes("reason=jsonl-purge-failed") && l.includes("manager-port-missing"))).toBe(true);
129
+ }
130
+ finally {
131
+ stderrSpy.mockRestore();
132
+ emit.restore();
133
+ }
134
+ });
135
+ it("skips candidate when conversationId is missing from _trashedKeys", async () => {
136
+ process.env.CLAUDE_SESSION_MANAGER_PORT = "9999";
137
+ const state = stageEmptyTrashCypherForOne({ labels: ["Conversation", "AdminConversation"] }, // no conversationId in snapshot
138
+ { expectDetachDelete: false });
139
+ const fetchImpl = vi.fn();
140
+ const emit = setupEmitCapture();
141
+ try {
142
+ const result = await memoryEmptyTrash({ accountId: ACCOUNT_ID, fetchImpl });
143
+ expect(result.emptied).toBe(0);
144
+ expect(state.detachDeleteCalls).toBe(0);
145
+ expect(lookupMock).not.toHaveBeenCalled();
146
+ }
147
+ finally {
148
+ emit.restore();
149
+ }
150
+ });
151
+ it("leaves :KnowledgeDocument path unchanged (no JSONL purge, no AdminConversation counter)", async () => {
152
+ const state = stageEmptyTrashCypherForOne({ labels: ["KnowledgeDocument"] }, { expectDetachDelete: true });
153
+ const fetchImpl = vi.fn();
154
+ const emit = setupEmitCapture();
155
+ try {
156
+ const result = await memoryEmptyTrash({ accountId: ACCOUNT_ID, fetchImpl });
157
+ expect(result.emptied).toBe(1);
158
+ expect(result.adminConversationsPurged).toBe(0);
159
+ expect(state.detachDeleteCalls).toBe(1);
160
+ expect(lookupMock).not.toHaveBeenCalled();
161
+ expect(fetchImpl.mock.calls).toHaveLength(0);
162
+ }
163
+ finally {
164
+ emit.restore();
165
+ }
166
+ });
167
+ });
168
+ //# sourceMappingURL=memory-empty-trash-admin-conversation.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memory-empty-trash-admin-conversation.test.js","sourceRoot":"","sources":["../../../src/tools/__tests__/memory-empty-trash-admin-conversation.test.ts"],"names":[],"mappings":"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"}
@@ -1,9 +1,21 @@
1
1
  import { type EmptyTrashResult } from "../../../../../lib/graph-trash/dist/index.js";
2
2
  /**
3
3
  * memory-empty-trash — hard-delete trashed nodes whose `trashedAt` is older
4
- * than the grace period. For `KnowledgeDocument` candidates, also removes the
5
- * on-disk attachment directory (snapshotted `attachmentId` is read from
6
- * `_trashedKeys`).
4
+ * than the grace period.
5
+ *
6
+ * Per-label cleanup runs in the `onEmpty` callback before the `DETACH DELETE`:
7
+ * - `:KnowledgeDocument` → removes the on-disk attachment directory
8
+ * (snapshotted `attachmentId` from `_trashedKeys`).
9
+ * - `:AdminConversation` → purges every attached JSONL via
10
+ * `purgeAdminConversationJsonls`. Required because the graph-page
11
+ * cascade is forward-only — `:Trashed :AdminConversation` rows
12
+ * produced by manual Cypher / future bulk-delete / legacy traffic
13
+ * still have JSONLs on disk that the Sidebar will surface as ghost rows.
14
+ *
15
+ * When the AdminConversation purge can't proceed (no manager port,
16
+ * conversationId missing from `_trashedKeys`, manager 409/5xx), the
17
+ * callback throws and the `emptyTrash` primitive skips the `DETACH DELETE`
18
+ * this run; the candidate becomes eligible again on the next sweep.
7
19
  *
8
20
  * Defaults: `graceDays=30`, `dryRun=false`, no label filter. `dryRun=true`
9
21
  * lists candidates without mutating; the absence of `[trash:emptied]` lines
@@ -14,9 +26,16 @@ export interface MemoryEmptyTrashParams {
14
26
  graceDays?: number;
15
27
  dryRun?: boolean;
16
28
  labels?: string[];
29
+ /** Loopback manager base URL — `http://127.0.0.1:${CLAUDE_SESSION_MANAGER_PORT}`.
30
+ * Defaults from env at call time. When neither this nor the env var is set,
31
+ * `:AdminConversation` candidates throw and skip; other labels proceed. */
32
+ managerBase?: string;
33
+ /** Test hook — defaults to global `fetch`. */
34
+ fetchImpl?: typeof fetch;
17
35
  }
18
36
  export interface MemoryEmptyTrashResult extends EmptyTrashResult {
19
37
  attachmentDirsRemoved: number;
38
+ adminConversationsPurged: number;
20
39
  }
21
40
  export declare function memoryEmptyTrash(params: MemoryEmptyTrashParams): Promise<MemoryEmptyTrashResult>;
22
41
  //# sourceMappingURL=memory-empty-trash.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"memory-empty-trash.d.ts","sourceRoot":"","sources":["../../src/tools/memory-empty-trash.ts"],"names":[],"mappings":"AACA,OAAO,EAAmC,KAAK,gBAAgB,EAAE,MAAM,8CAA8C,CAAC;AAItH;;;;;;;;;GASG;AAEH,MAAM,WAAW,sBAAsB;IACrC,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,MAAM,WAAW,sBAAuB,SAAQ,gBAAgB;IAC9D,qBAAqB,EAAE,MAAM,CAAC;CAC/B;AAED,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,sBAAsB,GAC7B,OAAO,CAAC,sBAAsB,CAAC,CAKjC"}
1
+ {"version":3,"file":"memory-empty-trash.d.ts","sourceRoot":"","sources":["../../src/tools/memory-empty-trash.ts"],"names":[],"mappings":"AACA,OAAO,EAAmC,KAAK,gBAAgB,EAAE,MAAM,8CAA8C,CAAC;AAKtH;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,MAAM,WAAW,sBAAsB;IACrC,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB;;gFAE4E;IAC5E,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,8CAA8C;IAC9C,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;CAC1B;AAED,MAAM,WAAW,sBAAuB,SAAQ,gBAAgB;IAC9D,qBAAqB,EAAE,MAAM,CAAC;IAC9B,wBAAwB,EAAE,MAAM,CAAC;CAClC;AAED,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,sBAAsB,GAC7B,OAAO,CAAC,sBAAsB,CAAC,CAKjC"}
@@ -1,14 +1,28 @@
1
- import { getSession } from "../lib/neo4j.js";
1
+ import { getSession, getClaudeSessionIdsByConversationElementId } from "../lib/neo4j.js";
2
2
  import { emptyTrash } from "../../../../../lib/graph-trash/dist/index.js";
3
+ import { purgeAdminConversationJsonls } from "../../../../../lib/admin-conversation-purge/dist/index.js";
3
4
  import { removeAttachmentDir } from "../lib/attachments.js";
4
5
  import { withWriteEvent } from "../lib/log-ingest.js";
5
6
  export async function memoryEmptyTrash(params) {
6
7
  return withWriteEvent({ tool: "memory-empty-trash", accountId: params.accountId, labels: params.labels }, () => memoryEmptyTrashImpl(params));
7
8
  }
9
+ function resolveManagerBase(explicit) {
10
+ if (explicit)
11
+ return explicit;
12
+ const port = process.env.CLAUDE_SESSION_MANAGER_PORT;
13
+ if (!port)
14
+ return null;
15
+ const parsed = Number(port);
16
+ if (!Number.isFinite(parsed) || parsed <= 0 || parsed >= 65536)
17
+ return null;
18
+ return `http://127.0.0.1:${parsed}`;
19
+ }
8
20
  async function memoryEmptyTrashImpl(params) {
9
- const { accountId, graceDays, dryRun, labels } = params;
21
+ const { accountId, graceDays, dryRun, labels, fetchImpl } = params;
22
+ const managerBase = resolveManagerBase(params.managerBase);
10
23
  const session = getSession();
11
24
  let attachmentDirsRemoved = 0;
25
+ let adminConversationsPurged = 0;
12
26
  try {
13
27
  const result = await emptyTrash({
14
28
  session,
@@ -17,24 +31,58 @@ async function memoryEmptyTrashImpl(params) {
17
31
  dryRun,
18
32
  labels,
19
33
  onEmpty: async (c) => {
20
- if (!c.labels.includes("KnowledgeDocument"))
21
- return;
22
- const aid = typeof c.trashedKeys.attachmentId === "string"
23
- ? c.trashedKeys.attachmentId
24
- : null;
25
- if (!aid)
34
+ if (c.labels.includes("AdminConversation")) {
35
+ await purgeAdminConversationForCandidate(c, accountId, managerBase, fetchImpl);
36
+ adminConversationsPurged++;
26
37
  return;
27
- const removed = await removeAttachmentDir(accountId, aid);
28
- if (removed) {
29
- attachmentDirsRemoved++;
30
- process.stderr.write(`[trash:emptied:disk] accountId=${accountId} attachmentId=${aid}\n`);
38
+ }
39
+ if (c.labels.includes("KnowledgeDocument")) {
40
+ const aid = typeof c.trashedKeys.attachmentId === "string"
41
+ ? c.trashedKeys.attachmentId
42
+ : null;
43
+ if (!aid)
44
+ return;
45
+ const removed = await removeAttachmentDir(accountId, aid);
46
+ if (removed) {
47
+ attachmentDirsRemoved++;
48
+ process.stderr.write(`[trash:emptied:disk] accountId=${accountId} attachmentId=${aid}\n`);
49
+ }
31
50
  }
32
51
  },
33
52
  });
34
- return { ...result, attachmentDirsRemoved };
53
+ return { ...result, attachmentDirsRemoved, adminConversationsPurged };
35
54
  }
36
55
  finally {
37
56
  await session.close();
38
57
  }
39
58
  }
59
+ async function purgeAdminConversationForCandidate(candidate, accountId, managerBase, fetchImpl) {
60
+ const rawConversationId = candidate.trashedKeys.conversationId;
61
+ const conversationId = typeof rawConversationId === "string" && rawConversationId.length > 0
62
+ ? rawConversationId
63
+ : null;
64
+ if (!conversationId) {
65
+ throw new Error(`AdminConversation elementId=${candidate.elementId.slice(0, 8)} has no conversationId in _trashedKeys — refusing to DETACH DELETE without JSONL purge attempt`);
66
+ }
67
+ if (!managerBase) {
68
+ process.stderr.write(`[trash:empty-run] reason=jsonl-purge-failed conversationId=${conversationId.slice(0, 8)} message=manager-port-missing\n`);
69
+ throw new Error(`manager-port-missing — set CLAUDE_SESSION_MANAGER_PORT to purge :AdminConversation JSONLs`);
70
+ }
71
+ const claudeSessionIds = await getClaudeSessionIdsByConversationElementId(candidate.elementId, accountId);
72
+ const outcome = await purgeAdminConversationJsonls({
73
+ conversationId,
74
+ accountId,
75
+ managerBase,
76
+ claudeSessionIds,
77
+ fetchImpl,
78
+ });
79
+ if (!outcome.ok) {
80
+ const claudeSessionId = outcome.claudeSessionId.slice(0, 8);
81
+ const message = outcome.reason === "pty-still-alive"
82
+ ? "pty-still-alive"
83
+ : `${outcome.reason}:${outcome.message}`;
84
+ process.stderr.write(`[trash:empty-run] reason=jsonl-purge-failed conversationId=${conversationId.slice(0, 8)} claudeSessionId=${claudeSessionId} message=${message}\n`);
85
+ throw new Error(`jsonl-purge-failed for claudeSessionId=${claudeSessionId}: ${message}`);
86
+ }
87
+ }
40
88
  //# sourceMappingURL=memory-empty-trash.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"memory-empty-trash.js","sourceRoot":"","sources":["../../src/tools/memory-empty-trash.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,UAAU,EAA8C,MAAM,8CAA8C,CAAC;AACtH,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAwBtD,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,MAA8B;IAE9B,OAAO,cAAc,CACnB,EAAE,IAAI,EAAE,oBAAoB,EAAE,SAAS,EAAE,MAAM,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,EAClF,GAAG,EAAE,CAAC,oBAAoB,CAAC,MAAM,CAAC,CACnC,CAAC;AACJ,CAAC;AAED,KAAK,UAAU,oBAAoB,CACjC,MAA8B;IAE9B,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,CAAC;IACxD,MAAM,OAAO,GAAG,UAAU,EAAE,CAAC;IAC7B,IAAI,qBAAqB,GAAG,CAAC,CAAC;IAE9B,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC;YAC9B,OAAO;YACP,SAAS;YACT,SAAS;YACT,MAAM;YACN,MAAM;YACN,OAAO,EAAE,KAAK,EAAE,CAAiB,EAAE,EAAE;gBACnC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,mBAAmB,CAAC;oBAAE,OAAO;gBACpD,MAAM,GAAG,GAAG,OAAO,CAAC,CAAC,WAAW,CAAC,YAAY,KAAK,QAAQ;oBACxD,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,YAAY;oBAC5B,CAAC,CAAC,IAAI,CAAC;gBACT,IAAI,CAAC,GAAG;oBAAE,OAAO;gBACjB,MAAM,OAAO,GAAG,MAAM,mBAAmB,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC;gBAC1D,IAAI,OAAO,EAAE,CAAC;oBACZ,qBAAqB,EAAE,CAAC;oBACxB,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,kCAAkC,SAAS,iBAAiB,GAAG,IAAI,CACpE,CAAC;gBACJ,CAAC;YACH,CAAC;SACF,CAAC,CAAC;QAEH,OAAO,EAAE,GAAG,MAAM,EAAE,qBAAqB,EAAE,CAAC;IAC9C,CAAC;YAAS,CAAC;QACT,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC;IACxB,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"memory-empty-trash.js","sourceRoot":"","sources":["../../src/tools/memory-empty-trash.ts"],"names":[],"mappings":"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"}
@@ -123,6 +123,12 @@ fi
123
123
  # and a PostToolUse regex matcher for parse tools.
124
124
  # UserPromptSubmit — archive-ingest-surface-gate.sh clears the
125
125
  # parse-error flag when the operator's next prompt arrives.
126
+ # askuserquestion-investigate-gate.sh (Task 241): PreToolUse
127
+ # matcher=AskUserQuestion. Blocks the question when no
128
+ # read-only investigation tool has fired since the latest
129
+ # real user turn in the session JSONL. Fail-open on
130
+ # missing transcript or parse error. One [ask-gate]
131
+ # decision line per call.
126
132
  #
127
133
  # Task 214 — the Stop-hook registration that fired
128
134
  # turn-completed-graph-write.sh once per admin turn is no longer written.
@@ -194,6 +200,12 @@ cat > "$ACCOUNT_SETTINGS" << SETTINGS_EOF
194
200
  "hooks": [
195
201
  { "type": "command", "command": "node $HOOKS_PATH/webfetch-preflight.mjs" }
196
202
  ]
203
+ },
204
+ {
205
+ "matcher": "AskUserQuestion",
206
+ "hooks": [
207
+ { "type": "command", "command": "bash $HOOKS_PATH/askuserquestion-investigate-gate.sh" }
208
+ ]
197
209
  }
198
210
  ],
199
211
  "PostToolUse": [