@zenera/neo 1.1.20 → 1.1.22
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/README.md +1 -1
- package/dist/fork.js +4 -3
- package/dist/inspect/client.d.ts +1 -1
- package/dist/inspect/client.js +95 -14
- package/dist/inspect/css.d.ts +1 -1
- package/dist/inspect/css.js +28 -3
- package/dist/kernel.js +22 -1
- package/dist/memory/files.d.ts +2 -0
- package/dist/memory/files.js +10 -9
- package/dist/memory/index.d.ts +1 -0
- package/dist/memory/index.js +48 -16
- package/dist/memory/tools.js +66 -16
- package/dist/project/config.d.ts +12 -0
- package/dist/project/config.js +14 -0
- package/dist/project/load.js +61 -0
- package/dist/tools/sandbox.d.ts +25 -0
- package/dist/tools/sandbox.js +58 -8
- package/dist/tools/workspace.js +2 -2
- package/dist/types.js +25 -10
- 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/* Section tags inside a prompt (<house_rules src=\"...\">, <role>, ...): the only\n structure a composed prompt has, and invisible without this. */\npre .xtag { color: #ffb0cd; background: #2d1b27; border: 1px solid #4e2c40;\n border-radius: 4px; padding: 0 4px; font-weight: 600; }\npre .xtag.end { color: #a07f92; background: #201a1e; border-color: #3b2b35;\n font-weight: 400; }\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; }\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,12 +183,37 @@ 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
|
+
.tagged-pre { margin: 4px 0; }
|
|
187
|
+
.tagged-pre > pre.text { margin: 0; padding: 6px 12px; }
|
|
186
188
|
/* Section tags inside a prompt (<house_rules src="...">, <role>, ...): the only
|
|
187
189
|
structure a composed prompt has, and invisible without this. */
|
|
188
|
-
|
|
189
|
-
border-radius: 4px; padding:
|
|
190
|
-
|
|
190
|
+
.xtag { color: #ffb0cd; background: #2d1b27; border: 1px solid #4e2c40;
|
|
191
|
+
border-radius: 4px; padding: 1px 5px; font-weight: 600; font: 12px var(--mono); }
|
|
192
|
+
.xtag.end { color: #a07f92; background: #201a1e; border-color: #3b2b35;
|
|
191
193
|
font-weight: 400; }
|
|
194
|
+
details.xtag-section {
|
|
195
|
+
border: 1px solid var(--line); border-radius: 6px; margin: 6px 12px;
|
|
196
|
+
background: var(--panel);
|
|
197
|
+
}
|
|
198
|
+
details.xtag-section > summary {
|
|
199
|
+
cursor: pointer; padding: 6px 10px; font: 12px/1.55 var(--mono);
|
|
200
|
+
color: var(--dim); white-space: nowrap; overflow: hidden;
|
|
201
|
+
text-overflow: ellipsis; user-select: none;
|
|
202
|
+
}
|
|
203
|
+
details.xtag-section > summary:hover { background: rgba(255, 255, 255, 0.03); }
|
|
204
|
+
details.xtag-section > summary .preview {
|
|
205
|
+
color: var(--dim); margin-left: 8px; font-size: 11px;
|
|
206
|
+
}
|
|
207
|
+
details.xtag-section[open] > summary {
|
|
208
|
+
white-space: normal; border-bottom: 1px solid var(--line);
|
|
209
|
+
background: rgba(0, 0, 0, 0.15);
|
|
210
|
+
}
|
|
211
|
+
details.xtag-section[open] > summary .preview { display: none; }
|
|
212
|
+
details.xtag-section > pre.xtag-body {
|
|
213
|
+
margin: 0; padding: 8px 12px 10px; background: #0d1017;
|
|
214
|
+
border-radius: 0 0 5px 5px; font: 12px/1.55 var(--mono);
|
|
215
|
+
white-space: pre-wrap; word-break: break-word; overflow-wrap: anywhere;
|
|
216
|
+
}
|
|
192
217
|
img.media { display: block; max-width: 100%; max-height: 420px; margin: 10px 12px;
|
|
193
218
|
border: 1px solid var(--line); border-radius: 6px; background: #0d1017; }
|
|
194
219
|
img.media.thumb { max-height: 88px; margin: 8px 12px; cursor: zoom-in; }
|
package/dist/kernel.js
CHANGED
|
@@ -378,11 +378,32 @@ export async function applySystemPrompt(state, reg, env) {
|
|
|
378
378
|
}
|
|
379
379
|
function parseForkArgs(raw) {
|
|
380
380
|
const args = parseArgs(raw);
|
|
381
|
-
|
|
381
|
+
let branches = args.branches;
|
|
382
|
+
// When a model delegates a single task, it frequently flattens the branch
|
|
383
|
+
// fields directly into the root arguments instead of wrapping them in a
|
|
384
|
+
// 1-element array. Coerce it if name and instructions are present at the root.
|
|
385
|
+
if (!Array.isArray(branches) &&
|
|
386
|
+
typeof args.name === 'string' &&
|
|
387
|
+
typeof args.instructions === 'string') {
|
|
388
|
+
branches = [
|
|
389
|
+
{
|
|
390
|
+
name: args.name,
|
|
391
|
+
instructions: args.instructions,
|
|
392
|
+
agent: typeof args.agent === 'string' ? args.agent : undefined,
|
|
393
|
+
},
|
|
394
|
+
];
|
|
395
|
+
}
|
|
382
396
|
// Spelled out rather than "invalid arguments": this string is fed back to
|
|
383
397
|
// the model as the tool result, and it is the only chance it gets to work
|
|
384
398
|
// out what to do differently.
|
|
385
399
|
if (!Array.isArray(branches) || branches.length === 0) {
|
|
400
|
+
if (args.name !== undefined ||
|
|
401
|
+
args.instructions !== undefined ||
|
|
402
|
+
args.agent !== undefined) {
|
|
403
|
+
return ('the "branches" argument is missing; "fork" requires an array of branches: ' +
|
|
404
|
+
'{ "branches": [{ "name": "...", "instructions": "...", "agent": "..." }] }. ' +
|
|
405
|
+
'Do not pass branch fields at the top level.');
|
|
406
|
+
}
|
|
386
407
|
return 'the "branches" argument is missing or empty; it must be an array of at least one branch';
|
|
387
408
|
}
|
|
388
409
|
const parsed = [];
|
package/dist/memory/files.d.ts
CHANGED
|
@@ -8,6 +8,8 @@ export declare const MEMORY_MOUNT = "/memory";
|
|
|
8
8
|
export declare const MAX_FILE_BYTES: number;
|
|
9
9
|
export interface RememberOptions {
|
|
10
10
|
maxBytes?: number;
|
|
11
|
+
/** Display path as passed to the tool, for reporting errors without leaking the host filesystem */
|
|
12
|
+
path?: string;
|
|
11
13
|
}
|
|
12
14
|
/**
|
|
13
15
|
* Copy `source` into the memory directory under `id` and describe it.
|
package/dist/memory/files.js
CHANGED
|
@@ -35,23 +35,24 @@ const MAX_FORMAT_LEN = 16;
|
|
|
35
35
|
*/
|
|
36
36
|
export async function rememberFile(dir, source, id, opts = {}) {
|
|
37
37
|
const maxBytes = opts.maxBytes ?? MAX_FILE_BYTES;
|
|
38
|
-
const
|
|
38
|
+
const name = opts.path ?? source;
|
|
39
|
+
const info = await describe(source, name);
|
|
39
40
|
if (!info.isFile()) {
|
|
40
|
-
throw new MemoryError(`${
|
|
41
|
+
throw new MemoryError(`${name} is not a regular file`, 'only files can be remembered; write a `fact` node describing a directory instead');
|
|
41
42
|
}
|
|
42
43
|
if (info.size > maxBytes) {
|
|
43
|
-
throw new MemoryError(`${
|
|
44
|
+
throw new MemoryError(`${name} is ${info.size} bytes, over the ${maxBytes} byte limit for a remembered file`, 'keep the artifact in the workspace and remember a `snippet` of the part that matters');
|
|
44
45
|
}
|
|
45
46
|
const bytes = await readFile(source);
|
|
46
47
|
if (bytes.byteLength > maxBytes) {
|
|
47
|
-
throw new MemoryError(`${
|
|
48
|
+
throw new MemoryError(`${name} grew past the ${maxBytes} byte limit while being read`);
|
|
48
49
|
}
|
|
49
50
|
const format = formatOf(source);
|
|
50
|
-
const
|
|
51
|
+
const fileName = id + (format ? `.${format}` : '');
|
|
51
52
|
await mkdir(join(dir, FILES_DIR), { recursive: true });
|
|
52
|
-
await writeFile(join(dir, FILES_DIR,
|
|
53
|
+
await writeFile(join(dir, FILES_DIR, fileName), bytes);
|
|
53
54
|
return {
|
|
54
|
-
path: `${MEMORY_MOUNT}/${
|
|
55
|
+
path: `${MEMORY_MOUNT}/${fileName}`,
|
|
55
56
|
bytes: bytes.byteLength,
|
|
56
57
|
sha256: createHash('sha256').update(bytes).digest('hex'),
|
|
57
58
|
format,
|
|
@@ -66,12 +67,12 @@ export function hostPath(dir, file) {
|
|
|
66
67
|
export async function forgetFile(dir, file) {
|
|
67
68
|
await rm(hostPath(dir, file), { force: true });
|
|
68
69
|
}
|
|
69
|
-
async function describe(source) {
|
|
70
|
+
async function describe(source, name = source) {
|
|
70
71
|
try {
|
|
71
72
|
return await stat(source);
|
|
72
73
|
}
|
|
73
74
|
catch {
|
|
74
|
-
throw new MemoryError(`${
|
|
75
|
+
throw new MemoryError(`${name} does not exist`, 'remember a file the run actually wrote, by the path the file tools use');
|
|
75
76
|
}
|
|
76
77
|
}
|
|
77
78
|
/**
|
package/dist/memory/index.d.ts
CHANGED
package/dist/memory/index.js
CHANGED
|
@@ -111,7 +111,9 @@ export class MemoryIndex {
|
|
|
111
111
|
const written = [];
|
|
112
112
|
try {
|
|
113
113
|
for (const item of plan.files) {
|
|
114
|
-
const file = await rememberFile(this.store.dir, item.source, item.id
|
|
114
|
+
const file = await rememberFile(this.store.dir, item.source, item.id, {
|
|
115
|
+
path: item.path,
|
|
116
|
+
});
|
|
115
117
|
written.push(file);
|
|
116
118
|
item.target.file = file;
|
|
117
119
|
}
|
|
@@ -239,20 +241,41 @@ export class MemoryIndex {
|
|
|
239
241
|
const files = [];
|
|
240
242
|
const embed = [];
|
|
241
243
|
for (const [i, node] of nodes.entries()) {
|
|
242
|
-
|
|
243
|
-
|
|
244
|
+
let ref = node.ref;
|
|
245
|
+
let id = node.id;
|
|
246
|
+
const where = ref ?? id ?? `nodes[${i}]`;
|
|
247
|
+
// If the model passed both ref and id with the same value (e.g. ref: "x", id: "x"),
|
|
248
|
+
// and id is not an existing node, treat it as a new node create.
|
|
249
|
+
if (ref !== undefined && id !== undefined) {
|
|
250
|
+
if (ref === id && !this.graph.has(id)) {
|
|
251
|
+
id = undefined;
|
|
252
|
+
}
|
|
253
|
+
else {
|
|
254
|
+
throw new MemoryError(`${where}: give either a ref (to create) or an id (to update), not both`);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
else if (ref === undefined &&
|
|
258
|
+
id !== undefined &&
|
|
259
|
+
!this.graph.has(id) &&
|
|
260
|
+
(node.kind !== undefined || (node.text !== undefined && node.text.trim()))) {
|
|
261
|
+
// Models often assign "id" to new nodes instead of "ref". If the id does not
|
|
262
|
+
// match an existing node in memory, treat it as the local ref for creation.
|
|
263
|
+
ref = id;
|
|
264
|
+
id = undefined;
|
|
265
|
+
}
|
|
266
|
+
if (ref === undefined && id === undefined) {
|
|
244
267
|
throw new MemoryError(`${where}: give either a ref (to create) or an id (to update), not both`);
|
|
245
268
|
}
|
|
246
269
|
const audience = this.#audience(node, opts.writes, where);
|
|
247
|
-
if (
|
|
248
|
-
const existing = this.graph.get(
|
|
270
|
+
if (id) {
|
|
271
|
+
const existing = this.graph.get(id);
|
|
249
272
|
if (!existing) {
|
|
250
273
|
throw new MemoryError(`${where}: no such memory`);
|
|
251
274
|
}
|
|
252
275
|
if (node.kind !== undefined) {
|
|
253
276
|
this.#kind(node.kind, where);
|
|
254
277
|
}
|
|
255
|
-
ids[
|
|
278
|
+
ids[id] = id;
|
|
256
279
|
const patch = {
|
|
257
280
|
...(node.kind !== undefined ? { kind: node.kind } : {}),
|
|
258
281
|
...(node.text !== undefined ? { text: node.text } : {}),
|
|
@@ -262,18 +285,22 @@ export class MemoryIndex {
|
|
|
262
285
|
? { expectedRevision: node.expectedRevision }
|
|
263
286
|
: {}),
|
|
264
287
|
};
|
|
265
|
-
planned.push({ existing: true, id
|
|
288
|
+
planned.push({ existing: true, id, patch });
|
|
266
289
|
// Only a text change invalidates the vector; re-embedding on an
|
|
267
290
|
// audience edit would burn a call to store the same numbers.
|
|
268
291
|
if (node.text !== undefined && node.text !== existing.text) {
|
|
269
|
-
embed.push({ id
|
|
292
|
+
embed.push({ id, text: node.text });
|
|
270
293
|
}
|
|
271
294
|
if (node.file) {
|
|
272
|
-
files.push({
|
|
295
|
+
files.push({
|
|
296
|
+
id,
|
|
297
|
+
source: node.file.source,
|
|
298
|
+
path: node.file.path,
|
|
299
|
+
target: patch,
|
|
300
|
+
});
|
|
273
301
|
}
|
|
274
302
|
continue;
|
|
275
303
|
}
|
|
276
|
-
const ref = node.ref;
|
|
277
304
|
if (ref in ids) {
|
|
278
305
|
throw new MemoryError(`${where}: two nodes share the ref "${ref}"`);
|
|
279
306
|
}
|
|
@@ -281,19 +308,24 @@ export class MemoryIndex {
|
|
|
281
308
|
throw new MemoryError(`${where}: a new node needs text`, 'text is the only thing a search can match, so it has to describe the node');
|
|
282
309
|
}
|
|
283
310
|
const kind = this.#kind(node.kind, where);
|
|
284
|
-
const
|
|
285
|
-
ids[ref] =
|
|
311
|
+
const newId = opts.clock.newId();
|
|
312
|
+
ids[ref] = newId;
|
|
286
313
|
const draft = {
|
|
287
|
-
id,
|
|
314
|
+
id: newId,
|
|
288
315
|
kind,
|
|
289
316
|
text: node.text,
|
|
290
317
|
audience: audience ?? [ALL_AGENTS],
|
|
291
318
|
...(node.metadata !== undefined ? { metadata: node.metadata } : {}),
|
|
292
319
|
};
|
|
293
|
-
planned.push({ existing: false, id, draft });
|
|
294
|
-
embed.push({ id, text: node.text });
|
|
320
|
+
planned.push({ existing: false, id: newId, draft });
|
|
321
|
+
embed.push({ id: newId, text: node.text });
|
|
295
322
|
if (node.file) {
|
|
296
|
-
files.push({
|
|
323
|
+
files.push({
|
|
324
|
+
id: newId,
|
|
325
|
+
source: node.file.source,
|
|
326
|
+
path: node.file.path,
|
|
327
|
+
target: draft,
|
|
328
|
+
});
|
|
297
329
|
}
|
|
298
330
|
}
|
|
299
331
|
const resolved = edges.map((edge, i) => {
|
package/dist/memory/tools.js
CHANGED
|
@@ -161,11 +161,11 @@ export function memoryTools(opts) {
|
|
|
161
161
|
properties: {
|
|
162
162
|
ref: {
|
|
163
163
|
type: 'string',
|
|
164
|
-
description: 'a short name for a new node, used by edges in this call',
|
|
164
|
+
description: 'a short local name for a new node (e.g. "task_1", "file_1"), used by edges in this call. Use ref (not id) when creating new nodes.',
|
|
165
165
|
},
|
|
166
166
|
id: {
|
|
167
167
|
type: 'string',
|
|
168
|
-
description: '
|
|
168
|
+
description: 'the permanent id of an existing node to update in place (from search/load). Do not use for new nodes; use ref instead.',
|
|
169
169
|
},
|
|
170
170
|
kind: { type: 'string', enum: kinds, description: kindHelp },
|
|
171
171
|
text: {
|
|
@@ -173,8 +173,25 @@ export function memoryTools(opts) {
|
|
|
173
173
|
description: 'what to remember, in your own words',
|
|
174
174
|
},
|
|
175
175
|
file: {
|
|
176
|
-
|
|
177
|
-
|
|
176
|
+
oneOf: [
|
|
177
|
+
{
|
|
178
|
+
type: 'string',
|
|
179
|
+
description: 'path of an existing file in the workspace to keep a copy of (e.g. "report.py" or "/workspace/report.py"), for kind "file"',
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
type: 'object',
|
|
183
|
+
properties: {
|
|
184
|
+
path: {
|
|
185
|
+
type: 'string',
|
|
186
|
+
description: 'path of an existing file in the workspace',
|
|
187
|
+
},
|
|
188
|
+
},
|
|
189
|
+
required: ['path'],
|
|
190
|
+
additionalProperties: false,
|
|
191
|
+
description: 'file object with path',
|
|
192
|
+
},
|
|
193
|
+
],
|
|
194
|
+
description: 'path of an existing file in the workspace to keep a copy of (e.g. "report.py" or "/workspace/report.py"), for kind "file"',
|
|
178
195
|
},
|
|
179
196
|
...audience,
|
|
180
197
|
expected_revision: {
|
|
@@ -192,11 +209,11 @@ export function memoryTools(opts) {
|
|
|
192
209
|
properties: {
|
|
193
210
|
from: {
|
|
194
211
|
type: 'string',
|
|
195
|
-
description: 'a ref from this call, or an id',
|
|
212
|
+
description: 'a ref from this call, or an existing node id',
|
|
196
213
|
},
|
|
197
214
|
to: {
|
|
198
215
|
type: 'string',
|
|
199
|
-
description: 'a ref from this call, or an id',
|
|
216
|
+
description: 'a ref from this call, or an existing node id',
|
|
200
217
|
},
|
|
201
218
|
relation: {
|
|
202
219
|
type: 'string',
|
|
@@ -216,15 +233,28 @@ export function memoryTools(opts) {
|
|
|
216
233
|
const opId = memoryOpId(tc.state.runId, tc.callId);
|
|
217
234
|
let nodes;
|
|
218
235
|
try {
|
|
219
|
-
nodes = args.nodes.map((n) =>
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
236
|
+
nodes = args.nodes.map((n) => {
|
|
237
|
+
let fileSpec;
|
|
238
|
+
if (n.file !== undefined) {
|
|
239
|
+
const rawPath = extractFilePath(n.file);
|
|
240
|
+
if (!rawPath.trim()) {
|
|
241
|
+
throw new MemoryError('file path is required', 'pass the path of the file in the workspace to remember, e.g. "report.py"');
|
|
242
|
+
}
|
|
243
|
+
fileSpec = {
|
|
244
|
+
source: source(tc, rawPath),
|
|
245
|
+
path: rawPath,
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
return {
|
|
249
|
+
ref: n.ref,
|
|
250
|
+
id: n.id,
|
|
251
|
+
kind: n.kind,
|
|
252
|
+
text: n.text,
|
|
253
|
+
audience: n.audience,
|
|
254
|
+
expectedRevision: n.expected_revision,
|
|
255
|
+
file: fileSpec,
|
|
256
|
+
};
|
|
257
|
+
});
|
|
228
258
|
}
|
|
229
259
|
catch (err) {
|
|
230
260
|
return refusal(err);
|
|
@@ -322,11 +352,31 @@ function refusal(err) {
|
|
|
322
352
|
}
|
|
323
353
|
throw err;
|
|
324
354
|
}
|
|
355
|
+
function extractFilePath(raw) {
|
|
356
|
+
if (typeof raw === 'string') {
|
|
357
|
+
return raw;
|
|
358
|
+
}
|
|
359
|
+
if (raw &&
|
|
360
|
+
typeof raw === 'object' &&
|
|
361
|
+
'path' in raw &&
|
|
362
|
+
typeof raw.path === 'string') {
|
|
363
|
+
return raw.path;
|
|
364
|
+
}
|
|
365
|
+
return '';
|
|
366
|
+
}
|
|
325
367
|
function source(tc, path) {
|
|
326
368
|
if (!tc.services.resolveFile) {
|
|
327
369
|
throw new MemoryError('this agent cannot remember files', 'commit the node without `file`, or give the agent a workspace');
|
|
328
370
|
}
|
|
329
|
-
|
|
371
|
+
try {
|
|
372
|
+
return tc.services.resolveFile(path);
|
|
373
|
+
}
|
|
374
|
+
catch (err) {
|
|
375
|
+
if (err instanceof MemoryError) {
|
|
376
|
+
throw err;
|
|
377
|
+
}
|
|
378
|
+
throw new MemoryError(err instanceof Error ? err.message : String(err), 'remember a file the run actually wrote, by the path the file tools use');
|
|
379
|
+
}
|
|
330
380
|
}
|
|
331
381
|
function changed(index, ids) {
|
|
332
382
|
return Object.values(ids).flatMap((id) => {
|
package/dist/project/config.d.ts
CHANGED
|
@@ -169,6 +169,10 @@ declare const sandbox: z.ZodObject<{
|
|
|
169
169
|
}>>;
|
|
170
170
|
workdir: z.ZodOptional<z.ZodString>;
|
|
171
171
|
timeout: z.ZodOptional<z.ZodInt>;
|
|
172
|
+
hardening: z.ZodOptional<z.ZodEnum<{
|
|
173
|
+
strict: "strict";
|
|
174
|
+
standard: "standard";
|
|
175
|
+
}>>;
|
|
172
176
|
user: z.ZodOptional<z.ZodString>;
|
|
173
177
|
persist: z.ZodOptional<z.ZodBoolean>;
|
|
174
178
|
env: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
@@ -295,6 +299,10 @@ export declare const projectSchema: z.ZodObject<{
|
|
|
295
299
|
}>>;
|
|
296
300
|
workdir: z.ZodOptional<z.ZodString>;
|
|
297
301
|
timeout: z.ZodOptional<z.ZodInt>;
|
|
302
|
+
hardening: z.ZodOptional<z.ZodEnum<{
|
|
303
|
+
strict: "strict";
|
|
304
|
+
standard: "standard";
|
|
305
|
+
}>>;
|
|
298
306
|
user: z.ZodOptional<z.ZodString>;
|
|
299
307
|
persist: z.ZodOptional<z.ZodBoolean>;
|
|
300
308
|
env: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
@@ -355,6 +363,10 @@ export declare const projectSchema: z.ZodObject<{
|
|
|
355
363
|
}>>;
|
|
356
364
|
workdir: z.ZodOptional<z.ZodString>;
|
|
357
365
|
timeout: z.ZodOptional<z.ZodInt>;
|
|
366
|
+
hardening: z.ZodOptional<z.ZodEnum<{
|
|
367
|
+
strict: "strict";
|
|
368
|
+
standard: "standard";
|
|
369
|
+
}>>;
|
|
358
370
|
user: z.ZodOptional<z.ZodString>;
|
|
359
371
|
persist: z.ZodOptional<z.ZodBoolean>;
|
|
360
372
|
env: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
package/dist/project/config.js
CHANGED
|
@@ -263,6 +263,20 @@ const sandbox = z
|
|
|
263
263
|
workdir: z.string().min(1).optional(),
|
|
264
264
|
/** seconds one command may take before it is killed */
|
|
265
265
|
timeout: z.int().positive().optional(),
|
|
266
|
+
/**
|
|
267
|
+
* How much of the host the container is allowed to be.
|
|
268
|
+
*
|
|
269
|
+
* `standard` (the default) keeps the kernel capabilities and a
|
|
270
|
+
* writable root filesystem, so `apt` and `pip` work — which is what a
|
|
271
|
+
* project whose agents install their own dependencies needs.
|
|
272
|
+
*
|
|
273
|
+
* `strict` drops every capability, mounts the root filesystem
|
|
274
|
+
* read-only with an in-memory `noexec` `/tmp`, runs as an
|
|
275
|
+
* unprivileged uid and turns the network off unless this block asks
|
|
276
|
+
* for one. Nothing can be installed at run time, so the image has to
|
|
277
|
+
* carry everything the agent will need. It requires rootless podman.
|
|
278
|
+
*/
|
|
279
|
+
hardening: z.enum(['standard', 'strict']).optional(),
|
|
266
280
|
/** uid, name or `uid:gid`; unset means the image's own user */
|
|
267
281
|
user: z.string().min(1).optional(),
|
|
268
282
|
/** keep the container between sessions instead of removing it */
|
package/dist/project/load.js
CHANGED
|
@@ -234,6 +234,63 @@ function readHouseRules(root) {
|
|
|
234
234
|
}
|
|
235
235
|
return rules;
|
|
236
236
|
}
|
|
237
|
+
const FILE_TOOLS = [
|
|
238
|
+
'read_file',
|
|
239
|
+
'list_dir',
|
|
240
|
+
'find_files',
|
|
241
|
+
'write_file',
|
|
242
|
+
'apply_patch',
|
|
243
|
+
'move_file',
|
|
244
|
+
'delete_file',
|
|
245
|
+
];
|
|
246
|
+
const FILE_WRITE_TOOLS = new Set(['write_file', 'apply_patch', 'move_file', 'delete_file']);
|
|
247
|
+
function activeFileTools(selectors) {
|
|
248
|
+
const active = new Set();
|
|
249
|
+
if (!selectors?.length) {
|
|
250
|
+
return active;
|
|
251
|
+
}
|
|
252
|
+
for (const raw of selectors) {
|
|
253
|
+
const drop = raw.startsWith('-');
|
|
254
|
+
const selector = (drop ? raw.slice(1) : raw).trim();
|
|
255
|
+
if (selector === '*' || selector === 'files:*' || selector === 'workspace:*') {
|
|
256
|
+
for (const t of FILE_TOOLS) {
|
|
257
|
+
if (drop) {
|
|
258
|
+
active.delete(t);
|
|
259
|
+
}
|
|
260
|
+
else {
|
|
261
|
+
active.add(t);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
266
|
+
const colon = selector.lastIndexOf(':');
|
|
267
|
+
const name = colon >= 0 ? selector.slice(colon + 1) : selector;
|
|
268
|
+
const group = colon >= 0 ? selector.slice(0, colon) : undefined;
|
|
269
|
+
if (FILE_TOOLS.includes(name)) {
|
|
270
|
+
if (!group || group === 'files' || group === 'workspace') {
|
|
271
|
+
if (drop) {
|
|
272
|
+
active.delete(name);
|
|
273
|
+
}
|
|
274
|
+
else {
|
|
275
|
+
active.add(name);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
return active;
|
|
281
|
+
}
|
|
282
|
+
function hasFileTools(spec) {
|
|
283
|
+
return activeFileTools(spec.tools).size > 0;
|
|
284
|
+
}
|
|
285
|
+
function hasFileWriteTools(spec) {
|
|
286
|
+
const active = activeFileTools(spec.tools);
|
|
287
|
+
for (const t of FILE_WRITE_TOOLS) {
|
|
288
|
+
if (active.has(t)) {
|
|
289
|
+
return true;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
return false;
|
|
293
|
+
}
|
|
237
294
|
/**
|
|
238
295
|
* What `requires:` may name, and what each one means. A closed vocabulary
|
|
239
296
|
* rather than an expression language: these are the capabilities the runtime
|
|
@@ -253,6 +310,10 @@ const CAPABILITIES = {
|
|
|
253
310
|
memory: (spec) => Boolean(spec.memory),
|
|
254
311
|
'memory-write': (spec) => spec.memory?.access === 'read-write' || spec.memory?.access === 'full',
|
|
255
312
|
'memory-forget': (spec) => spec.memory?.access === 'full',
|
|
313
|
+
files: (spec) => hasFileTools(spec),
|
|
314
|
+
'files-write': (spec) => hasFileWriteTools(spec),
|
|
315
|
+
workspace: (spec) => hasFileTools(spec),
|
|
316
|
+
'workspace-write': (spec) => hasFileWriteTools(spec),
|
|
256
317
|
};
|
|
257
318
|
/**
|
|
258
319
|
* The document as this agent gets it, or `undefined` when its conditions rule
|
package/dist/tools/sandbox.d.ts
CHANGED
|
@@ -23,6 +23,8 @@ export interface SandboxSpec {
|
|
|
23
23
|
workdir?: string;
|
|
24
24
|
/** default seconds a single command may take */
|
|
25
25
|
timeout?: number;
|
|
26
|
+
/** how much of the host the container is allowed to be; `standard` by default */
|
|
27
|
+
hardening?: SandboxHardening;
|
|
26
28
|
/** uid, name, or `uid:gid`; unset means the image's own user */
|
|
27
29
|
user?: string;
|
|
28
30
|
/** keep the container between sessions rather than removing it */
|
|
@@ -42,6 +44,27 @@ export interface SandboxSpec {
|
|
|
42
44
|
secrets?: readonly string[];
|
|
43
45
|
}
|
|
44
46
|
export type SandboxNetwork = 'bridge' | 'none' | 'host';
|
|
47
|
+
/**
|
|
48
|
+
* Two postures, because they cannot both be the default.
|
|
49
|
+
*
|
|
50
|
+
* `standard` is the one this tool was built around: a container with an init,
|
|
51
|
+
* a pid ceiling and `no-new-privileges`, running an image the agent can still
|
|
52
|
+
* install into. Installing a package is the ordinary use of a shell, so the
|
|
53
|
+
* capabilities stay and the root filesystem stays writable.
|
|
54
|
+
*
|
|
55
|
+
* `strict` is for running code nobody has read. Every capability is dropped,
|
|
56
|
+
* the root filesystem is read-only, the only writable temporary space is an
|
|
57
|
+
* in-memory `/tmp` with `noexec,nosuid,nodev`, the process runs as the host's
|
|
58
|
+
* own unprivileged uid rather than as container root, and the network is off
|
|
59
|
+
* unless the project turns it back on. What that costs is exactly what it
|
|
60
|
+
* removes: `apt`, `pip` and anything else that writes outside the mounts stop
|
|
61
|
+
* working, so a strict project bakes its dependencies into the image instead.
|
|
62
|
+
*
|
|
63
|
+
* What it does *not* do is make the bind mounts go away. `/workspace` is the
|
|
64
|
+
* work itself and stays writable — the profile shrinks the blast radius of a
|
|
65
|
+
* command, it does not pretend the agent has nothing to edit.
|
|
66
|
+
*/
|
|
67
|
+
export type SandboxHardening = 'standard' | 'strict';
|
|
45
68
|
export interface SandboxMount {
|
|
46
69
|
/** absolute host path */
|
|
47
70
|
host: string;
|
|
@@ -104,6 +127,8 @@ interface Resolved extends Required<Omit<SandboxSpec, 'user'>> {
|
|
|
104
127
|
readOnly: boolean;
|
|
105
128
|
mounts: readonly SandboxMount[];
|
|
106
129
|
engine: string;
|
|
130
|
+
explicitCpus?: boolean;
|
|
131
|
+
explicitMemory?: boolean;
|
|
107
132
|
}
|
|
108
133
|
export interface ExecOptions {
|
|
109
134
|
cwd?: string;
|