@possumtech/rummy 0.2.8 → 0.3.0
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 +11 -1
- package/EXCEPTIONS.md +46 -0
- package/PLUGINS.md +422 -188
- package/SPEC.md +284 -93
- package/migrations/001_initial_schema.sql +6 -4
- package/package.json +13 -5
- package/src/agent/AgentLoop.js +166 -15
- package/src/agent/ContextAssembler.js +18 -4
- package/src/agent/KnownStore.js +127 -13
- package/src/agent/ProjectAgent.js +4 -1
- package/src/agent/ResponseHealer.js +21 -1
- package/src/agent/TurnExecutor.js +365 -175
- package/src/agent/XmlParser.js +72 -39
- package/src/agent/known_store.sql +20 -4
- package/src/agent/schemes.sql +3 -0
- package/src/agent/tokens.js +6 -21
- package/src/agent/turns.sql +10 -1
- package/src/hooks/Hooks.js +18 -0
- package/src/hooks/PluginContext.js +14 -1
- package/src/hooks/RummyContext.js +16 -4
- package/src/hooks/ToolRegistry.js +83 -19
- package/src/llm/LlmProvider.js +27 -8
- package/src/llm/OpenAiClient.js +20 -0
- package/src/llm/OpenRouterClient.js +24 -2
- package/src/llm/XaiClient.js +47 -2
- package/src/plugins/ask_user/README.md +4 -4
- package/src/plugins/ask_user/ask_user.js +5 -5
- package/src/plugins/ask_user/ask_userDoc.js +29 -0
- package/src/plugins/budget/BudgetGuard.js +74 -0
- package/src/plugins/budget/README.md +43 -0
- package/src/plugins/budget/budget.js +79 -0
- package/src/plugins/cp/README.md +5 -4
- package/src/plugins/cp/cp.js +10 -6
- package/src/plugins/cp/cpDoc.js +29 -0
- package/src/plugins/current/README.md +4 -4
- package/src/plugins/current/current.js +9 -6
- package/src/plugins/engine/engine.sql +1 -8
- package/src/plugins/engine/turn_context.sql +4 -9
- package/src/plugins/env/README.md +3 -4
- package/src/plugins/env/env.js +5 -5
- package/src/plugins/env/envDoc.js +29 -0
- package/src/plugins/file/README.md +9 -12
- package/src/plugins/file/file.js +34 -35
- package/src/plugins/get/README.md +2 -2
- package/src/plugins/get/get.js +6 -5
- package/src/plugins/get/getDoc.js +41 -0
- package/src/plugins/hedberg/hedberg.js +2 -1
- package/src/plugins/hedberg/normalize.js +28 -0
- package/src/plugins/hedberg/patterns.js +25 -27
- package/src/plugins/hedberg/sed.js +17 -10
- package/src/plugins/index.js +66 -14
- package/src/plugins/instructions/README.md +6 -2
- package/src/plugins/instructions/instructions.js +20 -4
- package/src/plugins/instructions/preamble.md +9 -5
- package/src/plugins/known/README.md +10 -7
- package/src/plugins/known/known.js +29 -17
- package/src/plugins/known/knownDoc.js +33 -0
- package/src/plugins/mv/README.md +5 -4
- package/src/plugins/mv/mv.js +10 -6
- package/src/plugins/mv/mvDoc.js +31 -0
- package/src/plugins/persona/persona.js +78 -0
- package/src/plugins/previous/README.md +2 -2
- package/src/plugins/previous/previous.js +9 -6
- package/src/plugins/progress/progress.js +41 -15
- package/src/plugins/prompt/README.md +5 -5
- package/src/plugins/prompt/prompt.js +18 -13
- package/src/plugins/rm/README.md +4 -4
- package/src/plugins/rm/rm.js +5 -5
- package/src/plugins/rm/rmDoc.js +30 -0
- package/src/plugins/rpc/README.md +15 -28
- package/src/plugins/rpc/rpc.js +42 -77
- package/src/plugins/set/README.md +13 -12
- package/src/plugins/set/set.js +60 -5
- package/src/plugins/set/setDoc.js +45 -0
- package/src/plugins/sh/README.md +4 -4
- package/src/plugins/sh/sh.js +5 -5
- package/src/plugins/sh/shDoc.js +29 -0
- package/src/plugins/{skills/skills.js → skill/skill.js} +10 -51
- package/src/plugins/summarize/README.md +6 -5
- package/src/plugins/summarize/summarize.js +7 -6
- package/src/plugins/summarize/summarizeDoc.js +33 -0
- package/src/plugins/telemetry/telemetry.js +3 -1
- package/src/plugins/think/README.md +20 -0
- package/src/plugins/think/think.js +5 -0
- package/src/plugins/unknown/README.md +5 -5
- package/src/plugins/unknown/unknown.js +9 -7
- package/src/plugins/unknown/unknownDoc.js +31 -0
- package/src/plugins/update/README.md +3 -8
- package/src/plugins/update/update.js +7 -6
- package/src/plugins/update/updateDoc.js +33 -0
- package/src/server/RpcRegistry.js +52 -4
- package/src/sql/v_model_context.sql +16 -21
- package/src/plugins/ask_user/docs.md +0 -2
- package/src/plugins/cp/docs.md +0 -2
- package/src/plugins/env/docs.md +0 -4
- package/src/plugins/get/docs.md +0 -10
- package/src/plugins/known/docs.md +0 -3
- package/src/plugins/mv/docs.md +0 -2
- package/src/plugins/rm/docs.md +0 -6
- package/src/plugins/set/docs.md +0 -6
- package/src/plugins/sh/docs.md +0 -2
- package/src/plugins/skills/README.md +0 -25
- package/src/plugins/store/README.md +0 -20
- package/src/plugins/store/docs.md +0 -6
- package/src/plugins/store/store.js +0 -63
- package/src/plugins/summarize/docs.md +0 -4
- package/src/plugins/unknown/docs.md +0 -5
- package/src/plugins/update/docs.md +0 -4
package/src/llm/XaiClient.js
CHANGED
|
@@ -3,6 +3,7 @@ import msg from "../agent/messages.js";
|
|
|
3
3
|
export default class XaiClient {
|
|
4
4
|
#baseUrl;
|
|
5
5
|
#apiKey;
|
|
6
|
+
#contextCache = new Map();
|
|
6
7
|
|
|
7
8
|
constructor(baseUrl, apiKey) {
|
|
8
9
|
this.#baseUrl = baseUrl;
|
|
@@ -107,7 +108,51 @@ export default class XaiClient {
|
|
|
107
108
|
);
|
|
108
109
|
}
|
|
109
110
|
|
|
110
|
-
async getContextSize(
|
|
111
|
-
|
|
111
|
+
async getContextSize(model) {
|
|
112
|
+
if (this.#contextCache.has(model)) return this.#contextCache.get(model);
|
|
113
|
+
|
|
114
|
+
if (!this.#apiKey) throw new Error(msg("error.xai_api_key_missing"));
|
|
115
|
+
|
|
116
|
+
// Query xAI models endpoint
|
|
117
|
+
const modelsUrl = this.#baseUrl.replace(/\/responses$/, "/models");
|
|
118
|
+
const res = await fetch(modelsUrl, {
|
|
119
|
+
headers: { Authorization: `Bearer ${this.#apiKey}` },
|
|
120
|
+
signal: AbortSignal.timeout(5000),
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
if (res.ok) {
|
|
124
|
+
const data = await res.json();
|
|
125
|
+
const models = data.data || data.models || [];
|
|
126
|
+
const entry = models.find(
|
|
127
|
+
(m) => m.id === model || `${m.id}-latest` === model,
|
|
128
|
+
);
|
|
129
|
+
if (entry?.context_length) {
|
|
130
|
+
this.#contextCache.set(model, entry.context_length);
|
|
131
|
+
return entry.context_length;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Try /v1/language-models for richer metadata
|
|
136
|
+
const langUrl = this.#baseUrl.replace(
|
|
137
|
+
/\/responses$/,
|
|
138
|
+
`/language-models/${model}`,
|
|
139
|
+
);
|
|
140
|
+
const langRes = await fetch(langUrl, {
|
|
141
|
+
headers: { Authorization: `Bearer ${this.#apiKey}` },
|
|
142
|
+
signal: AbortSignal.timeout(5000),
|
|
143
|
+
}).catch(() => null);
|
|
144
|
+
|
|
145
|
+
if (langRes?.ok) {
|
|
146
|
+
const langData = await langRes.json();
|
|
147
|
+
if (langData?.context_length) {
|
|
148
|
+
this.#contextCache.set(model, langData.context_length);
|
|
149
|
+
return langData.context_length;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
throw new Error(
|
|
154
|
+
`Cannot determine context size for xAI model "${model}". ` +
|
|
155
|
+
"Register the model with addModel(contextLength) or set context_length in the models table.",
|
|
156
|
+
);
|
|
112
157
|
}
|
|
113
158
|
}
|
|
@@ -5,9 +5,8 @@ Presents a question to the user with optional multiple-choice answers.
|
|
|
5
5
|
## Registration
|
|
6
6
|
|
|
7
7
|
- **Tool**: `ask_user`
|
|
8
|
-
- **
|
|
9
|
-
- **
|
|
10
|
-
- **Handler**: Parses options (semicolon or comma delimited) and upserts a `proposed` entry awaiting user response.
|
|
8
|
+
- **Category**: `logging`
|
|
9
|
+
- **Handler**: Parses options (semicolon or comma delimited) and upserts at status 202 (proposed) awaiting user response.
|
|
11
10
|
|
|
12
11
|
## Projection
|
|
13
12
|
|
|
@@ -15,4 +14,5 @@ Shows the question and answer attributes.
|
|
|
15
14
|
|
|
16
15
|
## Behavior
|
|
17
16
|
|
|
18
|
-
Options are split by semicolons first, falling back to commas. The entry
|
|
17
|
+
Options are split by semicolons first, falling back to commas. The entry
|
|
18
|
+
stays at status 202 until resolved by the client via `run/resolve`.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import docs from "./ask_userDoc.js";
|
|
2
2
|
|
|
3
3
|
export default class AskUser {
|
|
4
4
|
#core;
|
|
@@ -9,10 +9,10 @@ export default class AskUser {
|
|
|
9
9
|
core.on("handler", this.handler.bind(this));
|
|
10
10
|
core.on("full", this.full.bind(this));
|
|
11
11
|
core.on("summary", this.summary.bind(this));
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
);
|
|
12
|
+
core.filter("instructions.toolDocs", async (docsMap) => {
|
|
13
|
+
docsMap.ask_user = docs;
|
|
14
|
+
return docsMap;
|
|
15
|
+
});
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
async handler(entry, rummy) {
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// Tool doc for <ask_user>. Each entry: [text, rationale].
|
|
2
|
+
// Text goes to the model. Rationale stays in source.
|
|
3
|
+
// Changing ANY line requires reading ALL rationales first.
|
|
4
|
+
const LINES = [
|
|
5
|
+
// --- Syntax: question attr + options in body
|
|
6
|
+
['## <ask_user question="[Question?]">[option1; option2; ...]</ask_user>'],
|
|
7
|
+
|
|
8
|
+
// --- Constraints FIRST: frames correct usage before examples
|
|
9
|
+
[
|
|
10
|
+
"* YOU SHOULD use for decisions, preferences, or approvals the user must make",
|
|
11
|
+
"Positive framing. Shows what ask_user IS for, not just what it isn't.",
|
|
12
|
+
],
|
|
13
|
+
[
|
|
14
|
+
"* YOU SHOULD use <get> to find information before asking the user",
|
|
15
|
+
"Gentle redirect. Encourages self-sufficiency without forbidding interaction.",
|
|
16
|
+
],
|
|
17
|
+
|
|
18
|
+
// --- Examples: genuine decision points where user input is valuable
|
|
19
|
+
[
|
|
20
|
+
'Example: <ask_user question="Which test framework?">Mocha; Jest; Node Native</ask_user>',
|
|
21
|
+
"Preference decision. Model truly cannot know this without asking.",
|
|
22
|
+
],
|
|
23
|
+
[
|
|
24
|
+
'Example: <ask_user question="Deploy to staging or production?">staging; production</ask_user>',
|
|
25
|
+
"Consequential action. Shows ask_user for high-stakes choices.",
|
|
26
|
+
],
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
export default LINES.map(([text]) => text).join("\n");
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { countTokens } from "../../agent/tokens.js";
|
|
2
|
+
|
|
3
|
+
export class BudgetExceeded extends Error {
|
|
4
|
+
constructor(path, requested, remaining) {
|
|
5
|
+
super(
|
|
6
|
+
`Budget exceeded: ${path} needs ${requested} tokens, ${remaining} remaining`,
|
|
7
|
+
);
|
|
8
|
+
this.name = "BudgetExceeded";
|
|
9
|
+
this.status = 413;
|
|
10
|
+
this.path = path;
|
|
11
|
+
this.requested = requested;
|
|
12
|
+
this.remaining = remaining;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export default class BudgetGuard {
|
|
17
|
+
#ceiling;
|
|
18
|
+
#baseline;
|
|
19
|
+
#spent;
|
|
20
|
+
#tripped;
|
|
21
|
+
#tripSource;
|
|
22
|
+
|
|
23
|
+
constructor(ceiling, baseline) {
|
|
24
|
+
this.#ceiling = ceiling ?? null;
|
|
25
|
+
this.#baseline = baseline;
|
|
26
|
+
this.#spent = 0;
|
|
27
|
+
this.#tripped = false;
|
|
28
|
+
this.#tripSource = null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
get isTripped() {
|
|
32
|
+
return this.#tripped;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
get tripSource() {
|
|
36
|
+
return this.#tripSource;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
get remaining() {
|
|
40
|
+
if (this.#ceiling === null) return Infinity;
|
|
41
|
+
return this.#ceiling - this.#baseline - this.#spent;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
get spent() {
|
|
45
|
+
return this.#spent;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
check(tokens, path) {
|
|
49
|
+
if (this.#ceiling === null) return;
|
|
50
|
+
if (this.#tripped) throw new BudgetExceeded(path, tokens, 0);
|
|
51
|
+
if (tokens <= 0) return;
|
|
52
|
+
const remaining = this.remaining;
|
|
53
|
+
if (tokens > remaining) throw new BudgetExceeded(path, tokens, remaining);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
charge(tokens) {
|
|
57
|
+
if (tokens > 0) this.#spent += tokens;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
trip(source) {
|
|
61
|
+
this.#tripped = true;
|
|
62
|
+
this.#tripSource = source;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Compute the token delta for an upsert. New entry = full cost.
|
|
67
|
+
* Update = difference between new and old body.
|
|
68
|
+
*/
|
|
69
|
+
static delta(newBody, existingBody) {
|
|
70
|
+
const newTokens = countTokens(newBody);
|
|
71
|
+
const oldTokens = existingBody ? countTokens(existingBody) : 0;
|
|
72
|
+
return newTokens - oldTokens;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# budget
|
|
2
|
+
|
|
3
|
+
Context ceiling enforcement and panic mode recovery.
|
|
4
|
+
|
|
5
|
+
## Files
|
|
6
|
+
|
|
7
|
+
- **budget.js** — Plugin. Pre-LLM enforce, BudgetGuard activation,
|
|
8
|
+
panic prompt generation.
|
|
9
|
+
- **BudgetGuard.js** — Write-layer gate. Installed on KnownStore during
|
|
10
|
+
dispatch. Checks token delta on every upsert, promote, and body update.
|
|
11
|
+
|
|
12
|
+
## Registration
|
|
13
|
+
|
|
14
|
+
- **Hook**: `hooks.budget.enforce` — pre-LLM ceiling check.
|
|
15
|
+
- **Hook**: `hooks.budget.activate(store, contextSize, assembledTokens)` — install guard.
|
|
16
|
+
- **Hook**: `hooks.budget.deactivate(store)` — remove guard.
|
|
17
|
+
- **Hook**: `hooks.budget.panicPrompt({ shortfall, assembledTokens, contextSize })` — generate panic prompt.
|
|
18
|
+
|
|
19
|
+
## Budget Contract
|
|
20
|
+
|
|
21
|
+
`contextSize` is the ceiling. `countTokens()` is the measurement.
|
|
22
|
+
Over = 413. Under = 200. No margins.
|
|
23
|
+
|
|
24
|
+
## BudgetGuard
|
|
25
|
+
|
|
26
|
+
Installed on KnownStore by TurnExecutor before dispatch, cleared in
|
|
27
|
+
`finally`. Gates `upsert()`, `promoteByPattern()`, `updateBodyByPattern()`.
|
|
28
|
+
|
|
29
|
+
Exemptions: `status >= 400` (error entries), `model_visible = 0` (audit),
|
|
30
|
+
`fidelity = "archive"` (not in context).
|
|
31
|
+
|
|
32
|
+
On first violation: `BudgetExceeded` thrown, guard trips, all subsequent
|
|
33
|
+
writes fail. TurnExecutor catches per-tool, writes 413 result entry.
|
|
34
|
+
|
|
35
|
+
## Panic Mode
|
|
36
|
+
|
|
37
|
+
When a new prompt exceeds the ceiling, AgentLoop enqueues a panic loop.
|
|
38
|
+
The model receives the exact shortfall and must free space using core
|
|
39
|
+
tools (get, set, known, unknown, rm, mv, cp, summarize, update).
|
|
40
|
+
Excluded: sh, env, search, ask_user.
|
|
41
|
+
|
|
42
|
+
Strike system: 3 consecutive turns without context reduction = hard 413.
|
|
43
|
+
Any reduction resets the counter. One panic attempt per drain cycle.
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { countTokens } from "../../agent/tokens.js";
|
|
2
|
+
import BudgetGuard, { BudgetExceeded } from "./BudgetGuard.js";
|
|
3
|
+
|
|
4
|
+
function measureMessages(messages) {
|
|
5
|
+
return messages.reduce((sum, m) => sum + countTokens(m.content), 0);
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export { BudgetExceeded };
|
|
9
|
+
|
|
10
|
+
export default class Budget {
|
|
11
|
+
#core;
|
|
12
|
+
|
|
13
|
+
constructor(core) {
|
|
14
|
+
this.#core = core;
|
|
15
|
+
core.hooks.budget = {
|
|
16
|
+
enforce: this.enforce.bind(this),
|
|
17
|
+
activate: this.activate.bind(this),
|
|
18
|
+
deactivate: this.deactivate.bind(this),
|
|
19
|
+
panicPrompt: Budget.panicPrompt,
|
|
20
|
+
BudgetExceeded,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
static panicPrompt({ assembledTokens, contextSize }) {
|
|
25
|
+
const target = Math.floor(contextSize * 0.75);
|
|
26
|
+
const mustFree = assembledTokens - target;
|
|
27
|
+
return [
|
|
28
|
+
`CONTEXT OVERFLOW: ${assembledTokens} tokens, ceiling ${contextSize}.`,
|
|
29
|
+
`YOU MUST free ${mustFree} tokens to get below ${target} (75%).`,
|
|
30
|
+
"YOU MUST NOT load or create new content. Only reduce.",
|
|
31
|
+
"",
|
|
32
|
+
"<knowns> above shows each entry with its token count.",
|
|
33
|
+
"Target the largest entries first.",
|
|
34
|
+
'<rm path="..."/> to delete entries you no longer need.',
|
|
35
|
+
'<set path="..." fidelity="summary" summary="keywords"/> to compress.',
|
|
36
|
+
'<set path="..." fidelity="archive"/> to archive out of context.',
|
|
37
|
+
"<summarize/> when done. <update/> if still working.",
|
|
38
|
+
].join("\n");
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async enforce({ contextSize, messages, rows }) {
|
|
42
|
+
if (!contextSize) {
|
|
43
|
+
return { messages, rows, demoted: [], assembledTokens: 0, status: 200 };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const assembledTokens = measureMessages(messages);
|
|
47
|
+
|
|
48
|
+
console.warn(
|
|
49
|
+
`[RUMMY] Budget enforce: ${assembledTokens} tokens, ceiling ${contextSize}, ${rows.length} rows`,
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
if (assembledTokens > contextSize) {
|
|
53
|
+
const overflow = assembledTokens - contextSize;
|
|
54
|
+
console.warn(
|
|
55
|
+
`[RUMMY] Budget 413: ${assembledTokens} tokens > ${contextSize} ceiling (${overflow} over)`,
|
|
56
|
+
);
|
|
57
|
+
return {
|
|
58
|
+
messages,
|
|
59
|
+
rows,
|
|
60
|
+
demoted: [],
|
|
61
|
+
assembledTokens,
|
|
62
|
+
status: 413,
|
|
63
|
+
overflow,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return { messages, rows, demoted: [], assembledTokens, status: 200 };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
activate(store, contextSize, assembledTokens) {
|
|
71
|
+
const guard = new BudgetGuard(contextSize, assembledTokens);
|
|
72
|
+
store.budgetGuard = guard;
|
|
73
|
+
return guard;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
deactivate(store) {
|
|
77
|
+
store.budgetGuard = null;
|
|
78
|
+
}
|
|
79
|
+
}
|
package/src/plugins/cp/README.md
CHANGED
|
@@ -5,9 +5,8 @@ Copies an entry from one path to another within the K/V store.
|
|
|
5
5
|
## Registration
|
|
6
6
|
|
|
7
7
|
- **Tool**: `cp`
|
|
8
|
-
- **
|
|
9
|
-
- **
|
|
10
|
-
- **Handler**: Reads source body, writes to destination. K/V destinations resolve immediately (`pass`); file destinations produce a `proposed` entry.
|
|
8
|
+
- **Category**: `logging`
|
|
9
|
+
- **Handler**: Reads source body, writes to destination. Scheme destinations resolve immediately (status 200); file destinations produce status 202 (proposed).
|
|
11
10
|
|
|
12
11
|
## Projection
|
|
13
12
|
|
|
@@ -15,4 +14,6 @@ Shows `cp {from} {to}`.
|
|
|
15
14
|
|
|
16
15
|
## Behavior
|
|
17
16
|
|
|
18
|
-
Warns if the destination already exists and will be overwritten. Uses
|
|
17
|
+
Warns if the destination already exists and will be overwritten. Uses
|
|
18
|
+
`KnownStore.scheme()` to determine whether the destination is a scheme
|
|
19
|
+
path or a file path.
|
package/src/plugins/cp/cp.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { readFileSync } from "node:fs";
|
|
2
1
|
import KnownStore from "../../agent/KnownStore.js";
|
|
2
|
+
import docs from "./cpDoc.js";
|
|
3
3
|
|
|
4
4
|
export default class Cp {
|
|
5
5
|
#core;
|
|
@@ -10,15 +10,19 @@ export default class Cp {
|
|
|
10
10
|
core.on("handler", this.handler.bind(this));
|
|
11
11
|
core.on("full", this.full.bind(this));
|
|
12
12
|
core.on("summary", this.summary.bind(this));
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
);
|
|
13
|
+
core.filter("instructions.toolDocs", async (docsMap) => {
|
|
14
|
+
docsMap.cp = docs;
|
|
15
|
+
return docsMap;
|
|
16
|
+
});
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
async handler(entry, rummy) {
|
|
20
20
|
const { entries: store, sequence: turn, runId, loopId } = rummy;
|
|
21
21
|
const { path, to } = entry.attributes;
|
|
22
|
+
const VALID = { stored: 1, summary: 1, index: 1, full: 1 };
|
|
23
|
+
const fidelity = VALID[entry.attributes.fidelity]
|
|
24
|
+
? entry.attributes.fidelity
|
|
25
|
+
: undefined;
|
|
22
26
|
|
|
23
27
|
const source = await store.getBody(runId, path);
|
|
24
28
|
if (source === null) return;
|
|
@@ -37,7 +41,7 @@ export default class Cp {
|
|
|
37
41
|
loopId,
|
|
38
42
|
});
|
|
39
43
|
} else {
|
|
40
|
-
await store.upsert(runId, turn, to, source, 200, { loopId });
|
|
44
|
+
await store.upsert(runId, turn, to, source, 200, { fidelity, loopId });
|
|
41
45
|
await store.upsert(runId, turn, entry.resultPath, body, 200, {
|
|
42
46
|
attributes: { from: path, to, isMove: false, warning },
|
|
43
47
|
loopId,
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// Tool doc for <cp>. Each entry: [text, rationale].
|
|
2
|
+
// Text goes to the model. Rationale stays in source.
|
|
3
|
+
// Changing ANY line requires reading ALL rationales first.
|
|
4
|
+
const LINES = [
|
|
5
|
+
// --- Syntax: path attr = source, body = destination
|
|
6
|
+
['## <cp path="[source]">[destination]</cp> - Copy a file or entry'],
|
|
7
|
+
|
|
8
|
+
// --- Examples: single copy, glob batch, cross-scheme
|
|
9
|
+
[
|
|
10
|
+
'Example: <cp path="src/config.js">src/config.backup.js</cp>',
|
|
11
|
+
"Simple file copy. Path = source, body = destination.",
|
|
12
|
+
],
|
|
13
|
+
[
|
|
14
|
+
'Example: <cp path="known://plan_*">known://archive_</cp>',
|
|
15
|
+
"Glob batch copy across known entries. Shows pattern operations on cp.",
|
|
16
|
+
],
|
|
17
|
+
|
|
18
|
+
// --- Constraints
|
|
19
|
+
[
|
|
20
|
+
"* Source path accepts globs: `src/*.js`, `known://draft_*`",
|
|
21
|
+
"Pattern support. Distributes glob teaching beyond get.",
|
|
22
|
+
],
|
|
23
|
+
[
|
|
24
|
+
"* Use `preview` to check matches before bulk copy",
|
|
25
|
+
"Safety pattern consistent with get and rm preview.",
|
|
26
|
+
],
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
export default LINES.map(([text]) => text).join("\n");
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# current
|
|
2
2
|
|
|
3
3
|
Renders the `<current>` section of the user message — the active loop's
|
|
4
|
-
|
|
4
|
+
tool results and lifecycle signals.
|
|
5
5
|
|
|
6
6
|
## Registration
|
|
7
7
|
|
|
@@ -9,6 +9,6 @@ model responses, tool results, and agent warnings.
|
|
|
9
9
|
|
|
10
10
|
## Behavior
|
|
11
11
|
|
|
12
|
-
Filters turn_context rows where `category
|
|
13
|
-
|
|
14
|
-
with status
|
|
12
|
+
Filters turn_context rows where `category === "logging"` and
|
|
13
|
+
`source_turn >= loopStartTurn`. Renders each entry chronologically
|
|
14
|
+
with turn number and status. Empty on the first turn of a loop.
|
|
@@ -8,9 +8,7 @@ export default class Current {
|
|
|
8
8
|
|
|
9
9
|
async assembleCurrent(content, ctx) {
|
|
10
10
|
const entries = ctx.rows.filter(
|
|
11
|
-
(r) =>
|
|
12
|
-
(r.category === "result" || r.category === "structural") &&
|
|
13
|
-
r.source_turn >= ctx.loopStartTurn,
|
|
11
|
+
(r) => r.category === "logging" && r.source_turn >= ctx.loopStartTurn,
|
|
14
12
|
);
|
|
15
13
|
if (entries.length === 0) return content;
|
|
16
14
|
|
|
@@ -27,8 +25,13 @@ async function renderToolTag(entry, core) {
|
|
|
27
25
|
? JSON.parse(entry.attributes)
|
|
28
26
|
: entry.attributes;
|
|
29
27
|
|
|
30
|
-
const
|
|
28
|
+
const target = attrs?.path || attrs?.file || attrs?.command || "";
|
|
29
|
+
const turn = entry.source_turn ? ` turn="${entry.source_turn}"` : "";
|
|
31
30
|
const status = entry.status ? ` status="${entry.status}"` : "";
|
|
31
|
+
const summary =
|
|
32
|
+
typeof attrs?.summary === "string"
|
|
33
|
+
? ` summary="${attrs.summary.slice(0, 80)}"`
|
|
34
|
+
: "";
|
|
32
35
|
|
|
33
36
|
let body;
|
|
34
37
|
try {
|
|
@@ -41,7 +44,7 @@ async function renderToolTag(entry, core) {
|
|
|
41
44
|
}
|
|
42
45
|
|
|
43
46
|
if (body) {
|
|
44
|
-
return
|
|
47
|
+
return `<${entry.scheme} path="${target}"${turn}${status}${summary}>${body}</${entry.scheme}>`;
|
|
45
48
|
}
|
|
46
|
-
return
|
|
49
|
+
return `<${entry.scheme} path="${target}"${turn}${status}${summary}/>`;
|
|
47
50
|
}
|
|
@@ -10,11 +10,4 @@ WHERE
|
|
|
10
10
|
AND s.model_visible = 1
|
|
11
11
|
ORDER BY ke.turn, ke.refs, ke.tokens DESC;
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
SELECT COALESCE(SUM(ke.tokens), 0) AS total
|
|
15
|
-
FROM known_entries AS ke
|
|
16
|
-
JOIN schemes AS s ON s.name = COALESCE(ke.scheme, 'file')
|
|
17
|
-
WHERE
|
|
18
|
-
ke.run_id = :run_id
|
|
19
|
-
AND ke.fidelity IN ('full', 'summary')
|
|
20
|
-
AND s.model_visible = 1;
|
|
13
|
+
|
|
@@ -37,15 +37,10 @@ WHERE run_id = :run_id AND turn = :turn;
|
|
|
37
37
|
-- PREP: get_turn_distribution
|
|
38
38
|
SELECT
|
|
39
39
|
CASE category
|
|
40
|
-
WHEN '
|
|
41
|
-
WHEN '
|
|
42
|
-
WHEN '
|
|
43
|
-
WHEN '
|
|
44
|
-
WHEN 'known_index' THEN 'keys'
|
|
45
|
-
WHEN 'unknown' THEN 'history'
|
|
46
|
-
WHEN 'result' THEN 'history'
|
|
47
|
-
WHEN 'prompt' THEN 'system'
|
|
48
|
-
WHEN 'system' THEN 'system'
|
|
40
|
+
WHEN 'data' THEN 'data'
|
|
41
|
+
WHEN 'logging' THEN 'logging'
|
|
42
|
+
WHEN 'unknown' THEN 'unknown'
|
|
43
|
+
WHEN 'prompt' THEN 'prompt'
|
|
49
44
|
ELSE 'system'
|
|
50
45
|
END AS bucket,
|
|
51
46
|
COALESCE(SUM(tokens), 0) AS tokens,
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
# env
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Runs an exploratory shell command and records the output.
|
|
4
4
|
|
|
5
5
|
## Registration
|
|
6
6
|
|
|
7
7
|
- **Tool**: `env`
|
|
8
|
-
- **
|
|
9
|
-
- **
|
|
10
|
-
- **Handler**: Upserts the entry body as `pass` state with original attributes preserved.
|
|
8
|
+
- **Category**: `logging`
|
|
9
|
+
- **Handler**: Upserts the entry at status 202 (proposed) with original attributes preserved.
|
|
11
10
|
|
|
12
11
|
## Projection
|
|
13
12
|
|
package/src/plugins/env/env.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import docs from "./envDoc.js";
|
|
2
2
|
|
|
3
3
|
export default class Env {
|
|
4
4
|
#core;
|
|
@@ -9,10 +9,10 @@ export default class Env {
|
|
|
9
9
|
core.on("handler", this.handler.bind(this));
|
|
10
10
|
core.on("full", this.full.bind(this));
|
|
11
11
|
core.on("summary", this.summary.bind(this));
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
);
|
|
12
|
+
core.filter("instructions.toolDocs", async (docsMap) => {
|
|
13
|
+
docsMap.env = docs;
|
|
14
|
+
return docsMap;
|
|
15
|
+
});
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
async handler(entry, rummy) {
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// Tool doc for <env>. Each entry: [text, rationale].
|
|
2
|
+
// Text goes to the model. Rationale stays in source.
|
|
3
|
+
// Changing ANY line requires reading ALL rationales first.
|
|
4
|
+
const LINES = [
|
|
5
|
+
// --- Syntax
|
|
6
|
+
["## <env>[command]</env> - Run an exploratory shell command"],
|
|
7
|
+
|
|
8
|
+
// --- Examples: version check and git status — safe, read-only commands
|
|
9
|
+
[
|
|
10
|
+
"Example: <env>npm --version</env>",
|
|
11
|
+
"Version check. Safe, no side effects.",
|
|
12
|
+
],
|
|
13
|
+
[
|
|
14
|
+
"Example: <env>git log --oneline -5</env>",
|
|
15
|
+
"Git history. Shows env for read-only investigation.",
|
|
16
|
+
],
|
|
17
|
+
|
|
18
|
+
// --- Constraints: hard boundaries
|
|
19
|
+
[
|
|
20
|
+
'* YOU MUST NOT use <env/> to read or list files — use <get path="*" preview/> instead',
|
|
21
|
+
"Prevents cat/ls through shell. Forces file access through get for proper tracking.",
|
|
22
|
+
],
|
|
23
|
+
[
|
|
24
|
+
"* YOU MUST use <sh/> for commands with side effects",
|
|
25
|
+
"Separates exploration from action. env = observe, sh = mutate.",
|
|
26
|
+
],
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
export default LINES.map(([text]) => text).join("\n");
|
|
@@ -2,24 +2,21 @@
|
|
|
2
2
|
|
|
3
3
|
Owns file-related projections and file constraint management.
|
|
4
4
|
|
|
5
|
-
## Files
|
|
6
|
-
|
|
7
|
-
- **file.js** — Plugin registration, projection hooks, and constraint CRUD (activate, ignore, drop).
|
|
8
|
-
- **FileScanner.js** — Scans project directories for file entries.
|
|
9
|
-
- **GitProvider.js** — Git integration for file discovery and status.
|
|
10
|
-
- **ProjectContext.js** — Builds project-level context from scanned files.
|
|
11
|
-
- **FsProvider.js** — Filesystem abstraction for file reading/writing.
|
|
12
|
-
|
|
13
5
|
## Registration
|
|
14
6
|
|
|
15
|
-
- **
|
|
7
|
+
- **Schemes**: `file` (bare paths), `http`, `https` — all `category: "data"`
|
|
8
|
+
- **Views**: `full` and `summary` for file scheme. Default identity views
|
|
9
|
+
for `http`/`https` (overridden by rummy.web when installed).
|
|
16
10
|
- **No tool handler** — file operations are dispatched through `set`, `get`, `rm`, etc.
|
|
17
11
|
|
|
18
12
|
## File Constraints
|
|
19
13
|
|
|
20
|
-
Static methods `
|
|
14
|
+
Static methods `setConstraint` and `dropConstraint` manage per-project
|
|
15
|
+
file constraints in the database. Constraints are project-level config
|
|
16
|
+
(backbone), not tool dispatch. See SPEC.md §2.3.
|
|
21
17
|
|
|
22
|
-
- `active` / `readonly` —
|
|
18
|
+
- `active` / `readonly` — promoted into context.
|
|
23
19
|
- `ignore` — excluded from scans; demotes existing entries.
|
|
24
20
|
|
|
25
|
-
|
|
21
|
+
Entry promotion/demotion from constraints goes through the standard
|
|
22
|
+
tool handler chain via `dispatchTool`.
|