@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.
Files changed (140) hide show
  1. package/.env.example +40 -15
  2. package/.xai.key +1 -0
  3. package/PLUGINS.md +169 -53
  4. package/README.md +38 -32
  5. package/SPEC.md +366 -179
  6. package/bin/digest.js +1097 -0
  7. package/biome/no-fallbacks.grit +2 -2
  8. package/gemini.key +1 -0
  9. package/lang/en.json +10 -1
  10. package/migrations/001_initial_schema.sql +9 -2
  11. package/package.json +19 -8
  12. package/service.js +1 -0
  13. package/src/agent/AgentLoop.js +76 -26
  14. package/src/agent/ContextAssembler.js +2 -0
  15. package/src/agent/Entries.js +238 -60
  16. package/src/agent/ProjectAgent.js +44 -0
  17. package/src/agent/TurnExecutor.js +99 -30
  18. package/src/agent/XmlParser.js +206 -111
  19. package/src/agent/errors.js +35 -0
  20. package/src/agent/known_queries.sql +1 -1
  21. package/src/agent/known_store.sql +3 -42
  22. package/src/agent/materializeContext.js +30 -1
  23. package/src/agent/runs.sql +8 -18
  24. package/src/agent/tokens.js +0 -1
  25. package/src/agent/turns.sql +1 -0
  26. package/src/hooks/Hooks.js +26 -0
  27. package/src/hooks/RummyContext.js +12 -1
  28. package/src/lib/hedberg/README.md +60 -0
  29. package/src/lib/hedberg/hedberg.js +60 -0
  30. package/src/lib/hedberg/marker.js +158 -0
  31. package/src/{plugins → lib}/hedberg/matcher.js +1 -2
  32. package/src/llm/LlmProvider.js +41 -3
  33. package/src/llm/openaiStream.js +17 -0
  34. package/src/plugins/ask_user/ask_user.js +12 -2
  35. package/src/plugins/ask_user/ask_userDoc.md +1 -5
  36. package/src/plugins/budget/README.md +29 -24
  37. package/src/plugins/budget/budget.js +166 -110
  38. package/src/plugins/cli/README.md +3 -4
  39. package/src/plugins/cli/cli.js +31 -5
  40. package/src/plugins/cloudflare/cloudflare.js +136 -0
  41. package/src/plugins/cp/cp.js +41 -4
  42. package/src/plugins/cp/cpDoc.md +5 -6
  43. package/src/plugins/engine/engine.sql +1 -1
  44. package/src/plugins/env/README.md +5 -4
  45. package/src/plugins/env/env.js +7 -4
  46. package/src/plugins/env/envDoc.md +7 -8
  47. package/src/plugins/error/error.js +56 -15
  48. package/src/plugins/file/README.md +12 -3
  49. package/src/plugins/file/file.js +2 -2
  50. package/src/plugins/get/get.js +59 -36
  51. package/src/plugins/get/getDoc.md +10 -34
  52. package/src/plugins/google/google.js +115 -0
  53. package/src/plugins/hedberg/hedberg.js +13 -56
  54. package/src/plugins/helpers.js +66 -12
  55. package/src/plugins/index.js +1 -2
  56. package/src/plugins/instructions/README.md +44 -47
  57. package/src/plugins/instructions/instructions-system.md +44 -0
  58. package/src/plugins/instructions/instructions-user.md +53 -0
  59. package/src/plugins/instructions/instructions.js +58 -189
  60. package/src/plugins/known/README.md +6 -7
  61. package/src/plugins/known/known.js +24 -30
  62. package/src/plugins/log/log.js +41 -32
  63. package/src/plugins/mv/mv.js +40 -1
  64. package/src/plugins/mv/mvDoc.md +1 -8
  65. package/src/plugins/ollama/ollama.js +4 -3
  66. package/src/plugins/openai/openai.js +4 -3
  67. package/src/plugins/openrouter/openrouter.js +14 -4
  68. package/src/plugins/persona/README.md +11 -13
  69. package/src/plugins/persona/default.md +29 -0
  70. package/src/plugins/persona/persona.js +10 -66
  71. package/src/plugins/policy/policy.js +23 -22
  72. package/src/plugins/prompt/README.md +37 -27
  73. package/src/plugins/prompt/prompt.js +13 -19
  74. package/src/plugins/rm/rm.js +18 -0
  75. package/src/plugins/rm/rmDoc.md +5 -6
  76. package/src/plugins/rpc/rpc.js +3 -3
  77. package/src/plugins/set/set.js +205 -323
  78. package/src/plugins/set/setDoc.md +47 -17
  79. package/src/plugins/sh/README.md +6 -5
  80. package/src/plugins/sh/sh.js +8 -5
  81. package/src/plugins/sh/shDoc.md +7 -8
  82. package/src/plugins/skill/README.md +37 -14
  83. package/src/plugins/skill/skill.js +200 -101
  84. package/src/plugins/skill/skillDoc.js +3 -0
  85. package/src/plugins/skill/skillDoc.md +9 -0
  86. package/src/plugins/stream/README.md +7 -6
  87. package/src/plugins/stream/finalize.js +100 -0
  88. package/src/plugins/stream/stream.js +13 -45
  89. package/src/plugins/telemetry/telemetry.js +27 -4
  90. package/src/plugins/think/think.js +2 -3
  91. package/src/plugins/think/thinkDoc.md +2 -4
  92. package/src/plugins/unknown/README.md +1 -1
  93. package/src/plugins/unknown/unknown.js +17 -19
  94. package/src/plugins/update/update.js +4 -51
  95. package/src/plugins/update/updateDoc.md +21 -6
  96. package/src/plugins/xai/xai.js +68 -102
  97. package/src/plugins/yolo/yolo.js +102 -75
  98. package/src/sql/functions/hedmatch.js +1 -1
  99. package/src/sql/functions/hedreplace.js +1 -1
  100. package/src/sql/functions/hedsearch.js +1 -1
  101. package/src/sql/functions/slugify.js +16 -2
  102. package/BENCH_ENVIRONMENT.md +0 -230
  103. package/CLIENT_INTERFACE.md +0 -396
  104. package/last_run.txt +0 -5617
  105. package/scriptify/ask_run.js +0 -77
  106. package/scriptify/cache_probe.js +0 -66
  107. package/scriptify/cache_probe_grok.js +0 -74
  108. package/src/agent/budget.js +0 -33
  109. package/src/agent/config.js +0 -38
  110. package/src/plugins/hedberg/README.md +0 -71
  111. package/src/plugins/hedberg/docs.md +0 -0
  112. package/src/plugins/hedberg/edits.js +0 -55
  113. package/src/plugins/hedberg/normalize.js +0 -17
  114. package/src/plugins/hedberg/sed.js +0 -49
  115. package/src/plugins/instructions/instructions.md +0 -34
  116. package/src/plugins/instructions/instructions_104.md +0 -8
  117. package/src/plugins/instructions/instructions_105.md +0 -39
  118. package/src/plugins/instructions/instructions_106.md +0 -22
  119. package/src/plugins/instructions/instructions_107.md +0 -17
  120. package/src/plugins/instructions/instructions_108.md +0 -0
  121. package/src/plugins/known/knownDoc.js +0 -3
  122. package/src/plugins/known/knownDoc.md +0 -8
  123. package/src/plugins/unknown/unknownDoc.js +0 -3
  124. package/src/plugins/unknown/unknownDoc.md +0 -11
  125. package/turns/cli_1777462658211/turn_001.txt +0 -772
  126. package/turns/cli_1777462658211/turn_002.txt +0 -606
  127. package/turns/cli_1777462658211/turn_003.txt +0 -667
  128. package/turns/cli_1777462658211/turn_004.txt +0 -297
  129. package/turns/cli_1777462658211/turn_005.txt +0 -301
  130. package/turns/cli_1777462658211/turn_006.txt +0 -262
  131. package/turns/cli_1777465095132/turn_001.txt +0 -715
  132. package/turns/cli_1777465095132/turn_002.txt +0 -236
  133. package/turns/cli_1777465095132/turn_003.txt +0 -287
  134. package/turns/cli_1777465095132/turn_004.txt +0 -694
  135. package/turns/cli_1777465095132/turn_005.txt +0 -422
  136. package/turns/cli_1777465095132/turn_006.txt +0 -365
  137. package/turns/cli_1777465095132/turn_007.txt +0 -885
  138. package/turns/cli_1777465095132/turn_008.txt +0 -1277
  139. package/turns/cli_1777465095132/turn_009.txt +0 -736
  140. /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 { FETCH_TIMEOUT } = config;
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: true };
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 { FETCH_TIMEOUT } = config;
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 === model);
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 "${model}".`,
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
- Runtime persona management. A persona is free-form text that gets
4
- prepended to the model's system prompt for a run.
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
- - **persona.js** — RPC registration and persona file loading.
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
- ## RPC Methods
12
+ ## Resolution
11
13
 
12
- | Method | Params | Notes |
13
- |--------|--------|-------|
14
- | `persona/set` | `{ run, name?, text? }` | Set persona by filename (`${RUMMY_HOME}/personas/<name>.md`) or raw text. Pass neither to clear. |
15
- | `listPersonas` | | Return `[{name, path}]` for available persona files. |
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
- this.#core = core;
9
- const r = core.hooks.rpc.registry;
10
-
11
- r.register("persona/set", {
12
- handler: async (params, ctx) => {
13
- if (!params.run) throw new Error("run is required");
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
- function configDir() {
62
- const home = process.env.RUMMY_HOME;
63
- if (home) return join(home, "personas");
64
- return null;
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, body) {
9
- return { ...entry, body, state: "failed", outcome: "permission" };
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
- return this.#fail(entry, "Rejected <sh> in ask mode");
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
- return this.#fail(
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
- return this.#fail(entry, `Rejected file rm of ${pathAttr} in ask mode`);
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
- return this.#fail(
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 mode="ask|act">` tag at the end of the user message.
4
- Always present on every turn the model always sees its task.
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 300 (always last)
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. The mode
13
- (`ask` or `act`) is stored in `attributes.mode`. Renders with `tools`
14
- attribute (available tool list) and optional `warn` attribute in ask
15
- mode. Falls back to the mode passed by the core if no prompt entry
16
- exists.
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: the singular exception to invisibility
24
+ ## Archived prompts disappear, by design
19
25
 
