@noetic-tools/core 0.2.0 → 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 (203) hide show
  1. package/dist/adapters/in-memory-fs-adapter.d.ts +1 -1
  2. package/dist/adapters/in-memory-fs-adapter.js +1 -1
  3. package/dist/adapters/in-memory-shell-adapter.d.ts +1 -1
  4. package/dist/adapters/in-memory-subprocess/manifest-persistence.d.ts +2 -2
  5. package/dist/adapters/in-memory-subprocess/metadata.d.ts +1 -1
  6. package/dist/adapters/in-memory-subprocess/types.d.ts +2 -2
  7. package/dist/adapters/in-memory-subprocess-adapter.d.ts +1 -1
  8. package/dist/adapters/openrouter.d.ts +2 -6
  9. package/dist/adapters/openrouter.js +1 -4
  10. package/dist/builders/channel-builder.d.ts +1 -1
  11. package/dist/builders/control-flow-builders.d.ts +2 -3
  12. package/dist/builders/control-flow-builders.js +1 -2
  13. package/dist/builders/every.d.ts +2 -3
  14. package/dist/builders/every.js +1 -1
  15. package/dist/builders/layer-provides-builders.d.ts +1 -1
  16. package/dist/builders/layer-provides-builders.js +1 -1
  17. package/dist/builders/loop-builder.d.ts +2 -2
  18. package/dist/builders/loop-builder.js +1 -1
  19. package/dist/builders/memory-builder.d.ts +1 -1
  20. package/dist/builders/memory-builder.js +1 -1
  21. package/dist/builders/provide-builder.d.ts +2 -2
  22. package/dist/builders/provide-builder.js +1 -1
  23. package/dist/builders/spawn-builder.d.ts +2 -3
  24. package/dist/builders/spawn-builder.js +1 -1
  25. package/dist/builders/step-builders.d.ts +2 -6
  26. package/dist/builders/step-builders.js +1 -1
  27. package/dist/builders/tool-builder.d.ts +1 -3
  28. package/dist/builders/tool-builder.js +1 -1
  29. package/dist/builders/workflow-hydrator.d.ts +2 -3
  30. package/dist/builders/workflow-hydrator.js +1 -2
  31. package/dist/conditions/conditions.d.ts +2 -3
  32. package/dist/conditions/conditions.js +1 -1
  33. package/dist/harness/agent-harness.d.ts +13 -3
  34. package/dist/harness/agent-harness.js +48 -5
  35. package/dist/harness/deps/memory.d.ts +2 -5
  36. package/dist/harness/deps/memory.js +1 -4
  37. package/dist/harness/deps/types.d.ts +3 -16
  38. package/dist/harness/deps/types.js +1 -1
  39. package/dist/harness/model-call.d.ts +1 -3
  40. package/dist/harness/model-call.js +1 -2
  41. package/dist/harness/model-schema.d.ts +2 -4
  42. package/dist/harness/model-schema.js +1 -1
  43. package/dist/index.d.ts +28 -49
  44. package/dist/index.js +9 -27
  45. package/dist/interpreter/action-deps.d.ts +4 -8
  46. package/dist/interpreter/action-deps.js +2 -6
  47. package/dist/interpreter/action-types.d.ts +3 -8
  48. package/dist/interpreter/action-types.js +1 -1
  49. package/dist/interpreter/collect-tools.d.ts +1 -2
  50. package/dist/interpreter/collect-tools.js +1 -1
  51. package/dist/interpreter/execute-action.js +104 -26
  52. package/dist/interpreter/execute-control.d.ts +1 -2
  53. package/dist/interpreter/execute-control.js +1 -3
  54. package/dist/interpreter/execute.d.ts +2 -3
  55. package/dist/interpreter/execute.js +1 -2
  56. package/dist/interpreter/message-helpers.d.ts +1 -1
  57. package/dist/interpreter/message-helpers.js +1 -1
  58. package/dist/interpreter/step-dispatcher.d.ts +2 -5
  59. package/dist/interpreter/typeguards.d.ts +3 -4
  60. package/dist/interpreter/typeguards.js +1 -1
  61. package/dist/observability/span-impl.d.ts +1 -1
  62. package/dist/observability/trace-exporter.d.ts +1 -1
  63. package/dist/patterns/dynamic-workflow.d.ts +2 -5
  64. package/dist/patterns/dynamic-workflow.js +1 -2
  65. package/dist/patterns/flow.d.ts +2 -2
  66. package/dist/patterns/flow.js +1 -1
  67. package/dist/patterns/interview.d.ts +2 -2
  68. package/dist/patterns/interview.js +1 -1
  69. package/dist/patterns/plans.d.ts +2 -3
  70. package/dist/patterns/plans.js +1 -1
  71. package/dist/patterns/ralph-wiggum.d.ts +2 -3
  72. package/dist/patterns/react.d.ts +2 -3
  73. package/dist/portable/memory-layers.d.ts +1 -7
  74. package/dist/portable/memory-layers.js +1 -7
  75. package/dist/portable.d.ts +2 -2
  76. package/dist/portable.js +2 -2
  77. package/dist/runtime/broadcaster-utils.d.ts +1 -1
  78. package/dist/runtime/channel-store.d.ts +1 -1
  79. package/dist/runtime/channel-store.js +1 -2
  80. package/dist/runtime/context-deps.d.ts +3 -3
  81. package/dist/runtime/context-deps.js +2 -2
  82. package/dist/runtime/context-type-deps.d.ts +2 -11
  83. package/dist/runtime/cwd-helpers.d.ts +1 -1
  84. package/dist/runtime/detached-handle.d.ts +2 -3
  85. package/dist/runtime/detached-handle.js +1 -3
  86. package/dist/runtime/durable/checkpoint-store.d.ts +1 -1
  87. package/dist/runtime/durable/checkpoint-store.js +1 -1
  88. package/dist/runtime/durable/harness-checkpoints.d.ts +2 -5
  89. package/dist/runtime/durable/runnable-loop.d.ts +1 -3
  90. package/dist/runtime/durable/session-seed.d.ts +1 -1
  91. package/dist/runtime/durable/stall-nudge.d.ts +1 -1
  92. package/dist/runtime/event-broadcaster.d.ts +1 -1
  93. package/dist/runtime/in-memory-storage.d.ts +1 -1
  94. package/dist/runtime/in-memory-storage.js +1 -1
  95. package/dist/runtime/item-log-impl.d.ts +1 -3
  96. package/dist/runtime/item-log-impl.js +1 -1
  97. package/dist/runtime/message-queue.d.ts +1 -2
  98. package/dist/runtime/session-runner.d.ts +1 -4
  99. package/dist/runtime/session-streams.d.ts +1 -2
  100. package/dist/runtime/session-streams.js +1 -1
  101. package/dist/runtime/step-registry.d.ts +2 -2
  102. package/dist/runtime/step-registry.js +1 -2
  103. package/dist/runtime/tool-memory.d.ts +1 -3
  104. package/dist/types/mutable-context.d.ts +2 -3
  105. package/dist/types/step-registrar.d.ts +1 -1
  106. package/dist/unstable.d.ts +5 -9
  107. package/dist/unstable.js +3 -6
  108. package/dist/until/combinators.d.ts +1 -1
  109. package/dist/until/never.d.ts +1 -1
  110. package/dist/until/predicates.d.ts +2 -2
  111. package/package.json +6 -4
  112. package/dist/errors/noetic-config-error.d.ts +0 -41
  113. package/dist/errors/noetic-config-error.js +0 -44
  114. package/dist/errors/noetic-error.d.ts +0 -18
  115. package/dist/errors/noetic-error.js +0 -54
  116. package/dist/memory/budget.d.ts +0 -23
  117. package/dist/memory/budget.js +0 -117
  118. package/dist/memory/exec-context-factory.d.ts +0 -25
  119. package/dist/memory/exec-context-factory.js +0 -30
  120. package/dist/memory/flow-schema.d.ts +0 -52
  121. package/dist/memory/flow-schema.js +0 -102
  122. package/dist/memory/function-call-utils.d.ts +0 -6
  123. package/dist/memory/function-call-utils.js +0 -27
  124. package/dist/memory/layer-api.d.ts +0 -14
  125. package/dist/memory/layer-api.js +0 -91
  126. package/dist/memory/layer-lifecycle.d.ts +0 -160
  127. package/dist/memory/layer-lifecycle.js +0 -552
  128. package/dist/memory/layer-provides.d.ts +0 -32
  129. package/dist/memory/layer-provides.js +0 -33
  130. package/dist/memory/layer-usage.d.ts +0 -16
  131. package/dist/memory/layer-usage.js +0 -54
  132. package/dist/memory/layers/durable-task-state.d.ts +0 -79
  133. package/dist/memory/layers/durable-task-state.js +0 -118
  134. package/dist/memory/layers/file-reference.d.ts +0 -55
  135. package/dist/memory/layers/file-reference.js +0 -594
  136. package/dist/memory/layers/history-window.d.ts +0 -29
  137. package/dist/memory/layers/history-window.js +0 -114
  138. package/dist/memory/layers/observational-memory.d.ts +0 -53
  139. package/dist/memory/layers/observational-memory.js +0 -109
  140. package/dist/memory/layers/plan.d.ts +0 -55
  141. package/dist/memory/layers/plan.js +0 -374
  142. package/dist/memory/layers/static-content.d.ts +0 -27
  143. package/dist/memory/layers/static-content.js +0 -35
  144. package/dist/memory/layers/steering.d.ts +0 -51
  145. package/dist/memory/layers/steering.js +0 -258
  146. package/dist/memory/layers/temporal.d.ts +0 -91
  147. package/dist/memory/layers/temporal.js +0 -245
  148. package/dist/memory/layers/tool-memory-layer.d.ts +0 -16
  149. package/dist/memory/layers/tool-memory-layer.js +0 -43
  150. package/dist/memory/layers/working-memory.d.ts +0 -44
  151. package/dist/memory/layers/working-memory.js +0 -92
  152. package/dist/memory/projector.d.ts +0 -10
  153. package/dist/memory/projector.js +0 -19
  154. package/dist/memory/scope.d.ts +0 -3
  155. package/dist/memory/scope.js +0 -31
  156. package/dist/memory/strip-unresolved.d.ts +0 -13
  157. package/dist/memory/strip-unresolved.js +0 -35
  158. package/dist/schemas/item.d.ts +0 -35
  159. package/dist/schemas/item.js +0 -155
  160. package/dist/types/channel.d.ts +0 -29
  161. package/dist/types/channel.js +0 -1
  162. package/dist/types/common.d.ts +0 -68
  163. package/dist/types/common.js +0 -1
  164. package/dist/types/context-parts/item-log.d.ts +0 -6
  165. package/dist/types/context-parts/item-log.js +0 -1
  166. package/dist/types/context-parts/layer-usage.d.ts +0 -18
  167. package/dist/types/context-parts/layer-usage.js +0 -1
  168. package/dist/types/context.d.ts +0 -202
  169. package/dist/types/context.js +0 -1
  170. package/dist/types/detached.d.ts +0 -17
  171. package/dist/types/detached.js +0 -7
  172. package/dist/types/error.d.ts +0 -66
  173. package/dist/types/error.js +0 -1
  174. package/dist/types/fs-adapter.d.ts +0 -51
  175. package/dist/types/fs-adapter.js +0 -3
  176. package/dist/types/harness-result.d.ts +0 -43
  177. package/dist/types/harness-result.js +0 -2
  178. package/dist/types/items.d.ts +0 -131
  179. package/dist/types/items.js +0 -2
  180. package/dist/types/memory-context.d.ts +0 -39
  181. package/dist/types/memory-context.js +0 -1
  182. package/dist/types/memory.d.ts +0 -306
  183. package/dist/types/memory.js +0 -13
  184. package/dist/types/observability.d.ts +0 -30
  185. package/dist/types/observability.js +0 -1
  186. package/dist/types/runtime.d.ts +0 -256
  187. package/dist/types/runtime.js +0 -1
  188. package/dist/types/shell-adapter.d.ts +0 -34
  189. package/dist/types/shell-adapter.js +0 -10
  190. package/dist/types/steering.d.ts +0 -89
  191. package/dist/types/steering.js +0 -14
  192. package/dist/types/step.d.ts +0 -231
  193. package/dist/types/step.js +0 -1
  194. package/dist/types/subprocess-adapter.d.ts +0 -157
  195. package/dist/types/subprocess-adapter.js +0 -3
  196. package/dist/types/tool-context.d.ts +0 -34
  197. package/dist/types/tool-context.js +0 -1
  198. package/dist/types/tool.d.ts +0 -64
  199. package/dist/types/tool.js +0 -1
  200. package/dist/util/framework-cast.d.ts +0 -10
  201. package/dist/util/framework-cast.js +0 -16
  202. package/dist/util/message-helpers.d.ts +0 -17
  203. package/dist/util/message-helpers.js +0 -64
