gnosys 5.12.0 → 5.12.3
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/dist/cli.js +48 -7
- package/dist/index.js +199 -14
- package/dist/lib/addCommand.js +0 -1
- package/dist/lib/archive.js +10 -6
- package/dist/lib/askCommand.js +1 -1
- package/dist/lib/attachCommand.d.ts +17 -0
- package/dist/lib/attachCommand.js +66 -0
- package/dist/lib/attachments.d.ts +43 -2
- package/dist/lib/attachments.js +81 -2
- package/dist/lib/chat/choose.js +2 -2
- package/dist/lib/clientReadOverlay.js +3 -0
- package/dist/lib/config.d.ts +1 -48
- package/dist/lib/configCommand.js +2 -2
- package/dist/lib/db.d.ts +16 -1
- package/dist/lib/db.js +248 -133
- package/dist/lib/dbWrite.d.ts +1 -1
- package/dist/lib/dearchiveCommand.js +1 -1
- package/dist/lib/docxExtract.js +1 -1
- package/dist/lib/dream.d.ts +8 -0
- package/dist/lib/dream.js +35 -1
- package/dist/lib/dreamLogCommand.js +1 -1
- package/dist/lib/dreamRunLog.d.ts +1 -1
- package/dist/lib/dreamRunLog.js +26 -4
- package/dist/lib/embeddings.js +0 -3
- package/dist/lib/exportProject.d.ts +3 -2
- package/dist/lib/exportProject.js +2 -1
- package/dist/lib/federated.js +1 -1
- package/dist/lib/ftsQuery.d.ts +25 -0
- package/dist/lib/ftsQuery.js +42 -0
- package/dist/lib/hybridSearch.d.ts +9 -0
- package/dist/lib/hybridSearch.js +14 -0
- package/dist/lib/hybridSearchCommand.js +6 -1
- package/dist/lib/importProject.js +2 -1
- package/dist/lib/llm.js +1 -1
- package/dist/lib/lock.d.ts +1 -1
- package/dist/lib/lock.js +5 -3
- package/dist/lib/migrate.js +0 -1
- package/dist/lib/multimodalIngest.js +1 -1
- package/dist/lib/platform.d.ts +0 -6
- package/dist/lib/platform.js +0 -28
- package/dist/lib/readCommand.js +11 -10
- package/dist/lib/remoteWizard.d.ts +1 -1
- package/dist/lib/remoteWizard.js +4 -4
- package/dist/lib/resolver.d.ts +17 -0
- package/dist/lib/resolver.js +43 -17
- package/dist/lib/rulesGen.d.ts +8 -0
- package/dist/lib/rulesGen.js +16 -0
- package/dist/lib/search.d.ts +0 -2
- package/dist/lib/search.js +33 -27
- package/dist/lib/semanticSearchCommand.js +1 -1
- package/dist/lib/setup/sections/providers.js +56 -4
- package/dist/lib/setup/sections/routing.js +42 -5
- package/dist/lib/setup/sections/taskRoutingEditor.d.ts +1 -5
- package/dist/lib/setup/sections/taskRoutingEditor.js +0 -10
- package/dist/lib/setup/ui/header.js +0 -1
- package/dist/lib/setup/ui/status.d.ts +0 -1
- package/dist/lib/setup/ui/status.js +0 -2
- package/dist/lib/setup.d.ts +0 -15
- package/dist/lib/setup.js +13 -158
- package/dist/lib/staleCommand.js +2 -2
- package/dist/lib/syncClient.d.ts +0 -6
- package/dist/lib/syncClient.js +36 -14
- package/dist/lib/syncDoctorCommand.js +2 -2
- package/dist/lib/syncIngest.d.ts +11 -0
- package/dist/lib/syncIngest.js +24 -1
- package/dist/lib/syncIngestStartup.js +2 -2
- package/dist/lib/syncSnapshot.d.ts +2 -0
- package/dist/lib/syncSnapshot.js +4 -0
- package/dist/lib/syncStaging.d.ts +0 -2
- package/dist/lib/syncStaging.js +0 -2
- package/dist/lib/updateCommand.js +1 -1
- package/dist/lib/webBuildCommand.js +1 -1
- package/dist/lib/webIndex.js +0 -1
- package/dist/lib/webIngestCommand.js +1 -1
- package/dist/sandbox/client.js +1 -1
- package/dist/sandbox/manager.js +1 -14
- package/dist/sandbox/server.js +3 -5
- package/package.json +5 -2
package/dist/lib/docxExtract.js
CHANGED
|
@@ -30,7 +30,7 @@ export async function extractDocxText(filePath) {
|
|
|
30
30
|
await assertDocxDecompressedSizeWithinLimit(buffer);
|
|
31
31
|
const result = await mammoth.convertToHtml({ buffer });
|
|
32
32
|
const html = result.value;
|
|
33
|
-
if (!html
|
|
33
|
+
if (!html?.trim()) {
|
|
34
34
|
return [];
|
|
35
35
|
}
|
|
36
36
|
// Convert HTML to markdown
|
package/dist/lib/dream.d.ts
CHANGED
|
@@ -107,6 +107,14 @@ export declare class GnosysDreamEngine {
|
|
|
107
107
|
private providerInitError;
|
|
108
108
|
private createPhase;
|
|
109
109
|
private finishPhase;
|
|
110
|
+
/**
|
|
111
|
+
* v5.12.1 crash safety: persist analyzed fingerprints at every phase
|
|
112
|
+
* boundary, not only in finalize(). A crash mid-run previously lost all
|
|
113
|
+
* pendingFingerprints, so the next run re-analyzed (and re-paid for) the
|
|
114
|
+
* same memory sets and could double-create summaries. Checkpointing only
|
|
115
|
+
* merges fingerprints — lastRunAt / watermarks remain finalize()'s job.
|
|
116
|
+
*/
|
|
117
|
+
private checkpointFingerprints;
|
|
110
118
|
private addTouched;
|
|
111
119
|
private recordLLMSkip;
|
|
112
120
|
private llmCalls;
|
package/dist/lib/dream.js
CHANGED
|
@@ -23,7 +23,7 @@ import { createProvider } from "./llm.js";
|
|
|
23
23
|
import { notifyDesktop } from "./desktopNotify.js";
|
|
24
24
|
import { syncConfidenceToDb, auditToDb } from "./dbWrite.js";
|
|
25
25
|
import { logError } from "./log.js";
|
|
26
|
-
import { estimateCost, estimateTokens, fingerprintMemories, memoryWatermark, readDreamState, writeDreamState, } from "./dreamRunLog.js";
|
|
26
|
+
import { estimateCost, acquireDreamLock, estimateTokens, fingerprintMemories, memoryWatermark, readDreamState, writeDreamState, } from "./dreamRunLog.js";
|
|
27
27
|
/** Layer 4 alert threshold: fire desktop notification at this many consecutive provider failures. */
|
|
28
28
|
const DREAM_FAILURE_NOTIFY_THRESHOLD = 3;
|
|
29
29
|
export const DEFAULT_DREAM_CONFIG = {
|
|
@@ -109,6 +109,30 @@ export class GnosysDreamEngine {
|
|
|
109
109
|
phase.durationMs = Date.now() - startedAtMs;
|
|
110
110
|
phase.memoryIdsTouched = Array.from(new Set(phase.memoryIdsTouched));
|
|
111
111
|
phase.estimatedCostUsd = Math.round(phase.estimatedCostUsd * 1_000_000) / 1_000_000;
|
|
112
|
+
this.checkpointFingerprints();
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* v5.12.1 crash safety: persist analyzed fingerprints at every phase
|
|
116
|
+
* boundary, not only in finalize(). A crash mid-run previously lost all
|
|
117
|
+
* pendingFingerprints, so the next run re-analyzed (and re-paid for) the
|
|
118
|
+
* same memory sets and could double-create summaries. Checkpointing only
|
|
119
|
+
* merges fingerprints — lastRunAt / watermarks remain finalize()'s job.
|
|
120
|
+
*/
|
|
121
|
+
checkpointFingerprints() {
|
|
122
|
+
if (Object.keys(this.pendingFingerprints).length === 0)
|
|
123
|
+
return;
|
|
124
|
+
try {
|
|
125
|
+
writeDreamState({
|
|
126
|
+
...this.dreamState,
|
|
127
|
+
analyzedFingerprints: {
|
|
128
|
+
...this.dreamState.analyzedFingerprints,
|
|
129
|
+
...this.pendingFingerprints,
|
|
130
|
+
},
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
// Best-effort: a failed checkpoint only costs re-analysis on resume.
|
|
135
|
+
}
|
|
112
136
|
}
|
|
113
137
|
addTouched(phase, memoryIds) {
|
|
114
138
|
for (const id of memoryIds) {
|
|
@@ -986,6 +1010,15 @@ export class DreamScheduler {
|
|
|
986
1010
|
const idleMs = Date.now() - this.lastActivity;
|
|
987
1011
|
const idleMinutes = idleMs / 60_000;
|
|
988
1012
|
if (idleMinutes >= this.config.idleMinutes) {
|
|
1013
|
+
// v5.12.1: the in-memory `running` flag does not survive a sandbox
|
|
1014
|
+
// restart and cannot see a concurrent manual `gnosys dream`. Tie the
|
|
1015
|
+
// scheduler to the same cross-process file lock the CLI uses.
|
|
1016
|
+
const lock = acquireDreamLock();
|
|
1017
|
+
if (!lock.acquired) {
|
|
1018
|
+
console.error(`[dream] scheduler skipped: ${lock.reason}`);
|
|
1019
|
+
this.lastActivity = Date.now(); // back off a full idle window
|
|
1020
|
+
return;
|
|
1021
|
+
}
|
|
989
1022
|
this.running = true;
|
|
990
1023
|
try {
|
|
991
1024
|
this.currentDream = this.engine.dream((phase, detail) => {
|
|
@@ -1002,6 +1035,7 @@ export class DreamScheduler {
|
|
|
1002
1035
|
this.running = false;
|
|
1003
1036
|
this.currentDream = null;
|
|
1004
1037
|
this.lastActivity = Date.now(); // Reset idle timer after dream
|
|
1038
|
+
lock.release();
|
|
1005
1039
|
}
|
|
1006
1040
|
}
|
|
1007
1041
|
}
|
|
@@ -8,7 +8,7 @@ export async function runDreamLogCommand(opts, context = {}) {
|
|
|
8
8
|
process.exitCode = 1;
|
|
9
9
|
return;
|
|
10
10
|
}
|
|
11
|
-
const limit = Math.max(1, parseInt(opts.last) || 20);
|
|
11
|
+
const limit = Math.max(1, parseInt(opts.last, 10) || 20);
|
|
12
12
|
const sinceIso = opts.since ? `${opts.since}T00:00:00Z` : undefined;
|
|
13
13
|
const runs = centralDb.getRecentDreamRuns(limit, {
|
|
14
14
|
failuresOnly: !!opts.failuresOnly,
|
|
@@ -88,7 +88,7 @@ export interface DreamReadOptions {
|
|
|
88
88
|
export declare function getDreamRunsPath(): string;
|
|
89
89
|
export declare function getDreamStatePath(): string;
|
|
90
90
|
export declare function getDreamLockPath(): string;
|
|
91
|
-
export declare function acquireDreamLock(): {
|
|
91
|
+
export declare function acquireDreamLock(depth?: number): {
|
|
92
92
|
acquired: true;
|
|
93
93
|
release: () => void;
|
|
94
94
|
} | {
|
package/dist/lib/dreamRunLog.js
CHANGED
|
@@ -26,7 +26,7 @@ export function getDreamStatePath() {
|
|
|
26
26
|
export function getDreamLockPath() {
|
|
27
27
|
return path.join(getGnosysHome(), "dream.lock");
|
|
28
28
|
}
|
|
29
|
-
export function acquireDreamLock() {
|
|
29
|
+
export function acquireDreamLock(depth = 0) {
|
|
30
30
|
const lockPath = getDreamLockPath();
|
|
31
31
|
fs.mkdirSync(path.dirname(lockPath), { recursive: true });
|
|
32
32
|
try {
|
|
@@ -46,17 +46,39 @@ export function acquireDreamLock() {
|
|
|
46
46
|
};
|
|
47
47
|
}
|
|
48
48
|
catch {
|
|
49
|
+
// Bounded retries so stale-lock cleanup can't recurse forever under
|
|
50
|
+
// genuine contention (two processes racing to re-acquire).
|
|
51
|
+
if (depth >= 3) {
|
|
52
|
+
return { acquired: false, reason: "dream lock contention" };
|
|
53
|
+
}
|
|
54
|
+
let raw;
|
|
55
|
+
try {
|
|
56
|
+
raw = fs.readFileSync(lockPath, "utf8");
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
// Lock vanished between our create attempt and this read — retry.
|
|
60
|
+
return acquireDreamLock(depth + 1);
|
|
61
|
+
}
|
|
49
62
|
try {
|
|
50
|
-
const raw = fs.readFileSync(lockPath, "utf8");
|
|
51
63
|
const parsed = JSON.parse(raw);
|
|
52
64
|
if (parsed.pid && !isProcessRunning(parsed.pid)) {
|
|
53
65
|
fs.unlinkSync(lockPath);
|
|
54
|
-
return acquireDreamLock();
|
|
66
|
+
return acquireDreamLock(depth + 1);
|
|
55
67
|
}
|
|
56
68
|
return { acquired: false, reason: `dream already running (pid ${parsed.pid || "unknown"})` };
|
|
57
69
|
}
|
|
58
70
|
catch {
|
|
59
|
-
|
|
71
|
+
// v5.12.1: unreadable/corrupt lock (crash mid-write) previously blocked
|
|
72
|
+
// dreaming forever until manual deletion. Treat it as stale: the lock
|
|
73
|
+
// payload is a single tiny write, so a live owner with a corrupt lock
|
|
74
|
+
// is implausible.
|
|
75
|
+
try {
|
|
76
|
+
fs.unlinkSync(lockPath);
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
// ignore — next acquire retries
|
|
80
|
+
}
|
|
81
|
+
return acquireDreamLock(depth + 1);
|
|
60
82
|
}
|
|
61
83
|
}
|
|
62
84
|
}
|
package/dist/lib/embeddings.js
CHANGED
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
* Embeddings are stored in SQLite as regeneratable sidecar data.
|
|
6
6
|
*/
|
|
7
7
|
// Dynamic import — gracefully handles missing native module (dlopen failures)
|
|
8
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
9
8
|
let Database = null;
|
|
10
9
|
try {
|
|
11
10
|
Database = (await import("better-sqlite3")).default;
|
|
@@ -17,10 +16,8 @@ import path from "path";
|
|
|
17
16
|
import fs from "fs/promises";
|
|
18
17
|
import { enableWAL } from "./lock.js";
|
|
19
18
|
const MODEL_NAME = "Xenova/all-MiniLM-L6-v2";
|
|
20
|
-
const EMBEDDING_DIM = 384;
|
|
21
19
|
export class GnosysEmbeddings {
|
|
22
20
|
pipeline = null;
|
|
23
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
24
21
|
db = null;
|
|
25
22
|
storePath;
|
|
26
23
|
modelReady = false;
|
|
@@ -14,9 +14,10 @@ interface BundleManifest {
|
|
|
14
14
|
source_user: string;
|
|
15
15
|
gnosys_version: string;
|
|
16
16
|
}
|
|
17
|
-
/** A memory row with its
|
|
18
|
-
export interface PortableMemory extends Omit<DbMemory, "embedding"> {
|
|
17
|
+
/** A memory row with its binary columns base64-encoded for JSON transport. */
|
|
18
|
+
export interface PortableMemory extends Omit<DbMemory, "embedding" | "attachment_data"> {
|
|
19
19
|
embedding_b64: string | null;
|
|
20
|
+
attachment_data_b64: string | null;
|
|
20
21
|
}
|
|
21
22
|
export interface ProjectBundle {
|
|
22
23
|
manifest: BundleManifest;
|
|
@@ -34,10 +34,11 @@ export function exportProject(db, opts) {
|
|
|
34
34
|
const totalIncludingArchived = db.getMemoriesByProject(opts.projectId, true).length;
|
|
35
35
|
const archivedExcluded = opts.includeArchived ? 0 : totalIncludingArchived - rawMemories.length;
|
|
36
36
|
const memories = rawMemories.map((m) => {
|
|
37
|
-
const { embedding: _embedding, ...rest } = m;
|
|
37
|
+
const { embedding: _embedding, attachment_data: _attachment, ...rest } = m;
|
|
38
38
|
return {
|
|
39
39
|
...rest,
|
|
40
40
|
embedding_b64: m.embedding ? Buffer.from(m.embedding).toString("base64") : null,
|
|
41
|
+
attachment_data_b64: m.attachment_data ? Buffer.from(m.attachment_data).toString("base64") : null,
|
|
41
42
|
};
|
|
42
43
|
});
|
|
43
44
|
const memoryIds = rawMemories.map((m) => m.id);
|
package/dist/lib/federated.js
CHANGED
|
@@ -132,7 +132,7 @@ export function detectAmbiguity(db, query, opts) {
|
|
|
132
132
|
const projectHits = new Map();
|
|
133
133
|
for (const r of results) {
|
|
134
134
|
const mem = db.getMemory(r.id);
|
|
135
|
-
if (!mem
|
|
135
|
+
if (!mem?.project_id)
|
|
136
136
|
continue;
|
|
137
137
|
projectHits.set(mem.project_id, (projectHits.get(mem.project_id) || 0) + 1);
|
|
138
138
|
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FTS5 MATCH query construction — shared by the central-DB search (db.ts),
|
|
3
|
+
* the per-store search index (search.ts), and archive search (archive.ts).
|
|
4
|
+
*
|
|
5
|
+
* FTS5 treats space-separated bare terms as implicit AND, so the long
|
|
6
|
+
* descriptive queries the tool docs encourage ("auth JWT session tokens
|
|
7
|
+
* refresh") return zero results unless EVERY term matches. Callers use
|
|
8
|
+
* these helpers to try AND first (precision), then retry with OR
|
|
9
|
+
* (recall) when AND finds nothing — BM25 still ranks the best-covered
|
|
10
|
+
* memories first in the OR pass.
|
|
11
|
+
*
|
|
12
|
+
* Every term is emitted as a quoted phrase, which also makes previously
|
|
13
|
+
* syntax-error-prone input (hyphens, colons, FTS5 keywords like NOT)
|
|
14
|
+
* safe. A trailing `*` is preserved as an FTS5 prefix query (`"term"*`).
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* Split a raw query into sanitized terms. Drops quote characters and any
|
|
18
|
+
* token with no letters or digits (pure punctuation can't match anything
|
|
19
|
+
* under the unicode61 tokenizer).
|
|
20
|
+
*/
|
|
21
|
+
export declare function ftsTerms(query: string): string[];
|
|
22
|
+
/** Implicit-AND MATCH expression: all terms must match. */
|
|
23
|
+
export declare function ftsAndQuery(terms: string[]): string;
|
|
24
|
+
/** OR MATCH expression: any term may match; BM25 ranks fuller matches higher. */
|
|
25
|
+
export declare function ftsOrQuery(terms: string[]): string;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FTS5 MATCH query construction — shared by the central-DB search (db.ts),
|
|
3
|
+
* the per-store search index (search.ts), and archive search (archive.ts).
|
|
4
|
+
*
|
|
5
|
+
* FTS5 treats space-separated bare terms as implicit AND, so the long
|
|
6
|
+
* descriptive queries the tool docs encourage ("auth JWT session tokens
|
|
7
|
+
* refresh") return zero results unless EVERY term matches. Callers use
|
|
8
|
+
* these helpers to try AND first (precision), then retry with OR
|
|
9
|
+
* (recall) when AND finds nothing — BM25 still ranks the best-covered
|
|
10
|
+
* memories first in the OR pass.
|
|
11
|
+
*
|
|
12
|
+
* Every term is emitted as a quoted phrase, which also makes previously
|
|
13
|
+
* syntax-error-prone input (hyphens, colons, FTS5 keywords like NOT)
|
|
14
|
+
* safe. A trailing `*` is preserved as an FTS5 prefix query (`"term"*`).
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* Split a raw query into sanitized terms. Drops quote characters and any
|
|
18
|
+
* token with no letters or digits (pure punctuation can't match anything
|
|
19
|
+
* under the unicode61 tokenizer).
|
|
20
|
+
*/
|
|
21
|
+
export function ftsTerms(query) {
|
|
22
|
+
return query
|
|
23
|
+
.replace(/['"]/g, "")
|
|
24
|
+
.split(/\s+/)
|
|
25
|
+
.filter((t) => /[\p{L}\p{N}]/u.test(t));
|
|
26
|
+
}
|
|
27
|
+
/** Render one term as a safe FTS5 phrase, preserving trailing-`*` prefix queries. */
|
|
28
|
+
function ftsPhrase(term) {
|
|
29
|
+
const prefixMatch = term.match(/^(.*?)\*+$/);
|
|
30
|
+
if (prefixMatch && /[\p{L}\p{N}]/u.test(prefixMatch[1])) {
|
|
31
|
+
return `"${prefixMatch[1]}"*`;
|
|
32
|
+
}
|
|
33
|
+
return `"${term}"`;
|
|
34
|
+
}
|
|
35
|
+
/** Implicit-AND MATCH expression: all terms must match. */
|
|
36
|
+
export function ftsAndQuery(terms) {
|
|
37
|
+
return terms.map(ftsPhrase).join(" ");
|
|
38
|
+
}
|
|
39
|
+
/** OR MATCH expression: any term may match; BM25 ranks fuller matches higher. */
|
|
40
|
+
export function ftsOrQuery(terms) {
|
|
41
|
+
return terms.map(ftsPhrase).join(" OR ");
|
|
42
|
+
}
|
|
@@ -57,6 +57,15 @@ export declare class GnosysHybridSearch {
|
|
|
57
57
|
* Check if embeddings are available.
|
|
58
58
|
*/
|
|
59
59
|
hasEmbeddings(): boolean;
|
|
60
|
+
/**
|
|
61
|
+
* True when hybrid/semantic search can actually run its semantic leg.
|
|
62
|
+
* In DB mode this requires BOTH stored vectors in the central DB and the
|
|
63
|
+
* store-local embeddings.db used to embed the query text — the central
|
|
64
|
+
* count alone can be non-zero on a machine that only syncs gnosys.db,
|
|
65
|
+
* in which case hybridSearch() silently runs keyword-only (embedQuery
|
|
66
|
+
* is never constructed).
|
|
67
|
+
*/
|
|
68
|
+
canRunSemantic(): boolean;
|
|
60
69
|
/**
|
|
61
70
|
* Get embedding count.
|
|
62
71
|
*/
|
package/dist/lib/hybridSearch.js
CHANGED
|
@@ -284,6 +284,20 @@ export class GnosysHybridSearch {
|
|
|
284
284
|
return this.dbSearch.hasEmbeddings();
|
|
285
285
|
return this.embeddings.hasEmbeddings();
|
|
286
286
|
}
|
|
287
|
+
/**
|
|
288
|
+
* True when hybrid/semantic search can actually run its semantic leg.
|
|
289
|
+
* In DB mode this requires BOTH stored vectors in the central DB and the
|
|
290
|
+
* store-local embeddings.db used to embed the query text — the central
|
|
291
|
+
* count alone can be non-zero on a machine that only syncs gnosys.db,
|
|
292
|
+
* in which case hybridSearch() silently runs keyword-only (embedQuery
|
|
293
|
+
* is never constructed).
|
|
294
|
+
*/
|
|
295
|
+
canRunSemantic() {
|
|
296
|
+
if (this.dbSearch) {
|
|
297
|
+
return this.dbSearch.hasEmbeddings() && this.embeddings.hasEmbeddings();
|
|
298
|
+
}
|
|
299
|
+
return this.embeddings.hasEmbeddings();
|
|
300
|
+
}
|
|
287
301
|
/**
|
|
288
302
|
* Get embedding count.
|
|
289
303
|
*/
|
|
@@ -67,7 +67,12 @@ export async function runHybridSearchCommand(getResolver, query, opts) {
|
|
|
67
67
|
const embeddings = new GnosysEmbeddings(storePath);
|
|
68
68
|
const hybridSearch = new GnosysHybridSearch(search, embeddings, resolver, storePath);
|
|
69
69
|
const mode = opts.mode;
|
|
70
|
-
|
|
70
|
+
// v5.12.3: hybrid used to degrade to keyword-only silently when the
|
|
71
|
+
// semantic leg can't run. Warn on stderr so --json stdout stays clean.
|
|
72
|
+
if (mode !== "keyword" && !hybridSearch.canRunSemantic()) {
|
|
73
|
+
console.error(`⚠ Semantic embeddings unavailable — ${mode} search will run keyword-only. Run 'gnosys reindex' to build embeddings.`);
|
|
74
|
+
}
|
|
75
|
+
const results = await hybridSearch.hybridSearch(query, parseInt(opts.limit, 10), mode);
|
|
71
76
|
if (results.length === 0) {
|
|
72
77
|
outputResult(!!opts.json, { query, mode, results: [] }, () => {
|
|
73
78
|
console.log(`No results for "${query}". Try gnosys reindex to build embeddings.`);
|
|
@@ -20,10 +20,11 @@ export function readBundle(bundlePath) {
|
|
|
20
20
|
return bundle;
|
|
21
21
|
}
|
|
22
22
|
function portableToDbMemory(p) {
|
|
23
|
-
const { embedding_b64, ...rest } = p;
|
|
23
|
+
const { embedding_b64, attachment_data_b64, ...rest } = p;
|
|
24
24
|
return {
|
|
25
25
|
...rest,
|
|
26
26
|
embedding: embedding_b64 ? Buffer.from(embedding_b64, "base64") : null,
|
|
27
|
+
attachment_data: attachment_data_b64 ? Buffer.from(attachment_data_b64, "base64") : null,
|
|
27
28
|
};
|
|
28
29
|
}
|
|
29
30
|
/** Restore a bundle into the central DB. Returns counts and the final project ID. */
|
package/dist/lib/llm.js
CHANGED
|
@@ -487,7 +487,7 @@ export function createProvider(provider, model, config, task) {
|
|
|
487
487
|
}
|
|
488
488
|
case "custom": {
|
|
489
489
|
const customConfig = config.llm.custom;
|
|
490
|
-
if (!customConfig
|
|
490
|
+
if (!customConfig?.baseUrl || !customConfig.model) {
|
|
491
491
|
throw new Error("Custom provider not configured. Set llm.custom.baseUrl and llm.custom.model in gnosys.json, or use: gnosys config set provider custom");
|
|
492
492
|
}
|
|
493
493
|
const apiKey = getCustomApiKey(config);
|
package/dist/lib/lock.d.ts
CHANGED
|
@@ -29,4 +29,4 @@ export declare function acquireWriteLock(storePath: string, operation?: string):
|
|
|
29
29
|
* unboundedly until something triggers a manual checkpoint — we observed
|
|
30
30
|
* 4MB+ WAL files in the wild with no checkpoint cadence in v5.4.0.
|
|
31
31
|
*/
|
|
32
|
-
export declare function enableWAL(db: any): void;
|
|
32
|
+
export declare function enableWAL(db: any, busyTimeoutMs?: number): void;
|
package/dist/lib/lock.js
CHANGED
|
@@ -134,11 +134,13 @@ function isLockStale(lock) {
|
|
|
134
134
|
* unboundedly until something triggers a manual checkpoint — we observed
|
|
135
135
|
* 4MB+ WAL files in the wild with no checkpoint cadence in v5.4.0.
|
|
136
136
|
*/
|
|
137
|
-
|
|
138
|
-
export function enableWAL(db) {
|
|
137
|
+
export function enableWAL(db, busyTimeoutMs = 5000) {
|
|
139
138
|
try {
|
|
140
139
|
db.pragma("journal_mode = WAL");
|
|
141
|
-
|
|
140
|
+
// Single source of truth for the connection's busy timeout — callers on
|
|
141
|
+
// network shares (central DB) pass a longer value (10s) instead of
|
|
142
|
+
// overriding with a second pragma afterwards.
|
|
143
|
+
db.pragma(`busy_timeout = ${Math.max(0, Math.floor(busyTimeoutMs))}`);
|
|
142
144
|
// Auto-checkpoint after every 1000 frames written to WAL. Default is
|
|
143
145
|
// 1000 anyway in newer SQLite, but set explicitly so behavior is
|
|
144
146
|
// predictable across SQLite versions.
|
package/dist/lib/migrate.js
CHANGED
|
@@ -10,7 +10,6 @@ import { GnosysDB, fnv1a } from "./db.js";
|
|
|
10
10
|
import { GnosysStore } from "./store.js";
|
|
11
11
|
import { GnosysArchive } from "./archive.js";
|
|
12
12
|
// Dynamic import for embeddings DB
|
|
13
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
14
13
|
let Database = null;
|
|
15
14
|
try {
|
|
16
15
|
Database = (await import("better-sqlite3")).default;
|
|
@@ -143,7 +143,7 @@ function buildTranscriptChunks(segments, targetSize) {
|
|
|
143
143
|
*/
|
|
144
144
|
export async function ingestFile(options) {
|
|
145
145
|
const startTime = Date.now();
|
|
146
|
-
const { filePath, storePath, mode = "llm", author = "human", authority = "imported", dryRun = false,
|
|
146
|
+
const { filePath, storePath, mode = "llm", author = "human", authority = "imported", dryRun = false, onProgress, } = options;
|
|
147
147
|
// Step 1: Detect file type
|
|
148
148
|
const fileInfo = await detectFileType(filePath);
|
|
149
149
|
// Reject unsupported types early
|
package/dist/lib/platform.d.ts
CHANGED
|
@@ -4,16 +4,10 @@
|
|
|
4
4
|
export type OsFamily = "macos" | "linux" | "windows";
|
|
5
5
|
/** Current OS family for CLI messages and help text. */
|
|
6
6
|
export declare function getOsFamily(): OsFamily;
|
|
7
|
-
/** Primary secure credential store name on this machine. */
|
|
8
|
-
export declare function getSecureStorageLabel(): string;
|
|
9
7
|
/** Short phrase for error messages (setup may still be required on Windows). */
|
|
10
8
|
export declare function getSecureStorageSetupHint(): string;
|
|
11
|
-
/** Order of API key resolution for user-facing help on the current OS. */
|
|
12
|
-
export declare function getApiKeyResolutionOrderText(): string;
|
|
13
9
|
/** Claude Desktop MCP config file path for the current platform. */
|
|
14
10
|
export declare function getClaudeDesktopConfigPath(): string;
|
|
15
|
-
/** Display path with ~ for home (for logs and help). */
|
|
16
|
-
export declare function displayClaudeDesktopConfigPath(): string;
|
|
17
11
|
/** Shell profile file(s) suggested for env vars on this OS. */
|
|
18
12
|
export declare function getShellProfileHint(): string;
|
|
19
13
|
/** Lines shown when user skips API key setup in gnosys setup. */
|
package/dist/lib/platform.js
CHANGED
|
@@ -11,17 +11,6 @@ export function getOsFamily() {
|
|
|
11
11
|
return "windows";
|
|
12
12
|
return "linux";
|
|
13
13
|
}
|
|
14
|
-
/** Primary secure credential store name on this machine. */
|
|
15
|
-
export function getSecureStorageLabel() {
|
|
16
|
-
switch (getOsFamily()) {
|
|
17
|
-
case "macos":
|
|
18
|
-
return "macOS Keychain";
|
|
19
|
-
case "linux":
|
|
20
|
-
return "GNOME Keyring";
|
|
21
|
-
case "windows":
|
|
22
|
-
return "Windows Credential Manager";
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
14
|
/** Short phrase for error messages (setup may still be required on Windows). */
|
|
26
15
|
export function getSecureStorageSetupHint() {
|
|
27
16
|
switch (getOsFamily()) {
|
|
@@ -33,17 +22,6 @@ export function getSecureStorageSetupHint() {
|
|
|
33
22
|
return "your user environment or ~/.config/gnosys/.env (via gnosys setup)";
|
|
34
23
|
}
|
|
35
24
|
}
|
|
36
|
-
/** Order of API key resolution for user-facing help on the current OS. */
|
|
37
|
-
export function getApiKeyResolutionOrderText() {
|
|
38
|
-
switch (getOsFamily()) {
|
|
39
|
-
case "macos":
|
|
40
|
-
return "macOS Keychain, environment variable, then ~/.config/gnosys/.env";
|
|
41
|
-
case "linux":
|
|
42
|
-
return "GNOME Keyring (when available), environment variable, then ~/.config/gnosys/.env";
|
|
43
|
-
case "windows":
|
|
44
|
-
return "environment variable, then ~/.config/gnosys/.env";
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
25
|
/** Claude Desktop MCP config file path for the current platform. */
|
|
48
26
|
export function getClaudeDesktopConfigPath() {
|
|
49
27
|
const home = os.homedir();
|
|
@@ -56,12 +34,6 @@ export function getClaudeDesktopConfigPath() {
|
|
|
56
34
|
}
|
|
57
35
|
return path.join(home, ".config", "Claude", "claude_desktop_config.json");
|
|
58
36
|
}
|
|
59
|
-
/** Display path with ~ for home (for logs and help). */
|
|
60
|
-
export function displayClaudeDesktopConfigPath() {
|
|
61
|
-
const home = os.homedir();
|
|
62
|
-
const p = getClaudeDesktopConfigPath();
|
|
63
|
-
return p.startsWith(home) ? "~" + p.slice(home.length) : p;
|
|
64
|
-
}
|
|
65
37
|
/** Shell profile file(s) suggested for env vars on this OS. */
|
|
66
38
|
export function getShellProfileHint() {
|
|
67
39
|
switch (getOsFamily()) {
|
package/dist/lib/readCommand.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import fs from "fs/promises";
|
|
2
|
-
import { GnosysDB } from "./db.js";
|
|
3
2
|
function outputResult(json, data, humanFn) {
|
|
4
3
|
if (json) {
|
|
5
4
|
console.log(JSON.stringify(data, null, 2));
|
|
@@ -9,11 +8,14 @@ function outputResult(json, data, humanFn) {
|
|
|
9
8
|
}
|
|
10
9
|
}
|
|
11
10
|
export async function runReadCommand(getResolver, memoryPath, opts) {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
11
|
+
// v13 client read path: master/snapshot routing + pending-offline-adds overlay,
|
|
12
|
+
// so `gnosys read <id>` sees the same memories as list/discover/search.
|
|
13
|
+
const { resolveClientRead, getMemoryWithOverlay } = await import("./clientReadResolve.js");
|
|
14
|
+
const resolved = resolveClientRead();
|
|
15
|
+
if (resolved) {
|
|
16
|
+
try {
|
|
17
|
+
const dbMem = getMemoryWithOverlay(resolved, memoryPath);
|
|
18
|
+
if (dbMem) {
|
|
17
19
|
const tags = dbMem.tags || "[]";
|
|
18
20
|
const headerLines = [
|
|
19
21
|
`---`,
|
|
@@ -42,12 +44,11 @@ export async function runReadCommand(getResolver, memoryPath, opts) {
|
|
|
42
44
|
});
|
|
43
45
|
return;
|
|
44
46
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
47
|
+
}
|
|
48
|
+
finally {
|
|
49
|
+
resolved.release();
|
|
48
50
|
}
|
|
49
51
|
}
|
|
50
|
-
centralDb.close();
|
|
51
52
|
const resolver = await getResolver();
|
|
52
53
|
const memory = await resolver.readMemory(memoryPath);
|
|
53
54
|
if (!memory) {
|
|
@@ -24,7 +24,7 @@ export declare function configureFromPath(centralDb: GnosysDB, remotePath: strin
|
|
|
24
24
|
migrate?: boolean;
|
|
25
25
|
role?: MultiMachineRole;
|
|
26
26
|
}): Promise<boolean>;
|
|
27
|
-
export { stagingDirForMachine, clientPresencePath } from "./syncStaging.js";
|
|
27
|
+
export { machineStagingDir as stagingDirForMachine, clientPresencePath } from "./syncStaging.js";
|
|
28
28
|
export declare const __test: {
|
|
29
29
|
matchesTypedPhrase: typeof matchesTypedPhrase;
|
|
30
30
|
detectClonedStagingPresence: typeof detectClonedStagingPresence;
|
package/dist/lib/remoteWizard.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* - Reconfigure: change path, re-validate, or disconnect (when already configured)
|
|
7
7
|
*/
|
|
8
8
|
import { randomUUID } from "crypto";
|
|
9
|
-
import { existsSync, mkdirSync, renameSync } from "fs";
|
|
9
|
+
import { existsSync, mkdirSync, renameSync, } from "fs";
|
|
10
10
|
import * as path from "path";
|
|
11
11
|
import { createInterface } from "readline/promises";
|
|
12
12
|
import { GnosysDB } from "./db.js";
|
|
@@ -16,7 +16,7 @@ import { getGnosysHome } from "./paths.js";
|
|
|
16
16
|
import { atomicWriteFileSync } from "./atomicWrite.js";
|
|
17
17
|
import { readMasterMarker, writeMasterMarker } from "./masterLease.js";
|
|
18
18
|
import { checkMasterPathLocalDisk, LOCAL_DISK_ACK_PHRASE, } from "./localDiskCheck.js";
|
|
19
|
-
import {
|
|
19
|
+
import { clientPresencePath, machineStagingDir } from "./syncStaging.js";
|
|
20
20
|
import { safeQuestion } from "./setup/ui/safePrompt.js";
|
|
21
21
|
import { Spinner } from "./setup/ui/spinner.js";
|
|
22
22
|
import { printStatus } from "./setup/ui/status.js";
|
|
@@ -500,10 +500,10 @@ export async function configureFromPath(centralDb, remotePath, opts = {}) {
|
|
|
500
500
|
}
|
|
501
501
|
return true;
|
|
502
502
|
}
|
|
503
|
-
export { stagingDirForMachine, clientPresencePath } from "./syncStaging.js";
|
|
503
|
+
export { machineStagingDir as stagingDirForMachine, clientPresencePath } from "./syncStaging.js";
|
|
504
504
|
export const __test = {
|
|
505
505
|
matchesTypedPhrase,
|
|
506
506
|
detectClonedStagingPresence,
|
|
507
|
-
stagingDirForMachine,
|
|
507
|
+
stagingDirForMachine: machineStagingDir,
|
|
508
508
|
clientPresencePath,
|
|
509
509
|
};
|
package/dist/lib/resolver.d.ts
CHANGED
|
@@ -46,6 +46,23 @@ export declare class GnosysResolver {
|
|
|
46
46
|
* Discover and initialize all store layers.
|
|
47
47
|
*/
|
|
48
48
|
resolve(): Promise<ResolvedStore[]>;
|
|
49
|
+
/**
|
|
50
|
+
* Load the env-configured store tiers (optional `GNOSYS_STORES`, personal
|
|
51
|
+
* `GNOSYS_PERSONAL`, global `GNOSYS_GLOBAL`) onto `this.stores`.
|
|
52
|
+
*
|
|
53
|
+
* Shared by {@link resolve} and {@link resolveForProject}: a per-tool
|
|
54
|
+
* `projectRoot` call must STILL see personal/global, otherwise cross-project
|
|
55
|
+
* and global memories are unreachable — you couldn't write `store: "global"`
|
|
56
|
+
* / `"personal"` nor read them back from another project. Since every tool
|
|
57
|
+
* is told to always pass `projectRoot`, omitting these here silently
|
|
58
|
+
* disabled the entire cross-project tier system.
|
|
59
|
+
*
|
|
60
|
+
* Global is loaded whenever `GNOSYS_GLOBAL` is set, creating its directory on
|
|
61
|
+
* demand (parity with personal) so a configured-but-empty global store still
|
|
62
|
+
* works. It remains write-only-when-explicitly-targeted: {@link getWriteTarget}
|
|
63
|
+
* never auto-selects it.
|
|
64
|
+
*/
|
|
65
|
+
private loadEnvTiers;
|
|
49
66
|
/**
|
|
50
67
|
* Get all stores in precedence order.
|
|
51
68
|
*/
|