@zenera/neo 1.1.24 → 1.1.26

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 (42) hide show
  1. package/dist/agent.js +2 -1
  2. package/dist/index.d.ts +3 -0
  3. package/dist/index.js +3 -0
  4. package/dist/inspect/client.d.ts +1 -1
  5. package/dist/inspect/client.js +82 -11
  6. package/dist/inspect/css.d.ts +1 -1
  7. package/dist/inspect/css.js +1 -0
  8. package/dist/inspect/index.d.ts +1 -1
  9. package/dist/inspect/report.d.ts +9 -0
  10. package/dist/inspect/report.js +1 -0
  11. package/dist/lock.d.ts +27 -0
  12. package/dist/lock.js +66 -0
  13. package/dist/match.d.ts +44 -0
  14. package/dist/match.js +100 -0
  15. package/dist/memory/diff.d.ts +39 -0
  16. package/dist/memory/diff.js +95 -0
  17. package/dist/memory/files.d.ts +1 -1
  18. package/dist/memory/grep.d.ts +1 -1
  19. package/dist/memory/grep.js +2 -2
  20. package/dist/memory/instructions.d.ts +15 -0
  21. package/dist/memory/instructions.js +103 -0
  22. package/dist/memory/merge.js +1 -31
  23. package/dist/memory/store.d.ts +1 -5
  24. package/dist/memory/store.js +7 -53
  25. package/dist/memory/twin.d.ts +6 -0
  26. package/dist/memory/twin.js +40 -0
  27. package/dist/memory-stores/factory.d.ts +15 -0
  28. package/dist/memory-stores/factory.js +29 -0
  29. package/dist/memory-stores/file.d.ts +37 -0
  30. package/dist/memory-stores/file.js +174 -0
  31. package/dist/memory-stores/in-memory.d.ts +17 -0
  32. package/dist/memory-stores/in-memory.js +80 -0
  33. package/dist/memory-stores/index.d.ts +4 -0
  34. package/dist/memory-stores/index.js +5 -0
  35. package/dist/memory.d.ts +115 -0
  36. package/dist/memory.js +213 -0
  37. package/dist/models/factory.d.ts +24 -0
  38. package/dist/models/factory.js +153 -14
  39. package/dist/project/config.d.ts +9 -9
  40. package/dist/runner.js +7 -0
  41. package/dist/tools/sandbox.js +40 -2
  42. package/package.json +1 -1
