@zenera/neo 1.1.25 → 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.
- package/dist/agent.js +2 -1
- package/dist/inspect/client.d.ts +1 -1
- package/dist/inspect/client.js +82 -11
- package/dist/inspect/css.d.ts +1 -1
- package/dist/inspect/css.js +1 -0
- package/dist/inspect/index.d.ts +1 -1
- package/dist/inspect/report.d.ts +9 -0
- package/dist/inspect/report.js +1 -0
- package/dist/memory/instructions.d.ts +15 -0
- package/dist/memory/instructions.js +103 -0
- package/dist/memory/match.d.ts +18 -0
- package/dist/memory/match.js +61 -0
- package/dist/memory/store.js +3 -2
- package/dist/memory-stores/factory.d.ts +15 -0
- package/dist/memory-stores/factory.js +29 -0
- package/dist/memory-stores/file.d.ts +37 -0
- package/dist/memory-stores/file.js +174 -0
- package/dist/memory-stores/in-memory.d.ts +17 -0
- package/dist/memory-stores/in-memory.js +80 -0
- package/dist/memory-stores/index.d.ts +4 -0
- package/dist/memory-stores/index.js +5 -0
- package/dist/memory.d.ts +115 -0
- package/dist/memory.js +213 -0
- package/dist/project/config.d.ts +9 -9
- package/package.json +1 -1
package/dist/inspect/css.d.ts
CHANGED
|
@@ -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
|
package/dist/inspect/css.js
CHANGED
|
@@ -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
|
package/dist/inspect/index.d.ts
CHANGED
|
@@ -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. */
|
package/dist/inspect/report.d.ts
CHANGED
|
@@ -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;
|
package/dist/inspect/report.js
CHANGED
|
@@ -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. */
|
|
@@ -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
|
|
@@ -0,0 +1,18 @@
|
|
|
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
|
+
/** The offset of the first match in `text`, or -1. */
|
|
11
|
+
export type Matcher = (text: string) => number;
|
|
12
|
+
/**
|
|
13
|
+
* A finder over a string. Literal by default: someone typing `user.id` means
|
|
14
|
+
* those seven characters, and a dot that quietly matched anything would be a
|
|
15
|
+
* worse answer than no answer.
|
|
16
|
+
*/
|
|
17
|
+
export declare function matcher(pattern: string, options?: MatchOptions): Matcher;
|
|
18
|
+
//# sourceMappingURL=match.d.ts.map
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// Matching, with nothing learned in between
|
|
3
|
+
//
|
|
4
|
+
// Everything here is exact. A substring is a substring, which is the whole
|
|
5
|
+
// point of the surface built on it: the vector index answers "what is near
|
|
6
|
+
// this", and near is a ranking, so it can only ever return the top of a list.
|
|
7
|
+
// When the question is "does the word `password` appear anywhere at all", a
|
|
8
|
+
// ranking is the wrong instrument and no amount of tuning makes it the right
|
|
9
|
+
// one.
|
|
10
|
+
//
|
|
11
|
+
// This is a copy of `common/match.ts` in @zenera/rag, and has to be: the
|
|
12
|
+
// dependency runs rag → cli → neo, so neo importing rag would close a cycle.
|
|
13
|
+
// Two differences earn their place rather than being drift. A matcher returns
|
|
14
|
+
// the offset it matched at, because grep reports *where*, not merely whether;
|
|
15
|
+
// and there are no globs, because the haystack here is prose and a glob is
|
|
16
|
+
// anchored to the whole string, so `error*` would match a line only when the
|
|
17
|
+
// line began with the word.
|
|
18
|
+
//
|
|
19
|
+
// A pattern may arrive from a model, so a regex is a bounded promise: the
|
|
20
|
+
// length is capped here and the scan that uses it keeps a deadline.
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
/** Long enough for any honest pattern, short enough to bound a bad one. */
|
|
23
|
+
export const MAX_PATTERN = 200;
|
|
24
|
+
export class PatternError extends Error {
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* A finder over a string. Literal by default: someone typing `user.id` means
|
|
28
|
+
* those seven characters, and a dot that quietly matched anything would be a
|
|
29
|
+
* worse answer than no answer.
|
|
30
|
+
*/
|
|
31
|
+
export function matcher(pattern, options = {}) {
|
|
32
|
+
guard(pattern);
|
|
33
|
+
if (!options.regex) {
|
|
34
|
+
if (options.caseSensitive) {
|
|
35
|
+
return (text) => text.indexOf(pattern);
|
|
36
|
+
}
|
|
37
|
+
const needle = pattern.toLowerCase();
|
|
38
|
+
return (text) => text.toLowerCase().indexOf(needle);
|
|
39
|
+
}
|
|
40
|
+
const expression = compile(pattern, options.caseSensitive ? '' : 'i');
|
|
41
|
+
// `lastIndex` is not carried between calls: the flags never include `g`.
|
|
42
|
+
return (text) => text.search(expression);
|
|
43
|
+
}
|
|
44
|
+
// ---------------------------------------------------------------------------
|
|
45
|
+
function guard(pattern) {
|
|
46
|
+
if (pattern.length === 0) {
|
|
47
|
+
throw new PatternError('the pattern is empty');
|
|
48
|
+
}
|
|
49
|
+
if (pattern.length > MAX_PATTERN) {
|
|
50
|
+
throw new PatternError(`the pattern is longer than ${MAX_PATTERN} characters`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
function compile(source, flags) {
|
|
54
|
+
try {
|
|
55
|
+
return new RegExp(source, flags);
|
|
56
|
+
}
|
|
57
|
+
catch (err) {
|
|
58
|
+
throw new PatternError(`invalid pattern: ${err.message}`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=match.js.map
|
package/dist/memory/store.js
CHANGED
|
@@ -115,12 +115,13 @@ export class MemoryStore {
|
|
|
115
115
|
if (this.readOnly) {
|
|
116
116
|
throw new MemoryError('this memory is open read-only', 'nothing this run learns can be kept — point it at a memory of its own');
|
|
117
117
|
}
|
|
118
|
-
|
|
118
|
+
// Indented so a committed memory diffs per node rather than as one line.
|
|
119
|
+
await atomic(join(this.dir, GRAPH_FILE), `${JSON.stringify(this.graph.export(), null, 2)}\n`);
|
|
119
120
|
const vectors = this.#vectors;
|
|
120
121
|
if (vectors) {
|
|
121
122
|
const raw = vectors.bytes();
|
|
122
123
|
await atomic(join(this.dir, VECTORS_FILE), new Uint8Array(raw.buffer, raw.byteOffset, raw.byteLength));
|
|
123
|
-
await atomic(join(this.dir, VECTOR_IDS_FILE), JSON.stringify({ dims: vectors.dims, ids: vectors.ids }));
|
|
124
|
+
await atomic(join(this.dir, VECTOR_IDS_FILE), `${JSON.stringify({ dims: vectors.dims, ids: vectors.ids }, null, 2)}\n`);
|
|
124
125
|
}
|
|
125
126
|
const manifest = {
|
|
126
127
|
version: MEMORY_VERSION,
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { MemoryStore } from '../memory.ts';
|
|
2
|
+
import { type FileMemoryStoreOptions } from './file.ts';
|
|
3
|
+
export interface MemoryMemoryStoreSpec {
|
|
4
|
+
kind: 'memory';
|
|
5
|
+
id?: string;
|
|
6
|
+
}
|
|
7
|
+
export interface FileMemoryStoreSpec extends FileMemoryStoreOptions {
|
|
8
|
+
kind: 'file';
|
|
9
|
+
}
|
|
10
|
+
/** Grows a member per backend (`pgvector`, `redis`, …). */
|
|
11
|
+
export type MemoryStoreSpec = MemoryMemoryStoreSpec | FileMemoryStoreSpec;
|
|
12
|
+
/** Shorthand: `mem`, `memory`, `file:./.data/memory`. */
|
|
13
|
+
export type MemoryStoreRef = MemoryStoreSpec | string;
|
|
14
|
+
export declare function createMemoryStore(ref: MemoryStoreRef): MemoryStore;
|
|
15
|
+
//# sourceMappingURL=factory.d.ts.map
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { FileMemoryStore } from "./file.js";
|
|
2
|
+
import { InMemoryMemoryStore } from "./in-memory.js";
|
|
3
|
+
export function createMemoryStore(ref) {
|
|
4
|
+
const spec = typeof ref === 'string' ? parseRef(ref) : ref;
|
|
5
|
+
switch (spec.kind) {
|
|
6
|
+
case 'memory':
|
|
7
|
+
return new InMemoryMemoryStore(spec.id);
|
|
8
|
+
case 'file':
|
|
9
|
+
return new FileMemoryStore(spec);
|
|
10
|
+
default:
|
|
11
|
+
throw new TypeError(`unknown memory store kind: ${spec.kind}`);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
function parseRef(ref) {
|
|
15
|
+
if (ref === 'mem' || ref === 'memory') {
|
|
16
|
+
return { kind: 'memory' };
|
|
17
|
+
}
|
|
18
|
+
const colon = ref.indexOf(':');
|
|
19
|
+
const scheme = colon < 0 ? ref : ref.slice(0, colon);
|
|
20
|
+
const rest = colon < 0 ? '' : ref.slice(colon + 1);
|
|
21
|
+
if (scheme === 'file') {
|
|
22
|
+
if (!rest) {
|
|
23
|
+
throw new TypeError(`missing directory in "${ref}" (expected file:<dir>)`);
|
|
24
|
+
}
|
|
25
|
+
return { kind: 'file', dir: rest };
|
|
26
|
+
}
|
|
27
|
+
throw new TypeError(`unknown memory store ref: "${ref}"`);
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=factory.js.map
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { type MemoryDraft, type MemoryHit, type MemoryPatch, type MemoryQuery, type MemoryRecord, type MemoryStore } from '../memory.ts';
|
|
2
|
+
export interface FileMemoryStoreOptions {
|
|
3
|
+
/** root directory; created on demand */
|
|
4
|
+
dir: string;
|
|
5
|
+
/** logical store id agents bind to; defaults to `file` */
|
|
6
|
+
id?: string;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* One JSON file per record, under one directory per scope:
|
|
10
|
+
*
|
|
11
|
+
* ```
|
|
12
|
+
* <dir>/<scope>/scope.json the unescaped scope name, for humans
|
|
13
|
+
* <dir>/<scope>/records/<id>.json
|
|
14
|
+
* <dir>/<scope>/ops/<opId>.json idempotency ledger
|
|
15
|
+
* ```
|
|
16
|
+
*
|
|
17
|
+
* A file per record (rather than one appended log) keeps writes atomic and
|
|
18
|
+
* deletes cheap without a compaction step. Search reads the whole scope and
|
|
19
|
+
* ranks in process, which is exactly the trade this backend makes: no server,
|
|
20
|
+
* no index, fine up to a few thousand records per scope.
|
|
21
|
+
*
|
|
22
|
+
* There is no cross-process lock. Concurrent writers to the *same* record can
|
|
23
|
+
* lose an update; `expectedRevision` narrows that window, and a deployment
|
|
24
|
+
* that needs real isolation should use a database-backed store.
|
|
25
|
+
*/
|
|
26
|
+
export declare class FileMemoryStore implements MemoryStore {
|
|
27
|
+
#private;
|
|
28
|
+
readonly id: string;
|
|
29
|
+
constructor(opts: FileMemoryStoreOptions | string);
|
|
30
|
+
get dir(): string;
|
|
31
|
+
search(scope: string, q: MemoryQuery): Promise<MemoryHit[]>;
|
|
32
|
+
get(scope: string, id: string): Promise<MemoryRecord | undefined>;
|
|
33
|
+
write(scope: string, rec: MemoryDraft, opId: string): Promise<MemoryRecord>;
|
|
34
|
+
update(scope: string, id: string, patch: MemoryPatch, opId: string): Promise<MemoryRecord>;
|
|
35
|
+
delete(scope: string, id: string, opId: string): Promise<void>;
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=file.d.ts.map
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { mkdir, readFile, readdir, rename, rm, writeFile } from 'node:fs/promises';
|
|
3
|
+
import { join, resolve } from 'node:path';
|
|
4
|
+
import { rankMemories, } from "../memory.js";
|
|
5
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
// Filesystem-backed memory store
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
/**
|
|
9
|
+
* Record ids and op ids reach this store from model-authored tool arguments
|
|
10
|
+
* (`memory_update`, `memory_delete`), so they are validated before becoming
|
|
11
|
+
* path segments. Dots are excluded outright, which makes `.` and `..`
|
|
12
|
+
* unrepresentable.
|
|
13
|
+
*/
|
|
14
|
+
const SAFE_ID = /^[A-Za-z0-9_-]+$/;
|
|
15
|
+
/**
|
|
16
|
+
* One JSON file per record, under one directory per scope:
|
|
17
|
+
*
|
|
18
|
+
* ```
|
|
19
|
+
* <dir>/<scope>/scope.json the unescaped scope name, for humans
|
|
20
|
+
* <dir>/<scope>/records/<id>.json
|
|
21
|
+
* <dir>/<scope>/ops/<opId>.json idempotency ledger
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* A file per record (rather than one appended log) keeps writes atomic and
|
|
25
|
+
* deletes cheap without a compaction step. Search reads the whole scope and
|
|
26
|
+
* ranks in process, which is exactly the trade this backend makes: no server,
|
|
27
|
+
* no index, fine up to a few thousand records per scope.
|
|
28
|
+
*
|
|
29
|
+
* There is no cross-process lock. Concurrent writers to the *same* record can
|
|
30
|
+
* lose an update; `expectedRevision` narrows that window, and a deployment
|
|
31
|
+
* that needs real isolation should use a database-backed store.
|
|
32
|
+
*/
|
|
33
|
+
export class FileMemoryStore {
|
|
34
|
+
id;
|
|
35
|
+
#dir;
|
|
36
|
+
constructor(opts) {
|
|
37
|
+
const o = typeof opts === 'string' ? { dir: opts } : opts;
|
|
38
|
+
this.#dir = resolve(o.dir);
|
|
39
|
+
this.id = o.id ?? 'file';
|
|
40
|
+
}
|
|
41
|
+
get dir() {
|
|
42
|
+
return this.#dir;
|
|
43
|
+
}
|
|
44
|
+
async search(scope, q) {
|
|
45
|
+
return rankMemories(await this.#all(scope), q);
|
|
46
|
+
}
|
|
47
|
+
async get(scope, id) {
|
|
48
|
+
return this.#readJson(this.#recordPath(scope, id));
|
|
49
|
+
}
|
|
50
|
+
async write(scope, rec, opId) {
|
|
51
|
+
const known = await this.#readOp(scope, opId);
|
|
52
|
+
if (known) {
|
|
53
|
+
const existing = await this.get(scope, known);
|
|
54
|
+
if (existing) {
|
|
55
|
+
return existing;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
const now = new Date().toISOString();
|
|
59
|
+
const record = {
|
|
60
|
+
id: randomUUID(),
|
|
61
|
+
scope,
|
|
62
|
+
kind: rec.kind ?? 'fact',
|
|
63
|
+
text: rec.text,
|
|
64
|
+
metadata: rec.metadata,
|
|
65
|
+
createdAt: now,
|
|
66
|
+
updatedAt: now,
|
|
67
|
+
revision: 1,
|
|
68
|
+
};
|
|
69
|
+
await this.#writeRecord(record);
|
|
70
|
+
await this.#writeOp(scope, opId, record.id);
|
|
71
|
+
return record;
|
|
72
|
+
}
|
|
73
|
+
async update(scope, id, patch, opId) {
|
|
74
|
+
const cur = await this.get(scope, id);
|
|
75
|
+
if (!cur) {
|
|
76
|
+
throw new Error(`memory record not found: ${scope}/${id}`);
|
|
77
|
+
}
|
|
78
|
+
if ((await this.#readOp(scope, opId)) === id) {
|
|
79
|
+
return cur;
|
|
80
|
+
}
|
|
81
|
+
if (patch.expectedRevision !== undefined && patch.expectedRevision !== cur.revision) {
|
|
82
|
+
throw new Error(`memory conflict on ${scope}/${id}: expected revision ` +
|
|
83
|
+
`${patch.expectedRevision}, found ${cur.revision}`);
|
|
84
|
+
}
|
|
85
|
+
const next = {
|
|
86
|
+
...cur,
|
|
87
|
+
kind: patch.kind ?? cur.kind,
|
|
88
|
+
text: patch.text ?? cur.text,
|
|
89
|
+
metadata: patch.metadata ?? cur.metadata,
|
|
90
|
+
updatedAt: new Date().toISOString(),
|
|
91
|
+
revision: cur.revision + 1,
|
|
92
|
+
};
|
|
93
|
+
await this.#writeRecord(next);
|
|
94
|
+
await this.#writeOp(scope, opId, id);
|
|
95
|
+
return next;
|
|
96
|
+
}
|
|
97
|
+
async delete(scope, id, opId) {
|
|
98
|
+
await rm(this.#recordPath(scope, id), { force: true });
|
|
99
|
+
await this.#writeOp(scope, opId, id);
|
|
100
|
+
}
|
|
101
|
+
// --- layout -----------------------------------------------------------
|
|
102
|
+
/**
|
|
103
|
+
* Percent-encoding is reversible and, with dots escaped too, cannot
|
|
104
|
+
* produce a traversal segment out of a scope like `../etc`.
|
|
105
|
+
*/
|
|
106
|
+
#scopeDir(scope) {
|
|
107
|
+
return join(this.#dir, encodeURIComponent(scope).replace(/\./g, '%2E'));
|
|
108
|
+
}
|
|
109
|
+
#recordPath(scope, id) {
|
|
110
|
+
return join(this.#scopeDir(scope), 'records', `${safeId(id, 'record id')}.json`);
|
|
111
|
+
}
|
|
112
|
+
#opPath(scope, opId) {
|
|
113
|
+
return join(this.#scopeDir(scope), 'ops', `${safeId(opId, 'op id')}.json`);
|
|
114
|
+
}
|
|
115
|
+
// --- io ---------------------------------------------------------------
|
|
116
|
+
async #all(scope) {
|
|
117
|
+
const dir = join(this.#scopeDir(scope), 'records');
|
|
118
|
+
let names;
|
|
119
|
+
try {
|
|
120
|
+
names = await readdir(dir);
|
|
121
|
+
}
|
|
122
|
+
catch (err) {
|
|
123
|
+
if (err.code === 'ENOENT') {
|
|
124
|
+
return [];
|
|
125
|
+
}
|
|
126
|
+
throw err;
|
|
127
|
+
}
|
|
128
|
+
const records = await Promise.all(names
|
|
129
|
+
.filter((n) => n.endsWith('.json'))
|
|
130
|
+
.map((n) => this.#readJson(join(dir, n))));
|
|
131
|
+
return records.filter((r) => r !== undefined);
|
|
132
|
+
}
|
|
133
|
+
async #readOp(scope, opId) {
|
|
134
|
+
const op = await this.#readJson(this.#opPath(scope, opId));
|
|
135
|
+
return op?.recordId;
|
|
136
|
+
}
|
|
137
|
+
async #writeOp(scope, opId, recordId) {
|
|
138
|
+
await writeJson(this.#opPath(scope, opId), { recordId });
|
|
139
|
+
}
|
|
140
|
+
async #writeRecord(record) {
|
|
141
|
+
await writeJson(join(this.#scopeDir(record.scope), 'scope.json'), { scope: record.scope });
|
|
142
|
+
await writeJson(this.#recordPath(record.scope, record.id), record);
|
|
143
|
+
}
|
|
144
|
+
async #readJson(path) {
|
|
145
|
+
try {
|
|
146
|
+
return JSON.parse(await readFile(path, 'utf8'));
|
|
147
|
+
}
|
|
148
|
+
catch (err) {
|
|
149
|
+
if (err.code === 'ENOENT') {
|
|
150
|
+
return undefined;
|
|
151
|
+
}
|
|
152
|
+
throw err;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
function safeId(value, what) {
|
|
157
|
+
if (!SAFE_ID.test(value)) {
|
|
158
|
+
throw new Error(`invalid ${what}: ${JSON.stringify(value)}`);
|
|
159
|
+
}
|
|
160
|
+
return value;
|
|
161
|
+
}
|
|
162
|
+
async function writeJson(path, value) {
|
|
163
|
+
await mkdir(join(path, '..'), { recursive: true });
|
|
164
|
+
const tmp = `${path}.${process.pid}.${randomUUID()}.tmp`;
|
|
165
|
+
await writeFile(tmp, JSON.stringify(value), 'utf8');
|
|
166
|
+
try {
|
|
167
|
+
await rename(tmp, path);
|
|
168
|
+
}
|
|
169
|
+
catch (err) {
|
|
170
|
+
await rm(tmp, { force: true });
|
|
171
|
+
throw err;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
//# sourceMappingURL=file.js.map
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type MemoryDraft, type MemoryHit, type MemoryPatch, type MemoryQuery, type MemoryRecord, type MemoryStore } from '../memory.ts';
|
|
2
|
+
/**
|
|
3
|
+
* Reference implementation: scoped maps plus token-overlap scoring. Good enough
|
|
4
|
+
* for tests and local runs; production deployments swap in a vector store
|
|
5
|
+
* without touching the kernel.
|
|
6
|
+
*/
|
|
7
|
+
export declare class InMemoryMemoryStore implements MemoryStore {
|
|
8
|
+
#private;
|
|
9
|
+
readonly id: string;
|
|
10
|
+
constructor(id?: string);
|
|
11
|
+
search(scope: string, q: MemoryQuery): Promise<MemoryHit[]>;
|
|
12
|
+
get(scope: string, id: string): Promise<MemoryRecord | undefined>;
|
|
13
|
+
write(scope: string, rec: MemoryDraft, opId: string): Promise<MemoryRecord>;
|
|
14
|
+
update(scope: string, id: string, patch: MemoryPatch, opId: string): Promise<MemoryRecord>;
|
|
15
|
+
delete(scope: string, id: string, opId: string): Promise<void>;
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=in-memory.d.ts.map
|