@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/plugins/rpc/rpc.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import KnownStore from "../../agent/KnownStore.js";
|
|
2
1
|
import msg from "../../agent/messages.js";
|
|
3
2
|
import RummyContext from "../../hooks/RummyContext.js";
|
|
4
3
|
import File from "../file/file.js";
|
|
@@ -90,15 +89,15 @@ export default class Rpc {
|
|
|
90
89
|
|
|
91
90
|
// --- Entry operations (same dispatch as model) ---
|
|
92
91
|
|
|
92
|
+
// Override: get has persist flag for file constraint management
|
|
93
93
|
r.register("get", {
|
|
94
94
|
handler: async (params, ctx) => {
|
|
95
95
|
if (!params.path) throw new Error("path is required");
|
|
96
96
|
|
|
97
97
|
if (params.persist) {
|
|
98
98
|
const visibility = params.readonly ? "readonly" : "active";
|
|
99
|
-
|
|
99
|
+
await File.setConstraint(
|
|
100
100
|
ctx.db,
|
|
101
|
-
ctx.projectAgent.entries,
|
|
102
101
|
ctx.projectId,
|
|
103
102
|
params.path,
|
|
104
103
|
visibility,
|
|
@@ -115,101 +114,50 @@ export default class Rpc {
|
|
|
115
114
|
description: "Promote entry to full state.",
|
|
116
115
|
params: {
|
|
117
116
|
path: "string — file path or glob pattern",
|
|
118
|
-
run: "string
|
|
119
|
-
persist: "boolean? — create file constraint",
|
|
117
|
+
run: "string — run alias",
|
|
118
|
+
persist: "boolean? — also create file constraint",
|
|
120
119
|
readonly: "boolean? — with persist, set readonly instead of active",
|
|
121
120
|
},
|
|
122
121
|
requiresInit: true,
|
|
123
122
|
});
|
|
124
123
|
|
|
124
|
+
// store is not a tool — it manages file constraints
|
|
125
125
|
r.register("store", {
|
|
126
126
|
handler: async (params, ctx) => {
|
|
127
127
|
if (!params.path) throw new Error("path is required");
|
|
128
128
|
|
|
129
129
|
if (params.clear) {
|
|
130
|
-
|
|
130
|
+
await File.dropConstraint(ctx.db, ctx.projectId, params.path);
|
|
131
|
+
return { status: "ok" };
|
|
131
132
|
}
|
|
132
133
|
if (params.persist) {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
}
|
|
141
|
-
return File.drop(ctx.db, ctx.projectId, params.path);
|
|
134
|
+
const visibility = params.ignore ? "ignore" : "active";
|
|
135
|
+
await File.setConstraint(
|
|
136
|
+
ctx.db,
|
|
137
|
+
ctx.projectId,
|
|
138
|
+
params.path,
|
|
139
|
+
visibility,
|
|
140
|
+
);
|
|
142
141
|
}
|
|
143
142
|
|
|
144
143
|
if (!params.run) throw new Error("run is required");
|
|
145
|
-
const
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
144
|
+
const runRow = await ctx.db.get_run_by_alias.get({ alias: params.run });
|
|
145
|
+
if (!runRow) throw new Error(`Run not found: ${params.run}`);
|
|
146
|
+
const store = ctx.projectAgent.entries;
|
|
147
|
+
await store.demoteByPattern(runRow.id, params.path, null);
|
|
149
148
|
return { status: "ok" };
|
|
150
149
|
},
|
|
151
150
|
description: "Demote entry to stored state.",
|
|
152
151
|
params: {
|
|
153
152
|
path: "string — file path or glob pattern",
|
|
154
153
|
run: "string? — run alias (required without persist)",
|
|
155
|
-
persist: "boolean? — create file constraint",
|
|
154
|
+
persist: "boolean? — also create file constraint",
|
|
156
155
|
ignore: "boolean? — with persist, exclude from scan",
|
|
157
156
|
clear: "boolean? — remove existing constraint",
|
|
158
157
|
},
|
|
159
158
|
requiresInit: true,
|
|
160
159
|
});
|
|
161
160
|
|
|
162
|
-
r.register("set", {
|
|
163
|
-
handler: async (params, ctx) => {
|
|
164
|
-
if (!params.path) throw new Error("path is required");
|
|
165
|
-
if (!params.run) throw new Error("run is required");
|
|
166
|
-
const { rummy } = await buildRunContext(hooks, ctx, params.run);
|
|
167
|
-
|
|
168
|
-
const scheme = KnownStore.scheme(params.path);
|
|
169
|
-
if (scheme) {
|
|
170
|
-
await rummy.set({
|
|
171
|
-
path: params.path,
|
|
172
|
-
body: params.body,
|
|
173
|
-
status: params.status || 200,
|
|
174
|
-
attributes: params.attributes,
|
|
175
|
-
});
|
|
176
|
-
} else {
|
|
177
|
-
await dispatchTool(hooks, rummy, "set", params.path, params.body, {
|
|
178
|
-
path: params.path,
|
|
179
|
-
...params.attributes,
|
|
180
|
-
});
|
|
181
|
-
}
|
|
182
|
-
return { status: "ok" };
|
|
183
|
-
},
|
|
184
|
-
description: "Create or update an entry.",
|
|
185
|
-
params: {
|
|
186
|
-
run: "string — run alias",
|
|
187
|
-
path: "string — entry path",
|
|
188
|
-
body: "string? — entry content",
|
|
189
|
-
status: "number? — HTTP status code (default: 200)",
|
|
190
|
-
attributes: "object? — JSON attributes",
|
|
191
|
-
},
|
|
192
|
-
requiresInit: true,
|
|
193
|
-
});
|
|
194
|
-
|
|
195
|
-
r.register("rm", {
|
|
196
|
-
handler: async (params, ctx) => {
|
|
197
|
-
if (!params.path) throw new Error("path is required");
|
|
198
|
-
if (!params.run) throw new Error("run is required");
|
|
199
|
-
const { rummy } = await buildRunContext(hooks, ctx, params.run);
|
|
200
|
-
await dispatchTool(hooks, rummy, "rm", params.path, "", {
|
|
201
|
-
path: params.path,
|
|
202
|
-
});
|
|
203
|
-
return { status: "ok" };
|
|
204
|
-
},
|
|
205
|
-
description: "Remove an entry.",
|
|
206
|
-
params: {
|
|
207
|
-
run: "string — run alias",
|
|
208
|
-
path: "string — entry path",
|
|
209
|
-
},
|
|
210
|
-
requiresInit: true,
|
|
211
|
-
});
|
|
212
|
-
|
|
213
161
|
r.register("getEntries", {
|
|
214
162
|
handler: async (params, ctx) => {
|
|
215
163
|
let run;
|
|
@@ -283,7 +231,9 @@ export default class Rpc {
|
|
|
283
231
|
temperature: params.temperature ?? null,
|
|
284
232
|
persona: params.persona ?? null,
|
|
285
233
|
contextLimit: params.contextLimit,
|
|
286
|
-
|
|
234
|
+
noRepo: params.noRepo,
|
|
235
|
+
noInteraction: params.noInteraction,
|
|
236
|
+
noWeb: params.noWeb,
|
|
287
237
|
fork: params.fork,
|
|
288
238
|
},
|
|
289
239
|
);
|
|
@@ -297,7 +247,9 @@ export default class Rpc {
|
|
|
297
247
|
temperature: "number?",
|
|
298
248
|
persona: "string?",
|
|
299
249
|
contextLimit: "number?",
|
|
300
|
-
|
|
250
|
+
noRepo: "boolean?",
|
|
251
|
+
noInteraction: "boolean? — disable ask_user tool",
|
|
252
|
+
noWeb: "boolean? — disable search and URL fetch",
|
|
301
253
|
fork: "boolean?",
|
|
302
254
|
},
|
|
303
255
|
requiresInit: true,
|
|
@@ -315,7 +267,9 @@ export default class Rpc {
|
|
|
315
267
|
temperature: params.temperature ?? null,
|
|
316
268
|
persona: params.persona ?? null,
|
|
317
269
|
contextLimit: params.contextLimit,
|
|
318
|
-
|
|
270
|
+
noRepo: params.noRepo,
|
|
271
|
+
noInteraction: params.noInteraction,
|
|
272
|
+
noWeb: params.noWeb,
|
|
319
273
|
fork: params.fork,
|
|
320
274
|
},
|
|
321
275
|
);
|
|
@@ -329,7 +283,9 @@ export default class Rpc {
|
|
|
329
283
|
temperature: "number?",
|
|
330
284
|
persona: "string?",
|
|
331
285
|
contextLimit: "number?",
|
|
332
|
-
|
|
286
|
+
noRepo: "boolean?",
|
|
287
|
+
noInteraction: "boolean? — disable ask_user tool",
|
|
288
|
+
noWeb: "boolean? — disable search and URL fetch",
|
|
333
289
|
fork: "boolean?",
|
|
334
290
|
},
|
|
335
291
|
requiresInit: true,
|
|
@@ -524,6 +480,10 @@ export default class Rpc {
|
|
|
524
480
|
r.registerNotification("run/progress", "Turn status.");
|
|
525
481
|
r.registerNotification("ui/render", "Streaming output.");
|
|
526
482
|
r.registerNotification("ui/notify", "Toast notification.");
|
|
483
|
+
|
|
484
|
+
// Auto-dispatch: any registered tool is callable via RPC.
|
|
485
|
+
// Checked at request time — no timing dependency on plugin load order.
|
|
486
|
+
r.setToolFallback(hooks, buildRunContext, dispatchTool);
|
|
527
487
|
}
|
|
528
488
|
}
|
|
529
489
|
|
|
@@ -544,7 +504,7 @@ async function buildRunContext(hooks, ctx, runAlias) {
|
|
|
544
504
|
sequence: runRow.next_turn,
|
|
545
505
|
runId: runRow.id,
|
|
546
506
|
turnId: null,
|
|
547
|
-
|
|
507
|
+
noRepo: false,
|
|
548
508
|
contextSize: null,
|
|
549
509
|
systemPrompt: "",
|
|
550
510
|
loopPrompt: "",
|
|
@@ -555,7 +515,12 @@ async function buildRunContext(hooks, ctx, runAlias) {
|
|
|
555
515
|
|
|
556
516
|
async function dispatchTool(hooks, rummy, scheme, path, body, attributes) {
|
|
557
517
|
const store = rummy.entries;
|
|
558
|
-
const resultPath = await store.dedup(
|
|
518
|
+
const resultPath = await store.dedup(
|
|
519
|
+
rummy.runId,
|
|
520
|
+
scheme,
|
|
521
|
+
path,
|
|
522
|
+
rummy.sequence,
|
|
523
|
+
);
|
|
559
524
|
|
|
560
525
|
await store.upsert(rummy.runId, rummy.sequence, resultPath, body, 200, {
|
|
561
526
|
attributes: attributes,
|
|
@@ -1,32 +1,33 @@
|
|
|
1
1
|
# set
|
|
2
2
|
|
|
3
|
-
Writes or edits entry content. Handles new files, full overwrites,
|
|
3
|
+
Writes or edits entry content. Handles new files, full overwrites,
|
|
4
|
+
SEARCH/REPLACE edits, and pattern updates.
|
|
4
5
|
|
|
5
6
|
## Files
|
|
6
7
|
|
|
7
8
|
- **set.js** — Plugin registration and edit dispatch logic.
|
|
8
|
-
- **HeuristicMatcher.js** — Fuzzy SEARCH/REPLACE matching.
|
|
9
|
+
- **HeuristicMatcher.js** — Fuzzy SEARCH/REPLACE matching.
|
|
9
10
|
- **HeuristicMatcher.test.js** — Tests for HeuristicMatcher.
|
|
10
11
|
|
|
11
12
|
## Registration
|
|
12
13
|
|
|
13
14
|
- **Tool**: `set`
|
|
14
|
-
- **
|
|
15
|
-
- **
|
|
16
|
-
- **Handler**: Routes to different paths based on attributes:
|
|
15
|
+
- **Category**: `logging`
|
|
16
|
+
- **Handler**: Routes based on attributes:
|
|
17
17
|
- `blocks` or `search` — SEARCH/REPLACE edit via `processEdit`.
|
|
18
18
|
- `preview` — pattern preview (dry run).
|
|
19
|
-
-
|
|
20
|
-
- File path — produces
|
|
19
|
+
- Scheme path — direct upsert at status 200.
|
|
20
|
+
- File path — produces status 202 (proposed) with unified diff patch.
|
|
21
21
|
- Glob/filter — bulk update via `updateBodyByPattern`.
|
|
22
22
|
|
|
23
23
|
## Projection
|
|
24
24
|
|
|
25
|
-
Shows `set {file}` with token delta (`before→after tokens`). Includes
|
|
25
|
+
Shows `set {file}` with token delta (`before→after tokens`). Includes
|
|
26
|
+
the merge conflict block when a SEARCH/REPLACE was performed.
|
|
26
27
|
|
|
27
28
|
## Behavior
|
|
28
29
|
|
|
29
|
-
- **Literal match first**: SEARCH text is matched literally
|
|
30
|
-
- **Heuristic fallback**: On literal failure,
|
|
31
|
-
- **Patch generation**: `generatePatch` produces unified diff
|
|
32
|
-
- File writes are always
|
|
30
|
+
- **Literal match first**: SEARCH text is matched literally.
|
|
31
|
+
- **Heuristic fallback**: On literal failure, fuzzy matching with warnings.
|
|
32
|
+
- **Patch generation**: `generatePatch` produces unified diff for client display.
|
|
33
|
+
- File writes are always status 202 (proposed); scheme writes resolve immediately.
|
package/src/plugins/set/set.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import { readFileSync } from "node:fs";
|
|
2
1
|
import KnownStore from "../../agent/KnownStore.js";
|
|
3
2
|
import Hedberg, { generatePatch } from "../hedberg/hedberg.js";
|
|
4
3
|
import { storePatternResult } from "../helpers.js";
|
|
4
|
+
import docs from "./setDoc.js";
|
|
5
|
+
|
|
6
|
+
const VALID_FIDELITY = { archive: 1, summary: 1, index: 1, full: 1 };
|
|
5
7
|
|
|
6
8
|
// biome-ignore lint/suspicious/noShadowRestrictedNames: tool name is "set"
|
|
7
9
|
export default class Set {
|
|
@@ -14,16 +16,69 @@ export default class Set {
|
|
|
14
16
|
core.on("full", this.full.bind(this));
|
|
15
17
|
core.on("summary", this.summary.bind(this));
|
|
16
18
|
core.on("turn.proposing", this.#materializeRevisions.bind(this));
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
);
|
|
19
|
+
core.filter("instructions.toolDocs", async (docsMap) => {
|
|
20
|
+
docsMap.set = docs;
|
|
21
|
+
return docsMap;
|
|
22
|
+
});
|
|
21
23
|
}
|
|
22
24
|
|
|
23
25
|
async handler(entry, rummy) {
|
|
24
26
|
const { entries: store, sequence: turn, runId, loopId } = rummy;
|
|
25
27
|
const attrs = entry.attributes;
|
|
26
28
|
|
|
29
|
+
// Fidelity control: <set path="..." fidelity="archive"/>
|
|
30
|
+
const fidelityAttr = VALID_FIDELITY[attrs.fidelity] ? attrs.fidelity : null;
|
|
31
|
+
if (fidelityAttr && attrs.path) {
|
|
32
|
+
const target = attrs.path;
|
|
33
|
+
const rawSummary =
|
|
34
|
+
typeof attrs.summary === "string" ? attrs.summary : null;
|
|
35
|
+
const summaryText = rawSummary ? rawSummary.slice(0, 80) : null;
|
|
36
|
+
const matches = await store.getEntriesByPattern(
|
|
37
|
+
runId,
|
|
38
|
+
target,
|
|
39
|
+
attrs.body,
|
|
40
|
+
);
|
|
41
|
+
if (entry.body) {
|
|
42
|
+
// Write content directly at specified fidelity
|
|
43
|
+
const entryAttrs = summaryText ? { summary: summaryText } : null;
|
|
44
|
+
for (const match of matches) {
|
|
45
|
+
await store.upsert(runId, turn, match.path, entry.body, 200, {
|
|
46
|
+
fidelity: fidelityAttr,
|
|
47
|
+
attributes: entryAttrs,
|
|
48
|
+
loopId,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
if (matches.length === 0) {
|
|
52
|
+
await store.upsert(runId, turn, target, entry.body, 200, {
|
|
53
|
+
fidelity: fidelityAttr,
|
|
54
|
+
attributes: entryAttrs,
|
|
55
|
+
loopId,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
} else {
|
|
59
|
+
// No body — change fidelity, attach summary if provided
|
|
60
|
+
for (const match of matches) {
|
|
61
|
+
await store.setFidelity(runId, match.path, fidelityAttr);
|
|
62
|
+
if (summaryText) {
|
|
63
|
+
await store.setAttributes(runId, match.path, {
|
|
64
|
+
summary: summaryText,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
const label =
|
|
70
|
+
fidelityAttr === "archive" ? "archived" : `set to ${fidelityAttr}`;
|
|
71
|
+
const body =
|
|
72
|
+
matches.length > 0
|
|
73
|
+
? `${matches.map((m) => m.path).join(", ")} ${label}`
|
|
74
|
+
: `${target} not found`;
|
|
75
|
+
await store.upsert(runId, turn, entry.resultPath, body, 200, {
|
|
76
|
+
fidelity: "archive",
|
|
77
|
+
loopId,
|
|
78
|
+
});
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
27
82
|
if (attrs.blocks || attrs.search != null) {
|
|
28
83
|
await this.#processEdit(rummy, entry, attrs);
|
|
29
84
|
return;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// Tool doc for <set>. 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 + body = edit content
|
|
6
|
+
['## <set path="[path/to/file]">[edit]</set> - Edit a file or entry'],
|
|
7
|
+
|
|
8
|
+
// --- Examples: sed, SEARCH/REPLACE, fidelity control
|
|
9
|
+
[
|
|
10
|
+
'Example: <set path="src/config.js">s/port = 3000/port = 8080/g</set>',
|
|
11
|
+
"Sed syntax: most common edit pattern. Shows s/old/new/ with g flag.",
|
|
12
|
+
],
|
|
13
|
+
[
|
|
14
|
+
`Example: <set path="src/app.js"><<<<<<< SEARCH
|
|
15
|
+
// TODO: add error handling
|
|
16
|
+
=======
|
|
17
|
+
// error handler configured
|
|
18
|
+
>>>>>>> REPLACE</set>`,
|
|
19
|
+
"SEARCH/REPLACE block: literal match and replace. Use when sed escaping is complex.",
|
|
20
|
+
],
|
|
21
|
+
[
|
|
22
|
+
'Example: <set path="known://plan" stored summary="Migration plan for Q2"/>',
|
|
23
|
+
"Fidelity + summary: archive an entry while preserving a description. Lifecycle endpoint.",
|
|
24
|
+
],
|
|
25
|
+
|
|
26
|
+
// --- Constraints
|
|
27
|
+
[
|
|
28
|
+
'* `fidelity="..."`: `archive`, `summary`, `index`, `full`',
|
|
29
|
+
"Fidelity control. Archive removes from context but preserves for retrieval.",
|
|
30
|
+
],
|
|
31
|
+
[
|
|
32
|
+
'* `summary="..."` (<= 80 chars) persists across fidelity changes',
|
|
33
|
+
"Model-authored descriptions survive demotion. No janitorial pass needed.",
|
|
34
|
+
],
|
|
35
|
+
[
|
|
36
|
+
"* YOU MUST NOT use <sh/> or <env/> to read, create, or edit files",
|
|
37
|
+
"Forces file operations through set/get. Prevents untracked mutations.",
|
|
38
|
+
],
|
|
39
|
+
[
|
|
40
|
+
"* Editing: s/old/new/ sed patterns and literal SEARCH/REPLACE blocks",
|
|
41
|
+
"Both syntaxes supported. Hedberg normalizes either form.",
|
|
42
|
+
],
|
|
43
|
+
];
|
|
44
|
+
|
|
45
|
+
export default LINES.map(([text]) => text).join("\n");
|
package/src/plugins/sh/README.md
CHANGED
|
@@ -5,9 +5,8 @@ Proposes shell command execution for client approval.
|
|
|
5
5
|
## Registration
|
|
6
6
|
|
|
7
7
|
- **Tool**: `sh`
|
|
8
|
-
- **
|
|
9
|
-
- **
|
|
10
|
-
- **Handler**: Upserts the entry as `proposed` state. The client must approve execution.
|
|
8
|
+
- **Category**: `logging`
|
|
9
|
+
- **Handler**: Upserts the entry at status 202 (proposed). The client must approve execution.
|
|
11
10
|
|
|
12
11
|
## Projection
|
|
13
12
|
|
|
@@ -15,4 +14,5 @@ Shows `sh {command}` followed by the entry body.
|
|
|
15
14
|
|
|
16
15
|
## Behavior
|
|
17
16
|
|
|
18
|
-
All shell commands require client-side approval — nothing executes
|
|
17
|
+
All shell commands require client-side approval — nothing executes
|
|
18
|
+
server-side. Act mode only; excluded in ask mode by `resolveForLoop`.
|
package/src/plugins/sh/sh.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import docs from "./shDoc.js";
|
|
2
2
|
|
|
3
3
|
export default class Sh {
|
|
4
4
|
#core;
|
|
@@ -9,10 +9,10 @@ export default class Sh {
|
|
|
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.sh = docs;
|
|
14
|
+
return docsMap;
|
|
15
|
+
});
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
async handler(entry, rummy) {
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// Tool doc for <sh>. 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
|
+
["## <sh>[command]</sh> - Run a shell command with side effects"],
|
|
7
|
+
|
|
8
|
+
// --- Examples: install and test — real mutations
|
|
9
|
+
[
|
|
10
|
+
"Example: <sh>npm install express</sh>",
|
|
11
|
+
"Package install. Shows a real side-effect command.",
|
|
12
|
+
],
|
|
13
|
+
[
|
|
14
|
+
"Example: <sh>npm test</sh>",
|
|
15
|
+
"Test execution. Another common side-effect action.",
|
|
16
|
+
],
|
|
17
|
+
|
|
18
|
+
// --- Constraints
|
|
19
|
+
[
|
|
20
|
+
"* YOU MUST NOT use <sh/> to read, create, or edit files — use <get/> and <set/>",
|
|
21
|
+
"Forces file operations through the entry system. Prevents untracked mutations.",
|
|
22
|
+
],
|
|
23
|
+
[
|
|
24
|
+
"* YOU MUST use <env/> for commands without side effects",
|
|
25
|
+
"Reinforces the env/sh split. Read = env, mutate = sh.",
|
|
26
|
+
],
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
export default LINES.map(([text]) => text).join("\n");
|
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
import fs from "node:fs/promises";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
|
|
4
|
-
export default class
|
|
4
|
+
export default class Skill {
|
|
5
5
|
#core;
|
|
6
6
|
|
|
7
7
|
constructor(core) {
|
|
8
8
|
this.#core = core;
|
|
9
9
|
core.registerScheme({
|
|
10
10
|
name: "skill",
|
|
11
|
-
category: "
|
|
11
|
+
category: "data",
|
|
12
12
|
});
|
|
13
|
+
core.hooks.tools.onView("skill", (entry) => entry.body);
|
|
14
|
+
|
|
13
15
|
const r = core.hooks.rpc.registry;
|
|
14
16
|
|
|
15
17
|
r.register("skill/add", {
|
|
@@ -22,19 +24,12 @@ export default class Skills {
|
|
|
22
24
|
|
|
23
25
|
const body = await loadFile("skills", params.name);
|
|
24
26
|
const store = ctx.projectAgent.entries;
|
|
25
|
-
await store.upsert(
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
body,
|
|
30
|
-
200,
|
|
31
|
-
{
|
|
32
|
-
attributes: {
|
|
33
|
-
name: params.name,
|
|
34
|
-
source: filePath("skills", params.name),
|
|
35
|
-
},
|
|
27
|
+
await store.upsert(runRow.id, 0, `skill://${params.name}`, body, 200, {
|
|
28
|
+
attributes: {
|
|
29
|
+
name: params.name,
|
|
30
|
+
source: filePath("skills", params.name),
|
|
36
31
|
},
|
|
37
|
-
);
|
|
32
|
+
});
|
|
38
33
|
|
|
39
34
|
return { status: "ok", skill: params.name };
|
|
40
35
|
},
|
|
@@ -97,43 +92,7 @@ export default class Skills {
|
|
|
97
92
|
requiresInit: true,
|
|
98
93
|
});
|
|
99
94
|
|
|
100
|
-
|
|
101
|
-
handler: async (params, ctx) => {
|
|
102
|
-
if (!params.run) throw new Error("run is required");
|
|
103
|
-
|
|
104
|
-
const runRow = await ctx.db.get_run_by_alias.get({ alias: params.run });
|
|
105
|
-
if (!runRow) throw new Error(`Run not found: ${params.run}`);
|
|
106
|
-
|
|
107
|
-
let text = params.text;
|
|
108
|
-
if (params.name && !text) {
|
|
109
|
-
text = await loadFile("personas", params.name);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
await ctx.db.update_run_config.run({
|
|
113
|
-
id: runRow.id,
|
|
114
|
-
temperature: null,
|
|
115
|
-
persona: text || null,
|
|
116
|
-
context_limit: null,
|
|
117
|
-
model: null,
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
return { status: "ok" };
|
|
121
|
-
},
|
|
122
|
-
description:
|
|
123
|
-
"Set persona on a run. Pass name or text. Pass neither to clear.",
|
|
124
|
-
params: {
|
|
125
|
-
run: "string — run alias",
|
|
126
|
-
name: "string? — persona filename (without .md)",
|
|
127
|
-
text: "string? — raw persona text (overrides name)",
|
|
128
|
-
},
|
|
129
|
-
requiresInit: true,
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
r.register("listPersonas", {
|
|
133
|
-
handler: async () => listAvailable("personas"),
|
|
134
|
-
description: "List available persona files. Returns [{ name, path }].",
|
|
135
|
-
requiresInit: true,
|
|
136
|
-
});
|
|
95
|
+
// Persona methods extracted to persona plugin.
|
|
137
96
|
}
|
|
138
97
|
}
|
|
139
98
|
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
# summarize
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Lifecycle signal — the model declares it has completed the task.
|
|
4
4
|
|
|
5
5
|
## Registration
|
|
6
6
|
|
|
7
7
|
- **Tool**: `summarize`
|
|
8
|
-
- **
|
|
9
|
-
- **
|
|
10
|
-
- **Handler**: None — projection only.
|
|
8
|
+
- **Category**: `logging`
|
|
9
|
+
- **Handler**: None — recorded by TurnExecutor as a lifecycle signal.
|
|
11
10
|
|
|
12
11
|
## Projection
|
|
13
12
|
|
|
@@ -15,4 +14,6 @@ Shows `summarize` followed by the entry body.
|
|
|
15
14
|
|
|
16
15
|
## Behavior
|
|
17
16
|
|
|
18
|
-
|
|
17
|
+
If the model sends `<summarize>` but actions in the same turn failed,
|
|
18
|
+
TurnExecutor overrides it to `<update>` — the model's assertion that
|
|
19
|
+
it's done is false.
|
|
@@ -1,17 +1,18 @@
|
|
|
1
|
-
import
|
|
1
|
+
import docs from "./summarizeDoc.js";
|
|
2
2
|
|
|
3
3
|
export default class Summarize {
|
|
4
4
|
#core;
|
|
5
5
|
|
|
6
6
|
constructor(core) {
|
|
7
7
|
this.#core = core;
|
|
8
|
-
core.
|
|
8
|
+
core.ensureTool();
|
|
9
|
+
core.registerScheme({ category: "logging" });
|
|
9
10
|
core.on("full", this.full.bind(this));
|
|
10
11
|
core.on("summary", this.summary.bind(this));
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
);
|
|
12
|
+
core.filter("instructions.toolDocs", async (docsMap) => {
|
|
13
|
+
docsMap.summarize = docs;
|
|
14
|
+
return docsMap;
|
|
15
|
+
});
|
|
15
16
|
}
|
|
16
17
|
|
|
17
18
|
full(entry) {
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// Tool doc for <summarize>. 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
|
+
["## <summarize>[answer or summary]</summarize> - Signal completion"],
|
|
7
|
+
|
|
8
|
+
// --- Examples: answer and task completion
|
|
9
|
+
[
|
|
10
|
+
"Example: <summarize>The port is 8080</summarize>",
|
|
11
|
+
"Direct answer. Shows summarize as the vehicle for delivering answers.",
|
|
12
|
+
],
|
|
13
|
+
[
|
|
14
|
+
"Example: <summarize>Installed express, updated config</summarize>",
|
|
15
|
+
"Task summary. Shows summarize for action completion.",
|
|
16
|
+
],
|
|
17
|
+
|
|
18
|
+
// --- Constraints: RFC-style MUST/MUST NOT
|
|
19
|
+
[
|
|
20
|
+
"* YOU MUST use <summarize> when done — describes the final state",
|
|
21
|
+
"Completion signal. Without this, the loop continues indefinitely.",
|
|
22
|
+
],
|
|
23
|
+
[
|
|
24
|
+
"* YOU MUST NOT use <summarize> if still working — use <update/> instead",
|
|
25
|
+
"Mutual exclusion with update. Prevents premature completion.",
|
|
26
|
+
],
|
|
27
|
+
[
|
|
28
|
+
"* YOU MUST keep <summarize> to <= 80 characters",
|
|
29
|
+
"Length cap. Matches the summary attribute constraint. Prevents verbose output.",
|
|
30
|
+
],
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
export default LINES.map(([text]) => text).join("\n");
|
|
@@ -75,8 +75,8 @@ export default class Telemetry {
|
|
|
75
75
|
result,
|
|
76
76
|
responseMessage,
|
|
77
77
|
content,
|
|
78
|
-
commands,
|
|
79
78
|
unparsed,
|
|
79
|
+
assembledTokens,
|
|
80
80
|
systemMsg,
|
|
81
81
|
userMsg,
|
|
82
82
|
}) {
|
|
@@ -149,6 +149,8 @@ export default class Telemetry {
|
|
|
149
149
|
0;
|
|
150
150
|
await rummy.db.update_turn_stats.run({
|
|
151
151
|
id: rummy.turnId,
|
|
152
|
+
context_tokens: assembledTokens ?? 0,
|
|
153
|
+
reasoning_content: responseMessage?.reasoning_content || null,
|
|
152
154
|
prompt_tokens: usage.prompt_tokens ?? 0,
|
|
153
155
|
cached_tokens: cachedTokens ?? 0,
|
|
154
156
|
completion_tokens: usage.completion_tokens ?? 0,
|