@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.
- package/dist/adapters/in-memory-fs-adapter.d.ts +1 -1
- package/dist/adapters/in-memory-fs-adapter.js +1 -1
- package/dist/adapters/in-memory-shell-adapter.d.ts +1 -1
- package/dist/adapters/in-memory-subprocess/manifest-persistence.d.ts +2 -2
- package/dist/adapters/in-memory-subprocess/metadata.d.ts +1 -1
- package/dist/adapters/in-memory-subprocess/types.d.ts +2 -2
- package/dist/adapters/in-memory-subprocess-adapter.d.ts +1 -1
- package/dist/adapters/openrouter.d.ts +2 -6
- package/dist/adapters/openrouter.js +1 -4
- package/dist/builders/channel-builder.d.ts +1 -1
- package/dist/builders/control-flow-builders.d.ts +2 -3
- package/dist/builders/control-flow-builders.js +1 -2
- package/dist/builders/every.d.ts +2 -3
- package/dist/builders/every.js +1 -1
- package/dist/builders/layer-provides-builders.d.ts +1 -1
- package/dist/builders/layer-provides-builders.js +1 -1
- package/dist/builders/loop-builder.d.ts +2 -2
- package/dist/builders/loop-builder.js +1 -1
- package/dist/builders/memory-builder.d.ts +1 -1
- package/dist/builders/memory-builder.js +1 -1
- package/dist/builders/provide-builder.d.ts +2 -2
- package/dist/builders/provide-builder.js +1 -1
- package/dist/builders/spawn-builder.d.ts +2 -3
- package/dist/builders/spawn-builder.js +1 -1
- package/dist/builders/step-builders.d.ts +2 -6
- package/dist/builders/step-builders.js +1 -1
- package/dist/builders/tool-builder.d.ts +1 -3
- package/dist/builders/tool-builder.js +1 -1
- package/dist/builders/workflow-hydrator.d.ts +2 -3
- package/dist/builders/workflow-hydrator.js +1 -2
- package/dist/conditions/conditions.d.ts +2 -3
- package/dist/conditions/conditions.js +1 -1
- package/dist/harness/agent-harness.d.ts +13 -3
- package/dist/harness/agent-harness.js +48 -5
- package/dist/harness/deps/memory.d.ts +2 -5
- package/dist/harness/deps/memory.js +1 -4
- package/dist/harness/deps/types.d.ts +3 -16
- package/dist/harness/deps/types.js +1 -1
- package/dist/harness/model-call.d.ts +1 -3
- package/dist/harness/model-call.js +1 -2
- package/dist/harness/model-schema.d.ts +2 -4
- package/dist/harness/model-schema.js +1 -1
- package/dist/index.d.ts +28 -49
- package/dist/index.js +9 -27
- package/dist/interpreter/action-deps.d.ts +4 -8
- package/dist/interpreter/action-deps.js +2 -6
- package/dist/interpreter/action-types.d.ts +3 -8
- package/dist/interpreter/action-types.js +1 -1
- package/dist/interpreter/collect-tools.d.ts +1 -2
- package/dist/interpreter/collect-tools.js +1 -1
- package/dist/interpreter/execute-action.js +104 -26
- package/dist/interpreter/execute-control.d.ts +1 -2
- package/dist/interpreter/execute-control.js +1 -3
- package/dist/interpreter/execute.d.ts +2 -3
- package/dist/interpreter/execute.js +1 -2
- package/dist/interpreter/message-helpers.d.ts +1 -1
- package/dist/interpreter/message-helpers.js +1 -1
- package/dist/interpreter/step-dispatcher.d.ts +2 -5
- package/dist/interpreter/typeguards.d.ts +3 -4
- package/dist/interpreter/typeguards.js +1 -1
- package/dist/observability/span-impl.d.ts +1 -1
- package/dist/observability/trace-exporter.d.ts +1 -1
- package/dist/patterns/dynamic-workflow.d.ts +2 -5
- package/dist/patterns/dynamic-workflow.js +1 -2
- package/dist/patterns/flow.d.ts +2 -2
- package/dist/patterns/flow.js +1 -1
- package/dist/patterns/interview.d.ts +2 -2
- package/dist/patterns/interview.js +1 -1
- package/dist/patterns/plans.d.ts +2 -3
- package/dist/patterns/plans.js +1 -1
- package/dist/patterns/ralph-wiggum.d.ts +2 -3
- package/dist/patterns/react.d.ts +2 -3
- package/dist/portable/memory-layers.d.ts +1 -7
- package/dist/portable/memory-layers.js +1 -7
- package/dist/portable.d.ts +2 -2
- package/dist/portable.js +2 -2
- package/dist/runtime/broadcaster-utils.d.ts +1 -1
- package/dist/runtime/channel-store.d.ts +1 -1
- package/dist/runtime/channel-store.js +1 -2
- package/dist/runtime/context-deps.d.ts +3 -3
- package/dist/runtime/context-deps.js +2 -2
- package/dist/runtime/context-type-deps.d.ts +2 -11
- package/dist/runtime/cwd-helpers.d.ts +1 -1
- package/dist/runtime/detached-handle.d.ts +2 -3
- package/dist/runtime/detached-handle.js +1 -3
- package/dist/runtime/durable/checkpoint-store.d.ts +1 -1
- package/dist/runtime/durable/checkpoint-store.js +1 -1
- package/dist/runtime/durable/harness-checkpoints.d.ts +2 -5
- package/dist/runtime/durable/runnable-loop.d.ts +1 -3
- package/dist/runtime/durable/session-seed.d.ts +1 -1
- package/dist/runtime/durable/stall-nudge.d.ts +1 -1
- package/dist/runtime/event-broadcaster.d.ts +1 -1
- package/dist/runtime/in-memory-storage.d.ts +1 -1
- package/dist/runtime/in-memory-storage.js +1 -1
- package/dist/runtime/item-log-impl.d.ts +1 -3
- package/dist/runtime/item-log-impl.js +1 -1
- package/dist/runtime/message-queue.d.ts +1 -2
- package/dist/runtime/session-runner.d.ts +1 -4
- package/dist/runtime/session-streams.d.ts +1 -2
- package/dist/runtime/session-streams.js +1 -1
- package/dist/runtime/step-registry.d.ts +2 -2
- package/dist/runtime/step-registry.js +1 -2
- package/dist/runtime/tool-memory.d.ts +1 -3
- package/dist/types/mutable-context.d.ts +2 -3
- package/dist/types/step-registrar.d.ts +1 -1
- package/dist/unstable.d.ts +5 -9
- package/dist/unstable.js +3 -6
- package/dist/until/combinators.d.ts +1 -1
- package/dist/until/never.d.ts +1 -1
- package/dist/until/predicates.d.ts +2 -2
- package/package.json +6 -4
- package/dist/errors/noetic-config-error.d.ts +0 -41
- package/dist/errors/noetic-config-error.js +0 -44
- package/dist/errors/noetic-error.d.ts +0 -18
- package/dist/errors/noetic-error.js +0 -54
- package/dist/memory/budget.d.ts +0 -23
- package/dist/memory/budget.js +0 -117
- package/dist/memory/exec-context-factory.d.ts +0 -25
- package/dist/memory/exec-context-factory.js +0 -30
- package/dist/memory/flow-schema.d.ts +0 -52
- package/dist/memory/flow-schema.js +0 -102
- package/dist/memory/function-call-utils.d.ts +0 -6
- package/dist/memory/function-call-utils.js +0 -27
- package/dist/memory/layer-api.d.ts +0 -14
- package/dist/memory/layer-api.js +0 -91
- package/dist/memory/layer-lifecycle.d.ts +0 -160
- package/dist/memory/layer-lifecycle.js +0 -552
- package/dist/memory/layer-provides.d.ts +0 -32
- package/dist/memory/layer-provides.js +0 -33
- package/dist/memory/layer-usage.d.ts +0 -16
- package/dist/memory/layer-usage.js +0 -54
- package/dist/memory/layers/durable-task-state.d.ts +0 -79
- package/dist/memory/layers/durable-task-state.js +0 -118
- package/dist/memory/layers/file-reference.d.ts +0 -55
- package/dist/memory/layers/file-reference.js +0 -594
- package/dist/memory/layers/history-window.d.ts +0 -29
- package/dist/memory/layers/history-window.js +0 -114
- package/dist/memory/layers/observational-memory.d.ts +0 -53
- package/dist/memory/layers/observational-memory.js +0 -109
- package/dist/memory/layers/plan.d.ts +0 -55
- package/dist/memory/layers/plan.js +0 -374
- package/dist/memory/layers/static-content.d.ts +0 -27
- package/dist/memory/layers/static-content.js +0 -35
- package/dist/memory/layers/steering.d.ts +0 -51
- package/dist/memory/layers/steering.js +0 -258
- package/dist/memory/layers/temporal.d.ts +0 -91
- package/dist/memory/layers/temporal.js +0 -245
- package/dist/memory/layers/tool-memory-layer.d.ts +0 -16
- package/dist/memory/layers/tool-memory-layer.js +0 -43
- package/dist/memory/layers/working-memory.d.ts +0 -44
- package/dist/memory/layers/working-memory.js +0 -92
- package/dist/memory/projector.d.ts +0 -10
- package/dist/memory/projector.js +0 -19
- package/dist/memory/scope.d.ts +0 -3
- package/dist/memory/scope.js +0 -31
- package/dist/memory/strip-unresolved.d.ts +0 -13
- package/dist/memory/strip-unresolved.js +0 -35
- package/dist/schemas/item.d.ts +0 -35
- package/dist/schemas/item.js +0 -155
- package/dist/types/channel.d.ts +0 -29
- package/dist/types/channel.js +0 -1
- package/dist/types/common.d.ts +0 -68
- package/dist/types/common.js +0 -1
- package/dist/types/context-parts/item-log.d.ts +0 -6
- package/dist/types/context-parts/item-log.js +0 -1
- package/dist/types/context-parts/layer-usage.d.ts +0 -18
- package/dist/types/context-parts/layer-usage.js +0 -1
- package/dist/types/context.d.ts +0 -202
- package/dist/types/context.js +0 -1
- package/dist/types/detached.d.ts +0 -17
- package/dist/types/detached.js +0 -7
- package/dist/types/error.d.ts +0 -66
- package/dist/types/error.js +0 -1
- package/dist/types/fs-adapter.d.ts +0 -51
- package/dist/types/fs-adapter.js +0 -3
- package/dist/types/harness-result.d.ts +0 -43
- package/dist/types/harness-result.js +0 -2
- package/dist/types/items.d.ts +0 -131
- package/dist/types/items.js +0 -2
- package/dist/types/memory-context.d.ts +0 -39
- package/dist/types/memory-context.js +0 -1
- package/dist/types/memory.d.ts +0 -306
- package/dist/types/memory.js +0 -13
- package/dist/types/observability.d.ts +0 -30
- package/dist/types/observability.js +0 -1
- package/dist/types/runtime.d.ts +0 -256
- package/dist/types/runtime.js +0 -1
- package/dist/types/shell-adapter.d.ts +0 -34
- package/dist/types/shell-adapter.js +0 -10
- package/dist/types/steering.d.ts +0 -89
- package/dist/types/steering.js +0 -14
- package/dist/types/step.d.ts +0 -231
- package/dist/types/step.js +0 -1
- package/dist/types/subprocess-adapter.d.ts +0 -157
- package/dist/types/subprocess-adapter.js +0 -3
- package/dist/types/tool-context.d.ts +0 -34
- package/dist/types/tool-context.js +0 -1
- package/dist/types/tool.d.ts +0 -64
- package/dist/types/tool.js +0 -1
- package/dist/util/framework-cast.d.ts +0 -10
- package/dist/util/framework-cast.js +0 -16
- package/dist/util/message-helpers.d.ts +0 -17
- 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>;
|