@@ -1,594 +0,0 @@
1
- import { Slot } from '../../types/memory.js';
2
- import { createMessage, estimateTokens } from '../../util/message-helpers.js';
3
- /** Default max file size: 1MB */
4
- const DEFAULT_MAX_FILE_SIZE = 1024 * 1024;
5
- /** Default allowed extensions */
6
- const DEFAULT_ALLOWED_EXTENSIONS = [
7
- '.ts',
8
- '.tsx',
9
- '.js',
10
- '.jsx',
11
- '.mjs',
12
- '.cjs',
13
- '.json',
14
- '.yaml',
15
- '.yml',
16
- '.toml',
17
- '.md',
18
- '.mdx',
19
- '.txt',
20
- '.csv',
21
- '.html',
22
- '.css',
23
- '.scss',
24
- '.less',
25
- '.py',
26
- '.rb',
27
- '.go',
28
- '.rs',
29
- '.java',
30
- '.kt',
31
- '.swift',
32
- '.c',
33
- '.cpp',
34
- '.h',
35
- '.hpp',
36
- '.sh',
37
- '.bash',
38
- '.zsh',
39
- '.sql',
40
- '.graphql',
41
- '.xml',
42
- '.svg',
43
- '.env',
44
- '.env.local',
45
- '.env.example',
46
- '.gitignore',
47
- '.dockerignore',
48
- 'Dockerfile',
49
- 'Makefile',
50
- 'Taskfile',
51
- ];
52
- //#endregion
53
- //#region Helpers
54
- // Pattern requires file-like structure: path with extension or explicit path separator
55
- // Avoids matching #hashtag, #123, #region, etc.
56
- const FILE_REF_PATTERN = /#((?:[a-zA-Z0-9_.-]+\/)*[a-zA-Z0-9_.-]+\.[a-zA-Z0-9]+)/g;
57
- function sanitizeAnchor(refPath) {
58
- return refPath.replace(/[^a-zA-Z0-9-]/g, '-').toLowerCase();
59
- }
60
- function transformReferencesToAnchors(text) {
61
- return text.replace(FILE_REF_PATTERN, (_match, filePath) => {
62
- const anchor = sanitizeAnchor(filePath);
63
- return `[#${filePath}](#${anchor})`;
64
- });
65
- }
66
- function extractFileReferences(text) {
67
- const refs = [];
68
- const pattern = new RegExp(FILE_REF_PATTERN);
69
- let match = pattern.exec(text);
70
- while (match !== null) {
71
- refs.push(match[1]);
72
- match = pattern.exec(text);
73
- }
74
- return [
75
- ...new Set(refs),
76
- ]; // Dedupe
77
- }
78
- function normalizePath(input) {
79
- const raw = input.length > 0 ? input : '.';
80
- const absolute = raw.startsWith('/') ? raw : `/${raw}`;
81
- const parts = [];
82
- for (const part of absolute.split('/')) {
83
- if (part.length === 0 || part === '.') {
84
- continue;
85
- }
86
- if (part === '..') {
87
- parts.pop();
88
- continue;
89
- }
90
- parts.push(part);
91
- }
92
- return `/${parts.join('/')}`;
93
- }
94
- function resolvePath(base, ref = '') {
95
- return normalizePath(ref.startsWith('/') ? ref : `${base}/${ref}`);
96
- }
97
- function pathBasename(input) {
98
- const normalized = normalizePath(input);
99
- if (normalized === '/') {
100
- return '';
101
- }
102
- return normalized.slice(normalized.lastIndexOf('/') + 1);
103
- }
104
- function pathExtname(input) {
105
- const name = pathBasename(input);
106
- const idx = name.lastIndexOf('.');
107
- return idx > 0 ? name.slice(idx) : '';
108
- }
109
- function isAbsolutePath(input) {
110
- return input.startsWith('/');
111
- }
112
- function currentWorkingDirectory() {
113
- return typeof process !== 'undefined' ? process.cwd() : '/';
114
- }
115
- function simpleHash(content) {
116
- let hash = 0;
117
- for (let i = 0; i < content.length; i++) {
118
- const char = content.charCodeAt(i);
119
- hash = (hash << 5) - hash + char;
120
- hash = hash & hash; // Convert to 32bit integer
121
- }
122
- // Include content length to reduce collision probability
123
- return `${content.length}:${hash.toString(16)}`;
124
- }
125
- function isInputTextPart(value) {
126
- return (typeof value === 'object' &&
127
- value !== null &&
128
- 'type' in value &&
129
- value.type === 'input_text' &&
130
- 'text' in value &&
131
- typeof value.text === 'string');
132
- }
133
- function isInputMessage(item) {
134
- return (item.type === 'message' &&
135
- 'role' in item &&
136
- 'content' in item &&
137
- Array.isArray(item.content) &&
138
- item.content.every(isInputTextPart));
139
- }
140
- function extractTextFromItem(item) {
141
- if (!isInputMessage(item)) {
142
- return '';
143
- }
144
- return item.content.map((c) => c.text).join('\n');
145
- }
146
- async function readFileContent(absolutePath, opts, fsAdapter) {
147
- try {
148
- // Security: Validate path stays within baseDir (prevent path traversal)
149
- const normalizedBase = resolvePath(opts.baseDir);
150
- const normalizedPath = resolvePath(absolutePath);
151
- if (!normalizedPath.startsWith(`${normalizedBase}/`) && normalizedPath !== normalizedBase) {
152
- return {
153
- content: null,
154
- deleted: false,
155
- error: 'PATH_TRAVERSAL: File outside allowed directory',
156
- };
157
- }
158
- // Security: Check file extension
159
- const ext = pathExtname(normalizedPath).toLowerCase();
160
- const basename = pathBasename(normalizedPath);
161
- const isAllowedExt = opts.allowedExtensions.some((allowed) => ext === allowed.toLowerCase() || basename === allowed);
162
- if (!isAllowedExt && opts.allowedExtensions.length > 0) {
163
- return {
164
- content: null,
165
- deleted: false,
166
- error: `DISALLOWED_EXTENSION: ${ext || 'no extension'}`,
167
- };
168
- }
169
- // Security: Check for symlinks if not allowed
170
- const stats = await fsAdapter.lstat(absolutePath);
171
- if (stats.isSymbolicLink() && !opts.followSymlinks) {
172
- return {
173
- content: null,
174
- deleted: false,
175
- error: 'SYMLINK: Symlinks not allowed',
176
- };
177
- }
178
- // Security: Check file size
179
- const fileStats = opts.followSymlinks ? await fsAdapter.stat(absolutePath) : stats;
180
- if (fileStats.size > opts.maxFileSize) {
181
- return {
182
- content: null,
183
- deleted: false,
184
- error: `FILE_TOO_LARGE: ${fileStats.size} bytes exceeds limit of ${opts.maxFileSize}`,
185
- };
186
- }
187
- const content = await fsAdapter.readFileText(absolutePath);
188
- return {
189
- content,
190
- deleted: false,
191
- };
192
- }
193
- catch (e) {
194
- if (isNodeError(e) && e.code === 'ENOENT') {
195
- return {
196
- content: null,
197
- deleted: true,
198
- };
199
- }
200
- if (isNodeError(e) && e.code === 'EACCES') {
201
- return {
202
- content: null,
203
- deleted: false,
204
- error: 'PERMISSION_DENIED: Cannot read file',
205
- };
206
- }
207
- throw e;
208
- }
209
- }
210
- function isNodeError(e) {
211
- return e instanceof Error && 'code' in e;
212
- }
213
- function extractResponseText(items) {
214
- const texts = [];
215
- for (const item of items) {
216
- if (item.type !== 'message') {
217
- continue;
218
- }
219
- if (!('content' in item) || !Array.isArray(item.content)) {
220
- continue;
221
- }
222
- for (const part of item.content) {
223
- if ('text' in part && typeof part.text === 'string') {
224
- texts.push(part.text);
225
- }
226
- }
227
- }
228
- return texts.join('').trim();
229
- }
230
- async function scoreFileRelevance(params) {
231
- const { filePath, fileContent, userQuery, ctx, model } = params;
232
- if (!ctx.callModel) {
233
- const queryLower = userQuery.toLowerCase();
234
- const pathLower = filePath.toLowerCase();
235
- if (pathLower.includes(queryLower) || queryLower.includes(pathBasename(pathLower))) {
236
- return 80;
237
- }
238
- return 50;
239
- }
240
- const scoringPrompt = `Rate the relevance of this file to the user's query.
241
-
242
- User Query: ${userQuery}
243
-
244
- File Path: ${filePath}
245
- File Content (first 2000 chars):
246
- ${fileContent.slice(0, 2e3)}
247
-
248
- Respond with ONLY a number from 0-100 where:
249
- - 0-20: Not relevant at all
250
- - 21-40: Slightly relevant
251
- - 41-60: Moderately relevant
252
- - 61-80: Highly relevant
253
- - 81-100: Critical/essential
254
-
255
- Score:`;
256
- try {
257
- const response = await ctx.callModel({
258
- model,
259
- items: [
260
- createMessage(scoringPrompt, 'user'),
261
- ],
262
- instructions: 'You are a relevance scorer. Respond with only a number 0-100.',
263
- });
264
- const responseText = extractResponseText(response.items);
265
- const score = Number.parseInt(responseText, 10);
266
- if (Number.isNaN(score) || score < 0 || score > 100) {
267
- return 50;
268
- }
269
- return score;
270
- }
271
- catch {
272
- return 50;
273
- }
274
- }
275
- function createFileReferenceRuntime(opts) {
276
- const baseDir = opts?.baseDir ?? currentWorkingDirectory();
277
- const maxFileSize = opts?.maxFileSize ?? DEFAULT_MAX_FILE_SIZE;
278
- const followSymlinks = opts?.followSymlinks ?? false;
279
- const allowedExtensions = opts?.allowedExtensions ?? DEFAULT_ALLOWED_EXTENSIONS;
280
- return {
281
- baseDir,
282
- slot: opts?.slot ?? Slot.RAG,
283
- scoringModel: opts?.scoringModel ?? 'anthropic/claude-haiku-4-5-20251001',
284
- readOpts: {
285
- maxFileSize,
286
- followSymlinks,
287
- baseDir,
288
- allowedExtensions,
289
- },
290
- };
291
- }
292
- async function initFileReferenceState(baseDir) {
293
- return {
294
- state: {
295
- files: new Map(),
296
- baseDir,
297
- },
298
- };
299
- }
300
- function absolutePathReference(ref) {
301
- return {
302
- absolutePath: ref,
303
- referencePath: ref,
304
- content: null,
305
- priority: 0,
306
- contentHash: '',
307
- deleted: false,
308
- tokenCount: 0,
309
- error: 'ABSOLUTE_PATH: Only relative paths are allowed',
310
- };
311
- }
312
- async function trackNewReference(args) {
313
- if (isAbsolutePath(args.ref)) {
314
- return absolutePathReference(args.ref);
315
- }
316
- const absolutePath = resolvePath(args.state.baseDir, args.ref);
317
- const result = await readFileContent(absolutePath, args.readOpts, args.ctx.fs);
318
- const priority = result.content
319
- ? await scoreFileRelevance({
320
- filePath: args.ref,
321
- fileContent: result.content,
322
- userQuery: args.userQuery,
323
- ctx: args.ctx,
324
- model: args.scoringModel,
325
- })
326
- : 0;
327
- return {
328
- absolutePath,
329
- referencePath: args.ref,
330
- content: result.content,
331
- priority,
332
- contentHash: result.content ? simpleHash(result.content) : '',
333
- deleted: result.deleted,
334
- tokenCount: result.content ? estimateTokens(result.content) : 0,
335
- error: result.error,
336
- };
337
- }
338
- async function addNewReferences(args) {
339
- let hasChanges = false;
340
- for (const ref of args.refs) {
341
- if (args.files.has(ref)) {
342
- continue;
343
- }
344
- args.files.set(ref, await trackNewReference({
345
- ...args,
346
- ref,
347
- }));
348
- hasChanges = true;
349
- }
350
- return hasChanges;
351
- }
352
- function transformItemReferences(item, refs) {
353
- if (refs.length === 0 || !isInputMessage(item)) {
354
- return item;
355
- }
356
- const transformedContent = item.content.map((c) => ({
357
- type: 'input_text',
358
- text: transformReferencesToAnchors(c.text),
359
- }));
360
- return {
361
- ...item,
362
- content: transformedContent,
363
- };
364
- }
365
- async function processAppendedItems(args) {
366
- const transformedItems = [];
367
- let hasChanges = false;
368
- let userQuery = '';
369
- for (const item of args.items) {
370
- const text = extractTextFromItem(item);
371
- if (!text) {
372
- transformedItems.push(item);
373
- continue;
374
- }
375
- if (item.type === 'message' && 'role' in item && item.role === 'user') {
376
- userQuery = text;
377
- }
378
- const refs = extractFileReferences(text);
379
- const added = await addNewReferences({
380
- refs,
381
- files: args.files,
382
- state: args.state,
383
- readOpts: args.readOpts,
384
- ctx: args.ctx,
385
- userQuery,
386
- scoringModel: args.scoringModel,
387
- });
388
- hasChanges = hasChanges || added;
389
- transformedItems.push(transformItemReferences(item, refs));
390
- }
391
- return {
392
- transformedItems,
393
- hasChanges,
394
- userQuery,
395
- };
396
- }
397
- async function refreshTrackedFile(args) {
398
- if (args.tracked.error) {
399
- return null;
400
- }
401
- const result = await readFileContent(args.tracked.absolutePath, args.readOpts, args.ctx.fs);
402
- if (result.deleted && !args.tracked.deleted) {
403
- return {
404
- ...args.tracked,
405
- content: null,
406
- deleted: true,
407
- };
408
- }
409
- if (result.error && !args.tracked.error) {
410
- return {
411
- ...args.tracked,
412
- content: null,
413
- error: result.error,
414
- };
415
- }
416
- if (result.deleted || !result.content) {
417
- return null;
418
- }
419
- const newHash = simpleHash(result.content);
420
- if (newHash === args.tracked.contentHash) {
421
- return null;
422
- }
423
- const priority = args.userQuery
424
- ? await scoreFileRelevance({
425
- filePath: args.ref,
426
- fileContent: result.content,
427
- userQuery: args.userQuery,
428
- ctx: args.ctx,
429
- model: args.scoringModel,
430
- })
431
- : args.tracked.priority;
432
- return {
433
- ...args.tracked,
434
- content: result.content,
435
- contentHash: newHash,
436
- deleted: false,
437
- tokenCount: estimateTokens(result.content),
438
- priority,
439
- error: undefined,
440
- };
441
- }
442
- async function refreshTrackedFiles(args) {
443
- let hasChanges = false;
444
- for (const [ref, tracked] of args.files) {
445
- const next = await refreshTrackedFile({
446
- ...args,
447
- ref,
448
- tracked,
449
- });
450
- if (next === null) {
451
- continue;
452
- }
453
- args.files.set(ref, next);
454
- hasChanges = true;
455
- }
456
- return hasChanges;
457
- }
458
- async function onFileReferenceItemAppend(args, runtime) {
459
- const newFiles = new Map(args.state.files);
460
- const currentReadOpts = {
461
- ...runtime.readOpts,
462
- baseDir: args.state.baseDir,
463
- };
464
- const processed = await processAppendedItems({
465
- items: args.items,
466
- files: newFiles,
467
- state: args.state,
468
- readOpts: currentReadOpts,
469
- ctx: args.ctx,
470
- scoringModel: runtime.scoringModel,
471
- });
472
- const refreshed = await refreshTrackedFiles({
473
- files: newFiles,
474
- readOpts: currentReadOpts,
475
- ctx: args.ctx,
476
- userQuery: processed.userQuery,
477
- scoringModel: runtime.scoringModel,
478
- });
479
- return {
480
- items: processed.transformedItems,
481
- state: {
482
- ...args.state,
483
- files: newFiles,
484
- },
485
- rerender: processed.hasChanges || refreshed,
486
- timing: 'immediate',
487
- scope: 'self',
488
- };
489
- }
490
- function buildDeletedOrErrorBlock(file) {
491
- if (file.deleted) {
492
- return `## ${file.referencePath}\n\n[FILE DELETED: ${file.absolutePath}]`;
493
- }
494
- if (file.error) {
495
- return `## ${file.referencePath}\n\n[ERROR: ${file.error}]`;
496
- }
497
- return null;
498
- }
499
- function truncateFileContent(file, remainingBudget) {
500
- if (!file.content || file.tokenCount <= remainingBudget) {
501
- return file.content ?? '';
502
- }
503
- const lines = file.content.split('\n');
504
- const headLines = Math.floor(lines.length * 0.6);
505
- const tailLines = Math.floor(lines.length * 0.3);
506
- const head = lines.slice(0, headLines).join('\n');
507
- const tail = lines.slice(-tailLines).join('\n');
508
- return `${head}\n\n... [truncated ${lines.length - headLines - tailLines} lines] ...\n\n${tail}`;
509
- }
510
- function buildContentBlock(file, totalTokens, budget) {
511
- const statusBlock = buildDeletedOrErrorBlock(file);
512
- if (statusBlock !== null) {
513
- return totalTokens + estimateTokens(statusBlock) <= budget ? statusBlock : null;
514
- }
515
- if (!file.content) {
516
- return null;
517
- }
518
- const header = `## ${file.referencePath}`;
519
- const headerTokens = estimateTokens(header);
520
- if (totalTokens + headerTokens >= budget) {
521
- return null;
522
- }
523
- const remainingBudget = budget - totalTokens - headerTokens - 10;
524
- const contentToInclude = truncateFileContent(file, remainingBudget);
525
- return `${header}\n\n\`\`\`\n${contentToInclude}\n\`\`\``;
526
- }
527
- async function recallFileReferences({ state, budget, }) {
528
- if (state.files.size === 0) {
529
- return {
530
- items: [],
531
- tokenCount: 0,
532
- };
533
- }
534
- const sortedFiles = [
535
- ...state.files.values(),
536
- ].sort((a, b) => b.priority - a.priority);
537
- const blocks = [];
538
- let totalTokens = 0;
539
- for (const file of sortedFiles) {
540
- const block = buildContentBlock(file, totalTokens, budget);
541
- if (block === null) {
542
- continue;
543
- }
544
- const blockTokens = estimateTokens(block);
545
- blocks.push(block);
546
- totalTokens += blockTokens;
547
- }
548
- if (blocks.length === 0) {
549
- return {
550
- items: [],
551
- tokenCount: 0,
552
- };
553
- }
554
- const content = `# Referenced Files\n\n${blocks.join('\n\n')}`;
555
- return {
556
- items: [
557
- createMessage(content, 'developer'),
558
- ],
559
- tokenCount: estimateTokens(content),
560
- };
561
- }
562
- //#endregion
563
- //#region Layer Factory
564
- /**
565
- * Creates a memory layer that tracks file references in user messages.
566
- *
567
- * Syntax: `#path/to/file` in user messages
568
- *
569
- * Behavior:
570
- * - Transforms references to markdown anchor links `[#path/to/file](#path-to-file)`
571
- * - Scores file relevance using LLM when first referenced
572
- * - Injects file contents into context via recall(), ordered by priority
573
- * - Detects file changes on each new message, triggers immediate re-render
574
- * - Shows warning for deleted files
575
- *
576
- * @public
577
- */
578
- export function fileReference(opts) {
579
- const runtime = createFileReferenceRuntime(opts);
580
- return {
581
- id: 'file-reference',
582
- name: 'File Reference',
583
- slot: runtime.slot,
584
- scope: 'thread',
585
- budget: 'auto',
586
- rerenderTiming: 'immediate',
587
- hooks: {
588
- init: () => initFileReferenceState(runtime.baseDir),
589
- onItemAppend: (args) => onFileReferenceItemAppend(args, runtime),
590
- recall: recallFileReferences,
591
- },
592
- };
593
- }
594
- //#endregion
@@ -1,29 +0,0 @@
1
- /**
2
- * History-window memory layer — caps the number of items projected to the LLM
3
- * without touching `itemLog` storage. Pure read-side projection via the
4
- * `projectHistory` hook.
5
- *
6
- * Algorithm per turn:
7
- * 1. Slice the last `maxItems`.
8
- * 2. If the slice lacks a user `message` or an assistant `message`, expand
9
- * backward until both are present. The cap may be exceeded — for
10
- * tool-burst histories the projected length can grow well beyond
11
- * `maxItems` because the most recent role-pair may be far back.
12
- * 3. Drop any orphan `function_call` / `function_call_output` left at the
13
- * slice boundary via `stripUnresolvedToolCalls`.
14
- */
15
- import type { MemoryLayer } from '../../types/memory.js';
16
- /** @public Configuration for the {@link historyWindow} layer. */
17
- export interface HistoryWindowConfig {
18
- /** Cap on the number of trailing items projected to the LLM. Defaults to 40. */
19
- maxItems?: number;
20
- }
21
- /**
22
- * Creates a history-window memory layer that caps the trailing items projected
23
- * to the LLM. Storage stays unbounded; only the wire payload is narrowed.
24
- *
25
- * @public
26
- * @param config - Optional `{ maxItems }`. Defaults to 40.
27
- * @returns A `MemoryLayer` that contributes only a `projectHistory` hook.
28
- */
29
- export declare function historyWindow(config?: HistoryWindowConfig): MemoryLayer<null>;