@gmickel/gno 1.40.0 → 1.42.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.
- package/README.md +1 -0
- package/assets/skill/SKILL.md +22 -1
- package/assets/skill/cli-reference.md +48 -0
- package/assets/skill/mcp-reference.md +31 -0
- package/browser-extension/artifacts/{gno-browser-clipper-v1.40.0.zip → gno-browser-clipper-v1.42.0.zip} +0 -0
- package/browser-extension/artifacts/gno-browser-clipper-v1.42.0.zip.sha256 +1 -0
- package/browser-extension/dist/manifest.json +1 -1
- package/package.json +3 -2
- package/spec/cli.md +146 -7
- package/spec/db/schema.sql +17 -0
- package/spec/mcp.md +350 -6
- package/spec/output-schemas/memory-recall.schema.json +159 -0
- package/spec/output-schemas/memory-remember.schema.json +164 -0
- package/spec/output-schemas/status.schema.json +269 -54
- package/src/cli/commands/daemon.ts +1 -0
- package/src/cli/commands/mcp.ts +3 -1
- package/src/cli/commands/memory.ts +491 -0
- package/src/cli/commands/status.ts +23 -4
- package/src/cli/options.ts +4 -0
- package/src/cli/program.ts +155 -0
- package/src/config/types.ts +10 -0
- package/src/core/audit-provenance.ts +91 -0
- package/src/core/audit-workspace.ts +17 -0
- package/src/core/connector-verifier.ts +2 -4
- package/src/core/memory-diagnostics.ts +144 -0
- package/src/core/memory-fence.ts +239 -0
- package/src/core/memory-recall.ts +269 -0
- package/src/core/memory-record.ts +435 -0
- package/src/core/memory-remember.ts +425 -0
- package/src/core/memory-types.ts +211 -0
- package/src/core/memory.ts +87 -0
- package/src/ingestion/sync.ts +17 -0
- package/src/mcp/AGENTS.md +7 -1
- package/src/mcp/CLAUDE.md +7 -1
- package/src/mcp/context.ts +37 -7
- package/src/mcp/http-egress.ts +2 -0
- package/src/mcp/http-modern.ts +214 -0
- package/src/mcp/http-security.ts +5 -0
- package/src/mcp/http-session.ts +4 -3
- package/src/mcp/http-transport.ts +81 -12
- package/src/mcp/resources/index.ts +3 -6
- package/src/mcp/server.ts +18 -16
- package/src/mcp/stdio-serving.ts +45 -0
- package/src/mcp/tool-descriptions-core.ts +56 -0
- package/src/mcp/tool-profile.ts +112 -0
- package/src/mcp/tools/index.ts +286 -126
- package/src/mcp/tools/memory-recall.ts +122 -0
- package/src/mcp/tools/memory-remember.ts +177 -0
- package/src/mcp/tools/memory-shared.ts +86 -0
- package/src/pipeline/search.ts +2 -0
- package/src/pipeline/types.ts +8 -0
- package/src/sdk/client.ts +94 -1
- package/src/sdk/index.ts +13 -0
- package/src/sdk/types.ts +28 -0
- package/src/serve/routes/api.ts +167 -0
- package/src/serve/routes/mcp.ts +1 -0
- package/src/serve/server.ts +27 -0
- package/src/store/migrations/027-memory-scopes.ts +37 -0
- package/src/store/migrations/index.ts +2 -0
- package/src/store/sqlite/adapter.ts +127 -3
- package/src/store/types.ts +54 -0
- package/browser-extension/artifacts/gno-browser-clipper-v1.40.0.zip.sha256 +0 -1
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP gno_recall tool - budgeted, cited, current-state memory recall.
|
|
3
|
+
*
|
|
4
|
+
* Thin adapter over the core `MemoryService`. Identity is mapped from the
|
|
5
|
+
* server session (MCP client name + transport session), never from tool
|
|
6
|
+
* arguments; see `memory-shared` for the mapping and service construction.
|
|
7
|
+
*
|
|
8
|
+
* @module src/mcp/tools/memory-recall
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { z } from "zod";
|
|
12
|
+
|
|
13
|
+
import type { ToolContext } from "../server";
|
|
14
|
+
|
|
15
|
+
import {
|
|
16
|
+
MEMORY_RECALL_MAX_FACTS,
|
|
17
|
+
MEMORY_RECALL_MAX_TOKENS,
|
|
18
|
+
type RecallResult,
|
|
19
|
+
} from "../../core/memory";
|
|
20
|
+
import { runTool, type ToolResult } from "./index";
|
|
21
|
+
import {
|
|
22
|
+
createMcpMemoryService,
|
|
23
|
+
type McpMemorySessionInfo,
|
|
24
|
+
memoryScopesInputSchema,
|
|
25
|
+
resolveMcpMemoryIdentity,
|
|
26
|
+
rethrowMemoryError,
|
|
27
|
+
} from "./memory-shared";
|
|
28
|
+
|
|
29
|
+
export const RECALL_MCP_ANNOTATIONS = {
|
|
30
|
+
readOnlyHint: true,
|
|
31
|
+
destructiveHint: false,
|
|
32
|
+
idempotentHint: true,
|
|
33
|
+
openWorldHint: false,
|
|
34
|
+
} as const;
|
|
35
|
+
|
|
36
|
+
export const recallInputSchema = z.object({
|
|
37
|
+
query: z
|
|
38
|
+
.string()
|
|
39
|
+
.trim()
|
|
40
|
+
.min(1, "Query cannot be empty")
|
|
41
|
+
.describe("What you need to know, phrased as the fact would be stated"),
|
|
42
|
+
collection: z
|
|
43
|
+
.string()
|
|
44
|
+
.trim()
|
|
45
|
+
.min(1, "Collection cannot be empty")
|
|
46
|
+
.describe("Memory-managed collection to recall from"),
|
|
47
|
+
scopes: memoryScopesInputSchema,
|
|
48
|
+
maxFacts: z
|
|
49
|
+
.number()
|
|
50
|
+
.int()
|
|
51
|
+
.min(1)
|
|
52
|
+
.max(64)
|
|
53
|
+
.optional()
|
|
54
|
+
.describe(`Fact budget (default ${MEMORY_RECALL_MAX_FACTS})`),
|
|
55
|
+
maxTokens: z
|
|
56
|
+
.number()
|
|
57
|
+
.int()
|
|
58
|
+
.min(1)
|
|
59
|
+
.max(8192)
|
|
60
|
+
.optional()
|
|
61
|
+
.describe(`Payload token budget (default ${MEMORY_RECALL_MAX_TOKENS})`),
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
export type RecallToolInput = z.infer<typeof recallInputSchema>;
|
|
65
|
+
|
|
66
|
+
export function formatRecallResult(result: RecallResult): string {
|
|
67
|
+
const lines: string[] = [];
|
|
68
|
+
lines.push(
|
|
69
|
+
`Facts: ${result.facts.length} (budget ${result.budget.maxFacts} facts / ${result.budget.maxTokens} tokens, used ${result.budget.usedTokens}, omitted ${result.budget.omitted})`
|
|
70
|
+
);
|
|
71
|
+
lines.push(
|
|
72
|
+
`Retrieval: ${result.retrieval.mode}${
|
|
73
|
+
result.retrieval.semanticUnavailable
|
|
74
|
+
? ` (${result.retrieval.semanticUnavailable})`
|
|
75
|
+
: ""
|
|
76
|
+
}`
|
|
77
|
+
);
|
|
78
|
+
for (const fact of result.facts) {
|
|
79
|
+
lines.push("");
|
|
80
|
+
lines.push(`- ${fact.text}`);
|
|
81
|
+
lines.push(` cite: ${fact.uri}`);
|
|
82
|
+
lines.push(
|
|
83
|
+
` scopes: ${fact.scopes.join(", ")} | hash: ${fact.contentHash} | created: ${fact.createdAt}`
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
if (result.hint) {
|
|
87
|
+
lines.push("");
|
|
88
|
+
lines.push(result.hint);
|
|
89
|
+
}
|
|
90
|
+
lines.push("");
|
|
91
|
+
lines.push(
|
|
92
|
+
`Receipt: ${result.receipt.digest} (caller ${result.receipt.caller}, session ${result.receipt.session}, ${result.receipt.memoryIds.length} ids)`
|
|
93
|
+
);
|
|
94
|
+
return lines.join("\n");
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function handleRecall(
|
|
98
|
+
args: RecallToolInput,
|
|
99
|
+
ctx: ToolContext,
|
|
100
|
+
info: McpMemorySessionInfo = {}
|
|
101
|
+
): Promise<ToolResult> {
|
|
102
|
+
return runTool(
|
|
103
|
+
ctx,
|
|
104
|
+
"gno_recall",
|
|
105
|
+
async () => {
|
|
106
|
+
const service = createMcpMemoryService(ctx);
|
|
107
|
+
try {
|
|
108
|
+
return await service.recall({
|
|
109
|
+
...resolveMcpMemoryIdentity(ctx, info),
|
|
110
|
+
query: args.query,
|
|
111
|
+
collection: args.collection,
|
|
112
|
+
scopes: args.scopes,
|
|
113
|
+
maxFacts: args.maxFacts,
|
|
114
|
+
maxTokens: args.maxTokens,
|
|
115
|
+
});
|
|
116
|
+
} catch (error) {
|
|
117
|
+
return rethrowMemoryError(error);
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
formatRecallResult
|
|
121
|
+
);
|
|
122
|
+
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP gno_remember tool - store one fact with supersession semantics.
|
|
3
|
+
*
|
|
4
|
+
* Thin adapter over the core `MemoryService`, registered only with
|
|
5
|
+
* `--enable-write`. The service acquires the shared write lease itself; this
|
|
6
|
+
* module performs no lock acquisition.
|
|
7
|
+
*
|
|
8
|
+
* @module src/mcp/tools/memory-remember
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { z } from "zod";
|
|
12
|
+
|
|
13
|
+
import type { ToolContext } from "../server";
|
|
14
|
+
|
|
15
|
+
import { type RememberResult } from "../../core/memory";
|
|
16
|
+
import { MEMORY_MAX_FACT_BYTES } from "../../core/memory-record";
|
|
17
|
+
import { runTool, type ToolResult } from "./index";
|
|
18
|
+
import {
|
|
19
|
+
createMcpMemoryService,
|
|
20
|
+
type McpMemorySessionInfo,
|
|
21
|
+
memoryScopesInputSchema,
|
|
22
|
+
resolveMcpMemoryIdentity,
|
|
23
|
+
rethrowMemoryError,
|
|
24
|
+
} from "./memory-shared";
|
|
25
|
+
|
|
26
|
+
export const REMEMBER_MCP_ANNOTATIONS = {
|
|
27
|
+
readOnlyHint: false,
|
|
28
|
+
destructiveHint: false,
|
|
29
|
+
idempotentHint: false,
|
|
30
|
+
openWorldHint: false,
|
|
31
|
+
} as const;
|
|
32
|
+
|
|
33
|
+
export const memoryReceiptInputSchema = z
|
|
34
|
+
.object({
|
|
35
|
+
caller: z.string(),
|
|
36
|
+
session: z.string(),
|
|
37
|
+
issuedAt: z.string(),
|
|
38
|
+
memoryIds: z.array(z.string()),
|
|
39
|
+
spanHashes: z.array(z.string()),
|
|
40
|
+
digest: z.string(),
|
|
41
|
+
})
|
|
42
|
+
.describe(
|
|
43
|
+
"The receipt from the gno_recall response the fact was derived from, if any; replayed recalled spans are rejected"
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
export const rememberInputSchema = z.object({
|
|
47
|
+
text: z
|
|
48
|
+
.string()
|
|
49
|
+
.trim()
|
|
50
|
+
.min(1, "Fact text cannot be empty")
|
|
51
|
+
.max(MEMORY_MAX_FACT_BYTES)
|
|
52
|
+
.describe(
|
|
53
|
+
"One fact, stated in full (single statement, not a document; use gno_capture for documents)"
|
|
54
|
+
),
|
|
55
|
+
collection: z
|
|
56
|
+
.string()
|
|
57
|
+
.trim()
|
|
58
|
+
.min(1, "Collection cannot be empty")
|
|
59
|
+
.describe("Memory-managed collection to write into"),
|
|
60
|
+
scopes: memoryScopesInputSchema,
|
|
61
|
+
decision: z
|
|
62
|
+
.enum(["add", "supersede"])
|
|
63
|
+
.optional()
|
|
64
|
+
.describe(
|
|
65
|
+
"Omit to receive candidates without writing; add creates a new fact; supersede replaces predecessorUri"
|
|
66
|
+
),
|
|
67
|
+
predecessorUri: z
|
|
68
|
+
.string()
|
|
69
|
+
.trim()
|
|
70
|
+
.min(1)
|
|
71
|
+
.optional()
|
|
72
|
+
.describe("gno:// URI of the fact being superseded (supersede only)"),
|
|
73
|
+
predecessorHash: z
|
|
74
|
+
.string()
|
|
75
|
+
.trim()
|
|
76
|
+
.min(1)
|
|
77
|
+
.optional()
|
|
78
|
+
.describe(
|
|
79
|
+
"contentHash of the predecessor as returned by gno_recall (supersede only)"
|
|
80
|
+
),
|
|
81
|
+
receipt: memoryReceiptInputSchema.optional(),
|
|
82
|
+
derivedFrom: z
|
|
83
|
+
.array(z.string().trim().min(1))
|
|
84
|
+
.max(32)
|
|
85
|
+
.optional()
|
|
86
|
+
.describe(
|
|
87
|
+
"Declared origins of the fact; any gno:// origin is rejected as GNO-derived"
|
|
88
|
+
),
|
|
89
|
+
source: z
|
|
90
|
+
.string()
|
|
91
|
+
.trim()
|
|
92
|
+
.min(1)
|
|
93
|
+
.optional()
|
|
94
|
+
.describe("Free-text evidence for the fact (where it came from)"),
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
export type RememberToolInput = z.infer<typeof rememberInputSchema>;
|
|
98
|
+
|
|
99
|
+
export function formatRememberResult(result: RememberResult): string {
|
|
100
|
+
const lines: string[] = [];
|
|
101
|
+
const matching = `matching: ${result.matching.mode} (threshold ${result.matching.threshold}${
|
|
102
|
+
result.matching.semanticUnavailable
|
|
103
|
+
? `, ${result.matching.semanticUnavailable}`
|
|
104
|
+
: ""
|
|
105
|
+
})`;
|
|
106
|
+
switch (result.outcome) {
|
|
107
|
+
case "existing":
|
|
108
|
+
lines.push(`Outcome: existing (exact duplicate, nothing written)`);
|
|
109
|
+
lines.push(`URI: ${result.record.uri}`);
|
|
110
|
+
lines.push(`Hash: ${result.record.contentHash}`);
|
|
111
|
+
break;
|
|
112
|
+
case "candidates":
|
|
113
|
+
lines.push(
|
|
114
|
+
`Outcome: candidates (${result.candidates.length}, nothing written). Decide: decision=add for a new fact, or decision=supersede with predecessorUri + predecessorHash.`
|
|
115
|
+
);
|
|
116
|
+
for (const candidate of result.candidates) {
|
|
117
|
+
lines.push(
|
|
118
|
+
`- [${candidate.match} ${candidate.similarity.toFixed(2)}] ${candidate.text}`
|
|
119
|
+
);
|
|
120
|
+
lines.push(` uri: ${candidate.uri} | hash: ${candidate.contentHash}`);
|
|
121
|
+
}
|
|
122
|
+
break;
|
|
123
|
+
default:
|
|
124
|
+
lines.push(`Outcome: ${result.outcome}`);
|
|
125
|
+
lines.push(`URI: ${result.record.uri}`);
|
|
126
|
+
lines.push(`Hash: ${result.record.contentHash}`);
|
|
127
|
+
lines.push(`Path: ${result.absPath}`);
|
|
128
|
+
lines.push(`Sync: ${result.sync.status}`);
|
|
129
|
+
if (result.record.supersedes.length > 0) {
|
|
130
|
+
lines.push(`Supersedes: ${result.record.supersedes.join(", ")}`);
|
|
131
|
+
}
|
|
132
|
+
break;
|
|
133
|
+
}
|
|
134
|
+
lines.push(matching);
|
|
135
|
+
return lines.join("\n");
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export function handleRemember(
|
|
139
|
+
args: RememberToolInput,
|
|
140
|
+
ctx: ToolContext,
|
|
141
|
+
info: McpMemorySessionInfo = {}
|
|
142
|
+
): Promise<ToolResult> {
|
|
143
|
+
return runTool(
|
|
144
|
+
ctx,
|
|
145
|
+
"gno_remember",
|
|
146
|
+
async () => {
|
|
147
|
+
if (!ctx.enableWrite) {
|
|
148
|
+
throw new Error(
|
|
149
|
+
"WRITE_DISABLED: gno_remember requires --enable-write or GNO_MCP_ENABLE_WRITE=1"
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
const service = createMcpMemoryService(ctx);
|
|
153
|
+
let result: RememberResult;
|
|
154
|
+
try {
|
|
155
|
+
result = await service.remember({
|
|
156
|
+
...resolveMcpMemoryIdentity(ctx, info),
|
|
157
|
+
text: args.text,
|
|
158
|
+
collection: args.collection,
|
|
159
|
+
scopes: args.scopes,
|
|
160
|
+
decision: args.decision,
|
|
161
|
+
predecessorUri: args.predecessorUri,
|
|
162
|
+
predecessorHash: args.predecessorHash,
|
|
163
|
+
receipt: args.receipt,
|
|
164
|
+
derivedFrom: args.derivedFrom,
|
|
165
|
+
source: args.source,
|
|
166
|
+
});
|
|
167
|
+
} catch (error) {
|
|
168
|
+
return rethrowMemoryError(error);
|
|
169
|
+
}
|
|
170
|
+
if (result.outcome === "added" || result.outcome === "superseded") {
|
|
171
|
+
ctx.markContentMutation?.();
|
|
172
|
+
}
|
|
173
|
+
return result;
|
|
174
|
+
},
|
|
175
|
+
formatRememberResult
|
|
176
|
+
);
|
|
177
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Helpers shared by the MCP gno_recall / gno_remember tools: identity
|
|
3
|
+
* mapping from the server session, service construction, error rethrow in
|
|
4
|
+
* the shape runTool parses, and the common scopes input schema.
|
|
5
|
+
*
|
|
6
|
+
* The service owns the shared write lease; this module never touches
|
|
7
|
+
* `ctx.writeLockPath` beyond naming the lease file for the service.
|
|
8
|
+
*
|
|
9
|
+
* @module src/mcp/tools/memory-shared
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { z } from "zod";
|
|
13
|
+
|
|
14
|
+
import type { ToolContext } from "../server";
|
|
15
|
+
|
|
16
|
+
import {
|
|
17
|
+
MemoryError,
|
|
18
|
+
type MemoryIdentity,
|
|
19
|
+
MemoryService,
|
|
20
|
+
} from "../../core/memory";
|
|
21
|
+
import { MEMORY_MAX_SCOPES } from "../../core/memory-record";
|
|
22
|
+
|
|
23
|
+
/** Caller name when the MCP client sent no implementation name. */
|
|
24
|
+
const DEFAULT_MCP_CALLER = "mcp";
|
|
25
|
+
|
|
26
|
+
/** Server-side identity inputs resolved at dispatch time by the registry. */
|
|
27
|
+
export interface McpMemorySessionInfo {
|
|
28
|
+
/** `clientInfo.name` from the MCP initialize handshake. */
|
|
29
|
+
clientName?: string;
|
|
30
|
+
/** Transport session id (2025-era Streamable HTTP); absent on stdio and on the 2026-07-28 sessionless leg. */
|
|
31
|
+
sessionId?: string;
|
|
32
|
+
/** Opaque per-caller identity the HTTP boundary derived (`ctx.getRequestIdentity`); absent on stdio. */
|
|
33
|
+
requestIdentity?: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export const memoryScopesInputSchema = z
|
|
37
|
+
.array(z.string().trim().min(1))
|
|
38
|
+
.min(1, "At least one explicit scope is required")
|
|
39
|
+
.max(MEMORY_MAX_SCOPES)
|
|
40
|
+
.describe(
|
|
41
|
+
`Explicit scopes (1-${MEMORY_MAX_SCOPES}, e.g. "project:gno"). Visibility is any-intersection; there is no implicit global scope`
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Map the MCP server session to the core identity contract.
|
|
46
|
+
*
|
|
47
|
+
* caller = MCP client implementation name; session = transport session id
|
|
48
|
+
* when the transport has one (2025-era Streamable HTTP), else the per-caller
|
|
49
|
+
* identity the HTTP boundary derived (2026-07-28 sessionless leg), else the
|
|
50
|
+
* per-process server instance id (stdio: one process is one session).
|
|
51
|
+
*/
|
|
52
|
+
export function resolveMcpMemoryIdentity(
|
|
53
|
+
ctx: ToolContext,
|
|
54
|
+
info: McpMemorySessionInfo
|
|
55
|
+
): MemoryIdentity {
|
|
56
|
+
const caller = info.clientName?.trim() || DEFAULT_MCP_CALLER;
|
|
57
|
+
const session =
|
|
58
|
+
info.sessionId?.trim() ||
|
|
59
|
+
info.requestIdentity?.trim() ||
|
|
60
|
+
ctx.serverInstanceId;
|
|
61
|
+
return { caller, session };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Construct the core service for one MCP call.
|
|
66
|
+
*
|
|
67
|
+
* The lease path names the same `.mcp-write.lock` file the rest of the MCP
|
|
68
|
+
* write surface uses, so a memory write and a capture serialise on one lease.
|
|
69
|
+
* Acquisition happens inside the service only.
|
|
70
|
+
*/
|
|
71
|
+
export function createMcpMemoryService(ctx: ToolContext): MemoryService {
|
|
72
|
+
return new MemoryService({
|
|
73
|
+
store: ctx.store,
|
|
74
|
+
config: ctx.config,
|
|
75
|
+
collections: ctx.collections,
|
|
76
|
+
lockPath: ctx.writeLockPath,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** Re-throw a core memory error in the `CODE: message` shape runTool parses. */
|
|
81
|
+
export function rethrowMemoryError(error: unknown): never {
|
|
82
|
+
if (error instanceof MemoryError) {
|
|
83
|
+
throw new Error(`${error.code}: ${error.message}`);
|
|
84
|
+
}
|
|
85
|
+
throw error;
|
|
86
|
+
}
|
package/src/pipeline/search.ts
CHANGED
|
@@ -211,6 +211,8 @@ export async function searchBm25(
|
|
|
211
211
|
until: temporalRange.until,
|
|
212
212
|
categories: options.categories,
|
|
213
213
|
author: options.author,
|
|
214
|
+
memoryScopesAny: options.memoryFilter?.scopes,
|
|
215
|
+
excludeSuperseded: options.memoryFilter?.excludeSuperseded,
|
|
214
216
|
});
|
|
215
217
|
|
|
216
218
|
if (!ftsResult.ok) {
|
package/src/pipeline/types.ts
CHANGED
|
@@ -212,6 +212,14 @@ export interface SearchOptions {
|
|
|
212
212
|
intent?: string;
|
|
213
213
|
/** Explicit exclusion terms for hard candidate pruning */
|
|
214
214
|
exclude?: string[];
|
|
215
|
+
/**
|
|
216
|
+
* Internal managed-memory filter: scope any-intersection plus superseded
|
|
217
|
+
* exclusion, executed inside the retrieval query (never post-hoc).
|
|
218
|
+
*/
|
|
219
|
+
memoryFilter?: {
|
|
220
|
+
scopes: string[];
|
|
221
|
+
excludeSuperseded: boolean;
|
|
222
|
+
};
|
|
215
223
|
}
|
|
216
224
|
|
|
217
225
|
/** Structured query mode identifier */
|
package/src/sdk/client.ts
CHANGED
|
@@ -38,7 +38,11 @@ import type {
|
|
|
38
38
|
GnoMoveNoteOptions,
|
|
39
39
|
GnoMultiGetOptions,
|
|
40
40
|
GnoQueryOptions,
|
|
41
|
+
GnoRecallInput,
|
|
42
|
+
GnoRecallResult,
|
|
41
43
|
GnoRefactorNoteResult,
|
|
44
|
+
GnoRememberInput,
|
|
45
|
+
GnoRememberResult,
|
|
42
46
|
GnoRenameNoteApplyOptions,
|
|
43
47
|
GnoRenameNoteOptions,
|
|
44
48
|
GnoSearchOptions,
|
|
@@ -113,6 +117,11 @@ import {
|
|
|
113
117
|
listKnowledgeChanges,
|
|
114
118
|
type KnowledgeDeltaServiceResult,
|
|
115
119
|
} from "../core/knowledge-delta";
|
|
120
|
+
import {
|
|
121
|
+
MemoryError,
|
|
122
|
+
type MemoryErrorCode,
|
|
123
|
+
MemoryService,
|
|
124
|
+
} from "../core/memory";
|
|
116
125
|
import { resolveNoteCreatePlan } from "../core/note-creation";
|
|
117
126
|
import { resolveNotePreset } from "../core/note-presets";
|
|
118
127
|
import {
|
|
@@ -144,6 +153,7 @@ import {
|
|
|
144
153
|
} from "../core/sections";
|
|
145
154
|
import { normalizeStructuredQueryInput } from "../core/structured-query";
|
|
146
155
|
import { parseAndValidateTagFilter } from "../core/tags";
|
|
156
|
+
import { writeLeasePath } from "../core/write-lease";
|
|
147
157
|
import {
|
|
148
158
|
defaultSyncService,
|
|
149
159
|
type SyncResult,
|
|
@@ -171,7 +181,7 @@ import {
|
|
|
171
181
|
multiGetDocuments,
|
|
172
182
|
} from "./documents";
|
|
173
183
|
import { runEmbed } from "./embed";
|
|
174
|
-
import { sdkError } from "./errors";
|
|
184
|
+
import { type GnoSdkErrorCode, sdkError } from "./errors";
|
|
175
185
|
|
|
176
186
|
interface OpenedClientState {
|
|
177
187
|
config: Config;
|
|
@@ -295,6 +305,41 @@ async function resolveClientState(
|
|
|
295
305
|
};
|
|
296
306
|
}
|
|
297
307
|
|
|
308
|
+
/** SDK error family per memory code; exhaustive so a new code fails to compile. */
|
|
309
|
+
const MEMORY_ERROR_TO_SDK: Readonly<Record<MemoryErrorCode, GnoSdkErrorCode>> =
|
|
310
|
+
{
|
|
311
|
+
MEMORY_TEXT_REQUIRED: "VALIDATION",
|
|
312
|
+
MEMORY_TEXT_TOO_LARGE: "VALIDATION",
|
|
313
|
+
MEMORY_QUERY_REQUIRED: "VALIDATION",
|
|
314
|
+
MEMORY_BUDGET_INVALID: "VALIDATION",
|
|
315
|
+
MEMORY_COLLECTION_REQUIRED: "VALIDATION",
|
|
316
|
+
MEMORY_COLLECTION_NOT_FOUND: "NOT_FOUND",
|
|
317
|
+
MEMORY_COLLECTION_UNMANAGED: "VALIDATION",
|
|
318
|
+
MEMORY_SCOPES_REQUIRED: "VALIDATION",
|
|
319
|
+
MEMORY_SCOPES_INVALID: "VALIDATION",
|
|
320
|
+
MEMORY_IDENTITY_REQUIRED: "VALIDATION",
|
|
321
|
+
MEMORY_DECISION_INVALID: "VALIDATION",
|
|
322
|
+
MEMORY_PREDECESSOR_REQUIRED: "VALIDATION",
|
|
323
|
+
MEMORY_PREDECESSOR_NOT_FOUND: "NOT_FOUND",
|
|
324
|
+
MEMORY_PREDECESSOR_HASH_MISMATCH: "RUNTIME",
|
|
325
|
+
MEMORY_SUPERSEDE_CONFLICT: "RUNTIME",
|
|
326
|
+
MEMORY_SUPERSEDE_PROJECTION_FAILED: "RUNTIME",
|
|
327
|
+
MEMORY_FENCED_REPLAY: "VALIDATION",
|
|
328
|
+
MEMORY_FENCED_DERIVED: "VALIDATION",
|
|
329
|
+
MEMORY_WRITE_LEASE_BUSY: "RUNTIME",
|
|
330
|
+
MEMORY_SYNC_FAILED: "RUNTIME",
|
|
331
|
+
MEMORY_QUERY_FAILED: "RUNTIME",
|
|
332
|
+
};
|
|
333
|
+
|
|
334
|
+
/** Map a core MemoryError onto the SDK error family; the memory code survives in `details.code`. */
|
|
335
|
+
function toMemorySdkError(cause: unknown): unknown {
|
|
336
|
+
if (!(cause instanceof MemoryError)) return cause;
|
|
337
|
+
return sdkError(MEMORY_ERROR_TO_SDK[cause.code], cause.message, {
|
|
338
|
+
cause,
|
|
339
|
+
details: { code: cause.code },
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
|
|
298
343
|
class GnoClientImpl implements GnoClient {
|
|
299
344
|
config: Config;
|
|
300
345
|
readonly dbPath: string;
|
|
@@ -1581,6 +1626,54 @@ class GnoClientImpl implements GnoClient {
|
|
|
1581
1626
|
};
|
|
1582
1627
|
}
|
|
1583
1628
|
|
|
1629
|
+
/**
|
|
1630
|
+
* The memory service owns the shared write lease; the SDK never takes it.
|
|
1631
|
+
* Embedding is best-effort: without a local model the service reports
|
|
1632
|
+
* lexical-only matching/retrieval instead of failing.
|
|
1633
|
+
*/
|
|
1634
|
+
private async withMemoryService<T>(
|
|
1635
|
+
collection: string | undefined,
|
|
1636
|
+
run: (service: MemoryService) => Promise<T>
|
|
1637
|
+
): Promise<T> {
|
|
1638
|
+
this.assertOpen();
|
|
1639
|
+
const ports = await this.createRuntimePorts({
|
|
1640
|
+
embed: true,
|
|
1641
|
+
collection: this.config.collections.some(
|
|
1642
|
+
(candidate) => candidate.name === collection
|
|
1643
|
+
)
|
|
1644
|
+
? collection
|
|
1645
|
+
: undefined,
|
|
1646
|
+
});
|
|
1647
|
+
try {
|
|
1648
|
+
return await run(
|
|
1649
|
+
new MemoryService({
|
|
1650
|
+
store: this.store,
|
|
1651
|
+
config: this.config,
|
|
1652
|
+
collections: this.config.collections,
|
|
1653
|
+
lockPath: writeLeasePath(this.dbPath),
|
|
1654
|
+
embedPort: ports.embedPort,
|
|
1655
|
+
vectorIndex: ports.vectorIndex,
|
|
1656
|
+
})
|
|
1657
|
+
);
|
|
1658
|
+
} catch (cause) {
|
|
1659
|
+
throw toMemorySdkError(cause);
|
|
1660
|
+
} finally {
|
|
1661
|
+
await this.disposeRuntimePorts(ports);
|
|
1662
|
+
}
|
|
1663
|
+
}
|
|
1664
|
+
|
|
1665
|
+
async remember(input: GnoRememberInput): Promise<GnoRememberResult> {
|
|
1666
|
+
return this.withMemoryService(input?.collection, (service) =>
|
|
1667
|
+
service.remember(input)
|
|
1668
|
+
);
|
|
1669
|
+
}
|
|
1670
|
+
|
|
1671
|
+
async recall(input: GnoRecallInput): Promise<GnoRecallResult> {
|
|
1672
|
+
return this.withMemoryService(input?.collection, (service) =>
|
|
1673
|
+
service.recall(input)
|
|
1674
|
+
);
|
|
1675
|
+
}
|
|
1676
|
+
|
|
1584
1677
|
async capture(options: GnoCaptureOptions): Promise<GnoCaptureResult> {
|
|
1585
1678
|
this.assertOpen();
|
|
1586
1679
|
const collection = this.getCollections(options.collection)[0];
|
package/src/sdk/index.ts
CHANGED
|
@@ -58,6 +58,10 @@ export type {
|
|
|
58
58
|
GnoMultiGetOptions,
|
|
59
59
|
GnoMultiGetResult,
|
|
60
60
|
GnoQueryOptions,
|
|
61
|
+
GnoRecallInput,
|
|
62
|
+
GnoRecallResult,
|
|
63
|
+
GnoRememberInput,
|
|
64
|
+
GnoRememberResult,
|
|
61
65
|
GnoRenameNoteApplyOptions,
|
|
62
66
|
GnoRenameNoteOptions,
|
|
63
67
|
GnoProjectHintOptions,
|
|
@@ -76,7 +80,16 @@ export type {
|
|
|
76
80
|
SectionTargetCreateSelector,
|
|
77
81
|
SectionTargetResolveResult,
|
|
78
82
|
SectionTargetV1,
|
|
83
|
+
MemoryCandidate,
|
|
84
|
+
MemoryFact,
|
|
85
|
+
MemoryRecallReceipt,
|
|
86
|
+
RecalledFact,
|
|
79
87
|
} from "./types";
|
|
88
|
+
export {
|
|
89
|
+
MemoryError,
|
|
90
|
+
type MemoryDecision,
|
|
91
|
+
type MemoryErrorCode,
|
|
92
|
+
} from "../core/memory";
|
|
80
93
|
export {
|
|
81
94
|
ContextCapsuleContractError,
|
|
82
95
|
type ContextCapsuleErrorCode,
|
package/src/sdk/types.ts
CHANGED
|
@@ -43,6 +43,16 @@ import type {
|
|
|
43
43
|
KnowledgeImpactResult,
|
|
44
44
|
ListKnowledgeChangesInput,
|
|
45
45
|
} from "../core/knowledge-delta";
|
|
46
|
+
import type {
|
|
47
|
+
MemoryCandidate,
|
|
48
|
+
MemoryFact,
|
|
49
|
+
MemoryRecallReceipt,
|
|
50
|
+
RecalledFact,
|
|
51
|
+
RecallInput,
|
|
52
|
+
RecallResult,
|
|
53
|
+
RememberInput,
|
|
54
|
+
RememberResult,
|
|
55
|
+
} from "../core/memory";
|
|
46
56
|
import type { NoteCollisionPolicy } from "../core/note-creation";
|
|
47
57
|
import type { NotePresetId } from "../core/note-presets";
|
|
48
58
|
import type {
|
|
@@ -245,6 +255,13 @@ export interface GnoCaptureOptions extends Omit<CaptureInput, "overwrite"> {}
|
|
|
245
255
|
|
|
246
256
|
export type GnoCaptureResult = CaptureReceipt;
|
|
247
257
|
|
|
258
|
+
/** Shared memory contract (identical on CLI, MCP, REST, and SDK). */
|
|
259
|
+
export type GnoRememberInput = RememberInput;
|
|
260
|
+
export type GnoRememberResult = RememberResult;
|
|
261
|
+
export type GnoRecallInput = RecallInput;
|
|
262
|
+
export type GnoRecallResult = RecallResult;
|
|
263
|
+
export type { MemoryCandidate, MemoryFact, MemoryRecallReceipt, RecalledFact };
|
|
264
|
+
|
|
248
265
|
export interface GnoCreateFolderOptions {
|
|
249
266
|
collection: string;
|
|
250
267
|
name: string;
|
|
@@ -370,6 +387,17 @@ export interface GnoClient {
|
|
|
370
387
|
embed(options?: GnoEmbedOptions): Promise<GnoEmbedResult>;
|
|
371
388
|
index(options?: GnoIndexOptions): Promise<GnoIndexResult>;
|
|
372
389
|
capture(options: GnoCaptureOptions): Promise<GnoCaptureResult>;
|
|
390
|
+
/**
|
|
391
|
+
* Store one fact in a memory-managed collection, or propose candidates when
|
|
392
|
+
* `decision` is omitted. Requires caller + session identity and explicit
|
|
393
|
+
* scopes. Errors carry the stable memory code in `details.code`.
|
|
394
|
+
*/
|
|
395
|
+
remember(input: GnoRememberInput): Promise<GnoRememberResult>;
|
|
396
|
+
/**
|
|
397
|
+
* Budgeted, cited recall of current facts in the caller's explicit scopes.
|
|
398
|
+
* The result carries a content-free fencing receipt.
|
|
399
|
+
*/
|
|
400
|
+
recall(input: GnoRecallInput): Promise<GnoRecallResult>;
|
|
373
401
|
createNote(options: GnoCreateNoteOptions): Promise<GnoCreateNoteResult>;
|
|
374
402
|
createFolder(options: GnoCreateFolderOptions): Promise<GnoCreateFolderResult>;
|
|
375
403
|
previewRenameNote(
|