@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,8 +1,8 @@
|
|
|
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
|
|
|
5
|
-
const
|
|
4
|
+
const FETCH_TIMEOUT = Number(process.env.RUMMY_FETCH_TIMEOUT);
|
|
5
|
+
const THINK = process.env.RUMMY_THINK === "1";
|
|
6
6
|
|
|
7
7
|
const PROVIDER = "openai";
|
|
8
8
|
|
|
@@ -29,7 +29,8 @@ export default class OpenAi {
|
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
async #completion(messages, model, options = {}) {
|
|
32
|
-
const body = { model, messages, think:
|
|
32
|
+
const body = { model, messages, think: THINK };
|
|
33
|
+
if (options.maxTokens !== undefined) body.max_tokens = options.maxTokens;
|
|
33
34
|
if (options.temperature !== undefined)
|
|
34
35
|
body.temperature = options.temperature;
|
|
35
36
|
|
|
@@ -1,8 +1,7 @@
|
|
|
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
|
|
|
5
|
-
const
|
|
4
|
+
const FETCH_TIMEOUT = Number(process.env.RUMMY_FETCH_TIMEOUT);
|
|
6
5
|
|
|
7
6
|
const PROVIDER = "openrouter";
|
|
8
7
|
|
|
@@ -31,7 +30,11 @@ export default class OpenRouter {
|
|
|
31
30
|
}
|
|
32
31
|
|
|
33
32
|
async #completion(messages, model, options = {}) {
|
|
33
|
+
// include_reasoning is OpenRouter's relay-level knob: does the proxy
|
|
34
|
+
// surface reasoning back to us? Always on — we pay for the tokens,
|
|
35
|
+
// we keep the telemetry. Orthogonal to RUMMY_THINK (model-side).
|
|
34
36
|
const body = { model, messages, include_reasoning: true };
|
|
37
|
+
if (options.maxTokens !== undefined) body.max_tokens = options.maxTokens;
|
|
35
38
|
if (options.temperature !== undefined)
|
|
36
39
|
body.temperature = options.temperature;
|
|
37
40
|
|
|
@@ -75,6 +78,13 @@ export default class OpenRouter {
|
|
|
75
78
|
async #getContextSize(model) {
|
|
76
79
|
if (this.#contextCache.has(model)) return this.#contextCache.get(model);
|
|
77
80
|
|
|
81
|
+
// OpenRouter provider-pinning shorthand (`google/gemma-4-31b-it:cloudflare`)
|
|
82
|
+
// routes the completion to a specific upstream, but the /models
|
|
83
|
+
// catalog lists the bare model id (`google/gemma-4-31b-it`). Split
|
|
84
|
+
// off the `:provider` suffix for the lookup; it's a routing hint,
|
|
85
|
+
// not part of the model identity.
|
|
86
|
+
const lookupId = model.split(":")[0];
|
|
87
|
+
|
|
78
88
|
const res = await fetch(`${this.#baseUrl}/models`, {
|
|
79
89
|
headers: { Authorization: `Bearer ${this.#apiKey}` },
|
|
80
90
|
signal: AbortSignal.timeout(FETCH_TIMEOUT),
|
|
@@ -85,10 +95,10 @@ export default class OpenRouter {
|
|
|
85
95
|
);
|
|
86
96
|
}
|
|
87
97
|
const data = await res.json();
|
|
88
|
-
const entry = data.data?.find((m) => m.id ===
|
|
98
|
+
const entry = data.data?.find((m) => m.id === lookupId);
|
|
89
99
|
if (!entry?.context_length) {
|
|
90
100
|
throw new Error(
|
|
91
|
-
`OpenRouter /models has no context_length for "${
|
|
101
|
+
`OpenRouter /models has no context_length for "${lookupId}".`,
|
|
92
102
|
);
|
|
93
103
|
}
|
|
94
104
|
this.#contextCache.set(model, entry.context_length);
|
|
@@ -1,20 +1,18 @@
|
|
|
1
1
|
# persona {#persona_plugin}
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
Workflow specification rendered into the system prompt below the
|
|
4
|
+
tooldocs. 1:1 run:persona — set at run creation, immutable thereafter.
|
|
5
5
|
|
|
6
6
|
## Files
|
|
7
7
|
|
|
8
|
-
- **
|
|
8
|
+
- **default.md** — default persona (7D state machine) injected when the
|
|
9
|
+
client supplies none.
|
|
10
|
+
- **persona.js** — registers the `persona` scheme + view callbacks.
|
|
9
11
|
|
|
10
|
-
##
|
|
12
|
+
## Resolution
|
|
11
13
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
## Behavior
|
|
18
|
-
|
|
19
|
-
Persona is stored on the run row (`runs.persona`). The instructions
|
|
20
|
-
plugin reads it during system-prompt assembly.
|
|
14
|
+
- Client supplies persona text via the run-creation RPC's `persona`
|
|
15
|
+
option.
|
|
16
|
+
- If null, `AgentLoop.ensureRun` loads `default.md`.
|
|
17
|
+
- Result persists to `runs.persona`; the instructions plugin reads it
|
|
18
|
+
during system-prompt assembly.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Folksonomic XML Command State Machine: Draft -> Decompose -> Discover -> Distill -> Define -> Determine -> Deliver
|
|
2
|
+
|
|
3
|
+
YOU MUST ONLY use the available Folksonomic XML Commands to Draft a plan, Decompose the prompt into its unknowns, then Discover and Distill information into knowns while demoting irrelevant source entries and log entries before Defining, Determining, and Delivering.
|
|
4
|
+
|
|
5
|
+
YOU MAY edit, expand, update, or revise the plan as you proceed.
|
|
6
|
+
YOU MUST perform the next step in the plan, optimizing visibility for relevance and budget constraints.
|
|
7
|
+
|
|
8
|
+
Example:
|
|
9
|
+
<set path="known://plan"><<NEW
|
|
10
|
+
- [ ] Draft a plan
|
|
11
|
+
- [ ] Decompose key, relevant unknowns into topical, taxonomized, and tagged unknown entries
|
|
12
|
+
- [ ] Discover key, relevant information
|
|
13
|
+
- [ ] Distill key, relevant information into topical, taxonomized, tagged, and referenced known entries
|
|
14
|
+
- [ ] Define the answer or solution
|
|
15
|
+
- [ ] Determine the validity of the answer or solution (and fix if failing)
|
|
16
|
+
- [ ] Deliver
|
|
17
|
+
NEW</set>
|
|
18
|
+
|
|
19
|
+
<set path="unknown://countries/france/capital" tags="countries,france,capital,geography"><<NEW
|
|
20
|
+
What is the capital of France?
|
|
21
|
+
NEW</set>
|
|
22
|
+
<set path="unknown://countries/france/population" tags="countries,france,population,demographics"><<NEW
|
|
23
|
+
What is the population of France?
|
|
24
|
+
NEW</set>
|
|
25
|
+
<set path="unknown://countries/france/area" tags="countries,france,area,geography"><<NEW
|
|
26
|
+
What is the area of France?
|
|
27
|
+
NEW</set>
|
|
28
|
+
|
|
29
|
+
<update status="102">plan drafted, unknowns decomposed</update>
|
|
@@ -1,71 +1,15 @@
|
|
|
1
|
-
import fs from "node:fs/promises";
|
|
2
|
-
import { join } from "node:path";
|
|
3
|
-
|
|
4
1
|
export default class Persona {
|
|
5
|
-
#core;
|
|
6
|
-
|
|
7
2
|
constructor(core) {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
const runRow = await ctx.db.get_run_by_alias.get({ alias: params.run });
|
|
16
|
-
if (!runRow) throw new Error(`Run not found: ${params.run}`);
|
|
17
|
-
|
|
18
|
-
let text = params.text;
|
|
19
|
-
if (params.name && !text) {
|
|
20
|
-
text = await loadFile(params.name);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
// "Pass neither to clear" — empty string counts as clear too.
|
|
24
|
-
let persona = null;
|
|
25
|
-
if (text) persona = text;
|
|
26
|
-
await ctx.db.update_run_config.run({
|
|
27
|
-
id: runRow.id,
|
|
28
|
-
temperature: null,
|
|
29
|
-
persona,
|
|
30
|
-
context_limit: null,
|
|
31
|
-
model: null,
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
return { status: "ok" };
|
|
35
|
-
},
|
|
36
|
-
description:
|
|
37
|
-
"Set persona on a run. Pass name or text. Pass neither to clear.",
|
|
38
|
-
params: {
|
|
39
|
-
run: "string — run alias",
|
|
40
|
-
name: "string? — persona filename (without .md)",
|
|
41
|
-
text: "string? — raw persona text (overrides name)",
|
|
42
|
-
},
|
|
43
|
-
requiresInit: true,
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
r.register("listPersonas", {
|
|
47
|
-
handler: async () => {
|
|
48
|
-
const dir = configDir();
|
|
49
|
-
if (!dir) return [];
|
|
50
|
-
const files = await fs.readdir(dir);
|
|
51
|
-
return files
|
|
52
|
-
.filter((f) => f.endsWith(".md"))
|
|
53
|
-
.map((f) => ({ name: f.replace(".md", ""), path: join(dir, f) }));
|
|
54
|
-
},
|
|
55
|
-
description: "List available persona files. Returns [{ name, path }].",
|
|
56
|
-
requiresInit: true,
|
|
57
|
-
});
|
|
3
|
+
core.registerScheme({ name: "persona", category: "data" });
|
|
4
|
+
core.hooks.tools.onView("persona", (entry) => entry.body, "visible");
|
|
5
|
+
core.hooks.tools.onView("persona", () => "", "summarized");
|
|
6
|
+
// assembly.system @ 150 — last system-prompt section. Body comes
|
|
7
|
+
// from runs.persona, plumbed in via ctx.persona by TurnExecutor.
|
|
8
|
+
core.filter("assembly.system", this.assembleSystemPersona.bind(this), 150);
|
|
58
9
|
}
|
|
59
|
-
}
|
|
60
10
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
async function loadFile(name) {
|
|
68
|
-
const dir = configDir();
|
|
69
|
-
if (!dir) throw new Error("RUMMY_HOME not configured");
|
|
70
|
-
return fs.readFile(join(dir, `${name}.md`), "utf8");
|
|
11
|
+
assembleSystemPersona(content, ctx) {
|
|
12
|
+
if (!ctx.persona) return content;
|
|
13
|
+
return `${content}\n\n## Operational Persona\n\n${ctx.persona}`;
|
|
14
|
+
}
|
|
71
15
|
}
|
|
@@ -1,49 +1,50 @@
|
|
|
1
1
|
import Entries from "../../agent/Entries.js";
|
|
2
2
|
|
|
3
3
|
export default class Policy {
|
|
4
|
+
#core;
|
|
5
|
+
|
|
4
6
|
constructor(core) {
|
|
7
|
+
this.#core = core;
|
|
5
8
|
core.filter("entry.recording", this.#enforceAskMode.bind(this), 1);
|
|
6
9
|
}
|
|
7
10
|
|
|
8
|
-
#fail(entry
|
|
9
|
-
return { ...entry,
|
|
11
|
+
#fail(entry) {
|
|
12
|
+
return { ...entry, state: "failed", outcome: "permission" };
|
|
10
13
|
}
|
|
11
14
|
|
|
12
15
|
async #enforceAskMode(entry, ctx) {
|
|
13
16
|
if (ctx.mode !== "ask") return entry;
|
|
14
17
|
|
|
18
|
+
let message = null;
|
|
15
19
|
if (entry.scheme === "sh") {
|
|
16
|
-
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
if (entry.scheme === "set" && entry.attributes?.path) {
|
|
20
|
+
message = "Rejected <sh> in ask mode";
|
|
21
|
+
} else if (entry.scheme === "set" && entry.attributes?.path) {
|
|
20
22
|
const scheme = Entries.scheme(entry.attributes.path);
|
|
21
23
|
if (scheme === null && entry.body) {
|
|
22
|
-
|
|
23
|
-
entry,
|
|
24
|
-
`Rejected file edit to ${entry.attributes.path} in ask mode`,
|
|
25
|
-
);
|
|
24
|
+
message = `Rejected file edit to ${entry.attributes.path} in ask mode`;
|
|
26
25
|
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
if (entry.scheme === "rm") {
|
|
26
|
+
} else if (entry.scheme === "rm") {
|
|
30
27
|
const pathAttr = entry.attributes?.path || entry.path;
|
|
31
28
|
const scheme = Entries.scheme(pathAttr);
|
|
32
29
|
if (scheme === null) {
|
|
33
|
-
|
|
30
|
+
message = `Rejected file rm of ${pathAttr} in ask mode`;
|
|
34
31
|
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
if (entry.scheme === "mv" || entry.scheme === "cp") {
|
|
32
|
+
} else if (entry.scheme === "mv" || entry.scheme === "cp") {
|
|
38
33
|
const destScheme = Entries.scheme(entry.attributes?.to);
|
|
39
34
|
if (destScheme === null) {
|
|
40
|
-
|
|
41
|
-
entry,
|
|
42
|
-
`Rejected ${entry.scheme} to file ${entry.attributes?.to} in ask mode`,
|
|
43
|
-
);
|
|
35
|
+
message = `Rejected ${entry.scheme} to file ${entry.attributes?.to} in ask mode`;
|
|
44
36
|
}
|
|
45
37
|
}
|
|
46
38
|
|
|
47
|
-
return entry;
|
|
39
|
+
if (!message) return entry;
|
|
40
|
+
await this.#core.hooks.error.log.emit({
|
|
41
|
+
store: ctx.store,
|
|
42
|
+
runId: ctx.runId,
|
|
43
|
+
turn: ctx.turn,
|
|
44
|
+
loopId: ctx.loopId,
|
|
45
|
+
message,
|
|
46
|
+
status: 403,
|
|
47
|
+
});
|
|
48
|
+
return this.#fail(entry);
|
|
48
49
|
}
|
|
49
50
|
}
|
|
@@ -1,38 +1,48 @@
|
|
|
1
1
|
# prompt {#prompt_plugin}
|
|
2
2
|
|
|
3
|
-
Renders the `<prompt
|
|
4
|
-
|
|
3
|
+
Renders the `<prompt>` tag at the front of the user message —
|
|
4
|
+
the model sees its task first, then the dynamic state blocks,
|
|
5
|
+
then the late-bound rules and budget. Always present on every
|
|
6
|
+
turn.
|
|
5
7
|
|
|
6
8
|
## Registration
|
|
7
9
|
|
|
8
|
-
- **Filter**: `assembly.user` at priority
|
|
10
|
+
- **Filter**: `assembly.user` at priority 30 (front of user
|
|
11
|
+
message, before all dynamic state and the `<instructions>`
|
|
12
|
+
block at 165)
|
|
9
13
|
|
|
10
14
|
## Behavior
|
|
11
15
|
|
|
12
|
-
Finds the latest `prompt://` entry in the turn_context rows.
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
16
|
+
Finds the latest `prompt://` entry in the turn_context rows. Renders
|
|
17
|
+
with `commands` attribute (available tool list) and an optional
|
|
18
|
+
`warn="File editing disallowed."` attribute when the loop's mode is
|
|
19
|
+
`ask` (read from `attributes.mode` on the prompt entry, falling back
|
|
20
|
+
to the type passed by the core). The mode itself is not rendered as
|
|
21
|
+
a tag attribute — the warn copy carries the only model-relevant
|
|
22
|
+
consequence.
|
|
17
23
|
|
|
18
|
-
## Archived prompts
|
|
24
|
+
## Archived prompts disappear, by design
|
|
19
25
|
|
|
20
26
|
`v_model_context.sql` filters archived entries out of the model's
|
|
21
|
-
context
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
`<
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
27
|
+
context for every scheme — `prompt` included. There is no carve-out.
|
|
28
|
+
An archived `prompt://N` does not appear in the user message at
|
|
29
|
+
all: no tag, no body, no metadata.
|
|
30
|
+
|
|
31
|
+
The model receives no instruction-side hint not to archive the
|
|
32
|
+
active prompt. If it archives the prompt anyway, the run will
|
|
33
|
+
visibly fail on the next turn (no `<prompt>` tag for the model to
|
|
34
|
+
act on; the model emits "please provide a prompt to act upon" or
|
|
35
|
+
similar confusion). That instructive failure mode is intentional —
|
|
36
|
+
paradigm purity (archived means archived, no exceptions) over
|
|
37
|
+
silent data-layer rescue.
|
|
38
|
+
|
|
39
|
+
If practical behavior at scale ever demands a guard, the right
|
|
40
|
+
surface is an action-gate (refuse the `<set>` of `visibility="archived"`
|
|
41
|
+
on the active `prompt://N` with a soft 403 the model can read),
|
|
42
|
+
not a read-view carve-out that quietly keeps the entry visible.
|
|
43
|
+
|
|
44
|
+
System-level auto-archive on new prompt is unaffected: when a fresh
|
|
45
|
+
`prompt://M` arrives, the engine archives `prompt://N` (M > N) so
|
|
46
|
+
the prior cycle's prompt cleanly leaves context. `unknown://` /
|
|
47
|
+
`known://` entries persist across cycles; logs are demoted per
|
|
48
|
+
stage instructions.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
import { renderEntry, SUMMARY_MAX_CHARS } from "../helpers.js";
|
|
2
2
|
|
|
3
3
|
export default class Prompt {
|
|
4
4
|
#core;
|
|
@@ -8,24 +8,17 @@ export default class Prompt {
|
|
|
8
8
|
core.hooks.tools.onView("prompt", (entry) => entry.body, "visible");
|
|
9
9
|
core.hooks.tools.onView(
|
|
10
10
|
"prompt",
|
|
11
|
-
(entry) =>
|
|
12
|
-
const full = entry.body;
|
|
13
|
-
if (full.length <= SUMMARIZED_PROMPT_CHAR_CAP) return full;
|
|
14
|
-
return `${full.slice(0, SUMMARIZED_PROMPT_CHAR_CAP)}\n[truncated — promote to see the complete prompt]`;
|
|
15
|
-
},
|
|
11
|
+
(entry) => entry.body.slice(0, SUMMARY_MAX_CHARS),
|
|
16
12
|
"summarized",
|
|
17
13
|
);
|
|
18
14
|
core.on("turn.started", this.onTurnStarted.bind(this));
|
|
19
|
-
core.filter("assembly.user", this.assemblePrompt.bind(this),
|
|
15
|
+
core.filter("assembly.user", this.assemblePrompt.bind(this), 60);
|
|
20
16
|
}
|
|
21
17
|
|
|
22
18
|
async onTurnStarted({ rummy, mode, prompt, isContinuation }) {
|
|
23
19
|
const { entries: store, sequence: turn, runId, loopId } = rummy;
|
|
24
20
|
|
|
25
21
|
if (!isContinuation && prompt) {
|
|
26
|
-
// New prompt = new cycle; archive prior cycle's prompts/logs (knowns/unknowns persist).
|
|
27
|
-
await store.archivePriorPromptArtifacts(runId, turn);
|
|
28
|
-
|
|
29
22
|
await store.set({
|
|
30
23
|
runId,
|
|
31
24
|
turn,
|
|
@@ -81,14 +74,15 @@ export default class Prompt {
|
|
|
81
74
|
}
|
|
82
75
|
}
|
|
83
76
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
const
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
77
|
+
// <prompt> wrapper carries section-level metadata (commands, mode
|
|
78
|
+
// warn, reverted-from-413). The body is heredoc-fenced so any
|
|
79
|
+
// task description containing tag-shaped text won't be parsed as
|
|
80
|
+
// a tool call when the model echoes attention through the packet.
|
|
81
|
+
const meta = {};
|
|
82
|
+
if (promptEntry?.visibility) meta.visibility = promptEntry.visibility;
|
|
83
|
+
if (promptEntry?.aTokens != null) meta.tokens = promptEntry.aTokens;
|
|
84
|
+
if (promptEntry?.vLines != null) meta.lines = promptEntry.vLines;
|
|
85
|
+
const fenced = promptEntry ? renderEntry(promptEntry.path, meta, body) : "";
|
|
86
|
+
return `${content}<prompt commands="${commands}"${warn}${reverted}>\n${fenced}\n</prompt>`;
|
|
93
87
|
}
|
|
94
88
|
}
|
package/src/plugins/rm/rm.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 "./rmDoc.js";
|
|
3
4
|
|
|
4
5
|
const LOG_ACTION_RE = /^log:\/\/turn_\d+\/(\w+)\//;
|
|
@@ -63,6 +64,23 @@ export default class Rm {
|
|
|
63
64
|
entry.attributes.body,
|
|
64
65
|
);
|
|
65
66
|
|
|
67
|
+
// Manifest: list what would be removed without performing the rm.
|
|
68
|
+
// Safety idiom for destructive bulk ops — the model can audit a
|
|
69
|
+
// glob's reach before committing to it.
|
|
70
|
+
if (entry.attributes.manifest !== undefined) {
|
|
71
|
+
await storePatternResult(
|
|
72
|
+
store,
|
|
73
|
+
runId,
|
|
74
|
+
turn,
|
|
75
|
+
"rm",
|
|
76
|
+
target,
|
|
77
|
+
entry.attributes.body,
|
|
78
|
+
matches,
|
|
79
|
+
{ manifest: true, loopId, attributes: { path: target } },
|
|
80
|
+
);
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
66
84
|
if (matches.length === 0) {
|
|
67
85
|
await store.set({
|
|
68
86
|
runId,
|
package/src/plugins/rm/rmDoc.md
CHANGED
|
@@ -3,11 +3,10 @@
|
|
|
3
3
|
Example: <rm path="src/config.js"/>
|
|
4
4
|
<!-- File removal. Simplest form. -->
|
|
5
5
|
|
|
6
|
-
Example: <rm path="known://
|
|
7
|
-
<!--
|
|
6
|
+
Example: <rm path="known://countries/france/*" manifest/>
|
|
7
|
+
<!-- Manifest before deleting. Safety pattern for bulk operations. -->
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
<!--
|
|
9
|
+
Example: <rm path="log://turn_3/get/**"/>
|
|
10
|
+
<!-- Bulk delete by glob. Recursive scheme glob; clears a turn's get logs in one call. -->
|
|
11
11
|
|
|
12
|
-
*
|
|
13
|
-
<!-- Canonical manifest teaching lives here — rm is the most intuitive 'check before committing' case. Model generalizes to cp/mv/get by analogy. Advanced uses (e.g. archive rediscovery via <get manifest>) belong in persona/skill docs, not here. -->
|
|
12
|
+
* Permanent. Prefer <set path="..." visibility="archived"/> to preserve for later retrieval.
|
package/src/plugins/rpc/rpc.js
CHANGED
|
@@ -2,7 +2,7 @@ import msg from "../../agent/messages.js";
|
|
|
2
2
|
import RummyContext from "../../hooks/RummyContext.js";
|
|
3
3
|
import File from "../file/file.js";
|
|
4
4
|
|
|
5
|
-
const CONSTRAINT_VISIBILITIES = new Set(["
|
|
5
|
+
const CONSTRAINT_VISIBILITIES = new Set(["add", "readonly", "ignore"]);
|
|
6
6
|
|
|
7
7
|
export default class Rpc {
|
|
8
8
|
#core;
|
|
@@ -261,11 +261,11 @@ export default class Rpc {
|
|
|
261
261
|
},
|
|
262
262
|
description:
|
|
263
263
|
"Set a project-level file constraint. Visibility ∈ " +
|
|
264
|
-
"{
|
|
264
|
+
"{add, readonly, ignore}. Patterns can be globs. " +
|
|
265
265
|
"Persists across runs; overlays git defaults.",
|
|
266
266
|
params: {
|
|
267
267
|
pattern: "string — file path or glob",
|
|
268
|
-
visibility: "string —
|
|
268
|
+
visibility: "string — add | readonly | ignore",
|
|
269
269
|
},
|
|
270
270
|
requiresInit: true,
|
|
271
271
|
});
|