@noetic-tools/core 0.0.1 → 0.1.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.js +1 -1
- package/dist/adapters/in-memory-subprocess/manifest-persistence.d.ts +8 -0
- package/dist/adapters/in-memory-subprocess/manifest-persistence.js +56 -0
- package/dist/adapters/in-memory-subprocess/metadata.d.ts +12 -0
- package/dist/adapters/in-memory-subprocess/metadata.js +58 -0
- package/dist/adapters/in-memory-subprocess/step-completion.d.ts +3 -0
- package/dist/adapters/in-memory-subprocess/step-completion.js +86 -0
- package/dist/adapters/in-memory-subprocess/types.d.ts +88 -0
- package/dist/adapters/in-memory-subprocess/types.js +2 -0
- package/dist/adapters/in-memory-subprocess-adapter.d.ts +12 -4
- package/dist/adapters/in-memory-subprocess-adapter.js +164 -55
- package/dist/adapters/openrouter.d.ts +11 -2
- package/dist/adapters/openrouter.js +66 -5
- package/dist/builders/control-flow-builders.js +8 -3
- package/dist/builders/every.js +4 -1
- package/dist/builders/layer-provides-builders.d.ts +1 -32
- package/dist/builders/layer-provides-builders.js +1 -33
- package/dist/builders/loop-builder.js +4 -1
- package/dist/builders/memory-builder.js +1 -1
- package/dist/builders/provide-builder.js +4 -1
- package/dist/builders/spawn-builder.d.ts +20 -7
- package/dist/builders/spawn-builder.js +16 -2
- package/dist/builders/step-builders.d.ts +49 -28
- package/dist/builders/step-builders.js +30 -11
- package/dist/builders/tool-builder.d.ts +5 -1
- package/dist/builders/tool-builder.js +2 -0
- package/dist/builders/workflow-hydrator.d.ts +38 -0
- package/dist/builders/workflow-hydrator.js +336 -0
- package/dist/conditions/conditions.js +1 -1
- package/dist/errors/noetic-error.js +2 -0
- package/dist/{runtime → harness}/agent-harness.d.ts +65 -45
- package/dist/harness/agent-harness.js +576 -0
- package/dist/harness/deps/adapters.d.ts +4 -0
- package/dist/harness/deps/adapters.js +4 -0
- package/dist/harness/deps/interpreter.d.ts +5 -0
- package/dist/harness/deps/interpreter.js +4 -0
- package/dist/harness/deps/memory.d.ts +5 -0
- package/dist/harness/deps/memory.js +4 -0
- package/dist/harness/deps/runtime.d.ts +10 -0
- package/dist/harness/deps/runtime.js +7 -0
- package/dist/harness/deps/types.d.ts +17 -0
- package/dist/harness/deps/types.js +1 -0
- package/dist/harness/model-call.d.ts +50 -0
- package/dist/harness/model-call.js +694 -0
- package/dist/harness/model-schema.d.ts +19 -0
- package/dist/harness/model-schema.js +46 -0
- package/dist/harness/step-bootstrap.d.ts +25 -0
- package/dist/harness/step-bootstrap.js +170 -0
- package/dist/index.d.ts +48 -12
- package/dist/index.js +29 -7
- package/dist/interpreter/action-deps.d.ts +12 -0
- package/dist/interpreter/action-deps.js +10 -0
- package/dist/interpreter/action-types.d.ts +8 -0
- package/dist/interpreter/action-types.js +1 -0
- package/dist/interpreter/collect-tools.d.ts +1 -1
- package/dist/interpreter/collect-tools.js +6 -2
- package/dist/interpreter/execute-action.d.ts +22 -0
- package/dist/interpreter/execute-action.js +527 -0
- package/dist/interpreter/{execute-every.d.ts → execute-control.d.ts} +7 -1
- package/dist/interpreter/execute-control.js +530 -0
- package/dist/interpreter/execute.d.ts +13 -0
- package/dist/interpreter/execute.js +151 -48
- package/dist/interpreter/message-helpers.d.ts +1 -9
- package/dist/interpreter/message-helpers.js +1 -42
- package/dist/interpreter/step-dispatcher.d.ts +61 -0
- package/dist/interpreter/step-dispatcher.js +67 -0
- package/dist/interpreter/typeguards.d.ts +3 -10
- package/dist/interpreter/typeguards.js +3 -20
- package/dist/{runtime → memory}/exec-context-factory.js +1 -1
- package/dist/memory/flow-schema.d.ts +52 -0
- package/dist/memory/flow-schema.js +102 -0
- package/dist/memory/function-call-utils.js +4 -2
- package/dist/memory/layer-api.d.ts +1 -1
- package/dist/memory/layer-api.js +2 -2
- package/dist/memory/layer-lifecycle.d.ts +1 -1
- package/dist/memory/layer-lifecycle.js +2 -2
- package/dist/memory/layer-provides.d.ts +32 -0
- package/dist/memory/layer-provides.js +33 -0
- package/dist/memory/layer-usage.d.ts +3 -2
- package/dist/memory/layer-usage.js +9 -3
- package/dist/memory/layers/durable-task-state.js +1 -1
- package/dist/memory/layers/file-reference.js +302 -246
- package/dist/memory/layers/history-window.js +1 -1
- package/dist/memory/layers/observational-memory.js +1 -2
- package/dist/memory/layers/plan.d.ts +1 -1
- package/dist/memory/layers/plan.js +13 -8
- package/dist/memory/layers/steering.js +1 -1
- package/dist/memory/layers/tool-memory-layer.d.ts +1 -1
- package/dist/memory/layers/working-memory.js +2 -2
- package/dist/patterns/dynamic-workflow.d.ts +55 -0
- package/dist/patterns/dynamic-workflow.js +194 -0
- package/dist/patterns/flow.d.ts +2 -52
- package/dist/patterns/flow.js +1 -103
- package/dist/patterns/interview.js +1 -1
- package/dist/patterns/plans.d.ts +3 -3
- package/dist/patterns/plans.js +3 -3
- package/dist/patterns/ralph-wiggum.d.ts +1 -1
- package/dist/patterns/react.d.ts +1 -1
- package/dist/portable/adapters.d.ts +6 -0
- package/dist/portable/adapters.js +6 -0
- package/dist/portable/ask-user.d.ts +4 -0
- package/dist/portable/ask-user.js +2 -0
- package/dist/portable/builders.d.ts +12 -0
- package/dist/portable/builders.js +12 -0
- package/dist/portable/memory-layers.d.ts +12 -0
- package/dist/portable/memory-layers.js +12 -0
- package/dist/portable/runtime.d.ts +4 -0
- package/dist/portable/runtime.js +4 -0
- package/dist/portable/until.d.ts +4 -0
- package/dist/portable/until.js +4 -0
- package/dist/portable.d.ts +8 -26
- package/dist/portable.js +8 -34
- package/dist/runtime/broadcaster-utils.d.ts +5 -1
- package/dist/runtime/broadcaster-utils.js +11 -6
- package/dist/runtime/channel-store.js +1 -1
- package/dist/runtime/context-deps.d.ts +4 -0
- package/dist/runtime/context-deps.js +2 -0
- package/dist/runtime/context-impl.d.ts +42 -12
- package/dist/runtime/context-impl.js +51 -4
- package/dist/runtime/context-type-deps.d.ts +14 -0
- package/dist/runtime/context-type-deps.js +1 -0
- package/dist/runtime/detached-handle.d.ts +50 -2
- package/dist/runtime/detached-handle.js +216 -12
- package/dist/runtime/durable/checkpoint-store.d.ts +38 -0
- package/dist/runtime/durable/checkpoint-store.js +101 -0
- package/dist/runtime/durable/detached-signal.d.ts +37 -0
- package/dist/runtime/durable/detached-signal.js +52 -0
- package/dist/runtime/durable/harness-checkpoints.d.ts +51 -0
- package/dist/runtime/durable/harness-checkpoints.js +104 -0
- package/dist/runtime/durable/index.d.ts +10 -0
- package/dist/runtime/durable/index.js +5 -0
- package/dist/runtime/durable/runnable-loop.d.ts +100 -0
- package/dist/runtime/durable/runnable-loop.js +90 -0
- package/dist/runtime/durable/session-seed.d.ts +28 -0
- package/dist/runtime/durable/session-seed.js +27 -0
- package/dist/runtime/durable/stall-nudge.d.ts +82 -0
- package/dist/runtime/durable/stall-nudge.js +87 -0
- package/dist/runtime/in-memory-storage.js +1 -1
- package/dist/runtime/item-log-impl.d.ts +1 -1
- package/dist/runtime/step-registry.d.ts +42 -0
- package/dist/runtime/step-registry.js +77 -0
- package/dist/schemas/workflow.d.ts +139 -0
- package/dist/schemas/workflow.js +232 -0
- package/dist/types/ask-user-types.d.ts +57 -0
- package/dist/types/ask-user-types.js +74 -0
- package/dist/types/checkpoint.d.ts +98 -0
- package/dist/types/checkpoint.js +81 -0
- package/dist/types/common.d.ts +1 -50
- package/dist/types/context-parts/item-log.d.ts +6 -0
- package/dist/types/context-parts/item-log.js +1 -0
- package/dist/types/context-parts/layer-usage.d.ts +18 -0
- package/dist/types/context-parts/layer-usage.js +1 -0
- package/dist/types/context.d.ts +135 -26
- package/dist/types/error.d.ts +15 -0
- package/dist/types/harness-result.d.ts +1 -1
- package/dist/types/items.d.ts +18 -2
- package/dist/types/memory-context.d.ts +39 -0
- package/dist/types/memory-context.js +1 -0
- package/dist/types/memory.d.ts +35 -65
- package/dist/types/runtime.d.ts +2 -1
- package/dist/types/steering.d.ts +1 -1
- package/dist/types/step-registrar.d.ts +46 -0
- package/dist/types/step-registrar.js +56 -0
- package/dist/types/step.d.ts +30 -5
- package/dist/types/subprocess-adapter.d.ts +119 -3
- package/dist/types/subprocess-adapter.js +2 -0
- package/dist/types/tool.d.ts +64 -0
- package/dist/types/tool.js +1 -0
- package/dist/unstable.d.ts +2 -2
- package/dist/unstable.js +2 -2
- package/dist/until/predicates.d.ts +9 -0
- package/dist/until/predicates.js +14 -0
- package/dist/util/message-helpers.d.ts +17 -0
- package/dist/util/message-helpers.js +64 -0
- package/package.json +1 -1
- package/dist/adapters/local-fs-adapter.d.ts +0 -3
- package/dist/adapters/local-fs-adapter.js +0 -43
- package/dist/adapters/local-shell-adapter.d.ts +0 -51
- package/dist/adapters/local-shell-adapter.js +0 -167
- package/dist/adapters/local-subprocess-adapter.d.ts +0 -16
- package/dist/adapters/local-subprocess-adapter.js +0 -187
- package/dist/interpreter/execute-branch.d.ts +0 -3
- package/dist/interpreter/execute-branch.js +0 -10
- package/dist/interpreter/execute-every.js +0 -143
- package/dist/interpreter/execute-fork.d.ts +0 -3
- package/dist/interpreter/execute-fork.js +0 -215
- package/dist/interpreter/execute-llm.d.ts +0 -4
- package/dist/interpreter/execute-llm.js +0 -217
- package/dist/interpreter/execute-loop.d.ts +0 -3
- package/dist/interpreter/execute-loop.js +0 -171
- package/dist/interpreter/execute-provide.d.ts +0 -10
- package/dist/interpreter/execute-provide.js +0 -64
- package/dist/interpreter/execute-run.d.ts +0 -3
- package/dist/interpreter/execute-run.js +0 -39
- package/dist/interpreter/execute-spawn.d.ts +0 -11
- package/dist/interpreter/execute-spawn.js +0 -113
- package/dist/interpreter/execute-tool.d.ts +0 -5
- package/dist/interpreter/execute-tool.js +0 -81
- package/dist/runtime/agent-harness.js +0 -1170
- /package/dist/{runtime → memory}/exec-context-factory.d.ts +0 -0
- /package/dist/{interpreter → util}/framework-cast.d.ts +0 -0
- /package/dist/{interpreter → util}/framework-cast.js +0 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { createMessage, estimateTokens } from '../../interpreter/message-helpers';
|
|
2
1
|
import { Slot } from '../../types/memory';
|
|
2
|
+
import { createMessage, estimateTokens } from '../../util/message-helpers';
|
|
3
3
|
/** Default max file size: 1MB */
|
|
4
4
|
const DEFAULT_MAX_FILE_SIZE = 1024 * 1024;
|
|
5
5
|
/** Default allowed extensions */
|
|
@@ -122,12 +122,20 @@ function simpleHash(content) {
|
|
|
122
122
|
// Include content length to reduce collision probability
|
|
123
123
|
return `${content.length}:${hash.toString(16)}`;
|
|
124
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
|
+
}
|
|
125
133
|
function isInputMessage(item) {
|
|
126
134
|
return (item.type === 'message' &&
|
|
127
135
|
'role' in item &&
|
|
128
136
|
'content' in item &&
|
|
129
137
|
Array.isArray(item.content) &&
|
|
130
|
-
item.content.every(
|
|
138
|
+
item.content.every(isInputTextPart));
|
|
131
139
|
}
|
|
132
140
|
function extractTextFromItem(item) {
|
|
133
141
|
if (!isInputMessage(item)) {
|
|
@@ -264,6 +272,293 @@ Score:`;
|
|
|
264
272
|
return 50;
|
|
265
273
|
}
|
|
266
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
|
+
}
|
|
267
562
|
//#endregion
|
|
268
563
|
//#region Layer Factory
|
|
269
564
|
/**
|
|
@@ -281,257 +576,18 @@ Score:`;
|
|
|
281
576
|
* @public
|
|
282
577
|
*/
|
|
283
578
|
export function fileReference(opts) {
|
|
284
|
-
const
|
|
285
|
-
const slot = opts?.slot ?? Slot.RAG;
|
|
286
|
-
const scoringModel = opts?.scoringModel ?? 'anthropic/claude-haiku-4-5-20251001';
|
|
287
|
-
const maxFileSize = opts?.maxFileSize ?? DEFAULT_MAX_FILE_SIZE;
|
|
288
|
-
const followSymlinks = opts?.followSymlinks ?? false;
|
|
289
|
-
const allowedExtensions = opts?.allowedExtensions ?? DEFAULT_ALLOWED_EXTENSIONS;
|
|
290
|
-
// Pre-compute read options for reuse
|
|
291
|
-
const readOpts = {
|
|
292
|
-
maxFileSize,
|
|
293
|
-
followSymlinks,
|
|
294
|
-
baseDir,
|
|
295
|
-
allowedExtensions,
|
|
296
|
-
};
|
|
579
|
+
const runtime = createFileReferenceRuntime(opts);
|
|
297
580
|
return {
|
|
298
581
|
id: 'file-reference',
|
|
299
582
|
name: 'File Reference',
|
|
300
|
-
slot,
|
|
583
|
+
slot: runtime.slot,
|
|
301
584
|
scope: 'thread',
|
|
302
585
|
budget: 'auto',
|
|
303
586
|
rerenderTiming: 'immediate',
|
|
304
587
|
hooks: {
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
files: new Map(),
|
|
309
|
-
baseDir,
|
|
310
|
-
},
|
|
311
|
-
};
|
|
312
|
-
},
|
|
313
|
-
async onItemAppend({ items, ctx, state }) {
|
|
314
|
-
const newFiles = new Map(state.files);
|
|
315
|
-
let hasChanges = false;
|
|
316
|
-
let userQuery = '';
|
|
317
|
-
// Update readOpts with current state's baseDir
|
|
318
|
-
const currentReadOpts = {
|
|
319
|
-
...readOpts,
|
|
320
|
-
baseDir: state.baseDir,
|
|
321
|
-
};
|
|
322
|
-
// Process each item
|
|
323
|
-
const transformedItems = [];
|
|
324
|
-
for (const item of items) {
|
|
325
|
-
const text = extractTextFromItem(item);
|
|
326
|
-
if (!text) {
|
|
327
|
-
transformedItems.push(item);
|
|
328
|
-
continue;
|
|
329
|
-
}
|
|
330
|
-
// Extract user query for scoring
|
|
331
|
-
if (item.type === 'message' && 'role' in item && item.role === 'user') {
|
|
332
|
-
userQuery = text;
|
|
333
|
-
}
|
|
334
|
-
// Extract file references
|
|
335
|
-
const refs = extractFileReferences(text);
|
|
336
|
-
// Process new references
|
|
337
|
-
for (const ref of refs) {
|
|
338
|
-
if (!newFiles.has(ref)) {
|
|
339
|
-
// Security: Reject absolute paths - all paths must be relative to baseDir
|
|
340
|
-
if (isAbsolutePath(ref)) {
|
|
341
|
-
newFiles.set(ref, {
|
|
342
|
-
absolutePath: ref,
|
|
343
|
-
referencePath: ref,
|
|
344
|
-
content: null,
|
|
345
|
-
priority: 0,
|
|
346
|
-
contentHash: '',
|
|
347
|
-
deleted: false,
|
|
348
|
-
tokenCount: 0,
|
|
349
|
-
error: 'ABSOLUTE_PATH: Only relative paths are allowed',
|
|
350
|
-
});
|
|
351
|
-
hasChanges = true;
|
|
352
|
-
continue;
|
|
353
|
-
}
|
|
354
|
-
const absolutePath = resolvePath(state.baseDir, ref);
|
|
355
|
-
const result = await readFileContent(absolutePath, currentReadOpts, ctx.fs);
|
|
356
|
-
const priority = result.content
|
|
357
|
-
? await scoreFileRelevance({
|
|
358
|
-
filePath: ref,
|
|
359
|
-
fileContent: result.content,
|
|
360
|
-
userQuery,
|
|
361
|
-
ctx,
|
|
362
|
-
model: scoringModel,
|
|
363
|
-
})
|
|
364
|
-
: 0;
|
|
365
|
-
newFiles.set(ref, {
|
|
366
|
-
absolutePath,
|
|
367
|
-
referencePath: ref,
|
|
368
|
-
content: result.content,
|
|
369
|
-
priority,
|
|
370
|
-
contentHash: result.content ? simpleHash(result.content) : '',
|
|
371
|
-
deleted: result.deleted,
|
|
372
|
-
tokenCount: result.content ? estimateTokens(result.content) : 0,
|
|
373
|
-
error: result.error,
|
|
374
|
-
});
|
|
375
|
-
hasChanges = true;
|
|
376
|
-
}
|
|
377
|
-
}
|
|
378
|
-
// Transform references to anchor links
|
|
379
|
-
if (refs.length > 0 && isInputMessage(item)) {
|
|
380
|
-
const transformedContent = item.content.map((c) => {
|
|
381
|
-
const part = {
|
|
382
|
-
type: 'input_text',
|
|
383
|
-
text: transformReferencesToAnchors(c.text),
|
|
384
|
-
};
|
|
385
|
-
return part;
|
|
386
|
-
});
|
|
387
|
-
const transformedItem = {
|
|
388
|
-
...item,
|
|
389
|
-
content: transformedContent,
|
|
390
|
-
};
|
|
391
|
-
transformedItems.push(transformedItem);
|
|
392
|
-
}
|
|
393
|
-
else {
|
|
394
|
-
transformedItems.push(item);
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
// Check existing files for changes
|
|
398
|
-
for (const [ref, tracked] of newFiles) {
|
|
399
|
-
// Skip files with errors - they won't change
|
|
400
|
-
if (tracked.error) {
|
|
401
|
-
continue;
|
|
402
|
-
}
|
|
403
|
-
const result = await readFileContent(tracked.absolutePath, currentReadOpts, ctx.fs);
|
|
404
|
-
if (result.deleted && !tracked.deleted) {
|
|
405
|
-
// File was deleted
|
|
406
|
-
newFiles.set(ref, {
|
|
407
|
-
...tracked,
|
|
408
|
-
content: null,
|
|
409
|
-
deleted: true,
|
|
410
|
-
});
|
|
411
|
-
hasChanges = true;
|
|
412
|
-
}
|
|
413
|
-
else if (result.error && !tracked.error) {
|
|
414
|
-
// File now has error (e.g., permission changed)
|
|
415
|
-
newFiles.set(ref, {
|
|
416
|
-
...tracked,
|
|
417
|
-
content: null,
|
|
418
|
-
error: result.error,
|
|
419
|
-
});
|
|
420
|
-
hasChanges = true;
|
|
421
|
-
}
|
|
422
|
-
else if (!result.deleted && result.content) {
|
|
423
|
-
const newHash = simpleHash(result.content);
|
|
424
|
-
if (newHash !== tracked.contentHash) {
|
|
425
|
-
// File content changed
|
|
426
|
-
const priority = userQuery
|
|
427
|
-
? await scoreFileRelevance({
|
|
428
|
-
filePath: ref,
|
|
429
|
-
fileContent: result.content,
|
|
430
|
-
userQuery,
|
|
431
|
-
ctx,
|
|
432
|
-
model: scoringModel,
|
|
433
|
-
})
|
|
434
|
-
: tracked.priority;
|
|
435
|
-
newFiles.set(ref, {
|
|
436
|
-
...tracked,
|
|
437
|
-
content: result.content,
|
|
438
|
-
contentHash: newHash,
|
|
439
|
-
deleted: false,
|
|
440
|
-
tokenCount: estimateTokens(result.content),
|
|
441
|
-
priority,
|
|
442
|
-
error: undefined,
|
|
443
|
-
});
|
|
444
|
-
hasChanges = true;
|
|
445
|
-
}
|
|
446
|
-
}
|
|
447
|
-
}
|
|
448
|
-
return {
|
|
449
|
-
items: transformedItems,
|
|
450
|
-
state: {
|
|
451
|
-
...state,
|
|
452
|
-
files: newFiles,
|
|
453
|
-
},
|
|
454
|
-
rerender: hasChanges,
|
|
455
|
-
timing: 'immediate',
|
|
456
|
-
scope: 'self',
|
|
457
|
-
};
|
|
458
|
-
},
|
|
459
|
-
async recall({ state, budget }) {
|
|
460
|
-
if (state.files.size === 0) {
|
|
461
|
-
return {
|
|
462
|
-
items: [],
|
|
463
|
-
tokenCount: 0,
|
|
464
|
-
};
|
|
465
|
-
}
|
|
466
|
-
// Sort files by priority (highest first)
|
|
467
|
-
const sortedFiles = [
|
|
468
|
-
...state.files.values(),
|
|
469
|
-
].sort((a, b) => b.priority - a.priority);
|
|
470
|
-
// Build content blocks within budget
|
|
471
|
-
const blocks = [];
|
|
472
|
-
let totalTokens = 0;
|
|
473
|
-
for (const file of sortedFiles) {
|
|
474
|
-
// Handle deleted files
|
|
475
|
-
if (file.deleted) {
|
|
476
|
-
const block = `## ${file.referencePath}\n\n[FILE DELETED: ${file.absolutePath}]`;
|
|
477
|
-
const tokens = estimateTokens(block);
|
|
478
|
-
if (totalTokens + tokens <= budget) {
|
|
479
|
-
blocks.push(block);
|
|
480
|
-
totalTokens += tokens;
|
|
481
|
-
}
|
|
482
|
-
continue;
|
|
483
|
-
}
|
|
484
|
-
// Handle files with errors (security, size, permission)
|
|
485
|
-
if (file.error) {
|
|
486
|
-
const block = `## ${file.referencePath}\n\n[ERROR: ${file.error}]`;
|
|
487
|
-
const tokens = estimateTokens(block);
|
|
488
|
-
if (totalTokens + tokens <= budget) {
|
|
489
|
-
blocks.push(block);
|
|
490
|
-
totalTokens += tokens;
|
|
491
|
-
}
|
|
492
|
-
continue;
|
|
493
|
-
}
|
|
494
|
-
if (!file.content) {
|
|
495
|
-
continue;
|
|
496
|
-
}
|
|
497
|
-
const header = `## ${file.referencePath}`;
|
|
498
|
-
const headerTokens = estimateTokens(header);
|
|
499
|
-
if (totalTokens + headerTokens >= budget) {
|
|
500
|
-
// Can't fit even the header
|
|
501
|
-
break;
|
|
502
|
-
}
|
|
503
|
-
const remainingBudget = budget - totalTokens - headerTokens - 10; // Buffer for formatting
|
|
504
|
-
let contentToInclude = file.content;
|
|
505
|
-
if (file.tokenCount > remainingBudget) {
|
|
506
|
-
// Truncate content - show head + tail
|
|
507
|
-
const lines = file.content.split('\n');
|
|
508
|
-
const headLines = Math.floor(lines.length * 0.6);
|
|
509
|
-
const tailLines = Math.floor(lines.length * 0.3);
|
|
510
|
-
const head = lines.slice(0, headLines).join('\n');
|
|
511
|
-
const tail = lines.slice(-tailLines).join('\n');
|
|
512
|
-
contentToInclude = `${head}\n\n... [truncated ${lines.length - headLines - tailLines} lines] ...\n\n${tail}`;
|
|
513
|
-
}
|
|
514
|
-
const block = `${header}\n\n\`\`\`\n${contentToInclude}\n\`\`\``;
|
|
515
|
-
const blockTokens = estimateTokens(block);
|
|
516
|
-
if (totalTokens + blockTokens <= budget) {
|
|
517
|
-
blocks.push(block);
|
|
518
|
-
totalTokens += blockTokens;
|
|
519
|
-
}
|
|
520
|
-
}
|
|
521
|
-
if (blocks.length === 0) {
|
|
522
|
-
return {
|
|
523
|
-
items: [],
|
|
524
|
-
tokenCount: 0,
|
|
525
|
-
};
|
|
526
|
-
}
|
|
527
|
-
const content = `# Referenced Files\n\n${blocks.join('\n\n')}`;
|
|
528
|
-
return {
|
|
529
|
-
items: [
|
|
530
|
-
createMessage(content, 'developer'),
|
|
531
|
-
],
|
|
532
|
-
tokenCount: estimateTokens(content),
|
|
533
|
-
};
|
|
534
|
-
},
|
|
588
|
+
init: () => initFileReferenceState(runtime.baseDir),
|
|
589
|
+
onItemAppend: (args) => onFileReferenceItemAppend(args, runtime),
|
|
590
|
+
recall: recallFileReferences,
|
|
535
591
|
},
|
|
536
592
|
};
|
|
537
593
|
}
|
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
* slice boundary via `stripUnresolvedToolCalls`.
|
|
14
14
|
*/
|
|
15
15
|
import { NoeticConfigError } from '../../errors/noetic-config-error';
|
|
16
|
-
import { isAssistantMessage, isUserMessage } from '../../interpreter/typeguards';
|
|
17
16
|
import { Slot } from '../../types/memory';
|
|
17
|
+
import { isAssistantMessage, isUserMessage } from '../../util/message-helpers';
|
|
18
18
|
import { stripUnresolvedToolCalls } from '../strip-unresolved';
|
|
19
19
|
//#region Constants
|
|
20
20
|
const DEFAULT_MAX_ITEMS = 40;
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { createMessage, estimateTokens } from '../../interpreter/message-helpers';
|
|
2
|
-
import { isOutputText } from '../../interpreter/typeguards';
|
|
3
1
|
import { Slot } from '../../types/memory';
|
|
2
|
+
import { createMessage, estimateTokens, isOutputText } from '../../util/message-helpers';
|
|
4
3
|
const DEFAULT_BUFFER_THRESHOLD_TOKENS = 2_000;
|
|
5
4
|
const DEFAULT_MAX_OBSERVATIONS = 50;
|
|
6
5
|
/**
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import { layerData, layerFn } from '../../builders/layer-provides-builders';
|
|
3
|
-
import { createMessage, estimateTokens } from '../../interpreter/message-helpers';
|
|
4
|
-
import { FlowSchema, flowDepth } from '../../patterns/flow';
|
|
5
2
|
import { Slot } from '../../types/memory';
|
|
6
3
|
import { SteeringAction } from '../../types/steering';
|
|
4
|
+
import { createMessage, estimateTokens } from '../../util/message-helpers';
|
|
5
|
+
import { FlowSchema, flowDepth } from '../flow-schema';
|
|
6
|
+
import { layerData, layerFn } from '../layer-provides';
|
|
7
7
|
//#region Constants
|
|
8
8
|
const MAX_PRD_LENGTH = 5e4;
|
|
9
9
|
const MAX_TREE_DEPTH = 5;
|
|
@@ -14,7 +14,12 @@ const ALLOWED_TOOLS_IN_PLAN_MODE = new Set([
|
|
|
14
14
|
'Grep',
|
|
15
15
|
'Find',
|
|
16
16
|
'Ls',
|
|
17
|
+
'AskUserQuestion',
|
|
17
18
|
'activateSkill',
|
|
19
|
+
'agent',
|
|
20
|
+
'checkAgent',
|
|
21
|
+
'sendMessage',
|
|
22
|
+
'requestPlanApproval',
|
|
18
23
|
'plan/enterPlanMode',
|
|
19
24
|
'plan/updatePrd',
|
|
20
25
|
'plan/setPlanTree',
|
|
@@ -63,15 +68,15 @@ function trimExecutionLog(log) {
|
|
|
63
68
|
function recallPlanning(state, additionalInstructions) {
|
|
64
69
|
const sections = [
|
|
65
70
|
'<plan_mode>',
|
|
66
|
-
'You are in PLAN MODE. You may only use read-only tools (Read, Grep, Find, Ls) to explore the codebase.',
|
|
71
|
+
'You are in PLAN MODE. You may only use read-only tools (Read, Grep, Find, Ls), AskUserQuestion, skill activation, sub-agent coordination tools, and requestPlanApproval to explore the codebase and request execution approval.',
|
|
67
72
|
'Your goal is to produce a PRD document and (optionally) a structured execution plan.',
|
|
68
73
|
'',
|
|
69
74
|
'## Workflow (5 phases)',
|
|
70
75
|
'',
|
|
71
|
-
'1. **Initial Understanding** — Read code and gather context. To parallelise exploration,
|
|
72
|
-
'2. **Design** — Synthesise findings. Optionally
|
|
76
|
+
'1. **Initial Understanding** — Read code and gather context. To parallelise exploration, use the `agent` tool to spawn bounded read-only sub-agents; up to 3 in parallel. Each sub-agent returns a focused report.',
|
|
77
|
+
'2. **Design** — Synthesise findings. Optionally spawn planning sub-agents (1–3 in parallel) to draft alternative implementation approaches and surface trade-offs.',
|
|
73
78
|
'3. **Review** — Read the critical files identified by your subagents directly so you understand them first-hand. If anything is ambiguous, ask the user a focused question.',
|
|
74
|
-
'4. **Final Plan** — Write the PRD via `plan/updatePrd`. Lead with a **Context** section (why this change), then your single recommended approach, the paths of files to modify, existing functions/utilities to reuse, and a **Verification** section.
|
|
79
|
+
'4. **Final Plan** — Write the PRD via `plan/updatePrd`. Lead with a **Context** section (why this change), then your single recommended approach, the paths of files to modify, existing functions/utilities to reuse, and a **Verification** section. Then call `plan/setPlanTree` with a JSON noetic flow built from the existing step primitives (`step.llm`, `step.branch`, `step.fork`, `step.spawn`, `step.loop`) — tool refs by name string.',
|
|
75
80
|
'5. **Exit** — Call `plan/exitPlanMode` with `{ action: "execute" }` to request approval. The user must accept before execution begins; if they reject, you stay in Plan Mode and may revise.',
|
|
76
81
|
'',
|
|
77
82
|
'## Available actions',
|
|
@@ -81,7 +86,7 @@ function recallPlanning(state, additionalInstructions) {
|
|
|
81
86
|
'- `plan/exitPlanMode` `{ action: "cancel" }` — discard plan and return to idle',
|
|
82
87
|
'',
|
|
83
88
|
'## Constraints',
|
|
84
|
-
'- DO NOT create, modify, or delete files (other than via `plan/updatePrd`).',
|
|
89
|
+
'- DO NOT create, modify, or delete files (other than via `plan/updatePrd` and `plan/setPlanTree`).',
|
|
85
90
|
'- DO NOT run mutating shell commands. Read-only exploration only.',
|
|
86
91
|
'- End each turn either by asking the user a focused clarifying question or by calling `plan/exitPlanMode`.',
|
|
87
92
|
];
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { NoeticConfigError } from '../../errors/noetic-config-error';
|
|
2
|
-
import { createMessage, estimateTokens, extractAssistantText, } from '../../interpreter/message-helpers';
|
|
3
2
|
import { Slot } from '../../types/memory';
|
|
4
3
|
import { LedgerEntryKind, SteeringAction } from '../../types/steering';
|
|
4
|
+
import { createMessage, estimateTokens, extractAssistantText } from '../../util/message-helpers';
|
|
5
5
|
import { mostRestrictive } from '../layer-lifecycle';
|
|
6
6
|
//#region Constants
|
|
7
7
|
const DEFAULT_MAX_LEDGER_ENTRIES = 100;
|