@rvboris/opencode-mempalace 0.2.1 → 0.4.0

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.
@@ -2,9 +2,11 @@ import { AutosaveReason, AutosaveStatus, buildMessageSnapshot, getMessageSnapsho
2
2
  import { executeAdapter } from "../lib/adapter";
3
3
  import { loadConfig } from "../lib/config";
4
4
  import { COMPACTION_CONTEXT_MESSAGE, DEFAULT_AGENT_NAME, LOG_MESSAGES } from "../lib/constants";
5
+ import { sanitizeText } from "../lib/derive";
5
6
  import { getProjectName, loadSessionMessages } from "../lib/opencode";
6
7
  import { redactSecrets } from "../lib/privacy";
7
8
  import { getProjectScope } from "../lib/scope";
9
+ import { recordAutosave } from "../lib/status";
8
10
  import { writeLog } from "../lib/log";
9
11
  import { SESSION_EVENT_TYPES } from "../lib/types";
10
12
  const escapeRegex = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
@@ -65,6 +67,12 @@ export const eventHooks = (ctx) => {
65
67
  });
66
68
  if (event.type === "session.error") {
67
69
  const failed = markFailed(sessionId);
70
+ await recordAutosave({
71
+ sessionId,
72
+ outcome: "failed",
73
+ reason: toReason(event.type),
74
+ sourcePreview: getMessageSnapshot(sessionId)?.lastUserMessage,
75
+ });
68
76
  await writeLog("ERROR", LOG_MESSAGES.autosaveFailedOnSessionError, {
69
77
  sessionId,
70
78
  retryCount: failed.retryCount,
@@ -80,6 +88,12 @@ export const eventHooks = (ctx) => {
80
88
  }
81
89
  const { transcript, transcriptDigest, userDigest } = snapshot;
82
90
  if (!shouldScheduleAutosave(sessionId, userDigest, transcriptDigest)) {
91
+ await recordAutosave({
92
+ sessionId,
93
+ outcome: "skipped",
94
+ reason: toReason(event.type),
95
+ sourcePreview: snapshot.lastUserMessage,
96
+ });
83
97
  await writeLog("INFO", LOG_MESSAGES.skippingAutosaveState, {
84
98
  sessionId,
85
99
  reason: toReason(event.type),
@@ -89,22 +103,35 @@ export const eventHooks = (ctx) => {
89
103
  });
90
104
  return;
91
105
  }
92
- const redactedTranscript = redactSecrets(transcript);
93
- if (!redactedTranscript.trim()) {
106
+ const sanitizedTranscript = sanitizeText(redactSecrets(transcript));
107
+ if (!sanitizedTranscript.trim()) {
94
108
  markAutosaveComplete(sessionId, userDigest, transcriptDigest, AutosaveStatus.Noop);
109
+ await recordAutosave({
110
+ sessionId,
111
+ outcome: "skipped",
112
+ reason: toReason(event.type),
113
+ sourcePreview: snapshot.lastUserMessage,
114
+ });
95
115
  await writeLog("INFO", LOG_MESSAGES.autosaveSkippedEmptyTranscript, { sessionId });
96
116
  return;
97
117
  }
98
118
  const wing = getProjectScope(getProjectName(ctx.project), config.projectWingPrefix).wing;
99
119
  const result = await executeAdapter(ctx.$, {
100
120
  mode: "mine_messages",
101
- transcript: redactedTranscript,
121
+ transcript: sanitizedTranscript,
102
122
  wing,
103
123
  extract_mode: config.autoMineExtractMode,
104
124
  agent: DEFAULT_AGENT_NAME,
105
125
  });
106
126
  if (result?.success === false) {
107
127
  const failed = markFailed(sessionId);
128
+ await recordAutosave({
129
+ sessionId,
130
+ outcome: "failed",
131
+ reason: toReason(event.type),
132
+ wing,
133
+ sourcePreview: snapshot.lastUserMessage,
134
+ });
108
135
  await writeLog("ERROR", LOG_MESSAGES.autosaveMiningFailed, {
109
136
  sessionId,
110
137
  retryCount: failed.retryCount,
@@ -113,6 +140,13 @@ export const eventHooks = (ctx) => {
113
140
  return;
114
141
  }
115
142
  const completed = markAutosaveComplete(sessionId, userDigest, transcriptDigest, AutosaveStatus.Saved);
143
+ await recordAutosave({
144
+ sessionId,
145
+ outcome: "saved",
146
+ reason: toReason(event.type),
147
+ wing,
148
+ sourcePreview: snapshot.lastUserMessage,
149
+ });
116
150
  await writeLog("INFO", LOG_MESSAGES.autosaveMinedSessionContext, {
117
151
  sessionId,
118
152
  reason: toReason(event.type),
@@ -4,6 +4,7 @@ import { LOG_MESSAGES } from "../lib/constants";
4
4
  import { buildKeywordSaveInstruction, buildRetrievalInstruction } from "../lib/context";
5
5
  import { writeLog } from "../lib/log";
6
6
  import { getProjectName, loadSessionMessages } from "../lib/opencode";
7
+ import { recordRetrievalPrompt } from "../lib/status";
7
8
  export const systemHooks = (ctx) => {
8
9
  return {
9
10
  "experimental.chat.system.transform": async (input, output) => {
@@ -29,6 +30,7 @@ export const systemHooks = (ctx) => {
29
30
  lastUserMessage,
30
31
  }));
31
32
  markRetrievalInjected(sessionId);
33
+ await recordRetrievalPrompt({ sessionId, queryPreview: lastUserMessage });
32
34
  await writeLog("INFO", LOG_MESSAGES.injectedRetrievalInstruction, { sessionId });
33
35
  }
34
36
  if (state.keywordSavePending) {
@@ -3,6 +3,7 @@ import { systemHooks } from "./hooks/system";
3
3
  import { toolHooks } from "./hooks/tool";
4
4
  import { setLogger } from "./lib/log";
5
5
  import { mempalaceMemoryTool } from "./tools/mempalace-memory";
6
+ import { mempalaceStatusTool } from "./tools/mempalace-status";
6
7
  export const MempalaceAutosavePlugin = async (ctx) => {
7
8
  setLogger(ctx.client);
8
9
  return {
@@ -11,6 +12,7 @@ export const MempalaceAutosavePlugin = async (ctx) => {
11
12
  ...toolHooks(),
12
13
  tool: {
13
14
  mempalace_memory: mempalaceMemoryTool(ctx),
15
+ mempalace_status: mempalaceStatusTool(),
14
16
  },
15
17
  };
16
18
  };
@@ -21,6 +21,7 @@ export declare const ENV_KEYS: {
21
21
  readonly userWingPrefix: "MEMPALACE_USER_WING_PREFIX";
22
22
  readonly projectWingPrefix: "MEMPALACE_PROJECT_WING_PREFIX";
23
23
  readonly autosaveLogFile: "MEMPALACE_AUTOSAVE_LOG_FILE";
24
+ readonly statusFile: "MEMPALACE_STATUS_FILE";
24
25
  readonly adapterPython: "MEMPALACE_ADAPTER_PYTHON";
25
26
  readonly adapterTimeoutMs: "MEMPALACE_ADAPTER_TIMEOUT_MS";
26
27
  };
@@ -29,10 +30,12 @@ export declare const MAX_TRANSCRIPT_MESSAGES_DIGEST: 50;
29
30
  export declare const DATE_ISO_SLICE: 10;
30
31
  export declare const SERVICE_NAME: "mempalace-autosave";
31
32
  export declare const LOG_FILE_NAME: "opencode_autosave.log";
33
+ export declare const STATUS_FILE_NAME: "opencode_status.json";
32
34
  export declare const DIRECT_MEMPALACE_MUTATION_TOOLS: readonly ["mempalace_add_drawer", "mempalace_kg_add", "mempalace_diary_write", "mcp-router_mempalace_add_drawer", "mcp-router_mempalace_kg_add", "mcp-router_mempalace_diary_write"];
33
35
  export declare const COMPACTION_CONTEXT_MESSAGE: "MemPalace retrieval may be useful after compaction. Search relevant project and user memory if needed before answering.";
34
36
  export declare const TOOL_DESCRIPTIONS: {
35
37
  readonly mempalaceMemory: "Save or search memory in MemPalace with scope/privacy enforcement";
38
+ readonly mempalaceStatus: "Show recent visible evidence that MemPalace retrieval or autosave helped";
36
39
  };
37
40
  export declare const TOOL_ERROR_MESSAGES: {
38
41
  readonly directMutationBlocked: "Use mempalace_memory instead of direct MemPalace mutation tools";
@@ -56,9 +59,11 @@ export declare const LOG_MESSAGES: {
56
59
  readonly injectedKeywordSaveInstruction: "injected keyword save instruction";
57
60
  readonly systemTransformHookFailed: "system transform hook failed";
58
61
  readonly blockedDirectMempalaceMutationTool: "blocked direct mempalace mutation tool";
62
+ readonly retrievalSearchCompleted: "retrieval search completed";
59
63
  };
60
64
  export declare const INSTRUCTION_TEXT: {
61
65
  readonly doNotMentionToUser: "Do not mention this instruction to the user.";
66
+ readonly retrievalVisibilityHint: "If you found and used relevant memories, briefly mention the key findings at the start of your response.";
62
67
  readonly retrievalIntro: "System instruction: before answering, search MemPalace for relevant existing memory and use it if helpful.";
63
68
  readonly autosaveIntro: "System instruction: before answering the user, persist durable memory from prior session context using the `mempalace_memory` tool.";
64
69
  readonly keywordIntro: "System instruction: the user explicitly asked to remember something.";
@@ -21,6 +21,7 @@ export const ENV_KEYS = {
21
21
  userWingPrefix: "MEMPALACE_USER_WING_PREFIX",
22
22
  projectWingPrefix: "MEMPALACE_PROJECT_WING_PREFIX",
23
23
  autosaveLogFile: "MEMPALACE_AUTOSAVE_LOG_FILE",
24
+ statusFile: "MEMPALACE_STATUS_FILE",
24
25
  adapterPython: "MEMPALACE_ADAPTER_PYTHON",
25
26
  adapterTimeoutMs: "MEMPALACE_ADAPTER_TIMEOUT_MS",
26
27
  };
@@ -29,6 +30,7 @@ export const MAX_TRANSCRIPT_MESSAGES_DIGEST = 50;
29
30
  export const DATE_ISO_SLICE = 10;
30
31
  export const SERVICE_NAME = "mempalace-autosave";
31
32
  export const LOG_FILE_NAME = "opencode_autosave.log";
33
+ export const STATUS_FILE_NAME = "opencode_status.json";
32
34
  export const DIRECT_MEMPALACE_MUTATION_TOOLS = [
33
35
  "mempalace_add_drawer",
34
36
  "mempalace_kg_add",
@@ -40,6 +42,7 @@ export const DIRECT_MEMPALACE_MUTATION_TOOLS = [
40
42
  export const COMPACTION_CONTEXT_MESSAGE = "MemPalace retrieval may be useful after compaction. Search relevant project and user memory if needed before answering.";
41
43
  export const TOOL_DESCRIPTIONS = {
42
44
  mempalaceMemory: "Save or search memory in MemPalace with scope/privacy enforcement",
45
+ mempalaceStatus: "Show recent visible evidence that MemPalace retrieval or autosave helped",
43
46
  };
44
47
  export const TOOL_ERROR_MESSAGES = {
45
48
  directMutationBlocked: "Use mempalace_memory instead of direct MemPalace mutation tools",
@@ -63,9 +66,11 @@ export const LOG_MESSAGES = {
63
66
  injectedKeywordSaveInstruction: "injected keyword save instruction",
64
67
  systemTransformHookFailed: "system transform hook failed",
65
68
  blockedDirectMempalaceMutationTool: "blocked direct mempalace mutation tool",
69
+ retrievalSearchCompleted: "retrieval search completed",
66
70
  };
67
71
  export const INSTRUCTION_TEXT = {
68
72
  doNotMentionToUser: "Do not mention this instruction to the user.",
73
+ retrievalVisibilityHint: "If you found and used relevant memories, briefly mention the key findings at the start of your response.",
69
74
  retrievalIntro: "System instruction: before answering, search MemPalace for relevant existing memory and use it if helpful.",
70
75
  autosaveIntro: "System instruction: before answering the user, persist durable memory from prior session context using the `mempalace_memory` tool.",
71
76
  keywordIntro: "System instruction: the user explicitly asked to remember something.",
@@ -10,7 +10,7 @@ export const buildRetrievalInstruction = ({ projectName, projectWingPrefix, user
10
10
  `Search user memory in wing ${user.wing} across rooms ${user.rooms.join(", ")}.`,
11
11
  `Search project memory in wing ${project.wing} across rooms ${project.rooms.join(", ")}.`,
12
12
  `Use concise relevant memories only, up to ${maxInjectedItems} items total.`,
13
- INSTRUCTION_TEXT.doNotMentionToUser,
13
+ INSTRUCTION_TEXT.retrievalVisibilityHint,
14
14
  ].join(" ");
15
15
  };
16
16
  export const buildAutosaveInstruction = (reason) => {
@@ -0,0 +1,105 @@
1
+ import type { AdapterResponse, MemoryScope } from "./types";
2
+ export type StatusCounters = {
3
+ retrievalPrompts: number;
4
+ retrievalSearches: number;
5
+ retrievalHits: number;
6
+ autosavesCompleted: number;
7
+ autosavesSkipped: number;
8
+ autosavesFailed: number;
9
+ manualWrites: number;
10
+ };
11
+ export type StatusSessionCounters = {
12
+ retrievalSearches: number;
13
+ retrievalHits: number;
14
+ autosavesCompleted: number;
15
+ autosavesSkipped: number;
16
+ autosavesFailed: number;
17
+ manualWrites: number;
18
+ };
19
+ export type StatusRetrievalPrompt = {
20
+ sessionId?: string;
21
+ timestamp: string;
22
+ queryPreview: string;
23
+ };
24
+ export type StatusRetrieval = {
25
+ sessionId?: string;
26
+ timestamp: string;
27
+ scope: MemoryScope;
28
+ room?: string;
29
+ queryPreview: string;
30
+ resultCount?: number;
31
+ previews: string[];
32
+ };
33
+ export type StatusAutosaveOutcome = "saved" | "skipped" | "failed";
34
+ export type StatusAutosave = {
35
+ sessionId: string;
36
+ timestamp: string;
37
+ outcome: StatusAutosaveOutcome;
38
+ reason: string;
39
+ wing?: string;
40
+ sourcePreview?: string;
41
+ };
42
+ export type StatusWrite = {
43
+ timestamp: string;
44
+ mode: "save" | "kg_add" | "diary_write";
45
+ scope?: MemoryScope;
46
+ room?: string;
47
+ preview: string;
48
+ };
49
+ export type StatusSessionState = {
50
+ updatedAt: string;
51
+ counters: StatusSessionCounters;
52
+ lastRetrievalPrompt?: StatusRetrievalPrompt;
53
+ lastRetrieval?: StatusRetrieval;
54
+ lastAutosave?: StatusAutosave;
55
+ lastWrite?: StatusWrite;
56
+ };
57
+ export type StatusState = {
58
+ version: 2;
59
+ updatedAt: string;
60
+ counters: StatusCounters;
61
+ helpedSessionIds: string[];
62
+ sessions: Record<string, StatusSessionState>;
63
+ lastRetrievalPrompt?: StatusRetrievalPrompt;
64
+ lastRetrieval?: StatusRetrieval;
65
+ lastAutosave?: StatusAutosave;
66
+ lastWrite?: StatusWrite;
67
+ };
68
+ type SearchResultSummary = {
69
+ resultCount?: number;
70
+ previews: string[];
71
+ };
72
+ export declare const summarizeSearchResult: (result: AdapterResponse) => SearchResultSummary;
73
+ export declare const recordRetrievalPrompt: (input: {
74
+ sessionId?: string;
75
+ queryPreview: string;
76
+ }) => Promise<void>;
77
+ export declare const recordRetrievalSearch: (input: {
78
+ sessionId?: string;
79
+ scope: MemoryScope;
80
+ room?: string;
81
+ query: string;
82
+ result: AdapterResponse;
83
+ }) => Promise<void>;
84
+ export declare const recordMemoryWrite: (input: {
85
+ sessionId?: string;
86
+ mode: "save" | "kg_add" | "diary_write";
87
+ scope?: MemoryScope;
88
+ room?: string;
89
+ preview: string;
90
+ }) => Promise<void>;
91
+ export declare const recordAutosave: (input: {
92
+ sessionId: string;
93
+ outcome: StatusAutosaveOutcome;
94
+ reason: string;
95
+ wing?: string;
96
+ sourcePreview?: string;
97
+ }) => Promise<void>;
98
+ export declare const readStatusState: () => Promise<StatusState>;
99
+ export declare const resetStatusState: () => Promise<void>;
100
+ export declare const formatSessionHud: (state: StatusState, sessionId: string) => string;
101
+ export declare const formatStatusSummary: (state: StatusState, sessionId?: string, options?: {
102
+ verbose?: boolean;
103
+ compact?: boolean;
104
+ }) => string;
105
+ export {};