@rubytech/create-maxy-code 0.1.87 → 0.1.90
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/__tests__/entitlement-flag.test.js +43 -0
- package/dist/__tests__/tier-flag.test.js +53 -0
- package/dist/index.js +68 -0
- package/dist/tier-flag.js +85 -0
- package/package.json +1 -1
- package/payload/platform/lib/admin-conversation-purge/dist/index.d.ts +54 -0
- package/payload/platform/lib/admin-conversation-purge/dist/index.d.ts.map +1 -0
- package/payload/platform/lib/admin-conversation-purge/dist/index.js +84 -0
- package/payload/platform/lib/admin-conversation-purge/dist/index.js.map +1 -0
- package/payload/platform/lib/admin-conversation-purge/src/index.ts +120 -0
- package/payload/platform/lib/admin-conversation-purge/tsconfig.json +8 -0
- package/payload/platform/lib/graph-trash/dist/index.d.ts +12 -1
- package/payload/platform/lib/graph-trash/dist/index.d.ts.map +1 -1
- package/payload/platform/lib/graph-trash/dist/index.js +8 -1
- package/payload/platform/lib/graph-trash/dist/index.js.map +1 -1
- package/payload/platform/lib/graph-trash/src/index.ts +20 -2
- package/payload/platform/package.json +2 -2
- package/payload/platform/plugins/.claude-plugin/marketplace.json +5 -0
- package/payload/platform/plugins/admin/PLUGIN.md +1 -0
- package/payload/platform/plugins/admin/hooks/__tests__/askuserquestion-investigate-gate.test.sh +176 -0
- package/payload/platform/plugins/admin/hooks/askuserquestion-investigate-gate.sh +257 -0
- package/payload/platform/plugins/admin/skills/admin-user-management/SKILL.md +2 -0
- package/payload/platform/plugins/docs/references/admin-session.md +5 -1
- package/payload/platform/plugins/docs/references/linkedin-extension.md +49 -0
- package/payload/platform/plugins/linkedin-extension/.claude-plugin/plugin.json +8 -0
- package/payload/platform/plugins/linkedin-extension/PLUGIN.md +58 -0
- package/payload/platform/plugins/linkedin-extension/extension/README.md +44 -0
- package/payload/platform/plugins/linkedin-extension/extension/__tests__/fixtures/profile.html +34 -0
- package/payload/platform/plugins/linkedin-extension/extension/__tests__/fixtures/thread.html +36 -0
- package/payload/platform/plugins/linkedin-extension/extension/assets/pill.css +52 -0
- package/payload/platform/plugins/linkedin-extension/extension/background/sw.js +60 -0
- package/payload/platform/plugins/linkedin-extension/extension/content/extractors.js +127 -0
- package/payload/platform/plugins/linkedin-extension/extension/content/profile.js +82 -0
- package/payload/platform/plugins/linkedin-extension/extension/content/thread.js +84 -0
- package/payload/platform/plugins/linkedin-extension/extension/manifest.json +32 -0
- package/payload/platform/plugins/linkedin-extension/extension/options/options.html +33 -0
- package/payload/platform/plugins/linkedin-extension/extension/options/options.js +30 -0
- package/payload/platform/plugins/linkedin-extension/skills/linkedin-extension/SKILL.md +91 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/neo4j.d.ts +14 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/neo4j.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/neo4j.js +28 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/neo4j.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-empty-trash-admin-conversation.test.d.ts +2 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-empty-trash-admin-conversation.test.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-empty-trash-admin-conversation.test.js +168 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-empty-trash-admin-conversation.test.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-empty-trash.d.ts +22 -3
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-empty-trash.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-empty-trash.js +61 -13
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-empty-trash.js.map +1 -1
- package/payload/platform/scripts/seed-neo4j.sh +12 -0
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.js +27 -17
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.js.map +1 -1
- package/payload/platform/templates/agents/admin/IDENTITY.md +3 -1
- package/payload/platform/templates/specialists/agents/database-operator.md +6 -2
- package/payload/server/public/assets/{ChatInput-CktRrxx1.css → ChatInput-dLWJ0Q8I.css} +1 -1
- package/payload/server/public/assets/{Checkbox-D2XqbR29.js → Checkbox-Bx8mRHQ3.js} +1 -1
- package/payload/server/public/assets/{admin-BK1F2LMD.js → admin-C57bDUgs.js} +6 -6
- package/payload/server/public/assets/{architectureDiagram-Q4EWVU46-9F6y6ESU.js → architectureDiagram-Q4EWVU46-BEW_bD50.js} +1 -1
- package/payload/server/public/assets/{blockDiagram-DXYQGD6D-LjGbzg0M.js → blockDiagram-DXYQGD6D-DFLT98C3.js} +1 -1
- package/payload/server/public/assets/{c4Diagram-AHTNJAMY-Bxpn5T5R.js → c4Diagram-AHTNJAMY-BemKtROA.js} +1 -1
- package/payload/server/public/assets/channel-CfpByn-m.js +1 -0
- package/payload/server/public/assets/{chunk-336JU56O-Cc2kxWVN.js → chunk-336JU56O-BL8fGJ7_.js} +2 -2
- package/payload/server/public/assets/{chunk-426QAEUC-DRzEhHvW.js → chunk-426QAEUC-BD6by0MP.js} +1 -1
- package/payload/server/public/assets/{chunk-4TB4RGXK-C8zRzH9g.js → chunk-4TB4RGXK-DX-cb6Nd.js} +1 -1
- package/payload/server/public/assets/{chunk-5FUZZQ4R-s8V7nzOd.js → chunk-5FUZZQ4R-B2QqtIZS.js} +1 -1
- package/payload/server/public/assets/{chunk-5PVQY5BW-KNShBlaM.js → chunk-5PVQY5BW-CzJWmsmd.js} +1 -1
- package/payload/server/public/assets/{chunk-EDXVE4YY-C6GvGxn3.js → chunk-EDXVE4YY-DP9vcvEU.js} +1 -1
- package/payload/server/public/assets/{chunk-ENJZ2VHE-XvXhU8ZS.js → chunk-ENJZ2VHE-D5xWEjF0.js} +1 -1
- package/payload/server/public/assets/{chunk-ICPOFSXX-OSDYUb0H.js → chunk-ICPOFSXX-AKduf9o4.js} +1 -1
- package/payload/server/public/assets/{chunk-OYMX7WX6-km_4dA-E.js → chunk-OYMX7WX6-D6jsVNyQ.js} +1 -1
- package/payload/server/public/assets/{chunk-U2HBQHQK-BGHqDvzJ.js → chunk-U2HBQHQK-BNAnTYh8.js} +1 -1
- package/payload/server/public/assets/{chunk-X2U36JSP-BKKSYdFO.js → chunk-X2U36JSP-UdQhYDnh.js} +1 -1
- package/payload/server/public/assets/{chunk-YZCP3GAM-DT_yhfJM.js → chunk-YZCP3GAM-DSSmSGmS.js} +1 -1
- package/payload/server/public/assets/{chunk-ZZ45TVLE-CP2JuBPA.js → chunk-ZZ45TVLE-Czm7IDMC.js} +1 -1
- package/payload/server/public/assets/classDiagram-6PBFFD2Q-DNc9R1IX.js +1 -0
- package/payload/server/public/assets/classDiagram-v2-HSJHXN6E-DM1DQh8N.js +1 -0
- package/payload/server/public/assets/clone-BSrNdvLW.js +1 -0
- package/payload/server/public/assets/{dagre-KV5264BT-Dk7osC4w.js → dagre-KV5264BT-CBuRCJE7.js} +1 -1
- package/payload/server/public/assets/{dagre-CUhFHAG8.js → dagre-hSIMHJcO.js} +1 -1
- package/payload/server/public/assets/data-3n20lir5.js +1 -0
- package/payload/server/public/assets/{diagram-5BDNPKRD-CdOEJbjg.js → diagram-5BDNPKRD-dbMrH0le.js} +1 -1
- package/payload/server/public/assets/{diagram-G4DWMVQ6-Dyx0MQk6.js → diagram-G4DWMVQ6-DBc6hs9a.js} +1 -1
- package/payload/server/public/assets/{diagram-MMDJMWI5-Dw6sxLh8.js → diagram-MMDJMWI5-ubSGybUk.js} +1 -1
- package/payload/server/public/assets/{diagram-TYMM5635-Bm3GRH-1.js → diagram-TYMM5635-WxL3cv-9.js} +1 -1
- package/payload/server/public/assets/{erDiagram-SMLLAGMA-CAJ3Z9uD.js → erDiagram-SMLLAGMA-CrBBOAN8.js} +1 -1
- package/payload/server/public/assets/{flowDiagram-DWJPFMVM-NrVUcQ2d.js → flowDiagram-DWJPFMVM-CXUq5Hzt.js} +1 -1
- package/payload/server/public/assets/{ganttDiagram-T4ZO3ILL-28YyrNXj.js → ganttDiagram-T4ZO3ILL-vTCBnYVS.js} +1 -1
- package/payload/server/public/assets/{gitGraphDiagram-UUTBAWPF-D73FSDE8.js → gitGraphDiagram-UUTBAWPF-BHrj_bRa.js} +1 -1
- package/payload/server/public/assets/graph-ChwMpxn_.js +1 -0
- package/payload/server/public/assets/graph-labels-CUgJgIwl.js +1 -0
- package/payload/server/public/assets/{graphlib-n_bUAhub.js → graphlib-CDO75l2X.js} +1 -1
- package/payload/server/public/assets/{infoDiagram-42DDH7IO-rFap2NoE.js → infoDiagram-42DDH7IO-CcQdaOhi.js} +1 -1
- package/payload/server/public/assets/{ishikawaDiagram-UXIWVN3A-KKAj626l.js → ishikawaDiagram-UXIWVN3A-3KvltFgV.js} +1 -1
- package/payload/server/public/assets/{journeyDiagram-VCZTEJTY-Do6hVThi.js → journeyDiagram-VCZTEJTY-DZCXaGFE.js} +1 -1
- package/payload/server/public/assets/{kanban-definition-6JOO6SKY-C38qlZwy.js → kanban-definition-6JOO6SKY-DAIAKy1-.js} +1 -1
- package/payload/server/public/assets/{lib-D-jcbjd4.js → lib-CCLURO3E.js} +1 -1
- package/payload/server/public/assets/{line-DsDSEpXU.js → line-C8iCE2Wx.js} +1 -1
- package/payload/server/public/assets/{mermaid-parser.core-Cvzc7ePL.js → mermaid-parser.core-ElvUhnV9.js} +1 -1
- package/payload/server/public/assets/{mermaid.core-BSZExB1C.js → mermaid.core-7dSH31Ao.js} +3 -3
- package/payload/server/public/assets/{mindmap-definition-QFDTVHPH-CrS3b5u5.js → mindmap-definition-QFDTVHPH-FZ0rcfac.js} +1 -1
- package/payload/server/public/assets/{page-C438DjPa.js → page-D3bFmqF4.js} +1 -1
- package/payload/server/public/assets/{page-fdrpcuv5.js → page-DNjUtT5q.js} +18 -17
- package/payload/server/public/assets/{pieDiagram-DEJITSTG-Dk-6uOEj.js → pieDiagram-DEJITSTG-CDuvMZbO.js} +1 -1
- package/payload/server/public/assets/{public-BK3eOHJs.js → public-g_2skbnY.js} +3 -3
- package/payload/server/public/assets/{quadrantDiagram-34T5L4WZ-CEMK5xQ0.js → quadrantDiagram-34T5L4WZ-xDjhkqNc.js} +1 -1
- package/payload/server/public/assets/{requirementDiagram-MS252O5E-vTyRxQOY.js → requirementDiagram-MS252O5E-BPKpyT0n.js} +1 -1
- package/payload/server/public/assets/{sankeyDiagram-XADWPNL6-s2z8dMpM.js → sankeyDiagram-XADWPNL6-BvFIrihC.js} +1 -1
- package/payload/server/public/assets/{sequenceDiagram-FGHM5R23-locoOgnm.js → sequenceDiagram-FGHM5R23-C6G1oAE0.js} +1 -1
- package/payload/server/public/assets/{stateDiagram-FHFEXIEX-m1d4aD0M.js → stateDiagram-FHFEXIEX-DbOJT_Xh.js} +1 -1
- package/payload/server/public/assets/stateDiagram-v2-QKLJ7IA2-Ce4GFk-H.js +1 -0
- package/payload/server/public/assets/{timeline-definition-GMOUNBTQ-HraDHuOp.js → timeline-definition-GMOUNBTQ-D1DSxHh6.js} +1 -1
- package/payload/server/public/assets/{vennDiagram-DHZGUBPP-DkyF2rFo.js → vennDiagram-DHZGUBPP-BYoSeMdo.js} +1 -1
- package/payload/server/public/assets/{wardleyDiagram-NUSXRM2D-BGScmzP0.js → wardleyDiagram-NUSXRM2D-DH3kZdea.js} +1 -1
- package/payload/server/public/assets/{xychartDiagram-5P7HB3ND-CPJ1JLj2.js → xychartDiagram-5P7HB3ND-C6MROMLG.js} +1 -1
- package/payload/server/public/data.html +5 -5
- package/payload/server/public/graph.html +6 -6
- package/payload/server/public/index.html +8 -8
- package/payload/server/public/public.html +5 -5
- package/payload/server/server.js +531 -118
- package/payload/server/public/assets/channel-CY_QocHj.js +0 -1
- package/payload/server/public/assets/classDiagram-6PBFFD2Q-BtwH3Rsu.js +0 -1
- package/payload/server/public/assets/classDiagram-v2-HSJHXN6E-tCRstAZ-.js +0 -1
- package/payload/server/public/assets/clone-Bc-lmwcQ.js +0 -1
- package/payload/server/public/assets/data-B3WPMQdR.js +0 -1
- package/payload/server/public/assets/graph-B6c5nlBL.js +0 -1
- package/payload/server/public/assets/graph-labels-BMqBNCYc.js +0 -1
- package/payload/server/public/assets/stateDiagram-v2-QKLJ7IA2-B7eWHz7x.js +0 -1
- /package/payload/server/public/assets/{ChatInput-D62NmYfJ.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 @@
|
|
|
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.
|
|
5
|
-
*
|
|
6
|
-
* `
|
|
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;
|
|
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 (
|
|
21
|
-
|
|
22
|
-
|
|
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
|
-
|
|
28
|
-
if (
|
|
29
|
-
|
|
30
|
-
|
|
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;
|
|
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": [
|