@zenera/neo 1.1.18 → 1.1.20
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/fork.d.ts +2 -8
- package/dist/fork.js +8 -52
- package/dist/frontmatter.d.ts +9 -0
- package/dist/frontmatter.js +40 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/inspect/client.d.ts +1 -1
- package/dist/inspect/client.js +44 -6
- package/dist/inspect/css.d.ts +1 -1
- package/dist/inspect/css.js +6 -0
- package/dist/kernel.js +20 -13
- package/dist/memory/render.d.ts +3 -1
- package/dist/memory/render.js +27 -2
- package/dist/project/load.js +67 -8
- package/dist/prompt.d.ts +11 -0
- package/dist/prompt.js +3 -0
- package/dist/skill-providers/file.d.ts +1 -1
- package/dist/skill-providers/file.js +3 -36
- package/dist/skill-providers/static.d.ts +1 -1
- package/dist/skill-providers/static.js +2 -1
- package/dist/skills.d.ts +7 -0
- package/dist/skills.js +66 -1
- package/package.json +1 -1
- package/dist/memory/instructions.d.ts +0 -15
- package/dist/memory/instructions.js +0 -103
package/dist/inspect/client.js
CHANGED
|
@@ -134,6 +134,42 @@ function textBlock(title, body, tag) {
|
|
|
134
134
|
return s;
|
|
135
135
|
}
|
|
136
136
|
|
|
137
|
+
// A prompt is assembled from tagged sections — <house_rules src="...">, <role>,
|
|
138
|
+
// <skills> — and unhighlighted they read as one wall of text, so the reader
|
|
139
|
+
// cannot see which file put which paragraph in front of the model. A section
|
|
140
|
+
// delimiter always stands alone on its line, which is what keeps prose and
|
|
141
|
+
// placeholders (/memory/<id>.<ext>) from lighting up as structure.
|
|
142
|
+
const TAG_LINE_RE = /^(\\s*)(<\\/?[A-Za-z][A-Za-z0-9._:-]*(?:\\s[^<>]*)?\\/?>)(\\s*)$/;
|
|
143
|
+
|
|
144
|
+
/** A <pre> with every section delimiter lifted out as its own span. */
|
|
145
|
+
function taggedPre(text) {
|
|
146
|
+
const s = String(text);
|
|
147
|
+
const pre = el('pre', 'text');
|
|
148
|
+
if (s.indexOf('<') < 0) { pre.textContent = s; return pre; }
|
|
149
|
+
const lines = s.split('\\n');
|
|
150
|
+
let buf = '';
|
|
151
|
+
function flush() {
|
|
152
|
+
if (buf) { pre.appendChild(document.createTextNode(buf)); buf = ''; }
|
|
153
|
+
}
|
|
154
|
+
for (let i = 0; i < lines.length; i++) {
|
|
155
|
+
const m = TAG_LINE_RE.exec(lines[i]);
|
|
156
|
+
if (!m) { buf += (i ? '\\n' : '') + lines[i]; continue; }
|
|
157
|
+
buf += (i ? '\\n' : '') + m[1];
|
|
158
|
+
flush();
|
|
159
|
+
pre.appendChild(el('span', m[2].indexOf('</') === 0 ? 'xtag end' : 'xtag', m[2]));
|
|
160
|
+
buf = m[3];
|
|
161
|
+
}
|
|
162
|
+
flush();
|
|
163
|
+
return pre;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function promptBlock(title, body, tag) {
|
|
167
|
+
const s = block(title, tag);
|
|
168
|
+
if (body) s.appendChild(taggedPre(body));
|
|
169
|
+
else s.appendChild(el('div', 'empty', '(empty)'));
|
|
170
|
+
return s;
|
|
171
|
+
}
|
|
172
|
+
|
|
137
173
|
function foldBlock(title, body) {
|
|
138
174
|
const s = block(title);
|
|
139
175
|
const d = document.createElement('details');
|
|
@@ -423,12 +459,14 @@ function message(m) {
|
|
|
423
459
|
|
|
424
460
|
if (m.role === 'user' && Array.isArray(m.content)) {
|
|
425
461
|
m.content.forEach(function (p) {
|
|
426
|
-
if (p.type === 'text') { wrap.appendChild(
|
|
462
|
+
if (p.type === 'text') { wrap.appendChild(taggedPre(p.text)); return; }
|
|
427
463
|
const img = imageEl(p, true);
|
|
428
464
|
wrap.appendChild(img || el('pre', 'text', '[' + p.type + '] ' + mediaUrl(p.url)));
|
|
429
465
|
});
|
|
430
466
|
} else if (typeof m.content === 'string' && m.content) {
|
|
431
|
-
wrap.appendChild(
|
|
467
|
+
wrap.appendChild(m.role === 'tool'
|
|
468
|
+
? el('pre', 'text', pretty(m.content))
|
|
469
|
+
: taggedPre(m.content));
|
|
432
470
|
} else if (!m.toolCalls) {
|
|
433
471
|
wrap.appendChild(el('div', 'empty', '(no content)'));
|
|
434
472
|
}
|
|
@@ -448,7 +486,7 @@ function requestBlocks(raw, into) {
|
|
|
448
486
|
into.appendChild(textBlock('Request (unparsed)', raw));
|
|
449
487
|
return;
|
|
450
488
|
}
|
|
451
|
-
if (req.system) into.appendChild(
|
|
489
|
+
if (req.system) into.appendChild(promptBlock('System prompt', req.system)).classList.add('sys');
|
|
452
490
|
|
|
453
491
|
const conv = block('Messages', (req.messages || []).length + ' messages');
|
|
454
492
|
(req.messages || []).forEach(function (m) { conv.appendChild(message(m)); });
|
|
@@ -496,10 +534,10 @@ function detailFor(e) {
|
|
|
496
534
|
|
|
497
535
|
switch (n.type) {
|
|
498
536
|
case 'system_prompt':
|
|
499
|
-
out.appendChild(
|
|
537
|
+
out.appendChild(promptBlock('Prompt', blob(n.prompt)));
|
|
500
538
|
// The editable half: which file to open when the instruction is wrong.
|
|
501
539
|
(n.sources || []).forEach(function (s) {
|
|
502
|
-
out.appendChild(
|
|
540
|
+
out.appendChild(promptBlock(s.path, blob(s.content), 'prompt file'));
|
|
503
541
|
});
|
|
504
542
|
break;
|
|
505
543
|
|
|
@@ -516,7 +554,7 @@ function detailFor(e) {
|
|
|
516
554
|
return s.name + (s.version ? '@' + s.version : '') + ' ' + s.contentHash.slice(0, 12)
|
|
517
555
|
+ (s.file ? ' ' + s.file : '');
|
|
518
556
|
}).join('\\n'), n.provider));
|
|
519
|
-
out.appendChild(
|
|
557
|
+
out.appendChild(promptBlock('Injected instructions', blob(n.content)));
|
|
520
558
|
out.appendChild(textBlock('Tools unlocked', n.toolNames.join('\\n')));
|
|
521
559
|
break;
|
|
522
560
|
|
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; }\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/* 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";
|
|
2
2
|
//# sourceMappingURL=css.d.ts.map
|
package/dist/inspect/css.js
CHANGED
|
@@ -183,6 +183,12 @@ 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
|
+
/* Section tags inside a prompt (<house_rules src="...">, <role>, ...): the only
|
|
187
|
+
structure a composed prompt has, and invisible without this. */
|
|
188
|
+
pre .xtag { color: #ffb0cd; background: #2d1b27; border: 1px solid #4e2c40;
|
|
189
|
+
border-radius: 4px; padding: 0 4px; font-weight: 600; }
|
|
190
|
+
pre .xtag.end { color: #a07f92; background: #201a1e; border-color: #3b2b35;
|
|
191
|
+
font-weight: 400; }
|
|
186
192
|
img.media { display: block; max-width: 100%; max-height: 420px; margin: 10px 12px;
|
|
187
193
|
border: 1px solid var(--line); border-radius: 6px; background: #0d1017; }
|
|
188
194
|
img.media.thumb { max-height: 88px; margin: 8px 12px; cursor: zoom-in; }
|
package/dist/kernel.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { Agent, AgentRegistry, handoffTarget, handoffTool } from "./agent.js";
|
|
3
|
-
import { FORK_DESCRIPTION,
|
|
3
|
+
import { FORK_DESCRIPTION, forkParameters } from "./fork.js";
|
|
4
4
|
import { systemClock } from "./ids.js";
|
|
5
|
-
import {
|
|
5
|
+
import { renderPreferences } from "./memory/render.js";
|
|
6
6
|
import { memoryTools } from "./memory/tools.js";
|
|
7
7
|
import { hash } from "./payload.js";
|
|
8
8
|
import { composePrompt } from "./prompt.js";
|
|
@@ -194,7 +194,13 @@ function forkAgents(state, reg) {
|
|
|
194
194
|
const known = allowed.filter((n) => reg.find(n));
|
|
195
195
|
return known.length ? known : [state.agentName];
|
|
196
196
|
}
|
|
197
|
-
/**
|
|
197
|
+
/**
|
|
198
|
+
* The tool is withdrawn at the depth cap, so a branch cannot spawn branches
|
|
199
|
+
* forever. `agents/fork-instructions.md` is gated on the same two conditions,
|
|
200
|
+
* from the other side: `requires: [fork]` for the binding, and a `when` on the
|
|
201
|
+
* depth, because a rule about a tool the model does not have is a rule it can
|
|
202
|
+
* only be confused by.
|
|
203
|
+
*/
|
|
198
204
|
function canFork(agent, state) {
|
|
199
205
|
return Boolean(agent.fork) && state.spec.forkDepth < state.spec.maxForkDepth;
|
|
200
206
|
}
|
|
@@ -251,12 +257,17 @@ function toSchema(t) {
|
|
|
251
257
|
return { name: t.name, description: t.description, parameters: t.parameters };
|
|
252
258
|
}
|
|
253
259
|
/**
|
|
254
|
-
* Prompt text the runtime owns rather than the author:
|
|
255
|
-
*
|
|
256
|
-
*
|
|
257
|
-
*
|
|
258
|
-
*
|
|
259
|
-
*
|
|
260
|
+
* Prompt text the runtime owns rather than the author: the user's standing
|
|
261
|
+
* preferences, the skill index and the `final_output` instruction. Most used to
|
|
262
|
+
* be appended inside `buildRequest`, which meant part of the system prompt
|
|
263
|
+
* existed in no node and showed up in no audit. Composed in now, always last,
|
|
264
|
+
* so the volatile tail never invalidates the cacheable prefix.
|
|
265
|
+
*
|
|
266
|
+
* How to *use* memory is not here: it is the project's own house rules, in
|
|
267
|
+
* `agents/memory-instructions.md`, so that one document is the only place it is
|
|
268
|
+
* written. An agent with memory tools and no such file gets the tool schemas
|
|
269
|
+
* and nothing else - `zen check` warns about it, the runtime does not invent a
|
|
270
|
+
* replacement. Forking is the same, in `agents/fork-instructions.md`.
|
|
260
271
|
*/
|
|
261
272
|
async function derivedPrompt(agent, state, env) {
|
|
262
273
|
const out = [];
|
|
@@ -266,7 +277,6 @@ async function derivedPrompt(agent, state, env) {
|
|
|
266
277
|
// because preferences change at most once in a run and
|
|
267
278
|
// `applySystemPrompt` is keyed on the rendered bytes, so a commit
|
|
268
279
|
// re-renders the prefix once rather than every turn.
|
|
269
|
-
out.push(memoryInstructions(memory));
|
|
270
280
|
out.push(renderPreferences(env.services.memory.preferences(memory.sees)));
|
|
271
281
|
}
|
|
272
282
|
if (agent.skills?.discovery === 'index') {
|
|
@@ -284,9 +294,6 @@ async function derivedPrompt(agent, state, env) {
|
|
|
284
294
|
out.push(rendered);
|
|
285
295
|
}
|
|
286
296
|
}
|
|
287
|
-
if (canFork(agent, state)) {
|
|
288
|
-
out.push(forkInstructions());
|
|
289
|
-
}
|
|
290
297
|
if (state.spec.outputSchema) {
|
|
291
298
|
out.push(FINAL_OUTPUT_INSTRUCTIONS);
|
|
292
299
|
}
|
package/dist/memory/render.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { type Recollection } from './types.ts';
|
|
1
|
+
import { type MemoryNode, type Recollection } from './types.ts';
|
|
2
2
|
export declare const RECOLLECTION_TAG = "memory-recollection";
|
|
3
|
+
export declare const PREFERENCES_TAG = "memory-preferences";
|
|
3
4
|
export interface RenderOptions {
|
|
4
5
|
/** wrap in the <memory-recollection> element; off for CLI output */
|
|
5
6
|
tagged?: boolean;
|
|
@@ -7,4 +8,5 @@ export interface RenderOptions {
|
|
|
7
8
|
now?: number;
|
|
8
9
|
}
|
|
9
10
|
export declare function renderRecollection(rec: Recollection, opts?: RenderOptions): string;
|
|
11
|
+
export declare function renderPreferences(nodes: readonly MemoryNode[]): string;
|
|
10
12
|
//# sourceMappingURL=render.d.ts.map
|
package/dist/memory/render.js
CHANGED
|
@@ -22,10 +22,17 @@ import { SPINE_RELATIONS, } from "./types.js";
|
|
|
22
22
|
//
|
|
23
23
|
// No SUPERSEDES arrow can appear on the ordinary path: recall drops the node
|
|
24
24
|
// one points at, so the edge loses an endpoint before it reaches here. It shows
|
|
25
|
-
// up only under `stale`, which is the audit view, and
|
|
26
|
-
// therefore
|
|
25
|
+
// up only under `stale`, which is the audit view, and the house rules in
|
|
26
|
+
// `agents/memory-instructions.md` therefore do not teach the model to look for
|
|
27
|
+
// one.
|
|
28
|
+
//
|
|
29
|
+
// The preferences block is here too. It is the other thing the runtime renders
|
|
30
|
+
// out of the graph for a model to read, and it exists for the one reason a
|
|
31
|
+
// static document cannot cover it: each line carries a live id, without which a
|
|
32
|
+
// preference can be read but not superseded.
|
|
27
33
|
// ---------------------------------------------------------------------------
|
|
28
34
|
export const RECOLLECTION_TAG = 'memory-recollection';
|
|
35
|
+
export const PREFERENCES_TAG = 'memory-preferences';
|
|
29
36
|
/** Enough to judge relevance; the model calls `memory_load` for the rest. */
|
|
30
37
|
const TEXT_CLIP = 160;
|
|
31
38
|
/** Two columns per level of nesting, so the tree is the indentation. */
|
|
@@ -225,4 +232,22 @@ function used(node, now) {
|
|
|
225
232
|
const ago = days <= 0 ? 'today' : days === 1 ? 'yesterday' : `${days}d ago`;
|
|
226
233
|
return `used ${node.useCount}\u00d7, ${ago}`;
|
|
227
234
|
}
|
|
235
|
+
export function renderPreferences(nodes) {
|
|
236
|
+
if (!nodes.length) {
|
|
237
|
+
return '';
|
|
238
|
+
}
|
|
239
|
+
const lines = nodes.map((n) => `- ${oneLine(n.text)} [${n.id}]`);
|
|
240
|
+
return [
|
|
241
|
+
`<${PREFERENCES_TAG}>`,
|
|
242
|
+
"Standing instructions from earlier runs. They apply unless this run's",
|
|
243
|
+
'request contradicts them; to change one, commit the replacement and link',
|
|
244
|
+
'it to the id here with SUPERSEDES.',
|
|
245
|
+
...lines,
|
|
246
|
+
`</${PREFERENCES_TAG}>`,
|
|
247
|
+
].join('\n');
|
|
248
|
+
}
|
|
249
|
+
/** Never clipped: half an instruction is a different instruction. */
|
|
250
|
+
function oneLine(text) {
|
|
251
|
+
return text.replace(/\s+/g, ' ').trim();
|
|
252
|
+
}
|
|
228
253
|
//# sourceMappingURL=render.js.map
|
package/dist/project/load.js
CHANGED
|
@@ -2,6 +2,7 @@ import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
|
|
|
2
2
|
import { basename, join, relative, resolve, sep } from 'node:path';
|
|
3
3
|
import { Agent, AgentRegistry } from "../agent.js";
|
|
4
4
|
import { RunStream } from "../events.js";
|
|
5
|
+
import { frontmatter, toList } from "../frontmatter.js";
|
|
5
6
|
import { MemoryIndex } from "../memory/index.js";
|
|
6
7
|
import { MemoryStore } from "../memory/store.js";
|
|
7
8
|
import { ModelRegistry, } from "../models/factory.js";
|
|
@@ -23,8 +24,6 @@ const CONFIG_NAMES = ['agents.yaml', 'agents.yml', 'agents/agents.yaml', 'agents
|
|
|
23
24
|
const HOUSE_RULES = 'agents/instructions.md';
|
|
24
25
|
/** Every other `agents/<topic>-instructions.md`, in filename order. */
|
|
25
26
|
const INSTRUCTIONS_SUFFIX = '-instructions.md';
|
|
26
|
-
/** Where the house rules used to live. Still read, so an old project still runs. */
|
|
27
|
-
const LEGACY_HOUSE_RULES = 'INSTRUCTIONS.md';
|
|
28
27
|
/**
|
|
29
28
|
* Finds and validates `agents.yaml`, and stops there. Loading a project builds
|
|
30
29
|
* its model clients, which needs credentials — so anything that wants to *look*
|
|
@@ -201,6 +200,11 @@ function within(root, path) {
|
|
|
201
200
|
* nothing to declare. Order is the filename's, which is the only ordering that
|
|
202
201
|
* is the same on every machine and is visible without opening another file.
|
|
203
202
|
*
|
|
203
|
+
* The exception a document declares for itself: `requires:` in its frontmatter
|
|
204
|
+
* names capabilities, and the document then reaches only the agents that have
|
|
205
|
+
* them. That stays a property of the document rather than of `agents.yaml`
|
|
206
|
+
* because the condition and the prose it guards are the same edit.
|
|
207
|
+
*
|
|
204
208
|
* All of it is optional: a one-agent project whose whole prompt is its role
|
|
205
209
|
* file should not need a second, empty document.
|
|
206
210
|
*/
|
|
@@ -208,19 +212,68 @@ function readHouseRules(root) {
|
|
|
208
212
|
const rules = [];
|
|
209
213
|
const take = (rel) => {
|
|
210
214
|
const path = join(root, rel);
|
|
211
|
-
if (existsSync(path)) {
|
|
212
|
-
|
|
215
|
+
if (!existsSync(path)) {
|
|
216
|
+
return;
|
|
213
217
|
}
|
|
218
|
+
const { data, body } = frontmatter(readFileSync(path, 'utf8'));
|
|
219
|
+
const requires = toList(data.requires);
|
|
220
|
+
for (const cap of requires) {
|
|
221
|
+
if (!(cap in CAPABILITIES)) {
|
|
222
|
+
throw new Error(`${rel}: unknown capability "${cap}" in requires: ` +
|
|
223
|
+
`(known: ${Object.keys(CAPABILITIES).join(', ')})`);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
// One object per document, shared by every agent it reaches, so the
|
|
227
|
+
// report says "one document, five prompts" rather than showing five
|
|
228
|
+
// identical blobs.
|
|
229
|
+
rules.push({ rel, requires, part: { text: body, path, section: 'house_rules', src: rel } });
|
|
214
230
|
};
|
|
215
|
-
// First, because it is the oldest and most general thing the project says:
|
|
216
|
-
// anything added under `agents/` during a migration layers on top of it.
|
|
217
|
-
take(LEGACY_HOUSE_RULES);
|
|
218
231
|
take(HOUSE_RULES);
|
|
219
232
|
for (const name of topics(root)) {
|
|
220
233
|
take(`${AGENTS_DIR}/${name}`);
|
|
221
234
|
}
|
|
222
235
|
return rules;
|
|
223
236
|
}
|
|
237
|
+
/**
|
|
238
|
+
* What `requires:` may name, and what each one means. A closed vocabulary
|
|
239
|
+
* rather than an expression language: these are the capabilities the runtime
|
|
240
|
+
* can turn on and off, so the set of useful conditions is finite and knowable,
|
|
241
|
+
* and a typo is a load error instead of a rule that silently never fires.
|
|
242
|
+
*
|
|
243
|
+
* Every entry reads `agents.yaml` and nothing else. In particular none of them
|
|
244
|
+
* consults the resolved tool set, which is a function of the *state* — a prompt
|
|
245
|
+
* that varied with it would move the provider's cache prefix mid-run.
|
|
246
|
+
*
|
|
247
|
+
* A capability may also contribute a run-time half, returned as `when`: `fork`
|
|
248
|
+
* does, because the tool is withdrawn at the depth cap and a rule about a tool
|
|
249
|
+
* the model does not have is a rule it can only be confused by.
|
|
250
|
+
*/
|
|
251
|
+
const CAPABILITIES = {
|
|
252
|
+
fork: (spec) => Boolean(spec.fork) && ((_ctx, run) => run.forkDepth < run.maxForkDepth),
|
|
253
|
+
memory: (spec) => Boolean(spec.memory),
|
|
254
|
+
'memory-write': (spec) => spec.memory?.access === 'read-write' || spec.memory?.access === 'full',
|
|
255
|
+
'memory-forget': (spec) => spec.memory?.access === 'full',
|
|
256
|
+
};
|
|
257
|
+
/**
|
|
258
|
+
* The document as this agent gets it, or `undefined` when its conditions rule
|
|
259
|
+
* the agent out. Several run-time conditions are met by meeting all of them.
|
|
260
|
+
*/
|
|
261
|
+
function ruleFor(rule, spec) {
|
|
262
|
+
const gates = [];
|
|
263
|
+
for (const cap of rule.requires) {
|
|
264
|
+
const resolved = CAPABILITIES[cap](spec);
|
|
265
|
+
if (!resolved) {
|
|
266
|
+
return undefined;
|
|
267
|
+
}
|
|
268
|
+
if (typeof resolved === 'function') {
|
|
269
|
+
gates.push(resolved);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
if (!gates.length) {
|
|
273
|
+
return rule.part;
|
|
274
|
+
}
|
|
275
|
+
return { ...rule.part, when: (ctx, run) => gates.every((g) => g(ctx, run)) };
|
|
276
|
+
}
|
|
224
277
|
/** `agents/<topic>-instructions.md`, sorted. Subdirectories are not searched. */
|
|
225
278
|
function topics(root) {
|
|
226
279
|
const dir = join(root, AGENTS_DIR);
|
|
@@ -238,7 +291,13 @@ function topics(root) {
|
|
|
238
291
|
* can reuse it.
|
|
239
292
|
*/
|
|
240
293
|
function instructionsFor(root, spec, houseRules) {
|
|
241
|
-
const parts = [
|
|
294
|
+
const parts = [];
|
|
295
|
+
for (const rule of houseRules) {
|
|
296
|
+
const part = ruleFor(rule, spec);
|
|
297
|
+
if (part) {
|
|
298
|
+
parts.push(part);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
242
301
|
const role = spec.system
|
|
243
302
|
? projectFile(root, spec.system, `agents.${spec.name}.system`)
|
|
244
303
|
: join(root, PROMPTS_DIR, `${spec.name}.md`);
|
package/dist/prompt.d.ts
CHANGED
|
@@ -20,6 +20,17 @@ export interface PromptText {
|
|
|
20
20
|
* different cache prefixes for the same project.
|
|
21
21
|
*/
|
|
22
22
|
src?: string;
|
|
23
|
+
/**
|
|
24
|
+
* Whether this block belongs in *this* run's prompt at all. False and the
|
|
25
|
+
* part is skipped entirely — no text, and no entry in `sources`, because a
|
|
26
|
+
* document the model never read is not a document that fed the prompt.
|
|
27
|
+
*
|
|
28
|
+
* It takes the same arguments as the function form of `PromptPart` and for
|
|
29
|
+
* the same reason: a condition on mutable state would move the cache prefix
|
|
30
|
+
* mid-run. This is how one shared document can be conditional per agent
|
|
31
|
+
* while still being read once.
|
|
32
|
+
*/
|
|
33
|
+
when?: (ctx: unknown, spec: RunSpec) => boolean;
|
|
23
34
|
}
|
|
24
35
|
/**
|
|
25
36
|
* One contributor to a system prompt: literal text, text computed from the run
|
package/dist/prompt.js
CHANGED
|
@@ -67,6 +67,9 @@ export async function composePrompt(instructions, ctx, spec, put, derived = [])
|
|
|
67
67
|
chunks.push(part(ctx, spec));
|
|
68
68
|
}
|
|
69
69
|
else {
|
|
70
|
+
if (part.when && !part.when(ctx, spec)) {
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
70
73
|
if (part.path && part.text) {
|
|
71
74
|
sources.push({ path: part.path, content: await put(part.text) });
|
|
72
75
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type Skill, type SkillProvider, type SkillSummary } from '../skills.ts';
|
|
2
2
|
import { type AnyTool } from '../types.ts';
|
|
3
3
|
/**
|
|
4
4
|
* A directory of skills, and the name the *agent* reaches it by — which is not
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { readFile, readdir } from 'node:fs/promises';
|
|
2
2
|
import { basename, join, relative, resolve } from 'node:path';
|
|
3
|
+
import { frontmatter, toList } from "../frontmatter.js";
|
|
4
|
+
import { unknownSkill } from "../skills.js";
|
|
3
5
|
import { selectTools } from "../types.js";
|
|
4
6
|
// ---------------------------------------------------------------------------
|
|
5
7
|
// Filesystem-backed skill provider
|
|
@@ -69,7 +71,7 @@ export class FileSkillProvider {
|
|
|
69
71
|
async load(name, version) {
|
|
70
72
|
const entry = (await this.#scan()).get(name);
|
|
71
73
|
if (!entry) {
|
|
72
|
-
throw new Error(
|
|
74
|
+
throw new Error(unknownSkill(name, await this.list()));
|
|
73
75
|
}
|
|
74
76
|
const { version: found } = entry.summary;
|
|
75
77
|
if (version && found && found !== version) {
|
|
@@ -163,41 +165,6 @@ async function readOptional(path) {
|
|
|
163
165
|
throw err;
|
|
164
166
|
}
|
|
165
167
|
}
|
|
166
|
-
/**
|
|
167
|
-
* Deliberately not a YAML parser: `key: value` and flow lists cover what a
|
|
168
|
-
* skill header needs, and a real dependency would buy nothing.
|
|
169
|
-
*/
|
|
170
|
-
function frontmatter(raw) {
|
|
171
|
-
const m = /^---\r?\n([\s\S]*?)\r?\n---[ \t]*(?:\r?\n|$)/.exec(raw);
|
|
172
|
-
if (!m) {
|
|
173
|
-
return { data: {}, body: raw.trim() };
|
|
174
|
-
}
|
|
175
|
-
const data = {};
|
|
176
|
-
for (const line of m[1].split(/\r?\n/)) {
|
|
177
|
-
const i = line.indexOf(':');
|
|
178
|
-
const key = i < 0 ? '' : line.slice(0, i).trim();
|
|
179
|
-
if (!key || key.startsWith('#')) {
|
|
180
|
-
continue;
|
|
181
|
-
}
|
|
182
|
-
data[key] = unquote(line.slice(i + 1).trim());
|
|
183
|
-
}
|
|
184
|
-
return { data, body: raw.slice(m[0].length).trim() };
|
|
185
|
-
}
|
|
186
|
-
function toList(value) {
|
|
187
|
-
if (!value) {
|
|
188
|
-
return [];
|
|
189
|
-
}
|
|
190
|
-
const inner = value.startsWith('[') && value.endsWith(']') ? value.slice(1, -1) : value;
|
|
191
|
-
return inner
|
|
192
|
-
.split(',')
|
|
193
|
-
.map((v) => unquote(v.trim()))
|
|
194
|
-
.filter(Boolean);
|
|
195
|
-
}
|
|
196
|
-
function unquote(value) {
|
|
197
|
-
const quoted = (value.startsWith('"') && value.endsWith('"')) ||
|
|
198
|
-
(value.startsWith("'") && value.endsWith("'"));
|
|
199
|
-
return quoted && value.length > 1 ? value.slice(1, -1) : value;
|
|
200
|
-
}
|
|
201
168
|
function firstLine(body) {
|
|
202
169
|
return (body
|
|
203
170
|
.split(/\r?\n/)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type Skill, type SkillProvider, type SkillSummary } from '../skills.ts';
|
|
2
2
|
import type { AnyTool } from '../types.ts';
|
|
3
3
|
/** Skills handed over in code, held in a map. No I/O, no catalog to scan. */
|
|
4
4
|
export declare class StaticSkillProvider implements SkillProvider {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { unknownSkill } from "../skills.js";
|
|
1
2
|
/** Skills handed over in code, held in a map. No I/O, no catalog to scan. */
|
|
2
3
|
export class StaticSkillProvider {
|
|
3
4
|
id;
|
|
@@ -33,7 +34,7 @@ export class StaticSkillProvider {
|
|
|
33
34
|
async load(name, version) {
|
|
34
35
|
const s = this.#skills.get(name);
|
|
35
36
|
if (!s) {
|
|
36
|
-
throw new Error(
|
|
37
|
+
throw new Error(unknownSkill(name, await this.list()));
|
|
37
38
|
}
|
|
38
39
|
if (version && s.version && s.version !== version) {
|
|
39
40
|
throw new Error(`skill ${name} is at ${s.version}, ${version} was requested`);
|
package/dist/skills.d.ts
CHANGED
|
@@ -113,4 +113,11 @@ export declare function lockedSkillTools<TCtx>(binding: SkillBinding, provider:
|
|
|
113
113
|
* are not duplicated in the tool result and can be compacted independently.
|
|
114
114
|
*/
|
|
115
115
|
export declare function skillTools<TCtx>(binding: SkillBinding): AnyTool<TCtx>[];
|
|
116
|
+
/**
|
|
117
|
+
* A model that misses does not usually misspell: it invents a plausible name
|
|
118
|
+
* out of something it read, so `memory-policy-instructions` is reaching for
|
|
119
|
+
* `zen-memory`. Shared words therefore rank above edit distance, and the
|
|
120
|
+
* catalog is listed either way so the retry needs no further guessing.
|
|
121
|
+
*/
|
|
122
|
+
export declare function unknownSkill(name: string, catalog: SkillSummary[]): string;
|
|
116
123
|
//# sourceMappingURL=skills.d.ts.map
|
package/dist/skills.js
CHANGED
|
@@ -213,7 +213,12 @@ export function skillTools(binding) {
|
|
|
213
213
|
if (summary && !allows(binding, summary)) {
|
|
214
214
|
throw new Error(`skill "${name}" is not available to this agent`);
|
|
215
215
|
}
|
|
216
|
-
|
|
216
|
+
if (!summary) {
|
|
217
|
+
// Only over what this agent may see: a suggestion must
|
|
218
|
+
// not name a skill the index and `skill_search` hide.
|
|
219
|
+
throw new Error(unknownSkill(name, index.filter((s) => allows(binding, s))));
|
|
220
|
+
}
|
|
221
|
+
loaded.push(await provider.load(name, summary.version));
|
|
217
222
|
}
|
|
218
223
|
// Two skills may declare the same tool; it is unlocked once.
|
|
219
224
|
const unlocked = [
|
|
@@ -246,4 +251,64 @@ export function skillTools(binding) {
|
|
|
246
251
|
}
|
|
247
252
|
return tools;
|
|
248
253
|
}
|
|
254
|
+
// ---------------------------------------------------------------------------
|
|
255
|
+
// Unknown names
|
|
256
|
+
// ---------------------------------------------------------------------------
|
|
257
|
+
const CATALOG_IN_ERROR = 30;
|
|
258
|
+
/**
|
|
259
|
+
* A model that misses does not usually misspell: it invents a plausible name
|
|
260
|
+
* out of something it read, so `memory-policy-instructions` is reaching for
|
|
261
|
+
* `zen-memory`. Shared words therefore rank above edit distance, and the
|
|
262
|
+
* catalog is listed either way so the retry needs no further guessing.
|
|
263
|
+
*/
|
|
264
|
+
export function unknownSkill(name, catalog) {
|
|
265
|
+
const names = catalog.map((s) => s.name);
|
|
266
|
+
const scored = names
|
|
267
|
+
.map((n) => ({ n, score: closeness(name, n) }))
|
|
268
|
+
.filter((c) => c.score > 0)
|
|
269
|
+
.sort((a, b) => b.score - a.score || a.n.localeCompare(b.n));
|
|
270
|
+
// Only the equal-best ones: a shared `zen-` prefix makes weak matches of
|
|
271
|
+
// the whole catalog, and three of those are worse than one good guess.
|
|
272
|
+
const near = scored
|
|
273
|
+
.filter((c) => c.score === scored[0]?.score)
|
|
274
|
+
.slice(0, 3)
|
|
275
|
+
.map((c) => c.n);
|
|
276
|
+
const list = names.slice(0, CATALOG_IN_ERROR).join(', ') || 'none';
|
|
277
|
+
const more = names.length > CATALOG_IN_ERROR ? `, … (${names.length} in all)` : '';
|
|
278
|
+
return (`unknown skill "${name}"` +
|
|
279
|
+
(near.length ? ` — did you mean ${near.map((n) => `"${n}"`).join(' or ')}?` : '') +
|
|
280
|
+
` (the catalog has: ${list}${more})`);
|
|
281
|
+
}
|
|
282
|
+
function closeness(query, candidate) {
|
|
283
|
+
const q = query.toLowerCase();
|
|
284
|
+
const c = candidate.toLowerCase();
|
|
285
|
+
if (c.includes(q) || q.includes(c)) {
|
|
286
|
+
return 4;
|
|
287
|
+
}
|
|
288
|
+
const wanted = new Set(words(q));
|
|
289
|
+
const shared = words(c).filter((w) => wanted.has(w)).length;
|
|
290
|
+
if (shared) {
|
|
291
|
+
return 1 + shared;
|
|
292
|
+
}
|
|
293
|
+
return distance(q, c) <= 2 ? 1 : 0;
|
|
294
|
+
}
|
|
295
|
+
function words(s) {
|
|
296
|
+
return s.split(/[^a-z0-9]+/).filter((w) => w.length > 2);
|
|
297
|
+
}
|
|
298
|
+
/** Levenshtein, one row at a time, giving up as soon as the lengths rule it out. */
|
|
299
|
+
function distance(a, b) {
|
|
300
|
+
if (Math.abs(a.length - b.length) > 2) {
|
|
301
|
+
return 3;
|
|
302
|
+
}
|
|
303
|
+
let row = Array.from({ length: b.length + 1 }, (_, i) => i);
|
|
304
|
+
for (let i = 1; i <= a.length; i++) {
|
|
305
|
+
const next = [i];
|
|
306
|
+
for (let j = 1; j <= b.length; j++) {
|
|
307
|
+
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
308
|
+
next[j] = Math.min(next[j - 1] + 1, row[j] + 1, row[j - 1] + cost);
|
|
309
|
+
}
|
|
310
|
+
row = next;
|
|
311
|
+
}
|
|
312
|
+
return row[b.length];
|
|
313
|
+
}
|
|
249
314
|
//# sourceMappingURL=skills.js.map
|
package/package.json
CHANGED
|
@@ -1,15 +0,0 @@
|
|
|
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
|