@@ -1,2 +1,2 @@
1
- export declare const CSS = "\n:root {\n --bg: #0f1115; --panel: #161922; --line: #262b36; --fg: #d7dbe3; --dim: #8a92a6;\n --accent: #6ea8fe; --ok: #4ec9a0; --err: #f0776c; --warn: #e2b341; --mono: ui-monospace, SFMono-Regular, Menlo, monospace;\n}\n* { box-sizing: border-box; }\nbody { margin: 0; background: var(--bg); color: var(--fg);\n font: 14px/1.5 system-ui, -apple-system, Segoe UI, sans-serif; }\nheader { padding: 12px 16px; border-bottom: 1px solid var(--line); background: var(--panel);\n display: flex; flex-wrap: wrap; gap: 8px 20px; align-items: baseline; }\nheader h1 { font-size: 15px; margin: 0 12px 0 0; font-weight: 600; }\nheader .kv { color: var(--dim); font-size: 12px; }\nheader .kv b { color: var(--fg); font-weight: 600; }\nmain { display: grid; grid-template-columns: 340px 1fr; height: calc(100vh - 49px); }\naside { border-right: 1px solid var(--line); overflow: auto; background: var(--panel); }\n.pad { padding: 10px; position: sticky; top: 0; background: var(--panel); border-bottom: 1px solid var(--line); }\n#filter { width: 100%; padding: 6px 8px; background: var(--bg); color: var(--fg);\n border: 1px solid var(--line); border-radius: 6px; }\n#timeline { list-style: none; margin: 0; padding: 4px 0; }\n#timeline li { padding: 6px 10px; border-left: 3px solid transparent; cursor: pointer; }\n#timeline li:hover { background: #1c2130; }\n#timeline li.sel { background: #1f2637; border-left-color: var(--accent); }\n#timeline li.covered { opacity: .45; }\n#timeline .row { display: flex; gap: 6px; align-items: baseline; }\n#timeline .t { color: var(--fg); font: 11px var(--mono); min-width: 38px; text-align: right; }\n#timeline .t.same { color: #3d4453; }\n#timeline .dur { margin-left: auto; color: var(--dim); font: 11px var(--mono); }\n#timeline .idx { color: var(--dim); font: 11px var(--mono); min-width: 22px; }\n#timeline .prev { color: var(--dim); font-size: 11px; white-space: nowrap;\n overflow: hidden; text-overflow: ellipsis; padding-left: 44px; }\n/* Branch histories are contiguous in this list, so a coloured edge plus a\n header is enough to stop ten parallel branches reading as one long run. */\n#timeline li.sep { cursor: default; padding: 10px 10px 4px; display: flex; gap: 7px;\n align-items: center; font-size: 11px; letter-spacing: .06em; text-transform: uppercase;\n color: var(--dim); border-left-color: transparent; }\n#timeline li.sep:hover { background: none; }\n#timeline li.sep .dot { width: 8px; height: 8px; border-radius: 2px;\n background: var(--bc, var(--dim)); }\n/* The group header shouts in uppercase; its elapsed time should not. */\n#timeline li.sep .dur { text-transform: none; letter-spacing: 0; }\n/* The branch the join actually waited for. */\n#timeline li.sep.slow { background: #232a38; color: var(--fg); }\n#timeline li.sep.slow .dur { color: var(--fg); }\n#timeline li.branch { padding-left: 20px; border-left-color: var(--bc, var(--line)); }\n#timeline li.branch.sel { border-left-color: var(--bc, var(--accent)); }\n#middle { display: flex; flex-direction: column; overflow: hidden; }\n.tabs { display: flex; gap: 4px; padding: 8px 12px; border-bottom: 1px solid var(--line); }\n.tabs button { background: none; border: 1px solid transparent; color: var(--dim);\n padding: 4px 12px; border-radius: 6px; cursor: pointer; font-size: 13px; }\n.tabs button.on { color: var(--fg); background: var(--panel); border-color: var(--line); }\n#detail { overflow: auto; padding: 16px; flex: 1; }\n#stats { overflow: auto; padding: 16px; flex: 1; }\ntable.st { width: 100%; border-collapse: collapse; font: 12px var(--mono); }\ntable.st th { text-align: right; color: var(--dim); font-weight: 600; padding: 6px 12px;\n border-bottom: 1px solid var(--line); white-space: nowrap; }\ntable.st td { text-align: right; padding: 5px 12px; border-bottom: 1px solid #1d2230;\n white-space: nowrap; }\ntable.st th:first-child, table.st td:first-child { text-align: left; }\ntable.st tbody tr:hover { background: #1c2130; }\ntable.st tbody tr:last-child td { border-bottom: none; }\n/* The total is a different kind of row, not one more of the same: it gets its\n own band so nobody reads it as another model or another agent. */\ntable.st tfoot td { color: var(--fg); font-weight: 600; background: #191e2a;\n border-top: 1px solid #2b3346; border-bottom: none; padding: 7px 12px; }\ntable.st tfoot td:first-child { color: var(--dim); text-transform: uppercase;\n letter-spacing: .07em; font-size: 11px; }\n/* Two panes hold a zoomable diagram (the run graph and the architecture), so\n everything about one is written for both. */\n.pane { display: flex; flex-direction: column; overflow: hidden; flex: 1; }\n/* An explicit display on the pane would otherwise beat the hidden attribute\n and both views would show at once. */\n.pane[hidden], #detail[hidden], #stats[hidden] { display: none; }\n.pane svg { max-width: none; display: block; }\n.pane .node { cursor: pointer; }\n/* A node with nothing behind it in this run is not clickable, and says so. */\n.pane .node.idle { cursor: default; }\n.gtools { display: flex; align-items: center; gap: 6px; padding: 6px 10px;\n border-bottom: 1px solid var(--line); background: var(--panel); }\n.gtools button { background: var(--bg); border: 1px solid var(--line); color: var(--fg);\n border-radius: 6px; padding: 2px 10px; cursor: pointer; font: 12px var(--mono); }\n.gtools button:hover { border-color: var(--accent); }\n.gtools .hint:first-of-type { margin-left: 8px; }\n.gtools .lvl { margin-left: auto; font-family: var(--mono); }\n.gtools label.check { display: flex; align-items: center; gap: 5px; margin-left: 8px;\n color: var(--dim); font-size: 12px; cursor: pointer; user-select: none; }\n/* The memory view reads left to right: the graph is the index, the panel is\n the page. Putting the body under a tab of its own would mean losing the\n picture every time you asked what a node actually says. */\n#memory .split { display: flex; flex: 1; overflow: hidden; }\n#mdetail { width: 380px; flex: none; padding: 16px; overflow: auto;\n border-right: none; border-left: 1px solid var(--line); }\n#mdetail .dt { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; margin: 0 0 10px; }\n#mdetail .idrow { display: flex; gap: 8px; align-items: center; margin: 0 0 14px; }\n#mdetail .idrow code { flex: 1; min-width: 0; font: 12px var(--mono); color: var(--fg);\n background: var(--bg); border: 1px solid var(--line); border-radius: 6px;\n padding: 4px 8px; word-break: break-all; }\n#mdetail .idrow button { flex: 0 0 auto; background: var(--bg); border: 1px solid var(--line);\n color: var(--dim); border-radius: 6px; padding: 4px 9px; font: 11px var(--mono);\n cursor: pointer; }\n#mdetail .idrow button:hover { border-color: var(--accent); color: var(--fg); }\n#mdetail dl.props { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: 5px 14px;\n margin: 0 0 14px; font: 12px var(--mono); }\n#mdetail dl.props dt { color: var(--dim); }\n#mdetail dl.props dd { margin: 0; word-break: break-word; }\n/* A block that shares the panel's own background has no edges to show. */\n#mdetail section.blk { background: var(--bg); }\n#mdetail ul.links { list-style: none; margin: 0; padding: 0; }\n#mdetail ul.links li { display: grid; grid-template-columns: 96px auto minmax(0, 1fr);\n gap: 8px; align-items: baseline; padding: 6px 12px; }\n#mdetail ul.links li + li { border-top: 1px solid var(--line); }\n#mdetail ul.links .rel { font: 11px var(--mono); color: var(--warn); white-space: nowrap; }\n#mdetail button.link { font: 12px/1.4 system-ui, sans-serif; padding: 0; text-align: left;\n overflow-wrap: anywhere; }\n#mdetail button.link:hover { text-decoration: underline; }\n#mcount .ok { color: var(--ok); }\n#mcount .bad { color: var(--err); }\n/* The kind palette is the memory page's, so a node reads the same in both. */\n.kind { font-size: 11px; letter-spacing: .04em; text-transform: uppercase;\n padding: 1px 6px; border-radius: 4px; background: #232a38; color: var(--dim); }\n.kind.task { background: #1e3357; color: #9dc1ff; }\n.kind.plan { background: #3a2450; color: #d1a6ff; }\n.kind.fact { background: #14392c; color: #8ee0c2; }\n.kind.snippet { background: #1e3d33; color: #8ee0c2; }\n.kind.file { background: #40331a; color: #f0dfb4; }\n.kind.operation { background: #3d2a1b; color: #f2d9c2; }\n.kind.preference { background: #3d1f2c; color: #f5b8cd; }\n/* Selection is a class rather than attributes written onto the shapes: the\n previous pick has to lose its outline without a redraw. White, because the\n accent already means \"the recall matched this one\". */\n#memory .node.picked rect, #memory .node.picked polygon,\n#memory .node.picked path, #memory .node.picked circle {\n stroke: var(--fg) !important; stroke-width: 4px !important; }\n/* The canvas is transformed, not scrolled: panning has to work past the edges\n of the diagram, which overflow:auto would forbid. */\n.viewport { flex: 1; overflow: hidden; position: relative; cursor: grab; touch-action: none;\n user-select: none; -webkit-user-select: none; }\n.viewport.drag { cursor: grabbing; }\n.canvas { position: absolute; top: 0; left: 0; transform-origin: 0 0; }\n.hint { color: var(--dim); font-size: 12px; }\n.hint.pad { padding: 16px; }\n.type { font-size: 11px; letter-spacing: .04em; text-transform: uppercase;\n padding: 1px 6px; border-radius: 4px; background: #232a38; color: var(--dim); }\n.type.llm_call { background: #1e3357; color: #9dc1ff; }\n.type.tool_call, .type.tool_result { background: #1e3d33; color: #8ee0c2; }\n.type.compaction { background: #40331a; color: var(--warn); }\n.type.fork, .type.join { background: #3a2450; color: #d1a6ff; }\n.type.final_output { background: #14392c; color: var(--ok); }\n.type.err { background: #4a1f1c; color: var(--err); }\nh2.dt { font-size: 16px; margin: 0 0 4px; display: flex; gap: 10px; align-items: center; }\n.meta { color: var(--dim); font-size: 12px; font-family: var(--mono); margin-bottom: 14px;\n display: flex; flex-wrap: wrap; gap: 4px 14px; }\nsection.blk { border: 1px solid var(--line); border-radius: 8px; margin-bottom: 12px;\n background: var(--panel); overflow: hidden; }\nsection.blk > h3 { margin: 0; padding: 7px 12px; font-size: 12px; font-weight: 600;\n color: var(--dim); border-bottom: 1px solid var(--line); letter-spacing: .03em; }\nsection.blk > h3 .tag { float: right; font-weight: 400; }\nsection.blk.sys { border-left: 3px solid var(--warn); }\n/* An LLM call has three things a reader wants to tell apart at a glance: what\n went in, what came back, and what it cost. Groups carry the first two. */\n.group { border: 1px solid var(--line); border-radius: 10px; margin-bottom: 14px;\n background: #12151d; overflow: hidden; }\n.group > .ghdr { display: flex; align-items: center; gap: 10px; padding: 8px 12px;\n font-size: 11px; font-weight: 700; letter-spacing: .09em; text-transform: uppercase;\n background: #1b2130; border-bottom: 1px solid var(--line); }\n.group > .ghdr .tag { margin-left: auto; font: 11px var(--mono); font-weight: 400;\n letter-spacing: 0; text-transform: none; color: var(--dim); }\n.group > .body { padding: 10px; }\n.group > .body > .blk:last-child { margin-bottom: 0; }\n.group.req { border-left: 3px solid var(--accent); }\n.group.req > .ghdr { color: #9dc1ff; }\n.group.res { border-left: 3px solid var(--ok); }\n.group.res > .ghdr { color: #8ee0c2; }\n.stats { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 14px; }\n.stat { background: var(--panel); border: 1px solid var(--line); border-radius: 999px;\n padding: 3px 12px; font: 12px var(--mono); color: var(--dim); }\n.stat b { color: var(--fg); font-weight: 600; }\npre { margin: 0; padding: 10px 12px; font: 12px/1.55 var(--mono); white-space: pre-wrap;\n word-break: break-word; overflow-wrap: anywhere; }\npre.text { white-space: pre-wrap; }\n.tagged-pre { margin: 4px 0; }\n.tagged-pre > pre.text { margin: 0; padding: 6px 12px; }\n/* Section tags inside a prompt (<house_rules src=\"...\">, <role>, ...): the only\n structure a composed prompt has, and invisible without this. */\n.xtag { color: #ffb0cd; background: #2d1b27; border: 1px solid #4e2c40;\n border-radius: 4px; padding: 1px 5px; font-weight: 600; font: 12px var(--mono); }\n.xtag.end { color: #a07f92; background: #201a1e; border-color: #3b2b35;\n font-weight: 400; }\ndetails.xtag-section {\n border: 1px solid var(--line); border-radius: 6px; margin: 6px 12px;\n background: var(--panel);\n}\ndetails.xtag-section > summary {\n cursor: pointer; padding: 6px 10px; font: 12px/1.55 var(--mono);\n color: var(--dim); white-space: nowrap; overflow: hidden;\n text-overflow: ellipsis; user-select: none;\n}\ndetails.xtag-section > summary:hover { background: rgba(255, 255, 255, 0.03); }\ndetails.xtag-section > summary .preview {\n color: var(--dim); margin-left: 8px; font-size: 11px;\n}\ndetails.xtag-section[open] > summary {\n white-space: normal; border-bottom: 1px solid var(--line);\n background: rgba(0, 0, 0, 0.15);\n}\ndetails.xtag-section[open] > summary .preview { display: none; }\ndetails.xtag-section > pre.xtag-body {\n margin: 0; padding: 8px 12px 10px; background: #0d1017;\n border-radius: 0 0 5px 5px; font: 12px/1.55 var(--mono);\n white-space: pre-wrap; word-break: break-word; overflow-wrap: anywhere;\n}\nimg.media { display: block; max-width: 100%; max-height: 420px; margin: 10px 12px;\n border: 1px solid var(--line); border-radius: 6px; background: #0d1017; }\nimg.media.thumb { max-height: 88px; margin: 8px 12px; cursor: zoom-in; }\n.msg { border-top: 1px solid var(--line); border-left: 3px solid transparent; }\n.msg:first-of-type { border-top: none; }\n.msg > .role { padding: 5px 12px; font-size: 11px; text-transform: uppercase;\n letter-spacing: .05em; color: var(--dim); background: #1b202c;\n display: flex; justify-content: space-between; }\n.msg.user > .role { color: #9dc1ff; }\n.msg.user { border-left-color: #35507f; }\n.msg.assistant > .role { color: #c3a6ff; }\n.msg.assistant { border-left-color: #5a4380; }\n.msg.tool > .role { color: #8ee0c2; }\n.msg.tool { border-left-color: #2f6b58; }\n.msg.system > .role { color: var(--warn); }\n.msg.system { border-left-color: #7a6535; }\n.msg.err > .role { color: var(--err); }\n.msg.err { border-left-color: #8a3a33; }\n.call { border-top: 1px dashed var(--line); }\n.call .name { padding: 5px 12px; font: 12px var(--mono); color: #8ee0c2; }\ndetails > summary { cursor: pointer; padding: 6px 12px; color: var(--dim); font-size: 12px; }\n.badge { font: 11px var(--mono); color: var(--dim); }\n.badge.ok { color: var(--ok); } .badge.bad { color: var(--err); }\n.empty { padding: 10px 12px; color: var(--dim); font-size: 12px; font-style: italic; }\nbutton.link { background: none; border: none; color: var(--accent); cursor: pointer;\n font: 12px var(--mono); padding: 0 6px 0 0; }\n";
1
+ export declare const CSS = "\n:root {\n --bg: #0f1115; --panel: #161922; --line: #262b36; --fg: #d7dbe3; --dim: #8a92a6;\n --accent: #6ea8fe; --ok: #4ec9a0; --err: #f0776c; --warn: #e2b341; --mono: ui-monospace, SFMono-Regular, Menlo, monospace;\n}\n* { box-sizing: border-box; }\nbody { margin: 0; background: var(--bg); color: var(--fg);\n font: 14px/1.5 system-ui, -apple-system, Segoe UI, sans-serif; }\nheader { padding: 12px 16px; border-bottom: 1px solid var(--line); background: var(--panel);\n display: flex; flex-wrap: wrap; gap: 8px 20px; align-items: baseline; }\nheader h1 { font-size: 15px; margin: 0 12px 0 0; font-weight: 600; }\nheader .kv { color: var(--dim); font-size: 12px; }\nheader .kv b { color: var(--fg); font-weight: 600; }\nmain { display: grid; grid-template-columns: 340px 1fr; height: calc(100vh - 49px); }\naside { border-right: 1px solid var(--line); overflow: auto; background: var(--panel); }\n.pad { padding: 10px; position: sticky; top: 0; background: var(--panel); border-bottom: 1px solid var(--line); }\n#filter { width: 100%; padding: 6px 8px; background: var(--bg); color: var(--fg);\n border: 1px solid var(--line); border-radius: 6px; }\n#timeline { list-style: none; margin: 0; padding: 4px 0; }\n#timeline li { padding: 6px 10px; border-left: 3px solid transparent; cursor: pointer; }\n#timeline li:hover { background: #1c2130; }\n#timeline li.sel { background: #1f2637; border-left-color: var(--accent); }\n#timeline li.covered { opacity: .45; }\n#timeline .row { display: flex; gap: 6px; align-items: baseline; }\n#timeline .t { color: var(--fg); font: 11px var(--mono); min-width: 38px; text-align: right; }\n#timeline .t.same { color: #3d4453; }\n#timeline .dur { margin-left: auto; color: var(--dim); font: 11px var(--mono); }\n#timeline .idx { color: var(--dim); font: 11px var(--mono); min-width: 22px; }\n#timeline .prev { color: var(--dim); font-size: 11px; white-space: nowrap;\n overflow: hidden; text-overflow: ellipsis; padding-left: 44px; }\n/* Branch histories are contiguous in this list, so a coloured edge plus a\n header is enough to stop ten parallel branches reading as one long run. */\n#timeline li.sep { cursor: default; padding: 10px 10px 4px; display: flex; gap: 7px;\n align-items: center; font-size: 11px; letter-spacing: .06em; text-transform: uppercase;\n color: var(--dim); border-left-color: transparent; }\n#timeline li.sep:hover { background: none; }\n#timeline li.sep .dot { width: 8px; height: 8px; border-radius: 2px;\n background: var(--bc, var(--dim)); }\n/* The group header shouts in uppercase; its elapsed time should not. */\n#timeline li.sep .dur { text-transform: none; letter-spacing: 0; }\n/* The branch the join actually waited for. */\n#timeline li.sep.slow { background: #232a38; color: var(--fg); }\n#timeline li.sep.slow .dur { color: var(--fg); }\n#timeline li.branch { padding-left: 20px; border-left-color: var(--bc, var(--line)); }\n#timeline li.branch.sel { border-left-color: var(--bc, var(--accent)); }\n#middle { display: flex; flex-direction: column; overflow: hidden; }\n.tabs { display: flex; gap: 4px; padding: 8px 12px; border-bottom: 1px solid var(--line); }\n.tabs button { background: none; border: 1px solid transparent; color: var(--dim);\n padding: 4px 12px; border-radius: 6px; cursor: pointer; font-size: 13px; }\n.tabs button.on { color: var(--fg); background: var(--panel); border-color: var(--line); }\n#detail { overflow: auto; padding: 16px; flex: 1; }\n#stats { overflow: auto; padding: 16px; flex: 1; }\ntable.st { width: 100%; border-collapse: collapse; font: 12px var(--mono); }\ntable.st th { text-align: right; color: var(--dim); font-weight: 600; padding: 6px 12px;\n border-bottom: 1px solid var(--line); white-space: nowrap; }\ntable.st td { text-align: right; padding: 5px 12px; border-bottom: 1px solid #1d2230;\n white-space: nowrap; }\ntable.st th:first-child, table.st td:first-child { text-align: left; }\ntable.st tbody tr:hover { background: #1c2130; }\ntable.st tbody tr:last-child td { border-bottom: none; }\n/* The total is a different kind of row, not one more of the same: it gets its\n own band so nobody reads it as another model or another agent. */\ntable.st tfoot td { color: var(--fg); font-weight: 600; background: #191e2a;\n border-top: 1px solid #2b3346; border-bottom: none; padding: 7px 12px; }\ntable.st tfoot td:first-child { color: var(--dim); text-transform: uppercase;\n letter-spacing: .07em; font-size: 11px; }\n/* Two panes hold a zoomable diagram (the run graph and the architecture), so\n everything about one is written for both. */\n.pane { display: flex; flex-direction: column; overflow: hidden; flex: 1; }\n/* An explicit display on the pane would otherwise beat the hidden attribute\n and both views would show at once. */\n.pane[hidden], #detail[hidden], #stats[hidden] { display: none; }\n.pane svg { max-width: none; display: block; }\n.pane .node { cursor: pointer; }\n/* A node with nothing behind it in this run is not clickable, and says so. */\n.pane .node.idle { cursor: default; }\n.gtools { display: flex; align-items: center; gap: 6px; padding: 6px 10px;\n border-bottom: 1px solid var(--line); background: var(--panel); }\n.gtools button { background: var(--bg); border: 1px solid var(--line); color: var(--fg);\n border-radius: 6px; padding: 2px 10px; cursor: pointer; font: 12px var(--mono); }\n.gtools button:hover { border-color: var(--accent); }\n.gtools .hint:first-of-type { margin-left: 8px; }\n.gtools .lvl { margin-left: auto; font-family: var(--mono); }\n.gtools label.check { display: flex; align-items: center; gap: 5px; margin-left: 8px;\n color: var(--dim); font-size: 12px; cursor: pointer; user-select: none; }\n/* The memory view reads left to right: the graph is the index, the panel is\n the page. Putting the body under a tab of its own would mean losing the\n picture every time you asked what a node actually says. */\n#memory .split { display: flex; flex: 1; overflow: hidden; }\n#mdetail { width: 380px; flex: none; padding: 16px; overflow: auto;\n border-right: none; border-left: 1px solid var(--line); }\n#mdetail .dt { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; margin: 0 0 10px; }\n#mdetail .idrow { display: flex; gap: 8px; align-items: center; margin: 0 0 14px; }\n#mdetail .idrow code { flex: 1; min-width: 0; font: 12px var(--mono); color: var(--fg);\n background: var(--bg); border: 1px solid var(--line); border-radius: 6px;\n padding: 4px 8px; word-break: break-all; }\n#mdetail .idrow button { flex: 0 0 auto; background: var(--bg); border: 1px solid var(--line);\n color: var(--dim); border-radius: 6px; padding: 4px 9px; font: 11px var(--mono);\n cursor: pointer; }\n#mdetail .idrow button:hover { border-color: var(--accent); color: var(--fg); }\n#mdetail dl.props { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: 5px 14px;\n margin: 0 0 14px; font: 12px var(--mono); }\n#mdetail dl.props dt { color: var(--dim); }\n#mdetail dl.props dd { margin: 0; word-break: break-word; }\n/* A block that shares the panel's own background has no edges to show. */\n#mdetail section.blk { background: var(--bg); }\n#mdetail ul.links { list-style: none; margin: 0; padding: 0; }\n#mdetail ul.links li { display: grid; grid-template-columns: 96px auto minmax(0, 1fr);\n gap: 8px; align-items: baseline; padding: 6px 12px; }\n#mdetail ul.links li + li { border-top: 1px solid var(--line); }\n#mdetail ul.links .rel { font: 11px var(--mono); color: var(--warn); white-space: nowrap; }\n#mdetail button.link { font: 12px/1.4 system-ui, sans-serif; padding: 0; text-align: left;\n overflow-wrap: anywhere; }\n#mdetail button.link:hover { text-decoration: underline; }\n#mcount .ok { color: var(--ok); }\n#mcount .bad { color: var(--err); }\n/* The kind palette is the memory page's, so a node reads the same in both. */\n.kind { font-size: 11px; letter-spacing: .04em; text-transform: uppercase;\n padding: 1px 6px; border-radius: 4px; background: #232a38; color: var(--dim); }\n.kind.task { background: #1e3357; color: #9dc1ff; }\n.kind.plan { background: #3a2450; color: #d1a6ff; }\n.kind.fact { background: #14392c; color: #8ee0c2; }\n.kind.snippet { background: #1e3d33; color: #8ee0c2; }\n.kind.file { background: #40331a; color: #f0dfb4; }\n.kind.operation { background: #3d2a1b; color: #f2d9c2; }\n.kind.preference { background: #3d1f2c; color: #f5b8cd; }\n/* Selection is a class rather than attributes written onto the shapes: the\n previous pick has to lose its outline without a redraw. White, because the\n accent already means \"the recall matched this one\". */\n#memory .node.picked rect, #memory .node.picked polygon,\n#memory .node.picked path, #memory .node.picked circle {\n stroke: var(--fg) !important; stroke-width: 4px !important; }\n/* The canvas is transformed, not scrolled: panning has to work past the edges\n of the diagram, which overflow:auto would forbid. */\n.viewport { flex: 1; overflow: hidden; position: relative; cursor: grab; touch-action: none;\n user-select: none; -webkit-user-select: none; }\n.viewport.drag { cursor: grabbing; }\n.canvas { position: absolute; top: 0; left: 0; transform-origin: 0 0; }\n.hint { color: var(--dim); font-size: 12px; }\n.hint.pad { padding: 16px; }\n.type { font-size: 11px; letter-spacing: .04em; text-transform: uppercase;\n padding: 1px 6px; border-radius: 4px; background: #232a38; color: var(--dim); }\n.type.llm_call { background: #1e3357; color: #9dc1ff; }\n.type.tool_call, .type.tool_result { background: #1e3d33; color: #8ee0c2; }\n.type.compaction { background: #40331a; color: var(--warn); }\n.type.fork, .type.join { background: #3a2450; color: #d1a6ff; }\n.type.final_output { background: #14392c; color: var(--ok); }\n.type.err { background: #4a1f1c; color: var(--err); }\nh2.dt { font-size: 16px; margin: 0 0 4px; display: flex; gap: 10px; align-items: center; }\n.meta { color: var(--dim); font-size: 12px; font-family: var(--mono); margin-bottom: 14px;\n display: flex; flex-wrap: wrap; gap: 4px 14px; }\nsection.blk { border: 1px solid var(--line); border-radius: 8px; margin-bottom: 12px;\n background: var(--panel); overflow: hidden; }\nsection.blk > h3 { margin: 0; padding: 7px 12px; font-size: 12px; font-weight: 600;\n color: var(--dim); border-bottom: 1px solid var(--line); letter-spacing: .03em; }\nsection.blk > h3 .tag { float: right; font-weight: 400; }\nsection.blk.sys { border-left: 3px solid var(--warn); }\n/* An LLM call has three things a reader wants to tell apart at a glance: what\n went in, what came back, and what it cost. Groups carry the first two. */\n.group { border: 1px solid var(--line); border-radius: 10px; margin-bottom: 14px;\n background: #12151d; overflow: hidden; }\n.group > .ghdr { display: flex; align-items: center; gap: 10px; padding: 8px 12px;\n font-size: 11px; font-weight: 700; letter-spacing: .09em; text-transform: uppercase;\n background: #1b2130; border-bottom: 1px solid var(--line); }\n.group > .ghdr .tag { margin-left: auto; font: 11px var(--mono); font-weight: 400;\n letter-spacing: 0; text-transform: none; color: var(--dim); }\n.group > .body { padding: 10px; }\n.group > .body > .blk:last-child { margin-bottom: 0; }\n.group.req { border-left: 3px solid var(--accent); }\n.group.req > .ghdr { color: #9dc1ff; }\n.group.res { border-left: 3px solid var(--ok); }\n.group.res > .ghdr { color: #8ee0c2; }\n.stats { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 14px; }\n.stat { background: var(--panel); border: 1px solid var(--line); border-radius: 999px;\n padding: 3px 12px; font: 12px var(--mono); color: var(--dim); }\n.stat b { color: var(--fg); font-weight: 600; }\npre { margin: 0; padding: 10px 12px; font: 12px/1.55 var(--mono); white-space: pre-wrap;\n word-break: break-word; overflow-wrap: anywhere; }\npre.text { white-space: pre-wrap; }\npre a.path { color: var(--accent); }\n.tagged-pre { margin: 4px 0; }\n.tagged-pre > pre.text { margin: 0; padding: 6px 12px; }\n/* Section tags inside a prompt (<house_rules src=\"...\">, <role>, ...): the only\n structure a composed prompt has, and invisible without this. */\n.xtag { color: #ffb0cd; background: #2d1b27; border: 1px solid #4e2c40;\n border-radius: 4px; padding: 1px 5px; font-weight: 600; font: 12px var(--mono); }\n.xtag.end { color: #a07f92; background: #201a1e; border-color: #3b2b35;\n font-weight: 400; }\ndetails.xtag-section {\n border: 1px solid var(--line); border-radius: 6px; margin: 6px 12px;\n background: var(--panel);\n}\ndetails.xtag-section > summary {\n cursor: pointer; padding: 6px 10px; font: 12px/1.55 var(--mono);\n color: var(--dim); white-space: nowrap; overflow: hidden;\n text-overflow: ellipsis; user-select: none;\n}\ndetails.xtag-section > summary:hover { background: rgba(255, 255, 255, 0.03); }\ndetails.xtag-section > summary .preview {\n color: var(--dim); margin-left: 8px; font-size: 11px;\n}\ndetails.xtag-section[open] > summary {\n white-space: normal; border-bottom: 1px solid var(--line);\n background: rgba(0, 0, 0, 0.15);\n}\ndetails.xtag-section[open] > summary .preview { display: none; }\ndetails.xtag-section > pre.xtag-body {\n margin: 0; padding: 8px 12px 10px; background: #0d1017;\n border-radius: 0 0 5px 5px; font: 12px/1.55 var(--mono);\n white-space: pre-wrap; word-break: break-word; overflow-wrap: anywhere;\n}\nimg.media { display: block; max-width: 100%; max-height: 420px; margin: 10px 12px;\n border: 1px solid var(--line); border-radius: 6px; background: #0d1017; }\nimg.media.thumb { max-height: 88px; margin: 8px 12px; cursor: zoom-in; }\n.msg { border-top: 1px solid var(--line); border-left: 3px solid transparent; }\n.msg:first-of-type { border-top: none; }\n.msg > .role { padding: 5px 12px; font-size: 11px; text-transform: uppercase;\n letter-spacing: .05em; color: var(--dim); background: #1b202c;\n display: flex; justify-content: space-between; }\n.msg.user > .role { color: #9dc1ff; }\n.msg.user { border-left-color: #35507f; }\n.msg.assistant > .role { color: #c3a6ff; }\n.msg.assistant { border-left-color: #5a4380; }\n.msg.tool > .role { color: #8ee0c2; }\n.msg.tool { border-left-color: #2f6b58; }\n.msg.system > .role { color: var(--warn); }\n.msg.system { border-left-color: #7a6535; }\n.msg.err > .role { color: var(--err); }\n.msg.err { border-left-color: #8a3a33; }\n.call { border-top: 1px dashed var(--line); }\n.call .name { padding: 5px 12px; font: 12px var(--mono); color: #8ee0c2; }\ndetails > summary { cursor: pointer; padding: 6px 12px; color: var(--dim); font-size: 12px; }\n.badge { font: 11px var(--mono); color: var(--dim); }\n.badge.ok { color: var(--ok); } .badge.bad { color: var(--err); }\n.empty { padding: 10px 12px; color: var(--dim); font-size: 12px; font-style: italic; }\nbutton.link { background: none; border: none; color: var(--accent); cursor: pointer;\n font: 12px var(--mono); padding: 0 6px 0 0; }\n";
2
2
  //# sourceMappingURL=css.d.ts.map
