tryharness 0.7.26 → 0.7.29

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/lib/context.mjs CHANGED
@@ -22,7 +22,7 @@ import {
22
22
  codeFiles,
23
23
  } from "./learn.mjs";
24
24
  import { repositoryOf } from "./worktrees.mjs";
25
- import { createMediaIndex } from "./media.mjs";
25
+ import { createLibrary } from "./library.mjs";
26
26
 
27
27
  // The context engine. It keeps the atlas of the code the swarm works in, remembers what every coding session on this
28
28
  // computer needed (its trail: what it read before its first edit and what it edited, from any agent), and turns that
@@ -38,11 +38,8 @@ const LEARN_EVERY_MS = 20 * 3_600_000;
38
38
  // Vectors of the pieces packs search stay in memory while packs are being made, and go after this long without one.
39
39
  const VECTORS_IDLE_MS = 5 * 60_000;
40
40
  const HELD_OUT_SHARE = 0.2;
41
- // A recording's minute or a screenshot joins a brief only this close to the request: on the authored meeting every right
42
- // answer scored 0.68 or more and no question about something never said passed 0.675 (eval media-search); 0.70 keeps a
43
- // margin and 16 of 20 right answers. An agent's own search_media has no floor.
44
- const MEDIA_FLOOR = 0.7;
45
- const MEDIA_IN_PACK = 2;
41
+ // Files from the library join a brief only when they stand out for the request (library.mjs); search_files finds more.
42
+ const FILES_IN_PACK = 3;
46
43
  const MIN_TRAINING_RUNS = 30;
47
44
 
