@pensar/apex 2.6.1 → 2.7.0-canary.6d9d1e0d

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 (144) hide show
  1. package/build/agent-davhjnxc.js +13 -0
  2. package/build/{agent-yby11zaq.js → agent-gk4vmr2h.js} +6 -6
  3. package/build/agent-mqqd38dq.js +13 -0
  4. package/build/{agent-runs-g37vkrjr.js → agent-runs-12pmj6vc.js} +16 -16
  5. package/build/{agent-ayreaj6z.js → agent-xtn1masf.js} +5 -5
  6. package/build/{apps-xn6km9yn.js → apps-yjyvn8vk.js} +13 -13
  7. package/build/{auth-d105wc64.js → auth-xzp4f1bh.js} +13 -13
  8. package/build/{blackboxAgent-s56sgx9b.js → blackboxAgent-ettjqj45.js} +6 -6
  9. package/build/{blackboxPentest-ps78v3w7.js → blackboxPentest-kf0pbhp5.js} +11 -11
  10. package/build/{cli-c05y640n.js → cli-0cwdrrq7.js} +1 -1
  11. package/build/{cli-fy6vh2a7.js → cli-0ry9v2cb.js} +1 -1
  12. package/build/{cli-7eea4e2y.js → cli-4mar7kvr.js} +6 -6
  13. package/build/{cli-k2nwxta0.js → cli-5gaxk6hd.js} +2 -2
  14. package/build/{cli-d37a42dc.js → cli-8hxrn15h.js} +8 -8
  15. package/build/{cli-az3xq70v.js → cli-arxx5hbs.js} +1 -1
  16. package/build/{cli-n2en7j5b.js → cli-ge8q6nxr.js} +1 -1
  17. package/build/{cli-1ve18npr.js → cli-gr4az5sf.js} +1 -1
  18. package/build/{cli-709rvekb.js → cli-h5wkaqwe.js} +1 -1
  19. package/build/{cli-65g6rcat.js → cli-hprqe9j2.js} +1 -1
  20. package/build/{cli-kb21qhvm.js → cli-m5ryqeav.js} +2 -2
  21. package/build/{cli-xtc2t6me.js → cli-metec8r8.js} +3 -3
  22. package/build/{cli-dpsw2fe6.js → cli-nj73990n.js} +353 -185
  23. package/build/{cli-ae8jqxqr.js → cli-pchzt5f7.js} +2 -2
  24. package/build/{cli-3sp6nd8d.js → cli-t3m0e9t2.js} +2 -2
  25. package/build/{cli-qgz1q9g5.js → cli-tfaqp0ty.js} +105 -36
  26. package/build/{cli-gx71jcke.js → cli-xpspbhqs.js} +1 -1
  27. package/build/{cli-ngq9dr8n.js → cli-zmeb85er.js} +3 -3
  28. package/build/cli.js +37 -37
  29. package/build/{config-zpqkd5cz.js → config-n03g5jad.js} +1 -1
  30. package/build/{fastStrike-mcjdvwez.js → fastStrike-9mys4r1b.js} +5 -5
  31. package/build/{fixes-kytx5dmj.js → fixes-tcd29w2t.js} +13 -13
  32. package/build/{index-je6hcsqr.js → index-3n19dh6s.js} +1 -1
  33. package/build/{index-wqetp6gq.js → index-42dnvkns.js} +5 -5
  34. package/build/{index-fv64eb32.js → index-6ptkm3fe.js} +2 -2
  35. package/build/{index-qv4cc65p.js → index-93br69mn.js} +13 -13
  36. package/build/{index-4vqdgb42.js → index-hh9bqe4y.js} +1236 -1195
  37. package/build/{index-dr3tg1w3.js → index-k1pvqjaq.js} +1 -1
  38. package/build/{index-r9yjkfrv.js → index-mmmx59w3.js} +4 -4
  39. package/build/{index-n65z6v7b.js → index-msb8ht2s.js} +6 -6
  40. package/build/{index-ns4m4s01.js → index-qtpprc5a.js} +4 -4
  41. package/build/{issues-eb3q9d3a.js → issues-d0e1tzda.js} +13 -13
  42. package/build/{knowledge-fqsvtx0f.js → knowledge-vgvrzey3.js} +13 -13
  43. package/build/lib/{chunk-1azdancq.js → chunk-0ec46jyd.js} +1 -1
  44. package/build/lib/{chunk-9yksqgzr.js → chunk-29bvrw6m.js} +11 -11
  45. package/build/lib/{chunk-z137x08b.js → chunk-3ac8kcj9.js} +5 -5
  46. package/build/lib/{chunk-bb0ekhkw.js → chunk-3g3ksmsv.js} +8 -8
  47. package/build/lib/{chunk-9tt2021m.js → chunk-45nvxhrc.js} +9 -9
  48. package/build/lib/{chunk-w66ce82a.js → chunk-66t5k77r.js} +1 -1
  49. package/build/lib/{chunk-r4j8ybn9.js → chunk-74ddy2e7.js} +37 -20
  50. package/build/lib/{chunk-4hwzqthr.js → chunk-851r7zp8.js} +1 -1
  51. package/build/lib/{chunk-1fy8azee.js → chunk-8m15nv6s.js} +1 -1
  52. package/build/lib/{chunk-7pwa92rn.js → chunk-9etzp9n2.js} +1 -1
  53. package/build/lib/{chunk-xhdpgpkm.js → chunk-acfewabw.js} +2 -2
  54. package/build/lib/{chunk-ba41jh41.js → chunk-bdbd8d8q.js} +297 -127
  55. package/build/lib/{chunk-z96pbjhd.js → chunk-c6gj39ew.js} +1 -1
  56. package/build/lib/{chunk-yrnrbjes.js → chunk-fytt5jz7.js} +1 -1
  57. package/build/lib/{chunk-3c00n0cy.js → chunk-gv1p302b.js} +3 -3
  58. package/build/lib/{chunk-1yct6cra.js → chunk-j1ftnbgr.js} +1 -1
  59. package/build/lib/{chunk-nhfax4gb.js → chunk-mepzy07f.js} +1 -1
  60. package/build/lib/{chunk-e8yy2k73.js → chunk-mqcwxrnh.js} +1 -1
  61. package/build/lib/{chunk-v3dk9j7k.js → chunk-n3kr09mb.js} +11 -11
  62. package/build/lib/{chunk-acgbyf5h.js → chunk-pd487tpr.js} +1 -1
  63. package/build/lib/{chunk-1jvwqnem.js → chunk-rchnq1ez.js} +3 -3
  64. package/build/lib/{chunk-2qsq76xq.js → chunk-wa0q3smh.js} +62 -10
  65. package/build/lib/{chunk-mg03jdmt.js → chunk-xw8d7pv7.js} +1 -1
  66. package/build/lib/{chunk-r2dkp4tb.js → chunk-y9m8yw7k.js} +1 -1
  67. package/build/lib/core/agents/offSecAgent/index.js +7 -7
  68. package/build/lib/core/agents/offSecAgent/subagentSpawner.js +5 -5
  69. package/build/lib/core/agents/offSecAgent/tools/index.js +4 -4
  70. package/build/lib/core/agents/offSecAgent/tools/listMemories.d.ts +11 -1
  71. package/build/lib/core/agents/specialized/attackSurface/blackboxAgent.js +9 -9
  72. package/build/lib/core/agents/specialized/attackSurface/schemas.js +9 -9
  73. package/build/lib/core/agents/specialized/authenticationAgent/index.js +10 -10
  74. package/build/lib/core/agents/specialized/codeAgent/agent.js +9 -9
  75. package/build/lib/core/agents/specialized/findingJudge/agent.js +8 -8
  76. package/build/lib/core/agents/specialized/findingJudge/index.js +3 -3
  77. package/build/lib/core/agents/specialized/patching/index.js +10 -10
  78. package/build/lib/core/agents/specialized/pentest/agent.js +9 -9
  79. package/build/lib/core/agents/specialized/whiteboxAttackSurface/index.js +9 -9
  80. package/build/lib/core/ai/ai.d.ts +23 -6
  81. package/build/lib/core/ai/ai.js +2 -2
  82. package/build/lib/core/ai/index.js +2 -2
  83. package/build/lib/core/ai/models/claude-capabilities.d.ts +3 -2
  84. package/build/lib/core/ai/models/index.d.ts +1 -1
  85. package/build/lib/core/ai/utils.d.ts +2 -0
  86. package/build/lib/core/ai/utils.js +4 -2
  87. package/build/lib/core/api/index.js +19 -19
  88. package/build/lib/core/api/targetedPentest.js +10 -10
  89. package/build/lib/core/config/index.js +1 -1
  90. package/build/lib/core/findings/registry.js +3 -3
  91. package/build/lib/core/session/index.js +2 -2
  92. package/build/lib/core/session/persistence.js +3 -3
  93. package/build/lib/core/workflows/pentest.js +15 -15
  94. package/build/lib/core/workflows/seams.js +4 -4
  95. package/build/lib/core/workflows/whiteboxAttackSurface.js +11 -11
  96. package/build/lib/index.js +22 -22
  97. package/build/{logs-x62b7hk1.js → logs-mvrvrkfa.js} +13 -13
  98. package/build/{native-rollout-evidence-c9yhfqcs.js → native-rollout-evidence-h2za8fyf.js} +3 -3
  99. package/build/{offesecAgent-5whnpeta.js → offesecAgent-r4hz607z.js} +4 -4
  100. package/build/pentest-52s2dn28.js +22 -0
  101. package/build/{pentests-e0f32aqx.js → pentests-yvt9wvxd.js} +13 -13
  102. package/build/recordedRunClient-6z78az3b.js +11 -0
  103. package/build/{targetedPentest-rcbs4nvp.js → targetedPentest-6aeg3nxd.js} +6 -6
  104. package/build/{targets-xj9g3q0z.js → targets-a38xnsgm.js} +13 -13
  105. package/build/threatModel-1gsb7ytk.js +21 -0
  106. package/build/{uninstall-7s784q4a.js → uninstall-kt485ay4.js} +1 -1
  107. package/build/{upload-4gvhwmvr.js → upload-ahw3are7.js} +4 -4
  108. package/build/{utils-cjrz64pc.js → utils-yqdepzr3.js} +3 -3
  109. package/package.json +1 -1
  110. package/src/core/agents/offSecAgent/offensiveSecurityAgent.test.ts +92 -5
  111. package/src/core/agents/offSecAgent/offensiveSecurityAgent.ts +49 -28
  112. package/src/core/agents/offSecAgent/tools/listMemories.test.ts +230 -0
  113. package/src/core/agents/offSecAgent/tools/listMemories.ts +73 -6
  114. package/src/core/ai/ai.operator-context.bench.test.ts +1408 -0
  115. package/src/core/ai/ai.recorded-context.test.ts +74 -0
  116. package/src/core/ai/ai.test.ts +54 -0
  117. package/src/core/ai/ai.ts +299 -80
  118. package/src/core/ai/compactionTelemetry.test.ts +2 -2
  119. package/src/core/ai/models/anthropic.ts +6 -0
  120. package/src/core/ai/models/bedrock.ts +18 -0
  121. package/src/core/ai/models/claude-capabilities.ts +29 -8
  122. package/src/core/ai/models/index.ts +4 -1
  123. package/src/core/ai/models/models.test.ts +23 -0
  124. package/src/core/ai/models/openrouter.ts +6 -0
  125. package/src/core/ai/providers/anthropic-models.test.ts +60 -1
  126. package/src/core/ai/providers/bedrock-models.test.ts +81 -0
  127. package/src/core/ai/providers/gateway-models.test.ts +16 -6
  128. package/src/core/ai/utils.native-rollout-evidence.test.ts +1 -0
  129. package/src/core/ai/utils.ts +189 -143
  130. package/src/core/observability/telemetry.test.ts +2 -3
  131. package/src/tui/components/chat/loading-indicator.tsx +20 -4
  132. package/src/tui/components/chat/message-list.tsx +1 -0
  133. package/src/tui/components/operator-dashboard/run-events.test.ts +3 -1
  134. package/src/tui/components/shared/__tests__/tool-renderer-lifecycle.tsx +18 -0
  135. package/src/tui/components/shared/action-label.test.ts +9 -0
  136. package/src/tui/components/shared/compacting-icon.tsx +18 -0
  137. package/src/tui/components/shared/index.ts +1 -0
  138. package/src/tui/components/shared/tool-registry.ts +1 -0
  139. package/src/tui/components/shared/tool-renderer.tsx +21 -7
  140. package/build/agent-6w7egcf1.js +0 -13
  141. package/build/agent-9zx75feh.js +0 -13
  142. package/build/pentest-e61hc38s.js +0 -22
  143. package/build/recordedRunClient-dp032c32.js +0 -11
  144. package/build/threatModel-jkxfa40s.js +0 -21
