@duetso/agent 0.2.6 → 0.3.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 (122) hide show
  1. package/README.md +15 -12
  2. package/dist/package.json +8 -3
  3. package/dist/src/cli/help.d.ts.map +1 -1
  4. package/dist/src/cli/help.js +41 -27
  5. package/dist/src/cli/help.js.map +1 -1
  6. package/dist/src/cli/memory-add.d.ts +18 -13
  7. package/dist/src/cli/memory-add.d.ts.map +1 -1
  8. package/dist/src/cli/memory-add.js +91 -32
  9. package/dist/src/cli/memory-add.js.map +1 -1
  10. package/dist/src/cli/memory-recall.d.ts.map +1 -1
  11. package/dist/src/cli/memory-recall.js +3 -0
  12. package/dist/src/cli/memory-recall.js.map +1 -1
  13. package/dist/src/cli/memory-sources.d.ts +20 -0
  14. package/dist/src/cli/memory-sources.d.ts.map +1 -0
  15. package/dist/src/cli/memory-sources.js +40 -0
  16. package/dist/src/cli/memory-sources.js.map +1 -0
  17. package/dist/src/cli/route.d.ts +2 -0
  18. package/dist/src/cli/route.d.ts.map +1 -1
  19. package/dist/src/cli/route.js +8 -8
  20. package/dist/src/cli/route.js.map +1 -1
  21. package/dist/src/cli/rpc.d.ts +3 -2
  22. package/dist/src/cli/rpc.d.ts.map +1 -1
  23. package/dist/src/cli/rpc.js +3 -2
  24. package/dist/src/cli/rpc.js.map +1 -1
  25. package/dist/src/cli/run.d.ts +1 -0
  26. package/dist/src/cli/run.d.ts.map +1 -1
  27. package/dist/src/cli/run.js +1 -0
  28. package/dist/src/cli/run.js.map +1 -1
  29. package/dist/src/cli/train.d.ts +31 -7
  30. package/dist/src/cli/train.d.ts.map +1 -1
  31. package/dist/src/cli/train.js +233 -79
  32. package/dist/src/cli/train.js.map +1 -1
  33. package/dist/src/memory/context-pack.d.ts +5 -0
  34. package/dist/src/memory/context-pack.d.ts.map +1 -1
  35. package/dist/src/memory/context-pack.js +6 -0
  36. package/dist/src/memory/context-pack.js.map +1 -1
  37. package/dist/src/memory/id.d.ts +3 -0
  38. package/dist/src/memory/id.d.ts.map +1 -0
  39. package/dist/src/memory/id.js +6 -0
  40. package/dist/src/memory/id.js.map +1 -0
  41. package/dist/src/memory/observational-prompts.d.ts +2 -0
  42. package/dist/src/memory/observational-prompts.d.ts.map +1 -1
  43. package/dist/src/memory/observational-prompts.js +4 -2
  44. package/dist/src/memory/observational-prompts.js.map +1 -1
  45. package/dist/src/memory/observational.d.ts +22 -0
  46. package/dist/src/memory/observational.d.ts.map +1 -1
  47. package/dist/src/memory/observational.js +65 -22
  48. package/dist/src/memory/observational.js.map +1 -1
  49. package/dist/src/memory/paths.d.ts +9 -0
  50. package/dist/src/memory/paths.d.ts.map +1 -0
  51. package/dist/src/memory/paths.js +11 -0
  52. package/dist/src/memory/paths.js.map +1 -0
  53. package/dist/src/memory/storage.d.ts.map +1 -1
  54. package/dist/src/memory/storage.js +1 -4
  55. package/dist/src/memory/storage.js.map +1 -1
  56. package/dist/src/memory/store/discovery.d.ts +7 -0
  57. package/dist/src/memory/store/discovery.d.ts.map +1 -0
  58. package/dist/src/memory/store/discovery.js +34 -0
  59. package/dist/src/memory/store/discovery.js.map +1 -0
  60. package/dist/src/memory/store/file.d.ts +52 -0
  61. package/dist/src/memory/store/file.d.ts.map +1 -0
  62. package/dist/src/memory/store/file.js +287 -0
  63. package/dist/src/memory/store/file.js.map +1 -0
  64. package/dist/src/memory/store/index.d.ts +5 -0
  65. package/dist/src/memory/store/index.d.ts.map +1 -0
  66. package/dist/src/memory/store/index.js +5 -0
  67. package/dist/src/memory/store/index.js.map +1 -0
  68. package/dist/src/memory/store/pack.d.ts +25 -0
  69. package/dist/src/memory/store/pack.d.ts.map +1 -0
  70. package/dist/src/memory/store/pack.js +71 -0
  71. package/dist/src/memory/store/pack.js.map +1 -0
  72. package/dist/src/memory/store/sources.d.ts +55 -0
  73. package/dist/src/memory/store/sources.d.ts.map +1 -0
  74. package/dist/src/memory/store/sources.js +85 -0
  75. package/dist/src/memory/store/sources.js.map +1 -0
  76. package/dist/src/memory/store/store.d.ts +46 -0
  77. package/dist/src/memory/store/store.d.ts.map +1 -0
  78. package/dist/src/memory/store/store.js +171 -0
  79. package/dist/src/memory/store/store.js.map +1 -0
  80. package/dist/src/memory/store.d.ts +19 -16
  81. package/dist/src/memory/store.d.ts.map +1 -1
  82. package/dist/src/memory/store.js +14 -11
  83. package/dist/src/memory/store.js.map +1 -1
  84. package/dist/src/memory-store.js +554 -0
  85. package/dist/src/model-routing/advisor-context.d.ts +31 -5
  86. package/dist/src/model-routing/advisor-context.d.ts.map +1 -1
  87. package/dist/src/model-routing/advisor-context.js +87 -10
  88. package/dist/src/model-routing/advisor-context.js.map +1 -1
  89. package/dist/src/model-routing/advisor-lifecycle.d.ts +14 -5
  90. package/dist/src/model-routing/advisor-lifecycle.d.ts.map +1 -1
  91. package/dist/src/model-routing/advisor-lifecycle.js +24 -7
  92. package/dist/src/model-routing/advisor-lifecycle.js.map +1 -1
  93. package/dist/src/model-routing/prompts.d.ts.map +1 -1
  94. package/dist/src/model-routing/prompts.js +18 -10
  95. package/dist/src/model-routing/prompts.js.map +1 -1
  96. package/dist/src/session/session-manager.d.ts +1 -4
  97. package/dist/src/session/session-manager.d.ts.map +1 -1
  98. package/dist/src/session/session-manager.js +2 -5
  99. package/dist/src/session/session-manager.js.map +1 -1
  100. package/dist/src/session/session.d.ts +2 -2
  101. package/dist/src/session/session.js +2 -2
  102. package/dist/src/train/archive.d.ts.map +1 -1
  103. package/dist/src/train/archive.js +5 -0
  104. package/dist/src/train/archive.js.map +1 -1
  105. package/dist/src/train/types.d.ts +8 -7
  106. package/dist/src/train/types.d.ts.map +1 -1
  107. package/dist/src/turn-runner/tools.d.ts +3 -3
  108. package/dist/src/turn-runner/tools.d.ts.map +1 -1
  109. package/dist/src/turn-runner/tools.js +1 -1
  110. package/dist/src/turn-runner/tools.js.map +1 -1
  111. package/dist/src/turn-runner/turn-runner.d.ts +15 -2
  112. package/dist/src/turn-runner/turn-runner.d.ts.map +1 -1
  113. package/dist/src/turn-runner/turn-runner.js +88 -14
  114. package/dist/src/turn-runner/turn-runner.js.map +1 -1
  115. package/dist/src/turn-runner/wire-shaping.d.ts +3 -1
  116. package/dist/src/turn-runner/wire-shaping.d.ts.map +1 -1
  117. package/dist/src/turn-runner/wire-shaping.js +5 -1
  118. package/dist/src/turn-runner/wire-shaping.js.map +1 -1
  119. package/dist/src/types/config.d.ts +7 -0
  120. package/dist/src/types/config.d.ts.map +1 -1
  121. package/dist/src/types/protocol.d.ts +2 -2
  122. package/package.json +8 -3
