@hasna/mementos 0.16.0 → 0.17.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.
Files changed (37) hide show
  1. package/README.md +21 -14
  2. package/dist/cli/commands/agent.d.ts.map +1 -1
  3. package/dist/cli/commands/consolidation.d.ts.map +1 -1
  4. package/dist/cli/commands/io-export.d.ts.map +1 -1
  5. package/dist/cli/commands/io-restore.d.ts.map +1 -1
  6. package/dist/cli/commands/memory-cmd-crud.d.ts.map +1 -1
  7. package/dist/cli/commands/memory-cmd-list.d.ts +1 -1
  8. package/dist/cli/commands/memory-cmd-list.d.ts.map +1 -1
  9. package/dist/cli/commands/memory-cmd-recall.d.ts.map +1 -1
  10. package/dist/cli/commands/memory-cmd-remove.d.ts.map +1 -1
  11. package/dist/cli/commands/memory-cmd-search.d.ts.map +1 -1
  12. package/dist/cli/commands/memory-cmd-view.d.ts.map +1 -1
  13. package/dist/cli/commands/memory-cmd-when-to-use.d.ts.map +1 -1
  14. package/dist/cli/commands/project.d.ts.map +1 -1
  15. package/dist/cli/commands/system-status.d.ts.map +1 -1
  16. package/dist/cli/helpers.d.ts +3 -1
  17. package/dist/cli/helpers.d.ts.map +1 -1
  18. package/dist/cli/index.js +415 -109
  19. package/dist/cli/structured-json.d.ts +54 -0
  20. package/dist/cli/structured-json.d.ts.map +1 -0
  21. package/dist/lib/conversations-transport.d.ts +21 -0
  22. package/dist/lib/conversations-transport.d.ts.map +1 -0
  23. package/dist/lib/export-v1.d.ts +2 -0
  24. package/dist/lib/export-v1.d.ts.map +1 -1
  25. package/dist/mcp/index.js +275 -70
  26. package/dist/mcp/memory-broadcast.d.ts +6 -8
  27. package/dist/mcp/memory-broadcast.d.ts.map +1 -1
  28. package/dist/mcp/tools/bounded-output.d.ts +27 -0
  29. package/dist/mcp/tools/bounded-output.d.ts.map +1 -0
  30. package/dist/mcp/tools/lock-tools.d.ts.map +1 -1
  31. package/dist/mcp/tools/memory-inject.d.ts.map +1 -1
  32. package/dist/mcp/tools/memory-io.d.ts.map +1 -1
  33. package/dist/mcp/tools/utility-tools.d.ts.map +1 -1
  34. package/dist/sdk/index.d.ts +1 -1
  35. package/dist/sdk/index.d.ts.map +1 -1
  36. package/dist/sdk/index.js +6 -3
  37. package/package.json +1 -1