@@ -183,6 +183,7 @@ section.blk.sys { border-left: 3px solid var(--warn); }
183
183
  pre { margin: 0; padding: 10px 12px; font: 12px/1.55 var(--mono); white-space: pre-wrap;
184
184
  word-break: break-word; overflow-wrap: anywhere; }
185
185
  pre.text { white-space: pre-wrap; }
186
+ pre a.path { color: var(--accent); }
186
187
  .tagged-pre { margin: 4px 0; }
187
188
  .tagged-pre > pre.text { margin: 0; padding: 6px 12px; }
188
189
  /* Section tags inside a prompt (<house_rules src="...">, <role>, ...): the only
@@ -1,7 +1,7 @@
1
1
  import type { PayloadResolver } from '../payload.ts';
2
2
  import type { AgentState } from '../state.ts';
3
3
  import { type ReportOptions, type RunReport } from './report.ts';
4
- export { buildRunReport, MAX_BLOB_BYTES, MAX_MEMORY_FILE_BYTES, MERMAID_URL, type ReportOptions, type RunMemory, type RunReport, } from './report.ts';
4
+ export { buildRunReport, MAX_BLOB_BYTES, MAX_MEMORY_FILE_BYTES, MERMAID_URL, type ReportMount, type ReportOptions, type RunMemory, type RunReport, } from './report.ts';
5
5
  /** Report → one standalone HTML document. */
