@sema-agent/core 5.17.0 → 5.18.1
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/CHANGELOG.md +121 -0
- package/dist/agents/subagent.js +24 -0
- package/dist/core/auto-compaction.d.ts +6 -0
- package/dist/core/auto-compaction.js +15 -1
- package/dist/core/checkpoint-store.d.ts +4 -2
- package/dist/core/governance-codes.js +1 -0
- package/dist/core/hooks.d.ts +9 -0
- package/dist/core/hooks.js +21 -0
- package/dist/core/mcp.js +3 -0
- package/dist/core/memory-engine/content-origin.d.ts +27 -0
- package/dist/core/memory-engine/content-origin.js +38 -0
- package/dist/core/memory-engine/engine.d.ts +12 -2
- package/dist/core/memory-engine/engine.js +172 -12
- package/dist/core/memory-engine/file-backend.d.ts +4 -0
- package/dist/core/memory-engine/file-backend.js +25 -3
- package/dist/core/memory-engine/index.d.ts +2 -1
- package/dist/core/memory-engine/index.js +2 -1
- package/dist/core/memory-engine/layout.d.ts +16 -0
- package/dist/core/memory-engine/layout.js +90 -2
- package/dist/core/memory-engine/sync-client.d.ts +1 -0
- package/dist/core/memory-engine/sync-client.js +23 -5
- package/dist/core/memory-engine/tools.d.ts +55 -0
- package/dist/core/memory-engine/tools.js +307 -0
- package/dist/core/memory-engine/types.d.ts +1 -1
- package/dist/core/memory.d.ts +4 -0
- package/dist/core/memory.js +15 -2
- package/dist/core/permission-rule-consent.d.ts +138 -0
- package/dist/core/permission-rule-consent.js +318 -0
- package/dist/core/permission-rule-model.d.ts +66 -0
- package/dist/core/permission-rule-model.js +135 -0
- package/dist/core/permission-rule-store.d.ts +89 -0
- package/dist/core/permission-rule-store.js +145 -0
- package/dist/core/permission-rules.d.ts +3 -2
- package/dist/core/permission-rules.js +9 -4
- package/dist/core/runner/prepare-memory.d.ts +3 -1
- package/dist/core/runner/prepare-memory.js +54 -14
- package/dist/core/runner/prepare-task.d.ts +12 -0
- package/dist/core/runner/prepare-task.js +206 -12
- package/dist/core/runner/runtask.d.ts +3 -1
- package/dist/core/runner/runtask.js +47 -5
- package/dist/core/runner/tool-output-projection.js +1 -1
- package/dist/core/tool-policy.d.ts +13 -1
- package/dist/core/tool-policy.js +93 -12
- package/dist/core/tools.js +1 -0
- package/dist/core/trace.d.ts +20 -0
- package/dist/core/types.d.ts +15 -0
- package/dist/core/wiring-manifest.d.ts +5 -1
- package/dist/core/wiring-manifest.js +2 -0
- package/dist/index.d.ts +7 -3
- package/dist/index.js +6 -2
- package/dist/stores/file/permission-rule-store.d.ts +32 -0
- package/dist/stores/file/permission-rule-store.js +213 -0
- package/dist/tools/fs/fs-bash.js +12 -5
- package/dist/tools/fs/fs-shared.d.ts +12 -0
- package/dist/tools/fs/fs-shared.js +65 -1
- package/dist/tools/web.js +2 -0
- package/package.json +1 -1
|
@@ -6,7 +6,7 @@ import { inlineUntrusted } from "../untrusted-text.js";
|
|
|
6
6
|
import { formatMemoryAge } from "../memory-recall.js";
|
|
7
7
|
import { computeEntryRev, parseEntryFile, serializeEntryFile } from "./frontmatter.js";
|
|
8
8
|
import { DEFAULT_MAX_ENTRY_DEPTH, MEMORY_INDEX_FILENAME, scanEntryFiles } from "./file-backend.js";
|
|
9
|
-
import { QUARANTINE_DIR, SCAN_FUSE_THRESHOLD, quarantineAndTombstone, readIndexRevs, writeIndexRevs, bumpScanFuse, canonicalize, claimRootScope, clearScanFuse, adoptCanonicalKeyedControlDir, deriveControlPlaneDir, drainMemoryAnnouncements, enqueueMemoryAnnouncement, ensureDirExists, isContainedIn, registerScope, registeredScopes, resolveMemoryEngineRoot, scopeDirFor, } from "./layout.js";
|
|
9
|
+
import { QUARANTINE_DIR, SCAN_FUSE_THRESHOLD, quarantineAndTombstone, readIndexRevs, writeIndexRevs, bumpScanFuse, canonicalize, claimRootScope, clearScanFuse, adoptCanonicalKeyedControlDir, deriveControlPlaneDir, drainMemoryAnnouncements, enqueueMemoryAnnouncement, ensureDirExists, isContainedIn, markSessionPolluted, readSessionPollution, recordRetrievedAccount, writeFileNoFollow, readRetrievedAccount, registerScope, registeredScopes, resolveMemoryEngineRoot, scopeDirFor, } from "./layout.js";
|
|
10
10
|
import { scanMemoryFileName, scanMemoryWrite, scanRemediation } from "./scan.js";
|
|
11
11
|
export const MEMORY_INSTRUCTION_TEMPLATE = `# Memory
|
|
12
12
|
|
|
@@ -32,6 +32,7 @@ export function buildMemoryInstruction(memoryDir, instructionFileName) {
|
|
|
32
32
|
const dir = memoryDir.endsWith("/") ? memoryDir : `${memoryDir}/`;
|
|
33
33
|
return MEMORY_INSTRUCTION_TEMPLATE.replaceAll("{{MEMORY_DIR}}", dir).replaceAll("{{INSTRUCTION_FILE}}", instructionFileName ?? "CLAUDE.md");
|
|
34
34
|
}
|
|
35
|
+
export const MEMORY_RECALL_DISCIPLINE = "Before answering questions about earlier work, decisions, dates, people, or the user's preferences, look them up: `memory_search` finds entries by keyword and `memory_get` reads a full entry — the injected memory index only lists what exists. When a lookup comes up empty, say that you checked memory and found nothing instead of guessing.";
|
|
35
36
|
export const MEMORY_INDEX_MAX_LINES = 200;
|
|
36
37
|
export const MEMORY_INDEX_MAX_BYTES = 25 * 1024;
|
|
37
38
|
export const STUB_ARCHIVED_LINE = "[body archived — request hydration by listing the slug in memory/.hydrate]";
|
|
@@ -85,6 +86,56 @@ export class MemoryEngine {
|
|
|
85
86
|
catch {
|
|
86
87
|
}
|
|
87
88
|
}
|
|
89
|
+
recordRetrieved(ids) {
|
|
90
|
+
try {
|
|
91
|
+
recordRetrievedAccount(this.controlDir, ids, this.now);
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
readRetrievedAccount() {
|
|
97
|
+
try {
|
|
98
|
+
return readRetrievedAccount(this.controlDir);
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
return {};
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
pollutedSessions = new Map();
|
|
105
|
+
markSessionPolluted(sessionId, reason) {
|
|
106
|
+
if (!this.pollutedSessions.has(sessionId))
|
|
107
|
+
this.pollutedSessions.set(sessionId, { at: this.now(), reason });
|
|
108
|
+
let durable = false;
|
|
109
|
+
try {
|
|
110
|
+
durable = markSessionPolluted(this.controlDir, sessionId, reason, this.now);
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
durable = false;
|
|
114
|
+
}
|
|
115
|
+
if (!durable) {
|
|
116
|
+
const sink = this.onIncident;
|
|
117
|
+
if (sink !== undefined) {
|
|
118
|
+
try {
|
|
119
|
+
const err = new Error(`memory pollution marker for session could not be persisted — the mark holds in-process only until it succeeds`);
|
|
120
|
+
err.code = "memory.pollution_mark_failed";
|
|
121
|
+
sink(err);
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
sessionPollution(sessionId) {
|
|
129
|
+
const inProcess = this.pollutedSessions.get(sessionId);
|
|
130
|
+
if (inProcess !== undefined)
|
|
131
|
+
return inProcess;
|
|
132
|
+
try {
|
|
133
|
+
return readSessionPollution(this.controlDir, sessionId);
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
return undefined;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
88
139
|
async materialize(scopes, writeScope) {
|
|
89
140
|
ensureDirExists(this.memoryDir);
|
|
90
141
|
ensureDirExists(this.controlDir);
|
|
@@ -246,8 +297,8 @@ export class MemoryEngine {
|
|
|
246
297
|
: `${f.reason}. ${scanRemediation(f.code)} Nothing was written.`;
|
|
247
298
|
return { ok: false, code: f.code, reason, muted };
|
|
248
299
|
}
|
|
249
|
-
async harvest(handle) {
|
|
250
|
-
const report = await this.harvestCore(handle);
|
|
300
|
+
async harvest(handle, opts) {
|
|
301
|
+
const report = await this.harvestCore(handle, opts);
|
|
251
302
|
try {
|
|
252
303
|
const gateItems = gateAnnouncementItems(report);
|
|
253
304
|
if (gateItems.length > 0)
|
|
@@ -262,7 +313,8 @@ export class MemoryEngine {
|
|
|
262
313
|
}
|
|
263
314
|
return report;
|
|
264
315
|
}
|
|
265
|
-
async harvestCore(handle) {
|
|
316
|
+
async harvestCore(handle, opts) {
|
|
317
|
+
const pollutedReason = opts?.polluted?.reason;
|
|
266
318
|
const startedAt = this.now();
|
|
267
319
|
const report = {
|
|
268
320
|
ok: true,
|
|
@@ -326,6 +378,97 @@ export class MemoryEngine {
|
|
|
326
378
|
if (r.parsed.id !== undefined)
|
|
327
379
|
presentIds.add(r.parsed.id);
|
|
328
380
|
const recordByPath = new Map(records.map((r) => [r.canonical, r]));
|
|
381
|
+
const pollutedStubs = new Map(handle.materialized.filter((m) => !m.readonly && m.stub).map((m) => [m.path, m]));
|
|
382
|
+
const containPollutedRecord = async (f) => {
|
|
383
|
+
const rel = f.rel;
|
|
384
|
+
if (f.canonical !== handle.writableRoot && !f.canonical.startsWith(`${handle.writableRoot}${sep}`)) {
|
|
385
|
+
report.rejections.push({ path: rel, code: "outside_root", reason: "file resolves outside the writable memory root (containment gate, fail-closed)" });
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
const baseId = handle.baseIds.get(f.canonical);
|
|
389
|
+
if (baseId !== undefined && !pollutedStubs.has(f.canonical) && revOfText(f.text, baseId) === handle.baseRevs.get(f.canonical))
|
|
390
|
+
return;
|
|
391
|
+
const pollutedStub = pollutedStubs.get(f.canonical);
|
|
392
|
+
if (pollutedStub) {
|
|
393
|
+
if (revOfText(f.text, pollutedStub.id) !== pollutedStub.rev) {
|
|
394
|
+
const sq = quarantineAndTombstone(f.canonical, f.text, join(this.controlDir, QUARANTINE_DIR), this.now);
|
|
395
|
+
const stubCaptured = sq.dest !== undefined;
|
|
396
|
+
if (stubCaptured && sq.removed)
|
|
397
|
+
report.movedToQuarantine.push(rel);
|
|
398
|
+
report.rejections.push({
|
|
399
|
+
path: rel,
|
|
400
|
+
code: "polluted",
|
|
401
|
+
reason: `memory write withheld: this session invoked a tool classified as an external content source, so its memory changes were not committed (archived-body stub; ${stubCaptured && sq.removed ? "file moved to quarantine for host review, and the next session re-projects the stub" : "containment incomplete — the edited stub may still be on the model-visible plane"})`,
|
|
402
|
+
});
|
|
403
|
+
if (sq.detail !== undefined || !sq.removed || !stubCaptured) {
|
|
404
|
+
const detail = `${sq.detail ?? (stubCaptured ? "suspect content still on the model-visible plane" : "quarantine capture failed")}${sq.removed ? "" : " — NOT contained"}`;
|
|
405
|
+
(report.quarantineFailures ??= []).push({ path: rel, contained: sq.removed, detail });
|
|
406
|
+
report.warnings.push(`quarantine escalation for ${rel}: ${detail}`);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
const q = quarantineAndTombstone(f.canonical, f.text, join(this.controlDir, QUARANTINE_DIR), this.now);
|
|
412
|
+
let contained = q.removed;
|
|
413
|
+
const priorId = handle.baseIds.get(f.canonical);
|
|
414
|
+
if (priorId !== undefined) {
|
|
415
|
+
const committed = await this.committedContentFor(priorId);
|
|
416
|
+
if (committed !== undefined) {
|
|
417
|
+
try {
|
|
418
|
+
writeFileNoFollow(f.canonical, committed);
|
|
419
|
+
report.restored.push(rel);
|
|
420
|
+
contained = true;
|
|
421
|
+
}
|
|
422
|
+
catch {
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
const captured = q.dest !== undefined;
|
|
427
|
+
if (captured && contained)
|
|
428
|
+
report.movedToQuarantine.push(rel);
|
|
429
|
+
report.rejections.push({
|
|
430
|
+
path: rel,
|
|
431
|
+
code: "polluted",
|
|
432
|
+
reason: `memory write withheld: this session invoked a tool classified as an external content source, so its memory changes were not committed (${captured && contained ? "file moved to quarantine for host review" : captured ? "quarantine copy captured; removal from the model-visible plane incomplete" : "quarantine copy FAILED; file removal " + (contained ? "succeeded" : "incomplete")})`,
|
|
433
|
+
});
|
|
434
|
+
if (q.detail !== undefined || !contained || !captured) {
|
|
435
|
+
const parts = [q.detail, !captured ? "quarantine capture failed" : undefined].filter((x) => x !== undefined);
|
|
436
|
+
const detail = `${parts.length > 0 ? parts.join("; ") : "suspect content still on the model-visible plane"}${contained ? "" : " — NOT contained"}`;
|
|
437
|
+
(report.quarantineFailures ??= []).push({ path: rel, contained, detail });
|
|
438
|
+
report.warnings.push(`quarantine escalation for ${rel}: ${detail}`);
|
|
439
|
+
}
|
|
440
|
+
};
|
|
441
|
+
const restorePollutedIndex = () => {
|
|
442
|
+
const pollutedIndexPath = join(handle.writableRoot, MEMORY_INDEX_FILENAME);
|
|
443
|
+
const canonicalIndex = canonicalize(pollutedIndexPath);
|
|
444
|
+
if (canonicalIndex !== pollutedIndexPath && !canonicalIndex.startsWith(`${handle.writableRoot}${sep}`)) {
|
|
445
|
+
report.warnings.push("memory index NOT restored: its path resolves outside the writable memory root (containment gate, fail-closed)");
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
448
|
+
const indexNow = readSafe(pollutedIndexPath);
|
|
449
|
+
if (indexNow === undefined || indexNow === handle.indexText)
|
|
450
|
+
return;
|
|
451
|
+
try {
|
|
452
|
+
const dest = join(this.controlDir, QUARANTINE_DIR, `${this.now()}-polluted-${MEMORY_INDEX_FILENAME}`);
|
|
453
|
+
ensureDirExists(dirname(dest));
|
|
454
|
+
writeFileSync(dest, indexNow, "utf8");
|
|
455
|
+
}
|
|
456
|
+
catch {
|
|
457
|
+
}
|
|
458
|
+
try {
|
|
459
|
+
writeFileNoFollow(pollutedIndexPath, handle.indexText);
|
|
460
|
+
report.warnings.push("memory index restored to its pre-session state — this session's index additions were not retained (session polluted; the removed text was captured to quarantine)");
|
|
461
|
+
}
|
|
462
|
+
catch (err) {
|
|
463
|
+
report.warnings.push(`memory index could NOT be restored to its pre-session state: ${err instanceof Error ? err.message : String(err)}`);
|
|
464
|
+
}
|
|
465
|
+
};
|
|
466
|
+
const containPollutedDomain = async () => {
|
|
467
|
+
for (const f of records)
|
|
468
|
+
await containPollutedRecord(f);
|
|
469
|
+
restorePollutedIndex();
|
|
470
|
+
report.warnings.push(`memory harvest committed nothing this session: ${inlineUntrusted(pollutedReason, 200)}`);
|
|
471
|
+
};
|
|
329
472
|
for (const m of handle.materialized.filter((x) => x.readonly)) {
|
|
330
473
|
const text = readSafe(m.path);
|
|
331
474
|
if (text === undefined) {
|
|
@@ -355,6 +498,8 @@ export class MemoryEngine {
|
|
|
355
498
|
kind: "mass_deletion",
|
|
356
499
|
detail: `${missingWritable.length}/${writable.length} materialized memory files are missing — judged an accident, harvest refused (no delete patches produced; explicit frontmatter tombstones are the only deletion channel)`,
|
|
357
500
|
};
|
|
501
|
+
if (pollutedReason !== undefined)
|
|
502
|
+
await containPollutedDomain();
|
|
358
503
|
return report;
|
|
359
504
|
}
|
|
360
505
|
const indexPath = join(handle.writableRoot, MEMORY_INDEX_FILENAME);
|
|
@@ -362,6 +507,8 @@ export class MemoryEngine {
|
|
|
362
507
|
if (handle.indexBaselineLines > 0 && indexNow !== undefined && indexNow.trim() === "" && missingWritable.length > 0) {
|
|
363
508
|
report.ok = false;
|
|
364
509
|
report.incident = { kind: "index_cleared", detail: "MEMORY.md was emptied alongside missing memory files — judged an accident, harvest refused" };
|
|
510
|
+
if (pollutedReason !== undefined)
|
|
511
|
+
await containPollutedDomain();
|
|
365
512
|
return report;
|
|
366
513
|
}
|
|
367
514
|
for (const m of missingWritable) {
|
|
@@ -382,7 +529,7 @@ export class MemoryEngine {
|
|
|
382
529
|
.sort((a, b) => Number(baselinePaths.has(b.canonical)) - Number(baselinePaths.has(a.canonical)) || a.slug.localeCompare(b.slug));
|
|
383
530
|
const kept = [];
|
|
384
531
|
for (const f of inDomain) {
|
|
385
|
-
if (kept.length >= this.maxFiles) {
|
|
532
|
+
if (pollutedReason === undefined && kept.length >= this.maxFiles) {
|
|
386
533
|
report.rejections.push({ path: f.rel, code: "file_cap", reason: `memory file count exceeds the cap (${this.maxFiles}); consolidate before adding more` });
|
|
387
534
|
continue;
|
|
388
535
|
}
|
|
@@ -401,7 +548,7 @@ export class MemoryEngine {
|
|
|
401
548
|
let processed = 0;
|
|
402
549
|
for (let i = 0; i < kept.length; i++) {
|
|
403
550
|
const f = kept[i];
|
|
404
|
-
if (processed >= this.harvestFileBudget || this.now() - startedAt > this.harvestDeadlineMs) {
|
|
551
|
+
if (pollutedReason === undefined && (processed >= this.harvestFileBudget || this.now() - startedAt > this.harvestDeadlineMs)) {
|
|
405
552
|
report.degraded = {
|
|
406
553
|
reason: processed >= this.harvestFileBudget ? "file_budget" : "deadline",
|
|
407
554
|
pending: kept.slice(i).map((r) => r.rel),
|
|
@@ -417,6 +564,10 @@ export class MemoryEngine {
|
|
|
417
564
|
if (fastBaseId !== undefined && !stubByPath.has(f.canonical) && revOfText(f.text, fastBaseId) === revByBasePath.get(f.canonical))
|
|
418
565
|
continue;
|
|
419
566
|
processed++;
|
|
567
|
+
if (pollutedReason !== undefined) {
|
|
568
|
+
await containPollutedRecord(f);
|
|
569
|
+
continue;
|
|
570
|
+
}
|
|
420
571
|
if (f.sizeBytes > this.perFileBytes) {
|
|
421
572
|
report.rejections.push({ path: rel, code: "too_large", reason: `memory file is ${f.sizeBytes} bytes — over the ${this.perFileBytes}-byte cap; split or trim it (rejected, NOT truncated)` });
|
|
422
573
|
continue;
|
|
@@ -524,13 +675,18 @@ export class MemoryEngine {
|
|
|
524
675
|
patches.push({ op: "add", id: entry.id, entry });
|
|
525
676
|
}
|
|
526
677
|
let patchReport;
|
|
527
|
-
|
|
528
|
-
patchReport =
|
|
678
|
+
if (pollutedReason !== undefined) {
|
|
679
|
+
patchReport = { applied: [], conflicts: [] };
|
|
529
680
|
}
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
681
|
+
else {
|
|
682
|
+
try {
|
|
683
|
+
patchReport = await this.backend.applyPatches(patches);
|
|
684
|
+
}
|
|
685
|
+
catch (err) {
|
|
686
|
+
report.ok = false;
|
|
687
|
+
report.incident = { kind: "sidecar_corrupt", detail: `memory commit refused: ${err instanceof Error ? err.message : String(err)}` };
|
|
688
|
+
return report;
|
|
689
|
+
}
|
|
534
690
|
}
|
|
535
691
|
const appliedIds = new Set(patchReport.applied.filter((a) => a.op !== "delete").map((a) => a.id));
|
|
536
692
|
for (const p of pendingProjections) {
|
|
@@ -558,6 +714,10 @@ export class MemoryEngine {
|
|
|
558
714
|
const drained = this.backend.drainInboundFindings?.();
|
|
559
715
|
if (drained !== undefined && drained.length > 0)
|
|
560
716
|
report.inboundFindings = drained;
|
|
717
|
+
if (pollutedReason !== undefined) {
|
|
718
|
+
restorePollutedIndex();
|
|
719
|
+
report.warnings.push(`memory harvest committed nothing this session: ${inlineUntrusted(pollutedReason, 200)}`);
|
|
720
|
+
}
|
|
561
721
|
const indexRejection = this.gateDerivedIndex(handle);
|
|
562
722
|
if (indexRejection !== undefined)
|
|
563
723
|
report.rejections.push(indexRejection);
|
|
@@ -38,10 +38,14 @@ export declare class FileMemoryEngineBackend implements MemoryBackend {
|
|
|
38
38
|
private inboundGate;
|
|
39
39
|
private readScope;
|
|
40
40
|
listHeaders(scopes: readonly string[]): Promise<MemoryEntryHeader[]>;
|
|
41
|
+
private listHeadersWith;
|
|
41
42
|
getByIds(ids: readonly string[]): Promise<MemoryEntry[]>;
|
|
43
|
+
private getByIdsWith;
|
|
44
|
+
retrievalView(): MemoryBackend;
|
|
42
45
|
search(query: string, scopes: readonly string[], opts?: {
|
|
43
46
|
limit?: number;
|
|
44
47
|
}): Promise<ScoredMemoryEntry[]>;
|
|
48
|
+
private searchWith;
|
|
45
49
|
applyPatches(patches: readonly NotePatch[]): Promise<PatchReport>;
|
|
46
50
|
private txnLockDir;
|
|
47
51
|
private acquireTxnLock;
|
|
@@ -374,27 +374,49 @@ export class FileMemoryEngineBackend {
|
|
|
374
374
|
return entries;
|
|
375
375
|
}
|
|
376
376
|
async listHeaders(scopes) {
|
|
377
|
+
return this.listHeadersWith(scopes, true);
|
|
378
|
+
}
|
|
379
|
+
listHeadersWith(scopes, adopt) {
|
|
377
380
|
const out = [];
|
|
378
381
|
for (const scope of scopes) {
|
|
379
382
|
const dir = this.scopeDir(scope);
|
|
380
|
-
for (const e of this.readScope(scope)) {
|
|
383
|
+
for (const e of this.readScope(scope, adopt)) {
|
|
381
384
|
out.push(headerOf(e, join(dir, `${e.slug}.md`)));
|
|
382
385
|
}
|
|
383
386
|
}
|
|
384
387
|
return out;
|
|
385
388
|
}
|
|
386
389
|
async getByIds(ids) {
|
|
390
|
+
return this.getByIdsWith(ids, true);
|
|
391
|
+
}
|
|
392
|
+
getByIdsWith(ids, adopt) {
|
|
387
393
|
const want = new Set(ids);
|
|
388
394
|
const out = [];
|
|
389
395
|
for (const scope of Object.keys(registeredScopes(this.controlPlaneRoot))) {
|
|
390
|
-
for (const e of this.readScope(scope)) {
|
|
396
|
+
for (const e of this.readScope(scope, adopt)) {
|
|
391
397
|
if (want.has(e.id))
|
|
392
398
|
out.push(e);
|
|
393
399
|
}
|
|
394
400
|
}
|
|
395
401
|
return out;
|
|
396
402
|
}
|
|
403
|
+
retrievalView() {
|
|
404
|
+
const refuse = (op) => {
|
|
405
|
+
throw new Error(`memory retrieval view is read-only — ${op} must go through the backend itself`);
|
|
406
|
+
};
|
|
407
|
+
return {
|
|
408
|
+
listHeaders: async (scopes) => this.listHeadersWith(scopes, false),
|
|
409
|
+
getByIds: async (ids) => this.getByIdsWith(ids, false),
|
|
410
|
+
search: async (query, scopes, opts) => this.searchWith(query, scopes, opts, false),
|
|
411
|
+
applyPatches: async () => refuse("applyPatches"),
|
|
412
|
+
getConsolidationCursor: async (scope) => this.getConsolidationCursor(scope),
|
|
413
|
+
setConsolidationCursor: async () => refuse("setConsolidationCursor"),
|
|
414
|
+
};
|
|
415
|
+
}
|
|
397
416
|
async search(query, scopes, opts) {
|
|
417
|
+
return this.searchWith(query, scopes, opts, true);
|
|
418
|
+
}
|
|
419
|
+
searchWith(query, scopes, opts, adopt) {
|
|
398
420
|
const limit = opts?.limit ?? 20;
|
|
399
421
|
const q = termSet(query);
|
|
400
422
|
if (q.size === 0)
|
|
@@ -402,7 +424,7 @@ export class FileMemoryEngineBackend {
|
|
|
402
424
|
const scored = [];
|
|
403
425
|
for (const scope of scopes) {
|
|
404
426
|
const dir = this.scopeDir(scope);
|
|
405
|
-
for (const e of this.readScope(scope)) {
|
|
427
|
+
for (const e of this.readScope(scope, adopt)) {
|
|
406
428
|
const haystack = `${e.frontmatter.name ?? e.slug} ${e.frontmatter.description ?? ""} ${e.body}`;
|
|
407
429
|
const d = jaccardDistance(q, haystack);
|
|
408
430
|
if (d === null)
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
export { MemoryEngine, buildMemoryInstruction, truncateIndex, MEMORY_INSTRUCTION_TEMPLATE, MEMORY_INDEX_MAX_LINES, MEMORY_INDEX_MAX_BYTES, STUB_ARCHIVED_LINE, DEFAULT_MAX_MEMORY_FILES, DEFAULT_HARVEST_DEADLINE_MS, DEFAULT_HARVEST_FILE_BUDGET, MASS_DELETION_FUSE_RATIO, renderAnnouncements, type MemoryEngineOptions, type MemoryInjection, } from "./engine.js";
|
|
1
|
+
export { MemoryEngine, buildMemoryInstruction, truncateIndex, MEMORY_INSTRUCTION_TEMPLATE, MEMORY_RECALL_DISCIPLINE, MEMORY_INDEX_MAX_LINES, MEMORY_INDEX_MAX_BYTES, STUB_ARCHIVED_LINE, DEFAULT_MAX_MEMORY_FILES, DEFAULT_HARVEST_DEADLINE_MS, DEFAULT_HARVEST_FILE_BUDGET, MASS_DELETION_FUSE_RATIO, renderAnnouncements, type MemoryEngineOptions, type MemoryInjection, } from "./engine.js";
|
|
2
|
+
export { MEMORY_SEARCH_TOOL_NAME, MEMORY_GET_TOOL_NAME, MEMORY_ENGINE_TOOL_NAMES, type MemorySearchDetails, type MemorySearchHit, type MemoryGetDetails, } from "./tools.js";
|
|
2
3
|
export { scanMemoryWrite, scanMemoryFileName, scanRemediation, MEMORY_FILENAME_SEGMENT_RE } from "./scan.js";
|
|
3
4
|
export { FileMemoryEngineBackend, scanEntryFiles, MEMORY_INDEX_FILENAME, DEFAULT_MAX_ENTRY_DEPTH, type ScannedEntryFile } from "./file-backend.js";
|
|
4
5
|
export { ControlPlaneCorruptError, deriveControlPlaneDir, deriveRepoControlPlaneDir, deriveRepoKey, deriveRepoMemoryDir, deriveProjectMemoryDir, deriveProjectControlDir, recordProjectIdHint, lookupProjectIdHint, PROJECT_ID_HINTS_FILE, resolveMemoryEngineRoot, scopeDirFor, scopeDirName, claimRootScope, rootScopeOf, enqueueMemoryAnnouncement, drainMemoryAnnouncements, peekMemoryAnnouncements, bumpScanFuse, scanFuseCount, clearScanFuse, ANNOUNCEMENTS_FILE, MEMORY_ANNOUNCEMENTS_MAX, SCAN_FUSE_FILE, SCAN_FUSE_THRESHOLD, } from "./layout.js";
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
export { MemoryEngine, buildMemoryInstruction, truncateIndex, MEMORY_INSTRUCTION_TEMPLATE, MEMORY_INDEX_MAX_LINES, MEMORY_INDEX_MAX_BYTES, STUB_ARCHIVED_LINE, DEFAULT_MAX_MEMORY_FILES, DEFAULT_HARVEST_DEADLINE_MS, DEFAULT_HARVEST_FILE_BUDGET, MASS_DELETION_FUSE_RATIO, renderAnnouncements, } from "./engine.js";
|
|
1
|
+
export { MemoryEngine, buildMemoryInstruction, truncateIndex, MEMORY_INSTRUCTION_TEMPLATE, MEMORY_RECALL_DISCIPLINE, MEMORY_INDEX_MAX_LINES, MEMORY_INDEX_MAX_BYTES, STUB_ARCHIVED_LINE, DEFAULT_MAX_MEMORY_FILES, DEFAULT_HARVEST_DEADLINE_MS, DEFAULT_HARVEST_FILE_BUDGET, MASS_DELETION_FUSE_RATIO, renderAnnouncements, } from "./engine.js";
|
|
2
|
+
export { MEMORY_SEARCH_TOOL_NAME, MEMORY_GET_TOOL_NAME, MEMORY_ENGINE_TOOL_NAMES, } from "./tools.js";
|
|
2
3
|
export { scanMemoryWrite, scanMemoryFileName, scanRemediation, MEMORY_FILENAME_SEGMENT_RE } from "./scan.js";
|
|
3
4
|
export { FileMemoryEngineBackend, scanEntryFiles, MEMORY_INDEX_FILENAME, DEFAULT_MAX_ENTRY_DEPTH } from "./file-backend.js";
|
|
4
5
|
export { ControlPlaneCorruptError, deriveControlPlaneDir, deriveRepoControlPlaneDir, deriveRepoKey, deriveRepoMemoryDir, deriveProjectMemoryDir, deriveProjectControlDir, recordProjectIdHint, lookupProjectIdHint, PROJECT_ID_HINTS_FILE, resolveMemoryEngineRoot, scopeDirFor, scopeDirName, claimRootScope, rootScopeOf, enqueueMemoryAnnouncement, drainMemoryAnnouncements, peekMemoryAnnouncements, bumpScanFuse, scanFuseCount, clearScanFuse, ANNOUNCEMENTS_FILE, MEMORY_ANNOUNCEMENTS_MAX, SCAN_FUSE_FILE, SCAN_FUSE_THRESHOLD, } from "./layout.js";
|
|
@@ -36,6 +36,7 @@ export interface QuarantineOutcome {
|
|
|
36
36
|
removed: boolean;
|
|
37
37
|
detail?: string;
|
|
38
38
|
}
|
|
39
|
+
export declare function writeFileNoFollow(path: string, content: string): void;
|
|
39
40
|
export declare function quarantineAndTombstone(path: string, content: string, quarantineDir: string, now: () => number): QuarantineOutcome;
|
|
40
41
|
export declare const ANNOUNCEMENTS_FILE = "announcements.json";
|
|
41
42
|
export declare const MEMORY_ANNOUNCEMENTS_MAX = 20;
|
|
@@ -57,6 +58,21 @@ export declare function bumpScanFuse(controlDir: string, key: string): number;
|
|
|
57
58
|
export declare function readIndexRevs(controlDir: string): Record<string, string>;
|
|
58
59
|
export declare function writeIndexRevs(controlDir: string, revs: Record<string, string>): void;
|
|
59
60
|
export declare function scanFuseCount(controlDir: string, key: string): number;
|
|
61
|
+
export declare const SESSION_POLLUTION_DIR = "session-pollution";
|
|
62
|
+
export interface SessionPollutionRecord {
|
|
63
|
+
at: number;
|
|
64
|
+
reason: string;
|
|
65
|
+
}
|
|
66
|
+
export declare function markSessionPolluted(controlDir: string, sessionId: string, reason: string, now: () => number): boolean;
|
|
67
|
+
export declare function readSessionPollution(controlDir: string, sessionId: string): SessionPollutionRecord | undefined;
|
|
68
|
+
export declare const USAGE_RETRIEVED_FILE = "usage-retrieved.json";
|
|
69
|
+
export declare const USAGE_RETRIEVED_MAX_IDS = 4096;
|
|
70
|
+
export interface RetrievedAccountRow {
|
|
71
|
+
count: number;
|
|
72
|
+
lastAt: number;
|
|
73
|
+
}
|
|
74
|
+
export declare function recordRetrievedAccount(controlDir: string, ids: readonly string[], now: () => number): void;
|
|
75
|
+
export declare function readRetrievedAccount(controlDir: string): Record<string, RetrievedAccountRow>;
|
|
60
76
|
export declare function clearScanFuse(controlDir: string, keys: Iterable<string>): void;
|
|
61
77
|
export declare function writeAllSync(fd: number, data: string): void;
|
|
62
78
|
export declare function atomicWriteFileSync(path: string, data: string): void;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { closeSync, existsSync, fsyncSync, mkdirSync, openSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, rmdirSync, statSync, unlinkSync, writeFileSync, writeSync } from "node:fs";
|
|
1
|
+
import { closeSync, constants as fsConstants, existsSync, fsyncSync, mkdirSync, openSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, rmdirSync, statSync, unlinkSync, writeFileSync, writeSync } from "node:fs";
|
|
2
|
+
const { O_WRONLY, O_CREAT, O_TRUNC, O_NOFOLLOW } = fsConstants;
|
|
2
3
|
import { homedir } from "node:os";
|
|
3
4
|
import { createHash } from "node:crypto";
|
|
4
5
|
import { basename, dirname, isAbsolute, join, resolve, sep } from "node:path";
|
|
@@ -306,6 +307,15 @@ export function ensureDirExists(dir) {
|
|
|
306
307
|
}
|
|
307
308
|
export const QUARANTINE_DIR = "quarantine";
|
|
308
309
|
const DELETED_TOMBSTONE = "---\ndeleted: true\n---\n";
|
|
310
|
+
export function writeFileNoFollow(path, content) {
|
|
311
|
+
const fd = openSync(path, O_WRONLY | O_CREAT | O_TRUNC | O_NOFOLLOW, 0o644);
|
|
312
|
+
try {
|
|
313
|
+
writeFileSync(fd, content, "utf8");
|
|
314
|
+
}
|
|
315
|
+
finally {
|
|
316
|
+
closeSync(fd);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
309
319
|
let quarantineSeq = 0;
|
|
310
320
|
export function quarantineAndTombstone(path, content, quarantineDir, now) {
|
|
311
321
|
let captured;
|
|
@@ -340,7 +350,7 @@ export function quarantineAndTombstone(path, content, quarantineDir, now) {
|
|
|
340
350
|
}
|
|
341
351
|
catch (rmErr) {
|
|
342
352
|
try {
|
|
343
|
-
|
|
353
|
+
writeFileNoFollow(path, DELETED_TOMBSTONE);
|
|
344
354
|
removed = true;
|
|
345
355
|
detail = `${detail !== undefined ? `${detail}; ` : ""}delete failed (${rmErr instanceof Error ? rmErr.message : String(rmErr)}) — tombstoned in place`;
|
|
346
356
|
}
|
|
@@ -589,6 +599,84 @@ export function writeIndexRevs(controlDir, revs) {
|
|
|
589
599
|
export function scanFuseCount(controlDir, key) {
|
|
590
600
|
return coerceFuse(readSidecarJson(controlDir, SCAN_FUSE_FILE))[key] ?? 0;
|
|
591
601
|
}
|
|
602
|
+
export const SESSION_POLLUTION_DIR = "session-pollution";
|
|
603
|
+
function pollutionPath(controlDir, sessionId) {
|
|
604
|
+
return join(controlDir, SESSION_POLLUTION_DIR, `${encodeURIComponent(sessionId)}.json`);
|
|
605
|
+
}
|
|
606
|
+
export function markSessionPolluted(controlDir, sessionId, reason, now) {
|
|
607
|
+
const path = pollutionPath(controlDir, sessionId);
|
|
608
|
+
try {
|
|
609
|
+
ensureDirExists(dirname(path));
|
|
610
|
+
const record = { at: now(), reason };
|
|
611
|
+
writeFileSync(path, `${JSON.stringify(record, null, 2)}\n`, { encoding: "utf8", flag: "wx" });
|
|
612
|
+
return true;
|
|
613
|
+
}
|
|
614
|
+
catch (err) {
|
|
615
|
+
if (err instanceof Error && "code" in err && err.code === "EEXIST")
|
|
616
|
+
return true;
|
|
617
|
+
return false;
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
export function readSessionPollution(controlDir, sessionId) {
|
|
621
|
+
const path = pollutionPath(controlDir, sessionId);
|
|
622
|
+
let raw;
|
|
623
|
+
try {
|
|
624
|
+
raw = readFileSync(path, "utf8");
|
|
625
|
+
}
|
|
626
|
+
catch {
|
|
627
|
+
if (!existsSync(path))
|
|
628
|
+
return undefined;
|
|
629
|
+
return { at: 0, reason: "pollution marker present but unreadable (kept fail-closed)" };
|
|
630
|
+
}
|
|
631
|
+
try {
|
|
632
|
+
const parsed = JSON.parse(raw);
|
|
633
|
+
if (parsed !== null && typeof parsed === "object") {
|
|
634
|
+
const at = parsed.at;
|
|
635
|
+
const reason = parsed.reason;
|
|
636
|
+
if (typeof at === "number" && typeof reason === "string")
|
|
637
|
+
return { at, reason };
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
catch {
|
|
641
|
+
}
|
|
642
|
+
return { at: 0, reason: "pollution marker present but unreadable (kept fail-closed)" };
|
|
643
|
+
}
|
|
644
|
+
export const USAGE_RETRIEVED_FILE = "usage-retrieved.json";
|
|
645
|
+
export const USAGE_RETRIEVED_MAX_IDS = 4096;
|
|
646
|
+
function coerceRetrievedAccount(raw) {
|
|
647
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw))
|
|
648
|
+
return {};
|
|
649
|
+
const out = {};
|
|
650
|
+
for (const [k, v] of Object.entries(raw)) {
|
|
651
|
+
const row = v;
|
|
652
|
+
if (row && typeof row === "object" && typeof row.count === "number" && Number.isFinite(row.count) && row.count > 0 && typeof row.lastAt === "number" && Number.isFinite(row.lastAt)) {
|
|
653
|
+
out[k] = { count: Math.floor(row.count), lastAt: row.lastAt };
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
return out;
|
|
657
|
+
}
|
|
658
|
+
export function recordRetrievedAccount(controlDir, ids, now) {
|
|
659
|
+
if (ids.length === 0)
|
|
660
|
+
return;
|
|
661
|
+
const at = now();
|
|
662
|
+
lockedJournaledUpdate(controlDir, USAGE_RETRIEVED_FILE, (current) => {
|
|
663
|
+
const rows = coerceRetrievedAccount(current);
|
|
664
|
+
for (const id of ids) {
|
|
665
|
+
const prior = rows[id];
|
|
666
|
+
rows[id] = { count: (prior?.count ?? 0) + 1, lastAt: at };
|
|
667
|
+
}
|
|
668
|
+
const keys = Object.keys(rows);
|
|
669
|
+
if (keys.length > USAGE_RETRIEVED_MAX_IDS) {
|
|
670
|
+
keys.sort((a, b) => rows[a].lastAt - rows[b].lastAt || (a < b ? -1 : 1));
|
|
671
|
+
for (const cold of keys.slice(0, keys.length - USAGE_RETRIEVED_MAX_IDS))
|
|
672
|
+
delete rows[cold];
|
|
673
|
+
}
|
|
674
|
+
return rows;
|
|
675
|
+
});
|
|
676
|
+
}
|
|
677
|
+
export function readRetrievedAccount(controlDir) {
|
|
678
|
+
return coerceRetrievedAccount(readSidecarJson(controlDir, USAGE_RETRIEVED_FILE));
|
|
679
|
+
}
|
|
592
680
|
export function clearScanFuse(controlDir, keys) {
|
|
593
681
|
const wanted = [...keys];
|
|
594
682
|
if (wanted.length === 0)
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import { computeEntryRev } from "./frontmatter.js";
|
|
1
|
+
import { computeEntryRev, serializeEntryFile } from "./frontmatter.js";
|
|
2
2
|
import { screenInboundEntries } from "./data-plane.js";
|
|
3
|
+
import { scanMemoryFileName, scanMemoryWrite } from "./scan.js";
|
|
4
|
+
import { MAX_MEMORY_BYTES } from "../memory.js";
|
|
3
5
|
import { reconcileMemoryEntries } from "./sync.js";
|
|
4
6
|
function memorySyncPath(scope) {
|
|
5
7
|
return `/v1/memory/sync/${encodeURIComponent(scope)}`;
|
|
@@ -153,7 +155,7 @@ export async function syncMemoryScope(opts) {
|
|
|
153
155
|
if (cursor !== undefined && cursor.peer !== peer) {
|
|
154
156
|
throw new Error(`memory sync: cursor is for peer ${JSON.stringify(cursor.peer)}, not ${JSON.stringify(peer)}`);
|
|
155
157
|
}
|
|
156
|
-
for (const [name, v] of [["maxPushEntries", opts.maxPushEntries], ["maxPullEntries", opts.maxPullEntries]]) {
|
|
158
|
+
for (const [name, v] of [["maxPushEntries", opts.maxPushEntries], ["maxPullEntries", opts.maxPullEntries], ["maxEntryBytes", opts.maxEntryBytes]]) {
|
|
157
159
|
if (v !== undefined && (!Number.isSafeInteger(v) || v < 1)) {
|
|
158
160
|
throw new Error(`memory sync: ${name} must be a positive integer (>= 1) when set, got ${String(v)}`);
|
|
159
161
|
}
|
|
@@ -167,10 +169,26 @@ export async function syncMemoryScope(opts) {
|
|
|
167
169
|
if (plan.pull.length > 0 || plan.deleteLocal.length > 0 || plan.conflicts.length > 0 || plan.cleared.length > 0) {
|
|
168
170
|
throw new Error("memory sync: internal invariant violated — a baseline-stub peer produced pull/deleteLocal/conflict/cleared legs");
|
|
169
171
|
}
|
|
170
|
-
|
|
172
|
+
const pushGateConflicts = [];
|
|
173
|
+
const gatedPush = plan.push.filter((e) => {
|
|
174
|
+
const findings = [];
|
|
175
|
+
const nameFinding = scanMemoryFileName(`${e.slug}.md`);
|
|
176
|
+
if (nameFinding !== undefined)
|
|
177
|
+
findings.push(nameFinding);
|
|
178
|
+
findings.push(...scanMemoryWrite(serializeEntryFile(e), { maxBytes: opts.maxEntryBytes ?? MAX_MEMORY_BYTES }));
|
|
179
|
+
if (findings.length === 0)
|
|
180
|
+
return true;
|
|
181
|
+
pushGateConflicts.push({
|
|
182
|
+
side: "local",
|
|
183
|
+
id: e.id,
|
|
184
|
+
reason: `push_gate: ${findings.map((f) => `${f.code}: ${f.reason}`).join("; ")} — the entry was NOT pushed (it stays local; fix or remove it to stop this report)`,
|
|
185
|
+
});
|
|
186
|
+
return false;
|
|
187
|
+
});
|
|
188
|
+
let pushEntries = gatedPush;
|
|
171
189
|
let pushTruncated = false;
|
|
172
190
|
if (opts.maxPushEntries !== undefined) {
|
|
173
|
-
pushEntries = [...
|
|
191
|
+
pushEntries = [...gatedPush].sort((a, b) => (a.id < b.id ? -1 : a.id > b.id ? 1 : 0));
|
|
174
192
|
if (pushEntries.length > opts.maxPushEntries) {
|
|
175
193
|
pushEntries = pushEntries.slice(0, opts.maxPushEntries);
|
|
176
194
|
pushTruncated = true;
|
|
@@ -184,7 +202,7 @@ export async function syncMemoryScope(opts) {
|
|
|
184
202
|
...(opts.maxPullEntries !== undefined ? { pull: { limit: opts.maxPullEntries } } : {}),
|
|
185
203
|
};
|
|
186
204
|
const resp = parseMemorySyncResponse(await transport(memorySyncPath(scope), request), scope, peer);
|
|
187
|
-
const conflicts = resp.conflicts.map((c) => ({ side: "server", ...c }));
|
|
205
|
+
const conflicts = [...pushGateConflicts, ...resp.conflicts.map((c) => ({ side: "server", ...c }))];
|
|
188
206
|
const rejected = new Set();
|
|
189
207
|
const localIds = new Set(local.map((e) => e.id));
|
|
190
208
|
const serverEntryIds = new Set(resp.serverEntries.map((e) => e.id));
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { ToolSpec } from "../types.js";
|
|
2
|
+
import type { MemoryBackend } from "./types.js";
|
|
3
|
+
export declare const MEMORY_SEARCH_TOOL_NAME = "memory_search";
|
|
4
|
+
export declare const MEMORY_GET_TOOL_NAME = "memory_get";
|
|
5
|
+
export declare const MEMORY_ENGINE_TOOL_NAMES: readonly ["memory_search", "memory_get"];
|
|
6
|
+
export declare const MEMORY_SEARCH_DEFAULT_LIMIT = 8;
|
|
7
|
+
export declare const MEMORY_SEARCH_MAX_LIMIT = 20;
|
|
8
|
+
export declare const MEMORY_SEARCH_SNIPPET_CAP = 600;
|
|
9
|
+
export declare const MEMORY_GET_PAGE_LINES = 200;
|
|
10
|
+
export declare const MEMORY_GET_MAX_PAGE_LINES = 1000;
|
|
11
|
+
export declare const MEMORY_GET_PAGE_CAP_BYTES: number;
|
|
12
|
+
export interface MemoryEnginePlane {
|
|
13
|
+
backend: MemoryBackend;
|
|
14
|
+
scopes: readonly string[];
|
|
15
|
+
recordRetrieved: (ids: readonly string[]) => void;
|
|
16
|
+
}
|
|
17
|
+
export interface MemoryEngineToolsOptions {
|
|
18
|
+
planes: ReadonlyArray<MemoryEnginePlane>;
|
|
19
|
+
now?: () => number;
|
|
20
|
+
}
|
|
21
|
+
export interface MemorySearchHit {
|
|
22
|
+
id: string;
|
|
23
|
+
scope: string;
|
|
24
|
+
slug: string;
|
|
25
|
+
name?: string;
|
|
26
|
+
description?: string;
|
|
27
|
+
score: number;
|
|
28
|
+
mtimeMs: number;
|
|
29
|
+
sizeBytes: number;
|
|
30
|
+
}
|
|
31
|
+
export interface MemorySearchDetails {
|
|
32
|
+
outcome: "ok" | "refused" | "failed";
|
|
33
|
+
reason?: string;
|
|
34
|
+
hits?: MemorySearchHit[];
|
|
35
|
+
}
|
|
36
|
+
export interface MemoryGetDetails {
|
|
37
|
+
outcome: "ok" | "not_found" | "ambiguous" | "refused" | "failed";
|
|
38
|
+
reason?: string;
|
|
39
|
+
id?: string;
|
|
40
|
+
scope?: string;
|
|
41
|
+
slug?: string;
|
|
42
|
+
candidates?: Array<{
|
|
43
|
+
scope: string;
|
|
44
|
+
slug: string;
|
|
45
|
+
id: string;
|
|
46
|
+
}>;
|
|
47
|
+
offset?: number;
|
|
48
|
+
lines?: number;
|
|
49
|
+
totalLines?: number;
|
|
50
|
+
}
|
|
51
|
+
export declare function cutToBytes(text: string, maxBytes: number): {
|
|
52
|
+
text: string;
|
|
53
|
+
omittedBytes: number;
|
|
54
|
+
};
|
|
55
|
+
export declare function createMemoryEngineTools(opts: MemoryEngineToolsOptions): ToolSpec[];
|