@@ -0,0 +1,554 @@
1
+ // src/memory/store/file.ts
2
+ import { Buffer } from "node:buffer";
3
+ import { basename } from "node:path";
4
+ var MAX_TRAIN_RECORD_CONTENT_BYTES = 10 * 1024 * 1024;
5
+ var SLUG_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
6
+ var ID_SEGMENT_PATTERN = /^[A-Za-z0-9_-]+$/;
7
+ var KEY_PATTERN = /^[A-Za-z][A-Za-z0-9]*$/;
8
+ var NUMBER_PATTERN = /^-?(?:0|[1-9]\d*)(?:\.\d+)?$/;
9
+ var KNOWN_KEYS = [
10
+ "version",
11
+ "id",
12
+ "kind",
13
+ "createdAt",
14
+ "headline",
15
+ "model",
16
+ "fileCount",
17
+ "archiveId",
18
+ "priority",
19
+ "source",
20
+ "tags"
21
+ ];
22
+ var KNOWN_KEY_ORDER = new Map(KNOWN_KEYS.map((key, index) => [key, index]));
23
+ var parsedLineEndings = new WeakMap;
24
+ function parseMemoryFile(text) {
25
+ const lineEnding = text.startsWith(`---\r
26
+ `) ? `\r
27
+ ` : text.startsWith(`---
28
+ `) ? `
29
+ ` : undefined;
30
+ if (!lineEnding)
31
+ throw new Error("Memory file must start with a frontmatter delimiter");
32
+ const closingDelimiter = `${lineEnding}---${lineEnding}`;
33
+ const closingIndex = text.indexOf(closingDelimiter, 3 + lineEnding.length);
34
+ if (closingIndex === -1)
35
+ throw new Error("Memory file is missing its closing delimiter");
36
+ const header = text.slice(3 + lineEnding.length, closingIndex);
37
+ const content = text.slice(closingIndex + closingDelimiter.length);
38
+ assertContent(content);
39
+ const values = new Map;
40
+ let lastKnownIndex = -1;
41
+ let sawUnknown = false;
42
+ for (const line of header.split(lineEnding)) {
43
+ const separator = line.indexOf(": ");
44
+ if (separator <= 0)
45
+ throw new Error(`Invalid frontmatter line: ${line}`);
46
+ const key = line.slice(0, separator);
47
+ const rawValue = line.slice(separator + 2);
48
+ if (!KEY_PATTERN.test(key))
49
+ throw new Error(`Invalid frontmatter key: ${key}`);
50
+ if (values.has(key))
51
+ throw new Error(`Duplicate frontmatter key: ${key}`);
52
+ if (key === "sourceFolder") {
53
+ throw new Error("sourceFolder is private archive metadata and cannot enter a memory file");
54
+ }
55
+ const knownIndex = KNOWN_KEY_ORDER.get(key);
56
+ if (knownIndex === undefined) {
57
+ sawUnknown = true;
58
+ } else {
59
+ if (sawUnknown || knownIndex <= lastKnownIndex) {
60
+ throw new Error(`Frontmatter key is out of canonical order: ${key}`);
61
+ }
62
+ lastKnownIndex = knownIndex;
63
+ }
64
+ values.set(key, parseScalar(rawValue));
65
+ }
66
+ const version = requireNumber(values, "version");
67
+ if (version !== 1)
68
+ throw new Error(`Unsupported memory file version: ${version}`);
69
+ const kind = requireString(values, "kind");
70
+ if (kind !== "train" && kind !== "note")
71
+ throw new Error(`Invalid memory kind: ${kind}`);
72
+ const createdAt = requireNumber(values, "createdAt");
73
+ if (!Number.isSafeInteger(createdAt) || createdAt < 0) {
74
+ throw new Error("createdAt must be a non-negative integer");
75
+ }
76
+ const fileCount = optionalNumber(values, "fileCount");
77
+ if (fileCount !== undefined && (!Number.isSafeInteger(fileCount) || fileCount < 0)) {
78
+ throw new Error("fileCount must be a non-negative integer");
79
+ }
80
+ const record = {
81
+ version: 1,
82
+ id: requireIdSegment(values, "id"),
83
+ kind,
84
+ createdAt,
85
+ ...optionalStringProperty(values, "headline"),
86
+ ...optionalStringProperty(values, "model"),
87
+ ...fileCount === undefined ? {} : { fileCount },
88
+ ...optionalIdSegmentProperty(values, "archiveId"),
89
+ ...optionalStringProperty(values, "priority"),
90
+ ...optionalStringProperty(values, "source"),
91
+ ...optionalStringArrayProperty(values, "tags"),
92
+ ...extraProperties(values),
93
+ content
94
+ };
95
+ parsedLineEndings.set(record, lineEnding);
96
+ return record;
97
+ }
98
+ function serializeMemoryFile(record) {
99
+ validateRecord(record);
100
+ const lineEnding = parsedLineEndings.get(record) ?? `
101
+ `;
102
+ const fields = [
103
+ ["version", record.version],
104
+ ["id", record.id],
105
+ ["kind", record.kind],
106
+ ["createdAt", record.createdAt]
107
+ ];
108
+ appendOptional(fields, "headline", record.headline);
109
+ appendOptional(fields, "model", record.model);
110
+ appendOptional(fields, "fileCount", record.fileCount);
111
+ appendOptional(fields, "archiveId", record.archiveId);
112
+ appendOptional(fields, "priority", record.priority);
113
+ appendOptional(fields, "source", record.source);
114
+ appendOptional(fields, "tags", record.tags);
115
+ for (const [key, value] of Object.entries(record.extra ?? {})) {
116
+ if (!KEY_PATTERN.test(key) || KNOWN_KEY_ORDER.has(key) || key === "sourceFolder") {
117
+ throw new Error(`Invalid extra frontmatter key: ${key}`);
118
+ }
119
+ fields.push([key, value]);
120
+ }
121
+ const header = fields.map(([key, value]) => `${key}: ${serializeScalar(value)}`).join(lineEnding);
122
+ return `---${lineEnding}${header}${lineEnding}---${lineEnding}${record.content}`;
123
+ }
124
+ function slugFromFilename(name) {
125
+ if (basename(name) !== name || !name.endsWith(".md")) {
126
+ throw new Error(`Memory filename must be a bare .md filename: ${name}`);
127
+ }
128
+ const slug = name.slice(0, -3);
129
+ if (!SLUG_PATTERN.test(slug))
130
+ throw new Error(`Unsafe memory slug: ${slug}`);
131
+ return slug;
132
+ }
133
+ function parseScalar(rawValue) {
134
+ if (NUMBER_PATTERN.test(rawValue)) {
135
+ const value = Number(rawValue);
136
+ if (Number.isFinite(value) && String(value) === rawValue)
137
+ return value;
138
+ }
139
+ try {
140
+ const value = JSON.parse(rawValue);
141
+ if (typeof value === "string" && JSON.stringify(value) === rawValue)
142
+ return value;
143
+ if (Array.isArray(value) && value.every((item) => typeof item === "string") && `[${value.map((item) => JSON.stringify(item)).join(", ")}]` === rawValue) {
144
+ return value;
145
+ }
146
+ } catch {}
147
+ throw new Error(`Unsupported frontmatter scalar: ${rawValue}`);
148
+ }
149
+ function serializeScalar(value) {
150
+ if (typeof value === "number") {
151
+ if (!Number.isFinite(value) || /[eE]/.test(String(value))) {
152
+ throw new Error(`Frontmatter numbers must be finite decimals: ${value}`);
153
+ }
154
+ return String(value);
155
+ }
156
+ if (typeof value === "string")
157
+ return JSON.stringify(value);
158
+ if (Array.isArray(value) && value.every((item) => typeof item === "string")) {
159
+ return `[${value.map((item) => JSON.stringify(item)).join(", ")}]`;
160
+ }
161
+ throw new Error("Frontmatter values must be strings, numbers, or string arrays");
162
+ }
163
+ function requireNumber(values, key) {
164
+ const value = values.get(key);
165
+ if (typeof value !== "number")
166
+ throw new Error(`Frontmatter ${key} must be a number`);
167
+ return value;
168
+ }
169
+ function optionalNumber(values, key) {
170
+ const value = values.get(key);
171
+ if (value === undefined)
172
+ return;
173
+ if (typeof value !== "number")
174
+ throw new Error(`Frontmatter ${key} must be a number`);
175
+ return value;
176
+ }
177
+ function requireString(values, key) {
178
+ const value = values.get(key);
179
+ if (typeof value !== "string")
180
+ throw new Error(`Frontmatter ${key} must be a string`);
181
+ return value;
182
+ }
183
+ function requireNonBlankString(values, key) {
184
+ const value = requireString(values, key);
185
+ if (value.trim().length === 0)
186
+ throw new Error(`Frontmatter ${key} cannot be blank`);
187
+ return value;
188
+ }
189
+ function requireIdSegment(values, key) {
190
+ return assertIdSegment(key, requireNonBlankString(values, key));
191
+ }
192
+ function optionalIdSegmentProperty(values, key) {
193
+ if (!values.has(key))
194
+ return {};
195
+ return { [key]: requireIdSegment(values, key) };
196
+ }
197
+ function assertIdSegment(key, value) {
198
+ if (!ID_SEGMENT_PATTERN.test(value)) {
199
+ throw new Error(`Frontmatter ${key} must be a safe path segment: ${value}`);
200
+ }
201
+ return value;
202
+ }
203
+ function optionalStringProperty(values, key) {
204
+ if (!values.has(key))
205
+ return {};
206
+ return { [key]: requireNonBlankString(values, key) };
207
+ }
208
+ function optionalStringArrayProperty(values, key) {
209
+ const value = values.get(key);
210
+ if (value === undefined)
211
+ return {};
212
+ if (!Array.isArray(value) || value.some((item) => item.trim().length === 0)) {
213
+ throw new Error(`Frontmatter ${key} must be a string array without blank items`);
214
+ }
215
+ return { tags: value };
216
+ }
217
+ function extraProperties(values) {
218
+ const extra = Object.fromEntries(Array.from(values).filter(([key]) => !KNOWN_KEY_ORDER.has(key)));
219
+ return Object.keys(extra).length === 0 ? {} : { extra };
220
+ }
221
+ function appendOptional(fields, key, value) {
222
+ if (value !== undefined)
223
+ fields.push([key, value]);
224
+ }
225
+ function validateRecord(record) {
226
+ if (record.version !== 1)
227
+ throw new Error(`Unsupported memory file version: ${record.version}`);
228
+ if (record.id.trim().length === 0)
229
+ throw new Error("Frontmatter id cannot be blank");
230
+ assertIdSegment("id", record.id);
231
+ if (record.archiveId !== undefined)
232
+ assertIdSegment("archiveId", record.archiveId);
233
+ if (record.kind !== "train" && record.kind !== "note") {
234
+ throw new Error(`Invalid memory kind: ${String(record.kind)}`);
235
+ }
236
+ if (!Number.isSafeInteger(record.createdAt) || record.createdAt < 0) {
237
+ throw new Error("createdAt must be a non-negative integer");
238
+ }
239
+ if (record.fileCount !== undefined && (!Number.isSafeInteger(record.fileCount) || record.fileCount < 0)) {
240
+ throw new Error("fileCount must be a non-negative integer");
241
+ }
242
+ for (const [key, value] of Object.entries({
243
+ headline: record.headline,
244
+ model: record.model,
245
+ archiveId: record.archiveId,
246
+ priority: record.priority,
247
+ source: record.source
248
+ })) {
249
+ if (value !== undefined && value.trim().length === 0) {
250
+ throw new Error(`Frontmatter ${key} cannot be blank`);
251
+ }
252
+ }
253
+ if (record.tags?.some((tag) => tag.trim().length === 0)) {
254
+ throw new Error("Frontmatter tags cannot contain blank items");
255
+ }
256
+ assertContent(record.content);
257
+ }
258
+ function assertContent(content) {
259
+ if (content.trim().length === 0)
260
+ throw new Error("Memory content cannot be blank");
261
+ const bytes = Buffer.byteLength(content, "utf8");
262
+ if (bytes > MAX_TRAIN_RECORD_CONTENT_BYTES) {
263
+ throw new Error(`Memory content exceeds ${MAX_TRAIN_RECORD_CONTENT_BYTES} UTF-8 bytes`);
264
+ }
265
+ }
266
+ // src/memory/store/discovery.ts
267
+ import { lstat } from "node:fs/promises";
268
+ import { join } from "node:path";
269
+
270
+ // src/lib/walk-ancestors.ts
271
+ import { homedir } from "node:os";
272
+ import { dirname, resolve } from "node:path";
273
+ function walkAncestors(cwd, home = homedir()) {
274
+ const resolvedHome = resolve(home);
275
+ const seen = new Set;
276
+ const result = [];
277
+ let current = resolve(cwd);
278
+ while (true) {
279
+ if (seen.has(current))
280
+ break;
281
+ seen.add(current);
282
+ if (current !== resolvedHome)
283
+ result.push(current);
284
+ const parent = dirname(current);
285
+ if (parent === current)
286
+ break;
287
+ current = parent;
288
+ }
289
+ return result;
290
+ }
291
+
292
+ // src/memory/store/discovery.ts
293
+ var MEMORY_ROOT_NAMES = [".duet", ".agents", ".claude"];
294
+ async function discoverMemoryStores(cwd) {
295
+ const stores = [];
296
+ for (const ancestor of walkAncestors(cwd)) {
297
+ for (const rootName of MEMORY_ROOT_NAMES) {
298
+ const storeDir = join(ancestor, rootName, "memories");
299
+ const status = await lstat(storeDir).catch((error) => {
300
+ if (isNodeError(error, "ENOENT") || isNodeError(error, "ENOTDIR"))
301
+ return;
302
+ throw error;
303
+ });
304
+ if (!status)
305
+ continue;
306
+ if (status.isSymbolicLink())
307
+ throw new Error(`Memory stores cannot be symlinks: ${storeDir}`);
308
+ if (!status.isDirectory())
309
+ throw new Error(`Memory store is not a directory: ${storeDir}`);
310
+ stores.push(storeDir);
311
+ }
312
+ }
313
+ return stores;
314
+ }
315
+ function isNodeError(error, code) {
316
+ return error instanceof Error && "code" in error && error.code === code;
317
+ }
318
+ // src/memory/store/sources.ts
319
+ import * as fileSystem from "node:fs/promises";
320
+ import { join as join3, resolve as resolve2 } from "node:path";
321
+
322
+ // src/memory/paths.ts
323
+ import { homedir as homedir2 } from "node:os";
324
+ import { join as join2 } from "node:path";
325
+ var DEFAULT_DUET_DIR = ".duet";
326
+ var DEFAULT_DUET_HOME = join2(homedir2(), DEFAULT_DUET_DIR);
327
+ var DEFAULT_SESSION_STORAGE_DIR = join2(DEFAULT_DUET_HOME, "sessions");
328
+ var DEFAULT_MEMORY_DB_PATH = join2(DEFAULT_DUET_HOME, "memory.db");
329
+
330
+ // src/memory/store/sources.ts
331
+ async function resolveSources(flags, cwd) {
332
+ const explicitCount = flags.stores.length + flags.dbs.length;
333
+ if (explicitCount > 0) {
334
+ const stores2 = unique(flags.stores);
335
+ const dbs = unique(flags.dbs);
336
+ const selected = flags.stores[0] ?? flags.dbs[0];
337
+ const writeTarget = explicitCount === 1 && selected ? { kind: flags.stores.length === 1 ? "store" : "db", path: selected } : undefined;
338
+ return {
339
+ stores: stores2,
340
+ dbs,
341
+ ...writeTarget ? { writeTarget } : {}
342
+ };
343
+ }
344
+ const stores = await discoverMemoryStores(cwd);
345
+ return {
346
+ stores,
347
+ dbs: [DEFAULT_MEMORY_DB_PATH],
348
+ writeTarget: { kind: "store", path: await nearestAgentMemoryStore(cwd) }
349
+ };
350
+ }
351
+ function mergeListings(storeEntries, dbEntries) {
352
+ const winners = new Map;
353
+ for (const listing of storeEntries) {
354
+ for (const entry of listing.entries) {
355
+ if (!winners.has(entry.slug))
356
+ winners.set(entry.slug, { ...entry, store: listing.source });
357
+ }
358
+ }
359
+ for (const listing of dbEntries) {
360
+ for (const entry of listing.entries) {
361
+ if (!winners.has(entry.slug))
362
+ winners.set(entry.slug, { ...entry });
363
+ }
364
+ }
365
+ return Array.from(winners.values()).sort((left, right) => right.createdAt - left.createdAt);
366
+ }
367
+ async function nearestAgentMemoryStore(cwd) {
368
+ for (const ancestor of walkAncestors(cwd)) {
369
+ const agentRoot = join3(ancestor, ".agents");
370
+ const status = await fileSystem.lstat(agentRoot).catch((error) => {
371
+ if (isNodeError2(error, "ENOENT") || isNodeError2(error, "ENOTDIR"))
372
+ return;
373
+ throw error;
374
+ });
375
+ if (!status)
376
+ continue;
377
+ if (status.isSymbolicLink())
378
+ throw new Error(`Agent directories cannot be symlinks: ${agentRoot}`);
379
+ if (!status.isDirectory())
380
+ throw new Error(`Agent path is not a directory: ${agentRoot}`);
381
+ return join3(agentRoot, "memories");
382
+ }
383
+ return join3(resolve2(cwd), ".agents", "memories");
384
+ }
385
+ function unique(paths) {
386
+ return Array.from(new Set(paths));
387
+ }
388
+ function isNodeError2(error, code) {
389
+ return error instanceof Error && "code" in error && error.code === code;
390
+ }
391
+ // src/memory/store/store.ts
392
+ import { randomUUID } from "node:crypto";
393
+ import { constants } from "node:fs";
394
+ import * as fileSystem2 from "node:fs/promises";
395
+ import { basename as basename2, join as join4, resolve as resolve3 } from "node:path";
396
+ async function listStore(dir) {
397
+ return listStoreWithInvalidPolicy(dir);
398
+ }
399
+ async function listStoreWithInvalidPolicy(dir, onInvalidEntry) {
400
+ const storeDir = await requireStoreDirectory(dir, false);
401
+ if (!storeDir)
402
+ return [];
403
+ const directoryEntries = await fileSystem2.readdir(storeDir, { withFileTypes: true });
404
+ const memoryEntries = directoryEntries.filter((entry) => entry.name.endsWith(".md")).sort((left, right) => left.name.localeCompare(right.name));
405
+ const loaded = await Promise.all(memoryEntries.map(async (entry) => {
406
+ const path = join4(storeDir, entry.name);
407
+ try {
408
+ if (entry.isSymbolicLink())
409
+ throw new Error(`Memory files cannot be symlinks: ${entry.name}`);
410
+ if (!entry.isFile())
411
+ throw new Error(`Memory entry is not a regular file: ${entry.name}`);
412
+ return await readEntry(storeDir, slugFromFilename(entry.name));
413
+ } catch (error) {
414
+ if (!onInvalidEntry)
415
+ throw error;
416
+ onInvalidEntry(path, error);
417
+ return;
418
+ }
419
+ }));
420
+ return loaded.filter((entry) => entry !== undefined);
421
+ }
422
+ async function readEntry(dir, slug) {
423
+ const safeSlug = assertSlug(slug);
424
+ const storeDir = await requireStoreDirectory(dir, false);
425
+ if (!storeDir)
426
+ throw new Error(`Memory store does not exist: ${resolve3(dir)}`);
427
+ return (await readParsedEntry(storeDir, safeSlug)).stored;
428
+ }
429
+ async function writeEntry(dir, record) {
430
+ const slug = assertSlug(record.slug);
431
+ const bytes = serializeMemoryFile(record);
432
+ const storeDir = await requireStoreDirectory(dir, true);
433
+ if (!storeDir)
434
+ throw new Error("Memory store could not be created");
435
+ const destination = join4(storeDir, `${slug}.md`);
436
+ await rejectSymlinkIfPresent(destination);
437
+ const temporary = join4(storeDir, `.${slug}.${randomUUID()}.tmp`);
438
+ const handle = await fileSystem2.open(temporary, "wx", 384);
439
+ try {
440
+ try {
441
+ await handle.writeFile(bytes, "utf8");
442
+ await handle.sync();
443
+ } finally {
444
+ await handle.close();
445
+ }
446
+ await fileSystem2.rename(temporary, destination);
447
+ } catch (error) {
448
+ await fileSystem2.rm(temporary, { force: true });
449
+ throw error;
450
+ }
451
+ return toStoredMemory(storeDir, slug, record);
452
+ }
453
+ async function updateEntry(dir, slug, content) {
454
+ const safeSlug = assertSlug(slug);
455
+ const storeDir = await requireStoreDirectory(dir, false);
456
+ if (!storeDir)
457
+ throw new Error(`Memory store does not exist: ${resolve3(dir)}`);
458
+ const { record } = await readParsedEntry(storeDir, safeSlug);
459
+ record.content = content;
460
+ return writeEntry(storeDir, Object.assign(record, { slug: safeSlug }));
461
+ }
462
+ async function deleteEntry(dir, slug) {
463
+ const safeSlug = assertSlug(slug);
464
+ const storeDir = await requireStoreDirectory(dir, false);
465
+ if (!storeDir)
466
+ throw new Error(`Memory store does not exist: ${resolve3(dir)}`);
467
+ const { stored } = await readParsedEntry(storeDir, safeSlug);
468
+ const path = join4(storeDir, `${safeSlug}.md`);
469
+ await rejectSymlinkIfPresent(path);
470
+ await fileSystem2.unlink(path);
471
+ return stored;
472
+ }
473
+ async function readParsedEntry(storeDir, slug) {
474
+ const path = join4(storeDir, `${slug}.md`);
475
+ const status = await fileSystem2.lstat(path);
476
+ if (status.isSymbolicLink())
477
+ throw new Error(`Memory files cannot be symlinks: ${path}`);
478
+ if (!status.isFile())
479
+ throw new Error(`Memory entry is not a regular file: ${path}`);
480
+ const handle = await fileSystem2.open(path, constants.O_RDONLY | constants.O_NOFOLLOW);
481
+ const text = await handle.readFile("utf8").finally(() => handle.close());
482
+ const record = parseMemoryFile(text);
483
+ return { stored: toStoredMemory(storeDir, slug, record), record };
484
+ }
485
+ function toStoredMemory(storeDir, slug, record) {
486
+ return {
487
+ slug,
488
+ storeDir,
489
+ id: record.id,
490
+ kind: record.kind,
491
+ createdAt: record.createdAt,
492
+ ...record.headline === undefined ? {} : { headline: record.headline },
493
+ ...record.model === undefined ? {} : { model: record.model },
494
+ ...record.fileCount === undefined ? {} : { fileCount: record.fileCount },
495
+ ...record.archiveId === undefined ? {} : { archiveId: record.archiveId },
496
+ content: record.content
497
+ };
498
+ }
499
+ function assertSlug(slug) {
500
+ if (basename2(slug) !== slug)
501
+ throw new Error(`Unsafe memory slug: ${slug}`);
502
+ return slugFromFilename(`${slug}.md`);
503
+ }
504
+ async function requireStoreDirectory(dir, create) {
505
+ const storeDir = resolve3(dir);
506
+ const status = await fileSystem2.lstat(storeDir).catch((error) => {
507
+ if (isNodeError3(error, "ENOENT"))
508
+ return;
509
+ throw error;
510
+ });
511
+ if (!status && create) {
512
+ await fileSystem2.mkdir(storeDir, { recursive: true, mode: 448 });
513
+ const createdStatus = await fileSystem2.lstat(storeDir);
514
+ if (!createdStatus.isDirectory() || createdStatus.isSymbolicLink()) {
515
+ throw new Error(`Memory store must be a real directory: ${storeDir}`);
516
+ }
517
+ return storeDir;
518
+ }
519
+ if (!status)
520
+ return;
521
+ if (status.isSymbolicLink())
522
+ throw new Error(`Memory stores cannot be symlinks: ${storeDir}`);
523
+ if (!status.isDirectory())
524
+ throw new Error(`Memory store is not a directory: ${storeDir}`);
525
+ return storeDir;
526
+ }
527
+ async function rejectSymlinkIfPresent(path) {
528
+ const status = await fileSystem2.lstat(path).catch((error) => {
529
+ if (isNodeError3(error, "ENOENT"))
530
+ return;
531
+ throw error;
532
+ });
533
+ if (status?.isSymbolicLink())
534
+ throw new Error(`Memory files cannot be symlinks: ${path}`);
535
+ if (status && !status.isFile())
536
+ throw new Error(`Memory entry is not a regular file: ${path}`);
537
+ }
538
+ function isNodeError3(error, code) {
539
+ return error instanceof Error && "code" in error && error.code === code;
540
+ }
541
+ export {
542
+ writeEntry,
543
+ updateEntry,
544
+ slugFromFilename,
545
+ serializeMemoryFile,
546
+ resolveSources,
547
+ readEntry,
548
+ parseMemoryFile,
549
+ mergeListings,
550
+ listStore,
551
+ discoverMemoryStores,
552
+ deleteEntry,
553
+ MAX_TRAIN_RECORD_CONTENT_BYTES
554
+ };
@@ -1,5 +1,19 @@
1
1
  import type { ImageContent, Message, Tool } from "@earendil-works/pi-ai";