@@ -0,0 +1,230 @@
1
+ import { describe, expect, it, vi } from "vitest";
2
+ import {
3
+ type Memory,
4
+ type MemoryBackend,
5
+ type MemorySummary,
6
+ withMemoryBackend,
7
+ } from "../../../memory";
8
+ import { type GetMemoryResult, getMemory } from "./getMemory";
9
+ import { type ListMemoriesResult, listMemories } from "./listMemories";
10
+ import type { ToolContext } from "./types";
11
+
12
+ const ctx = { session: { id: "synthetic" } } as ToolContext;
13
+ const options = { toolCallId: "synthetic-call", messages: [] };
14
+ const list = listMemories(ctx).execute;
15
+ const get = getMemory(ctx).execute;
16
+ if (!list || !get) throw new Error("Memory tools must be executable");
17
+
18
+ describe("list_memories pagination", () => {
19
+ it("continues from the first unreturned summary when the character budget fills", async () => {
20
+ const summaries: MemorySummary[] = Array.from({ length: 30 }, (_, i) => ({
21
+ id: `large-${i}`,
22
+ category: "general",
23
+ title: "t".repeat(300),
24
+ tags: Array.from({ length: 8 }, () => "tag".repeat(40)),
25
+ createdAt: new Date(Date.UTC(2026, 0, 1) - i * 1_000).toISOString(),
26
+ }));
27
+ const backend: MemoryBackend = {
28
+ add: vi.fn(async () => {
29
+ throw new Error("unexpected add");
30
+ }),
31
+ list: vi.fn(async () => summaries),
32
+ get: vi.fn(async () => null),
33
+ };
34
+ await withMemoryBackend(backend, async () => {
35
+ const first = (await list(
36
+ { toolCallDescription: "List large synthetic memories" },
37
+ options,
38
+ )) as ListMemoriesResult;
39
+ expect(first.memories.length).toBeLessThan(20);
40
+ expect(JSON.stringify(first).length).toBeLessThan(12_500);
41
+ expect(first.nextCursor).toEqual({
42
+ id: first.memories.at(-1)?.id,
43
+ category: first.memories.at(-1)?.category,
44
+ createdAt: first.memories.at(-1)?.createdAt,
45
+ });
46
+ const second = (await list(
47
+ {
48
+ cursor: first.nextCursor ?? undefined,
49
+ toolCallDescription: "Continue large synthetic memories",
50
+ },
51
+ options,
52
+ )) as ListMemoriesResult;
53
+ expect(second.memories[0]?.id).toBe(summaries[first.memories.length]?.id);
54
+ });
55
+ });
56
+
57
+ it("continues past the last returned memory when a newer one is added", async () => {
58
+ const original: MemorySummary[] = Array.from({ length: 30 }, (_, i) => ({
59
+ id: `memory-${i}`,
60
+ category: "general",
61
+ title: `Memory ${i}`,
62
+ tags: [],
63
+ createdAt: new Date(Date.UTC(2026, 0, 1) - i * 1_000).toISOString(),
64
+ }));
65
+ let summaries = original;
66
+ const backend: MemoryBackend = {
67
+ add: vi.fn(async () => {
68
+ throw new Error("unexpected add");
69
+ }),
70
+ list: vi.fn(async () => summaries),
71
+ get: vi.fn(async () => null),
72
+ };
73
+ await withMemoryBackend(backend, async () => {
74
+ const first = (await list(
75
+ { toolCallDescription: "List first memory page" },
76
+ options,
77
+ )) as ListMemoriesResult;
78
+ summaries = [
79
+ {
80
+ ...original[0],
81
+ id: "memory-new",
82
+ createdAt: new Date(Date.UTC(2026, 0, 2)).toISOString(),
83
+ },
84
+ ...original,
85
+ ];
86
+ const second = (await list(
87
+ {
88
+ cursor: first.nextCursor ?? undefined,
89
+ toolCallDescription: "Continue after an insertion",
90
+ },
91
+ options,
92
+ )) as ListMemoriesResult;
93
+
94
+ expect(first.memories.map((memory) => memory.id)).toEqual(
95
+ original.slice(0, 20).map((memory) => memory.id),
96
+ );
97
+ expect(second.memories.map((memory) => memory.id)).toEqual(
98
+ original.slice(20).map((memory) => memory.id),
99
+ );
100
+ expect(second.count).toBe(31);
101
+ expect(second.nextCursor).toBeNull();
102
+ });
103
+ });
104
+
105
+ it("uses stable tie ordering when the cursor memory is deleted", async () => {
106
+ const original: MemorySummary[] = Array.from({ length: 25 }, (_, i) => ({
107
+ id: `memory-${i.toString().padStart(2, "0")}`,
108
+ category: "general",
109
+ title: `Memory ${i}`,
110
+ tags: [],
111
+ createdAt: "2026-01-01T00:00:00.000Z",
112
+ }));
113
+ let summaries = [...original].reverse();
114
+ const backend: MemoryBackend = {
115
+ add: vi.fn(async () => {
116
+ throw new Error("unexpected add");
117
+ }),
118
+ list: vi.fn(async () => summaries),
119
+ get: vi.fn(async () => null),
120
+ };
121
+ await withMemoryBackend(backend, async () => {
122
+ const first = (await list(
123
+ { toolCallDescription: "List tied memories" },
124
+ options,
125
+ )) as ListMemoriesResult;
126
+ expect(first.memories.map((memory) => memory.id)).toEqual(
127
+ original.slice(0, 20).map((memory) => memory.id),
128
+ );
129
+ summaries = summaries.filter(
130
+ (memory) => memory.id !== first.nextCursor?.id,
131
+ );
132
+ const second = (await list(
133
+ {
134
+ cursor: first.nextCursor ?? undefined,
135
+ toolCallDescription: "Continue after anchor deletion",
136
+ },
137
+ options,
138
+ )) as ListMemoriesResult;
139
+ expect(second.memories.map((memory) => memory.id)).toEqual(
140
+ original.slice(20).map((memory) => memory.id),
141
+ );
142
+ expect(second.nextCursor).toBeNull();
143
+ });
144
+ });
145
+
146
+ it("bounds each result while keeping every older memory discoverable", async () => {
147
+ const summaries: MemorySummary[] = Array.from({ length: 200 }, (_, i) => ({
148
+ id: `memory-${i}`,
149
+ category: "general",
150
+ title: `Memory ${i} ${"x".repeat(480)}`,
151
+ tags: ["synthetic", "history"],
152
+ createdAt: new Date(Date.UTC(2026, 0, 1) - i * 1_000).toISOString(),
153
+ }));
154
+ const backend: MemoryBackend = {
155
+ add: vi.fn(async () => {
156
+ throw new Error("unexpected add");
157
+ }),
158
+ list: vi.fn(async () =>
159
+ summaries.map((summary, index) =>
160
+ index === 0 ? { ...summary, content: "full-body-marker" } : summary,
161
+ ),
162
+ ),
163
+ get: vi.fn(async (_category, id) => {
164
+ const summary = summaries.find((item) => item.id === id);
165
+ return summary
166
+ ? ({
167
+ ...summary,
168
+ content: `Full ${id}`,
169
+ updatedAt: summary.createdAt,
170
+ } satisfies Memory)
171
+ : null;
172
+ }),
173
+ };
174
+
175
+ await withMemoryBackend(backend, async () => {
176
+ const allIds: string[] = [];
177
+ let cursor: ListMemoriesResult["nextCursor"] = null;
178
+ let firstResultChars = 0;
179
+ let pages = 0;
180
+ for (;;) {
181
+ const result = (await list(
182
+ {
183
+ cursor: cursor ?? undefined,
184
+ toolCallDescription: "List synthetic memories",
185
+ },
186
+ options,
187
+ )) as ListMemoriesResult;
188
+ expect(result.success).toBe(true);
189
+ expect(result.count).toBe(200);
190
+ expect(result.memories.length).toBeGreaterThan(0);
191
+ expect(result.memories.length).toBeLessThanOrEqual(20);
192
+ expect(JSON.stringify(result).length).toBeLessThan(12_500);
193
+ if (pages === 0) {
194
+ firstResultChars = JSON.stringify(result).length;
195
+ expect(JSON.stringify(result)).not.toContain("full-body-marker");
196
+ expect(result.memories[0]?.truncated).toBe(true);
197
+ expect(result.memories[0]?.title.length).toBe(160);
198
+ }
199
+ allIds.push(...result.memories.map((memory) => memory.id));
200
+ pages++;
201
+ if (result.nextCursor === null) break;
202
+ expect(result.nextCursor.id).toBe(result.memories.at(-1)?.id);
203
+ cursor = result.nextCursor;
204
+ }
205
+
206
+ expect(allIds).toEqual(summaries.map((memory) => memory.id));
207
+ const oldest = (await get(
208
+ {
209
+ category: "general",
210
+ id: allIds.at(-1) ?? "",
211
+ toolCallDescription: "Read oldest synthetic memory",
212
+ },
213
+ options,
214
+ )) as GetMemoryResult;
215
+ expect(oldest.memory?.content).toBe("Full memory-199");
216
+ if (process.env.APEX_CONTEXT_BENCH === "1") {
217
+ const previousChars = JSON.stringify({
218
+ success: true,
219
+ error: "",
220
+ memories: summaries,
221
+ count: summaries.length,
222
+ }).length;
223
+ console.log(
224
+ "MEMORY_LIST_BENCH",
225
+ JSON.stringify({ previousChars, firstResultChars, pages }),
226
+ );
227
+ }
228
+ });
229
+ });
230
+ });
@@ -7,6 +7,9 @@ import {
7
7
  } from "../../../memory";
