toolnet-memory 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (329) hide show
  1. package/.env.example +28 -0
  2. package/README.md +147 -0
  3. package/bin/toolnet-memory +415 -0
  4. package/dist/packages/cli/commands.js +1 -0
  5. package/dist/packages/cli/index.js +1 -0
  6. package/dist/packages/sdk/client.js +78 -0
  7. package/dist/packages/sdk/index.js +1 -0
  8. package/dist/packages/sdk/types.js +1 -0
  9. package/dist/src/api/index.js +1 -0
  10. package/dist/src/api/routes/health.js +1 -0
  11. package/dist/src/api/routes/index.js +1 -0
  12. package/dist/src/api/routes/memory.js +1 -0
  13. package/dist/src/api/routes/projects.js +1 -0
  14. package/dist/src/api/routes/search.js +1 -0
  15. package/dist/src/api/server.js +1 -0
  16. package/dist/src/capture/activity-capture.js +21 -0
  17. package/dist/src/capture/command-capture.js +12 -0
  18. package/dist/src/capture/error-capture.js +14 -0
  19. package/dist/src/capture/event-deduplicator.js +28 -0
  20. package/dist/src/capture/event-queue.js +20 -0
  21. package/dist/src/capture/file-capture.js +23 -0
  22. package/dist/src/capture/index.js +8 -0
  23. package/dist/src/capture/session-capture.js +18 -0
  24. package/dist/src/capture/tool-capture.js +13 -0
  25. package/dist/src/code-intelligence/analysis/analysis-engine.js +26 -0
  26. package/dist/src/code-intelligence/analysis/dead-code-analyzer.js +127 -0
  27. package/dist/src/code-intelligence/analysis/dependency-analyzer.js +66 -0
  28. package/dist/src/code-intelligence/analysis/index.js +4 -0
  29. package/dist/src/code-intelligence/analysis/types.js +1 -0
  30. package/dist/src/code-intelligence/architecture/architecture-engine.js +48 -0
  31. package/dist/src/code-intelligence/architecture/clusters.js +322 -0
  32. package/dist/src/code-intelligence/architecture/entry-points.js +150 -0
  33. package/dist/src/code-intelligence/architecture/hotspots.js +136 -0
  34. package/dist/src/code-intelligence/architecture/index.js +6 -0
  35. package/dist/src/code-intelligence/architecture/layers.js +197 -0
  36. package/dist/src/code-intelligence/architecture/types.js +1 -0
  37. package/dist/src/code-intelligence/chunks/index.js +2 -0
  38. package/dist/src/code-intelligence/chunks/smart-chunker.js +125 -0
  39. package/dist/src/code-intelligence/chunks/types.js +1 -0
  40. package/dist/src/code-intelligence/git/git-diff.js +109 -0
  41. package/dist/src/code-intelligence/git/index.js +1 -0
  42. package/dist/src/code-intelligence/graph/architecture.js +26 -0
  43. package/dist/src/code-intelligence/graph/graph-builder.js +213 -0
  44. package/dist/src/code-intelligence/graph/graph-store.js +57 -0
  45. package/dist/src/code-intelligence/graph/index.js +1 -0
  46. package/dist/src/code-intelligence/graph/relationships.js +1 -0
  47. package/dist/src/code-intelligence/graph/trace.js +65 -0
  48. package/dist/src/code-intelligence/impact/blast-radius.js +160 -0
  49. package/dist/src/code-intelligence/impact/change-mapper.js +69 -0
  50. package/dist/src/code-intelligence/impact/edit-tool-detector.js +92 -0
  51. package/dist/src/code-intelligence/impact/impact-analyzer.js +60 -0
  52. package/dist/src/code-intelligence/impact/impact-guard.js +33 -0
  53. package/dist/src/code-intelligence/impact/index.js +5 -0
  54. package/dist/src/code-intelligence/incremental/file-hash.js +8 -0
  55. package/dist/src/code-intelligence/incremental/incremental-indexer.js +106 -0
  56. package/dist/src/code-intelligence/incremental/manifest-builder.js +19 -0
  57. package/dist/src/code-intelligence/incremental/manifest.js +31 -0
  58. package/dist/src/code-intelligence/index.js +23 -0
  59. package/dist/src/code-intelligence/indexer/file-watcher.js +1 -0
  60. package/dist/src/code-intelligence/indexer/incremental-indexer.js +1 -0
  61. package/dist/src/code-intelligence/indexer/index.js +1 -0
  62. package/dist/src/code-intelligence/indexer/repository-indexer.js +31 -0
  63. package/dist/src/code-intelligence/indexer/repository-scanner.js +50 -0
  64. package/dist/src/code-intelligence/parsers/index.js +1 -0
  65. package/dist/src/code-intelligence/parsers/tree-sitter/index.js +1 -0
  66. package/dist/src/code-intelligence/parsers/tree-sitter/languages.js +1 -0
  67. package/dist/src/code-intelligence/parsers/tree-sitter/parser.js +1 -0
  68. package/dist/src/code-intelligence/parsers/typescript-parser.js +190 -0
  69. package/dist/src/code-intelligence/query/graph-query-engine.js +275 -0
  70. package/dist/src/code-intelligence/query/index.js +2 -0
  71. package/dist/src/code-intelligence/query/types.js +1 -0
  72. package/dist/src/code-intelligence/resolution/index.js +2 -0
  73. package/dist/src/code-intelligence/resolution/types.js +1 -0
  74. package/dist/src/code-intelligence/resolution/typescript-resolver.js +316 -0
  75. package/dist/src/code-intelligence/rich/index.js +1 -0
  76. package/dist/src/code-intelligence/rich/rich-graph-enricher.js +463 -0
  77. package/dist/src/code-intelligence/semantic/index.js +2 -0
  78. package/dist/src/code-intelligence/semantic/semantic-code-engine.js +213 -0
  79. package/dist/src/code-intelligence/semantic/source-priority.js +22 -0
  80. package/dist/src/code-intelligence/symbols/index.js +1 -0
  81. package/dist/src/code-intelligence/symbols/reference-resolver.js +21 -0
  82. package/dist/src/code-intelligence/symbols/symbol-extractor.js +1 -0
  83. package/dist/src/code-intelligence/test-analysis.js +72 -0
  84. package/dist/src/code-intelligence/test-architecture.js +67 -0
  85. package/dist/src/code-intelligence/test-impact.js +47 -0
  86. package/dist/src/code-intelligence/test-incremental.js +24 -0
  87. package/dist/src/code-intelligence/test-index.js +39 -0
  88. package/dist/src/code-intelligence/test-resolution.js +54 -0
  89. package/dist/src/code-intelligence/test-rich-graph.js +48 -0
  90. package/dist/src/code-intelligence/test-semantic.js +57 -0
  91. package/dist/src/code-intelligence/test-visualization.js +36 -0
  92. package/dist/src/code-intelligence/types.js +1 -0
  93. package/dist/src/code-intelligence/visualization/index.js +2 -0
  94. package/dist/src/code-intelligence/visualization/types.js +1 -0
  95. package/dist/src/code-intelligence/visualization/visualization-builder.js +104 -0
  96. package/dist/src/core/config.js +66 -0
  97. package/dist/src/core/global-env.js +70 -0
  98. package/dist/src/core/importance.js +30 -0
  99. package/dist/src/core/index.js +5 -0
  100. package/dist/src/core/memory-engine.js +183 -0
  101. package/dist/src/core/project-manager.js +243 -0
  102. package/dist/src/core/types.js +1 -0
  103. package/dist/src/embeddings/batch-embedder.js +1 -0
  104. package/dist/src/embeddings/cache.js +1 -0
  105. package/dist/src/embeddings/embedder.js +51 -0
  106. package/dist/src/embeddings/index.js +4 -0
  107. package/dist/src/embeddings/local.js +37 -0
  108. package/dist/src/embeddings/provider.js +1 -0
  109. package/dist/src/embeddings/remote.js +76 -0
  110. package/dist/src/embeddings/test-connection.js +16 -0
  111. package/dist/src/hooks/after-edit.js +3 -0
  112. package/dist/src/hooks/after-tool.js +3 -0
  113. package/dist/src/hooks/auto-context.js +68 -0
  114. package/dist/src/hooks/auto-impact-guard.js +94 -0
  115. package/dist/src/hooks/auto-retrieval.js +27 -0
  116. package/dist/src/hooks/before-agent.js +18 -0
  117. package/dist/src/hooks/before-compact.js +3 -0
  118. package/dist/src/hooks/before-edit.js +20 -0
  119. package/dist/src/hooks/index.js +11 -0
  120. package/dist/src/hooks/runtime.js +110 -0
  121. package/dist/src/hooks/session-end.js +3 -0
  122. package/dist/src/hooks/session-start.js +3 -0
  123. package/dist/src/index.js +22 -0
  124. package/dist/src/mcp/bootstrap.js +68 -0
  125. package/dist/src/mcp/context.js +1 -0
  126. package/dist/src/mcp/index.js +3 -0
  127. package/dist/src/mcp/server.js +45 -0
  128. package/dist/src/mcp/tools/analyze-impact.js +41 -0
  129. package/dist/src/mcp/tools/dead-code.js +55 -0
  130. package/dist/src/mcp/tools/find-callers.js +24 -0
  131. package/dist/src/mcp/tools/find-dependencies.js +57 -0
  132. package/dist/src/mcp/tools/find-symbol.js +7 -0
  133. package/dist/src/mcp/tools/get-architecture.js +11 -0
  134. package/dist/src/mcp/tools/graph-dependents.js +24 -0
  135. package/dist/src/mcp/tools/graph-neighborhood.js +58 -0
  136. package/dist/src/mcp/tools/graph-path.js +43 -0
  137. package/dist/src/mcp/tools/graph-query-utils.js +32 -0
  138. package/dist/src/mcp/tools/impact-guard.js +34 -0
  139. package/dist/src/mcp/tools/index-repository.js +1 -0
  140. package/dist/src/mcp/tools/index.js +20 -0
  141. package/dist/src/mcp/tools/memory-decisions.js +1 -0
  142. package/dist/src/mcp/tools/memory-forget.js +14 -0
  143. package/dist/src/mcp/tools/memory-recent.js +1 -0
  144. package/dist/src/mcp/tools/memory-remember.js +33 -0
  145. package/dist/src/mcp/tools/memory-rules.js +1 -0
  146. package/dist/src/mcp/tools/memory-search.js +18 -0
  147. package/dist/src/mcp/tools/memory-todos.js +1 -0
  148. package/dist/src/mcp/tools/project-context.js +25 -0
  149. package/dist/src/mcp/tools/search-code.js +21 -0
  150. package/dist/src/mcp/tools/semantic-code-search.js +33 -0
  151. package/dist/src/mcp/tools/snapshot-create.js +13 -0
  152. package/dist/src/mcp/tools/snapshot-list.js +7 -0
  153. package/dist/src/mcp/tools/snapshot-restore.js +13 -0
  154. package/dist/src/mcp/tools/trace-calls.js +37 -0
  155. package/dist/src/memory/activity.js +5 -0
  156. package/dist/src/memory/conflict-detector.js +105 -0
  157. package/dist/src/memory/consolidator.js +104 -0
  158. package/dist/src/memory/decay.js +59 -0
  159. package/dist/src/memory/decisions.js +3 -0
  160. package/dist/src/memory/deduplicate.js +25 -0
  161. package/dist/src/memory/index.js +11 -0
  162. package/dist/src/memory/maintenance.js +16 -0
  163. package/dist/src/memory/remember.js +3 -0
  164. package/dist/src/memory/rules.js +3 -0
  165. package/dist/src/memory/search.js +3 -0
  166. package/dist/src/memory/summaries.js +1 -0
  167. package/dist/src/memory/todos.js +3 -0
  168. package/dist/src/processor/decision-extractor.js +13 -0
  169. package/dist/src/processor/importance-scorer.js +22 -0
  170. package/dist/src/processor/index.js +7 -0
  171. package/dist/src/processor/memory-extractor.js +59 -0
  172. package/dist/src/processor/memory-processor.js +105 -0
  173. package/dist/src/processor/rule-extractor.js +13 -0
  174. package/dist/src/processor/summarizer.js +71 -0
  175. package/dist/src/processor/todo-extractor.js +12 -0
  176. package/dist/src/production/config-check.js +34 -0
  177. package/dist/src/production/doctor.js +58 -0
  178. package/dist/src/production/full-index.js +114 -0
  179. package/dist/src/production/health.js +58 -0
  180. package/dist/src/production/index-pipeline.js +151 -0
  181. package/dist/src/production/index.js +4 -0
  182. package/dist/src/production/project-lock.js +95 -0
  183. package/dist/src/production/project-status.js +16 -0
  184. package/dist/src/production/recovery.js +15 -0
  185. package/dist/src/production/snapshot-cli.js +58 -0
  186. package/dist/src/project-manual/cli.js +77 -0
  187. package/dist/src/project-manual/index.js +2 -0
  188. package/dist/src/project-manual/manager.js +147 -0
  189. package/dist/src/project-manual/types.js +1 -0
  190. package/dist/src/retrieval/bm25/index.js +1 -0
  191. package/dist/src/retrieval/bm25/search.js +1 -0
  192. package/dist/src/retrieval/bm25.js +30 -0
  193. package/dist/src/retrieval/context-budget.js +1 -0
  194. package/dist/src/retrieval/context-builder.js +22 -0
  195. package/dist/src/retrieval/graph/index.js +1 -0
  196. package/dist/src/retrieval/graph/search.js +1 -0
  197. package/dist/src/retrieval/hybrid/hybrid-search.js +1 -0
  198. package/dist/src/retrieval/hybrid/index.js +1 -0
  199. package/dist/src/retrieval/hybrid-search.js +68 -0
  200. package/dist/src/retrieval/importance.js +16 -0
  201. package/dist/src/retrieval/index.js +12 -0
  202. package/dist/src/retrieval/query-analyzer.js +32 -0
  203. package/dist/src/retrieval/query-classifier.js +1 -0
  204. package/dist/src/retrieval/query-expansion.js +1 -0
  205. package/dist/src/retrieval/recency.js +12 -0
  206. package/dist/src/retrieval/reranker/index.js +1 -0
  207. package/dist/src/retrieval/reranker/reranker.js +1 -0
  208. package/dist/src/retrieval/retrieval-engine.js +17 -0
  209. package/dist/src/retrieval/tokenizer.js +9 -0
  210. package/dist/src/retrieval/type-priority.js +10 -0
  211. package/dist/src/retrieval/types.js +1 -0
  212. package/dist/src/retrieval/vector/index.js +4 -0
  213. package/dist/src/retrieval/vector/metadata-filter.js +1 -0
  214. package/dist/src/retrieval/vector/persistence.js +67 -0
  215. package/dist/src/retrieval/vector/search.js +12 -0
  216. package/dist/src/retrieval/vector/test-persistence.js +36 -0
  217. package/dist/src/retrieval/vector/upsert.js +40 -0
  218. package/dist/src/retrieval/vector/vector-store.js +89 -0
  219. package/dist/src/retrieval/vector-hybrid-engine.js +91 -0
  220. package/dist/src/runtime/bootstrap.js +28 -0
  221. package/dist/src/runtime/graceful.js +17 -0
  222. package/dist/src/runtime/index.js +3 -0
  223. package/dist/src/runtime/toolnet-memory-runtime.js +156 -0
  224. package/dist/src/security/encryption.js +1 -0
  225. package/dist/src/security/file-filter.js +30 -0
  226. package/dist/src/security/index.js +3 -0
  227. package/dist/src/security/sanitizer.js +49 -0
  228. package/dist/src/security/secret-scanner.js +56 -0
  229. package/dist/src/session/agy/adapter.js +159 -0
  230. package/dist/src/session/agy/cli.js +87 -0
  231. package/dist/src/session/agy/hook-installer.js +62 -0
  232. package/dist/src/session/agy/hook.js +167 -0
  233. package/dist/src/session/agy/index.js +5 -0
  234. package/dist/src/session/agy/project-resolver.js +27 -0
  235. package/dist/src/session/agy/recovery.js +112 -0
  236. package/dist/src/session/agy/transcript.js +191 -0
  237. package/dist/src/session/codex/adapter.js +138 -0
  238. package/dist/src/session/codex/cli.js +236 -0
  239. package/dist/src/session/codex/context-hook-installer.js +70 -0
  240. package/dist/src/session/codex/context-hook.js +69 -0
  241. package/dist/src/session/codex/discovery.js +138 -0
  242. package/dist/src/session/codex/index.js +6 -0
  243. package/dist/src/session/codex/notify-installer.js +135 -0
  244. package/dist/src/session/codex/project-resolver.js +25 -0
  245. package/dist/src/session/codex/recovery.js +27 -0
  246. package/dist/src/session/codex/rollout.js +186 -0
  247. package/dist/src/session/core.js +239 -0
  248. package/dist/src/session/identity.js +57 -0
  249. package/dist/src/session/index.js +9 -0
  250. package/dist/src/session/learner/cli.js +62 -0
  251. package/dist/src/session/learner/extractor.js +461 -0
  252. package/dist/src/session/learner/index.js +4 -0
  253. package/dist/src/session/learner/journal.js +159 -0
  254. package/dist/src/session/learner/learner.js +124 -0
  255. package/dist/src/session/learner/types.js +1 -0
  256. package/dist/src/session/opencode/adapter.js +553 -0
  257. package/dist/src/session/opencode/cli.js +121 -0
  258. package/dist/src/session/opencode/index.js +2 -0
  259. package/dist/src/session/opencode/plugin-installer.js +230 -0
  260. package/dist/src/session/store.js +242 -0
  261. package/dist/src/session/types.js +1 -0
  262. package/dist/src/session/utils.js +47 -0
  263. package/dist/src/session/wal.js +303 -0
  264. package/dist/src/snapshot/index.js +2 -0
  265. package/dist/src/snapshot/manager.js +130 -0
  266. package/dist/src/snapshot/test-snapshot.js +28 -0
  267. package/dist/src/snapshot/types.js +1 -0
  268. package/dist/src/storage/architecture-store.js +37 -0
  269. package/dist/src/storage/cleanup-health.js +20 -0
  270. package/dist/src/storage/code-analysis-store.js +35 -0
  271. package/dist/src/storage/code-chunk-store.js +25 -0
  272. package/dist/src/storage/code-graph-store.js +24 -0
  273. package/dist/src/storage/code-manifest-store.js +24 -0
  274. package/dist/src/storage/code-vector-store.js +25 -0
  275. package/dist/src/storage/github/client.js +1 -0
  276. package/dist/src/storage/github/index.js +1 -0
  277. package/dist/src/storage/google-drive/client.js +1 -0
  278. package/dist/src/storage/google-drive/index.js +1 -0
  279. package/dist/src/storage/huggingface/client.js +116 -0
  280. package/dist/src/storage/huggingface/index.js +1 -0
  281. package/dist/src/storage/index.js +16 -0
  282. package/dist/src/storage/list-bucket-raw.js +14 -0
  283. package/dist/src/storage/list-bucket-tree.js +38 -0
  284. package/dist/src/storage/local/client.js +99 -0
  285. package/dist/src/storage/local/index.js +1 -0
  286. package/dist/src/storage/memory-store.js +30 -0
  287. package/dist/src/storage/project/cleanup-layout-v1.js +72 -0
  288. package/dist/src/storage/project/folder.js +15 -0
  289. package/dist/src/storage/project/index.js +3 -0
  290. package/dist/src/storage/project/layout.js +74 -0
  291. package/dist/src/storage/project/migrate-layout-v2.js +101 -0
  292. package/dist/src/storage/project/migrate.js +73 -0
  293. package/dist/src/storage/project/scoped-provider.js +165 -0
  294. package/dist/src/storage/provider.js +26 -0
  295. package/dist/src/storage/retrying-provider.js +23 -0
  296. package/dist/src/storage/test-connection.js +32 -0
  297. package/dist/src/storage/type-resolution-store.js +26 -0
  298. package/dist/src/storage/types.js +1 -0
  299. package/dist/src/storage/vector-store.js +30 -0
  300. package/dist/src/storage/visualization-store.js +25 -0
  301. package/dist/src/sync/compressor.js +1 -0
  302. package/dist/src/sync/conflict-resolver.js +1 -0
  303. package/dist/src/sync/downloader.js +1 -0
  304. package/dist/src/sync/index.js +1 -0
  305. package/dist/src/sync/sync-engine.js +1 -0
  306. package/dist/src/sync/upload-queue.js +1 -0
  307. package/dist/src/utils/retry.js +29 -0
  308. package/dist/src/visualization/server.js +270 -0
  309. package/dist/src/work-continuity/brief-cache.js +107 -0
  310. package/dist/src/work-continuity/brief.js +349 -0
  311. package/dist/src/work-continuity/cli.js +96 -0
  312. package/dist/src/work-continuity/context-cli.js +106 -0
  313. package/dist/src/work-continuity/context-runtime-cli.js +88 -0
  314. package/dist/src/work-continuity/extractor.js +339 -0
  315. package/dist/src/work-continuity/handoff.js +185 -0
  316. package/dist/src/work-continuity/index.js +14 -0
  317. package/dist/src/work-continuity/injection.js +84 -0
  318. package/dist/src/work-continuity/journal.js +49 -0
  319. package/dist/src/work-continuity/learner.js +127 -0
  320. package/dist/src/work-continuity/reducer.js +316 -0
  321. package/dist/src/work-continuity/semantic-extractor.js +360 -0
  322. package/dist/src/work-continuity/semantic-journal.js +55 -0
  323. package/dist/src/work-continuity/semantic-learner.js +125 -0
  324. package/dist/src/work-continuity/semantic-reducer.js +268 -0
  325. package/dist/src/work-continuity/semantic-types.js +1 -0
  326. package/dist/src/work-continuity/types.js +1 -0
  327. package/dist/visualization/public/index.html +2896 -0
  328. package/dist/visualization/public/vendor/3d-force-graph.min.js +5 -0
  329. package/package.json +69 -0
