@gmickel/gno 2.4.0 → 2.6.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 +69 -6
- package/assets/skill/README.md +5 -1
- package/assets/skill/SKILL.md +98 -1
- package/assets/skill/cli-reference.md +140 -0
- package/assets/skill/examples.md +45 -0
- package/assets/skill/mcp-reference.md +62 -0
- 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.4.0.zip → gno-browser-clipper-v2.6.0.zip} +0 -0
- package/browser-extension/artifacts/gno-browser-clipper-v2.6.0.zip.sha256 +1 -0
- package/browser-extension/dist/manifest.json +1 -1
- package/package.json +2 -1
- package/spec/cli.md +358 -24
- package/spec/compiled-context.md +68 -0
- package/spec/mcp.md +223 -2
- package/spec/output-schemas/capture-receipt.schema.json +3 -0
- package/spec/output-schemas/compiled-context-check.schema.json +44 -0
- package/spec/output-schemas/compiled-context-file.schema.json +165 -0
- package/spec/output-schemas/compiled-context-preview.schema.json +142 -0
- package/spec/output-schemas/mcp-capture-result.schema.json +3 -0
- package/spec/output-schemas/memory-remember.schema.json +8 -2
- package/spec/output-schemas/request-status.schema.json +113 -0
- 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/src/app/compiled-context-files.ts +361 -0
- package/src/app/compiled-context.ts +240 -0
- package/src/app/context-surface.ts +7 -2
- package/src/cli/commands/ask.ts +14 -2
- package/src/cli/commands/audit.ts +4 -0
- package/src/cli/commands/capture.ts +55 -96
- package/src/cli/commands/context-compiled.ts +136 -0
- package/src/cli/commands/daemon.ts +41 -0
- package/src/cli/commands/ls.ts +3 -0
- package/src/cli/commands/memory.ts +12 -3
- 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/errors.ts +10 -3
- package/src/cli/program.ts +458 -1
- 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 +3 -0
- package/src/core/compiled-context.ts +254 -0
- package/src/core/context-budget.ts +2 -10
- package/src/core/file-lock.ts +22 -5
- package/src/core/folder-setup-planning.ts +2 -1
- package/src/core/memory-remember.ts +233 -122
- package/src/core/memory-types.ts +11 -0
- package/src/core/network-boundary-inventory.ts +16 -0
- package/src/core/request-receipts.ts +671 -0
- package/src/core/setup-receipt.ts +27 -20
- package/src/core/typed-metadata.ts +4 -0
- package/src/core/validation.ts +9 -2
- package/src/core/windows-private-path.ts +96 -0
- package/src/index.ts +11 -2
- package/src/ingestion/compiled-context.ts +15 -0
- package/src/ingestion/sync.ts +40 -8
- package/src/ingestion/walker.ts +5 -4
- package/src/llm/nodeLlamaCpp/simulator-install.ts +6 -2
- package/src/mcp/context.ts +8 -0
- package/src/mcp/http-egress.ts +15 -3
- package/src/mcp/http-transport.ts +2 -0
- package/src/mcp/retrieval-warnings.ts +24 -0
- package/src/mcp/tools/ask.ts +14 -1
- package/src/mcp/tools/capture.ts +87 -83
- package/src/mcp/tools/context.ts +46 -2
- package/src/mcp/tools/index.ts +119 -7
- package/src/mcp/tools/memory-remember.ts +7 -0
- package/src/mcp/tools/memory-shared.ts +7 -1
- package/src/mcp/tools/query.ts +3 -1
- package/src/mcp/tools/request-status.ts +73 -0
- package/src/mcp/tools/search.ts +3 -1
- package/src/mcp/tools/sessions.ts +208 -0
- package/src/mcp/tools/vsearch.ts +3 -1
- package/src/sdk/client.ts +252 -89
- package/src/sdk/index.ts +13 -0
- package/src/sdk/types.ts +82 -3
- package/src/serve/capture-service.ts +98 -32
- package/src/serve/compiled-context.ts +84 -0
- package/src/serve/config-sync.ts +3 -2
- package/src/serve/public/app.tsx +15 -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/components/sessions/snippet.tsx +53 -0
- package/src/serve/public/globals.built.css +1 -1
- package/src/serve/public/hooks/use-api.ts +10 -2
- package/src/serve/public/lib/request-intent.ts +69 -0
- package/src/serve/public/lib/workspace-actions.ts +12 -1
- package/src/serve/public/lib/workspace-tabs.ts +4 -0
- package/src/serve/public/pages/CompiledContext.tsx +364 -0
- package/src/serve/public/pages/Dashboard.tsx +17 -0
- package/src/serve/public/pages/DocView.tsx +15 -1
- package/src/serve/public/pages/DocumentEditor.tsx +139 -96
- package/src/serve/public/pages/Sessions.tsx +350 -0
- package/src/serve/resident-runtime.ts +43 -3
- 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 +248 -3
- package/src/serve/session-automation.ts +146 -0
- package/src/serve/spa-production-build.ts +6 -5
- 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/sqlite/adapter.ts +68 -16
- package/src/store/sqlite/scoped-index.ts +9 -0
- package/browser-extension/artifacts/gno-browser-clipper-v2.4.0.zip.sha256 +0 -1
|
@@ -12,6 +12,7 @@ import { dirname, join } from "node:path";
|
|
|
12
12
|
import type { ActivationVerificationReceipt } from "../store/types";
|
|
13
13
|
|
|
14
14
|
import { canonicalizeIndexName } from "../app/index-name";
|
|
15
|
+
import { withWriteLock } from "./file-lock";
|
|
15
16
|
|
|
16
17
|
export const SETUP_RECEIPT_SCHEMA_VERSION = "1.0" as const;
|
|
17
18
|
|
|
@@ -278,27 +279,33 @@ export async function persistSetupReceipt(
|
|
|
278
279
|
): Promise<void> {
|
|
279
280
|
const receiptDir = dirname(receipt.paths.receipt);
|
|
280
281
|
await mkdir(receiptDir, { recursive: true, mode: 0o700 });
|
|
281
|
-
|
|
282
|
+
// Windows cannot reliably replace the same destination concurrently.
|
|
283
|
+
// Reuse the process-shared writer lock; each completed receipt stays atomic.
|
|
284
|
+
await withWriteLock(`${receipt.paths.receipt}.lock`, async () => {
|
|
285
|
+
await chmod(receiptDir, 0o700);
|
|
282
286
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
287
|
+
const tempPath = `${receipt.paths.receipt}.tmp.${crypto.randomUUID()}`;
|
|
288
|
+
let tempFile: Awaited<ReturnType<typeof open>> | null = null;
|
|
289
|
+
try {
|
|
290
|
+
tempFile = await open(tempPath, "wx", 0o600);
|
|
291
|
+
await tempFile.writeFile(serializeSetupReceipt(receipt), "utf8");
|
|
292
|
+
// Set permissions before publication. Opening the shared destination for
|
|
293
|
+
// chmod can prevent a concurrent atomic replacement on Windows.
|
|
294
|
+
await tempFile.chmod(0o600);
|
|
295
|
+
await tempFile.sync();
|
|
296
|
+
await tempFile.close();
|
|
297
|
+
tempFile = null;
|
|
298
|
+
await rename(tempPath, receipt.paths.receipt);
|
|
299
|
+
} catch (error) {
|
|
300
|
+
await tempFile?.close().catch(() => {
|
|
301
|
+
/* best-effort temporary receipt handle cleanup */
|
|
302
|
+
});
|
|
303
|
+
await unlink(tempPath).catch(() => {
|
|
304
|
+
/* best-effort temporary receipt cleanup */
|
|
305
|
+
});
|
|
306
|
+
throw error;
|
|
307
|
+
}
|
|
308
|
+
});
|
|
302
309
|
}
|
|
303
310
|
|
|
304
311
|
export async function loadSetupReceipt(
|
|
@@ -12,6 +12,10 @@ export type MetadataPredicate =
|
|
|
12
12
|
| { op: "in" | "nin" | "all"; key: string; values: MetadataScalar[] }
|
|
13
13
|
| { op: "exists"; key: string; value: boolean };
|
|
14
14
|
|
|
15
|
+
export const METADATA_COVERAGE_GUIDANCE =
|
|
16
|
+
"Incomplete coverage is not proof of absence or of matching documents. Warnings describe the query scope, not a specific target. Keep requested filters unchanged. If an expected target is known, diagnose it with the same filter; otherwise report the coverage limit without inventing a target or broadening the search.";
|
|
17
|
+
export const METADATA_FILTER_DESCRIPTION = `Typed custom metadata predicate; intersects existing scope. ${METADATA_COVERAGE_GUIDANCE}`;
|
|
18
|
+
|
|
15
19
|
export const TYPED_METADATA_INGEST_VERSION = 7;
|
|
16
20
|
export const METADATA_LIMITS = {
|
|
17
21
|
keys: 64,
|
package/src/core/validation.ts
CHANGED
|
@@ -9,7 +9,14 @@ import { realpath } from "node:fs/promises";
|
|
|
9
9
|
// node:os for homedir (no Bun os utils)
|
|
10
10
|
import { homedir } from "node:os";
|
|
11
11
|
// node:path for path utils (no Bun path utils)
|
|
12
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
isAbsolute,
|
|
14
|
+
join,
|
|
15
|
+
parse,
|
|
16
|
+
posix as pathPosix,
|
|
17
|
+
relative,
|
|
18
|
+
sep,
|
|
19
|
+
} from "node:path";
|
|
13
20
|
|
|
14
21
|
import { toAbsolutePath } from "../config/paths";
|
|
15
22
|
|
|
@@ -87,7 +94,7 @@ export async function validateCollectionRoot(
|
|
|
87
94
|
DANGEROUS_ROOT_PATTERNS.map((p) => resolveRealPathSafe(p))
|
|
88
95
|
);
|
|
89
96
|
|
|
90
|
-
if (dangerousRoots.includes(realPath)) {
|
|
97
|
+
if (realPath === parse(realPath).root || dangerousRoots.includes(realPath)) {
|
|
91
98
|
throw new Error(`Cannot add ${inputPath}: resolves to dangerous root`);
|
|
92
99
|
}
|
|
93
100
|
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/** Windows owner-only evidence storage. Paths are data, never interpolated script.
|
|
2
|
+
* Fresh Windows objects may use the token default Owner instead of its User.
|
|
3
|
+
* Only those exact owner SIDs are accepted; allowed DACL entries remain User-only. */
|
|
4
|
+
// Use framework APIs directly: module auto-discovery depends on profile paths
|
|
5
|
+
// deliberately absent from isolated native workers.
|
|
6
|
+
const ACL = `
|
|
7
|
+
$ErrorActionPreference='Stop'
|
|
8
|
+
$p=$env:GNO_PRIVATE_PATH
|
|
9
|
+
$attributes=[IO.File]::GetAttributes($p)
|
|
10
|
+
$isDirectory=($attributes -band [IO.FileAttributes]::Directory) -ne 0
|
|
11
|
+
if (($attributes -band [IO.FileAttributes]::ReparsePoint) -ne 0) { throw 'Reparse private path' }
|
|
12
|
+
$identity=[Security.Principal.WindowsIdentity]::GetCurrent()
|
|
13
|
+
$sid=$identity.User
|
|
14
|
+
$defaultOwner=$identity.Owner
|
|
15
|
+
$acl=if ($isDirectory) { [IO.Directory]::GetAccessControl($p) } else { [IO.File]::GetAccessControl($p) }
|
|
16
|
+
$owner=$acl.GetOwner([Security.Principal.SecurityIdentifier])
|
|
17
|
+
if ($owner.Value -ne $sid.Value -and $owner.Value -ne $defaultOwner.Value) { throw 'Foreign private owner' }
|
|
18
|
+
if ($env:GNO_PRIVATE_CREATE -eq '1') {
|
|
19
|
+
if (-not $isDirectory) { throw 'Private directory required' }
|
|
20
|
+
$acl=[Security.AccessControl.DirectorySecurity]::new()
|
|
21
|
+
$acl.SetOwner($sid)
|
|
22
|
+
$acl.SetAccessRuleProtection($true,$false)
|
|
23
|
+
$rule=[Security.AccessControl.FileSystemAccessRule]::new($sid,'FullControl','ContainerInherit,ObjectInherit','None','Allow')
|
|
24
|
+
$acl.AddAccessRule($rule)
|
|
25
|
+
[IO.Directory]::SetAccessControl($p,$acl)
|
|
26
|
+
if ([IO.Directory]::GetFileSystemEntries($p).Length -ne 0) { throw 'Private directory must be empty' }
|
|
27
|
+
}
|
|
28
|
+
$acl=if ($isDirectory) { [IO.Directory]::GetAccessControl($p) } else { [IO.File]::GetAccessControl($p) }
|
|
29
|
+
$owner=$acl.GetOwner([Security.Principal.SecurityIdentifier])
|
|
30
|
+
if ($owner.Value -ne $sid.Value -and $owner.Value -ne $defaultOwner.Value) { throw 'Foreign private owner' }
|
|
31
|
+
$rules=$acl.GetAccessRules($true,$true,[Security.Principal.SecurityIdentifier])
|
|
32
|
+
$allowed=$false
|
|
33
|
+
foreach ($rule in $rules) {
|
|
34
|
+
if ($rule.AccessControlType -eq 'Allow') {
|
|
35
|
+
if ($rule.IdentityReference.Value -ne $sid.Value) { throw 'Private ACL permits another principal' }
|
|
36
|
+
if (($rule.FileSystemRights -band [Security.AccessControl.FileSystemRights]::FullControl) -eq [Security.AccessControl.FileSystemRights]::FullControl) { $allowed=$true }
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
if (-not $allowed) { throw 'Private owner access unavailable' }
|
|
40
|
+
`;
|
|
41
|
+
|
|
42
|
+
export async function windowsPrivatePath(
|
|
43
|
+
path: string,
|
|
44
|
+
create = false
|
|
45
|
+
): Promise<void> {
|
|
46
|
+
if (process.platform !== "win32")
|
|
47
|
+
throw new Error("Windows ACL operation requires Windows");
|
|
48
|
+
const started = performance.now();
|
|
49
|
+
const child = Bun.spawn(
|
|
50
|
+
[
|
|
51
|
+
"powershell.exe",
|
|
52
|
+
"-NoLogo",
|
|
53
|
+
"-NoProfile",
|
|
54
|
+
"-NonInteractive",
|
|
55
|
+
"-EncodedCommand",
|
|
56
|
+
Buffer.from(ACL, "utf16le").toString("base64"),
|
|
57
|
+
],
|
|
58
|
+
{
|
|
59
|
+
env: {
|
|
60
|
+
...process.env,
|
|
61
|
+
GNO_PRIVATE_PATH: path,
|
|
62
|
+
GNO_PRIVATE_CREATE: create ? "1" : "0",
|
|
63
|
+
},
|
|
64
|
+
stdin: "ignore",
|
|
65
|
+
// ACL checks return no data; do not allocate an unused stdout pipe.
|
|
66
|
+
stdout: "ignore",
|
|
67
|
+
stderr: "pipe",
|
|
68
|
+
timeout: 10000,
|
|
69
|
+
}
|
|
70
|
+
);
|
|
71
|
+
const reader = child.stderr.getReader();
|
|
72
|
+
try {
|
|
73
|
+
const chunks: Uint8Array[] = [];
|
|
74
|
+
let size = 0;
|
|
75
|
+
while (true) {
|
|
76
|
+
const { done, value } = await reader.read();
|
|
77
|
+
if (done) break;
|
|
78
|
+
size += value.byteLength;
|
|
79
|
+
if (size > 65536)
|
|
80
|
+
throw new Error("Private path ACL stderr exceeded 64 KiB");
|
|
81
|
+
chunks.push(value);
|
|
82
|
+
}
|
|
83
|
+
const exitCode = await child.exited;
|
|
84
|
+
if (exitCode !== 0) {
|
|
85
|
+
const stderr = new TextDecoder().decode(Buffer.concat(chunks)).trim();
|
|
86
|
+
throw new Error(
|
|
87
|
+
`Private path ACL unavailable (exit=${String(exitCode)}, signal=${String(child.signalCode)}, elapsedMs=${Math.round(performance.now() - started)}): ${stderr || "no stderr"}`
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
} finally {
|
|
91
|
+
reader.releaseLock();
|
|
92
|
+
if (child.exitCode === null && child.signalCode === null)
|
|
93
|
+
child.kill("SIGKILL");
|
|
94
|
+
await child.exited;
|
|
95
|
+
}
|
|
96
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
import { runCli } from "./cli/run";
|
|
10
10
|
import { resetModelManager } from "./llm/nodeLlamaCpp/lifecycle";
|
|
11
|
+
import { IMPORT_CHILD_ENV } from "./sessions/import-child-env";
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
14
|
* Cleanup models and exit.
|
|
@@ -37,8 +38,16 @@ process.on("SIGINT", () => {
|
|
|
37
38
|
});
|
|
38
39
|
});
|
|
39
40
|
|
|
40
|
-
//
|
|
41
|
-
|
|
41
|
+
// A compiled executable re-run as the session import child (see
|
|
42
|
+
// src/sessions/import-child.ts) serves that one request instead of the CLI.
|
|
43
|
+
if (process.env[IMPORT_CHILD_ENV] === "1") {
|
|
44
|
+
const { runImportChild } = await import("./sessions/import-child");
|
|
45
|
+
await runImportChild();
|
|
46
|
+
await cleanupAndExit(0);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Await module completion so pending piped stdin keeps Windows Bun alive.
|
|
50
|
+
await runCli(process.argv)
|
|
42
51
|
.then((code) => cleanupAndExit(interruptExitCode || code))
|
|
43
52
|
.catch((err) => {
|
|
44
53
|
process.stderr.write(
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/** Generated context is derived evidence and must not feed recursive retrieval. */
|
|
2
|
+
const SIDECAR_MARKER = /"artifactKind"\s*:\s*"gno_compiled_context_sidecar"/;
|
|
3
|
+
export function isCompiledContextPath(path: string): boolean {
|
|
4
|
+
return path
|
|
5
|
+
.toLowerCase()
|
|
6
|
+
.split(/[\\/]/)
|
|
7
|
+
.some((part) => part.includes(".gno-context."));
|
|
8
|
+
}
|
|
9
|
+
export function isCompiledContextContent(prefix: Uint8Array): boolean {
|
|
10
|
+
const text = new TextDecoder().decode(prefix).trimStart();
|
|
11
|
+
return (
|
|
12
|
+
text.startsWith("<!-- gno:compiled-context ") ||
|
|
13
|
+
(text.startsWith("{") && SIDECAR_MARKER.test(text))
|
|
14
|
+
);
|
|
15
|
+
}
|
package/src/ingestion/sync.ts
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
|
+
// node:fs/promises for realpath/stat (no Bun equivalent for canonical paths or file stats)
|
|
2
|
+
import { realpath, stat } from "node:fs/promises";
|
|
1
3
|
/**
|
|
2
4
|
* Sync service - orchestrates file ingestion.
|
|
3
5
|
* Walks collections, converts files, chunks content, updates store.
|
|
4
6
|
*
|
|
5
7
|
* @module src/ingestion/sync
|
|
6
8
|
*/
|
|
7
|
-
|
|
8
|
-
// node:fs/promises for realpath/stat (no Bun equivalent for canonical paths or file stats)
|
|
9
|
-
import { realpath, stat } from "node:fs/promises";
|
|
10
9
|
// node:path for join (no Bun path utils)
|
|
11
10
|
import { isAbsolute, join, relative, sep } from "node:path";
|
|
12
11
|
|
|
@@ -61,6 +60,10 @@ import { normalizeTag, validateTag } from "../core/tags";
|
|
|
61
60
|
import { TYPED_METADATA_INGEST_VERSION } from "../core/typed-metadata";
|
|
62
61
|
import { defaultChunker } from "./chunker";
|
|
63
62
|
import { persistChunkLayout, prepareChunking } from "./chunking";
|
|
63
|
+
import {
|
|
64
|
+
isCompiledContextPath,
|
|
65
|
+
isCompiledContextContent,
|
|
66
|
+
} from "./compiled-context";
|
|
64
67
|
import {
|
|
65
68
|
extractHashtags,
|
|
66
69
|
parseFrontmatter,
|
|
@@ -642,6 +645,14 @@ export class SyncService {
|
|
|
642
645
|
store: StorePort,
|
|
643
646
|
options: SyncOptions
|
|
644
647
|
): Promise<FileSyncResult> {
|
|
648
|
+
const generatedResult: FileSyncResult = {
|
|
649
|
+
relPath: entry.relPath,
|
|
650
|
+
status: "skipped",
|
|
651
|
+
errorCode: "GENERATED_CONTEXT",
|
|
652
|
+
errorMessage:
|
|
653
|
+
"Compiled context is derived material; index original sources instead",
|
|
654
|
+
};
|
|
655
|
+
if (isCompiledContextPath(entry.relPath)) return generatedResult;
|
|
645
656
|
const limits = {
|
|
646
657
|
maxBytes: options.limits?.maxBytes ?? DEFAULT_LIMITS.maxBytes,
|
|
647
658
|
timeoutMs: options.limits?.timeoutMs ?? DEFAULT_LIMITS.timeoutMs,
|
|
@@ -733,6 +744,13 @@ export class SyncService {
|
|
|
733
744
|
guardedBytes = sourceRead.bytes;
|
|
734
745
|
}
|
|
735
746
|
|
|
747
|
+
const generatedPrefix =
|
|
748
|
+
guardedBytes?.subarray(0, 4096) ??
|
|
749
|
+
new Uint8Array(
|
|
750
|
+
await Bun.file(entry.absPath).slice(0, 4096).arrayBuffer()
|
|
751
|
+
);
|
|
752
|
+
if (isCompiledContextContent(generatedPrefix)) return generatedResult;
|
|
753
|
+
|
|
736
754
|
if (recordAdapter) {
|
|
737
755
|
return await processRecordContainer({
|
|
738
756
|
adapter: recordAdapter,
|
|
@@ -754,11 +772,7 @@ export class SyncService {
|
|
|
754
772
|
});
|
|
755
773
|
}
|
|
756
774
|
|
|
757
|
-
const sniffBytes =
|
|
758
|
-
? guardedBytes.subarray(0, Math.min(512, guardedBytes.byteLength))
|
|
759
|
-
: new Uint8Array(
|
|
760
|
-
await Bun.file(entry.absPath).slice(0, 512).arrayBuffer()
|
|
761
|
-
);
|
|
775
|
+
const sniffBytes = generatedPrefix.subarray(0, 512);
|
|
762
776
|
const mime = this.mimeDetector.detect(entry.relPath, sniffBytes);
|
|
763
777
|
|
|
764
778
|
const priorRecordDocuments = mustOk(
|
|
@@ -1447,6 +1461,20 @@ export class SyncService {
|
|
|
1447
1461
|
store,
|
|
1448
1462
|
syncOptions
|
|
1449
1463
|
);
|
|
1464
|
+
if (result.errorCode === "GENERATED_CONTEXT") {
|
|
1465
|
+
const inactive = await this.inactivateOneAbsentSource(
|
|
1466
|
+
collection,
|
|
1467
|
+
store,
|
|
1468
|
+
relPath,
|
|
1469
|
+
projectionSourceIds,
|
|
1470
|
+
{ existingDoc, recordDocuments }
|
|
1471
|
+
);
|
|
1472
|
+
markedInactive += inactive.markedInactive;
|
|
1473
|
+
results.push(
|
|
1474
|
+
inactive.result.status === "error" ? inactive.result : result
|
|
1475
|
+
);
|
|
1476
|
+
continue;
|
|
1477
|
+
}
|
|
1450
1478
|
results.push(result);
|
|
1451
1479
|
if (result.status === "error" || result.status === "skipped") {
|
|
1452
1480
|
continue;
|
|
@@ -1872,6 +1900,8 @@ export class SyncService {
|
|
|
1872
1900
|
store,
|
|
1873
1901
|
syncOptions
|
|
1874
1902
|
);
|
|
1903
|
+
if (result.errorCode === "GENERATED_CONTEXT")
|
|
1904
|
+
seenPaths.delete(entry.relPath);
|
|
1875
1905
|
fileResults.push(result);
|
|
1876
1906
|
switch (result.status) {
|
|
1877
1907
|
case "added":
|
|
@@ -1938,6 +1968,8 @@ export class SyncService {
|
|
|
1938
1968
|
store,
|
|
1939
1969
|
syncOptions
|
|
1940
1970
|
);
|
|
1971
|
+
if (result.errorCode === "GENERATED_CONTEXT")
|
|
1972
|
+
seenPaths.delete(entry.relPath);
|
|
1941
1973
|
fileResults.push(result);
|
|
1942
1974
|
results.push(result);
|
|
1943
1975
|
} finally {
|
package/src/ingestion/walker.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
// node:fs - Bun has no synchronous Dirent enumeration for the guarded local walk.
|
|
2
|
+
import { readdirSync } from "node:fs";
|
|
1
3
|
/**
|
|
2
4
|
* File walker implementation.
|
|
3
5
|
* Walks collection directories using Bun.Glob (`any`) or hierarchical
|
|
@@ -5,9 +7,6 @@
|
|
|
5
7
|
*
|
|
6
8
|
* @module src/ingestion/walker
|
|
7
9
|
*/
|
|
8
|
-
|
|
9
|
-
// node:fs - Bun has no synchronous Dirent enumeration for the guarded local walk.
|
|
10
|
-
import { readdirSync } from "node:fs";
|
|
11
10
|
// node:fs/promises - Bun has no realpath equivalent for symlink-safe containment.
|
|
12
11
|
import { lstat, realpath } from "node:fs/promises";
|
|
13
12
|
// node:path - Bun has no path manipulation module
|
|
@@ -28,6 +27,7 @@ import {
|
|
|
28
27
|
matchesCollectionExclusion,
|
|
29
28
|
matchesCollectionSubtreeExclusion,
|
|
30
29
|
} from "../core/path-rules";
|
|
30
|
+
import { isCompiledContextPath } from "./compiled-context";
|
|
31
31
|
import { isRecordVirtualPath } from "./record-path";
|
|
32
32
|
import {
|
|
33
33
|
createDirectoryAvailability,
|
|
@@ -204,7 +204,8 @@ export function matchesWalkPath(
|
|
|
204
204
|
if (
|
|
205
205
|
isAbsolute(normalizedPath) ||
|
|
206
206
|
DANGEROUS_PATTERN_REGEX.test(normalizedPath) ||
|
|
207
|
-
isRecordVirtualPath(normalizedPath)
|
|
207
|
+
isRecordVirtualPath(normalizedPath) ||
|
|
208
|
+
isCompiledContextPath(normalizedPath)
|
|
208
209
|
) {
|
|
209
210
|
return false;
|
|
210
211
|
}
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
/** Install the pinned simulator guard in memory, including npm/nested installs. */
|
|
2
|
+
// node:url — filesystem conversion of file URLs has no Bun equivalent.
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
|
|
2
5
|
import type {
|
|
3
6
|
SimulatorBackend,
|
|
4
7
|
SimulatorDependencies,
|
|
@@ -52,14 +55,15 @@ export async function verifySimulatorPackage(
|
|
|
52
55
|
export async function loadSimulatorDependencies(
|
|
53
56
|
entry: string
|
|
54
57
|
): Promise<SimulatorDependencies> {
|
|
58
|
+
const parent = fileURLToPath(entry);
|
|
55
59
|
const [guards, cache, locks, binding, tensors, byteModule] =
|
|
56
60
|
await Promise.all([
|
|
57
61
|
import(new URL("./utils/DisposeGuard.js", entry).href),
|
|
58
62
|
import(new URL("./utils/LruCache.js", entry).href),
|
|
59
|
-
import(import.meta.resolve("lifecycle-utils",
|
|
63
|
+
import(import.meta.resolve("lifecycle-utils", parent)),
|
|
60
64
|
import(new URL("./bindings/types.js", entry).href),
|
|
61
65
|
import(new URL("./gguf/types/GgufTensorInfoTypes.js", entry).href),
|
|
62
|
-
import(import.meta.resolve("bytes",
|
|
66
|
+
import(import.meta.resolve("bytes", parent)),
|
|
63
67
|
]);
|
|
64
68
|
return {
|
|
65
69
|
DisposeGuard: guards.DisposeGuard,
|
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>,
|
|
@@ -216,6 +221,7 @@ export function createToolContext(
|
|
|
216
221
|
requestSnapshot.getStore()?.egress?.authorizationEpoch?.value,
|
|
217
222
|
getEgressContext: () => requestSnapshot.getStore()?.egress,
|
|
218
223
|
getRequestIdentity: () => requestSnapshot.getStore()?.requestIdentity,
|
|
224
|
+
getRequestNamespace: () => requestSnapshot.getStore()?.requestNamespace,
|
|
219
225
|
runWithEgressContext<T>(
|
|
220
226
|
egress: NonNullable<ToolContextSnapshot["egress"]>,
|
|
221
227
|
operation: () => Promise<T>,
|
|
@@ -228,6 +234,7 @@ export function createToolContext(
|
|
|
228
234
|
collections: config.collections,
|
|
229
235
|
egress,
|
|
230
236
|
requestIdentity: scope?.requestIdentity,
|
|
237
|
+
requestNamespace: scope?.requestNamespace,
|
|
231
238
|
},
|
|
232
239
|
operation
|
|
233
240
|
);
|
|
@@ -241,6 +248,7 @@ export function createToolContext(
|
|
|
241
248
|
collections: config.collections,
|
|
242
249
|
egress: current?.egress,
|
|
243
250
|
requestIdentity: current?.requestIdentity,
|
|
251
|
+
requestNamespace: current?.requestNamespace,
|
|
244
252
|
},
|
|
245
253
|
operation
|
|
246
254
|
);
|
package/src/mcp/http-egress.ts
CHANGED
|
@@ -23,6 +23,8 @@ export const MCP_HTTP_EGRESS_TOOLS = {
|
|
|
23
23
|
gno_changes: "metadata",
|
|
24
24
|
gno_clear_collection_embeddings: "metadata",
|
|
25
25
|
gno_context: "capsule",
|
|
26
|
+
gno_context_compiled_preview: "capsule",
|
|
27
|
+
gno_context_compiled_check: "capsule",
|
|
26
28
|
gno_context_verify: "capsule",
|
|
27
29
|
gno_create_folder: "metadata",
|
|
28
30
|
gno_diff: "metadata",
|
|
@@ -55,8 +57,12 @@ export const MCP_HTTP_EGRESS_TOOLS = {
|
|
|
55
57
|
gno_remember: "source",
|
|
56
58
|
gno_remove_collection: "metadata",
|
|
57
59
|
gno_rename_note: "metadata",
|
|
60
|
+
gno_request_status: "metadata",
|
|
58
61
|
gno_search: "snippet",
|
|
59
62
|
gno_section: "metadata",
|
|
63
|
+
gno_sessions_automation_run: "metadata",
|
|
64
|
+
gno_sessions_import: "metadata",
|
|
65
|
+
gno_sessions_status: "metadata",
|
|
60
66
|
gno_similar: "snippet",
|
|
61
67
|
gno_peek: "metadata",
|
|
62
68
|
gno_status: "metadata",
|
|
@@ -159,9 +165,15 @@ const enforceMessage = (
|
|
|
159
165
|
const params = asRecord(message.params);
|
|
160
166
|
const name = params?.name;
|
|
161
167
|
if (typeof name !== "string" || !(name in MCP_HTTP_EGRESS_TOOLS)) return;
|
|
162
|
-
//
|
|
163
|
-
//
|
|
164
|
-
|
|
168
|
+
// Derived exports authorize exact current lineage inside their shared
|
|
169
|
+
// runtime. Broad transport scoping would deny eligible subsets merely
|
|
170
|
+
// because another configured collection is private.
|
|
171
|
+
if (
|
|
172
|
+
name === "gno_trace_export" ||
|
|
173
|
+
name === "gno_context_compiled_preview" ||
|
|
174
|
+
name === "gno_context_compiled_check"
|
|
175
|
+
)
|
|
176
|
+
return;
|
|
165
177
|
contentClass =
|
|
166
178
|
MCP_HTTP_EGRESS_TOOLS[name as keyof typeof MCP_HTTP_EGRESS_TOOLS];
|
|
167
179
|
} else if (
|
|
@@ -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();
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { SearchMeta } from "../pipeline/types";
|
|
2
|
+
|
|
3
|
+
import { METADATA_COVERAGE_GUIDANCE } from "../core/typed-metadata";
|
|
4
|
+
|
|
5
|
+
/** Text-only MCP clients need the same warnings as structured clients. */
|
|
6
|
+
export function appendRetrievalWarnings(
|
|
7
|
+
text: string,
|
|
8
|
+
warnings: SearchMeta["warnings"]
|
|
9
|
+
): string {
|
|
10
|
+
if (!warnings?.length) return text;
|
|
11
|
+
const lines = warnings.map(
|
|
12
|
+
({ code, message }) => `Warning [${code}]: ${message}`
|
|
13
|
+
);
|
|
14
|
+
if (
|
|
15
|
+
warnings.some(
|
|
16
|
+
({ code }) =>
|
|
17
|
+
code === "METADATA_COVERAGE_INCOMPLETE" ||
|
|
18
|
+
code === "METADATA_COVERAGE_UNKNOWN"
|
|
19
|
+
)
|
|
20
|
+
) {
|
|
21
|
+
lines.push(METADATA_COVERAGE_GUIDANCE);
|
|
22
|
+
}
|
|
23
|
+
return `${text}\n\n${lines.join("\n")}`;
|
|
24
|
+
}
|
package/src/mcp/tools/ask.ts
CHANGED
|
@@ -17,6 +17,8 @@ import { attachRetrievalTraceMetadata } from "../../core/retrieval-trace-session
|
|
|
17
17
|
import { normalizeStructuredQueryInput } from "../../core/structured-query";
|
|
18
18
|
import {
|
|
19
19
|
metadataPredicateSchema,
|
|
20
|
+
METADATA_FILTER_DESCRIPTION,
|
|
21
|
+
METADATA_COVERAGE_GUIDANCE,
|
|
20
22
|
normalizeMetadataPredicate,
|
|
21
23
|
} from "../../core/typed-metadata";
|
|
22
24
|
import { resolveModelUri } from "../../llm/registry";
|
|
@@ -44,7 +46,9 @@ export const askInputSchema = z
|
|
|
44
46
|
candidateLimit: z.number().int().min(1).max(100).optional(),
|
|
45
47
|
exclude: z.array(z.string()).optional(),
|
|
46
48
|
queryModes: z.array(queryModeSchema).optional(),
|
|
47
|
-
filter: metadataPredicateSchema
|
|
49
|
+
filter: metadataPredicateSchema
|
|
50
|
+
.optional()
|
|
51
|
+
.describe(METADATA_FILTER_DESCRIPTION),
|
|
48
52
|
tagsAll: z.array(z.string()).optional(),
|
|
49
53
|
tagsAny: z.array(z.string()).optional(),
|
|
50
54
|
since: z.string().optional(),
|
|
@@ -111,6 +115,15 @@ export const formatVerifiedAskReadable = (
|
|
|
111
115
|
);
|
|
112
116
|
}
|
|
113
117
|
}
|
|
118
|
+
if (
|
|
119
|
+
result.verification?.capsule.warnings.some(
|
|
120
|
+
(warning) => warning.code === "metadata_coverage_incomplete"
|
|
121
|
+
)
|
|
122
|
+
) {
|
|
123
|
+
lines.push(
|
|
124
|
+
"Warning [metadata_coverage_incomplete]: " + METADATA_COVERAGE_GUIDANCE
|
|
125
|
+
);
|
|
126
|
+
}
|
|
114
127
|
for (const gap of result.verification?.capsule.coverage.gaps ?? []) {
|
|
115
128
|
lines.push(`Gap: ${gap.facet} (${gap.code})`);
|
|
116
129
|
}
|