@sema-agent/core 1.452.0 → 2.0.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/dist/agents/agent-transcript-tool.d.ts +15 -0
- package/dist/agents/agent-transcript-tool.js +122 -0
- package/dist/agents/cascade.js +5 -8
- package/dist/agents/repair-loop.d.ts +1 -1
- package/dist/agents/repair-loop.js +1 -1
- package/dist/agents/retain-ledger.d.ts +60 -0
- package/dist/agents/retain-ledger.js +225 -0
- package/dist/agents/roster-store.d.ts +1 -0
- package/dist/agents/roster-store.js +9 -0
- package/dist/agents/send-message-tool.d.ts +41 -0
- package/dist/agents/send-message-tool.js +469 -0
- package/dist/agents/subagent.d.ts +33 -108
- package/dist/agents/subagent.js +22 -834
- package/dist/agents/teacher.d.ts +0 -1
- package/dist/agents/teacher.js +0 -9
- package/dist/bin/sema-tb.js +3 -3
- package/dist/brain/anthropic.js +15 -40
- package/dist/brain/openai.js +15 -40
- package/dist/brain/reasoning.d.ts +1 -1
- package/dist/brain/stream-engine.js +2 -2
- package/dist/brain/stream-shared.d.ts +17 -0
- package/dist/brain/stream-shared.js +50 -0
- package/dist/brain/timeout.d.ts +1 -1
- package/dist/brain/timeout.js +1 -1
- package/dist/config/catalog.js +1 -3
- package/dist/config/defaults.d.ts +6 -0
- package/dist/config/defaults.js +6 -0
- package/dist/core/ask-question.d.ts +1 -1
- package/dist/core/background-shell.d.ts +1 -1
- package/dist/core/checkpoint-store.d.ts +0 -1
- package/dist/core/exec-gate.d.ts +1 -1
- package/dist/core/exec-output-tail.d.ts +1 -0
- package/dist/core/exec-output-tail.js +6 -0
- package/dist/core/file-snapshot-store.d.ts +1 -1
- package/dist/core/fs-write-gate-policy.d.ts +1 -1
- package/dist/core/git-worktree-env.d.ts +1 -1
- package/dist/core/hooks.d.ts +1 -1
- package/dist/core/image-downsample.d.ts +44 -0
- package/dist/core/image-downsample.js +97 -0
- package/dist/core/lsp.d.ts +1 -1
- package/dist/core/mcp.d.ts +3 -45
- package/dist/core/mcp.js +2 -97
- package/dist/core/media-byte-cap.d.ts +1 -1
- package/dist/core/memory-recall.d.ts +0 -12
- package/dist/core/memory-recall.js +0 -222
- package/dist/core/memory.d.ts +0 -6
- package/dist/core/memory.js +0 -88
- package/dist/core/message-utils.d.ts +1 -1
- package/dist/core/oracle-isolation.d.ts +1 -1
- package/dist/core/remote-env.d.ts +1 -1
- package/dist/core/roles.d.ts +1 -1
- package/dist/core/runner/active-skill-scope.d.ts +1 -1
- package/dist/core/runner/assemble-result.d.ts +0 -2
- package/dist/core/runner/memory-consolidation.d.ts +1 -1
- package/dist/core/runner/prepare-memory.d.ts +16 -0
- package/dist/core/runner/prepare-memory.js +191 -0
- package/dist/core/runner/prepare-task.d.ts +4 -28
- package/dist/core/runner/prepare-task.js +19 -207
- package/dist/core/runner/prompt-suggestions.d.ts +1 -1
- package/dist/core/runner/runtask.d.ts +2 -3
- package/dist/core/runner/runtask.js +337 -529
- package/dist/core/runner/session-rule-policy.d.ts +1 -1
- package/dist/core/runner/synthetic-tools.d.ts +5 -5
- package/dist/core/runner/synthetic-tools.js +4 -4
- package/dist/core/runner/tool-disclosure.d.ts +3 -3
- package/dist/core/runner/tool-disclosure.js +2 -2
- package/dist/core/runner/tool-output-projection.d.ts +6 -0
- package/dist/core/runner/tool-output-projection.js +81 -0
- package/dist/core/runtime.d.ts +1 -1
- package/dist/core/scheduler.d.ts +1 -1
- package/dist/core/sensitive-path-policy.d.ts +1 -1
- package/dist/core/session-reconcile.d.ts +1 -1
- package/dist/core/session-store.d.ts +2 -1
- package/dist/core/session-store.js +2 -1
- package/dist/core/session.d.ts +0 -1
- package/dist/core/task-registry-agent.d.ts +115 -0
- package/dist/core/task-registry-agent.js +1163 -0
- package/dist/core/task-registry-monitor.d.ts +13 -0
- package/dist/core/task-registry-monitor.js +347 -0
- package/dist/core/task-registry-shared.d.ts +294 -0
- package/dist/core/task-registry-shared.js +271 -0
- package/dist/core/task-registry-workflow.d.ts +5 -0
- package/dist/core/task-registry-workflow.js +143 -0
- package/dist/core/task-registry.d.ts +51 -216
- package/dist/core/task-registry.js +127 -1940
- package/dist/core/task-tool-shape.d.ts +32 -0
- package/dist/core/task-tool-shape.js +83 -0
- package/dist/core/tool-policy.d.ts +1 -2
- package/dist/core/tool-result-budget.d.ts +1 -1
- package/dist/core/tool-result-store.d.ts +2 -2
- package/dist/core/tool-result-store.js +1 -1
- package/dist/core/tools.d.ts +2 -3
- package/dist/core/tools.js +0 -8
- package/dist/core/trace.d.ts +2 -2
- package/dist/core/types.d.ts +0 -29
- package/dist/core/workflow-run-store-contract.d.ts +1 -1
- package/dist/core/workflow-run-store-contract.js +19 -19
- package/dist/engine/execution-env/node-execution-env.d.ts +13 -0
- package/dist/engine/execution-env/node-execution-env.js +55 -25
- package/dist/engine/lsp/frame-decoder.d.ts +1 -1
- package/dist/engine/lsp/frame-decoder.js +1 -1
- package/dist/engine/lsp/node-lsp-manager.d.ts +1 -1
- package/dist/engine/lsp/stdio-lsp-transport.js +2 -2
- package/dist/index.d.ts +16 -14
- package/dist/index.js +13 -11
- package/dist/internal/harness-types.d.ts +7 -0
- package/dist/internal/harness-types.js +1 -0
- package/dist/internal/harness.d.ts +1 -7
- package/dist/internal/harness.js +1 -0
- package/dist/orchestration/goal.js +2 -2
- package/dist/orchestration/run-workflow-tool.d.ts +1 -1
- package/dist/orchestration/workflow-types.d.ts +193 -0
- package/dist/orchestration/workflow-types.js +54 -0
- package/dist/orchestration/workflow.d.ts +3 -193
- package/dist/orchestration/workflow.js +7 -59
- package/dist/prompt-assembly/assemble.d.ts +3 -3
- package/dist/prompt-assembly/assemble.js +0 -16
- package/dist/prompt-assembly/explain.d.ts +1 -1
- package/dist/prompt-assembly/tool-catalog.d.ts +1 -1
- package/dist/prompts/default.d.ts +1 -6
- package/dist/prompts/default.js +3 -11
- package/dist/scenarios/env.d.ts +1 -1
- package/dist/scenarios/full-body.d.ts +0 -3
- package/dist/scenarios/full-body.js +0 -2
- package/dist/stores/file/background-agent-store.d.ts +2 -15
- package/dist/stores/file/background-agent-store.js +30 -100
- package/dist/stores/file/checkpoint-store.d.ts +1 -14
- package/dist/stores/file/checkpoint-store.js +47 -119
- package/dist/stores/file/file-snapshot-store.d.ts +1 -1
- package/dist/stores/file/shared-ledger.d.ts +35 -0
- package/dist/stores/file/shared-ledger.js +94 -0
- package/dist/stores/file/workflow-run-store.d.ts +1 -14
- package/dist/stores/file/workflow-run-store.js +24 -93
- package/dist/tools/fs/bash-readonly-classifier.d.ts +8 -0
- package/dist/tools/fs/bash-readonly-classifier.js +166 -0
- package/dist/tools/fs/fs-bash.d.ts +31 -0
- package/dist/tools/fs/fs-bash.js +672 -0
- package/dist/tools/fs/fs-pdf.d.ts +22 -0
- package/dist/tools/fs/fs-pdf.js +236 -0
- package/dist/tools/fs/fs-read.d.ts +8 -0
- package/dist/tools/fs/fs-read.js +286 -0
- package/dist/tools/fs/fs-search-tools.d.ts +5 -0
- package/dist/tools/fs/fs-search-tools.js +173 -0
- package/dist/tools/fs/fs-shared.d.ts +55 -0
- package/dist/tools/fs/fs-shared.js +144 -0
- package/dist/tools/fs/fs-write.d.ts +8 -0
- package/dist/tools/fs/fs-write.js +423 -0
- package/dist/tools/fs/index.d.ts +12 -65
- package/dist/tools/fs/index.js +26 -2090
- package/dist/tools/fs/pdf.d.ts +1 -1
- package/dist/tools/fs/repo-map.d.ts +3 -3
- package/dist/tools/fs/repo-map.js +1 -1
- package/dist/tools/fs/safety.d.ts +1 -1
- package/dist/tools/fs/search.d.ts +1 -1
- package/dist/tools/monitor.d.ts +1 -1
- package/dist/tools/scheduler-tools.d.ts +1 -1
- package/dist/tools/web.d.ts +2 -2
- package/dist/tools/web.js +1 -1
- package/dist/tools/worktree.d.ts +1 -1
- package/package.json +1 -1
- package/dist/core/dynamic-recall.d.ts +0 -32
- package/dist/core/dynamic-recall.js +0 -76
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
export const MCP_IMAGE_MAX_BASE64 = 5 * 1024 * 1024;
|
|
2
|
+
export const IMAGE_TARGET_RAW_SIZE = (MCP_IMAGE_MAX_BASE64 * 3) / 4;
|
|
3
|
+
export const IMAGE_MAX_WIDTH = 2000;
|
|
4
|
+
export const IMAGE_MAX_HEIGHT = 2000;
|
|
5
|
+
export function createImageDownsampler(sharp) {
|
|
6
|
+
const BOX = { fit: "inside", withoutEnlargement: true };
|
|
7
|
+
return async (input, mimeType) => {
|
|
8
|
+
if (input.length === 0)
|
|
9
|
+
return undefined;
|
|
10
|
+
try {
|
|
11
|
+
const meta = await sharp(input).metadata();
|
|
12
|
+
const fmt = meta.format === "jpg" ? "jpeg" : meta.format;
|
|
13
|
+
const passMime = fmt ? `image/${fmt}` : mimeType;
|
|
14
|
+
const originalSize = input.length;
|
|
15
|
+
if (!meta.width || !meta.height) {
|
|
16
|
+
if (originalSize > IMAGE_TARGET_RAW_SIZE) {
|
|
17
|
+
const buf = await sharp(input).jpeg({ quality: 80 }).toBuffer();
|
|
18
|
+
return { base64: buf.toString("base64"), mimeType: "image/jpeg" };
|
|
19
|
+
}
|
|
20
|
+
return { base64: input.toString("base64"), mimeType: passMime };
|
|
21
|
+
}
|
|
22
|
+
const originalWidth = meta.width;
|
|
23
|
+
const originalHeight = meta.height;
|
|
24
|
+
let width = originalWidth;
|
|
25
|
+
let height = originalHeight;
|
|
26
|
+
const dims = (dw, dh) => ({ originalWidth, originalHeight, displayWidth: dw, displayHeight: dh });
|
|
27
|
+
if (originalSize <= IMAGE_TARGET_RAW_SIZE && width <= IMAGE_MAX_WIDTH && height <= IMAGE_MAX_HEIGHT) {
|
|
28
|
+
return { base64: input.toString("base64"), mimeType: passMime, dimensions: dims(width, height) };
|
|
29
|
+
}
|
|
30
|
+
const needsDimensionResize = width > IMAGE_MAX_WIDTH || height > IMAGE_MAX_HEIGHT;
|
|
31
|
+
const isPng = fmt === "png";
|
|
32
|
+
if (!needsDimensionResize) {
|
|
33
|
+
if (isPng) {
|
|
34
|
+
const png = await sharp(input).png({ compressionLevel: 9, palette: true }).toBuffer();
|
|
35
|
+
if (png.length <= IMAGE_TARGET_RAW_SIZE)
|
|
36
|
+
return { base64: png.toString("base64"), mimeType: "image/png", dimensions: dims(width, height) };
|
|
37
|
+
}
|
|
38
|
+
for (const quality of [80, 60, 40, 20]) {
|
|
39
|
+
const buf = await sharp(input).jpeg({ quality }).toBuffer();
|
|
40
|
+
if (buf.length <= IMAGE_TARGET_RAW_SIZE)
|
|
41
|
+
return { base64: buf.toString("base64"), mimeType: "image/jpeg", dimensions: dims(width, height) };
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (width > IMAGE_MAX_WIDTH) {
|
|
45
|
+
height = Math.round((height * IMAGE_MAX_WIDTH) / width);
|
|
46
|
+
width = IMAGE_MAX_WIDTH;
|
|
47
|
+
}
|
|
48
|
+
if (height > IMAGE_MAX_HEIGHT) {
|
|
49
|
+
width = Math.round((width * IMAGE_MAX_HEIGHT) / height);
|
|
50
|
+
height = IMAGE_MAX_HEIGHT;
|
|
51
|
+
}
|
|
52
|
+
const resized = await sharp(input).resize(width, height, BOX).toBuffer();
|
|
53
|
+
if (resized.length <= IMAGE_TARGET_RAW_SIZE) {
|
|
54
|
+
return { base64: resized.toString("base64"), mimeType: passMime, dimensions: dims(width, height) };
|
|
55
|
+
}
|
|
56
|
+
if (isPng) {
|
|
57
|
+
const png = await sharp(input).resize(width, height, BOX).png({ compressionLevel: 9, palette: true }).toBuffer();
|
|
58
|
+
if (png.length <= IMAGE_TARGET_RAW_SIZE)
|
|
59
|
+
return { base64: png.toString("base64"), mimeType: "image/png", dimensions: dims(width, height) };
|
|
60
|
+
}
|
|
61
|
+
for (const quality of [80, 60, 40, 20]) {
|
|
62
|
+
const buf = await sharp(input).resize(width, height, BOX).jpeg({ quality }).toBuffer();
|
|
63
|
+
if (buf.length <= IMAGE_TARGET_RAW_SIZE)
|
|
64
|
+
return { base64: buf.toString("base64"), mimeType: "image/jpeg", dimensions: dims(width, height) };
|
|
65
|
+
}
|
|
66
|
+
const smallerWidth = Math.min(width, 1000);
|
|
67
|
+
const smallerHeight = Math.round((height * smallerWidth) / Math.max(width, 1));
|
|
68
|
+
const last = await sharp(input).resize(smallerWidth, smallerHeight, BOX).jpeg({ quality: 20 }).toBuffer();
|
|
69
|
+
return { base64: last.toString("base64"), mimeType: "image/jpeg", dimensions: dims(smallerWidth, smallerHeight) };
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
return undefined;
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
const SHARP_LIMIT_INPUT_PIXELS = 100_000_000;
|
|
77
|
+
export async function sharpImageDownsampler() {
|
|
78
|
+
try {
|
|
79
|
+
const mod = (await import("sharp"));
|
|
80
|
+
const real = (mod.default ?? mod);
|
|
81
|
+
return createImageDownsampler((input) => real(input, { limitInputPixels: SHARP_LIMIT_INPUT_PIXELS }));
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
return undefined;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
export async function sharpImageResizer() {
|
|
88
|
+
const down = await sharpImageDownsampler();
|
|
89
|
+
if (!down)
|
|
90
|
+
return undefined;
|
|
91
|
+
return async (base64, mimeType) => {
|
|
92
|
+
const out = await down(Buffer.from(base64, "base64"), mimeType).catch(() => undefined);
|
|
93
|
+
if (!out || out.base64.length > MCP_IMAGE_MAX_BASE64)
|
|
94
|
+
return undefined;
|
|
95
|
+
return { base64: out.base64, mimeType: out.mimeType };
|
|
96
|
+
};
|
|
97
|
+
}
|
package/dist/core/lsp.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AgentTool, ExecutionEnv } from "../internal/harness.js";
|
|
1
|
+
import type { AgentTool, ExecutionEnv } from "../internal/harness-types.js";
|
|
2
2
|
export type LspOperation = "goToDefinition" | "findReferences" | "hover" | "documentSymbol" | "workspaceSymbol" | "goToImplementation" | "prepareCallHierarchy" | "incomingCalls" | "outgoingCalls";
|
|
3
3
|
export declare const LSP_OPERATIONS: readonly LspOperation[];
|
|
4
4
|
export interface LspLocation {
|
package/dist/core/mcp.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import type { AgentTool } from "../internal/harness.js";
|
|
1
|
+
import type { AgentTool } from "../internal/harness-types.js";
|
|
2
2
|
import type { ImageContent, TextContent } from "../internal/llm.js";
|
|
3
|
+
import { type McpImageResizer } from "./image-downsample.js";
|
|
3
4
|
import type { McpServerSpec, OnElicit, ToolEffect } from "./types.js";
|
|
4
5
|
export interface McpToolAxis {
|
|
5
6
|
name: string;
|
|
@@ -69,49 +70,7 @@ export declare const MCP_IDLE_TIMEOUT_HTTP_DEFAULT_MS: number;
|
|
|
69
70
|
export declare function mcpIdleTimeoutMs(kind: "stdio" | "http"): number;
|
|
70
71
|
export declare function normalizeMcpName(name: string): string;
|
|
71
72
|
export declare function clampNameSegment(seg: string, max?: number): string;
|
|
72
|
-
export
|
|
73
|
-
export declare const IMAGE_TARGET_RAW_SIZE: number;
|
|
74
|
-
export declare const IMAGE_MAX_WIDTH = 2000;
|
|
75
|
-
export declare const IMAGE_MAX_HEIGHT = 2000;
|
|
76
|
-
export type McpImageResizer = (base64: string, mimeType: string) => Promise<{
|
|
77
|
-
base64: string;
|
|
78
|
-
mimeType: string;
|
|
79
|
-
} | undefined>;
|
|
80
|
-
export interface ImageDimensions {
|
|
81
|
-
originalWidth: number;
|
|
82
|
-
originalHeight: number;
|
|
83
|
-
displayWidth: number;
|
|
84
|
-
displayHeight: number;
|
|
85
|
-
}
|
|
86
|
-
export interface DownsampledImage {
|
|
87
|
-
base64: string;
|
|
88
|
-
mimeType: string;
|
|
89
|
-
dimensions?: ImageDimensions;
|
|
90
|
-
}
|
|
91
|
-
export type ImageDownsampler = (input: Buffer, mimeType: string) => Promise<DownsampledImage | undefined>;
|
|
92
|
-
type SharpFactory = (input: Buffer) => SharpOps;
|
|
93
|
-
interface SharpOps {
|
|
94
|
-
metadata: () => Promise<{
|
|
95
|
-
width?: number;
|
|
96
|
-
height?: number;
|
|
97
|
-
format?: string;
|
|
98
|
-
}>;
|
|
99
|
-
resize: (w: number, h: number, o: {
|
|
100
|
-
fit: "inside";
|
|
101
|
-
withoutEnlargement: boolean;
|
|
102
|
-
}) => SharpOps;
|
|
103
|
-
jpeg: (o: {
|
|
104
|
-
quality: number;
|
|
105
|
-
}) => SharpOps;
|
|
106
|
-
png: (o: {
|
|
107
|
-
compressionLevel: number;
|
|
108
|
-
palette: boolean;
|
|
109
|
-
}) => SharpOps;
|
|
110
|
-
toBuffer: () => Promise<Buffer>;
|
|
111
|
-
}
|
|
112
|
-
export declare function makeImageDownsampler(sharp: SharpFactory): ImageDownsampler;
|
|
113
|
-
export declare function sharpImageDownsampler(): Promise<ImageDownsampler | undefined>;
|
|
114
|
-
export declare function sharpImageResizer(): Promise<McpImageResizer | undefined>;
|
|
73
|
+
export * from "./image-downsample.js";
|
|
115
74
|
export declare function inferCompactSchema(value: unknown, depth?: number): string;
|
|
116
75
|
export declare function resolveMcpHttpHeaders(t: {
|
|
117
76
|
headers?: Record<string, string>;
|
|
@@ -147,4 +106,3 @@ export type McpSchemaNormalizeResult = {
|
|
|
147
106
|
export declare function normalizeMcpToolSchema(schema: unknown): McpSchemaNormalizeResult;
|
|
148
107
|
export declare function mcpToolSchemaProblem(schema: unknown): string | undefined;
|
|
149
108
|
export declare function materializeMcpTools(specs: McpServerSpec[], principal?: string, onElicit?: OnElicit, imageResizer?: McpImageResizer): Promise<MaterializedMcp>;
|
|
150
|
-
export {};
|
package/dist/core/mcp.js
CHANGED
|
@@ -6,6 +6,7 @@ import { lstat, mkdir, writeFile } from "node:fs/promises";
|
|
|
6
6
|
import { tmpdir } from "node:os";
|
|
7
7
|
import { join } from "node:path";
|
|
8
8
|
import { ElicitRequestSchema, ErrorCode, ListResourcesResultSchema, McpError } from "@modelcontextprotocol/sdk/types.js";
|
|
9
|
+
import { MCP_IMAGE_MAX_BASE64, sharpImageResizer } from "./image-downsample.js";
|
|
9
10
|
import { truncateError } from "./tool-errors.js";
|
|
10
11
|
import { delimitUntrusted, inlineUntrusted, sanitizeUntrustedText } from "./untrusted-text.js";
|
|
11
12
|
import { validateJsonSchemaShape } from "./runner/strict-output-schema.js";
|
|
@@ -204,10 +205,7 @@ export function clampNameSegment(seg, max = 64) {
|
|
|
204
205
|
const keep = max - digest.length - 1;
|
|
205
206
|
return keep > 0 ? `${seg.slice(0, keep)}_${digest}` : digest.slice(0, max);
|
|
206
207
|
}
|
|
207
|
-
export
|
|
208
|
-
export const IMAGE_TARGET_RAW_SIZE = (MCP_IMAGE_MAX_BASE64 * 3) / 4;
|
|
209
|
-
export const IMAGE_MAX_WIDTH = 2000;
|
|
210
|
-
export const IMAGE_MAX_HEIGHT = 2000;
|
|
208
|
+
export * from "./image-downsample.js";
|
|
211
209
|
const MCP_BLOB_DIR = join(tmpdir(), "sema-mcp-blobs");
|
|
212
210
|
function extensionForMime(mimeType) {
|
|
213
211
|
if (!mimeType)
|
|
@@ -267,99 +265,6 @@ async function blobToTextBlock(base64Data, mimeType, server, prefix, refetchHint
|
|
|
267
265
|
}
|
|
268
266
|
return { type: "text", text: `${prefix}Binary content (${mt}, ${r.bytes} bytes) saved to ${r.filePath}` };
|
|
269
267
|
}
|
|
270
|
-
export function makeImageDownsampler(sharp) {
|
|
271
|
-
const BOX = { fit: "inside", withoutEnlargement: true };
|
|
272
|
-
return async (input, mimeType) => {
|
|
273
|
-
if (input.length === 0)
|
|
274
|
-
return undefined;
|
|
275
|
-
try {
|
|
276
|
-
const meta = await sharp(input).metadata();
|
|
277
|
-
const fmt = meta.format === "jpg" ? "jpeg" : meta.format;
|
|
278
|
-
const passMime = fmt ? `image/${fmt}` : mimeType;
|
|
279
|
-
const originalSize = input.length;
|
|
280
|
-
if (!meta.width || !meta.height) {
|
|
281
|
-
if (originalSize > IMAGE_TARGET_RAW_SIZE) {
|
|
282
|
-
const buf = await sharp(input).jpeg({ quality: 80 }).toBuffer();
|
|
283
|
-
return { base64: buf.toString("base64"), mimeType: "image/jpeg" };
|
|
284
|
-
}
|
|
285
|
-
return { base64: input.toString("base64"), mimeType: passMime };
|
|
286
|
-
}
|
|
287
|
-
const originalWidth = meta.width;
|
|
288
|
-
const originalHeight = meta.height;
|
|
289
|
-
let width = originalWidth;
|
|
290
|
-
let height = originalHeight;
|
|
291
|
-
const dims = (dw, dh) => ({ originalWidth, originalHeight, displayWidth: dw, displayHeight: dh });
|
|
292
|
-
if (originalSize <= IMAGE_TARGET_RAW_SIZE && width <= IMAGE_MAX_WIDTH && height <= IMAGE_MAX_HEIGHT) {
|
|
293
|
-
return { base64: input.toString("base64"), mimeType: passMime, dimensions: dims(width, height) };
|
|
294
|
-
}
|
|
295
|
-
const needsDimensionResize = width > IMAGE_MAX_WIDTH || height > IMAGE_MAX_HEIGHT;
|
|
296
|
-
const isPng = fmt === "png";
|
|
297
|
-
if (!needsDimensionResize) {
|
|
298
|
-
if (isPng) {
|
|
299
|
-
const png = await sharp(input).png({ compressionLevel: 9, palette: true }).toBuffer();
|
|
300
|
-
if (png.length <= IMAGE_TARGET_RAW_SIZE)
|
|
301
|
-
return { base64: png.toString("base64"), mimeType: "image/png", dimensions: dims(width, height) };
|
|
302
|
-
}
|
|
303
|
-
for (const quality of [80, 60, 40, 20]) {
|
|
304
|
-
const buf = await sharp(input).jpeg({ quality }).toBuffer();
|
|
305
|
-
if (buf.length <= IMAGE_TARGET_RAW_SIZE)
|
|
306
|
-
return { base64: buf.toString("base64"), mimeType: "image/jpeg", dimensions: dims(width, height) };
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
if (width > IMAGE_MAX_WIDTH) {
|
|
310
|
-
height = Math.round((height * IMAGE_MAX_WIDTH) / width);
|
|
311
|
-
width = IMAGE_MAX_WIDTH;
|
|
312
|
-
}
|
|
313
|
-
if (height > IMAGE_MAX_HEIGHT) {
|
|
314
|
-
width = Math.round((width * IMAGE_MAX_HEIGHT) / height);
|
|
315
|
-
height = IMAGE_MAX_HEIGHT;
|
|
316
|
-
}
|
|
317
|
-
const resized = await sharp(input).resize(width, height, BOX).toBuffer();
|
|
318
|
-
if (resized.length <= IMAGE_TARGET_RAW_SIZE) {
|
|
319
|
-
return { base64: resized.toString("base64"), mimeType: passMime, dimensions: dims(width, height) };
|
|
320
|
-
}
|
|
321
|
-
if (isPng) {
|
|
322
|
-
const png = await sharp(input).resize(width, height, BOX).png({ compressionLevel: 9, palette: true }).toBuffer();
|
|
323
|
-
if (png.length <= IMAGE_TARGET_RAW_SIZE)
|
|
324
|
-
return { base64: png.toString("base64"), mimeType: "image/png", dimensions: dims(width, height) };
|
|
325
|
-
}
|
|
326
|
-
for (const quality of [80, 60, 40, 20]) {
|
|
327
|
-
const buf = await sharp(input).resize(width, height, BOX).jpeg({ quality }).toBuffer();
|
|
328
|
-
if (buf.length <= IMAGE_TARGET_RAW_SIZE)
|
|
329
|
-
return { base64: buf.toString("base64"), mimeType: "image/jpeg", dimensions: dims(width, height) };
|
|
330
|
-
}
|
|
331
|
-
const smallerWidth = Math.min(width, 1000);
|
|
332
|
-
const smallerHeight = Math.round((height * smallerWidth) / Math.max(width, 1));
|
|
333
|
-
const last = await sharp(input).resize(smallerWidth, smallerHeight, BOX).jpeg({ quality: 20 }).toBuffer();
|
|
334
|
-
return { base64: last.toString("base64"), mimeType: "image/jpeg", dimensions: dims(smallerWidth, smallerHeight) };
|
|
335
|
-
}
|
|
336
|
-
catch {
|
|
337
|
-
return undefined;
|
|
338
|
-
}
|
|
339
|
-
};
|
|
340
|
-
}
|
|
341
|
-
const SHARP_LIMIT_INPUT_PIXELS = 100_000_000;
|
|
342
|
-
export async function sharpImageDownsampler() {
|
|
343
|
-
try {
|
|
344
|
-
const mod = (await import("sharp"));
|
|
345
|
-
const real = (mod.default ?? mod);
|
|
346
|
-
return makeImageDownsampler((input) => real(input, { limitInputPixels: SHARP_LIMIT_INPUT_PIXELS }));
|
|
347
|
-
}
|
|
348
|
-
catch {
|
|
349
|
-
return undefined;
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
export async function sharpImageResizer() {
|
|
353
|
-
const down = await sharpImageDownsampler();
|
|
354
|
-
if (!down)
|
|
355
|
-
return undefined;
|
|
356
|
-
return async (base64, mimeType) => {
|
|
357
|
-
const out = await down(Buffer.from(base64, "base64"), mimeType).catch(() => undefined);
|
|
358
|
-
if (!out || out.base64.length > MCP_IMAGE_MAX_BASE64)
|
|
359
|
-
return undefined;
|
|
360
|
-
return { base64: out.base64, mimeType: out.mimeType };
|
|
361
|
-
};
|
|
362
|
-
}
|
|
363
268
|
async function boundedImageBlock(data, mimeType, server, resizer) {
|
|
364
269
|
if (data.length <= MCP_IMAGE_MAX_BASE64)
|
|
365
270
|
return { type: "image", data, mimeType };
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import type { MemoryNoteHeader, MemoryNoteRecord, MemoryStore } from "./memory.js";
|
|
2
|
-
import type { ModelRef, TaskSpec, TaskResult } from "./types.js";
|
|
3
2
|
export declare const DEFAULT_MAX_SELECTED = 5;
|
|
4
3
|
export declare const DEFAULT_MAX_LINKED = 3;
|
|
5
4
|
export declare const DEFAULT_SELECT_TIMEOUT_MS = 5000;
|
|
@@ -38,8 +37,6 @@ export declare function resolveLinkedIds(headers: MemoryNoteHeader[], selected:
|
|
|
38
37
|
export declare function buildManifestText(headers: MemoryNoteHeader[]): string;
|
|
39
38
|
export declare function validateSelectedIds(headers: MemoryNoteHeader[], ids: string[], max: number): string[];
|
|
40
39
|
export declare function composeSelectiveBody(manifestText: string, selected: MemoryNoteRecord[], nowMs: number, linked?: MemoryNoteRecord[], recallable?: boolean): string;
|
|
41
|
-
export declare function composeSelectiveMemoryBlock(scope: string, manifestText: string, selected: MemoryNoteRecord[], nowMs: number, linked?: MemoryNoteRecord[]): string;
|
|
42
|
-
export declare function selectAndComposeMemory(opts: SelectiveRecallOptions): Promise<SelectiveRecallResult>;
|
|
43
40
|
export declare function encodeSurfacedKey(scope: string, id: string): string;
|
|
44
41
|
export interface ScopedNoteHeader extends MemoryNoteHeader {
|
|
45
42
|
scope: string;
|
|
@@ -70,12 +67,3 @@ export type LayeredRecallResult = {
|
|
|
70
67
|
degrade: true;
|
|
71
68
|
surfacedKeys: string[];
|
|
72
69
|
};
|
|
73
|
-
export declare function selectAndComposeLayeredMemory(opts: LayeredRecallOptions): Promise<LayeredRecallResult>;
|
|
74
|
-
export interface MemorySelectorRunner {
|
|
75
|
-
runTask(spec: TaskSpec): Promise<TaskResult>;
|
|
76
|
-
}
|
|
77
|
-
export declare function createBrainMemorySelector(runner: MemorySelectorRunner, opts?: {
|
|
78
|
-
model?: ModelRef;
|
|
79
|
-
timeoutSec?: number;
|
|
80
|
-
release?: (sessionId: string) => Promise<void> | void;
|
|
81
|
-
}): MemorySelector;
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { Type } from "typebox";
|
|
2
1
|
import { sanitizeUntrustedText } from "./untrusted-text.js";
|
|
3
2
|
export const DEFAULT_MAX_SELECTED = 5;
|
|
4
3
|
export const DEFAULT_MAX_LINKED = 3;
|
|
@@ -125,227 +124,6 @@ export function composeSelectiveBody(manifestText, selected, nowMs, linked = [],
|
|
|
125
124
|
: "";
|
|
126
125
|
return `${manifestSection}${selectedSection}`;
|
|
127
126
|
}
|
|
128
|
-
export function composeSelectiveMemoryBlock(scope, manifestText, selected, nowMs, linked = []) {
|
|
129
|
-
return (`<user_memory scope="${scope}" mode="selective">\n` +
|
|
130
|
-
`${RECALL_CAVEAT}\n\n` +
|
|
131
|
-
`${composeSelectiveBody(manifestText, selected, nowMs, linked)}\n` +
|
|
132
|
-
`</user_memory>`);
|
|
133
|
-
}
|
|
134
|
-
export async function selectAndComposeMemory(opts) {
|
|
135
|
-
if (!opts.store.listStructuredNotes || !opts.store.getByIds)
|
|
136
|
-
return { degrade: true, surfacedIds: [] };
|
|
137
|
-
const allHeaders = await opts.store.listStructuredNotes(opts.scope);
|
|
138
|
-
if (allHeaders.length === 0)
|
|
139
|
-
return { degrade: true, surfacedIds: [] };
|
|
140
|
-
const headers = opts.priorSurfacedIds?.size
|
|
141
|
-
? allHeaders.filter((h) => !opts.priorSurfacedIds.has(h.id))
|
|
142
|
-
: allHeaders;
|
|
143
|
-
if (headers.length === 0)
|
|
144
|
-
return { block: undefined, surfacedIds: [] };
|
|
145
|
-
const manifestText = buildManifestText(headers);
|
|
146
|
-
const max = opts.maxSelected ?? DEFAULT_MAX_SELECTED;
|
|
147
|
-
const timeoutMs = opts.timeoutMs ?? DEFAULT_SELECT_TIMEOUT_MS;
|
|
148
|
-
const ac = new AbortController();
|
|
149
|
-
if (opts.signal?.aborted)
|
|
150
|
-
ac.abort();
|
|
151
|
-
const onOuterAbort = () => ac.abort();
|
|
152
|
-
opts.signal?.addEventListener("abort", onOuterAbort, { once: true });
|
|
153
|
-
let ids;
|
|
154
|
-
try {
|
|
155
|
-
ids = await withTimeout(opts.selector({ objective: opts.objective, manifest: headers, recentTools: opts.recentTools }, ac.signal), timeoutMs, () => ac.abort());
|
|
156
|
-
}
|
|
157
|
-
catch {
|
|
158
|
-
return { degrade: true, surfacedIds: [] };
|
|
159
|
-
}
|
|
160
|
-
finally {
|
|
161
|
-
opts.signal?.removeEventListener("abort", onOuterAbort);
|
|
162
|
-
}
|
|
163
|
-
const chosen = validateSelectedIds(headers, ids, max);
|
|
164
|
-
const records = chosen.length > 0 ? await opts.store.getByIds(opts.scope, chosen) : [];
|
|
165
|
-
let linked = [];
|
|
166
|
-
const maxLinked = opts.maxLinked ?? DEFAULT_MAX_LINKED;
|
|
167
|
-
if (records.length > 0 && maxLinked > 0) {
|
|
168
|
-
try {
|
|
169
|
-
const linkedIds = resolveLinkedIds(headers, records, maxLinked);
|
|
170
|
-
if (linkedIds.length > 0)
|
|
171
|
-
linked = await opts.store.getByIds(opts.scope, linkedIds);
|
|
172
|
-
}
|
|
173
|
-
catch {
|
|
174
|
-
linked = [];
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
const surfacedIds = [...records.map((r) => r.id), ...linked.map((r) => r.id)];
|
|
178
|
-
return {
|
|
179
|
-
block: composeSelectiveMemoryBlock(opts.scope, manifestText, records, opts.nowMs ?? Date.now(), linked),
|
|
180
|
-
surfacedIds,
|
|
181
|
-
};
|
|
182
|
-
}
|
|
183
127
|
export function encodeSurfacedKey(scope, id) {
|
|
184
128
|
return `${scope.length}:${scope}:${id}`;
|
|
185
129
|
}
|
|
186
|
-
export async function selectAndComposeLayeredMemory(opts) {
|
|
187
|
-
if (!opts.store.listStructuredNotes || !opts.store.getByIds)
|
|
188
|
-
return { degrade: true, surfacedKeys: [] };
|
|
189
|
-
const listStructuredNotes = opts.store.listStructuredNotes.bind(opts.store);
|
|
190
|
-
const getByIds = opts.store.getByIds.bind(opts.store);
|
|
191
|
-
const perScope = await Promise.all(opts.scopes.map(async (scope) => {
|
|
192
|
-
const headers = await listStructuredNotes(scope);
|
|
193
|
-
return headers.map((h) => ({ ...h, scope }));
|
|
194
|
-
}));
|
|
195
|
-
const allScoped = perScope.flat();
|
|
196
|
-
if (allScoped.length === 0)
|
|
197
|
-
return { degrade: true, surfacedKeys: [] };
|
|
198
|
-
const scoped = opts.priorSurfacedKeys?.size
|
|
199
|
-
? allScoped.filter((h) => !opts.priorSurfacedKeys.has(encodeSurfacedKey(h.scope, h.id)))
|
|
200
|
-
: allScoped;
|
|
201
|
-
if (scoped.length === 0)
|
|
202
|
-
return { block: undefined, surfacedKeys: [] };
|
|
203
|
-
const scopeIndex = new Map();
|
|
204
|
-
opts.scopes.forEach((s, i) => scopeIndex.set(s, i));
|
|
205
|
-
const tokenOf = (h) => `s${scopeIndex.get(h.scope) ?? 0}#${h.id}`;
|
|
206
|
-
const byToken = new Map();
|
|
207
|
-
const manifestHeaders = scoped.map((h) => {
|
|
208
|
-
const token = tokenOf(h);
|
|
209
|
-
byToken.set(token, h);
|
|
210
|
-
return { ...h, id: token, description: `[${h.scope}] ${h.description}` };
|
|
211
|
-
});
|
|
212
|
-
const max = opts.maxSelected ?? DEFAULT_MAX_SELECTED;
|
|
213
|
-
const timeoutMs = opts.timeoutMs ?? DEFAULT_SELECT_TIMEOUT_MS;
|
|
214
|
-
const ac = new AbortController();
|
|
215
|
-
if (opts.signal?.aborted)
|
|
216
|
-
ac.abort();
|
|
217
|
-
const onOuterAbort = () => ac.abort();
|
|
218
|
-
opts.signal?.addEventListener("abort", onOuterAbort, { once: true });
|
|
219
|
-
let chosenTokens;
|
|
220
|
-
try {
|
|
221
|
-
const ids = await withTimeout(opts.selector({ objective: opts.objective, manifest: manifestHeaders, recentTools: opts.recentTools }, ac.signal), timeoutMs, () => ac.abort());
|
|
222
|
-
chosenTokens = validateSelectedIds(manifestHeaders, ids, max);
|
|
223
|
-
}
|
|
224
|
-
catch {
|
|
225
|
-
return { degrade: true, surfacedKeys: [] };
|
|
226
|
-
}
|
|
227
|
-
finally {
|
|
228
|
-
opts.signal?.removeEventListener("abort", onOuterAbort);
|
|
229
|
-
}
|
|
230
|
-
const wantByScope = new Map();
|
|
231
|
-
for (const token of chosenTokens) {
|
|
232
|
-
const h = byToken.get(token);
|
|
233
|
-
if (!h)
|
|
234
|
-
continue;
|
|
235
|
-
const arr = wantByScope.get(h.scope) ?? [];
|
|
236
|
-
arr.push(h.id);
|
|
237
|
-
wantByScope.set(h.scope, arr);
|
|
238
|
-
}
|
|
239
|
-
const maxLinked = opts.maxLinked ?? DEFAULT_MAX_LINKED;
|
|
240
|
-
const recordsByScope = new Map();
|
|
241
|
-
const linkedByScope = new Map();
|
|
242
|
-
const scopedHeadersByScope = new Map();
|
|
243
|
-
for (const h of scoped) {
|
|
244
|
-
const arr = scopedHeadersByScope.get(h.scope) ?? [];
|
|
245
|
-
arr.push(h);
|
|
246
|
-
scopedHeadersByScope.set(h.scope, arr);
|
|
247
|
-
}
|
|
248
|
-
for (const [scope, ids] of wantByScope) {
|
|
249
|
-
const records = ids.length > 0 ? await getByIds(scope, ids) : [];
|
|
250
|
-
recordsByScope.set(scope, records);
|
|
251
|
-
if (records.length > 0 && maxLinked > 0) {
|
|
252
|
-
try {
|
|
253
|
-
const linkedIds = resolveLinkedIds(scopedHeadersByScope.get(scope) ?? [], records, maxLinked);
|
|
254
|
-
if (linkedIds.length > 0)
|
|
255
|
-
linkedByScope.set(scope, await getByIds(scope, linkedIds));
|
|
256
|
-
}
|
|
257
|
-
catch {
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
const nowMs = opts.nowMs ?? Date.now();
|
|
262
|
-
const recallableScope = opts.scopes[0];
|
|
263
|
-
const surfacedKeys = [];
|
|
264
|
-
const subsections = [];
|
|
265
|
-
for (const scope of opts.scopes) {
|
|
266
|
-
const records = recordsByScope.get(scope) ?? [];
|
|
267
|
-
const linked = linkedByScope.get(scope) ?? [];
|
|
268
|
-
if (records.length === 0 && linked.length === 0)
|
|
269
|
-
continue;
|
|
270
|
-
for (const r of records)
|
|
271
|
-
surfacedKeys.push(encodeSurfacedKey(scope, r.id));
|
|
272
|
-
for (const r of linked)
|
|
273
|
-
surfacedKeys.push(encodeSurfacedKey(scope, r.id));
|
|
274
|
-
const recallable = scope === recallableScope;
|
|
275
|
-
const body = composeSelectiveBody(recallable ? buildManifestText(scopedHeadersByScope.get(scope) ?? []) : "", records, nowMs, linked, recallable);
|
|
276
|
-
subsections.push(`<scope name="${sanitizeUntrustedText(scope, ["user_memory"])}">\n${body}\n</scope>`);
|
|
277
|
-
}
|
|
278
|
-
if (subsections.length === 0)
|
|
279
|
-
return { block: undefined, surfacedKeys: [] };
|
|
280
|
-
return {
|
|
281
|
-
block: `<user_memory mode="selective">\n${RECALL_CAVEAT}\n\n${subsections.join("\n")}\n</user_memory>`,
|
|
282
|
-
surfacedKeys,
|
|
283
|
-
};
|
|
284
|
-
}
|
|
285
|
-
const SELECT_PROMPT = "You choose which long-term memory notes are relevant to the current task. You are given the task and a " +
|
|
286
|
-
"numbered manifest of note ids + descriptions. Return ONLY the ids that are clearly useful for this task " +
|
|
287
|
-
"(at most a handful). If you are unsure, leave it out — an empty list is fine. For any tool the task will " +
|
|
288
|
-
"use, keep notes about its gotchas/warnings/known-issues but skip pure usage/API notes (that is when " +
|
|
289
|
-
"gotchas matter most). Never invent an id that is not in the manifest.";
|
|
290
|
-
export function createBrainMemorySelector(runner, opts = {}) {
|
|
291
|
-
const schema = Type.Object({ ids: Type.Array(Type.String()) });
|
|
292
|
-
return async (req, signal) => {
|
|
293
|
-
const manifest = sanitizeUntrustedText(buildManifestText(req.manifest), ["user_memory"]);
|
|
294
|
-
const tools = req.recentTools?.length ? `\n\nTools this task will use: ${req.recentTools.join(", ")}.` : "";
|
|
295
|
-
const objective = `TASK:\n${req.objective}${tools}\n\nMEMORY MANIFEST (id: description):\n${manifest}\n\nReturn the relevant note ids.`;
|
|
296
|
-
const selectorSessionId = `memsel-${globalThis.crypto.randomUUID()}`;
|
|
297
|
-
let r;
|
|
298
|
-
try {
|
|
299
|
-
r = await runner.runTask({
|
|
300
|
-
objective,
|
|
301
|
-
systemPrompt: SELECT_PROMPT,
|
|
302
|
-
...(opts.model ? { model: opts.model } : {}),
|
|
303
|
-
sessionId: selectorSessionId,
|
|
304
|
-
tools: [],
|
|
305
|
-
outputSchema: schema,
|
|
306
|
-
enableBlockedReport: false,
|
|
307
|
-
memory: undefined,
|
|
308
|
-
limits: { timeoutSec: opts.timeoutSec ?? 20, maxTurns: 2 },
|
|
309
|
-
signal,
|
|
310
|
-
});
|
|
311
|
-
}
|
|
312
|
-
catch (e) {
|
|
313
|
-
try {
|
|
314
|
-
if (opts.release)
|
|
315
|
-
await opts.release(selectorSessionId);
|
|
316
|
-
}
|
|
317
|
-
catch {
|
|
318
|
-
}
|
|
319
|
-
throw e;
|
|
320
|
-
}
|
|
321
|
-
try {
|
|
322
|
-
if (r.status !== "completed") {
|
|
323
|
-
throw new Error(`memory-select sub-task did not complete (status=${r.status}) — degrading to inject-all`);
|
|
324
|
-
}
|
|
325
|
-
const out = r.structuredOutput;
|
|
326
|
-
if (!out || !Array.isArray(out.ids)) {
|
|
327
|
-
throw new Error("memory-select sub-task produced no { ids } output — degrading to inject-all");
|
|
328
|
-
}
|
|
329
|
-
return out.ids.filter((x) => typeof x === "string");
|
|
330
|
-
}
|
|
331
|
-
finally {
|
|
332
|
-
if (opts.release)
|
|
333
|
-
await opts.release(r.sessionId);
|
|
334
|
-
}
|
|
335
|
-
};
|
|
336
|
-
}
|
|
337
|
-
function withTimeout(p, ms, onTimeout) {
|
|
338
|
-
return new Promise((resolve, reject) => {
|
|
339
|
-
const timer = setTimeout(() => {
|
|
340
|
-
onTimeout?.();
|
|
341
|
-
reject(new Error("memory-select timeout"));
|
|
342
|
-
}, ms);
|
|
343
|
-
p.then((v) => {
|
|
344
|
-
clearTimeout(timer);
|
|
345
|
-
resolve(v);
|
|
346
|
-
}, (e) => {
|
|
347
|
-
clearTimeout(timer);
|
|
348
|
-
reject(e);
|
|
349
|
-
});
|
|
350
|
-
});
|
|
351
|
-
}
|
package/dist/core/memory.d.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type { AgentTool } from "../internal/harness.js";
|
|
2
1
|
export interface ScoredMemory {
|
|
3
2
|
id: string;
|
|
4
3
|
text: string;
|
|
@@ -104,8 +103,3 @@ export declare function composeLayeredMemoryBlock(layers: ReadonlyArray<{
|
|
|
104
103
|
scope: string;
|
|
105
104
|
content: string | null | undefined;
|
|
106
105
|
}>): string | undefined;
|
|
107
|
-
export declare function createRecallTool(store: MemoryStore, scope: string): AgentTool;
|
|
108
|
-
export declare function createRememberTool(store: MemoryStore, scope: string, onRemembered?: (note: {
|
|
109
|
-
id?: string;
|
|
110
|
-
text: string;
|
|
111
|
-
}) => void): AgentTool;
|
package/dist/core/memory.js
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
import { Type } from "typebox";
|
|
2
1
|
import { uuidv7 } from "../internal/harness.js";
|
|
3
|
-
import { defineTool } from "./tools.js";
|
|
4
2
|
import { parseScopeKey } from "./memory-engine/scope-contract.js";
|
|
5
3
|
import { sanitizeUntrustedText } from "./untrusted-text.js";
|
|
6
4
|
export function supportsConsolidation(store) {
|
|
@@ -425,89 +423,3 @@ export function composeLayeredMemoryBlock(layers) {
|
|
|
425
423
|
return undefined;
|
|
426
424
|
return `<user_memory>\n${sections.join("\n")}\n</user_memory>`;
|
|
427
425
|
}
|
|
428
|
-
const REMEMBER_DESCRIPTION = "Append a durable note to long-term memory so it survives across sessions. Use this when you " +
|
|
429
|
-
"notice a stable preference, convention, or fact worth keeping (e.g. 'User prefers concise " +
|
|
430
|
-
"answers', 'The orders table is read-only'). Phrase it as a declarative fact, NOT an instruction " +
|
|
431
|
-
"to your future self, and NOT transient task state. Single, durable sentences only. Optionally tag " +
|
|
432
|
-
"it with a `type` (user | feedback | project | reference) so it can be recalled more precisely. " +
|
|
433
|
-
"For feedback/project notes, include WHY (the reason is what makes the note applicable later).";
|
|
434
|
-
export function createRecallTool(store, scope) {
|
|
435
|
-
return defineTool({
|
|
436
|
-
name: "Recall",
|
|
437
|
-
aliases: ["recall"],
|
|
438
|
-
contract: { contractId: "core.recall@1", implementationRevision: "1" },
|
|
439
|
-
description: "Search your long-term memory for durable facts/preferences relevant to a query. Use this " +
|
|
440
|
-
"to retrieve a specific remembered detail on demand (in addition to what is already in your " +
|
|
441
|
-
"system prompt). Returns matching notes, or nothing if none match. Pass \"*\" (or an empty " +
|
|
442
|
-
"query) to LIST the most recent memories instead of keyword-matching.",
|
|
443
|
-
effect: "read",
|
|
444
|
-
parameters: Type.Object({
|
|
445
|
-
query: Type.String({ description: "Keywords to search remembered notes for, or \"*\" to list recent memories." }),
|
|
446
|
-
}),
|
|
447
|
-
execute: async (args) => {
|
|
448
|
-
const query = String(args.query ?? "").trim();
|
|
449
|
-
if (query === "" || query === "*" || query.toLowerCase() === "all") {
|
|
450
|
-
const full = await store.read(scope);
|
|
451
|
-
const text = (full ?? "").trim();
|
|
452
|
-
const entries = text
|
|
453
|
-
? (/^- /m.test(text) ? text.split(/\n(?=- )/) : text.split("\n")).map((l) => l.trim()).filter(Boolean)
|
|
454
|
-
: [];
|
|
455
|
-
if (entries.length === 0) {
|
|
456
|
-
return { content: "No memories stored yet.", details: { type: "memory-recall", count: 0 } };
|
|
457
|
-
}
|
|
458
|
-
const recent = entries.slice(-20);
|
|
459
|
-
const header = entries.length > recent.length ? `[showing the ${recent.length} most recent of ${entries.length} memories]\n` : "";
|
|
460
|
-
return { content: header + sanitizeUntrustedText(recent.join("\n")), details: { type: "memory-recall", count: recent.length } };
|
|
461
|
-
}
|
|
462
|
-
if (!store.search) {
|
|
463
|
-
return { content: "No memory matches.", details: { type: "memory-recall", count: 0 } };
|
|
464
|
-
}
|
|
465
|
-
const matches = await store.search(scope, query);
|
|
466
|
-
return {
|
|
467
|
-
content: matches.length ? sanitizeUntrustedText(matches.join("\n")) : "No memory matches.",
|
|
468
|
-
details: { type: "memory-recall", count: matches.length },
|
|
469
|
-
};
|
|
470
|
-
},
|
|
471
|
-
});
|
|
472
|
-
}
|
|
473
|
-
export function createRememberTool(store, scope, onRemembered) {
|
|
474
|
-
return defineTool({
|
|
475
|
-
name: "Remember",
|
|
476
|
-
aliases: ["remember"],
|
|
477
|
-
contract: { contractId: "core.remember@1", implementationRevision: "1" },
|
|
478
|
-
effect: "write",
|
|
479
|
-
description: REMEMBER_DESCRIPTION,
|
|
480
|
-
parameters: Type.Object({
|
|
481
|
-
note: Type.String({ description: "A single durable, declarative fact or preference to remember." }),
|
|
482
|
-
type: Type.Optional(Type.String({ description: "Optional category: user | feedback | project | reference (default project)." })),
|
|
483
|
-
description: Type.Optional(Type.String({ description: "Optional one-line summary for the memory index (default: the note's first sentence)." })),
|
|
484
|
-
}),
|
|
485
|
-
execute: async (args) => {
|
|
486
|
-
const a = args;
|
|
487
|
-
const note = String(a.note ?? "").trim();
|
|
488
|
-
if (!note) {
|
|
489
|
-
return { content: "Nothing remembered (empty note).", details: { ok: false }, isError: true };
|
|
490
|
-
}
|
|
491
|
-
const oneLine = (v, cap) => {
|
|
492
|
-
if (typeof v !== "string")
|
|
493
|
-
return undefined;
|
|
494
|
-
const t = v.replace(/\s+/g, " ").trim();
|
|
495
|
-
return t ? t.slice(0, cap) : undefined;
|
|
496
|
-
};
|
|
497
|
-
const type = oneLine(a.type, 32);
|
|
498
|
-
const description = oneLine(a.description, 300);
|
|
499
|
-
let id;
|
|
500
|
-
if (store.appendStructured) {
|
|
501
|
-
id = await store.appendStructured(scope, { body: note, type, description });
|
|
502
|
-
}
|
|
503
|
-
else {
|
|
504
|
-
id = await store.append(scope, note);
|
|
505
|
-
}
|
|
506
|
-
onRemembered?.({ id: typeof id === "string" ? id : undefined, text: note });
|
|
507
|
-
return {
|
|
508
|
-
content: `Remembered: ${note}`,
|
|
509
|
-
details: { type: "memory-saved", ok: true, note, memoryType: type, description, ...(typeof id === "string" ? { id } : {}) },
|
|
510
|
-
};
|
|
511
|
-
},
|
|
512
|
-
});
|
|
513
|
-
}
|