@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
|
@@ -24,6 +24,7 @@ import type {
|
|
|
24
24
|
RememberResult,
|
|
25
25
|
} from "./memory-types";
|
|
26
26
|
|
|
27
|
+
import { buildUri } from "../app/constants";
|
|
27
28
|
import { defaultSyncService, withContentTypeRules } from "../ingestion";
|
|
28
29
|
import { withWriteLock } from "./file-lock";
|
|
29
30
|
import { atomicCreate } from "./file-ops";
|
|
@@ -56,6 +57,13 @@ import {
|
|
|
56
57
|
MEMORY_SEMANTIC_LIKELY_THRESHOLD,
|
|
57
58
|
MemoryError,
|
|
58
59
|
} from "./memory-types";
|
|
60
|
+
import {
|
|
61
|
+
isLeaseBusy,
|
|
62
|
+
RequestReceiptError,
|
|
63
|
+
requestDigest,
|
|
64
|
+
runRequestedWrite,
|
|
65
|
+
validateRequestId,
|
|
66
|
+
} from "./request-receipts";
|
|
59
67
|
|
|
60
68
|
export interface CandidateQuery {
|
|
61
69
|
text: string;
|
|
@@ -249,6 +257,12 @@ async function supersedesEdgeProjected(
|
|
|
249
257
|
// remember
|
|
250
258
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
251
259
|
|
|
260
|
+
interface RememberPlan {
|
|
261
|
+
frontmatter: MemoryRecordFrontmatter;
|
|
262
|
+
supersedes: string[];
|
|
263
|
+
relPath: string;
|
|
264
|
+
}
|
|
265
|
+
|
|
252
266
|
export async function rememberFact(
|
|
253
267
|
deps: MemoryServiceDeps,
|
|
254
268
|
rawInput: RememberInput
|
|
@@ -270,14 +284,26 @@ export async function rememberFact(
|
|
|
270
284
|
);
|
|
271
285
|
}
|
|
272
286
|
}
|
|
287
|
+
const requestId =
|
|
288
|
+
rawInput.requestId === undefined
|
|
289
|
+
? undefined
|
|
290
|
+
: validateRequestId(rawInput.requestId);
|
|
291
|
+
if (requestId !== undefined && decision === undefined) {
|
|
292
|
+
throw new RequestReceiptError(
|
|
293
|
+
"REQUEST_ID_INVALID",
|
|
294
|
+
"requestId applies only to writes: pass decision add or supersede."
|
|
295
|
+
);
|
|
296
|
+
}
|
|
273
297
|
|
|
274
298
|
const { candidates, matching } = await findMemoryCandidates(deps, {
|
|
275
299
|
text,
|
|
276
300
|
collection: collection.name,
|
|
277
301
|
scopes,
|
|
278
302
|
});
|
|
303
|
+
// With a request ID the exact-duplicate decision is made (and recorded)
|
|
304
|
+
// under the lease, so a later retry replays it instead of re-deciding.
|
|
279
305
|
const exact = candidates.find((candidate) => candidate.match === "exact");
|
|
280
|
-
if (exact && decision !== "supersede") {
|
|
306
|
+
if (exact && decision !== "supersede" && requestId === undefined) {
|
|
281
307
|
const { similarity: _similarity, match: _match, ...record } = exact;
|
|
282
308
|
return { outcome: "existing", record, matching };
|
|
283
309
|
}
|
|
@@ -285,135 +311,222 @@ export async function rememberFact(
|
|
|
285
311
|
return { outcome: "candidates", candidates, matching };
|
|
286
312
|
}
|
|
287
313
|
|
|
288
|
-
const createdAt = memoryNow(deps).toISOString();
|
|
289
314
|
const source = rawInput.source?.trim() || undefined;
|
|
290
|
-
const
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
315
|
+
const absPathOf = (plan: RememberPlan) => join(collection.path, plan.relPath);
|
|
316
|
+
|
|
317
|
+
/** Under the lease: decide against current state; never writes. */
|
|
318
|
+
const prepare = async (): Promise<
|
|
319
|
+
{ plan: RememberPlan } | { result: RememberResult }
|
|
320
|
+
> => {
|
|
321
|
+
const supersedes: string[] = [];
|
|
322
|
+
if (decision === "supersede") {
|
|
323
|
+
supersedes.push(
|
|
324
|
+
await verifyPredecessor(
|
|
325
|
+
deps,
|
|
326
|
+
collection.name,
|
|
327
|
+
rawInput.predecessorUri as string,
|
|
328
|
+
rawInput.predecessorHash as string
|
|
329
|
+
)
|
|
330
|
+
);
|
|
331
|
+
} else {
|
|
332
|
+
// The pre-lease check raced with any concurrent writer; decide
|
|
333
|
+
// idempotency on the state visible under the lease.
|
|
334
|
+
const existing = await findExactCurrent(deps, {
|
|
335
|
+
text,
|
|
336
|
+
collection: collection.name,
|
|
337
|
+
scopes,
|
|
338
|
+
contentHash,
|
|
339
|
+
});
|
|
340
|
+
if (existing) {
|
|
341
|
+
return { result: { outcome: "existing", record: existing, matching } };
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
const createdAt = memoryNow(deps).toISOString();
|
|
345
|
+
const frontmatter: MemoryRecordFrontmatter = {
|
|
346
|
+
recordId: buildMemoryRecordId({
|
|
347
|
+
contentHash,
|
|
348
|
+
createdAt,
|
|
349
|
+
caller: identity.caller,
|
|
350
|
+
session: identity.session,
|
|
351
|
+
}),
|
|
352
|
+
scopes,
|
|
294
353
|
caller: identity.caller,
|
|
295
354
|
session: identity.session,
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
355
|
+
createdAt,
|
|
356
|
+
contentHash,
|
|
357
|
+
...(source ? { source } : {}),
|
|
358
|
+
};
|
|
359
|
+
return {
|
|
360
|
+
plan: {
|
|
361
|
+
frontmatter,
|
|
362
|
+
supersedes,
|
|
363
|
+
relPath: buildMemoryRecordRelPath(frontmatter),
|
|
364
|
+
},
|
|
365
|
+
};
|
|
303
366
|
};
|
|
304
|
-
const
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
367
|
+
const serialize = (plan: RememberPlan): string =>
|
|
368
|
+
serializeMemoryRecord({
|
|
369
|
+
frontmatter: plan.frontmatter,
|
|
370
|
+
supersedes: plan.supersedes,
|
|
371
|
+
text,
|
|
372
|
+
});
|
|
373
|
+
const publish = async (plan: RememberPlan): Promise<void> => {
|
|
374
|
+
await mkdir(dirname(absPathOf(plan)), { recursive: true });
|
|
375
|
+
await atomicCreate(absPathOf(plan), serialize(plan));
|
|
376
|
+
};
|
|
377
|
+
/** Under the lease: lexical sync, projection check, result. */
|
|
378
|
+
const finish = async (plan: RememberPlan): Promise<RememberResult> => {
|
|
379
|
+
const { relPath, supersedes, frontmatter } = plan;
|
|
380
|
+
const absPath = absPathOf(plan);
|
|
381
|
+
// syncPaths (not syncFiles) so typed-edge projection errors surface.
|
|
382
|
+
const syncResult = await (deps.syncService ?? defaultSyncService).syncPaths(
|
|
383
|
+
collection,
|
|
384
|
+
store,
|
|
385
|
+
[relPath],
|
|
386
|
+
withContentTypeRules({ runUpdateCmd: false, gitPull: false }, config)
|
|
387
|
+
);
|
|
388
|
+
const fileResult = syncResult.files?.[0];
|
|
389
|
+
const doc = await store.getDocument(collection.name, relPath);
|
|
390
|
+
const sync: MemorySyncState =
|
|
391
|
+
fileResult?.status === "error" || !doc.ok || doc.value === null
|
|
392
|
+
? {
|
|
393
|
+
status: "failed",
|
|
394
|
+
error:
|
|
395
|
+
fileResult?.errorMessage ??
|
|
396
|
+
fileResult?.errorCode ??
|
|
397
|
+
"memory record was written but is not retrievable yet",
|
|
334
398
|
}
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
399
|
+
: { status: "completed" };
|
|
400
|
+
if (sync.status === "failed") {
|
|
401
|
+
throw new MemoryError(
|
|
402
|
+
"MEMORY_SYNC_FAILED",
|
|
403
|
+
`Memory record written to ${buildUri(collection.name, relPath)} but lexical sync failed: ${sync.error}. Run gno update to retry indexing.`
|
|
404
|
+
);
|
|
405
|
+
}
|
|
406
|
+
const written = (doc as { value: DocumentRow }).value;
|
|
407
|
+
const projectionErrors = syncResult.errors
|
|
408
|
+
.map((error) => `${error.relPath}: ${error.message}`)
|
|
409
|
+
.join("; ");
|
|
410
|
+
if (decision === "supersede") {
|
|
411
|
+
// The write is only a supersession once the edge is projected;
|
|
412
|
+
// until then the predecessor still reads as current.
|
|
413
|
+
const projected =
|
|
414
|
+
projectionErrors.length === 0 &&
|
|
415
|
+
(await supersedesEdgeProjected(deps, written.id, supersedes));
|
|
416
|
+
if (!projected) {
|
|
417
|
+
throw new MemoryError(
|
|
418
|
+
"MEMORY_SUPERSEDE_PROJECTION_FAILED",
|
|
419
|
+
`Successor written to ${buildUri(collection.name, relPath)} but its supersedes edge did not project${projectionErrors ? ` (${projectionErrors})` : ""}; the predecessor still reads as current. Run gno update to retry the projection.`
|
|
340
420
|
);
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
421
|
+
}
|
|
422
|
+
} else if (projectionErrors.length > 0) {
|
|
423
|
+
throw new MemoryError(
|
|
424
|
+
"MEMORY_SYNC_FAILED",
|
|
425
|
+
`Memory record written to ${buildUri(collection.name, relPath)} but typed-edge projection failed: ${projectionErrors}. Run gno update to retry indexing.`
|
|
426
|
+
);
|
|
427
|
+
}
|
|
428
|
+
const record: MemoryFact = {
|
|
429
|
+
uri: written.uri,
|
|
430
|
+
docid: written.docid,
|
|
431
|
+
recordId: frontmatter.recordId,
|
|
432
|
+
text,
|
|
433
|
+
scopes: frontmatter.scopes,
|
|
434
|
+
caller: frontmatter.caller,
|
|
435
|
+
session: frontmatter.session,
|
|
436
|
+
createdAt: frontmatter.createdAt,
|
|
437
|
+
contentHash,
|
|
438
|
+
supersedes,
|
|
439
|
+
...(frontmatter.source ? { source: frontmatter.source } : {}),
|
|
440
|
+
};
|
|
441
|
+
return {
|
|
442
|
+
outcome: decision === "supersede" ? "superseded" : "added",
|
|
443
|
+
record,
|
|
444
|
+
absPath,
|
|
445
|
+
sync,
|
|
446
|
+
matching,
|
|
447
|
+
};
|
|
448
|
+
};
|
|
449
|
+
|
|
450
|
+
const lockWaitMs = memoryLockWaitMs(deps);
|
|
451
|
+
try {
|
|
452
|
+
if (requestId === undefined) {
|
|
453
|
+
return await withWriteLock(
|
|
454
|
+
deps.lockPath,
|
|
455
|
+
async () => {
|
|
456
|
+
const prepared = await prepare();
|
|
457
|
+
if ("result" in prepared) return prepared.result;
|
|
458
|
+
await publish(prepared.plan);
|
|
459
|
+
return finish(prepared.plan);
|
|
460
|
+
},
|
|
461
|
+
lockWaitMs
|
|
462
|
+
);
|
|
463
|
+
}
|
|
464
|
+
if (!deps.requests) {
|
|
465
|
+
throw new RequestReceiptError(
|
|
466
|
+
"REQUEST_LEDGER_UNAVAILABLE",
|
|
467
|
+
"This memory service has no request ledger; retry without requestId."
|
|
468
|
+
);
|
|
469
|
+
}
|
|
470
|
+
const outcome = await runRequestedWrite<RememberPlan, RememberResult>({
|
|
471
|
+
ledgerPath: deps.requests.ledgerPath,
|
|
472
|
+
namespace: deps.requests.namespace,
|
|
473
|
+
requestId,
|
|
474
|
+
operation: "remember",
|
|
475
|
+
// Caller/session are provenance, not intent: a retry may reconnect
|
|
476
|
+
// under a new session and must still match.
|
|
477
|
+
digest: requestDigest("remember", {
|
|
478
|
+
collection: collection.name,
|
|
479
|
+
text,
|
|
480
|
+
scopes,
|
|
481
|
+
decision,
|
|
482
|
+
predecessorUri: rawInput.predecessorUri?.trim(),
|
|
483
|
+
predecessorHash: rawInput.predecessorHash,
|
|
484
|
+
source,
|
|
485
|
+
}),
|
|
486
|
+
lockPath: deps.lockPath,
|
|
487
|
+
lockWaitMs,
|
|
488
|
+
checkpoint: deps.requests.checkpoint,
|
|
489
|
+
prepare: async () => {
|
|
490
|
+
const prepared = await prepare();
|
|
491
|
+
if ("result" in prepared) return prepared;
|
|
492
|
+
return { plan: prepared.plan, publish: () => publish(prepared.plan) };
|
|
493
|
+
},
|
|
494
|
+
inspect: async (plan) => {
|
|
495
|
+
const file = Bun.file(absPathOf(plan));
|
|
496
|
+
if (!(await file.exists())) return "absent";
|
|
497
|
+
if ((await file.text()) !== serialize(plan)) return "unexpected";
|
|
498
|
+
// Another successor may have superseded the predecessor while this
|
|
499
|
+
// one sat unprojected: finishing would leave two successors.
|
|
500
|
+
const [predecessorUri] = plan.supersedes;
|
|
501
|
+
if (!predecessorUri) return "published";
|
|
502
|
+
const predecessor = await store.getDocumentByUri(predecessorUri);
|
|
503
|
+
if (!predecessor.ok || !predecessor.value) return "unexpected";
|
|
504
|
+
const successors = await store.getEdgeBacklinksForDoc(
|
|
505
|
+
predecessor.value.id,
|
|
506
|
+
{ edgeType: MEMORY_SUPERSEDES_EDGE }
|
|
349
507
|
);
|
|
350
|
-
const
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
status: "failed",
|
|
356
|
-
error:
|
|
357
|
-
fileResult?.errorMessage ??
|
|
358
|
-
fileResult?.errorCode ??
|
|
359
|
-
"memory record was written but is not retrievable yet",
|
|
360
|
-
}
|
|
361
|
-
: { status: "completed" };
|
|
362
|
-
if (sync.status === "failed") {
|
|
363
|
-
throw new MemoryError(
|
|
364
|
-
"MEMORY_SYNC_FAILED",
|
|
365
|
-
`Memory record written to ${absPath} but lexical sync failed: ${sync.error}. Run gno update to retry indexing.`
|
|
366
|
-
);
|
|
367
|
-
}
|
|
368
|
-
const written = (doc as { value: DocumentRow }).value;
|
|
369
|
-
const projectionErrors = syncResult.errors
|
|
370
|
-
.map((error) => `${error.relPath}: ${error.message}`)
|
|
371
|
-
.join("; ");
|
|
372
|
-
if (decision === "supersede") {
|
|
373
|
-
// The write is only a supersession once the edge is projected;
|
|
374
|
-
// until then the predecessor still reads as current.
|
|
375
|
-
const projected =
|
|
376
|
-
projectionErrors.length === 0 &&
|
|
377
|
-
(await supersedesEdgeProjected(deps, written.id, supersedes));
|
|
378
|
-
if (!projected) {
|
|
379
|
-
throw new MemoryError(
|
|
380
|
-
"MEMORY_SUPERSEDE_PROJECTION_FAILED",
|
|
381
|
-
`Successor written to ${absPath} but its supersedes edge did not project${projectionErrors ? ` (${projectionErrors})` : ""}; the predecessor still reads as current. Run gno update to retry the projection.`
|
|
382
|
-
);
|
|
383
|
-
}
|
|
384
|
-
} else if (projectionErrors.length > 0) {
|
|
385
|
-
throw new MemoryError(
|
|
386
|
-
"MEMORY_SYNC_FAILED",
|
|
387
|
-
`Memory record written to ${absPath} but typed-edge projection failed: ${projectionErrors}. Run gno update to retry indexing.`
|
|
388
|
-
);
|
|
389
|
-
}
|
|
390
|
-
const record: MemoryFact = {
|
|
391
|
-
uri: written.uri,
|
|
392
|
-
docid: written.docid,
|
|
393
|
-
recordId: frontmatter.recordId,
|
|
394
|
-
text,
|
|
395
|
-
scopes,
|
|
396
|
-
caller: identity.caller,
|
|
397
|
-
session: identity.session,
|
|
398
|
-
createdAt,
|
|
399
|
-
contentHash,
|
|
400
|
-
supersedes,
|
|
401
|
-
...(source ? { source } : {}),
|
|
402
|
-
};
|
|
403
|
-
return {
|
|
404
|
-
outcome: decision === "supersede" ? "superseded" : "added",
|
|
405
|
-
record,
|
|
406
|
-
absPath,
|
|
407
|
-
sync,
|
|
408
|
-
matching,
|
|
409
|
-
};
|
|
508
|
+
const ownUri = `gno://${collection.name}/${plan.relPath}`;
|
|
509
|
+
return successors.ok &&
|
|
510
|
+
successors.value.every((edge) => edge.sourceUri === ownUri)
|
|
511
|
+
? "published"
|
|
512
|
+
: "unexpected";
|
|
410
513
|
},
|
|
411
|
-
|
|
412
|
-
|
|
514
|
+
finish,
|
|
515
|
+
resultRef: (result) =>
|
|
516
|
+
result.outcome === "candidates"
|
|
517
|
+
? {}
|
|
518
|
+
: {
|
|
519
|
+
uri: result.record.uri,
|
|
520
|
+
docid: result.record.docid,
|
|
521
|
+
contentHash: result.record.contentHash,
|
|
522
|
+
},
|
|
523
|
+
});
|
|
524
|
+
return { ...outcome.result, request: outcome.request } as RememberResult;
|
|
413
525
|
} catch (error) {
|
|
414
|
-
if (error instanceof MemoryError
|
|
415
|
-
|
|
416
|
-
|
|
526
|
+
if (error instanceof MemoryError || error instanceof RequestReceiptError) {
|
|
527
|
+
throw error;
|
|
528
|
+
}
|
|
529
|
+
if (isLeaseBusy(error)) {
|
|
417
530
|
throw new MemoryError(
|
|
418
531
|
"MEMORY_WRITE_LEASE_BUSY",
|
|
419
532
|
`Could not acquire the shared write lease at ${deps.lockPath} within ${lockWaitMs}ms: another write holds it. The memory service takes the lease itself; callers must not pre-hold it.`
|
|
@@ -421,5 +534,4 @@ export async function rememberFact(
|
|
|
421
534
|
}
|
|
422
535
|
throw error;
|
|
423
536
|
}
|
|
424
|
-
return leased;
|
|
425
537
|
}
|
package/src/core/memory-types.ts
CHANGED
|
@@ -15,6 +15,7 @@ import type { EmbeddingPort } from "../llm/types";
|
|
|
15
15
|
import type { StorePort } from "../store/types";
|
|
16
16
|
import type { VectorIndexPort } from "../store/vector/types";
|
|
17
17
|
import type { EgressLineage } from "./egress-provenance";
|
|
18
|
+
import type { RequestCheckpoint, RequestReceiptInfo } from "./request-receipts";
|
|
18
19
|
|
|
19
20
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
20
21
|
// Binding defaults (tunable here, documented in docs/MEMORY.md)
|
|
@@ -106,6 +107,8 @@ export interface RememberInput extends MemoryIdentity {
|
|
|
106
107
|
/** Declared origins; any gno:// origin is fenced. */
|
|
107
108
|
derivedFrom?: string[];
|
|
108
109
|
source?: string;
|
|
110
|
+
/** Opt-in retry identity for add/supersede; see docs/MEMORY.md. */
|
|
111
|
+
requestId?: string;
|
|
109
112
|
}
|
|
110
113
|
|
|
111
114
|
export interface MemoryFact {
|
|
@@ -149,6 +152,7 @@ export type RememberResult =
|
|
|
149
152
|
outcome: "existing";
|
|
150
153
|
record: MemoryFact;
|
|
151
154
|
matching: MemoryMatchDiagnostics;
|
|
155
|
+
request?: RequestReceiptInfo;
|
|
152
156
|
}
|
|
153
157
|
| {
|
|
154
158
|
outcome: "candidates";
|
|
@@ -161,6 +165,7 @@ export type RememberResult =
|
|
|
161
165
|
absPath: string;
|
|
162
166
|
sync: MemorySyncState;
|
|
163
167
|
matching: MemoryMatchDiagnostics;
|
|
168
|
+
request?: RequestReceiptInfo;
|
|
164
169
|
};
|
|
165
170
|
|
|
166
171
|
export interface RecallInput extends MemoryIdentity {
|
|
@@ -208,4 +213,10 @@ export interface MemoryServiceDeps {
|
|
|
208
213
|
/** Must surface typed-edge projection errors (`syncPaths`, not `syncFiles`). */
|
|
209
214
|
syncService?: Pick<typeof defaultSyncService, "syncPaths">;
|
|
210
215
|
now?: () => Date;
|
|
216
|
+
/** Request ledger + caller namespace; required to honour `requestId`. */
|
|
217
|
+
requests?: {
|
|
218
|
+
ledgerPath: string;
|
|
219
|
+
namespace: string;
|
|
220
|
+
checkpoint?: (stage: RequestCheckpoint) => Promise<void> | void;
|
|
221
|
+
};
|
|
211
222
|
}
|
|
@@ -297,6 +297,14 @@ export const NETWORK_BOUNDARY_INVENTORY = [
|
|
|
297
297
|
action: "serve",
|
|
298
298
|
enforcement: "local_process_only",
|
|
299
299
|
},
|
|
300
|
+
{
|
|
301
|
+
id: "session-import-process",
|
|
302
|
+
key: "src/sessions/import-child.ts::child_process#1",
|
|
303
|
+
path: "src/sessions/import-child.ts",
|
|
304
|
+
primitive: "child_process",
|
|
305
|
+
action: null,
|
|
306
|
+
enforcement: "local_process_only",
|
|
307
|
+
},
|
|
300
308
|
{
|
|
301
309
|
id: "http-mcp-tools",
|
|
302
310
|
key: "logical::http-mcp-tools",
|