engrm 0.4.39 → 0.4.41
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 +6 -0
- package/dist/cli.js +55 -0
- package/dist/hooks/session-start.js +1 -1
- package/dist/hooks/stop.js +1 -1
- package/dist/server.js +1515 -1403
- package/package.json +1 -1
package/dist/server.js
CHANGED
|
@@ -20,6 +20,8 @@ var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
|
20
20
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
21
21
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
22
22
|
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
23
|
+
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
|
|
24
|
+
import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
|
|
23
25
|
|
|
24
26
|
// node_modules/zod/v4/classic/external.js
|
|
25
27
|
var exports_external = {};
|
|
@@ -13555,6 +13557,7 @@ function date4(params) {
|
|
|
13555
13557
|
config(en_default());
|
|
13556
13558
|
// src/server.ts
|
|
13557
13559
|
import { createServer } from "node:http";
|
|
13560
|
+
import { randomUUID } from "node:crypto";
|
|
13558
13561
|
|
|
13559
13562
|
// src/config.ts
|
|
13560
13563
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
@@ -19479,18 +19482,23 @@ function getCaptureStatus(db, input = {}) {
|
|
|
19479
19482
|
const codexHooks = join3(home, ".codex", "hooks.json");
|
|
19480
19483
|
const opencodeConfig = join3(home, ".config", "opencode", "opencode.json");
|
|
19481
19484
|
const opencodePlugin = join3(home, ".config", "opencode", "plugins", "engrm.js");
|
|
19485
|
+
const openclawConfig = join3(home, ".openclaw", "openclaw.json");
|
|
19486
|
+
const openclawPlugin = join3(home, ".openclaw", "extensions", "engrm", "openclaw.plugin.json");
|
|
19482
19487
|
const config2 = configExists() ? loadConfig() : null;
|
|
19483
19488
|
const claudeJsonContent = existsSync3(claudeJson) ? readFileSync3(claudeJson, "utf-8") : "";
|
|
19484
19489
|
const claudeSettingsContent = existsSync3(claudeSettings) ? readFileSync3(claudeSettings, "utf-8") : "";
|
|
19485
19490
|
const codexConfigContent = existsSync3(codexConfig) ? readFileSync3(codexConfig, "utf-8") : "";
|
|
19486
19491
|
const codexHooksContent = existsSync3(codexHooks) ? readFileSync3(codexHooks, "utf-8") : "";
|
|
19487
19492
|
const opencodeConfigContent = existsSync3(opencodeConfig) ? readFileSync3(opencodeConfig, "utf-8") : "";
|
|
19493
|
+
const openclawConfigContent = existsSync3(openclawConfig) ? readFileSync3(openclawConfig, "utf-8") : "";
|
|
19488
19494
|
const claudeMcpRegistered = claudeJsonContent.includes('"engrm"');
|
|
19489
19495
|
const claudeHooksRegistered = claudeSettingsContent.includes("engrm") || claudeSettingsContent.includes("session-start") || claudeSettingsContent.includes("user-prompt-submit");
|
|
19490
19496
|
const codexMcpRegistered = codexConfigContent.includes("[mcp_servers.engrm]") || codexConfigContent.includes(`[mcp_servers.${LEGACY_CODEX_SERVER_NAME}]`);
|
|
19491
19497
|
const codexHooksRegistered = codexHooksContent.includes('"SessionStart"') && codexHooksContent.includes('"Stop"');
|
|
19492
19498
|
const opencodeMcpRegistered = opencodeConfigContent.includes('"engrm"') && opencodeConfigContent.includes('"type"') && opencodeConfigContent.includes('"local"');
|
|
19493
19499
|
const opencodePluginRegistered = existsSync3(opencodePlugin);
|
|
19500
|
+
const openclawMcpRegistered = hasOpenClawMcpRegistration(openclawConfigContent);
|
|
19501
|
+
const openclawPluginRegistered = existsSync3(openclawPlugin);
|
|
19494
19502
|
let claudeHookCount = 0;
|
|
19495
19503
|
let claudeSessionStartHook = false;
|
|
19496
19504
|
let claudeUserPromptHook = false;
|
|
@@ -19580,6 +19588,8 @@ function getCaptureStatus(db, input = {}) {
|
|
|
19580
19588
|
codex_session_start_hook: codexSessionStartHook,
|
|
19581
19589
|
codex_stop_hook: codexStopHook,
|
|
19582
19590
|
codex_raw_chronology_supported: false,
|
|
19591
|
+
openclaw_mcp_registered: openclawMcpRegistered,
|
|
19592
|
+
openclaw_plugin_registered: openclawPluginRegistered,
|
|
19583
19593
|
opencode_mcp_registered: opencodeMcpRegistered,
|
|
19584
19594
|
opencode_plugin_registered: opencodePluginRegistered,
|
|
19585
19595
|
recent_user_prompts: recentUserPrompts,
|
|
@@ -19600,6 +19610,16 @@ function parseNullableInt(value) {
|
|
|
19600
19610
|
const parsed = Number.parseInt(value, 10);
|
|
19601
19611
|
return Number.isFinite(parsed) ? parsed : null;
|
|
19602
19612
|
}
|
|
19613
|
+
function hasOpenClawMcpRegistration(content) {
|
|
19614
|
+
if (!content)
|
|
19615
|
+
return false;
|
|
19616
|
+
try {
|
|
19617
|
+
const parsed = JSON.parse(content);
|
|
19618
|
+
return Boolean(parsed.mcp?.servers?.engrm);
|
|
19619
|
+
} catch {
|
|
19620
|
+
return content.includes('"mcp"') && content.includes('"servers"') && content.includes('"engrm"');
|
|
19621
|
+
}
|
|
19622
|
+
}
|
|
19603
19623
|
|
|
19604
19624
|
// src/tools/capture-quality.ts
|
|
19605
19625
|
function getCaptureQuality(db, input = {}) {
|
|
@@ -23060,388 +23080,389 @@ process.on("SIGTERM", () => {
|
|
|
23060
23080
|
db.close();
|
|
23061
23081
|
process.exit(0);
|
|
23062
23082
|
});
|
|
23063
|
-
|
|
23064
|
-
|
|
23065
|
-
|
|
23066
|
-
|
|
23067
|
-
|
|
23068
|
-
|
|
23069
|
-
|
|
23070
|
-
|
|
23071
|
-
|
|
23072
|
-
|
|
23073
|
-
|
|
23074
|
-
|
|
23075
|
-
|
|
23076
|
-
|
|
23077
|
-
|
|
23078
|
-
|
|
23079
|
-
|
|
23080
|
-
|
|
23081
|
-
|
|
23082
|
-
|
|
23083
|
-
|
|
23084
|
-
|
|
23085
|
-
|
|
23086
|
-
|
|
23087
|
-
|
|
23088
|
-
|
|
23089
|
-
|
|
23090
|
-
|
|
23083
|
+
function buildServer() {
|
|
23084
|
+
const server = new McpServer({
|
|
23085
|
+
name: "engrm",
|
|
23086
|
+
version: "0.4.41"
|
|
23087
|
+
});
|
|
23088
|
+
server.tool("save_observation", "Directly save a durable memory item now. Use this when something should be remembered on purpose instead of waiting for an end-of-session digest.", {
|
|
23089
|
+
type: exports_external.enum([
|
|
23090
|
+
"bugfix",
|
|
23091
|
+
"discovery",
|
|
23092
|
+
"decision",
|
|
23093
|
+
"pattern",
|
|
23094
|
+
"change",
|
|
23095
|
+
"feature",
|
|
23096
|
+
"refactor",
|
|
23097
|
+
"digest",
|
|
23098
|
+
"message"
|
|
23099
|
+
]),
|
|
23100
|
+
title: exports_external.string().describe("Brief title"),
|
|
23101
|
+
narrative: exports_external.string().optional().describe("What happened and why"),
|
|
23102
|
+
facts: exports_external.array(exports_external.string()).optional().describe("Key facts"),
|
|
23103
|
+
concepts: exports_external.array(exports_external.string()).optional().describe("Tags"),
|
|
23104
|
+
files_read: exports_external.array(exports_external.string()).optional().describe("Files read (project-relative)"),
|
|
23105
|
+
files_modified: exports_external.array(exports_external.string()).optional().describe("Files modified (project-relative)"),
|
|
23106
|
+
sensitivity: exports_external.enum(["shared", "personal", "secret"]).optional(),
|
|
23107
|
+
session_id: exports_external.string().optional(),
|
|
23108
|
+
supersedes: exports_external.number().optional().describe("ID of observation this replaces")
|
|
23109
|
+
}, async (params) => {
|
|
23110
|
+
const result = await saveObservation(db, config2, { ...params, agent: getDetectedAgent() });
|
|
23111
|
+
if (!result.success) {
|
|
23112
|
+
return {
|
|
23113
|
+
content: [
|
|
23114
|
+
{
|
|
23115
|
+
type: "text",
|
|
23116
|
+
text: `Not saved: ${result.reason}`
|
|
23117
|
+
}
|
|
23118
|
+
]
|
|
23119
|
+
};
|
|
23120
|
+
}
|
|
23121
|
+
if (result.merged_into) {
|
|
23122
|
+
return {
|
|
23123
|
+
content: [
|
|
23124
|
+
{
|
|
23125
|
+
type: "text",
|
|
23126
|
+
text: `Merged into observation #${result.merged_into} (quality: ${result.quality_score?.toFixed(2)})`
|
|
23127
|
+
}
|
|
23128
|
+
]
|
|
23129
|
+
};
|
|
23130
|
+
}
|
|
23131
|
+
let supersessionNote = "";
|
|
23132
|
+
if (params.supersedes && result.observation_id) {
|
|
23133
|
+
const superseded = db.supersedeObservation(params.supersedes, result.observation_id);
|
|
23134
|
+
if (superseded) {
|
|
23135
|
+
supersessionNote = `, supersedes #${params.supersedes}`;
|
|
23136
|
+
}
|
|
23137
|
+
}
|
|
23091
23138
|
return {
|
|
23092
23139
|
content: [
|
|
23093
23140
|
{
|
|
23094
23141
|
type: "text",
|
|
23095
|
-
text: `
|
|
23142
|
+
text: `Saved observation #${result.observation_id} (quality: ${result.quality_score?.toFixed(2)}${supersessionNote})`
|
|
23096
23143
|
}
|
|
23097
23144
|
]
|
|
23098
23145
|
};
|
|
23099
|
-
}
|
|
23100
|
-
|
|
23146
|
+
});
|
|
23147
|
+
server.tool("plugin_catalog", "List Engrm plugin manifests so tools can produce memory in a compatible shape", {
|
|
23148
|
+
surface: exports_external.enum(PLUGIN_SURFACES).optional().describe("Optional surface filter")
|
|
23149
|
+
}, async (params) => {
|
|
23150
|
+
const manifests = listPluginManifests(params.surface);
|
|
23151
|
+
const lines = manifests.map((manifest) => `- ${manifest.id} (${manifest.kind}) -> produces ${manifest.produces.join(", ")}; surfaces ${manifest.surfaces.join(", ")}`);
|
|
23101
23152
|
return {
|
|
23102
23153
|
content: [
|
|
23103
23154
|
{
|
|
23104
23155
|
type: "text",
|
|
23105
|
-
text: `
|
|
23106
|
-
}
|
|
23107
|
-
]
|
|
23108
|
-
};
|
|
23109
|
-
}
|
|
23110
|
-
let supersessionNote = "";
|
|
23111
|
-
if (params.supersedes && result.observation_id) {
|
|
23112
|
-
const superseded = db.supersedeObservation(params.supersedes, result.observation_id);
|
|
23113
|
-
if (superseded) {
|
|
23114
|
-
supersessionNote = `, supersedes #${params.supersedes}`;
|
|
23115
|
-
}
|
|
23116
|
-
}
|
|
23117
|
-
return {
|
|
23118
|
-
content: [
|
|
23119
|
-
{
|
|
23120
|
-
type: "text",
|
|
23121
|
-
text: `Saved observation #${result.observation_id} (quality: ${result.quality_score?.toFixed(2)}${supersessionNote})`
|
|
23122
|
-
}
|
|
23123
|
-
]
|
|
23124
|
-
};
|
|
23125
|
-
});
|
|
23126
|
-
server.tool("plugin_catalog", "List Engrm plugin manifests so tools can produce memory in a compatible shape", {
|
|
23127
|
-
surface: exports_external.enum(PLUGIN_SURFACES).optional().describe("Optional surface filter")
|
|
23128
|
-
}, async (params) => {
|
|
23129
|
-
const manifests = listPluginManifests(params.surface);
|
|
23130
|
-
const lines = manifests.map((manifest) => `- ${manifest.id} (${manifest.kind}) -> produces ${manifest.produces.join(", ")}; surfaces ${manifest.surfaces.join(", ")}`);
|
|
23131
|
-
return {
|
|
23132
|
-
content: [
|
|
23133
|
-
{
|
|
23134
|
-
type: "text",
|
|
23135
|
-
text: `Engrm plugin spec: ${PLUGIN_SPEC_VERSION}
|
|
23156
|
+
text: `Engrm plugin spec: ${PLUGIN_SPEC_VERSION}
|
|
23136
23157
|
|
|
23137
23158
|
` + (params.surface ? `Surface filter: ${params.surface}
|
|
23138
23159
|
|
|
23139
23160
|
` : "") + (lines.length > 0 ? lines.join(`
|
|
23140
23161
|
`) : "No plugin manifests available.")
|
|
23141
|
-
}
|
|
23142
|
-
]
|
|
23143
|
-
};
|
|
23144
|
-
});
|
|
23145
|
-
server.tool("save_plugin_memory", "Save reduced plugin output as durable Engrm memory with stable plugin provenance", {
|
|
23146
|
-
plugin_id: exports_external.string().describe("Stable plugin identifier such as 'engrm.git-diff'"),
|
|
23147
|
-
type: exports_external.enum([
|
|
23148
|
-
"bugfix",
|
|
23149
|
-
"discovery",
|
|
23150
|
-
"decision",
|
|
23151
|
-
"pattern",
|
|
23152
|
-
"change",
|
|
23153
|
-
"feature",
|
|
23154
|
-
"refactor",
|
|
23155
|
-
"digest",
|
|
23156
|
-
"message"
|
|
23157
|
-
]),
|
|
23158
|
-
title: exports_external.string().describe("Short durable memory title"),
|
|
23159
|
-
summary: exports_external.string().optional().describe("Reduced summary of what happened and why it matters"),
|
|
23160
|
-
facts: exports_external.array(exports_external.string()).optional().describe("Reusable facts worth remembering"),
|
|
23161
|
-
tags: exports_external.array(exports_external.string()).optional().describe("Plugin-specific tags"),
|
|
23162
|
-
source: exports_external.string().optional().describe("Upstream source like git, openclaw, ci, or issues"),
|
|
23163
|
-
source_refs: exports_external.array(exports_external.object({
|
|
23164
|
-
kind: exports_external.enum(["file", "url", "ticket", "commit", "thread", "command", "other"]),
|
|
23165
|
-
value: exports_external.string()
|
|
23166
|
-
})).optional().describe("Pointers back to the original evidence"),
|
|
23167
|
-
surfaces: exports_external.array(exports_external.enum(PLUGIN_SURFACES)).optional().describe("Engrm surfaces this memory is designed to feed"),
|
|
23168
|
-
files_read: exports_external.array(exports_external.string()).optional().describe("Files read (project-relative when possible)"),
|
|
23169
|
-
files_modified: exports_external.array(exports_external.string()).optional().describe("Files modified (project-relative when possible)"),
|
|
23170
|
-
sensitivity: exports_external.enum(["shared", "personal", "secret"]).optional(),
|
|
23171
|
-
session_id: exports_external.string().optional(),
|
|
23172
|
-
cwd: exports_external.string().optional().describe("Project root for relative-path normalization")
|
|
23173
|
-
}, async (params) => {
|
|
23174
|
-
const result = await savePluginMemory(db, config2, {
|
|
23175
|
-
...params,
|
|
23176
|
-
agent: getDetectedAgent()
|
|
23177
|
-
});
|
|
23178
|
-
if (!result.success) {
|
|
23179
|
-
return {
|
|
23180
|
-
content: [
|
|
23181
|
-
{
|
|
23182
|
-
type: "text",
|
|
23183
|
-
text: `Not saved: ${result.reason}`
|
|
23184
|
-
}
|
|
23185
|
-
]
|
|
23186
|
-
};
|
|
23187
|
-
}
|
|
23188
|
-
if (result.merged_into) {
|
|
23189
|
-
return {
|
|
23190
|
-
content: [
|
|
23191
|
-
{
|
|
23192
|
-
type: "text",
|
|
23193
|
-
text: `Merged plugin memory into observation #${result.merged_into} (quality: ${result.quality_score?.toFixed(2)})`
|
|
23194
23162
|
}
|
|
23195
23163
|
]
|
|
23196
23164
|
};
|
|
23197
|
-
}
|
|
23198
|
-
return {
|
|
23199
|
-
content: [
|
|
23200
|
-
{
|
|
23201
|
-
type: "text",
|
|
23202
|
-
text: `Saved plugin memory as observation #${result.observation_id} (quality: ${result.quality_score?.toFixed(2)})`
|
|
23203
|
-
}
|
|
23204
|
-
]
|
|
23205
|
-
};
|
|
23206
|
-
});
|
|
23207
|
-
server.tool("capture_git_diff", "Reduce a git diff into a durable Engrm memory object and save it with plugin provenance", {
|
|
23208
|
-
diff: exports_external.string().describe("Unified git diff text"),
|
|
23209
|
-
summary: exports_external.string().optional().describe("Optional human summary or commit-style title"),
|
|
23210
|
-
files: exports_external.array(exports_external.string()).optional().describe("Optional changed file paths if already known"),
|
|
23211
|
-
session_id: exports_external.string().optional(),
|
|
23212
|
-
cwd: exports_external.string().optional().describe("Project root for relative-path normalization")
|
|
23213
|
-
}, async (params) => {
|
|
23214
|
-
const reduced = reduceGitDiffToMemory({
|
|
23215
|
-
...params,
|
|
23216
|
-
cwd: params.cwd ?? process.cwd(),
|
|
23217
|
-
agent: getDetectedAgent()
|
|
23218
23165
|
});
|
|
23219
|
-
|
|
23220
|
-
|
|
23221
|
-
|
|
23222
|
-
|
|
23223
|
-
|
|
23224
|
-
|
|
23225
|
-
|
|
23226
|
-
|
|
23227
|
-
|
|
23228
|
-
|
|
23229
|
-
|
|
23230
|
-
|
|
23231
|
-
|
|
23232
|
-
|
|
23233
|
-
|
|
23234
|
-
|
|
23235
|
-
|
|
23236
|
-
|
|
23237
|
-
|
|
23238
|
-
|
|
23239
|
-
|
|
23240
|
-
})
|
|
23241
|
-
|
|
23242
|
-
|
|
23243
|
-
|
|
23244
|
-
|
|
23245
|
-
|
|
23246
|
-
|
|
23247
|
-
|
|
23248
|
-
|
|
23249
|
-
|
|
23250
|
-
|
|
23251
|
-
staged: params.staged
|
|
23166
|
+
server.tool("save_plugin_memory", "Save reduced plugin output as durable Engrm memory with stable plugin provenance", {
|
|
23167
|
+
plugin_id: exports_external.string().describe("Stable plugin identifier such as 'engrm.git-diff'"),
|
|
23168
|
+
type: exports_external.enum([
|
|
23169
|
+
"bugfix",
|
|
23170
|
+
"discovery",
|
|
23171
|
+
"decision",
|
|
23172
|
+
"pattern",
|
|
23173
|
+
"change",
|
|
23174
|
+
"feature",
|
|
23175
|
+
"refactor",
|
|
23176
|
+
"digest",
|
|
23177
|
+
"message"
|
|
23178
|
+
]),
|
|
23179
|
+
title: exports_external.string().describe("Short durable memory title"),
|
|
23180
|
+
summary: exports_external.string().optional().describe("Reduced summary of what happened and why it matters"),
|
|
23181
|
+
facts: exports_external.array(exports_external.string()).optional().describe("Reusable facts worth remembering"),
|
|
23182
|
+
tags: exports_external.array(exports_external.string()).optional().describe("Plugin-specific tags"),
|
|
23183
|
+
source: exports_external.string().optional().describe("Upstream source like git, openclaw, ci, or issues"),
|
|
23184
|
+
source_refs: exports_external.array(exports_external.object({
|
|
23185
|
+
kind: exports_external.enum(["file", "url", "ticket", "commit", "thread", "command", "other"]),
|
|
23186
|
+
value: exports_external.string()
|
|
23187
|
+
})).optional().describe("Pointers back to the original evidence"),
|
|
23188
|
+
surfaces: exports_external.array(exports_external.enum(PLUGIN_SURFACES)).optional().describe("Engrm surfaces this memory is designed to feed"),
|
|
23189
|
+
files_read: exports_external.array(exports_external.string()).optional().describe("Files read (project-relative when possible)"),
|
|
23190
|
+
files_modified: exports_external.array(exports_external.string()).optional().describe("Files modified (project-relative when possible)"),
|
|
23191
|
+
sensitivity: exports_external.enum(["shared", "personal", "secret"]).optional(),
|
|
23192
|
+
session_id: exports_external.string().optional(),
|
|
23193
|
+
cwd: exports_external.string().optional().describe("Project root for relative-path normalization")
|
|
23194
|
+
}, async (params) => {
|
|
23195
|
+
const result = await savePluginMemory(db, config2, {
|
|
23196
|
+
...params,
|
|
23197
|
+
agent: getDetectedAgent()
|
|
23252
23198
|
});
|
|
23253
|
-
|
|
23254
|
-
|
|
23255
|
-
|
|
23256
|
-
|
|
23257
|
-
|
|
23258
|
-
|
|
23259
|
-
|
|
23260
|
-
|
|
23261
|
-
|
|
23262
|
-
|
|
23263
|
-
|
|
23199
|
+
if (!result.success) {
|
|
23200
|
+
return {
|
|
23201
|
+
content: [
|
|
23202
|
+
{
|
|
23203
|
+
type: "text",
|
|
23204
|
+
text: `Not saved: ${result.reason}`
|
|
23205
|
+
}
|
|
23206
|
+
]
|
|
23207
|
+
};
|
|
23208
|
+
}
|
|
23209
|
+
if (result.merged_into) {
|
|
23210
|
+
return {
|
|
23211
|
+
content: [
|
|
23212
|
+
{
|
|
23213
|
+
type: "text",
|
|
23214
|
+
text: `Merged plugin memory into observation #${result.merged_into} (quality: ${result.quality_score?.toFixed(2)})`
|
|
23215
|
+
}
|
|
23216
|
+
]
|
|
23217
|
+
};
|
|
23218
|
+
}
|
|
23264
23219
|
return {
|
|
23265
23220
|
content: [
|
|
23266
23221
|
{
|
|
23267
23222
|
type: "text",
|
|
23268
|
-
text: `
|
|
23223
|
+
text: `Saved plugin memory as observation #${result.observation_id} (quality: ${result.quality_score?.toFixed(2)})`
|
|
23269
23224
|
}
|
|
23270
23225
|
]
|
|
23271
23226
|
};
|
|
23272
|
-
}
|
|
23273
|
-
const reduced = reduceGitDiffToMemory({
|
|
23274
|
-
diff: worktree.diff,
|
|
23275
|
-
summary: params.summary,
|
|
23276
|
-
files: worktree.files,
|
|
23277
|
-
session_id: params.session_id,
|
|
23278
|
-
cwd: worktree.cwd,
|
|
23279
|
-
agent: getDetectedAgent()
|
|
23280
23227
|
});
|
|
23281
|
-
|
|
23282
|
-
|
|
23283
|
-
|
|
23284
|
-
|
|
23285
|
-
|
|
23286
|
-
|
|
23287
|
-
|
|
23288
|
-
|
|
23289
|
-
|
|
23290
|
-
};
|
|
23291
|
-
}
|
|
23292
|
-
return {
|
|
23293
|
-
content: [
|
|
23294
|
-
{
|
|
23295
|
-
type: "text",
|
|
23296
|
-
text: `Saved ${params.staged ? "staged" : "worktree"} git diff as observation #${result.observation_id} ` + `(${reduced.type}: ${reduced.title})`
|
|
23297
|
-
}
|
|
23298
|
-
]
|
|
23299
|
-
};
|
|
23300
|
-
});
|
|
23301
|
-
server.tool("capture_repo_scan", "Run a lightweight repository scan and save reduced findings as durable memory. Best for quick architecture, risk, or implementation scans.", {
|
|
23302
|
-
cwd: exports_external.string().optional().describe("Repo path to scan. Defaults to the current working directory."),
|
|
23303
|
-
focus: exports_external.array(exports_external.string()).optional().describe("Optional topics to bias the scan toward, for example 'billing', 'auth', or 'validation'."),
|
|
23304
|
-
max_findings: exports_external.number().optional().describe("Maximum findings to keep before reduction."),
|
|
23305
|
-
summary: exports_external.string().optional().describe("Optional human summary for the saved memory."),
|
|
23306
|
-
session_id: exports_external.string().optional().describe("Optional session ID to link this scan to active work.")
|
|
23307
|
-
}, async (params) => {
|
|
23308
|
-
let scan;
|
|
23309
|
-
try {
|
|
23310
|
-
scan = captureRepoScan({
|
|
23228
|
+
server.tool("capture_git_diff", "Reduce a git diff into a durable Engrm memory object and save it with plugin provenance", {
|
|
23229
|
+
diff: exports_external.string().describe("Unified git diff text"),
|
|
23230
|
+
summary: exports_external.string().optional().describe("Optional human summary or commit-style title"),
|
|
23231
|
+
files: exports_external.array(exports_external.string()).optional().describe("Optional changed file paths if already known"),
|
|
23232
|
+
session_id: exports_external.string().optional(),
|
|
23233
|
+
cwd: exports_external.string().optional().describe("Project root for relative-path normalization")
|
|
23234
|
+
}, async (params) => {
|
|
23235
|
+
const reduced = reduceGitDiffToMemory({
|
|
23236
|
+
...params,
|
|
23311
23237
|
cwd: params.cwd ?? process.cwd(),
|
|
23312
|
-
|
|
23313
|
-
max_findings: params.max_findings
|
|
23238
|
+
agent: getDetectedAgent()
|
|
23314
23239
|
});
|
|
23315
|
-
|
|
23240
|
+
const result = await savePluginMemory(db, config2, reduced);
|
|
23241
|
+
if (!result.success) {
|
|
23242
|
+
return {
|
|
23243
|
+
content: [
|
|
23244
|
+
{
|
|
23245
|
+
type: "text",
|
|
23246
|
+
text: `Not saved: ${result.reason}`
|
|
23247
|
+
}
|
|
23248
|
+
]
|
|
23249
|
+
};
|
|
23250
|
+
}
|
|
23251
|
+
const reducedFacts = reduced.facts && reduced.facts.length > 0 ? `
|
|
23252
|
+
Facts: ${reduced.facts.join("; ")}` : "";
|
|
23316
23253
|
return {
|
|
23317
23254
|
content: [
|
|
23318
23255
|
{
|
|
23319
23256
|
type: "text",
|
|
23320
|
-
text: `
|
|
23257
|
+
text: `Saved git diff as observation #${result.observation_id} ` + `(${reduced.type}: ${reduced.title})${reducedFacts}`
|
|
23321
23258
|
}
|
|
23322
23259
|
]
|
|
23323
23260
|
};
|
|
23324
|
-
}
|
|
23325
|
-
|
|
23261
|
+
});
|
|
23262
|
+
server.tool("capture_git_worktree", "Capture the current git worktree as durable memory. Best for saving a meaningful local diff before context is lost.", {
|
|
23263
|
+
cwd: exports_external.string().optional().describe("Git repo path. Defaults to the current working directory."),
|
|
23264
|
+
staged: exports_external.boolean().optional().describe("If true, capture staged changes instead of unstaged worktree changes."),
|
|
23265
|
+
summary: exports_external.string().optional().describe("Optional human summary or commit-style title to steer the saved memory."),
|
|
23266
|
+
session_id: exports_external.string().optional().describe("Optional session ID to link this capture to active work.")
|
|
23267
|
+
}, async (params) => {
|
|
23268
|
+
let worktree;
|
|
23269
|
+
try {
|
|
23270
|
+
worktree = captureGitWorktree({
|
|
23271
|
+
cwd: params.cwd ?? process.cwd(),
|
|
23272
|
+
staged: params.staged
|
|
23273
|
+
});
|
|
23274
|
+
} catch (error48) {
|
|
23275
|
+
return {
|
|
23276
|
+
content: [
|
|
23277
|
+
{
|
|
23278
|
+
type: "text",
|
|
23279
|
+
text: `Not captured: ${error48 instanceof Error ? error48.message : "unable to read git worktree"}`
|
|
23280
|
+
}
|
|
23281
|
+
]
|
|
23282
|
+
};
|
|
23283
|
+
}
|
|
23284
|
+
if (!worktree.diff.trim()) {
|
|
23285
|
+
return {
|
|
23286
|
+
content: [
|
|
23287
|
+
{
|
|
23288
|
+
type: "text",
|
|
23289
|
+
text: `No ${params.staged ? "staged" : "unstaged"} git diff found in ${worktree.cwd}`
|
|
23290
|
+
}
|
|
23291
|
+
]
|
|
23292
|
+
};
|
|
23293
|
+
}
|
|
23294
|
+
const reduced = reduceGitDiffToMemory({
|
|
23295
|
+
diff: worktree.diff,
|
|
23296
|
+
summary: params.summary,
|
|
23297
|
+
files: worktree.files,
|
|
23298
|
+
session_id: params.session_id,
|
|
23299
|
+
cwd: worktree.cwd,
|
|
23300
|
+
agent: getDetectedAgent()
|
|
23301
|
+
});
|
|
23302
|
+
const result = await savePluginMemory(db, config2, reduced);
|
|
23303
|
+
if (!result.success) {
|
|
23304
|
+
return {
|
|
23305
|
+
content: [
|
|
23306
|
+
{
|
|
23307
|
+
type: "text",
|
|
23308
|
+
text: `Not saved: ${result.reason}`
|
|
23309
|
+
}
|
|
23310
|
+
]
|
|
23311
|
+
};
|
|
23312
|
+
}
|
|
23326
23313
|
return {
|
|
23327
23314
|
content: [
|
|
23328
23315
|
{
|
|
23329
23316
|
type: "text",
|
|
23330
|
-
text: `
|
|
23317
|
+
text: `Saved ${params.staged ? "staged" : "worktree"} git diff as observation #${result.observation_id} ` + `(${reduced.type}: ${reduced.title})`
|
|
23331
23318
|
}
|
|
23332
23319
|
]
|
|
23333
23320
|
};
|
|
23334
|
-
}
|
|
23335
|
-
const reduced = reduceRepoScanToMemory({
|
|
23336
|
-
summary: params.summary,
|
|
23337
|
-
findings: scan.findings,
|
|
23338
|
-
session_id: params.session_id,
|
|
23339
|
-
cwd: scan.cwd,
|
|
23340
|
-
agent: getDetectedAgent()
|
|
23341
23321
|
});
|
|
23342
|
-
|
|
23343
|
-
|
|
23322
|
+
server.tool("capture_repo_scan", "Run a lightweight repository scan and save reduced findings as durable memory. Best for quick architecture, risk, or implementation scans.", {
|
|
23323
|
+
cwd: exports_external.string().optional().describe("Repo path to scan. Defaults to the current working directory."),
|
|
23324
|
+
focus: exports_external.array(exports_external.string()).optional().describe("Optional topics to bias the scan toward, for example 'billing', 'auth', or 'validation'."),
|
|
23325
|
+
max_findings: exports_external.number().optional().describe("Maximum findings to keep before reduction."),
|
|
23326
|
+
summary: exports_external.string().optional().describe("Optional human summary for the saved memory."),
|
|
23327
|
+
session_id: exports_external.string().optional().describe("Optional session ID to link this scan to active work.")
|
|
23328
|
+
}, async (params) => {
|
|
23329
|
+
let scan;
|
|
23330
|
+
try {
|
|
23331
|
+
scan = captureRepoScan({
|
|
23332
|
+
cwd: params.cwd ?? process.cwd(),
|
|
23333
|
+
focus: params.focus,
|
|
23334
|
+
max_findings: params.max_findings
|
|
23335
|
+
});
|
|
23336
|
+
} catch (error48) {
|
|
23337
|
+
return {
|
|
23338
|
+
content: [
|
|
23339
|
+
{
|
|
23340
|
+
type: "text",
|
|
23341
|
+
text: `Not captured: ${error48 instanceof Error ? error48.message : "unable to scan repository"}`
|
|
23342
|
+
}
|
|
23343
|
+
]
|
|
23344
|
+
};
|
|
23345
|
+
}
|
|
23346
|
+
if (scan.findings.length === 0) {
|
|
23347
|
+
return {
|
|
23348
|
+
content: [
|
|
23349
|
+
{
|
|
23350
|
+
type: "text",
|
|
23351
|
+
text: `No lightweight repo-scan findings found in ${scan.cwd}`
|
|
23352
|
+
}
|
|
23353
|
+
]
|
|
23354
|
+
};
|
|
23355
|
+
}
|
|
23356
|
+
const reduced = reduceRepoScanToMemory({
|
|
23357
|
+
summary: params.summary,
|
|
23358
|
+
findings: scan.findings,
|
|
23359
|
+
session_id: params.session_id,
|
|
23360
|
+
cwd: scan.cwd,
|
|
23361
|
+
agent: getDetectedAgent()
|
|
23362
|
+
});
|
|
23363
|
+
const result = await savePluginMemory(db, config2, reduced);
|
|
23364
|
+
if (!result.success) {
|
|
23365
|
+
return {
|
|
23366
|
+
content: [
|
|
23367
|
+
{
|
|
23368
|
+
type: "text",
|
|
23369
|
+
text: `Not saved: ${result.reason}`
|
|
23370
|
+
}
|
|
23371
|
+
]
|
|
23372
|
+
};
|
|
23373
|
+
}
|
|
23374
|
+
const findingSummary = scan.findings.slice(0, 3).map((finding) => finding.title).join("; ");
|
|
23344
23375
|
return {
|
|
23345
23376
|
content: [
|
|
23346
23377
|
{
|
|
23347
23378
|
type: "text",
|
|
23348
|
-
text: `
|
|
23379
|
+
text: `Saved repo scan as observation #${result.observation_id} ` + `(${reduced.type}: ${reduced.title})` + `${findingSummary ? `
|
|
23380
|
+
Findings: ${findingSummary}` : ""}`
|
|
23349
23381
|
}
|
|
23350
23382
|
]
|
|
23351
23383
|
};
|
|
23352
|
-
}
|
|
23353
|
-
const findingSummary = scan.findings.slice(0, 3).map((finding) => finding.title).join("; ");
|
|
23354
|
-
return {
|
|
23355
|
-
content: [
|
|
23356
|
-
{
|
|
23357
|
-
type: "text",
|
|
23358
|
-
text: `Saved repo scan as observation #${result.observation_id} ` + `(${reduced.type}: ${reduced.title})` + `${findingSummary ? `
|
|
23359
|
-
Findings: ${findingSummary}` : ""}`
|
|
23360
|
-
}
|
|
23361
|
-
]
|
|
23362
|
-
};
|
|
23363
|
-
});
|
|
23364
|
-
server.tool("capture_openclaw_content", "Directly save OpenClaw content, research, and follow-up work as durable memory. Best for preserving posted outcomes, discoveries, and next actions during or right after the run.", {
|
|
23365
|
-
title: exports_external.string().optional().describe("Short content, campaign, or research title."),
|
|
23366
|
-
posted: exports_external.array(exports_external.string()).optional().describe("Concrete posted items or shipped content outcomes."),
|
|
23367
|
-
researched: exports_external.array(exports_external.string()).optional().describe("Research or discovery items worth retaining."),
|
|
23368
|
-
outcomes: exports_external.array(exports_external.string()).optional().describe("Meaningful outcomes from the run."),
|
|
23369
|
-
next_actions: exports_external.array(exports_external.string()).optional().describe("Real follow-up actions that remain."),
|
|
23370
|
-
links: exports_external.array(exports_external.string()).optional().describe("Thread or source URLs tied to the work."),
|
|
23371
|
-
session_id: exports_external.string().optional().describe("Optional session ID to link this memory to active work."),
|
|
23372
|
-
cwd: exports_external.string().optional().describe("Optional project path for attribution.")
|
|
23373
|
-
}, async (params) => {
|
|
23374
|
-
const reduced = reduceOpenClawContentToMemory({
|
|
23375
|
-
...params,
|
|
23376
|
-
cwd: params.cwd ?? process.cwd(),
|
|
23377
|
-
agent: getDetectedAgent()
|
|
23378
23384
|
});
|
|
23379
|
-
|
|
23380
|
-
|
|
23385
|
+
server.tool("capture_openclaw_content", "Directly save OpenClaw content, research, and follow-up work as durable memory. Best for preserving posted outcomes, discoveries, and next actions during or right after the run.", {
|
|
23386
|
+
title: exports_external.string().optional().describe("Short content, campaign, or research title."),
|
|
23387
|
+
posted: exports_external.array(exports_external.string()).optional().describe("Concrete posted items or shipped content outcomes."),
|
|
23388
|
+
researched: exports_external.array(exports_external.string()).optional().describe("Research or discovery items worth retaining."),
|
|
23389
|
+
outcomes: exports_external.array(exports_external.string()).optional().describe("Meaningful outcomes from the run."),
|
|
23390
|
+
next_actions: exports_external.array(exports_external.string()).optional().describe("Real follow-up actions that remain."),
|
|
23391
|
+
links: exports_external.array(exports_external.string()).optional().describe("Thread or source URLs tied to the work."),
|
|
23392
|
+
session_id: exports_external.string().optional().describe("Optional session ID to link this memory to active work."),
|
|
23393
|
+
cwd: exports_external.string().optional().describe("Optional project path for attribution.")
|
|
23394
|
+
}, async (params) => {
|
|
23395
|
+
const reduced = reduceOpenClawContentToMemory({
|
|
23396
|
+
...params,
|
|
23397
|
+
cwd: params.cwd ?? process.cwd(),
|
|
23398
|
+
agent: getDetectedAgent()
|
|
23399
|
+
});
|
|
23400
|
+
const result = await savePluginMemory(db, config2, reduced);
|
|
23401
|
+
if (!result.success) {
|
|
23402
|
+
return {
|
|
23403
|
+
content: [
|
|
23404
|
+
{
|
|
23405
|
+
type: "text",
|
|
23406
|
+
text: `Not saved: ${result.reason}`
|
|
23407
|
+
}
|
|
23408
|
+
]
|
|
23409
|
+
};
|
|
23410
|
+
}
|
|
23381
23411
|
return {
|
|
23382
23412
|
content: [
|
|
23383
23413
|
{
|
|
23384
23414
|
type: "text",
|
|
23385
|
-
text: `
|
|
23415
|
+
text: `Saved OpenClaw content memory as observation #${result.observation_id} ` + `(${reduced.type}: ${reduced.title})`
|
|
23386
23416
|
}
|
|
23387
23417
|
]
|
|
23388
23418
|
};
|
|
23389
|
-
}
|
|
23390
|
-
return {
|
|
23391
|
-
content: [
|
|
23392
|
-
{
|
|
23393
|
-
type: "text",
|
|
23394
|
-
text: `Saved OpenClaw content memory as observation #${result.observation_id} ` + `(${reduced.type}: ${reduced.title})`
|
|
23395
|
-
}
|
|
23396
|
-
]
|
|
23397
|
-
};
|
|
23398
|
-
});
|
|
23399
|
-
server.tool("search", "Search memory for observations", {
|
|
23400
|
-
query: exports_external.string().describe("Search query"),
|
|
23401
|
-
project_scoped: exports_external.boolean().optional().describe("Scope to project (default: true)"),
|
|
23402
|
-
limit: exports_external.number().optional().describe("Max results (default: 10)")
|
|
23403
|
-
}, async (params) => {
|
|
23404
|
-
const result = await searchObservations(db, {
|
|
23405
|
-
...params,
|
|
23406
|
-
user_id: config2.user_id
|
|
23407
23419
|
});
|
|
23408
|
-
|
|
23409
|
-
|
|
23410
|
-
|
|
23411
|
-
|
|
23412
|
-
|
|
23420
|
+
server.tool("search", "Search memory for observations", {
|
|
23421
|
+
query: exports_external.string().describe("Search query"),
|
|
23422
|
+
project_scoped: exports_external.boolean().optional().describe("Scope to project (default: true)"),
|
|
23423
|
+
limit: exports_external.number().optional().describe("Max results (default: 10)")
|
|
23424
|
+
}, async (params) => {
|
|
23425
|
+
const result = await searchObservations(db, {
|
|
23426
|
+
...params,
|
|
23427
|
+
user_id: config2.user_id
|
|
23428
|
+
});
|
|
23429
|
+
_lastSearchSessionId = "active";
|
|
23430
|
+
sessionMetrics.searchCount++;
|
|
23431
|
+
sessionMetrics.searchResultsTotal += result.total;
|
|
23432
|
+
persistSessionMetrics();
|
|
23433
|
+
if (result.total === 0) {
|
|
23434
|
+
return {
|
|
23435
|
+
content: [
|
|
23436
|
+
{
|
|
23437
|
+
type: "text",
|
|
23438
|
+
text: result.project ? `No observations found for "${params.query}" in project ${result.project}` : `No observations found for "${params.query}"`
|
|
23439
|
+
}
|
|
23440
|
+
]
|
|
23441
|
+
};
|
|
23442
|
+
}
|
|
23443
|
+
const includeProjectColumn = result.observations.some((obs) => obs.project_name);
|
|
23444
|
+
const header = includeProjectColumn ? "| ID | Type | Q | Title | Project | Created |" : "| ID | Type | Q | Title | Created |";
|
|
23445
|
+
const separator = includeProjectColumn ? "|---|---|---|---|---|---|" : "|---|---|---|---|---|";
|
|
23446
|
+
const rows = result.observations.map((obs) => {
|
|
23447
|
+
const qualityDots = qualityIndicator(obs.quality);
|
|
23448
|
+
const date5 = obs.created_at.split("T")[0];
|
|
23449
|
+
if (includeProjectColumn) {
|
|
23450
|
+
return `| ${obs.id} | ${obs.type} | ${qualityDots} | ${obs.title} | ${obs.project_name ?? "-"} | ${date5} |`;
|
|
23451
|
+
}
|
|
23452
|
+
return `| ${obs.id} | ${obs.type} | ${qualityDots} | ${obs.title} | ${date5} |`;
|
|
23453
|
+
});
|
|
23454
|
+
const previews = result.observations.slice(0, Math.min(3, result.observations.length)).map((obs) => {
|
|
23455
|
+
const preview = formatFactPreview(obs.facts, obs.narrative);
|
|
23456
|
+
const projectSuffix = obs.project_name ? ` [${obs.project_name}]` : "";
|
|
23457
|
+
return preview ? `- #${obs.id} [${obs.type}] ${obs.title}${projectSuffix}: ${preview}` : `- #${obs.id} [${obs.type}] ${obs.title}${projectSuffix}`;
|
|
23458
|
+
});
|
|
23459
|
+
const projectLine = result.project ? `Project: ${result.project}
|
|
23460
|
+
` : "";
|
|
23413
23461
|
return {
|
|
23414
23462
|
content: [
|
|
23415
23463
|
{
|
|
23416
23464
|
type: "text",
|
|
23417
|
-
text:
|
|
23418
|
-
}
|
|
23419
|
-
]
|
|
23420
|
-
};
|
|
23421
|
-
}
|
|
23422
|
-
const includeProjectColumn = result.observations.some((obs) => obs.project_name);
|
|
23423
|
-
const header = includeProjectColumn ? "| ID | Type | Q | Title | Project | Created |" : "| ID | Type | Q | Title | Created |";
|
|
23424
|
-
const separator = includeProjectColumn ? "|---|---|---|---|---|---|" : "|---|---|---|---|---|";
|
|
23425
|
-
const rows = result.observations.map((obs) => {
|
|
23426
|
-
const qualityDots = qualityIndicator(obs.quality);
|
|
23427
|
-
const date5 = obs.created_at.split("T")[0];
|
|
23428
|
-
if (includeProjectColumn) {
|
|
23429
|
-
return `| ${obs.id} | ${obs.type} | ${qualityDots} | ${obs.title} | ${obs.project_name ?? "-"} | ${date5} |`;
|
|
23430
|
-
}
|
|
23431
|
-
return `| ${obs.id} | ${obs.type} | ${qualityDots} | ${obs.title} | ${date5} |`;
|
|
23432
|
-
});
|
|
23433
|
-
const previews = result.observations.slice(0, Math.min(3, result.observations.length)).map((obs) => {
|
|
23434
|
-
const preview = formatFactPreview(obs.facts, obs.narrative);
|
|
23435
|
-
const projectSuffix = obs.project_name ? ` [${obs.project_name}]` : "";
|
|
23436
|
-
return preview ? `- #${obs.id} [${obs.type}] ${obs.title}${projectSuffix}: ${preview}` : `- #${obs.id} [${obs.type}] ${obs.title}${projectSuffix}`;
|
|
23437
|
-
});
|
|
23438
|
-
const projectLine = result.project ? `Project: ${result.project}
|
|
23439
|
-
` : "";
|
|
23440
|
-
return {
|
|
23441
|
-
content: [
|
|
23442
|
-
{
|
|
23443
|
-
type: "text",
|
|
23444
|
-
text: `${projectLine}Found ${result.total} result(s):
|
|
23465
|
+
text: `${projectLine}Found ${result.total} result(s):
|
|
23445
23466
|
|
|
23446
23467
|
${header}
|
|
23447
23468
|
${separator}
|
|
@@ -23451,149 +23472,149 @@ ${rows.join(`
|
|
|
23451
23472
|
Top context:
|
|
23452
23473
|
${previews.join(`
|
|
23453
23474
|
`)}`
|
|
23454
|
-
}
|
|
23455
|
-
]
|
|
23456
|
-
};
|
|
23457
|
-
});
|
|
23458
|
-
server.tool("search_recall", "Search live recall across durable memory and chat together. Best for questions like 'what were we just talking about?'", {
|
|
23459
|
-
query: exports_external.string().describe("Recall query"),
|
|
23460
|
-
project_scoped: exports_external.boolean().optional().describe("Scope to project (default: true)"),
|
|
23461
|
-
limit: exports_external.number().optional().describe("Max results (default: 10)"),
|
|
23462
|
-
cwd: exports_external.string().optional().describe("Optional cwd override for project-scoped recall"),
|
|
23463
|
-
user_id: exports_external.string().optional().describe("Optional user override")
|
|
23464
|
-
}, async (params) => {
|
|
23465
|
-
const result = await searchRecall(db, {
|
|
23466
|
-
query: params.query,
|
|
23467
|
-
project_scoped: params.project_scoped,
|
|
23468
|
-
limit: params.limit,
|
|
23469
|
-
cwd: params.cwd,
|
|
23470
|
-
user_id: params.user_id ?? config2.user_id
|
|
23471
|
-
});
|
|
23472
|
-
if (result.results.length === 0) {
|
|
23473
|
-
return {
|
|
23474
|
-
content: [
|
|
23475
|
-
{
|
|
23476
|
-
type: "text",
|
|
23477
|
-
text: result.project ? `No recall found for "${params.query}" in project ${result.project}` : `No recall found for "${params.query}"`
|
|
23478
23475
|
}
|
|
23479
23476
|
]
|
|
23480
23477
|
};
|
|
23481
|
-
}
|
|
23482
|
-
|
|
23478
|
+
});
|
|
23479
|
+
server.tool("search_recall", "Search live recall across durable memory and chat together. Best for questions like 'what were we just talking about?'", {
|
|
23480
|
+
query: exports_external.string().describe("Recall query"),
|
|
23481
|
+
project_scoped: exports_external.boolean().optional().describe("Scope to project (default: true)"),
|
|
23482
|
+
limit: exports_external.number().optional().describe("Max results (default: 10)"),
|
|
23483
|
+
cwd: exports_external.string().optional().describe("Optional cwd override for project-scoped recall"),
|
|
23484
|
+
user_id: exports_external.string().optional().describe("Optional user override")
|
|
23485
|
+
}, async (params) => {
|
|
23486
|
+
const result = await searchRecall(db, {
|
|
23487
|
+
query: params.query,
|
|
23488
|
+
project_scoped: params.project_scoped,
|
|
23489
|
+
limit: params.limit,
|
|
23490
|
+
cwd: params.cwd,
|
|
23491
|
+
user_id: params.user_id ?? config2.user_id
|
|
23492
|
+
});
|
|
23493
|
+
if (result.results.length === 0) {
|
|
23494
|
+
return {
|
|
23495
|
+
content: [
|
|
23496
|
+
{
|
|
23497
|
+
type: "text",
|
|
23498
|
+
text: result.project ? `No recall found for "${params.query}" in project ${result.project}` : `No recall found for "${params.query}"`
|
|
23499
|
+
}
|
|
23500
|
+
]
|
|
23501
|
+
};
|
|
23502
|
+
}
|
|
23503
|
+
const projectLine = result.project ? `Project: ${result.project}
|
|
23483
23504
|
` : "";
|
|
23484
|
-
|
|
23505
|
+
const summaryLine = `Matches: ${result.results.length} · memory ${result.totals.memory} · chat ${result.totals.chat}
|
|
23485
23506
|
`;
|
|
23486
|
-
|
|
23487
|
-
|
|
23488
|
-
|
|
23489
|
-
|
|
23490
|
-
|
|
23491
|
-
|
|
23492
|
-
|
|
23493
|
-
|
|
23494
|
-
|
|
23495
|
-
|
|
23496
|
-
|
|
23507
|
+
const rows = result.results.map((item) => {
|
|
23508
|
+
const sourceBits = [item.kind];
|
|
23509
|
+
if (item.type)
|
|
23510
|
+
sourceBits.push(item.type);
|
|
23511
|
+
if (item.role)
|
|
23512
|
+
sourceBits.push(item.role);
|
|
23513
|
+
if (item.source_kind)
|
|
23514
|
+
sourceBits.push(item.source_kind);
|
|
23515
|
+
const idBit = item.observation_id ? `#${item.observation_id}` : item.id ? `chat:${item.id}` : "";
|
|
23516
|
+
const title = `${idBit ? `${idBit} ` : ""}${item.title}${item.project_name ? ` (${item.project_name})` : ""}`;
|
|
23517
|
+
return `- [${sourceBits.join(" · ")}] ${title}
|
|
23497
23518
|
${item.detail.slice(0, 220)}`;
|
|
23498
|
-
|
|
23519
|
+
}).join(`
|
|
23499
23520
|
`);
|
|
23500
|
-
return {
|
|
23501
|
-
content: [
|
|
23502
|
-
{
|
|
23503
|
-
type: "text",
|
|
23504
|
-
text: `${projectLine}${summaryLine}Recall search for "${params.query}":
|
|
23505
|
-
${rows}`
|
|
23506
|
-
}
|
|
23507
|
-
]
|
|
23508
|
-
};
|
|
23509
|
-
});
|
|
23510
|
-
server.tool("list_recall_items", "USE FIRST when continuity feels fuzzy. List the best current handoffs, session threads, chat snippets, and memory entries before opening one exact item.", {
|
|
23511
|
-
cwd: exports_external.string().optional().describe("Optional cwd override for project-scoped recall"),
|
|
23512
|
-
project_scoped: exports_external.boolean().optional().describe("Scope to project (default: true)"),
|
|
23513
|
-
user_id: exports_external.string().optional().describe("Optional user override"),
|
|
23514
|
-
limit: exports_external.number().optional().describe("Max recall items to list")
|
|
23515
|
-
}, async (params) => {
|
|
23516
|
-
const result = listRecallItems(db, {
|
|
23517
|
-
cwd: params.cwd ?? process.cwd(),
|
|
23518
|
-
project_scoped: params.project_scoped,
|
|
23519
|
-
user_id: params.user_id ?? config2.user_id,
|
|
23520
|
-
current_device_id: config2.device_id,
|
|
23521
|
-
limit: params.limit
|
|
23522
|
-
});
|
|
23523
|
-
if (result.items.length === 0) {
|
|
23524
23521
|
return {
|
|
23525
23522
|
content: [
|
|
23526
23523
|
{
|
|
23527
23524
|
type: "text",
|
|
23528
|
-
text:
|
|
23525
|
+
text: `${projectLine}${summaryLine}Recall search for "${params.query}":
|
|
23526
|
+
${rows}`
|
|
23529
23527
|
}
|
|
23530
23528
|
]
|
|
23531
23529
|
};
|
|
23532
|
-
}
|
|
23533
|
-
|
|
23530
|
+
});
|
|
23531
|
+
server.tool("list_recall_items", "USE FIRST when continuity feels fuzzy. List the best current handoffs, session threads, chat snippets, and memory entries before opening one exact item.", {
|
|
23532
|
+
cwd: exports_external.string().optional().describe("Optional cwd override for project-scoped recall"),
|
|
23533
|
+
project_scoped: exports_external.boolean().optional().describe("Scope to project (default: true)"),
|
|
23534
|
+
user_id: exports_external.string().optional().describe("Optional user override"),
|
|
23535
|
+
limit: exports_external.number().optional().describe("Max recall items to list")
|
|
23536
|
+
}, async (params) => {
|
|
23537
|
+
const result = listRecallItems(db, {
|
|
23538
|
+
cwd: params.cwd ?? process.cwd(),
|
|
23539
|
+
project_scoped: params.project_scoped,
|
|
23540
|
+
user_id: params.user_id ?? config2.user_id,
|
|
23541
|
+
current_device_id: config2.device_id,
|
|
23542
|
+
limit: params.limit
|
|
23543
|
+
});
|
|
23544
|
+
if (result.items.length === 0) {
|
|
23545
|
+
return {
|
|
23546
|
+
content: [
|
|
23547
|
+
{
|
|
23548
|
+
type: "text",
|
|
23549
|
+
text: result.project ? `No recall items found yet for project ${result.project}` : "No recall items found yet."
|
|
23550
|
+
}
|
|
23551
|
+
]
|
|
23552
|
+
};
|
|
23553
|
+
}
|
|
23554
|
+
const projectLine = result.project ? `Project: ${result.project}
|
|
23534
23555
|
` : "";
|
|
23535
|
-
|
|
23556
|
+
const rows = result.items.map((item) => `- ${item.key} [${item.kind} · ${item.freshness}${item.source_agent ? ` · ${item.source_agent}` : ""}] ${item.title}${item.source_device_id ? ` (${item.source_device_id})` : ""}
|
|
23536
23557
|
${item.detail}`).join(`
|
|
23537
23558
|
`);
|
|
23538
|
-
return {
|
|
23539
|
-
content: [
|
|
23540
|
-
{
|
|
23541
|
-
type: "text",
|
|
23542
|
-
text: `${projectLine}Recall index (${result.continuity_mode} mode):
|
|
23543
|
-
` + `${rows}
|
|
23544
|
-
|
|
23545
|
-
` + `Suggested next step: use load_handoff for handoff:* items, get_observations([...]) for obs:* items, or resume_thread when you want one merged resume point.`
|
|
23546
|
-
}
|
|
23547
|
-
]
|
|
23548
|
-
};
|
|
23549
|
-
});
|
|
23550
|
-
server.tool("load_recall_item", "USE AFTER list_recall_items. Load one exact recall item key so you can inspect a specific handoff, thread, chat message, or memory entry without fuzzy recall guessing.", {
|
|
23551
|
-
key: exports_external.string().describe("Exact recall key from list_recall_items, such as handoff:12, session:sess-1, chat:55, or obs:402"),
|
|
23552
|
-
cwd: exports_external.string().optional().describe("Optional cwd override"),
|
|
23553
|
-
user_id: exports_external.string().optional().describe("Optional user override")
|
|
23554
|
-
}, async (params) => {
|
|
23555
|
-
const result = loadRecallItem(db, {
|
|
23556
|
-
key: params.key,
|
|
23557
|
-
cwd: params.cwd ?? process.cwd(),
|
|
23558
|
-
user_id: params.user_id ?? config2.user_id,
|
|
23559
|
-
current_device_id: config2.device_id
|
|
23560
|
-
});
|
|
23561
|
-
if (!result.payload) {
|
|
23562
23559
|
return {
|
|
23563
23560
|
content: [
|
|
23564
23561
|
{
|
|
23565
23562
|
type: "text",
|
|
23566
|
-
text:
|
|
23563
|
+
text: `${projectLine}Recall index (${result.continuity_mode} mode):
|
|
23564
|
+
` + `${rows}
|
|
23565
|
+
|
|
23566
|
+
` + `Suggested next step: use load_handoff for handoff:* items, get_observations([...]) for obs:* items, or resume_thread when you want one merged resume point.`
|
|
23567
23567
|
}
|
|
23568
23568
|
]
|
|
23569
23569
|
};
|
|
23570
|
-
}
|
|
23571
|
-
|
|
23572
|
-
|
|
23573
|
-
|
|
23574
|
-
|
|
23575
|
-
|
|
23576
|
-
|
|
23570
|
+
});
|
|
23571
|
+
server.tool("load_recall_item", "USE AFTER list_recall_items. Load one exact recall item key so you can inspect a specific handoff, thread, chat message, or memory entry without fuzzy recall guessing.", {
|
|
23572
|
+
key: exports_external.string().describe("Exact recall key from list_recall_items, such as handoff:12, session:sess-1, chat:55, or obs:402"),
|
|
23573
|
+
cwd: exports_external.string().optional().describe("Optional cwd override"),
|
|
23574
|
+
user_id: exports_external.string().optional().describe("Optional user override")
|
|
23575
|
+
}, async (params) => {
|
|
23576
|
+
const result = loadRecallItem(db, {
|
|
23577
|
+
key: params.key,
|
|
23578
|
+
cwd: params.cwd ?? process.cwd(),
|
|
23579
|
+
user_id: params.user_id ?? config2.user_id,
|
|
23580
|
+
current_device_id: config2.device_id
|
|
23581
|
+
});
|
|
23582
|
+
if (!result.payload) {
|
|
23583
|
+
return {
|
|
23584
|
+
content: [
|
|
23585
|
+
{
|
|
23586
|
+
type: "text",
|
|
23587
|
+
text: `No recall item found for ${params.key}`
|
|
23588
|
+
}
|
|
23589
|
+
]
|
|
23590
|
+
};
|
|
23591
|
+
}
|
|
23592
|
+
if (result.payload.type === "handoff") {
|
|
23593
|
+
return {
|
|
23594
|
+
content: [
|
|
23595
|
+
{
|
|
23596
|
+
type: "text",
|
|
23597
|
+
text: `Recall item ${result.key} [handoff]
|
|
23577
23598
|
` + `Title: ${result.title}
|
|
23578
23599
|
` + `Session: ${result.session_id ?? "(unknown)"}
|
|
23579
23600
|
` + `Source: ${result.source_device_id ?? "(unknown)"}
|
|
23580
23601
|
` + `Agent: ${result.source_agent ?? "(unknown)"}
|
|
23581
23602
|
|
|
23582
23603
|
` + `${result.payload.narrative ?? "(no narrative)"}`
|
|
23583
|
-
|
|
23584
|
-
|
|
23585
|
-
|
|
23586
|
-
|
|
23587
|
-
|
|
23588
|
-
|
|
23604
|
+
}
|
|
23605
|
+
]
|
|
23606
|
+
};
|
|
23607
|
+
}
|
|
23608
|
+
if (result.payload.type === "thread") {
|
|
23609
|
+
const outcomes = result.payload.recent_outcomes.length > 0 ? result.payload.recent_outcomes.map((item) => `- ${item}`).join(`
|
|
23589
23610
|
`) : "- (none)";
|
|
23590
|
-
|
|
23611
|
+
const hotFiles = result.payload.hot_files.length > 0 ? result.payload.hot_files.map((item) => `- ${item.path}${item.count > 1 ? ` (${item.count})` : ""}`).join(`
|
|
23591
23612
|
`) : "- (none)";
|
|
23592
|
-
|
|
23593
|
-
|
|
23594
|
-
|
|
23595
|
-
|
|
23596
|
-
|
|
23613
|
+
return {
|
|
23614
|
+
content: [
|
|
23615
|
+
{
|
|
23616
|
+
type: "text",
|
|
23617
|
+
text: `Recall item ${result.key} [thread]
|
|
23597
23618
|
` + `Title: ${result.title}
|
|
23598
23619
|
` + `Session: ${result.session_id ?? "(unknown)"}
|
|
23599
23620
|
` + `Source: ${result.source_device_id ?? "(unknown)"}
|
|
@@ -23606,31 +23627,31 @@ ${outcomes}
|
|
|
23606
23627
|
|
|
23607
23628
|
` + `Hot files:
|
|
23608
23629
|
${hotFiles}`
|
|
23609
|
-
|
|
23610
|
-
|
|
23611
|
-
|
|
23612
|
-
|
|
23613
|
-
|
|
23614
|
-
|
|
23615
|
-
|
|
23616
|
-
|
|
23617
|
-
|
|
23618
|
-
|
|
23630
|
+
}
|
|
23631
|
+
]
|
|
23632
|
+
};
|
|
23633
|
+
}
|
|
23634
|
+
if (result.payload.type === "chat") {
|
|
23635
|
+
return {
|
|
23636
|
+
content: [
|
|
23637
|
+
{
|
|
23638
|
+
type: "text",
|
|
23639
|
+
text: `Recall item ${result.key} [chat]
|
|
23619
23640
|
` + `Title: ${result.title}
|
|
23620
23641
|
` + `Session: ${result.session_id ?? "(unknown)"}
|
|
23621
23642
|
` + `Source: ${result.source_device_id ?? "(unknown)"}
|
|
23622
23643
|
` + `Agent: ${result.source_agent ?? "(unknown)"}
|
|
23623
23644
|
|
|
23624
23645
|
` + `${result.payload.content}`
|
|
23625
|
-
|
|
23626
|
-
|
|
23627
|
-
|
|
23628
|
-
|
|
23629
|
-
|
|
23630
|
-
|
|
23631
|
-
|
|
23632
|
-
|
|
23633
|
-
|
|
23646
|
+
}
|
|
23647
|
+
]
|
|
23648
|
+
};
|
|
23649
|
+
}
|
|
23650
|
+
return {
|
|
23651
|
+
content: [
|
|
23652
|
+
{
|
|
23653
|
+
type: "text",
|
|
23654
|
+
text: `Recall item ${result.key} [memory]
|
|
23634
23655
|
` + `Title: ${result.title}
|
|
23635
23656
|
` + `Session: ${result.session_id ?? "(unknown)"}
|
|
23636
23657
|
` + `Source: ${result.source_device_id ?? "(unknown)"}
|
|
@@ -23638,63 +23659,63 @@ ${hotFiles}`
|
|
|
23638
23659
|
` + `Type: ${result.payload.observation_type}
|
|
23639
23660
|
|
|
23640
23661
|
` + `${result.payload.narrative ?? result.payload.facts ?? "(no detail)"}`
|
|
23641
|
-
|
|
23642
|
-
|
|
23643
|
-
|
|
23644
|
-
});
|
|
23645
|
-
server.tool("resume_thread", "USE FIRST when you want one direct 'where were we?' answer. Build a clear resume point for the current project by combining handoff, live recall, current thread, and recent chat continuity.", {
|
|
23646
|
-
|
|
23647
|
-
|
|
23648
|
-
|
|
23649
|
-
|
|
23650
|
-
|
|
23651
|
-
}, async (params) => {
|
|
23652
|
-
|
|
23653
|
-
|
|
23654
|
-
|
|
23655
|
-
|
|
23656
|
-
|
|
23657
|
-
|
|
23658
|
-
|
|
23659
|
-
|
|
23660
|
-
|
|
23662
|
+
}
|
|
23663
|
+
]
|
|
23664
|
+
};
|
|
23665
|
+
});
|
|
23666
|
+
server.tool("resume_thread", "USE FIRST when you want one direct 'where were we?' answer. Build a clear resume point for the current project by combining handoff, live recall, current thread, and recent chat continuity.", {
|
|
23667
|
+
cwd: exports_external.string().optional().describe("Optional cwd override for the project to resume"),
|
|
23668
|
+
limit: exports_external.number().optional().describe("Max recall hits/chat snippets to include"),
|
|
23669
|
+
agent: exports_external.string().optional().describe("Optional agent to resume specifically, such as claude-code or codex-cli"),
|
|
23670
|
+
user_id: exports_external.string().optional().describe("Optional user override"),
|
|
23671
|
+
repair_if_needed: exports_external.boolean().optional().describe("If true, attempt recall repair before resuming when continuity is still weak")
|
|
23672
|
+
}, async (params) => {
|
|
23673
|
+
const result = await resumeThread(db, config2, {
|
|
23674
|
+
cwd: params.cwd ?? process.cwd(),
|
|
23675
|
+
limit: params.limit,
|
|
23676
|
+
agent: params.agent,
|
|
23677
|
+
user_id: params.user_id ?? config2.user_id,
|
|
23678
|
+
current_device_id: config2.device_id,
|
|
23679
|
+
repair_if_needed: params.repair_if_needed
|
|
23680
|
+
});
|
|
23681
|
+
const projectLine = result.project_name ? `Project: ${result.project_name}
|
|
23661
23682
|
` : "";
|
|
23662
|
-
|
|
23683
|
+
const handoffLine = result.handoff ? `Handoff: #${result.handoff.id} ${result.handoff.title}${result.handoff.source ? ` (${result.handoff.source})` : ""}
|
|
23663
23684
|
` : `Handoff: (none)
|
|
23664
23685
|
`;
|
|
23665
|
-
|
|
23686
|
+
const openExactLine = result.best_recall_key ? `Open exact: load_recall_item("${result.best_recall_key}")${result.best_recall_title ? ` # ${result.best_recall_title}` : ""}
|
|
23666
23687
|
` : "";
|
|
23667
|
-
|
|
23688
|
+
const basisLines = result.resume_basis.length > 0 ? result.resume_basis.map((item) => `- ${item}`).join(`
|
|
23668
23689
|
`) : "- (none)";
|
|
23669
|
-
|
|
23690
|
+
const toolTrailLines = result.tool_trail.length > 0 ? result.tool_trail.map((item) => `- ${item}`).join(`
|
|
23670
23691
|
`) : "- (none)";
|
|
23671
|
-
|
|
23692
|
+
const hotFileLines = result.hot_files.length > 0 ? result.hot_files.map((item) => `- ${item.path}${item.count > 1 ? ` (${item.count})` : ""}`).join(`
|
|
23672
23693
|
`) : "- (none)";
|
|
23673
|
-
|
|
23694
|
+
const nextActionLines = result.next_actions.length > 0 ? result.next_actions.map((item) => `- ${item}`).join(`
|
|
23674
23695
|
`) : "- (none)";
|
|
23675
|
-
|
|
23696
|
+
const repairLine = result.repair_attempted ? `Recall repair: attempted${result.repair_result ? ` · imported ${result.repair_result.imported_chat_messages} chat across ${result.repair_result.sessions_with_imports} session(s)` : ""}
|
|
23676
23697
|
` : "";
|
|
23677
|
-
|
|
23698
|
+
const outcomes = result.recent_outcomes.length > 0 ? result.recent_outcomes.map((item) => `- ${item}`).join(`
|
|
23678
23699
|
`) : "- (none)";
|
|
23679
|
-
|
|
23700
|
+
const chatLines = result.recent_chat.length > 0 ? result.recent_chat.map((item) => `- [${item.role}] [${item.source}] ${item.content.slice(0, 180)}`).join(`
|
|
23680
23701
|
`) : "- (none)";
|
|
23681
|
-
|
|
23682
|
-
|
|
23683
|
-
|
|
23684
|
-
|
|
23685
|
-
|
|
23686
|
-
|
|
23687
|
-
|
|
23688
|
-
|
|
23689
|
-
|
|
23702
|
+
const recallLines = result.recall_hits.length > 0 ? result.recall_hits.map((item) => {
|
|
23703
|
+
const bits = [item.kind];
|
|
23704
|
+
if (item.role)
|
|
23705
|
+
bits.push(item.role);
|
|
23706
|
+
if (item.source_kind)
|
|
23707
|
+
bits.push(item.source_kind);
|
|
23708
|
+
if (item.type)
|
|
23709
|
+
bits.push(item.type);
|
|
23710
|
+
return `- [${bits.join(" · ")}] ${item.title}
|
|
23690
23711
|
${item.detail.slice(0, 200)}`;
|
|
23691
|
-
|
|
23712
|
+
}).join(`
|
|
23692
23713
|
`) : "- (none)";
|
|
23693
|
-
|
|
23694
|
-
|
|
23695
|
-
|
|
23696
|
-
|
|
23697
|
-
|
|
23714
|
+
return {
|
|
23715
|
+
content: [
|
|
23716
|
+
{
|
|
23717
|
+
type: "text",
|
|
23718
|
+
text: `${projectLine}` + `${result.target_agent ? `Target agent: ${result.target_agent}
|
|
23698
23719
|
` : ""}` + `Continuity: ${result.continuity_state} — ${result.continuity_summary}
|
|
23699
23720
|
` + `Freshness: ${result.resume_freshness}
|
|
23700
23721
|
` + `Source: ${result.resume_source_session_id ?? "(unknown session)"}${result.resume_source_device_id ? ` (${result.resume_source_device_id})` : ""}
|
|
@@ -23724,254 +23745,254 @@ ${chatLines}
|
|
|
23724
23745
|
|
|
23725
23746
|
` + `Recall hits:
|
|
23726
23747
|
${recallLines}`
|
|
23727
|
-
}
|
|
23728
|
-
]
|
|
23729
|
-
};
|
|
23730
|
-
});
|
|
23731
|
-
server.tool("get_observations", "Get observations by ID", {
|
|
23732
|
-
ids: exports_external.array(exports_external.number()).describe("Observation IDs")
|
|
23733
|
-
}, async (params) => {
|
|
23734
|
-
const result = getObservations(db, {
|
|
23735
|
-
...params,
|
|
23736
|
-
user_id: config2.user_id
|
|
23737
|
-
});
|
|
23738
|
-
if (result.observations.length === 0) {
|
|
23739
|
-
return {
|
|
23740
|
-
content: [
|
|
23741
|
-
{
|
|
23742
|
-
type: "text",
|
|
23743
|
-
text: `No observations found for IDs: ${params.ids.join(", ")}`
|
|
23744
23748
|
}
|
|
23745
23749
|
]
|
|
23746
23750
|
};
|
|
23747
|
-
}
|
|
23748
|
-
const formatted = result.observations.map((obs) => {
|
|
23749
|
-
const parts = [
|
|
23750
|
-
`## Observation #${obs.id}`,
|
|
23751
|
-
`**Type**: ${obs.type} | **Quality**: ${obs.quality.toFixed(2)} | **Lifecycle**: ${obs.lifecycle}`,
|
|
23752
|
-
`**Title**: ${obs.title}`
|
|
23753
|
-
];
|
|
23754
|
-
if (obs.narrative)
|
|
23755
|
-
parts.push(`**Narrative**: ${obs.narrative}`);
|
|
23756
|
-
if (obs.facts)
|
|
23757
|
-
parts.push(`**Facts**: ${obs.facts}`);
|
|
23758
|
-
if (obs.concepts)
|
|
23759
|
-
parts.push(`**Concepts**: ${obs.concepts}`);
|
|
23760
|
-
if (obs.files_modified)
|
|
23761
|
-
parts.push(`**Files modified**: ${obs.files_modified}`);
|
|
23762
|
-
if (obs.files_read)
|
|
23763
|
-
parts.push(`**Files read**: ${obs.files_read}`);
|
|
23764
|
-
parts.push(`**Created**: ${obs.created_at}`);
|
|
23765
|
-
return parts.join(`
|
|
23766
|
-
`);
|
|
23767
23751
|
});
|
|
23768
|
-
|
|
23752
|
+
server.tool("get_observations", "Get observations by ID", {
|
|
23753
|
+
ids: exports_external.array(exports_external.number()).describe("Observation IDs")
|
|
23754
|
+
}, async (params) => {
|
|
23755
|
+
const result = getObservations(db, {
|
|
23756
|
+
...params,
|
|
23757
|
+
user_id: config2.user_id
|
|
23758
|
+
});
|
|
23759
|
+
if (result.observations.length === 0) {
|
|
23760
|
+
return {
|
|
23761
|
+
content: [
|
|
23762
|
+
{
|
|
23763
|
+
type: "text",
|
|
23764
|
+
text: `No observations found for IDs: ${params.ids.join(", ")}`
|
|
23765
|
+
}
|
|
23766
|
+
]
|
|
23767
|
+
};
|
|
23768
|
+
}
|
|
23769
|
+
const formatted = result.observations.map((obs) => {
|
|
23770
|
+
const parts = [
|
|
23771
|
+
`## Observation #${obs.id}`,
|
|
23772
|
+
`**Type**: ${obs.type} | **Quality**: ${obs.quality.toFixed(2)} | **Lifecycle**: ${obs.lifecycle}`,
|
|
23773
|
+
`**Title**: ${obs.title}`
|
|
23774
|
+
];
|
|
23775
|
+
if (obs.narrative)
|
|
23776
|
+
parts.push(`**Narrative**: ${obs.narrative}`);
|
|
23777
|
+
if (obs.facts)
|
|
23778
|
+
parts.push(`**Facts**: ${obs.facts}`);
|
|
23779
|
+
if (obs.concepts)
|
|
23780
|
+
parts.push(`**Concepts**: ${obs.concepts}`);
|
|
23781
|
+
if (obs.files_modified)
|
|
23782
|
+
parts.push(`**Files modified**: ${obs.files_modified}`);
|
|
23783
|
+
if (obs.files_read)
|
|
23784
|
+
parts.push(`**Files read**: ${obs.files_read}`);
|
|
23785
|
+
parts.push(`**Created**: ${obs.created_at}`);
|
|
23786
|
+
return parts.join(`
|
|
23787
|
+
`);
|
|
23788
|
+
});
|
|
23789
|
+
let text = formatted.join(`
|
|
23769
23790
|
|
|
23770
23791
|
---
|
|
23771
23792
|
|
|
23772
23793
|
`);
|
|
23773
|
-
|
|
23774
|
-
|
|
23794
|
+
if (result.not_found.length > 0) {
|
|
23795
|
+
text += `
|
|
23775
23796
|
|
|
23776
23797
|
Not found: ${result.not_found.join(", ")}`;
|
|
23777
|
-
|
|
23778
|
-
return {
|
|
23779
|
-
content: [{ type: "text", text }]
|
|
23780
|
-
};
|
|
23781
|
-
});
|
|
23782
|
-
server.tool("timeline", "Timeline around an observation", {
|
|
23783
|
-
anchor: exports_external.number().describe("Observation ID to centre on"),
|
|
23784
|
-
depth_before: exports_external.number().optional().describe("Before anchor (default: 3)"),
|
|
23785
|
-
depth_after: exports_external.number().optional().describe("After anchor (default: 3)"),
|
|
23786
|
-
project_scoped: exports_external.boolean().optional().describe("Scope to project (default: true)")
|
|
23787
|
-
}, async (params) => {
|
|
23788
|
-
const result = getTimeline(db, {
|
|
23789
|
-
anchor_id: params.anchor,
|
|
23790
|
-
depth_before: params.depth_before,
|
|
23791
|
-
depth_after: params.depth_after,
|
|
23792
|
-
project_scoped: params.project_scoped,
|
|
23793
|
-
user_id: config2.user_id
|
|
23794
|
-
});
|
|
23795
|
-
if (result.observations.length === 0) {
|
|
23798
|
+
}
|
|
23796
23799
|
return {
|
|
23797
|
-
content: [
|
|
23798
|
-
{
|
|
23799
|
-
type: "text",
|
|
23800
|
-
text: `Observation #${params.anchor} not found`
|
|
23801
|
-
}
|
|
23802
|
-
]
|
|
23800
|
+
content: [{ type: "text", text }]
|
|
23803
23801
|
};
|
|
23804
|
-
}
|
|
23805
|
-
const lines = result.observations.map((obs, i) => {
|
|
23806
|
-
const marker = i === result.anchor_index ? "→" : " ";
|
|
23807
|
-
const date5 = obs.created_at.split("T")[0];
|
|
23808
|
-
return `${marker} #${obs.id} [${date5}] ${obs.type}: ${obs.title}`;
|
|
23809
23802
|
});
|
|
23810
|
-
|
|
23803
|
+
server.tool("timeline", "Timeline around an observation", {
|
|
23804
|
+
anchor: exports_external.number().describe("Observation ID to centre on"),
|
|
23805
|
+
depth_before: exports_external.number().optional().describe("Before anchor (default: 3)"),
|
|
23806
|
+
depth_after: exports_external.number().optional().describe("After anchor (default: 3)"),
|
|
23807
|
+
project_scoped: exports_external.boolean().optional().describe("Scope to project (default: true)")
|
|
23808
|
+
}, async (params) => {
|
|
23809
|
+
const result = getTimeline(db, {
|
|
23810
|
+
anchor_id: params.anchor,
|
|
23811
|
+
depth_before: params.depth_before,
|
|
23812
|
+
depth_after: params.depth_after,
|
|
23813
|
+
project_scoped: params.project_scoped,
|
|
23814
|
+
user_id: config2.user_id
|
|
23815
|
+
});
|
|
23816
|
+
if (result.observations.length === 0) {
|
|
23817
|
+
return {
|
|
23818
|
+
content: [
|
|
23819
|
+
{
|
|
23820
|
+
type: "text",
|
|
23821
|
+
text: `Observation #${params.anchor} not found`
|
|
23822
|
+
}
|
|
23823
|
+
]
|
|
23824
|
+
};
|
|
23825
|
+
}
|
|
23826
|
+
const lines = result.observations.map((obs, i) => {
|
|
23827
|
+
const marker = i === result.anchor_index ? "→" : " ";
|
|
23828
|
+
const date5 = obs.created_at.split("T")[0];
|
|
23829
|
+
return `${marker} #${obs.id} [${date5}] ${obs.type}: ${obs.title}`;
|
|
23830
|
+
});
|
|
23831
|
+
const projectLine = result.project ? `Project: ${result.project}
|
|
23811
23832
|
` : "";
|
|
23812
|
-
|
|
23833
|
+
const promptSection = result.session_prompts && result.session_prompts.length > 0 ? `
|
|
23813
23834
|
|
|
23814
23835
|
Session requests:
|
|
23815
23836
|
${result.session_prompts.map((prompt) => `- #${prompt.prompt_number} ${prompt.prompt.replace(/\s+/g, " ").trim()}`).join(`
|
|
23816
23837
|
`)}` : "";
|
|
23817
|
-
|
|
23838
|
+
const toolSection = result.session_tool_events && result.session_tool_events.length > 0 ? `
|
|
23818
23839
|
|
|
23819
23840
|
Session tools:
|
|
23820
23841
|
${result.session_tool_events.slice(-8).map((tool) => {
|
|
23821
|
-
|
|
23822
|
-
|
|
23823
|
-
|
|
23842
|
+
const detail = tool.file_path ?? tool.command ?? tool.tool_response_preview ?? "";
|
|
23843
|
+
return `- ${tool.tool_name}${detail ? ` — ${detail}` : ""}`;
|
|
23844
|
+
}).join(`
|
|
23824
23845
|
`)}` : "";
|
|
23825
|
-
|
|
23826
|
-
|
|
23827
|
-
|
|
23828
|
-
|
|
23829
|
-
|
|
23846
|
+
return {
|
|
23847
|
+
content: [
|
|
23848
|
+
{
|
|
23849
|
+
type: "text",
|
|
23850
|
+
text: `${projectLine}Timeline around #${params.anchor}:
|
|
23830
23851
|
|
|
23831
23852
|
${lines.join(`
|
|
23832
23853
|
`)}${promptSection}${toolSection}`
|
|
23833
|
-
|
|
23834
|
-
|
|
23835
|
-
|
|
23836
|
-
});
|
|
23837
|
-
server.tool("pin_observation", "Pin/unpin observation", {
|
|
23838
|
-
|
|
23839
|
-
|
|
23840
|
-
}, async (params) => {
|
|
23841
|
-
|
|
23842
|
-
return {
|
|
23843
|
-
content: [
|
|
23844
|
-
{
|
|
23845
|
-
type: "text",
|
|
23846
|
-
text: result.success ? `Observation #${params.id} ${params.pinned ? "pinned" : "unpinned"}` : `Failed: ${result.reason}`
|
|
23847
|
-
}
|
|
23848
|
-
]
|
|
23849
|
-
};
|
|
23850
|
-
});
|
|
23851
|
-
server.tool("check_messages", "Check for messages sent from other devices or sessions. Messages are cross-device notes left by you or your team.", {
|
|
23852
|
-
mark_read: exports_external.boolean().optional().describe("Mark messages as read after viewing (default: true)")
|
|
23853
|
-
}, async (params) => {
|
|
23854
|
-
const markRead = params.mark_read !== false;
|
|
23855
|
-
const readKey = `messages_read_${config2.device_id}`;
|
|
23856
|
-
const lastReadId = parseInt(db.getSyncState(readKey) ?? "0", 10);
|
|
23857
|
-
const messages = getUnreadInboxMessages(db, config2.device_id, config2.user_id, lastReadId, 20);
|
|
23858
|
-
if (messages.length === 0) {
|
|
23854
|
+
}
|
|
23855
|
+
]
|
|
23856
|
+
};
|
|
23857
|
+
});
|
|
23858
|
+
server.tool("pin_observation", "Pin/unpin observation", {
|
|
23859
|
+
id: exports_external.number().describe("Observation ID"),
|
|
23860
|
+
pinned: exports_external.boolean().describe("true=pin, false=unpin")
|
|
23861
|
+
}, async (params) => {
|
|
23862
|
+
const result = pinObservation(db, params);
|
|
23859
23863
|
return {
|
|
23860
|
-
content: [
|
|
23864
|
+
content: [
|
|
23865
|
+
{
|
|
23866
|
+
type: "text",
|
|
23867
|
+
text: result.success ? `Observation #${params.id} ${params.pinned ? "pinned" : "unpinned"}` : `Failed: ${result.reason}`
|
|
23868
|
+
}
|
|
23869
|
+
]
|
|
23861
23870
|
};
|
|
23862
|
-
}
|
|
23863
|
-
|
|
23864
|
-
|
|
23865
|
-
|
|
23866
|
-
|
|
23867
|
-
|
|
23868
|
-
const
|
|
23869
|
-
const
|
|
23870
|
-
|
|
23871
|
+
});
|
|
23872
|
+
server.tool("check_messages", "Check for messages sent from other devices or sessions. Messages are cross-device notes left by you or your team.", {
|
|
23873
|
+
mark_read: exports_external.boolean().optional().describe("Mark messages as read after viewing (default: true)")
|
|
23874
|
+
}, async (params) => {
|
|
23875
|
+
const markRead = params.mark_read !== false;
|
|
23876
|
+
const readKey = `messages_read_${config2.device_id}`;
|
|
23877
|
+
const lastReadId = parseInt(db.getSyncState(readKey) ?? "0", 10);
|
|
23878
|
+
const messages = getUnreadInboxMessages(db, config2.device_id, config2.user_id, lastReadId, 20);
|
|
23879
|
+
if (messages.length === 0) {
|
|
23880
|
+
return {
|
|
23881
|
+
content: [{ type: "text", text: "No new messages." }]
|
|
23882
|
+
};
|
|
23883
|
+
}
|
|
23884
|
+
if (markRead && messages.length > 0) {
|
|
23885
|
+
const maxId = Math.max(...messages.map((m) => m.id));
|
|
23886
|
+
db.setSyncState(readKey, String(maxId));
|
|
23887
|
+
}
|
|
23888
|
+
const lines = messages.map((m) => {
|
|
23889
|
+
const from = m.device_id === config2.device_id ? "you (this device)" : m.device_id;
|
|
23890
|
+
const ago = formatTimeAgo(m.created_at);
|
|
23891
|
+
return `[${ago}] from ${from}:
|
|
23871
23892
|
${m.title}${m.narrative ? `
|
|
23872
23893
|
` + m.narrative : ""}`;
|
|
23873
|
-
|
|
23874
|
-
|
|
23875
|
-
|
|
23876
|
-
|
|
23877
|
-
|
|
23894
|
+
});
|
|
23895
|
+
return {
|
|
23896
|
+
content: [{
|
|
23897
|
+
type: "text",
|
|
23898
|
+
text: `${messages.length} message(s):
|
|
23878
23899
|
|
|
23879
23900
|
${lines.join(`
|
|
23880
23901
|
|
|
23881
23902
|
`)}`
|
|
23882
|
-
|
|
23883
|
-
|
|
23884
|
-
});
|
|
23885
|
-
server.tool("send_message", "Leave a cross-device or team note in Engrm's shared inbox", {
|
|
23886
|
-
title: exports_external.string().describe("Short message title"),
|
|
23887
|
-
narrative: exports_external.string().optional().describe("Optional message body"),
|
|
23888
|
-
concepts: exports_external.array(exports_external.string()).optional().describe("Optional tags"),
|
|
23889
|
-
session_id: exports_external.string().optional()
|
|
23890
|
-
}, async (params) => {
|
|
23891
|
-
const result = await sendMessage(db, config2, {
|
|
23892
|
-
...params,
|
|
23893
|
-
cwd: process.cwd()
|
|
23894
|
-
});
|
|
23895
|
-
return {
|
|
23896
|
-
content: [
|
|
23897
|
-
{
|
|
23898
|
-
type: "text",
|
|
23899
|
-
text: result.success ? `Message saved as observation #${result.observation_id}` : `Failed: ${result.reason}`
|
|
23900
|
-
}
|
|
23901
|
-
]
|
|
23902
|
-
};
|
|
23903
|
-
});
|
|
23904
|
-
server.tool("recent_activity", "Inspect the most recent observations, notes, and handoffs captured by Engrm", {
|
|
23905
|
-
limit: exports_external.number().optional().describe("Max observations to return (default: 10)"),
|
|
23906
|
-
project_scoped: exports_external.boolean().optional().describe("Scope to current project (default: true)"),
|
|
23907
|
-
type: exports_external.string().optional().describe("Optional observation type filter")
|
|
23908
|
-
}, async (params) => {
|
|
23909
|
-
const result = getRecentActivity(db, {
|
|
23910
|
-
...params,
|
|
23911
|
-
cwd: process.cwd(),
|
|
23912
|
-
user_id: config2.user_id
|
|
23903
|
+
}]
|
|
23904
|
+
};
|
|
23913
23905
|
});
|
|
23914
|
-
|
|
23906
|
+
server.tool("send_message", "Leave a cross-device or team note in Engrm's shared inbox", {
|
|
23907
|
+
title: exports_external.string().describe("Short message title"),
|
|
23908
|
+
narrative: exports_external.string().optional().describe("Optional message body"),
|
|
23909
|
+
concepts: exports_external.array(exports_external.string()).optional().describe("Optional tags"),
|
|
23910
|
+
session_id: exports_external.string().optional()
|
|
23911
|
+
}, async (params) => {
|
|
23912
|
+
const result = await sendMessage(db, config2, {
|
|
23913
|
+
...params,
|
|
23914
|
+
cwd: process.cwd()
|
|
23915
|
+
});
|
|
23915
23916
|
return {
|
|
23916
23917
|
content: [
|
|
23917
23918
|
{
|
|
23918
23919
|
type: "text",
|
|
23919
|
-
text: result.
|
|
23920
|
+
text: result.success ? `Message saved as observation #${result.observation_id}` : `Failed: ${result.reason}`
|
|
23920
23921
|
}
|
|
23921
23922
|
]
|
|
23922
23923
|
};
|
|
23923
|
-
}
|
|
23924
|
-
const showProject = !result.project;
|
|
23925
|
-
const header = showProject ? "| ID | Project | Type | Title | Created |" : "| ID | Type | Title | Created |";
|
|
23926
|
-
const separator = showProject ? "|---|---|---|---|---|" : "|---|---|---|---|";
|
|
23927
|
-
const displayType = (obs) => {
|
|
23928
|
-
if (obs.message_kind === "draft-handoff")
|
|
23929
|
-
return "handoff:draft";
|
|
23930
|
-
if (obs.message_kind === "handoff")
|
|
23931
|
-
return "handoff";
|
|
23932
|
-
if (obs.message_kind === "inbox-note")
|
|
23933
|
-
return "note";
|
|
23934
|
-
return obs.type;
|
|
23935
|
-
};
|
|
23936
|
-
const rows = result.observations.map((obs) => {
|
|
23937
|
-
const date5 = obs.created_at.split("T")[0];
|
|
23938
|
-
if (showProject) {
|
|
23939
|
-
return `| ${obs.id} | ${obs.project_name ?? "(unknown)"} | ${displayType(obs)} | ${obs.title} | ${date5} |`;
|
|
23940
|
-
}
|
|
23941
|
-
return `| ${obs.id} | ${displayType(obs)} | ${obs.title} | ${date5} |`;
|
|
23942
23924
|
});
|
|
23943
|
-
|
|
23925
|
+
server.tool("recent_activity", "Inspect the most recent observations, notes, and handoffs captured by Engrm", {
|
|
23926
|
+
limit: exports_external.number().optional().describe("Max observations to return (default: 10)"),
|
|
23927
|
+
project_scoped: exports_external.boolean().optional().describe("Scope to current project (default: true)"),
|
|
23928
|
+
type: exports_external.string().optional().describe("Optional observation type filter")
|
|
23929
|
+
}, async (params) => {
|
|
23930
|
+
const result = getRecentActivity(db, {
|
|
23931
|
+
...params,
|
|
23932
|
+
cwd: process.cwd(),
|
|
23933
|
+
user_id: config2.user_id
|
|
23934
|
+
});
|
|
23935
|
+
if (result.observations.length === 0) {
|
|
23936
|
+
return {
|
|
23937
|
+
content: [
|
|
23938
|
+
{
|
|
23939
|
+
type: "text",
|
|
23940
|
+
text: result.project ? `No recent observations found in project ${result.project}.` : "No recent observations found."
|
|
23941
|
+
}
|
|
23942
|
+
]
|
|
23943
|
+
};
|
|
23944
|
+
}
|
|
23945
|
+
const showProject = !result.project;
|
|
23946
|
+
const header = showProject ? "| ID | Project | Type | Title | Created |" : "| ID | Type | Title | Created |";
|
|
23947
|
+
const separator = showProject ? "|---|---|---|---|---|" : "|---|---|---|---|";
|
|
23948
|
+
const displayType = (obs) => {
|
|
23949
|
+
if (obs.message_kind === "draft-handoff")
|
|
23950
|
+
return "handoff:draft";
|
|
23951
|
+
if (obs.message_kind === "handoff")
|
|
23952
|
+
return "handoff";
|
|
23953
|
+
if (obs.message_kind === "inbox-note")
|
|
23954
|
+
return "note";
|
|
23955
|
+
return obs.type;
|
|
23956
|
+
};
|
|
23957
|
+
const rows = result.observations.map((obs) => {
|
|
23958
|
+
const date5 = obs.created_at.split("T")[0];
|
|
23959
|
+
if (showProject) {
|
|
23960
|
+
return `| ${obs.id} | ${obs.project_name ?? "(unknown)"} | ${displayType(obs)} | ${obs.title} | ${date5} |`;
|
|
23961
|
+
}
|
|
23962
|
+
return `| ${obs.id} | ${displayType(obs)} | ${obs.title} | ${date5} |`;
|
|
23963
|
+
});
|
|
23964
|
+
const projectLine = result.project ? `Project: ${result.project}
|
|
23944
23965
|
` : "";
|
|
23945
|
-
|
|
23946
|
-
|
|
23947
|
-
|
|
23948
|
-
|
|
23949
|
-
|
|
23966
|
+
return {
|
|
23967
|
+
content: [
|
|
23968
|
+
{
|
|
23969
|
+
type: "text",
|
|
23970
|
+
text: `${projectLine}Recent activity:
|
|
23950
23971
|
|
|
23951
23972
|
${header}
|
|
23952
23973
|
${separator}
|
|
23953
23974
|
${rows.join(`
|
|
23954
23975
|
`)}`
|
|
23955
|
-
|
|
23956
|
-
|
|
23957
|
-
|
|
23958
|
-
});
|
|
23959
|
-
server.tool("memory_stats", "Show high-level Engrm capture and sync statistics", {}, async () => {
|
|
23960
|
-
|
|
23961
|
-
|
|
23962
|
-
|
|
23976
|
+
}
|
|
23977
|
+
]
|
|
23978
|
+
};
|
|
23979
|
+
});
|
|
23980
|
+
server.tool("memory_stats", "Show high-level Engrm capture and sync statistics", {}, async () => {
|
|
23981
|
+
const stats = getMemoryStats(db);
|
|
23982
|
+
const packs = stats.installed_packs.length > 0 ? stats.installed_packs.join(", ") : "(none)";
|
|
23983
|
+
const recentRequests = stats.recent_requests.length > 0 ? stats.recent_requests.map((item) => `- ${item}`).join(`
|
|
23963
23984
|
`) : "- (none)";
|
|
23964
|
-
|
|
23985
|
+
const recentLessons = stats.recent_lessons.length > 0 ? stats.recent_lessons.map((item) => `- ${item}`).join(`
|
|
23965
23986
|
`) : "- (none)";
|
|
23966
|
-
|
|
23987
|
+
const recentCompleted = stats.recent_completed.length > 0 ? stats.recent_completed.map((item) => `- ${item}`).join(`
|
|
23967
23988
|
`) : "- (none)";
|
|
23968
|
-
|
|
23989
|
+
const nextSteps = stats.next_steps.length > 0 ? stats.next_steps.map((item) => `- ${item}`).join(`
|
|
23969
23990
|
`) : "- (none)";
|
|
23970
|
-
|
|
23971
|
-
|
|
23972
|
-
|
|
23973
|
-
|
|
23974
|
-
|
|
23991
|
+
return {
|
|
23992
|
+
content: [
|
|
23993
|
+
{
|
|
23994
|
+
type: "text",
|
|
23995
|
+
text: `Active observations: ${stats.active_observations}
|
|
23975
23996
|
` + `User prompts: ${stats.user_prompts}
|
|
23976
23997
|
` + `Tool events: ${stats.tool_events}
|
|
23977
23998
|
` + `Inbox notes: ${stats.inbox_messages}
|
|
@@ -23992,72 +24013,72 @@ ${recentCompleted}
|
|
|
23992
24013
|
|
|
23993
24014
|
` + `Next steps:
|
|
23994
24015
|
${nextSteps}`
|
|
23995
|
-
|
|
23996
|
-
|
|
23997
|
-
|
|
23998
|
-
});
|
|
23999
|
-
server.tool("memory_console", "Show a high-signal local overview of what Engrm currently knows about this project", {
|
|
24000
|
-
cwd: exports_external.string().optional(),
|
|
24001
|
-
project_scoped: exports_external.boolean().optional(),
|
|
24002
|
-
user_id: exports_external.string().optional()
|
|
24003
|
-
}, async (params) => {
|
|
24004
|
-
const result = getMemoryConsole(db, {
|
|
24005
|
-
...params,
|
|
24006
|
-
cwd: params.cwd ?? process.cwd(),
|
|
24007
|
-
user_id: params.user_id ?? config2.user_id
|
|
24016
|
+
}
|
|
24017
|
+
]
|
|
24018
|
+
};
|
|
24008
24019
|
});
|
|
24009
|
-
|
|
24010
|
-
|
|
24011
|
-
|
|
24012
|
-
|
|
24020
|
+
server.tool("memory_console", "Show a high-signal local overview of what Engrm currently knows about this project", {
|
|
24021
|
+
cwd: exports_external.string().optional(),
|
|
24022
|
+
project_scoped: exports_external.boolean().optional(),
|
|
24023
|
+
user_id: exports_external.string().optional()
|
|
24024
|
+
}, async (params) => {
|
|
24025
|
+
const result = getMemoryConsole(db, {
|
|
24026
|
+
...params,
|
|
24027
|
+
cwd: params.cwd ?? process.cwd(),
|
|
24028
|
+
user_id: params.user_id ?? config2.user_id
|
|
24029
|
+
});
|
|
24030
|
+
const sessionLines = result.sessions.length > 0 ? result.sessions.map((session) => {
|
|
24031
|
+
const label = session.request ?? session.completed ?? "(no summary)";
|
|
24032
|
+
return `- ${session.session_id} :: ${label.replace(/\s+/g, " ").trim()}`;
|
|
24033
|
+
}).join(`
|
|
24013
24034
|
`) : "- (none)";
|
|
24014
|
-
|
|
24035
|
+
const requestLines = result.requests.length > 0 ? result.requests.map((prompt) => `- #${prompt.prompt_number} ${prompt.prompt.replace(/\s+/g, " ").trim()}`).join(`
|
|
24015
24036
|
`) : "- (none)";
|
|
24016
|
-
|
|
24017
|
-
|
|
24018
|
-
|
|
24019
|
-
|
|
24037
|
+
const toolLines = result.tools.length > 0 ? result.tools.map((tool) => {
|
|
24038
|
+
const detail = tool.file_path ?? tool.command ?? tool.tool_response_preview ?? "";
|
|
24039
|
+
return `- ${tool.tool_name}${detail ? ` — ${detail}` : ""}`;
|
|
24040
|
+
}).join(`
|
|
24020
24041
|
`) : "- (none)";
|
|
24021
|
-
|
|
24042
|
+
const handoffLines = result.recent_handoffs.length > 0 ? result.recent_handoffs.map((obs) => `- #${obs.id} ${obs.title}`).join(`
|
|
24022
24043
|
`) : "- (none)";
|
|
24023
|
-
|
|
24044
|
+
const inboxNoteLines = result.recent_inbox_notes.length > 0 ? result.recent_inbox_notes.map((obs) => `- #${obs.id} ${obs.title}`).join(`
|
|
24024
24045
|
`) : "- (none)";
|
|
24025
|
-
|
|
24046
|
+
const recentChatLines = result.recent_chat.length > 0 ? result.recent_chat.map((msg) => `- [${msg.role}] ${msg.content.replace(/\s+/g, " ").trim().slice(0, 180)}`).join(`
|
|
24026
24047
|
`) : "- (none)";
|
|
24027
|
-
|
|
24028
|
-
|
|
24029
|
-
|
|
24030
|
-
|
|
24031
|
-
|
|
24032
|
-
|
|
24033
|
-
|
|
24034
|
-
|
|
24048
|
+
const observationLines = result.observations.length > 0 ? result.observations.map((obs) => {
|
|
24049
|
+
const provenance = [];
|
|
24050
|
+
if (obs.source_tool)
|
|
24051
|
+
provenance.push(`via ${obs.source_tool}`);
|
|
24052
|
+
if (typeof obs.source_prompt_number === "number")
|
|
24053
|
+
provenance.push(`#${obs.source_prompt_number}`);
|
|
24054
|
+
return `- #${obs.id} [${obs.type}] ${obs.title}${provenance.length ? ` (${provenance.join(" · ")})` : ""}`;
|
|
24055
|
+
}).join(`
|
|
24035
24056
|
`) : "- (none)";
|
|
24036
|
-
|
|
24057
|
+
const provenanceLines = result.provenance_summary.length > 0 ? result.provenance_summary.map((item) => `- ${item.tool}: ${item.count}`).join(`
|
|
24037
24058
|
`) : "- (none)";
|
|
24038
|
-
|
|
24059
|
+
const provenanceMixLines = result.provenance_type_mix.length > 0 ? result.provenance_type_mix.map((item) => `- ${item.tool}: ${item.top_types.map((entry) => `${entry.type} ${entry.count}`).join(", ")}`).join(`
|
|
24039
24060
|
`) : "- (none)";
|
|
24040
|
-
|
|
24061
|
+
const checkpointTypeLines = result.assistant_checkpoint_types.length > 0 ? result.assistant_checkpoint_types.map((item) => `- ${item.type}: ${item.count}`).join(`
|
|
24041
24062
|
`) : "- (none)";
|
|
24042
|
-
|
|
24063
|
+
const topTypes = result.top_types.length > 0 ? result.top_types.map((item) => `- ${item.type}: ${item.count}`).join(`
|
|
24043
24064
|
`) : "- (none)";
|
|
24044
|
-
|
|
24065
|
+
const recallPreviewLines = result.recall_index_preview.length > 0 ? result.recall_index_preview.map((item) => `- ${item.key} [${item.kind} · ${item.freshness}${item.source_agent ? ` · ${item.source_agent}` : ""}] ${item.title}`).join(`
|
|
24045
24066
|
`) : "- (none)";
|
|
24046
|
-
|
|
24067
|
+
const openExactLine = result.best_recall_key ? `Open exact: load_recall_item("${result.best_recall_key}")${result.best_recall_title ? ` # ${result.best_recall_title}` : ""}
|
|
24047
24068
|
` : "";
|
|
24048
|
-
|
|
24069
|
+
const projectLine = result.project ? `Project: ${result.project}
|
|
24049
24070
|
|
|
24050
24071
|
` : "";
|
|
24051
|
-
|
|
24072
|
+
const captureLine = result.capture_mode === "rich" ? `Raw chronology: active (${result.requests.length} requests, ${result.tools.length} tools)
|
|
24052
24073
|
|
|
24053
24074
|
` : `Raw chronology: observations-only so far (prompt/tool hooks have not produced local history here yet)
|
|
24054
24075
|
|
|
24055
24076
|
`;
|
|
24056
|
-
|
|
24057
|
-
|
|
24058
|
-
|
|
24059
|
-
|
|
24060
|
-
|
|
24077
|
+
return {
|
|
24078
|
+
content: [
|
|
24079
|
+
{
|
|
24080
|
+
type: "text",
|
|
24081
|
+
text: `${projectLine}` + `${captureLine}` + `${result.active_agents.length > 0 ? `Agents active: ${result.active_agents.join(", ")}
|
|
24061
24082
|
` : ""}` + `Continuity: ${result.continuity_state} — ${result.continuity_summary}
|
|
24062
24083
|
` + `Recall index: ${result.recall_mode} · ${result.recall_items_ready} items ready
|
|
24063
24084
|
` + `Resume readiness: ${result.resume_freshness} · ${result.resume_source_session_id ?? "(unknown session)"}${result.resume_source_device_id ? ` (${result.resume_source_device_id})` : ""}
|
|
@@ -24104,25 +24125,25 @@ ${recentChatLines}
|
|
|
24104
24125
|
|
|
24105
24126
|
` + `Recent observations:
|
|
24106
24127
|
${observationLines}`
|
|
24107
|
-
|
|
24108
|
-
|
|
24109
|
-
|
|
24110
|
-
});
|
|
24111
|
-
server.tool("capture_status", "Show whether Engrm hook registration and recent prompt/tool chronology capture are actually active on this machine", {
|
|
24112
|
-
|
|
24113
|
-
|
|
24114
|
-
}, async (params) => {
|
|
24115
|
-
|
|
24116
|
-
|
|
24117
|
-
|
|
24118
|
-
|
|
24119
|
-
|
|
24120
|
-
|
|
24121
|
-
|
|
24122
|
-
|
|
24123
|
-
|
|
24124
|
-
|
|
24125
|
-
|
|
24128
|
+
}
|
|
24129
|
+
]
|
|
24130
|
+
};
|
|
24131
|
+
});
|
|
24132
|
+
server.tool("capture_status", "Show whether Engrm hook registration and recent prompt/tool chronology capture are actually active on this machine", {
|
|
24133
|
+
lookback_hours: exports_external.number().optional(),
|
|
24134
|
+
user_id: exports_external.string().optional()
|
|
24135
|
+
}, async (params) => {
|
|
24136
|
+
const result = getCaptureStatus(db, {
|
|
24137
|
+
lookback_hours: params.lookback_hours,
|
|
24138
|
+
user_id: params.user_id ?? config2.user_id
|
|
24139
|
+
});
|
|
24140
|
+
const latestPrompt = result.latest_prompt_epoch ? new Date(result.latest_prompt_epoch * 1000).toISOString() : "none";
|
|
24141
|
+
const latestTool = result.latest_tool_event_epoch ? new Date(result.latest_tool_event_epoch * 1000).toISOString() : "none";
|
|
24142
|
+
return {
|
|
24143
|
+
content: [
|
|
24144
|
+
{
|
|
24145
|
+
type: "text",
|
|
24146
|
+
text: `Schema: v${result.schema_version} (${result.schema_current ? "current" : "outdated"})
|
|
24126
24147
|
` + `HTTP MCP: ${result.http_enabled ? `enabled${result.http_port ? ` (:${result.http_port})` : ""}` : "disabled"}
|
|
24127
24148
|
` + `HTTP bearer tokens: ${result.http_bearer_token_count}
|
|
24128
24149
|
` + `Fleet project: ${result.fleet_project_name ?? "none"}
|
|
@@ -24135,6 +24156,8 @@ server.tool("capture_status", "Show whether Engrm hook registration and recent p
|
|
|
24135
24156
|
` + `Codex hooks: ${result.codex_hooks_registered ? "registered" : "missing"}
|
|
24136
24157
|
` + `Codex raw chronology: ${result.codex_raw_chronology_supported ? "supported" : "not yet supported (start/stop only)"}
|
|
24137
24158
|
|
|
24159
|
+
` + `OpenClaw MCP: ${result.openclaw_mcp_registered ? "registered" : "missing"}
|
|
24160
|
+
` + `OpenClaw plugin: ${result.openclaw_plugin_registered ? "installed" : "missing"}
|
|
24138
24161
|
` + `OpenCode MCP: ${result.opencode_mcp_registered ? "registered" : "missing"}
|
|
24139
24162
|
` + `OpenCode plugin: ${result.opencode_plugin_registered ? "installed" : "missing"}
|
|
24140
24163
|
|
|
@@ -24148,31 +24171,31 @@ server.tool("capture_status", "Show whether Engrm hook registration and recent p
|
|
|
24148
24171
|
` + `Latest PostToolUse hook: ${formatEpoch(result.latest_post_tool_hook_epoch)}
|
|
24149
24172
|
` + `Last PostToolUse parse: ${result.latest_post_tool_parse_status ?? "unknown"}
|
|
24150
24173
|
` + `Last PostToolUse tool: ${result.latest_post_tool_name ?? "unknown"}`
|
|
24151
|
-
|
|
24152
|
-
|
|
24153
|
-
|
|
24154
|
-
});
|
|
24155
|
-
server.tool("capture_quality", "Show how healthy Engrm capture is across the workspace: raw chronology coverage, checkpoints, and provenance by tool.", {
|
|
24156
|
-
|
|
24157
|
-
|
|
24158
|
-
}, async (params) => {
|
|
24159
|
-
|
|
24160
|
-
|
|
24161
|
-
|
|
24162
|
-
|
|
24163
|
-
|
|
24174
|
+
}
|
|
24175
|
+
]
|
|
24176
|
+
};
|
|
24177
|
+
});
|
|
24178
|
+
server.tool("capture_quality", "Show how healthy Engrm capture is across the workspace: raw chronology coverage, checkpoints, and provenance by tool.", {
|
|
24179
|
+
limit: exports_external.number().optional().describe("Maximum projects to include in the top-projects section."),
|
|
24180
|
+
user_id: exports_external.string().optional().describe("Optional user override; defaults to the configured user.")
|
|
24181
|
+
}, async (params) => {
|
|
24182
|
+
const result = getCaptureQuality(db, {
|
|
24183
|
+
limit: params.limit,
|
|
24184
|
+
user_id: params.user_id ?? config2.user_id
|
|
24185
|
+
});
|
|
24186
|
+
const provenanceLines = result.provenance_summary.length > 0 ? result.provenance_summary.map((item) => `- ${item.tool}: ${item.count}`).join(`
|
|
24164
24187
|
`) : "- (none)";
|
|
24165
|
-
|
|
24188
|
+
const checkpointTypeLines = result.assistant_checkpoint_types.length > 0 ? result.assistant_checkpoint_types.map((item) => `- ${item.type}: ${item.count}`).join(`
|
|
24166
24189
|
`) : "- (none)";
|
|
24167
|
-
|
|
24190
|
+
const provenanceMixLines = result.provenance_type_mix.length > 0 ? result.provenance_type_mix.map((item) => `- ${item.tool}: ${item.top_types.map((entry) => `${entry.type} ${entry.count}`).join(", ")}`).join(`
|
|
24168
24191
|
`) : "- (none)";
|
|
24169
|
-
|
|
24192
|
+
const projectLines = result.top_projects.length > 0 ? result.top_projects.map((project) => `- ${project.name} [${project.raw_capture_state}] obs=${project.observation_count} sessions=${project.session_count} prompts=${project.prompt_count} tools=${project.tool_event_count} checkpoints=${project.assistant_checkpoint_count} chat=${project.chat_message_count} (${project.chat_coverage_state})`).join(`
|
|
24170
24193
|
`) : "- (none)";
|
|
24171
|
-
|
|
24172
|
-
|
|
24173
|
-
|
|
24174
|
-
|
|
24175
|
-
|
|
24194
|
+
return {
|
|
24195
|
+
content: [
|
|
24196
|
+
{
|
|
24197
|
+
type: "text",
|
|
24198
|
+
text: `Workspace totals: projects=${result.totals.projects}, observations=${result.totals.observations}, sessions=${result.totals.sessions}, prompts=${result.totals.prompts}, tools=${result.totals.tool_events}, checkpoints=${result.totals.assistant_checkpoints}, chat=${result.totals.chat_messages}
|
|
24176
24199
|
|
|
24177
24200
|
` + `Session capture states: rich=${result.session_states.rich}, partial=${result.session_states.partial}, summary-only=${result.session_states.summary_only}, legacy=${result.session_states.legacy}
|
|
24178
24201
|
|
|
@@ -24191,126 +24214,126 @@ ${provenanceMixLines}
|
|
|
24191
24214
|
|
|
24192
24215
|
` + `Top projects:
|
|
24193
24216
|
${projectLines}`
|
|
24194
|
-
|
|
24195
|
-
|
|
24196
|
-
|
|
24197
|
-
});
|
|
24198
|
-
server.tool("agent_memory_index", "Compare continuity and capture health across Claude Code, Codex, OpenClaw, and other agents for the current project or workspace.", {
|
|
24199
|
-
|
|
24200
|
-
|
|
24201
|
-
|
|
24202
|
-
}, async (params) => {
|
|
24203
|
-
|
|
24204
|
-
|
|
24205
|
-
|
|
24206
|
-
|
|
24207
|
-
|
|
24208
|
-
|
|
24209
|
-
|
|
24210
|
-
|
|
24211
|
-
|
|
24212
|
-
|
|
24213
|
-
|
|
24214
|
-
|
|
24217
|
+
}
|
|
24218
|
+
]
|
|
24219
|
+
};
|
|
24220
|
+
});
|
|
24221
|
+
server.tool("agent_memory_index", "Compare continuity and capture health across Claude Code, Codex, OpenClaw, and other agents for the current project or workspace.", {
|
|
24222
|
+
cwd: exports_external.string().optional().describe("Project path to inspect. Defaults to the current working directory."),
|
|
24223
|
+
project_scoped: exports_external.boolean().optional().describe("If true, limit results to the current project instead of the whole workspace."),
|
|
24224
|
+
user_id: exports_external.string().optional().describe("Optional user override; defaults to the configured user.")
|
|
24225
|
+
}, async (params) => {
|
|
24226
|
+
const result = getAgentMemoryIndex(db, {
|
|
24227
|
+
cwd: params.cwd ?? process.cwd(),
|
|
24228
|
+
project_scoped: params.project_scoped,
|
|
24229
|
+
user_id: params.user_id ?? config2.user_id
|
|
24230
|
+
});
|
|
24231
|
+
const rows = result.agents.length > 0 ? result.agents.map((agent) => {
|
|
24232
|
+
const lastSeen = agent.last_seen_epoch ? new Date(agent.last_seen_epoch * 1000).toISOString().replace("T", " ").slice(0, 16) : "unknown";
|
|
24233
|
+
const latest = agent.latest_summary ? ` latest="${agent.latest_summary.replace(/\s+/g, " ").trim().slice(0, 120)}"` : "";
|
|
24234
|
+
const devices = agent.devices.length > 0 ? ` devices=[${agent.devices.join(", ")}]` : "";
|
|
24235
|
+
const exact = agent.best_recall_key ? ` open=load_recall_item("${agent.best_recall_key}")` : "";
|
|
24236
|
+
return `- ${agent.agent}: continuity=${agent.continuity_state} capture=${agent.capture_state} resume=${agent.resume_freshness} chat=${agent.chat_coverage_state} sessions=${agent.session_count} prompts=${agent.prompt_count} tools=${agent.tool_event_count} obs=${agent.observation_count} handoffs=${agent.handoff_count} chat_msgs=${agent.chat_message_count} last_seen=${lastSeen}${devices}${latest}${exact} resume_call=resume_thread(agent="${agent.agent}")`;
|
|
24237
|
+
}).join(`
|
|
24215
24238
|
`) : "- (none)";
|
|
24216
|
-
|
|
24217
|
-
|
|
24218
|
-
|
|
24219
|
-
|
|
24220
|
-
|
|
24239
|
+
return {
|
|
24240
|
+
content: [
|
|
24241
|
+
{
|
|
24242
|
+
type: "text",
|
|
24243
|
+
text: `${result.project ? `Project: ${result.project}
|
|
24221
24244
|
|
|
24222
24245
|
` : ""}` + `Agent memory index:
|
|
24223
24246
|
${rows}
|
|
24224
24247
|
|
|
24225
24248
|
` + `Suggested next step: ${result.suggested_tools.join(", ") || "(none)"}`
|
|
24226
|
-
|
|
24227
|
-
|
|
24228
|
-
|
|
24229
|
-
});
|
|
24230
|
-
server.tool("tool_memory_index", "Show which tools are actually producing durable memory, which plugins they exercise, and what memory types they create.", {
|
|
24231
|
-
|
|
24232
|
-
|
|
24233
|
-
|
|
24234
|
-
|
|
24235
|
-
}, async (params) => {
|
|
24236
|
-
|
|
24237
|
-
|
|
24238
|
-
|
|
24239
|
-
|
|
24240
|
-
|
|
24241
|
-
|
|
24242
|
-
|
|
24243
|
-
|
|
24244
|
-
|
|
24245
|
-
|
|
24246
|
-
|
|
24247
|
-
|
|
24248
|
-
|
|
24249
|
+
}
|
|
24250
|
+
]
|
|
24251
|
+
};
|
|
24252
|
+
});
|
|
24253
|
+
server.tool("tool_memory_index", "Show which tools are actually producing durable memory, which plugins they exercise, and what memory types they create.", {
|
|
24254
|
+
cwd: exports_external.string().optional().describe("Project path to inspect. Defaults to the current working directory."),
|
|
24255
|
+
project_scoped: exports_external.boolean().optional().describe("If true, limit results to the current project instead of the whole workspace."),
|
|
24256
|
+
limit: exports_external.number().optional().describe("Maximum tools to include."),
|
|
24257
|
+
user_id: exports_external.string().optional().describe("Optional user override; defaults to the configured user.")
|
|
24258
|
+
}, async (params) => {
|
|
24259
|
+
const result = getToolMemoryIndex(db, {
|
|
24260
|
+
cwd: params.cwd ?? process.cwd(),
|
|
24261
|
+
project_scoped: params.project_scoped,
|
|
24262
|
+
limit: params.limit,
|
|
24263
|
+
user_id: params.user_id ?? config2.user_id
|
|
24264
|
+
});
|
|
24265
|
+
const toolLines = result.tools.length > 0 ? result.tools.map((tool) => {
|
|
24266
|
+
const typeMix = tool.top_types.map((item) => `${item.type} ${item.count}`).join(", ");
|
|
24267
|
+
const pluginMix = tool.top_plugins.length > 0 ? ` plugins=[${tool.top_plugins.map((item) => `${item.plugin} ${item.count}`).join(", ")}]` : "";
|
|
24268
|
+
const sample = tool.sample_titles[0] ? ` sample="${tool.sample_titles[0]}"` : "";
|
|
24269
|
+
const promptInfo = typeof tool.latest_prompt_number === "number" ? ` latest_prompt=#${tool.latest_prompt_number}` : "";
|
|
24270
|
+
return `- ${tool.tool}: obs=${tool.observation_count} sessions=${tool.session_count}${promptInfo} types=[${typeMix}]${pluginMix}${sample}`;
|
|
24271
|
+
}).join(`
|
|
24249
24272
|
`) : "- (none)";
|
|
24250
|
-
|
|
24251
|
-
|
|
24252
|
-
|
|
24253
|
-
|
|
24254
|
-
|
|
24273
|
+
return {
|
|
24274
|
+
content: [
|
|
24275
|
+
{
|
|
24276
|
+
type: "text",
|
|
24277
|
+
text: `${result.project ? `Project: ${result.project}
|
|
24255
24278
|
|
|
24256
24279
|
` : ""}` + `Tools producing durable memory:
|
|
24257
24280
|
${toolLines}`
|
|
24258
|
-
|
|
24259
|
-
|
|
24260
|
-
|
|
24261
|
-
});
|
|
24262
|
-
server.tool("session_tool_memory", "Show which tools in one session produced durable memory and which tools produced none", {
|
|
24263
|
-
|
|
24264
|
-
}, async (params) => {
|
|
24265
|
-
|
|
24266
|
-
|
|
24267
|
-
|
|
24268
|
-
|
|
24269
|
-
|
|
24270
|
-
|
|
24271
|
-
|
|
24272
|
-
|
|
24281
|
+
}
|
|
24282
|
+
]
|
|
24283
|
+
};
|
|
24284
|
+
});
|
|
24285
|
+
server.tool("session_tool_memory", "Show which tools in one session produced durable memory and which tools produced none", {
|
|
24286
|
+
session_id: exports_external.string().describe("Session ID to inspect")
|
|
24287
|
+
}, async (params) => {
|
|
24288
|
+
const result = getSessionToolMemory(db, params);
|
|
24289
|
+
const toolLines = result.tools.length > 0 ? result.tools.map((tool) => {
|
|
24290
|
+
const typeMix = tool.top_types.map((item) => `${item.type} ${item.count}`).join(", ");
|
|
24291
|
+
const pluginMix = tool.top_plugins.length > 0 ? ` plugins=[${tool.top_plugins.map((item) => `${item.plugin} ${item.count}`).join(", ")}]` : "";
|
|
24292
|
+
const sample = tool.sample_titles[0] ? ` sample="${tool.sample_titles[0]}"` : "";
|
|
24293
|
+
const promptInfo = typeof tool.latest_prompt_number === "number" ? ` latest_prompt=#${tool.latest_prompt_number}` : "";
|
|
24294
|
+
return `- ${tool.tool}: events=${tool.tool_event_count} observations=${tool.observation_count}${promptInfo} types=[${typeMix}]${pluginMix}${sample}`;
|
|
24295
|
+
}).join(`
|
|
24273
24296
|
`) : "- (none)";
|
|
24274
|
-
|
|
24297
|
+
const unmappedLines = result.tools_without_memory.length > 0 ? result.tools_without_memory.map((tool) => `- ${tool.tool}: events=${tool.tool_event_count}`).join(`
|
|
24275
24298
|
`) : "- (none)";
|
|
24276
|
-
|
|
24277
|
-
|
|
24278
|
-
|
|
24279
|
-
|
|
24280
|
-
|
|
24299
|
+
return {
|
|
24300
|
+
content: [
|
|
24301
|
+
{
|
|
24302
|
+
type: "text",
|
|
24303
|
+
text: `Session: ${result.session_id}
|
|
24281
24304
|
|
|
24282
24305
|
` + `Tools producing durable memory:
|
|
24283
24306
|
${toolLines}
|
|
24284
24307
|
|
|
24285
24308
|
` + `Tools without durable memory:
|
|
24286
24309
|
${unmappedLines}`
|
|
24287
|
-
|
|
24288
|
-
|
|
24289
|
-
};
|
|
24290
|
-
});
|
|
24291
|
-
server.tool("session_context", "Preview the exact project memory context Engrm would inject at session start", {
|
|
24292
|
-
cwd: exports_external.string().optional(),
|
|
24293
|
-
token_budget: exports_external.number().optional(),
|
|
24294
|
-
scope: exports_external.enum(["personal", "team", "all"]).optional(),
|
|
24295
|
-
user_id: exports_external.string().optional()
|
|
24296
|
-
}, async (params) => {
|
|
24297
|
-
const result = getSessionContext(db, {
|
|
24298
|
-
cwd: params.cwd ?? process.cwd(),
|
|
24299
|
-
token_budget: params.token_budget,
|
|
24300
|
-
scope: params.scope,
|
|
24301
|
-
user_id: params.user_id ?? config2.user_id,
|
|
24302
|
-
current_device_id: config2.device_id
|
|
24303
|
-
});
|
|
24304
|
-
if (!result) {
|
|
24305
|
-
return {
|
|
24306
|
-
content: [{ type: "text", text: "No session context available." }]
|
|
24310
|
+
}
|
|
24311
|
+
]
|
|
24307
24312
|
};
|
|
24308
|
-
}
|
|
24309
|
-
|
|
24310
|
-
|
|
24311
|
-
|
|
24312
|
-
|
|
24313
|
-
|
|
24313
|
+
});
|
|
24314
|
+
server.tool("session_context", "Preview the exact project memory context Engrm would inject at session start", {
|
|
24315
|
+
cwd: exports_external.string().optional(),
|
|
24316
|
+
token_budget: exports_external.number().optional(),
|
|
24317
|
+
scope: exports_external.enum(["personal", "team", "all"]).optional(),
|
|
24318
|
+
user_id: exports_external.string().optional()
|
|
24319
|
+
}, async (params) => {
|
|
24320
|
+
const result = getSessionContext(db, {
|
|
24321
|
+
cwd: params.cwd ?? process.cwd(),
|
|
24322
|
+
token_budget: params.token_budget,
|
|
24323
|
+
scope: params.scope,
|
|
24324
|
+
user_id: params.user_id ?? config2.user_id,
|
|
24325
|
+
current_device_id: config2.device_id
|
|
24326
|
+
});
|
|
24327
|
+
if (!result) {
|
|
24328
|
+
return {
|
|
24329
|
+
content: [{ type: "text", text: "No session context available." }]
|
|
24330
|
+
};
|
|
24331
|
+
}
|
|
24332
|
+
return {
|
|
24333
|
+
content: [
|
|
24334
|
+
{
|
|
24335
|
+
type: "text",
|
|
24336
|
+
text: `Project: ${result.project_name}
|
|
24314
24337
|
` + `Canonical ID: ${result.canonical_id}
|
|
24315
24338
|
` + `Agents active: ${result.active_agents.join(", ") || "(none)"}
|
|
24316
24339
|
` + `Continuity: ${result.continuity_state} — ${result.continuity_summary}
|
|
@@ -24334,77 +24357,77 @@ server.tool("session_context", "Preview the exact project memory context Engrm w
|
|
|
24334
24357
|
` + `Raw chronology active: ${result.raw_capture_active ? "yes" : "no"}
|
|
24335
24358
|
|
|
24336
24359
|
` + result.preview
|
|
24337
|
-
|
|
24338
|
-
|
|
24339
|
-
|
|
24340
|
-
});
|
|
24341
|
-
server.tool("activity_feed", "Show one chronological local feed across prompts, tools, chat, observations, handoffs, and summaries", {
|
|
24342
|
-
limit: exports_external.number().optional(),
|
|
24343
|
-
project_scoped: exports_external.boolean().optional(),
|
|
24344
|
-
session_id: exports_external.string().optional(),
|
|
24345
|
-
cwd: exports_external.string().optional(),
|
|
24346
|
-
user_id: exports_external.string().optional()
|
|
24347
|
-
}, async (params) => {
|
|
24348
|
-
const result = getActivityFeed(db, {
|
|
24349
|
-
...params,
|
|
24350
|
-
cwd: params.cwd ?? process.cwd(),
|
|
24351
|
-
user_id: params.user_id ?? config2.user_id
|
|
24360
|
+
}
|
|
24361
|
+
]
|
|
24362
|
+
};
|
|
24352
24363
|
});
|
|
24353
|
-
|
|
24364
|
+
server.tool("activity_feed", "Show one chronological local feed across prompts, tools, chat, observations, handoffs, and summaries", {
|
|
24365
|
+
limit: exports_external.number().optional(),
|
|
24366
|
+
project_scoped: exports_external.boolean().optional(),
|
|
24367
|
+
session_id: exports_external.string().optional(),
|
|
24368
|
+
cwd: exports_external.string().optional(),
|
|
24369
|
+
user_id: exports_external.string().optional()
|
|
24370
|
+
}, async (params) => {
|
|
24371
|
+
const result = getActivityFeed(db, {
|
|
24372
|
+
...params,
|
|
24373
|
+
cwd: params.cwd ?? process.cwd(),
|
|
24374
|
+
user_id: params.user_id ?? config2.user_id
|
|
24375
|
+
});
|
|
24376
|
+
const projectLine = result.project ? `Project: ${result.project}
|
|
24354
24377
|
` : "";
|
|
24355
|
-
|
|
24356
|
-
|
|
24357
|
-
|
|
24358
|
-
|
|
24359
|
-
|
|
24378
|
+
const rows = result.events.length > 0 ? result.events.map((event) => {
|
|
24379
|
+
const stamp = new Date(event.created_at_epoch * 1000).toISOString().replace("T", " ").slice(0, 16);
|
|
24380
|
+
const kind = event.kind === "handoff" && event.handoff_kind ? `${event.kind}:${event.handoff_kind}` : event.kind === "observation" && event.observation_type ? `${event.kind}:${event.observation_type}` : event.kind;
|
|
24381
|
+
return `- ${stamp} [${kind}] ${event.title}${event.detail ? ` — ${event.detail}` : ""}`;
|
|
24382
|
+
}).join(`
|
|
24360
24383
|
`) : "- (none)";
|
|
24361
|
-
return {
|
|
24362
|
-
content: [
|
|
24363
|
-
{
|
|
24364
|
-
type: "text",
|
|
24365
|
-
text: `${projectLine}Activity feed:
|
|
24366
|
-
${rows}`
|
|
24367
|
-
}
|
|
24368
|
-
]
|
|
24369
|
-
};
|
|
24370
|
-
});
|
|
24371
|
-
server.tool("project_memory_index", "Show a typed local memory index for the current project", {
|
|
24372
|
-
cwd: exports_external.string().optional(),
|
|
24373
|
-
user_id: exports_external.string().optional()
|
|
24374
|
-
}, async (params) => {
|
|
24375
|
-
const result = getProjectMemoryIndex(db, {
|
|
24376
|
-
cwd: params.cwd ?? process.cwd(),
|
|
24377
|
-
user_id: params.user_id ?? config2.user_id
|
|
24378
|
-
});
|
|
24379
|
-
if (!result) {
|
|
24380
24384
|
return {
|
|
24381
|
-
content: [
|
|
24385
|
+
content: [
|
|
24386
|
+
{
|
|
24387
|
+
type: "text",
|
|
24388
|
+
text: `${projectLine}Activity feed:
|
|
24389
|
+
${rows}`
|
|
24390
|
+
}
|
|
24391
|
+
]
|
|
24382
24392
|
};
|
|
24383
|
-
}
|
|
24384
|
-
|
|
24393
|
+
});
|
|
24394
|
+
server.tool("project_memory_index", "Show a typed local memory index for the current project", {
|
|
24395
|
+
cwd: exports_external.string().optional(),
|
|
24396
|
+
user_id: exports_external.string().optional()
|
|
24397
|
+
}, async (params) => {
|
|
24398
|
+
const result = getProjectMemoryIndex(db, {
|
|
24399
|
+
cwd: params.cwd ?? process.cwd(),
|
|
24400
|
+
user_id: params.user_id ?? config2.user_id
|
|
24401
|
+
});
|
|
24402
|
+
if (!result) {
|
|
24403
|
+
return {
|
|
24404
|
+
content: [{ type: "text", text: "No project memory found for this folder yet." }]
|
|
24405
|
+
};
|
|
24406
|
+
}
|
|
24407
|
+
const counts = Object.entries(result.observation_counts).sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0])).map(([type, count]) => `- ${type}: ${count}`).join(`
|
|
24385
24408
|
`) || "- (none)";
|
|
24386
|
-
|
|
24387
|
-
|
|
24388
|
-
|
|
24389
|
-
|
|
24409
|
+
const sessions = result.recent_sessions.length > 0 ? result.recent_sessions.map((session) => {
|
|
24410
|
+
const label = session.request ?? session.completed ?? "(no summary)";
|
|
24411
|
+
return `- ${session.session_id} :: ${label.replace(/\s+/g, " ").trim()}`;
|
|
24412
|
+
}).join(`
|
|
24390
24413
|
`) : "- (none)";
|
|
24391
|
-
|
|
24414
|
+
const hotFiles = result.hot_files.length > 0 ? result.hot_files.map((file2) => `- ${file2.path} (${file2.count})`).join(`
|
|
24392
24415
|
`) : "- (none)";
|
|
24393
|
-
|
|
24416
|
+
const provenance = result.provenance_summary.length > 0 ? result.provenance_summary.map((item) => `- ${item.tool}: ${item.count}`).join(`
|
|
24394
24417
|
`) : "- (none)";
|
|
24395
|
-
|
|
24418
|
+
const topTypes = result.top_types.length > 0 ? result.top_types.map((item) => `- ${item.type}: ${item.count}`).join(`
|
|
24396
24419
|
`) : "- (none)";
|
|
24397
|
-
|
|
24420
|
+
const topTitles = result.top_titles.length > 0 ? result.top_titles.map((item) => `- #${item.id} [${item.type}] ${item.title}`).join(`
|
|
24398
24421
|
`) : "- (none)";
|
|
24399
|
-
|
|
24422
|
+
const recallPreviewLines = result.recall_index_preview.length > 0 ? result.recall_index_preview.map((item) => `- ${item.key} [${item.kind} · ${item.freshness}${item.source_agent ? ` · ${item.source_agent}` : ""}] ${item.title}`).join(`
|
|
24400
24423
|
`) : "- (none)";
|
|
24401
|
-
|
|
24424
|
+
const openExactLine = result.best_recall_key ? `Open exact: load_recall_item("${result.best_recall_key}")${result.best_recall_title ? ` # ${result.best_recall_title}` : ""}
|
|
24402
24425
|
` : "";
|
|
24403
|
-
|
|
24404
|
-
|
|
24405
|
-
|
|
24406
|
-
|
|
24407
|
-
|
|
24426
|
+
return {
|
|
24427
|
+
content: [
|
|
24428
|
+
{
|
|
24429
|
+
type: "text",
|
|
24430
|
+
text: `Project: ${result.project}
|
|
24408
24431
|
` + `Canonical ID: ${result.canonical_id}
|
|
24409
24432
|
` + `Agents active: ${result.active_agents.join(", ") || "(none)"}
|
|
24410
24433
|
` + `Continuity: ${result.continuity_state} — ${result.continuity_summary}
|
|
@@ -24449,84 +24472,84 @@ ${provenance}
|
|
|
24449
24472
|
|
|
24450
24473
|
` + `Recent memory objects:
|
|
24451
24474
|
${topTitles}`
|
|
24452
|
-
|
|
24453
|
-
|
|
24454
|
-
|
|
24455
|
-
});
|
|
24456
|
-
server.tool("project_related_work", "Show work that looks relevant to the current repo but is currently stored under other projects", {
|
|
24457
|
-
|
|
24458
|
-
|
|
24459
|
-
|
|
24460
|
-
}, async (params) => {
|
|
24461
|
-
|
|
24462
|
-
|
|
24463
|
-
|
|
24464
|
-
|
|
24465
|
-
|
|
24466
|
-
|
|
24475
|
+
}
|
|
24476
|
+
]
|
|
24477
|
+
};
|
|
24478
|
+
});
|
|
24479
|
+
server.tool("project_related_work", "Show work that looks relevant to the current repo but is currently stored under other projects", {
|
|
24480
|
+
cwd: exports_external.string().optional(),
|
|
24481
|
+
user_id: exports_external.string().optional(),
|
|
24482
|
+
limit: exports_external.number().optional()
|
|
24483
|
+
}, async (params) => {
|
|
24484
|
+
const result = getProjectRelatedWork(db, {
|
|
24485
|
+
cwd: params.cwd ?? process.cwd(),
|
|
24486
|
+
user_id: params.user_id ?? config2.user_id,
|
|
24487
|
+
limit: params.limit
|
|
24488
|
+
});
|
|
24489
|
+
const rows = result.related.length > 0 ? result.related.map((item) => `- #${item.id} [${item.type}] ${item.title} :: stored under ${item.source_project} (${item.matched_on})`).join(`
|
|
24467
24490
|
`) : "- (none)";
|
|
24468
|
-
|
|
24469
|
-
|
|
24470
|
-
|
|
24471
|
-
|
|
24472
|
-
|
|
24491
|
+
return {
|
|
24492
|
+
content: [
|
|
24493
|
+
{
|
|
24494
|
+
type: "text",
|
|
24495
|
+
text: `Project: ${result.project}
|
|
24473
24496
|
` + `Canonical ID: ${result.canonical_id}
|
|
24474
24497
|
|
|
24475
24498
|
` + `Related work stored under other projects:
|
|
24476
24499
|
${rows}`
|
|
24477
|
-
|
|
24478
|
-
|
|
24479
|
-
|
|
24480
|
-
});
|
|
24481
|
-
server.tool("reclassify_project_memory", "Move repo-relevant observations currently stored under other projects into the current git project", {
|
|
24482
|
-
|
|
24483
|
-
|
|
24484
|
-
|
|
24485
|
-
|
|
24486
|
-
}, async (params) => {
|
|
24487
|
-
|
|
24488
|
-
|
|
24489
|
-
|
|
24490
|
-
|
|
24491
|
-
|
|
24492
|
-
|
|
24493
|
-
|
|
24500
|
+
}
|
|
24501
|
+
]
|
|
24502
|
+
};
|
|
24503
|
+
});
|
|
24504
|
+
server.tool("reclassify_project_memory", "Move repo-relevant observations currently stored under other projects into the current git project", {
|
|
24505
|
+
cwd: exports_external.string().optional(),
|
|
24506
|
+
user_id: exports_external.string().optional(),
|
|
24507
|
+
limit: exports_external.number().optional(),
|
|
24508
|
+
dry_run: exports_external.boolean().optional()
|
|
24509
|
+
}, async (params) => {
|
|
24510
|
+
const result = reclassifyProjectMemory(db, {
|
|
24511
|
+
cwd: params.cwd ?? process.cwd(),
|
|
24512
|
+
user_id: params.user_id ?? config2.user_id,
|
|
24513
|
+
limit: params.limit,
|
|
24514
|
+
dry_run: params.dry_run
|
|
24515
|
+
});
|
|
24516
|
+
const rows = result.candidates.length > 0 ? result.candidates.map((item) => `- #${item.id} [${item.type}] ${item.title} :: from ${item.from} (${item.matched_on})${item.moved ? " -> moved" : ""}`).join(`
|
|
24494
24517
|
`) : "- (none)";
|
|
24495
|
-
|
|
24496
|
-
|
|
24497
|
-
|
|
24498
|
-
|
|
24499
|
-
|
|
24518
|
+
return {
|
|
24519
|
+
content: [
|
|
24520
|
+
{
|
|
24521
|
+
type: "text",
|
|
24522
|
+
text: `Project: ${result.project}
|
|
24500
24523
|
` + `Canonical ID: ${result.canonical_id}
|
|
24501
24524
|
` + `Dry run: ${params.dry_run === true ? "yes" : "no"}
|
|
24502
24525
|
` + `Moved: ${result.moved}
|
|
24503
24526
|
|
|
24504
24527
|
` + `Candidates:
|
|
24505
24528
|
${rows}`
|
|
24506
|
-
|
|
24507
|
-
|
|
24508
|
-
|
|
24509
|
-
});
|
|
24510
|
-
server.tool("workspace_memory_index", "Show a cross-project local memory index for the whole Engrm workspace", {
|
|
24511
|
-
|
|
24512
|
-
|
|
24513
|
-
}, async (params) => {
|
|
24514
|
-
|
|
24515
|
-
|
|
24516
|
-
|
|
24517
|
-
|
|
24518
|
-
|
|
24519
|
-
|
|
24520
|
-
|
|
24521
|
-
|
|
24529
|
+
}
|
|
24530
|
+
]
|
|
24531
|
+
};
|
|
24532
|
+
});
|
|
24533
|
+
server.tool("workspace_memory_index", "Show a cross-project local memory index for the whole Engrm workspace", {
|
|
24534
|
+
limit: exports_external.number().optional(),
|
|
24535
|
+
user_id: exports_external.string().optional()
|
|
24536
|
+
}, async (params) => {
|
|
24537
|
+
const result = getWorkspaceMemoryIndex(db, {
|
|
24538
|
+
limit: params.limit,
|
|
24539
|
+
user_id: params.user_id ?? config2.user_id
|
|
24540
|
+
});
|
|
24541
|
+
const projectLines = result.projects.length > 0 ? result.projects.map((project) => {
|
|
24542
|
+
const when = new Date(project.last_active_epoch * 1000).toISOString().split("T")[0];
|
|
24543
|
+
return `- ${project.name} (${when}) obs=${project.observation_count} sessions=${project.session_count} prompts=${project.prompt_count} tools=${project.tool_event_count} checkpoints=${project.assistant_checkpoint_count}`;
|
|
24544
|
+
}).join(`
|
|
24522
24545
|
`) : "- (none)";
|
|
24523
|
-
|
|
24546
|
+
const provenanceLines = result.provenance_summary.length > 0 ? result.provenance_summary.map((item) => `- ${item.tool}: ${item.count}`).join(`
|
|
24524
24547
|
`) : "- (none)";
|
|
24525
|
-
|
|
24526
|
-
|
|
24527
|
-
|
|
24528
|
-
|
|
24529
|
-
|
|
24548
|
+
return {
|
|
24549
|
+
content: [
|
|
24550
|
+
{
|
|
24551
|
+
type: "text",
|
|
24552
|
+
text: `Workspace totals: observations=${result.totals.observations}, sessions=${result.totals.sessions}, prompts=${result.totals.prompts}, tools=${result.totals.tool_events}, checkpoints=${result.totals.assistant_checkpoints}
|
|
24530
24553
|
|
|
24531
24554
|
` + `Projects with raw chronology: ${result.projects_with_raw_capture}
|
|
24532
24555
|
|
|
@@ -24535,364 +24558,364 @@ ${provenanceLines}
|
|
|
24535
24558
|
|
|
24536
24559
|
` + `Projects:
|
|
24537
24560
|
${projectLines}`
|
|
24538
|
-
|
|
24539
|
-
|
|
24540
|
-
};
|
|
24541
|
-
});
|
|
24542
|
-
server.tool("create_handoff", "Capture an explicit cross-device handoff from the current or specified session into syncable memory", {
|
|
24543
|
-
session_id: exports_external.string().optional().describe("Optional session ID to hand off; defaults to the latest recent session"),
|
|
24544
|
-
cwd: exports_external.string().optional().describe("Repo path used to scope the handoff when no session ID is provided"),
|
|
24545
|
-
title: exports_external.string().optional().describe("Optional short handoff title"),
|
|
24546
|
-
include_chat: exports_external.boolean().optional().describe("Include a few recent chat snippets in the handoff"),
|
|
24547
|
-
chat_limit: exports_external.number().optional().describe("How many recent chat snippets to include when include_chat is true")
|
|
24548
|
-
}, async (params) => {
|
|
24549
|
-
const result = await createHandoff(db, config2, params);
|
|
24550
|
-
if (!result.success) {
|
|
24551
|
-
return {
|
|
24552
|
-
content: [{ type: "text", text: `Handoff not created: ${result.reason}` }]
|
|
24561
|
+
}
|
|
24562
|
+
]
|
|
24553
24563
|
};
|
|
24554
|
-
}
|
|
24555
|
-
|
|
24556
|
-
|
|
24557
|
-
|
|
24558
|
-
|
|
24559
|
-
|
|
24560
|
-
|
|
24561
|
-
|
|
24562
|
-
|
|
24563
|
-
|
|
24564
|
-
|
|
24565
|
-
|
|
24566
|
-
|
|
24567
|
-
|
|
24568
|
-
include_chat: exports_external.boolean().optional().describe("Include a few recent chat snippets in the rolling handoff draft"),
|
|
24569
|
-
chat_limit: exports_external.number().optional().describe("How many recent chat snippets to include when include_chat is true")
|
|
24570
|
-
}, async (params) => {
|
|
24571
|
-
const result = await upsertRollingHandoff(db, config2, params);
|
|
24572
|
-
if (!result.success) {
|
|
24564
|
+
});
|
|
24565
|
+
server.tool("create_handoff", "Capture an explicit cross-device handoff from the current or specified session into syncable memory", {
|
|
24566
|
+
session_id: exports_external.string().optional().describe("Optional session ID to hand off; defaults to the latest recent session"),
|
|
24567
|
+
cwd: exports_external.string().optional().describe("Repo path used to scope the handoff when no session ID is provided"),
|
|
24568
|
+
title: exports_external.string().optional().describe("Optional short handoff title"),
|
|
24569
|
+
include_chat: exports_external.boolean().optional().describe("Include a few recent chat snippets in the handoff"),
|
|
24570
|
+
chat_limit: exports_external.number().optional().describe("How many recent chat snippets to include when include_chat is true")
|
|
24571
|
+
}, async (params) => {
|
|
24572
|
+
const result = await createHandoff(db, config2, params);
|
|
24573
|
+
if (!result.success) {
|
|
24574
|
+
return {
|
|
24575
|
+
content: [{ type: "text", text: `Handoff not created: ${result.reason}` }]
|
|
24576
|
+
};
|
|
24577
|
+
}
|
|
24573
24578
|
return {
|
|
24574
|
-
content: [
|
|
24575
|
-
|
|
24576
|
-
|
|
24577
|
-
|
|
24578
|
-
content: [
|
|
24579
|
-
{
|
|
24580
|
-
type: "text",
|
|
24581
|
-
text: `Rolling handoff draft #${result.observation_id} refreshed for session ${result.session_id}
|
|
24579
|
+
content: [
|
|
24580
|
+
{
|
|
24581
|
+
type: "text",
|
|
24582
|
+
text: `Created handoff #${result.observation_id} for session ${result.session_id}
|
|
24582
24583
|
Title: ${result.title}`
|
|
24583
|
-
|
|
24584
|
-
|
|
24585
|
-
|
|
24586
|
-
});
|
|
24587
|
-
server.tool("
|
|
24588
|
-
|
|
24589
|
-
|
|
24590
|
-
|
|
24591
|
-
|
|
24592
|
-
|
|
24593
|
-
|
|
24594
|
-
|
|
24584
|
+
}
|
|
24585
|
+
]
|
|
24586
|
+
};
|
|
24587
|
+
});
|
|
24588
|
+
server.tool("refresh_handoff", "Refresh the rolling live handoff draft for the current or specified session without creating a new saved handoff", {
|
|
24589
|
+
session_id: exports_external.string().optional().describe("Optional session ID to refresh; defaults to the latest recent session"),
|
|
24590
|
+
cwd: exports_external.string().optional().describe("Repo path used to scope the rolling handoff when no session ID is provided"),
|
|
24591
|
+
include_chat: exports_external.boolean().optional().describe("Include a few recent chat snippets in the rolling handoff draft"),
|
|
24592
|
+
chat_limit: exports_external.number().optional().describe("How many recent chat snippets to include when include_chat is true")
|
|
24593
|
+
}, async (params) => {
|
|
24594
|
+
const result = await upsertRollingHandoff(db, config2, params);
|
|
24595
|
+
if (!result.success) {
|
|
24596
|
+
return {
|
|
24597
|
+
content: [{ type: "text", text: `Rolling handoff not refreshed: ${result.reason}` }]
|
|
24598
|
+
};
|
|
24599
|
+
}
|
|
24595
24600
|
return {
|
|
24596
|
-
content: [
|
|
24601
|
+
content: [
|
|
24602
|
+
{
|
|
24603
|
+
type: "text",
|
|
24604
|
+
text: `Rolling handoff draft #${result.observation_id} refreshed for session ${result.session_id}
|
|
24605
|
+
Title: ${result.title}`
|
|
24606
|
+
}
|
|
24607
|
+
]
|
|
24597
24608
|
};
|
|
24598
|
-
}
|
|
24599
|
-
|
|
24600
|
-
|
|
24601
|
-
|
|
24602
|
-
|
|
24603
|
-
|
|
24604
|
-
|
|
24609
|
+
});
|
|
24610
|
+
server.tool("refresh_chat_recall", "Hydrate the separate chat lane from the current Claude transcript so long sessions keep their full user/assistant thread", {
|
|
24611
|
+
session_id: exports_external.string().optional().describe("Optional session ID; defaults to the current session when called from hooks or the active repo session when known"),
|
|
24612
|
+
cwd: exports_external.string().optional().describe("Project directory used to resolve the Claude transcript path"),
|
|
24613
|
+
transcript_path: exports_external.string().optional().describe("Optional explicit Claude transcript JSONL path")
|
|
24614
|
+
}, async (params) => {
|
|
24615
|
+
const cwd = params.cwd ?? process.cwd();
|
|
24616
|
+
const sessionId = params.session_id ?? db.getRecentSessions(null, 1, config2.user_id)[0]?.session_id ?? null;
|
|
24617
|
+
if (!sessionId) {
|
|
24618
|
+
return {
|
|
24619
|
+
content: [{ type: "text", text: "No session available to hydrate chat recall from." }]
|
|
24620
|
+
};
|
|
24621
|
+
}
|
|
24622
|
+
const result = await syncTranscriptChat(db, config2, sessionId, cwd, params.transcript_path);
|
|
24623
|
+
return {
|
|
24624
|
+
content: [
|
|
24625
|
+
{
|
|
24626
|
+
type: "text",
|
|
24627
|
+
text: `Chat recall refreshed for session ${sessionId}
|
|
24605
24628
|
Imported: ${result.imported}
|
|
24606
24629
|
Transcript messages seen: ${result.total}`
|
|
24607
|
-
|
|
24608
|
-
|
|
24609
|
-
|
|
24610
|
-
});
|
|
24611
|
-
server.tool("repair_recall", "USE WHEN recall feels thin or under-captured. Rehydrate recent session recall for the current project from Claude transcripts or history fallback before resuming.", {
|
|
24612
|
-
|
|
24613
|
-
|
|
24614
|
-
|
|
24615
|
-
|
|
24616
|
-
|
|
24617
|
-
}, async (params) => {
|
|
24618
|
-
|
|
24619
|
-
|
|
24620
|
-
|
|
24621
|
-
|
|
24622
|
-
|
|
24623
|
-
|
|
24624
|
-
|
|
24625
|
-
|
|
24630
|
+
}
|
|
24631
|
+
]
|
|
24632
|
+
};
|
|
24633
|
+
});
|
|
24634
|
+
server.tool("repair_recall", "USE WHEN recall feels thin or under-captured. Rehydrate recent session recall for the current project from Claude transcripts or history fallback before resuming.", {
|
|
24635
|
+
session_id: exports_external.string().optional().describe("Optional single session ID to repair instead of scanning recent project sessions"),
|
|
24636
|
+
cwd: exports_external.string().optional().describe("Project directory used to resolve project sessions and Claude history/transcript files"),
|
|
24637
|
+
limit: exports_external.number().optional().describe("How many recent sessions to inspect when repairing a project"),
|
|
24638
|
+
user_id: exports_external.string().optional().describe("Optional user override; defaults to the configured user"),
|
|
24639
|
+
transcript_path: exports_external.string().optional().describe("Optional explicit transcript JSONL path when repairing a single session")
|
|
24640
|
+
}, async (params) => {
|
|
24641
|
+
const result = await repairRecall(db, config2, {
|
|
24642
|
+
session_id: params.session_id,
|
|
24643
|
+
cwd: params.cwd ?? process.cwd(),
|
|
24644
|
+
limit: params.limit,
|
|
24645
|
+
user_id: params.user_id ?? config2.user_id,
|
|
24646
|
+
transcript_path: params.transcript_path
|
|
24647
|
+
});
|
|
24648
|
+
const projectLine = result.project_name ? `Project: ${result.project_name}
|
|
24626
24649
|
` : "";
|
|
24627
|
-
|
|
24650
|
+
const rows = result.results.length > 0 ? result.results.map((session) => `- ${session.session_id} [${session.chat_coverage_state}] imported=${session.imported_chat_messages} chat=${session.chat_messages_after} prompts=${session.prompt_count_after} ` + `(transcript ${session.chat_source_summary.transcript} · history ${session.chat_source_summary.history} · hook ${session.chat_source_summary.hook})`).join(`
|
|
24628
24651
|
`) : "- (none)";
|
|
24629
|
-
|
|
24630
|
-
|
|
24631
|
-
|
|
24632
|
-
|
|
24633
|
-
|
|
24652
|
+
return {
|
|
24653
|
+
content: [
|
|
24654
|
+
{
|
|
24655
|
+
type: "text",
|
|
24656
|
+
text: `${projectLine}` + `Scope: ${result.scope}
|
|
24634
24657
|
` + `Inspected sessions: ${result.inspected_sessions}
|
|
24635
24658
|
` + `Sessions with new recall: ${result.sessions_with_imports}
|
|
24636
24659
|
` + `Imported chat messages: ${result.imported_chat_messages}
|
|
24637
24660
|
|
|
24638
24661
|
` + `Repair results:
|
|
24639
24662
|
${rows}`
|
|
24640
|
-
|
|
24641
|
-
|
|
24642
|
-
|
|
24643
|
-
});
|
|
24644
|
-
server.tool("recent_handoffs", "List recent saved handoffs and rolling handoff drafts so you can resume work on another device or in a new session", {
|
|
24645
|
-
limit: exports_external.number().optional(),
|
|
24646
|
-
project_scoped: exports_external.boolean().optional(),
|
|
24647
|
-
cwd: exports_external.string().optional(),
|
|
24648
|
-
user_id: exports_external.string().optional()
|
|
24649
|
-
}, async (params) => {
|
|
24650
|
-
const result = getRecentHandoffs(db, {
|
|
24651
|
-
...params,
|
|
24652
|
-
user_id: params.user_id ?? config2.user_id,
|
|
24653
|
-
current_device_id: config2.device_id
|
|
24663
|
+
}
|
|
24664
|
+
]
|
|
24665
|
+
};
|
|
24654
24666
|
});
|
|
24655
|
-
|
|
24667
|
+
server.tool("recent_handoffs", "List recent saved handoffs and rolling handoff drafts so you can resume work on another device or in a new session", {
|
|
24668
|
+
limit: exports_external.number().optional(),
|
|
24669
|
+
project_scoped: exports_external.boolean().optional(),
|
|
24670
|
+
cwd: exports_external.string().optional(),
|
|
24671
|
+
user_id: exports_external.string().optional()
|
|
24672
|
+
}, async (params) => {
|
|
24673
|
+
const result = getRecentHandoffs(db, {
|
|
24674
|
+
...params,
|
|
24675
|
+
user_id: params.user_id ?? config2.user_id,
|
|
24676
|
+
current_device_id: config2.device_id
|
|
24677
|
+
});
|
|
24678
|
+
const projectLine = result.project ? `Project: ${result.project}
|
|
24656
24679
|
` : "";
|
|
24657
|
-
|
|
24658
|
-
|
|
24659
|
-
|
|
24660
|
-
|
|
24661
|
-
|
|
24680
|
+
const rows = result.handoffs.length > 0 ? result.handoffs.map((handoff) => {
|
|
24681
|
+
const stamp = new Date(handoff.created_at_epoch * 1000).toISOString().replace("T", " ").slice(0, 16);
|
|
24682
|
+
const kind = isDraftHandoff(handoff) ? "draft" : "saved";
|
|
24683
|
+
return `- #${handoff.id} (${stamp}) [${kind}] ${handoff.title}${handoff.project_name ? ` [${handoff.project_name}]` : ""} (${formatHandoffSource(handoff)})`;
|
|
24684
|
+
}).join(`
|
|
24662
24685
|
`) : "- (none)";
|
|
24663
|
-
return {
|
|
24664
|
-
content: [{ type: "text", text: `${projectLine}Recent handoffs:
|
|
24665
|
-
${rows}` }]
|
|
24666
|
-
};
|
|
24667
|
-
});
|
|
24668
|
-
server.tool("load_handoff", "Open the best saved handoff or rolling draft and turn it back into a clear resume point for a new session", {
|
|
24669
|
-
id: exports_external.number().optional().describe("Optional handoff observation ID; defaults to the latest recent handoff"),
|
|
24670
|
-
cwd: exports_external.string().optional(),
|
|
24671
|
-
project_scoped: exports_external.boolean().optional(),
|
|
24672
|
-
user_id: exports_external.string().optional()
|
|
24673
|
-
}, async (params) => {
|
|
24674
|
-
const result = loadHandoff(db, {
|
|
24675
|
-
...params,
|
|
24676
|
-
user_id: params.user_id ?? config2.user_id,
|
|
24677
|
-
current_device_id: config2.device_id
|
|
24678
|
-
});
|
|
24679
|
-
if (!result.handoff) {
|
|
24680
24686
|
return {
|
|
24681
|
-
content: [{ type: "text", text:
|
|
24687
|
+
content: [{ type: "text", text: `${projectLine}Recent handoffs:
|
|
24688
|
+
${rows}` }]
|
|
24682
24689
|
};
|
|
24683
|
-
}
|
|
24684
|
-
|
|
24685
|
-
|
|
24686
|
-
|
|
24687
|
-
|
|
24688
|
-
|
|
24689
|
-
|
|
24690
|
+
});
|
|
24691
|
+
server.tool("load_handoff", "Open the best saved handoff or rolling draft and turn it back into a clear resume point for a new session", {
|
|
24692
|
+
id: exports_external.number().optional().describe("Optional handoff observation ID; defaults to the latest recent handoff"),
|
|
24693
|
+
cwd: exports_external.string().optional(),
|
|
24694
|
+
project_scoped: exports_external.boolean().optional(),
|
|
24695
|
+
user_id: exports_external.string().optional()
|
|
24696
|
+
}, async (params) => {
|
|
24697
|
+
const result = loadHandoff(db, {
|
|
24698
|
+
...params,
|
|
24699
|
+
user_id: params.user_id ?? config2.user_id,
|
|
24700
|
+
current_device_id: config2.device_id
|
|
24701
|
+
});
|
|
24702
|
+
if (!result.handoff) {
|
|
24703
|
+
return {
|
|
24704
|
+
content: [{ type: "text", text: "No matching handoff found" }]
|
|
24705
|
+
};
|
|
24690
24706
|
}
|
|
24691
|
-
|
|
24692
|
-
|
|
24707
|
+
const facts = result.handoff.facts ? (() => {
|
|
24708
|
+
try {
|
|
24709
|
+
const parsed = JSON.parse(result.handoff.facts);
|
|
24710
|
+
return Array.isArray(parsed) ? parsed : [];
|
|
24711
|
+
} catch {
|
|
24712
|
+
return [];
|
|
24713
|
+
}
|
|
24714
|
+
})() : [];
|
|
24715
|
+
const factLines = facts.length > 0 ? `
|
|
24693
24716
|
|
|
24694
24717
|
Facts:
|
|
24695
24718
|
${facts.map((fact) => `- ${fact}`).join(`
|
|
24696
24719
|
`)}` : "";
|
|
24697
|
-
|
|
24720
|
+
const projectLine = result.handoff.project_name ? `Project: ${result.handoff.project_name}
|
|
24698
24721
|
` : "";
|
|
24699
|
-
|
|
24722
|
+
const sourceLine = `Source: ${formatHandoffSource(result.handoff)}
|
|
24700
24723
|
`;
|
|
24701
|
-
|
|
24702
|
-
|
|
24703
|
-
|
|
24704
|
-
|
|
24705
|
-
|
|
24724
|
+
return {
|
|
24725
|
+
content: [
|
|
24726
|
+
{
|
|
24727
|
+
type: "text",
|
|
24728
|
+
text: `${projectLine}Handoff #${result.handoff.id}
|
|
24706
24729
|
` + sourceLine + `Title: ${result.handoff.title}
|
|
24707
24730
|
|
|
24708
24731
|
` + `${result.handoff.narrative ?? "(no handoff narrative stored)"}${factLines}`
|
|
24709
|
-
|
|
24710
|
-
|
|
24711
|
-
|
|
24712
|
-
});
|
|
24713
|
-
server.tool("recent_chat", "Inspect recently captured chat messages in the separate chat lane", {
|
|
24714
|
-
|
|
24715
|
-
|
|
24716
|
-
|
|
24717
|
-
|
|
24718
|
-
|
|
24719
|
-
}, async (params) => {
|
|
24720
|
-
|
|
24721
|
-
|
|
24732
|
+
}
|
|
24733
|
+
]
|
|
24734
|
+
};
|
|
24735
|
+
});
|
|
24736
|
+
server.tool("recent_chat", "Inspect recently captured chat messages in the separate chat lane", {
|
|
24737
|
+
limit: exports_external.number().optional(),
|
|
24738
|
+
project_scoped: exports_external.boolean().optional(),
|
|
24739
|
+
session_id: exports_external.string().optional(),
|
|
24740
|
+
cwd: exports_external.string().optional(),
|
|
24741
|
+
user_id: exports_external.string().optional()
|
|
24742
|
+
}, async (params) => {
|
|
24743
|
+
const result = getRecentChat(db, params);
|
|
24744
|
+
const projectLine = result.project ? `Project: ${result.project}
|
|
24722
24745
|
` : "";
|
|
24723
|
-
|
|
24746
|
+
const coverageLine = `Coverage: ${result.messages.length} messages across ${result.session_count} session${result.session_count === 1 ? "" : "s"} ` + `· transcript ${result.source_summary.transcript} · history ${result.source_summary.history} · hook ${result.source_summary.hook}
|
|
24724
24747
|
` + `${result.transcript_backed ? "" : `Hint: run refresh_chat_recall for one session or repair_recall for recent project sessions if this looks under-captured.
|
|
24725
24748
|
`}`;
|
|
24726
|
-
|
|
24727
|
-
|
|
24728
|
-
|
|
24729
|
-
|
|
24749
|
+
const rows = result.messages.length > 0 ? result.messages.map((msg) => {
|
|
24750
|
+
const stamp = new Date(msg.created_at_epoch * 1000).toISOString().split("T")[0];
|
|
24751
|
+
return `- ${stamp} [${msg.role}] [${getChatCaptureOrigin(msg)}] ${msg.content.replace(/\s+/g, " ").trim().slice(0, 200)}`;
|
|
24752
|
+
}).join(`
|
|
24730
24753
|
`) : "- (none)";
|
|
24731
|
-
|
|
24732
|
-
|
|
24733
|
-
|
|
24734
|
-
|
|
24735
|
-
|
|
24754
|
+
return {
|
|
24755
|
+
content: [
|
|
24756
|
+
{
|
|
24757
|
+
type: "text",
|
|
24758
|
+
text: `${projectLine}${coverageLine}Recent chat:
|
|
24736
24759
|
${rows}`
|
|
24737
|
-
|
|
24738
|
-
|
|
24739
|
-
|
|
24740
|
-
});
|
|
24741
|
-
server.tool("search_chat", "Search the separate chat lane without mixing it into durable memory observations", {
|
|
24742
|
-
|
|
24743
|
-
|
|
24744
|
-
|
|
24745
|
-
|
|
24746
|
-
|
|
24747
|
-
}, async (params) => {
|
|
24748
|
-
|
|
24749
|
-
|
|
24760
|
+
}
|
|
24761
|
+
]
|
|
24762
|
+
};
|
|
24763
|
+
});
|
|
24764
|
+
server.tool("search_chat", "Search the separate chat lane without mixing it into durable memory observations", {
|
|
24765
|
+
query: exports_external.string().describe("Text to search for in captured chat"),
|
|
24766
|
+
limit: exports_external.number().optional(),
|
|
24767
|
+
project_scoped: exports_external.boolean().optional(),
|
|
24768
|
+
cwd: exports_external.string().optional(),
|
|
24769
|
+
user_id: exports_external.string().optional()
|
|
24770
|
+
}, async (params) => {
|
|
24771
|
+
const result = await searchChat(db, params);
|
|
24772
|
+
const projectLine = result.project ? `Project: ${result.project}
|
|
24750
24773
|
` : "";
|
|
24751
|
-
|
|
24774
|
+
const coverageLine = `Coverage: ${result.messages.length} matches across ${result.session_count} session${result.session_count === 1 ? "" : "s"} ` + `· transcript ${result.source_summary.transcript} · history ${result.source_summary.history} · hook ${result.source_summary.hook}` + `${result.semantic_backed ? " · semantic yes" : ""}
|
|
24752
24775
|
` + `${result.transcript_backed ? "" : `Hint: run refresh_chat_recall for one session or repair_recall for recent project sessions if this looks under-captured.
|
|
24753
24776
|
`}`;
|
|
24754
|
-
|
|
24755
|
-
|
|
24756
|
-
|
|
24757
|
-
|
|
24777
|
+
const rows = result.messages.length > 0 ? result.messages.map((msg) => {
|
|
24778
|
+
const stamp = new Date(msg.created_at_epoch * 1000).toISOString().split("T")[0];
|
|
24779
|
+
return `- ${stamp} [${msg.role}] [${getChatCaptureOrigin(msg)}] ${msg.content.replace(/\s+/g, " ").trim().slice(0, 200)}`;
|
|
24780
|
+
}).join(`
|
|
24758
24781
|
`) : "- (none)";
|
|
24759
|
-
|
|
24760
|
-
|
|
24761
|
-
|
|
24762
|
-
|
|
24763
|
-
|
|
24782
|
+
return {
|
|
24783
|
+
content: [
|
|
24784
|
+
{
|
|
24785
|
+
type: "text",
|
|
24786
|
+
text: `${projectLine}${coverageLine}Chat search for "${params.query}":
|
|
24764
24787
|
${rows}`
|
|
24765
|
-
|
|
24766
|
-
|
|
24767
|
-
|
|
24768
|
-
});
|
|
24769
|
-
server.tool("recent_requests", "Inspect recently captured raw user requests and prompt chronology", {
|
|
24770
|
-
|
|
24771
|
-
|
|
24772
|
-
|
|
24773
|
-
|
|
24774
|
-
|
|
24775
|
-
}, async (params) => {
|
|
24776
|
-
|
|
24777
|
-
|
|
24788
|
+
}
|
|
24789
|
+
]
|
|
24790
|
+
};
|
|
24791
|
+
});
|
|
24792
|
+
server.tool("recent_requests", "Inspect recently captured raw user requests and prompt chronology", {
|
|
24793
|
+
limit: exports_external.number().optional(),
|
|
24794
|
+
project_scoped: exports_external.boolean().optional(),
|
|
24795
|
+
session_id: exports_external.string().optional(),
|
|
24796
|
+
cwd: exports_external.string().optional(),
|
|
24797
|
+
user_id: exports_external.string().optional()
|
|
24798
|
+
}, async (params) => {
|
|
24799
|
+
const result = getRecentRequests(db, params);
|
|
24800
|
+
const projectLine = result.project ? `Project: ${result.project}
|
|
24778
24801
|
` : "";
|
|
24779
|
-
|
|
24780
|
-
|
|
24781
|
-
|
|
24782
|
-
|
|
24802
|
+
const rows = result.prompts.length > 0 ? result.prompts.map((prompt) => {
|
|
24803
|
+
const stamp = new Date(prompt.created_at_epoch * 1000).toISOString().split("T")[0];
|
|
24804
|
+
return `- #${prompt.prompt_number} (${stamp}) ${prompt.prompt.replace(/\s+/g, " ").trim()}`;
|
|
24805
|
+
}).join(`
|
|
24783
24806
|
`) : "- (none)";
|
|
24784
|
-
|
|
24785
|
-
|
|
24786
|
-
|
|
24787
|
-
|
|
24788
|
-
|
|
24807
|
+
return {
|
|
24808
|
+
content: [
|
|
24809
|
+
{
|
|
24810
|
+
type: "text",
|
|
24811
|
+
text: `${projectLine}Recent requests:
|
|
24789
24812
|
${rows}`
|
|
24790
|
-
|
|
24791
|
-
|
|
24792
|
-
|
|
24793
|
-
});
|
|
24794
|
-
server.tool("recent_tools", "Inspect recently captured raw tool chronology", {
|
|
24795
|
-
|
|
24796
|
-
|
|
24797
|
-
|
|
24798
|
-
|
|
24799
|
-
|
|
24800
|
-
}, async (params) => {
|
|
24801
|
-
|
|
24802
|
-
|
|
24813
|
+
}
|
|
24814
|
+
]
|
|
24815
|
+
};
|
|
24816
|
+
});
|
|
24817
|
+
server.tool("recent_tools", "Inspect recently captured raw tool chronology", {
|
|
24818
|
+
limit: exports_external.number().optional(),
|
|
24819
|
+
project_scoped: exports_external.boolean().optional(),
|
|
24820
|
+
session_id: exports_external.string().optional(),
|
|
24821
|
+
cwd: exports_external.string().optional(),
|
|
24822
|
+
user_id: exports_external.string().optional()
|
|
24823
|
+
}, async (params) => {
|
|
24824
|
+
const result = getRecentTools(db, params);
|
|
24825
|
+
const projectLine = result.project ? `Project: ${result.project}
|
|
24803
24826
|
` : "";
|
|
24804
|
-
|
|
24805
|
-
|
|
24806
|
-
|
|
24807
|
-
|
|
24808
|
-
|
|
24827
|
+
const rows = result.tool_events.length > 0 ? result.tool_events.map((tool) => {
|
|
24828
|
+
const stamp = new Date(tool.created_at_epoch * 1000).toISOString().split("T")[0];
|
|
24829
|
+
const detail = tool.file_path ?? tool.command ?? tool.tool_response_preview ?? "";
|
|
24830
|
+
return `- ${stamp} ${tool.tool_name}${detail ? ` — ${detail}` : ""}`;
|
|
24831
|
+
}).join(`
|
|
24809
24832
|
`) : "- (none)";
|
|
24810
|
-
|
|
24811
|
-
|
|
24812
|
-
|
|
24813
|
-
|
|
24814
|
-
|
|
24833
|
+
return {
|
|
24834
|
+
content: [
|
|
24835
|
+
{
|
|
24836
|
+
type: "text",
|
|
24837
|
+
text: `${projectLine}Recent tools:
|
|
24815
24838
|
${rows}`
|
|
24816
|
-
|
|
24817
|
-
|
|
24818
|
-
|
|
24819
|
-
});
|
|
24820
|
-
server.tool("recent_sessions", "List the latest captured sessions so you can inspect one in detail", {
|
|
24821
|
-
|
|
24822
|
-
|
|
24823
|
-
|
|
24824
|
-
|
|
24825
|
-
}, async (params) => {
|
|
24826
|
-
|
|
24827
|
-
|
|
24839
|
+
}
|
|
24840
|
+
]
|
|
24841
|
+
};
|
|
24842
|
+
});
|
|
24843
|
+
server.tool("recent_sessions", "List the latest captured sessions so you can inspect one in detail", {
|
|
24844
|
+
limit: exports_external.number().optional(),
|
|
24845
|
+
project_scoped: exports_external.boolean().optional(),
|
|
24846
|
+
cwd: exports_external.string().optional(),
|
|
24847
|
+
user_id: exports_external.string().optional()
|
|
24848
|
+
}, async (params) => {
|
|
24849
|
+
const result = getRecentSessions(db, params);
|
|
24850
|
+
const projectLine = result.project ? `Project: ${result.project}
|
|
24828
24851
|
` : "";
|
|
24829
|
-
|
|
24830
|
-
|
|
24831
|
-
|
|
24832
|
-
|
|
24833
|
-
|
|
24834
|
-
|
|
24852
|
+
const rows = result.sessions.length > 0 ? result.sessions.map((session) => {
|
|
24853
|
+
const whenEpoch = session.completed_at_epoch ?? session.started_at_epoch ?? 0;
|
|
24854
|
+
const when = whenEpoch > 0 ? new Date(whenEpoch * 1000).toISOString().split("T")[0] : "unknown";
|
|
24855
|
+
const summary = session.request ?? session.completed ?? "(no summary)";
|
|
24856
|
+
return `- ${session.session_id} (${when}) [${session.capture_state}] prompts=${session.prompt_count} tools=${session.tool_event_count} obs=${session.observation_count} :: ${summary.replace(/\s+/g, " ").trim()}`;
|
|
24857
|
+
}).join(`
|
|
24835
24858
|
`) : "- (none)";
|
|
24836
|
-
return {
|
|
24837
|
-
content: [
|
|
24838
|
-
{
|
|
24839
|
-
type: "text",
|
|
24840
|
-
text: `${projectLine}Recent sessions:
|
|
24841
|
-
${rows}`
|
|
24842
|
-
}
|
|
24843
|
-
]
|
|
24844
|
-
};
|
|
24845
|
-
});
|
|
24846
|
-
server.tool("session_story", "Show the full local memory story for one session", {
|
|
24847
|
-
session_id: exports_external.string().describe("Session ID to inspect")
|
|
24848
|
-
}, async (params) => {
|
|
24849
|
-
const result = getSessionStory(db, params);
|
|
24850
|
-
if (!result.session) {
|
|
24851
24859
|
return {
|
|
24852
|
-
content: [
|
|
24860
|
+
content: [
|
|
24861
|
+
{
|
|
24862
|
+
type: "text",
|
|
24863
|
+
text: `${projectLine}Recent sessions:
|
|
24864
|
+
${rows}`
|
|
24865
|
+
}
|
|
24866
|
+
]
|
|
24853
24867
|
};
|
|
24854
|
-
}
|
|
24855
|
-
|
|
24856
|
-
|
|
24857
|
-
|
|
24858
|
-
|
|
24859
|
-
result.
|
|
24860
|
-
|
|
24861
|
-
|
|
24868
|
+
});
|
|
24869
|
+
server.tool("session_story", "Show the full local memory story for one session", {
|
|
24870
|
+
session_id: exports_external.string().describe("Session ID to inspect")
|
|
24871
|
+
}, async (params) => {
|
|
24872
|
+
const result = getSessionStory(db, params);
|
|
24873
|
+
if (!result.session) {
|
|
24874
|
+
return {
|
|
24875
|
+
content: [{ type: "text", text: `Session ${params.session_id} not found` }]
|
|
24876
|
+
};
|
|
24877
|
+
}
|
|
24878
|
+
const summaryLines = result.summary ? [
|
|
24879
|
+
result.summary.request ? `Request: ${result.summary.request}` : null,
|
|
24880
|
+
result.summary.investigated ? `Investigated: ${result.summary.investigated}` : null,
|
|
24881
|
+
result.summary.learned ? `Learned: ${result.summary.learned}` : null,
|
|
24882
|
+
result.summary.completed ? `Completed: ${result.summary.completed}` : null,
|
|
24883
|
+
result.summary.next_steps ? `Next steps: ${result.summary.next_steps}` : null
|
|
24884
|
+
].filter(Boolean).join(`
|
|
24862
24885
|
`) : "(none)";
|
|
24863
|
-
|
|
24886
|
+
const promptLines = result.prompts.length > 0 ? result.prompts.map((prompt) => `- #${prompt.prompt_number} ${prompt.prompt.replace(/\s+/g, " ").trim()}`).join(`
|
|
24864
24887
|
`) : "- (none)";
|
|
24865
|
-
|
|
24888
|
+
const chatLines = result.chat_messages.length > 0 ? result.chat_messages.slice(-12).map((msg) => `- [${msg.role}] [${msg.source_kind}] ${msg.content.replace(/\s+/g, " ").trim().slice(0, 200)}`).join(`
|
|
24866
24889
|
`) : "- (none)";
|
|
24867
|
-
|
|
24868
|
-
|
|
24869
|
-
|
|
24870
|
-
|
|
24890
|
+
const toolLines = result.tool_events.length > 0 ? result.tool_events.slice(-15).map((tool) => {
|
|
24891
|
+
const detail = tool.file_path ?? tool.command ?? tool.tool_response_preview ?? "";
|
|
24892
|
+
return `- ${tool.tool_name}${detail ? ` — ${detail}` : ""}`;
|
|
24893
|
+
}).join(`
|
|
24871
24894
|
`) : "- (none)";
|
|
24872
|
-
|
|
24873
|
-
|
|
24874
|
-
|
|
24875
|
-
|
|
24876
|
-
|
|
24877
|
-
|
|
24878
|
-
|
|
24879
|
-
|
|
24895
|
+
const observationLines = result.observations.length > 0 ? result.observations.slice(-15).map((obs) => {
|
|
24896
|
+
const provenance = [];
|
|
24897
|
+
if (obs.source_tool)
|
|
24898
|
+
provenance.push(`via ${obs.source_tool}`);
|
|
24899
|
+
if (typeof obs.source_prompt_number === "number")
|
|
24900
|
+
provenance.push(`#${obs.source_prompt_number}`);
|
|
24901
|
+
return `- #${obs.id} [${obs.type}] ${obs.title}${provenance.length ? ` (${provenance.join(" · ")})` : ""}`;
|
|
24902
|
+
}).join(`
|
|
24880
24903
|
`) : "- (none)";
|
|
24881
|
-
|
|
24882
|
-
|
|
24883
|
-
|
|
24884
|
-
|
|
24904
|
+
const handoffLines = result.handoffs.length > 0 ? result.handoffs.slice(-8).map((obs) => {
|
|
24905
|
+
const kind = isDraftHandoff(obs) ? "draft" : "saved";
|
|
24906
|
+
return `- #${obs.id} [${kind}] ${obs.title}`;
|
|
24907
|
+
}).join(`
|
|
24885
24908
|
`) : "- (none)";
|
|
24886
|
-
|
|
24887
|
-
|
|
24909
|
+
const metrics = result.metrics ? `files=${result.metrics.files_touched_count}, searches=${result.metrics.searches_performed}, tools=${result.metrics.tool_calls_count}, observations=${result.metrics.observation_count}` : "metrics unavailable";
|
|
24910
|
+
const captureGaps = result.capture_gaps.length > 0 ? result.capture_gaps.map((gap) => `- ${gap}`).join(`
|
|
24888
24911
|
`) : "- none";
|
|
24889
|
-
|
|
24912
|
+
const provenanceSummary = result.provenance_summary.length > 0 ? result.provenance_summary.map((item) => `- ${item.tool}: ${item.count}`).join(`
|
|
24890
24913
|
`) : "- none";
|
|
24891
|
-
|
|
24892
|
-
|
|
24893
|
-
|
|
24894
|
-
|
|
24895
|
-
|
|
24914
|
+
return {
|
|
24915
|
+
content: [
|
|
24916
|
+
{
|
|
24917
|
+
type: "text",
|
|
24918
|
+
text: `Session: ${result.session.session_id}
|
|
24896
24919
|
` + `Status: ${result.session.status}
|
|
24897
24920
|
` + `Capture: ${result.capture_state}
|
|
24898
24921
|
` + `Handoff split: ${result.saved_handoffs.length} saved, ${result.rolling_handoff_drafts.length} rolling drafts
|
|
@@ -24923,131 +24946,134 @@ ${captureGaps}
|
|
|
24923
24946
|
|
|
24924
24947
|
` + `Observations:
|
|
24925
24948
|
${observationLines}`
|
|
24926
|
-
}
|
|
24927
|
-
]
|
|
24928
|
-
};
|
|
24929
|
-
});
|
|
24930
|
-
function formatTimeAgo(isoDate) {
|
|
24931
|
-
const diff = Date.now() - new Date(isoDate).getTime();
|
|
24932
|
-
const mins = Math.floor(diff / 60000);
|
|
24933
|
-
if (mins < 60)
|
|
24934
|
-
return `${mins}m ago`;
|
|
24935
|
-
const hrs = Math.floor(mins / 60);
|
|
24936
|
-
if (hrs < 24)
|
|
24937
|
-
return `${hrs}h ago`;
|
|
24938
|
-
return `${Math.floor(hrs / 24)}d ago`;
|
|
24939
|
-
}
|
|
24940
|
-
server.tool("install_pack", "Install a help pack (pre-curated observations for a technology stack)", {
|
|
24941
|
-
pack_name: exports_external.string().describe("Pack name (e.g. 'typescript-patterns', 'react-gotchas')")
|
|
24942
|
-
}, async (params) => {
|
|
24943
|
-
const installed = db.getInstalledPacks();
|
|
24944
|
-
if (installed.includes(params.pack_name)) {
|
|
24945
|
-
return {
|
|
24946
|
-
content: [
|
|
24947
|
-
{
|
|
24948
|
-
type: "text",
|
|
24949
|
-
text: `Pack '${params.pack_name}' is already installed.`
|
|
24950
24949
|
}
|
|
24951
24950
|
]
|
|
24952
24951
|
};
|
|
24953
|
-
}
|
|
24954
|
-
|
|
24955
|
-
|
|
24952
|
+
});
|
|
24953
|
+
function formatTimeAgo(isoDate) {
|
|
24954
|
+
const diff = Date.now() - new Date(isoDate).getTime();
|
|
24955
|
+
const mins = Math.floor(diff / 60000);
|
|
24956
|
+
if (mins < 60)
|
|
24957
|
+
return `${mins}m ago`;
|
|
24958
|
+
const hrs = Math.floor(mins / 60);
|
|
24959
|
+
if (hrs < 24)
|
|
24960
|
+
return `${hrs}h ago`;
|
|
24961
|
+
return `${Math.floor(hrs / 24)}d ago`;
|
|
24962
|
+
}
|
|
24963
|
+
server.tool("install_pack", "Install a help pack (pre-curated observations for a technology stack)", {
|
|
24964
|
+
pack_name: exports_external.string().describe("Pack name (e.g. 'typescript-patterns', 'react-gotchas')")
|
|
24965
|
+
}, async (params) => {
|
|
24966
|
+
const installed = db.getInstalledPacks();
|
|
24967
|
+
if (installed.includes(params.pack_name)) {
|
|
24968
|
+
return {
|
|
24969
|
+
content: [
|
|
24970
|
+
{
|
|
24971
|
+
type: "text",
|
|
24972
|
+
text: `Pack '${params.pack_name}' is already installed.`
|
|
24973
|
+
}
|
|
24974
|
+
]
|
|
24975
|
+
};
|
|
24976
|
+
}
|
|
24977
|
+
const pack = loadPack(params.pack_name);
|
|
24978
|
+
if (!pack) {
|
|
24979
|
+
return {
|
|
24980
|
+
content: [
|
|
24981
|
+
{
|
|
24982
|
+
type: "text",
|
|
24983
|
+
text: `Pack '${params.pack_name}' not found. Available packs can be recommended based on your project's technology stack.`
|
|
24984
|
+
}
|
|
24985
|
+
]
|
|
24986
|
+
};
|
|
24987
|
+
}
|
|
24988
|
+
let savedCount = 0;
|
|
24989
|
+
for (const obs of pack.observations) {
|
|
24990
|
+
try {
|
|
24991
|
+
await saveObservation(db, config2, {
|
|
24992
|
+
type: obs.type,
|
|
24993
|
+
title: obs.title,
|
|
24994
|
+
narrative: obs.narrative,
|
|
24995
|
+
concepts: obs.concepts,
|
|
24996
|
+
agent: getDetectedAgent()
|
|
24997
|
+
});
|
|
24998
|
+
savedCount++;
|
|
24999
|
+
} catch {}
|
|
25000
|
+
}
|
|
25001
|
+
db.markPackInstalled(params.pack_name, savedCount);
|
|
24956
25002
|
return {
|
|
24957
25003
|
content: [
|
|
24958
25004
|
{
|
|
24959
25005
|
type: "text",
|
|
24960
|
-
text: `
|
|
25006
|
+
text: `Installed pack '${params.pack_name}': ${savedCount}/${pack.observations.length} observations saved.`
|
|
24961
25007
|
}
|
|
24962
25008
|
]
|
|
24963
25009
|
};
|
|
24964
|
-
}
|
|
24965
|
-
|
|
24966
|
-
|
|
24967
|
-
|
|
24968
|
-
|
|
24969
|
-
|
|
24970
|
-
|
|
24971
|
-
|
|
24972
|
-
|
|
24973
|
-
|
|
24974
|
-
|
|
24975
|
-
|
|
24976
|
-
|
|
24977
|
-
|
|
24978
|
-
|
|
24979
|
-
|
|
24980
|
-
|
|
24981
|
-
|
|
24982
|
-
|
|
24983
|
-
|
|
24984
|
-
|
|
24985
|
-
|
|
24986
|
-
|
|
24987
|
-
}
|
|
24988
|
-
|
|
24989
|
-
|
|
24990
|
-
|
|
24991
|
-
|
|
25010
|
+
});
|
|
25011
|
+
server.tool("load_session_context", "Load project memory for this session", {
|
|
25012
|
+
max_observations: exports_external.number().optional().describe("Max observations (default: token-budgeted)")
|
|
25013
|
+
}, async (params) => {
|
|
25014
|
+
if (contextServed) {
|
|
25015
|
+
return {
|
|
25016
|
+
content: [
|
|
25017
|
+
{
|
|
25018
|
+
type: "text",
|
|
25019
|
+
text: "Context already loaded for this session. Use search for specific queries."
|
|
25020
|
+
}
|
|
25021
|
+
]
|
|
25022
|
+
};
|
|
25023
|
+
}
|
|
25024
|
+
const context = buildSessionContext(db, process.cwd(), params.max_observations ? { maxCount: params.max_observations, userId: config2.user_id, currentDeviceId: config2.device_id } : { tokenBudget: 800, userId: config2.user_id, currentDeviceId: config2.device_id });
|
|
25025
|
+
if (!context || context.observations.length === 0) {
|
|
25026
|
+
return {
|
|
25027
|
+
content: [
|
|
25028
|
+
{
|
|
25029
|
+
type: "text",
|
|
25030
|
+
text: context ? `Project: ${context.project_name} — no prior observations found.` : "Could not detect project."
|
|
25031
|
+
}
|
|
25032
|
+
]
|
|
25033
|
+
};
|
|
25034
|
+
}
|
|
25035
|
+
contextServed = true;
|
|
25036
|
+
sessionMetrics.contextObsInjected = context.observations.length;
|
|
25037
|
+
sessionMetrics.contextTotalAvailable = context.total_active;
|
|
25038
|
+
persistSessionMetrics();
|
|
24992
25039
|
return {
|
|
24993
25040
|
content: [
|
|
24994
25041
|
{
|
|
24995
25042
|
type: "text",
|
|
24996
|
-
text:
|
|
25043
|
+
text: formatContextForInjection(context)
|
|
24997
25044
|
}
|
|
24998
25045
|
]
|
|
24999
25046
|
};
|
|
25047
|
+
});
|
|
25048
|
+
function qualityIndicator(quality) {
|
|
25049
|
+
const filled = Math.round(quality * 5);
|
|
25050
|
+
return "●".repeat(filled) + "○".repeat(5 - filled);
|
|
25000
25051
|
}
|
|
25001
|
-
|
|
25002
|
-
|
|
25003
|
-
|
|
25004
|
-
|
|
25005
|
-
{
|
|
25006
|
-
|
|
25007
|
-
|
|
25052
|
+
function formatFactPreview(factsRaw, narrative) {
|
|
25053
|
+
if (factsRaw) {
|
|
25054
|
+
try {
|
|
25055
|
+
const parsed = JSON.parse(factsRaw);
|
|
25056
|
+
if (Array.isArray(parsed)) {
|
|
25057
|
+
const facts = parsed.filter((item) => typeof item === "string" && item.trim().length > 0).slice(0, 2);
|
|
25058
|
+
if (facts.length > 0) {
|
|
25059
|
+
return facts.join("; ");
|
|
25060
|
+
}
|
|
25008
25061
|
}
|
|
25009
|
-
|
|
25010
|
-
|
|
25011
|
-
|
|
25012
|
-
|
|
25013
|
-
sessionMetrics.contextObsInjected = context.observations.length;
|
|
25014
|
-
sessionMetrics.contextTotalAvailable = context.total_active;
|
|
25015
|
-
persistSessionMetrics();
|
|
25016
|
-
return {
|
|
25017
|
-
content: [
|
|
25018
|
-
{
|
|
25019
|
-
type: "text",
|
|
25020
|
-
text: formatContextForInjection(context)
|
|
25021
|
-
}
|
|
25022
|
-
]
|
|
25023
|
-
};
|
|
25024
|
-
});
|
|
25025
|
-
function qualityIndicator(quality) {
|
|
25026
|
-
const filled = Math.round(quality * 5);
|
|
25027
|
-
return "●".repeat(filled) + "○".repeat(5 - filled);
|
|
25028
|
-
}
|
|
25029
|
-
function formatFactPreview(factsRaw, narrative) {
|
|
25030
|
-
if (factsRaw) {
|
|
25031
|
-
try {
|
|
25032
|
-
const parsed = JSON.parse(factsRaw);
|
|
25033
|
-
if (Array.isArray(parsed)) {
|
|
25034
|
-
const facts = parsed.filter((item) => typeof item === "string" && item.trim().length > 0).slice(0, 2);
|
|
25035
|
-
if (facts.length > 0) {
|
|
25036
|
-
return facts.join("; ");
|
|
25062
|
+
} catch {
|
|
25063
|
+
const trimmedFacts = factsRaw.trim();
|
|
25064
|
+
if (trimmedFacts.length > 0) {
|
|
25065
|
+
return trimmedFacts.length > 160 ? `${trimmedFacts.slice(0, 157)}...` : trimmedFacts;
|
|
25037
25066
|
}
|
|
25038
25067
|
}
|
|
25039
|
-
} catch {
|
|
25040
|
-
const trimmedFacts = factsRaw.trim();
|
|
25041
|
-
if (trimmedFacts.length > 0) {
|
|
25042
|
-
return trimmedFacts.length > 160 ? `${trimmedFacts.slice(0, 157)}...` : trimmedFacts;
|
|
25043
|
-
}
|
|
25044
25068
|
}
|
|
25069
|
+
if (!narrative)
|
|
25070
|
+
return null;
|
|
25071
|
+
const trimmed = narrative.trim().replace(/\s+/g, " ");
|
|
25072
|
+
return trimmed.length > 160 ? `${trimmed.slice(0, 157)}...` : trimmed;
|
|
25045
25073
|
}
|
|
25046
|
-
|
|
25047
|
-
return null;
|
|
25048
|
-
const trimmed = narrative.trim().replace(/\s+/g, " ");
|
|
25049
|
-
return trimmed.length > 160 ? `${trimmed.slice(0, 157)}...` : trimmed;
|
|
25074
|
+
return server;
|
|
25050
25075
|
}
|
|
25076
|
+
var server = buildServer();
|
|
25051
25077
|
async function main() {
|
|
25052
25078
|
runDueJobs(db);
|
|
25053
25079
|
if (db.vecAvailable) {
|
|
@@ -25087,13 +25113,17 @@ async function startHttpServer() {
|
|
|
25087
25113
|
if (tokens.length === 0) {
|
|
25088
25114
|
throw new Error("HTTP mode requires at least one bearer token via settings.json http.bearer_tokens or ENGRM_HTTP_BEARER_TOKENS");
|
|
25089
25115
|
}
|
|
25090
|
-
const
|
|
25091
|
-
|
|
25092
|
-
});
|
|
25093
|
-
await server.connect(transport);
|
|
25116
|
+
const streamableTransports = new Map;
|
|
25117
|
+
const sseTransports = new Map;
|
|
25094
25118
|
const httpServer = createServer(async (req, res) => {
|
|
25095
25119
|
try {
|
|
25096
|
-
if (!req.url
|
|
25120
|
+
if (!req.url) {
|
|
25121
|
+
res.writeHead(404).end("Not found");
|
|
25122
|
+
return;
|
|
25123
|
+
}
|
|
25124
|
+
const url2 = new URL(req.url, "http://localhost");
|
|
25125
|
+
const pathname = url2.pathname;
|
|
25126
|
+
if (pathname !== "/mcp" && pathname !== "/sse" && pathname !== "/messages") {
|
|
25097
25127
|
res.writeHead(404).end("Not found");
|
|
25098
25128
|
return;
|
|
25099
25129
|
}
|
|
@@ -25107,7 +25137,89 @@ async function startHttpServer() {
|
|
|
25107
25137
|
const authorizedReq = req;
|
|
25108
25138
|
authorizedReq.auth = { token };
|
|
25109
25139
|
const parsedBody = req.method === "POST" ? await readJsonBody(req) : undefined;
|
|
25110
|
-
|
|
25140
|
+
if (pathname === "/sse" && req.method === "GET") {
|
|
25141
|
+
const transport = new SSEServerTransport("/messages", res);
|
|
25142
|
+
sseTransports.set(transport.sessionId, transport);
|
|
25143
|
+
transport.onclose = () => {
|
|
25144
|
+
sseTransports.delete(transport.sessionId);
|
|
25145
|
+
};
|
|
25146
|
+
const sseServer = buildServer();
|
|
25147
|
+
await sseServer.connect(transport);
|
|
25148
|
+
return;
|
|
25149
|
+
}
|
|
25150
|
+
if (pathname === "/messages" && req.method === "POST") {
|
|
25151
|
+
const sessionId = url2.searchParams.get("sessionId") ?? "";
|
|
25152
|
+
const transport = sseTransports.get(sessionId);
|
|
25153
|
+
if (!transport) {
|
|
25154
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
25155
|
+
res.end(JSON.stringify({
|
|
25156
|
+
jsonrpc: "2.0",
|
|
25157
|
+
error: { code: -32000, message: "Bad Request: No SSE transport found for sessionId" },
|
|
25158
|
+
id: null
|
|
25159
|
+
}));
|
|
25160
|
+
return;
|
|
25161
|
+
}
|
|
25162
|
+
await transport.handlePostMessage(authorizedReq, res, parsedBody);
|
|
25163
|
+
return;
|
|
25164
|
+
}
|
|
25165
|
+
const sessionIdHeader = Array.isArray(req.headers["mcp-session-id"]) ? req.headers["mcp-session-id"][0] : req.headers["mcp-session-id"];
|
|
25166
|
+
if (sessionIdHeader) {
|
|
25167
|
+
const transport = streamableTransports.get(sessionIdHeader);
|
|
25168
|
+
if (!transport) {
|
|
25169
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
25170
|
+
res.end(JSON.stringify({
|
|
25171
|
+
jsonrpc: "2.0",
|
|
25172
|
+
error: { code: -32000, message: "Bad Request: No valid session ID provided" },
|
|
25173
|
+
id: null
|
|
25174
|
+
}));
|
|
25175
|
+
return;
|
|
25176
|
+
}
|
|
25177
|
+
await transport.handleRequest(authorizedReq, res, parsedBody);
|
|
25178
|
+
return;
|
|
25179
|
+
}
|
|
25180
|
+
if (pathname === "/mcp" && req.method === "POST" && isInitializeRequest(parsedBody)) {
|
|
25181
|
+
let transport;
|
|
25182
|
+
transport = new StreamableHTTPServerTransport({
|
|
25183
|
+
sessionIdGenerator: () => randomUUID(),
|
|
25184
|
+
onsessioninitialized: (sessionId) => {
|
|
25185
|
+
streamableTransports.set(sessionId, transport);
|
|
25186
|
+
}
|
|
25187
|
+
});
|
|
25188
|
+
transport.onclose = () => {
|
|
25189
|
+
const sid = transport.sessionId;
|
|
25190
|
+
if (sid)
|
|
25191
|
+
streamableTransports.delete(sid);
|
|
25192
|
+
};
|
|
25193
|
+
const streamableServer = buildServer();
|
|
25194
|
+
await streamableServer.connect(transport);
|
|
25195
|
+
await transport.handleRequest(authorizedReq, res, parsedBody);
|
|
25196
|
+
return;
|
|
25197
|
+
}
|
|
25198
|
+
const wantsSse = (req.headers.accept ?? "").includes("text/event-stream");
|
|
25199
|
+
if (pathname === "/mcp" && req.method === "GET" && wantsSse) {
|
|
25200
|
+
const transport = new SSEServerTransport("/mcp", res);
|
|
25201
|
+
sseTransports.set(transport.sessionId, transport);
|
|
25202
|
+
transport.onclose = () => {
|
|
25203
|
+
sseTransports.delete(transport.sessionId);
|
|
25204
|
+
};
|
|
25205
|
+
const sseServer = buildServer();
|
|
25206
|
+
await sseServer.connect(transport);
|
|
25207
|
+
return;
|
|
25208
|
+
}
|
|
25209
|
+
if (pathname === "/mcp" && req.method === "POST") {
|
|
25210
|
+
const sessionId = url2.searchParams.get("sessionId") ?? "";
|
|
25211
|
+
const transport = sseTransports.get(sessionId);
|
|
25212
|
+
if (transport) {
|
|
25213
|
+
await transport.handlePostMessage(authorizedReq, res, parsedBody);
|
|
25214
|
+
return;
|
|
25215
|
+
}
|
|
25216
|
+
}
|
|
25217
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
25218
|
+
res.end(JSON.stringify({
|
|
25219
|
+
jsonrpc: "2.0",
|
|
25220
|
+
error: { code: -32000, message: "Bad Request: No valid session ID provided" },
|
|
25221
|
+
id: null
|
|
25222
|
+
}));
|
|
25111
25223
|
} catch (error48) {
|
|
25112
25224
|
res.writeHead(500, { "Content-Type": "application/json" });
|
|
25113
25225
|
res.end(JSON.stringify({ error: error48 instanceof Error ? error48.message : String(error48) }));
|
|
@@ -25117,7 +25229,7 @@ async function startHttpServer() {
|
|
|
25117
25229
|
httpServer.once("error", reject);
|
|
25118
25230
|
httpServer.listen(port, () => resolve4());
|
|
25119
25231
|
});
|
|
25120
|
-
console.error(`Engrm HTTP MCP listening on :${port}/mcp`);
|
|
25232
|
+
console.error(`Engrm HTTP MCP listening on :${port}/mcp (compat: /sse, /messages)`);
|
|
25121
25233
|
}
|
|
25122
25234
|
async function readJsonBody(req) {
|
|
25123
25235
|
const chunks = [];
|