gnosys 5.12.0 → 5.12.2

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 (72) hide show
  1. package/dist/cli.js +48 -7
  2. package/dist/index.js +179 -10
  3. package/dist/lib/addCommand.js +0 -1
  4. package/dist/lib/archive.js +0 -2
  5. package/dist/lib/askCommand.js +1 -1
  6. package/dist/lib/attachCommand.d.ts +17 -0
  7. package/dist/lib/attachCommand.js +66 -0
  8. package/dist/lib/attachments.d.ts +43 -2
  9. package/dist/lib/attachments.js +81 -2
  10. package/dist/lib/chat/choose.js +2 -2
  11. package/dist/lib/clientReadOverlay.js +3 -0
  12. package/dist/lib/config.d.ts +1 -48
  13. package/dist/lib/configCommand.js +2 -2
  14. package/dist/lib/db.d.ts +16 -1
  15. package/dist/lib/db.js +216 -119
  16. package/dist/lib/dbWrite.d.ts +1 -1
  17. package/dist/lib/dearchiveCommand.js +1 -1
  18. package/dist/lib/docxExtract.js +1 -1
  19. package/dist/lib/dream.d.ts +8 -0
  20. package/dist/lib/dream.js +35 -1
  21. package/dist/lib/dreamLogCommand.js +1 -1
  22. package/dist/lib/dreamRunLog.d.ts +1 -1
  23. package/dist/lib/dreamRunLog.js +26 -4
  24. package/dist/lib/embeddings.js +0 -3
  25. package/dist/lib/exportProject.d.ts +3 -2
  26. package/dist/lib/exportProject.js +2 -1
  27. package/dist/lib/federated.js +1 -1
  28. package/dist/lib/hybridSearchCommand.js +1 -1
  29. package/dist/lib/importProject.js +2 -1
  30. package/dist/lib/llm.js +1 -1
  31. package/dist/lib/lock.d.ts +1 -1
  32. package/dist/lib/lock.js +5 -3
  33. package/dist/lib/migrate.js +0 -1
  34. package/dist/lib/multimodalIngest.js +1 -1
  35. package/dist/lib/platform.d.ts +0 -6
  36. package/dist/lib/platform.js +0 -28
  37. package/dist/lib/readCommand.js +11 -10
  38. package/dist/lib/remoteWizard.d.ts +1 -1
  39. package/dist/lib/remoteWizard.js +4 -4
  40. package/dist/lib/rulesGen.d.ts +8 -0
  41. package/dist/lib/rulesGen.js +16 -0
  42. package/dist/lib/search.d.ts +0 -2
  43. package/dist/lib/search.js +0 -7
  44. package/dist/lib/semanticSearchCommand.js +1 -1
  45. package/dist/lib/setup/sections/providers.js +56 -4
  46. package/dist/lib/setup/sections/routing.js +42 -5
  47. package/dist/lib/setup/sections/taskRoutingEditor.d.ts +1 -5
  48. package/dist/lib/setup/sections/taskRoutingEditor.js +0 -10
  49. package/dist/lib/setup/ui/header.js +0 -1
  50. package/dist/lib/setup/ui/status.d.ts +0 -1
  51. package/dist/lib/setup/ui/status.js +0 -2
  52. package/dist/lib/setup.d.ts +0 -15
  53. package/dist/lib/setup.js +13 -158
  54. package/dist/lib/staleCommand.js +2 -2
  55. package/dist/lib/syncClient.d.ts +0 -6
  56. package/dist/lib/syncClient.js +36 -14
  57. package/dist/lib/syncDoctorCommand.js +2 -2
  58. package/dist/lib/syncIngest.d.ts +11 -0
  59. package/dist/lib/syncIngest.js +24 -1
  60. package/dist/lib/syncIngestStartup.js +2 -2
  61. package/dist/lib/syncSnapshot.d.ts +2 -0
  62. package/dist/lib/syncSnapshot.js +4 -0
  63. package/dist/lib/syncStaging.d.ts +0 -2
  64. package/dist/lib/syncStaging.js +0 -2
  65. package/dist/lib/updateCommand.js +1 -1
  66. package/dist/lib/webBuildCommand.js +1 -1
  67. package/dist/lib/webIndex.js +0 -1
  68. package/dist/lib/webIngestCommand.js +1 -1
  69. package/dist/sandbox/client.js +1 -1
  70. package/dist/sandbox/manager.js +1 -14
  71. package/dist/sandbox/server.js +3 -5
  72. package/package.json +5 -2
