@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.
Files changed (202) hide show
  1. package/dist/adapters/in-memory-fs-adapter.js +1 -1
  2. package/dist/adapters/in-memory-subprocess/manifest-persistence.d.ts +8 -0
  3. package/dist/adapters/in-memory-subprocess/manifest-persistence.js +56 -0
  4. package/dist/adapters/in-memory-subprocess/metadata.d.ts +12 -0
  5. package/dist/adapters/in-memory-subprocess/metadata.js +58 -0
  6. package/dist/adapters/in-memory-subprocess/step-completion.d.ts +3 -0
  7. package/dist/adapters/in-memory-subprocess/step-completion.js +86 -0
  8. package/dist/adapters/in-memory-subprocess/types.d.ts +88 -0
  9. package/dist/adapters/in-memory-subprocess/types.js +2 -0
  10. package/dist/adapters/in-memory-subprocess-adapter.d.ts +12 -4
  11. package/dist/adapters/in-memory-subprocess-adapter.js +164 -55
  12. package/dist/adapters/openrouter.d.ts +11 -2
  13. package/dist/adapters/openrouter.js +66 -5
  14. package/dist/builders/control-flow-builders.js +8 -3
  15. package/dist/builders/every.js +4 -1
  16. package/dist/builders/layer-provides-builders.d.ts +1 -32
  17. package/dist/builders/layer-provides-builders.js +1 -33
  18. package/dist/builders/loop-builder.js +4 -1
  19. package/dist/builders/memory-builder.js +1 -1
  20. package/dist/builders/provide-builder.js +4 -1
  21. package/dist/builders/spawn-builder.d.ts +20 -7
  22. package/dist/builders/spawn-builder.js +16 -2
  23. package/dist/builders/step-builders.d.ts +49 -28
  24. package/dist/builders/step-builders.js +30 -11
  25. package/dist/builders/tool-builder.d.ts +5 -1
  26. package/dist/builders/tool-builder.js +2 -0
  27. package/dist/builders/workflow-hydrator.d.ts +38 -0
  28. package/dist/builders/workflow-hydrator.js +336 -0
  29. package/dist/conditions/conditions.js +1 -1
  30. package/dist/errors/noetic-error.js +2 -0
  31. package/dist/{runtime → harness}/agent-harness.d.ts +65 -45
  32. package/dist/harness/agent-harness.js +576 -0
  33. package/dist/harness/deps/adapters.d.ts +4 -0
  34. package/dist/harness/deps/adapters.js +4 -0
  35. package/dist/harness/deps/interpreter.d.ts +5 -0
  36. package/dist/harness/deps/interpreter.js +4 -0
  37. package/dist/harness/deps/memory.d.ts +5 -0
  38. package/dist/harness/deps/memory.js +4 -0
  39. package/dist/harness/deps/runtime.d.ts +10 -0
  40. package/dist/harness/deps/runtime.js +7 -0
  41. package/dist/harness/deps/types.d.ts +17 -0
  42. package/dist/harness/deps/types.js +1 -0
  43. package/dist/harness/model-call.d.ts +50 -0
  44. package/dist/harness/model-call.js +694 -0
  45. package/dist/harness/model-schema.d.ts +19 -0
  46. package/dist/harness/model-schema.js +46 -0
  47. package/dist/harness/step-bootstrap.d.ts +25 -0
  48. package/dist/harness/step-bootstrap.js +170 -0
  49. package/dist/index.d.ts +48 -12
  50. package/dist/index.js +29 -7
  51. package/dist/interpreter/action-deps.d.ts +12 -0
  52. package/dist/interpreter/action-deps.js +10 -0
  53. package/dist/interpreter/action-types.d.ts +8 -0
  54. package/dist/interpreter/action-types.js +1 -0
  55. package/dist/interpreter/collect-tools.d.ts +1 -1
  56. package/dist/interpreter/collect-tools.js +6 -2
  57. package/dist/interpreter/execute-action.d.ts +22 -0
  58. package/dist/interpreter/execute-action.js +527 -0
  59. package/dist/interpreter/{execute-every.d.ts → execute-control.d.ts} +7 -1
  60. package/dist/interpreter/execute-control.js +530 -0
  61. package/dist/interpreter/execute.d.ts +13 -0
  62. package/dist/interpreter/execute.js +151 -48
  63. package/dist/interpreter/message-helpers.d.ts +1 -9
  64. package/dist/interpreter/message-helpers.js +1 -42
  65. package/dist/interpreter/step-dispatcher.d.ts +61 -0
  66. package/dist/interpreter/step-dispatcher.js +67 -0
  67. package/dist/interpreter/typeguards.d.ts +3 -10
  68. package/dist/interpreter/typeguards.js +3 -20
  69. package/dist/{runtime → memory}/exec-context-factory.js +1 -1
  70. package/dist/memory/flow-schema.d.ts +52 -0
  71. package/dist/memory/flow-schema.js +102 -0
  72. package/dist/memory/function-call-utils.js +4 -2
  73. package/dist/memory/layer-api.d.ts +1 -1
  74. package/dist/memory/layer-api.js +2 -2
  75. package/dist/memory/layer-lifecycle.d.ts +1 -1
  76. package/dist/memory/layer-lifecycle.js +2 -2
  77. package/dist/memory/layer-provides.d.ts +32 -0
  78. package/dist/memory/layer-provides.js +33 -0
  79. package/dist/memory/layer-usage.d.ts +3 -2
  80. package/dist/memory/layer-usage.js +9 -3
  81. package/dist/memory/layers/durable-task-state.js +1 -1
  82. package/dist/memory/layers/file-reference.js +302 -246
  83. package/dist/memory/layers/history-window.js +1 -1
  84. package/dist/memory/layers/observational-memory.js +1 -2
  85. package/dist/memory/layers/plan.d.ts +1 -1
  86. package/dist/memory/layers/plan.js +13 -8
  87. package/dist/memory/layers/steering.js +1 -1
  88. package/dist/memory/layers/tool-memory-layer.d.ts +1 -1
  89. package/dist/memory/layers/working-memory.js +2 -2
  90. package/dist/patterns/dynamic-workflow.d.ts +55 -0
  91. package/dist/patterns/dynamic-workflow.js +194 -0
  92. package/dist/patterns/flow.d.ts +2 -52
  93. package/dist/patterns/flow.js +1 -103
  94. package/dist/patterns/interview.js +1 -1
  95. package/dist/patterns/plans.d.ts +3 -3
  96. package/dist/patterns/plans.js +3 -3
  97. package/dist/patterns/ralph-wiggum.d.ts +1 -1
  98. package/dist/patterns/react.d.ts +1 -1
  99. package/dist/portable/adapters.d.ts +6 -0
  100. package/dist/portable/adapters.js +6 -0
  101. package/dist/portable/ask-user.d.ts +4 -0
  102. package/dist/portable/ask-user.js +2 -0
  103. package/dist/portable/builders.d.ts +12 -0
  104. package/dist/portable/builders.js +12 -0
  105. package/dist/portable/memory-layers.d.ts +12 -0
  106. package/dist/portable/memory-layers.js +12 -0
  107. package/dist/portable/runtime.d.ts +4 -0
  108. package/dist/portable/runtime.js +4 -0
  109. package/dist/portable/until.d.ts +4 -0
  110. package/dist/portable/until.js +4 -0
  111. package/dist/portable.d.ts +8 -26
  112. package/dist/portable.js +8 -34
  113. package/dist/runtime/broadcaster-utils.d.ts +5 -1
  114. package/dist/runtime/broadcaster-utils.js +11 -6
  115. package/dist/runtime/channel-store.js +1 -1
  116. package/dist/runtime/context-deps.d.ts +4 -0
  117. package/dist/runtime/context-deps.js +2 -0
  118. package/dist/runtime/context-impl.d.ts +42 -12
  119. package/dist/runtime/context-impl.js +51 -4
  120. package/dist/runtime/context-type-deps.d.ts +14 -0
  121. package/dist/runtime/context-type-deps.js +1 -0
  122. package/dist/runtime/detached-handle.d.ts +50 -2
  123. package/dist/runtime/detached-handle.js +216 -12
  124. package/dist/runtime/durable/checkpoint-store.d.ts +38 -0
  125. package/dist/runtime/durable/checkpoint-store.js +101 -0
  126. package/dist/runtime/durable/detached-signal.d.ts +37 -0
  127. package/dist/runtime/durable/detached-signal.js +52 -0
  128. package/dist/runtime/durable/harness-checkpoints.d.ts +51 -0
  129. package/dist/runtime/durable/harness-checkpoints.js +104 -0
  130. package/dist/runtime/durable/index.d.ts +10 -0
  131. package/dist/runtime/durable/index.js +5 -0
  132. package/dist/runtime/durable/runnable-loop.d.ts +100 -0
  133. package/dist/runtime/durable/runnable-loop.js +90 -0
  134. package/dist/runtime/durable/session-seed.d.ts +28 -0
  135. package/dist/runtime/durable/session-seed.js +27 -0
  136. package/dist/runtime/durable/stall-nudge.d.ts +82 -0
  137. package/dist/runtime/durable/stall-nudge.js +87 -0
  138. package/dist/runtime/in-memory-storage.js +1 -1
  139. package/dist/runtime/item-log-impl.d.ts +1 -1
  140. package/dist/runtime/step-registry.d.ts +42 -0
  141. package/dist/runtime/step-registry.js +77 -0
  142. package/dist/schemas/workflow.d.ts +139 -0
  143. package/dist/schemas/workflow.js +232 -0
  144. package/dist/types/ask-user-types.d.ts +57 -0
  145. package/dist/types/ask-user-types.js +74 -0
  146. package/dist/types/checkpoint.d.ts +98 -0
  147. package/dist/types/checkpoint.js +81 -0
  148. package/dist/types/common.d.ts +1 -50
  149. package/dist/types/context-parts/item-log.d.ts +6 -0
  150. package/dist/types/context-parts/item-log.js +1 -0
  151. package/dist/types/context-parts/layer-usage.d.ts +18 -0
  152. package/dist/types/context-parts/layer-usage.js +1 -0
  153. package/dist/types/context.d.ts +135 -26
  154. package/dist/types/error.d.ts +15 -0
  155. package/dist/types/harness-result.d.ts +1 -1
  156. package/dist/types/items.d.ts +18 -2
  157. package/dist/types/memory-context.d.ts +39 -0
  158. package/dist/types/memory-context.js +1 -0
  159. package/dist/types/memory.d.ts +35 -65
  160. package/dist/types/runtime.d.ts +2 -1
  161. package/dist/types/steering.d.ts +1 -1
  162. package/dist/types/step-registrar.d.ts +46 -0
  163. package/dist/types/step-registrar.js +56 -0
  164. package/dist/types/step.d.ts +30 -5
  165. package/dist/types/subprocess-adapter.d.ts +119 -3
  166. package/dist/types/subprocess-adapter.js +2 -0
  167. package/dist/types/tool.d.ts +64 -0
  168. package/dist/types/tool.js +1 -0
  169. package/dist/unstable.d.ts +2 -2
  170. package/dist/unstable.js +2 -2
  171. package/dist/until/predicates.d.ts +9 -0
  172. package/dist/until/predicates.js +14 -0
  173. package/dist/util/message-helpers.d.ts +17 -0
  174. package/dist/util/message-helpers.js +64 -0
  175. package/package.json +1 -1
  176. package/dist/adapters/local-fs-adapter.d.ts +0 -3
  177. package/dist/adapters/local-fs-adapter.js +0 -43
  178. package/dist/adapters/local-shell-adapter.d.ts +0 -51
  179. package/dist/adapters/local-shell-adapter.js +0 -167
  180. package/dist/adapters/local-subprocess-adapter.d.ts +0 -16
  181. package/dist/adapters/local-subprocess-adapter.js +0 -187
  182. package/dist/interpreter/execute-branch.d.ts +0 -3
  183. package/dist/interpreter/execute-branch.js +0 -10
  184. package/dist/interpreter/execute-every.js +0 -143
  185. package/dist/interpreter/execute-fork.d.ts +0 -3
  186. package/dist/interpreter/execute-fork.js +0 -215
  187. package/dist/interpreter/execute-llm.d.ts +0 -4
  188. package/dist/interpreter/execute-llm.js +0 -217
  189. package/dist/interpreter/execute-loop.d.ts +0 -3
  190. package/dist/interpreter/execute-loop.js +0 -171
  191. package/dist/interpreter/execute-provide.d.ts +0 -10
  192. package/dist/interpreter/execute-provide.js +0 -64
  193. package/dist/interpreter/execute-run.d.ts +0 -3
  194. package/dist/interpreter/execute-run.js +0 -39
  195. package/dist/interpreter/execute-spawn.d.ts +0 -11
  196. package/dist/interpreter/execute-spawn.js +0 -113
  197. package/dist/interpreter/execute-tool.d.ts +0 -5
  198. package/dist/interpreter/execute-tool.js +0 -81
  199. package/dist/runtime/agent-harness.js +0 -1170
  200. /package/dist/{runtime → memory}/exec-context-factory.d.ts +0 -0
  201. /package/dist/{interpreter → util}/framework-cast.d.ts +0 -0
  202. /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((c) => typeof c === 'object' && c !== null && 'type' in c && c.type === 'input_text'));
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 baseDir = opts?.baseDir ?? currentWorkingDirectory();
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
- async init() {
306
- return {
307
- state: {
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,5 +1,5 @@
1
- import type { FlowNode } from '../../patterns/flow';
2
1
  import type { MemoryLayer, MemoryScope } from '../../types/memory';
2
+ import type { FlowNode } from '../flow-schema';
3
3
  export declare const PlanPhase: {
4
4
  readonly Idle: "idle";
5
5
  readonly Planning: "planning";
@@ -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, fork over `{ kind: "subagent", preset: "explore", prompt: "..." }` nodes; up to 3 in parallel. Each Explore subagent returns a focused report.',
72
- '2. **Design** — Synthesise findings. Optionally invoke `{ kind: "subagent", preset: "plan", prompt: "..." }` (1–3 in parallel) to draft alternative implementation approaches and surface trade-offs.',
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. If the task warrants custom step orchestration, also 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. Otherwise omit the tree.',
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;
@@ -1,5 +1,5 @@
1
- import type { Tool } from '../../types/common';
2
1
  import type { MemoryLayer } from '../../types/memory';
2
+ import type { Tool } from '../../types/tool';
3
3
  interface ToolMemoryLayerOpts {
4
4
  slot?: number;
5
5
  }