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