feat(sources v1): the agent locates files itself — ls/find/grep + tool-call cards

User pivot (2026-09-19): no attachments UI at all. Pasting paths is native
for technical users and alien for everyone else - so the agent gets the
tools to find what the learner merely NAMES. The learner communicates in
messages; the agent locates and reads. Zero cross-stream seams: paths
already live in tool parts, so the sources "list" is the transcript.

- ls/find/grep: pi-verbatim schemas, descriptions, and output formats
  (fd's glob semantics, rg's path:line: match format, pi's truncation
  notices), ported to pure Node - no binaries, no shell-out, read-only by
  construction. Deltas from pi: home-anchored resolution (the harness has
  no cwd), .gitignore replaced by hard noise exclusions
  (node_modules/.git/Library/.Trash), 10s walk timeout with partial results.
- read: description swapped to pi-verbatim (doctrine moved to the prompt);
  relative paths now resolve against the learner's home like the rest.
- prompts/shared/05-learner-files.md: one minimal doctrine fragment
  composed into BOTH agents (composeInstructions grew a shared/ dir,
  before the agent's own fragments). teacher/30-jobs.md keeps only the
  pdf-dispatch addendum.
- ToolCallCard: the generic quiet tool-call card the user specced -
  collapsed one-liner (tool name + headline argument), expandable to full
  input/output, errors red, image payloads never render. Registered for
  read/ls/find/grep.
- ui-message.ts: TeacherUITools entries for the four tools (mechanical).
- tests: fs-tools suite (glob semantics, pi formats, exclusions, limits),
  registry/card renders, shared-fragment composition pins, tool-registry
  key updates, read re-anchor.
This commit is contained in:
amosblomqvist
2026-09-19 16:50:20 +03:00
parent dfe1a21989
commit 7a3e19e95f
18 changed files with 977 additions and 41 deletions
+13 -6
View File
@@ -47,17 +47,24 @@ const PROMPTS_DIR = path.join(PATHS.root, "src", "agent", "prompts");
/** A prompt fragment: numeric order key + .md (e.g. 20-teaching.md). Nothing else loads. */
const FRAGMENT_RE = /^\d+-[\w.-]+\.md$/;
/** Exported for tests: assert what composes into the model's prompt. */
export function composeInstructions(kind: AgentKind): string {
const base = fs.readFileSync(PATHS.teacherPrompt, "utf-8");
const dir = path.join(PROMPTS_DIR, kind);
if (!fs.existsSync(dir)) return base;
const fragments = fs
/** Load one directory's fragments (sorted filename order, trimmed,
* empty ones dropped) — the loader enforces the numeric prefix. */
function loadFragments(dir: string): string[] {
if (!fs.existsSync(dir)) return [];
return fs
.readdirSync(dir)
.filter((f) => FRAGMENT_RE.test(f))
.sort()
.map((f) => fs.readFileSync(path.join(dir, f), "utf-8").trim())
.filter(Boolean);
}
/** Exported for tests: assert what composes into the model's prompt.
* Composition order: shared base → shared/ fragments (both agents, the
* sources-v1 seam for cross-agent doctrine) → the agent's own fragments. */
export function composeInstructions(kind: AgentKind): string {
const base = fs.readFileSync(PATHS.teacherPrompt, "utf-8");
const fragments = [...loadFragments(path.join(PROMPTS_DIR, "shared")), ...loadFragments(path.join(PROMPTS_DIR, kind))];
return [base.trim(), ...fragments].join("\n\n---\n\n");
}