@rvboris/opencode-mempalace 0.2.1 → 0.3.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";
@@ -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",
@@ -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 {};