@possumtech/rummy 0.2.8 → 0.3.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 +13 -2
- package/EXCEPTIONS.md +46 -0
- package/PLUGINS.md +422 -188
- package/SPEC.md +440 -106
- package/migrations/001_initial_schema.sql +5 -3
- package/package.json +17 -5
- package/service.js +5 -3
- package/src/agent/AgentLoop.js +252 -55
- package/src/agent/ContextAssembler.js +20 -4
- package/src/agent/KnownStore.js +82 -25
- package/src/agent/ProjectAgent.js +4 -1
- package/src/agent/ResponseHealer.js +86 -32
- package/src/agent/TurnExecutor.js +542 -207
- package/src/agent/XmlParser.js +77 -41
- package/src/agent/known_store.sql +68 -4
- package/src/agent/schemes.sql +3 -0
- package/src/agent/tokens.js +7 -21
- package/src/agent/turns.sql +15 -1
- package/src/hooks/HookRegistry.js +7 -0
- package/src/hooks/Hooks.js +15 -0
- package/src/hooks/PluginContext.js +14 -1
- package/src/hooks/RummyContext.js +16 -4
- package/src/hooks/ToolRegistry.js +77 -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/README.md +31 -0
- package/src/plugins/budget/budget.js +55 -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/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 +77 -6
- package/src/plugins/get/getDoc.js +51 -0
- package/src/plugins/hedberg/hedberg.js +2 -1
- package/src/plugins/hedberg/matcher.js +10 -29
- 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 +19 -5
- package/src/plugins/known/README.md +10 -7
- package/src/plugins/known/known.js +23 -17
- package/src/plugins/known/knownDoc.js +34 -0
- package/src/plugins/mv/README.md +5 -4
- package/src/plugins/mv/mv.js +27 -6
- package/src/plugins/mv/mvDoc.js +45 -0
- package/src/plugins/performed/README.md +15 -0
- package/src/plugins/performed/performed.js +45 -0
- package/src/plugins/persona/persona.js +78 -0
- package/src/plugins/previous/README.md +3 -2
- package/src/plugins/previous/previous.js +33 -24
- package/src/plugins/progress/README.md +1 -2
- package/src/plugins/progress/progress.js +33 -21
- package/src/plugins/prompt/README.md +5 -5
- package/src/plugins/prompt/prompt.js +15 -17
- package/src/plugins/rm/README.md +4 -4
- package/src/plugins/rm/rm.js +32 -20
- 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 +107 -16
- package/src/plugins/set/setDoc.js +49 -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 +16 -9
- package/src/plugins/think/README.md +20 -0
- package/src/plugins/think/think.js +5 -0
- package/src/plugins/unknown/README.md +6 -5
- package/src/plugins/unknown/unknown.js +12 -9
- 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/ClientConnection.js +59 -45
- package/src/server/RpcRegistry.js +52 -4
- package/src/sql/v_model_context.sql +10 -25
- package/src/plugins/ask_user/docs.md +0 -2
- package/src/plugins/cp/docs.md +0 -2
- package/src/plugins/current/README.md +0 -14
- package/src/plugins/current/current.js +0 -47
- 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/rm/README.md
CHANGED
|
@@ -5,9 +5,8 @@ Removes entries by path or glob pattern.
|
|
|
5
5
|
## Registration
|
|
6
6
|
|
|
7
7
|
- **Tool**: `rm`
|
|
8
|
-
- **
|
|
9
|
-
- **
|
|
10
|
-
- **Handler**: Matches entries by pattern. K/V entries are removed immediately (`pass`); file entries produce `proposed` state for client approval.
|
|
8
|
+
- **Category**: `logging`
|
|
9
|
+
- **Handler**: Matches entries by pattern. Scheme entries are removed immediately (status 200); file entries produce status 202 (proposed) for client approval.
|
|
11
10
|
|
|
12
11
|
## Projection
|
|
13
12
|
|
|
@@ -15,4 +14,5 @@ Shows `rm {path}`.
|
|
|
15
14
|
|
|
16
15
|
## Behavior
|
|
17
16
|
|
|
18
|
-
Supports glob patterns and body filters via `getEntriesByPattern`. Each
|
|
17
|
+
Supports glob patterns and body filters via `getEntriesByPattern`. Each
|
|
18
|
+
matched entry is processed independently.
|
package/src/plugins/rm/rm.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { readFileSync } from "node:fs";
|
|
2
1
|
import KnownStore from "../../agent/KnownStore.js";
|
|
2
|
+
import docs from "./rmDoc.js";
|
|
3
3
|
|
|
4
4
|
export default class Rm {
|
|
5
5
|
#core;
|
|
@@ -10,10 +10,10 @@ export default class Rm {
|
|
|
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.rm = docs;
|
|
15
|
+
return docsMap;
|
|
16
|
+
});
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
async handler(entry, rummy) {
|
|
@@ -41,25 +41,37 @@ export default class Rm {
|
|
|
41
41
|
return;
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
44
|
+
const fileMatches = matches.filter((m) => m.scheme === null);
|
|
45
|
+
const schemeMatches = matches.filter((m) => m.scheme !== null);
|
|
46
|
+
|
|
47
|
+
// Scheme entries: remove all, write one aggregate result entry
|
|
48
|
+
for (const match of schemeMatches) await store.remove(runId, match.path);
|
|
49
|
+
if (schemeMatches.length > 0) {
|
|
50
|
+
const paths = schemeMatches.map((m) => m.path).join("\n");
|
|
51
|
+
await store.upsert(runId, turn, entry.resultPath, paths, 200, {
|
|
52
|
+
attributes: { path: target },
|
|
53
|
+
loopId,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// File entries: individual 202 proposals (require user resolution)
|
|
58
|
+
if (fileMatches.length > 0 && schemeMatches.length > 0)
|
|
59
|
+
await store.remove(runId, entry.resultPath);
|
|
60
|
+
for (const match of fileMatches) {
|
|
61
|
+
const resultPath =
|
|
62
|
+
schemeMatches.length === 0 && fileMatches.length === 1
|
|
63
|
+
? entry.resultPath
|
|
64
|
+
: await store.dedup(runId, "rm", match.path, turn);
|
|
65
|
+
await store.upsert(runId, turn, resultPath, match.path, 202, {
|
|
66
|
+
attributes: { path: match.path },
|
|
67
|
+
loopId,
|
|
68
|
+
});
|
|
58
69
|
}
|
|
59
70
|
}
|
|
60
71
|
|
|
61
72
|
full(entry) {
|
|
62
|
-
|
|
73
|
+
const header = `# rm ${entry.attributes.path || entry.path}`;
|
|
74
|
+
return entry.body ? `${header}\n${entry.body}` : header;
|
|
63
75
|
}
|
|
64
76
|
|
|
65
77
|
summary(entry) {
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// Tool doc for <rm>. 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, self-closing
|
|
6
|
+
['## <rm path="[path]"/> - Remove a file or entry'],
|
|
7
|
+
|
|
8
|
+
// --- Examples: file, known (with slug path), preview safety
|
|
9
|
+
['Example: <rm path="src/config.js"/>', "File removal. Simplest form."],
|
|
10
|
+
[
|
|
11
|
+
'Example: <rm path="known://config/deprecated_service"/>',
|
|
12
|
+
"Shows topic-hierarchy path convention. Paths are category/key, not sentence slugs.",
|
|
13
|
+
],
|
|
14
|
+
[
|
|
15
|
+
'Example: <rm path="known://temp_*" preview/>',
|
|
16
|
+
"Preview before deleting. Glob pattern. Safety pattern for bulk operations.",
|
|
17
|
+
],
|
|
18
|
+
|
|
19
|
+
// --- Constraints
|
|
20
|
+
[
|
|
21
|
+
'* Permanent. Prefer <set fidelity="archive"/> to preserve for later retrieval',
|
|
22
|
+
"Nudges toward archive over rm. Archive keeps the key; rm deletes permanently.",
|
|
23
|
+
],
|
|
24
|
+
[
|
|
25
|
+
"* Paths accept patterns — use `preview` to check matches first",
|
|
26
|
+
"Reinforces preview safety pattern. Prevents accidental bulk deletion.",
|
|
27
|
+
],
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
export default LINES.map(([text]) => text).join("\n");
|
|
@@ -1,45 +1,32 @@
|
|
|
1
1
|
# rpc
|
|
2
2
|
|
|
3
|
-
Registers
|
|
3
|
+
Registers core RPC methods and provides automatic tool dispatch for
|
|
4
|
+
all registered tools.
|
|
4
5
|
|
|
5
6
|
## Registration
|
|
6
7
|
|
|
7
|
-
- **No tool handler** —
|
|
8
|
+
- **No tool handler** — registers RPC methods on `hooks.rpc.registry`.
|
|
9
|
+
- **Tool fallback** — any registered tool is automatically callable via
|
|
10
|
+
RPC without explicit registration. Third-party plugins get RPC for free.
|
|
8
11
|
|
|
9
12
|
## RPC Methods
|
|
10
13
|
|
|
11
14
|
### Protocol
|
|
12
|
-
- `ping`
|
|
13
|
-
- `discover` — returns method/notification catalog.
|
|
14
|
-
- `init` — initialize project (sets projectId, projectRoot, configPath).
|
|
15
|
+
- `ping`, `discover`, `init`
|
|
15
16
|
|
|
16
17
|
### Models
|
|
17
|
-
- `getModels`, `addModel`, `removeModel`
|
|
18
|
+
- `getModels`, `addModel`, `removeModel`
|
|
18
19
|
|
|
19
|
-
### Entry Operations
|
|
20
|
-
- `
|
|
21
|
-
- `store` — demote entry
|
|
22
|
-
-
|
|
23
|
-
- `delete` — remove entry via `rm` handler dispatch.
|
|
20
|
+
### Entry Operations (all dispatch through tool handler chain)
|
|
21
|
+
- `get` — promote entry; with `persist` flag, also sets file constraint.
|
|
22
|
+
- `store` — demote entry or manage file constraints (not a model tool).
|
|
23
|
+
- All other registered tools — auto-dispatched via tool fallback.
|
|
24
24
|
- `getEntries` — query entries by glob pattern.
|
|
25
25
|
|
|
26
26
|
### Runs
|
|
27
|
-
- `startRun`
|
|
28
|
-
- `
|
|
29
|
-
- `
|
|
30
|
-
- `run/resolve` — resolve a proposed entry (accept/reject).
|
|
31
|
-
- `run/abort` — abort an in-flight run.
|
|
32
|
-
- `run/rename` — rename a run alias.
|
|
33
|
-
- `run/inject` — inject a message into an idle or active run.
|
|
34
|
-
- `run/config` — update run parameters (temperature, persona, context_limit, model).
|
|
35
|
-
- `getRuns`, `getRun` — query run list and full run detail.
|
|
27
|
+
- `startRun`, `ask`, `act`
|
|
28
|
+
- `run/resolve`, `run/abort`, `run/rename`, `run/inject`, `run/config`
|
|
29
|
+
- `getRuns`, `getRun`
|
|
36
30
|
|
|
37
31
|
### Notifications
|
|
38
|
-
- `run/state`
|
|
39
|
-
- `run/progress` — turn status (thinking/processing).
|
|
40
|
-
- `ui/render` — streaming output.
|
|
41
|
-
- `ui/notify` — toast notification.
|
|
42
|
-
|
|
43
|
-
## Behavior
|
|
44
|
-
|
|
45
|
-
Client operations (read, write, delete, store) build a `RummyContext` for the target run and dispatch through the same handler chain as model operations via `dispatchTool`.
|
|
32
|
+
- `run/state`, `run/progress`, `ui/render`, `ui/notify`
|
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,10 @@
|
|
|
1
|
-
import { readFileSync } from "node:fs";
|
|
2
1
|
import KnownStore from "../../agent/KnownStore.js";
|
|
2
|
+
import { countTokens } from "../../agent/tokens.js";
|
|
3
3
|
import Hedberg, { generatePatch } from "../hedberg/hedberg.js";
|
|
4
4
|
import { storePatternResult } from "../helpers.js";
|
|
5
|
+
import docs from "./setDoc.js";
|
|
6
|
+
|
|
7
|
+
const VALID_FIDELITY = { archive: 1, summary: 1, index: 1, full: 1 };
|
|
5
8
|
|
|
6
9
|
// biome-ignore lint/suspicious/noShadowRestrictedNames: tool name is "set"
|
|
7
10
|
export default class Set {
|
|
@@ -14,16 +17,86 @@ export default class Set {
|
|
|
14
17
|
core.on("full", this.full.bind(this));
|
|
15
18
|
core.on("summary", this.summary.bind(this));
|
|
16
19
|
core.on("turn.proposing", this.#materializeRevisions.bind(this));
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
);
|
|
20
|
+
core.filter("instructions.toolDocs", async (docsMap) => {
|
|
21
|
+
docsMap.set = docs;
|
|
22
|
+
return docsMap;
|
|
23
|
+
});
|
|
21
24
|
}
|
|
22
25
|
|
|
23
26
|
async handler(entry, rummy) {
|
|
24
27
|
const { entries: store, sequence: turn, runId, loopId } = rummy;
|
|
25
28
|
const attrs = entry.attributes;
|
|
26
29
|
|
|
30
|
+
// Fidelity control: <set path="..." fidelity="archive"/>
|
|
31
|
+
const fidelityAttr = VALID_FIDELITY[attrs.fidelity] ? attrs.fidelity : null;
|
|
32
|
+
if (fidelityAttr && attrs.path) {
|
|
33
|
+
const target = attrs.path;
|
|
34
|
+
const rawSummary =
|
|
35
|
+
typeof attrs.summary === "string" ? attrs.summary : null;
|
|
36
|
+
const summaryText = rawSummary ? rawSummary.slice(0, 80) : null;
|
|
37
|
+
const matches = await store.getEntriesByPattern(
|
|
38
|
+
runId,
|
|
39
|
+
target,
|
|
40
|
+
attrs.body,
|
|
41
|
+
);
|
|
42
|
+
if (entry.body) {
|
|
43
|
+
// Write content directly at specified fidelity
|
|
44
|
+
const entryAttrs = summaryText ? { summary: summaryText } : null;
|
|
45
|
+
for (const match of matches) {
|
|
46
|
+
await store.upsert(runId, turn, match.path, entry.body, 200, {
|
|
47
|
+
fidelity: fidelityAttr,
|
|
48
|
+
attributes: entryAttrs,
|
|
49
|
+
loopId,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
if (matches.length === 0) {
|
|
53
|
+
await store.upsert(runId, turn, target, entry.body, 200, {
|
|
54
|
+
fidelity: fidelityAttr,
|
|
55
|
+
attributes: entryAttrs,
|
|
56
|
+
loopId,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
} else {
|
|
60
|
+
// No body — change fidelity, attach summary if provided
|
|
61
|
+
for (const match of matches) {
|
|
62
|
+
await store.setFidelity(runId, match.path, fidelityAttr);
|
|
63
|
+
if (summaryText) {
|
|
64
|
+
await store.setAttributes(runId, match.path, {
|
|
65
|
+
summary: summaryText,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if (matches.length === 0) {
|
|
71
|
+
await store.upsert(
|
|
72
|
+
runId,
|
|
73
|
+
turn,
|
|
74
|
+
entry.resultPath,
|
|
75
|
+
`${target} not found`,
|
|
76
|
+
404,
|
|
77
|
+
{
|
|
78
|
+
fidelity: "archive",
|
|
79
|
+
loopId,
|
|
80
|
+
},
|
|
81
|
+
);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
const label =
|
|
85
|
+
fidelityAttr === "archive" ? "archived" : `set to ${fidelityAttr}`;
|
|
86
|
+
await store.upsert(
|
|
87
|
+
runId,
|
|
88
|
+
turn,
|
|
89
|
+
entry.resultPath,
|
|
90
|
+
`${matches.map((m) => m.path).join(", ")} ${label}`,
|
|
91
|
+
200,
|
|
92
|
+
{
|
|
93
|
+
fidelity: "archive",
|
|
94
|
+
loopId,
|
|
95
|
+
},
|
|
96
|
+
);
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
|
|
27
100
|
if (attrs.blocks || attrs.search != null) {
|
|
28
101
|
await this.#processEdit(rummy, entry, attrs);
|
|
29
102
|
return;
|
|
@@ -143,7 +216,7 @@ export default class Set {
|
|
|
143
216
|
? `<<<<<<< SEARCH\n${searchText}\n=======\n${replaceText}\n>>>>>>> REPLACE`
|
|
144
217
|
: null;
|
|
145
218
|
const beforeTokens = match.tokens_full || 0;
|
|
146
|
-
const afterTokens = patch ? (patch
|
|
219
|
+
const afterTokens = patch ? countTokens(patch) : beforeTokens;
|
|
147
220
|
|
|
148
221
|
await store.upsert(runId, turn, resultPath, match.body, status, {
|
|
149
222
|
attributes: {
|
|
@@ -210,7 +283,7 @@ export default class Set {
|
|
|
210
283
|
: null;
|
|
211
284
|
const merge = mergeBlocks.length > 0 ? mergeBlocks.join("\n") : null;
|
|
212
285
|
const beforeTokens = fileEntry[0].tokens_full || 0;
|
|
213
|
-
const afterTokens = current ? (current
|
|
286
|
+
const afterTokens = current ? countTokens(current) : beforeTokens;
|
|
214
287
|
|
|
215
288
|
await store.upsert(runId, turn, entry.path, original, state, {
|
|
216
289
|
attributes: {
|
|
@@ -232,10 +305,7 @@ export default class Set {
|
|
|
232
305
|
return { search: attrs.search, replace: attrs.replace ?? "" };
|
|
233
306
|
}
|
|
234
307
|
if (attrs.blocks?.length > 0) {
|
|
235
|
-
return {
|
|
236
|
-
search: attrs.blocks[0].search,
|
|
237
|
-
replace: attrs.blocks[0].replace,
|
|
238
|
-
};
|
|
308
|
+
return { blocks: attrs.blocks };
|
|
239
309
|
}
|
|
240
310
|
return null;
|
|
241
311
|
}
|
|
@@ -257,11 +327,32 @@ export default class Set {
|
|
|
257
327
|
};
|
|
258
328
|
}
|
|
259
329
|
if (body && attrs.blocks?.length > 0) {
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
330
|
+
if (attrs.blocks.length === 1) {
|
|
331
|
+
const block = attrs.blocks[0];
|
|
332
|
+
return Hedberg.replace(body, block.search, block.replace, {
|
|
333
|
+
sed: block.sed,
|
|
334
|
+
flags: block.flags,
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
// Multi-block: apply sequentially, no per-hunk merge notation
|
|
338
|
+
let current = body;
|
|
339
|
+
let lastWarning = null;
|
|
340
|
+
for (const block of attrs.blocks) {
|
|
341
|
+
const result = Hedberg.replace(current, block.search, block.replace, {
|
|
342
|
+
sed: block.sed,
|
|
343
|
+
flags: block.flags,
|
|
344
|
+
});
|
|
345
|
+
if (result.error) return result;
|
|
346
|
+
if (result.warning) lastWarning = result.warning;
|
|
347
|
+
if (result.patch) current = result.patch;
|
|
348
|
+
}
|
|
349
|
+
return {
|
|
350
|
+
patch: current !== body ? current : null,
|
|
351
|
+
searchText: null,
|
|
352
|
+
replaceText: null,
|
|
353
|
+
warning: lastWarning,
|
|
354
|
+
error: null,
|
|
355
|
+
};
|
|
265
356
|
}
|
|
266
357
|
return {
|
|
267
358
|
patch: null,
|
|
@@ -0,0 +1,49 @@
|
|
|
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://project/milestones" fidelity="summary" summary="milestone,deadline,2026"/> ... <set path="prompt://3" fidelity="index"/>',
|
|
23
|
+
"Fidelity control: compress a known entry to keywords, demote a previous prompt to index-only. Both free context while keeping paths visible.",
|
|
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
|
+
'* `fidelity="summary"` HIDES the body — does NOT require reading or compressing content. Write any short keyword label you already know.',
|
|
33
|
+
"M-10 fix: model was reading files before compressing to summary, believing it needed semantic content. It does not. The body is preserved on disk; only context visibility changes.",
|
|
34
|
+
],
|
|
35
|
+
[
|
|
36
|
+
'* `summary="..."` (<= 80 chars) persists across fidelity changes',
|
|
37
|
+
"Model-authored descriptions survive demotion. No janitorial pass needed.",
|
|
38
|
+
],
|
|
39
|
+
[
|
|
40
|
+
"* YOU MUST NOT use <sh/> or <env/> to read, create, or edit files",
|
|
41
|
+
"Forces file operations through set/get. Prevents untracked mutations.",
|
|
42
|
+
],
|
|
43
|
+
[
|
|
44
|
+
"* Editing: s/old/new/ sed patterns and literal SEARCH/REPLACE blocks",
|
|
45
|
+
"Both syntaxes supported. Hedberg normalizes either form.",
|
|
46
|
+
],
|
|
47
|
+
];
|
|
48
|
+
|
|
49
|
+
export default LINES.map(([text]) => text).join("\n");
|