20
26
  `v_model_context.sql` filters archived entries out of the model's
21
- context every scheme **except `prompt`**. Archived `prompt://`
22
- entries flow through with `effective_visibility = 'archived'` and
23
- their body suppressed (per `projected.body`'s visibility CASE). The
24
- plugin then renders the tag with full attributes (`path`,
25
- `visibility="archived"`, etc.) but empty body.
26
-
27
- The exception exists because the prompt is run identity: every other
28
- archived entry is recoverable by pattern search if the model ever
29
- needs it back, but the prompt is the question the run is answering.
30
- A model that loses sight of its prompt cannot honestly act. Keeping
31
- the archived prompt's path visible lets the model emit
32
- `<get path="prompt://N"/>` to promote it back if it archived
33
- prematurely (or step back to an earlier stage via
34
- `<update status="174">`).
35
-
36
- This is the only entry-type exception to the "archived = invisible"
37
- contract. New schemes that warrant similar treatment should be added
38
- explicitly here, not by accident.
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
- const SUMMARIZED_PROMPT_CHAR_CAP = 500;
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), 225);
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
- const path = promptEntry ? ` path="${promptEntry.path}"` : "";
85
- const visibility = promptEntry?.visibility
86
- ? ` visibility="${promptEntry.visibility}"`
87
- : "";
88
- const tokens =
89
- promptEntry?.aTokens != null ? ` tokens="${promptEntry.aTokens}"` : "";
90
- const lines =
91
- promptEntry?.vLines != null ? ` lines="${promptEntry.vLines}"` : "";
92
- return `${content}<prompt mode="${mode}"${path} commands="${commands}"${warn}${reverted}${visibility}${tokens}${lines}>${body}</prompt>`;
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
  }
@@ -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,
@@ -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://temp_*" manifest/>
7
- <!-- Optional: Manifest before deleting. Safety pattern for bulk operations. -->
6
+ Example: <rm path="known://countries/france/*" manifest/>
7
+ <!-- Manifest before deleting. Safety pattern for bulk operations. -->
8
8
 
9
- * Permanent. Prefer <set path="..." visibility="archived"/> to preserve for later retrieval
10
- <!-- Nudges toward archive over rm. Path attr included so the model sees a complete invocation shape, not a fragment. -->
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
- * `manifest` lists what paths would be affected without performing the operation.
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.
@@ -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(["active", "readonly", "ignore"]);
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
- "{active, readonly, ignore}. Patterns can be globs. " +
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 — active | readonly | ignore",
268
+ visibility: "string — add | readonly | ignore",
269
269
  },
270
270
  requiresInit: true,
271
271
  });