@theronap/cortex-mcp 0.4.5 → 0.4.6
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/bin/cortex-mcp.mjs +1 -1
- package/lib/capture.mjs +28 -9
- package/lib/server.mjs +28 -0
- package/package.json +1 -1
package/bin/cortex-mcp.mjs
CHANGED
package/lib/capture.mjs
CHANGED
|
@@ -25,23 +25,42 @@ function readStdin() {
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
// Claude transcripts are JSONL; pull human-readable text from the tail so the
|
|
28
|
-
// server has real content to summarize (not raw tool JSON).
|
|
28
|
+
// server has real content to summarize (not raw tool JSON). ALSO scan the FULL
|
|
29
|
+
// file for lifecycle-verb lines (§4b status recall — a "we're pausing X" said at
|
|
30
|
+
// minute 5 of a 2-hour session must reach the server's status prefilter even
|
|
31
|
+
// though only the tail ships) and append the flagged spans.
|
|
32
|
+
const LIFECYCLE_RE = /\b(paus\w*|resum\w*|shipp?\w*|kill\w*|cancel\w*|on hold|sunset\w*)\b/i
|
|
33
|
+
|
|
29
34
|
function transcriptTail(path) {
|
|
30
35
|
let raw = ''
|
|
31
36
|
try { raw = readFileSync(path, 'utf8') } catch { return '' }
|
|
32
|
-
const
|
|
33
|
-
const
|
|
34
|
-
for (const line of lines) {
|
|
37
|
+
const jsonLines = raw.split('\n').filter(Boolean)
|
|
38
|
+
const textOf = (line) => {
|
|
35
39
|
try {
|
|
36
40
|
const obj = JSON.parse(line)
|
|
37
41
|
const content = obj?.message?.content ?? obj?.content
|
|
38
|
-
if (typeof content === 'string')
|
|
39
|
-
|
|
40
|
-
for (const c of content) if (c?.type === 'text' && c.text) texts.push(c.text)
|
|
41
|
-
}
|
|
42
|
+
if (typeof content === 'string') return [content]
|
|
43
|
+
if (Array.isArray(content)) return content.filter((c) => c?.type === 'text' && c.text).map((c) => c.text)
|
|
42
44
|
} catch { /* skip non-JSON */ }
|
|
45
|
+
return []
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const texts = jsonLines.slice(-60).flatMap(textOf)
|
|
49
|
+
const tail = texts.join('\n').slice(-6000)
|
|
50
|
+
|
|
51
|
+
// Full-session status-candidate spans (last 12 matching lines, capped).
|
|
52
|
+
const flagged = []
|
|
53
|
+
for (const line of jsonLines) {
|
|
54
|
+
for (const t of textOf(line)) {
|
|
55
|
+
for (const tl of t.split('\n')) {
|
|
56
|
+
if (LIFECYCLE_RE.test(tl)) flagged.push(tl.trim().slice(0, 200))
|
|
57
|
+
}
|
|
58
|
+
}
|
|
43
59
|
}
|
|
44
|
-
|
|
60
|
+
const spans = flagged.slice(-12).join('\n').slice(0, 1200)
|
|
61
|
+
return spans && !tail.includes(spans)
|
|
62
|
+
? `${tail}\n\n[status-candidate lines from the full session]\n${spans}`
|
|
63
|
+
: tail
|
|
45
64
|
}
|
|
46
65
|
|
|
47
66
|
export async function runCapture() {
|
package/lib/server.mjs
CHANGED
|
@@ -102,6 +102,34 @@ export async function runServer(version) {
|
|
|
102
102
|
},
|
|
103
103
|
)
|
|
104
104
|
|
|
105
|
+
server.registerTool(
|
|
106
|
+
'who_knows',
|
|
107
|
+
{
|
|
108
|
+
title: 'Who knows about X',
|
|
109
|
+
description: 'Ranked teammates who have visibly worked on a topic, with evidence — derived only from records you are permitted to see.',
|
|
110
|
+
inputSchema: { topic: z.string().describe('the topic/system/skill, e.g. "the embedding pipeline" or "stripe webhooks"') },
|
|
111
|
+
},
|
|
112
|
+
async ({ topic }) => {
|
|
113
|
+
let res
|
|
114
|
+
try {
|
|
115
|
+
res = await fetchCortex(`${BASE}/api/who-knows?q=${encodeURIComponent(topic)}`, {
|
|
116
|
+
headers: { Authorization: `Bearer ${TOKEN}` },
|
|
117
|
+
})
|
|
118
|
+
} catch (e) {
|
|
119
|
+
return { content: [{ type: 'text', text: `Could not look up experts: ${e.message}` }] }
|
|
120
|
+
}
|
|
121
|
+
if (!res.ok) {
|
|
122
|
+
const d = classify(res.status, res.headers.get('content-type'), await res.text(), res.headers.get('x-vercel-id'))
|
|
123
|
+
return { content: [{ type: 'text', text: `Could not look up experts: ${d.message}` }] }
|
|
124
|
+
}
|
|
125
|
+
const { experts } = await res.json()
|
|
126
|
+
if (!experts?.length) return { content: [{ type: 'text', text: `No one visible to you has recorded work about "${topic}".` }] }
|
|
127
|
+
const lines = experts.map((e, i) =>
|
|
128
|
+
`${i + 1}. ${e.name} — ${e.count} visible record${e.count === 1 ? '' : 's'}\n e.g. ${e.evidence.join(' · ')}`)
|
|
129
|
+
return { content: [{ type: 'text', text: `People with visible work on "${topic}":\n${lines.join('\n')}` }] }
|
|
130
|
+
},
|
|
131
|
+
)
|
|
132
|
+
|
|
105
133
|
server.registerTool(
|
|
106
134
|
'set_record_privacy',
|
|
107
135
|
{
|