2
2
  import type { AgentMessage, AgentState } from "@earendil-works/pi-agent-core";
3
+ /**
4
+ * Soft total-input target above which the consultation pipeline compacts older
5
+ * work into observations. Paid repeated-run telemetry showed that forwarding
6
+ * larger raw transcripts costs the advisor more than it saves the observer.
7
+ * The hard model window remains the final safety ceiling.
8
+ */
9
+ export declare const ADVISOR_INPUT_TARGET_TOKENS = 32000;
10
+ /**
11
+ * Recent raw executor-message allowance kept beside compacted observations.
12
+ * The newest complete tool interaction remains protected even when it exceeds
13
+ * this target, so the allowance buys ordinary continuity without clipping the
14
+ * evidence the advisor was called to inspect.
15
+ */
16
+ export declare const ADVISOR_RECENT_MESSAGE_TARGET_TOKENS = 8000;
3
17
  /** Minimal live-agent surface needed to capture the executor request faithfully. */
4
18
  export interface AdvisorContextSource {
5
19
  /** Current prompt, transcript, tools, and any partial assistant message. */
@@ -15,6 +29,13 @@ export interface AdvisorExecutorContext {
15
29
  messages: readonly Message[];
16
30
  /** Exact tool definitions available to the executor for the current turn. */
17
31
  tools: readonly Tool[];
32
+ /** Raw executor messages represented by observational context instead of repeated verbatim. */
33
+ compactedMessages?: number;
34
+ }
35
+ /** Optional advisor-only projection applied before conversion to provider messages. */
36
+ export interface CaptureAdvisorContextOptions {
37
+ /** Replaces older history with normal observational context and a recent raw tail. */
38
+ transformMessages: (messages: AgentMessage[]) => Promise<AgentMessage[]>;
18
39
  }
19
40
  /** Inputs for fitting the executor context into the advisor model's real request window. */
20
41
  export interface BuildAdvisorContextInput {
@@ -35,10 +56,14 @@ export interface AdvisorContextMetadata {
35
56
  safetyMarginTokens: number;
36
57
  /** Maximum estimated tokens available to the quoted executor context. */
37
58
  inputLimitTokens: number;
59
+ /** Preferred total advisor input; the hard model limit remains a safety ceiling. */
60
+ inputTargetTokens: number;
38
61
  /** Heuristic token estimate including the shared coarse per-image charge. */
39
62
  estimatedInputTokens: number;
40
63
  /** Number of executor messages included in the serialized context. */
41
64
  includedMessages: number;
65
+ /** Raw executor messages represented by observational context rather than copied verbatim. */
66
+ compactedMessages: number;
42
67
  /** Number of older executor messages omitted at whole-message boundaries. */
43
68
  omittedMessages: number;
44
69
  /** True only when the advisor model's real window forced message omission. */
@@ -60,12 +85,13 @@ export interface AdvisorContext {
60
85
  * Tool execution normally starts after the assistant message is finalized; the
61
86
  * streaming fallback also preserves a partial turn for callers at other phases.
62
87
  */
63
- export declare function captureAdvisorExecutorContext(source: AdvisorContextSource): Promise<AdvisorExecutorContext>;
88
+ export declare function captureAdvisorExecutorContext(source: AdvisorContextSource, options?: CaptureAdvisorContextOptions): Promise<AdvisorExecutorContext>;
64
89
  /**
65
- * Serialize the actual executor context without observer projection. When the
66
- * advisor model cannot fit it, remove only the oldest complete messages; tool
67
- * definitions, the resolved system prompt, and retained message structures
68
- * remain intact. Image bytes travel as matching multimodal attachments.
90
+ * Serialize the captured raw or observationally compacted executor context.
91
+ * When the advisor's hard model window still cannot fit it, remove only the
92
+ * oldest complete messages; tool definitions, the resolved system prompt, and
93
+ * retained message structures remain intact. Image bytes travel as matching
94
+ * multimodal attachments.
69
95
  */
70
96
  export declare function buildAdvisorContext(input: BuildAdvisorContextInput): AdvisorContext;
71
97
  //# sourceMappingURL=advisor-context.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"advisor-context.d.ts","sourceRoot":"","sources":["../../../src/model-routing/advisor-context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAW,YAAY,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,uBAAuB,CAAC;AAClF,OAAO,KAAK,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAQ9E,oFAAoF;AACpF,MAAM,WAAW,oBAAoB;IACnC,4EAA4E;IAC5E,KAAK,EAAE,IAAI,CAAC,UAAU,EAAE,cAAc,GAAG,UAAU,GAAG,OAAO,GAAG,kBAAkB,CAAC,CAAC;IACpF,8EAA8E;IAC9E,YAAY,EAAE,CAAC,QAAQ,EAAE,YAAY,EAAE,KAAK,OAAO,EAAE,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC;CAC5E;AAED,mEAAmE;AACnE,MAAM,WAAW,sBAAsB;IACrC,yDAAyD;IACzD,YAAY,EAAE,MAAM,CAAC;IACrB,mFAAmF;IACnF,QAAQ,EAAE,SAAS,OAAO,EAAE,CAAC;IAC7B,6EAA6E;IAC7E,KAAK,EAAE,SAAS,IAAI,EAAE,CAAC;CACxB;AAED,4FAA4F;AAC5F,MAAM,WAAW,wBAAwB;IACvC,2EAA2E;IAC3E,OAAO,EAAE,sBAAsB,CAAC;IAChC,oEAAoE;IACpE,mBAAmB,EAAE,MAAM,CAAC;IAC5B,oFAAoF;IACpF,oBAAoB,EAAE,MAAM,CAAC;CAC9B;AAED,wEAAwE;AACxE,MAAM,WAAW,sBAAsB;IACrC,mEAAmE;IACnE,mBAAmB,EAAE,MAAM,CAAC;IAC5B,qEAAqE;IACrE,oBAAoB,EAAE,MAAM,CAAC;IAC7B,0FAA0F;IAC1F,kBAAkB,EAAE,MAAM,CAAC;IAC3B,yEAAyE;IACzE,gBAAgB,EAAE,MAAM,CAAC;IACzB,6EAA6E;IAC7E,oBAAoB,EAAE,MAAM,CAAC;IAC7B,sEAAsE;IACtE,gBAAgB,EAAE,MAAM,CAAC;IACzB,6EAA6E;IAC7E,eAAe,EAAE,MAAM,CAAC;IACxB,8EAA8E;IAC9E,SAAS,EAAE,OAAO,CAAC;IACnB,mFAAmF;IACnF,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,+DAA+D;AAC/D,MAAM,WAAW,cAAc;IAC7B,uEAAuE;IACvE,IAAI,EAAE,MAAM,CAAC;IACb,iFAAiF;IACjF,MAAM,EAAE,SAAS,YAAY,EAAE,CAAC;IAChC,sEAAsE;IACtE,QAAQ,EAAE,sBAAsB,CAAC;CAClC;AAED;;;;GAIG;AACH,wBAAsB,6BAA6B,CACjD,MAAM,EAAE,oBAAoB,GAC3B,OAAO,CAAC,sBAAsB,CAAC,CAejC;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,wBAAwB,GAAG,cAAc,CAoDnF"}
1
+ {"version":3,"file":"advisor-context.d.ts","sourceRoot":"","sources":["../../../src/model-routing/advisor-context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,uBAAuB,CAAC;AACzE,OAAO,KAAK,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAQ9E;;;;;GAKG;AACH,eAAO,MAAM,2BAA2B,QAAS,CAAC;AAElD;;;;;GAKG;AACH,eAAO,MAAM,oCAAoC,OAAQ,CAAC;AAE1D,oFAAoF;AACpF,MAAM,WAAW,oBAAoB;IACnC,4EAA4E;IAC5E,KAAK,EAAE,IAAI,CAAC,UAAU,EAAE,cAAc,GAAG,UAAU,GAAG,OAAO,GAAG,kBAAkB,CAAC,CAAC;IACpF,8EAA8E;IAC9E,YAAY,EAAE,CAAC,QAAQ,EAAE,YAAY,EAAE,KAAK,OAAO,EAAE,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC;CAC5E;AAED,mEAAmE;AACnE,MAAM,WAAW,sBAAsB;IACrC,yDAAyD;IACzD,YAAY,EAAE,MAAM,CAAC;IACrB,mFAAmF;IACnF,QAAQ,EAAE,SAAS,OAAO,EAAE,CAAC;IAC7B,6EAA6E;IAC7E,KAAK,EAAE,SAAS,IAAI,EAAE,CAAC;IACvB,+FAA+F;IAC/F,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,uFAAuF;AACvF,MAAM,WAAW,4BAA4B;IAC3C,sFAAsF;IACtF,iBAAiB,EAAE,CAAC,QAAQ,EAAE,YAAY,EAAE,KAAK,OAAO,CAAC,YAAY,EAAE,CAAC,CAAC;CAC1E;AAED,4FAA4F;AAC5F,MAAM,WAAW,wBAAwB;IACvC,2EAA2E;IAC3E,OAAO,EAAE,sBAAsB,CAAC;IAChC,oEAAoE;IACpE,mBAAmB,EAAE,MAAM,CAAC;IAC5B,oFAAoF;IACpF,oBAAoB,EAAE,MAAM,CAAC;CAC9B;AAED,wEAAwE;AACxE,MAAM,WAAW,sBAAsB;IACrC,mEAAmE;IACnE,mBAAmB,EAAE,MAAM,CAAC;IAC5B,qEAAqE;IACrE,oBAAoB,EAAE,MAAM,CAAC;IAC7B,0FAA0F;IAC1F,kBAAkB,EAAE,MAAM,CAAC;IAC3B,yEAAyE;IACzE,gBAAgB,EAAE,MAAM,CAAC;IACzB,oFAAoF;IACpF,iBAAiB,EAAE,MAAM,CAAC;IAC1B,6EAA6E;IAC7E,oBAAoB,EAAE,MAAM,CAAC;IAC7B,sEAAsE;IACtE,gBAAgB,EAAE,MAAM,CAAC;IACzB,8FAA8F;IAC9F,iBAAiB,EAAE,MAAM,CAAC;IAC1B,6EAA6E;IAC7E,eAAe,EAAE,MAAM,CAAC;IACxB,8EAA8E;IAC9E,SAAS,EAAE,OAAO,CAAC;IACnB,mFAAmF;IACnF,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,+DAA+D;AAC/D,MAAM,WAAW,cAAc;IAC7B,uEAAuE;IACvE,IAAI,EAAE,MAAM,CAAC;IACb,iFAAiF;IACjF,MAAM,EAAE,SAAS,YAAY,EAAE,CAAC;IAChC,sEAAsE;IACtE,QAAQ,EAAE,sBAAsB,CAAC;CAClC;AAED;;;;GAIG;AACH,wBAAsB,6BAA6B,CACjD,MAAM,EAAE,oBAAoB,EAC5B,OAAO,CAAC,EAAE,4BAA4B,GACrC,OAAO,CAAC,sBAAsB,CAAC,CAwBjC;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,wBAAwB,GAAG,cAAc,CA6DnF"}