@scitrera/memorylayer-opencode-plugin 0.1.22
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 +178 -0
- package/dist/src/hooks/event.d.ts +15 -0
- package/dist/src/hooks/event.d.ts.map +1 -0
- package/dist/src/hooks/event.js +53 -0
- package/dist/src/hooks/event.js.map +1 -0
- package/dist/src/hooks/message.d.ts +20 -0
- package/dist/src/hooks/message.d.ts.map +1 -0
- package/dist/src/hooks/message.js +128 -0
- package/dist/src/hooks/message.js.map +1 -0
- package/dist/src/hooks/session.d.ts +20 -0
- package/dist/src/hooks/session.d.ts.map +1 -0
- package/dist/src/hooks/session.js +121 -0
- package/dist/src/hooks/session.js.map +1 -0
- package/dist/src/hooks/tool.d.ts +23 -0
- package/dist/src/hooks/tool.d.ts.map +1 -0
- package/dist/src/hooks/tool.js +168 -0
- package/dist/src/hooks/tool.js.map +1 -0
- package/dist/src/index.d.ts +31 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +176 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/shared/client.d.ts +27 -0
- package/dist/src/shared/client.d.ts.map +1 -0
- package/dist/src/shared/client.js +69 -0
- package/dist/src/shared/client.js.map +1 -0
- package/dist/src/shared/formatters.d.ts +33 -0
- package/dist/src/shared/formatters.d.ts.map +1 -0
- package/dist/src/shared/formatters.js +187 -0
- package/dist/src/shared/formatters.js.map +1 -0
- package/dist/src/shared/observation.d.ts +59 -0
- package/dist/src/shared/observation.d.ts.map +1 -0
- package/dist/src/shared/observation.js +406 -0
- package/dist/src/shared/observation.js.map +1 -0
- package/dist/src/shared/state.d.ts +66 -0
- package/dist/src/shared/state.d.ts.map +1 -0
- package/dist/src/shared/state.js +144 -0
- package/dist/src/shared/state.js.map +1 -0
- package/dist/src/shared/types.d.ts +141 -0
- package/dist/src/shared/types.d.ts.map +1 -0
- package/dist/src/shared/types.js +9 -0
- package/dist/src/shared/types.js.map +1 -0
- package/opencode.example.json +15 -0
- package/package.json +52 -0
- package/src/commands/recall.md +55 -0
- package/src/commands/remember.md +37 -0
- package/src/commands/setup.md +47 -0
- package/src/commands/status.md +59 -0
- package/src/hooks/event.ts +63 -0
- package/src/hooks/message.ts +147 -0
- package/src/hooks/session.ts +127 -0
- package/src/hooks/tool.ts +207 -0
- package/src/index.ts +197 -0
- package/src/shared/client.ts +73 -0
- package/src/shared/formatters.ts +237 -0
- package/src/shared/observation.ts +447 -0
- package/src/shared/state.ts +159 -0
- package/src/shared/types.ts +144 -0
- package/tsconfig.json +24 -0
package/src/index.ts
ADDED
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MemoryLayer OpenCode Plugin
|
|
3
|
+
*
|
|
4
|
+
* Provides persistent memory for OpenCode sessions via hooks that:
|
|
5
|
+
* - Inject workspace briefing and directives at session start
|
|
6
|
+
* - Recall relevant memories when users ask questions
|
|
7
|
+
* - Capture tool observations as working memory
|
|
8
|
+
* - Commit working memory before context compaction
|
|
9
|
+
* - Clean up sessions on exit
|
|
10
|
+
*
|
|
11
|
+
* The plugin works alongside the MemoryLayer MCP server which provides
|
|
12
|
+
* the full suite of 21+ memory tools to the LLM.
|
|
13
|
+
*
|
|
14
|
+
* @module @scitrera/memorylayer-opencode-plugin
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import type { MemoryLayerHooks, PluginInput, PluginOptions } from "./shared/types.js";
|
|
18
|
+
import { setPluginDirectory } from "./shared/client.js";
|
|
19
|
+
import { initializeSession, finalizeSession } from "./hooks/session.js";
|
|
20
|
+
import { handleUserMessage, extractMessageText } from "./hooks/message.js";
|
|
21
|
+
import { handleToolBefore, handleToolAfter } from "./hooks/tool.js";
|
|
22
|
+
import { handleCompacting } from "./hooks/event.js";
|
|
23
|
+
|
|
24
|
+
// Re-export types for downstream consumers (e.g., enterprise plugins)
|
|
25
|
+
export type { MemoryLayerHooks, PluginInput, PluginOptions, Part, Model, HookState } from "./shared/types.js";
|
|
26
|
+
|
|
27
|
+
/** Track whether session has been initialized */
|
|
28
|
+
let sessionInitialized = false;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* MemoryLayer plugin for OpenCode.
|
|
32
|
+
*
|
|
33
|
+
* Usage in opencode.json:
|
|
34
|
+
* ```json
|
|
35
|
+
* {
|
|
36
|
+
* "plugin": ["@scitrera/memorylayer-opencode-plugin"]
|
|
37
|
+
* }
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
export default async function memorylayerPlugin(
|
|
41
|
+
ctx: PluginInput,
|
|
42
|
+
_options?: PluginOptions
|
|
43
|
+
): Promise<MemoryLayerHooks> {
|
|
44
|
+
// Initialize client with workspace detection from plugin context
|
|
45
|
+
setPluginDirectory(ctx.worktree || ctx.directory);
|
|
46
|
+
|
|
47
|
+
const hooks: MemoryLayerHooks = {
|
|
48
|
+
/**
|
|
49
|
+
* System prompt transform — inject MemoryLayer context on first interaction.
|
|
50
|
+
*
|
|
51
|
+
* This hook modifies the system prompt to include workspace briefing,
|
|
52
|
+
* directives, and session guidance. It runs once per session (on first call)
|
|
53
|
+
* and injects the formatted context into the system prompt array.
|
|
54
|
+
*/
|
|
55
|
+
"experimental.chat.system.transform": async (_input, output) => {
|
|
56
|
+
if (!sessionInitialized) {
|
|
57
|
+
sessionInitialized = true;
|
|
58
|
+
const context = await initializeSession();
|
|
59
|
+
if (context) {
|
|
60
|
+
output.system.push(context);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* User message hook — detect patterns and recall relevant memories.
|
|
67
|
+
*
|
|
68
|
+
* When a user sends a message matching known patterns (preference questions,
|
|
69
|
+
* recall requests, implementation tasks, error reports), this hook performs
|
|
70
|
+
* a targeted recall and injects the results as additional message parts.
|
|
71
|
+
*/
|
|
72
|
+
"chat.message": async (_input, output) => {
|
|
73
|
+
const messageText = extractMessageText(output.parts);
|
|
74
|
+
if (!messageText) return;
|
|
75
|
+
|
|
76
|
+
const context = await handleUserMessage(messageText);
|
|
77
|
+
if (context) {
|
|
78
|
+
output.parts.push({
|
|
79
|
+
type: "text",
|
|
80
|
+
text: `\n\n<memory-context>\n${context}\n</memory-context>`,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Pre-tool hook — inject relevant context before tool execution.
|
|
87
|
+
*
|
|
88
|
+
* For write/edit tools: recalls context relevant to the file being modified.
|
|
89
|
+
* For task/delegation tools: recalls and suggests including context in subagent prompts.
|
|
90
|
+
*/
|
|
91
|
+
"tool.execute.before": async (input, output) => {
|
|
92
|
+
const context = await handleToolBefore(input.tool, output.args);
|
|
93
|
+
if (context) {
|
|
94
|
+
// Inject context as metadata that the LLM can see
|
|
95
|
+
// OpenCode passes args to the tool — we add a _memorylayer_context field
|
|
96
|
+
// that tools can optionally use, and it appears in the tool call metadata
|
|
97
|
+
(output.args as Record<string, unknown>)._memorylayer_context = context;
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Post-tool hook — capture tool observations as working memory.
|
|
103
|
+
*
|
|
104
|
+
* Silently captures structured observations (files read/modified, facts,
|
|
105
|
+
* concepts, intent) and stores them as working memory. Fire-and-forget
|
|
106
|
+
* to avoid blocking tool execution.
|
|
107
|
+
*
|
|
108
|
+
* For significant events (git commits, build errors), injects guidance
|
|
109
|
+
* suggesting the user store important information.
|
|
110
|
+
*/
|
|
111
|
+
"tool.execute.after": async (input, output) => {
|
|
112
|
+
const guidance = await handleToolAfter(
|
|
113
|
+
input.tool,
|
|
114
|
+
input.args,
|
|
115
|
+
output.output
|
|
116
|
+
);
|
|
117
|
+
if (guidance) {
|
|
118
|
+
// Append guidance to tool output so the LLM sees it
|
|
119
|
+
output.output = output.output + `\n\n${guidance}`;
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Compaction hook — preserve memory state before context window is trimmed.
|
|
125
|
+
*
|
|
126
|
+
* Commits working memory to long-term storage and checkpoints the
|
|
127
|
+
* server-side sandbox so state survives context compaction.
|
|
128
|
+
*/
|
|
129
|
+
"experimental.session.compacting": async (input, output) => {
|
|
130
|
+
const context = await handleCompacting(input.sessionID);
|
|
131
|
+
output.context.push(...context);
|
|
132
|
+
},
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Shell environment hook — inject MemoryLayer env vars into shell commands.
|
|
136
|
+
*/
|
|
137
|
+
"shell.env": async (_input, output) => {
|
|
138
|
+
if (process.env.MEMORYLAYER_URL) {
|
|
139
|
+
output.env.MEMORYLAYER_URL = process.env.MEMORYLAYER_URL;
|
|
140
|
+
}
|
|
141
|
+
if (process.env.MEMORYLAYER_API_KEY) {
|
|
142
|
+
output.env.MEMORYLAYER_API_KEY = process.env.MEMORYLAYER_API_KEY;
|
|
143
|
+
}
|
|
144
|
+
},
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Command hook — handle memorylayer slash commands.
|
|
148
|
+
*/
|
|
149
|
+
"command.execute.before": async (input, output) => {
|
|
150
|
+
const cmd = input.command;
|
|
151
|
+
|
|
152
|
+
if (cmd === "memorylayer-remember") {
|
|
153
|
+
output.parts.push({
|
|
154
|
+
type: "text",
|
|
155
|
+
text: `Use the \`memory_remember\` tool to store the following: ${input.arguments}\n\nAuto-detect appropriate type, subtype, importance, and tags from the content.`,
|
|
156
|
+
});
|
|
157
|
+
} else if (cmd === "memorylayer-recall") {
|
|
158
|
+
output.parts.push({
|
|
159
|
+
type: "text",
|
|
160
|
+
text: `Use the \`memory_recall\` tool to search for: ${input.arguments}\n\nDisplay results with relevance scores and key metadata.`,
|
|
161
|
+
});
|
|
162
|
+
} else if (cmd === "memorylayer-status") {
|
|
163
|
+
output.parts.push({
|
|
164
|
+
type: "text",
|
|
165
|
+
text: "Check MemoryLayer connection status: use `memory_briefing` to verify the MCP server is connected, then report server URL, workspace, memory statistics, and active session info.",
|
|
166
|
+
});
|
|
167
|
+
} else if (cmd === "memorylayer-setup") {
|
|
168
|
+
output.parts.push({
|
|
169
|
+
type: "text",
|
|
170
|
+
text: [
|
|
171
|
+
"Run MemoryLayer setup verification:",
|
|
172
|
+
"1. Check server health (curl http://localhost:61001/health)",
|
|
173
|
+
"2. Verify MCP tools are connected (call memory_briefing)",
|
|
174
|
+
"3. Smoke test: store a test memory, recall it, then forget it",
|
|
175
|
+
"4. Report: server URL, workspace, tool count, connection status",
|
|
176
|
+
"",
|
|
177
|
+
"If server is not running, suggest: pip install memorylayer-server && memorylayer serve",
|
|
178
|
+
].join("\n"),
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
},
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
// Register cleanup on process exit
|
|
185
|
+
const cleanup = () => {
|
|
186
|
+
finalizeSession().catch(() => {});
|
|
187
|
+
};
|
|
188
|
+
process.on("beforeExit", cleanup);
|
|
189
|
+
process.on("SIGTERM", cleanup);
|
|
190
|
+
process.on("SIGINT", cleanup);
|
|
191
|
+
|
|
192
|
+
return hooks;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Also export the plugin as a PluginModule shape
|
|
196
|
+
export const id = "memorylayer";
|
|
197
|
+
export const server = memorylayerPlugin;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hook client - provides access to the MemoryLayerClient for hook operations.
|
|
3
|
+
*
|
|
4
|
+
* Hooks use the exact same MemoryLayerClient as MCP tools, just with a shorter
|
|
5
|
+
* timeout since hooks need to respond quickly.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { MemoryLayerClient, detectWorkspaceId } from "@scitrera/memorylayer-mcp-server";
|
|
9
|
+
import { resolveSessionId } from "./state.js";
|
|
10
|
+
|
|
11
|
+
/** Singleton client instance for hooks */
|
|
12
|
+
let clientInstance: MemoryLayerClient | null = null;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Set the plugin directory for workspace auto-detection.
|
|
16
|
+
* Called once during plugin initialization. Sets CWD so that
|
|
17
|
+
* detectWorkspaceId() (which reads git config) resolves correctly.
|
|
18
|
+
*/
|
|
19
|
+
export function setPluginDirectory(directory: string): void {
|
|
20
|
+
try {
|
|
21
|
+
process.chdir(directory);
|
|
22
|
+
} catch {
|
|
23
|
+
// Ignore if directory doesn't exist
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Get or create the singleton MemoryLayerClient instance.
|
|
29
|
+
* This is the same client class used by MCP tools.
|
|
30
|
+
*
|
|
31
|
+
* On each call, syncs the session ID via resolveSessionId() so that
|
|
32
|
+
* hooks running after session start send the X-Session-ID header for
|
|
33
|
+
* correct workspace resolution on the server.
|
|
34
|
+
*/
|
|
35
|
+
export function getClient(): MemoryLayerClient {
|
|
36
|
+
if (!clientInstance) {
|
|
37
|
+
let workspaceId = process.env.MEMORYLAYER_WORKSPACE_ID;
|
|
38
|
+
if (!workspaceId) {
|
|
39
|
+
try {
|
|
40
|
+
workspaceId = detectWorkspaceId();
|
|
41
|
+
} catch {
|
|
42
|
+
workspaceId = "_default";
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
clientInstance = new MemoryLayerClient({
|
|
47
|
+
baseUrl: process.env.MEMORYLAYER_URL,
|
|
48
|
+
apiKey: process.env.MEMORYLAYER_API_KEY,
|
|
49
|
+
workspaceId,
|
|
50
|
+
timeout: 5000, // Shorter timeout for hooks
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Sync session ID on every call
|
|
55
|
+
const sessionId = resolveSessionId("client");
|
|
56
|
+
if (sessionId && clientInstance.getSessionId() !== sessionId) {
|
|
57
|
+
clientInstance.setSessionId(sessionId);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return clientInstance;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Check if the MemoryLayer server is reachable
|
|
65
|
+
*/
|
|
66
|
+
export async function checkHealth(): Promise<boolean> {
|
|
67
|
+
try {
|
|
68
|
+
await getClient().getBriefing({ limit: 1, includeMemories: false });
|
|
69
|
+
return true;
|
|
70
|
+
} catch {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Format API responses for injection into OpenCode's context.
|
|
3
|
+
*
|
|
4
|
+
* Uses the same types returned by MemoryLayerClient (the MCP adapter),
|
|
5
|
+
* so hooks and MCP tools share the same data contracts.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { Memory, RecallResult, ToolResponse } from "@scitrera/memorylayer-mcp-server";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Format a single memory for display
|
|
12
|
+
*/
|
|
13
|
+
function formatMemory(memory: Memory, index: number): string {
|
|
14
|
+
const lines: string[] = [];
|
|
15
|
+
|
|
16
|
+
const typeStr = memory.subtype
|
|
17
|
+
? `${memory.type}/${memory.subtype}`
|
|
18
|
+
: memory.type;
|
|
19
|
+
const relevanceStr = memory.relevance_score
|
|
20
|
+
? ` (relevance: ${(memory.relevance_score * 100).toFixed(0)}%)`
|
|
21
|
+
: "";
|
|
22
|
+
|
|
23
|
+
lines.push(`${index + 1}. [${typeStr}]${relevanceStr}`);
|
|
24
|
+
lines.push(` ${memory.content}`);
|
|
25
|
+
|
|
26
|
+
const tags = memory.tags ?? [];
|
|
27
|
+
if (tags.length > 0) {
|
|
28
|
+
lines.push(` Tags: ${tags.join(", ")}`);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return lines.join("\n");
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Format recall results for context injection
|
|
36
|
+
*/
|
|
37
|
+
export function formatRecallResult(result: RecallResult, query: string): string {
|
|
38
|
+
if (result.memories.length === 0) {
|
|
39
|
+
return `No memories found matching "${query}".`;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const totalCount = result.total_count ?? result.memories.length;
|
|
43
|
+
const lines: string[] = [
|
|
44
|
+
`Found ${totalCount} memories for "${query}" (showing ${result.memories.length}):`,
|
|
45
|
+
"",
|
|
46
|
+
];
|
|
47
|
+
|
|
48
|
+
for (let i = 0; i < result.memories.length; i++) {
|
|
49
|
+
lines.push(formatMemory(result.memories[i], i));
|
|
50
|
+
if (i < result.memories.length - 1) {
|
|
51
|
+
lines.push("");
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return lines.join("\n");
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Format briefing for context injection.
|
|
60
|
+
* Accepts the ToolResponse from MemoryLayerClient.getBriefing().
|
|
61
|
+
*/
|
|
62
|
+
export function formatBriefing(briefing: ToolResponse): string {
|
|
63
|
+
if (!briefing) {
|
|
64
|
+
return "=== Workspace Briefing ===\n\nNo workspace data available.";
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const totalMemories = (briefing.total_memories as number) ?? 0;
|
|
68
|
+
const activeTopics = (briefing.active_topics as string[]) ?? [];
|
|
69
|
+
const memoryTypes = (briefing.memory_types as Record<string, number>) ?? {};
|
|
70
|
+
const recentActivity = (briefing.recent_activity as Array<{
|
|
71
|
+
timestamp?: string;
|
|
72
|
+
summary?: string;
|
|
73
|
+
memories_created?: number;
|
|
74
|
+
}>) ?? [];
|
|
75
|
+
|
|
76
|
+
const lines: string[] = [
|
|
77
|
+
"=== Workspace Briefing ===",
|
|
78
|
+
"",
|
|
79
|
+
`Total memories: ${totalMemories}`,
|
|
80
|
+
];
|
|
81
|
+
|
|
82
|
+
if (activeTopics.length > 0) {
|
|
83
|
+
lines.push(`Active topics: ${activeTopics.join(", ")}`);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Memory type breakdown
|
|
87
|
+
const types = Object.entries(memoryTypes);
|
|
88
|
+
if (types.length > 0) {
|
|
89
|
+
lines.push("");
|
|
90
|
+
lines.push("Memory types:");
|
|
91
|
+
for (const [type, count] of types) {
|
|
92
|
+
lines.push(` - ${type}: ${count}`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Recent activity
|
|
97
|
+
if (recentActivity.length > 0) {
|
|
98
|
+
lines.push("");
|
|
99
|
+
lines.push("Recent activity:");
|
|
100
|
+
for (const activity of recentActivity.slice(0, 3)) {
|
|
101
|
+
const date = new Date(activity.timestamp ?? new Date().toISOString()).toLocaleDateString();
|
|
102
|
+
lines.push(` - ${date}: ${activity.summary ?? ""} (${activity.memories_created ?? 0} memories)`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return lines.join("\n");
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Format directive memories specially (high importance user instructions)
|
|
111
|
+
*/
|
|
112
|
+
export function formatDirectives(memories: Memory[]): string {
|
|
113
|
+
const directives = memories.filter(
|
|
114
|
+
m => m.subtype === "directive" || (m.subtype === "preference" && (m.importance ?? 0) >= 0.9)
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
if (directives.length === 0) {
|
|
118
|
+
return "";
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const lines: string[] = [
|
|
122
|
+
"=== User Directives (must follow) ===",
|
|
123
|
+
"",
|
|
124
|
+
];
|
|
125
|
+
|
|
126
|
+
for (const directive of directives) {
|
|
127
|
+
lines.push(`- ${directive.content}`);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return lines.join("\n");
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Format sandbox state for context injection (post-compaction recovery)
|
|
135
|
+
*/
|
|
136
|
+
export function formatSandboxState(inspectResult: Record<string, unknown>): string {
|
|
137
|
+
const variableCount = (inspectResult.variable_count as number) ?? 0;
|
|
138
|
+
const variables = (inspectResult.variables as Record<string, { type: string; preview: string }>) ?? {};
|
|
139
|
+
|
|
140
|
+
const lines: string[] = [
|
|
141
|
+
"=== Existing Sandbox State (server-side) ===",
|
|
142
|
+
"",
|
|
143
|
+
`The server-side sandbox has ${variableCount} variable(s) from a prior session or before context compaction.`,
|
|
144
|
+
"These variables are live and available for memory_context_exec, memory_context_query, and memory_context_rlm.",
|
|
145
|
+
"",
|
|
146
|
+
];
|
|
147
|
+
|
|
148
|
+
for (const [name, info] of Object.entries(variables)) {
|
|
149
|
+
lines.push(` ${name} (${info.type}): ${info.preview}`);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
lines.push("");
|
|
153
|
+
lines.push("Use `memory_context_inspect` for detailed variable inspection.");
|
|
154
|
+
|
|
155
|
+
return lines.join("\n");
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Format combined session start output
|
|
160
|
+
*/
|
|
161
|
+
export function formatSessionStart(
|
|
162
|
+
briefing: ToolResponse | null,
|
|
163
|
+
directives: Memory[],
|
|
164
|
+
topicRecall: RecallResult | null,
|
|
165
|
+
topic?: string,
|
|
166
|
+
sandboxState?: Record<string, unknown> | null
|
|
167
|
+
): string {
|
|
168
|
+
const sections: string[] = [];
|
|
169
|
+
|
|
170
|
+
// Briefing first
|
|
171
|
+
if (briefing) {
|
|
172
|
+
sections.push(formatBriefing(briefing));
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Directives (high priority)
|
|
176
|
+
const directiveSection = formatDirectives(directives);
|
|
177
|
+
if (directiveSection) {
|
|
178
|
+
sections.push(directiveSection);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Topic-specific recall
|
|
182
|
+
if (topicRecall && topicRecall.memories.length > 0 && topic) {
|
|
183
|
+
sections.push(formatRecallResult(topicRecall, topic));
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Existing sandbox state (post-compaction or resumed session)
|
|
187
|
+
if (sandboxState) {
|
|
188
|
+
sections.push(formatSandboxState(sandboxState));
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Add session guidance
|
|
192
|
+
const guidance = `=== Session Guidance ===
|
|
193
|
+
|
|
194
|
+
Recalling Memories:
|
|
195
|
+
- Before answering questions about preferences, setup, conventions, or past decisions, call \`memory_recall\` first
|
|
196
|
+
- For broad context gathering, use \`memory_recall\` with relevant keywords
|
|
197
|
+
- Directives (subtype: directive) and preferences (subtype: preference) represent explicit user instructions — prioritize these
|
|
198
|
+
|
|
199
|
+
Storing Memories:
|
|
200
|
+
- \`memory_remember\`: store to long-term memory with type, subtype, and importance
|
|
201
|
+
- \`memory_session_commit\`: checkpoint working memory mid-session (without ending it)
|
|
202
|
+
|
|
203
|
+
Importance Guide: directives/preferences -> 0.9 | decisions/architecture -> 0.7-0.8 | fixes/solutions -> 0.7 | patterns -> 0.5-0.6
|
|
204
|
+
Types: semantic (facts), procedural (how-to), episodic (events), working (current context, auto-expires)
|
|
205
|
+
Subtypes: directive, decision, fix, solution, code_pattern, error, workflow, preference, problem
|
|
206
|
+
|
|
207
|
+
Context Environment (sandbox survives compaction):
|
|
208
|
+
- Load + analyze memories server-side: \`memory_context_load\` -> \`memory_context_query\` or \`memory_context_rlm\`
|
|
209
|
+
- Run computations on loaded data: \`memory_context_exec\` (Python sandbox)
|
|
210
|
+
- After compaction, call \`memory_context_inspect\` to re-orient with existing sandbox variables`;
|
|
211
|
+
sections.push(guidance);
|
|
212
|
+
|
|
213
|
+
if (sections.length === 1) {
|
|
214
|
+
// Only guidance, no prior context
|
|
215
|
+
return "MemoryLayer: No prior context found for this session.\n\n" + guidance;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return sections.join("\n\n");
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Format guidance for storing memories after tool use
|
|
223
|
+
*/
|
|
224
|
+
export function formatStorageGuidance(toolName: string, isSignificant: boolean): string {
|
|
225
|
+
if (!isSignificant) {
|
|
226
|
+
return "";
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const guidance: Record<string, string> = {
|
|
230
|
+
task: "Consider storing exploration/research findings with `memory_remember` (subtype: decision/problem/entity).",
|
|
231
|
+
bash: "If this was a significant git commit, test result, or build output, store with `memory_remember` (subtype: workflow).",
|
|
232
|
+
edit: "If this edit completes a milestone, store with `memory_remember` (type: working, tags: [active-file]).",
|
|
233
|
+
write: "If this file represents a significant deliverable, store with `memory_remember` (type: working, tags: [active-file]).",
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
return guidance[toolName] || "";
|
|
237
|
+
}
|