@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.
- package/dist/agent.js +2 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- 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/lock.d.ts +27 -0
- package/dist/lock.js +66 -0
- package/dist/match.d.ts +44 -0
- package/dist/match.js +100 -0
- package/dist/memory/diff.d.ts +39 -0
- package/dist/memory/diff.js +95 -0
- package/dist/memory/files.d.ts +1 -1
- package/dist/memory/grep.d.ts +1 -1
- package/dist/memory/grep.js +2 -2
- package/dist/memory/instructions.d.ts +15 -0
- package/dist/memory/instructions.js +103 -0
- package/dist/memory/merge.js +1 -31
- package/dist/memory/store.d.ts +1 -5
- package/dist/memory/store.js +7 -53
- package/dist/memory/twin.d.ts +6 -0
- package/dist/memory/twin.js +40 -0
- 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/models/factory.d.ts +24 -0
- package/dist/models/factory.js +153 -14
- package/dist/project/config.d.ts +9 -9
- package/dist/runner.js +7 -0
- package/dist/tools/sandbox.js +40 -2
- 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. */
|
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
|
package/dist/match.d.ts
ADDED
|
@@ -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
|
package/dist/memory/files.d.ts
CHANGED
|
@@ -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
|
package/dist/memory/grep.d.ts
CHANGED
package/dist/memory/grep.js
CHANGED
|
@@ -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
|
|
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
|