6
6
  export declare function renderReportHtml(report: RunReport, opts?: ReportOptions): string;
7
7
  /** The usual entry point: state in, HTML out. */
@@ -36,6 +36,14 @@ export interface ReportOptions {
36
36
  memory?: MemoryStore;
37
37
  /** ceiling on an inlined remembered file; 0 to inline none */
38
38
  maxMemoryFileBytes?: number;
39
+ /** Trees the run named by a mount, so the page can link those paths to this machine. */
40
+ mounts?: readonly ReportMount[];
41
+ }
42
+ export interface ReportMount {
43
+ /** the name the agent used, e.g. `/workspace` */
44
+ at: string;
45
+ /** the `file:` url of the directory behind it */
46
+ href: string;
39
47
  }
40
48
  /** Everything the page needs, and nothing that needs a store to interpret. */
41
49
  export interface RunReport {
@@ -64,6 +72,7 @@ export interface RunReport {
64
72
  * can have moved on a revision since, or be gone from the graph entirely.
65
73
  */
66
74
  memory?: RunMemory;
75
+ mounts?: ReportMount[];
67
76
  }
68
77
  export interface RunMemory {
69
78
  dir: string;
@@ -62,6 +62,7 @@ export async function buildRunReport(state, payloads, opts = {}) {
62
62
  media,
63
63
  architecture: opts.architecture,
64
64
  memory: opts.memory ? await remembered(opts.memory, state, media, opts) : undefined,
65
+ ...(opts.mounts?.length ? { mounts: [...opts.mounts] } : {}),
65
66
  };
66
67
  }
67
68
  /** Every memory node the run touched, branches included. */
package/dist/lock.d.ts ADDED
@@ -0,0 +1,27 @@
1
+ export interface Lock {
2
+ pid: number;
3
+ host: string;
4
+ startedAt: string;
5
+ }
6
+ /** A lock body for this process, with whatever else the holder wants to say. */
7
+ export declare function ownLock<T extends object>(extra?: T): Lock & T;
8
+ /**
9
+ * `kill(pid, 0)` sends no signal and only asks whether the process exists.
10
+ * EPERM means it exists and belongs to someone else, which still counts.
11
+ */
12
+ export declare function pidAlive(pid: number): boolean;
13
+ /** The lock file as written, or undefined when absent or unreadable. */
14
+ export declare function readLock<T extends Lock = Lock>(path: string): T | undefined;
15
+ /**
16
+ * Who holds a lock right now, if anyone. Read-only: it never claims the lock
17
+ * and never clears a stale one. A lock from another host cannot be verified
18
+ * here, so it is not reported as held.
19
+ */
20
+ export declare function liveHolder<T extends Lock = Lock>(path: string): T | undefined;
21
+ /**
22
+ * Writes `lock` to `path`, or calls `refuse` with the live holder and throws
23
+ * what it returns. `wx` makes the create and the check one operation, so two
24
+ * processes racing for the same directory cannot both win.
25
+ */
26
+ export declare function claimLock<T extends Lock>(path: string, lock: T, refuse: (held: T) => Error, indent?: number): void;
27
+ //# sourceMappingURL=lock.d.ts.map
package/dist/lock.js ADDED
@@ -0,0 +1,66 @@
1
+ import { readFileSync, writeFileSync } from 'node:fs';
2
+ import { hostname } from 'node:os';
3
+ /** A lock body for this process, with whatever else the holder wants to say. */
4
+ export function ownLock(extra) {
5
+ return {
6
+ pid: process.pid,
7
+ host: hostname(),
8
+ startedAt: new Date().toISOString(),
9
+ ...extra,
10
+ };
11
+ }
12
+ /**
13
+ * `kill(pid, 0)` sends no signal and only asks whether the process exists.
14
+ * EPERM means it exists and belongs to someone else, which still counts.
15
+ */
16
+ export function pidAlive(pid) {
17
+ try {
18
+ process.kill(pid, 0);
19
+ return true;
20
+ }
21
+ catch (err) {
22
+ return err.code === 'EPERM';
23
+ }
24
+ }
25
+ /** The lock file as written, or undefined when absent or unreadable. */
26
+ export function readLock(path) {
27
+ try {
28
+ return JSON.parse(readFileSync(path, 'utf8'));
29
+ }
30
+ catch {
31
+ return undefined;
32
+ }
33
+ }
34
+ /**
35
+ * Who holds a lock right now, if anyone. Read-only: it never claims the lock
36
+ * and never clears a stale one. A lock from another host cannot be verified
37
+ * here, so it is not reported as held.
38
+ */
39
+ export function liveHolder(path) {
40
+ const held = readLock(path);
41
+ return held && held.host === hostname() && pidAlive(held.pid) ? held : undefined;
42
+ }
43
+ /**
44
+ * Writes `lock` to `path`, or calls `refuse` with the live holder and throws
45
+ * what it returns. `wx` makes the create and the check one operation, so two
46
+ * processes racing for the same directory cannot both win.
47
+ */
48
+ export function claimLock(path, lock, refuse, indent = 4) {
49
+ const body = `${JSON.stringify(lock, null, indent)}\n`;
50
+ try {
51
+ writeFileSync(path, body, { flag: 'wx' });
52
+ return;
53
+ }
54
+ catch (err) {
55
+ if (err.code !== 'EEXIST') {
56
+ throw err;
57
+ }
58
+ }
59
+ const held = liveHolder(path);
60
+ if (held) {
61
+ throw refuse(held);
62
+ }
63
+ // Stale, or from another machine's run that cannot be verified here.
64
+ writeFileSync(path, body);
65
+ }
66
+ //# sourceMappingURL=lock.js.map
@@ -0,0 +1,44 @@
1
+ /** Long enough for any honest pattern, short enough to bound a bad one. */
2
+ export declare const MAX_PATTERN = 200;
3
+ export declare class PatternError extends Error {
4
+ }
5
+ export interface MatchOptions {
6
+ /** read the pattern as a regular expression rather than as a literal */
7
+ regex?: boolean;
8
+ caseSensitive?: boolean;
9
+ }
10
+ export type Matcher = (text: string) => boolean;
11
+ /** The offset of the first match in `text`, or -1. */
12
+ export type Finder = (text: string) => number;
13
+ /**
14
+ * Where a pattern first occurs. Literal by default: someone typing `user.id`
15
+ * means those seven characters, and a dot that quietly matched anything would
16
+ * be a worse answer than no answer.
17
+ */
18
+ export declare function finder(pattern: string, options?: MatchOptions): Finder;
19
+ /** Whether a pattern occurs at all, read the same way `finder` reads it. */
20
+ export declare function matcher(pattern: string, options?: MatchOptions): Matcher;
21
+ /**
22
+ * A glob, matched against the whole string. Globs rather than regexes because
23
+ * these are for naming things — a path, a schema — and a star is what everyone
24
+ * reaches for first.
25
+ */
26
+ export declare function wildcard(pattern: string, options?: {
27
+ caseSensitive?: boolean;
28
+ }): Matcher;
29
+ /** Whether a pattern is asking to be read as a glob at all. */
30
+ export declare const isGlob: (pattern: string) => boolean;
31
+ /**
32
+ * What someone means when they type a name into a filter. With a star in it,
33
+ * a glob; without one, a substring — because `password` typed into `--name` is
34
+ * a search for the word, and a whole-string match would answer nothing and
35
+ * look like the field does not exist.
36
+ *
37
+ * `regex` settles it outright, and has to: a star is punctuation in both
38
+ * languages, so `^/(users|teams)/.*` read as a glob would match nothing and
39
+ * never say why.
40
+ */
41
+ export declare function loose(pattern: string, options?: MatchOptions): Matcher;
42
+ /** True when any of the patterns matches; no patterns means no opinion. */
43
+ export declare function anyOf(matchers: readonly Matcher[]): Matcher | undefined;
44
+ //# sourceMappingURL=match.d.ts.map
package/dist/match.js ADDED
@@ -0,0 +1,100 @@
1
+ // ---------------------------------------------------------------------------
2
+ // Matching, with nothing learned in between
3
+ //
4
+ // Everything here is exact. A glob matches the characters it names and a
5
+ // substring is a substring, which is the whole point of the surfaces built on
6
+ // it: a vector index answers "what is near this", and near is a ranking, so it
7
+ // can only ever return the top of a list. When the question is "does the word
8
+ // `password` appear anywhere at all", a ranking is the wrong instrument and no
9
+ // amount of tuning makes it the right one.
10
+ //
11
+ // Memory grep and @zenera/rag's lookups both stand on this. A `finder` says
12
+ // *where*, for grep, which reports the line; a `matcher` says *whether*.
13
+ //
14
+ // A pattern may arrive from a model, so a regex is a bounded promise: the
15
+ // length is capped here and the scan that uses it keeps a deadline.
16
+ // ---------------------------------------------------------------------------
17
+ /** Long enough for any honest pattern, short enough to bound a bad one. */
18
+ export const MAX_PATTERN = 200;
19
+ export class PatternError extends Error {
20
+ }
21
+ /**
22
+ * Where a pattern first occurs. Literal by default: someone typing `user.id`
23
+ * means those seven characters, and a dot that quietly matched anything would
24
+ * be a worse answer than no answer.
25
+ */
26
+ export function finder(pattern, options = {}) {
27
+ guard(pattern);
28
+ if (!options.regex) {
29
+ if (options.caseSensitive) {
30
+ return (text) => text.indexOf(pattern);
31
+ }
32
+ const needle = pattern.toLowerCase();
33
+ return (text) => text.toLowerCase().indexOf(needle);
34
+ }
35
+ const expression = compile(pattern, options.caseSensitive ? '' : 'i');
36
+ // `lastIndex` is not carried between calls: the flags never include `g`.
37
+ return (text) => text.search(expression);
38
+ }
39
+ /** Whether a pattern occurs at all, read the same way `finder` reads it. */
40
+ export function matcher(pattern, options = {}) {
41
+ const find = finder(pattern, options);
42
+ return (text) => find(text) >= 0;
43
+ }
44
+ /**
45
+ * A glob, matched against the whole string. Globs rather than regexes because
46
+ * these are for naming things — a path, a schema — and a star is what everyone
47
+ * reaches for first.
48
+ */
49
+ export function wildcard(pattern, options = {}) {
50
+ guard(pattern);
51
+ const source = [...pattern]
52
+ .map((char) => (char === '*' ? '.*' : char === '?' ? '.' : escape(char)))
53
+ .join('');
54
+ const expression = compile(`^${source}$`, options.caseSensitive ? '' : 'i');
55
+ return (text) => expression.test(text);
56
+ }
57
+ /** Whether a pattern is asking to be read as a glob at all. */
58
+ export const isGlob = (pattern) => /[*?]/.test(pattern);
59
+ /**
60
+ * What someone means when they type a name into a filter. With a star in it,
61
+ * a glob; without one, a substring — because `password` typed into `--name` is
62
+ * a search for the word, and a whole-string match would answer nothing and
63
+ * look like the field does not exist.
64
+ *
65
+ * `regex` settles it outright, and has to: a star is punctuation in both
66
+ * languages, so `^/(users|teams)/.*` read as a glob would match nothing and
67
+ * never say why.
68
+ */
69
+ export function loose(pattern, options = {}) {
70
+ if (options.regex) {
71
+ return matcher(pattern, options);
72
+ }
73
+ return isGlob(pattern) ? wildcard(pattern, options) : matcher(pattern, options);
74
+ }
75
+ /** True when any of the patterns matches; no patterns means no opinion. */
76
+ export function anyOf(matchers) {
77
+ if (matchers.length === 0) {
78
+ return undefined;
79
+ }
80
+ return (text) => matchers.some((match) => match(text));
81
+ }
82
+ // ---------------------------------------------------------------------------
83
+ function guard(pattern) {
84
+ if (pattern.length === 0) {
85
+ throw new PatternError('the pattern is empty');
86
+ }
87
+ if (pattern.length > MAX_PATTERN) {
88
+ throw new PatternError(`the pattern is longer than ${MAX_PATTERN} characters`);
89
+ }
90
+ }
91
+ function compile(source, flags) {
92
+ try {
93
+ return new RegExp(source, flags);
94
+ }
95
+ catch (err) {
96
+ throw new PatternError(`invalid pattern: ${err.message}`);
97
+ }
98
+ }
99
+ const escape = (char) => char.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
100
+ //# sourceMappingURL=match.js.map
@@ -0,0 +1,39 @@
1
+ import type { MemoryStore } from './store.ts';
2
+ import type { MemoryNode } from './types.ts';
3
+ export interface MemoryNearest {
4
+ id: string;
5
+ score: number;
6
+ text: string;
7
+ }
8
+ export interface MemoryDiffAdded {
9
+ node: MemoryNode;
10
+ /** the closest comparable node the base held, if any */
11
+ nearest?: MemoryNearest;
12
+ /** `merge` would fold it onto `nearest`: the base already said this */
13
+ twin: boolean;
14
+ }
15
+ export interface MemoryDiff {
16
+ base: string;
17
+ other: string;
18
+ /** how `nearest` was scored: cosine when both sides hold vectors, else term overlap */
19
+ by: 'vector' | 'text';
20
+ added: MemoryDiffAdded[];
21
+ revised: {
22
+ before: MemoryNode;
23
+ after: MemoryNode;
24
+ }[];
25
+ /** base nodes `memory_load` returned since — what the run actually read */
26
+ used: {
27
+ node: MemoryNode;
28
+ loads: number;
29
+ }[];
30
+ removed: MemoryNode[];
31
+ /** edges `other` has that `base` did not */
32
+ edges: number;
33
+ supersedes: {
34
+ source: string;
35
+ target: string;
36
+ }[];
37
+ }
38
+ export declare function diffMemories(base: MemoryStore, other: MemoryStore): MemoryDiff;
39
+ //# sourceMappingURL=diff.d.ts.map
@@ -0,0 +1,95 @@
1
+ import { DUPLICATE_SCORE } from "./index.js";
2
+ import { comparable, sameContent, twinKey } from "./twin.js";
3
+ import { overlap, tokenize } from "./vectors.js";
4
+ export function diffMemories(base, other) {
5
+ const stale = base.graph.superseded();
6
+ const block = base.vectors;
7
+ const theirs = other.vectors;
8
+ const byVector = !!block?.rows && !!theirs && theirs.dims === block.dims;
9
+ const held = base.graph.nodes();
10
+ const lexical = new Map(held.filter((n) => !stale.has(n.id)).map((n) => [twinKey(n), n.id]));
11
+ const nearestOf = (node) => {
12
+ if (node.file) {
13
+ return { twin: false };
14
+ }
15
+ const allow = (id) => {
16
+ const n = base.graph.get(id);
17
+ return !stale.has(id) && !!n && comparable(n, node);
18
+ };
19
+ const exact = lexical.get(twinKey(node));
20
+ const vector = byVector ? theirs.get(node.id) : undefined;
21
+ if (vector) {
22
+ const hit = block.topK(vector, 1, allow)[0];
23
+ if (hit) {
24
+ const text = base.graph.get(hit.id).text;
25
+ return {
26
+ nearest: { id: hit.id, score: hit.score, text },
27
+ twin: hit.score >= DUPLICATE_SCORE,
28
+ };
29
+ }
30
+ }
31
+ if (exact) {
32
+ return {
33
+ nearest: { id: exact, score: 1, text: base.graph.get(exact).text },
34
+ twin: true,
35
+ };
36
+ }
37
+ const words = tokenize(node.text);
38
+ let best;
39
+ for (const n of held) {
40
+ if (!allow(n.id)) {
41
+ continue;
42
+ }
43
+ const score = overlap(words, tokenize(n.text));
44
+ if (score > 0 && (!best || score > best.score)) {
45
+ best = { id: n.id, score, text: n.text };
46
+ }
47
+ }
48
+ return { nearest: best, twin: false };
49
+ };
50
+ const diff = {
51
+ base: base.dir,
52
+ other: other.dir,
53
+ by: byVector ? 'vector' : 'text',
54
+ added: [],
55
+ revised: [],
56
+ used: [],
57
+ removed: [],
58
+ edges: 0,
59
+ supersedes: [],
60
+ };
61
+ for (const after of other.graph.nodes()) {
62
+ const before = base.graph.get(after.id);
63
+ if (!before) {
64
+ diff.added.push({ node: after, ...nearestOf(after) });
65
+ continue;
66
+ }
67
+ if (before.revision !== after.revision || !sameContent(before, after)) {
68
+ diff.revised.push({ before, after });
69
+ }
70
+ if (after.useCount > before.useCount) {
71
+ diff.used.push({ node: after, loads: after.useCount - before.useCount });
72
+ }
73
+ }
74
+ for (const node of held) {
75
+ if (!other.graph.has(node.id)) {
76
+ diff.removed.push(node);
77
+ }
78
+ }
79
+ const key = (source, target, relation) => `${source}\u0000${target}\u0000${relation}`;
80
+ const had = new Set(base.graph.links().map((l) => key(l.source, l.target, l.attrs.relation)));
81
+ for (const link of other.graph.links()) {
82
+ if (had.has(key(link.source, link.target, link.attrs.relation))) {
83
+ continue;
84
+ }
85
+ diff.edges++;
86
+ if (link.attrs.relation === 'SUPERSEDES') {
87
+ diff.supersedes.push({ source: link.source, target: link.target });
88
+ }
89
+ }
90
+ const newest = (a, b) => b.node.createdAt.localeCompare(a.node.createdAt);
91
+ diff.added.sort(newest);
92
+ diff.used.sort((a, b) => b.loads - a.loads);
93
+ return diff;
94
+ }
95
+ //# sourceMappingURL=diff.js.map
@@ -20,7 +20,7 @@ export interface RememberOptions {
20
20
  */
21
21
  export declare function rememberFile(dir: string, source: string, id: string, opts?: RememberOptions): Promise<MemoryFile>;
22
22
  /** The host path of a remembered file, for reading it back or deleting it. */
23
- export declare function hostPath(dir: string, file: MemoryFile): string;
23
+ export declare function hostPath(dir: string, file: Pick<MemoryFile, 'path'>): string;
24
24
  /** Drop the bytes of a forgotten node. Missing is success — the goal is absence. */
25
25
  export declare function forgetFile(dir: string, file: MemoryFile): Promise<void>;
26
26
  //# sourceMappingURL=files.d.ts.map
@@ -1,4 +1,4 @@
1
- import { type MatchOptions } from './match.ts';
1
+ import { type MatchOptions } from '../match.ts';
2
2
  import type { Omitted } from './report.ts';
3
3
  import type { MemoryStore } from './store.ts';
4
4
  import { type MemoryNode } from './types.ts';
@@ -1,6 +1,6 @@
1
1
  import { readFile, stat } from 'node:fs/promises';
2
+ import { PatternError, finder } from "../match.js";
2
3
  import { hostPath } from "./files.js";
3
- import { PatternError, matcher } from "./match.js";
4
4
  import { MemoryError } from "./types.js";
5
5
  // ---------------------------------------------------------------------------
6
6
  // The memory, read exhaustively
@@ -106,7 +106,7 @@ export async function grepMemory(store, pattern, filter = {}) {
106
106
  */
107
107
  function compile(pattern, options) {
108
108
  try {
109
- return matcher(pattern, options);
109
+ return finder(pattern, options);
110
110
  }
111
111
  catch (err) {
112
112
  if (err instanceof PatternError) {
@@ -0,0 +1,15 @@
1
+ import { type MemoryNode, type ResolvedMemoryBinding } from './types.ts';
2
+ export declare const PREFERENCES_TAG = "memory-preferences";
3
+ /**
4
+ * Deliberately not a function of `sees`: those labels come from the run's
5
+ * context, and naming them here would rewrite the prompt whenever the context
6
+ * moved. It also tells an agent the shape of a slice it was not given.
7
+ */
8
+ export declare function memoryInstructions(binding: ResolvedMemoryBinding): string;
9
+ /**
10
+ * The id on each line is what makes a preference revisable rather than merely
11
+ * readable: without it the model can read the instruction but cannot name the
12
+ * thing it would supersede.
13
+ */
14
+ export declare function renderPreferences(nodes: readonly MemoryNode[]): string;
15
+ //# sourceMappingURL=instructions.d.ts.map
@@ -0,0 +1,103 @@
1
+ import { MEMORY_COMMIT_TOOL, MEMORY_FORGET_TOOL, MEMORY_LOAD_TOOL, MEMORY_SEARCH_TOOL, } from "../types.js";
2
+ import { RECOLLECTION_TAG } from "./render.js";
3
+ import { canForget, canWrite } from "./types.js";
4
+ // ---------------------------------------------------------------------------
5
+ // Teaching the model to read and write memory
6
+ //
7
+ // The tool schemas say what the arguments are; none of them says when a thing
8
+ // is worth remembering, or that a recollection is a subgraph rather than a
9
+ // ranked list. That is policy, it is the same for every agent, and it belongs
10
+ // in the system prompt — which is also the only place it can be stated once
11
+ // per run instead of once per call.
12
+ //
13
+ // Both halves go in through `composePrompt`'s `derived` array, so they sit in
14
+ // the cacheable prefix and are recorded in the `system_prompt` node like every
15
+ // other part of what the model read.
16
+ //
17
+ // The text is gated on the binding, not on the project: an agent that cannot
18
+ // write is never told how to commit. A rule it cannot act on is a rule it can
19
+ // only be confused by, and an unusable tool name in the prompt invites a call
20
+ // that will be refused.
21
+ // ---------------------------------------------------------------------------
22
+ export const PREFERENCES_TAG = 'memory-preferences';
23
+ /**
24
+ * Deliberately not a function of `sees`: those labels come from the run's
25
+ * context, and naming them here would rewrite the prompt whenever the context
26
+ * moved. It also tells an agent the shape of a slice it was not given.
27
+ */
28
+ export function memoryInstructions(binding) {
29
+ const parts = [READING];
30
+ if (canWrite(binding)) {
31
+ parts.push(WRITING);
32
+ }
33
+ if (canForget(binding)) {
34
+ parts.push(FORGETTING);
35
+ }
36
+ return parts.join('\n\n');
37
+ }
38
+ const READING = `Memory is a graph shared with other agents, and it outlives this run.
39
+
40
+ A <${RECOLLECTION_TAG}> block is a subgraph, not a ranked list — the links are
41
+ as much of the answer as the nodes. It is an outline, and the indentation is
42
+ the graph. A line at the left margin reads \`score kind id\` and is something
43
+ the search matched. A line indented under it reads \`<arrow>relation kind id\`
44
+ and is a neighbour: \`\u2192produced\` means the line above produced this one,
45
+ \`\u2190informed\` means this one informed the line above. A neighbour the
46
+ search matched in its own right carries a score too, after the arrow. Under
47
+ each header, indented further, is that node's text — and for a \`file\`, the
48
+ path under /memory you can open or run, its size and how often it has been read.
49
+
50
+ A line beginning \`+\` is a link to an id already shown above, for an edge the
51
+ outline could not nest. Text is clipped: copy an id into \`${MEMORY_LOAD_TOOL}\`
52
+ to read a node whole. \`${MEMORY_SEARCH_TOOL}\` finds ids, it does not return
53
+ contents.
54
+
55
+ You see part of the graph, not all of it. Finding nothing means nothing you can
56
+ see, not that it never happened.`;
57
+ const WRITING = `Commit when a run produced something a later run would otherwise have to work
58
+ out again: a script that ran, an interface that behaved differently from its
59
+ documentation, a call sequence that turned out to be necessary. Commit it in
60
+ one \`${MEMORY_COMMIT_TOOL}\` call — the artifact and what explains it are one
61
+ memory, and splitting them leaves a later run holding a script with no idea why
62
+ it exists.
63
+
64
+ Do not commit the request restated, progress notes, anything you have not
65
+ verified, or anything a search already returned. A node that says what one
66
+ already in memory says is folded into it and its existing id comes back under
67
+ your ref, so the links you asked for still land — on the memory that was
68
+ already there.
69
+
70
+ Never edit a memory to correct it. Add the new one and link it to the old with
71
+ SUPERSEDES; the wrong answer is evidence too, and the link is what hides it
72
+ from later recalls without destroying the history.
73
+
74
+ Commit a preference only when the user's own words generalise — "always", "from
75
+ now on", "I prefer", "never". A single request being fulfilled is not a
76
+ standing instruction.`;
77
+ const FORGETTING = `Forget only what is wrong and has no successor. A superseded memory is already
78
+ hidden from recall and should stay for the history, so \`${MEMORY_FORGET_TOOL}\`
79
+ is for what should never have been written, not for what has been replaced.`;
80
+ /**
81
+ * The id on each line is what makes a preference revisable rather than merely
82
+ * readable: without it the model can read the instruction but cannot name the
83
+ * thing it would supersede.
84
+ */
85
+ export function renderPreferences(nodes) {
86
+ if (!nodes.length) {
87
+ return '';
88
+ }
89
+ const lines = nodes.map((n) => `- ${oneLine(n.text)} [${n.id}]`);
90
+ return [
91
+ `<${PREFERENCES_TAG}>`,
92
+ "Standing instructions from earlier runs. They apply unless this run's",
93
+ 'request contradicts them; to change one, commit the replacement and link',
94
+ 'it to the id here with SUPERSEDES.',
95
+ ...lines,
96
+ `</${PREFERENCES_TAG}>`,
97
+ ].join('\n');
98
+ }
99
+ /** Never clipped: half an instruction is a different instruction. */
100
+ function oneLine(text) {
101
+ return text.replace(/\s+/g, ' ').trim();
102
+ }
103
+ //# sourceMappingURL=instructions.js.map