8
8
  import type { ToolContext } from "./types";
9
9
 
10
+ const PAGE_SIZE = 20;
11
+ const PAGE_CHAR_BUDGET = 12_000;
12
+
10
13
  const listMemoriesInputSchema = z.object({
11
14
  category: z
12
15
  .enum(MEMORY_CATEGORIES)
@@ -19,6 +22,14 @@ const listMemoriesInputSchema = z.object({
19
22
  .string()
20
23
  .optional()
21
24
  .describe("Optional tag to filter memories by (exact match)"),
25
+ cursor: z
26
+ .object({
27
+ createdAt: z.string(),
28
+ category: z.enum(MEMORY_CATEGORIES),
29
+ id: z.string(),
30
+ })
31
+ .optional()
32
+ .describe("Pass the previous nextCursor to continue a listing"),
22
33
  toolCallDescription: z
23
34
  .string()
24
35
  .describe(
@@ -27,24 +38,53 @@ const listMemoriesInputSchema = z.object({
27
38
  });
28
39
 
29
40
  type ListMemoriesInput = z.infer<typeof listMemoriesInputSchema>;
41
+ type MemoryCursor = Pick<MemorySummary, "createdAt" | "category" | "id">;
30
42
 
31
43
  export type ListMemoriesResult = {
32
44
  success: boolean;
33
45
  error: string;
34
- memories: MemorySummary[];
46
+ memories: Array<MemorySummary & { truncated?: true }>;
35
47
  count: number;
48
+ nextCursor: MemoryCursor | null;
36
49
  };
37
50
 
51
+ function compareMemories(a: MemoryCursor, b: MemoryCursor): number {
52
+ const byCreatedAt = Date.parse(b.createdAt) - Date.parse(a.createdAt);
53
+ if (byCreatedAt > 0 || byCreatedAt < 0) return byCreatedAt;
54
+ return a.category.localeCompare(b.category) || a.id.localeCompare(b.id);
55
+ }
56
+
57
+ function previewSummary(memory: MemorySummary) {
58
+ const title = memory.title.slice(0, 160);
59
+ const tags = memory.tags.slice(0, 8).map((tag) => tag.slice(0, 48));
60
+ const truncated =
61
+ title.length < memory.title.length ||
62
+ tags.length < memory.tags.length ||
63
+ tags.some(
64
+ (tag, index) => tag.length < (memory.tags[index]?.length ?? tag.length),
65
+ );
66
+ return {
67
+ id: memory.id,
68
+ category: memory.category,
69
+ title,
70
+ tags,
71
+ createdAt: memory.createdAt,
72
+ ...(truncated ? { truncated: true as const } : {}),
73
+ };
74
+ }
75
+
38
76
  export function listMemories(ctx: ToolContext) {
39
77
  return tool({
40
78
  description: `List saved memories, optionally filtered by category and/or tag.
41
79
 
42
- Returns lightweight summaries (id, category, title, tags, createdAt) sorted by
43
- most recent first. Use the returned category + id with get_memory to retrieve
44
- full content.`,
80
+ Returns up to 20 lightweight summaries (id, category, title, tags, createdAt)
81
+ sorted by most recent first. Use nextCursor to continue until it is null;
82
+ count is the current total matching memories. A fresh listing includes newer
83
+ memories added ahead of a cursor. Titles and tags may be abbreviated in the
84
+ listing. Use category + id with get_memory to retrieve full content.`,
45
85
  inputSchema: listMemoriesInputSchema,
46
86
  execute: async (
47
- { category, tag },
87
+ { category, tag, cursor },
48
88
  { toolCallId },
49
89
  ): Promise<ListMemoriesResult> => {
50
90
  try {
@@ -52,11 +92,37 @@ full content.`,
52
92
  { category, tag },
53
93
  { sessionId: ctx.session.id, toolCallId },
54
94
  );
95
+ const ordered = [...memories].sort(compareMemories);
96
+ let start = 0;
97
+ if (cursor) {
98
+ start = ordered.findIndex(
99
+ (memory) => compareMemories(memory, cursor) > 0,
100
+ );
101
+ if (start < 0) start = ordered.length;
102
+ }
103
+ const page: ListMemoriesResult["memories"] = [];
104
+ let pageChars = 0;
105
+ for (const memory of ordered.slice(start, start + PAGE_SIZE)) {
106
+ const summary = previewSummary(memory);
107
+ const chars = JSON.stringify(summary).length + 1;
108
+ if (page.length > 0 && pageChars + chars > PAGE_CHAR_BUDGET) break;
109
+ page.push(summary);
110
+ pageChars += chars;
111
+ }
112
+ const last = page.at(-1);
55
113
  return {
56
114
  success: true,
57
115
  error: "",
58
- memories,
116
+ memories: page,
59
117
  count: memories.length,
118
+ nextCursor:
119
+ start + page.length < ordered.length && last
120
+ ? {
121
+ createdAt: last.createdAt,
122
+ category: last.category,
123
+ id: last.id,
124
+ }
125
+ : null,
60
126
  };
61
127
  } catch (err: unknown) {
62
128
  return {
@@ -64,6 +130,7 @@ full content.`,
64
130
  error: err instanceof Error ? err.message : String(err),
65
131
  memories: [],
66
132
  count: 0,
133
+ nextCursor: null,
67
134
  };
68
135
  }
69
136
  },