@@ -1,8 +1,13 @@
1
1
  /**
2
2
  * Gnosys Attachments — File attachment management for multimodal ingestion.
3
3
  *
4
- * Stores binary files in .gnosys/attachments/<uuid>.<ext> with a JSON manifest
5
- * at .gnosys/attachments/attachments.json for tracking metadata and memory links.
4
+ * Two storage modes:
5
+ * - Filesystem (legacy, large media): bytes copied to .gnosys/attachments/<uuid>.<ext>
6
+ * with a JSON manifest. Does NOT travel between machines.
7
+ * - Inline DB blob (v5.12, small assets): bytes stored in the memory row's
8
+ * attachment_data column. Travels over the same row-copy sync rail as
9
+ * embeddings, so it works single-machine, multi-machine, and with a future
10
+ * dockerized MCP without any shared volume.
6
11
  */
7
12
  import * as fs from "fs/promises";
8
13
  import * as path from "path";
@@ -37,6 +42,10 @@ const MIME_MAP = {
37
42
  function mimeFromExtension(ext) {
38
43
  return MIME_MAP[ext.toLowerCase()] || "application/octet-stream";
39
44
  }
45
+ /** Infer a MIME type from a file path's extension. */
46
+ export function inferMimeType(filePath) {
47
+ return mimeFromExtension(path.extname(filePath).slice(1));
48
+ }
40
49
  // ─── Helpers ────────────────────────────────────────────────────────────
41
50
  function getAttachmentsDir(storePath) {
42
51
  return path.join(storePath, "attachments");
@@ -151,3 +160,73 @@ export async function linkMemoryToAttachment(storePath, uuid, memoryId) {
151
160
  await writeManifest(storePath, manifest);
152
161
  }
153
162
  }
163
+ // ─── Inline DB-blob attachments (v5.12) ─────────────────────────────────
164
+ //
165
+ // Small binary assets (logos, diagrams, screenshots) are stored directly in
166
+ // the memory row's attachment_data column. Because remote sync copies whole
167
+ // rows (the same way it already moves the `embedding` blob), these attachments
168
+ // travel machine-to-machine for free and need no shared filesystem — which is
169
+ // exactly what a dockerized MCP server needs.
170
+ /**
171
+ * Maximum size for an inline DB-blob attachment. Larger files should use the
172
+ * filesystem path (`gnosys ingest`) so the synced central DB stays lean.
173
+ */
174
+ export const MAX_INLINE_ATTACHMENT_BYTES = 10 * 1024 * 1024; // 10 MB
175
+ /**
176
+ * Read a file and store its bytes inline on a memory row (attachment_data).
177
+ * Enforces the size cap and skips the write if the same bytes are already
178
+ * attached (content-hash dedup). Bumps `modified` so remote sync picks it up.
179
+ */
180
+ export async function attachFileToMemory(db, memoryId, filePath) {
181
+ const mem = db.getMemory(memoryId);
182
+ if (!mem) {
183
+ throw new Error(`Memory not found: ${memoryId}`);
184
+ }
185
+ const stat = await fs.stat(filePath);
186
+ if (stat.size > MAX_INLINE_ATTACHMENT_BYTES) {
187
+ const limitMb = (MAX_INLINE_ATTACHMENT_BYTES / (1024 * 1024)).toFixed(0);
188
+ const sizeMb = (stat.size / (1024 * 1024)).toFixed(1);
189
+ throw new Error(`File is ${sizeMb}MB, which exceeds the ${limitMb}MB inline-attachment limit. ` +
190
+ `Use 'gnosys ingest' for large media (it stores the file on disk instead).`);
191
+ }
192
+ const data = await fs.readFile(filePath);
193
+ const name = path.basename(filePath);
194
+ const mime = inferMimeType(filePath);
195
+ // Dedup: if the same bytes are already attached, skip the write.
196
+ if (mem.attachment_data && Buffer.from(mem.attachment_data).equals(data)) {
197
+ return { memoryId, name, mime, sizeBytes: data.length, unchanged: true };
198
+ }
199
+ db.updateMemory(memoryId, {
200
+ attachment_data: data,
201
+ attachment_mime: mime,
202
+ attachment_name: name,
203
+ modified: new Date().toISOString(),
204
+ });
205
+ return { memoryId, name, mime, sizeBytes: data.length, unchanged: false };
206
+ }
207
+ /** Return the inline attachment stored on a memory row, or null if none. */
208
+ export function getMemoryAttachment(db, memoryId) {
209
+ const mem = db.getMemory(memoryId);
210
+ if (!mem?.attachment_data)
211
+ return null;
212
+ const data = Buffer.from(mem.attachment_data);
213
+ return {
214
+ data,
215
+ mime: mem.attachment_mime || "application/octet-stream",
216
+ name: mem.attachment_name || `${memoryId}.bin`,
217
+ sizeBytes: data.length,
218
+ };
219
+ }
220
+ /** Remove an inline attachment from a memory row (keeps the memory itself). */
221
+ export function detachFromMemory(db, memoryId) {
222
+ const mem = db.getMemory(memoryId);
223
+ if (!mem?.attachment_data)
224
+ return false;
225
+ db.updateMemory(memoryId, {
226
+ attachment_data: null,
227
+ attachment_mime: null,
228
+ attachment_name: null,
229
+ modified: new Date().toISOString(),
230
+ });
231
+ return true;
232
+ }
@@ -115,7 +115,7 @@ export function parseChooseYaml(yaml) {
115
115
  const itemStart = line.match(/^\s*-\s*id:\s*(.+?)\s*$/);
116
116
  if (itemStart) {
117
117
  // Push previous item if any
118
- if (current && current.label)
118
+ if (current?.label)
119
119
  options.push(current);
120
120
  current = { id: itemStart[1], label: "" };
121
121
  continue;
@@ -133,7 +133,7 @@ export function parseChooseYaml(yaml) {
133
133
  }
134
134
  }
135
135
  // Flush the last option
136
- if (current && current.label)
136
+ if (current?.label)
137
137
  options.push(current);
138
138
  return { prompt, options };
139
139
  }
@@ -27,6 +27,9 @@ export function pendingAddToDbMemory(p) {
27
27
  source_file: null,
28
28
  source_page: null,
29
29
  source_timerange: null,
30
+ attachment_data: null,
31
+ attachment_mime: null,
32
+ attachment_name: null,
30
33
  project_id: p.project_id,
31
34
  scope: p.scope,
32
35
  };
@@ -3,7 +3,6 @@
3
3
  * Uses Zod for schema validation with sensible defaults.
4
4
  */
5
5
  import { z } from "zod";
6
- export type { LlmTaskName } from "./apiKeyVault.js";
7
6
  declare const LLMProviderEnum: z.ZodEnum<{
8
7
  anthropic: "anthropic";
9
8
  ollama: "ollama";
@@ -52,53 +51,6 @@ declare const RecallConfigSchema: z.ZodObject<{
52
51
  minRelevance: z.ZodDefault<z.ZodNumber>;
53
52
  }, z.core.$strip>;
54
53
  export type RecallConfig = z.infer<typeof RecallConfigSchema>;
55
- declare const ChatConfigSchema: z.ZodObject<{
56
- toolsEnabled: z.ZodDefault<z.ZodBoolean>;
57
- autoSummarizeAfterTurns: z.ZodDefault<z.ZodNumber>;
58
- systemPromptPrefix: z.ZodDefault<z.ZodString>;
59
- }, z.core.$strip>;
60
- export type ChatConfig = z.infer<typeof ChatConfigSchema>;
61
- declare const MultimodalConfigSchema: z.ZodObject<{
62
- transcriptionProvider: z.ZodDefault<z.ZodEnum<{
63
- groq: "groq";
64
- openai: "openai";
65
- local: "local";
66
- }>>;
67
- whisperModel: z.ZodDefault<z.ZodString>;
68
- visionProvider: z.ZodOptional<z.ZodEnum<{
69
- anthropic: "anthropic";
70
- ollama: "ollama";
71
- groq: "groq";
72
- openai: "openai";
73
- lmstudio: "lmstudio";
74
- xai: "xai";
75
- mistral: "mistral";
76
- openrouter: "openrouter";
77
- custom: "custom";
78
- }>>;
79
- visionModel: z.ZodOptional<z.ZodString>;
80
- chunkSize: z.ZodDefault<z.ZodNumber>;
81
- maxFileSizeMb: z.ZodDefault<z.ZodNumber>;
82
- }, z.core.$strip>;
83
- export type MultimodalConfig = z.infer<typeof MultimodalConfigSchema>;
84
- declare const WebConfigSchema: z.ZodObject<{
85
- source: z.ZodDefault<z.ZodEnum<{
86
- sitemap: "sitemap";
87
- directory: "directory";
88
- urls: "urls";
89
- }>>;
90
- sitemapUrl: z.ZodOptional<z.ZodString>;
91
- contentDir: z.ZodOptional<z.ZodString>;
92
- urls: z.ZodOptional<z.ZodArray<z.ZodString>>;
93
- outputDir: z.ZodDefault<z.ZodString>;
94
- exclude: z.ZodDefault<z.ZodArray<z.ZodString>>;
95
- categories: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodString>>;
96
- llmEnrich: z.ZodDefault<z.ZodBoolean>;
97
- prune: z.ZodDefault<z.ZodBoolean>;
98
- concurrency: z.ZodDefault<z.ZodNumber>;
99
- crawlDelayMs: z.ZodDefault<z.ZodNumber>;
100
- }, z.core.$strip>;
101
- export type WebConfig = z.infer<typeof WebConfigSchema>;
102
54
  export declare const GnosysConfigSchema: z.ZodObject<{
103
55
  llm: z.ZodDefault<z.ZodObject<{
104
56
  defaultProvider: z.ZodDefault<z.ZodEnum<{
@@ -422,3 +374,4 @@ export declare function generateConfigTemplate(): string;
422
374
  * All supported provider names.
423
375
  */
424
376
  export declare const ALL_PROVIDERS: LLMProviderName[];
377
+ export {};
@@ -243,7 +243,7 @@ export async function runConfigSetCommand(getResolver, key, value, extra) {
243
243
  break;
244
244
  case "maxMemories": {
245
245
  const n = parseInt(recallValue, 10);
246
- if (isNaN(n) || n < 1 || n > 20) {
246
+ if (Number.isNaN(n) || n < 1 || n > 20) {
247
247
  printStatus("fail", "maxMemories must be between 1 and 20");
248
248
  process.exit(1);
249
249
  }
@@ -253,7 +253,7 @@ export async function runConfigSetCommand(getResolver, key, value, extra) {
253
253
  }
254
254
  case "minRelevance": {
255
255
  const f = parseFloat(recallValue);
256
- if (isNaN(f) || f < 0 || f > 1) {
256
+ if (Number.isNaN(f) || f < 0 || f > 1) {
257
257
  printStatus("fail", "minRelevance must be between 0 and 1");
258
258
  process.exit(1);
259
259
  }
package/dist/lib/db.d.ts CHANGED
@@ -32,6 +32,9 @@ export interface DbMemory {
32
32
  source_file: string | null;
33
33
  source_page: string | null;
34
34
  source_timerange: string | null;
35
+ attachment_data: Buffer | null;
36
+ attachment_mime: string | null;
37
+ attachment_name: string | null;
35
38
  project_id: string | null;
36
39
  scope: string;
37
40
  }
@@ -96,6 +99,9 @@ export interface MigrationStats {
96
99
  declare function fnv1a(str: string): string;
97
100
  export declare class GnosysDB {
98
101
  private db;
102
+ /** v5.12.x perf: prepared-statement cache, keyed by SQL. Invalidated on
103
+ * reopen()/close() — statements are bound to their connection handle. */
104
+ private stmtCache;
99
105
  private storePath;
100
106
  private available;
101
107
  private dbFilePath;
@@ -175,6 +181,12 @@ export declare class GnosysDB {
175
181
  * Read methods are also wrapped because reads against stale pages can
176
182
  * surface the same error.
177
183
  */
184
+ /**
185
+ * Prepare-with-cache for fixed-SQL hot paths. Dynamic SQL (e.g. the
186
+ * field-built UPDATE in updateMemory) must keep using db.prepare directly
187
+ * so the cache stays bounded. Cache is invalidated on reopen()/close().
188
+ */
189
+ private prep;
178
190
  private withRecovery;
179
191
  /**
180
192
  * Restore from a backup file. Closes current DB, copies backup over, re-opens.
@@ -189,11 +201,14 @@ export declare class GnosysDB {
189
201
  isAvailable(): boolean;
190
202
  getDbPath(): string;
191
203
  getStorePath(): string;
192
- insertMemory(mem: Omit<DbMemory, "embedding" | "source_file" | "source_page" | "source_timerange"> & {
204
+ insertMemory(mem: Omit<DbMemory, "embedding" | "source_file" | "source_page" | "source_timerange" | "attachment_data" | "attachment_mime" | "attachment_name"> & {
193
205
  embedding?: Buffer | null;
194
206
  source_file?: string | null;
195
207
  source_page?: string | null;
196
208
  source_timerange?: string | null;
209
+ attachment_data?: Buffer | null;
210
+ attachment_mime?: string | null;
211
+ attachment_name?: string | null;
197
212
  }): void;
198
213
  getMemory(id: string): DbMemory | null;
199
214
  getActiveMemories(): DbMemory[];