opencode-hindsight-plus 0.3.8 → 0.3.10

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 CHANGED
@@ -7,13 +7,15 @@ Fork of [`@vectorize-io/opencode-hindsight`](https://github.com/vectorize-io/hin
7
7
  ## Features
8
8
 
9
9
  - **Custom tools**: `hindsight_retain`, `hindsight_recall`, `hindsight_reflect` — the agent calls these explicitly
10
- - **Per-turn auto-recall**: On every user message, queries Hindsight with the current prompt (Claude Code `UserPromptSubmit` alignment). Default `recallContextTurns: 1` uses only the latest user text — no full-session re-fetch (Claude `recall.py` path). Multi-turn prior context (`recallContextTurns > 1`) is composed from the transform payload already in hand. Default inject mode is `synthetic-user` (a `synthetic: true` text part on the latest user message, closer to Claude `additionalContext`). Set `recallInjectMode: "system"` to fold into `system[0]` instead (that path still loads session messages). Tool-loop reuses the turn cache.
11
- - **Auto-retain**: Captures conversation on `session.idle` (Claude Code `Stop` alignment) and stores to Hindsight
10
+ - **Per-turn auto-recall**: On every user message, queries Hindsight with the current prompt (Claude Code `UserPromptSubmit` alignment). Default `recallContextTurns: 1` uses only the latest user text — no full-session re-fetch (Claude `recall.py` path). Multi-turn prior context (`recallContextTurns > 1`) is composed from the transform payload already in hand. Default inject mode is `synthetic-user` (a `synthetic: true` text part on the latest user message, closer to Claude `additionalContext`). Set `recallInjectMode: "system"` to fold into `system[0]` instead (that path still loads session messages). Tool-loop reuses the turn cache. Successful inject logs at **INFO** (chars). Optional TUI toast via `"injectToast": true` (fresh inject only; tool-loop silent).
11
+ - **Multi-bank recall**: `recallAdditionalBanks` merges results from extra banks (Claude `recallAdditionalBanks`).
12
+ - **Auto-retain**: Captures conversation on `session.idle` (Claude Code `Stop` alignment). Throttle is Claude-style **`userTurns % retainEveryNTurns === 0`**. Transcripts are sanitized (strip `U+0000` / C0 controls) before retain.
12
13
  - **SessionEnd flush**: Force-retains any pending turns on `session.deleted` and plugin `dispose` (Claude Code `SessionEnd` alignment), even when under `retainEveryNTurns`
13
14
  - **Tool trajectory retain** (opt-in): Set `retainToolCalls: true` to include tool call inputs/outputs in retained transcripts (skips `hindsight_*` tools). Default is `false` (Claude Code-aligned) to avoid fact/consolidation blowups from agent tool noise
14
15
  - **Retain tag templates**: `retainTags` / `retainMetadata` support `{session_id}`, `{bank_id}`, `{timestamp}`, `{user_id}` (empty `user:` tags dropped when unset)
16
+ - **Bank routing**: `directoryBankMap` (path → bank) + `resolveWorktrees` (linked worktrees share main-repo bank) — Claude `bank.py` order
15
17
  - **Coding-oriented bank missions**: default `bankMission` / `retainMission` guide fact extraction
16
- - **Knowledge pages**: `hindsight_page_*` tools (Hindsight mental-models; Claude `agent_knowledge_*` equivalent). Create long-lived pages with a `source_query` that re-synthesizes after consolidations.
18
+ - **Tools**: `hindsight_retain` / `recall` / `reflect`, `hindsight_bank_current`, `hindsight_ingest`, `hindsight_ingest_file`, plus `hindsight_page_*` knowledge pages (Claude `agent_knowledge_*` equivalents)
17
19
  - **Compaction hook**: Retains + injects query-relevant memories during context compaction so they survive window trimming
18
20
 
19
21
  ## Quick Start
@@ -126,10 +128,13 @@ Create `~/.hindsight/opencode.json` for persistent configuration:
126
128
  | `HINDSIGHT_USER_ID` | Used by `{user_id}` template in retain tags/metadata | (empty) |
127
129
  | `HINDSIGHT_ENABLE_KNOWLEDGE_PAGES` | Register `hindsight_page_*` tools | `true` |
128
130
  | `HINDSIGHT_RECALL_INJECT_MODE` | `synthetic-user` (default) or `system` | `synthetic-user` |
131
+ | `HINDSIGHT_INJECT_TOAST` | Show TUI toast on fresh memory inject | `false` |
132
+ | `HINDSIGHT_RECALL_ADDITIONAL_BANKS` | Comma-separated extra banks to merge on recall | (none) |
129
133
  | `HINDSIGHT_RECALL_TAGS` | Comma-separated, filter recalls | (none) |
130
134
  | `HINDSIGHT_RECALL_TAGS_MATCH` | Tag match mode: `any`, `all`, `any_strict`, `all_strict` | `any` |
131
135
  | `HINDSIGHT_RETAIN_TAGS` | Comma-separated, added to every retain | (none) |
132
- | `HINDSIGHT_DYNAMIC_BANK_ID` | Enable dynamic bank ID derivation | `false` |
136
+ | `HINDSIGHT_DYNAMIC_BANK_ID` | Enable dynamic bank ID derivation | `true` |
137
+ | `HINDSIGHT_RESOLVE_WORKTREES` | Share bank across git linked worktrees | `true` |
133
138
  | `HINDSIGHT_BANK_MISSION` | Bank mission/context | (none) |
134
139
 
135
140
  > **Debug logging** is a config-only option (`"debug": true` in `opencode.json`
package/dist/index.d.ts CHANGED
@@ -18,6 +18,8 @@ interface HindsightConfig {
18
18
  recallPromptPreamble: string;
19
19
  recallTags: string[];
20
20
  recallTagsMatch: "any" | "all" | "any_strict" | "all_strict";
21
+ /** Extra bank IDs to query on every auto-recall (Claude recallAdditionalBanks). */
22
+ recallAdditionalBanks: string[];
21
23
  autoRetain: boolean;
22
24
  retainMode: string;
23
25
  retainEveryNTurns: number;
@@ -33,11 +35,27 @@ interface HindsightConfig {
33
35
  bankIdPrefix: string;
34
36
  dynamicBankId: boolean;
35
37
  dynamicBankGranularity: string[];
38
+ /**
39
+ * Explicit cwd/path → bank_id map (Claude directoryBankMap).
40
+ * Highest priority when a realpath match is found.
41
+ */
42
+ directoryBankMap: Record<string, string>;
43
+ /**
44
+ * When true (default), gitProject / directory map resolution uses the main
45
+ * worktree root so linked worktrees share one bank (Claude resolveWorktrees).
46
+ */
47
+ resolveWorktrees: boolean;
36
48
  bankMission: string;
37
49
  retainMission: string | null;
38
50
  agentName: string;
39
51
  /** Register knowledge page tools (Hindsight mental-models / Claude agent_knowledge pages). */
40
52
  enableKnowledgePages: boolean;
53
+ /**
54
+ * When true, show an OpenCode TUI toast on a *fresh* successful inject
55
+ * (new user turn with memories). Cache re-applies during tool loops stay silent.
56
+ * Headless `opencode run` has no TUI — toast is a no-op there.
57
+ */
58
+ injectToast: boolean;
41
59
  debug: boolean;
42
60
  }
43
61
 
package/dist/index.js CHANGED
@@ -19,6 +19,7 @@ var DEFAULTS = {
19
19
  recallInjectMode: "synthetic-user",
20
20
  recallTags: [],
21
21
  recallTagsMatch: "any",
22
+ recallAdditionalBanks: [],
22
23
  recallPromptPreamble: "Relevant memories from past conversations (prioritize recent when conflicting). Only use memories that are directly useful to continue this conversation; ignore the rest:",
23
24
  // Retain — defaults aligned with Claude Code (Stop every N turns)
24
25
  autoRetain: true,
@@ -39,11 +40,14 @@ var DEFAULTS = {
39
40
  bankIdPrefix: "",
40
41
  dynamicBankId: true,
41
42
  dynamicBankGranularity: ["gitProject"],
43
+ directoryBankMap: {},
44
+ resolveWorktrees: true,
42
45
  bankMission: "You are an OpenCode AI coding agent. Focus on technical discussions, decisions, and context relevant to the user's projects.",
43
46
  retainMission: "Extract technical decisions, architectural choices, user preferences, project context, and people/tool relationships. Ignore routine greetings and transient operational details.",
44
47
  agentName: "opencode",
45
48
  // Misc
46
49
  enableKnowledgePages: true,
50
+ injectToast: false,
47
51
  debug: false
48
52
  };
49
53
  var ENV_OVERRIDES = {
@@ -71,7 +75,9 @@ var ENV_OVERRIDES = {
71
75
  HINDSIGHT_RETAIN_TAGS: ["retainTags", "string"],
72
76
  HINDSIGHT_RECALL_TAGS_MATCH: ["recallTagsMatch", "string"],
73
77
  HINDSIGHT_RECALL_PROMPT_PREAMBLE: ["recallPromptPreamble", "string"],
74
- HINDSIGHT_RETAIN_CONTEXT: ["retainContext", "string"]
78
+ HINDSIGHT_RETAIN_CONTEXT: ["retainContext", "string"],
79
+ HINDSIGHT_RESOLVE_WORKTREES: ["resolveWorktrees", "bool"],
80
+ HINDSIGHT_INJECT_TOAST: ["injectToast", "bool"]
75
81
  // NOTE: `debug` is intentionally NOT an env override. It is a proper config
76
82
  // option set via opencode.json plugin options or ~/.hindsight/opencode.json,
77
83
  // because env vars are unreliable to set for OpenCode's plugin runtime
@@ -130,11 +136,24 @@ function loadConfig(pluginOptions) {
130
136
  if (retainTagsEnv !== void 0) {
131
137
  config["retainTags"] = retainTagsEnv.split(",").map((t) => t.trim()).filter(Boolean);
132
138
  }
139
+ const additionalBanksEnv = process.env["HINDSIGHT_RECALL_ADDITIONAL_BANKS"];
140
+ if (additionalBanksEnv !== void 0) {
141
+ config["recallAdditionalBanks"] = additionalBanksEnv.split(",").map((t) => t.trim()).filter(Boolean);
142
+ }
133
143
  const result = config;
144
+ if (!Array.isArray(result.recallAdditionalBanks)) {
145
+ result.recallAdditionalBanks = [];
146
+ }
147
+ if (!result.directoryBankMap || typeof result.directoryBankMap !== "object") {
148
+ result.directoryBankMap = {};
149
+ }
150
+ if (result.retainMode === "chunked") {
151
+ result.retainMode = "last-turn";
152
+ }
134
153
  const VALID_RETAIN_MODES = ["full-session", "last-turn"];
135
154
  if (!VALID_RETAIN_MODES.includes(result.retainMode)) {
136
155
  console.error(
137
- `[Hindsight] Unknown retainMode "${result.retainMode}" \u2014 valid: ${VALID_RETAIN_MODES.join(", ")}. Falling back to "full-session".`
156
+ `[Hindsight] Unknown retainMode "${result.retainMode}" \u2014 valid: ${VALID_RETAIN_MODES.join(", ")}, chunked. Falling back to "full-session".`
138
157
  );
139
158
  result.retainMode = "full-session";
140
159
  }
@@ -163,7 +182,8 @@ function loadConfig(pluginOptions) {
163
182
  }
164
183
 
165
184
  // src/bank.ts
166
- import { basename, dirname } from "path";
185
+ import { basename, dirname, resolve } from "path";
186
+ import { realpathSync } from "fs";
167
187
  import { execFileSync } from "child_process";
168
188
 
169
189
  // src/logger.ts
@@ -215,6 +235,13 @@ function errorToString(error) {
215
235
  // src/bank.ts
216
236
  var DEFAULT_BANK_NAME = "opencode";
217
237
  var VALID_FIELDS = /* @__PURE__ */ new Set(["agent", "project", "gitProject", "channel", "user"]);
238
+ function safeRealpath(path) {
239
+ try {
240
+ return realpathSync(path);
241
+ } catch {
242
+ return resolve(path);
243
+ }
244
+ }
218
245
  function getProjectRootFromGit(directory) {
219
246
  if (!directory) return null;
220
247
  try {
@@ -237,13 +264,34 @@ function getProjectRootFromGit(directory) {
237
264
  return null;
238
265
  }
239
266
  }
240
- function deriveGitProjectName(directory) {
241
- const projectRoot = getProjectRootFromGit(directory);
242
- if (projectRoot) return basename(projectRoot);
267
+ function deriveGitProjectName(directory, resolveWorktrees) {
268
+ if (resolveWorktrees) {
269
+ const projectRoot = getProjectRootFromGit(directory);
270
+ if (projectRoot) return basename(projectRoot);
271
+ }
243
272
  return directory ? basename(directory) : "unknown";
244
273
  }
274
+ function bankFromDirectoryMap(directory, dirMap, resolveWorktrees) {
275
+ if (!directory || !dirMap || !Object.keys(dirMap).length) return null;
276
+ const candidates = /* @__PURE__ */ new Set([safeRealpath(directory)]);
277
+ if (resolveWorktrees) {
278
+ const root = getProjectRootFromGit(directory);
279
+ if (root) candidates.add(safeRealpath(root));
280
+ }
281
+ for (const [dirPath, bankId] of Object.entries(dirMap)) {
282
+ if (!dirPath || !bankId) continue;
283
+ const mapped = safeRealpath(dirPath);
284
+ if (candidates.has(mapped)) return bankId;
285
+ }
286
+ return null;
287
+ }
245
288
  function deriveBankId(config, directory) {
246
289
  const prefix = config.bankIdPrefix;
290
+ const resolveWorktrees = config.resolveWorktrees !== false;
291
+ const mapped = bankFromDirectoryMap(directory, config.directoryBankMap, resolveWorktrees);
292
+ if (mapped) {
293
+ return prefix ? `${prefix}-${mapped}` : mapped;
294
+ }
247
295
  if (!config.dynamicBankId) {
248
296
  const base = config.bankId || DEFAULT_BANK_NAME;
249
297
  return prefix ? `${prefix}-${base}` : base;
@@ -261,7 +309,7 @@ function deriveBankId(config, directory) {
261
309
  const fieldResolvers = {
262
310
  agent: () => config.agentName || "opencode",
263
311
  project: () => directory ? basename(directory) : "unknown",
264
- gitProject: () => deriveGitProjectName(directory),
312
+ gitProject: () => deriveGitProjectName(directory, resolveWorktrees),
265
313
  channel: () => channelId || "default",
266
314
  user: () => userId || "anonymous"
267
315
  };
@@ -292,6 +340,8 @@ async function ensureBankMission(client, bankId, config, missionsSet, logger = n
292
340
  }
293
341
 
294
342
  // src/tools.ts
343
+ import { readFileSync as readFileSync2 } from "fs";
344
+ import { basename as basename2 } from "path";
295
345
  import { tool as tool2 } from "@opencode-ai/plugin/tool";
296
346
 
297
347
  // src/content.ts
@@ -469,6 +519,9 @@ function resolveRetainMetadata(rawMetadata, vars, messageCount) {
469
519
  }
470
520
  return metadata;
471
521
  }
522
+ function sanitizeForRetain(text) {
523
+ return text.replace(/[\u0000-\u0008\u000B\u000C\u000E-\u001F]/g, "");
524
+ }
472
525
  function prepareRetentionTranscript(messages, retainFullWindow = false) {
473
526
  if (!messages.length) return { transcript: null, messageCount: 0 };
474
527
  let targetMessages;
@@ -487,7 +540,7 @@ function prepareRetentionTranscript(messages, retainFullWindow = false) {
487
540
  }
488
541
  const parts = [];
489
542
  for (const msg of targetMessages) {
490
- const content = stripMemoryTags(msg.content).trim();
543
+ const content = sanitizeForRetain(stripMemoryTags(msg.content)).trim();
491
544
  if (!content) continue;
492
545
  parts.push(`[role: ${msg.role}]
493
546
  ${content}
@@ -637,6 +690,9 @@ Content generates in the background; use hindsight_page_get or hindsight_page_re
637
690
  }
638
691
 
639
692
  // src/tools.ts
693
+ function toDocumentId(title) {
694
+ return title.trim().toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9._-]/g, "").slice(0, 200) || "document";
695
+ }
640
696
  function createTools(client, bankId, config, missionsSet, logger = new Logger({ silent: true })) {
641
697
  const hindsight_retain = tool2({
642
698
  description: "Store information in long-term memory. Use this to remember important facts, user preferences, project context, decisions, and anything worth recalling in future sessions. Be specific \u2014 include who, what, when, and why.",
@@ -666,14 +722,23 @@ function createTools(client, bankId, config, missionsSet, logger = new Logger({
666
722
  query: tool2.schema.string().describe("Natural language search query. Be specific about what you need to know.")
667
723
  },
668
724
  async execute(args) {
669
- const response = await client.recall(bankId, args.query, {
725
+ const recallOpts = {
670
726
  budget: config.recallBudget,
671
727
  maxTokens: config.recallMaxTokens,
672
728
  types: config.recallTypes,
673
729
  tags: config.recallTags.length ? config.recallTags : void 0,
674
730
  tagsMatch: config.recallTags.length ? config.recallTagsMatch : void 0
675
- });
676
- const results = response.results || [];
731
+ };
732
+ const response = await client.recall(bankId, args.query, recallOpts);
733
+ let results = response.results || [];
734
+ for (const extraBank of config.recallAdditionalBanks) {
735
+ if (!extraBank || extraBank === bankId) continue;
736
+ try {
737
+ const extra = await client.recall(extraBank, args.query, recallOpts);
738
+ results = results.concat(extra.results || []);
739
+ } catch {
740
+ }
741
+ }
677
742
  if (!results.length) return "No relevant memories found.";
678
743
  const formatted = formatMemories(results);
679
744
  return `Found ${results.length} relevant memories (as of ${formatCurrentTime()} UTC):
@@ -698,10 +763,66 @@ ${formatted}`;
698
763
  return response.text || "No relevant information found to reflect on.";
699
764
  }
700
765
  });
766
+ const hindsight_bank_current = tool2({
767
+ description: "Get the current Hindsight memory bank ID this session is bound to (Claude agent_knowledge_get_current_bank).",
768
+ args: {},
769
+ async execute() {
770
+ return `Current memory bank: ${bankId}`;
771
+ }
772
+ });
773
+ const hindsight_ingest = tool2({
774
+ description: "Upload text content into the memory bank as a named document (Claude agent_knowledge_ingest). Pass full raw content \u2014 do not summarize first. Re-ingesting the same title replaces the document.",
775
+ args: {
776
+ title: tool2.schema.string().describe("Document title; becomes the document ID."),
777
+ content: tool2.schema.string().describe("Full raw text content to store.")
778
+ },
779
+ async execute(args) {
780
+ if (missionsSet) {
781
+ await ensureBankMission(client, bankId, config, missionsSet, logger);
782
+ }
783
+ const cleaned = sanitizeForRetain(args.content);
784
+ if (!cleaned.trim()) return "Error: content is empty after sanitization.";
785
+ const documentId = toDocumentId(args.title);
786
+ await client.retain(bankId, cleaned, {
787
+ documentId,
788
+ context: config.retainContext
789
+ });
790
+ return `Ingested document "${documentId}" into bank ${bankId}.`;
791
+ }
792
+ });
793
+ const hindsight_ingest_file = tool2({
794
+ description: "Ingest a file from disk into the memory bank (Claude agent_knowledge_ingest_file). Filename (without extension) becomes the document ID.",
795
+ args: {
796
+ file_path: tool2.schema.string().describe("Absolute or relative path to a UTF-8 text file.")
797
+ },
798
+ async execute(args) {
799
+ if (missionsSet) {
800
+ await ensureBankMission(client, bankId, config, missionsSet, logger);
801
+ }
802
+ let raw;
803
+ try {
804
+ raw = readFileSync2(args.file_path, "utf-8");
805
+ } catch (e) {
806
+ return `Error: cannot read file: ${e instanceof Error ? e.message : String(e)}`;
807
+ }
808
+ const cleaned = sanitizeForRetain(raw);
809
+ if (!cleaned.trim()) return `Error: file is empty: ${args.file_path}`;
810
+ const base2 = basename2(args.file_path).replace(/\.[^.]+$/, "");
811
+ const documentId = toDocumentId(base2);
812
+ await client.retain(bankId, cleaned, {
813
+ documentId,
814
+ context: config.retainContext
815
+ });
816
+ return `Ingested file "${args.file_path}" as document "${documentId}" into bank ${bankId}.`;
817
+ }
818
+ });
701
819
  const base = {
702
820
  hindsight_retain,
703
821
  hindsight_recall,
704
- hindsight_reflect
822
+ hindsight_reflect,
823
+ hindsight_bank_current,
824
+ hindsight_ingest,
825
+ hindsight_ingest_file
705
826
  };
706
827
  if (!config.enableKnowledgePages) {
707
828
  return base;
@@ -793,14 +914,29 @@ function createHooks(hindsightClient, bankId, config, state2, opencodeClient, lo
793
914
  }
794
915
  async function recallForContext(query) {
795
916
  try {
796
- const response = await hindsightClient.recall(bankId, query, {
917
+ const recallOpts = {
797
918
  budget: config.recallBudget,
798
919
  maxTokens: config.recallMaxTokens,
799
920
  types: config.recallTypes,
800
921
  tags: config.recallTags.length ? config.recallTags : void 0,
801
922
  tagsMatch: config.recallTags.length ? config.recallTagsMatch : void 0
802
- });
803
- const results = response.results || [];
923
+ };
924
+ const response = await hindsightClient.recall(bankId, query, recallOpts);
925
+ let results = response.results || [];
926
+ for (const extraBank of config.recallAdditionalBanks) {
927
+ if (!extraBank || extraBank === bankId) continue;
928
+ try {
929
+ const extra = await hindsightClient.recall(extraBank, query, recallOpts);
930
+ const extraResults = extra.results || [];
931
+ if (extraResults.length) {
932
+ results = results.concat(extraResults);
933
+ }
934
+ } catch (e) {
935
+ logger.debug(`Recall from additional bank '${extraBank}' failed`, {
936
+ error: String(e)
937
+ });
938
+ }
939
+ }
804
940
  if (!results.length) return { context: null, ok: true };
805
941
  const formatted = formatMemories(results);
806
942
  const context = `<hindsight_memories>
@@ -809,10 +945,10 @@ Current time: ${formatCurrentTime()} UTC
809
945
 
810
946
  ${formatted}
811
947
  </hindsight_memories>`;
812
- return { context, ok: true };
948
+ return { context, ok: true, resultCount: results.length };
813
949
  } catch (e) {
814
950
  logger.error("Recall failed", e);
815
- return { context: null, ok: false };
951
+ return { context: null, ok: false, resultCount: 0 };
816
952
  }
817
953
  }
818
954
  async function getSessionMessages(sessionId, includeToolCalls) {
@@ -876,15 +1012,18 @@ ${formatted}
876
1012
  const userTurns = countUserTurns(messages);
877
1013
  const lastRetained = state2.lastRetainedTurn.get(sessionId) || 0;
878
1014
  const pending = userTurns - lastRetained;
1015
+ const everyN = Math.max(1, config.retainEveryNTurns);
879
1016
  if (pending <= 0) {
880
1017
  logger.debug(`${opts.reason}: nothing pending for ${sessionId}`);
881
1018
  return;
882
1019
  }
883
- if (!opts.force && pending < config.retainEveryNTurns) {
884
- logger.debug(
885
- `${opts.reason}: waiting for more turns (${pending}/${config.retainEveryNTurns})`
886
- );
887
- return;
1020
+ if (!opts.force) {
1021
+ if (everyN > 1 && userTurns % everyN !== 0) {
1022
+ logger.debug(
1023
+ `${opts.reason}: turn ${userTurns} not multiple of ${everyN}, skip retain`
1024
+ );
1025
+ return;
1026
+ }
888
1027
  }
889
1028
  try {
890
1029
  await retainSession(sessionId, messages);
@@ -972,6 +1111,24 @@ ${formatted}
972
1111
  logger.error("Compaction hook error", e);
973
1112
  }
974
1113
  };
1114
+ function showInjectToast(chars) {
1115
+ if (!config.injectToast) return;
1116
+ const showToast = opencodeClient.tui?.showToast;
1117
+ if (typeof showToast !== "function") return;
1118
+ try {
1119
+ const result = showToast({
1120
+ title: "Hindsight",
1121
+ message: `Memory injected \xB7 ${chars} chars`,
1122
+ variant: "info",
1123
+ duration: 2500
1124
+ });
1125
+ if (result && typeof result.then === "function") {
1126
+ result.then(void 0, () => {
1127
+ });
1128
+ }
1129
+ } catch {
1130
+ }
1131
+ }
975
1132
  async function ensureTurnRecall(sessionId, providedMessages) {
976
1133
  trackSession(sessionId);
977
1134
  const messages = providedMessages ? [...providedMessages] : await getSessionMessages(sessionId, false);
@@ -983,20 +1140,20 @@ ${formatted}
983
1140
  const lastUser = lastUserMessage(messages);
984
1141
  if (!lastUser) {
985
1142
  logger.debug(`ensureTurnRecall: no user message yet for ${sessionId}`);
986
- return null;
1143
+ return { context: null, fresh: false };
987
1144
  }
988
1145
  const prompt = lastUser.content.trim();
989
1146
  if (prompt.length < config.minRecallPromptChars) {
990
1147
  logger.debug(
991
1148
  `ensureTurnRecall: prompt too short (${prompt.length} < ${config.minRecallPromptChars}), skip`
992
1149
  );
993
- return null;
1150
+ return { context: null, fresh: false };
994
1151
  }
995
1152
  const userTurns = countUserTurns(messages);
996
1153
  const cached = state2.turnRecall.get(sessionId);
997
1154
  if (cached && cached.userTurnCount === userTurns) {
998
1155
  logger.debug(`ensureTurnRecall: cache hit session ${sessionId} turn ${userTurns}`);
999
- return cached.context;
1156
+ return { context: cached.context, fresh: false };
1000
1157
  }
1001
1158
  await ensureBankMission(hindsightClient, bankId, config, state2.missionsSet, logger);
1002
1159
  const query = composeRecallQuery(prompt, messages, config.recallContextTurns);
@@ -1007,7 +1164,7 @@ ${formatted}
1007
1164
  state2.turnRecall.set(sessionId, { userTurnCount: userTurns, context });
1008
1165
  capMapSize(state2.turnRecall, 1e3);
1009
1166
  }
1010
- return context;
1167
+ return { context, fresh: ok };
1011
1168
  }
1012
1169
  const systemTransform = async (input, output) => {
1013
1170
  try {
@@ -1015,10 +1172,16 @@ ${formatted}
1015
1172
  if (config.recallInjectMode !== "system") return;
1016
1173
  const sessionId = input.sessionID;
1017
1174
  if (!sessionId) return;
1018
- const context = await ensureTurnRecall(sessionId);
1175
+ const { context, fresh } = await ensureTurnRecall(sessionId);
1019
1176
  if (context) {
1020
1177
  applyContextToSystem(output, context);
1021
- logger.debug(`Injected per-turn recall into system for session ${sessionId}`);
1178
+ if (fresh) {
1179
+ logger.info(`Injected per-turn recall into system`, {
1180
+ session: sessionId,
1181
+ chars: context.length
1182
+ });
1183
+ showInjectToast(context.length);
1184
+ }
1022
1185
  }
1023
1186
  } catch (e) {
1024
1187
  logger.error("System transform hook error", e);
@@ -1033,13 +1196,20 @@ ${formatted}
1033
1196
  logger.debug("messagesTransform: no sessionID on messages, skip");
1034
1197
  return;
1035
1198
  }
1036
- const context = await ensureTurnRecall(sessionId, messagesFromTransformOutput(output));
1199
+ const { context, fresh } = await ensureTurnRecall(
1200
+ sessionId,
1201
+ messagesFromTransformOutput(output)
1202
+ );
1037
1203
  if (!context) return;
1038
1204
  const ok = applyContextToLatestUserMessage(output, context);
1039
1205
  if (ok) {
1040
- logger.debug(
1041
- `Injected per-turn recall as synthetic user part for session ${sessionId}`
1042
- );
1206
+ if (fresh) {
1207
+ logger.info(`Injected per-turn recall as synthetic user part`, {
1208
+ session: sessionId,
1209
+ chars: context.length
1210
+ });
1211
+ showInjectToast(context.length);
1212
+ }
1043
1213
  } else {
1044
1214
  logger.debug(`messagesTransform: no user message to attach synthetic part`);
1045
1215
  }
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/config.ts","../src/bank.ts","../src/logger.ts","../src/tools.ts","../src/content.ts","../src/pages.ts","../src/hooks.ts"],"sourcesContent":["/**\n * Hindsight OpenCode Plugin — persistent long-term memory for OpenCode agents.\n *\n * Provides:\n * - Custom tools: hindsight_retain/recall/reflect + optional knowledge pages\n * - Per-user-turn auto-recall (Claude Code UserPromptSubmit alignment)\n * - Auto-retain on session.idle (Claude Code Stop alignment)\n * - Force-retain on session.deleted + dispose (Claude Code SessionEnd alignment)\n * - Optional tool-call retention (retainToolCalls)\n * - Memory preservation during context compaction\n *\n * @example\n * ```json\n * // opencode.json\n * { \"plugin\": [\"opencode-hindsight-plus\"] }\n *\n * // With options:\n * { \"plugin\": [[\"opencode-hindsight-plus\", { \"bankId\": \"my-bank\" }]] }\n * ```\n */\n\nimport type { Plugin } from \"@opencode-ai/plugin\";\nimport { HindsightClient } from \"@vectorize-io/hindsight-client\";\nimport { loadConfig } from \"./config.js\";\nimport { deriveBankId } from \"./bank.js\";\nimport { createTools } from \"./tools.js\";\nimport { createHooks, type PluginState } from \"./hooks.js\";\nimport { Logger, type OpencodeLogClient } from \"./logger.js\";\n\n// Module-level state persists across sessions (plugin is instantiated per session,\n// but the module is loaded once per OpenCode server process).\nconst state: PluginState = {\n turnCount: 0,\n missionsSet: new Set(),\n turnRecall: new Map(),\n lastRetainedTurn: new Map(),\n activeSessions: new Set(),\n};\n\nconst HindsightPlugin: Plugin = async (input, options) => {\n const config = loadConfig(options);\n\n // Route logs through OpenCode's server log stream (TUI-safe). error/warn/info\n // are always emitted; debug is gated on config.debug.\n const logger = new Logger({\n client: input.client as unknown as OpencodeLogClient,\n debug: config.debug,\n });\n\n // hindsightApiUrl always resolves to a value (DEFAULT_HINDSIGHT_API_URL by default),\n // so plugin instantiation never fails just because the URL is unset.\n // Requests fail at call time if no API key is configured for a Cloud URL —\n // that surfaces a clear, actionable error from the server rather than silently\n // disabling the plugin.\n const client = new HindsightClient({\n baseUrl: config.hindsightApiUrl!,\n apiKey: config.hindsightApiToken || undefined,\n });\n\n const bankId = deriveBankId(config, input.directory);\n // Always log the resolved endpoint + bank so users can see which instance the\n // plugin is talking to (a common source of \"memories aren't saving\" confusion).\n logger.info(\"Hindsight plugin initialized\", {\n api: config.hindsightApiUrl,\n bank: bankId,\n authenticated: Boolean(config.hindsightApiToken),\n autoRecall: config.autoRecall,\n autoRetain: config.autoRetain,\n knowledgePages: config.enableKnowledgePages,\n });\n\n const tools = createTools(client, bankId, config, state.missionsSet, logger);\n const hooks = createHooks(\n client,\n bankId,\n config,\n state,\n input.client as unknown as Parameters<typeof createHooks>[4],\n logger\n );\n\n return {\n tool: tools,\n ...hooks,\n };\n};\n\n// Named export for direct import\nexport { HindsightPlugin };\n\n// Default export is the Plugin function itself — OpenCode's legacy loader\n// iterates Object.values(mod) and calls every function export as a Plugin\n// factory. Keep the entry surface free of utility re-exports (loadConfig,\n// deriveBankId) so they are not invoked as plugins. The plugin itself imports\n// those utilities directly from their modules.\nexport default HindsightPlugin;\n\n// Re-export types for consumers\nexport type { HindsightConfig } from \"./config.js\";\nexport type { PluginState } from \"./hooks.js\";\n","/**\n * Configuration management for the Hindsight OpenCode plugin.\n *\n * Loading order (later entries win):\n * 1. Built-in defaults\n * 2. User config file (~/.hindsight/opencode.json)\n * 3. Plugin options (from opencode.json plugin tuple)\n * 4. Environment variable overrides\n */\n\nimport { readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { homedir } from \"node:os\";\n\n/** Default API URL used when no override is supplied via env, file, or plugin options. */\nexport const DEFAULT_HINDSIGHT_API_URL = \"https://api.hindsight.vectorize.io\";\n\nexport interface HindsightConfig {\n // Recall\n autoRecall: boolean;\n recallBudget: string;\n recallMaxTokens: number;\n recallTypes: string[];\n recallContextTurns: number;\n recallMaxQueryChars: number;\n /** Skip auto-recall when the latest user prompt is shorter than this (Claude Code: 5). */\n minRecallPromptChars: number;\n /**\n * Where to inject recalled memories:\n * - synthetic-user: synthetic text part on latest user message (default; Claude-like hidden context)\n * - system: fold into system[0] (fallback if synthetic is problematic for a provider)\n */\n recallInjectMode: \"system\" | \"synthetic-user\";\n recallPromptPreamble: string;\n recallTags: string[];\n recallTagsMatch: \"any\" | \"all\" | \"any_strict\" | \"all_strict\";\n\n // Retain\n autoRetain: boolean;\n retainMode: string;\n retainEveryNTurns: number;\n retainOverlapTurns: number;\n /** Include tool call/result parts in retained transcripts (coding-context quality). */\n retainToolCalls: boolean;\n retainContext: string;\n retainTags: string[];\n retainMetadata: Record<string, string>;\n\n // Connection\n hindsightApiUrl: string | null;\n hindsightApiToken: string | null;\n\n // Bank\n bankId: string | null;\n bankIdPrefix: string;\n dynamicBankId: boolean;\n dynamicBankGranularity: string[];\n bankMission: string;\n retainMission: string | null;\n agentName: string;\n\n // Misc\n /** Register knowledge page tools (Hindsight mental-models / Claude agent_knowledge pages). */\n enableKnowledgePages: boolean;\n debug: boolean;\n}\n\nconst DEFAULTS: HindsightConfig = {\n // Recall — defaults aligned with Claude Code hindsight-memory plugin\n autoRecall: true,\n recallBudget: \"mid\",\n recallMaxTokens: 1024,\n // Observations are consolidated beliefs; reduces duplicate raw facts per turn.\n recallTypes: [\"observation\"],\n recallContextTurns: 1,\n recallMaxQueryChars: 800,\n minRecallPromptChars: 5,\n recallInjectMode: \"synthetic-user\",\n recallTags: [],\n recallTagsMatch: \"any\",\n recallPromptPreamble:\n \"Relevant memories from past conversations (prioritize recent when \" +\n \"conflicting). Only use memories that are directly useful to continue \" +\n \"this conversation; ignore the rest:\",\n\n // Retain — defaults aligned with Claude Code (Stop every N turns)\n autoRetain: true,\n retainMode: \"full-session\",\n retainEveryNTurns: 10,\n retainOverlapTurns: 2,\n // Align with Claude Code default (false). Tool transcripts explode fact\n // extraction / consolidation queues; opt in via config when needed.\n retainToolCalls: false,\n retainContext: \"opencode\",\n // Claude Code default shape; templates: {session_id} {bank_id} {timestamp} {user_id}\n retainTags: [\"{session_id}\"],\n retainMetadata: {},\n\n // Connection\n hindsightApiUrl: DEFAULT_HINDSIGHT_API_URL,\n hindsightApiToken: null,\n\n bankId: null,\n bankIdPrefix: \"\",\n dynamicBankId: true,\n dynamicBankGranularity: [\"gitProject\"],\n bankMission:\n \"You are an OpenCode AI coding agent. Focus on technical discussions, decisions, \" +\n \"and context relevant to the user's projects.\",\n retainMission:\n \"Extract technical decisions, architectural choices, user preferences, project context, \" +\n \"and people/tool relationships. Ignore routine greetings and transient operational details.\",\n agentName: \"opencode\",\n\n // Misc\n enableKnowledgePages: true,\n debug: false,\n};\n\n/** Env var → config key + type mapping */\nconst ENV_OVERRIDES: Record<string, [keyof HindsightConfig, \"string\" | \"bool\" | \"int\"]> = {\n HINDSIGHT_API_URL: [\"hindsightApiUrl\", \"string\"],\n HINDSIGHT_API_TOKEN: [\"hindsightApiToken\", \"string\"],\n HINDSIGHT_BANK_ID: [\"bankId\", \"string\"],\n HINDSIGHT_AGENT_NAME: [\"agentName\", \"string\"],\n HINDSIGHT_AUTO_RECALL: [\"autoRecall\", \"bool\"],\n HINDSIGHT_AUTO_RETAIN: [\"autoRetain\", \"bool\"],\n HINDSIGHT_RETAIN_MODE: [\"retainMode\", \"string\"],\n HINDSIGHT_RECALL_BUDGET: [\"recallBudget\", \"string\"],\n HINDSIGHT_RECALL_MAX_TOKENS: [\"recallMaxTokens\", \"int\"],\n HINDSIGHT_RECALL_MAX_QUERY_CHARS: [\"recallMaxQueryChars\", \"int\"],\n HINDSIGHT_RECALL_CONTEXT_TURNS: [\"recallContextTurns\", \"int\"],\n HINDSIGHT_MIN_RECALL_PROMPT_CHARS: [\"minRecallPromptChars\", \"int\"],\n HINDSIGHT_RECALL_INJECT_MODE: [\"recallInjectMode\", \"string\"],\n HINDSIGHT_DYNAMIC_BANK_ID: [\"dynamicBankId\", \"bool\"],\n HINDSIGHT_BANK_MISSION: [\"bankMission\", \"string\"],\n HINDSIGHT_BANK_ID_PREFIX: [\"bankIdPrefix\", \"string\"],\n HINDSIGHT_ENABLE_KNOWLEDGE_PAGES: [\"enableKnowledgePages\", \"bool\"],\n HINDSIGHT_RETAIN_EVERY_N_TURNS: [\"retainEveryNTurns\", \"int\"],\n HINDSIGHT_RETAIN_OVERLAP_TURNS: [\"retainOverlapTurns\", \"int\"],\n HINDSIGHT_RETAIN_TOOL_CALLS: [\"retainToolCalls\", \"bool\"],\n HINDSIGHT_RECALL_TAGS: [\"recallTags\", \"string\"],\n HINDSIGHT_RETAIN_TAGS: [\"retainTags\", \"string\"],\n HINDSIGHT_RECALL_TAGS_MATCH: [\"recallTagsMatch\", \"string\"],\n HINDSIGHT_RECALL_PROMPT_PREAMBLE: [\"recallPromptPreamble\", \"string\"],\n HINDSIGHT_RETAIN_CONTEXT: [\"retainContext\", \"string\"],\n // NOTE: `debug` is intentionally NOT an env override. It is a proper config\n // option set via opencode.json plugin options or ~/.hindsight/opencode.json,\n // because env vars are unreliable to set for OpenCode's plugin runtime\n // (notably on Windows).\n};\n\nfunction castEnv(value: string, typ: \"string\" | \"bool\" | \"int\"): string | boolean | number | null {\n if (typ === \"bool\") return [\"true\", \"1\", \"yes\"].includes(value.toLowerCase());\n if (typ === \"int\") {\n const n = parseInt(value, 10);\n return isNaN(n) ? null : n;\n }\n return value;\n}\n\nfunction loadSettingsFile(path: string): Record<string, unknown> {\n try {\n const raw = readFileSync(path, \"utf-8\");\n return JSON.parse(raw);\n } catch {\n return {};\n }\n}\n\nexport function loadConfig(pluginOptions?: Record<string, unknown>): HindsightConfig {\n // 1. Start with defaults\n const config: Record<string, unknown> = { ...DEFAULTS };\n\n // 2. User config file (~/.hindsight/opencode.json)\n const userConfigPath = join(homedir(), \".hindsight\", \"opencode.json\");\n const fileConfig = loadSettingsFile(userConfigPath);\n for (const [key, value] of Object.entries(fileConfig)) {\n if (value !== null && value !== undefined) {\n config[key] = value;\n }\n }\n\n // 3. Plugin options (from opencode.json: [\"@vectorize-io/opencode-hindsight\", { ... }])\n if (pluginOptions) {\n for (const [key, value] of Object.entries(pluginOptions)) {\n if (value !== null && value !== undefined) {\n config[key] = value;\n }\n }\n }\n\n // 4. Environment variable overrides (highest priority)\n for (const [envName, [key, typ]] of Object.entries(ENV_OVERRIDES)) {\n const val = process.env[envName];\n if (val !== undefined) {\n const castVal = castEnv(val, typ);\n if (castVal !== null) {\n config[key] = castVal;\n }\n }\n }\n\n // Array env vars (comma-separated)\n const recallTagsEnv = process.env[\"HINDSIGHT_RECALL_TAGS\"];\n if (recallTagsEnv !== undefined) {\n config[\"recallTags\"] = recallTagsEnv\n .split(\",\")\n .map((t) => t.trim())\n .filter(Boolean);\n }\n const recallTagsMatchEnv = process.env[\"HINDSIGHT_RECALL_TAGS_MATCH\"];\n if (recallTagsMatchEnv !== undefined) {\n config[\"recallTagsMatch\"] = recallTagsMatchEnv;\n }\n\n const retainTagsEnv = process.env[\"HINDSIGHT_RETAIN_TAGS\"];\n if (retainTagsEnv !== undefined) {\n config[\"retainTags\"] = retainTagsEnv\n .split(\",\")\n .map((t) => t.trim())\n .filter(Boolean);\n }\n\n const result = config as unknown as HindsightConfig;\n\n // Validate enum-like fields to catch typos early\n const VALID_RETAIN_MODES = [\"full-session\", \"last-turn\"];\n if (!VALID_RETAIN_MODES.includes(result.retainMode)) {\n console.error(\n `[Hindsight] Unknown retainMode \"${result.retainMode}\" — ` +\n `valid: ${VALID_RETAIN_MODES.join(\", \")}. Falling back to \"full-session\".`\n );\n result.retainMode = \"full-session\";\n }\n\n const VALID_TAGS_MATCH = [\"any\", \"all\", \"any_strict\", \"all_strict\"];\n if (!VALID_TAGS_MATCH.includes(result.recallTagsMatch)) {\n console.error(\n `[Hindsight] Unknown recallTagsMatch \"${result.recallTagsMatch}\" — ` +\n `valid: ${VALID_TAGS_MATCH.join(\", \")}. Falling back to \"any\".`\n );\n result.recallTagsMatch = \"any\";\n }\n\n const VALID_BUDGETS = [\"low\", \"mid\", \"high\"];\n if (!VALID_BUDGETS.includes(result.recallBudget)) {\n console.error(\n `[Hindsight] Unknown recallBudget \"${result.recallBudget}\" — ` +\n `valid: ${VALID_BUDGETS.join(\", \")}. Falling back to \"mid\".`\n );\n result.recallBudget = \"mid\";\n }\n\n const VALID_INJECT_MODES = [\"system\", \"synthetic-user\"];\n if (!VALID_INJECT_MODES.includes(result.recallInjectMode)) {\n console.error(\n `[Hindsight] Unknown recallInjectMode \"${result.recallInjectMode}\" — ` +\n `valid: ${VALID_INJECT_MODES.join(\", \")}. Falling back to \"synthetic-user\".`\n );\n result.recallInjectMode = \"synthetic-user\";\n }\n\n return result;\n}\n","/**\n * Bank ID derivation and mission management.\n *\n * Port of Claude Code plugin's bank.py, adapted for OpenCode's context model.\n *\n * Dimensions for dynamic bank IDs:\n * - agent → configured name or \"opencode\"\n * - project → derived from the working directory basename\n * - gitProject → derived from the main worktree's basename when inside a\n * git repository (so all linked worktrees of the same repo\n * share a single memory bank). Falls back to the working\n * directory basename when git is unavailable or the\n * directory is not a repo.\n */\n\nimport { basename, dirname } from \"node:path\";\nimport { execFileSync } from \"node:child_process\";\nimport type { HindsightConfig } from \"./config.js\";\nimport { Logger } from \"./logger.js\";\nimport type { HindsightClient } from \"@vectorize-io/hindsight-client\";\n\nconst DEFAULT_BANK_NAME = \"opencode\";\nconst VALID_FIELDS = new Set([\"agent\", \"project\", \"gitProject\", \"channel\", \"user\"]);\n\n/**\n * Resolve the main worktree root for a directory inside a git repository.\n *\n * Uses `git rev-parse --path-format=absolute --git-common-dir`, which always\n * points to the .git directory of the *main* worktree, even when invoked from\n * a linked worktree (created with `git worktree add`). The parent of that path\n * is the main worktree root, so all linked worktrees of the same repo resolve\n * to the same root and end up sharing one memory bank.\n *\n * Returns `null` when git is unavailable, the directory is not a repo, or the\n * git invocation fails for any other reason.\n */\nfunction getProjectRootFromGit(directory: string): string | null {\n if (!directory) return null;\n try {\n const commonDir = execFileSync(\n \"git\",\n [\"rev-parse\", \"--path-format=absolute\", \"--git-common-dir\"],\n {\n cwd: directory,\n encoding: \"utf-8\",\n stdio: [\"ignore\", \"pipe\", \"ignore\"],\n timeout: 1000,\n }\n ).trim();\n if (!commonDir) return null;\n // For typical clones and `git worktree add`, common-dir is `<root>/.git`,\n // so the parent is the main worktree root. For bare repos, common-dir is\n // the bare directory itself (e.g. `myrepo.git`); use it directly.\n if (basename(commonDir) === \".git\") {\n return dirname(commonDir);\n }\n return commonDir;\n } catch {\n return null;\n }\n}\n\nfunction deriveGitProjectName(directory: string): string {\n const projectRoot = getProjectRootFromGit(directory);\n if (projectRoot) return basename(projectRoot);\n return directory ? basename(directory) : \"unknown\";\n}\n\n/**\n * Derive a bank ID from context and config.\n *\n * Static mode: returns config.bankId or DEFAULT_BANK_NAME.\n * Dynamic mode: composes from granularity fields joined by '::'.\n */\nexport function deriveBankId(config: HindsightConfig, directory: string): string {\n const prefix = config.bankIdPrefix;\n\n if (!config.dynamicBankId) {\n const base = config.bankId || DEFAULT_BANK_NAME;\n return prefix ? `${prefix}-${base}` : base;\n }\n\n const fields = config.dynamicBankGranularity?.length\n ? config.dynamicBankGranularity\n : [\"gitProject\"];\n\n for (const f of fields) {\n if (!VALID_FIELDS.has(f)) {\n console.error(\n `[Hindsight] Unknown dynamicBankGranularity field \"${f}\" — ` +\n `valid: ${[...VALID_FIELDS].sort().join(\", \")}`\n );\n }\n }\n\n const channelId = process.env.HINDSIGHT_CHANNEL_ID || \"\";\n const userId = process.env.HINDSIGHT_USER_ID || \"\";\n\n // Lazy resolution so we don't spawn `git` for `gitProject` when the field\n // isn't part of the configured granularity.\n const fieldResolvers: Record<string, () => string> = {\n agent: () => config.agentName || \"opencode\",\n project: () => (directory ? basename(directory) : \"unknown\"),\n gitProject: () => deriveGitProjectName(directory),\n channel: () => channelId || \"default\",\n user: () => userId || \"anonymous\",\n };\n\n // bank_id is stored as-is server-side; HTTP path encoding is the client layer's job.\n const segments = fields.map((f) => fieldResolvers[f]?.() || \"unknown\");\n const baseBankId = segments.join(\"::\");\n\n return prefix ? `${prefix}-${baseBankId}` : baseBankId;\n}\n\n/**\n * Set bank mission on first use, skip if already set.\n * Uses an in-memory Set (plugin is long-lived, unlike Claude Code's ephemeral hooks).\n */\nexport async function ensureBankMission(\n client: HindsightClient,\n bankId: string,\n config: HindsightConfig,\n missionsSet: Set<string>,\n logger: Logger = new Logger({ silent: true })\n): Promise<void> {\n const mission = config.bankMission;\n if (!mission?.trim()) return;\n if (missionsSet.has(bankId)) return;\n\n try {\n await client.createBank(bankId, {\n reflectMission: mission,\n retainMission: config.retainMission || undefined,\n });\n missionsSet.add(bankId);\n // Cap tracked banks\n if (missionsSet.size > 10000) {\n const keys = [...missionsSet].sort();\n for (const k of keys.slice(0, keys.length >> 1)) {\n missionsSet.delete(k);\n }\n }\n logger.debug(`Set mission for bank: ${bankId}`);\n } catch (e) {\n // Don't fail if mission set fails — bank may not exist yet\n logger.debug(`Could not set bank mission for ${bankId}`, { error: String(e) });\n }\n}\n","/**\n * Lightweight logger for the Hindsight OpenCode plugin.\n *\n * Routes through OpenCode's server log API (`client.app.log`) when available so\n * messages land in OpenCode's own log stream — visible via `--print-logs` and\n * the OpenCode log files — without writing to stdout/stderr and corrupting the\n * TUI. Falls back to `console.error` when the OpenCode client is unavailable\n * (e.g. degraded load); unit tests use a silent logger.\n *\n * Levels:\n * - error / warn / info → always emitted, so failures and the resolved\n * endpoint are visible WITHOUT any debug opt-in.\n * - debug → emitted only when `debug` is enabled in config.\n */\n\nexport type LogLevel = \"debug\" | \"info\" | \"warn\" | \"error\";\n\n/** Minimal shape of OpenCode's `client.app.log` that we depend on. */\nexport interface OpencodeLogClient {\n app?: {\n log?: (options: {\n body: {\n service: string;\n level: LogLevel;\n message: string;\n extra?: Record<string, unknown>;\n };\n }) => unknown;\n };\n}\n\nexport interface LoggerOptions {\n /** OpenCode client used to write into the server log stream. */\n client?: OpencodeLogClient;\n /** When true, `debug()` messages are emitted. */\n debug?: boolean;\n /** When true, the logger emits nothing (used as a safe default in tests). */\n silent?: boolean;\n}\n\nconst SERVICE = \"hindsight\";\n\nexport class Logger {\n private readonly client?: OpencodeLogClient;\n private readonly debugEnabled: boolean;\n private readonly silent: boolean;\n\n constructor(options: LoggerOptions = {}) {\n this.client = options.client;\n this.debugEnabled = options.debug ?? false;\n this.silent = options.silent ?? false;\n }\n\n private emit(level: LogLevel, message: string, extra?: Record<string, unknown>): void {\n if (this.silent) return;\n\n const app = this.client?.app;\n if (app && typeof app.log === \"function\") {\n try {\n // IMPORTANT: call as a method on `app` so `this` is preserved.\n // OpenCode's `app.log` is a class method that uses `this` internally;\n // calling a detached reference (`const log = app.log; log(...)`) throws\n // `this._client is undefined`, which would otherwise be swallowed below\n // and produce no log at all.\n const result = app.log({ body: { service: SERVICE, level, message, extra } });\n // Fire-and-forget: a logging failure must never surface to OpenCode.\n if (result && typeof (result as Promise<unknown>).then === \"function\") {\n (result as Promise<unknown>).then(undefined, () => {});\n }\n return;\n } catch {\n // Fall through to the console fallback if app.log throws synchronously.\n }\n }\n\n // Fallback when no OpenCode client is available (or app.log failed).\n // OpenCode captures plugin console output into its logs, so this stays\n // visible and TUI-safe.\n const line = extra\n ? `[Hindsight] ${message} ${JSON.stringify(extra)}`\n : `[Hindsight] ${message}`;\n console.error(line);\n }\n\n error(message: string, error?: unknown): void {\n this.emit(\"error\", message, error === undefined ? undefined : { error: errorToString(error) });\n }\n\n warn(message: string, extra?: Record<string, unknown>): void {\n this.emit(\"warn\", message, extra);\n }\n\n info(message: string, extra?: Record<string, unknown>): void {\n this.emit(\"info\", message, extra);\n }\n\n debug(message: string, extra?: Record<string, unknown>): void {\n if (this.debugEnabled) this.emit(\"debug\", message, extra);\n }\n}\n\nfunction errorToString(error: unknown): string {\n if (error instanceof Error) return error.stack || `${error.name}: ${error.message}`;\n return String(error);\n}\n","/**\n * Custom tool definitions for the Hindsight OpenCode plugin.\n *\n * Registers hindsight_retain, hindsight_recall, and hindsight_reflect\n * as tools the agent can call explicitly.\n */\n\nimport { tool } from \"@opencode-ai/plugin/tool\";\nimport type { ToolDefinition } from \"@opencode-ai/plugin/tool\";\nimport type { HindsightClient } from \"@vectorize-io/hindsight-client\";\nimport type { HindsightConfig } from \"./config.js\";\nimport {\n formatMemories,\n formatCurrentTime,\n buildRetainTemplateVars,\n resolveRetainTags,\n resolveRetainMetadata,\n} from \"./content.js\";\nimport { ensureBankMission } from \"./bank.js\";\nimport { createPageTools } from \"./pages.js\";\nimport { Logger } from \"./logger.js\";\n\nexport interface HindsightTools {\n hindsight_retain: ToolDefinition;\n hindsight_recall: ToolDefinition;\n hindsight_reflect: ToolDefinition;\n // Index signature so the object is assignable to OpenCode's Hooks.tool\n // (Record<string, ToolDefinition>) without losing the specific keys above.\n [key: string]: ToolDefinition;\n}\n\nexport function createTools(\n client: HindsightClient,\n bankId: string,\n config: HindsightConfig,\n missionsSet?: Set<string>,\n logger: Logger = new Logger({ silent: true })\n): HindsightTools {\n const hindsight_retain = tool({\n description:\n \"Store information in long-term memory. Use this to remember important facts, \" +\n \"user preferences, project context, decisions, and anything worth recalling in future sessions. \" +\n \"Be specific — include who, what, when, and why.\",\n args: {\n content: tool.schema\n .string()\n .describe(\"The information to remember. Be specific and self-contained.\"),\n context: tool.schema\n .string()\n .optional()\n .describe(\"Optional context about where this information came from.\"),\n },\n async execute(args, ctx) {\n if (missionsSet) {\n await ensureBankMission(client, bankId, config, missionsSet, logger);\n }\n const sessionId =\n ctx && typeof ctx === \"object\" && typeof (ctx as { sessionID?: unknown }).sessionID === \"string\"\n ? (ctx as { sessionID: string }).sessionID\n : \"\";\n const vars = buildRetainTemplateVars({ sessionId, bankId });\n const tags = resolveRetainTags(config.retainTags, vars);\n const metadata = resolveRetainMetadata(config.retainMetadata, vars);\n await client.retain(bankId, args.content, {\n context: args.context || config.retainContext,\n tags,\n metadata,\n });\n return \"Memory stored successfully.\";\n },\n });\n\n const hindsight_recall = tool({\n description:\n \"Search long-term memory for relevant information. Use this proactively before \" +\n \"answering questions about past conversations, user preferences, project history, \" +\n \"or any topic where prior context would help. When in doubt, recall first.\",\n args: {\n query: tool.schema\n .string()\n .describe(\"Natural language search query. Be specific about what you need to know.\"),\n },\n async execute(args) {\n const response = await client.recall(bankId, args.query, {\n budget: config.recallBudget as \"low\" | \"mid\" | \"high\",\n maxTokens: config.recallMaxTokens,\n types: config.recallTypes,\n tags: config.recallTags.length ? config.recallTags : undefined,\n tagsMatch: config.recallTags.length ? config.recallTagsMatch : undefined,\n });\n\n const results = response.results || [];\n if (!results.length) return \"No relevant memories found.\";\n\n const formatted = formatMemories(results);\n return `Found ${results.length} relevant memories (as of ${formatCurrentTime()} UTC):\\n\\n${formatted}`;\n },\n });\n\n const hindsight_reflect = tool({\n description:\n \"Generate a thoughtful answer using long-term memory. Unlike recall (which returns \" +\n \"raw memories), reflect synthesizes memories into a coherent answer. Use for questions \" +\n 'like \"What do you know about this user?\" or \"Summarize our project decisions.\"',\n args: {\n query: tool.schema.string().describe(\"The question to answer using long-term memory.\"),\n context: tool.schema\n .string()\n .optional()\n .describe(\"Optional additional context to guide the reflection.\"),\n },\n async execute(args) {\n if (missionsSet) {\n await ensureBankMission(client, bankId, config, missionsSet, logger);\n }\n const response = await client.reflect(bankId, args.query, {\n context: args.context,\n budget: config.recallBudget as \"low\" | \"mid\" | \"high\",\n });\n\n return response.text || \"No relevant information found to reflect on.\";\n },\n });\n\n const base: HindsightTools = {\n hindsight_retain,\n hindsight_recall,\n hindsight_reflect,\n };\n\n if (!config.enableKnowledgePages) {\n return base;\n }\n\n return {\n ...base,\n ...createPageTools(client, bankId, config, missionsSet, logger),\n };\n}\n","/**\n * Content processing utilities.\n *\n * Port of the Claude Code plugin's content.py:\n * - Memory tag stripping (anti-feedback-loop)\n * - Recall query composition and truncation\n * - Memory formatting for context injection\n * - Retention transcript formatting\n */\n\n/** Strip <hindsight_memories> and <relevant_memories> blocks to prevent retain feedback loops. */\nexport function stripMemoryTags(content: string): string {\n content = content.replace(/<hindsight_memories>[\\s\\S]*?<\\/hindsight_memories>/g, \"\");\n content = content.replace(/<relevant_memories>[\\s\\S]*?<\\/relevant_memories>/g, \"\");\n return content;\n}\n\n/**\n * Fold a fresh `<hindsight_memories>` block into a system section.\n * Replaces any previous block so per-turn recall does not stack forever.\n */\nexport function injectHindsightMemories(systemSection: string, context: string): string {\n const base = stripMemoryTags(systemSection)\n .replace(/\\n{3,}/g, \"\\n\\n\")\n .trimEnd();\n return base ? `${base}\\n\\n${context}` : context;\n}\n\nexport interface RecallResult {\n text: string;\n type?: string | null;\n mentioned_at?: string | null;\n}\n\n/** Format recall results into human-readable text for context injection. */\nexport function formatMemories(results: RecallResult[]): string {\n if (!results.length) return \"\";\n return results\n .map((r) => {\n const typeStr = r.type ? ` [${r.type}]` : \"\";\n const dateStr = r.mentioned_at ? ` (${r.mentioned_at})` : \"\";\n return `- ${r.text}${typeStr}${dateStr}`;\n })\n .join(\"\\n\\n\");\n}\n\n/** Format current UTC time for recall context. */\nexport function formatCurrentTime(): string {\n const now = new Date();\n const y = now.getUTCFullYear();\n const m = String(now.getUTCMonth() + 1).padStart(2, \"0\");\n const d = String(now.getUTCDate()).padStart(2, \"0\");\n const h = String(now.getUTCHours()).padStart(2, \"0\");\n const min = String(now.getUTCMinutes()).padStart(2, \"0\");\n return `${y}-${m}-${d} ${h}:${min}`;\n}\n\nexport interface Message {\n role: string;\n content: string;\n}\n\n/** Minimal OpenCode tool-part shape used when retainToolCalls is enabled. */\nexport interface ToolPartLike {\n type: string;\n tool?: string;\n state?: {\n status?: string;\n input?: Record<string, unknown>;\n output?: string;\n error?: string;\n title?: string;\n };\n}\n\nconst TOOL_INPUT_MAX = 500;\nconst TOOL_OUTPUT_MAX = 1000;\n\nfunction truncate(text: string, max: number): string {\n if (text.length <= max) return text;\n return `${text.slice(0, max)}…`;\n}\n\n/** True for hindsight operational tools — skip to avoid retain feedback loops. */\nexport function isHindsightOperationalTool(name: string): boolean {\n const lower = name.toLowerCase();\n return (\n lower.startsWith(\"hindsight_\") ||\n lower.startsWith(\"hindsight_page_\") ||\n /(?:^|__)(?:agent_knowledge_|knowledge_)/.test(lower)\n );\n}\n\n/**\n * Format an OpenCode tool part for retention transcripts.\n * Returns null when the part should be skipped.\n */\nexport function formatToolPartForRetain(part: ToolPartLike): string | null {\n if (part.type !== \"tool\") return null;\n const name = (part.tool || \"unknown\").trim();\n if (!name || isHindsightOperationalTool(name)) return null;\n\n const state = part.state;\n const input = state?.input ?? {};\n let inputJson: string;\n try {\n inputJson = JSON.stringify(input);\n } catch {\n inputJson = String(input);\n }\n inputJson = truncate(inputJson, TOOL_INPUT_MAX);\n\n const lines = [`[tool_call: ${name}]`, `input: ${inputJson}`];\n if (state?.title) lines.push(`title: ${state.title}`);\n\n if (state?.status === \"completed\" && typeof state.output === \"string\" && state.output) {\n lines.push(`output: ${truncate(state.output, TOOL_OUTPUT_MAX)}`);\n } else if (state?.status === \"error\" && state.error) {\n lines.push(`error: ${truncate(state.error, TOOL_OUTPUT_MAX)}`);\n } else if (state?.status) {\n lines.push(`status: ${state.status}`);\n }\n\n lines.push(\"[tool_call:end]\");\n return lines.join(\"\\n\");\n}\n\n/**\n * Build message content from OpenCode parts (text + optional tool parts).\n */\nexport function buildMessageContent(\n parts: ReadonlyArray<{ type: string; text?: string } & Partial<ToolPartLike>>,\n includeToolCalls: boolean\n): string {\n const chunks: string[] = [];\n for (const part of parts) {\n if (part.type === \"text\" && part.text) {\n chunks.push(part.text);\n continue;\n }\n if (includeToolCalls && part.type === \"tool\") {\n const formatted = formatToolPartForRetain(part);\n if (formatted) chunks.push(formatted);\n }\n }\n return chunks.join(\"\\n\\n\");\n}\n\n/**\n * Compose a multi-turn recall query from conversation history.\n *\n * When recallContextTurns > 1, includes prior context above the latest query.\n */\nexport function composeRecallQuery(\n latestQuery: string,\n messages: Message[],\n recallContextTurns: number\n): string {\n const latest = latestQuery.trim();\n if (recallContextTurns <= 1 || !messages.length) return latest;\n\n const contextual = sliceLastTurnsByUserBoundary(messages, recallContextTurns);\n const contextLines: string[] = [];\n\n for (const msg of contextual) {\n const content = stripMemoryTags(msg.content).trim();\n if (!content) continue;\n if (msg.role === \"user\" && content === latest) continue;\n contextLines.push(`${msg.role}: ${content}`);\n }\n\n if (!contextLines.length) return latest;\n\n return [\"Prior context:\", contextLines.join(\"\\n\"), latest].join(\"\\n\\n\");\n}\n\n/**\n * Truncate a composed recall query to maxChars.\n * Preserves the latest user message, drops oldest context lines first.\n */\nexport function truncateRecallQuery(query: string, latestQuery: string, maxChars: number): string {\n if (maxChars <= 0 || query.length <= maxChars) return query;\n\n const latest = latestQuery.trim();\n const latestOnly = latest.length > maxChars ? latest.slice(0, maxChars) : latest;\n\n if (!query.includes(\"Prior context:\")) return latestOnly;\n\n const contextMarker = \"Prior context:\\n\\n\";\n const markerIndex = query.indexOf(contextMarker);\n if (markerIndex === -1) return latestOnly;\n\n const suffix = \"\\n\\n\" + latest;\n const suffixIndex = query.lastIndexOf(suffix);\n if (suffixIndex === -1) return latestOnly;\n if (suffix.length >= maxChars) return latestOnly;\n\n const contextBody = query.slice(markerIndex + contextMarker.length, suffixIndex);\n const contextLines = contextBody.split(\"\\n\").filter(Boolean);\n\n const kept: string[] = [];\n for (let i = contextLines.length - 1; i >= 0; i--) {\n kept.unshift(contextLines[i]);\n const candidate = `${contextMarker}${kept.join(\"\\n\")}${suffix}`;\n if (candidate.length > maxChars) {\n kept.shift();\n break;\n }\n }\n\n if (kept.length) return `${contextMarker}${kept.join(\"\\n\")}${suffix}`;\n return latestOnly;\n}\n\n/** Slice messages to the last N turns, where a turn starts at a user message. */\nexport function sliceLastTurnsByUserBoundary(messages: Message[], turns: number): Message[] {\n if (!messages.length || turns <= 0) return [];\n\n let userTurnsSeen = 0;\n let startIndex = -1;\n\n for (let i = messages.length - 1; i >= 0; i--) {\n if (messages[i].role === \"user\") {\n userTurnsSeen++;\n if (userTurnsSeen >= turns) {\n startIndex = i;\n break;\n }\n }\n }\n\n return startIndex === -1 ? [...messages] : messages.slice(startIndex);\n}\n\nexport interface RetainTemplateVars {\n session_id: string;\n bank_id: string;\n timestamp: string;\n user_id: string;\n}\n\nexport interface ResolveRetainTemplatesInput {\n sessionId: string;\n bankId: string;\n messageCount?: number;\n userId?: string;\n now?: Date;\n}\n\n/** Build template vars for retain tags/metadata (Claude Code retain.py). */\nexport function buildRetainTemplateVars(input: ResolveRetainTemplatesInput): RetainTemplateVars {\n const now = input.now ?? new Date();\n const timestamp = now.toISOString().replace(/\\.\\d{3}Z$/, \"Z\");\n return {\n session_id: input.sessionId,\n bank_id: input.bankId,\n timestamp,\n user_id: input.userId ?? process.env.HINDSIGHT_USER_ID ?? \"\",\n };\n}\n\nexport function applyTemplateString(value: string, vars: RetainTemplateVars): string {\n let out = value;\n for (const [key, val] of Object.entries(vars) as Array<[keyof RetainTemplateVars, string]>) {\n out = out.split(`{${key}}`).join(val);\n }\n return out;\n}\n\n/**\n * Resolve retainTags with template vars.\n * Drops empty tags and tags whose value after `:` is empty (e.g. `user:` when user_id unset).\n */\nexport function resolveRetainTags(\n rawTags: readonly string[],\n vars: RetainTemplateVars\n): string[] | undefined {\n if (!rawTags.length) return undefined;\n const tags: string[] = [];\n for (const original of rawTags) {\n const resolved = applyTemplateString(original, vars).trim();\n if (!resolved) continue;\n const colon = resolved.indexOf(\":\");\n if (colon !== -1 && resolved.slice(colon + 1) === \"\") continue;\n tags.push(resolved);\n }\n return tags.length ? tags : undefined;\n}\n\n/** Resolve retainMetadata templates and merge session defaults. */\nexport function resolveRetainMetadata(\n rawMetadata: Record<string, string>,\n vars: RetainTemplateVars,\n messageCount?: number\n): Record<string, string> {\n const metadata: Record<string, string> = {\n retained_at: vars.timestamp,\n session_id: vars.session_id,\n };\n if (messageCount !== undefined) {\n metadata.message_count = String(messageCount);\n }\n for (const [key, value] of Object.entries(rawMetadata)) {\n metadata[key] = applyTemplateString(String(value), vars);\n }\n return metadata;\n}\n\n/**\n * Format messages into a retention transcript.\n *\n * Uses [role: ...]...[role:end] markers for structured retention.\n */\nexport function prepareRetentionTranscript(\n messages: Message[],\n retainFullWindow: boolean = false\n): { transcript: string | null; messageCount: number } {\n if (!messages.length) return { transcript: null, messageCount: 0 };\n\n let targetMessages: Message[];\n if (retainFullWindow) {\n targetMessages = messages;\n } else {\n // Default: retain only the last turn\n let lastUserIdx = -1;\n for (let i = messages.length - 1; i >= 0; i--) {\n if (messages[i].role === \"user\") {\n lastUserIdx = i;\n break;\n }\n }\n if (lastUserIdx === -1) return { transcript: null, messageCount: 0 };\n targetMessages = messages.slice(lastUserIdx);\n }\n\n const parts: string[] = [];\n for (const msg of targetMessages) {\n const content = stripMemoryTags(msg.content).trim();\n if (!content) continue;\n parts.push(`[role: ${msg.role}]\\n${content}\\n[${msg.role}:end]`);\n }\n\n if (!parts.length) return { transcript: null, messageCount: 0 };\n\n const transcript = parts.join(\"\\n\\n\");\n if (transcript.trim().length < 10) return { transcript: null, messageCount: 0 };\n\n return { transcript, messageCount: parts.length };\n}\n","/**\n * Knowledge pages tools — OpenCode wrappers around Hindsight mental-models.\n * Claude Code exposes these as agent_knowledge_* MCP tools.\n */\n\nimport { tool } from \"@opencode-ai/plugin/tool\";\nimport type { ToolDefinition } from \"@opencode-ai/plugin/tool\";\nimport type { HindsightClient } from \"@vectorize-io/hindsight-client\";\nimport type { HindsightConfig } from \"./config.js\";\nimport { ensureBankMission } from \"./bank.js\";\nimport { Logger } from \"./logger.js\";\n\nexport interface PageTools {\n hindsight_page_list: ToolDefinition;\n hindsight_page_get: ToolDefinition;\n hindsight_page_create: ToolDefinition;\n hindsight_page_update: ToolDefinition;\n hindsight_page_delete: ToolDefinition;\n hindsight_page_refresh: ToolDefinition;\n [key: string]: ToolDefinition;\n}\n\nfunction asItems(response: unknown): Array<Record<string, unknown>> {\n if (!response || typeof response !== \"object\") return [];\n const items = (response as { items?: unknown }).items;\n if (!Array.isArray(items)) return [];\n return items.filter((x): x is Record<string, unknown> => !!x && typeof x === \"object\");\n}\n\nfunction formatPageSummary(page: Record<string, unknown>): string {\n const id = String(page.id ?? page.mental_model_id ?? \"?\");\n const name = String(page.name ?? \"(unnamed)\");\n const query = page.source_query ? String(page.source_query) : \"\";\n return query ? `- ${id}: ${name}\\n source_query: ${query}` : `- ${id}: ${name}`;\n}\n\nfunction formatPageDetail(page: Record<string, unknown>): string {\n const id = String(page.id ?? page.mental_model_id ?? \"?\");\n const name = String(page.name ?? \"(unnamed)\");\n const query = page.source_query != null ? String(page.source_query) : \"\";\n const content = page.content != null ? String(page.content) : \"(no content yet)\";\n const lines = [\n `id: ${id}`,\n `name: ${name}`,\n query ? `source_query: ${query}` : null,\n \"\",\n content,\n ].filter((line): line is string => line !== null);\n return lines.join(\"\\n\");\n}\n\nexport function createPageTools(\n client: HindsightClient,\n bankId: string,\n config: HindsightConfig,\n missionsSet?: Set<string>,\n logger: Logger = new Logger({ silent: true })\n): PageTools {\n const ensureMission = async () => {\n if (missionsSet) {\n await ensureBankMission(client, bankId, config, missionsSet, logger);\n }\n };\n\n const hindsight_page_list = tool({\n description:\n \"List knowledge pages (IDs and names) in the current Hindsight bank. \" +\n \"Pages are synthesized long-lived docs rebuilt from memory via a source_query. \" +\n \"Use hindsight_page_get to read full content.\",\n args: {},\n async execute() {\n await ensureMission();\n const response = await client.listMentalModels(bankId);\n const items = asItems(response);\n if (!items.length) return \"No knowledge pages in this bank.\";\n return `Knowledge pages (${items.length}) in bank \"${bankId}\":\\n\\n${items\n .map(formatPageSummary)\n .join(\"\\n\")}`;\n },\n });\n\n const hindsight_page_get = tool({\n description:\n \"Read a knowledge page by id. Returns full synthesized content when available.\",\n args: {\n page_id: tool.schema.string().describe(\"Knowledge page / mental-model id.\"),\n },\n async execute(args) {\n await ensureMission();\n const page = await client.getMentalModel(bankId, args.page_id);\n if (!page || typeof page !== \"object\") return `Page not found: ${args.page_id}`;\n return formatPageDetail(page as Record<string, unknown>);\n },\n });\n\n const hindsight_page_create = tool({\n description:\n \"Create a knowledge page. source_query is re-asked after consolidations to rebuild \" +\n \"the page from observations (e.g. 'What are the key architecture decisions?').\",\n args: {\n name: tool.schema.string().describe(\"Human-readable page title.\"),\n source_query: tool.schema\n .string()\n .describe(\"Question used to synthesize/refresh page content from memories.\"),\n page_id: tool.schema\n .string()\n .optional()\n .describe(\"Optional stable page id. Server generates one if omitted.\"),\n },\n async execute(args) {\n await ensureMission();\n const created = await client.createMentalModel(bankId, args.name, args.source_query, {\n id: args.page_id,\n trigger: { refreshAfterConsolidation: true },\n });\n const id =\n created && typeof created === \"object\"\n ? String(\n (created as { mental_model_id?: string; id?: string }).mental_model_id ??\n (created as { id?: string }).id ??\n args.page_id ??\n \"?\"\n )\n : String(args.page_id ?? \"?\");\n return `Knowledge page created.\\nid: ${id}\\nname: ${args.name}\\nsource_query: ${args.source_query}\\nContent generates in the background; use hindsight_page_get or hindsight_page_refresh shortly.`;\n },\n });\n\n const hindsight_page_update = tool({\n description: \"Update a knowledge page name and/or source_query.\",\n args: {\n page_id: tool.schema.string().describe(\"Knowledge page id.\"),\n name: tool.schema.string().optional().describe(\"New title.\"),\n source_query: tool.schema.string().optional().describe(\"New synthesis question.\"),\n },\n async execute(args) {\n if (!args.name && !args.source_query) {\n return \"Nothing to update: provide name and/or source_query.\";\n }\n await ensureMission();\n await client.updateMentalModel(bankId, args.page_id, {\n name: args.name,\n sourceQuery: args.source_query,\n });\n return `Knowledge page updated: ${args.page_id}`;\n },\n });\n\n const hindsight_page_delete = tool({\n description: \"Permanently delete a knowledge page.\",\n args: {\n page_id: tool.schema.string().describe(\"Knowledge page id to delete.\"),\n },\n async execute(args) {\n await ensureMission();\n await client.deleteMentalModel(bankId, args.page_id);\n return `Knowledge page deleted: ${args.page_id}`;\n },\n });\n\n const hindsight_page_refresh = tool({\n description:\n \"Refresh a knowledge page now by re-running its source_query against current memories.\",\n args: {\n page_id: tool.schema.string().describe(\"Knowledge page id to refresh.\"),\n },\n async execute(args) {\n await ensureMission();\n const refreshed = await client.refreshMentalModel(bankId, args.page_id);\n if (refreshed && typeof refreshed === \"object\" && \"content\" in refreshed) {\n return formatPageDetail(refreshed as Record<string, unknown>);\n }\n return `Refresh started for page ${args.page_id}. Use hindsight_page_get to read content when ready.`;\n },\n });\n\n return {\n hindsight_page_list,\n hindsight_page_get,\n hindsight_page_create,\n hindsight_page_update,\n hindsight_page_delete,\n hindsight_page_refresh,\n };\n}\n","/**\n * Hook implementations for the Hindsight OpenCode plugin.\n *\n * Aligned with Claude Code hindsight-memory:\n * - experimental.chat.system.transform → per-user-turn auto-recall\n * - event (session.idle) → auto-retain after the assistant finishes\n * - event (session.deleted) + dispose → force-retain pending turns (SessionEnd)\n * - experimental.session.compacting → retain + inject for compaction\n */\n\nimport type { HindsightClient } from \"@vectorize-io/hindsight-client\";\nimport type { HindsightConfig } from \"./config.js\";\nimport { Logger } from \"./logger.js\";\nimport {\n formatMemories,\n formatCurrentTime,\n composeRecallQuery,\n truncateRecallQuery,\n prepareRetentionTranscript,\n sliceLastTurnsByUserBoundary,\n injectHindsightMemories,\n buildMessageContent,\n buildRetainTemplateVars,\n resolveRetainTags,\n resolveRetainMetadata,\n type Message,\n type ToolPartLike,\n} from \"./content.js\";\nimport { ensureBankMission } from \"./bank.js\";\n\nexport interface TurnRecallCache {\n userTurnCount: number;\n context: string | null;\n}\n\nexport interface PluginState {\n turnCount: number;\n missionsSet: Set<string>;\n turnRecall: Map<string, TurnRecallCache>;\n lastRetainedTurn: Map<string, number>;\n /** Sessions observed this process — used by dispose force-retain. */\n activeSessions: Set<string>;\n}\n\ninterface EventInput {\n event: {\n type: string;\n properties: Record<string, unknown>;\n };\n}\n\ninterface CompactingInput {\n sessionID: string;\n}\n\ninterface CompactingOutput {\n context: string[];\n prompt?: string;\n}\n\ninterface SystemTransformInput {\n sessionID?: string;\n model: unknown;\n}\n\ninterface SystemTransformOutput {\n system: string[];\n}\n\ntype SessionPart = {\n type: string;\n text?: string;\n tool?: string;\n synthetic?: boolean;\n state?: ToolPartLike[\"state\"];\n [key: string]: unknown;\n};\n\ninterface MessagesTransformOutput {\n messages: Array<{\n info: { role: string; sessionID?: string; id?: string };\n parts: SessionPart[];\n }>;\n}\n\ntype OpencodeClient = {\n session: {\n messages: (params: { path: { id: string } }) => Promise<{\n data?: Array<{\n info: { role: string };\n parts: SessionPart[];\n }>;\n error?: unknown;\n request?: unknown;\n response?: unknown;\n }>;\n };\n};\n\nexport interface HindsightHooks {\n event: (input: EventInput) => Promise<void>;\n dispose: () => Promise<void>;\n \"experimental.session.compacting\": (\n input: CompactingInput,\n output: CompactingOutput\n ) => Promise<void>;\n \"experimental.chat.system.transform\": (\n input: SystemTransformInput,\n output: SystemTransformOutput\n ) => Promise<void>;\n \"experimental.chat.messages.transform\": (\n input: Record<string, never>,\n output: MessagesTransformOutput\n ) => Promise<void>;\n}\n\nfunction countUserTurns(messages: readonly Message[]): number {\n return messages.filter((m) => m.role === \"user\").length;\n}\n\nfunction lastUserMessage(messages: readonly Message[]): Message | undefined {\n for (let i = messages.length - 1; i >= 0; i--) {\n if (messages[i].role === \"user\") return messages[i];\n }\n return undefined;\n}\n\nfunction applyContextToSystem(output: SystemTransformOutput, context: string): void {\n output.system[0] = injectHindsightMemories(output.system[0] ?? \"\", context);\n}\n\nfunction isHindsightMemoryPart(part: SessionPart): boolean {\n return (\n part.type === \"text\" &&\n typeof part.text === \"string\" &&\n part.text.includes(\"<hindsight_memories>\")\n );\n}\n\nfunction applyContextToLatestUserMessage(\n output: MessagesTransformOutput,\n context: string\n): boolean {\n for (let i = output.messages.length - 1; i >= 0; i--) {\n const msg = output.messages[i];\n if (msg.info.role !== \"user\") continue;\n msg.parts = msg.parts.filter((p) => !isHindsightMemoryPart(p));\n msg.parts.push({\n type: \"text\",\n text: context,\n synthetic: true,\n });\n return true;\n }\n return false;\n}\n\nfunction sessionIdFromMessages(output: MessagesTransformOutput): string | undefined {\n for (let i = output.messages.length - 1; i >= 0; i--) {\n const id = output.messages[i]?.info?.sessionID;\n if (typeof id === \"string\" && id) return id;\n }\n return undefined;\n}\n\n/**\n * Text content from transform parts for recall query composition.\n * Skips prior hindsight injects. Prefer non-synthetic text (Claude: user prompt only).\n */\nfunction textFromParts(\n parts: readonly SessionPart[],\n includeSynthetic: boolean\n): string {\n const chunks: string[] = [];\n for (const part of parts) {\n if (part.type !== \"text\" || typeof part.text !== \"string\" || !part.text) continue;\n if (isHindsightMemoryPart(part)) continue;\n if (!includeSynthetic && part.synthetic) continue;\n chunks.push(part.text);\n }\n return chunks.join(\"\\n\").trim();\n}\n\n/**\n * Convert OpenCode transform messages → text-only Message[] for recall.\n * Claude Code uses hook `prompt` (+ optional transcript); we use the transform\n * payload already in hand so we never re-fetch the full session for recall.\n */\nfunction messagesFromTransformOutput(output: MessagesTransformOutput): Message[] {\n const messages: Message[] = [];\n for (const msg of output.messages) {\n const role = msg.info.role;\n if (role !== \"user\" && role !== \"assistant\") continue;\n const content =\n role === \"user\"\n ? textFromParts(msg.parts, false) || textFromParts(msg.parts, true)\n : textFromParts(msg.parts, true);\n if (content) messages.push({ role, content });\n }\n return messages;\n}\n\nfunction capMapSize<K, V>(map: Map<K, V>, max: number): void {\n if (map.size <= max) return;\n const first = map.keys().next().value;\n if (first !== undefined) map.delete(first);\n}\n\nfunction extractSessionIdFromDeleted(properties: Record<string, unknown>): string | undefined {\n if (typeof properties.sessionID === \"string\") return properties.sessionID;\n const info = properties.info;\n if (info && typeof info === \"object\" && info !== null) {\n const id = (info as { id?: unknown }).id;\n if (typeof id === \"string\") return id;\n }\n return undefined;\n}\n\nexport function createHooks(\n hindsightClient: HindsightClient,\n bankId: string,\n config: HindsightConfig,\n state: PluginState,\n opencodeClient: OpencodeClient,\n logger: Logger = new Logger({ silent: true })\n): HindsightHooks {\n interface RecallOutcome {\n context: string | null;\n ok: boolean;\n }\n\n function trackSession(sessionId: string): void {\n state.activeSessions.add(sessionId);\n if (state.activeSessions.size > 1000) {\n const first = state.activeSessions.values().next().value;\n if (first) state.activeSessions.delete(first);\n }\n }\n\n async function recallForContext(query: string): Promise<RecallOutcome> {\n try {\n const response = await hindsightClient.recall(bankId, query, {\n budget: config.recallBudget as \"low\" | \"mid\" | \"high\",\n maxTokens: config.recallMaxTokens,\n types: config.recallTypes,\n tags: config.recallTags.length ? config.recallTags : undefined,\n tagsMatch: config.recallTags.length ? config.recallTagsMatch : undefined,\n });\n\n const results = response.results || [];\n if (!results.length) return { context: null, ok: true };\n\n const formatted = formatMemories(results);\n const context =\n `<hindsight_memories>\\n` +\n `${config.recallPromptPreamble}\\n` +\n `Current time: ${formatCurrentTime()} UTC\\n\\n` +\n `${formatted}\\n` +\n `</hindsight_memories>`;\n return { context, ok: true };\n } catch (e) {\n logger.error(\"Recall failed\", e);\n return { context: null, ok: false };\n }\n }\n\n /**\n * @param includeToolCalls - retain path may include tools; recall path stays text-only\n * so tool dumps do not dilute the recall query.\n */\n async function getSessionMessages(\n sessionId: string,\n includeToolCalls: boolean\n ): Promise<Message[]> {\n try {\n logger.debug(`getSessionMessages: fetching messages for session ${sessionId}`);\n const response = await opencodeClient.session.messages({\n path: { id: sessionId },\n });\n if (response.error) {\n logger.warn(\"getSessionMessages: OpenCode returned an error\", {\n error: JSON.stringify(response.error)?.substring(0, 500),\n });\n }\n const rawMessages = response.data || [];\n const messages: Message[] = [];\n for (const msg of rawMessages) {\n const role = msg.info.role;\n if (role !== \"user\" && role !== \"assistant\") continue;\n const content = buildMessageContent(msg.parts, includeToolCalls);\n if (content.trim()) {\n messages.push({ role, content });\n }\n }\n logger.debug(`getSessionMessages: raw=${rawMessages.length}, parsed=${messages.length}`);\n return messages;\n } catch (e) {\n logger.error(\"Failed to get session messages\", e);\n return [];\n }\n }\n\n async function retainSession(sessionId: string, messages: Message[]): Promise<void> {\n const retainFullWindow = config.retainMode === \"full-session\";\n let targetMessages: Message[];\n let documentId: string;\n\n if (retainFullWindow) {\n targetMessages = messages;\n documentId = sessionId;\n } else {\n const windowTurns = config.retainEveryNTurns + config.retainOverlapTurns;\n targetMessages = sliceLastTurnsByUserBoundary(messages, windowTurns);\n documentId = `${sessionId}-${Date.now()}`;\n }\n\n const { transcript, messageCount } = prepareRetentionTranscript(targetMessages, true);\n if (!transcript) return;\n\n const vars = buildRetainTemplateVars({ sessionId, bankId });\n const tags = resolveRetainTags(config.retainTags, vars);\n const metadata = resolveRetainMetadata(config.retainMetadata, vars, messageCount);\n\n await ensureBankMission(hindsightClient, bankId, config, state.missionsSet, logger);\n await hindsightClient.retain(bankId, transcript, {\n documentId,\n context: config.retainContext,\n tags,\n metadata,\n async: true,\n });\n }\n\n /**\n * Retain when there are user turns not yet covered by lastRetainedTurn.\n * force=true ignores retainEveryNTurns (SessionEnd / dispose / deleted).\n */\n async function maybeRetainSession(\n sessionId: string,\n opts: { force: boolean; reason: string }\n ): Promise<void> {\n if (!config.autoRetain) return;\n\n const messages = await getSessionMessages(sessionId, config.retainToolCalls);\n if (!messages.length) return;\n\n const userTurns = countUserTurns(messages);\n const lastRetained = state.lastRetainedTurn.get(sessionId) || 0;\n const pending = userTurns - lastRetained;\n\n if (pending <= 0) {\n logger.debug(`${opts.reason}: nothing pending for ${sessionId}`);\n return;\n }\n if (!opts.force && pending < config.retainEveryNTurns) {\n logger.debug(\n `${opts.reason}: waiting for more turns (${pending}/${config.retainEveryNTurns})`\n );\n return;\n }\n\n try {\n await retainSession(sessionId, messages);\n state.lastRetainedTurn.set(sessionId, userTurns);\n logger.info(`Auto-retained ${messages.length} messages`, {\n session: sessionId,\n bank: bankId,\n reason: opts.reason,\n force: opts.force,\n userTurns,\n });\n } catch (e) {\n logger.error(`Auto-retain failed (${opts.reason})`, e);\n }\n }\n\n const event = async (input: EventInput): Promise<void> => {\n try {\n const { event: evt } = input;\n logger.debug(`event hook fired: type=${evt.type}`);\n\n if (evt.type === \"session.idle\") {\n const sessionId = (evt.properties as { sessionID?: string }).sessionID;\n if (sessionId) {\n trackSession(sessionId);\n await maybeRetainSession(sessionId, { force: false, reason: \"session.idle\" });\n }\n return;\n }\n\n // Claude Code SessionEnd equivalent: flush any pending turns.\n if (evt.type === \"session.deleted\") {\n const sessionId = extractSessionIdFromDeleted(\n evt.properties as Record<string, unknown>\n );\n if (sessionId) {\n trackSession(sessionId);\n await maybeRetainSession(sessionId, { force: true, reason: \"session.deleted\" });\n state.activeSessions.delete(sessionId);\n state.turnRecall.delete(sessionId);\n }\n }\n } catch (e) {\n logger.error(\"Event hook error\", e);\n }\n };\n\n const dispose = async (): Promise<void> => {\n if (!config.autoRetain) return;\n const sessionIds = [...state.activeSessions];\n logger.debug(`dispose: force-retain ${sessionIds.length} active session(s)`);\n for (const sessionId of sessionIds) {\n await maybeRetainSession(sessionId, { force: true, reason: \"dispose\" });\n }\n };\n\n const compacting = async (input: CompactingInput, output: CompactingOutput): Promise<void> => {\n try {\n trackSession(input.sessionID);\n const retainMessages = await getSessionMessages(input.sessionID, config.retainToolCalls);\n if (retainMessages.length && config.autoRetain) {\n try {\n await retainSession(input.sessionID, retainMessages);\n state.lastRetainedTurn.delete(input.sessionID);\n logger.debug(\"Pre-compaction retain completed\");\n } catch (e) {\n logger.error(\"Pre-compaction retain failed\", e);\n }\n }\n\n const recallMessages = await getSessionMessages(input.sessionID, false);\n if (recallMessages.length) {\n const lastUserMsg = lastUserMessage(recallMessages);\n if (lastUserMsg) {\n const query = composeRecallQuery(\n lastUserMsg.content,\n recallMessages,\n config.recallContextTurns\n );\n const truncated = truncateRecallQuery(\n query,\n lastUserMsg.content,\n config.recallMaxQueryChars\n );\n const { context } = await recallForContext(truncated);\n if (context) {\n output.context.push(context);\n }\n }\n }\n } catch (e) {\n logger.error(\"Compaction hook error\", e);\n }\n };\n\n /**\n * Shared per-turn recall: returns context string (or null) and updates turn cache.\n *\n * Claude Code alignment (recall.py):\n * - Default recallContextTurns=1 → query is latest user prompt only\n * - Does NOT re-fetch full session for every recall\n * - Multi-turn prior context only when turns > 1 (from provided messages /\n * transcript), not a full session.messages dump\n *\n * OpenCode synthetic-user path passes transform messages (already in hand).\n * System mode falls back to getSessionMessages (no transform payload).\n */\n async function ensureTurnRecall(\n sessionId: string,\n providedMessages?: readonly Message[]\n ): Promise<string | null> {\n trackSession(sessionId);\n\n const messages = providedMessages\n ? [...providedMessages]\n : await getSessionMessages(sessionId, false);\n\n if (providedMessages) {\n logger.debug(\n `ensureTurnRecall: using ${messages.length} transform message(s), no session fetch`\n );\n }\n\n const lastUser = lastUserMessage(messages);\n if (!lastUser) {\n logger.debug(`ensureTurnRecall: no user message yet for ${sessionId}`);\n return null;\n }\n\n const prompt = lastUser.content.trim();\n if (prompt.length < config.minRecallPromptChars) {\n logger.debug(\n `ensureTurnRecall: prompt too short (${prompt.length} < ${config.minRecallPromptChars}), skip`\n );\n return null;\n }\n\n const userTurns = countUserTurns(messages);\n const cached = state.turnRecall.get(sessionId);\n\n if (cached && cached.userTurnCount === userTurns) {\n logger.debug(`ensureTurnRecall: cache hit session ${sessionId} turn ${userTurns}`);\n return cached.context;\n }\n\n await ensureBankMission(hindsightClient, bankId, config, state.missionsSet, logger);\n\n // Claude: turns<=1 → query = prompt; turns>1 → compose from recent history.\n const query = composeRecallQuery(prompt, messages, config.recallContextTurns);\n const truncated = truncateRecallQuery(query, prompt, config.recallMaxQueryChars);\n logger.debug(`ensureTurnRecall: recalling turn ${userTurns}, queryLen=${truncated.length}`);\n\n const { context, ok } = await recallForContext(truncated);\n if (ok) {\n state.turnRecall.set(sessionId, { userTurnCount: userTurns, context });\n capMapSize(state.turnRecall, 1000);\n }\n return context;\n }\n\n const systemTransform = async (\n input: SystemTransformInput,\n output: SystemTransformOutput\n ): Promise<void> => {\n try {\n if (!config.autoRecall) return;\n if (config.recallInjectMode !== \"system\") return;\n const sessionId = input.sessionID;\n if (!sessionId) return;\n\n // System mode has no transform messages — fetch text-only history (legacy path).\n const context = await ensureTurnRecall(sessionId);\n if (context) {\n applyContextToSystem(output, context);\n logger.debug(`Injected per-turn recall into system for session ${sessionId}`);\n }\n } catch (e) {\n logger.error(\"System transform hook error\", e);\n }\n };\n\n const messagesTransform = async (\n _input: Record<string, never>,\n output: MessagesTransformOutput\n ): Promise<void> => {\n try {\n if (!config.autoRecall) return;\n if (config.recallInjectMode !== \"synthetic-user\") return;\n\n const sessionId = sessionIdFromMessages(output);\n if (!sessionId) {\n logger.debug(\"messagesTransform: no sessionID on messages, skip\");\n return;\n }\n\n // Claude-like: use messages already in the prompt pipeline — no full session API.\n const context = await ensureTurnRecall(sessionId, messagesFromTransformOutput(output));\n if (!context) return;\n\n const ok = applyContextToLatestUserMessage(output, context);\n if (ok) {\n logger.debug(\n `Injected per-turn recall as synthetic user part for session ${sessionId}`\n );\n } else {\n logger.debug(`messagesTransform: no user message to attach synthetic part`);\n }\n } catch (e) {\n logger.error(\"Messages transform hook error\", e);\n }\n };\n\n return {\n event,\n dispose,\n \"experimental.session.compacting\": compacting,\n \"experimental.chat.system.transform\": systemTransform,\n \"experimental.chat.messages.transform\": messagesTransform,\n };\n}\n"],"mappings":"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","names":["tool","state","tool","state"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/config.ts","../src/bank.ts","../src/logger.ts","../src/tools.ts","../src/content.ts","../src/pages.ts","../src/hooks.ts"],"sourcesContent":["/**\n * Hindsight OpenCode Plugin — persistent long-term memory for OpenCode agents.\n *\n * Provides:\n * - Custom tools: hindsight_retain/recall/reflect + optional knowledge pages\n * - Per-user-turn auto-recall (Claude Code UserPromptSubmit alignment)\n * - Auto-retain on session.idle (Claude Code Stop alignment)\n * - Force-retain on session.deleted + dispose (Claude Code SessionEnd alignment)\n * - Optional tool-call retention (retainToolCalls)\n * - Memory preservation during context compaction\n *\n * @example\n * ```json\n * // opencode.json\n * { \"plugin\": [\"opencode-hindsight-plus\"] }\n *\n * // With options:\n * { \"plugin\": [[\"opencode-hindsight-plus\", { \"bankId\": \"my-bank\" }]] }\n * ```\n */\n\nimport type { Plugin } from \"@opencode-ai/plugin\";\nimport { HindsightClient } from \"@vectorize-io/hindsight-client\";\nimport { loadConfig } from \"./config.js\";\nimport { deriveBankId } from \"./bank.js\";\nimport { createTools } from \"./tools.js\";\nimport { createHooks, type PluginState } from \"./hooks.js\";\nimport { Logger, type OpencodeLogClient } from \"./logger.js\";\n\n// Module-level state persists across sessions (plugin is instantiated per session,\n// but the module is loaded once per OpenCode server process).\nconst state: PluginState = {\n turnCount: 0,\n missionsSet: new Set(),\n turnRecall: new Map(),\n lastRetainedTurn: new Map(),\n activeSessions: new Set(),\n};\n\nconst HindsightPlugin: Plugin = async (input, options) => {\n const config = loadConfig(options);\n\n // Route logs through OpenCode's server log stream (TUI-safe). error/warn/info\n // are always emitted; debug is gated on config.debug.\n const logger = new Logger({\n client: input.client as unknown as OpencodeLogClient,\n debug: config.debug,\n });\n\n // hindsightApiUrl always resolves to a value (DEFAULT_HINDSIGHT_API_URL by default),\n // so plugin instantiation never fails just because the URL is unset.\n // Requests fail at call time if no API key is configured for a Cloud URL —\n // that surfaces a clear, actionable error from the server rather than silently\n // disabling the plugin.\n const client = new HindsightClient({\n baseUrl: config.hindsightApiUrl!,\n apiKey: config.hindsightApiToken || undefined,\n });\n\n const bankId = deriveBankId(config, input.directory);\n // Always log the resolved endpoint + bank so users can see which instance the\n // plugin is talking to (a common source of \"memories aren't saving\" confusion).\n logger.info(\"Hindsight plugin initialized\", {\n api: config.hindsightApiUrl,\n bank: bankId,\n authenticated: Boolean(config.hindsightApiToken),\n autoRecall: config.autoRecall,\n autoRetain: config.autoRetain,\n knowledgePages: config.enableKnowledgePages,\n });\n\n const tools = createTools(client, bankId, config, state.missionsSet, logger);\n const hooks = createHooks(\n client,\n bankId,\n config,\n state,\n input.client as unknown as Parameters<typeof createHooks>[4],\n logger\n );\n\n return {\n tool: tools,\n ...hooks,\n };\n};\n\n// Named export for direct import\nexport { HindsightPlugin };\n\n// Default export is the Plugin function itself — OpenCode's legacy loader\n// iterates Object.values(mod) and calls every function export as a Plugin\n// factory. Keep the entry surface free of utility re-exports (loadConfig,\n// deriveBankId) so they are not invoked as plugins. The plugin itself imports\n// those utilities directly from their modules.\nexport default HindsightPlugin;\n\n// Re-export types for consumers\nexport type { HindsightConfig } from \"./config.js\";\nexport type { PluginState } from \"./hooks.js\";\n","/**\n * Configuration management for the Hindsight OpenCode plugin.\n *\n * Loading order (later entries win):\n * 1. Built-in defaults\n * 2. User config file (~/.hindsight/opencode.json)\n * 3. Plugin options (from opencode.json plugin tuple)\n * 4. Environment variable overrides\n */\n\nimport { readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { homedir } from \"node:os\";\n\n/** Default API URL used when no override is supplied via env, file, or plugin options. */\nexport const DEFAULT_HINDSIGHT_API_URL = \"https://api.hindsight.vectorize.io\";\n\nexport interface HindsightConfig {\n // Recall\n autoRecall: boolean;\n recallBudget: string;\n recallMaxTokens: number;\n recallTypes: string[];\n recallContextTurns: number;\n recallMaxQueryChars: number;\n /** Skip auto-recall when the latest user prompt is shorter than this (Claude Code: 5). */\n minRecallPromptChars: number;\n /**\n * Where to inject recalled memories:\n * - synthetic-user: synthetic text part on latest user message (default; Claude-like hidden context)\n * - system: fold into system[0] (fallback if synthetic is problematic for a provider)\n */\n recallInjectMode: \"system\" | \"synthetic-user\";\n recallPromptPreamble: string;\n recallTags: string[];\n recallTagsMatch: \"any\" | \"all\" | \"any_strict\" | \"all_strict\";\n /** Extra bank IDs to query on every auto-recall (Claude recallAdditionalBanks). */\n recallAdditionalBanks: string[];\n\n // Retain\n autoRetain: boolean;\n retainMode: string;\n retainEveryNTurns: number;\n retainOverlapTurns: number;\n /** Include tool call/result parts in retained transcripts (coding-context quality). */\n retainToolCalls: boolean;\n retainContext: string;\n retainTags: string[];\n retainMetadata: Record<string, string>;\n\n // Connection\n hindsightApiUrl: string | null;\n hindsightApiToken: string | null;\n\n // Bank\n bankId: string | null;\n bankIdPrefix: string;\n dynamicBankId: boolean;\n dynamicBankGranularity: string[];\n /**\n * Explicit cwd/path → bank_id map (Claude directoryBankMap).\n * Highest priority when a realpath match is found.\n */\n directoryBankMap: Record<string, string>;\n /**\n * When true (default), gitProject / directory map resolution uses the main\n * worktree root so linked worktrees share one bank (Claude resolveWorktrees).\n */\n resolveWorktrees: boolean;\n bankMission: string;\n retainMission: string | null;\n agentName: string;\n\n // Misc\n /** Register knowledge page tools (Hindsight mental-models / Claude agent_knowledge pages). */\n enableKnowledgePages: boolean;\n /**\n * When true, show an OpenCode TUI toast on a *fresh* successful inject\n * (new user turn with memories). Cache re-applies during tool loops stay silent.\n * Headless `opencode run` has no TUI — toast is a no-op there.\n */\n injectToast: boolean;\n debug: boolean;\n}\n\nconst DEFAULTS: HindsightConfig = {\n // Recall — defaults aligned with Claude Code hindsight-memory plugin\n autoRecall: true,\n recallBudget: \"mid\",\n recallMaxTokens: 1024,\n // Observations are consolidated beliefs; reduces duplicate raw facts per turn.\n recallTypes: [\"observation\"],\n recallContextTurns: 1,\n recallMaxQueryChars: 800,\n minRecallPromptChars: 5,\n recallInjectMode: \"synthetic-user\",\n recallTags: [],\n recallTagsMatch: \"any\",\n recallAdditionalBanks: [],\n recallPromptPreamble:\n \"Relevant memories from past conversations (prioritize recent when \" +\n \"conflicting). Only use memories that are directly useful to continue \" +\n \"this conversation; ignore the rest:\",\n\n // Retain — defaults aligned with Claude Code (Stop every N turns)\n autoRetain: true,\n retainMode: \"full-session\",\n retainEveryNTurns: 10,\n retainOverlapTurns: 2,\n // Align with Claude Code default (false). Tool transcripts explode fact\n // extraction / consolidation queues; opt in via config when needed.\n retainToolCalls: false,\n retainContext: \"opencode\",\n // Claude Code default shape; templates: {session_id} {bank_id} {timestamp} {user_id}\n retainTags: [\"{session_id}\"],\n retainMetadata: {},\n\n // Connection\n hindsightApiUrl: DEFAULT_HINDSIGHT_API_URL,\n hindsightApiToken: null,\n\n bankId: null,\n bankIdPrefix: \"\",\n dynamicBankId: true,\n dynamicBankGranularity: [\"gitProject\"],\n directoryBankMap: {},\n resolveWorktrees: true,\n bankMission:\n \"You are an OpenCode AI coding agent. Focus on technical discussions, decisions, \" +\n \"and context relevant to the user's projects.\",\n retainMission:\n \"Extract technical decisions, architectural choices, user preferences, project context, \" +\n \"and people/tool relationships. Ignore routine greetings and transient operational details.\",\n agentName: \"opencode\",\n\n // Misc\n enableKnowledgePages: true,\n injectToast: false,\n debug: false,\n};\n\n/** Env var → config key + type mapping */\nconst ENV_OVERRIDES: Record<string, [keyof HindsightConfig, \"string\" | \"bool\" | \"int\"]> = {\n HINDSIGHT_API_URL: [\"hindsightApiUrl\", \"string\"],\n HINDSIGHT_API_TOKEN: [\"hindsightApiToken\", \"string\"],\n HINDSIGHT_BANK_ID: [\"bankId\", \"string\"],\n HINDSIGHT_AGENT_NAME: [\"agentName\", \"string\"],\n HINDSIGHT_AUTO_RECALL: [\"autoRecall\", \"bool\"],\n HINDSIGHT_AUTO_RETAIN: [\"autoRetain\", \"bool\"],\n HINDSIGHT_RETAIN_MODE: [\"retainMode\", \"string\"],\n HINDSIGHT_RECALL_BUDGET: [\"recallBudget\", \"string\"],\n HINDSIGHT_RECALL_MAX_TOKENS: [\"recallMaxTokens\", \"int\"],\n HINDSIGHT_RECALL_MAX_QUERY_CHARS: [\"recallMaxQueryChars\", \"int\"],\n HINDSIGHT_RECALL_CONTEXT_TURNS: [\"recallContextTurns\", \"int\"],\n HINDSIGHT_MIN_RECALL_PROMPT_CHARS: [\"minRecallPromptChars\", \"int\"],\n HINDSIGHT_RECALL_INJECT_MODE: [\"recallInjectMode\", \"string\"],\n HINDSIGHT_DYNAMIC_BANK_ID: [\"dynamicBankId\", \"bool\"],\n HINDSIGHT_BANK_MISSION: [\"bankMission\", \"string\"],\n HINDSIGHT_BANK_ID_PREFIX: [\"bankIdPrefix\", \"string\"],\n HINDSIGHT_ENABLE_KNOWLEDGE_PAGES: [\"enableKnowledgePages\", \"bool\"],\n HINDSIGHT_RETAIN_EVERY_N_TURNS: [\"retainEveryNTurns\", \"int\"],\n HINDSIGHT_RETAIN_OVERLAP_TURNS: [\"retainOverlapTurns\", \"int\"],\n HINDSIGHT_RETAIN_TOOL_CALLS: [\"retainToolCalls\", \"bool\"],\n HINDSIGHT_RECALL_TAGS: [\"recallTags\", \"string\"],\n HINDSIGHT_RETAIN_TAGS: [\"retainTags\", \"string\"],\n HINDSIGHT_RECALL_TAGS_MATCH: [\"recallTagsMatch\", \"string\"],\n HINDSIGHT_RECALL_PROMPT_PREAMBLE: [\"recallPromptPreamble\", \"string\"],\n HINDSIGHT_RETAIN_CONTEXT: [\"retainContext\", \"string\"],\n HINDSIGHT_RESOLVE_WORKTREES: [\"resolveWorktrees\", \"bool\"],\n HINDSIGHT_INJECT_TOAST: [\"injectToast\", \"bool\"],\n // NOTE: `debug` is intentionally NOT an env override. It is a proper config\n // option set via opencode.json plugin options or ~/.hindsight/opencode.json,\n // because env vars are unreliable to set for OpenCode's plugin runtime\n // (notably on Windows).\n};\n\nfunction castEnv(value: string, typ: \"string\" | \"bool\" | \"int\"): string | boolean | number | null {\n if (typ === \"bool\") return [\"true\", \"1\", \"yes\"].includes(value.toLowerCase());\n if (typ === \"int\") {\n const n = parseInt(value, 10);\n return isNaN(n) ? null : n;\n }\n return value;\n}\n\nfunction loadSettingsFile(path: string): Record<string, unknown> {\n try {\n const raw = readFileSync(path, \"utf-8\");\n return JSON.parse(raw);\n } catch {\n return {};\n }\n}\n\nexport function loadConfig(pluginOptions?: Record<string, unknown>): HindsightConfig {\n // 1. Start with defaults\n const config: Record<string, unknown> = { ...DEFAULTS };\n\n // 2. User config file (~/.hindsight/opencode.json)\n const userConfigPath = join(homedir(), \".hindsight\", \"opencode.json\");\n const fileConfig = loadSettingsFile(userConfigPath);\n for (const [key, value] of Object.entries(fileConfig)) {\n if (value !== null && value !== undefined) {\n config[key] = value;\n }\n }\n\n // 3. Plugin options (from opencode.json: [\"@vectorize-io/opencode-hindsight\", { ... }])\n if (pluginOptions) {\n for (const [key, value] of Object.entries(pluginOptions)) {\n if (value !== null && value !== undefined) {\n config[key] = value;\n }\n }\n }\n\n // 4. Environment variable overrides (highest priority)\n for (const [envName, [key, typ]] of Object.entries(ENV_OVERRIDES)) {\n const val = process.env[envName];\n if (val !== undefined) {\n const castVal = castEnv(val, typ);\n if (castVal !== null) {\n config[key] = castVal;\n }\n }\n }\n\n // Array env vars (comma-separated)\n const recallTagsEnv = process.env[\"HINDSIGHT_RECALL_TAGS\"];\n if (recallTagsEnv !== undefined) {\n config[\"recallTags\"] = recallTagsEnv\n .split(\",\")\n .map((t) => t.trim())\n .filter(Boolean);\n }\n const recallTagsMatchEnv = process.env[\"HINDSIGHT_RECALL_TAGS_MATCH\"];\n if (recallTagsMatchEnv !== undefined) {\n config[\"recallTagsMatch\"] = recallTagsMatchEnv;\n }\n\n const retainTagsEnv = process.env[\"HINDSIGHT_RETAIN_TAGS\"];\n if (retainTagsEnv !== undefined) {\n config[\"retainTags\"] = retainTagsEnv\n .split(\",\")\n .map((t) => t.trim())\n .filter(Boolean);\n }\n\n const additionalBanksEnv = process.env[\"HINDSIGHT_RECALL_ADDITIONAL_BANKS\"];\n if (additionalBanksEnv !== undefined) {\n config[\"recallAdditionalBanks\"] = additionalBanksEnv\n .split(\",\")\n .map((t) => t.trim())\n .filter(Boolean);\n }\n\n const result = config as unknown as HindsightConfig;\n\n if (!Array.isArray(result.recallAdditionalBanks)) {\n result.recallAdditionalBanks = [];\n }\n if (!result.directoryBankMap || typeof result.directoryBankMap !== \"object\") {\n result.directoryBankMap = {};\n }\n\n // Validate enum-like fields to catch typos early\n // \"chunked\" is Claude Code's name for last-turn sliding-window retain\n if (result.retainMode === \"chunked\") {\n result.retainMode = \"last-turn\";\n }\n const VALID_RETAIN_MODES = [\"full-session\", \"last-turn\"];\n if (!VALID_RETAIN_MODES.includes(result.retainMode)) {\n console.error(\n `[Hindsight] Unknown retainMode \"${result.retainMode}\" — ` +\n `valid: ${VALID_RETAIN_MODES.join(\", \")}, chunked. Falling back to \"full-session\".`\n );\n result.retainMode = \"full-session\";\n }\n\n const VALID_TAGS_MATCH = [\"any\", \"all\", \"any_strict\", \"all_strict\"];\n if (!VALID_TAGS_MATCH.includes(result.recallTagsMatch)) {\n console.error(\n `[Hindsight] Unknown recallTagsMatch \"${result.recallTagsMatch}\" — ` +\n `valid: ${VALID_TAGS_MATCH.join(\", \")}. Falling back to \"any\".`\n );\n result.recallTagsMatch = \"any\";\n }\n\n const VALID_BUDGETS = [\"low\", \"mid\", \"high\"];\n if (!VALID_BUDGETS.includes(result.recallBudget)) {\n console.error(\n `[Hindsight] Unknown recallBudget \"${result.recallBudget}\" — ` +\n `valid: ${VALID_BUDGETS.join(\", \")}. Falling back to \"mid\".`\n );\n result.recallBudget = \"mid\";\n }\n\n const VALID_INJECT_MODES = [\"system\", \"synthetic-user\"];\n if (!VALID_INJECT_MODES.includes(result.recallInjectMode)) {\n console.error(\n `[Hindsight] Unknown recallInjectMode \"${result.recallInjectMode}\" — ` +\n `valid: ${VALID_INJECT_MODES.join(\", \")}. Falling back to \"synthetic-user\".`\n );\n result.recallInjectMode = \"synthetic-user\";\n }\n\n return result;\n}\n","/**\n * Bank ID derivation and mission management.\n *\n * Port of Claude Code plugin's bank.py, adapted for OpenCode's context model.\n *\n * Dimensions for dynamic bank IDs:\n * - agent → configured name or \"opencode\"\n * - project → derived from the working directory basename\n * - gitProject → derived from the main worktree's basename when inside a\n * git repository (so all linked worktrees of the same repo\n * share a single memory bank). Falls back to the working\n * directory basename when git is unavailable or the\n * directory is not a repo.\n */\n\nimport { basename, dirname, resolve } from \"node:path\";\nimport { realpathSync } from \"node:fs\";\nimport { execFileSync } from \"node:child_process\";\nimport type { HindsightConfig } from \"./config.js\";\nimport { Logger } from \"./logger.js\";\nimport type { HindsightClient } from \"@vectorize-io/hindsight-client\";\n\nconst DEFAULT_BANK_NAME = \"opencode\";\nconst VALID_FIELDS = new Set([\"agent\", \"project\", \"gitProject\", \"channel\", \"user\"]);\n\nfunction safeRealpath(path: string): string {\n try {\n return realpathSync(path);\n } catch {\n return resolve(path);\n }\n}\n\n/** Main worktree root via git-common-dir, or null. */\nfunction getProjectRootFromGit(directory: string): string | null {\n if (!directory) return null;\n try {\n const commonDir = execFileSync(\n \"git\",\n [\"rev-parse\", \"--path-format=absolute\", \"--git-common-dir\"],\n {\n cwd: directory,\n encoding: \"utf-8\",\n stdio: [\"ignore\", \"pipe\", \"ignore\"],\n timeout: 1000,\n }\n ).trim();\n if (!commonDir) return null;\n if (basename(commonDir) === \".git\") {\n return dirname(commonDir);\n }\n return commonDir;\n } catch {\n return null;\n }\n}\n\nfunction deriveGitProjectName(directory: string, resolveWorktrees: boolean): string {\n if (resolveWorktrees) {\n const projectRoot = getProjectRootFromGit(directory);\n if (projectRoot) return basename(projectRoot);\n }\n return directory ? basename(directory) : \"unknown\";\n}\n\n/** Claude directoryBankMap: exact realpath match on cwd (or main worktree root). */\nfunction bankFromDirectoryMap(\n directory: string,\n dirMap: Record<string, string>,\n resolveWorktrees: boolean\n): string | null {\n if (!directory || !dirMap || !Object.keys(dirMap).length) return null;\n\n const candidates = new Set<string>([safeRealpath(directory)]);\n if (resolveWorktrees) {\n const root = getProjectRootFromGit(directory);\n if (root) candidates.add(safeRealpath(root));\n }\n\n for (const [dirPath, bankId] of Object.entries(dirMap)) {\n if (!dirPath || !bankId) continue;\n const mapped = safeRealpath(dirPath);\n if (candidates.has(mapped)) return bankId;\n }\n return null;\n}\n\n/**\n * Derive a bank ID from context and config.\n *\n * Order (Claude bank.py): directoryBankMap → static bankId → dynamic fields.\n */\nexport function deriveBankId(config: HindsightConfig, directory: string): string {\n const prefix = config.bankIdPrefix;\n const resolveWorktrees = config.resolveWorktrees !== false;\n\n const mapped = bankFromDirectoryMap(directory, config.directoryBankMap, resolveWorktrees);\n if (mapped) {\n return prefix ? `${prefix}-${mapped}` : mapped;\n }\n\n if (!config.dynamicBankId) {\n const base = config.bankId || DEFAULT_BANK_NAME;\n return prefix ? `${prefix}-${base}` : base;\n }\n\n const fields = config.dynamicBankGranularity?.length\n ? config.dynamicBankGranularity\n : [\"gitProject\"];\n\n for (const f of fields) {\n if (!VALID_FIELDS.has(f)) {\n console.error(\n `[Hindsight] Unknown dynamicBankGranularity field \"${f}\" — ` +\n `valid: ${[...VALID_FIELDS].sort().join(\", \")}`\n );\n }\n }\n\n const channelId = process.env.HINDSIGHT_CHANNEL_ID || \"\";\n const userId = process.env.HINDSIGHT_USER_ID || \"\";\n\n const fieldResolvers: Record<string, () => string> = {\n agent: () => config.agentName || \"opencode\",\n project: () => (directory ? basename(directory) : \"unknown\"),\n gitProject: () => deriveGitProjectName(directory, resolveWorktrees),\n channel: () => channelId || \"default\",\n user: () => userId || \"anonymous\",\n };\n\n const segments = fields.map((f) => fieldResolvers[f]?.() || \"unknown\");\n const baseBankId = segments.join(\"::\");\n\n return prefix ? `${prefix}-${baseBankId}` : baseBankId;\n}\n\n/**\n * Set bank mission on first use, skip if already set.\n * Uses an in-memory Set (plugin is long-lived, unlike Claude Code's ephemeral hooks).\n */\nexport async function ensureBankMission(\n client: HindsightClient,\n bankId: string,\n config: HindsightConfig,\n missionsSet: Set<string>,\n logger: Logger = new Logger({ silent: true })\n): Promise<void> {\n const mission = config.bankMission;\n if (!mission?.trim()) return;\n if (missionsSet.has(bankId)) return;\n\n try {\n await client.createBank(bankId, {\n reflectMission: mission,\n retainMission: config.retainMission || undefined,\n });\n missionsSet.add(bankId);\n // Cap tracked banks\n if (missionsSet.size > 10000) {\n const keys = [...missionsSet].sort();\n for (const k of keys.slice(0, keys.length >> 1)) {\n missionsSet.delete(k);\n }\n }\n logger.debug(`Set mission for bank: ${bankId}`);\n } catch (e) {\n // Don't fail if mission set fails — bank may not exist yet\n logger.debug(`Could not set bank mission for ${bankId}`, { error: String(e) });\n }\n}\n","/**\n * Lightweight logger for the Hindsight OpenCode plugin.\n *\n * Routes through OpenCode's server log API (`client.app.log`) when available so\n * messages land in OpenCode's own log stream — visible via `--print-logs` and\n * the OpenCode log files — without writing to stdout/stderr and corrupting the\n * TUI. Falls back to `console.error` when the OpenCode client is unavailable\n * (e.g. degraded load); unit tests use a silent logger.\n *\n * Levels:\n * - error / warn / info → always emitted, so failures and the resolved\n * endpoint are visible WITHOUT any debug opt-in.\n * - debug → emitted only when `debug` is enabled in config.\n */\n\nexport type LogLevel = \"debug\" | \"info\" | \"warn\" | \"error\";\n\n/** Minimal shape of OpenCode's `client.app.log` that we depend on. */\nexport interface OpencodeLogClient {\n app?: {\n log?: (options: {\n body: {\n service: string;\n level: LogLevel;\n message: string;\n extra?: Record<string, unknown>;\n };\n }) => unknown;\n };\n}\n\nexport interface LoggerOptions {\n /** OpenCode client used to write into the server log stream. */\n client?: OpencodeLogClient;\n /** When true, `debug()` messages are emitted. */\n debug?: boolean;\n /** When true, the logger emits nothing (used as a safe default in tests). */\n silent?: boolean;\n}\n\nconst SERVICE = \"hindsight\";\n\nexport class Logger {\n private readonly client?: OpencodeLogClient;\n private readonly debugEnabled: boolean;\n private readonly silent: boolean;\n\n constructor(options: LoggerOptions = {}) {\n this.client = options.client;\n this.debugEnabled = options.debug ?? false;\n this.silent = options.silent ?? false;\n }\n\n private emit(level: LogLevel, message: string, extra?: Record<string, unknown>): void {\n if (this.silent) return;\n\n const app = this.client?.app;\n if (app && typeof app.log === \"function\") {\n try {\n // IMPORTANT: call as a method on `app` so `this` is preserved.\n // OpenCode's `app.log` is a class method that uses `this` internally;\n // calling a detached reference (`const log = app.log; log(...)`) throws\n // `this._client is undefined`, which would otherwise be swallowed below\n // and produce no log at all.\n const result = app.log({ body: { service: SERVICE, level, message, extra } });\n // Fire-and-forget: a logging failure must never surface to OpenCode.\n if (result && typeof (result as Promise<unknown>).then === \"function\") {\n (result as Promise<unknown>).then(undefined, () => {});\n }\n return;\n } catch {\n // Fall through to the console fallback if app.log throws synchronously.\n }\n }\n\n // Fallback when no OpenCode client is available (or app.log failed).\n // OpenCode captures plugin console output into its logs, so this stays\n // visible and TUI-safe.\n const line = extra\n ? `[Hindsight] ${message} ${JSON.stringify(extra)}`\n : `[Hindsight] ${message}`;\n console.error(line);\n }\n\n error(message: string, error?: unknown): void {\n this.emit(\"error\", message, error === undefined ? undefined : { error: errorToString(error) });\n }\n\n warn(message: string, extra?: Record<string, unknown>): void {\n this.emit(\"warn\", message, extra);\n }\n\n info(message: string, extra?: Record<string, unknown>): void {\n this.emit(\"info\", message, extra);\n }\n\n debug(message: string, extra?: Record<string, unknown>): void {\n if (this.debugEnabled) this.emit(\"debug\", message, extra);\n }\n}\n\nfunction errorToString(error: unknown): string {\n if (error instanceof Error) return error.stack || `${error.name}: ${error.message}`;\n return String(error);\n}\n","/**\n * Custom tool definitions for the Hindsight OpenCode plugin.\n *\n * Registers hindsight_retain, hindsight_recall, and hindsight_reflect\n * as tools the agent can call explicitly.\n */\n\nimport { readFileSync } from \"node:fs\";\nimport { basename } from \"node:path\";\nimport { tool } from \"@opencode-ai/plugin/tool\";\nimport type { ToolDefinition } from \"@opencode-ai/plugin/tool\";\nimport type { HindsightClient } from \"@vectorize-io/hindsight-client\";\nimport type { HindsightConfig } from \"./config.js\";\nimport {\n formatMemories,\n formatCurrentTime,\n buildRetainTemplateVars,\n resolveRetainTags,\n resolveRetainMetadata,\n sanitizeForRetain,\n} from \"./content.js\";\nimport { ensureBankMission } from \"./bank.js\";\nimport { createPageTools } from \"./pages.js\";\nimport { Logger } from \"./logger.js\";\n\nexport interface HindsightTools {\n hindsight_retain: ToolDefinition;\n hindsight_recall: ToolDefinition;\n hindsight_reflect: ToolDefinition;\n hindsight_bank_current: ToolDefinition;\n hindsight_ingest: ToolDefinition;\n hindsight_ingest_file: ToolDefinition;\n // Index signature so the object is assignable to OpenCode's Hooks.tool\n // (Record<string, ToolDefinition>) without losing the specific keys above.\n [key: string]: ToolDefinition;\n}\n\nfunction toDocumentId(title: string): string {\n return title\n .trim()\n .toLowerCase()\n .replace(/\\s+/g, \"-\")\n .replace(/[^a-z0-9._-]/g, \"\")\n .slice(0, 200) || \"document\";\n}\n\nexport function createTools(\n client: HindsightClient,\n bankId: string,\n config: HindsightConfig,\n missionsSet?: Set<string>,\n logger: Logger = new Logger({ silent: true })\n): HindsightTools {\n const hindsight_retain = tool({\n description:\n \"Store information in long-term memory. Use this to remember important facts, \" +\n \"user preferences, project context, decisions, and anything worth recalling in future sessions. \" +\n \"Be specific — include who, what, when, and why.\",\n args: {\n content: tool.schema\n .string()\n .describe(\"The information to remember. Be specific and self-contained.\"),\n context: tool.schema\n .string()\n .optional()\n .describe(\"Optional context about where this information came from.\"),\n },\n async execute(args, ctx) {\n if (missionsSet) {\n await ensureBankMission(client, bankId, config, missionsSet, logger);\n }\n const sessionId =\n ctx && typeof ctx === \"object\" && typeof (ctx as { sessionID?: unknown }).sessionID === \"string\"\n ? (ctx as { sessionID: string }).sessionID\n : \"\";\n const vars = buildRetainTemplateVars({ sessionId, bankId });\n const tags = resolveRetainTags(config.retainTags, vars);\n const metadata = resolveRetainMetadata(config.retainMetadata, vars);\n await client.retain(bankId, args.content, {\n context: args.context || config.retainContext,\n tags,\n metadata,\n });\n return \"Memory stored successfully.\";\n },\n });\n\n const hindsight_recall = tool({\n description:\n \"Search long-term memory for relevant information. Use this proactively before \" +\n \"answering questions about past conversations, user preferences, project history, \" +\n \"or any topic where prior context would help. When in doubt, recall first.\",\n args: {\n query: tool.schema\n .string()\n .describe(\"Natural language search query. Be specific about what you need to know.\"),\n },\n async execute(args) {\n const recallOpts = {\n budget: config.recallBudget as \"low\" | \"mid\" | \"high\",\n maxTokens: config.recallMaxTokens,\n types: config.recallTypes,\n tags: config.recallTags.length ? config.recallTags : undefined,\n tagsMatch: config.recallTags.length ? config.recallTagsMatch : undefined,\n };\n const response = await client.recall(bankId, args.query, recallOpts);\n let results = response.results || [];\n for (const extraBank of config.recallAdditionalBanks) {\n if (!extraBank || extraBank === bankId) continue;\n try {\n const extra = await client.recall(extraBank, args.query, recallOpts);\n results = results.concat(extra.results || []);\n } catch {\n /* optional bank */\n }\n }\n if (!results.length) return \"No relevant memories found.\";\n\n const formatted = formatMemories(results);\n return `Found ${results.length} relevant memories (as of ${formatCurrentTime()} UTC):\\n\\n${formatted}`;\n },\n });\n\n const hindsight_reflect = tool({\n description:\n \"Generate a thoughtful answer using long-term memory. Unlike recall (which returns \" +\n \"raw memories), reflect synthesizes memories into a coherent answer. Use for questions \" +\n 'like \"What do you know about this user?\" or \"Summarize our project decisions.\"',\n args: {\n query: tool.schema.string().describe(\"The question to answer using long-term memory.\"),\n context: tool.schema\n .string()\n .optional()\n .describe(\"Optional additional context to guide the reflection.\"),\n },\n async execute(args) {\n if (missionsSet) {\n await ensureBankMission(client, bankId, config, missionsSet, logger);\n }\n const response = await client.reflect(bankId, args.query, {\n context: args.context,\n budget: config.recallBudget as \"low\" | \"mid\" | \"high\",\n });\n\n return response.text || \"No relevant information found to reflect on.\";\n },\n });\n\n const hindsight_bank_current = tool({\n description:\n \"Get the current Hindsight memory bank ID this session is bound to \" +\n \"(Claude agent_knowledge_get_current_bank).\",\n args: {},\n async execute() {\n return `Current memory bank: ${bankId}`;\n },\n });\n\n const hindsight_ingest = tool({\n description:\n \"Upload text content into the memory bank as a named document \" +\n \"(Claude agent_knowledge_ingest). Pass full raw content — do not summarize first. \" +\n \"Re-ingesting the same title replaces the document.\",\n args: {\n title: tool.schema.string().describe(\"Document title; becomes the document ID.\"),\n content: tool.schema.string().describe(\"Full raw text content to store.\"),\n },\n async execute(args) {\n if (missionsSet) {\n await ensureBankMission(client, bankId, config, missionsSet, logger);\n }\n const cleaned = sanitizeForRetain(args.content);\n if (!cleaned.trim()) return \"Error: content is empty after sanitization.\";\n const documentId = toDocumentId(args.title);\n await client.retain(bankId, cleaned, {\n documentId,\n context: config.retainContext,\n });\n return `Ingested document \"${documentId}\" into bank ${bankId}.`;\n },\n });\n\n const hindsight_ingest_file = tool({\n description:\n \"Ingest a file from disk into the memory bank \" +\n \"(Claude agent_knowledge_ingest_file). Filename (without extension) becomes the document ID.\",\n args: {\n file_path: tool.schema.string().describe(\"Absolute or relative path to a UTF-8 text file.\"),\n },\n async execute(args) {\n if (missionsSet) {\n await ensureBankMission(client, bankId, config, missionsSet, logger);\n }\n let raw: string;\n try {\n raw = readFileSync(args.file_path, \"utf-8\");\n } catch (e) {\n return `Error: cannot read file: ${e instanceof Error ? e.message : String(e)}`;\n }\n const cleaned = sanitizeForRetain(raw);\n if (!cleaned.trim()) return `Error: file is empty: ${args.file_path}`;\n const base = basename(args.file_path).replace(/\\.[^.]+$/, \"\");\n const documentId = toDocumentId(base);\n await client.retain(bankId, cleaned, {\n documentId,\n context: config.retainContext,\n });\n return `Ingested file \"${args.file_path}\" as document \"${documentId}\" into bank ${bankId}.`;\n },\n });\n\n const base: HindsightTools = {\n hindsight_retain,\n hindsight_recall,\n hindsight_reflect,\n hindsight_bank_current,\n hindsight_ingest,\n hindsight_ingest_file,\n };\n\n if (!config.enableKnowledgePages) {\n return base;\n }\n\n return {\n ...base,\n ...createPageTools(client, bankId, config, missionsSet, logger),\n };\n}\n","/**\n * Content processing utilities.\n *\n * Port of the Claude Code plugin's content.py:\n * - Memory tag stripping (anti-feedback-loop)\n * - Recall query composition and truncation\n * - Memory formatting for context injection\n * - Retention transcript formatting\n */\n\n/** Strip <hindsight_memories> and <relevant_memories> blocks to prevent retain feedback loops. */\nexport function stripMemoryTags(content: string): string {\n content = content.replace(/<hindsight_memories>[\\s\\S]*?<\\/hindsight_memories>/g, \"\");\n content = content.replace(/<relevant_memories>[\\s\\S]*?<\\/relevant_memories>/g, \"\");\n return content;\n}\n\n/**\n * Fold a fresh `<hindsight_memories>` block into a system section.\n * Replaces any previous block so per-turn recall does not stack forever.\n */\nexport function injectHindsightMemories(systemSection: string, context: string): string {\n const base = stripMemoryTags(systemSection)\n .replace(/\\n{3,}/g, \"\\n\\n\")\n .trimEnd();\n return base ? `${base}\\n\\n${context}` : context;\n}\n\nexport interface RecallResult {\n text: string;\n type?: string | null;\n mentioned_at?: string | null;\n}\n\n/** Format recall results into human-readable text for context injection. */\nexport function formatMemories(results: RecallResult[]): string {\n if (!results.length) return \"\";\n return results\n .map((r) => {\n const typeStr = r.type ? ` [${r.type}]` : \"\";\n const dateStr = r.mentioned_at ? ` (${r.mentioned_at})` : \"\";\n return `- ${r.text}${typeStr}${dateStr}`;\n })\n .join(\"\\n\\n\");\n}\n\n/** Format current UTC time for recall context. */\nexport function formatCurrentTime(): string {\n const now = new Date();\n const y = now.getUTCFullYear();\n const m = String(now.getUTCMonth() + 1).padStart(2, \"0\");\n const d = String(now.getUTCDate()).padStart(2, \"0\");\n const h = String(now.getUTCHours()).padStart(2, \"0\");\n const min = String(now.getUTCMinutes()).padStart(2, \"0\");\n return `${y}-${m}-${d} ${h}:${min}`;\n}\n\nexport interface Message {\n role: string;\n content: string;\n}\n\n/** Minimal OpenCode tool-part shape used when retainToolCalls is enabled. */\nexport interface ToolPartLike {\n type: string;\n tool?: string;\n state?: {\n status?: string;\n input?: Record<string, unknown>;\n output?: string;\n error?: string;\n title?: string;\n };\n}\n\nconst TOOL_INPUT_MAX = 500;\nconst TOOL_OUTPUT_MAX = 1000;\n\nfunction truncate(text: string, max: number): string {\n if (text.length <= max) return text;\n return `${text.slice(0, max)}…`;\n}\n\n/** True for hindsight operational tools — skip to avoid retain feedback loops. */\nexport function isHindsightOperationalTool(name: string): boolean {\n const lower = name.toLowerCase();\n return (\n lower.startsWith(\"hindsight_\") ||\n lower.startsWith(\"hindsight_page_\") ||\n /(?:^|__)(?:agent_knowledge_|knowledge_)/.test(lower)\n );\n}\n\n/**\n * Format an OpenCode tool part for retention transcripts.\n * Returns null when the part should be skipped.\n */\nexport function formatToolPartForRetain(part: ToolPartLike): string | null {\n if (part.type !== \"tool\") return null;\n const name = (part.tool || \"unknown\").trim();\n if (!name || isHindsightOperationalTool(name)) return null;\n\n const state = part.state;\n const input = state?.input ?? {};\n let inputJson: string;\n try {\n inputJson = JSON.stringify(input);\n } catch {\n inputJson = String(input);\n }\n inputJson = truncate(inputJson, TOOL_INPUT_MAX);\n\n const lines = [`[tool_call: ${name}]`, `input: ${inputJson}`];\n if (state?.title) lines.push(`title: ${state.title}`);\n\n if (state?.status === \"completed\" && typeof state.output === \"string\" && state.output) {\n lines.push(`output: ${truncate(state.output, TOOL_OUTPUT_MAX)}`);\n } else if (state?.status === \"error\" && state.error) {\n lines.push(`error: ${truncate(state.error, TOOL_OUTPUT_MAX)}`);\n } else if (state?.status) {\n lines.push(`status: ${state.status}`);\n }\n\n lines.push(\"[tool_call:end]\");\n return lines.join(\"\\n\");\n}\n\n/**\n * Build message content from OpenCode parts (text + optional tool parts).\n */\nexport function buildMessageContent(\n parts: ReadonlyArray<{ type: string; text?: string } & Partial<ToolPartLike>>,\n includeToolCalls: boolean\n): string {\n const chunks: string[] = [];\n for (const part of parts) {\n if (part.type === \"text\" && part.text) {\n chunks.push(part.text);\n continue;\n }\n if (includeToolCalls && part.type === \"tool\") {\n const formatted = formatToolPartForRetain(part);\n if (formatted) chunks.push(formatted);\n }\n }\n return chunks.join(\"\\n\\n\");\n}\n\n/**\n * Compose a multi-turn recall query from conversation history.\n *\n * When recallContextTurns > 1, includes prior context above the latest query.\n */\nexport function composeRecallQuery(\n latestQuery: string,\n messages: Message[],\n recallContextTurns: number\n): string {\n const latest = latestQuery.trim();\n if (recallContextTurns <= 1 || !messages.length) return latest;\n\n const contextual = sliceLastTurnsByUserBoundary(messages, recallContextTurns);\n const contextLines: string[] = [];\n\n for (const msg of contextual) {\n const content = stripMemoryTags(msg.content).trim();\n if (!content) continue;\n if (msg.role === \"user\" && content === latest) continue;\n contextLines.push(`${msg.role}: ${content}`);\n }\n\n if (!contextLines.length) return latest;\n\n return [\"Prior context:\", contextLines.join(\"\\n\"), latest].join(\"\\n\\n\");\n}\n\n/**\n * Truncate a composed recall query to maxChars.\n * Preserves the latest user message, drops oldest context lines first.\n */\nexport function truncateRecallQuery(query: string, latestQuery: string, maxChars: number): string {\n if (maxChars <= 0 || query.length <= maxChars) return query;\n\n const latest = latestQuery.trim();\n const latestOnly = latest.length > maxChars ? latest.slice(0, maxChars) : latest;\n\n if (!query.includes(\"Prior context:\")) return latestOnly;\n\n const contextMarker = \"Prior context:\\n\\n\";\n const markerIndex = query.indexOf(contextMarker);\n if (markerIndex === -1) return latestOnly;\n\n const suffix = \"\\n\\n\" + latest;\n const suffixIndex = query.lastIndexOf(suffix);\n if (suffixIndex === -1) return latestOnly;\n if (suffix.length >= maxChars) return latestOnly;\n\n const contextBody = query.slice(markerIndex + contextMarker.length, suffixIndex);\n const contextLines = contextBody.split(\"\\n\").filter(Boolean);\n\n const kept: string[] = [];\n for (let i = contextLines.length - 1; i >= 0; i--) {\n kept.unshift(contextLines[i]);\n const candidate = `${contextMarker}${kept.join(\"\\n\")}${suffix}`;\n if (candidate.length > maxChars) {\n kept.shift();\n break;\n }\n }\n\n if (kept.length) return `${contextMarker}${kept.join(\"\\n\")}${suffix}`;\n return latestOnly;\n}\n\n/** Slice messages to the last N turns, where a turn starts at a user message. */\nexport function sliceLastTurnsByUserBoundary(messages: Message[], turns: number): Message[] {\n if (!messages.length || turns <= 0) return [];\n\n let userTurnsSeen = 0;\n let startIndex = -1;\n\n for (let i = messages.length - 1; i >= 0; i--) {\n if (messages[i].role === \"user\") {\n userTurnsSeen++;\n if (userTurnsSeen >= turns) {\n startIndex = i;\n break;\n }\n }\n }\n\n return startIndex === -1 ? [...messages] : messages.slice(startIndex);\n}\n\nexport interface RetainTemplateVars {\n session_id: string;\n bank_id: string;\n timestamp: string;\n user_id: string;\n}\n\nexport interface ResolveRetainTemplatesInput {\n sessionId: string;\n bankId: string;\n messageCount?: number;\n userId?: string;\n now?: Date;\n}\n\n/** Build template vars for retain tags/metadata (Claude Code retain.py). */\nexport function buildRetainTemplateVars(input: ResolveRetainTemplatesInput): RetainTemplateVars {\n const now = input.now ?? new Date();\n const timestamp = now.toISOString().replace(/\\.\\d{3}Z$/, \"Z\");\n return {\n session_id: input.sessionId,\n bank_id: input.bankId,\n timestamp,\n user_id: input.userId ?? process.env.HINDSIGHT_USER_ID ?? \"\",\n };\n}\n\nexport function applyTemplateString(value: string, vars: RetainTemplateVars): string {\n let out = value;\n for (const [key, val] of Object.entries(vars) as Array<[keyof RetainTemplateVars, string]>) {\n out = out.split(`{${key}}`).join(val);\n }\n return out;\n}\n\n/**\n * Resolve retainTags with template vars.\n * Drops empty tags and tags whose value after `:` is empty (e.g. `user:` when user_id unset).\n */\nexport function resolveRetainTags(\n rawTags: readonly string[],\n vars: RetainTemplateVars\n): string[] | undefined {\n if (!rawTags.length) return undefined;\n const tags: string[] = [];\n for (const original of rawTags) {\n const resolved = applyTemplateString(original, vars).trim();\n if (!resolved) continue;\n const colon = resolved.indexOf(\":\");\n if (colon !== -1 && resolved.slice(colon + 1) === \"\") continue;\n tags.push(resolved);\n }\n return tags.length ? tags : undefined;\n}\n\n/** Resolve retainMetadata templates and merge session defaults. */\nexport function resolveRetainMetadata(\n rawMetadata: Record<string, string>,\n vars: RetainTemplateVars,\n messageCount?: number\n): Record<string, string> {\n const metadata: Record<string, string> = {\n retained_at: vars.timestamp,\n session_id: vars.session_id,\n };\n if (messageCount !== undefined) {\n metadata.message_count = String(messageCount);\n }\n for (const [key, value] of Object.entries(rawMetadata)) {\n metadata[key] = applyTemplateString(String(value), vars);\n }\n return metadata;\n}\n\n/**\n * Format messages into a retention transcript.\n *\n * Uses [role: ...]...[role:end] markers for structured retention.\n */\n/**\n * Strip characters that break Hindsight/Postgres text storage (e.g. U+0000).\n * Keeps tab/newline/carriage-return; drops other C0 controls.\n */\nexport function sanitizeForRetain(text: string): string {\n // eslint-disable-next-line no-control-regex -- intentional C0 strip for DB safety\n return text.replace(/[\\u0000-\\u0008\\u000B\\u000C\\u000E-\\u001F]/g, \"\");\n}\n\nexport function prepareRetentionTranscript(\n messages: Message[],\n retainFullWindow: boolean = false\n): { transcript: string | null; messageCount: number } {\n if (!messages.length) return { transcript: null, messageCount: 0 };\n\n let targetMessages: Message[];\n if (retainFullWindow) {\n targetMessages = messages;\n } else {\n // Default: retain only the last turn\n let lastUserIdx = -1;\n for (let i = messages.length - 1; i >= 0; i--) {\n if (messages[i].role === \"user\") {\n lastUserIdx = i;\n break;\n }\n }\n if (lastUserIdx === -1) return { transcript: null, messageCount: 0 };\n targetMessages = messages.slice(lastUserIdx);\n }\n\n const parts: string[] = [];\n for (const msg of targetMessages) {\n const content = sanitizeForRetain(stripMemoryTags(msg.content)).trim();\n if (!content) continue;\n parts.push(`[role: ${msg.role}]\\n${content}\\n[${msg.role}:end]`);\n }\n\n if (!parts.length) return { transcript: null, messageCount: 0 };\n\n const transcript = parts.join(\"\\n\\n\");\n if (transcript.trim().length < 10) return { transcript: null, messageCount: 0 };\n\n return { transcript, messageCount: parts.length };\n}\n","/**\n * Knowledge pages tools — OpenCode wrappers around Hindsight mental-models.\n * Claude Code exposes these as agent_knowledge_* MCP tools.\n */\n\nimport { tool } from \"@opencode-ai/plugin/tool\";\nimport type { ToolDefinition } from \"@opencode-ai/plugin/tool\";\nimport type { HindsightClient } from \"@vectorize-io/hindsight-client\";\nimport type { HindsightConfig } from \"./config.js\";\nimport { ensureBankMission } from \"./bank.js\";\nimport { Logger } from \"./logger.js\";\n\nexport interface PageTools {\n hindsight_page_list: ToolDefinition;\n hindsight_page_get: ToolDefinition;\n hindsight_page_create: ToolDefinition;\n hindsight_page_update: ToolDefinition;\n hindsight_page_delete: ToolDefinition;\n hindsight_page_refresh: ToolDefinition;\n [key: string]: ToolDefinition;\n}\n\nfunction asItems(response: unknown): Array<Record<string, unknown>> {\n if (!response || typeof response !== \"object\") return [];\n const items = (response as { items?: unknown }).items;\n if (!Array.isArray(items)) return [];\n return items.filter((x): x is Record<string, unknown> => !!x && typeof x === \"object\");\n}\n\nfunction formatPageSummary(page: Record<string, unknown>): string {\n const id = String(page.id ?? page.mental_model_id ?? \"?\");\n const name = String(page.name ?? \"(unnamed)\");\n const query = page.source_query ? String(page.source_query) : \"\";\n return query ? `- ${id}: ${name}\\n source_query: ${query}` : `- ${id}: ${name}`;\n}\n\nfunction formatPageDetail(page: Record<string, unknown>): string {\n const id = String(page.id ?? page.mental_model_id ?? \"?\");\n const name = String(page.name ?? \"(unnamed)\");\n const query = page.source_query != null ? String(page.source_query) : \"\";\n const content = page.content != null ? String(page.content) : \"(no content yet)\";\n const lines = [\n `id: ${id}`,\n `name: ${name}`,\n query ? `source_query: ${query}` : null,\n \"\",\n content,\n ].filter((line): line is string => line !== null);\n return lines.join(\"\\n\");\n}\n\nexport function createPageTools(\n client: HindsightClient,\n bankId: string,\n config: HindsightConfig,\n missionsSet?: Set<string>,\n logger: Logger = new Logger({ silent: true })\n): PageTools {\n const ensureMission = async () => {\n if (missionsSet) {\n await ensureBankMission(client, bankId, config, missionsSet, logger);\n }\n };\n\n const hindsight_page_list = tool({\n description:\n \"List knowledge pages (IDs and names) in the current Hindsight bank. \" +\n \"Pages are synthesized long-lived docs rebuilt from memory via a source_query. \" +\n \"Use hindsight_page_get to read full content.\",\n args: {},\n async execute() {\n await ensureMission();\n const response = await client.listMentalModels(bankId);\n const items = asItems(response);\n if (!items.length) return \"No knowledge pages in this bank.\";\n return `Knowledge pages (${items.length}) in bank \"${bankId}\":\\n\\n${items\n .map(formatPageSummary)\n .join(\"\\n\")}`;\n },\n });\n\n const hindsight_page_get = tool({\n description:\n \"Read a knowledge page by id. Returns full synthesized content when available.\",\n args: {\n page_id: tool.schema.string().describe(\"Knowledge page / mental-model id.\"),\n },\n async execute(args) {\n await ensureMission();\n const page = await client.getMentalModel(bankId, args.page_id);\n if (!page || typeof page !== \"object\") return `Page not found: ${args.page_id}`;\n return formatPageDetail(page as Record<string, unknown>);\n },\n });\n\n const hindsight_page_create = tool({\n description:\n \"Create a knowledge page. source_query is re-asked after consolidations to rebuild \" +\n \"the page from observations (e.g. 'What are the key architecture decisions?').\",\n args: {\n name: tool.schema.string().describe(\"Human-readable page title.\"),\n source_query: tool.schema\n .string()\n .describe(\"Question used to synthesize/refresh page content from memories.\"),\n page_id: tool.schema\n .string()\n .optional()\n .describe(\"Optional stable page id. Server generates one if omitted.\"),\n },\n async execute(args) {\n await ensureMission();\n const created = await client.createMentalModel(bankId, args.name, args.source_query, {\n id: args.page_id,\n trigger: { refreshAfterConsolidation: true },\n });\n const id =\n created && typeof created === \"object\"\n ? String(\n (created as { mental_model_id?: string; id?: string }).mental_model_id ??\n (created as { id?: string }).id ??\n args.page_id ??\n \"?\"\n )\n : String(args.page_id ?? \"?\");\n return `Knowledge page created.\\nid: ${id}\\nname: ${args.name}\\nsource_query: ${args.source_query}\\nContent generates in the background; use hindsight_page_get or hindsight_page_refresh shortly.`;\n },\n });\n\n const hindsight_page_update = tool({\n description: \"Update a knowledge page name and/or source_query.\",\n args: {\n page_id: tool.schema.string().describe(\"Knowledge page id.\"),\n name: tool.schema.string().optional().describe(\"New title.\"),\n source_query: tool.schema.string().optional().describe(\"New synthesis question.\"),\n },\n async execute(args) {\n if (!args.name && !args.source_query) {\n return \"Nothing to update: provide name and/or source_query.\";\n }\n await ensureMission();\n await client.updateMentalModel(bankId, args.page_id, {\n name: args.name,\n sourceQuery: args.source_query,\n });\n return `Knowledge page updated: ${args.page_id}`;\n },\n });\n\n const hindsight_page_delete = tool({\n description: \"Permanently delete a knowledge page.\",\n args: {\n page_id: tool.schema.string().describe(\"Knowledge page id to delete.\"),\n },\n async execute(args) {\n await ensureMission();\n await client.deleteMentalModel(bankId, args.page_id);\n return `Knowledge page deleted: ${args.page_id}`;\n },\n });\n\n const hindsight_page_refresh = tool({\n description:\n \"Refresh a knowledge page now by re-running its source_query against current memories.\",\n args: {\n page_id: tool.schema.string().describe(\"Knowledge page id to refresh.\"),\n },\n async execute(args) {\n await ensureMission();\n const refreshed = await client.refreshMentalModel(bankId, args.page_id);\n if (refreshed && typeof refreshed === \"object\" && \"content\" in refreshed) {\n return formatPageDetail(refreshed as Record<string, unknown>);\n }\n return `Refresh started for page ${args.page_id}. Use hindsight_page_get to read content when ready.`;\n },\n });\n\n return {\n hindsight_page_list,\n hindsight_page_get,\n hindsight_page_create,\n hindsight_page_update,\n hindsight_page_delete,\n hindsight_page_refresh,\n };\n}\n","/**\n * Hook implementations for the Hindsight OpenCode plugin.\n *\n * Aligned with Claude Code hindsight-memory:\n * - experimental.chat.system.transform → per-user-turn auto-recall\n * - event (session.idle) → auto-retain after the assistant finishes\n * - event (session.deleted) + dispose → force-retain pending turns (SessionEnd)\n * - experimental.session.compacting → retain + inject for compaction\n */\n\nimport type { HindsightClient } from \"@vectorize-io/hindsight-client\";\nimport type { HindsightConfig } from \"./config.js\";\nimport { Logger } from \"./logger.js\";\nimport {\n formatMemories,\n formatCurrentTime,\n composeRecallQuery,\n truncateRecallQuery,\n prepareRetentionTranscript,\n sliceLastTurnsByUserBoundary,\n injectHindsightMemories,\n buildMessageContent,\n buildRetainTemplateVars,\n resolveRetainTags,\n resolveRetainMetadata,\n type Message,\n type ToolPartLike,\n} from \"./content.js\";\nimport { ensureBankMission } from \"./bank.js\";\n\nexport interface TurnRecallCache {\n userTurnCount: number;\n context: string | null;\n}\n\nexport interface PluginState {\n turnCount: number;\n missionsSet: Set<string>;\n turnRecall: Map<string, TurnRecallCache>;\n lastRetainedTurn: Map<string, number>;\n /** Sessions observed this process — used by dispose force-retain. */\n activeSessions: Set<string>;\n}\n\ninterface EventInput {\n event: {\n type: string;\n properties: Record<string, unknown>;\n };\n}\n\ninterface CompactingInput {\n sessionID: string;\n}\n\ninterface CompactingOutput {\n context: string[];\n prompt?: string;\n}\n\ninterface SystemTransformInput {\n sessionID?: string;\n model: unknown;\n}\n\ninterface SystemTransformOutput {\n system: string[];\n}\n\ntype SessionPart = {\n type: string;\n text?: string;\n tool?: string;\n synthetic?: boolean;\n state?: ToolPartLike[\"state\"];\n [key: string]: unknown;\n};\n\ninterface MessagesTransformOutput {\n messages: Array<{\n info: { role: string; sessionID?: string; id?: string };\n parts: SessionPart[];\n }>;\n}\n\ntype OpencodeClient = {\n session: {\n messages: (params: { path: { id: string } }) => Promise<{\n data?: Array<{\n info: { role: string };\n parts: SessionPart[];\n }>;\n error?: unknown;\n request?: unknown;\n response?: unknown;\n }>;\n };\n tui?: {\n showToast?: (params: {\n title?: string;\n message?: string;\n variant?: \"info\" | \"success\" | \"warning\" | \"error\";\n duration?: number;\n }) => unknown;\n };\n};\n\ntype TurnRecallResult = {\n context: string | null;\n /** True when a new Hindsight API recall ran (not turn-cache). */\n fresh: boolean;\n};\n\nexport interface HindsightHooks {\n event: (input: EventInput) => Promise<void>;\n dispose: () => Promise<void>;\n \"experimental.session.compacting\": (\n input: CompactingInput,\n output: CompactingOutput\n ) => Promise<void>;\n \"experimental.chat.system.transform\": (\n input: SystemTransformInput,\n output: SystemTransformOutput\n ) => Promise<void>;\n \"experimental.chat.messages.transform\": (\n input: Record<string, never>,\n output: MessagesTransformOutput\n ) => Promise<void>;\n}\n\nfunction countUserTurns(messages: readonly Message[]): number {\n return messages.filter((m) => m.role === \"user\").length;\n}\n\nfunction lastUserMessage(messages: readonly Message[]): Message | undefined {\n for (let i = messages.length - 1; i >= 0; i--) {\n if (messages[i].role === \"user\") return messages[i];\n }\n return undefined;\n}\n\nfunction applyContextToSystem(output: SystemTransformOutput, context: string): void {\n output.system[0] = injectHindsightMemories(output.system[0] ?? \"\", context);\n}\n\nfunction isHindsightMemoryPart(part: SessionPart): boolean {\n return (\n part.type === \"text\" &&\n typeof part.text === \"string\" &&\n part.text.includes(\"<hindsight_memories>\")\n );\n}\n\nfunction applyContextToLatestUserMessage(\n output: MessagesTransformOutput,\n context: string\n): boolean {\n for (let i = output.messages.length - 1; i >= 0; i--) {\n const msg = output.messages[i];\n if (msg.info.role !== \"user\") continue;\n msg.parts = msg.parts.filter((p) => !isHindsightMemoryPart(p));\n msg.parts.push({\n type: \"text\",\n text: context,\n synthetic: true,\n });\n return true;\n }\n return false;\n}\n\nfunction sessionIdFromMessages(output: MessagesTransformOutput): string | undefined {\n for (let i = output.messages.length - 1; i >= 0; i--) {\n const id = output.messages[i]?.info?.sessionID;\n if (typeof id === \"string\" && id) return id;\n }\n return undefined;\n}\n\n/**\n * Text content from transform parts for recall query composition.\n * Skips prior hindsight injects. Prefer non-synthetic text (Claude: user prompt only).\n */\nfunction textFromParts(\n parts: readonly SessionPart[],\n includeSynthetic: boolean\n): string {\n const chunks: string[] = [];\n for (const part of parts) {\n if (part.type !== \"text\" || typeof part.text !== \"string\" || !part.text) continue;\n if (isHindsightMemoryPart(part)) continue;\n if (!includeSynthetic && part.synthetic) continue;\n chunks.push(part.text);\n }\n return chunks.join(\"\\n\").trim();\n}\n\n/**\n * Convert OpenCode transform messages → text-only Message[] for recall.\n * Claude Code uses hook `prompt` (+ optional transcript); we use the transform\n * payload already in hand so we never re-fetch the full session for recall.\n */\nfunction messagesFromTransformOutput(output: MessagesTransformOutput): Message[] {\n const messages: Message[] = [];\n for (const msg of output.messages) {\n const role = msg.info.role;\n if (role !== \"user\" && role !== \"assistant\") continue;\n const content =\n role === \"user\"\n ? textFromParts(msg.parts, false) || textFromParts(msg.parts, true)\n : textFromParts(msg.parts, true);\n if (content) messages.push({ role, content });\n }\n return messages;\n}\n\nfunction capMapSize<K, V>(map: Map<K, V>, max: number): void {\n if (map.size <= max) return;\n const first = map.keys().next().value;\n if (first !== undefined) map.delete(first);\n}\n\nfunction extractSessionIdFromDeleted(properties: Record<string, unknown>): string | undefined {\n if (typeof properties.sessionID === \"string\") return properties.sessionID;\n const info = properties.info;\n if (info && typeof info === \"object\" && info !== null) {\n const id = (info as { id?: unknown }).id;\n if (typeof id === \"string\") return id;\n }\n return undefined;\n}\n\nexport function createHooks(\n hindsightClient: HindsightClient,\n bankId: string,\n config: HindsightConfig,\n state: PluginState,\n opencodeClient: OpencodeClient,\n logger: Logger = new Logger({ silent: true })\n): HindsightHooks {\n interface RecallOutcome {\n context: string | null;\n ok: boolean;\n resultCount?: number;\n }\n\n function trackSession(sessionId: string): void {\n state.activeSessions.add(sessionId);\n if (state.activeSessions.size > 1000) {\n const first = state.activeSessions.values().next().value;\n if (first) state.activeSessions.delete(first);\n }\n }\n\n async function recallForContext(query: string): Promise<RecallOutcome> {\n try {\n const recallOpts = {\n budget: config.recallBudget as \"low\" | \"mid\" | \"high\",\n maxTokens: config.recallMaxTokens,\n types: config.recallTypes,\n tags: config.recallTags.length ? config.recallTags : undefined,\n tagsMatch: config.recallTags.length ? config.recallTagsMatch : undefined,\n };\n\n const response = await hindsightClient.recall(bankId, query, recallOpts);\n let results = response.results || [];\n\n // Claude recallAdditionalBanks: merge extra bank hits into one inject block\n for (const extraBank of config.recallAdditionalBanks) {\n if (!extraBank || extraBank === bankId) continue;\n try {\n const extra = await hindsightClient.recall(extraBank, query, recallOpts);\n const extraResults = extra.results || [];\n if (extraResults.length) {\n results = results.concat(extraResults);\n }\n } catch (e) {\n logger.debug(`Recall from additional bank '${extraBank}' failed`, {\n error: String(e),\n });\n }\n }\n\n if (!results.length) return { context: null, ok: true };\n\n const formatted = formatMemories(results);\n const context =\n `<hindsight_memories>\\n` +\n `${config.recallPromptPreamble}\\n` +\n `Current time: ${formatCurrentTime()} UTC\\n\\n` +\n `${formatted}\\n` +\n `</hindsight_memories>`;\n return { context, ok: true, resultCount: results.length };\n } catch (e) {\n logger.error(\"Recall failed\", e);\n return { context: null, ok: false, resultCount: 0 };\n }\n }\n\n /**\n * @param includeToolCalls - retain path may include tools; recall path stays text-only\n * so tool dumps do not dilute the recall query.\n */\n async function getSessionMessages(\n sessionId: string,\n includeToolCalls: boolean\n ): Promise<Message[]> {\n try {\n logger.debug(`getSessionMessages: fetching messages for session ${sessionId}`);\n const response = await opencodeClient.session.messages({\n path: { id: sessionId },\n });\n if (response.error) {\n logger.warn(\"getSessionMessages: OpenCode returned an error\", {\n error: JSON.stringify(response.error)?.substring(0, 500),\n });\n }\n const rawMessages = response.data || [];\n const messages: Message[] = [];\n for (const msg of rawMessages) {\n const role = msg.info.role;\n if (role !== \"user\" && role !== \"assistant\") continue;\n const content = buildMessageContent(msg.parts, includeToolCalls);\n if (content.trim()) {\n messages.push({ role, content });\n }\n }\n logger.debug(`getSessionMessages: raw=${rawMessages.length}, parsed=${messages.length}`);\n return messages;\n } catch (e) {\n logger.error(\"Failed to get session messages\", e);\n return [];\n }\n }\n\n async function retainSession(sessionId: string, messages: Message[]): Promise<void> {\n const retainFullWindow = config.retainMode === \"full-session\";\n let targetMessages: Message[];\n let documentId: string;\n\n if (retainFullWindow) {\n targetMessages = messages;\n documentId = sessionId;\n } else {\n const windowTurns = config.retainEveryNTurns + config.retainOverlapTurns;\n targetMessages = sliceLastTurnsByUserBoundary(messages, windowTurns);\n documentId = `${sessionId}-${Date.now()}`;\n }\n\n const { transcript, messageCount } = prepareRetentionTranscript(targetMessages, true);\n if (!transcript) return;\n\n const vars = buildRetainTemplateVars({ sessionId, bankId });\n const tags = resolveRetainTags(config.retainTags, vars);\n const metadata = resolveRetainMetadata(config.retainMetadata, vars, messageCount);\n\n await ensureBankMission(hindsightClient, bankId, config, state.missionsSet, logger);\n await hindsightClient.retain(bankId, transcript, {\n documentId,\n context: config.retainContext,\n tags,\n metadata,\n async: true,\n });\n }\n\n /**\n * Claude Stop-style retain gate:\n * - force (SessionEnd/dispose): any pending turns\n * - else: fire when userTurns % retainEveryNTurns === 0 (and not already retained)\n * OpenCode fires this on session.idle (closest equivalent to Claude Stop).\n */\n async function maybeRetainSession(\n sessionId: string,\n opts: { force: boolean; reason: string }\n ): Promise<void> {\n if (!config.autoRetain) return;\n\n const messages = await getSessionMessages(sessionId, config.retainToolCalls);\n if (!messages.length) return;\n\n const userTurns = countUserTurns(messages);\n const lastRetained = state.lastRetainedTurn.get(sessionId) || 0;\n const pending = userTurns - lastRetained;\n const everyN = Math.max(1, config.retainEveryNTurns);\n\n if (pending <= 0) {\n logger.debug(`${opts.reason}: nothing pending for ${sessionId}`);\n return;\n }\n if (!opts.force) {\n // Claude: if every_n > 1 and turn_count % every_n != 0 → skip\n if (everyN > 1 && userTurns % everyN !== 0) {\n logger.debug(\n `${opts.reason}: turn ${userTurns} not multiple of ${everyN}, skip retain`\n );\n return;\n }\n }\n\n try {\n await retainSession(sessionId, messages);\n state.lastRetainedTurn.set(sessionId, userTurns);\n logger.info(`Auto-retained ${messages.length} messages`, {\n session: sessionId,\n bank: bankId,\n reason: opts.reason,\n force: opts.force,\n userTurns,\n });\n } catch (e) {\n logger.error(`Auto-retain failed (${opts.reason})`, e);\n }\n }\n\n const event = async (input: EventInput): Promise<void> => {\n try {\n const { event: evt } = input;\n logger.debug(`event hook fired: type=${evt.type}`);\n\n if (evt.type === \"session.idle\") {\n const sessionId = (evt.properties as { sessionID?: string }).sessionID;\n if (sessionId) {\n trackSession(sessionId);\n await maybeRetainSession(sessionId, { force: false, reason: \"session.idle\" });\n }\n return;\n }\n\n // Claude Code SessionEnd equivalent: flush any pending turns.\n if (evt.type === \"session.deleted\") {\n const sessionId = extractSessionIdFromDeleted(\n evt.properties as Record<string, unknown>\n );\n if (sessionId) {\n trackSession(sessionId);\n await maybeRetainSession(sessionId, { force: true, reason: \"session.deleted\" });\n state.activeSessions.delete(sessionId);\n state.turnRecall.delete(sessionId);\n }\n }\n } catch (e) {\n logger.error(\"Event hook error\", e);\n }\n };\n\n const dispose = async (): Promise<void> => {\n if (!config.autoRetain) return;\n const sessionIds = [...state.activeSessions];\n logger.debug(`dispose: force-retain ${sessionIds.length} active session(s)`);\n for (const sessionId of sessionIds) {\n await maybeRetainSession(sessionId, { force: true, reason: \"dispose\" });\n }\n };\n\n const compacting = async (input: CompactingInput, output: CompactingOutput): Promise<void> => {\n try {\n trackSession(input.sessionID);\n const retainMessages = await getSessionMessages(input.sessionID, config.retainToolCalls);\n if (retainMessages.length && config.autoRetain) {\n try {\n await retainSession(input.sessionID, retainMessages);\n state.lastRetainedTurn.delete(input.sessionID);\n logger.debug(\"Pre-compaction retain completed\");\n } catch (e) {\n logger.error(\"Pre-compaction retain failed\", e);\n }\n }\n\n const recallMessages = await getSessionMessages(input.sessionID, false);\n if (recallMessages.length) {\n const lastUserMsg = lastUserMessage(recallMessages);\n if (lastUserMsg) {\n const query = composeRecallQuery(\n lastUserMsg.content,\n recallMessages,\n config.recallContextTurns\n );\n const truncated = truncateRecallQuery(\n query,\n lastUserMsg.content,\n config.recallMaxQueryChars\n );\n const { context } = await recallForContext(truncated);\n if (context) {\n output.context.push(context);\n }\n }\n }\n } catch (e) {\n logger.error(\"Compaction hook error\", e);\n }\n };\n\n /**\n * Shared per-turn recall: returns context string (or null) and updates turn cache.\n *\n * Claude Code alignment (recall.py):\n * - Default recallContextTurns=1 → query is latest user prompt only\n * - Does NOT re-fetch full session for every recall\n * - Multi-turn prior context only when turns > 1 (from provided messages /\n * transcript), not a full session.messages dump\n *\n * OpenCode synthetic-user path passes transform messages (already in hand).\n * System mode falls back to getSessionMessages (no transform payload).\n */\n function showInjectToast(chars: number): void {\n if (!config.injectToast) return;\n const showToast = opencodeClient.tui?.showToast;\n if (typeof showToast !== \"function\") return;\n try {\n const result = showToast({\n title: \"Hindsight\",\n message: `Memory injected · ${chars} chars`,\n variant: \"info\",\n duration: 2500,\n });\n if (result && typeof (result as Promise<unknown>).then === \"function\") {\n (result as Promise<unknown>).then(undefined, () => {});\n }\n } catch {\n /* toast is best-effort; headless / no TUI is fine */\n }\n }\n\n async function ensureTurnRecall(\n sessionId: string,\n providedMessages?: readonly Message[]\n ): Promise<TurnRecallResult> {\n trackSession(sessionId);\n\n const messages = providedMessages\n ? [...providedMessages]\n : await getSessionMessages(sessionId, false);\n\n if (providedMessages) {\n logger.debug(\n `ensureTurnRecall: using ${messages.length} transform message(s), no session fetch`\n );\n }\n\n const lastUser = lastUserMessage(messages);\n if (!lastUser) {\n logger.debug(`ensureTurnRecall: no user message yet for ${sessionId}`);\n return { context: null, fresh: false };\n }\n\n const prompt = lastUser.content.trim();\n if (prompt.length < config.minRecallPromptChars) {\n logger.debug(\n `ensureTurnRecall: prompt too short (${prompt.length} < ${config.minRecallPromptChars}), skip`\n );\n return { context: null, fresh: false };\n }\n\n const userTurns = countUserTurns(messages);\n const cached = state.turnRecall.get(sessionId);\n\n if (cached && cached.userTurnCount === userTurns) {\n logger.debug(`ensureTurnRecall: cache hit session ${sessionId} turn ${userTurns}`);\n return { context: cached.context, fresh: false };\n }\n\n await ensureBankMission(hindsightClient, bankId, config, state.missionsSet, logger);\n\n // Claude: turns<=1 → query = prompt; turns>1 → compose from recent history.\n const query = composeRecallQuery(prompt, messages, config.recallContextTurns);\n const truncated = truncateRecallQuery(query, prompt, config.recallMaxQueryChars);\n logger.debug(`ensureTurnRecall: recalling turn ${userTurns}, queryLen=${truncated.length}`);\n\n const { context, ok } = await recallForContext(truncated);\n if (ok) {\n state.turnRecall.set(sessionId, { userTurnCount: userTurns, context });\n capMapSize(state.turnRecall, 1000);\n }\n return { context, fresh: ok };\n }\n\n const systemTransform = async (\n input: SystemTransformInput,\n output: SystemTransformOutput\n ): Promise<void> => {\n try {\n if (!config.autoRecall) return;\n if (config.recallInjectMode !== \"system\") return;\n const sessionId = input.sessionID;\n if (!sessionId) return;\n\n // System mode has no transform messages — fetch text-only history (legacy path).\n const { context, fresh } = await ensureTurnRecall(sessionId);\n if (context) {\n applyContextToSystem(output, context);\n if (fresh) {\n logger.info(`Injected per-turn recall into system`, {\n session: sessionId,\n chars: context.length,\n });\n showInjectToast(context.length);\n }\n }\n } catch (e) {\n logger.error(\"System transform hook error\", e);\n }\n };\n\n const messagesTransform = async (\n _input: Record<string, never>,\n output: MessagesTransformOutput\n ): Promise<void> => {\n try {\n if (!config.autoRecall) return;\n if (config.recallInjectMode !== \"synthetic-user\") return;\n\n const sessionId = sessionIdFromMessages(output);\n if (!sessionId) {\n logger.debug(\"messagesTransform: no sessionID on messages, skip\");\n return;\n }\n\n // Claude-like: use messages already in the prompt pipeline — no full session API.\n const { context, fresh } = await ensureTurnRecall(\n sessionId,\n messagesFromTransformOutput(output)\n );\n if (!context) return;\n\n const ok = applyContextToLatestUserMessage(output, context);\n if (ok) {\n if (fresh) {\n logger.info(`Injected per-turn recall as synthetic user part`, {\n session: sessionId,\n chars: context.length,\n });\n showInjectToast(context.length);\n }\n } else {\n logger.debug(`messagesTransform: no user message to attach synthetic part`);\n }\n } catch (e) {\n logger.error(\"Messages transform hook error\", e);\n }\n };\n\n return {\n event,\n dispose,\n \"experimental.session.compacting\": compacting,\n \"experimental.chat.system.transform\": systemTransform,\n \"experimental.chat.messages.transform\": messagesTransform,\n };\n}\n"],"mappings":"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","names":["readFileSync","basename","tool","state","tool","readFileSync","base","basename","state"]}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "opencode-hindsight-plus",
3
- "version": "0.3.8",
4
- "description": "Hindsight memory plugin for OpenCode with Claude-aligned Hindsight for OpenCode: per-project banks, transform-local recall, synthetic-user inject, knowledge pages",
3
+ "version": "0.3.10",
4
+ "description": "Claude-aligned Hindsight for OpenCode: transform-local recall, multi-bank, ingest tools, synthetic-user inject, knowledge pages",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
7
  "type": "module",