@gmickel/gno 2.5.1 → 2.7.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 +60 -5
- package/assets/skill/README.md +5 -1
- package/assets/skill/SKILL.md +80 -4
- package/assets/skill/cli-reference.md +132 -2
- package/assets/skill/examples.md +30 -0
- package/assets/skill/mcp-reference.md +54 -1
- package/assets/skill/recipes/capture-and-file.md +6 -0
- package/assets/skill/recipes/memory-file-decision.md +6 -0
- package/assets/skill/recipes/memory-supersede-fact.md +5 -0
- package/assets/skill/recipes/session-evidence-lookup.md +98 -0
- package/assets/spa-production.json.gz +0 -0
- package/browser-extension/artifacts/{gno-browser-clipper-v2.5.1.zip → gno-browser-clipper-v2.7.0.zip} +0 -0
- package/browser-extension/artifacts/gno-browser-clipper-v2.7.0.zip.sha256 +1 -0
- package/browser-extension/dist/manifest.json +1 -1
- package/package.json +2 -1
- package/spec/cli.md +449 -35
- package/spec/mcp.md +234 -4
- package/spec/output-schemas/ask.schema.json +1 -1
- package/spec/output-schemas/capture-receipt.schema.json +4 -1
- package/spec/output-schemas/doctor.schema.json +88 -0
- package/spec/output-schemas/error.schema.json +11 -2
- package/spec/output-schemas/get.schema.json +1 -1
- package/spec/output-schemas/mcp-capture-result.schema.json +4 -2
- package/spec/output-schemas/memory-remember.schema.json +10 -4
- package/spec/output-schemas/multi-get.schema.json +4 -1
- package/spec/output-schemas/peek.schema.json +2 -9
- package/spec/output-schemas/request-status.schema.json +113 -0
- package/spec/output-schemas/resident-status.schema.json +22 -0
- package/spec/output-schemas/search-result.schema.json +1 -1
- package/spec/output-schemas/search-results.schema.json +1 -1
- package/spec/output-schemas/sessions-automation-run.schema.json +46 -0
- package/spec/output-schemas/sessions-discovery.schema.json +38 -0
- package/spec/output-schemas/sessions-import-receipt.schema.json +156 -0
- package/spec/output-schemas/sessions-status.schema.json +432 -0
- package/spec/output-schemas/status.schema.json +98 -0
- package/src/cli/commands/ask.ts +14 -2
- package/src/cli/commands/capture.ts +55 -96
- package/src/cli/commands/daemon.ts +41 -0
- package/src/cli/commands/doctor.ts +54 -20
- package/src/cli/commands/embed.ts +41 -3
- package/src/cli/commands/ls.ts +3 -0
- package/src/cli/commands/memory.ts +12 -3
- package/src/cli/commands/query.ts +5 -0
- package/src/cli/commands/request-status.ts +59 -0
- package/src/cli/commands/reset.ts +39 -5
- package/src/cli/commands/sessions.ts +713 -0
- package/src/cli/commands/shared.ts +14 -1
- package/src/cli/commands/status.ts +63 -5
- package/src/cli/commands/vec.ts +54 -0
- package/src/cli/detach.ts +29 -1
- package/src/cli/errors.ts +13 -9
- package/src/cli/program.ts +441 -2
- package/src/cli/session-binding.ts +49 -0
- package/src/config/types.ts +8 -0
- package/src/core/capture-publish.ts +239 -0
- package/src/core/capture-sync.ts +12 -2
- package/src/core/host-paths.ts +31 -0
- package/src/core/memory-remember.ts +234 -122
- package/src/core/memory-types.ts +11 -0
- package/src/core/network-boundary-inventory.ts +8 -0
- package/src/core/request-receipts.ts +671 -0
- package/src/core/shutdown-budget.ts +6 -0
- package/src/core/vector-partition-status.ts +52 -0
- package/src/embed/backlog.ts +124 -18
- package/src/embed/fingerprint.ts +6 -3
- package/src/embed/retry.ts +66 -27
- package/src/embed/variant-backlog.ts +15 -10
- package/src/embed/variant-retry.ts +31 -22
- package/src/index.ts +30 -2
- package/src/llm/native-worker/dispatcher.ts +2 -0
- package/src/llm/native-worker/embedding-identity.ts +42 -0
- package/src/llm/native-worker/protocol.ts +1 -0
- package/src/llm/types.ts +3 -0
- package/src/mcp/context.ts +17 -0
- package/src/mcp/http-egress.ts +4 -0
- package/src/mcp/http-transport.ts +2 -0
- package/src/mcp/resources/index.ts +6 -5
- package/src/mcp/tool-descriptions-core.ts +1 -1
- package/src/mcp/tools/capture.ts +87 -85
- package/src/mcp/tools/index.ts +77 -4
- package/src/mcp/tools/memory-remember.ts +8 -1
- package/src/mcp/tools/memory-shared.ts +7 -1
- package/src/mcp/tools/request-status.ts +73 -0
- package/src/mcp/tools/sessions.ts +208 -0
- package/src/mcp/tools/status.ts +4 -0
- package/src/pipeline/hybrid.ts +37 -7
- package/src/pipeline/vsearch.ts +14 -2
- package/src/sdk/client.ts +180 -84
- package/src/sdk/index.ts +6 -0
- package/src/sdk/types.ts +54 -2
- package/src/serve/capture-service.ts +98 -32
- package/src/serve/config-sync.ts +3 -2
- package/src/serve/embed-scheduler.ts +133 -19
- package/src/serve/host-path-redaction.ts +79 -0
- package/src/serve/public/app.tsx +4 -1
- package/src/serve/public/components/CaptureModal.tsx +26 -8
- package/src/serve/public/components/sessions/AutomationPanel.tsx +800 -0
- package/src/serve/public/components/sessions/ImportReceipt.tsx +238 -0
- package/src/serve/public/components/sessions/SessionSearch.tsx +286 -0
- package/src/serve/public/components/sessions/SourcesPanel.tsx +541 -0
- package/src/serve/public/components/sessions/api.ts +40 -0
- package/src/serve/public/globals.built.css +1 -1
- package/src/serve/public/hooks/use-api.ts +26 -3
- package/src/serve/public/lib/request-intent.ts +77 -0
- package/src/serve/public/lib/snippet.tsx +52 -0
- package/src/serve/public/lib/workspace-actions.ts +12 -1
- package/src/serve/public/lib/workspace-tabs.ts +2 -0
- package/src/serve/public/pages/Dashboard.tsx +22 -9
- package/src/serve/public/pages/DocView.tsx +25 -6
- package/src/serve/public/pages/DocumentEditor.tsx +224 -104
- package/src/serve/public/pages/Search.tsx +1 -41
- package/src/serve/public/pages/Sessions.tsx +350 -0
- package/src/serve/resident-runtime.ts +69 -4
- package/src/serve/resident-status.ts +13 -1
- package/src/serve/routes/api.ts +476 -147
- package/src/serve/routes/sessions.ts +766 -0
- package/src/serve/security.ts +9 -0
- package/src/serve/server.ts +215 -10
- package/src/serve/session-automation.ts +146 -0
- package/src/serve/status-model.ts +16 -0
- package/src/serve/status.ts +2 -0
- package/src/serve/watch-reconciliation-shared.ts +3 -0
- package/src/serve/watch-service-events.ts +3 -2
- package/src/serve/watch-service-run-flush.ts +35 -2
- package/src/serve/watch-service.ts +5 -0
- package/src/sessions/archive.ts +348 -0
- package/src/sessions/automation-state.ts +444 -0
- package/src/sessions/automation-status.ts +239 -0
- package/src/sessions/automation.ts +1169 -0
- package/src/sessions/binding.ts +105 -0
- package/src/sessions/claude-hook.ts +240 -0
- package/src/sessions/config.ts +176 -0
- package/src/sessions/format.ts +191 -0
- package/src/sessions/import-child-env.ts +8 -0
- package/src/sessions/import-child.ts +152 -0
- package/src/sessions/parsers/claude-code.ts +259 -0
- package/src/sessions/parsers/codex.ts +303 -0
- package/src/sessions/parsers/hermes.ts +248 -0
- package/src/sessions/parsers/openclaw.ts +496 -0
- package/src/sessions/parsers/shared.ts +184 -0
- package/src/sessions/sanitize.ts +222 -0
- package/src/sessions/service.ts +1533 -0
- package/src/sessions/setup.ts +477 -0
- package/src/sessions/sources.ts +518 -0
- package/src/sessions/state.ts +118 -0
- package/src/sessions/types.ts +457 -0
- package/src/store/migrations/031-runtime-independent-vectors.ts +29 -0
- package/src/store/migrations/032-vector-runtime-callers.ts +17 -0
- package/src/store/migrations/index.ts +4 -0
- package/src/store/sqlite/adapter.ts +76 -16
- package/src/store/sqlite/scoped-index.ts +9 -0
- package/src/store/types.ts +11 -1
- package/src/store/vector/lazy.ts +46 -43
- package/src/store/vector/runtime-compat.ts +651 -0
- package/src/store/vector/sqlite-vec.ts +20 -2
- package/src/store/vector/status.ts +276 -35
- package/src/store/vector/types.ts +2 -0
- package/src/store/vector/variant-search.ts +71 -23
- package/src/store/vector/variants.ts +49 -14
- package/browser-extension/artifacts/gno-browser-clipper-v2.5.1.zip.sha256 +0 -1
package/src/mcp/context.ts
CHANGED
|
@@ -66,11 +66,14 @@ export interface ToolContextSnapshot {
|
|
|
66
66
|
* session id, so this is what keeps two modern callers apart.
|
|
67
67
|
*/
|
|
68
68
|
requestIdentity?: string;
|
|
69
|
+
/** Stable request-receipt namespace of the authorized HTTP identity. */
|
|
70
|
+
requestNamespace?: string;
|
|
69
71
|
}
|
|
70
72
|
|
|
71
73
|
export interface RequestScope {
|
|
72
74
|
egress: NonNullable<ToolContextSnapshot["egress"]>;
|
|
73
75
|
requestIdentity?: string;
|
|
76
|
+
requestNamespace?: string;
|
|
74
77
|
}
|
|
75
78
|
|
|
76
79
|
export interface ToolContext {
|
|
@@ -107,6 +110,8 @@ export interface ToolContext {
|
|
|
107
110
|
getEgressContext?: () => ToolContextSnapshot["egress"];
|
|
108
111
|
/** Per-caller identity of the current request; absent on stdio. */
|
|
109
112
|
getRequestIdentity?: () => string | undefined;
|
|
113
|
+
/** Request-receipt namespace of the current HTTP caller; absent on stdio. */
|
|
114
|
+
getRequestNamespace?: () => string | undefined;
|
|
110
115
|
runWithEgressContext?<T>(
|
|
111
116
|
egress: NonNullable<ToolContextSnapshot["egress"]>,
|
|
112
117
|
operation: () => Promise<T>,
|
|
@@ -115,6 +120,15 @@ export interface ToolContext {
|
|
|
115
120
|
runWithSnapshot?<T>(operation: () => Promise<T>): Promise<T>;
|
|
116
121
|
}
|
|
117
122
|
|
|
123
|
+
/**
|
|
124
|
+
* Host absolute paths reach only stdio callers, which run on the owner's
|
|
125
|
+
* machine. Every Streamable HTTP request runs inside an egress context,
|
|
126
|
+
* whatever its peer, so HTTP callers get URIs and relative paths only.
|
|
127
|
+
*/
|
|
128
|
+
export const exposesHostPaths = (
|
|
129
|
+
ctx: Pick<ToolContext, "getEgressContext">
|
|
130
|
+
): boolean => ctx.getEgressContext?.() === undefined;
|
|
131
|
+
|
|
118
132
|
export interface CreateToolContextOptions {
|
|
119
133
|
store: SqliteAdapter;
|
|
120
134
|
getConfig: () => Config;
|
|
@@ -216,6 +230,7 @@ export function createToolContext(
|
|
|
216
230
|
requestSnapshot.getStore()?.egress?.authorizationEpoch?.value,
|
|
217
231
|
getEgressContext: () => requestSnapshot.getStore()?.egress,
|
|
218
232
|
getRequestIdentity: () => requestSnapshot.getStore()?.requestIdentity,
|
|
233
|
+
getRequestNamespace: () => requestSnapshot.getStore()?.requestNamespace,
|
|
219
234
|
runWithEgressContext<T>(
|
|
220
235
|
egress: NonNullable<ToolContextSnapshot["egress"]>,
|
|
221
236
|
operation: () => Promise<T>,
|
|
@@ -228,6 +243,7 @@ export function createToolContext(
|
|
|
228
243
|
collections: config.collections,
|
|
229
244
|
egress,
|
|
230
245
|
requestIdentity: scope?.requestIdentity,
|
|
246
|
+
requestNamespace: scope?.requestNamespace,
|
|
231
247
|
},
|
|
232
248
|
operation
|
|
233
249
|
);
|
|
@@ -241,6 +257,7 @@ export function createToolContext(
|
|
|
241
257
|
collections: config.collections,
|
|
242
258
|
egress: current?.egress,
|
|
243
259
|
requestIdentity: current?.requestIdentity,
|
|
260
|
+
requestNamespace: current?.requestNamespace,
|
|
244
261
|
},
|
|
245
262
|
operation
|
|
246
263
|
);
|
package/src/mcp/http-egress.ts
CHANGED
|
@@ -57,8 +57,12 @@ export const MCP_HTTP_EGRESS_TOOLS = {
|
|
|
57
57
|
gno_remember: "source",
|
|
58
58
|
gno_remove_collection: "metadata",
|
|
59
59
|
gno_rename_note: "metadata",
|
|
60
|
+
gno_request_status: "metadata",
|
|
60
61
|
gno_search: "snippet",
|
|
61
62
|
gno_section: "metadata",
|
|
63
|
+
gno_sessions_automation_run: "metadata",
|
|
64
|
+
gno_sessions_import: "metadata",
|
|
65
|
+
gno_sessions_status: "metadata",
|
|
62
66
|
gno_similar: "snippet",
|
|
63
67
|
gno_peek: "metadata",
|
|
64
68
|
gno_status: "metadata",
|
|
@@ -15,6 +15,7 @@ import type {
|
|
|
15
15
|
|
|
16
16
|
import { MCP_SERVER_NAME, VERSION } from "../app/constants";
|
|
17
17
|
import { EgressDeniedError } from "../core/egress-enforcement";
|
|
18
|
+
import { httpMcpRequestNamespace } from "../core/request-receipts";
|
|
18
19
|
import { withInferenceScope } from "../llm/inference-scope";
|
|
19
20
|
import { createMcpServerSurface, type ToolContext } from "./context";
|
|
20
21
|
import {
|
|
@@ -445,6 +446,7 @@ export class HttpMcpTransport {
|
|
|
445
446
|
this.#runtime.mcpContext.serverInstanceId,
|
|
446
447
|
context.identity
|
|
447
448
|
),
|
|
449
|
+
requestNamespace: httpMcpRequestNamespace(context.identity),
|
|
448
450
|
}
|
|
449
451
|
)
|
|
450
452
|
: await handle();
|
|
@@ -21,6 +21,7 @@ import { resolveEffectiveIndex } from "../../core/indexed-reference";
|
|
|
21
21
|
import { normalizeTag, validateTag } from "../../core/tags";
|
|
22
22
|
import { normalizeCollectionName } from "../../core/validation";
|
|
23
23
|
import { openScopedIndexStore } from "../../store/sqlite/scoped-index";
|
|
24
|
+
import { exposesHostPaths } from "../context";
|
|
24
25
|
|
|
25
26
|
// Tags resource URI prefix
|
|
26
27
|
const TAGS_URI = `${URI_PREFIX}tags`;
|
|
@@ -53,15 +54,15 @@ function formatResourceContent(
|
|
|
53
54
|
ctx: ToolContext,
|
|
54
55
|
indexName = ctx.indexName
|
|
55
56
|
): string {
|
|
56
|
-
//
|
|
57
|
+
// Host path for stdio callers; HTTP callers see the relative path.
|
|
57
58
|
const uriParsed = parseUri(doc.uri);
|
|
58
|
-
let
|
|
59
|
-
if (uriParsed) {
|
|
59
|
+
let source = doc.relPath;
|
|
60
|
+
if (uriParsed && exposesHostPaths(ctx)) {
|
|
60
61
|
const collection = ctx.collections.find(
|
|
61
62
|
(c) => c.name === uriParsed.collection
|
|
62
63
|
);
|
|
63
64
|
if (collection) {
|
|
64
|
-
|
|
65
|
+
source = pathJoin(collection.path, doc.relPath);
|
|
65
66
|
}
|
|
66
67
|
}
|
|
67
68
|
|
|
@@ -72,7 +73,7 @@ function formatResourceContent(
|
|
|
72
73
|
const displayUri = decorateUriForIndex(doc.uri, indexName);
|
|
73
74
|
const header = `<!-- ${displayUri}
|
|
74
75
|
docid: ${doc.docid}
|
|
75
|
-
source: ${
|
|
76
|
+
source: ${source}
|
|
76
77
|
mime: ${doc.sourceMime}${langLine}
|
|
77
78
|
-->
|
|
78
79
|
|
|
@@ -32,7 +32,7 @@ export const MCP_CORE_TOOL_DESCRIPTIONS: Readonly<Record<string, string>> = {
|
|
|
32
32
|
gno_recall:
|
|
33
33
|
"Call before answering about the user's preferences, decisions, people, or prior work, and before gno_remember to find the predecessor of a changed fact. Retrieves current facts from a memory-managed collection for the explicit scopes you pass; superseded facts are excluded. Returns at most 8 facts within 512 tokens by default, each with text, scopes, provenance, gno:// cite, and content hash, plus a content-free receipt. Pass that receipt to gno_remember when a stored fact derives from this recall. An empty result names the command that stores the first fact.",
|
|
34
34
|
gno_capture:
|
|
35
|
-
"Call to create a new note from text the user wants kept: pass collection and content (or a presetId scaffold), optionally title, path or folderPath, tags, and source provenance. Writes the file to disk with source: frontmatter, syncs it for keyword search, and returns a receipt with uri, docid, relPath, absPath, contentHash, collisionPolicyResult, and sync and embed status. Embedding is a separate step (embed.status stays short of completed until gno_index or gno_embed runs), so vector search sees the note later. An existing target follows collisionPolicy: error, open_existing, or create_with_suffix.",
|
|
35
|
+
"Call to create a new note from text the user wants kept: pass collection and content (or a presetId scaffold), optionally title, path or folderPath, tags, and source provenance. Writes the file to disk with source: frontmatter, syncs it for keyword search, and returns a receipt with uri, docid, relPath, absPath (stdio only), contentHash, collisionPolicyResult, and sync and embed status. Embedding is a separate step (embed.status stays short of completed until gno_index or gno_embed runs), so vector search sees the note later. An existing target follows collisionPolicy: error, open_existing, or create_with_suffix.",
|
|
36
36
|
gno_remember:
|
|
37
37
|
"Call when the user states a durable preference, decision, or fact worth recalling later; documents go through gno_capture and existing notes through file edits. Stores one fact in a memory-managed collection under the explicit scopes you pass. Without decision it returns likely matches and writes nothing; decision=add writes a new fact; decision=supersede replaces predecessorUri after a hash check, one successor per fact. Returns outcome (candidates, existing, added, or superseded) with the stored record; an exact duplicate returns the existing record. Text that replays a recall receipt span or declares a gno:// origin is rejected. The fact is lexically searchable when the call returns.",
|
|
38
38
|
};
|
package/src/mcp/tools/capture.ts
CHANGED
|
@@ -4,33 +4,36 @@
|
|
|
4
4
|
* @module src/mcp/tools/capture
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
// node:fs/promises for mkdir (no Bun equivalent for structure ops)
|
|
8
|
-
import { mkdir } from "node:fs/promises";
|
|
9
7
|
// node:path for path utils (no Bun path utils)
|
|
10
|
-
import {
|
|
8
|
+
import { extname } from "node:path";
|
|
11
9
|
|
|
12
10
|
import type { NoteCollisionPolicy } from "../../core/note-creation";
|
|
13
11
|
import type { NotePresetId } from "../../core/note-presets";
|
|
14
12
|
import type { ToolContext } from "../server";
|
|
15
13
|
|
|
16
14
|
import {
|
|
17
|
-
buildCaptureReceipt,
|
|
18
15
|
CaptureSyncError,
|
|
19
|
-
ensureCapturedFileIndexed,
|
|
20
16
|
listCaptureDiskRelPaths,
|
|
21
17
|
planCapture,
|
|
22
|
-
syncCapturedFile,
|
|
23
18
|
type CaptureInput as SharedCaptureInput,
|
|
19
|
+
type CapturePlan,
|
|
24
20
|
type CaptureReceipt,
|
|
25
|
-
type SyncCapturedFileResult,
|
|
26
21
|
} from "../../core/capture";
|
|
27
|
-
import {
|
|
22
|
+
import {
|
|
23
|
+
publishCapture,
|
|
24
|
+
type PublishedCapture,
|
|
25
|
+
} from "../../core/capture-publish";
|
|
28
26
|
import { MCP_ERRORS } from "../../core/errors";
|
|
29
|
-
import { withWriteLock } from "../../core/file-lock";
|
|
30
27
|
import { recordContentMutation } from "../../core/mutation-generations";
|
|
28
|
+
import {
|
|
29
|
+
formatRequestReceiptLine,
|
|
30
|
+
type RequestReceiptInfo,
|
|
31
|
+
requestLedgerPath,
|
|
32
|
+
} from "../../core/request-receipts";
|
|
31
33
|
import { normalizeCollectionName } from "../../core/validation";
|
|
32
34
|
import { DEFAULT_LOCK_WAIT_MS } from "../../core/write-lease";
|
|
33
35
|
import { runTool, type ToolResult } from "./index";
|
|
36
|
+
import { mcpRequestNamespace, rethrowRequestError } from "./request-status";
|
|
34
37
|
|
|
35
38
|
interface CaptureInput extends Omit<
|
|
36
39
|
SharedCaptureInput,
|
|
@@ -39,13 +42,14 @@ interface CaptureInput extends Omit<
|
|
|
39
42
|
path?: string;
|
|
40
43
|
collisionPolicy?: NoteCollisionPolicy;
|
|
41
44
|
presetId?: NotePresetId;
|
|
45
|
+
requestId?: string;
|
|
42
46
|
}
|
|
43
47
|
|
|
44
48
|
type McpCaptureResult = CaptureReceipt & {
|
|
45
49
|
docid: string;
|
|
46
|
-
absPath: string;
|
|
47
50
|
overwritten: boolean;
|
|
48
51
|
serverInstanceId: string;
|
|
52
|
+
request?: RequestReceiptInfo;
|
|
49
53
|
};
|
|
50
54
|
|
|
51
55
|
const SENSITIVE_SUBPATHS = new Set([
|
|
@@ -75,7 +79,7 @@ function formatCaptureResult(result: McpCaptureResult): string {
|
|
|
75
79
|
const lines: string[] = [];
|
|
76
80
|
lines.push(`Doc: ${result.docid}`);
|
|
77
81
|
lines.push(`URI: ${result.uri}`);
|
|
78
|
-
lines.push(`Path: ${result.absPath}`);
|
|
82
|
+
if (result.absPath) lines.push(`Path: ${result.absPath}`);
|
|
79
83
|
lines.push(`Created: ${result.created ? "yes" : "no"}`);
|
|
80
84
|
lines.push(`Opened existing: ${result.openedExisting ? "yes" : "no"}`);
|
|
81
85
|
lines.push(`Overwritten: ${result.overwritten ? "yes" : "no"}`);
|
|
@@ -86,6 +90,7 @@ function formatCaptureResult(result: McpCaptureResult): string {
|
|
|
86
90
|
if (result.tags.length > 0) {
|
|
87
91
|
lines.push(`Tags: ${result.tags.join(", ")}`);
|
|
88
92
|
}
|
|
93
|
+
if (result.request) lines.push(formatRequestReceiptLine(result.request));
|
|
89
94
|
return lines.join("\n");
|
|
90
95
|
}
|
|
91
96
|
|
|
@@ -112,7 +117,7 @@ function rethrowCaptureError(error: unknown): never {
|
|
|
112
117
|
if (error instanceof CaptureSyncError) {
|
|
113
118
|
throw new Error(`${error.code}: ${error.message}`);
|
|
114
119
|
}
|
|
115
|
-
|
|
120
|
+
return rethrowRequestError(error);
|
|
116
121
|
}
|
|
117
122
|
|
|
118
123
|
export function handleCapture(
|
|
@@ -140,83 +145,80 @@ export function handleCapture(
|
|
|
140
145
|
// Write + lexical sync complete under the shared write lease: the tool
|
|
141
146
|
// succeeds only once the capture is retrievable (v1.38 contention
|
|
142
147
|
// contract: wait for the lease, LOCKED when it stays busy).
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
diskRelPaths: await listCaptureDiskRelPaths(collection.path),
|
|
157
|
-
});
|
|
158
|
-
} catch (error) {
|
|
159
|
-
const message =
|
|
160
|
-
error instanceof Error ? error.message : String(error);
|
|
161
|
-
throw new Error(`${MCP_ERRORS.INVALID_INPUT.code}: ${message}`);
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
assertNotSensitive(plan.relPath);
|
|
165
|
-
|
|
166
|
-
const absPath = join(collection.path, plan.relPath);
|
|
167
|
-
const syncInput = {
|
|
168
|
-
collection,
|
|
169
|
-
store: ctx.store,
|
|
170
|
-
relPath: plan.relPath,
|
|
171
|
-
absPath,
|
|
172
|
-
config: ctx.config,
|
|
173
|
-
};
|
|
174
|
-
|
|
175
|
-
let synced: SyncCapturedFileResult;
|
|
176
|
-
let overwritten = false;
|
|
177
|
-
try {
|
|
178
|
-
if (plan.openedExisting) {
|
|
179
|
-
synced = await ensureCapturedFileIndexed(syncInput);
|
|
180
|
-
} else {
|
|
181
|
-
overwritten =
|
|
182
|
-
(await Bun.file(absPath).exists()) && args.overwrite === true;
|
|
183
|
-
await mkdir(dirname(absPath), { recursive: true });
|
|
184
|
-
await writeCapturePlanFile(plan, absPath);
|
|
185
|
-
synced = await syncCapturedFile(syncInput);
|
|
148
|
+
const input = buildSharedInput(args, collection.name);
|
|
149
|
+
let published: PublishedCapture;
|
|
150
|
+
try {
|
|
151
|
+
published = await publishCapture({
|
|
152
|
+
collection,
|
|
153
|
+
store: ctx.store,
|
|
154
|
+
lockPath: ctx.writeLockPath,
|
|
155
|
+
lockWaitMs: DEFAULT_LOCK_WAIT_MS,
|
|
156
|
+
config: ctx.config,
|
|
157
|
+
plan: async () => {
|
|
158
|
+
const existingDocs = await ctx.store.listDocuments(collectionName);
|
|
159
|
+
if (!existingDocs.ok) {
|
|
160
|
+
throw new Error(existingDocs.error.message);
|
|
186
161
|
}
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
162
|
+
let plan: CapturePlan;
|
|
163
|
+
try {
|
|
164
|
+
plan = planCapture({
|
|
165
|
+
input,
|
|
166
|
+
existingRelPaths: existingDocs.value.map((doc) => doc.relPath),
|
|
167
|
+
diskRelPaths: await listCaptureDiskRelPaths(collection.path),
|
|
168
|
+
});
|
|
169
|
+
} catch (error) {
|
|
170
|
+
const message =
|
|
171
|
+
error instanceof Error ? error.message : String(error);
|
|
172
|
+
throw new Error(`${MCP_ERRORS.INVALID_INPUT.code}: ${message}`);
|
|
173
|
+
}
|
|
174
|
+
assertNotSensitive(plan.relPath);
|
|
175
|
+
return plan;
|
|
176
|
+
},
|
|
177
|
+
afterSync: async (synced, receipt) => {
|
|
178
|
+
if (synced.result) {
|
|
179
|
+
recordContentMutation(synced.result, ctx.markContentMutation);
|
|
180
|
+
}
|
|
181
|
+
const isMarkdown =
|
|
182
|
+
receipt.relPath.endsWith(".md") ||
|
|
183
|
+
receipt.relPath.endsWith(".markdown");
|
|
184
|
+
if (
|
|
185
|
+
!isMarkdown &&
|
|
186
|
+
!receipt.openedExisting &&
|
|
187
|
+
receipt.tags.length > 0
|
|
188
|
+
) {
|
|
189
|
+
const tagResult = await ctx.store.setDocTags(
|
|
190
|
+
synced.documentId,
|
|
191
|
+
receipt.tags,
|
|
192
|
+
"user"
|
|
205
193
|
);
|
|
194
|
+
if (!tagResult.ok) {
|
|
195
|
+
console.error(
|
|
196
|
+
`[MCP] Warning: Document created but tags not stored: ${tagResult.error.message}`
|
|
197
|
+
);
|
|
198
|
+
}
|
|
206
199
|
}
|
|
207
|
-
}
|
|
200
|
+
},
|
|
201
|
+
request:
|
|
202
|
+
args.requestId === undefined
|
|
203
|
+
? undefined
|
|
204
|
+
: {
|
|
205
|
+
ledgerPath: requestLedgerPath(ctx.store.getDbPath()),
|
|
206
|
+
namespace: mcpRequestNamespace(ctx),
|
|
207
|
+
requestId: args.requestId,
|
|
208
|
+
input,
|
|
209
|
+
},
|
|
210
|
+
});
|
|
211
|
+
} catch (error) {
|
|
212
|
+
rethrowCaptureError(error);
|
|
213
|
+
}
|
|
208
214
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
}) as McpCaptureResult;
|
|
217
|
-
},
|
|
218
|
-
DEFAULT_LOCK_WAIT_MS
|
|
219
|
-
);
|
|
215
|
+
return {
|
|
216
|
+
...published.receipt,
|
|
217
|
+
docid: published.receipt.docid ?? "",
|
|
218
|
+
overwritten: published.receipt.overwritten ?? false,
|
|
219
|
+
serverInstanceId: ctx.serverInstanceId,
|
|
220
|
+
...(published.request ? { request: published.request } : {}),
|
|
221
|
+
} as McpCaptureResult;
|
|
220
222
|
},
|
|
221
223
|
formatCaptureResult
|
|
222
224
|
);
|
package/src/mcp/tools/index.ts
CHANGED
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
compiledContextPreviewSchema,
|
|
21
21
|
compiledContextCheckSchema,
|
|
22
22
|
} from "../../core/compiled-context";
|
|
23
|
+
import { withoutHostPaths } from "../../core/host-paths";
|
|
23
24
|
import { NOTE_PRESETS, type NotePresetId } from "../../core/note-presets";
|
|
24
25
|
import { RETRIEVAL_TRACE_METADATA } from "../../core/retrieval-trace-session";
|
|
25
26
|
import { normalizeTag } from "../../core/tags";
|
|
@@ -31,6 +32,7 @@ import {
|
|
|
31
32
|
assertInferenceActive,
|
|
32
33
|
acquireInferencePermit,
|
|
33
34
|
} from "../../llm/inference-scope";
|
|
35
|
+
import { exposesHostPaths } from "../context";
|
|
34
36
|
import { profileToolDescription } from "../tool-descriptions-core";
|
|
35
37
|
import {
|
|
36
38
|
createProfileToolRegistrar,
|
|
@@ -101,6 +103,12 @@ import { handleMultiGet } from "./multi-get";
|
|
|
101
103
|
import { handlePeek, PEEK_MCP_ANNOTATIONS } from "./peek";
|
|
102
104
|
import { handleQuery, handleQueryDiagnose } from "./query";
|
|
103
105
|
import { handleRemoveCollection } from "./remove-collection";
|
|
106
|
+
import {
|
|
107
|
+
handleRequestStatus,
|
|
108
|
+
REQUEST_STATUS_MCP_ANNOTATIONS,
|
|
109
|
+
requestIdInputSchema,
|
|
110
|
+
requestStatusInputSchema,
|
|
111
|
+
} from "./request-status";
|
|
104
112
|
import { handleSearch } from "./search";
|
|
105
113
|
import {
|
|
106
114
|
handleSection,
|
|
@@ -108,6 +116,17 @@ import {
|
|
|
108
116
|
sectionInputSchema,
|
|
109
117
|
sectionOutputSchema,
|
|
110
118
|
} from "./sections";
|
|
119
|
+
import {
|
|
120
|
+
handleSessionsAutomationRun,
|
|
121
|
+
handleSessionsImport,
|
|
122
|
+
handleSessionsStatus,
|
|
123
|
+
SESSIONS_AUTOMATION_RUN_MCP_ANNOTATIONS,
|
|
124
|
+
SESSIONS_IMPORT_MCP_ANNOTATIONS,
|
|
125
|
+
SESSIONS_STATUS_MCP_ANNOTATIONS,
|
|
126
|
+
sessionsAutomationRunInputSchema,
|
|
127
|
+
sessionsImportInputSchema,
|
|
128
|
+
sessionsStatusInputSchema,
|
|
129
|
+
} from "./sessions";
|
|
111
130
|
import { handleStatus } from "./status";
|
|
112
131
|
import { handleSync } from "./sync";
|
|
113
132
|
import {
|
|
@@ -175,6 +194,8 @@ export const MCP_TOOL_DESCRIPTIONS = {
|
|
|
175
194
|
ask: "Generate one answer from a deterministic Context Capsule, verify every substantive claim against exact retained spans, and abstain unless support coverage is complete. Read-only; returns the Capsule, freshness receipt, claim verdicts, gaps, and evidence IDs.",
|
|
176
195
|
recall:
|
|
177
196
|
"Recall current facts from a memory-managed collection for explicit scopes. Call before answering about the user's preferences, decisions, people, or prior work, and before gno_remember to find the predecessor of a changed fact. Returns at most 8 facts within 512 tokens by default, each with text, scopes, provenance, gno:// cite, and content hash, plus a content-free receipt; superseded facts are excluded. Pass the receipt to gno_remember when a stored fact derives from this recall. An empty result names the command that stores the first fact.",
|
|
197
|
+
requestStatus:
|
|
198
|
+
"Look up a write request ID sent with gno_capture or gno_remember before retrying it. Returns pending, committed (with the result uri and hashes), expired, or not_found within this caller's own namespace; never returns note or fact content. Committed: do not resend. Pending: retry the same request ID later. not_found: nothing was accepted under that ID.",
|
|
178
199
|
remember:
|
|
179
200
|
"Store one fact in a memory-managed collection under explicit scopes. Call when the user states a durable preference, decision, or fact worth recalling later; use gno_capture for documents and file edits for existing notes. Without decision it returns likely matches and writes nothing; decision=add writes a new fact; decision=supersede replaces predecessorUri after a hash check, one successor per fact. Exact duplicates return the existing record. Text that replays a recall receipt span or declares a gno:// origin is rejected. The fact is lexically searchable when the call returns.",
|
|
180
201
|
} as const;
|
|
@@ -366,6 +387,7 @@ export const captureInputSchema = z.object({
|
|
|
366
387
|
.describe(
|
|
367
388
|
"Structured provenance metadata written under source frontmatter"
|
|
368
389
|
),
|
|
390
|
+
requestId: requestIdInputSchema.optional(),
|
|
369
391
|
});
|
|
370
392
|
|
|
371
393
|
const addCollectionInputSchema = z.object({
|
|
@@ -952,6 +974,10 @@ export interface ToolResult {
|
|
|
952
974
|
// DRY Helper: Exception Firewall + Mutex + Response Shaping
|
|
953
975
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
954
976
|
|
|
977
|
+
/** Tool data as the current caller may see it (no host paths over HTTP). */
|
|
978
|
+
const forCaller = <T>(ctx: ToolContext, data: T): T =>
|
|
979
|
+
exposesHostPaths(ctx) ? data : withoutHostPaths(data);
|
|
980
|
+
|
|
955
981
|
export async function runTool<T>(
|
|
956
982
|
ctx: ToolContext,
|
|
957
983
|
name: string,
|
|
@@ -970,12 +996,13 @@ export async function runTool<T>(
|
|
|
970
996
|
const release = await acquireInferencePermit(() => ctx.toolMutex.acquire());
|
|
971
997
|
try {
|
|
972
998
|
assertInferenceActive();
|
|
973
|
-
const
|
|
999
|
+
const raw = await (ctx.runWithSnapshot?.(fn) ?? fn());
|
|
974
1000
|
assertInferenceActive();
|
|
975
1001
|
const traceMetadata =
|
|
976
|
-
|
|
977
|
-
? (
|
|
1002
|
+
raw !== null && typeof raw === "object"
|
|
1003
|
+
? (raw as Record<PropertyKey, unknown>)[RETRIEVAL_TRACE_METADATA]
|
|
978
1004
|
: undefined;
|
|
1005
|
+
const data = forCaller(ctx, raw);
|
|
979
1006
|
return {
|
|
980
1007
|
content: [{ type: "text", text: formatText(data) }],
|
|
981
1008
|
structuredContent: data as { [x: string]: unknown },
|
|
@@ -1019,7 +1046,7 @@ export async function runToolNoMutex<T>(
|
|
|
1019
1046
|
|
|
1020
1047
|
try {
|
|
1021
1048
|
assertInferenceActive();
|
|
1022
|
-
const data = await (ctx.runWithSnapshot?.(fn) ?? fn());
|
|
1049
|
+
const data = forCaller(ctx, await (ctx.runWithSnapshot?.(fn) ?? fn()));
|
|
1023
1050
|
assertInferenceActive();
|
|
1024
1051
|
return {
|
|
1025
1052
|
content: [{ type: "text", text: formatText(data) }],
|
|
@@ -1141,6 +1168,17 @@ export function registerTools(server: McpServer, ctx: ToolContext): void {
|
|
|
1141
1168
|
})
|
|
1142
1169
|
);
|
|
1143
1170
|
|
|
1171
|
+
registerTool(
|
|
1172
|
+
"gno_sessions_status",
|
|
1173
|
+
{
|
|
1174
|
+
description:
|
|
1175
|
+
"Status of this session-archive instance: archive collections, owner-registered session sources (by ID, no host paths), pending/incomplete/failed units, last import, and opt-in automation profiles (enabled hook/schedule, daemon availability, pending/running/partial/failed, last success, next due, recovery action). Only available when the server runs on a dedicated session-archive config/index pair; nothing is imported unless the owner enabled it.",
|
|
1176
|
+
inputSchema: sessionsStatusInputSchema,
|
|
1177
|
+
annotations: SESSIONS_STATUS_MCP_ANNOTATIONS,
|
|
1178
|
+
},
|
|
1179
|
+
() => handleSessionsStatus(ctx)
|
|
1180
|
+
);
|
|
1181
|
+
|
|
1144
1182
|
registerTool(
|
|
1145
1183
|
"gno_search",
|
|
1146
1184
|
{
|
|
@@ -1501,6 +1539,28 @@ export function registerTools(server: McpServer, ctx: ToolContext): void {
|
|
|
1501
1539
|
})
|
|
1502
1540
|
);
|
|
1503
1541
|
|
|
1542
|
+
registerTool(
|
|
1543
|
+
"gno_sessions_import",
|
|
1544
|
+
{
|
|
1545
|
+
description:
|
|
1546
|
+
"Manually import one owner-registered session source into the session archive and sync it for search. Takes a source ID only (never a host path); dryRun=true reports without writing. Returns a receipt with imported/updated/unchanged/skipped/incomplete/failed counts, lexical readiness and embedding backlog; partial imports stay visible and are retried by the next call.",
|
|
1547
|
+
inputSchema: sessionsImportInputSchema,
|
|
1548
|
+
annotations: SESSIONS_IMPORT_MCP_ANNOTATIONS,
|
|
1549
|
+
},
|
|
1550
|
+
(args) => handleSessionsImport(args, ctx)
|
|
1551
|
+
);
|
|
1552
|
+
|
|
1553
|
+
registerTool(
|
|
1554
|
+
"gno_sessions_automation_run",
|
|
1555
|
+
{
|
|
1556
|
+
description:
|
|
1557
|
+
"Run one owner-configured session automation profile now: imports its registered sources through the manual importer and records the outcome in automation status. Takes a profile ID only; it cannot enable hooks or schedules, add sources or change destinations. Returns the run outcome (complete, up_to_date, partial, failed, or not_started with a reason such as busy) and one import receipt per source.",
|
|
1558
|
+
inputSchema: sessionsAutomationRunInputSchema,
|
|
1559
|
+
annotations: SESSIONS_AUTOMATION_RUN_MCP_ANNOTATIONS,
|
|
1560
|
+
},
|
|
1561
|
+
(args) => handleSessionsAutomationRun(args, ctx)
|
|
1562
|
+
);
|
|
1563
|
+
|
|
1504
1564
|
registerTool(
|
|
1505
1565
|
"gno_capture",
|
|
1506
1566
|
{
|
|
@@ -1513,6 +1573,19 @@ export function registerTools(server: McpServer, ctx: ToolContext): void {
|
|
|
1513
1573
|
(args) => handleCapture(args, ctx)
|
|
1514
1574
|
);
|
|
1515
1575
|
|
|
1576
|
+
registerTool(
|
|
1577
|
+
"gno_request_status",
|
|
1578
|
+
{
|
|
1579
|
+
description: describe(
|
|
1580
|
+
"gno_request_status",
|
|
1581
|
+
MCP_TOOL_DESCRIPTIONS.requestStatus
|
|
1582
|
+
),
|
|
1583
|
+
inputSchema: requestStatusInputSchema,
|
|
1584
|
+
annotations: REQUEST_STATUS_MCP_ANNOTATIONS,
|
|
1585
|
+
},
|
|
1586
|
+
(args) => handleRequestStatus(args, ctx)
|
|
1587
|
+
);
|
|
1588
|
+
|
|
1516
1589
|
registerTool(
|
|
1517
1590
|
"gno_add_collection",
|
|
1518
1591
|
{
|
|
@@ -14,6 +14,7 @@ import type { ToolContext } from "../server";
|
|
|
14
14
|
|
|
15
15
|
import { type RememberResult } from "../../core/memory";
|
|
16
16
|
import { MEMORY_MAX_FACT_BYTES } from "../../core/memory-record";
|
|
17
|
+
import { formatRequestReceiptLine } from "../../core/request-receipts";
|
|
17
18
|
import { runTool, type ToolResult } from "./index";
|
|
18
19
|
import {
|
|
19
20
|
createMcpMemoryService,
|
|
@@ -22,6 +23,7 @@ import {
|
|
|
22
23
|
resolveMcpMemoryIdentity,
|
|
23
24
|
rethrowMemoryError,
|
|
24
25
|
} from "./memory-shared";
|
|
26
|
+
import { requestIdInputSchema } from "./request-status";
|
|
25
27
|
|
|
26
28
|
export const REMEMBER_MCP_ANNOTATIONS = {
|
|
27
29
|
readOnlyHint: false,
|
|
@@ -92,6 +94,7 @@ export const rememberInputSchema = z.object({
|
|
|
92
94
|
.min(1)
|
|
93
95
|
.optional()
|
|
94
96
|
.describe("Free-text evidence for the fact (where it came from)"),
|
|
97
|
+
requestId: requestIdInputSchema.optional(),
|
|
95
98
|
});
|
|
96
99
|
|
|
97
100
|
export type RememberToolInput = z.infer<typeof rememberInputSchema>;
|
|
@@ -124,7 +127,7 @@ export function formatRememberResult(result: RememberResult): string {
|
|
|
124
127
|
lines.push(`Outcome: ${result.outcome}`);
|
|
125
128
|
lines.push(`URI: ${result.record.uri}`);
|
|
126
129
|
lines.push(`Hash: ${result.record.contentHash}`);
|
|
127
|
-
lines.push(`Path: ${result.absPath}`);
|
|
130
|
+
if (result.absPath) lines.push(`Path: ${result.absPath}`);
|
|
128
131
|
lines.push(`Sync: ${result.sync.status}`);
|
|
129
132
|
if (result.record.supersedes.length > 0) {
|
|
130
133
|
lines.push(`Supersedes: ${result.record.supersedes.join(", ")}`);
|
|
@@ -132,6 +135,9 @@ export function formatRememberResult(result: RememberResult): string {
|
|
|
132
135
|
break;
|
|
133
136
|
}
|
|
134
137
|
lines.push(matching);
|
|
138
|
+
if ("request" in result && result.request) {
|
|
139
|
+
lines.push(formatRequestReceiptLine(result.request));
|
|
140
|
+
}
|
|
135
141
|
return lines.join("\n");
|
|
136
142
|
}
|
|
137
143
|
|
|
@@ -163,6 +169,7 @@ export function handleRemember(
|
|
|
163
169
|
receipt: args.receipt,
|
|
164
170
|
derivedFrom: args.derivedFrom,
|
|
165
171
|
source: args.source,
|
|
172
|
+
requestId: args.requestId,
|
|
166
173
|
});
|
|
167
174
|
} catch (error) {
|
|
168
175
|
return rethrowMemoryError(error);
|
|
@@ -19,6 +19,8 @@ import {
|
|
|
19
19
|
MemoryService,
|
|
20
20
|
} from "../../core/memory";
|
|
21
21
|
import { MEMORY_MAX_SCOPES } from "../../core/memory-record";
|
|
22
|
+
import { requestLedgerPath } from "../../core/request-receipts";
|
|
23
|
+
import { mcpRequestNamespace, rethrowRequestError } from "./request-status";
|
|
22
24
|
|
|
23
25
|
/** Caller name when the MCP client sent no implementation name. */
|
|
24
26
|
const DEFAULT_MCP_CALLER = "mcp";
|
|
@@ -74,6 +76,10 @@ export function createMcpMemoryService(ctx: ToolContext): MemoryService {
|
|
|
74
76
|
config: ctx.config,
|
|
75
77
|
collections: ctx.collections,
|
|
76
78
|
lockPath: ctx.writeLockPath,
|
|
79
|
+
requests: {
|
|
80
|
+
ledgerPath: requestLedgerPath(ctx.store.getDbPath()),
|
|
81
|
+
namespace: mcpRequestNamespace(ctx),
|
|
82
|
+
},
|
|
77
83
|
});
|
|
78
84
|
}
|
|
79
85
|
|
|
@@ -82,5 +88,5 @@ export function rethrowMemoryError(error: unknown): never {
|
|
|
82
88
|
if (error instanceof MemoryError) {
|
|
83
89
|
throw new Error(`${error.code}: ${error.message}`);
|
|
84
90
|
}
|
|
85
|
-
|
|
91
|
+
return rethrowRequestError(error);
|
|
86
92
|
}
|