@nookplot/mcp 0.4.113 → 0.4.115
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/README.md +293 -293
- package/SKILL.md +145 -145
- package/dist/auth.d.ts +112 -5
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +355 -54
- package/dist/auth.js.map +1 -1
- package/dist/gateway.d.ts.map +1 -1
- package/dist/gateway.js +5 -1
- package/dist/gateway.js.map +1 -1
- package/dist/index.d.ts +12 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +648 -51
- package/dist/index.js.map +1 -1
- package/dist/profileName.d.ts +65 -0
- package/dist/profileName.d.ts.map +1 -0
- package/dist/profileName.js +114 -0
- package/dist/profileName.js.map +1 -0
- package/dist/server.js +81 -81
- package/dist/setup.js +7 -7
- package/dist/syncSessions.d.ts +84 -0
- package/dist/syncSessions.d.ts.map +1 -0
- package/dist/syncSessions.js +260 -0
- package/dist/syncSessions.js.map +1 -0
- package/dist/syncSessionsExtractor.d.ts +123 -0
- package/dist/syncSessionsExtractor.d.ts.map +1 -0
- package/dist/syncSessionsExtractor.js +362 -0
- package/dist/syncSessionsExtractor.js.map +1 -0
- package/dist/syncSessionsState.d.ts +89 -0
- package/dist/syncSessionsState.d.ts.map +1 -0
- package/dist/syncSessionsState.js +145 -0
- package/dist/syncSessionsState.js.map +1 -0
- package/dist/tools/cognitiveWorkspace.d.ts.map +1 -1
- package/dist/tools/cognitiveWorkspace.js +30 -0
- package/dist/tools/cognitiveWorkspace.js.map +1 -1
- package/dist/tools/ecosystem.d.ts.map +1 -1
- package/dist/tools/ecosystem.js +1 -5
- package/dist/tools/ecosystem.js.map +1 -1
- package/dist/tools/forgePresets.d.ts +7 -2
- package/dist/tools/forgePresets.d.ts.map +1 -1
- package/dist/tools/forgePresets.js +133 -3
- package/dist/tools/forgePresets.js.map +1 -1
- package/dist/tools/knowledgeGraph.js +1 -1
- package/dist/tools/knowledgeGraph.js.map +1 -1
- package/dist/tools/memory.d.ts.map +1 -1
- package/dist/tools/memory.js +0 -33
- package/dist/tools/memory.js.map +1 -1
- package/dist/tools/miningPipeline.d.ts +6 -2
- package/dist/tools/miningPipeline.d.ts.map +1 -1
- package/dist/tools/miningPipeline.js +392 -3
- package/dist/tools/miningPipeline.js.map +1 -1
- package/dist/tools/onchain.d.ts.map +1 -1
- package/dist/tools/onchain.js +133 -19
- package/dist/tools/onchain.js.map +1 -1
- package/dist/tools/papers.d.ts.map +1 -1
- package/dist/tools/papers.js +16 -0
- package/dist/tools/papers.js.map +1 -1
- package/dist/tools/read.d.ts.map +1 -1
- package/dist/tools/read.js +27 -6
- package/dist/tools/read.js.map +1 -1
- package/dist/tools/reasoningWork.js +60 -60
- package/dist/tools/swarms.d.ts.map +1 -1
- package/dist/tools/swarms.js +21 -1
- package/dist/tools/swarms.js.map +1 -1
- package/dist/tools/tokens.d.ts.map +1 -1
- package/dist/tools/tokens.js +8 -3
- package/dist/tools/tokens.js.map +1 -1
- package/package.json +96 -96
- package/skills/hermes/nookplot/DESCRIPTION.md +59 -0
- package/skills/hermes/nookplot/daemon/SKILL.md +103 -0
- package/skills/hermes/nookplot/learn/SKILL.md +131 -0
- package/skills/hermes/nookplot/mine/SKILL.md +111 -0
- package/skills/hermes/nookplot/social/SKILL.md +104 -0
- package/skills/hermes/nookplot/sync/SKILL.md +110 -0
- package/skills/learn/SKILL.md +70 -70
- package/skills/mine/SKILL.md +85 -85
- package/skills/nookplot/SKILL.md +222 -222
- package/skills/social/SKILL.md +84 -84
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Phase 2b — session post-processor.
|
|
3
|
+
*
|
|
4
|
+
* `nookplot-mcp sync-sessions` walks `~/.hermes/sessions/session_*.json`,
|
|
5
|
+
* finds sessions we haven't processed yet, extracts findings + reasoning
|
|
6
|
+
* traces heuristically, and POSTs each to the Phase 2c capture queue at
|
|
7
|
+
* `/v1/me/captures`. Each POST goes through the same sybil gate +
|
|
8
|
+
* ContentScanner + rate-limit that the realtime MCP tools already use —
|
|
9
|
+
* this file is a thin driver, not a new write surface.
|
|
10
|
+
*
|
|
11
|
+
* Safety net positioning:
|
|
12
|
+
* - The MCP tools in Phase 2a run DURING the session; this subcommand
|
|
13
|
+
* runs AFTER. Together they ensure that even if the agent forgot to
|
|
14
|
+
* call `nookplot_capture_finding` during work, the synthesis it
|
|
15
|
+
* produced doesn't get lost.
|
|
16
|
+
* - Everything goes through the 24h review queue, so the user can
|
|
17
|
+
* still reject anything the heuristic misidentified as a finding.
|
|
18
|
+
* - Dedup is two-layered: `processed_sessions.json` skips whole
|
|
19
|
+
* sessions on the next run, and the gateway's UNIQUE (agent_address,
|
|
20
|
+
* kind, content_hash) index blocks exact-duplicate bodies across
|
|
21
|
+
* different sessions too.
|
|
22
|
+
*
|
|
23
|
+
* @module syncSessions
|
|
24
|
+
*/
|
|
25
|
+
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
26
|
+
import { homedir } from "node:os";
|
|
27
|
+
import { join } from "node:path";
|
|
28
|
+
import { extractFromSession } from "./syncSessionsExtractor.js";
|
|
29
|
+
import { defaultStatePath, isSessionProcessed, isItemAlreadyCaptured, loadState, markSessionProcessed, saveState, } from "./syncSessionsState.js";
|
|
30
|
+
// ---------------------------------------------------------------------------
|
|
31
|
+
// File discovery
|
|
32
|
+
// ---------------------------------------------------------------------------
|
|
33
|
+
/**
|
|
34
|
+
* Default sessions directory. Tests override via `opts.sessionsDir`; in
|
|
35
|
+
* production this is where Hermes v0.8.0 writes session JSONs.
|
|
36
|
+
*/
|
|
37
|
+
function defaultSessionsDir() {
|
|
38
|
+
return join(homedir(), ".hermes", "sessions");
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Session files are named `session_<YYYYMMDD>_<HHMMSS>_<hex>.json`.
|
|
42
|
+
* We ignore `request_dump_*.json` (Hermes's per-request snapshots) since
|
|
43
|
+
* those are noisy duplicates of the session data with much more tool
|
|
44
|
+
* metadata — we only want the clean session stream.
|
|
45
|
+
*/
|
|
46
|
+
function isSessionFile(filename) {
|
|
47
|
+
return /^session_\d{8}_\d{6}_[a-f0-9]+\.json$/.test(filename);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Extract a stable session id from a Hermes session file. We prefer the
|
|
51
|
+
* in-file `session_id` (which Hermes guarantees unique per chat) but
|
|
52
|
+
* fall back to the filename-derived id if the file is malformed.
|
|
53
|
+
*/
|
|
54
|
+
function sessionIdOf(session, filename) {
|
|
55
|
+
if (typeof session.session_id === "string" && session.session_id.length > 0) {
|
|
56
|
+
return session.session_id;
|
|
57
|
+
}
|
|
58
|
+
// filename is `session_<id>.json`, strip the prefix + suffix.
|
|
59
|
+
return filename.replace(/^session_/, "").replace(/\.json$/, "");
|
|
60
|
+
}
|
|
61
|
+
// ---------------------------------------------------------------------------
|
|
62
|
+
// Gateway POST
|
|
63
|
+
// ---------------------------------------------------------------------------
|
|
64
|
+
/**
|
|
65
|
+
* Send one extracted item to the gateway's capture queue. Returns the
|
|
66
|
+
* queue row id on success, an error string on failure.
|
|
67
|
+
*
|
|
68
|
+
* We deliberately tolerate the gateway responding with a "duplicate"
|
|
69
|
+
* path — `POST /v1/me/captures` is idempotent per the Phase 2c design
|
|
70
|
+
* (same content-hash returns the existing row), so a re-run of
|
|
71
|
+
* `sync-sessions` that hits an already-captured item is NOT an error.
|
|
72
|
+
*/
|
|
73
|
+
async function postCapture(opts, gatewayUrl, item, sessionId, agentAddress) {
|
|
74
|
+
const fetchFn = opts._fetch ?? fetch;
|
|
75
|
+
const timeoutMs = opts.timeoutMs ?? 15_000;
|
|
76
|
+
const controller = new AbortController();
|
|
77
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
78
|
+
try {
|
|
79
|
+
const res = await fetchFn(`${gatewayUrl.replace(/\/$/, "")}/v1/me/captures`, {
|
|
80
|
+
method: "POST",
|
|
81
|
+
headers: {
|
|
82
|
+
"Content-Type": "application/json",
|
|
83
|
+
Authorization: `Bearer ${opts.credentials.apiKey}`,
|
|
84
|
+
},
|
|
85
|
+
body: JSON.stringify({
|
|
86
|
+
kind: item.kind,
|
|
87
|
+
payload: item.payload,
|
|
88
|
+
agentAddress,
|
|
89
|
+
sourceSessionId: sessionId,
|
|
90
|
+
}),
|
|
91
|
+
signal: controller.signal,
|
|
92
|
+
});
|
|
93
|
+
if (!res.ok) {
|
|
94
|
+
const body = await res.text().catch(() => "");
|
|
95
|
+
return {
|
|
96
|
+
error: `HTTP ${res.status}: ${body.slice(0, 200)}`,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
const parsed = (await res.json());
|
|
100
|
+
if (typeof parsed.id !== "string") {
|
|
101
|
+
return { error: "Gateway response missing `id` field" };
|
|
102
|
+
}
|
|
103
|
+
return { captureId: parsed.id };
|
|
104
|
+
}
|
|
105
|
+
catch (err) {
|
|
106
|
+
return { error: err instanceof Error ? err.message : String(err) };
|
|
107
|
+
}
|
|
108
|
+
finally {
|
|
109
|
+
clearTimeout(timer);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
// ---------------------------------------------------------------------------
|
|
113
|
+
// Orchestration
|
|
114
|
+
// ---------------------------------------------------------------------------
|
|
115
|
+
/**
|
|
116
|
+
* Main entry point. Walks sessions, extracts, posts. Returns a summary
|
|
117
|
+
* caller can print. Tests drive this directly with mocked `_fetch` +
|
|
118
|
+
* `sessionsDir` + `statePath`.
|
|
119
|
+
*/
|
|
120
|
+
export async function syncSessions(opts) {
|
|
121
|
+
const gatewayUrl = opts.gatewayUrl ??
|
|
122
|
+
process.env.NOOKPLOT_GATEWAY_URL ??
|
|
123
|
+
"https://gateway.nookplot.com";
|
|
124
|
+
const sessionsDir = opts.sessionsDir ?? defaultSessionsDir();
|
|
125
|
+
const statePath = opts.statePath ?? defaultStatePath();
|
|
126
|
+
const limit = opts.limit ?? 10;
|
|
127
|
+
const scopedAgentAddress = opts.scopedAgentAddress ?? process.env.NOOKPLOT_AGENT_ADDRESS ?? undefined;
|
|
128
|
+
const result = {
|
|
129
|
+
inspected: 0,
|
|
130
|
+
processed: 0,
|
|
131
|
+
skipped: 0,
|
|
132
|
+
failed: 0,
|
|
133
|
+
capturesCreated: 0,
|
|
134
|
+
perSession: [],
|
|
135
|
+
};
|
|
136
|
+
if (!existsSync(sessionsDir)) {
|
|
137
|
+
// Fresh install — no sessions yet. Not an error, just nothing to do.
|
|
138
|
+
return result;
|
|
139
|
+
}
|
|
140
|
+
let state = loadState(statePath);
|
|
141
|
+
// Sort files oldest → newest so resumed runs process in chronological
|
|
142
|
+
// order, matching the natural "replay of the user's work" semantics.
|
|
143
|
+
const files = readdirSync(sessionsDir)
|
|
144
|
+
.filter(isSessionFile)
|
|
145
|
+
.map((name) => {
|
|
146
|
+
const full = join(sessionsDir, name);
|
|
147
|
+
const stat = statSync(full);
|
|
148
|
+
return { name, full, mtimeMs: stat.mtimeMs };
|
|
149
|
+
})
|
|
150
|
+
.sort((a, b) => a.mtimeMs - b.mtimeMs);
|
|
151
|
+
for (const file of files) {
|
|
152
|
+
if (result.processed >= limit)
|
|
153
|
+
break;
|
|
154
|
+
result.inspected += 1;
|
|
155
|
+
// --since filter: skip anything older than the cutoff.
|
|
156
|
+
if (opts.since && file.mtimeMs < opts.since.getTime()) {
|
|
157
|
+
result.skipped += 1;
|
|
158
|
+
result.perSession.push({
|
|
159
|
+
sessionId: file.name,
|
|
160
|
+
filePath: file.full,
|
|
161
|
+
status: "skipped",
|
|
162
|
+
extracted: 0,
|
|
163
|
+
captured: 0,
|
|
164
|
+
errors: [],
|
|
165
|
+
skipReason: "older than --since cutoff",
|
|
166
|
+
});
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
// Parse the session.
|
|
170
|
+
let session;
|
|
171
|
+
try {
|
|
172
|
+
const raw = readFileSync(file.full, "utf8");
|
|
173
|
+
session = JSON.parse(raw);
|
|
174
|
+
}
|
|
175
|
+
catch (err) {
|
|
176
|
+
result.failed += 1;
|
|
177
|
+
result.perSession.push({
|
|
178
|
+
sessionId: file.name,
|
|
179
|
+
filePath: file.full,
|
|
180
|
+
status: "failed",
|
|
181
|
+
extracted: 0,
|
|
182
|
+
captured: 0,
|
|
183
|
+
errors: [
|
|
184
|
+
`parse: ${err instanceof Error ? err.message : String(err)}`,
|
|
185
|
+
],
|
|
186
|
+
});
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
const sid = sessionIdOf(session, file.name);
|
|
190
|
+
// Already-processed fast path. --force bypasses this but still dedups
|
|
191
|
+
// on the item level below, so we never double-POST known items.
|
|
192
|
+
if (!opts.force && isSessionProcessed(state, sid)) {
|
|
193
|
+
result.skipped += 1;
|
|
194
|
+
result.perSession.push({
|
|
195
|
+
sessionId: sid,
|
|
196
|
+
filePath: file.full,
|
|
197
|
+
status: "skipped",
|
|
198
|
+
extracted: 0,
|
|
199
|
+
captured: 0,
|
|
200
|
+
errors: [],
|
|
201
|
+
skipReason: "already processed (use --force to reprocess)",
|
|
202
|
+
});
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
const extracted = extractFromSession(session);
|
|
206
|
+
const itemResults = [];
|
|
207
|
+
const errors = [];
|
|
208
|
+
let captured = 0;
|
|
209
|
+
for (const item of extracted) {
|
|
210
|
+
// Item-level dedup for --force re-runs: if we know the gateway
|
|
211
|
+
// already has this hash from a prior pass, skip the POST entirely.
|
|
212
|
+
if (opts.force && isItemAlreadyCaptured(state, sid, item.hash)) {
|
|
213
|
+
captured += 1; // count it as already-captured for the summary
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
216
|
+
if (opts.dryRun) {
|
|
217
|
+
itemResults.push({ hash: item.hash, kind: item.kind });
|
|
218
|
+
captured += 1;
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
const postResult = await postCapture(opts, gatewayUrl, item, sid, scopedAgentAddress);
|
|
222
|
+
if ("captureId" in postResult) {
|
|
223
|
+
itemResults.push({
|
|
224
|
+
hash: item.hash,
|
|
225
|
+
kind: item.kind,
|
|
226
|
+
captureId: postResult.captureId,
|
|
227
|
+
});
|
|
228
|
+
captured += 1;
|
|
229
|
+
}
|
|
230
|
+
else {
|
|
231
|
+
itemResults.push({
|
|
232
|
+
hash: item.hash,
|
|
233
|
+
kind: item.kind,
|
|
234
|
+
error: postResult.error,
|
|
235
|
+
});
|
|
236
|
+
errors.push(`${item.kind}: ${postResult.error}`);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
// Record this session (successes + failures) so we don't re-process.
|
|
240
|
+
// Failures are still recorded: on the next run we skip the whole
|
|
241
|
+
// session unless --force is used. If the user wants to retry just
|
|
242
|
+
// the failed items they run with --force.
|
|
243
|
+
if (!opts.dryRun) {
|
|
244
|
+
state = markSessionProcessed(state, sid, itemResults);
|
|
245
|
+
saveState(state, statePath);
|
|
246
|
+
}
|
|
247
|
+
result.processed += 1;
|
|
248
|
+
result.capturesCreated += captured;
|
|
249
|
+
result.perSession.push({
|
|
250
|
+
sessionId: sid,
|
|
251
|
+
filePath: file.full,
|
|
252
|
+
status: "processed",
|
|
253
|
+
extracted: extracted.length,
|
|
254
|
+
captured,
|
|
255
|
+
errors,
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
return result;
|
|
259
|
+
}
|
|
260
|
+
//# sourceMappingURL=syncSessions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"syncSessions.js","sourceRoot":"","sources":["../src/syncSessions.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Phase 2b — heuristic extractor for Hermes session files.
|
|
3
|
+
*
|
|
4
|
+
* Given a parsed Hermes session JSON (shape observed in
|
|
5
|
+
* `~/.hermes/sessions/session_*.json`), produces a list of
|
|
6
|
+
* capture-queue-ready items without calling an LLM. The LLM path is
|
|
7
|
+
* intentionally deferred — the plan suggests using the user's configured
|
|
8
|
+
* provider, but:
|
|
9
|
+
*
|
|
10
|
+
* - Shipping an extractor with zero external deps means `sync-sessions`
|
|
11
|
+
* just works the moment it's installed, no BYOK setup required.
|
|
12
|
+
* - Heuristic misses are caught by the gateway's quality gate +
|
|
13
|
+
* ContentScanner, so low-value captures don't pollute the KG.
|
|
14
|
+
* - The code below is structured around a pure function
|
|
15
|
+
* `extractFromSession(sessionJson) -> ExtractedItem[]`, so the LLM
|
|
16
|
+
* strategy can slot in later behind the same interface.
|
|
17
|
+
*
|
|
18
|
+
* Heuristic rules (conservative on purpose — we'd rather under-capture
|
|
19
|
+
* than spam the review queue):
|
|
20
|
+
*
|
|
21
|
+
* - A FINDING is extracted when a session has ≥2 tool-call turns
|
|
22
|
+
* followed by an assistant text turn. The final assistant text is
|
|
23
|
+
* the body; the first user message is the title / taskSummary.
|
|
24
|
+
* - A REASONING TRACE is extracted when a session has ≥2 assistant
|
|
25
|
+
* text turns interleaved with tool calls. Each text turn becomes a
|
|
26
|
+
* step; the last becomes the conclusion.
|
|
27
|
+
* - Body must be ≥200 chars for findings, ≥50 for conclusions.
|
|
28
|
+
* Shorter syntheses aren't worth queuing.
|
|
29
|
+
* - Tool-call outputs are NEVER used as the body — only the assistant's
|
|
30
|
+
* own text. This is the Phase 2d §6 mitigation against session
|
|
31
|
+
* transcript poisoning.
|
|
32
|
+
*
|
|
33
|
+
* @module syncSessionsExtractor
|
|
34
|
+
*/
|
|
35
|
+
/** One message from a Hermes session `messages[]` array. */
|
|
36
|
+
export interface HermesMessage {
|
|
37
|
+
role: "user" | "assistant" | "tool" | "system";
|
|
38
|
+
content?: string | null;
|
|
39
|
+
/** Present on assistant turns that invoked tools. */
|
|
40
|
+
tool_calls?: Array<{
|
|
41
|
+
id?: string;
|
|
42
|
+
type?: string;
|
|
43
|
+
function?: {
|
|
44
|
+
name?: string;
|
|
45
|
+
arguments?: string;
|
|
46
|
+
};
|
|
47
|
+
}>;
|
|
48
|
+
/** Present on `tool` turns, referring back to the assistant's call. */
|
|
49
|
+
tool_call_id?: string;
|
|
50
|
+
/** Present on `tool` turns — the tool that produced the result. */
|
|
51
|
+
name?: string;
|
|
52
|
+
}
|
|
53
|
+
/** The subset of a Hermes session file we care about. */
|
|
54
|
+
export interface HermesSession {
|
|
55
|
+
session_id?: string;
|
|
56
|
+
model?: string;
|
|
57
|
+
messages?: HermesMessage[];
|
|
58
|
+
session_start?: string;
|
|
59
|
+
last_updated?: string;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* What we extract. Shape mirrors the `payload` field the capture-queue
|
|
63
|
+
* endpoint expects, plus a `kind` discriminator + a content hash we use
|
|
64
|
+
* for local dedup.
|
|
65
|
+
*/
|
|
66
|
+
export type ExtractedItem = {
|
|
67
|
+
kind: "finding";
|
|
68
|
+
hash: string;
|
|
69
|
+
payload: {
|
|
70
|
+
title: string;
|
|
71
|
+
body: string;
|
|
72
|
+
sources?: string[];
|
|
73
|
+
domain?: string;
|
|
74
|
+
tags?: string[];
|
|
75
|
+
};
|
|
76
|
+
} | {
|
|
77
|
+
kind: "reasoning";
|
|
78
|
+
hash: string;
|
|
79
|
+
payload: {
|
|
80
|
+
taskSummary: string;
|
|
81
|
+
steps: Array<{
|
|
82
|
+
step: string;
|
|
83
|
+
rationale?: string;
|
|
84
|
+
}>;
|
|
85
|
+
conclusion: string;
|
|
86
|
+
modelUsed?: string;
|
|
87
|
+
};
|
|
88
|
+
};
|
|
89
|
+
/**
|
|
90
|
+
* Extract a FINDING from a session that researched + synthesized.
|
|
91
|
+
*
|
|
92
|
+
* Preconditions checked inside:
|
|
93
|
+
* - ≥2 tool-call invocations (otherwise it's a trivial lookup, not a finding)
|
|
94
|
+
* - Final assistant text is ≥200 chars
|
|
95
|
+
* - There's a user prompt to use as the title
|
|
96
|
+
*
|
|
97
|
+
* Returns `null` if the session doesn't pattern-match. That's the common
|
|
98
|
+
* case — most sessions are one-shot Q&A, not research.
|
|
99
|
+
*/
|
|
100
|
+
export declare function extractFindingHeuristic(session: HermesSession): ExtractedItem | null;
|
|
101
|
+
/**
|
|
102
|
+
* Extract a REASONING TRACE from a session with multi-step thinking.
|
|
103
|
+
*
|
|
104
|
+
* Preconditions:
|
|
105
|
+
* - ≥2 non-empty assistant text turns (steps + conclusion)
|
|
106
|
+
* - ≥2 tool-call invocations OR total message count ≥ 5 (so a
|
|
107
|
+
* pure-text chat-of-thought still qualifies if it had structure)
|
|
108
|
+
* - Conclusion length ≥50 chars
|
|
109
|
+
*
|
|
110
|
+
* The `steps` array is built from every assistant text turn EXCEPT the
|
|
111
|
+
* last one. The last becomes the `conclusion`. If there's only one text
|
|
112
|
+
* turn, we bail — single-step "reasoning" is just a finding.
|
|
113
|
+
*/
|
|
114
|
+
export declare function extractReasoningHeuristic(session: HermesSession): ExtractedItem | null;
|
|
115
|
+
/**
|
|
116
|
+
* Main extraction entry point. Runs both extractors and returns every
|
|
117
|
+
* item that matched. It is valid for a single session to yield both a
|
|
118
|
+
* finding AND a reasoning trace — research sessions that pivoted midway
|
|
119
|
+
* have both a synthesis (the finding) and a process worth remembering
|
|
120
|
+
* (the reasoning trace). The gateway dedup guards prevent actual dupes.
|
|
121
|
+
*/
|
|
122
|
+
export declare function extractFromSession(session: HermesSession): ExtractedItem[];
|
|
123
|
+
//# sourceMappingURL=syncSessionsExtractor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"syncSessionsExtractor.d.ts","sourceRoot":"","sources":["../src/syncSessionsExtractor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AASH,4DAA4D;AAC5D,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,GAAG,WAAW,GAAG,MAAM,GAAG,QAAQ,CAAC;IAC/C,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,qDAAqD;IACrD,UAAU,CAAC,EAAE,KAAK,CAAC;QACjB,EAAE,CAAC,EAAE,MAAM,CAAC;QACZ,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,QAAQ,CAAC,EAAE;YAAE,IAAI,CAAC,EAAE,MAAM,CAAC;YAAC,SAAS,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC;KAClD,CAAC,CAAC;IACH,uEAAuE;IACvE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,mEAAmE;IACnE,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,yDAAyD;AACzD,MAAM,WAAW,aAAa;IAC5B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,aAAa,EAAE,CAAC;IAC3B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;;;GAIG;AACH,MAAM,MAAM,aAAa,GACrB;IACE,IAAI,EAAE,SAAS,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE;QACP,KAAK,EAAE,MAAM,CAAC;QACd,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;QACnB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;KACjB,CAAC;CACH,GACD;IACE,IAAI,EAAE,WAAW,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE;QACP,WAAW,EAAE,MAAM,CAAC;QACpB,KAAK,EAAE,KAAK,CAAC;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,SAAS,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC,CAAC;QACnD,UAAU,EAAE,MAAM,CAAC;QACnB,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB,CAAC;CACH,CAAC;AAyNN;;;;;;;;;;GAUG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,aAAa,GAAG,aAAa,GAAG,IAAI,CA0CpF;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,aAAa,GAAG,aAAa,GAAG,IAAI,CAyCtF;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,aAAa,GAAG,aAAa,EAAE,CAO1E"}
|