@@ -0,0 +1,54 @@
1
+ import type { Agent, Memory, Project, MemorySearchResult } from "../types/index.js";
2
+ export declare const STRUCTURED_PAGE_MAX_ROWS = 1000;
3
+ export declare const STRUCTURED_ALL_MAX_ROWS = 100000;
4
+ export declare const STRUCTURED_DEFAULT_MAX_BYTES: number;
5
+ export declare const STRUCTURED_FULL_MAX_BYTES: number;
6
+ export declare const STRUCTURED_ALL_MAX_BYTES: number;
7
+ export declare const STRUCTURED_MIN_MAX_BYTES = 1024;
8
+ export type StructuredDetail = "compact" | "full";
9
+ export interface StructuredCollectionMeta {
10
+ receipt: string;
11
+ count: number;
12
+ limit: number | null;
13
+ offset: number;
14
+ next_cursor: number | null;
15
+ has_more: boolean;
16
+ complete: boolean;
17
+ all: boolean;
18
+ detail: StructuredDetail;
19
+ max_rows: number;
20
+ max_bytes: number;
21
+ response_bytes: number;
22
+ truncated: boolean;
23
+ truncation_reason: "limit" | "cursor" | "max_bytes" | null;
24
+ omitted_from_page: number;
25
+ next_arguments: Record<string, unknown> | null;
26
+ continuation_scope: "unchanged_snapshot";
27
+ }
28
+ export declare function structuredMaxBytes(value: unknown, opts: {
29
+ all: boolean;
30
+ detail: StructuredDetail;
31
+ }): number;
32
+ export declare function structuredPageLimit(value: unknown, fallback: number): number;
33
+ export declare function compactMemory(memory: Memory, opts?: {
34
+ history?: boolean;
35
+ }): Record<string, unknown>;
36
+ export declare function compactProject(project: Project): Record<string, unknown>;
37
+ export declare function compactAgent(agent: Agent): Record<string, unknown>;
38
+ export declare function compactSearchResult(result: MemorySearchResult): Record<string, unknown>;
39
+ interface CollectionArgs<T> {
40
+ collection: string;
41
+ receipt: string;
42
+ items: T[];
43
+ offset: number;
44
+ limit: number;
45
+ sourceHasMore: boolean;
46
+ all: boolean;
47
+ detail: StructuredDetail;
48
+ maxBytes: number;
49
+ nextArguments?: Record<string, unknown>;
50
+ includeDetailInNextArguments?: boolean;
51
+ }
52
+ export declare function structuredCollectionOutput<T>(args: CollectionArgs<T>): string;
53
+ export {};
54
+ //# sourceMappingURL=structured-json.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"structured-json.d.ts","sourceRoot":"","sources":["../../src/cli/structured-json.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAEpF,eAAO,MAAM,wBAAwB,OAAQ,CAAC;AAC9C,eAAO,MAAM,uBAAuB,SAAU,CAAC;AAC/C,eAAO,MAAM,4BAA4B,QAAY,CAAC;AACtD,eAAO,MAAM,yBAAyB,QAAY,CAAC;AACnD,eAAO,MAAM,wBAAwB,QAAmB,CAAC;AACzD,eAAO,MAAM,wBAAwB,OAAO,CAAC;AAE7C,MAAM,MAAM,gBAAgB,GAAG,SAAS,GAAG,MAAM,CAAC;AAElD,MAAM,WAAW,wBAAwB;IACvC,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,QAAQ,EAAE,OAAO,CAAC;IAClB,QAAQ,EAAE,OAAO,CAAC;IAClB,GAAG,EAAE,OAAO,CAAC;IACb,MAAM,EAAE,gBAAgB,CAAC;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,EAAE,OAAO,CAAC;IACnB,iBAAiB,EAAE,OAAO,GAAG,QAAQ,GAAG,WAAW,GAAG,IAAI,CAAC;IAC3D,iBAAiB,EAAE,MAAM,CAAC;IAC1B,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IAC/C,kBAAkB,EAAE,oBAAoB,CAAC;CAC1C;AAED,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,OAAO,EACd,IAAI,EAAE;IAAE,GAAG,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,gBAAgB,CAAA;CAAE,GAC/C,MAAM,CAkBR;AAED,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAM5E;AAED,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,GAAE;IAAE,OAAO,CAAC,EAAE,OAAO,CAAA;CAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAkBvG;AAED,wBAAgB,cAAc,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CASxE;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,KAAK,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CASlE;AAED,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,kBAAkB,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAavF;AAED,UAAU,cAAc,CAAC,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,CAAC,EAAE,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,OAAO,CAAC;IACvB,GAAG,EAAE,OAAO,CAAC;IACb,MAAM,EAAE,gBAAgB,CAAC;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACxC,4BAA4B,CAAC,EAAE,OAAO,CAAC;CACxC;AAsDD,wBAAgB,0BAA0B,CAAC,CAAC,EAAE,IAAI,EAAE,cAAc,CAAC,CAAC,CAAC,GAAG,MAAM,CA6B7E"}
@@ -0,0 +1,21 @@
1
+ import { type CredentialChainOptions } from "@hasna/contracts/client";
2
+ export interface ConversationsTransportOptions {
3
+ env?: Record<string, string | undefined>;
4
+ credentials?: CredentialChainOptions;
5
+ fetch?: typeof fetch;
6
+ }
7
+ export interface ConversationsTransport {
8
+ baseUrl: string;
9
+ apiKey: string | null;
10
+ localExplicit: boolean;
11
+ fetch: typeof fetch;
12
+ }
13
+ /**
14
+ * Resolve Conversations through the shared hosted credential chain. There is
15
+ * no implicit localhost default. A caller may retain an explicitly configured
16
+ * loopback URL for a deliberate local/test setup; ambient hosted credentials
17
+ * are never attached to that caller-chosen authority.
18
+ */
19
+ export declare function resolveConversationsTransport(options?: ConversationsTransportOptions): ConversationsTransport;
20
+ export declare function conversationsRequest(path: string, init?: RequestInit, options?: ConversationsTransportOptions): Promise<Response>;
21
+ //# sourceMappingURL=conversations-transport.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"conversations-transport.d.ts","sourceRoot":"","sources":["../../src/lib/conversations-transport.ts"],"names":[],"mappings":"AAAA,OAAO,EAKL,KAAK,sBAAsB,EAC5B,MAAM,yBAAyB,CAAC;AAEjC,MAAM,WAAW,6BAA6B;IAC5C,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;IACzC,WAAW,CAAC,EAAE,sBAAsB,CAAC;IACrC,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;CACtB;AAED,MAAM,WAAW,sBAAsB;IACrC,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,aAAa,EAAE,OAAO,CAAC;IACvB,KAAK,EAAE,OAAO,KAAK,CAAC;CACrB;AAoBD;;;;;GAKG;AACH,wBAAgB,6BAA6B,CAC3C,OAAO,GAAE,6BAAkC,GAC1C,sBAAsB,CA2BxB;AAED,wBAAsB,oBAAoB,CACxC,IAAI,EAAE,MAAM,EACZ,IAAI,GAAE,WAAgB,EACtB,OAAO,GAAE,6BAAkC,GAC1C,OAAO,CAAC,QAAQ,CAAC,CASnB"}
@@ -19,6 +19,8 @@ export interface ExportV1Entry {
19
19
  * Returns an array of export entries (caller serializes to JSONL).
20
20
  */
21
21
  export declare function exportV1(filter?: MemoryFilter, db?: Database): ExportV1Entry[];
22
+ /** Build portable v1 entries for an already bounded memory page. */
23
+ export declare function exportV1Entries(memories: Memory[], db?: Database): ExportV1Entry[];
22
24
  /**
23
25
  * Serialize export entries to JSONL string (one JSON object per line).
24
26
  */
@@ -1 +1 @@
1
- {"version":3,"file":"export-v1.d.ts","sourceRoot":"","sources":["../../src/lib/export-v1.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAI9D,OAAO,EAAE,aAAa,IAAI,QAAQ,EAAE,MAAM,eAAe,CAAC;AAG1D,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,oBAAoB,CAAC;IAC9B,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,KAAK,CAAC;QAClB,WAAW,EAAE,MAAM,CAAC;QACpB,WAAW,EAAE,MAAM,CAAC;QACpB,IAAI,EAAE,MAAM,CAAC;KACd,CAAC,CAAC;CACJ;AAED;;;GAGG;AACH,wBAAgB,QAAQ,CACtB,MAAM,CAAC,EAAE,YAAY,EACrB,EAAE,CAAC,EAAE,QAAQ,GACZ,aAAa,EAAE,CA6BjB;AAED;;GAEG;AACH,wBAAgB,OAAO,CAAC,OAAO,EAAE,aAAa,EAAE,GAAG,MAAM,CAExD;AAED;;GAEG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,aAAa,EAAE,CAKxD"}
1
+ {"version":3,"file":"export-v1.d.ts","sourceRoot":"","sources":["../../src/lib/export-v1.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAI9D,OAAO,EAAE,aAAa,IAAI,QAAQ,EAAE,MAAM,eAAe,CAAC;AAE1D,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,oBAAoB,CAAC;IAC9B,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,KAAK,CAAC;QAClB,WAAW,EAAE,MAAM,CAAC;QACpB,WAAW,EAAE,MAAM,CAAC;QACpB,IAAI,EAAE,MAAM,CAAC;KACd,CAAC,CAAC;CACJ;AAED;;;GAGG;AACH,wBAAgB,QAAQ,CACtB,MAAM,CAAC,EAAE,YAAY,EACrB,EAAE,CAAC,EAAE,QAAQ,GACZ,aAAa,EAAE,CAGjB;AAED,oEAAoE;AACpE,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,MAAM,EAAE,EAClB,EAAE,CAAC,EAAE,QAAQ,GACZ,aAAa,EAAE,CA0BjB;AAED;;GAEG;AACH,wBAAgB,OAAO,CAAC,OAAO,EAAE,aAAa,EAAE,GAAG,MAAM,CAExD;AAED;;GAEG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,aAAa,EAAE,CAKxD"}
package/dist/mcp/index.js CHANGED
@@ -12917,38 +12917,102 @@ var init_zod = __esm(() => {
12917
12917
  init_external();
12918
12918
  });
12919
12919
 
12920
+ // src/lib/conversations-transport.ts
12921
+ import {
12922
+ clientTransportEnvKeys as clientTransportEnvKeys3,
12923
+ resolveClientTransport as resolveClientTransport2,
12924
+ resolveCredential as resolveCredential2,
12925
+ toV1BaseUrl as toV1BaseUrl2
12926
+ } from "@hasna/contracts/client";
12927
+ function explicitApiUrl(env2) {
12928
+ const keys = clientTransportEnvKeys3("conversations").apiUrlKeys;
12929
+ for (const key of keys) {
12930
+ const value = env2[key]?.trim();
12931
+ if (value)
12932
+ return value;
12933
+ }
12934
+ return;
12935
+ }
12936
+ function isLoopbackUrl(value) {
12937
+ try {
12938
+ const host = new URL(value).hostname.replace(/^\[|\]$/g, "").toLowerCase();
12939
+ return host === "localhost" || host === "::1" || /^127\./.test(host);
12940
+ } catch {
12941
+ return false;
12942
+ }
12943
+ }
12944
+ function resolveConversationsTransport(options = {}) {
12945
+ const env2 = options.env ?? process.env;
12946
+ const explicitUrl = explicitApiUrl(env2);
12947
+ if (explicitUrl && isLoopbackUrl(explicitUrl)) {
12948
+ return {
12949
+ baseUrl: toV1BaseUrl2(explicitUrl),
12950
+ apiKey: null,
12951
+ localExplicit: true,
12952
+ fetch: options.fetch ?? globalThis.fetch.bind(globalThis)
12953
+ };
12954
+ }
12955
+ const credential = resolveCredential2("conversations", env2, options.credentials ?? {});
12956
+ if (!credential) {
12957
+ throw new Error("Conversations notifications require a hosted credential from the Keychain, ~/.hasna/conversations/config/credentials, or HASNA_CONVERSATIONS_API_KEY; refusing localhost fallback");
12958
+ }
12959
+ const resolution = resolveClientTransport2("conversations", env2, {
12960
+ credentials: { ...options.credentials ?? {}, apiKey: credential.apiKey }
12961
+ });
12962
+ return {
12963
+ baseUrl: toV1BaseUrl2(resolution.baseUrl),
12964
+ apiKey: credential.apiKey,
12965
+ localExplicit: false,
12966
+ fetch: options.fetch ?? globalThis.fetch.bind(globalThis)
12967
+ };
12968
+ }
12969
+ async function conversationsRequest(path, init = {}, options = {}) {
12970
+ const transport = resolveConversationsTransport(options);
12971
+ const headers = new Headers(init.headers ?? {});
12972
+ if (transport.apiKey) {
12973
+ headers.set("x-api-key", transport.apiKey);
12974
+ headers.set("Authorization", `Bearer ${transport.apiKey}`);
12975
+ }
12976
+ const route = path.startsWith("/") ? path : `/${path}`;
12977
+ return transport.fetch(`${transport.baseUrl}${route}`, { ...init, headers });
12978
+ }
12979
+ var init_conversations_transport = () => {};
12980
+
12920
12981
  // src/mcp/memory-broadcast.ts
12921
12982
  var exports_memory_broadcast = {};
12922
12983
  __export(exports_memory_broadcast, {
12923
12984
  broadcastSharedMemory: () => broadcastSharedMemory
12924
12985
  });
12925
- async function broadcastSharedMemory(memory, savingAgentId) {
12986
+ async function broadcastSharedMemory(memory, savingAgentId, transport = {}) {
12926
12987
  if (!memory.project_id)
12927
12988
  return;
12928
12989
  try {
12929
- const listRes = await fetch(`${CONVERSATIONS_API}/api/v1/agents?active=true&project_id=${memory.project_id}`, {
12930
- signal: AbortSignal.timeout(3000)
12990
+ const query = new URLSearchParams({
12991
+ online_only: "true",
12992
+ project_id: memory.project_id
12931
12993
  });
12994
+ const listRes = await conversationsRequest(`/agents?${query}`, {
12995
+ signal: AbortSignal.timeout(3000)
12996
+ }, transport);
12932
12997
  if (!listRes.ok)
12933
12998
  return;
12934
12999
  const { agents } = await listRes.json();
12935
- if (!agents?.length)
12936
- return;
12937
- const otherAgents = agents.filter((a) => a.id !== savingAgentId);
12938
- if (!otherAgents.length)
13000
+ const otherAgents = (agents ?? []).filter((agent) => agent.id !== savingAgentId);
13001
+ if (otherAgents.length === 0)
12939
13002
  return;
12940
13003
  const notification = `[Memory Update] Agent ${savingAgentId} saved shared memory: "${memory.key}" \u2014 ${memory.summary || memory.value.slice(0, 100)}. Consider recalling this memory if relevant to your current task.`;
12941
- await Promise.all(otherAgents.map((agent) => fetch(`${CONVERSATIONS_API}/api/v1/messages`, {
13004
+ await Promise.all(otherAgents.map((agent) => conversationsRequest("/messages", {
12942
13005
  method: "POST",
12943
13006
  headers: { "Content-Type": "application/json" },
12944
13007
  body: JSON.stringify({ from: savingAgentId, to: agent.id, content: notification }),
12945
13008
  signal: AbortSignal.timeout(3000)
12946
- }).catch(() => {})));
13009
+ }, transport).catch(() => {
13010
+ return;
13011
+ })));
12947
13012
  } catch {}
12948
13013
  }
12949
- var CONVERSATIONS_API;
12950
13014
  var init_memory_broadcast = __esm(() => {
12951
- CONVERSATIONS_API = process.env.CONVERSATIONS_API_URL || "http://localhost:7020";
13015
+ init_conversations_transport();
12952
13016
  });
12953
13017
 
12954
13018
  // src/audit-contract.ts
@@ -13612,18 +13676,21 @@ var exports_export_v1 = {};
13612
13676
  __export(exports_export_v1, {
13613
13677
  toJsonl: () => toJsonl,
13614
13678
  fromJsonl: () => fromJsonl,
13679
+ exportV1Entries: () => exportV1Entries,
13615
13680
  exportV1: () => exportV1
13616
13681
  });
13617
13682
  function exportV1(filter, db) {
13618
- const d = db || getDatabase();
13619
- const memories = listMemories(filter, d);
13683
+ const memories = listMemoriesBounded(filter ?? {}, undefined, db).rows;
13684
+ return exportV1Entries(memories, db);
13685
+ }
13686
+ function exportV1Entries(memories, db) {
13620
13687
  const exportedAt = new Date().toISOString();
13621
13688
  return memories.map((memory) => {
13622
13689
  let entityLinks = [];
13623
13690
  try {
13624
- const links = getEntityMemoryLinks(undefined, memory.id, d);
13691
+ const links = getEntityMemoryLinks(undefined, memory.id, db);
13625
13692
  entityLinks = links.map((link) => {
13626
- const entity = getEntity(link.entity_id, d);
13693
+ const entity = getEntity(link.entity_id, db);
13627
13694
  return {
13628
13695
  entity_name: entity?.name ?? "unknown",
13629
13696
  entity_type: entity?.type ?? "unknown",
@@ -13651,7 +13718,6 @@ var init_export_v1 = __esm(() => {
13651
13718
  init_memories();
13652
13719
  init_entity_memories();
13653
13720
  init_entities();
13654
- init_database();
13655
13721
  });
13656
13722
 
13657
13723
  // src/lib/injection-project.ts
@@ -65352,22 +65418,115 @@ function registerMemoryAuditTools(server) {
65352
65418
  // src/mcp/tools/memory-io.ts
65353
65419
  init_zod();
65354
65420
  init_memories();
65421
+ init_redact();
65422
+
65423
+ // src/mcp/tools/bounded-output.ts
65424
+ var MCP_COMPACT_MAX_BYTES = 32 * 1024;
65425
+ var MCP_FULL_MAX_BYTES = 64 * 1024;
65426
+ var MCP_EXPLICIT_MAX_BYTES = 1024 * 1024;
65427
+ function mcpMaxBytes(value, detail) {
65428
+ if (value === undefined)
65429
+ return detail === "full" ? MCP_FULL_MAX_BYTES : MCP_COMPACT_MAX_BYTES;
65430
+ const parsed = typeof value === "number" ? value : Number(value);
65431
+ if (!Number.isInteger(parsed) || parsed < 1024 || parsed > MCP_EXPLICIT_MAX_BYTES) {
65432
+ throw new Error(`max_bytes must be an integer from 1024 to ${MCP_EXPLICIT_MAX_BYTES}`);
65433
+ }
65434
+ return parsed;
65435
+ }
65436
+ function boundedMcpOutput(args) {
65437
+ const makeEnvelope = (items, byteTruncated, omitted) => {
65438
+ const hasMore = byteTruncated || args.sourceHasMore;
65439
+ const nextOffset = hasMore ? args.offset + items.length : null;
65440
+ const envelope = {
65441
+ [args.collection]: items,
65442
+ _meta: {
65443
+ receipt: args.receipt,
65444
+ count: items.length,
65445
+ limit: args.limit,
65446
+ offset: args.offset,
65447
+ next_offset: nextOffset,
65448
+ has_more: hasMore,
65449
+ complete: args.offset === 0 && !hasMore,
65450
+ detail: args.detail,
65451
+ max_bytes: args.maxBytes,
65452
+ response_bytes: 0,
65453
+ truncated: hasMore,
65454
+ truncation_reason: byteTruncated ? "max_bytes" : args.sourceHasMore ? "limit" : null,
65455
+ omitted_from_page: omitted,
65456
+ next_arguments: nextOffset === null ? null : {
65457
+ offset: nextOffset,
65458
+ limit: args.limit,
65459
+ ...args.includeDetailInNextArguments !== false && args.detail === "full" ? { detail: "full" } : {},
65460
+ max_bytes: args.maxBytes,
65461
+ ...args.nextArguments
65462
+ },
65463
+ continuation_scope: "unchanged_snapshot",
65464
+ ...args.metadata
65465
+ }
65466
+ };
65467
+ for (let attempt = 0;attempt < 8; attempt += 1) {
65468
+ const bytes = Buffer.byteLength(JSON.stringify(envelope));
65469
+ const meta = envelope["_meta"];
65470
+ if (meta["response_bytes"] === bytes)
65471
+ break;
65472
+ meta["response_bytes"] = bytes;
65473
+ }
65474
+ return envelope;
65475
+ };
65476
+ for (let count = args.items.length;count >= 0; count -= 1) {
65477
+ const byteTruncated = count < args.items.length;
65478
+ const envelope = makeEnvelope(args.items.slice(0, count), byteTruncated, args.items.length - count);
65479
+ const text = JSON.stringify(envelope);
65480
+ if (Buffer.byteLength(text) > args.maxBytes)
65481
+ continue;
65482
+ if (count === 0 && args.items.length > 0) {
65483
+ throw new Error(`One ${args.detail} result exceeds max_bytes=${args.maxBytes}; request compact detail or a larger max_bytes value`);
65484
+ }
65485
+ const meta = envelope["_meta"];
65486
+ return {
65487
+ text,
65488
+ selectedCount: count,
65489
+ hasMore: meta["has_more"] === true,
65490
+ nextOffset: typeof meta["next_offset"] === "number" ? meta["next_offset"] : null
65491
+ };
65492
+ }
65493
+ throw new Error(`Response metadata exceeds max_bytes=${args.maxBytes}`);
65494
+ }
65495
+
65496
+ // src/mcp/tools/memory-io.ts
65355
65497
  function registerMemoryIoTools(server) {
65356
- server.tool("memory_export", "Export memories. format='json' (default) returns JSON array. format='v1' returns mementos-export-v1 JSONL with entity links.", {
65498
+ server.tool("memory_export", "Export one truthful byte-bounded page. format='json' returns full memory objects; format='v1' returns portable entries with entity links. Continue with next_offset.", {
65357
65499
  scope: exports_external.enum(["global", "shared", "private", "working"]).optional(),
65358
65500
  category: exports_external.enum(["preference", "fact", "knowledge", "history", "procedural", "resource"]).optional(),
65359
65501
  agent_id: exports_external.string().optional(),
65360
65502
  project_id: exports_external.string().optional(),
65361
- format: exports_external.enum(["json", "v1"]).optional().describe("Export format: json (default) or v1 (JSONL with entity links)")
65503
+ format: exports_external.enum(["json", "v1"]).optional().describe("Export page format: json (default) or portable v1 entries"),
65504
+ limit: exports_external.coerce.number().int().min(1).max(1000).optional().describe("Page size (default: 20, maximum: 1000)"),
65505
+ offset: exports_external.coerce.number().int().min(0).optional().describe("Continuation offset"),
65506
+ max_bytes: exports_external.coerce.number().int().min(1024).max(1024 * 1024).optional().describe("Response ceiling (default: 65536, maximum: 1048576)")
65362
65507
  }, async (args) => {
65363
65508
  try {
65364
- if (args.format === "v1") {
65365
- const { exportV1: exportV12, toJsonl: toJsonl2 } = await Promise.resolve().then(() => (init_export_v1(), exports_export_v1));
65366
- const entries = exportV12({ ...args });
65367
- return { content: [{ type: "text", text: toJsonl2(entries) }] };
65368
- }
65369
- const memories = listMemoriesBounded({ ...args }, 1e4).rows;
65370
- return { content: [{ type: "text", text: JSON.stringify(memories, null, 2) }] };
65509
+ const { format, limit: requestedLimit, offset: requestedOffset, max_bytes, ...filter } = args;
65510
+ const limit = requestedLimit ?? 20;
65511
+ const offset = requestedOffset ?? 0;
65512
+ const page = listMemoriesBounded({ ...filter, offset }, limit);
65513
+ const memories = page.rows.map(redactMemoryForOutput);
65514
+ const portable = format === "v1";
65515
+ const items = portable ? (await Promise.resolve().then(() => (init_export_v1(), exports_export_v1))).exportV1Entries(memories) : memories;
65516
+ const output = boundedMcpOutput({
65517
+ collection: portable ? "entries" : "memories",
65518
+ receipt: portable ? "mementos.export.v1.page.v1" : "mementos.export.page.v1",
65519
+ items,
65520
+ offset,
65521
+ limit,
65522
+ sourceHasMore: page.has_more,
65523
+ detail: "full",
65524
+ maxBytes: mcpMaxBytes(max_bytes, "full"),
65525
+ metadata: { format: portable ? "v1" : "json" },
65526
+ nextArguments: { format: portable ? "v1" : "json", ...filter },
65527
+ includeDetailInNextArguments: false
65528
+ });
65529
+ return { content: [{ type: "text", text: output.text }] };
65371
65530
  } catch (e) {
65372
65531
  return { content: [{ type: "text", text: formatError(e) }], isError: true };
65373
65532
  }
@@ -65473,7 +65632,7 @@ function getSubscriptionNotifications(agentId, db) {
65473
65632
  init_hooks();
65474
65633
  init_machine_visibility();
65475
65634
  function registerMemoryInjectTools(server) {
65476
- server.tool("memory_inject", "Get memory context for system prompt injection. Selects by scope, importance, recency. Use strategy='smart' with a query for embedding-based relevance scoring.", {
65635
+ server.tool("memory_inject", "Get memory context for system prompt injection. Defaults to lightweight hints; set mode='full' explicitly for complete content.", {
65477
65636
  agent_id: exports_external.string().optional(),
65478
65637
  project_id: exports_external.string().optional(),
65479
65638
  session_id: exports_external.string().optional(),
@@ -65485,11 +65644,12 @@ function registerMemoryInjectTools(server) {
65485
65644
  strategy: exports_external.enum(["default", "smart"]).optional().describe("Injection strategy: 'default' uses importance+recency, 'smart' uses embedding similarity+importance+recency"),
65486
65645
  query: exports_external.string().optional().describe("Query for smart injection relevance scoring. Required when strategy='smart'."),
65487
65646
  task_context: exports_external.string().optional().describe("What the agent is about to do. When provided, activates intent-based retrieval \u2014 matches against when_to_use fields for situationally relevant memories."),
65488
- mode: exports_external.enum(["full", "hints"]).optional().default("full").describe("'full' = inject complete memory content (default), 'hints' = inject lightweight topic summary with counts, saving 60-70% tokens. Agent uses memory_recall to pull details as needed."),
65647
+ mode: exports_external.enum(["full", "hints"]).optional().default("hints").describe("'hints' = lightweight topic summary (default); 'full' = explicit complete content compatibility."),
65489
65648
  machine_id: exports_external.string().optional().describe("Current machine ID for machine-local memory visibility. Defaults to the current machine.")
65490
65649
  }, async (args) => {
65491
65650
  try {
65492
- if (args.strategy === "smart" && args.task_context && args.mode !== "hints") {
65651
+ const mode = args.mode ?? "hints";
65652
+ if (args.strategy === "smart" && args.task_context && mode !== "hints") {
65493
65653
  const { smartInject: smartInject2 } = await Promise.resolve().then(() => (init_injector(), exports_injector));
65494
65654
  const result = await smartInject2({
65495
65655
  task_context: args.task_context,
@@ -65620,7 +65780,7 @@ function registerMemoryInjectTools(server) {
65620
65780
  return new Date(b.updated_at).getTime() - new Date(a.updated_at).getTime();
65621
65781
  });
65622
65782
  }
65623
- if (args.mode === "hints") {
65783
+ if (mode === "hints") {
65624
65784
  let extractTopics = function(memories, maxTopics = 8) {
65625
65785
  const wordCounts = new Map;
65626
65786
  for (const m of memories) {
@@ -66956,6 +67116,7 @@ function checkMemoryWriteLock(key, scope, projectId, db) {
66956
67116
 
66957
67117
  // src/mcp/tools/lock-tools.ts
66958
67118
  init_api_mode();
67119
+ init_conversations_transport();
66959
67120
  function formatLockLine(lock, index) {
66960
67121
  return `${index + 1}. ${lock.id.slice(0, 8)} ${lock.lock_type} ${lock.resource_type}:${compactText(lock.resource_id, 48)} agent=${lock.agent_id} expires=${lock.expires_at}`;
66961
67122
  }
@@ -67111,15 +67272,16 @@ ${visible.map(formatLockLine).join(`
67111
67272
  const expired = cleanExpiredLocksWithInfo();
67112
67273
  const count = expired.length;
67113
67274
  if (count > 0) {
67114
- const conversationsUrl = process.env.CONVERSATIONS_API_URL || "http://localhost:7020";
67115
67275
  for (const lock of expired) {
67116
67276
  const msg = `Your ${lock.lock_type} lock on ${lock.resource_type}/${lock.resource_id} has expired. Another agent may now acquire it.`;
67117
- fetch(`${conversationsUrl}/api/v1/messages`, {
67277
+ conversationsRequest("/messages", {
67118
67278
  method: "POST",
67119
67279
  headers: { "Content-Type": "application/json" },
67120
67280
  body: JSON.stringify({ from: "system", to: lock.agent_id, content: msg }),
67121
67281
  signal: AbortSignal.timeout(2000)
67122
- }).catch(() => {});
67282
+ }).catch(() => {
67283
+ return;
67284
+ });
67123
67285
  }
67124
67286
  }
67125
67287
  return { content: [{ type: "text", text: `Cleaned ${count} expired lock(s)${count > 0 ? ` and notified ${count} agent(s)` : ""}.` }] };
@@ -69506,7 +69668,10 @@ var UTILITY_TOOL_SCHEMAS = {
69506
69668
  agent_id: { type: "string", description: "Agent UUID filter" },
69507
69669
  project_id: { type: "string", description: "Project UUID filter" },
69508
69670
  scope: { type: "string", description: "Scope filter", enum: ["global", "shared", "private", "working"] },
69509
- limit: { type: "number", description: "Max results (default: 30)" },
69671
+ limit: { type: "number", description: "Max results (default: 10, maximum: 50)" },
69672
+ offset: { type: "number", description: "Continuation offset in the ranked result set" },
69673
+ detail: { type: "string", description: "compact previews (default) or explicit full memory objects", enum: ["compact", "full"] },
69674
+ max_bytes: { type: "number", description: "Response byte ceiling (compact default: 32768; full default: 65536)" },
69510
69675
  decay_halflife_days: { type: "number", description: "Importance half-life in days (default: 90). Lower = more weight on recent memories." },
69511
69676
  no_decay: { type: "boolean", description: "Set true to disable decay and sort purely by importance." },
69512
69677
  task_context: { type: "string", description: "What the agent is about to do. When provided, activates intent-based retrieval \u2014 matches against when_to_use fields." },
@@ -69630,15 +69795,18 @@ Summary: ${newMems.length} new, ${updatedMems.length} updated, ${expiredMems.len
69630
69795
  return { content: [{ type: "text", text: formatError(e) }], isError: true };
69631
69796
  }
69632
69797
  });
69633
- server.tool("memory_context", "Get memories relevant to current context. Uses time-weighted scoring: score = importance \xD7 decay(age). Pinned memories are exempt. Returns effective_score on each memory.", {
69798
+ server.tool("memory_context", "Get a ranked, byte-bounded memory-context page. Compact detail is the default; set detail='full' explicitly for complete memory fields.", {
69634
69799
  agent_id: exports_external.string().optional(),
69635
69800
  project_id: exports_external.string().optional(),
69636
69801
  scope: exports_external.enum(["global", "shared", "private", "working"]).optional(),
69637
- limit: exports_external.coerce.number().optional(),
69802
+ limit: exports_external.coerce.number().optional().describe("Max results (default: 10, maximum: 50)"),
69803
+ offset: exports_external.coerce.number().int().min(0).optional().describe("Continuation offset in the ranked result set"),
69804
+ detail: exports_external.enum(["compact", "full"]).optional().default("compact").describe("compact returns ~240-character previews; full returns complete memory objects"),
69805
+ max_bytes: exports_external.coerce.number().int().min(1024).max(1024 * 1024).optional().describe("Response ceiling (compact default: 32768; full default: 65536)"),
69638
69806
  decay_halflife_days: exports_external.coerce.number().optional().describe("Importance half-life in days (default: 90). Lower = more weight on recent memories."),
69639
69807
  no_decay: exports_external.coerce.boolean().optional().describe("Set true to disable decay and sort purely by importance."),
69640
- task_context: exports_external.string().optional().describe("What the agent is about to do. When provided, activates intent-based retrieval \u2014 matches against when_to_use fields for situationally relevant memories."),
69641
- strategy: exports_external.enum(["default", "smart"]).optional().default("default").describe("Injection strategy: 'default' = decay-scored, 'smart' = activation-matched + layered + tool-aware (requires task_context)"),
69808
+ task_context: exports_external.string().optional().describe("What the agent is about to do. When provided, activates intent-based retrieval \u2014 matches against when_to_use fields."),
69809
+ strategy: exports_external.enum(["default", "smart"]).optional().default("default").describe("'default' = ranked bounded page, 'smart' = activation-matched pipeline when task_context is provided"),
69642
69810
  machine_id: exports_external.string().optional().describe("Current machine ID for machine-local memory visibility. Defaults to the current machine.")
69643
69811
  }, async (args) => {
69644
69812
  try {
@@ -69653,16 +69821,18 @@ Summary: ${newMems.length} new, ${updatedMems.length} updated, ${expiredMems.len
69653
69821
  });
69654
69822
  return { content: [{ type: "text", text: result.output }] };
69655
69823
  }
69824
+ const limit = positiveLimit(args.limit, 10);
69825
+ const offset = args.offset ?? 0;
69826
+ const detail = args.detail ?? "compact";
69656
69827
  const visibleMachineId = resolveVisibleMachineId(args.machine_id);
69657
69828
  const filter = {
69658
69829
  scope: args.scope,
69659
69830
  agent_id: args.agent_id,
69660
69831
  project_id: args.project_id,
69661
69832
  status: "active",
69662
- visible_to_machine_id: visibleMachineId,
69663
- limit: (args.limit || 30) * 2
69833
+ visible_to_machine_id: visibleMachineId
69664
69834
  };
69665
- const memories = listMemories(filter);
69835
+ const memories = listMemoriesBounded(filter, undefined).rows;
69666
69836
  const activationBoostedIds = new Set;
69667
69837
  if (args.task_context) {
69668
69838
  try {
@@ -69674,45 +69844,80 @@ Summary: ${newMems.length} new, ${updatedMems.length} updated, ${expiredMems.len
69674
69844
  agent_id: args.agent_id,
69675
69845
  project_id: args.project_id
69676
69846
  });
69677
- const seenIds = new Set(memories.map((m) => m.id));
69678
- for (const r of activationResults) {
69679
- if (!isMemoryVisibleToMachine(r.memory, visibleMachineId))
69847
+ const seenIds = new Set(memories.map((memory) => memory.id));
69848
+ for (const result of activationResults) {
69849
+ if (!isMemoryVisibleToMachine(result.memory, visibleMachineId))
69680
69850
  continue;
69681
- activationBoostedIds.add(r.memory.id);
69682
- if (!seenIds.has(r.memory.id)) {
69683
- seenIds.add(r.memory.id);
69684
- memories.push(r.memory);
69851
+ activationBoostedIds.add(result.memory.id);
69852
+ if (!seenIds.has(result.memory.id)) {
69853
+ seenIds.add(result.memory.id);
69854
+ memories.push(result.memory);
69685
69855
  }
69686
69856
  }
69687
69857
  } catch {}
69688
69858
  }
69689
- if (memories.length === 0) {
69690
- return { content: [{ type: "text", text: "No memories in current context." }] };
69691
- }
69692
69859
  const halflifeDays = args.decay_halflife_days ?? 90;
69693
69860
  const now3 = Date.now();
69694
- const scored = memories.map((m) => {
69695
- const activationBoost = activationBoostedIds.has(m.id) ? 3 : 0;
69696
- let effectiveScore = m.importance + activationBoost;
69697
- if (!args.no_decay && !m.pinned) {
69698
- const ageMs = now3 - new Date(m.updated_at).getTime();
69699
- const ageDays = ageMs / 86400000;
69700
- const decayFactor = Math.pow(0.5, ageDays / halflifeDays);
69701
- effectiveScore = (m.importance + activationBoost) * decayFactor;
69702
- }
69703
- if (m.flag)
69861
+ const scored = memories.map((memory) => {
69862
+ const activationBoost = activationBoostedIds.has(memory.id) ? 3 : 0;
69863
+ let effectiveScore = memory.importance + activationBoost;
69864
+ if (!args.no_decay && !memory.pinned) {
69865
+ const ageDays = (now3 - new Date(memory.updated_at).getTime()) / 86400000;
69866
+ effectiveScore *= Math.pow(0.5, ageDays / halflifeDays);
69867
+ }
69868
+ if (memory.flag)
69704
69869
  effectiveScore = Math.max(effectiveScore, 11);
69705
- return { ...m, effective_score: Math.round(effectiveScore * 100) / 100 };
69870
+ return {
69871
+ ...memory,
69872
+ effective_score: Math.round(effectiveScore * 100) / 100,
69873
+ activation_matched: activationBoostedIds.has(memory.id)
69874
+ };
69706
69875
  });
69707
- const limit = args.limit || 30;
69708
- scored.sort((a, b) => b.effective_score - a.effective_score);
69709
- const top = scored.slice(0, limit);
69710
- for (const m of top) {
69711
- touchMemory(m.id);
69712
- }
69713
- const lines = top.map((m) => `[${m.scope}/${m.category}] ${m.key}: ${m.value} (score: ${m.effective_score}, raw: ${m.importance}${m.pinned ? ", pinned" : ""}${m.flag ? `, flag: ${m.flag}` : ""})`);
69714
- return { content: [{ type: "text", text: lines.join(`
69715
- `) }] };
69876
+ scored.sort((a, b) => b.effective_score - a.effective_score || b.importance - a.importance || new Date(b.updated_at).getTime() - new Date(a.updated_at).getTime() || b.id.localeCompare(a.id));
69877
+ const page = scored.slice(offset, offset + limit + 1);
69878
+ const hasMore = page.length > limit;
69879
+ const visible = hasMore ? page.slice(0, limit) : page;
69880
+ const items = detail === "full" ? visible : visible.map((memory) => ({
69881
+ id: memory.id,
69882
+ key: memory.key,
69883
+ preview: compactText(memory.summary || memory.value, 240),
69884
+ scope: memory.scope,
69885
+ category: memory.category,
69886
+ importance: memory.importance,
69887
+ effective_score: memory.effective_score,
69888
+ pinned: memory.pinned,
69889
+ ...memory.flag ? { flag: memory.flag } : {},
69890
+ ...memory.activation_matched ? { activation_matched: true } : {},
69891
+ updated_at: memory.updated_at
69892
+ }));
69893
+ const output = boundedMcpOutput({
69894
+ collection: "memories",
69895
+ receipt: "mementos.context.page.v1",
69896
+ items,
69897
+ offset,
69898
+ limit,
69899
+ sourceHasMore: hasMore,
69900
+ detail,
69901
+ maxBytes: mcpMaxBytes(args.max_bytes, detail),
69902
+ metadata: {
69903
+ total_ranked: scored.length,
69904
+ ranking: {
69905
+ order: "effective_score_desc,importance_desc,updated_at_desc,id_desc",
69906
+ decay_halflife_days: halflifeDays,
69907
+ decay_enabled: !args.no_decay,
69908
+ activation_boost: 3,
69909
+ flagged_score_floor: 11
69910
+ }
69911
+ },
69912
+ nextArguments: {
69913
+ ...args.scope ? { scope: args.scope } : {},
69914
+ ...args.agent_id ? { agent_id: args.agent_id } : {},
69915
+ ...args.project_id ? { project_id: args.project_id } : {}
69916
+ }
69917
+ });
69918
+ for (const memory of visible.slice(0, output.selectedCount))
69919
+ touchMemory(memory.id);
69920
+ return { content: [{ type: "text", text: output.text }] };
69716
69921
  } catch (e) {
69717
69922
  return { content: [{ type: "text", text: formatError(e) }], isError: true };
69718
69923
  }
@@ -1,12 +1,10 @@
1
1
  /**
2
2
  * Memory broadcast utility — notifies active agents when shared memories are saved.
3
- * Uses the conversations MCP service if available.
4
- * Non-blocking: failures are silently ignored.
3
+ * Conversations authority and credentials resolve through @hasna/contracts.
4
+ * Non-blocking: notification failures never fail the memory write.
5
5
  */
6
- import type { Memory } from '../types/index.js';
7
- /**
8
- * Broadcast a newly saved shared memory to all active agents on the project
9
- * via the conversations MCP send_message endpoint.
10
- */
11
- export declare function broadcastSharedMemory(memory: Memory, savingAgentId: string): Promise<void>;
6
+ import type { Memory } from "../types/index.js";
7
+ import { type ConversationsTransportOptions } from "../lib/conversations-transport.js";
8
+ /** Broadcast a newly saved shared memory to the other active project agents. */
9
+ export declare function broadcastSharedMemory(memory: Memory, savingAgentId: string, transport?: ConversationsTransportOptions): Promise<void>;
12
10
  //# sourceMappingURL=memory-broadcast.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"memory-broadcast.d.ts","sourceRoot":"","sources":["../../src/mcp/memory-broadcast.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAIhD;;;GAGG;AACH,wBAAsB,qBAAqB,CAAC,MAAM,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA4BhG"}
1
+ {"version":3,"file":"memory-broadcast.d.ts","sourceRoot":"","sources":["../../src/mcp/memory-broadcast.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAEL,KAAK,6BAA6B,EACnC,MAAM,mCAAmC,CAAC;AAE3C,gFAAgF;AAChF,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,MAAM,EACd,aAAa,EAAE,MAAM,EACrB,SAAS,GAAE,6BAAkC,GAC5C,OAAO,CAAC,IAAI,CAAC,CA8Bf"}