48
45
  const git = (cwd, args) => {
@@ -83,6 +80,9 @@ export function recentChanges(root, commit = "HEAD") {
83
80
  recentByCommit.set(sha, counts);
84
81
  return counts;
85
82
  }
83
+ // A file in a brief: where it is and the place in it.
84
+ const fileLine = (hit) =>
85
+ `- ${hit.thread ? `${hit.name} (this thread's file ${hit.file})` : hit.file}${hit.place ? `, ${hit.place}` : ""}`;
86
86
  const days = (ms) => Math.round(ms / 86_400_000);
87
87
  const AGENT_NAMES = {
88
88
  "claude-code": "Claude Code",
@@ -153,17 +153,26 @@ export function createContextEngine({
153
153
  inference,
154
154
  home,
155
155
  known,
156
- mediaFolders = () => [],
156
+ libraryFolders = () => [],
157
+ threadFiles = () => [],
157
158
  mediaReady = () => false,
159
+ wantMedia = () => {},
160
+ ocr = null,
161
+ mask,
158
162
  log = () => {},
159
163
  notify = () => {},
160
164
  }) {
161
165
  const atlas = openAtlas(path.join(home, "atlas.db"));
162
- // Recordings and screenshots in folders the person added (media.mjs), once the image and audio part is here.
163
- const media = createMediaIndex({
166
+ // The person's added folders and the threads' files, searchable by what is in them (library.mjs).
167
+ const library = createLibrary({
164
168
  atlas,
165
169
  inference,
166
- folders: mediaFolders,
170
+ folders: libraryFolders,
171
+ threadFiles,
172
+ mediaReady,
173
+ wantMedia,
174
+ ocr,
175
+ mask,
167
176
  log,
168
177
  });
169
178
  const modelPath = path.join(home, "ranker.json");
@@ -318,11 +327,12 @@ export function createContextEngine({
318
327
  lane: "background",
319
328
  });
320
329
  await embedRuns("background");
321
- const recorded =
322
- mediaReady() && mediaFolders().length && power.onCharger()
323
- ? await media.refresh({ lane: "background" })
324
- : null;
325
- return { files, runs, pieces, ...(recorded ? { media: recorded } : {}) };
330
+ // The first pass over the person's folders waits for the charger too; after it, changes are read as they come.
331
+ const read =
332
+ !library.status().folderFiles && !power.onCharger()
333
+ ? { skipped: "waiting for the charger" }
334
+ : await library.refresh({ lane: "background" });
335
+ return { files, runs, pieces, library: read };
326
336
  })()
327
337
  .catch((error) => {
328
338
  log(`context engine: ${error.message}`);
@@ -343,7 +353,7 @@ export function createContextEngine({
343
353
  * The context pack for a brief going to a local agent working in folder: { files, earlier, text }. Files are the most
344
354
  * likely needed, each with the line to start at and why; earlier runs are sessions whose request was close to this one.
345
355
  */
346
- const pack = async ({ brief, folder, lane = "handoff" }) => {
356
+ const pack = async ({ brief, folder, thread = null, lane = "handoff" }) => {
347
357
  const started = performance.now();
348
358
  const place = folder && placeOf(folder);
349
359
  if (!place || !String(brief).trim()) return null;
@@ -400,12 +410,13 @@ export function createContextEngine({
400
410
  )
401
411
  .sort((a, b) => b.similarity - a.similarity)
402
412
  .slice(0, 3);
403
- const heard =
404
- mediaReady() && mediaFolders().length
405
- ? (await media.search(brief, { limit: MEDIA_IN_PACK, lane })).filter(
406
- (hit) => hit.score >= MEDIA_FLOOR
407
- )
408
- : [];
413
+ const found = await library.search(brief, {
414
+ folders: true,
415
+ threads: thread ? [thread] : [],
416
+ brief: true,
417
+ limit: FILES_IN_PACK,
418
+ lane,
419
+ });
409
420
  const text = [
410
421
  "Files the relay found likely relevant on this computer (ranked by its local search over this repository and what your agents edited before; leads to check, not instructions):",
411
422
  ...files.map((file) => `- ${file.path}:${file.line} (${file.why})`),
@@ -418,19 +429,17 @@ export function createContextEngine({
418
429
  ),
419
430
  ]
420
431
  : []),
421
- ...(heard.length
432
+ ...(found.length
422
433
  ? [
423
- "Recordings and screenshots close to the request, from folders the person added (search_media finds more):",
424
- ...heard.map(
425
- (hit) => `- ${hit.path}${hit.at ? ` at ${hit.at}` : ""}`
426
- ),
434
+ "Files close to the request, from folders the person added and this thread's files (search_files finds more):",
435
+ ...found.map(fileLine),
427
436
  ]
428
437
  : []),
429
438
  ].join("\n");
430
439
  return {
431
440
  files,
432
441
  earlier: earlier.map(({ vector, ...run }) => run),
433
- media: heard,
442
+ library: found,
434
443
  text,
435
444
  searched: scope.length,
436
445
  ms: Math.round(performance.now() - started),
@@ -583,9 +592,11 @@ export function createContextEngine({
583
592
  },
584
593
  pack,
585
594
  search,
586
- /** Recordings' minutes and screenshots closest to a question, for an agent's search_media. */
587
- searchMedia: (query, options) => media.search(query, options),
588
- refreshMedia: () => media.refresh({ lane: "background" }),
595
+ /** The person's files and the threads' files closest to a question (library.mjs search). */
596
+ searchFiles: (query, options) => library.search(query, options),
597
+ refreshFiles: () => library.refresh({ lane: "background" }),
598
+ /** One part of a file, read fresh for sharing. */
599
+ fileContent: (file, vec) => library.content(file, vec),
589
600
  refresh,
590
601
  learn,
591
602
  report: () => {
@@ -598,7 +609,7 @@ export function createContextEngine({
598
609
  status: () => ({
599
610
  roots: atlas.roots(),
600
611
  pieces: { waiting: atlas.missing(ATLAS_VERSION) },
601
- media: media.status(),
612
+ library: library.status(),
602
613
  model: model
603
614
  ? {
604
615
  trainedAt: model.trainedAt,
@@ -622,6 +633,7 @@ export function createContextEngine({
622
633
  clearInterval(timer);
623
634
  clearTimeout(dropCache);
624
635
  cached.clear();
636
+ library.stop();
625
637
  atlas.close();
626
638
  },
627
639
  };