@@ -0,0 +1,121 @@
1
+ import { loadConfig, ProjectManager, } from "../../core/index.js";
2
+ import { createStorageProvider, ProjectScopedStorageProvider, withStorageRetry, } from "../../storage/index.js";
3
+ import { recoverOpenCodeProject, syncOpenCodeSession, } from "./adapter.js";
4
+ import { installOpenCodePlugin, } from "./plugin-installer.js";
5
+ import { refreshStartupBriefCache, } from "../../work-continuity/brief-cache.js";
6
+ function valueAfter(args, flag) {
7
+ const index = args.indexOf(flag);
8
+ if (index < 0) {
9
+ return undefined;
10
+ }
11
+ return args[index + 1];
12
+ }
13
+ function has(args, flag) {
14
+ return args.includes(flag);
15
+ }
16
+ function storageFor(project) {
17
+ const config = loadConfig();
18
+ const raw = withStorageRetry(createStorageProvider({
19
+ provider: config.storage.provider,
20
+ huggingface: config.storage
21
+ .huggingface,
22
+ localRoot: config.storage
23
+ .localRoot,
24
+ }), {
25
+ attempts: 3,
26
+ });
27
+ return new ProjectScopedStorageProvider(raw, project.id, project.name, project.remote ??
28
+ project.name);
29
+ }
30
+ async function main() {
31
+ const [command = "help", ...args] = process.argv.slice(2);
32
+ if (command ===
33
+ "install-plugin") {
34
+ const target = installOpenCodePlugin({
35
+ binary: valueAfter(args, "--bin"),
36
+ });
37
+ console.log(`✅ OpenCode plugin installed: ${target}`);
38
+ console.log("OpenCode will load it automatically on next start.");
39
+ return;
40
+ }
41
+ const projectPath = valueAfter(args, "--project") ??
42
+ process.cwd();
43
+ const project = new ProjectManager()
44
+ .detect(projectPath);
45
+ const storage = storageFor(project);
46
+ const dbPath = valueAfter(args, "--db");
47
+ if (command ===
48
+ "sync") {
49
+ const nativeSessionId = args.find(value => !value.startsWith("--") &&
50
+ value !==
51
+ projectPath &&
52
+ value !==
53
+ dbPath);
54
+ if (!nativeSessionId) {
55
+ throw new Error("Usage: session:opencode-sync <session-id>");
56
+ }
57
+ const idle = has(args, "--idle");
58
+ const error = has(args, "--error");
59
+ const compacted = has(args, "--compacted");
60
+ const result = await syncOpenCodeSession({
61
+ project,
62
+ storage,
63
+ nativeSessionId,
64
+ dbPath,
65
+ idle,
66
+ error,
67
+ compacted,
68
+ });
69
+ if (idle ||
70
+ compacted ||
71
+ error) {
72
+ try {
73
+ await refreshStartupBriefCache(project, storage, 900);
74
+ }
75
+ catch {
76
+ // Derived cache must never break session capture.
77
+ }
78
+ }
79
+ console.log(JSON.stringify(result, null, 2));
80
+ return;
81
+ }
82
+ if (command ===
83
+ "recover") {
84
+ const limitValue = valueAfter(args, "--limit");
85
+ const limit = limitValue
86
+ ? Number(limitValue)
87
+ : 100;
88
+ const results = await recoverOpenCodeProject({
89
+ project,
90
+ storage,
91
+ dbPath,
92
+ limit: Number.isFinite(limit)
93
+ ? limit
94
+ : 100,
95
+ });
96
+ console.log(JSON.stringify({
97
+ project: project.name,
98
+ sessions: results.length,
99
+ importedMessages: results.reduce((total, item) => total +
100
+ item
101
+ .importedMessages, 0),
102
+ importedParts: results.reduce((total, item) => total +
103
+ item
104
+ .importedParts, 0),
105
+ }, null, 2));
106
+ return;
107
+ }
108
+ console.log(`OpenCode Session Adapter
109
+
110
+ Commands:
111
+ sync <session-id> [--project PATH] [--idle]
112
+ recover [--project PATH] [--limit N]
113
+ install-plugin [--bin PATH]
114
+ `);
115
+ }
116
+ main().catch(error => {
117
+ console.error(error instanceof Error
118
+ ? error.message
119
+ : error);
120
+ process.exit(1);
121
+ });
@@ -0,0 +1,2 @@
1
+ export * from "./adapter.js";
2
+ export * from "./plugin-installer.js";
@@ -0,0 +1,230 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync, } from "node:fs";
2
+ import { homedir, } from "node:os";
3
+ import { join, } from "node:path";
4
+ const START = "<!-- TOOLNET_MEMORY_BOOTSTRAP_START -->";
5
+ const END = "<!-- TOOLNET_MEMORY_BOOTSTRAP_END -->";
6
+ function installBootstrapInstructions() {
7
+ const file = join(homedir(), ".config", "opencode", "AGENTS.md");
8
+ mkdirSync(join(homedir(), ".config", "opencode"), {
9
+ recursive: true,
10
+ });
11
+ const block = `${START}
12
+ ## ToolNet Memory — Project Continuity
13
+
14
+ When the current workspace belongs to a ToolNet project, identified by a nearest ancestor containing \`.toolnet/project.json\`:
15
+
16
+ 1. Before planning, editing, or running implementation commands, read that project root's \`.toolnet/context/startup.md\`.
17
+ 2. Treat its PROJECT RULES, MISSION, CURRENT OBJECTIVE, rationale, current phase/task, Definition of Done, blockers and next actions as project continuity context.
18
+ 3. Do not restart work already marked completed unless repository evidence proves it must be revisited.
19
+ 4. Verify current repository state before continuing old work.
20
+ 5. If \`.toolnet/context/startup.md\` is missing, run \`toolnet-memory context:sync --project <project-root>\`, then read it.
21
+ 6. Never invent missing rationale. If ToolNet says a reason was not explicitly recorded, inspect the implementation or ask rather than guessing.
22
+
23
+ This applies automatically to every ToolNet-managed project.
24
+ ${END}`;
25
+ let content = existsSync(file)
26
+ ? readFileSync(file, "utf8")
27
+ : "";
28
+ const start = content.indexOf(START);
29
+ const end = content.indexOf(END);
30
+ if (start >= 0 &&
31
+ end >= start) {
32
+ content =
33
+ content.slice(0, start) +
34
+ block +
35
+ content.slice(end +
36
+ END.length);
37
+ }
38
+ else {
39
+ content =
40
+ content.trimEnd();
41
+ if (content) {
42
+ content +=
43
+ "\n\n";
44
+ }
45
+ content +=
46
+ block;
47
+ }
48
+ writeFileSync(file, content.trimEnd() +
49
+ "\n", {
50
+ encoding: "utf8",
51
+ mode: 0o600,
52
+ });
53
+ return file;
54
+ }
55
+ export function installOpenCodePlugin(options = {}) {
56
+ const directory = options.directory ??
57
+ join(homedir(), ".config", "opencode", "plugins");
58
+ mkdirSync(directory, {
59
+ recursive: true,
60
+ });
61
+ installBootstrapInstructions();
62
+ const target = join(directory, "toolnet-memory.js");
63
+ const binary = options.binary ??
64
+ process.env
65
+ .TOOLNET_MEMORY_BIN ??
66
+ "toolnet-memory";
67
+ const content = `// Generated by ToolNet Memory.
68
+ // OpenCode session capture + continuity bootstrap.
69
+
70
+ function getSessionId(event) {
71
+ const p = event?.properties ?? {}
72
+
73
+ const candidates = [
74
+ p.sessionID,
75
+ p.sessionId,
76
+ p.info?.id,
77
+ p.session?.id,
78
+ ]
79
+
80
+ return candidates.find(
81
+ value => typeof value === "string" && value.length > 0
82
+ ) ?? null
83
+ }
84
+
85
+ export const ToolNetMemoryPlugin = async ({ directory }) => {
86
+ const binary = ${JSON.stringify(binary)}
87
+
88
+ async function refreshContext() {
89
+ try {
90
+ const child = Bun.spawn(
91
+ [
92
+ binary,
93
+ "context:sync",
94
+ "--project",
95
+ directory,
96
+ ],
97
+ {
98
+ stdin: "ignore",
99
+ stdout: "ignore",
100
+ stderr: "ignore",
101
+ }
102
+ )
103
+
104
+ await child.exited
105
+ } catch {
106
+ // Context sync must never break OpenCode startup.
107
+ }
108
+ }
109
+
110
+ async function readContext() {
111
+ try {
112
+ const child = Bun.spawn(
113
+ [
114
+ binary,
115
+ "context:print",
116
+ "--project",
117
+ directory,
118
+ "--tokens",
119
+ "900",
120
+ ],
121
+ {
122
+ stdin: "ignore",
123
+ stdout: "pipe",
124
+ stderr: "ignore",
125
+ }
126
+ )
127
+
128
+ const text =
129
+ await new Response(
130
+ child.stdout
131
+ ).text()
132
+
133
+ await child.exited
134
+
135
+ return text.trim()
136
+ } catch {
137
+ return ""
138
+ }
139
+ }
140
+
141
+ function sync(sessionId, flag) {
142
+ if (!sessionId) return
143
+
144
+ try {
145
+ const args = [
146
+ binary,
147
+ "session:opencode-sync",
148
+ sessionId,
149
+ "--project",
150
+ directory,
151
+ ]
152
+
153
+ if (flag) {
154
+ args.push(flag)
155
+ }
156
+
157
+ const child = Bun.spawn(args, {
158
+ stdin: "ignore",
159
+ stdout: "ignore",
160
+ stderr: "ignore",
161
+ })
162
+
163
+ if (
164
+ typeof child.unref === "function"
165
+ ) {
166
+ child.unref()
167
+ }
168
+ } catch {
169
+ // Memory capture must never break OpenCode.
170
+ }
171
+ }
172
+
173
+ /*
174
+ * Pull the latest project brief from ToolNet remote storage
175
+ * before the new OpenCode session starts doing work.
176
+ */
177
+ await refreshContext()
178
+
179
+ return {
180
+ event: async ({ event }) => {
181
+ const sessionId =
182
+ getSessionId(event)
183
+
184
+ if (!sessionId) {
185
+ return
186
+ }
187
+
188
+ if (event.type === "session.idle") {
189
+ sync(sessionId, "--idle")
190
+ return
191
+ }
192
+
193
+ if (event.type === "session.compacted") {
194
+ sync(sessionId, "--compacted")
195
+ return
196
+ }
197
+
198
+ if (event.type === "session.error") {
199
+ sync(sessionId, "--error")
200
+ }
201
+ },
202
+
203
+ /*
204
+ * Official OpenCode compaction hook.
205
+ * Preserve ToolNet's semantic state after context compaction.
206
+ */
207
+ "experimental.session.compacting": async (_input, output) => {
208
+ const context =
209
+ await readContext()
210
+
211
+ if (
212
+ context &&
213
+ Array.isArray(
214
+ output.context
215
+ )
216
+ ) {
217
+ output.context.push(
218
+ context
219
+ )
220
+ }
221
+ },
222
+ }
223
+ }
224
+ `;
225
+ writeFileSync(target, content, {
226
+ encoding: "utf8",
227
+ mode: 0o600,
228
+ });
229
+ return target;
230
+ }
@@ -0,0 +1,242 @@
1
+ import { sha256, } from "./utils.js";
2
+ function padSequence(value) {
3
+ return String(value).padStart(12, "0");
4
+ }
5
+ export class RemoteSessionStore {
6
+ storage;
7
+ maxEventsPerChunk;
8
+ maxChunkBytes;
9
+ constructor(storage, maxEventsPerChunk = 100, maxChunkBytes = 512 * 1024) {
10
+ this.storage = storage;
11
+ this.maxEventsPerChunk = maxEventsPerChunk;
12
+ this.maxChunkBytes = maxChunkBytes;
13
+ if (maxEventsPerChunk <
14
+ 1) {
15
+ throw new Error("maxEventsPerChunk must be positive");
16
+ }
17
+ if (maxChunkBytes <
18
+ 1_024) {
19
+ throw new Error("maxChunkBytes is too small");
20
+ }
21
+ }
22
+ async getJson(key) {
23
+ const text = await this.storage
24
+ .getText(key);
25
+ if (!text) {
26
+ return null;
27
+ }
28
+ return JSON.parse(text);
29
+ }
30
+ async putJson(key, value) {
31
+ await this.storage.put(key, JSON.stringify(value, null, 2) + "\n", "application/json");
32
+ }
33
+ async scan(identity) {
34
+ const prefix = `${identity.remotePrefix}/events/`;
35
+ const objects = await this.storage.list(prefix);
36
+ const chunks = [];
37
+ let maxSequence = 0;
38
+ for (const object of objects) {
39
+ const match = object.key.match(/\/events\/(\d+)-(\d+)-[a-f0-9]+\.jsonl$/);
40
+ if (!match) {
41
+ continue;
42
+ }
43
+ const start = Number(match[1]);
44
+ const end = Number(match[2]);
45
+ if (!Number.isFinite(start) ||
46
+ !Number.isFinite(end)) {
47
+ continue;
48
+ }
49
+ chunks.push({
50
+ key: object.key,
51
+ start,
52
+ end,
53
+ });
54
+ maxSequence =
55
+ Math.max(maxSequence, end);
56
+ }
57
+ chunks.sort((left, right) => left.start -
58
+ right.start);
59
+ return {
60
+ chunks,
61
+ maxSequence,
62
+ };
63
+ }
64
+ split(events) {
65
+ const result = [];
66
+ let current = [];
67
+ let bytes = 0;
68
+ for (const event of events) {
69
+ const lineBytes = Buffer.byteLength(JSON.stringify(event) + "\n", "utf8");
70
+ if (current.length >
71
+ 0 &&
72
+ (current.length >=
73
+ this
74
+ .maxEventsPerChunk ||
75
+ bytes +
76
+ lineBytes >
77
+ this
78
+ .maxChunkBytes)) {
79
+ result.push(current);
80
+ current =
81
+ [];
82
+ bytes =
83
+ 0;
84
+ }
85
+ current.push(event);
86
+ bytes +=
87
+ lineBytes;
88
+ }
89
+ if (current.length >
90
+ 0) {
91
+ result.push(current);
92
+ }
93
+ return result;
94
+ }
95
+ async loadManifest(identity) {
96
+ return this.getJson(`${identity.remotePrefix}/session.json`);
97
+ }
98
+ async loadCursor(identity) {
99
+ return this.getJson(`${identity.remotePrefix}/cursor.json`);
100
+ }
101
+ async recover(identity) {
102
+ const scan = await this.scan(identity);
103
+ return {
104
+ maxSequence: scan.maxSequence,
105
+ chunkCount: scan.chunks.length,
106
+ };
107
+ }
108
+ async append(identity, events, sourceCursors, options = {}) {
109
+ const previousManifest = await this.loadManifest(identity);
110
+ const before = await this.scan(identity);
111
+ /*
112
+ * Crash recovery:
113
+ * chunks may already exist even when cursor/session.json
114
+ * was not successfully updated.
115
+ */
116
+ const pending = events.filter(event => event.sequence >
117
+ before.maxSequence);
118
+ let uploadedEvents = 0;
119
+ for (const chunk of this.split(pending)) {
120
+ const first = chunk[0];
121
+ const last = chunk[chunk.length -
122
+ 1];
123
+ const content = chunk
124
+ .map(event => JSON.stringify(event))
125
+ .join("\n") +
126
+ "\n";
127
+ const digest = sha256(content).slice(0, 16);
128
+ const key = [
129
+ identity
130
+ .remotePrefix,
131
+ "events",
132
+ `${padSequence(first.sequence)}-${padSequence(last.sequence)}-${digest}.jsonl`,
133
+ ].join("/");
134
+ if (!await this.storage
135
+ .exists(key)) {
136
+ await this.storage.put(key, content, "application/x-ndjson");
137
+ }
138
+ uploadedEvents +=
139
+ chunk.length;
140
+ }
141
+ const after = await this.scan(identity);
142
+ const lastInput = events[events.length -
143
+ 1];
144
+ let status = previousManifest
145
+ ?.status ??
146
+ "active";
147
+ if ((lastInput
148
+ ?.type ===
149
+ "session_end" ||
150
+ lastInput
151
+ ?.type ===
152
+ "session_idle")) {
153
+ status =
154
+ "idle";
155
+ }
156
+ else if (lastInput
157
+ ?.type ===
158
+ "error") {
159
+ status =
160
+ "error";
161
+ }
162
+ else if (events.length >
163
+ 0) {
164
+ status =
165
+ "active";
166
+ }
167
+ const now = new Date()
168
+ .toISOString();
169
+ const firstInput = events[0];
170
+ const manifest = {
171
+ version: 1,
172
+ projectId: identity.projectId,
173
+ projectName: identity
174
+ .projectName,
175
+ agent: identity.agent,
176
+ nativeSessionId: identity
177
+ .nativeSessionId,
178
+ sessionKey: identity.sessionKey,
179
+ status,
180
+ createdAt: previousManifest
181
+ ?.createdAt ??
182
+ firstInput
183
+ ?.timestamp ??
184
+ now,
185
+ updatedAt: lastInput
186
+ ?.timestamp ??
187
+ now,
188
+ firstEventAt: previousManifest
189
+ ?.firstEventAt ??
190
+ firstInput
191
+ ?.timestamp,
192
+ lastEventAt: lastInput
193
+ ?.timestamp ??
194
+ previousManifest
195
+ ?.lastEventAt,
196
+ eventCount: after.maxSequence,
197
+ chunkCount: after.chunks.length,
198
+ metadata: {
199
+ ...previousManifest
200
+ ?.metadata,
201
+ ...options.metadata,
202
+ },
203
+ };
204
+ if (options.title ??
205
+ previousManifest
206
+ ?.title) {
207
+ manifest.title =
208
+ options.title ??
209
+ previousManifest
210
+ ?.title;
211
+ }
212
+ const cursor = {
213
+ version: 1,
214
+ projectId: identity.projectId,
215
+ agent: identity.agent,
216
+ nativeSessionId: identity
217
+ .nativeSessionId,
218
+ lastLocalSequence: events.length >
219
+ 0
220
+ ? events[events.length -
221
+ 1].sequence
222
+ : after
223
+ .maxSequence,
224
+ lastRemoteSequence: after.maxSequence,
225
+ sourceCursors,
226
+ updatedAt: now,
227
+ };
228
+ /*
229
+ * Chunk first, then cursor, then manifest.
230
+ * Immutable chunks are the source of truth.
231
+ */
232
+ await this.putJson(`${identity.remotePrefix}/cursor.json`, cursor);
233
+ await this.putJson(`${identity.remotePrefix}/session.json`, manifest);
234
+ return {
235
+ uploadedEvents,
236
+ lastRemoteSequence: after.maxSequence,
237
+ eventCount: manifest.eventCount,
238
+ chunkCount: manifest.chunkCount,
239
+ status,
240
+ };
241
+ }
242
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,47 @@
1
+ import { createHash, } from "node:crypto";
2
+ import { dirname, } from "node:path";
3
+ import { mkdirSync, readFileSync, renameSync, writeFileSync, } from "node:fs";
4
+ export function sha256(value) {
5
+ return createHash("sha256")
6
+ .update(value)
7
+ .digest("hex");
8
+ }
9
+ function normalize(value) {
10
+ if (Array.isArray(value)) {
11
+ return value.map(normalize);
12
+ }
13
+ if (value &&
14
+ typeof value ===
15
+ "object") {
16
+ const source = value;
17
+ const result = {};
18
+ for (const key of Object.keys(source).sort()) {
19
+ result[key] =
20
+ normalize(source[key]);
21
+ }
22
+ return result;
23
+ }
24
+ return value;
25
+ }
26
+ export function stableStringify(value) {
27
+ return JSON.stringify(normalize(value));
28
+ }
29
+ export function readJsonFile(filePath) {
30
+ try {
31
+ return JSON.parse(readFileSync(filePath, "utf8"));
32
+ }
33
+ catch {
34
+ return null;
35
+ }
36
+ }
37
+ export function writeJsonAtomic(filePath, value) {
38
+ mkdirSync(dirname(filePath), {
39
+ recursive: true,
40
+ });
41
+ const temporary = `${filePath}.${process.pid}.tmp`;
42
+ writeFileSync(temporary, JSON.stringify(value, null, 2) + "\n", {
43
+ encoding: "utf8",
44
+ mode: 0o600,
45
+ });
46
+ renameSync(temporary, filePath);
47
+ }