@possumtech/rummy 2.1.0 → 2.2.1
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/.env.example +40 -15
- package/.xai.key +1 -0
- package/PLUGINS.md +169 -53
- package/README.md +38 -32
- package/SPEC.md +366 -179
- package/bin/digest.js +1097 -0
- package/biome/no-fallbacks.grit +2 -2
- package/gemini.key +1 -0
- package/lang/en.json +10 -1
- package/migrations/001_initial_schema.sql +9 -2
- package/package.json +19 -8
- package/service.js +1 -0
- package/src/agent/AgentLoop.js +76 -26
- package/src/agent/ContextAssembler.js +2 -0
- package/src/agent/Entries.js +238 -60
- package/src/agent/ProjectAgent.js +44 -0
- package/src/agent/TurnExecutor.js +99 -30
- package/src/agent/XmlParser.js +206 -111
- package/src/agent/errors.js +35 -0
- package/src/agent/known_queries.sql +1 -1
- package/src/agent/known_store.sql +3 -42
- package/src/agent/materializeContext.js +30 -1
- package/src/agent/runs.sql +8 -18
- package/src/agent/tokens.js +0 -1
- package/src/agent/turns.sql +1 -0
- package/src/hooks/Hooks.js +26 -0
- package/src/hooks/RummyContext.js +12 -1
- package/src/lib/hedberg/README.md +60 -0
- package/src/lib/hedberg/hedberg.js +60 -0
- package/src/lib/hedberg/marker.js +158 -0
- package/src/{plugins → lib}/hedberg/matcher.js +1 -2
- package/src/llm/LlmProvider.js +41 -3
- package/src/llm/openaiStream.js +17 -0
- package/src/plugins/ask_user/ask_user.js +12 -2
- package/src/plugins/ask_user/ask_userDoc.md +1 -5
- package/src/plugins/budget/README.md +29 -24
- package/src/plugins/budget/budget.js +166 -110
- package/src/plugins/cli/README.md +3 -4
- package/src/plugins/cli/cli.js +31 -5
- package/src/plugins/cloudflare/cloudflare.js +136 -0
- package/src/plugins/cp/cp.js +41 -4
- package/src/plugins/cp/cpDoc.md +5 -6
- package/src/plugins/engine/engine.sql +1 -1
- package/src/plugins/env/README.md +5 -4
- package/src/plugins/env/env.js +7 -4
- package/src/plugins/env/envDoc.md +7 -8
- package/src/plugins/error/error.js +56 -15
- package/src/plugins/file/README.md +12 -3
- package/src/plugins/file/file.js +2 -2
- package/src/plugins/get/get.js +59 -36
- package/src/plugins/get/getDoc.md +10 -34
- package/src/plugins/google/google.js +115 -0
- package/src/plugins/hedberg/hedberg.js +13 -56
- package/src/plugins/helpers.js +66 -12
- package/src/plugins/index.js +1 -2
- package/src/plugins/instructions/README.md +44 -47
- package/src/plugins/instructions/instructions-system.md +44 -0
- package/src/plugins/instructions/instructions-user.md +53 -0
- package/src/plugins/instructions/instructions.js +58 -189
- package/src/plugins/known/README.md +6 -7
- package/src/plugins/known/known.js +24 -30
- package/src/plugins/log/log.js +41 -32
- package/src/plugins/mv/mv.js +40 -1
- package/src/plugins/mv/mvDoc.md +1 -8
- package/src/plugins/ollama/ollama.js +4 -3
- package/src/plugins/openai/openai.js +4 -3
- package/src/plugins/openrouter/openrouter.js +14 -4
- package/src/plugins/persona/README.md +11 -13
- package/src/plugins/persona/default.md +29 -0
- package/src/plugins/persona/persona.js +10 -66
- package/src/plugins/policy/policy.js +23 -22
- package/src/plugins/prompt/README.md +37 -27
- package/src/plugins/prompt/prompt.js +13 -19
- package/src/plugins/rm/rm.js +18 -0
- package/src/plugins/rm/rmDoc.md +5 -6
- package/src/plugins/rpc/rpc.js +3 -3
- package/src/plugins/set/set.js +205 -323
- package/src/plugins/set/setDoc.md +47 -17
- package/src/plugins/sh/README.md +6 -5
- package/src/plugins/sh/sh.js +8 -5
- package/src/plugins/sh/shDoc.md +7 -8
- package/src/plugins/skill/README.md +37 -14
- package/src/plugins/skill/skill.js +200 -101
- package/src/plugins/skill/skillDoc.js +3 -0
- package/src/plugins/skill/skillDoc.md +9 -0
- package/src/plugins/stream/README.md +7 -6
- package/src/plugins/stream/finalize.js +100 -0
- package/src/plugins/stream/stream.js +13 -45
- package/src/plugins/telemetry/telemetry.js +27 -4
- package/src/plugins/think/think.js +2 -3
- package/src/plugins/think/thinkDoc.md +2 -4
- package/src/plugins/unknown/README.md +1 -1
- package/src/plugins/unknown/unknown.js +17 -19
- package/src/plugins/update/update.js +4 -51
- package/src/plugins/update/updateDoc.md +21 -6
- package/src/plugins/xai/xai.js +68 -102
- package/src/plugins/yolo/yolo.js +102 -75
- package/src/sql/functions/hedmatch.js +1 -1
- package/src/sql/functions/hedreplace.js +1 -1
- package/src/sql/functions/hedsearch.js +1 -1
- package/src/sql/functions/slugify.js +16 -2
- package/BENCH_ENVIRONMENT.md +0 -230
- package/CLIENT_INTERFACE.md +0 -396
- package/last_run.txt +0 -5617
- package/scriptify/ask_run.js +0 -77
- package/scriptify/cache_probe.js +0 -66
- package/scriptify/cache_probe_grok.js +0 -74
- package/src/agent/budget.js +0 -33
- package/src/agent/config.js +0 -38
- package/src/plugins/hedberg/README.md +0 -71
- package/src/plugins/hedberg/docs.md +0 -0
- package/src/plugins/hedberg/edits.js +0 -55
- package/src/plugins/hedberg/normalize.js +0 -17
- package/src/plugins/hedberg/sed.js +0 -49
- package/src/plugins/instructions/instructions.md +0 -34
- package/src/plugins/instructions/instructions_104.md +0 -8
- package/src/plugins/instructions/instructions_105.md +0 -39
- package/src/plugins/instructions/instructions_106.md +0 -22
- package/src/plugins/instructions/instructions_107.md +0 -17
- package/src/plugins/instructions/instructions_108.md +0 -0
- package/src/plugins/known/knownDoc.js +0 -3
- package/src/plugins/known/knownDoc.md +0 -8
- package/src/plugins/unknown/unknownDoc.js +0 -3
- package/src/plugins/unknown/unknownDoc.md +0 -11
- package/turns/cli_1777462658211/turn_001.txt +0 -772
- package/turns/cli_1777462658211/turn_002.txt +0 -606
- package/turns/cli_1777462658211/turn_003.txt +0 -667
- package/turns/cli_1777462658211/turn_004.txt +0 -297
- package/turns/cli_1777462658211/turn_005.txt +0 -301
- package/turns/cli_1777462658211/turn_006.txt +0 -262
- package/turns/cli_1777465095132/turn_001.txt +0 -715
- package/turns/cli_1777465095132/turn_002.txt +0 -236
- package/turns/cli_1777465095132/turn_003.txt +0 -287
- package/turns/cli_1777465095132/turn_004.txt +0 -694
- package/turns/cli_1777465095132/turn_005.txt +0 -422
- package/turns/cli_1777465095132/turn_006.txt +0 -365
- package/turns/cli_1777465095132/turn_007.txt +0 -885
- package/turns/cli_1777465095132/turn_008.txt +0 -1277
- package/turns/cli_1777465095132/turn_009.txt +0 -736
- /package/src/{plugins → lib}/hedberg/patterns.js +0 -0
|
@@ -1,144 +1,86 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
2
|
import Protocol from "./protocol.js";
|
|
3
3
|
|
|
4
|
+
// Base system-prompt content (header + Core XML Command Grammar). The
|
|
5
|
+
// `[%TOOLS%]` placeholder is substituted with the active-toolset tag list
|
|
6
|
+
// at render time. Tool-specific docs no longer live here; they're a
|
|
7
|
+
// separate `assembly.system` participant at priority 100.
|
|
4
8
|
const baseInstructions = readFileSync(
|
|
5
|
-
new URL("./instructions.md", import.meta.url),
|
|
9
|
+
new URL("./instructions-system.md", import.meta.url),
|
|
6
10
|
"utf8",
|
|
7
11
|
);
|
|
8
12
|
|
|
9
|
-
//
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
);
|
|
17
|
-
const TURN_FROM_PATH = /^log:\/\/turn_(\d+)\/update\//;
|
|
18
|
-
|
|
19
|
-
function phaseForStatus(status) {
|
|
20
|
-
if (status == null) return 4;
|
|
21
|
-
if (status === 200) return 7;
|
|
22
|
-
const last = status % 10;
|
|
23
|
-
return PHASES.includes(last) ? last : 4;
|
|
24
|
-
}
|
|
13
|
+
// Tight, non-modal reminder rendered LATE in the user message
|
|
14
|
+
// (`assembly.user` priority 165, between unknowns at 150 and budget at
|
|
15
|
+
// 175) so the rules sit adjacent to the action site — recency keeps the
|
|
16
|
+
// per-turn discipline warm.
|
|
17
|
+
const userInstructions = readFileSync(
|
|
18
|
+
new URL("./instructions-user.md", import.meta.url),
|
|
19
|
+
"utf8",
|
|
20
|
+
).trim();
|
|
25
21
|
|
|
26
|
-
|
|
27
|
-
function latestUpdateStatusFromRows(rows) {
|
|
28
|
-
let bestTurn = -1;
|
|
29
|
-
let bestStatus = null;
|
|
30
|
-
for (const r of rows) {
|
|
31
|
-
const m = TURN_FROM_PATH.exec(r.path);
|
|
32
|
-
if (!m) continue;
|
|
33
|
-
const turn = Number(m[1]);
|
|
34
|
-
const attrs =
|
|
35
|
-
typeof r.attributes === "string"
|
|
36
|
-
? JSON.parse(r.attributes)
|
|
37
|
-
: r.attributes;
|
|
38
|
-
const status = attrs?.status;
|
|
39
|
-
if (status == null) continue;
|
|
40
|
-
if (attrs?.rejected) continue;
|
|
41
|
-
if (turn > bestTurn || (turn === bestTurn && status > bestStatus)) {
|
|
42
|
-
bestTurn = turn;
|
|
43
|
-
bestStatus = status;
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
return bestStatus;
|
|
47
|
-
}
|
|
22
|
+
const TURN_FROM_PATH = /^log:\/\/turn_(\d+)\/update\//;
|
|
48
23
|
|
|
49
24
|
export default class Instructions {
|
|
50
25
|
#core;
|
|
51
26
|
|
|
52
27
|
constructor(core) {
|
|
53
28
|
this.#core = core;
|
|
54
|
-
core.on("visible", this.full.bind(this));
|
|
55
|
-
core.on("turn.started", this.onTurnStarted.bind(this));
|
|
56
|
-
core.hooks.instructions.resolveSystemPrompt =
|
|
57
|
-
this.resolveSystemPrompt.bind(this);
|
|
58
|
-
core.hooks.instructions.validateNavigation =
|
|
59
|
-
this.validateNavigation.bind(this);
|
|
60
29
|
core.hooks.instructions.findLatestSummary =
|
|
61
30
|
this.findLatestSummary.bind(this);
|
|
62
|
-
|
|
31
|
+
// System message: priority chain. Base header + grammar at 50,
|
|
32
|
+
// joined per-tool docs at 100, persona at 150 (in persona.js).
|
|
33
|
+
core.filter("assembly.system", this.assembleSystemBase.bind(this), 50);
|
|
34
|
+
core.filter("assembly.system", this.assembleSystemToolDocs.bind(this), 100);
|
|
35
|
+
// User message: per-turn reminder block at the front of the user
|
|
36
|
+
// packet — sets discipline before the prompt.
|
|
37
|
+
core.filter("assembly.user", this.assembleInstructions.bind(this), 30);
|
|
63
38
|
new Protocol(core);
|
|
64
39
|
}
|
|
65
40
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
runId,
|
|
71
|
-
"instructions://system",
|
|
72
|
-
null,
|
|
73
|
-
{ includeAuditSchemes: true },
|
|
74
|
-
);
|
|
75
|
-
// The entry is always written by onTurnStarted before this runs.
|
|
76
|
-
const entry = entries[0];
|
|
77
|
-
const attributes = await store.getAttributes(
|
|
78
|
-
runId,
|
|
79
|
-
"instructions://system",
|
|
80
|
-
);
|
|
81
|
-
return hooks.tools.view("instructions", {
|
|
82
|
-
path: "instructions://system",
|
|
83
|
-
scheme: "instructions",
|
|
84
|
-
body: entry.body,
|
|
85
|
-
attributes,
|
|
86
|
-
visibility: "visible",
|
|
87
|
-
category: "system",
|
|
88
|
-
});
|
|
41
|
+
#activeToolSet(ctx) {
|
|
42
|
+
return ctx.toolSet
|
|
43
|
+
? new Set(ctx.toolSet)
|
|
44
|
+
: new Set(this.#core.hooks.tools.names);
|
|
89
45
|
}
|
|
90
46
|
|
|
91
|
-
//
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
const
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
}
|
|
101
|
-
if (nextPhase === 7) {
|
|
102
|
-
const visible = await this.#countVisiblePriorPrompts(rummy);
|
|
103
|
-
if (visible > 0) {
|
|
104
|
-
return {
|
|
105
|
-
ok: false,
|
|
106
|
-
reason: `Illegal navigation attempt: ${visible} visible prior prompts`,
|
|
107
|
-
};
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
return { ok: true };
|
|
47
|
+
// assembly.system @ 50 — header + Core XML Command Grammar with the
|
|
48
|
+
// advertised tool tag list substituted into the `[%TOOLS%]` slot.
|
|
49
|
+
assembleSystemBase(content, ctx) {
|
|
50
|
+
const activeTools = this.#activeToolSet(ctx);
|
|
51
|
+
const advertised = this.#core.hooks.tools.advertisedNames.filter((n) =>
|
|
52
|
+
activeTools.has(n),
|
|
53
|
+
);
|
|
54
|
+
const tools = advertised.map((n) => `<${n}/>`).join(", ");
|
|
55
|
+
return `${content}${baseInstructions.replace("[%TOOLS%]", tools)}`;
|
|
111
56
|
}
|
|
112
57
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
58
|
+
// assembly.system @ 100 — per-tool docs joined in tool-registration
|
|
59
|
+
// order. Each tool plugin contributes its block via the
|
|
60
|
+
// `instructions.toolDocs` sub-filter (registry-style: filter
|
|
61
|
+
// participants mutate a docsMap keyed by tool name).
|
|
62
|
+
async assembleSystemToolDocs(content, ctx) {
|
|
63
|
+
const activeTools = this.#activeToolSet(ctx);
|
|
64
|
+
const toolDocs = await this.#core.hooks.instructions.toolDocs.filter(
|
|
65
|
+
{},
|
|
66
|
+
{ toolSet: activeTools },
|
|
119
67
|
);
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
if (attrs?.status == null) continue;
|
|
133
|
-
if (turn > bestTurn) {
|
|
134
|
-
bestTurn = turn;
|
|
135
|
-
bestStatus = attrs.status;
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
return phaseForStatus(bestStatus);
|
|
68
|
+
const docsText = this.#core.hooks.tools.names
|
|
69
|
+
.filter((n) => activeTools.has(n))
|
|
70
|
+
.filter((key) => toolDocs[key])
|
|
71
|
+
.map((key) => toolDocs[key])
|
|
72
|
+
.join("\n\n");
|
|
73
|
+
if (!docsText) return content;
|
|
74
|
+
return `${content}\n\n${docsText}`;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// assembly.user @ 165 — per-turn reminder, same body every turn.
|
|
78
|
+
assembleInstructions(content, _ctx) {
|
|
79
|
+
return `${content}<instructions>\n${userInstructions}\n</instructions>\n`;
|
|
139
80
|
}
|
|
140
81
|
|
|
141
|
-
// Latest
|
|
82
|
+
// Latest terminal update (status=200) — used by cli.js to print the
|
|
83
|
+
// run's final answer. State-machine knowledge lives here, not AgentLoop.
|
|
142
84
|
findLatestSummary(logEntries) {
|
|
143
85
|
return logEntries
|
|
144
86
|
.filter((e) => {
|
|
@@ -151,77 +93,4 @@ export default class Instructions {
|
|
|
151
93
|
})
|
|
152
94
|
.at(-1);
|
|
153
95
|
}
|
|
154
|
-
|
|
155
|
-
async #countVisiblePriorPrompts(rummy) {
|
|
156
|
-
const prompts = await rummy.entries.getEntriesByPattern(
|
|
157
|
-
rummy.runId,
|
|
158
|
-
"prompt://*",
|
|
159
|
-
null,
|
|
160
|
-
);
|
|
161
|
-
const visible = prompts.filter((p) => p.visibility === "visible");
|
|
162
|
-
if (visible.length === 0) return 0;
|
|
163
|
-
// Exclude the latest prompt; only PRIOR prompts trigger demote-before-Deployment.
|
|
164
|
-
let maxNum = -1;
|
|
165
|
-
for (const p of visible) {
|
|
166
|
-
const m = /^prompt:\/\/(\d+)$/.exec(p.path);
|
|
167
|
-
if (m && Number(m[1]) > maxNum) maxNum = Number(m[1]);
|
|
168
|
-
}
|
|
169
|
-
return visible.filter((p) => {
|
|
170
|
-
const m = /^prompt:\/\/(\d+)$/.exec(p.path);
|
|
171
|
-
return !m || Number(m[1]) !== maxNum;
|
|
172
|
-
}).length;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
async onTurnStarted({ rummy }) {
|
|
176
|
-
const { entries: store, sequence: turn, runId } = rummy;
|
|
177
|
-
const runRow = await store.getRun(runId);
|
|
178
|
-
const toolSet = rummy.toolSet
|
|
179
|
-
? [...rummy.toolSet]
|
|
180
|
-
: this.#core.hooks.tools.names;
|
|
181
|
-
// instructions://system stays cache-stable; phase selection at assembly.user.
|
|
182
|
-
await store.set({
|
|
183
|
-
runId,
|
|
184
|
-
turn,
|
|
185
|
-
path: "instructions://system",
|
|
186
|
-
body: "",
|
|
187
|
-
state: "resolved",
|
|
188
|
-
writer: "system",
|
|
189
|
-
attributes: {
|
|
190
|
-
persona: runRow.persona,
|
|
191
|
-
toolSet,
|
|
192
|
-
},
|
|
193
|
-
});
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
async full(entry) {
|
|
197
|
-
const attrs = entry.attributes;
|
|
198
|
-
const activeTools = attrs.toolSet
|
|
199
|
-
? new Set(attrs.toolSet)
|
|
200
|
-
: new Set(this.#core.hooks.tools.names);
|
|
201
|
-
const toolDocs = await this.#core.hooks.instructions.toolDocs.filter(
|
|
202
|
-
{},
|
|
203
|
-
{ toolSet: activeTools },
|
|
204
|
-
);
|
|
205
|
-
const sorted = this.#core.hooks.tools.advertisedNames.filter((n) =>
|
|
206
|
-
activeTools.has(n),
|
|
207
|
-
);
|
|
208
|
-
const tools = sorted.map((n) => `<${n}/>`).join(", ");
|
|
209
|
-
const docsText = sorted
|
|
210
|
-
.filter((key) => toolDocs[key])
|
|
211
|
-
.map((key) => toolDocs[key])
|
|
212
|
-
.join("\n\n");
|
|
213
|
-
let prompt = baseInstructions
|
|
214
|
-
.replace("[%TOOLS%]", tools)
|
|
215
|
-
.replace("[%TOOLDOCS%]", docsText);
|
|
216
|
-
if (attrs.persona) prompt += `\n\n## Persona\n\n${attrs.persona}`;
|
|
217
|
-
return prompt;
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
// Render <instructions> for current phase; absent phase file → no block.
|
|
221
|
-
assembleInstructions(content, ctx) {
|
|
222
|
-
const status = latestUpdateStatusFromRows(ctx.rows);
|
|
223
|
-
const step = phaseInstructions[phaseForStatus(status)];
|
|
224
|
-
if (!step) return content;
|
|
225
|
-
return `${content}<instructions>\n${step}\n</instructions>\n`;
|
|
226
|
-
}
|
|
227
96
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# known {#known_plugin}
|
|
2
2
|
|
|
3
3
|
Writes knowledge entries into the store at full visibility, and renders
|
|
4
|
-
the project's data surface as the bifurcated `<
|
|
4
|
+
the project's data surface as the bifurcated `<summary>` /
|
|
5
5
|
`<visible>` blocks at the top of the user message.
|
|
6
6
|
|
|
7
7
|
## Registration
|
|
@@ -10,7 +10,7 @@ the project's data surface as the bifurcated `<summarized>` /
|
|
|
10
10
|
- **Category**: `data`
|
|
11
11
|
- **Handler**: Upserts the entry body at the target path with status 200.
|
|
12
12
|
- **Filters**:
|
|
13
|
-
- `assembly.user` priority 50 — renders `<
|
|
13
|
+
- `assembly.user` priority 50 — renders `<summary>`.
|
|
14
14
|
- `assembly.user` priority 75 — renders `<visible>`.
|
|
15
15
|
|
|
16
16
|
## Projection
|
|
@@ -22,19 +22,18 @@ Shows `# known {path}` followed by the entry body.
|
|
|
22
22
|
Filters `ctx.rows` where `category === "data"`. Two separate blocks
|
|
23
23
|
emit at the top of the user message in this order:
|
|
24
24
|
|
|
25
|
-
- `<
|
|
25
|
+
- `<summary>` — each data entry whose visibility is `visible` or
|
|
26
26
|
`summarized`, rendered under its scheme tag with the plugin's
|
|
27
27
|
summary projection as body (truncated knowns, code symbols,
|
|
28
28
|
page abstracts — whatever the plugin's `summary()` hook produces).
|
|
29
|
-
|
|
30
|
-
(
|
|
31
|
-
back after demotion.
|
|
29
|
+
Archived entries — including prompts — are filtered out uniformly
|
|
30
|
+
(no carve-out).
|
|
32
31
|
- `<visible>` — each data entry whose visibility is `visible`,
|
|
33
32
|
rendered with the plugin's visible projection (full body) as the
|
|
34
33
|
tag body. A visible entry appears in *both* blocks: summary
|
|
35
34
|
projection up top, full body below.
|
|
36
35
|
|
|
37
|
-
This split lets `<
|
|
36
|
+
This split lets `<summary>` stay cache-stable across promote/demote
|
|
38
37
|
operations — only `<visible>` mutates when the model promotes a
|
|
39
38
|
summary or demotes a visible entry. Third-party plugins that register
|
|
40
39
|
with `category: "data"` automatically appear in both blocks under
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { stateToStatus } from "../../agent/httpStatus.js";
|
|
2
2
|
import { countTokens } from "../../agent/tokens.js";
|
|
3
|
+
import { renderEntry, SUMMARY_MAX_CHARS } from "../helpers.js";
|
|
3
4
|
|
|
4
5
|
const MAX_ENTRY_TOKENS = Number(process.env.RUMMY_MAX_ENTRY_TOKENS);
|
|
5
6
|
|
|
@@ -8,13 +9,15 @@ export default class Known {
|
|
|
8
9
|
|
|
9
10
|
constructor(core) {
|
|
10
11
|
this.#core = core;
|
|
12
|
+
core.ensureTool();
|
|
11
13
|
core.registerScheme({ category: "data" });
|
|
12
14
|
core.on("handler", this.handler.bind(this));
|
|
13
15
|
core.on("visible", this.full.bind(this));
|
|
14
16
|
core.on("summarized", this.summary.bind(this));
|
|
15
|
-
core.filter("assembly.
|
|
16
|
-
core.filter("assembly.
|
|
17
|
-
// Hidden tool
|
|
17
|
+
core.filter("assembly.system", this.assembleSummarized.bind(this), 200);
|
|
18
|
+
core.filter("assembly.system", this.assembleVisible.bind(this), 250);
|
|
19
|
+
// Hidden from the advertised tool list — written via <set path="known://...">.
|
|
20
|
+
// The known:// scheme lifecycle is taught in instructions-user.md.
|
|
18
21
|
core.markHidden();
|
|
19
22
|
}
|
|
20
23
|
|
|
@@ -45,7 +48,7 @@ export default class Known {
|
|
|
45
48
|
runId,
|
|
46
49
|
"known",
|
|
47
50
|
entry.body,
|
|
48
|
-
entry.attributes?.
|
|
51
|
+
entry.attributes?.tags,
|
|
49
52
|
);
|
|
50
53
|
}
|
|
51
54
|
|
|
@@ -80,11 +83,13 @@ export default class Known {
|
|
|
80
83
|
return entry.body;
|
|
81
84
|
}
|
|
82
85
|
|
|
83
|
-
// Summarized: first
|
|
86
|
+
// Summarized: first SUMMARY_MAX_CHARS of the body. The model already
|
|
87
|
+
// knows summarized data is approximate (taught in instructions), so
|
|
88
|
+
// we don't owe it a "[truncated]" marker that would push the body
|
|
89
|
+
// past the contract floor.
|
|
84
90
|
summary(entry) {
|
|
85
91
|
if (!entry.body) return "";
|
|
86
|
-
|
|
87
|
-
return `${entry.body.slice(0, 500)}\n[truncated — promote to see the full body]`;
|
|
92
|
+
return entry.body.slice(0, SUMMARY_MAX_CHARS);
|
|
88
93
|
}
|
|
89
94
|
|
|
90
95
|
// Identity-keyed summary lines: every data entry the run is tracking
|
|
@@ -99,7 +104,7 @@ export default class Known {
|
|
|
99
104
|
const lines = entries.map((e) =>
|
|
100
105
|
renderContextTag(e, e.sBody != null ? e.sBody : e.body),
|
|
101
106
|
);
|
|
102
|
-
return `${content}<
|
|
107
|
+
return `${content}<summary>\n${lines.join("\n")}\n</summary>\n`;
|
|
103
108
|
}
|
|
104
109
|
|
|
105
110
|
async assembleVisible(content, ctx) {
|
|
@@ -115,10 +120,6 @@ export default class Known {
|
|
|
115
120
|
}
|
|
116
121
|
|
|
117
122
|
function renderContextTag(entry, projectedBody) {
|
|
118
|
-
const tag = entry.scheme ? entry.scheme : "file";
|
|
119
|
-
const turn = entry.source_turn ? ` turn="${entry.source_turn}"` : "";
|
|
120
|
-
const tokens = entry.aTokens != null ? ` tokens="${entry.aTokens}"` : "";
|
|
121
|
-
const lines = entry.vLines != null ? ` lines="${entry.vLines}"` : "";
|
|
122
123
|
const attrs =
|
|
123
124
|
typeof entry.attributes === "string"
|
|
124
125
|
? JSON.parse(entry.attributes)
|
|
@@ -129,23 +130,16 @@ function renderContextTag(entry, projectedBody) {
|
|
|
129
130
|
: entry.state
|
|
130
131
|
? stateToStatus(entry.state, entry.outcome)
|
|
131
132
|
: null;
|
|
132
|
-
const
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
const visibility =
|
|
140
|
-
entry.visibility === "archived" ? ` visibility="archived"` : "";
|
|
141
|
-
const summaryText =
|
|
142
|
-
typeof attrs?.summary === "string"
|
|
143
|
-
? attrs.summary.replace(/"/g, "'").slice(0, 80)
|
|
144
|
-
: "";
|
|
145
|
-
const summary = ` summary="${summaryText}"`;
|
|
146
|
-
const attrStr = `${turn}${status}${stateAttr}${outcomeAttr}${summary}${visibility}${tokens}${lines}`;
|
|
147
|
-
if (projectedBody) {
|
|
148
|
-
return `<${tag} path="${entry.path}"${attrStr}>${projectedBody}</${tag}>`;
|
|
133
|
+
const meta = {};
|
|
134
|
+
if (entry.source_turn) meta.turn = entry.source_turn;
|
|
135
|
+
if (statusValue != null && statusValue !== 200) meta.status = statusValue;
|
|
136
|
+
if (entry.state && entry.state !== "resolved") meta.state = entry.state;
|
|
137
|
+
if (entry.outcome) meta.outcome = entry.outcome;
|
|
138
|
+
if (typeof attrs?.tags === "string") {
|
|
139
|
+
meta.tags = attrs.tags.slice(0, 80);
|
|
149
140
|
}
|
|
150
|
-
|
|
141
|
+
if (entry.visibility === "archived") meta.visibility = "archived";
|
|
142
|
+
if (entry.aTokens != null) meta.tokens = entry.aTokens;
|
|
143
|
+
if (entry.vLines != null) meta.lines = entry.vLines;
|
|
144
|
+
return renderEntry(entry.path, meta, projectedBody);
|
|
151
145
|
}
|
package/src/plugins/log/log.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { stateToStatus } from "../../agent/httpStatus.js";
|
|
2
|
+
import { renderEntry } from "../helpers.js";
|
|
2
3
|
|
|
3
4
|
// sh/env span multiple channels; channels render their own tokens in <visible>.
|
|
4
5
|
const STREAM_NO_TOKENS = new Set(["sh", "env"]);
|
|
@@ -8,7 +9,7 @@ export default class Log {
|
|
|
8
9
|
|
|
9
10
|
constructor(core) {
|
|
10
11
|
this.#core = core;
|
|
11
|
-
core.filter("assembly.
|
|
12
|
+
core.filter("assembly.system", this.assembleLog.bind(this), 300);
|
|
12
13
|
}
|
|
13
14
|
|
|
14
15
|
async assembleLog(content, ctx) {
|
|
@@ -16,8 +17,13 @@ export default class Log {
|
|
|
16
17
|
const latestPrompt = ctx.rows.findLast(
|
|
17
18
|
(r) => r.category === "prompt" && r.scheme === "prompt",
|
|
18
19
|
);
|
|
20
|
+
// All time-indexed activity belongs here: log entries (actions,
|
|
21
|
+
// errors, updates) AND streaming data channels from env/sh which
|
|
22
|
+
// are also time-indexed. Visibility controls the body projection
|
|
23
|
+
// (vBody for visible, sBody for summarized) — not which section
|
|
24
|
+
// the entry lives in.
|
|
19
25
|
const entries = ctx.rows.filter((r) => {
|
|
20
|
-
if (r.category === "logging"
|
|
26
|
+
if (r.category === "logging") return true;
|
|
21
27
|
if (r.category === "prompt" && r.scheme === "prompt") {
|
|
22
28
|
return r !== latestPrompt;
|
|
23
29
|
}
|
|
@@ -31,13 +37,30 @@ export default class Log {
|
|
|
31
37
|
}
|
|
32
38
|
}
|
|
33
39
|
|
|
34
|
-
// Action
|
|
40
|
+
// Action label for the entry's <log> rendering. log://turn_N/<action>/<slug>
|
|
41
|
+
// uses the path's action segment; env://turn_N/* and sh://turn_N/* are
|
|
42
|
+
// streaming channels, so the scheme itself is the action.
|
|
35
43
|
function actionFromPath(path) {
|
|
36
44
|
if (path?.startsWith("prompt://")) return "prompt";
|
|
45
|
+
if (path?.startsWith("env://")) return "env";
|
|
46
|
+
if (path?.startsWith("sh://")) return "sh";
|
|
37
47
|
const match = path?.match(/^log:\/\/turn_\d+\/([^/]+)\//);
|
|
38
48
|
return match ? match[1] : "log";
|
|
39
49
|
}
|
|
40
50
|
|
|
51
|
+
// Visibility controls projection within <log>: summarized entries render
|
|
52
|
+
// the compact sBody; visible entries render the full vBody (or fall back
|
|
53
|
+
// to the raw body when no projection exists).
|
|
54
|
+
function projectedBody(entry) {
|
|
55
|
+
if (entry.visibility === "summarized" && entry.sBody != null) {
|
|
56
|
+
return entry.sBody;
|
|
57
|
+
}
|
|
58
|
+
if (entry.visibility === "visible" && entry.vBody != null) {
|
|
59
|
+
return entry.vBody;
|
|
60
|
+
}
|
|
61
|
+
return entry.body;
|
|
62
|
+
}
|
|
63
|
+
|
|
41
64
|
function renderLogTag(entry, rowsByPath) {
|
|
42
65
|
const attrs =
|
|
43
66
|
typeof entry.attributes === "string"
|
|
@@ -45,20 +68,12 @@ function renderLogTag(entry, rowsByPath) {
|
|
|
45
68
|
: entry.attributes;
|
|
46
69
|
|
|
47
70
|
const action = actionFromPath(entry.path);
|
|
48
|
-
|
|
49
71
|
const statusValue =
|
|
50
72
|
attrs?.status != null
|
|
51
73
|
? attrs.status
|
|
52
74
|
: entry.state
|
|
53
75
|
? stateToStatus(entry.state, entry.outcome)
|
|
54
76
|
: null;
|
|
55
|
-
// Suppress status on prompts; uniform 200 carries no signal.
|
|
56
|
-
const status =
|
|
57
|
-
statusValue != null && action !== "prompt"
|
|
58
|
-
? ` status="${statusValue}"`
|
|
59
|
-
: "";
|
|
60
|
-
const outcomeAttr = entry.outcome ? ` outcome="${entry.outcome}"` : "";
|
|
61
|
-
// tokens = aTokens of the thing this tag represents (target via attrs.path, else self).
|
|
62
77
|
const isSlice = attrs?.lineStart != null;
|
|
63
78
|
const targetEntry = attrs?.path ? rowsByPath.get(attrs.path) : null;
|
|
64
79
|
let tokenSource = null;
|
|
@@ -76,27 +91,21 @@ function renderLogTag(entry, rowsByPath) {
|
|
|
76
91
|
tokenSource = entry.aTokens;
|
|
77
92
|
lineSource = entry.vLines;
|
|
78
93
|
}
|
|
79
|
-
const tokens = tokenSource != null ? ` tokens="${tokenSource}"` : "";
|
|
80
|
-
const summary =
|
|
81
|
-
typeof attrs?.summary === "string"
|
|
82
|
-
? ` summary="${attrs.summary.slice(0, 80)}"`
|
|
83
|
-
: "";
|
|
84
|
-
const query =
|
|
85
|
-
typeof attrs?.query === "string" ? ` query="${attrs.query}"` : "";
|
|
86
|
-
const command =
|
|
87
|
-
typeof attrs?.command === "string" ? ` command="${attrs.command}"` : "";
|
|
88
|
-
const target = attrs?.path ? ` target="${attrs.path}"` : "";
|
|
89
|
-
// Slice reads emit lines="a-b/total"; others emit simple lines="N".
|
|
90
|
-
const lines = isSlice
|
|
91
|
-
? ` lines="${attrs.lineStart}-${attrs.lineEnd}/${attrs.totalLines}"`
|
|
92
|
-
: lineSource != null
|
|
93
|
-
? ` lines="${lineSource}"`
|
|
94
|
-
: "";
|
|
95
|
-
|
|
96
|
-
const attrStr = `${target}${status}${outcomeAttr}${query}${command}${summary}${lines}${tokens}`;
|
|
97
94
|
|
|
98
|
-
|
|
99
|
-
|
|
95
|
+
const meta = { action };
|
|
96
|
+
if (attrs?.path) meta.target = attrs.path;
|
|
97
|
+
// Suppress status on prompts; uniform 200 carries no signal.
|
|
98
|
+
if (statusValue != null && action !== "prompt") meta.status = statusValue;
|
|
99
|
+
if (entry.outcome) meta.outcome = entry.outcome;
|
|
100
|
+
if (typeof attrs?.query === "string") meta.query = attrs.query;
|
|
101
|
+
if (typeof attrs?.command === "string") meta.command = attrs.command;
|
|
102
|
+
if (typeof attrs?.tags === "string") meta.tags = attrs.tags.slice(0, 80);
|
|
103
|
+
if (isSlice) {
|
|
104
|
+
meta.lines = `${attrs.lineStart}-${attrs.lineEnd}/${attrs.totalLines}`;
|
|
105
|
+
} else if (lineSource != null) {
|
|
106
|
+
meta.lines = lineSource;
|
|
100
107
|
}
|
|
101
|
-
|
|
108
|
+
if (tokenSource != null) meta.tokens = tokenSource;
|
|
109
|
+
|
|
110
|
+
return renderEntry(entry.path, meta, projectedBody(entry));
|
|
102
111
|
}
|
package/src/plugins/mv/mv.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import Entries from "../../agent/Entries.js";
|
|
2
|
+
import { storePatternResult } from "../helpers.js";
|
|
2
3
|
import docs from "./mvDoc.js";
|
|
3
4
|
|
|
4
5
|
const LOG_ACTION_RE = /^log:\/\/turn_\d+\/(\w+)\//;
|
|
@@ -34,6 +35,17 @@ export default class Mv {
|
|
|
34
35
|
? entry.attributes.visibility
|
|
35
36
|
: undefined;
|
|
36
37
|
|
|
38
|
+
// Manifest: list what would be affected without performing the mv.
|
|
39
|
+
if (entry.attributes.manifest !== undefined) {
|
|
40
|
+
const matches = await store.getEntriesByPattern(runId, path);
|
|
41
|
+
await storePatternResult(store, runId, turn, "mv", path, null, matches, {
|
|
42
|
+
manifest: true,
|
|
43
|
+
loopId,
|
|
44
|
+
attributes: { path, to },
|
|
45
|
+
});
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
37
49
|
// Visibility-in-place: no destination, change visibility of matched entries
|
|
38
50
|
if (visibility && !to) {
|
|
39
51
|
const matches = await store.getEntriesByPattern(runId, path);
|
|
@@ -58,6 +70,18 @@ export default class Mv {
|
|
|
58
70
|
|
|
59
71
|
const source = await store.getBody(runId, path);
|
|
60
72
|
if (source === null) return;
|
|
73
|
+
// Tags propagate: explicit `tags=` on the mv wins; otherwise the
|
|
74
|
+
// destination inherits the source entry's tags. Same shape as
|
|
75
|
+
// visibility — explicit attr overrides, default inherits.
|
|
76
|
+
let destTags = null;
|
|
77
|
+
if (typeof entry.attributes.tags === "string") {
|
|
78
|
+
destTags = entry.attributes.tags;
|
|
79
|
+
} else {
|
|
80
|
+
const sourceAttrs = await store.getAttributes(runId, path);
|
|
81
|
+
if (sourceAttrs && typeof sourceAttrs.tags === "string") {
|
|
82
|
+
destTags = sourceAttrs.tags;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
61
85
|
|
|
62
86
|
const destScheme = Entries.scheme(to);
|
|
63
87
|
const existing = await store.getBody(runId, to);
|
|
@@ -68,13 +92,27 @@ export default class Mv {
|
|
|
68
92
|
|
|
69
93
|
const body = `${path} ${to}`;
|
|
70
94
|
if (destScheme === null) {
|
|
95
|
+
// Bare-file destination: hand the shared materializer (set.js
|
|
96
|
+
// #materializeFile, gated on attrs.path + attrs.patched) the
|
|
97
|
+
// authoritative new body so it writes the source content to
|
|
98
|
+
// disk on accept. Without this the source rm fired but the
|
|
99
|
+
// destination was never created. Same shape as cp's bare-file
|
|
100
|
+
// branch.
|
|
71
101
|
await store.set({
|
|
72
102
|
runId,
|
|
73
103
|
turn,
|
|
74
104
|
path: entry.resultPath,
|
|
75
105
|
body,
|
|
76
106
|
state: "proposed",
|
|
77
|
-
attributes: {
|
|
107
|
+
attributes: {
|
|
108
|
+
from: path,
|
|
109
|
+
to,
|
|
110
|
+
isMove: true,
|
|
111
|
+
warning,
|
|
112
|
+
path: to,
|
|
113
|
+
patched: source,
|
|
114
|
+
visibility,
|
|
115
|
+
},
|
|
78
116
|
loopId,
|
|
79
117
|
});
|
|
80
118
|
} else {
|
|
@@ -85,6 +123,7 @@ export default class Mv {
|
|
|
85
123
|
body: source,
|
|
86
124
|
state: "resolved",
|
|
87
125
|
visibility,
|
|
126
|
+
attributes: destTags ? { tags: destTags } : null,
|
|
88
127
|
loopId,
|
|
89
128
|
});
|
|
90
129
|
await store.rm({ runId: runId, path: path });
|
package/src/plugins/mv/mvDoc.md
CHANGED
|
@@ -1,10 +1,3 @@
|
|
|
1
1
|
## <mv path="[source]">[destination]</mv> - Move or rename a file or entry
|
|
2
|
-
|
|
3
|
-
Example: <mv path="known://active_task">known://completed_task</mv>
|
|
4
|
-
<!-- Entry rename. Most common mv use case. -->
|
|
5
|
-
|
|
6
2
|
Example: <mv path="src/old_name.js">src/new_name.js</mv>
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
Example: <mv path="known://project/*" visibility="summarized"/>
|
|
10
|
-
<!-- Batch visibility change via pattern. No destination = visibility in place. -->
|
|
3
|
+
Example: <mv path="known://drafts/final_report.md">final_report.md</mv>
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import config from "../../agent/config.js";
|
|
2
1
|
import msg from "../../agent/messages.js";
|
|
3
2
|
import { chatCompletionStream } from "../../llm/openaiStream.js";
|
|
4
3
|
import { retryWithBackoff } from "../../llm/retry.js";
|
|
5
4
|
|
|
6
|
-
const
|
|
5
|
+
const FETCH_TIMEOUT = Number(process.env.RUMMY_FETCH_TIMEOUT);
|
|
6
|
+
const THINK = process.env.RUMMY_THINK === "1";
|
|
7
7
|
|
|
8
8
|
const PROVIDER = "ollama";
|
|
9
9
|
|
|
@@ -28,7 +28,8 @@ export default class Ollama {
|
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
async #completion(messages, model, options = {}) {
|
|
31
|
-
const body = { model, messages, think:
|
|
31
|
+
const body = { model, messages, think: THINK };
|
|
32
|
+
if (options.maxTokens !== undefined) body.max_tokens = options.maxTokens;
|
|
32
33
|
if (options.temperature !== undefined)
|
|
33
34
|
body.temperature = options.temperature;
|
|
34
35
|
|